From da67177a18caac4b7cefce51ae26e65769de5503 Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Fri, 17 Feb 2023 07:20:19 +0000 Subject: [PATCH] Begin work on scheduling management command --- core/management/__init__.py | 0 core/management/commands/__init__.py | 0 core/management/commands/scheduling.py | 47 ++++++++++++++++++++++++++ core/tests/trading/test_checks.py | 26 ++++++++++++++ core/trading/active_management.py | 3 ++ core/trading/checks.py | 19 +++++++++++ core/trading/market.py | 2 +- docker-compose.yml | 29 ++++++++++++++++ requirements.txt | 1 + 9 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 core/management/__init__.py create mode 100644 core/management/commands/__init__.py create mode 100644 core/management/commands/scheduling.py create mode 100644 core/tests/trading/test_checks.py create mode 100644 core/trading/active_management.py create mode 100644 core/trading/checks.py diff --git a/core/management/__init__.py b/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/commands/scheduling.py b/core/management/commands/scheduling.py new file mode 100644 index 0000000..024abc2 --- /dev/null +++ b/core/management/commands/scheduling.py @@ -0,0 +1,47 @@ +import asyncio + +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from asgiref.sync import sync_to_async +from django.core.management.base import BaseCommand + +from core.models import Strategy +from core.util import logs +from core.trading import active_management + +log = logs.get_logger("scheduling") + +INTERVAL = 5 + + +async def job(): + """ + Run all schedules matching the given interval. + :param interval_seconds: The interval to run. + """ + strategies = await sync_to_async(list)( + Strategy.objects.filter(enabled=True, active_management_enabled=True) + ) + log.debug(f"Found {len(strategies)} strategies") + for strategy in strategies: + log.debug(f"Running strategy {strategy.name}") + ams = active_management.ActiveManagement(strategy) + + + +class Command(BaseCommand): + def handle(self, *args, **options): + """ + Start the scheduling process. + """ + scheduler = AsyncIOScheduler() + + log.debug(f"Scheduling checking process job every {INTERVAL} seconds") + scheduler.add_job(job, "interval", seconds=INTERVAL) + scheduler.start() + loop = asyncio.get_event_loop() + try: + loop.run_forever() + except (KeyboardInterrupt, SystemExit): + log.info("Process terminating") + finally: + loop.close() diff --git a/core/tests/trading/test_checks.py b/core/tests/trading/test_checks.py new file mode 100644 index 0000000..23fba53 --- /dev/null +++ b/core/tests/trading/test_checks.py @@ -0,0 +1,26 @@ +from django.test import TestCase + +from core.trading import checks + +from core.models import TradingTime, Strategy, OrderSettings, User + +class ChecksTestCase(TestCase): + def setUp(self): + self.user = User.objects.create_user( + username="testuser", email="test@example.com", password="test" + ) + self.order_settings = OrderSettings.objects.create(user=self.user, name="Default") + self.trading_time_now = TradingTime.objects.create( + user=self.user, + name="Test Trading Time", + start_day=1, # Monday + start_time="08:00", + end_day=1, # Monday + end_time="16:00", + ) + + + self.strategy = Strategy.objects.create(user=self.user, name="Test Strategy", ) + + def test_within_trading_times(self): + pass \ No newline at end of file diff --git a/core/trading/active_management.py b/core/trading/active_management.py new file mode 100644 index 0000000..ed6f5cc --- /dev/null +++ b/core/trading/active_management.py @@ -0,0 +1,3 @@ +class ActiveManagement(object): + def __init__(self, strategy): + self.strategy = strategy diff --git a/core/trading/checks.py b/core/trading/checks.py new file mode 100644 index 0000000..4048afd --- /dev/null +++ b/core/trading/checks.py @@ -0,0 +1,19 @@ +from datetime import datetime +from core.util import logs + +log = logs.get_logger("checks") + + +def within_trading_times(strategy, ts=None): + if not ts: + ts = datetime.utcnow() + # Check if we can trade now! + trading_times = strategy.trading_times.all() + if not trading_times: + log.error("No trading times set for strategy") + return False + matches = [x.within_range(ts) for x in trading_times] + if not any(matches): + log.debug("Not within trading time range") + return False + return True \ No newline at end of file diff --git a/core/trading/market.py b/core/trading/market.py index ee90e44..cd69d56 100644 --- a/core/trading/market.py +++ b/core/trading/market.py @@ -5,7 +5,7 @@ from core.exchanges import common from core.exchanges.convert import get_price, side_to_direction from core.lib.notify import sendmsg from core.models import Account, Strategy, Trade -from core.trading import assetfilter +from core.trading import assetfilter, checks from core.trading.crossfilter import crossfilter from core.trading.risk import check_risk from core.util import logs diff --git a/docker-compose.yml b/docker-compose.yml index 2f29fa8..e146517 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +31,35 @@ services: - xf - elastic + scheduling: + image: xf/fisk:prod + container_name: scheduling_fisk + build: + context: . + args: + OPERATION: ${OPERATION} + command: sh -c '. /venv/bin/activate && python manage.py scheduling' + volumes: + - ${PORTAINER_GIT_DIR}:/code + - ${PORTAINER_GIT_DIR}/docker/uwsgi.ini:/conf/uwsgi.ini + - ${APP_DATABASE_FILE}:/conf/db.sqlite3 + - fisk_static:${STATIC_ROOT} + env_file: + - stack.env + volumes_from: + - tmp + depends_on: + redis: + condition: service_healthy + migration: + condition: service_started + collectstatic: + condition: service_started + networks: + - default + - xf + - elastic + migration: image: xf/fisk:prod container_name: migration_fisk diff --git a/requirements.txt b/requirements.txt index 5f76049..38f8b46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,6 +21,7 @@ alpaca-py oandapyV20 glom elasticsearch +apscheduler git+https://git.zm.is/XF/django-crud-mixins # pyroscope-io # For caching