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.

Files changed (192) hide show
  1. investing_algorithm_framework/__init__.py +147 -44
  2. investing_algorithm_framework/app/__init__.py +23 -6
  3. investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
  4. investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
  5. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  6. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  7. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  8. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  9. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  10. investing_algorithm_framework/app/app.py +1322 -707
  11. investing_algorithm_framework/app/context.py +196 -88
  12. investing_algorithm_framework/app/eventloop.py +590 -0
  13. investing_algorithm_framework/app/reporting/__init__.py +16 -5
  14. investing_algorithm_framework/app/reporting/ascii.py +57 -202
  15. investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
  16. investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
  17. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  18. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  19. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
  20. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  21. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  22. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
  23. investing_algorithm_framework/app/reporting/generate.py +100 -114
  24. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
  25. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
  26. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
  27. investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
  28. investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
  29. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
  30. investing_algorithm_framework/app/strategy.py +315 -175
  31. investing_algorithm_framework/app/task.py +5 -3
  32. investing_algorithm_framework/cli/cli.py +30 -12
  33. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
  34. investing_algorithm_framework/cli/initialize_app.py +20 -1
  35. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
  36. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  37. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  38. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
  39. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
  40. investing_algorithm_framework/create_app.py +3 -5
  41. investing_algorithm_framework/dependency_container.py +25 -39
  42. investing_algorithm_framework/domain/__init__.py +45 -38
  43. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  44. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  45. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  46. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  47. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  48. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  49. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  50. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  51. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  52. investing_algorithm_framework/domain/config.py +27 -0
  53. investing_algorithm_framework/domain/constants.py +6 -34
  54. investing_algorithm_framework/domain/data_provider.py +200 -56
  55. investing_algorithm_framework/domain/exceptions.py +34 -1
  56. investing_algorithm_framework/domain/models/__init__.py +10 -19
  57. investing_algorithm_framework/domain/models/base_model.py +0 -6
  58. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  59. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  60. investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
  61. investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
  62. investing_algorithm_framework/domain/models/order/order.py +34 -13
  63. investing_algorithm_framework/domain/models/order/order_status.py +1 -1
  64. investing_algorithm_framework/domain/models/order/order_type.py +1 -1
  65. investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
  66. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
  67. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
  68. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  69. investing_algorithm_framework/domain/models/position/position.py +9 -0
  70. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  71. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  72. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  73. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  74. investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
  75. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  76. investing_algorithm_framework/domain/models/time_frame.py +7 -0
  77. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  78. investing_algorithm_framework/domain/models/time_unit.py +63 -1
  79. investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
  80. investing_algorithm_framework/domain/models/trade/trade.py +56 -32
  81. investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
  82. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
  83. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
  84. investing_algorithm_framework/domain/order_executor.py +19 -0
  85. investing_algorithm_framework/domain/portfolio_provider.py +20 -1
  86. investing_algorithm_framework/domain/services/__init__.py +0 -13
  87. investing_algorithm_framework/domain/strategy.py +1 -29
  88. investing_algorithm_framework/domain/utils/__init__.py +5 -1
  89. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  90. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  91. investing_algorithm_framework/domain/utils/polars.py +17 -14
  92. investing_algorithm_framework/download_data.py +40 -10
  93. investing_algorithm_framework/infrastructure/__init__.py +13 -25
  94. investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
  95. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
  96. investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
  97. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  98. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  99. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
  100. investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
  101. investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
  102. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
  103. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
  104. investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
  105. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  106. investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
  107. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
  108. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
  109. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
  110. investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
  111. investing_algorithm_framework/services/__init__.py +105 -8
  112. investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
  113. investing_algorithm_framework/services/configuration_service.py +14 -4
  114. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  115. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  116. investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
  117. investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
  118. investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
  119. investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
  120. investing_algorithm_framework/services/metrics/generate.py +358 -0
  121. investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
  122. investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
  123. investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
  124. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  125. investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
  126. investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
  127. investing_algorithm_framework/services/metrics/trades.py +500 -0
  128. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  129. investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
  130. investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
  131. investing_algorithm_framework/services/order_service/order_service.py +9 -71
  132. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
  133. investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
  134. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
  135. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
  136. investing_algorithm_framework/services/repository_service.py +5 -2
  137. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  138. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  139. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  140. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  141. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  142. investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
  143. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  144. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  145. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  146. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
  147. investing_algorithm_framework/app/reporting/evaluation.py +0 -243
  148. investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
  149. investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
  150. investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
  151. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
  152. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
  153. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  154. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
  155. investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
  156. investing_algorithm_framework/domain/models/data_source.py +0 -21
  157. investing_algorithm_framework/domain/models/date_range.py +0 -64
  158. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
  159. investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
  160. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  161. investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
  162. investing_algorithm_framework/domain/services/market_service.py +0 -153
  163. investing_algorithm_framework/domain/services/observable.py +0 -51
  164. investing_algorithm_framework/domain/services/observer.py +0 -19
  165. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
  166. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
  167. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
  168. investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
  169. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  170. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
  171. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  172. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  173. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
  174. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
  175. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
  176. investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
  177. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
  178. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
  179. investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
  180. /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
  181. /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
  182. /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
  183. /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
  184. /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
  185. /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
  186. /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
  187. /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
  188. /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
  189. /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
  190. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  191. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
  192. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
@@ -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 = []