investing-algorithm-framework 7.19.14__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 (260) hide show
  1. investing_algorithm_framework/__init__.py +197 -0
  2. investing_algorithm_framework/app/__init__.py +47 -0
  3. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  4. investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
  5. investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
  6. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  7. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  8. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  9. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  10. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  11. investing_algorithm_framework/app/app.py +2204 -0
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1667 -0
  14. investing_algorithm_framework/app/eventloop.py +590 -0
  15. investing_algorithm_framework/app/reporting/__init__.py +27 -0
  16. investing_algorithm_framework/app/reporting/ascii.py +921 -0
  17. investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
  18. investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
  19. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  20. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  21. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
  22. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  23. investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
  24. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  25. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
  26. investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
  27. investing_algorithm_framework/app/reporting/generate.py +185 -0
  28. investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
  29. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
  30. investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
  31. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  32. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  33. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  34. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  35. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  36. investing_algorithm_framework/app/stateless/__init__.py +35 -0
  37. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
  38. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
  39. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
  40. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
  41. investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
  42. investing_algorithm_framework/app/strategy.py +675 -0
  43. investing_algorithm_framework/app/task.py +41 -0
  44. investing_algorithm_framework/app/web/__init__.py +5 -0
  45. investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
  46. investing_algorithm_framework/app/web/controllers/orders.py +20 -0
  47. investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
  48. investing_algorithm_framework/app/web/controllers/positions.py +18 -0
  49. investing_algorithm_framework/app/web/create_app.py +20 -0
  50. investing_algorithm_framework/app/web/error_handler.py +59 -0
  51. investing_algorithm_framework/app/web/responses.py +20 -0
  52. investing_algorithm_framework/app/web/run_strategies.py +4 -0
  53. investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
  54. investing_algorithm_framework/app/web/schemas/order.py +12 -0
  55. investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
  56. investing_algorithm_framework/app/web/schemas/position.py +15 -0
  57. investing_algorithm_framework/app/web/setup_cors.py +6 -0
  58. investing_algorithm_framework/cli/__init__.py +0 -0
  59. investing_algorithm_framework/cli/cli.py +207 -0
  60. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
  61. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  62. investing_algorithm_framework/cli/initialize_app.py +603 -0
  63. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  64. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  65. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  66. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  67. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  68. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  69. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  70. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  71. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  72. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  73. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  74. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  75. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  76. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  77. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  78. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  79. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  80. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  81. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  82. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  83. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  84. investing_algorithm_framework/create_app.py +54 -0
  85. investing_algorithm_framework/dependency_container.py +155 -0
  86. investing_algorithm_framework/domain/__init__.py +148 -0
  87. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  88. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  92. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  93. investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
  94. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  95. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  96. investing_algorithm_framework/domain/config.py +111 -0
  97. investing_algorithm_framework/domain/constants.py +83 -0
  98. investing_algorithm_framework/domain/data_provider.py +334 -0
  99. investing_algorithm_framework/domain/data_structures.py +42 -0
  100. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  101. investing_algorithm_framework/domain/exceptions.py +112 -0
  102. investing_algorithm_framework/domain/models/__init__.py +43 -0
  103. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  104. investing_algorithm_framework/domain/models/base_model.py +25 -0
  105. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  106. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  107. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  108. investing_algorithm_framework/domain/models/event.py +35 -0
  109. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  110. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  111. investing_algorithm_framework/domain/models/order/__init__.py +6 -0
  112. investing_algorithm_framework/domain/models/order/order.py +384 -0
  113. investing_algorithm_framework/domain/models/order/order_side.py +36 -0
  114. investing_algorithm_framework/domain/models/order/order_status.py +37 -0
  115. investing_algorithm_framework/domain/models/order/order_type.py +30 -0
  116. investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
  117. investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
  118. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
  119. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  120. investing_algorithm_framework/domain/models/position/__init__.py +4 -0
  121. investing_algorithm_framework/domain/models/position/position.py +68 -0
  122. investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
  123. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  124. investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
  125. investing_algorithm_framework/domain/models/time_frame.py +153 -0
  126. investing_algorithm_framework/domain/models/time_interval.py +124 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +149 -0
  128. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  129. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  130. investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +388 -0
  132. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
  133. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  134. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
  135. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
  136. investing_algorithm_framework/domain/order_executor.py +112 -0
  137. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  138. investing_algorithm_framework/domain/positions/__init__.py +4 -0
  139. investing_algorithm_framework/domain/positions/position_size.py +41 -0
  140. investing_algorithm_framework/domain/services/__init__.py +11 -0
  141. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  142. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  143. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  144. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  145. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  146. investing_algorithm_framework/domain/stateless_actions.py +7 -0
  147. investing_algorithm_framework/domain/strategy.py +44 -0
  148. investing_algorithm_framework/domain/utils/__init__.py +27 -0
  149. investing_algorithm_framework/domain/utils/csv.py +104 -0
  150. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  151. investing_algorithm_framework/domain/utils/dates.py +57 -0
  152. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  153. investing_algorithm_framework/domain/utils/polars.py +53 -0
  154. investing_algorithm_framework/domain/utils/random.py +41 -0
  155. investing_algorithm_framework/domain/utils/signatures.py +17 -0
  156. investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
  157. investing_algorithm_framework/domain/utils/synchronized.py +12 -0
  158. investing_algorithm_framework/download_data.py +108 -0
  159. investing_algorithm_framework/infrastructure/__init__.py +50 -0
  160. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  161. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  162. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  163. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  164. investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
  165. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
  166. investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
  167. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  168. investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
  169. investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
  170. investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
  171. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  172. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  173. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
  174. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  175. investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
  176. investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
  177. investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
  178. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  179. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  180. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  181. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
  182. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
  183. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  184. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  185. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  186. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  187. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  188. investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
  189. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  190. investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
  191. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
  192. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  193. investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
  194. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  195. investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
  196. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  197. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
  198. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
  199. investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
  200. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  201. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  202. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  203. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  204. investing_algorithm_framework/services/__init__.py +132 -0
  205. investing_algorithm_framework/services/backtesting/__init__.py +5 -0
  206. investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
  207. investing_algorithm_framework/services/configuration_service.py +96 -0
  208. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  209. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  210. investing_algorithm_framework/services/market_credential_service.py +40 -0
  211. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  212. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  213. investing_algorithm_framework/services/metrics/beta.py +0 -0
  214. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  215. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  216. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  217. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  218. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  219. investing_algorithm_framework/services/metrics/generate.py +358 -0
  220. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  221. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  222. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  223. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  224. investing_algorithm_framework/services/metrics/returns.py +452 -0
  225. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  226. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  227. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  228. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  229. investing_algorithm_framework/services/metrics/trades.py +500 -0
  230. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  231. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  232. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  233. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  234. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  235. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  237. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  238. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  239. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  240. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  241. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
  242. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  243. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  244. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  245. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  246. investing_algorithm_framework/services/positions/__init__.py +7 -0
  247. investing_algorithm_framework/services/positions/position_service.py +210 -0
  248. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  249. investing_algorithm_framework/services/repository_service.py +40 -0
  250. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  251. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
  252. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
  253. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
  254. investing_algorithm_framework/services/trade_service/__init__.py +3 -0
  255. investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
  256. investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
  257. investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
  258. investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
  259. investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
  260. investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,675 @@
1
+ from datetime import datetime
2
+ from typing import List, Dict, Any
3
+
4
+ import pandas as pd
5
+
6
+ from investing_algorithm_framework.domain import OperationalException, Position
7
+ from investing_algorithm_framework.domain import PositionSize, \
8
+ TimeUnit, StrategyProfile, Trade, DataSource, OrderSide
9
+ from .context import Context
10
+
11
+
12
+ class TradingStrategy:
13
+ """
14
+ TradingStrategy is the base class for all trading strategies. A trading
15
+ strategy is a set of rules that defines when to buy or sell an asset.
16
+
17
+ Attributes:
18
+ time_unit: TimeUnit - the time unit of the strategy that defines
19
+ when the strategy should run e.g. HOUR, DAY, WEEK, MONTH
20
+ interval: int - the interval of the strategy that defines how often
21
+ the strategy should run within the time unit e.g. every 5 hours,
22
+ every 2 days, every 3 weeks, every 4 months
23
+ worker_id (optional): str - the id of the worker
24
+ strategy_id (optional): str - the id of the strategy
25
+ decorated (optional): function - the decorated function
26
+ data_sources (List[DataSource] optional): the list of data
27
+ sources to use for the strategy. The data sources will be used
28
+ to indentify data providers that will be called to gather data
29
+ and pass to the strategy before its run.
30
+ metadata (optional): Dict[str, Any] - a dictionary
31
+ containing metadata about the strategy. This can be used to
32
+ store additional information about the strategy, such as its
33
+ author, version, description, params etc.
34
+ """
35
+ time_unit: TimeUnit = None
36
+ interval: int = None
37
+ worker_id: str = None
38
+ strategy_id: str = None
39
+ decorated = None
40
+ data_sources: List[DataSource] = []
41
+ traces = None
42
+ context: Context = None
43
+ metadata: Dict[str, Any] = None
44
+ position_sizes: List[PositionSize] = []
45
+ symbols: List[str] = []
46
+ trading_symbol: str = None
47
+
48
+ def __init__(
49
+ self,
50
+ strategy_id=None,
51
+ time_unit=None,
52
+ interval=None,
53
+ data_sources=None,
54
+ metadata=None,
55
+ position_sizes=None,
56
+ symbols=None,
57
+ trading_symbol=None,
58
+ worker_id=None,
59
+ decorated=None
60
+ ):
61
+ if time_unit is not None:
62
+ self.time_unit = TimeUnit.from_value(time_unit)
63
+ else:
64
+ # Check if time_unit is None
65
+ if self.time_unit is None:
66
+ raise OperationalException(
67
+ f"Time unit attribute not set for "
68
+ f"strategy instance {self.strategy_id}"
69
+ )
70
+
71
+ self.time_unit = TimeUnit.from_value(self.time_unit)
72
+
73
+ if interval is not None:
74
+ self.interval = interval
75
+
76
+ if data_sources is not None:
77
+ self.data_sources = data_sources
78
+
79
+ self.metadata = metadata
80
+
81
+ if decorated is not None:
82
+ self.decorated = decorated
83
+
84
+ if worker_id is not None:
85
+ self.worker_id = worker_id
86
+ elif self.decorated:
87
+ self.worker_id = decorated.__name__
88
+ else:
89
+ self.worker_id = self.__class__.__name__
90
+
91
+ if strategy_id is not None:
92
+ self.strategy_id = strategy_id
93
+ else:
94
+ self.strategy_id = self.worker_id
95
+
96
+ if position_sizes is not None:
97
+ self.position_sizes = position_sizes
98
+
99
+ if symbols is not None:
100
+ self.symbols = symbols
101
+
102
+ if trading_symbol is not None:
103
+ self.trading_symbol = trading_symbol
104
+
105
+ # Check if interval is None
106
+ if self.interval is None:
107
+ raise OperationalException(
108
+ f"Interval not set for strategy instance {self.strategy_id}"
109
+ )
110
+
111
+ # context initialization
112
+ self._context = None
113
+ self._last_run = None
114
+
115
+ def generate_buy_signals(
116
+ self, data: Dict[str, Any]
117
+ ) -> Dict[str, pd.Series]:
118
+ """
119
+ Function that needs to be implemented by the user.
120
+ This function should return a pandas Series containing the buy signals.
121
+
122
+ Args:
123
+ data (Dict[str, Any]): All the data that matched the
124
+ data sources of the strategy.
125
+
126
+ Returns:
127
+ Dict[str, Series]: A dictionary where the keys are the
128
+ symbols and the values are pandas Series containing
129
+ the buy signals.
130
+ """
131
+ raise NotImplementedError(
132
+ "generate_buy_signals method not implemented"
133
+ )
134
+
135
+ def generate_sell_signals(
136
+ self, data: Dict[str, Any]
137
+ ) -> Dict[str, pd.Series]:
138
+ """
139
+ Function that needs to be implemented by the user.
140
+ This function should return a pandas Series containing
141
+ the sell signals.
142
+
143
+ Args:
144
+ data (Dict[str, Any]): All the data that matched the
145
+ data sources of the strategy.
146
+
147
+ Returns:
148
+ Dict[str, Series]: A dictionary where the keys are the
149
+ symbols and the values are pandas Series containing
150
+ the sell signals.
151
+ """
152
+ raise NotImplementedError(
153
+ "generate_sell_signals method not implemented"
154
+ )
155
+
156
+ def run_strategy(self, context: Context, data: Dict[str, Any]):
157
+ """
158
+ Main function for running your strategy. This function will be called
159
+ by the framework when the trigger of your strategy is met.
160
+
161
+ During execution of this function, the context and market data
162
+ will be passed to the function. The context is an instance of
163
+ the Context class, this class has various methods to do operations
164
+ with your portfolio, orders, trades, positions and other components.
165
+
166
+ The market data is a dictionary containing all the data retrieved
167
+ from the specified data sources.
168
+
169
+ Args:
170
+ context (Context): The context of the strategy. This is an instance
171
+ of the Context class, this class has various methods to do
172
+ operations with your portfolio, orders, trades, positions and
173
+ other components.
174
+ data (Dict[str, Any]): The data for the strategy.
175
+ This is a dictionary containing all the data retrieved from the
176
+ specified data sources.
177
+
178
+ Returns:
179
+ None
180
+ """
181
+ self.context = context
182
+ buy_signals = self.generate_buy_signals(data)
183
+ sell_signals = self.generate_sell_signals(data)
184
+
185
+ for symbol in self.symbols:
186
+
187
+ if self.has_open_orders(symbol):
188
+ continue
189
+
190
+ if not self.has_position(symbol) \
191
+ and not self.has_open_orders(symbol):
192
+
193
+ if symbol in buy_signals:
194
+ signals = buy_signals[symbol]
195
+
196
+ # Check in the last row if there is a buy signal
197
+ last_row = signals.iloc[-1]
198
+ if last_row:
199
+ position_size = next(
200
+ (ps for ps in self.position_sizes
201
+ if ps.symbol == symbol), None
202
+ )
203
+ if position_size is None:
204
+ raise OperationalException(
205
+ f"No position size defined for symbol "
206
+ f"{symbol} in strategy "
207
+ f"{self.strategy_id}"
208
+ )
209
+ full_symbol = (f"{symbol}/"
210
+ f"{self.context.get_trading_symbol()}")
211
+ price = self.context.get_latest_price(full_symbol)
212
+ amount = position_size.get_size(
213
+ self.context.get_portfolio(), price
214
+ )
215
+ order_amount = amount / price
216
+ self.create_limit_order(
217
+ target_symbol=symbol,
218
+ order_side=OrderSide.BUY,
219
+ amount=order_amount,
220
+ price=price,
221
+ execute=True,
222
+ validate=True,
223
+ sync=True
224
+ )
225
+
226
+ if self.has_position(symbol) \
227
+ and not self.has_open_orders(symbol):
228
+
229
+ # Check in the last row if there is a sell signal
230
+ if symbol in sell_signals:
231
+ signals = sell_signals[symbol]
232
+
233
+ # Check in the last row if there is a sell signal
234
+ last_row = signals.iloc[-1]
235
+
236
+ if last_row:
237
+ position = self.get_position(symbol)
238
+
239
+ if position is None:
240
+ raise OperationalException(
241
+ f"No position found for symbol {symbol} "
242
+ f"in strategy {self.strategy_id}"
243
+ )
244
+
245
+ full_symbol = (f"{symbol}/"
246
+ f"{self.context.get_trading_symbol()}")
247
+ price = self.context.get_latest_price(full_symbol)
248
+ self.create_limit_order(
249
+ target_symbol=symbol,
250
+ order_side=OrderSide.SELL,
251
+ amount=position.amount,
252
+ execute=True,
253
+ validate=True,
254
+ sync=True,
255
+ price=price
256
+ )
257
+
258
+ def apply_strategy(self, context, data):
259
+ if self.decorated:
260
+ self.decorated(context=context, data=data)
261
+ else:
262
+ raise NotImplementedError("Apply strategy is not implemented")
263
+
264
+ @property
265
+ def strategy_profile(self):
266
+ return StrategyProfile(
267
+ strategy_id=self.worker_id,
268
+ interval=self.interval,
269
+ time_unit=self.time_unit,
270
+ data_sources=self.data_sources
271
+ )
272
+
273
+ def _update_trades_and_orders(self, market_data):
274
+ self.context.order_service.check_pending_orders()
275
+ self.context.trade_service\
276
+ .update_trades_with_market_data(market_data)
277
+
278
+ def _update_trades_and_orders_for_backtest(self, market_data):
279
+ self.context.order_service.check_pending_orders(market_data)
280
+ self.context.trade_service\
281
+ .update_trades_with_market_data(market_data)
282
+
283
+ def _check_stop_losses(self):
284
+ """
285
+ Check if there are any stop losses that result in trades being closed.
286
+ """
287
+ trade_service = self.context.trade_service
288
+
289
+ stop_losses_orders_data = trade_service\
290
+ .get_triggered_stop_loss_orders()
291
+
292
+ order_service = self.context.order_service
293
+
294
+ for stop_loss_order in stop_losses_orders_data:
295
+ order_service.create(stop_loss_order)
296
+
297
+ def _check_take_profits(self):
298
+ """
299
+ Check if there are any take profits that result in trades being closed.
300
+ """
301
+ trade_service = self.context.trade_service
302
+ take_profit_orders_data = trade_service.\
303
+ get_triggered_take_profit_orders()
304
+ order_service = self.context.order_service
305
+
306
+ for take_profit_order in take_profit_orders_data:
307
+ order_service.create(take_profit_order)
308
+
309
+ def on_trade_closed(self, context: Context, trade: Trade):
310
+ pass
311
+
312
+ def on_trade_updated(self, context: Context, trade: Trade):
313
+ pass
314
+
315
+ def on_trade_created(self, context: Context, trade: Trade):
316
+ pass
317
+
318
+ def on_trade_opened(self, context: Context, trade: Trade):
319
+ pass
320
+
321
+ def on_trade_stop_loss_triggered(self, context: Context, trade: Trade):
322
+ pass
323
+
324
+ def on_trade_trailing_stop_loss_triggered(
325
+ self, context: Context, trade: Trade
326
+ ):
327
+ pass
328
+
329
+ def on_trade_take_profit_triggered(
330
+ self, context: Context, trade: Trade
331
+ ):
332
+ pass
333
+
334
+ def on_trade_stop_loss_updated(self, context: Context, trade: Trade):
335
+ pass
336
+
337
+ def on_trade_trailing_stop_loss_updated(
338
+ self, context: Context, trade: Trade
339
+ ):
340
+ pass
341
+
342
+ def on_trade_take_profit_updated(self, context: Context, trade: Trade):
343
+ pass
344
+
345
+ def on_trade_stop_loss_created(self, context: Context, trade: Trade):
346
+ pass
347
+
348
+ def on_trade_trailing_stop_loss_created(
349
+ self, context: Context, trade: Trade
350
+ ):
351
+ pass
352
+
353
+ def on_trade_take_profit_created(self, context: Context, trade: Trade):
354
+ pass
355
+
356
+ @property
357
+ def strategy_identifier(self):
358
+
359
+ if self.strategy_id is not None:
360
+ return self.strategy_id
361
+
362
+ return self.worker_id
363
+
364
+ def has_open_orders(
365
+ self, target_symbol=None, identifier=None, market=None
366
+ ) -> bool:
367
+ """
368
+ Check if there are open orders for a given symbol
369
+
370
+ Args:
371
+ target_symbol (str): The symbol of the asset e.g BTC if the
372
+ asset is BTC/USDT
373
+ identifier (str): The identifier of the portfolio
374
+ market (str): The market of the asset
375
+
376
+ Returns:
377
+ bool: True if there are open orders, False otherwise
378
+ """
379
+ return self.context.has_open_orders(
380
+ target_symbol=target_symbol, identifier=identifier, market=market
381
+ )
382
+
383
+ def create_limit_order(
384
+ self,
385
+ target_symbol,
386
+ price,
387
+ order_side,
388
+ amount=None,
389
+ amount_trading_symbol=None,
390
+ percentage=None,
391
+ percentage_of_portfolio=None,
392
+ percentage_of_position=None,
393
+ precision=None,
394
+ market=None,
395
+ execute=True,
396
+ validate=True,
397
+ sync=True
398
+ ):
399
+ """
400
+ Function to create a limit order. This function will create
401
+ a limit order and execute it if the execute parameter is set to True.
402
+ If the validate parameter is set to True, the order will be validated
403
+
404
+ Args:
405
+ target_symbol: The symbol of the asset to trade
406
+ price: The price of the asset
407
+ order_side: The side of the order
408
+ amount (optional): The amount of the asset to trade
409
+ amount_trading_symbol (optional): The amount of the trading
410
+ symbol to trade
411
+ percentage (optional): The percentage of the portfolio to
412
+ allocate to the order
413
+ percentage_of_portfolio (optional): The percentage of
414
+ the portfolio to allocate to the order
415
+ percentage_of_position (optional): The percentage of
416
+ the position to allocate to the order.
417
+ (Only supported for SELL orders)
418
+ precision (optional): The precision of the amount
419
+ market (optional): The market to trade the asset
420
+ execute (optional): Default True. If set to True, the order
421
+ will be executed
422
+ validate (optional): Default True. If set to True, the order
423
+ will be validated
424
+ sync (optional): Default True. If set to True, the created
425
+ order will be synced with the portfolio of the context
426
+
427
+ Returns:
428
+ Order: Instance of the order created
429
+ """
430
+ self.context.create_limit_order(
431
+ target_symbol=target_symbol,
432
+ price=price,
433
+ order_side=order_side,
434
+ amount=amount,
435
+ amount_trading_symbol=amount_trading_symbol,
436
+ percentage=percentage,
437
+ percentage_of_portfolio=percentage_of_portfolio,
438
+ percentage_of_position=percentage_of_position,
439
+ precision=precision,
440
+ market=market,
441
+ execute=execute,
442
+ validate=validate,
443
+ sync=sync
444
+ )
445
+
446
+ def close_position(
447
+ self, symbol, market=None, identifier=None, precision=None
448
+ ):
449
+ """
450
+ Function to close a position. This function will close a position
451
+ by creating a market order to sell the position. If the precision
452
+ parameter is specified, the amount of the order will be rounded
453
+ down to the specified precision.
454
+
455
+ Args:
456
+ symbol: The symbol of the asset
457
+ market: The market of the asset
458
+ identifier: The identifier of the portfolio
459
+ precision: The precision of the amount
460
+
461
+ Returns:
462
+ None
463
+ """
464
+ self.context.close_position(
465
+ symbol=symbol,
466
+ market=market,
467
+ identifier=identifier,
468
+ precision=precision
469
+ )
470
+
471
+ def get_positions(
472
+ self,
473
+ market=None,
474
+ identifier=None,
475
+ amount_gt=None,
476
+ amount_gte=None,
477
+ amount_lt=None,
478
+ amount_lte=None
479
+ ) -> List[Position]:
480
+ """
481
+ Function to get all positions. This function will return all
482
+ positions that match the specified query parameters. If the
483
+ market parameter is specified, the positions of the specified
484
+ market will be returned. If the identifier parameter is
485
+ specified, the positions of the specified portfolio will be
486
+ returned. If the amount_gt parameter is specified, the positions
487
+ with an amount greater than the specified amount will be returned.
488
+ If the amount_gte parameter is specified, the positions with an
489
+ amount greater than or equal to the specified amount will be
490
+ returned. If the amount_lt parameter is specified, the positions
491
+ with an amount less than the specified amount will be returned.
492
+ If the amount_lte parameter is specified, the positions with an
493
+ amount less than or equal to the specified amount will be returned.
494
+
495
+ Args:
496
+ market: The market of the portfolio where the positions are
497
+ identifier: The identifier of the portfolio
498
+ amount_gt: The amount of the asset must be greater than this
499
+ amount_gte: The amount of the asset must be greater than or
500
+ equal to this
501
+ amount_lt: The amount of the asset must be less than this
502
+ amount_lte: The amount of the asset must be less than or equal
503
+ to this
504
+
505
+ Returns:
506
+ List[Position]: A list of positions that match the query parameters
507
+ """
508
+ return self.context.get_positions(
509
+ market=market,
510
+ identifier=identifier,
511
+ amount_gt=amount_gt,
512
+ amount_gte=amount_gte,
513
+ amount_lt=amount_lt,
514
+ amount_lte=amount_lte
515
+ )
516
+
517
+ def get_trades(self, market=None) -> List[Trade]:
518
+ """
519
+ Function to get all trades. This function will return all trades
520
+ that match the specified query parameters. If the market parameter
521
+ is specified, the trades with the specified market will be returned.
522
+
523
+ Args:
524
+ market: The market of the asset
525
+
526
+ Returns:
527
+ List[Trade]: A list of trades that match the query parameters
528
+ """
529
+ return self.context.get_trades(market)
530
+
531
+ def get_closed_trades(self) -> List[Trade]:
532
+ """
533
+ Function to get all closed trades. This function will return all
534
+ closed trades of the context.
535
+
536
+ Returns:
537
+ List[Trade]: A list of closed trades
538
+ """
539
+ return self.context.get_closed_trades()
540
+
541
+ def get_open_trades(self, target_symbol=None, market=None) -> List[Trade]:
542
+ """
543
+ Function to get all open trades. This function will return all
544
+ open trades that match the specified query parameters. If the
545
+ target_symbol parameter is specified, the open trades with the
546
+ specified target symbol will be returned. If the market parameter
547
+ is specified, the open trades with the specified market will be
548
+ returned.
549
+
550
+ Args:
551
+ target_symbol: The symbol of the asset
552
+ market: The market of the asset
553
+
554
+ Returns:
555
+ List[Trade]: A list of open trades that match the query parameters
556
+ """
557
+ return self.context.get_open_trades(target_symbol, market)
558
+
559
+ def close_trade(self, trade, market=None, precision=None) -> None:
560
+ """
561
+ Function to close a trade. This function will close a trade by
562
+ creating a market order to sell the position. If the precision
563
+ parameter is specified, the amount of the order will be rounded
564
+ down to the specified precision.
565
+
566
+ Args:
567
+ trade: Trade - The trade to close
568
+ market: str - The market of the trade
569
+ precision: float - The precision of the amount
570
+
571
+ Returns:
572
+ None
573
+ """
574
+ self.context.close_trade(
575
+ trade=trade, market=market, precision=precision
576
+ )
577
+
578
+ def get_number_of_positions(self):
579
+ """
580
+ Returns the number of positions that have a positive amount.
581
+
582
+ Returns:
583
+ int: The number of positions
584
+ """
585
+ return self.context.get_number_of_positions()
586
+
587
+ def get_position(
588
+ self, symbol, market=None, identifier=None
589
+ ) -> Position:
590
+ """
591
+ Function to get a position. This function will return the
592
+ position that matches the specified query parameters. If the
593
+ market parameter is specified, the position of the specified
594
+ market will be returned. If the identifier parameter is
595
+ specified, the position of the specified portfolio will be
596
+ returned.
597
+
598
+ Args:
599
+ symbol: The symbol of the asset that represents the position
600
+ market: The market of the portfolio where the position is located
601
+ identifier: The identifier of the portfolio
602
+
603
+ Returns:
604
+ Position: The position that matches the query parameters
605
+ """
606
+ return self.context.get_position(
607
+ symbol=symbol,
608
+ market=market,
609
+ identifier=identifier
610
+ )
611
+
612
+ def has_position(
613
+ self,
614
+ symbol,
615
+ market=None,
616
+ identifier=None,
617
+ amount_gt=0,
618
+ amount_gte=None,
619
+ amount_lt=None,
620
+ amount_lte=None
621
+ ):
622
+ """
623
+ Function to check if a position exists. This function will return
624
+ True if a position exists, False otherwise. This function will check
625
+ if the amount > 0 condition by default.
626
+
627
+ Args:
628
+ param symbol: The symbol of the asset
629
+ param market: The market of the asset
630
+ param identifier: The identifier of the portfolio
631
+ param amount_gt: The amount of the asset must be greater than this
632
+ param amount_gte: The amount of the asset must be greater than
633
+ or equal to this
634
+ param amount_lt: The amount of the asset must be less than this
635
+ param amount_lte: The amount of the asset must be less than
636
+ or equal to this
637
+
638
+ Returns:
639
+ Boolean: True if a position exists, False otherwise
640
+ """
641
+ return self.context.has_position(
642
+ symbol=symbol,
643
+ market=market,
644
+ identifier=identifier,
645
+ amount_gt=amount_gt,
646
+ amount_gte=amount_gte,
647
+ amount_lt=amount_lt,
648
+ amount_lte=amount_lte
649
+ )
650
+
651
+ def has_balance(self, symbol, amount, market=None):
652
+ """
653
+ Function to check if the portfolio has enough balance to
654
+ create an order. This function will return True if the
655
+ portfolio has enough balance to create an order, False
656
+ otherwise.
657
+
658
+ Args:
659
+ symbol: The symbol of the asset
660
+ amount: The amount of the asset
661
+ market: The market of the asset
662
+
663
+ Returns:
664
+ Boolean: True if the portfolio has enough balance
665
+ """
666
+ return self.context.has_balance(symbol, amount, market)
667
+
668
+ def last_run(self) -> datetime:
669
+ """
670
+ Function to get the last run of the strategy
671
+
672
+ Returns:
673
+ DateTime: The last run of the strategy
674
+ """
675
+ return self.context.last_run()