investing-algorithm-framework 6.9.1__py3-none-any.whl → 7.19.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +147 -44
- investing_algorithm_framework/app/__init__.py +23 -6
- investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1322 -707
- investing_algorithm_framework/app/context.py +196 -88
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +16 -5
- investing_algorithm_framework/app/reporting/ascii.py +57 -202
- investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
- investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
- investing_algorithm_framework/app/reporting/generate.py +100 -114
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
- investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
- investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
- investing_algorithm_framework/app/strategy.py +315 -175
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/cli/cli.py +30 -12
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
- investing_algorithm_framework/cli/initialize_app.py +20 -1
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
- investing_algorithm_framework/create_app.py +3 -5
- investing_algorithm_framework/dependency_container.py +25 -39
- investing_algorithm_framework/domain/__init__.py +45 -38
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +27 -0
- investing_algorithm_framework/domain/constants.py +6 -34
- investing_algorithm_framework/domain/data_provider.py +200 -56
- investing_algorithm_framework/domain/exceptions.py +34 -1
- investing_algorithm_framework/domain/models/__init__.py +10 -19
- investing_algorithm_framework/domain/models/base_model.py +0 -6
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
- investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +34 -13
- investing_algorithm_framework/domain/models/order/order_status.py +1 -1
- investing_algorithm_framework/domain/models/order/order_type.py +1 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +9 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +7 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +63 -1
- investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
- investing_algorithm_framework/domain/models/trade/trade.py +56 -32
- investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
- investing_algorithm_framework/domain/order_executor.py +19 -0
- investing_algorithm_framework/domain/portfolio_provider.py +20 -1
- investing_algorithm_framework/domain/services/__init__.py +0 -13
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +5 -1
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +17 -14
- investing_algorithm_framework/download_data.py +40 -10
- investing_algorithm_framework/infrastructure/__init__.py +13 -25
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
- investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
- investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
- investing_algorithm_framework/services/__init__.py +105 -8
- investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
- investing_algorithm_framework/services/configuration_service.py +14 -4
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
- investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
- investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
- investing_algorithm_framework/services/order_service/order_service.py +9 -71
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
- investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
- investing_algorithm_framework/services/repository_service.py +5 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
- investing_algorithm_framework/app/reporting/evaluation.py +0 -243
- investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
- investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
- investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
- investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
- investing_algorithm_framework/domain/models/data_source.py +0 -21
- investing_algorithm_framework/domain/models/date_range.py +0 -64
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/services/observable.py +0 -51
- investing_algorithm_framework/domain/services/observer.py +0 -19
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
- investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
- investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
- investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
- /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py
DELETED
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
from datetime import datetime, timezone
|
|
3
|
-
|
|
4
|
-
from investing_algorithm_framework.domain import MarketService, \
|
|
5
|
-
MarketDataSource, OHLCVMarketDataSource, TickerMarketDataSource, \
|
|
6
|
-
OrderBookMarketDataSource, TimeFrame, OperationalException, \
|
|
7
|
-
MarketDataType
|
|
8
|
-
from investing_algorithm_framework.services.configuration_service import \
|
|
9
|
-
ConfigurationService
|
|
10
|
-
from investing_algorithm_framework.services.market_credential_service \
|
|
11
|
-
import MarketCredentialService
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class MarketDataSourceService:
|
|
15
|
-
"""
|
|
16
|
-
This class is responsible for managing the market data sources.
|
|
17
|
-
|
|
18
|
-
It is used by the algorithm to get market data from different sources.
|
|
19
|
-
The MarketDataSourceService will first check if there is a market data
|
|
20
|
-
source that matches the symbol, market and time frame provided by the user.
|
|
21
|
-
If there is, it will use that market data source to get the data. If there
|
|
22
|
-
is not, it will use the MarketService to get the data.
|
|
23
|
-
|
|
24
|
-
Attributes:
|
|
25
|
-
- market_service: MarketService
|
|
26
|
-
The market service that is used to get the data from the market
|
|
27
|
-
- market_credential_service: MarketCredentialService
|
|
28
|
-
The market credential service that is used to get the credentials
|
|
29
|
-
for the market
|
|
30
|
-
- configuration_service: ConfigurationService
|
|
31
|
-
The configuration service that is used to get the configuration
|
|
32
|
-
for the algorithm
|
|
33
|
-
- _market_data_sources: List[MarketDataSource]
|
|
34
|
-
The list of market data sources that are used by the algorithm
|
|
35
|
-
"""
|
|
36
|
-
_market_data_sources: List[MarketDataSource] = []
|
|
37
|
-
|
|
38
|
-
def __init__(
|
|
39
|
-
self,
|
|
40
|
-
market_service: MarketService,
|
|
41
|
-
market_credential_service: MarketCredentialService,
|
|
42
|
-
configuration_service: ConfigurationService,
|
|
43
|
-
market_data_sources: List[MarketDataSource] = None
|
|
44
|
-
):
|
|
45
|
-
|
|
46
|
-
if market_data_sources is not None:
|
|
47
|
-
self._market_data_sources: List[MarketDataSource] \
|
|
48
|
-
= market_data_sources
|
|
49
|
-
|
|
50
|
-
self._market_service: MarketService = market_service
|
|
51
|
-
self._market_credential_service: MarketCredentialService = \
|
|
52
|
-
market_credential_service
|
|
53
|
-
self._configuration_service = configuration_service
|
|
54
|
-
|
|
55
|
-
def initialize_market_data_sources(self):
|
|
56
|
-
|
|
57
|
-
for market_data_source in self._market_data_sources:
|
|
58
|
-
market_data_source.market_credential_service = \
|
|
59
|
-
self._market_credential_service
|
|
60
|
-
|
|
61
|
-
def get_ticker(self, symbol, market=None):
|
|
62
|
-
ticker_market_data_source = self.get_ticker_market_data_source(
|
|
63
|
-
symbol=symbol, market=market
|
|
64
|
-
)
|
|
65
|
-
config = self._configuration_service.get_config()
|
|
66
|
-
date = config.get("DATE_TIME", None)
|
|
67
|
-
|
|
68
|
-
if ticker_market_data_source is not None:
|
|
69
|
-
|
|
70
|
-
if date is not None:
|
|
71
|
-
return ticker_market_data_source.get_data(
|
|
72
|
-
end_date=date, config=config
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
return ticker_market_data_source.get_data(
|
|
76
|
-
end_date=datetime.now(tz=timezone.utc), config=config
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
return self._market_service.get_ticker(symbol, market)
|
|
80
|
-
|
|
81
|
-
def get_order_book(self, symbol, market=None):
|
|
82
|
-
order_book_market_data_source = self.get_order_book_market_data_source(
|
|
83
|
-
symbol=symbol, market=market
|
|
84
|
-
)
|
|
85
|
-
config = self._configuration_service.get_config()
|
|
86
|
-
|
|
87
|
-
if order_book_market_data_source is not None:
|
|
88
|
-
return order_book_market_data_source.get_data(
|
|
89
|
-
end_date=datetime.now(tz=timezone.utc), config=config
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
return self._market_service.get_order_book(symbol, market)
|
|
93
|
-
|
|
94
|
-
def get_ohlcv(
|
|
95
|
-
self,
|
|
96
|
-
symbol,
|
|
97
|
-
from_timestamp,
|
|
98
|
-
time_frame,
|
|
99
|
-
market=None,
|
|
100
|
-
to_timestamp=None
|
|
101
|
-
):
|
|
102
|
-
ohlcv_market_data_source = self.get_ohlcv_market_data_source(
|
|
103
|
-
symbol=symbol, market=market, time_frame=time_frame
|
|
104
|
-
)
|
|
105
|
-
config = self._configuration_service.get_config()
|
|
106
|
-
|
|
107
|
-
if ohlcv_market_data_source is not None:
|
|
108
|
-
return ohlcv_market_data_source.get_data(
|
|
109
|
-
end_date=datetime.now(tz=timezone.utc), config=config
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
return self._market_service.get_ohlcv(
|
|
113
|
-
symbol=symbol,
|
|
114
|
-
time_frame=time_frame,
|
|
115
|
-
from_timestamp=from_timestamp,
|
|
116
|
-
market=market,
|
|
117
|
-
to_timestamp=to_timestamp
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
def get_data_for_strategy(self, strategy):
|
|
121
|
-
"""
|
|
122
|
-
Function to get the data for the strategy. It loops over all
|
|
123
|
-
the market data sources in the strategy and returns the
|
|
124
|
-
data for each
|
|
125
|
-
|
|
126
|
-
Args:
|
|
127
|
-
strategy: The strategy for which the data is required
|
|
128
|
-
|
|
129
|
-
Returns:
|
|
130
|
-
The data for the strategy in dictionary format with
|
|
131
|
-
the keys being the identifier of the market data sources
|
|
132
|
-
"""
|
|
133
|
-
identifiers = []
|
|
134
|
-
if strategy.market_data_sources is not None:
|
|
135
|
-
for market_data_source in strategy.market_data_sources:
|
|
136
|
-
|
|
137
|
-
if isinstance(market_data_source, MarketDataSource):
|
|
138
|
-
identifiers.append(market_data_source.get_identifier())
|
|
139
|
-
elif isinstance(market_data_source, str):
|
|
140
|
-
identifiers.append(market_data_source)
|
|
141
|
-
else:
|
|
142
|
-
raise OperationalException(
|
|
143
|
-
"Market data source must be a string " +
|
|
144
|
-
"or MarketDataSource"
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
market_data = {"metadata": {
|
|
148
|
-
MarketDataType.OHLCV: {},
|
|
149
|
-
MarketDataType.TICKER: {},
|
|
150
|
-
MarketDataType.ORDER_BOOK: {},
|
|
151
|
-
MarketDataType.CUSTOM: {}
|
|
152
|
-
}}
|
|
153
|
-
|
|
154
|
-
for identifier in identifiers:
|
|
155
|
-
result_data = self.get_data(identifier)
|
|
156
|
-
|
|
157
|
-
if "symbol" in result_data and result_data["symbol"] is not None \
|
|
158
|
-
and "type" in result_data \
|
|
159
|
-
and result_data["type"] is not None:
|
|
160
|
-
type = result_data["type"]
|
|
161
|
-
symbol = result_data["symbol"]
|
|
162
|
-
time_frame = result_data["time_frame"]
|
|
163
|
-
|
|
164
|
-
if symbol not in market_data["metadata"][type]:
|
|
165
|
-
market_data["metadata"][type][symbol] = {}
|
|
166
|
-
|
|
167
|
-
if time_frame is None:
|
|
168
|
-
market_data["metadata"][type][symbol] = identifier
|
|
169
|
-
|
|
170
|
-
if time_frame is not None and \
|
|
171
|
-
time_frame not in \
|
|
172
|
-
market_data["metadata"][type][symbol]:
|
|
173
|
-
market_data["metadata"][type][symbol][time_frame] = identifier
|
|
174
|
-
|
|
175
|
-
market_data[identifier] = result_data["data"]
|
|
176
|
-
return market_data
|
|
177
|
-
|
|
178
|
-
def get_data(self, identifier):
|
|
179
|
-
for market_data_source in self._market_data_sources:
|
|
180
|
-
|
|
181
|
-
if market_data_source.get_identifier() == identifier:
|
|
182
|
-
config = self._configuration_service.get_config()
|
|
183
|
-
date = config.get("DATE_TIME", None)
|
|
184
|
-
|
|
185
|
-
if date is not None:
|
|
186
|
-
data = market_data_source.get_data(
|
|
187
|
-
end_date=date, config=config
|
|
188
|
-
)
|
|
189
|
-
elif "DATE_TIME" in config:
|
|
190
|
-
data = market_data_source.get_data(
|
|
191
|
-
end_date=config["DATE_TIME"], config=config,
|
|
192
|
-
)
|
|
193
|
-
else:
|
|
194
|
-
data = market_data_source.get_data(
|
|
195
|
-
end_date=datetime.now(tz=timezone.utc), config=config,
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
result = {
|
|
199
|
-
"data": data,
|
|
200
|
-
"type": None,
|
|
201
|
-
"symbol": None,
|
|
202
|
-
"time_frame": None
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
# Add metadata to the data
|
|
206
|
-
if isinstance(market_data_source, OHLCVMarketDataSource):
|
|
207
|
-
result["type"] = MarketDataType.OHLCV
|
|
208
|
-
time_frame = market_data_source.time_frame
|
|
209
|
-
|
|
210
|
-
if time_frame is not None:
|
|
211
|
-
time_frame = TimeFrame.from_value(time_frame)
|
|
212
|
-
result["time_frame"] = time_frame.value
|
|
213
|
-
else:
|
|
214
|
-
result["time_frame"] = TimeFrame.CURRENT.value
|
|
215
|
-
|
|
216
|
-
result["symbol"] = market_data_source.symbol
|
|
217
|
-
return result
|
|
218
|
-
|
|
219
|
-
if isinstance(market_data_source, TickerMarketDataSource):
|
|
220
|
-
result["type"] = MarketDataType.TICKER
|
|
221
|
-
result["time_frame"] = TimeFrame.CURRENT
|
|
222
|
-
result["symbol"] = market_data_source.symbol
|
|
223
|
-
return result
|
|
224
|
-
|
|
225
|
-
if isinstance(market_data_source, OrderBookMarketDataSource):
|
|
226
|
-
result["type"] = MarketDataType.ORDER_BOOK
|
|
227
|
-
result["time_frame"] = TimeFrame.CURRENT
|
|
228
|
-
result["symbol"] = market_data_source.symbol
|
|
229
|
-
return result
|
|
230
|
-
|
|
231
|
-
result["type"] = MarketDataType.CUSTOM
|
|
232
|
-
result["time_frame"] = TimeFrame.CURRENT
|
|
233
|
-
result["symbol"] = market_data_source.symbol
|
|
234
|
-
return result
|
|
235
|
-
|
|
236
|
-
raise OperationalException(
|
|
237
|
-
f"Market data source not found for {identifier}"
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
def get_ticker_market_data_source(self, symbol, market=None):
|
|
241
|
-
|
|
242
|
-
if self.market_data_sources is not None:
|
|
243
|
-
for market_data_source in self._market_data_sources:
|
|
244
|
-
if isinstance(market_data_source, TickerMarketDataSource):
|
|
245
|
-
if market is not None:
|
|
246
|
-
if market_data_source.market.upper() == market.upper()\
|
|
247
|
-
and market_data_source.symbol.upper() \
|
|
248
|
-
== symbol.upper():
|
|
249
|
-
return market_data_source
|
|
250
|
-
else:
|
|
251
|
-
if market_data_source.symbol.upper() \
|
|
252
|
-
== symbol.upper():
|
|
253
|
-
return market_data_source
|
|
254
|
-
|
|
255
|
-
return None
|
|
256
|
-
|
|
257
|
-
def get_ohlcv_market_data_source(
|
|
258
|
-
self, symbol, time_frame=None, market=None
|
|
259
|
-
):
|
|
260
|
-
"""
|
|
261
|
-
Function to get the OHLCV market data source for a symbol,
|
|
262
|
-
time frame and market.
|
|
263
|
-
|
|
264
|
-
Parameters:
|
|
265
|
-
symbol: str - The symbol of the asset
|
|
266
|
-
time_frame: TimeFrame - The time frame of the data
|
|
267
|
-
market: str - The market of the asset
|
|
268
|
-
|
|
269
|
-
Returns:
|
|
270
|
-
OHLCVMarketDataSource - The OHLCV market data source for the
|
|
271
|
-
symbol, time frame and market
|
|
272
|
-
"""
|
|
273
|
-
if time_frame is not None:
|
|
274
|
-
time_frame = TimeFrame.from_value(time_frame)
|
|
275
|
-
|
|
276
|
-
if self.market_data_sources is not None:
|
|
277
|
-
|
|
278
|
-
for market_data_source in self._market_data_sources:
|
|
279
|
-
|
|
280
|
-
if isinstance(market_data_source, OHLCVMarketDataSource):
|
|
281
|
-
if market is not None and time_frame is not None:
|
|
282
|
-
if market_data_source.market.upper() == market.upper()\
|
|
283
|
-
and market_data_source.symbol.upper() \
|
|
284
|
-
== symbol.upper() and \
|
|
285
|
-
time_frame.equals(
|
|
286
|
-
market_data_source.time_frame
|
|
287
|
-
):
|
|
288
|
-
return market_data_source
|
|
289
|
-
elif market is not None:
|
|
290
|
-
|
|
291
|
-
if market_data_source.market.upper() == market.upper()\
|
|
292
|
-
and market_data_source.symbol.upper() \
|
|
293
|
-
== symbol.upper():
|
|
294
|
-
return market_data_source
|
|
295
|
-
elif time_frame is not None:
|
|
296
|
-
if market_data_source.symbol.upper() \
|
|
297
|
-
== symbol.upper() and \
|
|
298
|
-
time_frame.equals(
|
|
299
|
-
market_data_source.time_frame
|
|
300
|
-
):
|
|
301
|
-
return market_data_source
|
|
302
|
-
else:
|
|
303
|
-
if market_data_source.symbol.upper() \
|
|
304
|
-
== symbol.upper():
|
|
305
|
-
return market_data_source
|
|
306
|
-
|
|
307
|
-
return None
|
|
308
|
-
|
|
309
|
-
def get_order_book_market_data_source(self, symbol, market=None):
|
|
310
|
-
|
|
311
|
-
if self.market_data_sources is not None:
|
|
312
|
-
for market_data_source in self._market_data_sources:
|
|
313
|
-
if isinstance(market_data_source, OrderBookMarketDataSource):
|
|
314
|
-
|
|
315
|
-
if market is not None:
|
|
316
|
-
if market_data_source.market.upper() == market.upper()\
|
|
317
|
-
and market_data_source.symbol.upper() \
|
|
318
|
-
== symbol.upper():
|
|
319
|
-
return market_data_source
|
|
320
|
-
else:
|
|
321
|
-
if market_data_source.symbol.upper() \
|
|
322
|
-
== symbol.upper():
|
|
323
|
-
return market_data_source
|
|
324
|
-
|
|
325
|
-
return None
|
|
326
|
-
|
|
327
|
-
@property
|
|
328
|
-
def market_data_sources(self):
|
|
329
|
-
return self._market_data_sources
|
|
330
|
-
|
|
331
|
-
@market_data_sources.setter
|
|
332
|
-
def market_data_sources(self, market_data_sources):
|
|
333
|
-
|
|
334
|
-
for market_data_source in market_data_sources:
|
|
335
|
-
self.add(market_data_source)
|
|
336
|
-
|
|
337
|
-
def clear_market_data_sources(self):
|
|
338
|
-
"""
|
|
339
|
-
Function to clear the market data sources
|
|
340
|
-
"""
|
|
341
|
-
self._market_data_sources = []
|
|
342
|
-
|
|
343
|
-
def add(self, market_data_source):
|
|
344
|
-
|
|
345
|
-
# Check if the market data source is an instance of MarketDataSource
|
|
346
|
-
if not isinstance(market_data_source, MarketDataSource):
|
|
347
|
-
return
|
|
348
|
-
|
|
349
|
-
# Check if there is already a market data source with the same
|
|
350
|
-
# identifier
|
|
351
|
-
for existing_market_data_source in self._market_data_sources:
|
|
352
|
-
if existing_market_data_source.get_identifier() == \
|
|
353
|
-
market_data_source.get_identifier():
|
|
354
|
-
return
|
|
355
|
-
|
|
356
|
-
market_data_source.market_credential_service = \
|
|
357
|
-
self._market_credential_service
|
|
358
|
-
self._market_data_sources.append(market_data_source)
|
|
359
|
-
|
|
360
|
-
def get_market_data_sources(self):
|
|
361
|
-
return self._market_data_sources
|
|
362
|
-
|
|
363
|
-
def has_ticker_market_data_source(self, symbol, market=None):
|
|
364
|
-
return self.get_ticker_market_data_source(symbol, market) is not None
|
|
365
|
-
|
|
366
|
-
def get_market_data_source_identifiers(self):
|
|
367
|
-
"""
|
|
368
|
-
Function to get all list of all market data source identifiers.
|
|
369
|
-
|
|
370
|
-
Returns:
|
|
371
|
-
List[str]: A list of identifiers for all market data sources.
|
|
372
|
-
"""
|
|
373
|
-
|
|
374
|
-
return [
|
|
375
|
-
market_data_source.get_identifier() for market_data_source
|
|
376
|
-
in self._market_data_sources
|
|
377
|
-
]
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from datetime import datetime, timezone
|
|
3
|
-
|
|
4
|
-
import schedule
|
|
5
|
-
|
|
6
|
-
from investing_algorithm_framework.domain import StoppableThread, TimeUnit, \
|
|
7
|
-
OperationalException
|
|
8
|
-
from investing_algorithm_framework.services.market_data_source_service \
|
|
9
|
-
import MarketDataSourceService
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger("investing_algorithm_framework")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class StrategyOrchestratorService:
|
|
15
|
-
"""
|
|
16
|
-
Service that orchestrates the execution of strategies and tasks
|
|
17
|
-
|
|
18
|
-
Attributes:
|
|
19
|
-
- history: dict
|
|
20
|
-
A dictionary that keeps track of the last time a strategy or task was
|
|
21
|
-
run
|
|
22
|
-
- _strategies: list
|
|
23
|
-
A list of strategies
|
|
24
|
-
- _tasks: list
|
|
25
|
-
A list of tasks
|
|
26
|
-
- threads: list
|
|
27
|
-
A list of threads that are currently running
|
|
28
|
-
- iterations: int
|
|
29
|
-
The number of iterations that have been run
|
|
30
|
-
- max_iterations: int
|
|
31
|
-
The maximum number of iterations that can be run
|
|
32
|
-
- market_data_source_service: MarketDataSourceService
|
|
33
|
-
The service that provides market data
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
def __init__(
|
|
37
|
-
self,
|
|
38
|
-
market_data_source_service: MarketDataSourceService,
|
|
39
|
-
configuration_service,
|
|
40
|
-
):
|
|
41
|
-
self._app_hooks = []
|
|
42
|
-
self.history = {}
|
|
43
|
-
self._strategies = []
|
|
44
|
-
self._tasks = []
|
|
45
|
-
self.threads = []
|
|
46
|
-
self.iterations = 0
|
|
47
|
-
self.max_iterations = -1
|
|
48
|
-
self.clear()
|
|
49
|
-
self.market_data_source_service: MarketDataSourceService \
|
|
50
|
-
= market_data_source_service
|
|
51
|
-
self.configuration_service = configuration_service
|
|
52
|
-
|
|
53
|
-
def initialize(self, algorithm) -> None:
|
|
54
|
-
"""
|
|
55
|
-
Initialize the strategy orchestrator service with an algorithm.
|
|
56
|
-
With the provided algorithm, the service will set the
|
|
57
|
-
strategies, tasks, and on_strategy_run_hooks.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
algorithm (Algorithm): The algorithm to initialize the service with
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
None
|
|
64
|
-
"""
|
|
65
|
-
self._app_hooks = algorithm.on_strategy_run_hooks or []
|
|
66
|
-
self._add_strategies(algorithm.strategies)
|
|
67
|
-
self._add_tasks(algorithm.tasks)
|
|
68
|
-
|
|
69
|
-
def cleanup_threads(self):
|
|
70
|
-
|
|
71
|
-
for stoppable in self.threads:
|
|
72
|
-
if not stoppable.is_alive():
|
|
73
|
-
# get results from thread
|
|
74
|
-
stoppable.done = True
|
|
75
|
-
self.threads = [t for t in self.threads if not t.done]
|
|
76
|
-
|
|
77
|
-
def run_strategy(self, strategy, context, sync=False):
|
|
78
|
-
self.cleanup_threads()
|
|
79
|
-
matching_thread = next(
|
|
80
|
-
(t for t in self.threads if t.name == strategy.worker_id),
|
|
81
|
-
None
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
# Don't run a strategy that is already running
|
|
85
|
-
if matching_thread:
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
market_data = \
|
|
89
|
-
self.market_data_source_service.get_data_for_strategy(strategy)
|
|
90
|
-
|
|
91
|
-
logger.info(f"Running strategy {strategy.worker_id}")
|
|
92
|
-
|
|
93
|
-
# Run the app hooks
|
|
94
|
-
for app_hook in self._app_hooks:
|
|
95
|
-
app_hook.on_run(context=context)
|
|
96
|
-
|
|
97
|
-
if sync:
|
|
98
|
-
strategy.run_strategy(
|
|
99
|
-
market_data=market_data,
|
|
100
|
-
context=context,
|
|
101
|
-
)
|
|
102
|
-
else:
|
|
103
|
-
self.iterations += 1
|
|
104
|
-
thread = StoppableThread(
|
|
105
|
-
target=strategy.run_strategy,
|
|
106
|
-
kwargs={
|
|
107
|
-
"market_data": market_data,
|
|
108
|
-
"context": context,
|
|
109
|
-
}
|
|
110
|
-
)
|
|
111
|
-
thread.name = strategy.worker_id
|
|
112
|
-
thread.start()
|
|
113
|
-
self.threads.append(thread)
|
|
114
|
-
|
|
115
|
-
self.history[strategy.worker_id] = \
|
|
116
|
-
{"last_run": datetime.now(tz=timezone.utc)}
|
|
117
|
-
|
|
118
|
-
def run_backtest_strategy(self, strategy, context, config):
|
|
119
|
-
data = self.market_data_source_service.get_data_for_strategy(strategy)
|
|
120
|
-
strategy.run_strategy(
|
|
121
|
-
market_data=data,
|
|
122
|
-
context=context,
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
def run_task(self, task, context, sync=False):
|
|
126
|
-
self.cleanup_threads()
|
|
127
|
-
|
|
128
|
-
matching_thread = next(
|
|
129
|
-
(t for t in self.threads if t.name == task.worker_id),
|
|
130
|
-
None
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
# Don't run a strategy that is already running
|
|
134
|
-
if matching_thread:
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
logger.info(f"Running task {task.worker_id}")
|
|
138
|
-
|
|
139
|
-
if sync:
|
|
140
|
-
task.run(context=context)
|
|
141
|
-
else:
|
|
142
|
-
self.iterations += 1
|
|
143
|
-
thread = StoppableThread(
|
|
144
|
-
target=task.run,
|
|
145
|
-
kwargs={"context": context}
|
|
146
|
-
)
|
|
147
|
-
thread.name = task.worker_id
|
|
148
|
-
thread.start()
|
|
149
|
-
self.threads.append(thread)
|
|
150
|
-
|
|
151
|
-
self.history[task.worker_id] = \
|
|
152
|
-
{"last_run": datetime.now(tz=timezone.utc)}
|
|
153
|
-
|
|
154
|
-
def start(self, context, number_of_iterations=None) -> None:
|
|
155
|
-
"""
|
|
156
|
-
Function to start and schedule the strategies and tasks. This
|
|
157
|
-
function will not start the strategies, but will calculate the
|
|
158
|
-
schedule and queue the jobs.
|
|
159
|
-
|
|
160
|
-
Args:
|
|
161
|
-
context (Context): The application context
|
|
162
|
-
number_of_iterations (int): The number of iterations that the
|
|
163
|
-
strategies and tasks will run. If None, the
|
|
164
|
-
strategies and tasks
|
|
165
|
-
|
|
166
|
-
Returns:
|
|
167
|
-
None
|
|
168
|
-
"""
|
|
169
|
-
self.max_iterations = number_of_iterations
|
|
170
|
-
|
|
171
|
-
for strategy in self.strategies:
|
|
172
|
-
if TimeUnit.SECOND.equals(strategy.time_unit):
|
|
173
|
-
schedule.every(strategy.interval)\
|
|
174
|
-
.seconds.do(self.run_strategy, strategy, context)
|
|
175
|
-
elif TimeUnit.MINUTE.equals(strategy.time_unit):
|
|
176
|
-
schedule.every(strategy.interval)\
|
|
177
|
-
.minutes.do(self.run_strategy, strategy, context)
|
|
178
|
-
elif TimeUnit.HOUR.equals(strategy.time_unit):
|
|
179
|
-
schedule.every(strategy.interval)\
|
|
180
|
-
.hours.do(self.run_strategy, strategy, context)
|
|
181
|
-
|
|
182
|
-
for task in self.tasks:
|
|
183
|
-
if TimeUnit.SECOND.equals(task.time_unit):
|
|
184
|
-
schedule.every(task.interval)\
|
|
185
|
-
.seconds.do(self.run_task, task, context)
|
|
186
|
-
elif TimeUnit.MINUTE.equals(task.time_unit):
|
|
187
|
-
schedule.every(task.interval)\
|
|
188
|
-
.minutes.do(self.run_task, task, context)
|
|
189
|
-
elif TimeUnit.HOUR.equals(task.time_unit):
|
|
190
|
-
schedule.every(task.interval)\
|
|
191
|
-
.hours.do(self.run_task, task, context)
|
|
192
|
-
|
|
193
|
-
def stop(self):
|
|
194
|
-
for thread in self.threads:
|
|
195
|
-
thread.stop()
|
|
196
|
-
|
|
197
|
-
schedule.clear()
|
|
198
|
-
|
|
199
|
-
def clear(self):
|
|
200
|
-
self.threads = []
|
|
201
|
-
schedule.clear()
|
|
202
|
-
|
|
203
|
-
def get_strategies(self, identifiers=None):
|
|
204
|
-
if identifiers is None:
|
|
205
|
-
return self.strategies
|
|
206
|
-
|
|
207
|
-
strategies = []
|
|
208
|
-
for strategy in self.strategies:
|
|
209
|
-
if strategy.worker_id in identifiers:
|
|
210
|
-
strategies.append(strategy)
|
|
211
|
-
|
|
212
|
-
return strategies
|
|
213
|
-
|
|
214
|
-
def get_tasks(self):
|
|
215
|
-
return self._tasks
|
|
216
|
-
|
|
217
|
-
def get_jobs(self):
|
|
218
|
-
return schedule.jobs
|
|
219
|
-
|
|
220
|
-
def run_pending_jobs(self):
|
|
221
|
-
if self.max_iterations is not None and \
|
|
222
|
-
self.max_iterations != -1 and \
|
|
223
|
-
self.iterations >= self.max_iterations:
|
|
224
|
-
self.clear()
|
|
225
|
-
else:
|
|
226
|
-
schedule.run_pending()
|
|
227
|
-
|
|
228
|
-
def _add_strategies(self, strategies):
|
|
229
|
-
has_duplicates = False
|
|
230
|
-
|
|
231
|
-
for i in range(len(strategies)):
|
|
232
|
-
for j in range(i + 1, len(strategies)):
|
|
233
|
-
if strategies[i].worker_id == strategies[j].worker_id:
|
|
234
|
-
has_duplicates = True
|
|
235
|
-
break
|
|
236
|
-
|
|
237
|
-
if has_duplicates:
|
|
238
|
-
raise OperationalException(
|
|
239
|
-
"There are duplicate strategies with the same name"
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
self._strategies = strategies
|
|
243
|
-
|
|
244
|
-
def _add_tasks(self, tasks):
|
|
245
|
-
has_duplicates = False
|
|
246
|
-
|
|
247
|
-
for i in range(len(tasks)):
|
|
248
|
-
for j in range(i + 1, len(tasks)):
|
|
249
|
-
if tasks[i].worker_id == tasks[j].worker_id:
|
|
250
|
-
has_duplicates = True
|
|
251
|
-
break
|
|
252
|
-
|
|
253
|
-
if has_duplicates:
|
|
254
|
-
raise OperationalException(
|
|
255
|
-
"There are duplicate tasks with the same name"
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
self._tasks = tasks
|
|
259
|
-
|
|
260
|
-
@property
|
|
261
|
-
def strategies(self):
|
|
262
|
-
return self._strategies
|
|
263
|
-
|
|
264
|
-
@property
|
|
265
|
-
def app_hooks(self):
|
|
266
|
-
return self._app_hooks
|
|
267
|
-
|
|
268
|
-
@property
|
|
269
|
-
def tasks(self):
|
|
270
|
-
return self._tasks
|
|
271
|
-
|
|
272
|
-
@property
|
|
273
|
-
def running(self):
|
|
274
|
-
if len(self.strategies) == 0 and len(self.tasks) == 0:
|
|
275
|
-
return False
|
|
276
|
-
|
|
277
|
-
if self.max_iterations == -1:
|
|
278
|
-
return True
|
|
279
|
-
|
|
280
|
-
return self.max_iterations is None \
|
|
281
|
-
or self.iterations < self.max_iterations
|
|
282
|
-
|
|
283
|
-
def has_run(self, worker_id):
|
|
284
|
-
return worker_id in self.history
|
|
285
|
-
|
|
286
|
-
def reset(self):
|
|
287
|
-
"""
|
|
288
|
-
Reset the strategy orchestrator service
|
|
289
|
-
"""
|
|
290
|
-
self.clear()
|
|
291
|
-
self.history = {}
|
|
292
|
-
self.iterations = 0
|
|
293
|
-
self.max_iterations = -1
|
|
294
|
-
self._strategies = []
|
|
295
|
-
self._tasks = []
|
|
296
|
-
self._app_hooks = []
|