From 67ec6e62d093be62e91a107aea21a371a2430ff0 Mon Sep 17 00:00:00 2001 From: marcvanduyn Date: Wed, 27 Mar 2024 15:55:03 +0100 Subject: [PATCH] Fix sync orders on first load --- README.md | 24 +- .../algorithms/algorithm.py | 29 +- examples/backtest_experiment/backtest.py | 54 +- investing_algorithm_framework/__init__.py | 5 +- investing_algorithm_framework/app/app.py | 15 +- .../domain/services/market_service.py | 7 +- .../models/market_data_sources/ccxt.py | 2 +- .../market_service/ccxt_market_service.py | 17 +- tests/app/test_add_portfolio_configuration.py | 18 +- tests/app/test_app_initialize.py | 39 +- tests/app/test_app_stateless.py | 63 +- tests/app/test_config.py | 3 +- .../order_controller/test_list_orders.py | 22 +- .../test_list_portfolio.py | 25 +- .../test_list_positions.py | 21 +- .../repositories/test_order_repository.py | 57 +- .../repositories/test_position_repository.py | 54 +- tests/resources/test_base.py | 86 +-- tests/services/test_order_backtest_service.py | 94 ++- tests/services/test_order_service.py | 87 ++- tests/services/test_portfolio_service.py | 569 +----------------- tests/services/test_portfolio_sync_service.py | 27 +- tests/test_create_app.py | 3 +- 23 files changed, 429 insertions(+), 892 deletions(-) diff --git a/README.md b/README.md index b70126de..90828532 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,9 @@ from investing_algorithm_framework import create_app, PortfolioConfiguration, \ RESOURCE_DIRECTORY, TimeUnit, CCXTOHLCVMarketDataSource, Algorithm, \ CCXTTickerMarketDataSource, MarketCredential, SYMBOLS -# Define the symbols you want to trade for optimization, otherwise it will -# check all available symbols on the market +# Define the symbols you want to trade for optimization, otherwise the +# algorithm will check if you have orders and balances on all available +# symbols on the market symbols = ["BTC/EUR"] # Define resource directory and the symbols you want to trade @@ -100,16 +101,25 @@ def perform_strategy(algorithm: Algorithm, market_data: dict): # Ticker data is passed as {"": } ticker_data = market_data["BTC-ticker"] - order = algorithm.create_limit_order( + unallocated_balance = algorithm.get_unallocated() + positions = algorithm.get_positions() + trades = algorithm.get_trades() + open_trades = algorithm.get_open_trades() + closed_trades = algorithm.get_closed_trades() + + # Create a buy oder + algorithm.create_limit_order( target_symbol="BTC/EUR", order_side="buy", amount=0.01, price=ticker_data["ask"], ) - positions = algorithm.get_positions() - trades = algorithm.get_trades() - open_trades = algorithm.get_open_trades() - closed_trades = algorithm.get_closed_trades() + + # Close a trade + algorithm.close_trade(trades[0].id) + + # Close a position + algorithm.close_position(positions[0].get_symbol()) if __name__ == "__main__": app.run() diff --git a/examples/backtest_experiment/algorithms/algorithm.py b/examples/backtest_experiment/algorithms/algorithm.py index 3786d04b..bfc59a97 100644 --- a/examples/backtest_experiment/algorithms/algorithm.py +++ b/examples/backtest_experiment/algorithms/algorithm.py @@ -1,6 +1,8 @@ from investing_algorithm_framework import Algorithm, TradingStrategy, \ - TimeUnit, OrderSide + TimeUnit, OrderSide, DATETIME_FORMAT import tulipy as ti +import polars as pl + def is_below_trend(fast_series, slow_series): return fast_series[-1] < slow_series[-1] @@ -39,10 +41,11 @@ class Strategy(TradingStrategy): ] symbols = ["BTC/EUR", "DOT/EUR"] - def __init__(self, fast, slow, trend): + def __init__(self, fast, slow, trend, stop_loss_percentage=4): self.fast = fast self.slow = slow self.trend = trend + self.stop_loss_percentage = stop_loss_percentage super().__init__() def apply_strategy(self, algorithm: Algorithm, market_data): @@ -81,17 +84,35 @@ def apply_strategy(self, algorithm: Algorithm, market_data): for trade in open_trades: algorithm.close_trade(trade) + # Checking manual stop losses + open_trades = algorithm.get_open_trades(target_symbol) + + for open_trade in open_trades: + filtered_df = df.filter( + pl.col('Datetime') >= open_trade.opened_at.strftime(DATETIME_FORMAT) + ) + close_prices = filtered_df['Close'].to_numpy() + current_price = market_data[f"{symbol}-ticker"] + + if open_trade.is_manual_stop_loss_trigger( + prices=close_prices, + current_price=current_price["bid"], + stop_loss_percentage=self.stop_loss_percentage + ): + algorithm.close_trade(open_trade) + def create_algorithm( name, description, fast, slow, - trend + trend, + stop_loss_percentage ) -> Algorithm: algorithm = Algorithm( name=name, description=description ) - algorithm.add_strategy(Strategy(fast, slow, trend)) + algorithm.add_strategy(Strategy(fast, slow, trend, stop_loss_percentage)) return algorithm diff --git a/examples/backtest_experiment/backtest.py b/examples/backtest_experiment/backtest.py index 2e76a417..42442796 100644 --- a/examples/backtest_experiment/backtest.py +++ b/examples/backtest_experiment/backtest.py @@ -25,126 +25,144 @@ description="9-50-100", fast=9, slow=50, - trend=100 + trend=100, + stop_loss_percentage=7 ), create_algorithm( name="10-50-100", description="10-50-100", fast=10, slow=50, - trend=100 + trend=100, + stop_loss_percentage=7 ), create_algorithm( name="11-50-100", description="11-50-100", fast=11, slow=50, - trend=100 + trend=100, + stop_loss_percentage=7 ), create_algorithm( name="9-75-150", description="9-75-150", fast=9, slow=75, - trend=150 + trend=150, + stop_loss_percentage=7 ), create_algorithm( name="10-75-150", description="10-75-150", fast=10, slow=75, - trend=150 + trend=150, + stop_loss_percentage=7 ), create_algorithm( name="11-75-150", description="11-75-150", fast=11, slow=75, - trend=150 + trend=150, + stop_loss_percentage=7 ), create_algorithm( name="20-75-150", description="20-75-150", fast=20, slow=75, - trend=150 + trend=150, + stop_loss_percentage=7 ), create_algorithm( name="21-75-150", description="21-75-150", fast=21, slow=75, - trend=150 + trend=150, + stop_loss_percentage=7 ), create_algorithm( name="22-75-150", description="22-75-150", fast=22, slow=75, - trend=150 + trend=150, + stop_loss_percentage=7 ), create_algorithm( name="23-75-150", description="23-75-150", fast=23, slow=75, - trend=150 + trend=150, + stop_loss_percentage=7 ), create_algorithm( name="24-75-150", description="24-75-150", fast=24, slow=75, - trend=150 + trend=150, + stop_loss_percentage=7 ), create_algorithm( name="25-75-150", description="25-75-150", fast=25, slow=75, - trend=150 + trend=150, + stop_loss_percentage=7 ), create_algorithm( name="20-75-200", description="20-75-200", fast=20, slow=75, - trend=200 + trend=200, + stop_loss_percentage=7 ), create_algorithm( name="21-75-200", description="24-75-200", fast=24, slow=75, - trend=200 + trend=200, + stop_loss_percentage=7 ), create_algorithm( name="22-75-200", description="24-75-200", fast=24, slow=75, - trend=200 + trend=200, + stop_loss_percentage=7 ), create_algorithm( name="23-75-200", description="24-75-200", fast=24, slow=75, - trend=200 + trend=200, + stop_loss_percentage=7 ), create_algorithm( name="24-75-200", description="24-75-200", fast=24, slow=75, - trend=200 + trend=200, + stop_loss_percentage=7 ), create_algorithm( name="25-75-150", description="25-75-200", fast=25, slow=75, - trend=200 + trend=200, + stop_loss_percentage=7 ), ], start_date=start_date, diff --git a/investing_algorithm_framework/__init__.py b/investing_algorithm_framework/__init__.py index 63d4cb6e..726d1c2b 100644 --- a/investing_algorithm_framework/__init__.py +++ b/investing_algorithm_framework/__init__.py @@ -8,7 +8,7 @@ Trade, OHLCVMarketDataSource, OrderBookMarketDataSource, SYMBOLS, \ TickerMarketDataSource, MarketService, BacktestReportsEvaluation, \ pretty_print_backtest_reports_evaluation, load_backtest_reports, \ - RESERVED_BALANCES, APP_MODE, AppMode + RESERVED_BALANCES, APP_MODE, AppMode, DATETIME_FORMAT from investing_algorithm_framework.app import TradingStrategy, \ StatelessAction, Task from investing_algorithm_framework.infrastructure import \ @@ -59,5 +59,6 @@ "SYMBOLS", "RESERVED_BALANCES", "APP_MODE", - "AppMode" + "AppMode", + "DATETIME_FORMAT", ] diff --git a/investing_algorithm_framework/app/app.py b/investing_algorithm_framework/app/app.py index 0d82f488..34f8450e 100644 --- a/investing_algorithm_framework/app/app.py +++ b/investing_algorithm_framework/app/app.py @@ -109,12 +109,13 @@ def initialize(self, sync=False): trade_service=self.container.trade_service(), ) - if self._stateless: - self.config[APP_MODE] = AppMode.STATELESS.value - elif self._web: - self.config[APP_MODE] = AppMode.WEB.value - else: - self.config[APP_MODE] = AppMode.DEFAULT.value + if APP_MODE not in self.config: + if self._stateless: + self.config[APP_MODE] = AppMode.STATELESS.value + elif self._web: + self.config[APP_MODE] = AppMode.WEB.value + else: + self.config[APP_MODE] = AppMode.DEFAULT.value if AppMode.WEB.from_value(self.config[APP_MODE]): self._initialize_web() @@ -460,7 +461,7 @@ def run( for hook in self._on_after_initialize_hooks: hook.on_run(self, self.algorithm) - self.initialize() + self.initialize(sync=sync) # Run all on_initialize hooks for hook in self._on_initialize_hooks: diff --git a/investing_algorithm_framework/domain/services/market_service.py b/investing_algorithm_framework/domain/services/market_service.py index feb2e75e..8d8d2d4f 100644 --- a/investing_algorithm_framework/domain/services/market_service.py +++ b/investing_algorithm_framework/domain/services/market_service.py @@ -1,7 +1,8 @@ import logging +from typing import Dict from abc import ABC, abstractmethod from datetime import datetime - +from polars import DataFrame logger = logging.getLogger("investing_algorithm_framework") @@ -98,13 +99,13 @@ def get_closed_orders( @abstractmethod def get_ohlcv( self, symbol, time_frame, from_timestamp, market, to_timestamp=None - ): + ) -> DataFrame: raise NotImplementedError() @abstractmethod def get_ohlcvs( self, symbols, time_frame, from_timestamp, market, to_timestamp=None - ): + ) -> Dict[str, DataFrame]: raise NotImplementedError() @property diff --git a/investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py b/investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py index 6a5b2e6f..678989fe 100644 --- a/investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +++ b/investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py @@ -370,7 +370,7 @@ def get_data(self, **kwargs): first_row = df.head(1)[0] if first_row["Datetime"][0] > end_date.strftime(DATETIME_FORMAT): - logger.warn( + logger.warning( f"No ticker data available for the given backtest " f"index date {backtest_index_date} and symbol {self.symbol} " f"and market {self.market}" diff --git a/investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py b/investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py index be362c45..57763b64 100644 --- a/investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +++ b/investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py @@ -1,7 +1,8 @@ import logging +from typing import Dict from datetime import datetime from time import sleep - +import polars as pl import ccxt from dateutil import parser from dateutil.tz import gettz @@ -341,7 +342,7 @@ def get_closed_orders( def get_ohlcv( self, symbol, time_frame, from_timestamp, market, to_timestamp=None - ): + ) -> pl.DataFrame: market_credential = self.get_market_credential(market) exchange = self.initialize_exchange(market, market_credential) @@ -388,7 +389,15 @@ def get_ohlcv( sleep(exchange.rateLimit / 1000) - return data + # Predefined column names + col_names = ["Datetime", "Open", "High", "Low", "Close", "Volume"] + + # Combine the Series into a DataFrame with given column names + df = pl.DataFrame(data) + + # Assign column names after DataFrame creation + df.columns = col_names + return df def get_ohlcvs( self, @@ -397,7 +406,7 @@ def get_ohlcvs( from_timestamp, market, to_timestamp=None - ): + ) -> Dict[str, pl.DataFrame]: ohlcvs = {} for symbol in symbols: diff --git a/tests/app/test_add_portfolio_configuration.py b/tests/app/test_add_portfolio_configuration.py index 7d470890..491ddfae 100644 --- a/tests/app/test_add_portfolio_configuration.py +++ b/tests/app/test_add_portfolio_configuration.py @@ -1,11 +1,6 @@ -import os -from unittest import TestCase - -from investing_algorithm_framework import create_app, PortfolioConfiguration, \ - RESOURCE_DIRECTORY, Algorithm, MarketCredential -from investing_algorithm_framework.dependency_container import \ - DependencyContainer -from tests.resources import MarketServiceStub, TestBase +from investing_algorithm_framework import PortfolioConfiguration, \ + MarketCredential +from tests.resources import TestBase class Test(TestBase): @@ -27,11 +22,6 @@ class Test(TestBase): } def test_add(self): - self.app.add_portfolio_configuration( - PortfolioConfiguration( - market="BITVAVO", - trading_symbol="EUR", - ) - ) self.assertEqual(1, self.app.algorithm.portfolio_service.count()) + self.assertEqual(1, self.app.algorithm.position_service.count()) self.assertEqual(1000, self.app.algorithm.get_unallocated()) diff --git a/tests/app/test_app_initialize.py b/tests/app/test_app_initialize.py index 047e5783..6fb325c4 100644 --- a/tests/app/test_app_initialize.py +++ b/tests/app/test_app_initialize.py @@ -2,12 +2,28 @@ from unittest import TestCase from investing_algorithm_framework import create_app, PortfolioConfiguration, \ - MarketCredential, Algorithm + MarketCredential, Algorithm, AppMode, APP_MODE from investing_algorithm_framework.domain import SQLALCHEMY_DATABASE_URI -from tests.resources import TestBase, MarketServiceStub +from tests.resources import MarketServiceStub class TestAppInitialize(TestCase): + portfolio_configurations = [ + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ] + market_credentials = [ + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ] + external_balances = { + "EUR": 1000, + } def setUp(self) -> None: self.resource_dir = os.path.abspath( @@ -33,7 +49,7 @@ def test_app_initialize_default(self): app.add_portfolio_configuration( PortfolioConfiguration( market="BITVAVO", - trading_symbol="USDT", + trading_symbol="EUR", ) ) algorithm = Algorithm() @@ -47,9 +63,8 @@ def test_app_initialize_default(self): ) app.initialize() self.assertIsNotNone(app.config) - self.assertIsNone(app._flask_app) - self.assertFalse(app.stateless) - self.assertFalse(app.web) + self.assertIsNotNone(app._flask_app) + self.assertTrue(AppMode.DEFAULT.equals(app.config[APP_MODE])) order_service = app.container.order_service() self.assertEqual(0, order_service.count()) @@ -62,7 +77,7 @@ def test_app_initialize_web(self): app.add_portfolio_configuration( PortfolioConfiguration( market="BITVAVO", - trading_symbol="USDT", + trading_symbol="EUR", ) ) algorithm = Algorithm() @@ -77,8 +92,7 @@ def test_app_initialize_web(self): app.initialize() self.assertIsNotNone(app.config) self.assertIsNotNone(app._flask_app) - self.assertFalse(app.stateless) - self.assertTrue(app.web) + self.assertTrue(AppMode.WEB.equals(app.config[APP_MODE])) order_service = app.container.order_service() self.assertEqual(0, order_service.count()) @@ -91,7 +105,7 @@ def test_app_initialize_stateless(self): app.add_portfolio_configuration( PortfolioConfiguration( market="BITVAVO", - trading_symbol="USDT" + trading_symbol="EUR" ) ) algorithm = Algorithm() @@ -106,8 +120,7 @@ def test_app_initialize_stateless(self): app.initialize() order_service = app.container.order_service() self.assertIsNotNone(app.config) - self.assertIsNone(app._flask_app) - self.assertTrue(app.stateless) - self.assertFalse(app.web) + self.assertIsNotNone(app._flask_app) + self.assertTrue(AppMode.STATELESS.equals(app.config[APP_MODE])) self.assertEqual(app.config[SQLALCHEMY_DATABASE_URI], "sqlite://") self.assertEqual(0, order_service.count()) diff --git a/tests/app/test_app_stateless.py b/tests/app/test_app_stateless.py index f3d9a06a..2cc50caa 100644 --- a/tests/app/test_app_stateless.py +++ b/tests/app/test_app_stateless.py @@ -1,6 +1,7 @@ from tests.resources import TestBase from investing_algorithm_framework import PortfolioConfiguration, \ - MarketCredential, APP_MODE, AppMode, StatelessAction + MarketCredential, APP_MODE, AppMode, StatelessAction, create_app +from tests.resources import MarketServiceStub class Test(TestBase): @@ -27,7 +28,10 @@ class Test(TestBase): def test_run(self): config = self.app.config self.assertTrue(AppMode.STATELESS.equals(config[APP_MODE])) - self.app.run(number_of_iterations=1) + self.app.run( + number_of_iterations=1, + payload={"action": StatelessAction.RUN_STRATEGY.value} + ) self.assertEqual(1000, self.app.algorithm.get_unallocated()) trading_symbol_position = self.app.algorithm.get_position( symbol="EUR" @@ -35,22 +39,61 @@ def test_run(self): self.assertEqual(1000, trading_symbol_position.get_amount()) def test_run_with_changed_external_positions(self): - config = self.app.config - self.assertTrue(AppMode.STATELESS.equals(config[APP_MODE])) + app = create_app(config={APP_MODE: AppMode.STATELESS.value}) + app.add_algorithm(self.algorithm) + app.add_market_credential( + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ) + app.add_portfolio_configuration( + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ) + market_service = MarketServiceStub(None) + market_service.balances = { + "EUR": 1000, + "BTC": 1 + } + app.container.market_service.override(market_service) + app.initialize() + self.assertTrue(AppMode.STATELESS.equals(app.config[APP_MODE])) self.app.run(payload={ "action": StatelessAction.RUN_STRATEGY.value, }) - self.assertEqual(1000, self.app.algorithm.get_unallocated()) + self.assertEqual(1000, app.algorithm.get_unallocated()) trading_symbol_position = self.app.algorithm.get_position( symbol="EUR" ) self.assertEqual(1000, trading_symbol_position.get_amount()) - self.market_service.external_balances = { + app = create_app(config={APP_MODE: AppMode.STATELESS.value}) + app.add_algorithm(self.algorithm) + app.add_market_credential( + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ) + app.add_portfolio_configuration( + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ) + market_service = MarketServiceStub(None) + market_service.balances = { "EUR": 2000, "BTC": 1 } - self.app.container.market_service.override(self.market_service) - self.app.run(payload={ - "action": StatelessAction.RUN_STRATEGY.value, - }) + app.container.market_service.override(market_service) + app.initialize() + self.assertEqual(2000, app.algorithm.get_unallocated()) + trading_symbol_position = self.app.algorithm.get_position( + symbol="EUR" + ) self.assertEqual(2000, trading_symbol_position.get_amount()) diff --git a/tests/app/test_config.py b/tests/app/test_config.py index 1d473206..69d5c26f 100644 --- a/tests/app/test_config.py +++ b/tests/app/test_config.py @@ -1,11 +1,12 @@ from investing_algorithm_framework import create_app +from unittest import TestCase from investing_algorithm_framework.domain import BACKTEST_DATA_DIRECTORY_NAME from tests.resources import random_string, TestBase TEST_VALUE = random_string(10) -class TestConfig(TestBase): +class TestConfig(TestCase): ATTRIBUTE_ONE = "ATTRIBUTE_ONE" def test_config(self): diff --git a/tests/app/web/controllers/order_controller/test_list_orders.py b/tests/app/web/controllers/order_controller/test_list_orders.py index 477bbc14..edbdf879 100644 --- a/tests/app/web/controllers/order_controller/test_list_orders.py +++ b/tests/app/web/controllers/order_controller/test_list_orders.py @@ -1,6 +1,7 @@ import json -from investing_algorithm_framework import PortfolioConfiguration +from investing_algorithm_framework import PortfolioConfiguration, \ + MarketCredential from tests.resources import FlaskTestBase @@ -8,18 +9,23 @@ class Test(FlaskTestBase): portfolio_configurations = [ PortfolioConfiguration( market="BITVAVO", - trading_symbol="USDT" + trading_symbol="EUR" ) ] - - def setUp(self) -> None: - super(Test, self).setUp() + market_credentials = [ + MarketCredential( + market="BITVAVO", + api_key="", + secret_key="" + ) + ] + external_balances = { + "EUR": 1000 + } def test_list_portfolios(self): - order_repository = self.iaf_app.container.order_repository() self.iaf_app.run(number_of_iterations=1) - self.assertEqual(1, order_repository.count()) - response = self.client.get("/api/orders") + response = self.client.get("/api/portfolios") data = json.loads(response.data.decode()) self.assertEqual(200, response.status_code) self.assertEqual(1, len(data["items"])) diff --git a/tests/app/web/controllers/portfolio_controller/test_list_portfolio.py b/tests/app/web/controllers/portfolio_controller/test_list_portfolio.py index 1ce49f96..5bf96afd 100644 --- a/tests/app/web/controllers/portfolio_controller/test_list_portfolio.py +++ b/tests/app/web/controllers/portfolio_controller/test_list_portfolio.py @@ -1,7 +1,7 @@ import json -from investing_algorithm_framework import TradingStrategy, \ - TimeUnit, PortfolioConfiguration +from investing_algorithm_framework import MarketCredential, \ + PortfolioConfiguration from tests.resources import FlaskTestBase @@ -9,14 +9,27 @@ class Test(FlaskTestBase): portfolio_configurations = [ PortfolioConfiguration( market="BITVAVO", - trading_symbol="USDT" + trading_symbol="EUR" ) ] - - def setUp(self) -> None: - super(Test, self).setUp() + market_credentials = [ + MarketCredential( + market="BITVAVO", + api_key="", + secret_key="" + ) + ] + external_balances = { + "EUR": 1000 + } def test_list_portfolios(self): + self.iaf_app.algorithm.create_limit_order( + target_symbol="KSM", + price=10, + amount=10, + order_side="BUY" + ) order_repository = self.iaf_app.container.order_repository() self.iaf_app.run(number_of_iterations=1) self.assertEqual(1, order_repository.count()) diff --git a/tests/app/web/controllers/position_controller/test_list_positions.py b/tests/app/web/controllers/position_controller/test_list_positions.py index 03580c3d..f507524c 100644 --- a/tests/app/web/controllers/position_controller/test_list_positions.py +++ b/tests/app/web/controllers/position_controller/test_list_positions.py @@ -1,6 +1,7 @@ import json -from investing_algorithm_framework import PortfolioConfiguration +from investing_algorithm_framework import PortfolioConfiguration, \ + MarketCredential from tests.resources import FlaskTestBase @@ -8,14 +9,30 @@ class Test(FlaskTestBase): portfolio_configurations = [ PortfolioConfiguration( market="BITVAVO", - trading_symbol="USDT" + trading_symbol="EUR" ) ] + market_credentials = [ + MarketCredential( + market="BITVAVO", + api_key="", + secret_key="" + ) + ] + external_balances = { + "EUR": 1000 + } def setUp(self) -> None: super(Test, self).setUp() def test_list_portfolios(self): + self.iaf_app.algorithm.create_limit_order( + amount=10, + target_symbol="KSM", + price=10, + order_side="BUY" + ) order_repository = self.iaf_app.container.order_repository() self.iaf_app.run(number_of_iterations=1) self.assertEqual(1, order_repository.count()) diff --git a/tests/infrastructure/repositories/test_order_repository.py b/tests/infrastructure/repositories/test_order_repository.py index 809b6b1b..58a19cef 100644 --- a/tests/infrastructure/repositories/test_order_repository.py +++ b/tests/infrastructure/repositories/test_order_repository.py @@ -1,47 +1,24 @@ -import os - -from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY, \ - PortfolioConfiguration, MarketCredential, Algorithm -from tests.resources import TestBase, MarketServiceStub +from investing_algorithm_framework import PortfolioConfiguration, MarketCredential +from tests.resources import TestBase class Test(TestBase): - - def setUp(self) -> None: - self.resource_dir = os.path.abspath( - os.path.join( - os.path.join( - os.path.join( - os.path.join( - os.path.realpath(__file__), - os.pardir - ), - os.pardir - ), - os.pardir - ), - "resources" - ) + market_credentials = [ + MarketCredential( + market="BINANCE", + api_key="api_key", + secret_key="secret_key", ) - self.app = create_app(config={RESOURCE_DIRECTORY: self.resource_dir}) - self.app.add_portfolio_configuration( - PortfolioConfiguration( - market="BINANCE", - trading_symbol="USDT" - ) - ) - self.app.container.market_service.override( - MarketServiceStub(self.app.container.market_credential_service()) - ) - algorithm = Algorithm() - self.app.add_algorithm(algorithm) - self.app.add_market_credential( - MarketCredential( - market="BINANCE", - api_key="api_key", - secret_key="secret_key", - ) + ] + portfolio_configurations = [ + PortfolioConfiguration( + market="BINANCE", + trading_symbol="EUR" ) + ] + external_balances = { + "EUR": 1000, + } def test_get_all(self): self.app.run(number_of_iterations=1) @@ -53,7 +30,7 @@ def test_get_all(self): "portfolio_id": 1, "target_symbol": "BTC", "amount": 1, - "trading_symbol": "USDT", + "trading_symbol": "EUR", "price": 10, "order_side": "BUY", "order_type": "LIMIT", diff --git a/tests/infrastructure/repositories/test_position_repository.py b/tests/infrastructure/repositories/test_position_repository.py index 2a859d73..1b926cbb 100644 --- a/tests/infrastructure/repositories/test_position_repository.py +++ b/tests/infrastructure/repositories/test_position_repository.py @@ -6,42 +6,22 @@ class Test(TestBase): - - def setUp(self) -> None: - self.resource_dir = os.path.abspath( - os.path.join( - os.path.join( - os.path.join( - os.path.join( - os.path.realpath(__file__), - os.pardir - ), - os.pardir - ), - os.pardir - ), - "resources" - ) - ) - self.app = create_app(config={RESOURCE_DIRECTORY: self.resource_dir}) - algorithm = Algorithm() - self.app.add_algorithm(algorithm) - self.app.add_market_credential( - MarketCredential( - market="BINANCE", - api_key="api_key", - secret_key="secret_key" - ) - ) - self.app.add_portfolio_configuration( - PortfolioConfiguration( - market="BINANCE", - trading_symbol="USDT" - ) - ) - self.app.container.market_service.override( - MarketServiceStub(self.app.container.market_credential_service()) - ) + market_credentials = [ + MarketCredential( + market="BINANCE", + api_key="api_key", + secret_key="secret_key" + ) + ] + portfolio_configurations = [ + PortfolioConfiguration( + market="BINANCE", + trading_symbol="EUR" + ) + ] + external_balances = { + "EUR": 1000, + } def test_get_all(self): self.app.run(number_of_iterations=1) @@ -54,7 +34,7 @@ def test_get_all(self): "portfolio_id": 1, "target_symbol": "BTC", "amount": 1, - "trading_symbol": "USDT", + "trading_symbol": "EUR", "price": 10, "order_side": "BUY", "order_type": "LIMIT", diff --git a/tests/resources/test_base.py b/tests/resources/test_base.py index aad38140..1b093001 100644 --- a/tests/resources/test_base.py +++ b/tests/resources/test_base.py @@ -37,8 +37,10 @@ class TestBase(TestCase): market_credentials = [] market_service = MarketServiceStub(None) initialize = True + resource_directory = os.path.dirname(__file__) def setUp(self) -> None: + self.remove_database() self.resource_directory = os.path.dirname(__file__) config = self.config config[RESOURCE_DIRECTORY] = self.resource_directory @@ -48,26 +50,25 @@ def setUp(self) -> None: self.market_service.orders = self.external_orders self.app.container.market_service.override(self.market_service) - if self.initialize: - - if len(self.portfolio_configurations) > 0: - for portfolio_configuration in self.portfolio_configurations: - self.app.add_portfolio_configuration( - portfolio_configuration - ) + if len(self.portfolio_configurations) > 0: + for portfolio_configuration in self.portfolio_configurations: + self.app.add_portfolio_configuration( + portfolio_configuration + ) - self.app.add_algorithm(self.algorithm) + self.app.add_algorithm(self.algorithm) - # Add all market credentials - if len(self.market_credentials) > 0: - for market_credential in self.market_credentials: - self.app.add_market_credential(market_credential) + # Add all market credentials + if len(self.market_credentials) > 0: + for market_credential in self.market_credentials: + self.app.add_market_credential(market_credential) - self.app.initialize() + if self.initialize: + self.app.initialize() def tearDown(self) -> None: database_path = os.path.join( - os.path.dirname(__file__), "databases/prod-database.sqlite3" + self.resource_directory, "databases/prod-database.sqlite3" ) if os.path.exists(database_path): @@ -80,41 +81,58 @@ def tearDown(self) -> None: except Exception as e: logger.error(e) + def remove_database(self): + + try: + database_path = os.path.join( + self.resource_directory, "databases/prod-database.sqlite3" + ) + + if os.path.exists(database_path): + os.remove(database_path) + except Exception as e: + logger.error(e) + class FlaskTestBase(FlaskTestCase): portfolio_configurations = [] market_credentials = [] iaf_app = None + config = {} + algorithm = Algorithm() + external_balances = {} + external_orders = [] + external_available_symbols = [] + market_service = MarketServiceStub(None) + initialize = True def create_app(self): self.resource_directory = os.path.dirname(__file__) self.iaf_app: App = create_app( {RESOURCE_DIRECTORY: self.resource_directory}, web=True ) + self.market_service.symbols = self.external_available_symbols + self.market_service.balances = self.external_balances + self.market_service.orders = self.external_orders + self.iaf_app.container.market_service.override(self.market_service) + + if self.initialize: + + if len(self.portfolio_configurations) > 0: + for portfolio_configuration in self.portfolio_configurations: + self.iaf_app.add_portfolio_configuration( + portfolio_configuration + ) - # Add all portfolio configurations - for portfolio_configuration in self.portfolio_configurations: - self.iaf_app.add_portfolio_configuration(portfolio_configuration) + self.iaf_app.add_algorithm(self.algorithm) - self.iaf_app.container.market_service.override(MarketServiceStub(None)) - self.iaf_app.add_algorithm(Algorithm()) + # Add all market credentials + if len(self.market_credentials) > 0: + for market_credential in self.market_credentials: + self.iaf_app.add_market_credential(market_credential) - # Add all market credentials - for market_credential in self.market_credentials: - self.iaf_app.add_market_credential(market_credential) - - algorithm = Algorithm() - algorithm.add_strategy(StrategyOne) - self.iaf_app.add_algorithm(algorithm) - self.iaf_app.add_market_credential( - MarketCredential( - market="BITVAVO", - api_key="api_key", - secret_key="secret_key", - ) - ) + self.iaf_app.initialize() - self.iaf_app.initialize() return self.iaf_app._flask_app def tearDown(self) -> None: diff --git a/tests/services/test_order_backtest_service.py b/tests/services/test_order_backtest_service.py index 5b9b7009..05a83bac 100644 --- a/tests/services/test_order_backtest_service.py +++ b/tests/services/test_order_backtest_service.py @@ -12,40 +12,26 @@ class TestOrderBacktestService(TestBase): + market_credentials = [ + MarketCredential( + market="binance", + api_key="api_key", + secret_key="secret_key", + ) + ] + portfolio_configurations = [ + PortfolioConfiguration( + market="binance", + trading_symbol="EUR" + ) + ] + external_balances = { + "EUR": 1000 + } + initialize = False def setUp(self) -> None: - self.resource_dir = os.path.abspath( - os.path.join( - os.path.join( - os.path.join( - os.path.realpath(__file__), - os.pardir - ), - os.pardir - ), - "resources" - ) - ) - self.app = create_app(config={RESOURCE_DIRECTORY: self.resource_dir}) - self.app.add_portfolio_configuration( - PortfolioConfiguration( - market="binance", - trading_symbol="USDT" - ) - ) - self.app.container.market_service.override( - MarketServiceStub(self.app.container.market_credential_service()) - ) - algorithm = Algorithm() - self.app.add_algorithm(algorithm) - self.app.add_market_credential( - MarketCredential( - market="binance", - api_key="api_key", - secret_key="secret_key", - ) - ) - + super(TestOrderBacktestService, self).setUp() # Override the MarketDataSourceService service with the backtest # market data source service equivalent. Additionally, convert the # market data sources to backtest market data sources @@ -97,7 +83,7 @@ def test_create_limit_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -112,7 +98,7 @@ def test_create_limit_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -124,7 +110,7 @@ def test_update_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -156,7 +142,7 @@ def test_create_limit_buy_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -171,7 +157,7 @@ def test_create_limit_buy_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -184,7 +170,7 @@ def test_create_limit_sell_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -199,7 +185,7 @@ def test_create_limit_sell_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -216,7 +202,7 @@ def test_create_limit_sell_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "SELL", "price": 0.24262, @@ -231,7 +217,7 @@ def test_create_limit_sell_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("SELL", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -252,7 +238,7 @@ def test_update_sell_order_with_successful_order_filled_and_closing_partial_buy_ buy_order_one = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 5, "order_side": "BUY", "price": 0.24262, @@ -272,7 +258,7 @@ def test_update_sell_order_with_successful_order_filled_and_closing_partial_buy_ buy_order_two = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 5, "order_side": "BUY", "price": 0.24262, @@ -292,7 +278,7 @@ def test_update_sell_order_with_successful_order_filled_and_closing_partial_buy_ sell_order_one = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2.5, "order_side": "SELL", "price": 0.24262, @@ -324,7 +310,7 @@ def test_update_sell_order_with_successful_order_filled_and_closing_partial_buy_ sell_order_two = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 5, "order_side": "SELL", "price": 0.24262, @@ -376,7 +362,7 @@ def test_trade_closing_winning_trade(self): buy_order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 1000, "order_side": "BUY", "price": 0.2, @@ -401,7 +387,7 @@ def test_trade_closing_winning_trade(self): sell_order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 1000, "order_side": "SELL", "price": 0.3, @@ -436,7 +422,7 @@ def test_trade_closing_losing_trade(self): buy_order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 1000, "order_side": "BUY", "price": 0.2, @@ -461,7 +447,7 @@ def test_trade_closing_losing_trade(self): sell_order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 1000, "order_side": "SELL", "price": 0.1, @@ -498,7 +484,7 @@ def test_has_executed_buy_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -513,7 +499,7 @@ def test_has_executed_buy_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -649,7 +635,7 @@ def test_has_executed_sell_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -664,7 +650,7 @@ def test_has_executed_sell_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -683,7 +669,7 @@ def test_has_executed_sell_order(self): sell_order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "SELL", "price": 0.24262, diff --git a/tests/services/test_order_service.py b/tests/services/test_order_service.py index d8c8ba00..162442ad 100644 --- a/tests/services/test_order_service.py +++ b/tests/services/test_order_service.py @@ -1,53 +1,34 @@ -import os from decimal import Decimal -from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY, \ - PortfolioConfiguration, Algorithm, MarketCredential -from tests.resources import TestBase, MarketServiceStub +from investing_algorithm_framework import PortfolioConfiguration, \ + MarketCredential +from tests.resources import TestBase class TestOrderService(TestBase): - - def setUp(self) -> None: - self.resource_dir = os.path.abspath( - os.path.join( - os.path.join( - os.path.join( - os.path.realpath(__file__), - os.pardir - ), - os.pardir - ), - "resources" - ) - ) - self.app = create_app(config={RESOURCE_DIRECTORY: self.resource_dir}) - self.app.add_portfolio_configuration( - PortfolioConfiguration( - market="binance", - trading_symbol="USDT" - ) - ) - self.app.container.market_service.override( - MarketServiceStub(self.app.container.market_credential_service()) + market_credentials = [ + MarketCredential( + market="binance", + api_key="api_key", + secret_key="secret_key", ) - algorithm = Algorithm() - self.app.add_algorithm(algorithm) - self.app.add_market_credential( - MarketCredential( - market="binance", - api_key="api_key", - secret_key="secret_key", - ) + ] + portfolio_configurations = [ + PortfolioConfiguration( + market="binance", + trading_symbol="EUR" ) - self.app.initialize() + ] + external_balances = { + "EUR": 1000 + } def test_create_limit_order(self): order_service = self.app.container.order_service() order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -62,7 +43,7 @@ def test_create_limit_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -72,7 +53,7 @@ def test_update_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -102,7 +83,7 @@ def test_create_limit_buy_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -117,7 +98,7 @@ def test_create_limit_buy_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -127,7 +108,7 @@ def test_create_limit_sell_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "BUY", "price": 0.24262, @@ -142,7 +123,7 @@ def test_create_limit_sell_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -159,7 +140,7 @@ def test_create_limit_sell_order(self): order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2004.5303357979318, "order_side": "SELL", "price": 0.24262, @@ -174,7 +155,7 @@ def test_create_limit_sell_order(self): self.assertEqual(2004.5303357979318, order.get_remaining()) self.assertEqual(0.24262, order.get_price()) self.assertEqual("ADA", order.get_target_symbol()) - self.assertEqual("USDT", order.get_trading_symbol()) + self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("SELL", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) self.assertEqual("CREATED", order.get_status()) @@ -193,7 +174,7 @@ def test_update_sell_order_with_successful_order_filled_and_closing_partial_buy_ buy_order_one = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 5, "order_side": "BUY", "price": 0.24262, @@ -213,7 +194,7 @@ def test_update_sell_order_with_successful_order_filled_and_closing_partial_buy_ buy_order_two = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 5, "order_side": "BUY", "price": 0.24262, @@ -233,7 +214,7 @@ def test_update_sell_order_with_successful_order_filled_and_closing_partial_buy_ sell_order_one = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 2.5, "order_side": "SELL", "price": 0.24262, @@ -265,7 +246,7 @@ def test_update_sell_order_with_successful_order_filled_and_closing_partial_buy_ sell_order_two = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 5, "order_side": "SELL", "price": 0.24262, @@ -315,7 +296,7 @@ def test_trade_closing_winning_trade(self): buy_order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 1000, "order_side": "BUY", "price": 0.2, @@ -340,7 +321,7 @@ def test_trade_closing_winning_trade(self): sell_order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 1000, "order_side": "SELL", "price": 0.3, @@ -373,7 +354,7 @@ def test_trade_closing_losing_trade(self): buy_order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 1000, "order_side": "BUY", "price": 0.2, @@ -398,7 +379,7 @@ def test_trade_closing_losing_trade(self): sell_order = order_service.create( { "target_symbol": "ADA", - "trading_symbol": "USDT", + "trading_symbol": "EUR", "amount": 1000, "order_side": "SELL", "price": 0.1, diff --git a/tests/services/test_portfolio_service.py b/tests/services/test_portfolio_service.py index 5a0aab71..75b03c49 100644 --- a/tests/services/test_portfolio_service.py +++ b/tests/services/test_portfolio_service.py @@ -1,12 +1,11 @@ -from investing_algorithm_framework import PortfolioConfiguration, Order, \ - MarketCredential, RESERVED_BALANCES -from investing_algorithm_framework.services import PortfolioService, \ - OrderService -from tests.resources import FlaskTestBase +from investing_algorithm_framework import PortfolioConfiguration, \ + MarketCredential +from investing_algorithm_framework.services import PortfolioService from tests.resources import MarketServiceStub +from tests.resources import TestBase -class TestPortfolioService(FlaskTestBase): +class TestPortfolioService(TestBase): portfolio_configurations = [ PortfolioConfiguration( market="binance", @@ -14,6 +13,9 @@ class TestPortfolioService(FlaskTestBase): initial_balance=1000, ) ] + external_balances = { + "EUR": 1000 + } market_credentials = [ MarketCredential( market="binance", @@ -22,55 +24,9 @@ class TestPortfolioService(FlaskTestBase): ) ] - def test_sync_portfolio_balances(self): - portfolio_configuration_service = self.iaf_app.container \ - .portfolio_configuration_service() - portfolio_configuration_service.clear() - portfolio_configuration_service.add( - PortfolioConfiguration( - market="binance", - trading_symbol="EUR", - ) - ) - portfolio_service: PortfolioService \ - = self.iaf_app.container.portfolio_service() - - market_service_stub = MarketServiceStub(None) - market_service_stub.balances = { - "EUR": 1000, - "BTC": 20, - } - portfolio_service.market_service = market_service_stub - portfolio = portfolio_service.find({"market": "binance"}) - portfolio_service.sync_portfolio_balances(portfolio) - self.assertEqual(1000, portfolio.unallocated) - position_service = self.iaf_app.container.position_service() - self.assertEqual(2, position_service.count()) - self.assertEqual(1000, position_service.find( - {"portfolio_id": portfolio.id, "symbol": "EUR"} - ).amount) - self.assertEqual(20, position_service.find( - {"portfolio_id": portfolio.id, "symbol": "BTC"} - ).amount) - - # Test when a new sync is ma de the unallocated amount is updated - market_service_stub.balances = { - "EUR": 2000, - "BTC": 30, - } - - portfolio_service.sync_portfolio_balances(portfolio) - portfolio = portfolio_service.find({"market": "binance"}) - self.assertEqual(2000, position_service.find( - {"portfolio_id": portfolio.id, "symbol": "EUR"} - ).amount) - self.assertEqual(30, position_service.find( - {"portfolio_id": portfolio.id, "symbol": "BTC"} - ).amount) - def test_create_portfolio_configuration(self): portfolio_service: PortfolioService \ - = self.iaf_app.container.portfolio_service() + = self.app.container.portfolio_service() market_service_stub = MarketServiceStub(None) market_service_stub.balances = { @@ -101,510 +57,3 @@ def test_create_portfolio_configuration(self): ) portfolio = portfolio_service.find({"market": "binance"}) self.assertEqual(1000, portfolio.unallocated) - - def test_sync_create_portfolio_configuration_with_reserved(self): - self.iaf_app.config[RESERVED_BALANCES] = { - "EUR": 100, - "BTC": 10, - } - configuration_service = self.iaf_app.container.configuration_service() - configuration_service.config[RESERVED_BALANCES] = { - "EUR": 100, - "BTC": 10, - } - portfolio_configuration_service = self.iaf_app.container \ - .portfolio_configuration_service() - portfolio_configuration_service.clear() - portfolio_configuration_service.add( - PortfolioConfiguration( - market="binance", - trading_symbol="EUR", - ) - ) - portfolio_service: PortfolioService \ - = self.iaf_app.container.portfolio_service() - - market_service_stub = MarketServiceStub(None) - market_service_stub.balances = { - "EUR": 1000, - "BTC": 20, - } - portfolio_service.market_service = market_service_stub - portfolio = portfolio_service.find({"market": "binance"}) - portfolio_service.create_portfolio_from_configuration( - PortfolioConfiguration( - market="binance", - trading_symbol="EUR", - initial_balance=1000 - ) - ) - self.assertEqual(900, portfolio.unallocated) - - def test_sync_portfolio_balances_with_reserved_balances(self): - self.iaf_app.config[RESERVED_BALANCES] = { - "EUR": 100, - "BTC": 10, - } - portfolio_configuration_service = self.iaf_app.container \ - .portfolio_configuration_service() - portfolio_configuration_service.clear() - portfolio_configuration_service.add( - PortfolioConfiguration( - market="binance", - trading_symbol="EUR", - ) - ) - portfolio_service: PortfolioService \ - = self.iaf_app.container.portfolio_service() - - market_service_stub = MarketServiceStub(None) - market_service_stub.balances = { - "EUR": 1000, - "BTC": 20, - } - portfolio_service.market_service = market_service_stub - portfolio = portfolio_service.find({"market": "binance"}) - portfolio_service.sync_portfolio_balances(portfolio) - - position_service = self.iaf_app.container.position_service() - self.assertEqual(2, position_service.count()) - self.assertEqual(900, position_service.find( - {"portfolio_id": portfolio.id, "symbol": "EUR"} - ).amount) - self.assertEqual(10, position_service.find( - {"portfolio_id": portfolio.id, "symbol": "BTC"} - ).amount) - - # Test when a new sync is ma de the unallocated amount is updated - market_service_stub.balances = { - "EUR": 2000, - "BTC": 30, - } - - portfolio_service.sync_portfolio_balances(portfolio) - portfolio = portfolio_service.find({"market": "binance"}) - self.assertEqual(1900, position_service.find( - {"portfolio_id": portfolio.id, "symbol": "EUR"} - ).amount) - self.assertEqual(20, position_service.find( - {"portfolio_id": portfolio.id, "symbol": "BTC"} - ).amount) - - def test_sync_portfolio_orders(self): - """ - In stateless mode, the portfolio service should first retrieve - the unallocated amount. Then it should load in all its orders and - positions. The orders should not update its unallocated amount, but - they should update the positions. - """ - portfolio_configuration_service = self.iaf_app.container \ - .portfolio_configuration_service() - portfolio_configuration_service.clear() - - # This should not be necessary, but it is here to make sure that - # the portfolio configuration is not being loaded from the database - # when it should not be. In stateless mode the trading bot - # can't keep track of the unallocated amount. Therefore, it reads - # the full amount from the exchange. - portfolio_configuration_service.add( - PortfolioConfiguration( - market="binance", - trading_symbol="EUR", - initial_balance=500, - ) - ) - portfolio_service: PortfolioService \ - = self.iaf_app.container.portfolio_service() - market_service_stub = MarketServiceStub(None) - market_service_stub.balances = {"EUR": 1000} - market_service_stub.orders = [ - Order.from_dict( - { - "id": "1323", - "side": "buy", - "symbol": "BTC/EUR", - "amount": 10, - "price": 10.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-08-08T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "2332", - "side": "sell", - "symbol": "BTC/EUR", - "amount": 10, - "price": 20.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "sell", - "created_at": "2023-08-10T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "14354", - "side": "buy", - "symbol": "DOT/EUR", - "amount": 10, - "price": 10.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-09-22T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "14354435", - "side": "sell", - "symbol": "DOT/EUR", - "amount": 10, - "price": 11.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-09-23T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "49394", - "side": "buy", - "symbol": "ETH/EUR", - "amount": 10, - "price": 10.0, - "status": "OPEN", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-08-08T14:40:56.626362Z", - "filled": 0, - "remaining": 0, - }, - ), - ] - market_service_stub.symbols = [ - "BTC/EUR", "DOT/EUR", "ADA/EUR", "ETH/EUR" - ] - portfolio_service.market_service = market_service_stub - portfolio = portfolio_service.find({"market": "binance"}) - portfolio_service.sync_portfolio_orders(portfolio) - self.assertEqual(500, portfolio.unallocated) - - - - - - def test_sync_portfolio_orders_without_position_and_unallocated_sync(self): - """ - Test that the portfolio service can sync existing orders - - The test should make sure that the portfolio service can sync - existing orders from the market service to the order service. - """ - portfolio_configuration_service = self.iaf_app.container\ - .portfolio_configuration_service() - portfolio_configuration_service.clear() - portfolio_configuration_service.add( - PortfolioConfiguration( - market="binance", - trading_symbol="EUR", - initial_balance=1000, - ) - ) - portfolio_service: PortfolioService \ - = self.iaf_app.container.portfolio_service() - market_service_stub = MarketServiceStub(None) - market_service_stub.orders = [ - Order.from_dict( - { - "id": "1323", - "side": "buy", - "symbol": "BTC/EUR", - "amount": 10, - "price": 10.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-08-08T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "2332", - "side": "sell", - "symbol": "BTC/EUR", - "amount": 10, - "price": 20.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "sell", - "created_at": "2023-08-10T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "14354", - "side": "buy", - "symbol": "DOT/EUR", - "amount": 10, - "price": 10.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-09-22T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "49394", - "side": "buy", - "symbol": "ETH/EUR", - "amount": 10, - "price": 10.0, - "status": "OPEN", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-08-08T14:40:56.626362Z", - "filled": 0, - "remaining": 0, - }, - ), - ] - market_service_stub.symbols = [ - "BTC/EUR", "DOT/EUR", "ADA/EUR", "ETH/EUR" - ] - portfolio_service.market_service = market_service_stub - portfolio = portfolio_service.find({"market": "binance"}) - portfolio_service.sync_portfolio_orders(portfolio) - - # Check that the portfolio has the correct amount of orders - order_service = self.iaf_app.container.order_service() - self.assertEqual(4, order_service.count()) - self.assertEqual( - 4, order_service.count({"portfolio": portfolio.id}) - ) - self.assertEqual( - 2, order_service.count({"target_symbol": "BTC"}) - ) - self.assertEqual( - 0, order_service.count({"portfolio_id": 2321}) - ) - self.assertEqual( - 1, order_service.count({"target_symbol": "DOT"}) - ) - self.assertEqual( - 1, order_service.count({"target_symbol": "ETH"}) - ) - - # Check that the portfolio has the correct amount of trades - trade_service = self.iaf_app.container.trade_service() - self.assertEqual(2, trade_service.count()) - self.assertEqual( - 1, trade_service.count( - {"portfolio_id": portfolio.id, "status": "CLOSED"} - ) - ) - self.assertEqual( - 1, trade_service.count( - {"portfolio_id": portfolio.id, "status": "OPEN"} - ) - ) - - # Check if all positions are made - position_service = self.iaf_app.container.position_service() - self.assertEqual(4, position_service.count()) - - # Check if btc position exists - btc_position = position_service.find( - {"portfolio_id": portfolio.id, "symbol": "BTC"} - ) - self.assertEqual(0, btc_position.amount) - - # Check if dot position exists - dot_position = position_service.find( - {"portfolio_id": portfolio.id, "symbol": "DOT"} - ) - self.assertEqual(10, dot_position.amount) - - # Check if eth position exists, but has amount set to 0 - eth_position = position_service.find( - {"portfolio_id": portfolio.id, "symbol": "ETH"} - ) - self.assertEqual(0, eth_position.amount) - - # Check if eur position exists - eur_position = position_service.find( - {"portfolio_id": portfolio.id, "symbol": "EUR"} - ) - self.assertEqual(1000, eur_position.amount) - - # Check that there is the correct amount of pending orders - order_service: OrderService = self.iaf_app.container.order_service() - self.assertEqual(1, order_service.count({"status": "OPEN"})) - pending_orders = self.iaf_app.algorithm.get_pending_orders() - self.assertEqual(1, len(pending_orders)) - self.assertEqual(10, pending_orders[0].amount) - - def test_sync_portfolio_orders_with_symbols_config(self): - """ - Test that the portfolio service can sync existing orders with - symbols configuration in the app config. - - The test should make sure that the portfolio service can sync - existing orders from the market service to the order service. It - should also only sync orders that are in the symbols configuration - in the app config. - """ - configuration_service = self.iaf_app.container.configuration_service() - configuration_service.config["SYMBOLS"] = ["BTC/EUR", "DOT/EUR"] - portfolio_service: PortfolioService \ - = self.iaf_app.container.portfolio_service() - market_service_stub = MarketServiceStub(None) - market_service_stub.orders = [ - Order.from_dict( - { - "id": "1323", - "side": "buy", - "symbol": "BTC/EUR", - "amount": 10, - "price": 10.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-08-08T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "2332", - "side": "sell", - "symbol": "BTC/EUR", - "amount": 10, - "price": 20.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "sell", - "created_at": "2023-08-10T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "14354", - "side": "buy", - "symbol": "DOT/EUR", - "amount": 10, - "price": 10.0, - "status": "CLOSED", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-09-22T14:40:56.626362Z", - "filled": 10, - "remaining": 0, - }, - ), - Order.from_dict( - { - "id": "49394", - "side": "buy", - "symbol": "ETH/EUR", - "amount": 10, - "price": 10.0, - "status": "OPEN", - "order_type": "limit", - "order_side": "buy", - "created_at": "2023-08-08T14:40:56.626362Z", - "filled": 0, - "remaining": 0, - }, - ), - ] - market_service_stub.symbols = [ - "BTC/EUR", "DOT/EUR", "ADA/EUR", "ETH/EUR" - ] - portfolio_service.market_service = market_service_stub - portfolio = portfolio_service.find({"market": "binance"}) - portfolio_service.sync_portfolio_orders(portfolio) - - # Check that the portfolio has the correct amount of orders - order_service = self.iaf_app.container.order_service() - self.assertEqual(3, order_service.count()) - self.assertEqual( - 3, order_service.count({"portfolio": portfolio.id}) - ) - self.assertEqual( - 2, order_service.count({"target_symbol": "BTC"}) - ) - self.assertEqual( - 0, order_service.count({"portfolio_id": 2321}) - ) - self.assertEqual( - 1, order_service.count({"target_symbol": "DOT"}) - ) - self.assertEqual( - 0, order_service.count({"target_symbol": "ETH"}) - ) - - # Check that the portfolio has the correct amount of trades - trade_service = self.iaf_app.container.trade_service() - self.assertEqual(2, trade_service.count()) - self.assertEqual( - 1, trade_service.count( - {"portfolio_id": portfolio.id, "status": "CLOSED"} - ) - ) - self.assertEqual( - 1, trade_service.count( - {"portfolio_id": portfolio.id, "status": "OPEN"} - ) - ) - - # Check if all positions are made - position_service = self.iaf_app.container.position_service() - self.assertEqual(3, position_service.count()) - - # Check if btc position exists - btc_position = position_service.find( - {"portfolio_id": portfolio.id, "symbol": "BTC"} - ) - self.assertEqual(0, btc_position.amount) - - # Check if dot position exists - dot_position = position_service.find( - {"portfolio_id": portfolio.id, "symbol": "DOT"} - ) - self.assertEqual(10, dot_position.amount) - - # Check if eur position exists - eur_position = position_service.find( - {"portfolio_id": portfolio.id, "symbol": "EUR"} - ) - self.assertEqual(1000, eur_position.amount) - - # Check that there is the correct amount of pending orders - order_service: OrderService = self.iaf_app.container.order_service() - self.assertEqual(0, order_service.count({"status": "OPEN"})) - pending_orders = self.iaf_app.algorithm.get_pending_orders() - self.assertEqual(0, len(pending_orders)) - diff --git a/tests/services/test_portfolio_sync_service.py b/tests/services/test_portfolio_sync_service.py index 59ef0a7c..6a166e53 100644 --- a/tests/services/test_portfolio_sync_service.py +++ b/tests/services/test_portfolio_sync_service.py @@ -3,7 +3,7 @@ from investing_algorithm_framework import PortfolioConfiguration, Algorithm, \ MarketCredential, OperationalException, RESERVED_BALANCES, APP_MODE, \ - Order, SYMBOLS + Order, SYMBOLS, AppMode class Test(TestBase): @@ -185,8 +185,9 @@ def test_sync_unallocated_with_stateless(self): ) self.market_service.balances = {"EUR": 1200} self.app.add_algorithm(Algorithm()) - self.app._stateless = True - self.app._web = False + configuration_service = self.app.container.configuration_service() + configuration_service.config[APP_MODE] = AppMode.STATELESS.value + self.app.config[APP_MODE] = AppMode.STATELESS.value self.app.initialize() configuration_service = self.app.container.configuration_service() @@ -267,7 +268,7 @@ def test_sync_positions_with_reserved(self): } self.app.add_algorithm(Algorithm()) configuration_service = self.app.container.configuration_service() - configuration_service.config[APP_MODE] = "STATELESS" + configuration_service.config[APP_MODE] = AppMode.DEFAULT.value configuration_service.config[RESERVED_BALANCES] = { "BTC": 0.5, "ETH": 1, @@ -279,12 +280,10 @@ def test_sync_positions_with_reserved(self): portfolio = self.app.container.portfolio_service() \ .find({"identifier": "test"}) self.assertEqual(500, portfolio.unallocated) - self.assertFalse( - self.app.container.position_service().exists({ - "symbol": "BTC", - "portfolio_id": portfolio.id, - }) + btc_position = self.app.container.position_service().find( + {"symbol": "BTC", "portfolio_id": portfolio.id} ) + self.assertEqual(0, btc_position.amount) eth_position = self.app.container.position_service().find( {"symbol": "ETH", "portfolio_id": portfolio.id} ) @@ -333,7 +332,8 @@ def test_sync_orders(self): 4. Check if the portfolio still has the 1000eu """ configuration_service = self.app.container.configuration_service() - configuration_service.config[SYMBOLS] = ["KSM"] + configuration_service.config[SYMBOLS] = ["KSM/EUR"] + self.market_service.symbols = ["KSM/EUR"] self.app.add_portfolio_configuration( PortfolioConfiguration( identifier="test", @@ -476,8 +476,8 @@ def test_sync_orders_with_track_from_attribute_set(self): 4. Check if the portfolio still has the 1000eu """ configuration_service = self.app.container.configuration_service() - configuration_service.config[SYMBOLS] = ["KSM"] - + configuration_service.config[SYMBOLS] = ["KSM/EUR"] + self.market_service.symbols = ["KSM/EUR"] self.app.add_portfolio_configuration( PortfolioConfiguration( identifier="test", @@ -586,7 +586,7 @@ def test_sync_orders_with_track_from_attribute_set(self): def test_sync_trades(self): configuration_service = self.app.container.configuration_service() configuration_service.config[SYMBOLS] = ["KSM/EUR"] - + self.market_service.symbols = ["KSM/EUR"] self.app.add_portfolio_configuration( PortfolioConfiguration( identifier="test", @@ -715,6 +715,7 @@ def test_sync_trades(self): def test_sync_trades_stateless(self): configuration_service = self.app.container.configuration_service() configuration_service.config[SYMBOLS] = ["KSM/EUR"] + self.market_service.symbols = ["KSM/EUR"] configuration_service.config[APP_MODE] = "STATELESS" self.app.add_portfolio_configuration( diff --git a/tests/test_create_app.py b/tests/test_create_app.py index 304a43a4..c75773e3 100644 --- a/tests/test_create_app.py +++ b/tests/test_create_app.py @@ -1,11 +1,12 @@ import os +from unittest import TestCase from investing_algorithm_framework import create_app, Config, \ PortfolioConfiguration, Algorithm, MarketCredential from investing_algorithm_framework.domain import RESOURCE_DIRECTORY from tests.resources import TestBase, MarketServiceStub -class TestCreateApp(TestBase): +class TestCreateApp(TestCase): def setUp(self) -> None: super(TestCreateApp, self).setUp()