Compare commits

...

49 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
0bf3329b61 Remove comment 2023-02-18 21:25:11 +00:00
911ccde37b Implement trade mutation pipeline and active management actions 2023-02-18 21:23:59 +00:00
ae104f446a Start implementing active management actions 2023-02-18 17:55:39 +00:00
15a8bec105 Simplify active management by only specifying trade IDs for violations 2023-02-18 14:36:58 +00:00
466b17400f Finish implementing active management hooks 2023-02-18 11:54:30 +00:00
3e35214e82 Fix open trades checks 2023-02-17 22:23:12 +00:00
d262f208b5 Write crossfilter, asset groups and max open trades implementation and tests 2023-02-17 22:11:46 +00:00
128 changed files with 3673 additions and 2793 deletions

2
.gitignore vendored
View File

@@ -158,3 +158,5 @@ cython_debug/
.vscode/
core/static/admin
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(",")
# 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_API_KEY_TEST = getenv("STRIPE_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
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:
import socket # only if you haven't already imported this
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + [
"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",
]
# 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_ALLOWED_TEMPLATE_PACKS = ("bulma",)
DJANGO_TABLES2_TEMPLATE = "django-tables2/bulma.html"
@@ -184,7 +167,7 @@ REST_FRAMEWORK = {
INTERNAL_IPS = [
"127.0.0.1",
"10.1.10.11",
# "10.1.10.11",
]
DEBUG_TOOLBAR_PANELS = [
@@ -208,6 +191,24 @@ DEBUG_TOOLBAR_PANELS = [
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
import pyroscope

View File

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

View File

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

View File

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

View File

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

View File

@@ -98,5 +98,4 @@ def to_currency(direction, account, amount, from_currency, to_currency):
# Convert the amount to the destination currency
converted = D(amount) * price
return converted

View File

@@ -79,7 +79,6 @@ def tp_price_to_percent(tp_price, side, current_price, current_units, unrealised
initial_price = D(current_price) - pl_per_unit
else:
initial_price = D(current_price) + pl_per_unit
# Get the percent change of the TP price from the initial price.
change_percent = ((initial_price - D(tp_price)) / initial_price) * 100
@@ -106,6 +105,7 @@ def tp_price_to_percent(tp_price, side, current_price, current_units, unrealised
def tp_percent_to_price(tp_percent, side, current_price, current_units, unrealised_pl):
"""
Determine the price of the TP percent from the initial price.
Negative values for tp_percent indicate a loss.
"""
pl_per_unit = D(unrealised_pl) / D(current_units)
if side == "long":
@@ -117,12 +117,31 @@ def tp_percent_to_price(tp_percent, side, current_price, current_units, unrealis
change_percent = D(tp_percent) / 100
# Get the price of the TP percent from the initial price.
change_price = initial_price * change_percent
change_price = initial_price * abs(change_percent)
# loss is true if tp_percent is:
# - below initial_price for long
# - above initial_price for short
if D(tp_percent) < D(0):
loss = True
else:
loss = False
if side == "long":
tp_price = initial_price - change_price
if loss:
tp_price = D(initial_price) - change_price
else:
tp_price = D(initial_price) + change_price
else:
tp_price = initial_price + change_price
if loss:
tp_price = D(initial_price) + change_price
else:
tp_price = D(initial_price) - change_price
# if side == "long":
# tp_price = initial_price - change_price
# else:
# tp_price = initial_price + change_price
return round(tp_price, 5)
@@ -184,12 +203,23 @@ def sl_percent_to_price(sl_percent, side, current_price, current_units, unrealis
change_percent = D(sl_percent) / 100
# Get the price of the SL percent from the initial price.
change_price = initial_price * change_percent
change_price = initial_price * abs(change_percent)
if D(sl_percent) < D(0):
profit = True
else:
profit = False
if side == "long":
sl_price = initial_price - change_price
if profit:
sl_price = D(initial_price) + change_price
else:
sl_price = D(initial_price) - change_price
else:
sl_price = initial_price + change_price
if profit:
sl_price = D(initial_price) - change_price
else:
sl_price = D(initial_price) + change_price
return round(sl_price, 5)
@@ -270,6 +300,8 @@ def open_trade_to_unified_format(trade):
"id": trade["id"],
"symbol": trade["symbol"],
"amount": current_units,
# For crossfilter
"units": current_units,
"side": side,
"direction": side_to_direction(side),
"state": trade["state"],

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 core.exchanges import BaseExchange, common
from core.util import logs
log = logs.get_logger("oanda")
class OANDAExchange(BaseExchange):
@@ -98,25 +101,58 @@ class OANDAExchange(BaseExchange):
trade.save()
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.
"""
r = trades.TradeClose(accountID=self.account_id, tradeID=trade_id)
return self.call(r)
if not units:
r = trades.TradeClose(accountID=self.account_id, tradeID=trade_id)
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):
# OANDA is off by one...
r = trades.TradeDetails(accountID=self.account_id, tradeID=trade_id)
return self.call(r)
def update_trade(self, trade):
raise NotImplementedError
# r = orders.OrderReplace(
# accountID=self.account_id, orderID=trade.order_id, data=data
# )
# self.client.request(r)
# return r.response
def update_trade(self, trade_id, take_profit_price, stop_loss_price):
data = {}
if take_profit_price:
data["takeProfit"] = {"price": str(take_profit_price)}
if stop_loss_price:
data["stopLoss"] = {"price": str(stop_loss_price)}
r = trades.TradeCRCDO(accountID=self.account_id, tradeID=trade_id, data=data)
return self.call(r)
def cancel_trade(self, trade_id):
raise NotImplementedError

View File

@@ -217,6 +217,21 @@ class StrategyForm(RestrictedFormMixin, ModelForm):
]
)
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"):
self.add_error(
"active_management_policy",
@@ -399,6 +414,7 @@ class ActiveManagementPolicyForm(RestrictedFormMixin, ModelForm):
model = ActiveManagementPolicy
fields = (
"name",
"description",
"when_trading_time_violated",
"when_trends_violated",
"when_position_size_violated",
@@ -412,6 +428,7 @@ class ActiveManagementPolicyForm(RestrictedFormMixin, ModelForm):
)
help_texts = {
"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_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.",
@@ -419,7 +436,7 @@ class ActiveManagementPolicyForm(RestrictedFormMixin, ModelForm):
"when_asset_groups_violated": "The action to take when a trade violating the asset group rules is discovered.",
"when_max_open_trades_violated": "The action to take when a trade puts the account above the maximum open trades.",
"when_max_open_trades_per_symbol_violated": "The action to take when a trade puts the account above the maximum open trades per symbol.",
"when_max_loss_violated": "The action to take when a trade puts the account above the maximum loss.",
"when_max_loss_violated": "The action to take when the account exceeds its maximum loss. NOTE: The close action will close all trades.",
"when_max_risk_violated": "The action to take when a trade exposes the account to more than the maximum risk.",
"when_crossfilter_violated": "The action to take when a trade is deemed to conflict with another -- e.g. a buy and sell on the same asset.",
}

View File

@@ -1,4 +1,111 @@
# from lago_python_client import Client
# from django.conf import settings
import stripe
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 django.conf import settings
from elastic_transport import ConnectionError
from elasticsearch import Elasticsearch
from core.util import logs
@@ -22,11 +23,16 @@ def initialise_elasticsearch():
def store_msg(index, msg):
global client
if not client:
client = initialise_elasticsearch()
if "ts" not in msg:
msg["ts"] = datetime.utcnow().isoformat()
result = client.index(index=index, body=msg)
if not result["result"] == "created":
log.error(f"Indexing of '{msg}' failed: {result}")
return
# global client
# if not client:
# client = initialise_elasticsearch()
# if "ts" not in msg:
# msg["ts"] = datetime.utcnow().isoformat()
# try:
# 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):
"""
Get all the plans from the database and create an object Stripe wants.
"""
line_items = []
for plan in await sync_to_async(list)(Plan.objects.all()):
if product_id_filter:
if plan.product_id != product_id_filter:
continue
line_items.append(
{
"price": plan.product_id,
"quantity": 1,
}
)
return line_items
# async def assemble_plan_map(product_id_filter=None):
# """
# Get all the plans from the database and create an object Stripe wants.
# """
# line_items = []
# for plan in await sync_to_async(list)(Plan.objects.all()):
# if product_id_filter:
# if plan.product_id != product_id_filter:
# continue
# line_items.append(
# {
# "price": plan.product_id,
# "quantity": 1,
# }
# )
# 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 typing import Optional
from pydantic import BaseModel
class PositionLong(BaseModel):
units: str
averagePrice: str | None
averagePrice: Optional[str] = None
pl: str
resettablePL: str
financing: str
dividendAdjustment: str
guaranteedExecutionFees: str
tradeIDs: list[str] | None
tradeIDs: Optional[list[str]] = []
unrealizedPL: str
class PositionShort(BaseModel):
units: str
averagePrice: str | None
averagePrice: Optional[str] = None
pl: str
resettablePL: str
financing: str
dividendAdjustment: str
guaranteedExecutionFees: str
tradeIDs: list[str] | None
tradeIDs: Optional[list[str]] = []
unrealizedPL: str
@@ -306,7 +307,7 @@ class PositionDetailsNested(BaseModel):
dividendAdjustment: str
guaranteedExecutionFees: str
unrealizedPL: str
marginUsed: str | None
marginUsed: Optional[str] = None
class PositionDetails(BaseModel):
@@ -373,7 +374,9 @@ class Instrument(BaseModel):
guaranteedStopLossOrderMode: str
tags: list[InstrumentTag]
financing: InstrumentFinancing
guaranteedStopLossOrderLevelRestriction: InstrumentGuaranteedRestriction | None
guaranteedStopLossOrderLevelRestriction: Optional[
InstrumentGuaranteedRestriction
] = None
class AccountInstruments(BaseModel):
@@ -474,33 +477,33 @@ class Trade(BaseModel):
quoteGuaranteedExecutionFee: str
halfSpreadCost: str
# takeProfitOrder: TakeProfitOrder | None
takeProfitOrder: dict | None
stopLossOrder: dict | None
trailingStopLossOrder: dict | None
takeProfitOrder: Optional[dict] = None
stopLossOrder: Optional[dict] = None
trailingStopLossOrder: Optional[dict] = None
class SideCarOrder(BaseModel):
id: str
createTime: str
state: str
price: str | None
price: Optional[str] = None
timeInForce: str
gtdTime: str | None
clientExtensions: dict | None
gtdTime: Optional[str] = None
clientExtensions: Optional[dict] = None
tradeID: str
clientTradeID: str | None
clientTradeID: Optional[str] = None
type: str
time: str | None
priceBound: str | None
positionFill: str | None
reason: str | None
orderFillTransactionID: str | None
tradeOpenedID: str | None
tradeReducedID: str | None
tradeClosedIDs: list[str] | None
cancellingTransactionID: str | None
replacesOrderID: str | None
replacedByOrderID: str | None
time: Optional[str] = None
priceBound: Optional[str] = None
positionFill: Optional[str] = None
reason: Optional[str] = None
orderFillTransactionID: Optional[str] = None
tradeOpenedID: Optional[str] = None
tradeReducedID: Optional[str] = None
tradeClosedIDs: Optional[list[str]] = []
cancellingTransactionID: Optional[str] = None
replacesOrderID: Optional[str] = None
replacedByOrderID: Optional[str] = None
class OpenTradesTrade(BaseModel):
@@ -517,10 +520,10 @@ class OpenTradesTrade(BaseModel):
dividendAdjustment: str
unrealizedPL: str
marginUsed: str
takeProfitOrder: SideCarOrder | None
stopLossOrder: SideCarOrder | None
trailingStopLossOrder: SideCarOrder | None
trailingStopValue: dict | None
takeProfitOrder: Optional[SideCarOrder] = None
stopLossOrder: Optional[SideCarOrder] = None
trailingStopLossOrder: Optional[SideCarOrder] = None
trailingStopValue: Optional[dict] = None
class OpenTrades(BaseModel):
@@ -578,13 +581,13 @@ class OrderTransaction(BaseModel):
requestID: str
time: str
type: str
instrument: str | None
units: str | None
timeInForce: str | None
positionFill: str | None
instrument: Optional[str] = None
units: Optional[str] = None
timeInForce: Optional[str] = None
positionFill: Optional[str] = None
reason: str
longPositionCloseout: LongPositionCloseout | None
longOrderFillTransaction: dict | None
longOrderFillTransaction: Optional[dict] = None
class OrderCreate(BaseModel):
@@ -677,12 +680,12 @@ class TradeDetailsTrade(BaseModel):
state: str
currentUnits: str
realizedPL: str
closingTransactionIDs: list[str] | None
closingTransactionIDs: Optional[list[str]] = []
financing: str
dividendAdjustment: str
closeTime: str | None
averageClosePrice: str | None
clientExtensions: ClientExtensions | None
closeTime: Optional[str] = None
averageClosePrice: Optional[str] = None
clientExtensions: Optional[ClientExtensions] = None
class TradeDetails(BaseModel):
@@ -730,3 +733,22 @@ TradeCloseSchema = {
"longPositionCloseout": "orderCreateTransaction.longPositionCloseout",
"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:
log.debug(f"Running strategy {strategy.name}")
ams = active_management.ActiveManagement(strategy) # noqa
ams.run_checks()
ams.execute_actions()
class Command(BaseCommand):
@@ -36,11 +38,15 @@ class Command(BaseCommand):
log.debug(f"Scheduling checking process job every {INTERVAL} seconds")
scheduler.add_job(job, "interval", seconds=INTERVAL)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
scheduler._eventloop = loop
scheduler.start()
loop = asyncio.get_event_loop()
try:
loop.run_forever()
except (KeyboardInterrupt, SystemExit):
log.info("Process terminating")
finally:
scheduler.shutdown(wait=False)
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_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')),
('stripe_id', models.CharField(blank=True, max_length=255, null=True)),
('last_payment', models.DateTimeField(blank=True, null=True)),
('billing_provider_id', models.CharField(blank=True, max_length=255, null=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')),
],
@@ -44,32 +43,6 @@ class Migration(migrations.Migration):
('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(
model_name='user',
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
@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
('core', '0003_user_customer_id'),
]
operations = [
migrations.AddField(
model_name='session',
name='session',
model_name='user',
name='stripe_id',
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.fake import FakeExchange
from core.exchanges.mexc import MEXCExchange
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
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 = (
("market", "Market"),
("limit", "Limit"),
@@ -78,68 +86,73 @@ ADJUST_CLOSE_NOTIFY_CHOICES = (
("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):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=1024, null=True, blank=True)
cost = models.IntegerField()
product_id = models.CharField(max_length=255, unique=True, null=True, blank=True)
image = models.CharField(max_length=1024, null=True, blank=True)
# class Plan(models.Model):
# name = models.CharField(max_length=255, unique=True)
# description = models.CharField(max_length=1024, null=True, blank=True)
# cost = models.IntegerField()
# product_id = models.CharField(max_length=255, unique=True, null=True, blank=True)
# image = models.CharField(max_length=1024, null=True, blank=True)
def __str__(self):
return f"{self.name}{self.cost})"
# def __str__(self):
# return f"{self.name} (£{self.cost})"
class User(AbstractUser):
# Stripe customer ID
stripe_id = models.CharField(max_length=255, null=True, blank=True)
last_payment = models.DateTimeField(null=True, blank=True)
plans = models.ManyToManyField(Plan, blank=True)
customer_id = models.UUIDField(default=uuid.uuid4, null=True, 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)
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):
if settings.STRIPE_ENABLED:
if settings.BILLING_ENABLED:
if self.stripe_id:
stripe.Customer.delete(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)
def has_plan(self, plan):
plan_list = [plan.name for plan in self.plans.all()]
return plan in plan_list
# Override save to update attributes in Lago
def save(self, *args, **kwargs):
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):
return NotificationSettings.objects.get_or_create(user=self)[0]
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)
name = models.CharField(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)
instruments = models.JSONField(default=list)
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)
def __str__(self):
@@ -166,11 +176,13 @@ class Account(models.Model):
if client:
response = client.get_instruments()
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}")
self.supported_symbols = supported_symbols
self.instruments = response
self.currency = currency
if "currency" in acct_info.keys():
currency = acct_info["currency"]
self.currency = currency
if save:
self.save()
@@ -211,14 +223,6 @@ class Account(models.Model):
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):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=1024)

View File

@@ -1,5 +1,4 @@
{% load static %}
{% load has_plan %}
{% load cache %}
<!DOCTYPE html>
@@ -287,7 +286,7 @@
</div>
</div>
{% endif %}
{% if settings.STRIPE_ENABLED %}
{% if settings.BILLING_ENABLED %}
{% if user.is_authenticated %}
<a class="navbar-item" href="{% url 'billing' %}">
Billing
@@ -312,7 +311,10 @@
{% endif %}
{% 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 %}
</div>

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