fisk/core/models.py

257 lines
9.6 KiB
Python
Raw Normal View History

2022-10-13 14:26:43 +00:00
import stripe
2022-10-13 17:20:30 +00:00
from django.conf import settings
2022-10-13 14:26:43 +00:00
from django.contrib.auth.models import AbstractUser
from django.db import models
2022-10-13 17:20:30 +00:00
2022-10-30 10:57:53 +00:00
from core.exchanges.alpaca import AlpacaExchange
from core.exchanges.oanda import OANDAExchange
2022-10-13 14:26:43 +00:00
from core.lib.customers import get_or_create, update_customer_fields
2022-10-17 06:20:30 +00:00
from core.util import logs
2022-10-13 14:26:43 +00:00
2022-10-17 06:20:30 +00:00
log = logs.get_logger(__name__)
2022-10-30 10:57:53 +00:00
EXCHANGE_MAP = {"alpaca": AlpacaExchange, "oanda": OANDAExchange}
TYPE_CHOICES = (
("market", "Market"),
("limit", "Limit"),
)
DIRECTION_CHOICES = (
("buy", "Buy"),
("sell", "Sell"),
)
TIF_CHOICES = (
("gtc", "GTC (Good Til Cancelled)"),
("gfd", "GFD (Good For Day)"),
("fok", "FOK (Fill Or Kill)"),
("ioc", "IOC (Immediate Or Cancel)"),
)
2022-10-13 14:26:43 +00:00
class Plan(models.Model):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=1024, null=True, blank=True)
cost = models.IntegerField()
product_id = models.CharField(max_length=255, unique=True, null=True, blank=True)
image = models.CharField(max_length=1024, null=True, blank=True)
def __str__(self):
return f"{self.name}{self.cost})"
class User(AbstractUser):
# Stripe customer ID
stripe_id = models.CharField(max_length=255, null=True, blank=True)
last_payment = models.DateTimeField(null=True, blank=True)
plans = models.ManyToManyField(Plan, blank=True)
email = models.EmailField(unique=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._original = self
def save(self, *args, **kwargs):
"""
Override the save function to create a Stripe customer.
"""
if settings.STRIPE_ENABLED:
if not self.stripe_id: # stripe ID not stored
2022-10-13 17:20:30 +00:00
self.stripe_id = get_or_create(
self.email, self.first_name, self.last_name
)
2022-10-13 14:26:43 +00:00
to_update = {}
if self.email != self._original.email:
to_update["email"] = self.email
if self.first_name != self._original.first_name:
to_update["first_name"] = self.first_name
if self.last_name != self._original.last_name:
to_update["last_name"] = self.last_name
update_customer_fields(self.stripe_id, **to_update)
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
if settings.STRIPE_ENABLED:
if self.stripe_id:
stripe.Customer.delete(self.stripe_id)
2022-10-17 06:20:30 +00:00
log.info(f"Deleted Stripe customer {self.stripe_id}")
2022-10-13 14:26:43 +00:00
super().delete(*args, **kwargs)
def has_plan(self, plan):
plan_list = [plan.name for plan in self.plans.all()]
return plan in plan_list
2022-10-17 17:56:16 +00:00
class Account(models.Model):
EXCHANGE_CHOICES = (("alpaca", "Alpaca"), ("oanda", "OANDA"))
2022-10-17 17:56:16 +00:00
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
2022-10-18 06:22:22 +00:00
exchange = models.CharField(choices=EXCHANGE_CHOICES, max_length=255)
2022-10-17 17:56:16 +00:00
api_key = models.CharField(max_length=255)
api_secret = models.CharField(max_length=255)
2022-10-18 06:22:22 +00:00
sandbox = models.BooleanField(default=False)
supported_symbols = models.JSONField(default=list)
instruments = models.JSONField(default=list)
currency = models.CharField(max_length=255, null=True, blank=True)
2022-10-17 17:56:16 +00:00
def __str__(self):
name = f"{self.name} ({self.exchange})"
if self.sandbox:
name += " (sandbox)"
return name
def update_info(self, save=True):
2022-10-30 10:57:53 +00:00
client = self.get_client()
if client:
response = client.get_instruments()
supported_symbols = client.get_supported_assets(response)
currency = client.get_account()["currency"]
log.debug(f"Supported symbols for {self.name}: {supported_symbols}")
self.supported_symbols = supported_symbols
self.instruments = response
self.currency = currency
if save:
self.save()
def save(self, *args, **kwargs):
"""
Override the save function to update supported symbols.
"""
self.update_info(save=False)
super().save(*args, **kwargs)
def get_client(self):
2022-10-30 10:57:53 +00:00
if self.exchange in EXCHANGE_MAP:
return EXCHANGE_MAP[self.exchange](self)
else:
2022-11-04 07:20:55 +00:00
raise Exception(f"Exchange not supported : {self.exchange}")
@property
def client(self):
"""
Convenience property for one-off API calls.
"""
return self.get_client()
2022-10-30 10:57:53 +00:00
@property
def rawclient(self):
"""
Convenience property for one-off API calls.
"""
return self.get_client().client
@classmethod
def get_by_id(cls, account_id, user):
return cls.objects.get(id=account_id, user=user)
2022-10-17 17:56:16 +00:00
2022-10-13 14:26:43 +00:00
class Session(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
request = models.CharField(max_length=255, null=True, blank=True)
session = models.CharField(max_length=255, null=True, blank=True)
subscription_id = models.CharField(max_length=255, null=True, blank=True)
plan = models.ForeignKey(Plan, null=True, blank=True, on_delete=models.CASCADE)
2022-10-14 06:20:30 +00:00
class Hook(models.Model):
2022-10-12 06:22:22 +00:00
user = models.ForeignKey(User, on_delete=models.CASCADE)
2022-10-15 22:00:02 +00:00
name = models.CharField(max_length=1024, null=True, blank=True, unique=True)
hook = models.CharField(max_length=255, unique=True)
direction = models.CharField(choices=DIRECTION_CHOICES, max_length=255)
2022-10-14 06:20:30 +00:00
received = models.IntegerField(default=0)
def __str__(self):
return f"{self.name} ({self.hook})"
2022-10-14 06:20:30 +00:00
2022-10-17 17:56:16 +00:00
class Trade(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
2022-10-17 17:56:16 +00:00
account = models.ForeignKey(Account, on_delete=models.CASCADE)
hook = models.ForeignKey(Hook, on_delete=models.CASCADE, null=True, blank=True)
symbol = models.CharField(max_length=255)
time_in_force = models.CharField(choices=TIF_CHOICES, max_length=255, default="gtc")
2022-10-18 06:22:22 +00:00
type = models.CharField(choices=TYPE_CHOICES, max_length=255)
amount = models.FloatField(null=True, blank=True)
amount_usd = models.FloatField(null=True, blank=True)
2022-10-17 06:20:30 +00:00
price = models.FloatField(null=True, blank=True)
2022-10-17 17:56:16 +00:00
stop_loss = models.FloatField(null=True, blank=True)
2022-11-15 07:20:17 +00:00
trailing_stop_loss = models.FloatField(null=True, blank=True)
2022-10-17 17:56:16 +00:00
take_profit = models.FloatField(null=True, blank=True)
2022-10-18 06:22:22 +00:00
status = models.CharField(max_length=255, null=True, blank=True)
2022-10-21 23:15:27 +00:00
direction = models.CharField(choices=DIRECTION_CHOICES, max_length=255)
2022-10-18 06:22:22 +00:00
2022-10-17 06:20:30 +00:00
# To populate from the trade
order_id = models.CharField(max_length=255, null=True, blank=True)
client_order_id = models.CharField(max_length=255, null=True, blank=True)
response = models.JSONField(null=True, blank=True)
2022-10-18 06:22:22 +00:00
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._original = self
def post(self):
2022-10-30 10:57:53 +00:00
return self.account.client.post_trade(self)
2022-10-18 06:22:22 +00:00
def delete(self, *args, **kwargs):
# close the trade
super().delete(*args, **kwargs)
2022-10-17 17:56:16 +00:00
class Callback(models.Model):
hook = models.ForeignKey(Hook, on_delete=models.CASCADE)
title = models.CharField(max_length=1024, null=True, blank=True)
message = models.CharField(max_length=1024, null=True, blank=True)
period = models.CharField(max_length=255, null=True, blank=True)
sent = models.BigIntegerField(null=True, blank=True)
trade = models.BigIntegerField(null=True, blank=True)
exchange = models.CharField(max_length=255, null=True, blank=True)
base = models.CharField(max_length=255, null=True, blank=True)
quote = models.CharField(max_length=255, null=True, blank=True)
contract = models.CharField(max_length=255, null=True, blank=True)
price = models.FloatField(null=True, blank=True)
symbol = models.CharField(max_length=255)
class Strategy(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
account = models.ForeignKey(Account, on_delete=models.CASCADE)
order_type = models.CharField(
choices=TYPE_CHOICES, max_length=255, default="market"
)
time_in_force = models.CharField(choices=TIF_CHOICES, max_length=255, default="gtc")
hooks = models.ManyToManyField(Hook)
enabled = models.BooleanField(default=False)
2022-11-06 07:20:24 +00:00
take_profit_percent = models.FloatField(default=1.5)
stop_loss_percent = models.FloatField(default=1.0)
2022-11-15 07:20:17 +00:00
trailing_stop_loss_percent = models.FloatField(default=1.0, null=True, blank=True)
price_slippage_percent = models.FloatField(default=2.5)
callback_price_deviation_percent = models.FloatField(default=0.5)
2022-11-06 07:20:24 +00:00
trade_size_percent = models.FloatField(default=0.5)
class Meta:
verbose_name_plural = "strategies"
def __str__(self):
return self.name
2022-10-13 14:26:43 +00:00
# class Perms(models.Model):
# class Meta:
# permissions = (
# ("bypass_hashing", "Can bypass field hashing"), #
# ("bypass_blacklist", "Can bypass the blacklist"), #
# ("bypass_encryption", "Can bypass field encryption"), #
# ("bypass_obfuscation", "Can bypass field obfuscation"), #
# ("bypass_delay", "Can bypass data delay"), #
# ("bypass_randomisation", "Can bypass data randomisation"), #
# ("post_irc", "Can post to IRC"),
# ("post_discord", "Can post to Discord"),
# ("query_search", "Can search with query strings"), #
# ("use_insights", "Can use the Insights page"),
# ("index_int", "Can use the internal index"),
# ("index_meta", "Can use the meta index"),
# ("restricted_sources", "Can access restricted sources"),
# )