Skip to content

Commit dd9773f

Browse files
committed
Setup initial event loop service
1 parent 4972f88 commit dd9773f

File tree

10 files changed

+524
-31
lines changed

10 files changed

+524
-31
lines changed

investing_algorithm_framework/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
TickerMarketDataSource, MarketService, \
1313
RESERVED_BALANCES, APP_MODE, AppMode, DATETIME_FORMAT, \
1414
BacktestDateRange, convert_polars_to_pandas, \
15-
DEFAULT_LOGGING_CONFIG, \
15+
DEFAULT_LOGGING_CONFIG, DataType, \
1616
BacktestResult, TradeStatus, MarketDataType, TradeRiskType, \
1717
APPLICATION_DIRECTORY, DataSource, OrderExecutor, PortfolioProvider, \
1818
SnapshotInterval, AWS_S3_STATE_BUCKET_NAME
@@ -91,5 +91,6 @@
9191
"AWSS3StorageStateHandler",
9292
"AWS_S3_STATE_BUCKET_NAME",
9393
"AWS_LAMBDA_LOGGING_CONFIG",
94-
'select_backtest_date_ranges'
94+
'select_backtest_date_ranges',
95+
'DataType'
9596
]

investing_algorithm_framework/app/eventloop.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import List
2+
from time import sleep
23
from datetime import datetime, timedelta, timezone
34
from investing_algorithm_framework.domain import TimeUnit, ENVIRONMENT, \
45
Environment, BACKTESTING_INDEX_DATETIME
@@ -54,12 +55,12 @@ def __init__(
5455
"""
5556
self.tasks = []
5657
self._algorithm = None
57-
self._strategies = []
58+
self.strategies = []
5859
self._order_service = order_service
5960
self._portfolio_service = portfolio_service
6061
self._configuration_service = configuration_service
6162
self._data_provider_service = data_provider_service
62-
self._data_configurations = []
63+
self.data_sources = set()
6364
self.next_run_times = {}
6465

6566
def _get_due_strategies(self):
@@ -89,7 +90,7 @@ def _get_due_strategies(self):
8990

9091
return due
9192

92-
def initialize(self, algorithm):
93+
def initialize(self):
9394
"""
9495
Initializes the event loop service by calculating the schedule for
9596
running strategies and tasks based on their defined intervals and time
@@ -104,21 +105,18 @@ def initialize(self, algorithm):
104105
Returns:
105106
None
106107
"""
107-
108-
self._algorithm = algorithm
109-
self._strategies = algorithm.strategies
110108
self.next_run_times = {
111-
strategy.identifier: {
112-
"next_run": datetime.now(timezone.utc)
113-
"data_configurations": strategy.data_configurations
109+
strategy.strategy_id: {
110+
"next_run": datetime.now(timezone.utc),
111+
"data_sources": strategy.data_sources
114112
}
115-
for strategy in self._strategies
113+
for strategy in self.strategies
116114
}
117115

118-
# Collect all data configurations
119-
for strategy in self._strategies:
120-
self._data_configurations.append(
121-
strategy.data_configurations
116+
# Collect all data sources
117+
for strategy in self.strategies:
118+
self.data_sources = self.data_sources.union(
119+
set(strategy.data_sources)
122120
)
123121

124122
def start(self, number_of_iterations=None):
@@ -129,7 +127,14 @@ def start(self, number_of_iterations=None):
129127
number_of_iterations: Optional; the number of iterations to run.
130128
If None, runs indefinitely.
131129
"""
132-
pass
130+
131+
# if number_of_iterations is None:
132+
try:
133+
self._run_iteration()
134+
number_of_iterations_since_last_orders_check += 1
135+
sleep(1)
136+
except KeyboardInterrupt:
137+
exit(0)
133138

134139
def _run_iteration_backtest(self):
135140
"""

investing_algorithm_framework/app/strategy.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ class TradingStrategy:
2323
worker_id (optional): str - the id of the worker
2424
strategy_id (optional): str - the id of the strategy
2525
decorated (optional): function - the decorated function
26-
market_data_sources (optional): list - the list of market data
27-
sources to use for the strategy. This will be passed to the
28-
run_strategy function.
26+
data_sources (List[DataSource] optional):the list of data
27+
sources to use for the strategy. The data sources will be used
28+
to indetify data providers that will be called to gather data
29+
and pass to the strategy before its run.
2930
"""
3031
time_unit: str = None
3132
interval: int = None
3233
worker_id: str = None
3334
strategy_id: str = None
3435
decorated = None
35-
market_data_sources = None
36+
data_sources = None
3637
traces = None
3738
context: Context = None
3839

@@ -41,7 +42,7 @@ def __init__(
4142
strategy_id=None,
4243
time_unit=None,
4344
interval=None,
44-
market_data_sources=None,
45+
data_sources=None,
4546
worker_id=None,
4647
decorated=None
4748
):
@@ -55,8 +56,8 @@ def __init__(
5556
if time_unit is not None:
5657
self.time_unit = TimeUnit.from_value(time_unit)
5758

58-
if market_data_sources is not None:
59-
self.market_data_sources = market_data_sources
59+
if data_sources is not None:
60+
self.data_sources = data_sources
6061

6162
if decorated is not None:
6263
self.decorated = decorated
@@ -146,7 +147,7 @@ def strategy_profile(self):
146147
strategy_id=self.worker_id,
147148
interval=self.interval,
148149
time_unit=self.time_unit,
149-
market_data_sources=self.market_data_sources
150+
data_sources=self.data_sources
150151
)
151152

152153
def _update_trades_and_orders(self, market_data):

investing_algorithm_framework/dependency_container.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
PortfolioConfigurationService, MarketDataSourceService, BacktestService, \
1414
ConfigurationService, PortfolioSnapshotService, PositionSnapshotService, \
1515
MarketCredentialService, TradeService, PortfolioSyncService, \
16-
OrderExecutorLookup, PortfolioProviderLookup
16+
OrderExecutorLookup, PortfolioProviderLookup, DataProviderService
1717

1818

1919
def setup_dependency_container(app, modules=None, packages=None):
@@ -162,6 +162,11 @@ class DependencyContainer(containers.DeclarativeContainer):
162162
strategy_orchestrator_service=strategy_orchestrator_service,
163163
portfolio_snapshot_service=portfolio_snapshot_service,
164164
)
165+
data_provider_service = providers.ThreadSafeSingleton(
166+
DataProviderService,
167+
configuration_service=configuration_service,
168+
market_credentials_service=market_credential_service
169+
)
165170
context = providers.Factory(
166171
Context,
167172
configuration_service=configuration_service,

investing_algorithm_framework/domain/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
from .models import OrderStatus, OrderSide, OrderType, TimeInterval, \
1919
TimeUnit, TimeFrame, TradingTimeFrame, TradingDataType, \
2020
PortfolioConfiguration, Portfolio, Position, Order, TradeStatus, \
21-
BacktestResult, PortfolioSnapshot, StrategyProfile, \
22-
BacktestPosition, Trade, MarketCredential, PositionSnapshot, \
23-
AppMode, BacktestDateRange, DataType, DataSource, \
21+
PortfolioSnapshot, StrategyProfile, \
22+
Trade, MarketCredential, PositionSnapshot, \
23+
AppMode, DataType, DataSource, \
2424
PortfolioSnapshot, StrategyProfile, \
2525
Trade, MarketCredential, PositionSnapshot, AppMode, \
2626
MarketDataType, TradeRiskType, TradeTakeProfit, TradeStopLoss, \
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from dataclasses import dataclass
22
from .data_type import DataType
33

4-
@dataclass()
4+
@dataclass(frozen=True)
55
class DataSource:
66
"""
77
Base class for data sources.
88
"""
9-
identifier: str = None
9+
data_provider_identifier: str = None
1010
data_type: DataType = None
1111
symbol: str = None
1212
window_size: int = None
1313
time_frame: str = None
14+
market: str = None

tests/app/test_eventloop.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from unittest import TestCase
2+
from datetime import datetime, timezone, timedelta
3+
4+
from investing_algorithm_framework.app.eventloop import EventLoopService
5+
from investing_algorithm_framework import TradingStrategy, DataSource, \
6+
DataType, MarketCredential, PortfolioConfiguration
7+
8+
from tests.resources import TestBase
9+
10+
11+
class StragyForTesting(TradingStrategy):
12+
data_sources = [
13+
DataSource(
14+
data_type=DataType.OHLCV,
15+
window_size=200,
16+
symbol="DOT/EUR",
17+
time_frame="2h",
18+
market="bitvavo"
19+
)
20+
]
21+
time_unit = "hour"
22+
interval = 2
23+
24+
def run_strategy(self, context, market_data):
25+
pass
26+
27+
28+
class StrategyForTestingTwo(TradingStrategy):
29+
data_sources = [
30+
DataSource(
31+
data_type=DataType.OHLCV,
32+
window_size=200,
33+
symbol="ETH/EUR",
34+
time_frame="2h",
35+
market="bitvavo"
36+
),
37+
DataSource(
38+
data_type=DataType.CUSTOM,
39+
data_provider_identifier="custom_feed_data"
40+
),
41+
]
42+
time_unit = "hour"
43+
interval = 4
44+
45+
def run_strategy(self, context, market_data):
46+
pass
47+
48+
49+
class StrategyForTestingThree(TradingStrategy):
50+
data_sources = [
51+
DataSource(
52+
data_type=DataType.OHLCV,
53+
window_size=200,
54+
symbol="BTC/EUR",
55+
time_frame="2h",
56+
market="bitvavo"
57+
),
58+
DataSource(
59+
data_type=DataType.CUSTOM,
60+
data_provider_identifier="twitter_data"
61+
),
62+
]
63+
time_unit = "day"
64+
interval = 1
65+
66+
def run_strategy(self, context, market_data):
67+
pass
68+
69+
70+
class TestEventloopService(TestBase):
71+
storage_repo_type = "pandas"
72+
market_credentials = [
73+
MarketCredential(
74+
market="binance",
75+
api_key="api_key",
76+
secret_key="secret_key",
77+
)
78+
]
79+
portfolio_configurations = [
80+
PortfolioConfiguration(
81+
market="binance",
82+
trading_symbol="EUR"
83+
)
84+
]
85+
external_balances = {
86+
"EUR": 1000
87+
}
88+
89+
def test_initialize(self):
90+
event_loop_service = EventLoopService(
91+
order_service=self.app.container.order_service(),
92+
portfolio_service=self.app.container.portfolio_service(),
93+
configuration_service=self.app.container.configuration_service(),
94+
data_provider_service=self.app.container.data_provider_service()
95+
)
96+
event_loop_service.strategies = [
97+
StragyForTesting(),
98+
StrategyForTestingTwo(),
99+
StrategyForTestingThree()
100+
]
101+
event_loop_service.initialize()
102+
self.assertEqual(len(event_loop_service.next_run_times), 3)
103+
self.assertEqual(len(event_loop_service.data_sources), 5)
104+
105+
# Each next run time should be set to the current datatime
106+
# because no runs have been executed yet
107+
for strategy in event_loop_service.strategies:
108+
self.assertIn(
109+
strategy.strategy_id,
110+
event_loop_service.next_run_times
111+
)
112+
self.assertAlmostEqual(
113+
event_loop_service\
114+
.next_run_times[strategy.strategy_id]["next_run"],
115+
datetime.now(tz=timezone.utc),
116+
delta=timedelta(seconds=10)
117+
)

tests/app/test_strategy.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from unittest import TestCase
2+
from investing_algorithm_framework import TradingStrategy, DataSource, \
3+
DataType, TimeUnit
4+
5+
6+
class StrategyForTesting(TradingStrategy):
7+
data_sources = [
8+
DataSource(
9+
data_type=DataType.OHLCV,
10+
window_size=200,
11+
symbol="BTC/EUR",
12+
time_frame="2h",
13+
market="bitvavo"
14+
),
15+
DataSource(
16+
data_type=DataType.CUSTOM,
17+
data_provider_identifier="twitter_data"
18+
),
19+
]
20+
time_unit = "hour"
21+
interval = 2
22+
23+
def run_strategy(self, context, market_data):
24+
pass
25+
26+
27+
class TestStrategy(TestCase):
28+
29+
def test_configurations(self):
30+
strategy = StrategyForTesting()
31+
self.assertEqual(len(strategy.data_sources), 2)
32+
self.assertTrue(TimeUnit.HOUR.equals(strategy.time_unit))
33+
self.assertEqual(strategy.interval, 2)

0 commit comments

Comments
 (0)