Compare commits

...

42 Commits

Author SHA1 Message Date
e94d693a39 Update compose to work with Podman 2025-01-23 11:40:30 +00:00
7a44660fc1 Resolve conflict with Redis 2025-01-23 11:40:04 +00:00
9ac3ffa540 Add static directory generated by collectstatic to ignore 2025-01-23 11:38:53 +00:00
6ccf84be26 Make project work with Podman 2024-12-28 13:20:55 +00:00
6d9c78d2e1 Remove dev compose 2024-12-22 17:23:31 +00:00
4079207a05 Begin implementing MEXC 2024-12-03 14:12:42 +00:00
761b084704 Fix Redis, begin implementing MEXC 2024-11-16 17:31:43 +00:00
95a4a6930c Fix changed OANDA API 2024-06-10 06:42:46 +01:00
a788a65ba6 Change Redis cache 2024-06-10 05:28:31 +01:00
e10c6f5c46 Fix price extraction bug and remove debugging statements 2023-08-26 11:05:28 +00:00
cd32dff779 Narrowing down 2023-08-24 17:59:17 +00:00
a2f3170ab5 Even more... 2023-08-24 17:55:53 +00:00
3d91fb394a More debugging 2023-08-24 17:52:18 +00:00
771a944a13 Add more debugging 2023-08-24 17:50:55 +00:00
542dca8324 Add debugging information 2023-08-24 17:47:02 +00:00
a68ade9efe Fix development 2023-08-10 17:11:40 +00:00
aca9897f44 Add development Makefile 2023-07-29 16:34:29 +00:00
9474a516ac Undo Podman changes 2023-07-29 16:28:12 +00:00
8ef39ffe48 Migrate to Podman 2023-07-06 16:11:02 +00:00
b4424a7782 Begin work on increasing position size 2023-02-28 07:20:12 +00:00
5843000df6 Add comments and clean up Lago customers 2023-02-27 07:20:42 +00:00
9d37e2bfb8 Integrate Lago with Stripe 2023-02-24 07:20:51 +00:00
cde1392e68 Consolidate migrations 2023-02-24 07:20:31 +00:00
be10375f60 Amend admin for user 2023-02-24 07:20:31 +00:00
ac4c248175 Begin implementing billing 2023-02-24 07:20:31 +00:00
0937f7299a Remove old models from admin 2023-02-24 07:20:31 +00:00
c6dd0ff286 Remove new ID field 2023-02-24 07:20:31 +00:00
86ace02de8 Attempt to fix migrations 2023-02-24 07:20:31 +00:00
fb5521c9f7 Migrate user id to UUID 2023-02-24 07:20:31 +00:00
682c42c0e8 Separate live tests for active management 2023-02-22 07:20:21 +00:00
9c537187f0 Finish AMS tests 2023-02-22 07:20:58 +00:00
ed63085e10 Implement updating protection 2023-02-22 07:20:37 +00:00
ba8eb69309 Begin protection checks 2023-02-20 23:57:20 +00:00
314d4022ea Add description to AMS policy form 2023-02-20 17:21:33 +00:00
89ef8408e6 Amend asset filter matching to be more explicit 2023-02-20 07:20:01 +00:00
9e22abe057 Implement adjusting positions and begin writing live tests for AMS 2023-02-20 07:20:03 +00:00
a840be3834 Adjust initial balance in live tests 2023-02-20 07:20:37 +00:00
8bb5c2c91b Remove empty functions from checks 2023-02-20 07:20:22 +00:00
db58fb34eb Ensure an account only has one strategy with active management 2023-02-18 21:42:56 +00:00
ea0a6f21ce Remove some comments 2023-02-18 21:39:06 +00:00
8d9fe15346 Fix returning the balance 2023-02-18 21:36:46 +00:00
2b6f00a889 Run checks and actions from the management command 2023-02-18 21:36:38 +00:00
121 changed files with 2273 additions and 2652 deletions

2
.gitignore vendored
View File

@@ -158,3 +158,5 @@ cython_debug/
.vscode/ .vscode/
core/static/admin core/static/admin
core/static/debug_toolbar core/static/debug_toolbar
Makefile
static/

View File

@@ -1,26 +0,0 @@
run:
docker-compose --env-file=stack.env up -d
build:
docker-compose --env-file=stack.env build
stop:
docker-compose --env-file=stack.env down
log:
docker-compose --env-file=stack.env logs -f
test:
docker-compose --env-file=stack.env run -e LIVE=$(LIVE) --rm app sh -c ". /venv/bin/activate && python manage.py test $(MODULES) -v 2"
migrate:
docker-compose --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py migrate"
makemigrations:
docker-compose --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py makemigrations"
auth:
docker-compose --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py createsuperuser"
token:
docker-compose --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py addstatictoken m"

26
Makefile-dev Normal file
View File

@@ -0,0 +1,26 @@
run:
docker-compose --env-file=stack.env up -d
build:
docker-compose --env-file=stack.env build
stop:
docker-compose --env-file=stack.env down
log:
docker-compose --env-file=stack.env logs -f
test:
docker-compose --env-file=stack.env run -e LIVE=$(LIVE) --rm app_dev sh -c ". /venv/bin/activate && python manage.py test $(MODULES) -v 2"
migrate:
docker-compose --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py migrate"
makemigrations:
docker-compose --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py makemigrations"
auth:
docker-compose --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py createsuperuser"
token:
docker-compose --env-file=stack.env run --rm app_dev sh -c ". /venv/bin/activate && python manage.py addstatictoken m"

26
Makefile-prod Normal file
View File

@@ -0,0 +1,26 @@
run:
docker-compose -f docker-compose.prod.yml --env-file=stack.env up -d
build:
docker-compose -f docker-compose.prod.yml --env-file=stack.env build
stop:
docker-compose -f docker-compose.prod.yml --env-file=stack.env down
log:
docker-compose -f docker-compose.prod.yml --env-file=stack.env logs -f
test:
docker-compose -f docker-compose.prod.yml --env-file=stack.env run -e LIVE=$(LIVE) --rm app sh -c ". /venv/bin/activate && python manage.py test $(MODULES) -v 2"
migrate:
docker-compose -f docker-compose.prod.yml --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py migrate"
makemigrations:
docker-compose -f docker-compose.prod.yml --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py makemigrations"
auth:
docker-compose -f docker-compose.prod.yml --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py createsuperuser"
token:
docker-compose -f docker-compose.prod.yml --env-file=stack.env run --rm app sh -c ". /venv/bin/activate && python manage.py addstatictoken m"

View File

@@ -13,7 +13,8 @@ ALLOWED_HOSTS = getenv("ALLOWED_HOSTS", f"127.0.0.1,{DOMAIN}").split(",")
CSRF_TRUSTED_ORIGINS = getenv("CSRF_TRUSTED_ORIGINS", URL).split(",") CSRF_TRUSTED_ORIGINS = getenv("CSRF_TRUSTED_ORIGINS", URL).split(",")
# Stripe # Stripe
STRIPE_ENABLED = getenv("STRIPE_ENABLED", "false").lower() in trues BILLING_ENABLED = getenv("BILLING_ENABLED", "false").lower() in trues
STRIPE_TEST = getenv("STRIPE_TEST", "true").lower() in trues STRIPE_TEST = getenv("STRIPE_TEST", "true").lower() in trues
STRIPE_API_KEY_TEST = getenv("STRIPE_API_KEY_TEST", "") STRIPE_API_KEY_TEST = getenv("STRIPE_API_KEY_TEST", "")
STRIPE_PUBLIC_API_KEY_TEST = getenv("STRIPE_PUBLIC_API_KEY_TEST", "") STRIPE_PUBLIC_API_KEY_TEST = getenv("STRIPE_PUBLIC_API_KEY_TEST", "")
@@ -47,13 +48,18 @@ LAGO_URL = getenv("LAGO_URL", "")
DEBUG = getenv("DEBUG", "false").lower() in trues DEBUG = getenv("DEBUG", "false").lower() in trues
PROFILER = getenv("PROFILER", "false").lower() in trues PROFILER = getenv("PROFILER", "false").lower() in trues
REDIS_HOST = getenv("REDIS_HOST", "redis_fisk_dev")
REDIS_PASSWORD = getenv("REDIS_PASSWORD", "changeme")
REDIS_DB = int(getenv("REDIS_DB", "10"))
REDIS_PORT = int(getenv("REDIS_PORT", "6379"))
if DEBUG: if DEBUG:
import socket # only if you haven't already imported this import socket # only if you haven't already imported this
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + [ INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + [
"127.0.0.1", "127.0.0.1",
"10.0.2.2", # "10.0.2.2",
] ]
SETTINGS_EXPORT = ["STRIPE_ENABLED", "URL", "HOOK_PATH", "ASSET_PATH"] SETTINGS_EXPORT = ["BILLING_ENABLED", "URL", "HOOK_PATH", "ASSET_PATH"]

View File

@@ -56,23 +56,6 @@ INSTALLED_APPS = [
"cachalot", "cachalot",
] ]
# Performance optimisations
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "unix:///var/run/socks/redis.sock",
"OPTIONS": {
"db": "10",
"parser_class": "redis.connection.HiredisParser",
"pool_class": "redis.BlockingConnectionPool",
},
}
}
# CACHE_MIDDLEWARE_ALIAS = 'default'
# CACHE_MIDDLEWARE_SECONDS = '600'
# CACHE_MIDDLEWARE_KEY_PREFIX = ''
CRISPY_TEMPLATE_PACK = "bulma" CRISPY_TEMPLATE_PACK = "bulma"
CRISPY_ALLOWED_TEMPLATE_PACKS = ("bulma",) CRISPY_ALLOWED_TEMPLATE_PACKS = ("bulma",)
DJANGO_TABLES2_TEMPLATE = "django-tables2/bulma.html" DJANGO_TABLES2_TEMPLATE = "django-tables2/bulma.html"
@@ -184,7 +167,7 @@ REST_FRAMEWORK = {
INTERNAL_IPS = [ INTERNAL_IPS = [
"127.0.0.1", "127.0.0.1",
"10.1.10.11", # "10.1.10.11",
] ]
DEBUG_TOOLBAR_PANELS = [ DEBUG_TOOLBAR_PANELS = [
@@ -208,6 +191,24 @@ DEBUG_TOOLBAR_PANELS = [
from app.local_settings import * # noqa from app.local_settings import * # noqa
# Performance optimisations
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
# "LOCATION": "unix:///var/run/socks/redis.sock",
"LOCATION": "unix:///var/run/redis.sock",
"OPTIONS": {
"db": REDIS_DB,
# "parser_class": "django_redis.cache.RedisCache",
# "PASSWORD": REDIS_PASSWORD,
"pool_class": "redis.BlockingConnectionPool",
},
}
}
# CACHE_MIDDLEWARE_ALIAS = 'default'
# CACHE_MIDDLEWARE_SECONDS = '600'
# CACHE_MIDDLEWARE_KEY_PREFIX = ''
if PROFILER: # noqa - trust me its there if PROFILER: # noqa - trust me its there
import pyroscope import pyroscope

View File

@@ -18,7 +18,6 @@ from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.views import LogoutView from django.contrib.auth.views import LogoutView
from django.urls import include, path from django.urls import include, path
from django.views.generic import TemplateView
from two_factor.urls import urlpatterns as tf_urls from two_factor.urls import urlpatterns as tf_urls
from core.views import ( from core.views import (
@@ -38,23 +37,24 @@ from core.views import (
strategies, strategies,
trades, trades,
) )
from core.views.stripe_callbacks import Callback
# from core.views.stripe_callbacks import Callback
urlpatterns = [ urlpatterns = [
path("__debug__/", include("debug_toolbar.urls")), path("__debug__/", include("debug_toolbar.urls")),
path("", base.Home.as_view(), name="home"), path("", base.Home.as_view(), name="home"),
path("callback", Callback.as_view(), name="callback"), # path("callback", Callback.as_view(), name="callback"),
path("billing/", base.Billing.as_view(), name="billing"), path("billing/", base.Billing.as_view(), name="billing"),
path("order/<str:plan_name>/", base.Order.as_view(), name="order"), # path("order/<str:plan_name>/", base.Order.as_view(), name="order"),
path( # path(
"cancel_subscription/<str:plan_name>/", # "cancel_subscription/<str:plan_name>/",
base.Cancel.as_view(), # base.Cancel.as_view(),
name="cancel_subscription", # name="cancel_subscription",
), # ),
path( # path(
"success/", TemplateView.as_view(template_name="success.html"), name="success" # "success/", TemplateView.as_view(template_name="success.html"), name="success"
), # ),
path("cancel/", TemplateView.as_view(template_name="cancel.html"), name="cancel"), # path("cancel/", TemplateView.as_view(template_name="cancel.html"), name="cancel"),
path("portal", base.Portal.as_view(), name="portal"), path("portal", base.Portal.as_view(), name="portal"),
path("sapp/", admin.site.urls), path("sapp/", admin.site.urls),
# 2FA login urls # 2FA login urls

View File

@@ -2,15 +2,13 @@ from django.contrib import admin
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm from .forms import CustomUserCreationForm
from .models import ( # AssetRestriction, from .models import ( # AssetRestriction,; Plan,; Session,
Account, Account,
AssetGroup, AssetGroup,
Callback, Callback,
Hook, Hook,
NotificationSettings, NotificationSettings,
Plan,
RiskModel, RiskModel,
Session,
Signal, Signal,
Strategy, Strategy,
Trade, Trade,
@@ -27,24 +25,24 @@ from .models import ( # AssetRestriction,
# Register your models here. # Register your models here.
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
list_filter = ["plans"] # list_filter = ["plans"]
model = User model = User
add_form = CustomUserCreationForm add_form = CustomUserCreationForm
fieldsets = ( fieldsets = (
*UserAdmin.fieldsets, *UserAdmin.fieldsets,
( (
"Stripe information", "Billing information",
{"fields": ("stripe_id",)}, {"fields": ("billing_provider_id", "customer_id", "stripe_id")},
),
(
"Payment information",
{
"fields": (
"plans",
"last_payment",
)
},
), ),
# (
# "Payment information",
# {
# "fields": (
# # "plans",
# "last_payment",
# )
# },
# ),
) )
@@ -101,8 +99,8 @@ class AssetGroupAdmin(admin.ModelAdmin):
admin.site.register(User, CustomUserAdmin) admin.site.register(User, CustomUserAdmin)
admin.site.register(Plan) # admin.site.register(Plan)
admin.site.register(Session) # admin.site.register(Session)
admin.site.register(Account, AccountAdmin) admin.site.register(Account, AccountAdmin)
admin.site.register(Hook, HookAdmin) admin.site.register(Hook, HookAdmin)

View File

@@ -205,7 +205,7 @@ class BaseExchange(ABC):
pass pass
@abstractmethod @abstractmethod
def close_trade(self, trade_id): def close_trade(self, trade_id, units=None):
pass pass
@abstractmethod @abstractmethod

View File

@@ -121,7 +121,7 @@ class AlpacaExchange(BaseExchange):
trade.save() trade.save()
return order return order
def close_trade(self, trade_id): # TODO def close_trade(self, trade_id, units=None): # TODO
""" """
Close a trade Close a trade
""" """

82
core/exchanges/mexc.py Normal file

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,9 @@ from oandapyV20 import API
from oandapyV20.endpoints import accounts, orders, positions, pricing, trades from oandapyV20.endpoints import accounts, orders, positions, pricing, trades
from core.exchanges import BaseExchange, common from core.exchanges import BaseExchange, common
from core.util import logs
log = logs.get_logger("oanda")
class OANDAExchange(BaseExchange): class OANDAExchange(BaseExchange):
@@ -98,25 +101,58 @@ class OANDAExchange(BaseExchange):
trade.save() trade.save()
return response return response
def close_trade(self, trade_id): def get_trade_precision(self, symbol):
instruments = self.account.instruments
if not instruments:
log.error("No instruments found")
return None
# Extract the information for the symbol
instrument = self.extract_instrument(instruments, symbol)
if not instrument:
log.error(f"Symbol not found: {symbol}")
return None
# Get the required precision
try:
trade_precision = instrument["tradeUnitsPrecision"]
return trade_precision
except KeyError:
log.error(f"Precision not found for {symbol} from {instrument}")
return None
def close_trade(self, trade_id, units=None, symbol=None):
""" """
Close a trade. Close a trade.
""" """
if not units:
r = trades.TradeClose(accountID=self.account_id, tradeID=trade_id) r = trades.TradeClose(accountID=self.account_id, tradeID=trade_id)
return self.call(r) return self.call(r)
else:
trade_precision = self.get_trade_precision(symbol)
if trade_precision is None:
log.error(f"Unable to get trade precision for {symbol}")
return None
units = round(units, trade_precision)
data = {
"units": str(units),
}
r = trades.TradeClose(
accountID=self.account_id, tradeID=trade_id, data=data
)
return self.call(r)
def get_trade(self, trade_id): def get_trade(self, trade_id):
# OANDA is off by one... # OANDA is off by one...
r = trades.TradeDetails(accountID=self.account_id, tradeID=trade_id) r = trades.TradeDetails(accountID=self.account_id, tradeID=trade_id)
return self.call(r) return self.call(r)
def update_trade(self, trade): def update_trade(self, trade_id, take_profit_price, stop_loss_price):
raise NotImplementedError data = {}
# r = orders.OrderReplace( if take_profit_price:
# accountID=self.account_id, orderID=trade.order_id, data=data data["takeProfit"] = {"price": str(take_profit_price)}
# ) if stop_loss_price:
# self.client.request(r) data["stopLoss"] = {"price": str(stop_loss_price)}
# return r.response r = trades.TradeCRCDO(accountID=self.account_id, tradeID=trade_id, data=data)
return self.call(r)
def cancel_trade(self, trade_id): def cancel_trade(self, trade_id):
raise NotImplementedError raise NotImplementedError

View File

@@ -217,6 +217,21 @@ class StrategyForm(RestrictedFormMixin, ModelForm):
] ]
) )
if cleaned_data.get("active_management_enabled"): if cleaned_data.get("active_management_enabled"):
# Ensure that no other strategy with this account has active management enabled
if (
Strategy.objects.filter(
account=cleaned_data.get("account"),
active_management_enabled=True,
enabled=True,
)
.exclude(id=self.instance.id)
.exists()
):
self.add_error(
"active_management_enabled",
"You cannot have more than one strategy with active management enabled for the same account.",
)
return
if not cleaned_data.get("active_management_policy"): if not cleaned_data.get("active_management_policy"):
self.add_error( self.add_error(
"active_management_policy", "active_management_policy",
@@ -399,6 +414,7 @@ class ActiveManagementPolicyForm(RestrictedFormMixin, ModelForm):
model = ActiveManagementPolicy model = ActiveManagementPolicy
fields = ( fields = (
"name", "name",
"description",
"when_trading_time_violated", "when_trading_time_violated",
"when_trends_violated", "when_trends_violated",
"when_position_size_violated", "when_position_size_violated",
@@ -412,6 +428,7 @@ class ActiveManagementPolicyForm(RestrictedFormMixin, ModelForm):
) )
help_texts = { help_texts = {
"name": "Name of the active management policy. Informational only.", "name": "Name of the active management policy. Informational only.",
"description": "Description of the active management policy. Informational only.",
"when_trading_time_violated": "The action to take when the trading time is violated.", "when_trading_time_violated": "The action to take when the trading time is violated.",
"when_trends_violated": "The action to take a trade against the trend is discovered.", "when_trends_violated": "The action to take a trade against the trend is discovered.",
"when_position_size_violated": "The action to take when a trade exceeding the position size is discovered.", "when_position_size_violated": "The action to take when a trade exceeding the position size is discovered.",

View File

@@ -1,4 +1,111 @@
# from lago_python_client import Client import stripe
# from django.conf import settings from django.conf import settings
from lago_python_client import Client
from lago_python_client.exceptions import LagoApiError
from lago_python_client.models import Customer, CustomerBillingConfiguration
# client = Client(api_key=settings.LAGO_API_KEY, api_url=settings.LAGO_URL) client = Client(api_key=settings.LAGO_API_KEY, api_url=settings.LAGO_URL)
def expand_name(first_name, last_name):
"""
Convert two name variables into one.
Last name without a first name is ignored.
:param first_name: The first name
:param last_name: The last name
:return: A string with the first and last name, or None if both are None
"""
name = None
if first_name:
name = first_name
# We only want to put the last name if we have a first name
if last_name:
name += f" {last_name}"
return name
def get_or_create(email, first_name, last_name):
"""
Get a customer ID from Stripe if one with the given email exists.
Create a customer if one does not.
Raise an exception if two or more customers matching the given email exist.
:param email: The email address of the customer
:param first_name: The first name of the customer
:param last_name: The last name of the customer
:return: The customer ID
"""
# Let's see if we're just missing the ID
matching_customers = stripe.Customer.list(email=email, limit=2)
if len(matching_customers) == 2:
# Something is horribly wrong
raise Exception(f"Two customers found for email {email}")
elif len(matching_customers) == 1:
# We found a customer. Let's copy the ID
customer = matching_customers["data"][0]
customer_id = customer["id"]
return customer_id
else:
# We didn't find anything. Create the customer
# Create a name, since we have 2 variables which could be null
name = expand_name(first_name, last_name)
cast = {"email": email}
if name:
cast["name"] = name
customer = stripe.Customer.create(**cast)
return customer.id
def update_customer_fields(user):
"""
Update the customer fields in Stripe.
"""
stripe.Customer.modify(user.stripe_id, email=user.email)
name = expand_name(user.first_name, user.last_name)
stripe.Customer.modify(user.stripe_id, name=name)
def create_or_update_customer(user):
"""
Create or update a customer in Lago.
"""
try:
customer = client.customers().find(str(user.customer_id))
except LagoApiError:
customer = None
if not customer:
customer = Customer(
external_id=str(user.customer_id),
name=f"{user.first_name} {user.last_name}",
)
customer.external_id = str(user.customer_id)
customer.email = user.email
customer.name = f"{user.first_name} {user.last_name}"
customer.billing_configuration = CustomerBillingConfiguration(
payment_provider="stripe",
provider_customer_id=str(user.stripe_id),
)
try:
created = client.customers().create(customer)
except LagoApiError as e:
print(e.response)
lago_id = created.lago_id
return lago_id
def delete_customer(user):
"""
Delete a customer from Lago.
:param user: User object to delete
"""
try:
client.customers().destroy(str(user.customer_id))
except LagoApiError:
pass

View File

@@ -1,65 +0,0 @@
import logging
import stripe
logger = logging.getLogger(__name__)
def expand_name(first_name, last_name):
"""
Convert two name variables into one.
Last name without a first name is ignored.
"""
name = None
if first_name:
name = first_name
# We only want to put the last name if we have a first name
if last_name:
name += f" {last_name}"
return name
def get_or_create(email, first_name, last_name):
"""
Get a customer ID from Stripe if one with the given email exists.
Create a customer if one does not.
Raise an exception if two or more customers matching the given email exist.
"""
# Let's see if we're just missing the ID
matching_customers = stripe.Customer.list(email=email, limit=2)
if len(matching_customers) == 2:
# Something is horribly wrong
logger.error(f"Two customers found for email {email}")
raise Exception(f"Two customers found for email {email}")
elif len(matching_customers) == 1:
# We found a customer. Let's copy the ID
customer = matching_customers["data"][0]
customer_id = customer["id"]
return customer_id
else:
# We didn't find anything. Create the customer
# Create a name, since we have 2 variables which could be null
name = expand_name(first_name, last_name)
cast = {"email": email}
if name:
cast["name"] = name
customer = stripe.Customer.create(**cast)
logger.info(f"Created new Stripe customer {customer.id} with email {email}")
return customer.id
def update_customer_fields(stripe_id, email=None, first_name=None, last_name=None):
"""
Update the customer fields in Stripe.
"""
if email:
stripe.Customer.modify(stripe_id, email=email)
logger.info(f"Modified Stripe customer {stripe_id} to have email {email}")
if first_name or last_name:
name = expand_name(first_name, last_name)
stripe.Customer.modify(stripe_id, name=name)
logger.info(f"Modified Stripe customer {stripe_id} to have email {name}")

View File

@@ -1,6 +1,7 @@
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from elastic_transport import ConnectionError
from elasticsearch import Elasticsearch from elasticsearch import Elasticsearch
from core.util import logs from core.util import logs
@@ -22,11 +23,16 @@ def initialise_elasticsearch():
def store_msg(index, msg): def store_msg(index, msg):
global client return
if not client: # global client
client = initialise_elasticsearch() # if not client:
if "ts" not in msg: # client = initialise_elasticsearch()
msg["ts"] = datetime.utcnow().isoformat() # if "ts" not in msg:
result = client.index(index=index, body=msg) # msg["ts"] = datetime.utcnow().isoformat()
if not result["result"] == "created": # try:
log.error(f"Indexing of '{msg}' failed: {result}") # result = client.index(index=index, body=msg)
# except ConnectionError as e:
# log.error(f"Error indexing '{msg}': {e}")
# return
# if not result["result"] == "created":
# log.error(f"Indexing of '{msg}' failed: {result}")

View File

@@ -1,21 +1,21 @@
from asgiref.sync import sync_to_async # from asgiref.sync import sync_to_async
from core.models import Plan # from core.models import Plan
async def assemble_plan_map(product_id_filter=None): # async def assemble_plan_map(product_id_filter=None):
""" # """
Get all the plans from the database and create an object Stripe wants. # Get all the plans from the database and create an object Stripe wants.
""" # """
line_items = [] # line_items = []
for plan in await sync_to_async(list)(Plan.objects.all()): # for plan in await sync_to_async(list)(Plan.objects.all()):
if product_id_filter: # if product_id_filter:
if plan.product_id != product_id_filter: # if plan.product_id != product_id_filter:
continue # continue
line_items.append( # line_items.append(
{ # {
"price": plan.product_id, # "price": plan.product_id,
"quantity": 1, # "quantity": 1,
} # }
) # )
return line_items # return line_items

View File

@@ -1 +1 @@
from core.lib.schemas import alpaca_s, drakdoo_s, oanda_s # noqa from core.lib.schemas import alpaca_s, drakdoo_s, oanda_s, mexc_s # noqa

View File

@@ -0,0 +1 @@
from pydantic import BaseModel, Field

View File

@@ -1,29 +1,30 @@
from decimal import Decimal as D from decimal import Decimal as D
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
class PositionLong(BaseModel): class PositionLong(BaseModel):
units: str units: str
averagePrice: str | None averagePrice: Optional[str] = None
pl: str pl: str
resettablePL: str resettablePL: str
financing: str financing: str
dividendAdjustment: str dividendAdjustment: str
guaranteedExecutionFees: str guaranteedExecutionFees: str
tradeIDs: list[str] | None tradeIDs: Optional[list[str]] = []
unrealizedPL: str unrealizedPL: str
class PositionShort(BaseModel): class PositionShort(BaseModel):
units: str units: str
averagePrice: str | None averagePrice: Optional[str] = None
pl: str pl: str
resettablePL: str resettablePL: str
financing: str financing: str
dividendAdjustment: str dividendAdjustment: str
guaranteedExecutionFees: str guaranteedExecutionFees: str
tradeIDs: list[str] | None tradeIDs: Optional[list[str]] = []
unrealizedPL: str unrealizedPL: str
@@ -306,7 +307,7 @@ class PositionDetailsNested(BaseModel):
dividendAdjustment: str dividendAdjustment: str
guaranteedExecutionFees: str guaranteedExecutionFees: str
unrealizedPL: str unrealizedPL: str
marginUsed: str | None marginUsed: Optional[str] = None
class PositionDetails(BaseModel): class PositionDetails(BaseModel):
@@ -373,7 +374,9 @@ class Instrument(BaseModel):
guaranteedStopLossOrderMode: str guaranteedStopLossOrderMode: str
tags: list[InstrumentTag] tags: list[InstrumentTag]
financing: InstrumentFinancing financing: InstrumentFinancing
guaranteedStopLossOrderLevelRestriction: InstrumentGuaranteedRestriction | None guaranteedStopLossOrderLevelRestriction: Optional[
InstrumentGuaranteedRestriction
] = None
class AccountInstruments(BaseModel): class AccountInstruments(BaseModel):
@@ -474,33 +477,33 @@ class Trade(BaseModel):
quoteGuaranteedExecutionFee: str quoteGuaranteedExecutionFee: str
halfSpreadCost: str halfSpreadCost: str
# takeProfitOrder: TakeProfitOrder | None # takeProfitOrder: TakeProfitOrder | None
takeProfitOrder: dict | None takeProfitOrder: Optional[dict] = None
stopLossOrder: dict | None stopLossOrder: Optional[dict] = None
trailingStopLossOrder: dict | None trailingStopLossOrder: Optional[dict] = None
class SideCarOrder(BaseModel): class SideCarOrder(BaseModel):
id: str id: str
createTime: str createTime: str
state: str state: str
price: str | None price: Optional[str] = None
timeInForce: str timeInForce: str
gtdTime: str | None gtdTime: Optional[str] = None
clientExtensions: dict | None clientExtensions: Optional[dict] = None
tradeID: str tradeID: str
clientTradeID: str | None clientTradeID: Optional[str] = None
type: str type: str
time: str | None time: Optional[str] = None
priceBound: str | None priceBound: Optional[str] = None
positionFill: str | None positionFill: Optional[str] = None
reason: str | None reason: Optional[str] = None
orderFillTransactionID: str | None orderFillTransactionID: Optional[str] = None
tradeOpenedID: str | None tradeOpenedID: Optional[str] = None
tradeReducedID: str | None tradeReducedID: Optional[str] = None
tradeClosedIDs: list[str] | None tradeClosedIDs: Optional[list[str]] = []
cancellingTransactionID: str | None cancellingTransactionID: Optional[str] = None
replacesOrderID: str | None replacesOrderID: Optional[str] = None
replacedByOrderID: str | None replacedByOrderID: Optional[str] = None
class OpenTradesTrade(BaseModel): class OpenTradesTrade(BaseModel):
@@ -517,10 +520,10 @@ class OpenTradesTrade(BaseModel):
dividendAdjustment: str dividendAdjustment: str
unrealizedPL: str unrealizedPL: str
marginUsed: str marginUsed: str
takeProfitOrder: SideCarOrder | None takeProfitOrder: Optional[SideCarOrder] = None
stopLossOrder: SideCarOrder | None stopLossOrder: Optional[SideCarOrder] = None
trailingStopLossOrder: SideCarOrder | None trailingStopLossOrder: Optional[SideCarOrder] = None
trailingStopValue: dict | None trailingStopValue: Optional[dict] = None
class OpenTrades(BaseModel): class OpenTrades(BaseModel):
@@ -578,13 +581,13 @@ class OrderTransaction(BaseModel):
requestID: str requestID: str
time: str time: str
type: str type: str
instrument: str | None instrument: Optional[str] = None
units: str | None units: Optional[str] = None
timeInForce: str | None timeInForce: Optional[str] = None
positionFill: str | None positionFill: Optional[str] = None
reason: str reason: str
longPositionCloseout: LongPositionCloseout | None longPositionCloseout: LongPositionCloseout | None
longOrderFillTransaction: dict | None longOrderFillTransaction: Optional[dict] = None
class OrderCreate(BaseModel): class OrderCreate(BaseModel):
@@ -677,12 +680,12 @@ class TradeDetailsTrade(BaseModel):
state: str state: str
currentUnits: str currentUnits: str
realizedPL: str realizedPL: str
closingTransactionIDs: list[str] | None closingTransactionIDs: Optional[list[str]] = []
financing: str financing: str
dividendAdjustment: str dividendAdjustment: str
closeTime: str | None closeTime: Optional[str] = None
averageClosePrice: str | None averageClosePrice: Optional[str] = None
clientExtensions: ClientExtensions | None clientExtensions: Optional[ClientExtensions] = None
class TradeDetails(BaseModel): class TradeDetails(BaseModel):
@@ -730,3 +733,22 @@ TradeCloseSchema = {
"longPositionCloseout": "orderCreateTransaction.longPositionCloseout", "longPositionCloseout": "orderCreateTransaction.longPositionCloseout",
"longOrderFillTransaction": "orderCreateTransaction.longOrderFillTransaction", "longOrderFillTransaction": "orderCreateTransaction.longOrderFillTransaction",
} }
class TradeCRCDO(BaseModel):
takeProfitOrderCancelTransaction: Optional[OrderTransaction]
takeProfitOrderTransaction: Optional[OrderTransaction]
stopLossOrderCancelTransaction: Optional[OrderTransaction]
stopLossOrderTransaction: Optional[OrderTransaction]
relatedTransactionIDs: list[str]
lastTransactionID: str
TradeCRCDOSchema = {
"takeProfitOrderCancelTransaction": "takeProfitOrderCancelTransaction",
"takeProfitOrderTransaction": "takeProfitOrderTransaction",
"stopLossOrderCancelTransaction": "stopLossOrderCancelTransaction",
"stopLossOrderTransaction": "stopLossOrderTransaction",
"relatedTransactionIDs": "relatedTransactionIDs",
"lastTransactionID": "lastTransactionID",
}

View File

@@ -25,6 +25,8 @@ async def job():
for strategy in strategies: for strategy in strategies:
log.debug(f"Running strategy {strategy.name}") log.debug(f"Running strategy {strategy.name}")
ams = active_management.ActiveManagement(strategy) # noqa ams = active_management.ActiveManagement(strategy) # noqa
ams.run_checks()
ams.execute_actions()
class Command(BaseCommand): class Command(BaseCommand):
@@ -36,11 +38,15 @@ class Command(BaseCommand):
log.debug(f"Scheduling checking process job every {INTERVAL} seconds") log.debug(f"Scheduling checking process job every {INTERVAL} seconds")
scheduler.add_job(job, "interval", seconds=INTERVAL) scheduler.add_job(job, "interval", seconds=INTERVAL)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
scheduler._eventloop = loop
scheduler.start() scheduler.start()
loop = asyncio.get_event_loop()
try: try:
loop.run_forever() loop.run_forever()
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
log.info("Process terminating") log.info("Process terminating")
finally: finally:
scheduler.shutdown(wait=False)
loop.close() loop.close()

View File

@@ -30,8 +30,7 @@ class Migration(migrations.Migration):
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('stripe_id', models.CharField(blank=True, max_length=255, null=True)), ('billing_provider_id', models.CharField(blank=True, max_length=255, null=True)),
('last_payment', models.DateTimeField(blank=True, null=True)),
('email', models.EmailField(max_length=254, unique=True)), ('email', models.EmailField(max_length=254, unique=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
], ],
@@ -44,32 +43,6 @@ class Migration(migrations.Migration):
('objects', django.contrib.auth.models.UserManager()), ('objects', django.contrib.auth.models.UserManager()),
], ],
), ),
migrations.CreateModel(
name='Plan',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('description', models.CharField(blank=True, max_length=1024, null=True)),
('cost', models.IntegerField()),
('product_id', models.CharField(blank=True, max_length=255, null=True, unique=True)),
('image', models.CharField(blank=True, max_length=1024, null=True)),
],
),
migrations.CreateModel(
name='Session',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('request', models.CharField(blank=True, max_length=255, null=True)),
('subscription_id', models.CharField(blank=True, max_length=255, null=True)),
('plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.plan')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='user',
name='plans',
field=models.ManyToManyField(blank=True, to='core.plan'),
),
migrations.AddField( migrations.AddField(
model_name='user', model_name='user',
name='user_permissions', name='user_permissions',

View File

@@ -0,0 +1,231 @@
# Generated by Django 4.1.7 on 2023-02-24 13:18
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('exchange', models.CharField(choices=[('alpaca', 'Alpaca'), ('oanda', 'OANDA'), ('fake', 'Fake')], max_length=255)),
('api_key', models.CharField(max_length=255)),
('api_secret', models.CharField(max_length=255)),
('sandbox', models.BooleanField(default=False)),
('enabled', models.BooleanField(default=True)),
('supported_symbols', models.JSONField(default=list)),
('instruments', models.JSONField(default=list)),
('currency', models.CharField(blank=True, max_length=255, null=True)),
('initial_balance', models.FloatField(default=0)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='ActiveManagementPolicy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('when_trading_time_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255)),
('when_trends_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255)),
('when_position_size_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only'), ('adjust', 'Adjust violating trades')], default='none', max_length=255)),
('when_protection_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only'), ('adjust', 'Adjust violating trades')], default='none', max_length=255)),
('when_asset_groups_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255)),
('when_max_open_trades_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255)),
('when_max_open_trades_per_symbol_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255)),
('when_max_loss_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255)),
('when_max_risk_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255)),
('when_crossfilter_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='AssetGroup',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('webhook_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('when_no_data', models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=7)),
('when_no_match', models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=6)),
('when_no_aggregation', models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=6)),
('when_not_in_bounds', models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=6)),
('when_bullish', models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=2)),
('when_bearish', models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=3)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Hook',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=1024)),
('hook', models.CharField(max_length=255, unique=True)),
('received', models.IntegerField(default=0)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='OrderSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('order_type', models.CharField(choices=[('market', 'Market'), ('limit', 'Limit')], default='market', max_length=255)),
('time_in_force', models.CharField(choices=[('gtc', 'GTC (Good Til Cancelled)'), ('gfd', 'GFD (Good For Day)'), ('fok', 'FOK (Fill Or Kill)'), ('ioc', 'IOC (Immediate Or Cancel)')], default='gtc', max_length=255)),
('take_profit_percent', models.FloatField(default=1.5)),
('stop_loss_percent', models.FloatField(default=1.0)),
('trailing_stop_loss_percent', models.FloatField(blank=True, default=1.0, null=True)),
('trade_size_percent', models.FloatField(default=0.5)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='RiskModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('max_loss_percent', models.FloatField(default=0.05)),
('max_risk_percent', models.FloatField(default=0.05)),
('max_open_trades', models.IntegerField(default=10)),
('max_open_trades_per_symbol', models.IntegerField(default=2)),
('price_slippage_percent', models.FloatField(default=2.5)),
('callback_price_deviation_percent', models.FloatField(default=0.5)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Signal',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=1024)),
('signal', models.CharField(max_length=256)),
('direction', models.CharField(choices=[('buy', 'Buy'), ('sell', 'Sell')], max_length=255)),
('received', models.IntegerField(default=0)),
('type', models.CharField(choices=[('entry', 'Entry'), ('exit', 'Exit'), ('trend', 'Trend')], max_length=255)),
('hook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.hook')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='TradingTime',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('start_day', models.IntegerField(choices=[(1, 'Monday'), (2, 'Tuesday'), (3, 'Wednesday'), (4, 'Thursday'), (5, 'Friday'), (6, 'Saturday'), (7, 'Sunday')])),
('end_day', models.IntegerField(choices=[(1, 'Monday'), (2, 'Tuesday'), (3, 'Wednesday'), (4, 'Thursday'), (5, 'Friday'), (6, 'Saturday'), (7, 'Sunday')])),
('start_time', models.TimeField()),
('end_time', models.TimeField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Trade',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('symbol', models.CharField(max_length=255)),
('time_in_force', models.CharField(choices=[('gtc', 'GTC (Good Til Cancelled)'), ('gfd', 'GFD (Good For Day)'), ('fok', 'FOK (Fill Or Kill)'), ('ioc', 'IOC (Immediate Or Cancel)')], default='gtc', max_length=255)),
('type', models.CharField(choices=[('market', 'Market'), ('limit', 'Limit')], max_length=255)),
('amount', models.FloatField(blank=True, null=True)),
('amount_usd', models.FloatField(blank=True, null=True)),
('price', models.FloatField(blank=True, null=True)),
('stop_loss', models.FloatField(blank=True, null=True)),
('trailing_stop_loss', models.FloatField(blank=True, null=True)),
('take_profit', models.FloatField(blank=True, null=True)),
('status', models.CharField(blank=True, max_length=255, null=True)),
('information', models.JSONField(blank=True, null=True)),
('direction', models.CharField(choices=[('buy', 'Buy'), ('sell', 'Sell')], max_length=255)),
('order_id', models.CharField(blank=True, max_length=255, null=True)),
('client_order_id', models.CharField(blank=True, max_length=255, null=True)),
('response', models.JSONField(blank=True, null=True)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.account')),
('hook', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.hook')),
('signal', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.signal')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Strategy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('enabled', models.BooleanField(default=False)),
('signal_trading_enabled', models.BooleanField(default=False)),
('active_management_enabled', models.BooleanField(default=False)),
('trends', models.JSONField(blank=True, null=True)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.account')),
('active_management_policy', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.activemanagementpolicy')),
('asset_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.assetgroup')),
('entry_signals', models.ManyToManyField(blank=True, related_name='entry_strategies', to='core.signal')),
('exit_signals', models.ManyToManyField(blank=True, related_name='exit_strategies', to='core.signal')),
('order_settings', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='core.ordersettings')),
('risk_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.riskmodel')),
('trading_times', models.ManyToManyField(to='core.tradingtime')),
('trend_signals', models.ManyToManyField(blank=True, related_name='trend_strategies', to='core.signal')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name_plural': 'strategies',
},
),
migrations.CreateModel(
name='NotificationSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ntfy_topic', models.CharField(blank=True, max_length=255, null=True)),
('ntfy_url', models.CharField(blank=True, max_length=255, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Callback',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(blank=True, max_length=1024, null=True)),
('message', models.CharField(blank=True, max_length=1024, null=True)),
('period', models.CharField(blank=True, max_length=255, null=True)),
('sent', models.BigIntegerField(blank=True, null=True)),
('trade', models.BigIntegerField(blank=True, null=True)),
('exchange', models.CharField(blank=True, max_length=255, null=True)),
('base', models.CharField(blank=True, max_length=255, null=True)),
('quote', models.CharField(blank=True, max_length=255, null=True)),
('contract', models.CharField(blank=True, max_length=255, null=True)),
('price', models.FloatField(blank=True, null=True)),
('symbol', models.CharField(max_length=255)),
('hook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.hook')),
('signal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.signal')),
],
),
migrations.CreateModel(
name='AssetRule',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('asset', models.CharField(max_length=64)),
('aggregation', models.CharField(choices=[('none', 'None'), ('avg_sentiment', 'Average sentiment')], default='none', max_length=255)),
('value', models.FloatField(blank=True, null=True)),
('original_status', models.IntegerField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Bullish'), (3, 'Bearish'), (4, 'No aggregation'), (5, 'Not in bounds'), (6, 'Always allow'), (7, 'Always deny')], default=0)),
('status', models.IntegerField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Bullish'), (3, 'Bearish'), (4, 'No aggregation'), (5, 'Not in bounds'), (6, 'Always allow'), (7, 'Always deny')], default=0)),
('trigger_below', models.FloatField(blank=True, null=True)),
('trigger_above', models.FloatField(blank=True, null=True)),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.assetgroup')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('asset', 'group')},
},
),
]

View File

@@ -1,25 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-14 23:15
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0002_session_session'),
]
operations = [
migrations.CreateModel(
name='Hook',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=1024, null=True)),
('hook', models.CharField(max_length=255)),
('received', models.IntegerField(default=0)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.1.7 on 2023-02-24 13:21
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0002_account_activemanagementpolicy_assetgroup_hook_and_more'),
]
operations = [
migrations.AddField(
model_name='user',
name='customer_id',
field=models.UUIDField(blank=True, default=uuid.uuid4, null=True),
),
]

View File

@@ -1,22 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-15 18:19
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0003_hook'),
]
operations = [
migrations.CreateModel(
name='Callback',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data', models.JSONField()),
('hook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.hook')),
],
),
]

View File

@@ -1,4 +1,4 @@
# Generated by Django 4.0.6 on 2022-10-12 09:08 # Generated by Django 4.1.7 on 2023-02-24 16:09
from django.db import migrations, models from django.db import migrations, models
@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0001_initial'), ('core', '0003_user_customer_id'),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='session', model_name='user',
name='session', name='stripe_id',
field=models.CharField(blank=True, max_length=255, null=True), field=models.CharField(blank=True, max_length=255, null=True),
), ),
] ]

View File

@@ -1,77 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-15 22:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0004_callback'),
]
operations = [
migrations.RemoveField(
model_name='callback',
name='data',
),
migrations.AddField(
model_name='callback',
name='market',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='callback',
name='market_contract',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='callback',
name='market_currency',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='callback',
name='market_exchange',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='callback',
name='market_item',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='callback',
name='message',
field=models.CharField(blank=True, max_length=1024, null=True),
),
migrations.AddField(
model_name='callback',
name='period',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='callback',
name='timestamp_sent',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='callback',
name='timestamp_trade',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='callback',
name='title',
field=models.CharField(blank=True, max_length=1024, null=True),
),
migrations.AlterField(
model_name='hook',
name='hook',
field=models.CharField(max_length=255, unique=True),
),
migrations.AlterField(
model_name='hook',
name='name',
field=models.CharField(blank=True, max_length=1024, null=True, unique=True),
),
]

View File

@@ -1,27 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-16 13:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_remove_callback_data_callback_market_and_more'),
]
operations = [
migrations.RemoveField(
model_name='callback',
name='market',
),
migrations.AlterField(
model_name='callback',
name='timestamp_sent',
field=models.BigIntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='callback',
name='timestamp_trade',
field=models.BigIntegerField(blank=True, null=True),
),
]

View File

@@ -1,26 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-17 17:18
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_remove_callback_market_alter_callback_timestamp_sent_and_more'),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('exchange', models.CharField(max_length=255)),
('api_key', models.CharField(max_length=255)),
('api_secret', models.CharField(max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -1,28 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-17 17:39
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0007_account'),
]
operations = [
migrations.CreateModel(
name='Trade',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('symbol', models.CharField(max_length=255)),
('type', models.CharField(max_length=255)),
('amount', models.FloatField()),
('price', models.FloatField()),
('stop_loss', models.FloatField(blank=True, null=True)),
('take_profit', models.FloatField(blank=True, null=True)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.account')),
('hook', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.hook')),
],
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-17 18:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_trade'),
]
operations = [
migrations.AddField(
model_name='trade',
name='exchange_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@@ -1,38 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-17 18:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0009_trade_exchange_id'),
]
operations = [
migrations.AddField(
model_name='account',
name='sandbox',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='trade',
name='direction',
field=models.CharField(blank=True, choices=[('buy', 'Buy'), ('sell', 'Sell')], max_length=255, null=True),
),
migrations.AddField(
model_name='trade',
name='status',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='trade',
name='symbol',
field=models.CharField(choices=[('BTCUSD', 'Bitcoin/USD')], max_length=255),
),
migrations.AlterField(
model_name='trade',
name='type',
field=models.CharField(choices=[('market', 'Market'), ('limit', 'Limit')], max_length=255),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-18 08:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0010_account_sandbox_trade_direction_trade_status_and_more'),
]
operations = [
migrations.AlterField(
model_name='account',
name='exchange',
field=models.CharField(choices=[('binance', 'Binance'), ('alpaca', 'Alpaca')], max_length=255),
),
]

View File

@@ -1,33 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-18 13:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0011_alter_account_exchange'),
]
operations = [
migrations.RenameField(
model_name='trade',
old_name='exchange_id',
new_name='client_order_id',
),
migrations.AddField(
model_name='trade',
name='order_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='trade',
name='response',
field=models.JSONField(blank=True, null=True),
),
migrations.AlterField(
model_name='trade',
name='symbol',
field=models.CharField(choices=[('BTC/USD', 'Bitcoin/US Dollar'), ('LTC/USD', 'Litecoin/US Dollar')], max_length=255),
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-18 13:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0012_rename_exchange_id_trade_client_order_id_and_more'),
]
operations = [
migrations.AlterField(
model_name='trade',
name='direction',
field=models.CharField(choices=[('buy', 'Buy'), ('sell', 'Sell')], default='buy', max_length=255),
preserve_default=False,
),
migrations.AlterField(
model_name='trade',
name='price',
field=models.FloatField(blank=True, null=True),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-21 22:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0013_alter_trade_direction_alter_trade_price'),
]
operations = [
migrations.AlterField(
model_name='account',
name='exchange',
field=models.CharField(choices=[('alpaca', 'Alpaca')], max_length=255),
),
]

View File

@@ -1,27 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-25 21:08
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0014_alter_account_exchange'),
]
operations = [
migrations.CreateModel(
name='Strategy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('enabled', models.BooleanField(default=False)),
('take_profit_percent', models.FloatField(default=300.0)),
('stop_loss_percent', models.FloatField(default=100.0)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.account')),
('hooks', models.ManyToManyField(to='core.hook')),
],
),
]

View File

@@ -1,27 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-25 21:26
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0015_strategy'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='user',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
migrations.AddField(
model_name='trade',
name='user',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-26 09:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0016_strategy_user_trade_user'),
]
operations = [
migrations.AddField(
model_name='hook',
name='direction',
field=models.CharField(choices=[('buy', 'Buy'), ('sell', 'Sell')], default='buy', max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='strategy',
name='price_slippage_percent',
field=models.FloatField(default=2.5),
),
]

View File

@@ -1,28 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-26 09:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0017_hook_direction_strategy_price_slippage_percent'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='trade_size_percent',
field=models.FloatField(default=2.5),
),
migrations.AddField(
model_name='trade',
name='amount_usd',
field=models.FloatField(blank=True, null=True),
),
migrations.AlterField(
model_name='trade',
name='amount',
field=models.FloatField(blank=True, null=True),
),
]

View File

@@ -1,33 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-27 16:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0018_strategy_trade_size_percent_trade_amount_usd_and_more'),
]
operations = [
migrations.AddField(
model_name='account',
name='supported_symbols',
field=models.JSONField(default=list),
),
migrations.AlterField(
model_name='strategy',
name='stop_loss_percent',
field=models.FloatField(default=1.0),
),
migrations.AlterField(
model_name='strategy',
name='take_profit_percent',
field=models.FloatField(default=3.0),
),
migrations.AlterField(
model_name='trade',
name='symbol',
field=models.CharField(max_length=255),
),
]

View File

@@ -1,54 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-27 16:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0019_account_supported_symbols_and_more'),
]
operations = [
migrations.RenameField(
model_name='callback',
old_name='market_item',
new_name='base',
),
migrations.RenameField(
model_name='callback',
old_name='market_contract',
new_name='contract',
),
migrations.RenameField(
model_name='callback',
old_name='market_exchange',
new_name='exchange',
),
migrations.RenameField(
model_name='callback',
old_name='market_currency',
new_name='quote',
),
migrations.RenameField(
model_name='callback',
old_name='timestamp_sent',
new_name='sent',
),
migrations.RenameField(
model_name='callback',
old_name='timestamp_trade',
new_name='trade',
),
migrations.AddField(
model_name='callback',
name='price',
field=models.FloatField(blank=True, null=True),
),
migrations.AddField(
model_name='callback',
name='symbol',
field=models.CharField(default='NUL/NUL', max_length=255),
preserve_default=False,
),
]

View File

@@ -1,33 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-10 18:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0020_rename_market_item_callback_base_and_more'),
]
operations = [
migrations.AddField(
model_name='account',
name='instruments',
field=models.JSONField(default=list),
),
migrations.AlterField(
model_name='account',
name='exchange',
field=models.CharField(choices=[('alpaca', 'Alpaca'), ('oanda', 'OANDA')], max_length=255),
),
migrations.AlterField(
model_name='strategy',
name='take_profit_percent',
field=models.FloatField(default=1.5),
),
migrations.AlterField(
model_name='strategy',
name='trade_size_percent',
field=models.FloatField(default=0.5),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-10 18:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0021_account_instruments_alter_account_exchange_and_more'),
]
operations = [
migrations.AddField(
model_name='account',
name='currency',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@@ -1,27 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-15 15:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0022_account_currency'),
]
operations = [
migrations.AlterModelOptions(
name='strategy',
options={'verbose_name_plural': 'strategies'},
),
migrations.AddField(
model_name='strategy',
name='callback_price_deviation_percent',
field=models.FloatField(default=0.5),
),
migrations.AddField(
model_name='strategy',
name='order_type',
field=models.CharField(choices=[('market', 'Market'), ('limit', 'Limit')], default='market', max_length=255),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-15 15:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0023_alter_strategy_options_and_more'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='time_in_force',
field=models.CharField(choices=[('gtc', 'Good Til Cancelled'), ('gfd', 'Good For Day'), ('fok', 'Fill Or Kill'), ('ioc', 'Immediate Or Cancel')], default='gtc', max_length=255),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-15 15:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0024_strategy_time_in_force'),
]
operations = [
migrations.AlterField(
model_name='strategy',
name='time_in_force',
field=models.CharField(choices=[('gtc', 'GTC (Good Til Cancelled)'), ('gfd', 'GFD (Good For Day)'), ('fok', 'FOK (Fill Or Kill)'), ('ioc', 'IOC (Immediate Or Cancel)')], default='gtc', max_length=255),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-15 15:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0025_alter_strategy_time_in_force'),
]
operations = [
migrations.AddField(
model_name='trade',
name='time_in_force',
field=models.CharField(choices=[('gtc', 'GTC (Good Til Cancelled)'), ('gfd', 'GFD (Good For Day)'), ('fok', 'FOK (Fill Or Kill)'), ('ioc', 'IOC (Immediate Or Cancel)')], default='gtc', max_length=255),
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-15 15:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0026_trade_time_in_force'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='trailing_stop_loss_percent',
field=models.FloatField(blank=True, default=1.0, null=True),
),
migrations.AddField(
model_name='trade',
name='trailing_stop_loss',
field=models.FloatField(blank=True, null=True),
),
]

View File

@@ -1,26 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-25 17:39
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0027_strategy_trailing_stop_loss_percent_and_more'),
]
operations = [
migrations.CreateModel(
name='TradingTime',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=255, null=True)),
('description', models.TextField(blank=True, null=True)),
('start_ts', models.DateTimeField()),
('end_ts', models.DateTimeField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-25 17:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0028_tradingtime'),
]
operations = [
migrations.AlterField(
model_name='tradingtime',
name='name',
field=models.CharField(default='DEFAULT', max_length=255),
preserve_default=False,
),
]

View File

@@ -1,45 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-25 17:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0029_alter_tradingtime_name'),
]
operations = [
migrations.RemoveField(
model_name='tradingtime',
name='end_ts',
),
migrations.RemoveField(
model_name='tradingtime',
name='start_ts',
),
migrations.AddField(
model_name='tradingtime',
name='end_day',
field=models.CharField(choices=[('monday', 'Monday'), ('tuesday', 'Tuesday'), ('wednesday', 'Wednesday'), ('thursday', 'Thursday'), ('friday', 'Friday'), ('saturday', 'Saturday'), ('sunday', 'Sunday')], default='monday', max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='tradingtime',
name='end_time',
field=models.TimeField(default='12:00'),
preserve_default=False,
),
migrations.AddField(
model_name='tradingtime',
name='start_day',
field=models.CharField(choices=[('monday', 'Monday'), ('tuesday', 'Tuesday'), ('wednesday', 'Wednesday'), ('thursday', 'Thursday'), ('friday', 'Friday'), ('saturday', 'Saturday'), ('sunday', 'Sunday')], default='monday', max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='tradingtime',
name='start_time',
field=models.TimeField(default='12:00'),
preserve_default=False,
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-25 18:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0030_remove_tradingtime_end_ts_and_more'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='trading_times',
field=models.ManyToManyField(blank=True, to='core.tradingtime'),
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-25 18:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0031_strategy_trading_times'),
]
operations = [
migrations.AlterField(
model_name='tradingtime',
name='end_day',
field=models.CharField(choices=[(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday')], max_length=255),
),
migrations.AlterField(
model_name='tradingtime',
name='start_day',
field=models.CharField(choices=[(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday')], max_length=255),
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-25 18:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0032_alter_tradingtime_end_day_and_more'),
]
operations = [
migrations.AlterField(
model_name='tradingtime',
name='end_day',
field=models.IntegerField(choices=[(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday')]),
),
migrations.AlterField(
model_name='tradingtime',
name='start_day',
field=models.IntegerField(choices=[(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday')]),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-25 18:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0033_alter_tradingtime_end_day_and_more'),
]
operations = [
migrations.AlterField(
model_name='strategy',
name='trading_times',
field=models.ManyToManyField(to='core.tradingtime'),
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-25 19:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0034_alter_strategy_trading_times'),
]
operations = [
migrations.AlterField(
model_name='tradingtime',
name='end_day',
field=models.IntegerField(choices=[(1, 'Monday'), (2, 'Tuesday'), (3, 'Wednesday'), (4, 'Thursday'), (5, 'Friday'), (6, 'Saturday'), (7, 'Sunday')]),
),
migrations.AlterField(
model_name='tradingtime',
name='start_day',
field=models.IntegerField(choices=[(1, 'Monday'), (2, 'Tuesday'), (3, 'Wednesday'), (4, 'Thursday'), (5, 'Friday'), (6, 'Saturday'), (7, 'Sunday')]),
),
]

View File

@@ -1,37 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-01 18:22
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0035_alter_tradingtime_end_day_and_more'),
]
operations = [
migrations.RemoveField(
model_name='hook',
name='direction',
),
migrations.AlterField(
model_name='hook',
name='name',
field=models.CharField(default='Unknown', max_length=1024),
preserve_default=False,
),
migrations.CreateModel(
name='Signal',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=1024)),
('signal', models.CharField(max_length=256)),
('direction', models.CharField(choices=[('buy', 'Buy'), ('sell', 'Sell')], max_length=255)),
('received', models.IntegerField(default=0)),
('hook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.hook')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-01 18:33
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0036_remove_hook_direction_alter_hook_name_signal'),
]
operations = [
migrations.AddField(
model_name='callback',
name='signal',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.signal'),
preserve_default=False,
),
]

View File

@@ -1,33 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-01 18:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0037_callback_signal'),
]
operations = [
migrations.RemoveField(
model_name='strategy',
name='hooks',
),
migrations.AddField(
model_name='strategy',
name='entry_signals',
field=models.ManyToManyField(related_name='entry_strategies', to='core.signal'),
),
migrations.AddField(
model_name='strategy',
name='exit_signals',
field=models.ManyToManyField(related_name='exit_signals', to='core.signal'),
),
migrations.AddField(
model_name='trade',
name='signal',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.signal'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-01 18:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0038_remove_strategy_hooks_strategy_entry_signals_and_more'),
]
operations = [
migrations.AlterField(
model_name='strategy',
name='exit_signals',
field=models.ManyToManyField(related_name='exit_strategies', to='core.signal'),
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-01 18:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0039_alter_strategy_exit_signals'),
]
operations = [
migrations.AlterField(
model_name='strategy',
name='entry_signals',
field=models.ManyToManyField(blank=True, null=True, related_name='entry_strategies', to='core.signal'),
),
migrations.AlterField(
model_name='strategy',
name='exit_signals',
field=models.ManyToManyField(blank=True, null=True, related_name='exit_strategies', to='core.signal'),
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-01 18:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0040_alter_strategy_entry_signals_and_more'),
]
operations = [
migrations.AlterField(
model_name='strategy',
name='entry_signals',
field=models.ManyToManyField(blank=True, related_name='entry_strategies', to='core.signal'),
),
migrations.AlterField(
model_name='strategy',
name='exit_signals',
field=models.ManyToManyField(blank=True, related_name='exit_strategies', to='core.signal'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-01 19:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0041_alter_strategy_entry_signals_and_more'),
]
operations = [
migrations.AddField(
model_name='trade',
name='information',
field=models.JSONField(blank=True, null=True),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-06 19:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0042_trade_information'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='trend_signals',
field=models.ManyToManyField(blank=True, related_name='trend_strategies', to='core.signal'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-06 19:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0043_strategy_trend_signals'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='trends',
field=models.JSONField(blank=True, null=True),
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-07 09:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0044_strategy_trends'),
]
operations = [
migrations.AddField(
model_name='hook',
name='type',
field=models.CharField(choices=[('entry', 'Entry'), ('exit', 'Exit'), ('trend', 'Trend')], default='entry', max_length=255),
preserve_default=False,
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-07 10:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0045_hook_type'),
]
operations = [
migrations.RemoveField(
model_name='hook',
name='type',
),
migrations.AddField(
model_name='signal',
name='type',
field=models.CharField(choices=[('entry', 'Entry'), ('exit', 'Exit'), ('trend', 'Trend')], default='entry', max_length=255),
preserve_default=False,
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 4.1.3 on 2022-12-18 17:10
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0046_remove_hook_type_signal_type'),
]
operations = [
migrations.CreateModel(
name='NotificationSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ntfy_topic', models.CharField(blank=True, max_length=255, null=True)),
('ntfy_url', models.CharField(blank=True, max_length=255, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -1,33 +0,0 @@
# Generated by Django 4.1.4 on 2022-12-21 21:43
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0047_notificationsettings'),
]
operations = [
migrations.CreateModel(
name='RiskModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('max_loss_percent', models.FloatField(default=0.05)),
('max_risk_percent', models.FloatField(default=0.05)),
('max_open_trades', models.IntegerField(default=10)),
('max_open_trades_per_symbol', models.IntegerField(default=2)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='account',
name='riskmodel',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.riskmodel'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.4 on 2022-12-21 21:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0048_riskmodel_account_riskmodel'),
]
operations = [
migrations.RenameField(
model_name='account',
old_name='riskmodel',
new_name='risk_model',
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.4 on 2023-01-01 15:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0049_rename_riskmodel_account_risk_model'),
]
operations = [
migrations.AddField(
model_name='account',
name='enabled',
field=models.BooleanField(default=True),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.4 on 2023-01-11 17:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0050_account_enabled'),
]
operations = [
migrations.AddField(
model_name='account',
name='initial_balance',
field=models.FloatField(default=0),
),
]

View File

@@ -1,37 +0,0 @@
# Generated by Django 4.1.4 on 2023-02-10 13:29
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0051_account_initial_balance'),
]
operations = [
migrations.CreateModel(
name='AssetGroup',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('allowed', models.JSONField(blank=True, null=True)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.account')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='AssetRestriction',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('pairs', models.CharField(blank=True, max_length=4096, null=True)),
('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.assetgroup')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.4 on 2023-02-10 13:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0052_assetgroup_assetrestriction'),
]
operations = [
migrations.AddField(
model_name='assetrestriction',
name='pairs_parsed',
field=models.JSONField(blank=True, null=True),
),
migrations.AlterField(
model_name='assetgroup',
name='allowed',
field=models.JSONField(blank=True, default={}, null=True),
),
]

View File

@@ -1,25 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-10 21:07
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0053_assetrestriction_pairs_parsed_and_more'),
]
operations = [
migrations.AddField(
model_name='assetrestriction',
name='webhook_id',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.AlterField(
model_name='assetgroup',
name='allowed',
field=models.JSONField(blank=True, default=dict, null=True),
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-10 22:57
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0054_assetrestriction_webhook_id_alter_assetgroup_allowed'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='asset_group',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.assetgroup'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-10 23:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0055_strategy_asset_group'),
]
operations = [
migrations.AlterField(
model_name='assetrestriction',
name='pairs_parsed',
field=models.JSONField(blank=True, default=dict, null=True),
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-11 18:17
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0056_alter_assetrestriction_pairs_parsed'),
]
operations = [
migrations.AlterField(
model_name='assetgroup',
name='account',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.account'),
),
migrations.AlterField(
model_name='assetrestriction',
name='pairs_parsed',
field=models.JSONField(blank=True, default=list, null=True),
),
]

View File

@@ -1,17 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-11 18:46
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0057_alter_assetgroup_account_and_more'),
]
operations = [
migrations.RemoveField(
model_name='assetgroup',
name='account',
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-13 10:28
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0058_remove_assetgroup_account'),
]
operations = [
migrations.AddField(
model_name='assetgroup',
name='webhook_id',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.DeleteModel(
name='AssetRestriction',
),
]

View File

@@ -1,28 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-13 10:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0059_assetgroup_webhook_id_delete_assetrestriction'),
]
operations = [
migrations.AddField(
model_name='assetgroup',
name='aggregation',
field=models.CharField(choices=[('none', 'None'), ('avg_sentiment', 'Average sentiment')], default='none', max_length=255),
),
migrations.AddField(
model_name='assetgroup',
name='trigger_above',
field=models.FloatField(blank=True, null=True),
),
migrations.AddField(
model_name='assetgroup',
name='trigger_below',
field=models.FloatField(blank=True, null=True),
),
]

View File

@@ -1,29 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-13 18:56
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0060_assetgroup_aggregation_assetgroup_trigger_above_and_more'),
]
operations = [
migrations.CreateModel(
name='AssetRule',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('asset', models.CharField(max_length=64)),
('aggregation', models.CharField(choices=[('none', 'None'), ('avg_sentiment', 'Average sentiment')], default='none', max_length=255)),
('value', models.FloatField(blank=True, null=True)),
('status', models.FloatField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Positive'), (3, 'Negative')], default=0)),
('trigger_below', models.FloatField(blank=True, null=True)),
('trigger_above', models.FloatField(blank=True, null=True)),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.assetgroup')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-13 18:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0061_assetrule'),
]
operations = [
migrations.AlterField(
model_name='assetrule',
name='status',
field=models.IntegerField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Positive'), (3, 'Negative')], default=0),
),
]

View File

@@ -1,17 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-13 19:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0062_alter_assetrule_status'),
]
operations = [
migrations.AlterUniqueTogether(
name='assetrule',
unique_together={('asset', 'group')},
),
]

View File

@@ -1,29 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-13 19:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0063_alter_assetrule_unique_together'),
]
operations = [
migrations.RemoveField(
model_name='assetgroup',
name='aggregation',
),
migrations.RemoveField(
model_name='assetgroup',
name='allowed',
),
migrations.RemoveField(
model_name='assetgroup',
name='trigger_above',
),
migrations.RemoveField(
model_name='assetgroup',
name='trigger_below',
),
]

View File

@@ -1,53 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-13 20:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0064_remove_assetgroup_aggregation_and_more'),
]
operations = [
migrations.AddField(
model_name='assetgroup',
name='when_bearish',
field=models.IntegerField(choices=[(6, 'Ignore (no action)'), (-1, 'Default (no remapping)'), (2, 'Bullish'), (3, 'Bearish')], default=-1, max_length=255),
),
migrations.AddField(
model_name='assetgroup',
name='when_bullish',
field=models.IntegerField(choices=[(6, 'Ignore (no action)'), (-1, 'Default (no remapping)'), (2, 'Bullish'), (3, 'Bearish')], default=-1, max_length=255),
),
migrations.AddField(
model_name='assetgroup',
name='when_no_aggregation',
field=models.IntegerField(choices=[(6, 'Ignore (no action)'), (-1, 'Default (no remapping)'), (2, 'Bullish'), (3, 'Bearish')], default=-1, max_length=255),
),
migrations.AddField(
model_name='assetgroup',
name='when_no_data',
field=models.IntegerField(choices=[(6, 'Ignore (no action)'), (-1, 'Default (no remapping)'), (2, 'Bullish'), (3, 'Bearish')], default=-1, max_length=255),
),
migrations.AddField(
model_name='assetgroup',
name='when_no_match',
field=models.IntegerField(choices=[(6, 'Ignore (no action)'), (-1, 'Default (no remapping)'), (2, 'Bullish'), (3, 'Bearish')], default=-1, max_length=255),
),
migrations.AddField(
model_name='assetgroup',
name='when_not_in_bounds',
field=models.IntegerField(choices=[(6, 'Ignore (no action)'), (-1, 'Default (no remapping)'), (2, 'Bullish'), (3, 'Bearish')], default=-1, max_length=255),
),
migrations.AddField(
model_name='assetrule',
name='original_status',
field=models.IntegerField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Bullish'), (3, 'Bearish'), (4, 'No aggregation'), (5, 'Not in bounds'), (6, 'No action')], default=0),
),
migrations.AlterField(
model_name='assetrule',
name='status',
field=models.IntegerField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Bullish'), (3, 'Bearish'), (4, 'No aggregation'), (5, 'Not in bounds'), (6, 'No action')], default=0),
),
]

View File

@@ -1,81 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-15 18:11
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0065_assetgroup_when_bearish_assetgroup_when_bullish_and_more'),
]
operations = [
migrations.RemoveField(
model_name='account',
name='risk_model',
),
migrations.RemoveField(
model_name='strategy',
name='callback_price_deviation_percent',
),
migrations.RemoveField(
model_name='strategy',
name='price_slippage_percent',
),
migrations.AddField(
model_name='riskmodel',
name='callback_price_deviation_percent',
field=models.FloatField(default=0.5),
),
migrations.AddField(
model_name='riskmodel',
name='price_slippage_percent',
field=models.FloatField(default=2.5),
),
migrations.AddField(
model_name='strategy',
name='risk_model',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.riskmodel'),
),
migrations.AlterField(
model_name='assetgroup',
name='when_bearish',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=3),
),
migrations.AlterField(
model_name='assetgroup',
name='when_bullish',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=2),
),
migrations.AlterField(
model_name='assetgroup',
name='when_no_aggregation',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=6),
),
migrations.AlterField(
model_name='assetgroup',
name='when_no_data',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=7),
),
migrations.AlterField(
model_name='assetgroup',
name='when_no_match',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=6),
),
migrations.AlterField(
model_name='assetgroup',
name='when_not_in_bounds',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=6),
),
migrations.AlterField(
model_name='assetrule',
name='original_status',
field=models.IntegerField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Bullish'), (3, 'Bearish'), (4, 'No aggregation'), (5, 'Not in bounds'), (6, 'Always allow'), (7, 'Always deny')], default=0),
),
migrations.AlterField(
model_name='assetrule',
name='status',
field=models.IntegerField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Bullish'), (3, 'Bearish'), (4, 'No aggregation'), (5, 'Not in bounds'), (6, 'Always allow'), (7, 'Always deny')], default=0),
),
]

View File

@@ -1,30 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-15 18:31
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0066_remove_account_risk_model_and_more'),
]
operations = [
migrations.CreateModel(
name='OrderSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('order_type', models.CharField(choices=[('market', 'Market'), ('limit', 'Limit')], default='market', max_length=255)),
('time_in_force', models.CharField(choices=[('gtc', 'GTC (Good Til Cancelled)'), ('gfd', 'GFD (Good For Day)'), ('fok', 'FOK (Fill Or Kill)'), ('ioc', 'IOC (Immediate Or Cancel)')], default='gtc', max_length=255)),
('take_profit_percent', models.FloatField(default=1.5)),
('stop_loss_percent', models.FloatField(default=1.0)),
('trailing_stop_loss_percent', models.FloatField(blank=True, default=1.0, null=True)),
('trade_size_percent', models.FloatField(default=0.5)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-15 18:34
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0067_ordersettings_strategy_order_settings'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='order_settings',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to='core.ordersettings'),
preserve_default=False,
),
]

View File

@@ -1,37 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-15 19:12
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0068_strategy_order_settings'),
]
operations = [
migrations.RemoveField(
model_name='strategy',
name='order_type',
),
migrations.RemoveField(
model_name='strategy',
name='stop_loss_percent',
),
migrations.RemoveField(
model_name='strategy',
name='take_profit_percent',
),
migrations.RemoveField(
model_name='strategy',
name='time_in_force',
),
migrations.RemoveField(
model_name='strategy',
name='trade_size_percent',
),
migrations.RemoveField(
model_name='strategy',
name='trailing_stop_loss_percent',
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.6 on 2023-02-15 19:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0069_remove_strategy_order_type_and_more'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='active_management_enabled',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='strategy',
name='signal_trading_enabled',
field=models.BooleanField(default=False),
),
]

View File

@@ -1,30 +0,0 @@
# Generated by Django 4.1.7 on 2023-02-17 11:50
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0070_strategy_active_management_enabled_and_more'),
]
operations = [
migrations.AlterField(
model_name='account',
name='exchange',
field=models.CharField(choices=[('alpaca', 'Alpaca'), ('oanda', 'OANDA'), ('fake', 'Fake')], max_length=255),
),
migrations.CreateModel(
name='ActiveManagementPolicy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('when_trading_time_violated', models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -1,58 +0,0 @@
# Generated by Django 4.1.7 on 2023-02-17 11:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0071_alter_account_exchange_activemanagementpolicy'),
]
operations = [
migrations.AddField(
model_name='activemanagementpolicy',
name='when_asset_groups_violated',
field=models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255),
),
migrations.AddField(
model_name='activemanagementpolicy',
name='when_crossfilter_violated',
field=models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255),
),
migrations.AddField(
model_name='activemanagementpolicy',
name='when_max_loss_violated',
field=models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255),
),
migrations.AddField(
model_name='activemanagementpolicy',
name='when_max_open_trades_per_symbol_violated',
field=models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255),
),
migrations.AddField(
model_name='activemanagementpolicy',
name='when_max_open_trades_violated',
field=models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255),
),
migrations.AddField(
model_name='activemanagementpolicy',
name='when_max_risk_violated',
field=models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255),
),
migrations.AddField(
model_name='activemanagementpolicy',
name='when_position_size_violated',
field=models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only'), ('adjust', 'Adjust violating trades')], default='none', max_length=255),
),
migrations.AddField(
model_name='activemanagementpolicy',
name='when_protection_violated',
field=models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only'), ('adjust', 'Adjust violating trades')], default='none', max_length=255),
),
migrations.AddField(
model_name='activemanagementpolicy',
name='when_trends_violated',
field=models.CharField(choices=[('none', 'None'), ('close', 'Close violating trades'), ('notify', 'Notify only')], default='none', max_length=255),
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 4.1.7 on 2023-02-17 13:16
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0072_activemanagementpolicy_when_asset_groups_violated_and_more'),
]
operations = [
migrations.AddField(
model_name='strategy',
name='active_management_policy',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.activemanagementpolicy'),
),
]

View File

@@ -8,12 +8,20 @@ from django.db import models
from core.exchanges.alpaca import AlpacaExchange from core.exchanges.alpaca import AlpacaExchange
from core.exchanges.fake import FakeExchange from core.exchanges.fake import FakeExchange
from core.exchanges.mexc import MEXCExchange
from core.exchanges.oanda import OANDAExchange from core.exchanges.oanda import OANDAExchange
from core.lib.customers import get_or_create, update_customer_fields
# from core.lib.customers import get_or_create, update_customer_fields
from core.lib import billing
from core.util import logs from core.util import logs
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
EXCHANGE_MAP = {"alpaca": AlpacaExchange, "oanda": OANDAExchange, "fake": FakeExchange} EXCHANGE_MAP = {
"alpaca": AlpacaExchange,
"oanda": OANDAExchange,
"mexc": MEXCExchange,
"fake": FakeExchange,
}
TYPE_CHOICES = ( TYPE_CHOICES = (
("market", "Market"), ("market", "Market"),
("limit", "Limit"), ("limit", "Limit"),
@@ -78,68 +86,73 @@ ADJUST_CLOSE_NOTIFY_CHOICES = (
("adjust", "Adjust violating trades"), ("adjust", "Adjust violating trades"),
) )
ADJUST_WITH_DIRECTION_CHOICES = (
("none", "None"),
("close", "Close violating trades"),
("notify", "Notify only"),
("adjust", "Increase and reduce"),
("adjust_up", "Increase only"),
("adjust_down", "Reduce only"),
)
class Plan(models.Model): # class Plan(models.Model):
name = models.CharField(max_length=255, unique=True) # name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=1024, null=True, blank=True) # description = models.CharField(max_length=1024, null=True, blank=True)
cost = models.IntegerField() # cost = models.IntegerField()
product_id = models.CharField(max_length=255, unique=True, null=True, blank=True) # product_id = models.CharField(max_length=255, unique=True, null=True, blank=True)
image = models.CharField(max_length=1024, null=True, blank=True) # image = models.CharField(max_length=1024, null=True, blank=True)
def __str__(self): # def __str__(self):
return f"{self.name}{self.cost})" # return f"{self.name} (£{self.cost})"
class User(AbstractUser): class User(AbstractUser):
# Stripe customer ID # Stripe customer ID
stripe_id = models.CharField(max_length=255, null=True, blank=True) stripe_id = models.CharField(max_length=255, null=True, blank=True)
last_payment = models.DateTimeField(null=True, blank=True) customer_id = models.UUIDField(default=uuid.uuid4, null=True, blank=True)
plans = models.ManyToManyField(Plan, blank=True) billing_provider_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) 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
self.stripe_id = get_or_create(
self.email, self.first_name, self.last_name
)
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): def delete(self, *args, **kwargs):
if settings.STRIPE_ENABLED: if settings.BILLING_ENABLED:
if self.stripe_id: if self.stripe_id:
stripe.Customer.delete(self.stripe_id) stripe.Customer.delete(self.stripe_id)
log.info(f"Deleted Stripe customer {self.stripe_id}") log.info(f"Deleted Stripe customer {self.stripe_id}")
if self.billing_provider_id:
billing.delete_customer(self)
log.info(f"Deleted Billing customer {self.billing_provider_id}")
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
def has_plan(self, plan): # Override save to update attributes in Lago
plan_list = [plan.name for plan in self.plans.all()] def save(self, *args, **kwargs):
return plan in plan_list if self.customer_id is None:
self.customer_id = uuid.uuid4()
if settings.BILLING_ENABLED:
if not self.stripe_id: # stripe ID not stored
self.stripe_id = billing.get_or_create(
self.email, self.first_name, self.last_name
)
if not self.billing_provider_id:
self.billing_provider_id = billing.create_or_update_customer(self)
billing.update_customer_fields(self)
super().save(*args, **kwargs)
def get_notification_settings(self): def get_notification_settings(self):
return NotificationSettings.objects.get_or_create(user=self)[0] return NotificationSettings.objects.get_or_create(user=self)[0]
class Account(models.Model): class Account(models.Model):
EXCHANGE_CHOICES = (("alpaca", "Alpaca"), ("oanda", "OANDA"), ("fake", "Fake")) EXCHANGE_CHOICES = (
("alpaca", "Alpaca"),
("oanda", "OANDA"),
("mexc", "MEXC"),
("fake", "Fake"),
)
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
exchange = models.CharField(choices=EXCHANGE_CHOICES, max_length=255) exchange = models.CharField(choices=EXCHANGE_CHOICES, max_length=255)
@@ -150,9 +163,6 @@ class Account(models.Model):
supported_symbols = models.JSONField(default=list) supported_symbols = models.JSONField(default=list)
instruments = models.JSONField(default=list) instruments = models.JSONField(default=list)
currency = models.CharField(max_length=255, null=True, blank=True) currency = models.CharField(max_length=255, null=True, blank=True)
# risk_model = models.ForeignKey(
# "core.RiskModel", on_delete=models.SET_NULL, null=True, blank=True
# )
initial_balance = models.FloatField(default=0) initial_balance = models.FloatField(default=0)
def __str__(self): def __str__(self):
@@ -166,10 +176,12 @@ class Account(models.Model):
if client: if client:
response = client.get_instruments() response = client.get_instruments()
supported_symbols = client.get_supported_assets(response) supported_symbols = client.get_supported_assets(response)
currency = client.get_account()["currency"] acct_info = client.get_account()
log.debug(f"Supported symbols for {self.name}: {supported_symbols}") log.debug(f"Supported symbols for {self.name}: {supported_symbols}")
self.supported_symbols = supported_symbols self.supported_symbols = supported_symbols
self.instruments = response self.instruments = response
if "currency" in acct_info.keys():
currency = acct_info["currency"]
self.currency = currency self.currency = currency
if save: if save:
self.save() self.save()
@@ -211,14 +223,6 @@ class Account(models.Model):
return cls.objects.get(id=account_id) return cls.objects.get(id=account_id)
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)
class Hook(models.Model): class Hook(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=1024) name = models.CharField(max_length=1024)

View File

@@ -1,5 +1,4 @@
{% load static %} {% load static %}
{% load has_plan %}
{% load cache %} {% load cache %}
<!DOCTYPE html> <!DOCTYPE html>
@@ -287,7 +286,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if settings.STRIPE_ENABLED %} {% if settings.BILLING_ENABLED %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a class="navbar-item" href="{% url 'billing' %}"> <a class="navbar-item" href="{% url 'billing' %}">
Billing Billing
@@ -312,7 +311,10 @@
{% endif %} {% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a class="button" href="{% url 'logout' %}">Logout</a> <form method="POST" action="{% url 'logout' %}" style="display:inline;">
{% csrf_token %}
<button type="submit" class="button">Logout</button>
</form>
{% endif %} {% endif %}
</div> </div>

View File

@@ -10,20 +10,6 @@
</span> </span>
<span class="tag">{{ user.first_name }} {{ user.last_name }}</span> <span class="tag">{{ user.first_name }} {{ user.last_name }}</span>
</a> </a>
<a class="panel-block">
<span class="panel-icon">
<i class="fas fa-binary" aria-hidden="true"></i>
</span>
{% for plan in user.plans.all %}
<span class="tag">{{ plan.name }}</span>
{% endfor %}
</a>
<a class="panel-block">
<span class="panel-icon">
<i class="fas fa-credit-card" aria-hidden="true"></i>
</span>
<span class="tag">{{ user.last_payment }}</span>
</a>
<a class="panel-block" href="{% url 'portal' %}"> <a class="panel-block" href="{% url 'portal' %}">
<span class="panel-icon"> <span class="panel-icon">
<i class="fa-brands fa-stripe-s" aria-hidden="true"></i> <i class="fa-brands fa-stripe-s" aria-hidden="true"></i>

Some files were not shown because too many files have changed in this diff Show More