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,11 +1,12 @@
1
- from typing import List, Dict, Any
1
+ from datetime import datetime
2
+ from typing import List, Dict, Any, Union
3
+
2
4
  import pandas as pd
3
- from datetime import datetime, timezone
4
5
 
5
- from investing_algorithm_framework.domain import OperationalException, Position
6
- from investing_algorithm_framework.domain import \
7
- TimeUnit, StrategyProfile, Trade, ENVIRONMENT, Environment, \
8
- BACKTESTING_INDEX_DATETIME
6
+ from investing_algorithm_framework.domain import OperationalException, \
7
+ Position, PositionSize, TimeUnit, StrategyProfile, Trade, \
8
+ DataSource, OrderSide, StopLossRule, TakeProfitRule, Order, \
9
+ INDEX_DATETIME
9
10
  from .context import Context
10
11
 
11
12
 
@@ -23,40 +24,62 @@ class TradingStrategy:
23
24
  worker_id (optional): str - the id of the worker
24
25
  strategy_id (optional): str - the id of the strategy
25
26
  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.
27
+ data_sources (List[DataSource] optional): the list of data
28
+ sources to use for the strategy. The data sources will be used
29
+ to indentify data providers that will be called to gather data
30
+ and pass to the strategy before its run.
31
+ metadata (optional): Dict[str, Any] - a dictionary
32
+ containing metadata about the strategy. This can be used to
33
+ store additional information about the strategy, such as its
34
+ author, version, description, params etc.
29
35
  """
30
- time_unit: str = None
36
+ time_unit: TimeUnit = None
31
37
  interval: int = None
32
38
  worker_id: str = None
33
39
  strategy_id: str = None
34
40
  decorated = None
35
- market_data_sources = None
41
+ data_sources: List[DataSource] = []
36
42
  traces = None
37
43
  context: Context = None
44
+ metadata: Dict[str, Any] = None
45
+ position_sizes: List[PositionSize] = []
46
+ stop_loss_rules: List[StopLossRule] = []
47
+ take_profit_rules: List[TakeProfitRule] = []
48
+ symbols: List[str] = []
49
+ trading_symbol: str = None
38
50
 
39
51
  def __init__(
40
52
  self,
41
53
  strategy_id=None,
42
54
  time_unit=None,
43
55
  interval=None,
44
- market_data_sources=None,
56
+ data_sources=None,
57
+ metadata=None,
58
+ position_sizes=None,
59
+ symbols=None,
60
+ trading_symbol=None,
45
61
  worker_id=None,
46
62
  decorated=None
47
63
  ):
48
-
49
64
  if time_unit is not None:
50
65
  self.time_unit = TimeUnit.from_value(time_unit)
66
+ else:
67
+ # Check if time_unit is None
68
+ if self.time_unit is None:
69
+ raise OperationalException(
70
+ f"Time unit attribute not set for "
71
+ f"strategy instance {self.strategy_id}"
72
+ )
73
+
74
+ self.time_unit = TimeUnit.from_value(self.time_unit)
51
75
 
52
76
  if interval is not None:
53
77
  self.interval = interval
54
78
 
55
- if time_unit is not None:
56
- self.time_unit = TimeUnit.from_value(time_unit)
79
+ if data_sources is not None:
80
+ self.data_sources = data_sources
57
81
 
58
- if market_data_sources is not None:
59
- self.market_data_sources = market_data_sources
82
+ self.metadata = metadata
60
83
 
61
84
  if decorated is not None:
62
85
  self.decorated = decorated
@@ -73,12 +96,14 @@ class TradingStrategy:
73
96
  else:
74
97
  self.strategy_id = self.worker_id
75
98
 
76
- # Check if time_unit is None
77
- if self.time_unit is None:
78
- raise OperationalException(
79
- f"Time unit attribute not set for "
80
- f"strategy instance {self.strategy_id}"
81
- )
99
+ if position_sizes is not None:
100
+ self.position_sizes = position_sizes
101
+
102
+ if symbols is not None:
103
+ self.symbols = symbols
104
+
105
+ if trading_symbol is not None:
106
+ self.trading_symbol = trading_symbol
82
107
 
83
108
  # Check if interval is None
84
109
  if self.interval is None:
@@ -89,12 +114,97 @@ class TradingStrategy:
89
114
  # context initialization
90
115
  self._context = None
91
116
  self._last_run = None
117
+ self.stop_loss_rules_lookup = {}
118
+ self.take_profit_rules_lookup = {}
119
+ self.position_sizes_lookup = {}
92
120
 
93
- def run_strategy(self, context: Context, market_data: Dict[str, Any]):
121
+ def generate_buy_signals(
122
+ self, data: Dict[str, Any]
123
+ ) -> Dict[str, pd.Series]:
94
124
  """
95
- Main function for running your strategy. This function will be called
125
+ Function that needs to be implemented by the user.
126
+ This function should return a pandas Series containing the buy signals.
127
+
128
+ Args:
129
+ data (Dict[str, Any]): All the data that matched the
130
+ data sources of the strategy.
131
+
132
+ Returns:
133
+ Dict[str, Series]: A dictionary where the keys are the
134
+ symbols and the values are pandas Series containing
135
+ the buy signals. The series must be a pandas Series with
136
+ a boolean value for each row in the data source, e.g.
137
+ pd.Series([True, False, False, True, ...], index=data.index)
138
+ Also the return dictionary must look like:
139
+ {
140
+ "BTC": pd.Series([...]),
141
+ "ETH": pd.Series([...]),
142
+ ...
143
+ }
144
+ where the symbols are exactly the same as defined in the
145
+ symbols attribute of the strategy.
146
+ """
147
+ raise NotImplementedError(
148
+ "generate_buy_signals method not implemented"
149
+ )
150
+
151
+ def generate_sell_signals(
152
+ self, data: Dict[str, Any]
153
+ ) -> Dict[str, pd.Series]:
154
+ """
155
+ Function that needs to be implemented by the user.
156
+ This function should return a pandas Series containing
157
+ the sell signals.
158
+
159
+ Args:
160
+ data (Dict[str, Any]): All the data that is defined in the
161
+ data sources of the strategy. E.g. if there is a data source
162
+ defined as DataSource(identifier="bitvavo_btc_eur_1h",
163
+ symbol="BTC/EUR", time_frame="1h", data_type=DataType.OHLCV,
164
+ window_size=100, market="BITVAVO"), the data dictionary
165
+ will contain a key "bitvavo_btc_eur_1h"
166
+ with the corresponding data as a polars DataFrame.
167
+
168
+ Returns:
169
+ Dict[str, Series]: A dictionary where the keys are the
170
+ symbols and the values are pandas Series containing
171
+ the sell signals. The series must be a pandas Series with
172
+ a boolean value for each row in the data source, e.g.
173
+ pd.Series([True, False, False, True, ...], index=data.index)
174
+ Also the return dictionary must look like:
175
+ {
176
+ "BTC": pd.Series([...]),
177
+ "ETH": pd.Series([...]),
178
+ ...
179
+ }
180
+ where the symbols are exactly the same as defined in the
181
+ symbols attribute of the strategy.
182
+ """
183
+ raise NotImplementedError(
184
+ "generate_sell_signals method not implemented"
185
+ )
186
+
187
+ def run_strategy(self, context: Context, data: Dict[str, Any]):
188
+ """
189
+ Main function for running the strategy. This function will be called
96
190
  by the framework when the trigger of your strategy is met.
97
191
 
192
+ The flow of this function is as follows:
193
+ 1. Loop through all the symbols defined in the strategy.
194
+ 2. For each symbol, check if there are any open orders.
195
+ A. If there are open orders, skip to the next symbol.
196
+ 3. If there is no open position, generate buy signals
197
+ A. Generate buy signals
198
+ B. If there is a buy signal, retrieve the position size
199
+ defined for the symbol.
200
+ C. If there is a take profit or stop loss rule defined
201
+ for the symbol, register them for the trade that
202
+ has been created as part of the order execution.
203
+ 4. If there is an open position, generate sell signals
204
+ A. Generate sell signals
205
+ B. If there is a sell signal, create a limit order to
206
+ sell the position.
207
+
98
208
  During execution of this function, the context and market data
99
209
  will be passed to the function. The context is an instance of
100
210
  the Context class, this class has various methods to do operations
@@ -103,40 +213,129 @@ class TradingStrategy:
103
213
  The market data is a dictionary containing all the data retrieved
104
214
  from the specified data sources.
105
215
 
216
+ When buy or sell signals are generated, the strategy will create
217
+ limit orders to buy or sell the assets based on the generated signals.
218
+ For each symbol a corresponding position size must be defined. If
219
+ no position size is defined, an OperationalException will be raised.
220
+
221
+ Before creating new orders, the strategy will check if there are any
222
+ stop losses or take profits for symbol registered. It will
223
+ use the function get_stop_losses and get_take_profits, these functions
224
+ can be overridden by the user to provide custom stop losses and
225
+ take profits logic. The default functions will return the stop losses
226
+ and take profits that are registered for the symbol if any.
227
+
106
228
  Args:
107
229
  context (Context): The context of the strategy. This is an instance
108
230
  of the Context class, this class has various methods to do
109
231
  operations with your portfolio, orders, trades, positions and
110
232
  other components.
111
- market_data (Dict[str, Any]): The data for the strategy.
233
+ data (Dict[str, Any]): The data for the strategy.
112
234
  This is a dictionary containing all the data retrieved from the
113
- specified data sources.
235
+ specified data sources. The keys are either the
236
+ identifiers of the data sources or a generated key, usually
237
+ <target_symbol>_<trading_symbol>_<time_frame> e.g. BTC-EUR_1h.
114
238
 
115
239
  Returns:
116
240
  None
117
241
  """
118
242
  self.context = context
119
- config = self.context.get_config()
120
-
121
- if config[ENVIRONMENT] == Environment.BACKTEST.value:
122
- self._update_trades_and_orders_for_backtest(market_data)
123
- else:
124
- self._update_trades_and_orders(market_data)
125
-
126
- self._check_stop_losses()
127
- self._check_take_profits()
128
-
129
- # Run user defined strategy
130
- self.apply_strategy(context=context, market_data=market_data)
131
-
132
- if config[ENVIRONMENT] == Environment.BACKTEST.value:
133
- self._last_run = config[BACKTESTING_INDEX_DATETIME]
134
- else:
135
- self._last_run = datetime.now(tz=timezone.utc)
136
-
137
- def apply_strategy(self, context, market_data):
243
+ index_datetime = context.config[INDEX_DATETIME]
244
+ buy_signals = self.generate_buy_signals(data)
245
+ sell_signals = self.generate_sell_signals(data)
246
+
247
+ for symbol in self.symbols:
248
+
249
+ if self.has_open_orders(symbol):
250
+ continue
251
+
252
+ if not self.has_position(symbol):
253
+
254
+ if symbol not in buy_signals:
255
+ continue
256
+
257
+ signals = buy_signals[symbol]
258
+ last_row = signals.iloc[-1]
259
+
260
+ if last_row:
261
+ position_size = self.get_position_size(symbol)
262
+ full_symbol = (f"{symbol}/"
263
+ f"{self.context.get_trading_symbol()}")
264
+ price = self.context.get_latest_price(full_symbol)
265
+ amount = position_size.get_size(
266
+ self.context.get_portfolio(), price
267
+ )
268
+ order_amount = amount / price
269
+ order = self.create_limit_order(
270
+ target_symbol=symbol,
271
+ order_side=OrderSide.BUY,
272
+ amount=order_amount,
273
+ price=price,
274
+ execute=True,
275
+ validate=True,
276
+ sync=True
277
+ )
278
+
279
+ # Retrieve stop loss and take profit rules if any
280
+ stop_loss_rule = self.get_stop_loss_rule(symbol)
281
+ take_profit_rule = self.get_take_profit_rule(symbol)
282
+
283
+ if stop_loss_rule is not None:
284
+ trade = self.context.get_trade(
285
+ order_id=order.id
286
+ )
287
+ self.context.add_stop_loss(
288
+ trade=trade,
289
+ percentage=stop_loss_rule.percentage_threshold,
290
+ trailing=stop_loss_rule.trailing,
291
+ sell_percentage=stop_loss_rule.sell_percentage,
292
+ created_at=index_datetime
293
+ )
294
+
295
+ if take_profit_rule is not None:
296
+ trade = self.context.get_trade(
297
+ order_id=order.id
298
+ )
299
+ self.context.add_take_profit(
300
+ trade=trade,
301
+ percentage=take_profit_rule.percentage_threshold,
302
+ trailing=take_profit_rule.trailing,
303
+ sell_percentage=take_profit_rule.sell_percentage,
304
+ created_at=index_datetime
305
+ )
306
+ else:
307
+ # Check in the last row if there is a sell signal
308
+ if symbol not in sell_signals:
309
+ continue
310
+
311
+ signals = sell_signals[symbol]
312
+ last_row = signals.iloc[-1]
313
+
314
+ if last_row:
315
+ position = self.get_position(symbol)
316
+
317
+ if position is None:
318
+ raise OperationalException(
319
+ f"No position found for symbol {symbol} "
320
+ f"in strategy {self.strategy_id}"
321
+ )
322
+
323
+ full_symbol = (f"{symbol}/"
324
+ f"{self.context.get_trading_symbol()}")
325
+ price = self.context.get_latest_price(full_symbol)
326
+ self.create_limit_order(
327
+ target_symbol=symbol,
328
+ order_side=OrderSide.SELL,
329
+ amount=position.amount,
330
+ execute=True,
331
+ validate=True,
332
+ sync=True,
333
+ price=price
334
+ )
335
+
336
+ def apply_strategy(self, context, data):
138
337
  if self.decorated:
139
- self.decorated(context=context, market_data=market_data)
338
+ self.decorated(context=context, data=data)
140
339
  else:
141
340
  raise NotImplementedError("Apply strategy is not implemented")
142
341
 
@@ -146,44 +345,84 @@ class TradingStrategy:
146
345
  strategy_id=self.worker_id,
147
346
  interval=self.interval,
148
347
  time_unit=self.time_unit,
149
- market_data_sources=self.market_data_sources
348
+ data_sources=self.data_sources
150
349
  )
151
350
 
152
- def _update_trades_and_orders(self, market_data):
153
- self.context.order_service.check_pending_orders()
154
- self.context.trade_service\
155
- .update_trades_with_market_data(market_data)
351
+ def get_take_profit_rule(self, symbol: str) -> Union[TakeProfitRule, None]:
352
+ """
353
+ Get the take profit definition for a given symbol.
156
354
 
157
- def _update_trades_and_orders_for_backtest(self, market_data):
158
- self.context.order_service.check_pending_orders(market_data)
159
- self.context.trade_service\
160
- .update_trades_with_market_data(market_data)
355
+ Args:
356
+ symbol (str): The symbol of the asset.
161
357
 
162
- def _check_stop_losses(self):
358
+ Returns:
359
+ Union[TakeProfitRule, None]: The take profit rule if found,
360
+ None otherwise.
163
361
  """
164
- Check if there are any stop losses that result in trades being closed.
362
+
363
+ if len(self.take_profit_rules) == 0:
364
+ return None
365
+
366
+ if self.take_profit_rules_lookup == {}:
367
+ for tp in self.take_profit_rules:
368
+ self.take_profit_rules_lookup[tp.symbol] = tp
369
+
370
+ return self.take_profit_rules_lookup.get(symbol, None)
371
+
372
+ def get_stop_loss_rule(self, symbol: str) -> Union[StopLossRule, None]:
165
373
  """
166
- trade_service = self.context.trade_service
374
+ Get the stop loss definition for a given symbol.
167
375
 
168
- stop_losses_orders_data = trade_service\
169
- .get_triggered_stop_loss_orders()
376
+ Args:
377
+ symbol (str): The symbol of the asset.
378
+
379
+ Returns:
380
+ Union[StopLossRule, None]: The stop loss rule if found,
381
+ None otherwise.
382
+ """
170
383
 
171
- order_service = self.context.order_service
384
+ if len(self.stop_loss_rules) == 0:
385
+ return None
172
386
 
173
- for stop_loss_order in stop_losses_orders_data:
174
- order_service.create(stop_loss_order)
387
+ if self.stop_loss_rules_lookup == {}:
388
+ for sl in self.stop_loss_rules:
389
+ self.stop_loss_rules_lookup[sl.symbol] = sl
175
390
 
176
- def _check_take_profits(self):
391
+ return self.stop_loss_rules_lookup.get(symbol, None)
392
+
393
+ def get_position_size(self, symbol: str) -> Union[PositionSize, None]:
177
394
  """
178
- Check if there are any take profits that result in trades being closed.
395
+ Get the position size definition for a given symbol.
396
+
397
+ Args:
398
+ symbol (str): The symbol of the asset.
399
+
400
+ Returns:
401
+ Union[PositionSize, None]: The position size if found,
402
+ None otherwise.
179
403
  """
180
- trade_service = self.context.trade_service
181
- take_profit_orders_data = trade_service.\
182
- get_triggered_take_profit_orders()
183
- order_service = self.context.order_service
184
404
 
185
- for take_profit_order in take_profit_orders_data:
186
- order_service.create(take_profit_order)
405
+ if len(self.position_sizes) == 0:
406
+ raise OperationalException(
407
+ f"No position size defined for symbol "
408
+ f"{symbol} in strategy "
409
+ f"{self.strategy_id}"
410
+ )
411
+
412
+ if self.position_sizes_lookup == {}:
413
+ for ps in self.position_sizes:
414
+ self.position_sizes_lookup[ps.symbol] = ps
415
+
416
+ position_size = self.position_sizes_lookup.get(symbol, None)
417
+
418
+ if position_size is None:
419
+ raise OperationalException(
420
+ f"No position size defined for symbol "
421
+ f"{symbol} in strategy "
422
+ f"{self.strategy_id}"
423
+ )
424
+
425
+ return position_size
187
426
 
188
427
  def on_trade_closed(self, context: Context, trade: Trade):
189
428
  pass
@@ -240,67 +479,6 @@ class TradingStrategy:
240
479
 
241
480
  return self.worker_id
242
481
 
243
- def add_trace(
244
- self,
245
- symbol: str,
246
- data,
247
- drop_duplicates=True
248
- ) -> None:
249
- """
250
- Add data to the straces object for a given symbol
251
-
252
- Args:
253
- symbol (str): The symbol
254
- data (pd.DataFrame): The data to add to the tracing
255
- drop_duplicates (bool): Drop duplicates
256
-
257
- Returns:
258
- None
259
- """
260
-
261
- # Check if data is a DataFrame
262
- if not isinstance(data, pd.DataFrame):
263
- raise ValueError(
264
- "Currently only pandas DataFrames are "
265
- "supported as tracing data objects."
266
- )
267
-
268
- data: pd.DataFrame = data
269
-
270
- # Check if index is a datetime object
271
- if not isinstance(data.index, pd.DatetimeIndex):
272
- raise ValueError("Dataframe Index must be a datetime object.")
273
-
274
- if self.traces is None:
275
- self.traces = {}
276
-
277
- # Check if the key is already in the context dictionary
278
- if symbol in self.traces:
279
- # If the key is already in the context dictionary,
280
- # append the new data to the existing data
281
- combined = pd.concat([self.traces[symbol], data])
282
- else:
283
- # If the key is not in the context dictionary,
284
- # add the new data to the context dictionary
285
- combined = data
286
-
287
- if drop_duplicates:
288
- # Drop duplicates and sort the data by the index
289
- combined = combined[~combined.index.duplicated(keep='first')]
290
-
291
- # Set the datetime column as the index
292
- combined.set_index(pd.DatetimeIndex(combined.index), inplace=True)
293
- self.traces[symbol] = combined
294
-
295
- def get_traces(self) -> dict:
296
- """
297
- Get the traces object
298
-
299
- Returns:
300
- dict: The traces object
301
- """
302
- return self.traces
303
-
304
482
  def has_open_orders(
305
483
  self, target_symbol=None, identifier=None, market=None
306
484
  ) -> bool:
@@ -335,7 +513,7 @@ class TradingStrategy:
335
513
  execute=True,
336
514
  validate=True,
337
515
  sync=True
338
- ):
516
+ ) -> Order:
339
517
  """
340
518
  Function to create a limit order. This function will create
341
519
  a limit order and execute it if the execute parameter is set to True.
@@ -367,7 +545,7 @@ class TradingStrategy:
367
545
  Returns:
368
546
  Order: Instance of the order created
369
547
  """
370
- self.context.create_limit_order(
548
+ return self.context.create_limit_order(
371
549
  target_symbol=target_symbol,
372
550
  price=price,
373
551
  order_side=order_side,
@@ -383,47 +561,9 @@ class TradingStrategy:
383
561
  sync=sync
384
562
  )
385
563
 
386
- def create_market_order(
387
- self,
388
- target_symbol,
389
- order_side,
390
- amount,
391
- market=None,
392
- execute=False,
393
- validate=False,
394
- sync=True
395
- ):
396
- """
397
- Function to create a market order. This function will create a market
398
- order and execute it if the execute parameter is set to True. If the
399
- validate parameter is set to True, the order will be validated
400
-
401
- Args:
402
- target_symbol: The symbol of the asset to trade
403
- order_side: The side of the order
404
- amount: The amount of the asset to trade
405
- market: The market to trade the asset
406
- execute: If set to True, the order will be executed
407
- validate: If set to True, the order will be validated
408
- sync: If set to True, the created order will be synced with the
409
- portfolio of the context
410
-
411
- Returns:
412
- Order: Instance of the order created
413
- """
414
- self.context.create_market_order(
415
- target_symbol=target_symbol,
416
- order_side=order_side,
417
- amount=amount,
418
- market=market,
419
- execute=execute,
420
- validate=validate,
421
- sync=sync
422
- )
423
-
424
564
  def close_position(
425
565
  self, symbol, market=None, identifier=None, precision=None
426
- ):
566
+ ) -> Order:
427
567
  """
428
568
  Function to close a position. This function will close a position
429
569
  by creating a market order to sell the position. If the precision
@@ -439,7 +579,7 @@ class TradingStrategy:
439
579
  Returns:
440
580
  None
441
581
  """
442
- self.context.close_position(
582
+ return self.context.close_position(
443
583
  symbol=symbol,
444
584
  market=market,
445
585
  identifier=identifier,
@@ -31,9 +31,11 @@ class Task:
31
31
  else:
32
32
  self.worker_id = self.__class__.__name__
33
33
 
34
- def run(self, algorithm):
34
+ def run(self, context):
35
35
 
36
36
  if self.decorated:
37
- self.decorated(algorithm=algorithm)
37
+ self.decorated(context=context)
38
38
  else:
39
- raise NotImplementedError("Apply strategy is not implemented")
39
+ raise NotImplementedError(
40
+ "run method must be implemented in the subclass"
41
+ )