investing-algorithm-framework 1.3.1__py3-none-any.whl → 7.25.6__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.
Files changed (282) hide show
  1. investing_algorithm_framework/__init__.py +195 -16
  2. investing_algorithm_framework/analysis/__init__.py +16 -0
  3. investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
  4. investing_algorithm_framework/analysis/data.py +170 -0
  5. investing_algorithm_framework/analysis/markdown.py +91 -0
  6. investing_algorithm_framework/analysis/ranking.py +298 -0
  7. investing_algorithm_framework/app/__init__.py +31 -4
  8. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  9. investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
  10. investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
  11. investing_algorithm_framework/app/app.py +2233 -264
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1724 -0
  14. investing_algorithm_framework/app/eventloop.py +620 -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/action_handlers/__init__.py +6 -3
  37. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
  38. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
  39. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  40. investing_algorithm_framework/app/stateless/exception_handler.py +1 -1
  41. investing_algorithm_framework/app/strategy.py +873 -52
  42. investing_algorithm_framework/app/task.py +5 -3
  43. investing_algorithm_framework/app/web/__init__.py +2 -1
  44. investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
  45. investing_algorithm_framework/app/web/controllers/orders.py +4 -3
  46. investing_algorithm_framework/app/web/controllers/portfolio.py +1 -1
  47. investing_algorithm_framework/app/web/controllers/positions.py +3 -3
  48. investing_algorithm_framework/app/web/create_app.py +4 -2
  49. investing_algorithm_framework/app/web/error_handler.py +1 -1
  50. investing_algorithm_framework/app/web/schemas/order.py +2 -2
  51. investing_algorithm_framework/app/web/schemas/position.py +1 -0
  52. investing_algorithm_framework/cli/__init__.py +0 -0
  53. investing_algorithm_framework/cli/cli.py +231 -0
  54. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  55. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  56. investing_algorithm_framework/cli/initialize_app.py +603 -0
  57. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  58. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  59. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  60. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  61. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  62. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  63. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  64. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  65. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  66. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  67. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  68. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  69. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  70. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  71. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  72. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  73. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  74. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  75. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  76. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  77. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  78. investing_algorithm_framework/cli/validate_backtest_checkpoints.py +197 -0
  79. investing_algorithm_framework/create_app.py +43 -9
  80. investing_algorithm_framework/dependency_container.py +121 -33
  81. investing_algorithm_framework/domain/__init__.py +109 -22
  82. investing_algorithm_framework/domain/algorithm_id.py +69 -0
  83. investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
  84. investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
  85. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
  86. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
  87. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
  88. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
  92. investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
  93. investing_algorithm_framework/domain/config.py +60 -138
  94. investing_algorithm_framework/domain/constants.py +23 -34
  95. investing_algorithm_framework/domain/data_provider.py +334 -0
  96. investing_algorithm_framework/domain/data_structures.py +42 -0
  97. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  98. investing_algorithm_framework/domain/exceptions.py +51 -1
  99. investing_algorithm_framework/domain/models/__init__.py +29 -14
  100. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  101. investing_algorithm_framework/domain/models/base_model.py +3 -1
  102. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  103. investing_algorithm_framework/domain/models/data/data_source.py +222 -0
  104. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  105. investing_algorithm_framework/domain/models/event.py +35 -0
  106. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  107. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  108. investing_algorithm_framework/domain/models/order/__init__.py +3 -4
  109. investing_algorithm_framework/domain/models/order/order.py +243 -86
  110. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  111. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  112. investing_algorithm_framework/domain/models/portfolio/__init__.py +7 -2
  113. investing_algorithm_framework/domain/models/portfolio/portfolio.py +134 -1
  114. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -37
  115. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  116. investing_algorithm_framework/domain/models/position/__init__.py +3 -2
  117. investing_algorithm_framework/domain/models/position/position.py +29 -0
  118. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  119. investing_algorithm_framework/domain/models/position/{position_cost.py → position_snapshot.py} +16 -8
  120. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  121. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  122. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -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 +94 -98
  126. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +111 -2
  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 +11 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +389 -0
  132. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  133. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  134. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  135. investing_algorithm_framework/domain/order_executor.py +112 -0
  136. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  137. investing_algorithm_framework/domain/services/__init__.py +11 -0
  138. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  139. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  140. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  141. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  142. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  143. investing_algorithm_framework/domain/strategy.py +1 -29
  144. investing_algorithm_framework/domain/utils/__init__.py +16 -4
  145. investing_algorithm_framework/domain/utils/csv.py +22 -0
  146. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  147. investing_algorithm_framework/domain/utils/dates.py +57 -0
  148. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  149. investing_algorithm_framework/domain/utils/polars.py +53 -0
  150. investing_algorithm_framework/domain/utils/random.py +29 -0
  151. investing_algorithm_framework/download_data.py +244 -0
  152. investing_algorithm_framework/infrastructure/__init__.py +39 -11
  153. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  154. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -0
  155. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  156. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  157. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  158. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +87 -13
  159. investing_algorithm_framework/infrastructure/models/__init__.py +13 -4
  160. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  161. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
  162. investing_algorithm_framework/infrastructure/models/order/order.py +73 -73
  163. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  164. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  165. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +3 -2
  166. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  167. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +57 -3
  168. investing_algorithm_framework/infrastructure/models/position/__init__.py +2 -2
  169. investing_algorithm_framework/infrastructure/models/position/position.py +16 -11
  170. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  171. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  172. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  173. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  174. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  175. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  176. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  177. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  178. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  179. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  180. investing_algorithm_framework/infrastructure/repositories/__init__.py +13 -5
  181. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  182. investing_algorithm_framework/infrastructure/repositories/order_repository.py +32 -19
  183. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
  184. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  185. investing_algorithm_framework/infrastructure/repositories/position_repository.py +47 -4
  186. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  187. investing_algorithm_framework/infrastructure/repositories/repository.py +85 -31
  188. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  189. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  190. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  191. investing_algorithm_framework/infrastructure/services/__init__.py +9 -2
  192. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  193. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -0
  194. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  195. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  196. investing_algorithm_framework/infrastructure/services/backtesting/__init__.py +9 -0
  197. investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
  198. investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
  199. investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
  200. investing_algorithm_framework/services/__init__.py +127 -10
  201. investing_algorithm_framework/services/configuration_service.py +95 -0
  202. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  203. investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
  204. investing_algorithm_framework/services/market_credential_service.py +40 -0
  205. investing_algorithm_framework/services/metrics/__init__.py +119 -0
  206. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  207. investing_algorithm_framework/services/metrics/beta.py +0 -0
  208. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  209. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  210. investing_algorithm_framework/services/metrics/drawdown.py +218 -0
  211. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  212. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  213. investing_algorithm_framework/services/metrics/generate.py +358 -0
  214. investing_algorithm_framework/services/metrics/mean_daily_return.py +84 -0
  215. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  216. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  217. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  218. investing_algorithm_framework/services/metrics/returns.py +452 -0
  219. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  220. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  221. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  222. investing_algorithm_framework/services/metrics/standard_deviation.py +156 -0
  223. investing_algorithm_framework/services/metrics/trades.py +473 -0
  224. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  225. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  226. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  227. investing_algorithm_framework/services/metrics/volatility.py +118 -0
  228. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  229. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  230. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  231. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  232. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  233. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  234. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  235. investing_algorithm_framework/services/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
  236. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  237. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  238. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  239. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  240. investing_algorithm_framework/services/positions/__init__.py +7 -0
  241. investing_algorithm_framework/services/positions/position_service.py +210 -0
  242. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  243. investing_algorithm_framework/services/repository_service.py +8 -2
  244. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  245. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -0
  246. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  247. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  248. investing_algorithm_framework/services/trade_service/__init__.py +9 -0
  249. investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
  250. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  251. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  252. investing_algorithm_framework-7.25.6.dist-info/METADATA +535 -0
  253. investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
  254. {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
  255. investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
  256. investing_algorithm_framework/app/algorithm.py +0 -410
  257. investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
  258. investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
  259. investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -76
  260. investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
  261. investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
  262. investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
  263. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  264. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -205
  265. investing_algorithm_framework/domain/singleton.py +0 -9
  266. investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
  267. investing_algorithm_framework/infrastructure/models/position/position_cost.py +0 -32
  268. investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
  269. investing_algorithm_framework/infrastructure/repositories/position_cost_repository.py +0 -16
  270. investing_algorithm_framework/infrastructure/services/market_service.py +0 -422
  271. investing_algorithm_framework/services/market_data_service.py +0 -75
  272. investing_algorithm_framework/services/order_service.py +0 -464
  273. investing_algorithm_framework/services/portfolio_service.py +0 -105
  274. investing_algorithm_framework/services/position_cost_service.py +0 -5
  275. investing_algorithm_framework/services/position_service.py +0 -50
  276. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -219
  277. investing_algorithm_framework/setup_logging.py +0 -40
  278. investing_algorithm_framework-1.3.1.dist-info/AUTHORS.md +0 -8
  279. investing_algorithm_framework-1.3.1.dist-info/METADATA +0 -172
  280. investing_algorithm_framework-1.3.1.dist-info/RECORD +0 -103
  281. investing_algorithm_framework-1.3.1.dist-info/top_level.txt +0 -1
  282. {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
@@ -1,86 +1,907 @@
1
- from typing import List
2
- from investing_algorithm_framework.domain import \
3
- TimeUnit, TradingTimeFrame, TradingDataType
1
+ import logging
2
+ from datetime import datetime
3
+ from typing import List, Dict, Any, Union
4
+
5
+ import pandas as pd
6
+
7
+ from investing_algorithm_framework.domain import OperationalException, \
8
+ Position, PositionSize, TimeUnit, StrategyProfile, Trade, \
9
+ DataSource, OrderSide, StopLossRule, TakeProfitRule, Order, \
10
+ INDEX_DATETIME
11
+ from .context import Context
12
+
13
+ logger = logging.getLogger(__name__)
4
14
 
5
15
 
6
16
  class TradingStrategy:
7
- symbols: List[str] = []
8
- time_unit: str = None
17
+ """
18
+ TradingStrategy is the base class for all trading strategies. A trading
19
+ strategy is a set of rules that defines when to buy or sell an asset.
20
+
21
+ Attributes:
22
+ algorithm_id (string): the unique id for your
23
+ combined strategy instances. An algorithm consists out of one or
24
+ more strategy instances that run together. The algorithm_id
25
+ is used to uniquely indentify the combined strategy instances.
26
+ This is id is used in various places in the framework, e.g. for
27
+ backtesting results, logging, monitoring etc.
28
+ time_unit (TimeUnit): the time unit of the strategy that defines
29
+ when the strategy should run e.g. HOUR, DAY, WEEK, MONTH
30
+ interval (int): the interval of the strategy that defines how often
31
+ the strategy should run within the time unit e.g. every 5 hours,
32
+ every 2 days, every 3 weeks, every 4 months
33
+ worker_id ((optional) str): the id of the worker
34
+ strategy_id ((optional) str): the id of the strategy
35
+ decorated ((optional) bool): the decorated function
36
+ data_sources (List[DataSource] optional): the list of data
37
+ sources to use for the strategy. The data sources will be used
38
+ to indentify data providers that will be called to gather data
39
+ and pass to the strategy before its run.
40
+ metadata (optional): Dict[str, Any] - a dictionary
41
+ containing metadata about the strategy. This can be used to
42
+ store additional information about the strategy, such as its
43
+ author, version, description, params etc.
44
+ """
45
+ algorithm_id: str
46
+ time_unit: TimeUnit = None
9
47
  interval: int = None
10
- market: str = None
11
- trading_data_type = None
12
- trading_data_types: list = None
13
- trading_time_frame = None
14
- trading_time_frame_start_date = None
15
- worker_id: str = None
48
+ strategy_id: str = None
16
49
  decorated = None
50
+ data_sources: List[DataSource] = []
51
+ traces = None
52
+ context: Context = None
53
+ metadata: Dict[str, Any] = None
54
+ position_sizes: List[PositionSize] = []
55
+ stop_losses: List[StopLossRule] = []
56
+ take_profits: List[TakeProfitRule] = []
57
+ symbols: List[str] = []
58
+ trading_symbol: str = None
17
59
 
18
60
  def __init__(
19
61
  self,
62
+ algorithm_id=None,
63
+ strategy_id=None,
20
64
  time_unit=None,
21
65
  interval=None,
22
- market=None,
66
+ data_sources=None,
67
+ metadata=None,
68
+ position_sizes=None,
69
+ stop_losses=None,
70
+ take_profits=None,
23
71
  symbols=None,
24
- trading_data_type=None,
25
- trading_data_types=None,
26
- trading_time_frame=None,
27
- trading_time_frame_start_date=None,
28
- worker_id=None,
72
+ trading_symbol=None,
29
73
  decorated=None
30
74
  ):
75
+ if metadata is None:
76
+ metadata = {}
77
+
78
+ self.metadata = metadata
79
+
80
+ if strategy_id is not None:
81
+ self.strategy_id = strategy_id
82
+ else:
83
+ self.strategy_id = self.__class__.__name__
84
+
85
+ # Initialize algorithm_id: use provided value, fall back to class
86
+ # attribute if set, otherwise None
87
+ if algorithm_id is not None:
88
+ self.algorithm_id = algorithm_id
89
+ elif "algorithm_id" in self.metadata:
90
+ self.algorithm_id = self.metadata["algorithm_id"]
91
+ else:
92
+ # Check if class has algorithm_id defined as an actual value
93
+ # (not just a type hint). Type hints result in the type being
94
+ # returned (e.g., str, int), so we check for that.
95
+ class_algorithm_id = getattr(self.__class__, 'algorithm_id', None)
96
+
97
+ # If it's a type (like str, int) or None, it's just a type hint
98
+ # In that case, use the class name as the algorithm_id
99
+ if (class_algorithm_id is None
100
+ or isinstance(class_algorithm_id, type)):
101
+ self.algorithm_id = None
102
+ else:
103
+ self.algorithm_id = class_algorithm_id
31
104
 
32
105
  if time_unit is not None:
33
106
  self.time_unit = TimeUnit.from_value(time_unit)
107
+ else:
108
+ # Check if time_unit is None
109
+ if self.time_unit is None:
110
+ raise OperationalException(
111
+ f"Time unit attribute not set for "
112
+ f"strategy instance {self.strategy_id}"
113
+ )
114
+
115
+ self.time_unit = TimeUnit.from_value(self.time_unit)
34
116
 
35
117
  if interval is not None:
36
118
  self.interval = interval
37
119
 
38
- if market is not None:
39
- self.market = market
120
+ # Initialize data_sources as a new list per instance
121
+ # to avoid sharing the class-level mutable default
122
+ if data_sources is not None:
123
+ self.data_sources = list(data_sources)
124
+ else:
125
+ # Check if class has data_sources defined, copy them
126
+ class_data_sources = getattr(self.__class__, 'data_sources', [])
127
+ self.data_sources = list(class_data_sources) \
128
+ if class_data_sources else []
40
129
 
41
- if time_unit is not None:
42
- self.time_unit = TimeUnit.from_value(time_unit)
130
+ if decorated is not None:
131
+ self.decorated = decorated
132
+
133
+ # Initialize position_sizes as a new list per instance
134
+ if position_sizes is not None:
135
+ self.position_sizes = list(position_sizes)
136
+ else:
137
+ class_position_sizes = getattr(
138
+ self.__class__, 'position_sizes', []
139
+ )
140
+ self.position_sizes = list(class_position_sizes) \
141
+ if class_position_sizes else []
43
142
 
143
+ # Initialize symbols as a new list per instance
44
144
  if symbols is not None:
45
- self.symbols = symbols
145
+ self.symbols = list(symbols)
146
+ else:
147
+ class_symbols = getattr(self.__class__, 'symbols', [])
148
+ self.symbols = list(class_symbols) if class_symbols else []
46
149
 
47
- if trading_time_frame_start_date is not None:
48
- self.trading_time_frame_start_date = trading_time_frame_start_date
150
+ if trading_symbol is not None:
151
+ self.trading_symbol = trading_symbol
49
152
 
50
- if trading_data_type is not None:
51
- self.trading_data_types = [
52
- TradingDataType.from_value(trading_data_type)
53
- ]
153
+ # Check if interval is None
154
+ if self.interval is None:
155
+ raise OperationalException(
156
+ f"Interval not set for strategy instance {self.strategy_id}"
157
+ )
54
158
 
55
- if trading_data_types is not None:
56
- self.trading_data_types = [
57
- TradingDataType.from_value(trading_data_type)
58
- for trading_data_type in trading_data_types
59
- ]
159
+ # Initialize stop_losses as a new list per instance
160
+ if stop_losses is not None:
161
+ self.stop_losses = list(stop_losses)
162
+ else:
163
+ class_stop_losses = getattr(self.__class__, 'stop_losses', [])
164
+ self.stop_losses = list(class_stop_losses) \
165
+ if class_stop_losses else []
60
166
 
61
- if trading_time_frame is not None:
62
- self.trading_time_frame = TradingTimeFrame\
63
- .from_value(trading_time_frame)
167
+ # Initialize take_profits as a new list per instance
168
+ if take_profits is not None:
169
+ self.take_profits = list(take_profits)
170
+ else:
171
+ class_take_profits = getattr(self.__class__, 'take_profits', [])
172
+ self.take_profits = list(class_take_profits) \
173
+ if class_take_profits else []
64
174
 
65
- if decorated is not None:
66
- self.decorated = decorated
175
+ # context initialization
176
+ self._context = None
177
+ self._last_run = None
178
+ self.stop_loss_rules_lookup = {}
179
+ self.take_profit_rules_lookup = {}
180
+ self.position_sizes_lookup = {}
67
181
 
68
- if worker_id is not None:
69
- self.worker_id = worker_id
70
- elif self.decorated:
71
- self.worker_id = decorated.__name__
72
- else:
73
- self.worker_id = self.__class__.__name__
182
+ def generate_buy_signals(
183
+ self, data: Dict[str, Any]
184
+ ) -> Dict[str, pd.Series]:
185
+ """
186
+ Function that needs to be implemented by the user.
187
+ This function should return a pandas Series containing the buy signals.
74
188
 
75
- def run_strategy(self, market_data, algorithm):
76
- self.apply_strategy(algorithm=algorithm, market_data=market_data)
189
+ Args:
190
+ data (Dict[str, Any]): All the data that matched the
191
+ data sources of the strategy.
77
192
 
78
- def apply_strategy(
79
- self,
80
- algorithm,
81
- market_data,
82
- ):
193
+ Returns:
194
+ Dict[str, Series]: A dictionary where the keys are the
195
+ symbols and the values are pandas Series containing
196
+ the buy signals. The series must be a pandas Series with
197
+ a boolean value for each row in the data source, e.g.
198
+ pd.Series([True, False, False, True, ...], index=data.index)
199
+ Also the return dictionary must look like:
200
+ {
201
+ "BTC": pd.Series([...]),
202
+ "ETH": pd.Series([...]),
203
+ ...
204
+ }
205
+ where the symbols are exactly the same as defined in the
206
+ symbols attribute of the strategy.
207
+ """
208
+ raise NotImplementedError(
209
+ "generate_buy_signals method not implemented"
210
+ )
211
+
212
+ def generate_sell_signals(
213
+ self, data: Dict[str, Any]
214
+ ) -> Dict[str, pd.Series]:
215
+ """
216
+ Function that needs to be implemented by the user.
217
+ This function should return a pandas Series containing
218
+ the sell signals.
219
+
220
+ Args:
221
+ data (Dict[str, Any]): All the data that is defined in the
222
+ data sources of the strategy. E.g. if there is a data source
223
+ defined as DataSource(identifier="bitvavo_btc_eur_1h",
224
+ symbol="BTC/EUR", time_frame="1h", data_type=DataType.OHLCV,
225
+ window_size=100, market="BITVAVO"), the data dictionary
226
+ will contain a key "bitvavo_btc_eur_1h"
227
+ with the corresponding data as a polars DataFrame.
228
+
229
+ Returns:
230
+ Dict[str, Series]: A dictionary where the keys are the
231
+ symbols and the values are pandas Series containing
232
+ the sell signals. The series must be a pandas Series with
233
+ a boolean value for each row in the data source, e.g.
234
+ pd.Series([True, False, False, True, ...], index=data.index)
235
+ Also the return dictionary must look like:
236
+ {
237
+ "BTC": pd.Series([...]),
238
+ "ETH": pd.Series([...]),
239
+ ...
240
+ }
241
+ where the symbols are exactly the same as defined in the
242
+ symbols attribute of the strategy.
243
+ """
244
+ raise NotImplementedError(
245
+ "generate_sell_signals method not implemented"
246
+ )
247
+
248
+ def run_strategy(self, context: Context, data: Dict[str, Any]):
249
+ """
250
+ Main function for running the strategy. This function will be called
251
+ by the framework when the trigger of your strategy is met.
252
+
253
+ The flow of this function is as follows:
254
+ 1. Loop through all the symbols defined in the strategy.
255
+ 2. For each symbol, check if there are any open orders.
256
+ A. If there are open orders, skip to the next symbol.
257
+ 3. If there is no open position, generate buy signals
258
+ A. Generate buy signals
259
+ B. If there is a buy signal, retrieve the position size
260
+ defined for the symbol.
261
+ C. If there is a take profit or stop loss rule defined
262
+ for the symbol, register them for the trade that
263
+ has been created as part of the order execution.
264
+ 4. If there is an open position, generate sell signals
265
+ A. Generate sell signals
266
+ B. If there is a sell signal, create a limit order to
267
+ sell the position.
268
+
269
+ During execution of this function, the context and market data
270
+ will be passed to the function. The context is an instance of
271
+ the Context class, this class has various methods to do operations
272
+ with your portfolio, orders, trades, positions and other components.
273
+
274
+ The market data is a dictionary containing all the data retrieved
275
+ from the specified data sources.
276
+
277
+ When buy or sell signals are generated, the strategy will create
278
+ limit orders to buy or sell the assets based on the generated signals.
279
+ For each symbol a corresponding position size must be defined. If
280
+ no position size is defined, an OperationalException will be raised.
281
+
282
+ Before creating new orders, the strategy will check if there are any
283
+ stop losses or take profits for symbol registered. It will
284
+ use the function get_stop_losses and get_take_profits, these functions
285
+ can be overridden by the user to provide custom stop losses and
286
+ take profits logic. The default functions will return the stop losses
287
+ and take profits that are registered for the symbol if any.
288
+
289
+ Args:
290
+ context (Context): The context of the strategy. This is an instance
291
+ of the Context class, this class has various methods to do
292
+ operations with your portfolio, orders, trades, positions and
293
+ other components.
294
+ data (Dict[str, Any]): The data for the strategy.
295
+ This is a dictionary containing all the data retrieved from the
296
+ specified data sources. The keys are either the
297
+ identifiers of the data sources or a generated key, usually
298
+ <target_symbol>_<trading_symbol>_<time_frame> e.g. BTC-EUR_1h.
299
+
300
+ Returns:
301
+ None
302
+ """
303
+ self.context = context
304
+ index_datetime = context.config[INDEX_DATETIME]
305
+ buy_signals = self.generate_buy_signals(data)
306
+ sell_signals = self.generate_sell_signals(data)
307
+
308
+ # Phase 1: Collect all pending buy orders
309
+ pending_buy_orders = []
310
+ portfolio = self.context.get_portfolio()
311
+ available_funds = self.context.get_unallocated()
312
+
313
+ for symbol in self.symbols:
314
+
315
+ if self.has_open_orders(symbol):
316
+ continue
317
+
318
+ if not self.has_position(symbol):
319
+
320
+ if symbol not in buy_signals:
321
+ continue
322
+
323
+ signals = buy_signals[symbol]
324
+ last_row = signals.iloc[-1]
325
+
326
+ if last_row:
327
+ position_size = self.get_position_size(symbol)
328
+ full_symbol = (f"{symbol}/"
329
+ f"{self.context.get_trading_symbol()}")
330
+ price = self.context.get_latest_price(full_symbol)
331
+ amount = position_size.get_size(portfolio, price)
332
+
333
+ pending_buy_orders.append({
334
+ 'symbol': symbol,
335
+ 'full_symbol': full_symbol,
336
+ 'price': price,
337
+ 'amount': amount,
338
+ })
339
+
340
+ # Phase 2: Scale orders proportionally if total exceeds available
341
+ total_required = sum(o['amount'] for o in pending_buy_orders)
342
+
343
+ if total_required > available_funds and total_required > 0:
344
+ scale_factor = available_funds / total_required
345
+ logger.warning(
346
+ f"Total allocation ({total_required:.2f}) exceeds available "
347
+ f"funds ({available_funds:.2f}). Scaling all orders by "
348
+ f"{scale_factor:.2%} to maintain proportional allocation."
349
+ )
350
+ for order in pending_buy_orders:
351
+ order['amount'] *= scale_factor
352
+
353
+ # Phase 3: Execute all pending buy orders
354
+ for order_data in pending_buy_orders:
355
+ symbol = order_data['symbol']
356
+ amount = order_data['amount']
357
+ price = order_data['price']
358
+
359
+ # Skip if amount is too small after scaling
360
+ if amount <= 0.01:
361
+ logger.warning(
362
+ f"Skipping buy order for {symbol}: "
363
+ f"amount too small after scaling ({amount:.4f})"
364
+ )
365
+ continue
366
+
367
+ order_amount = amount / price
368
+ order = self.create_limit_order(
369
+ target_symbol=symbol,
370
+ order_side=OrderSide.BUY,
371
+ amount=order_amount,
372
+ price=price,
373
+ execute=True,
374
+ validate=True,
375
+ sync=True
376
+ )
377
+
378
+ # Retrieve and apply stop loss and take profit rules
379
+ stop_loss_rule = self.get_stop_loss_rule(symbol)
380
+ take_profit_rule = self.get_take_profit_rule(symbol)
381
+
382
+ if stop_loss_rule is not None:
383
+ trade = self.context.get_trade(order_id=order.id)
384
+ self.context.add_stop_loss(
385
+ trade=trade,
386
+ percentage=stop_loss_rule.percentage_threshold,
387
+ trailing=stop_loss_rule.trailing,
388
+ sell_percentage=stop_loss_rule.sell_percentage,
389
+ created_at=index_datetime
390
+ )
391
+
392
+ if take_profit_rule is not None:
393
+ trade = self.context.get_trade(order_id=order.id)
394
+ self.context.add_take_profit(
395
+ trade=trade,
396
+ percentage=take_profit_rule.percentage_threshold,
397
+ trailing=take_profit_rule.trailing,
398
+ sell_percentage=take_profit_rule.sell_percentage,
399
+ created_at=index_datetime
400
+ )
401
+
402
+ # Phase 4: Process sell signals
403
+ for symbol in self.symbols:
404
+
405
+ if self.has_open_orders(symbol):
406
+ continue
407
+
408
+ if self.has_position(symbol):
409
+ # Check in the last row if there is a sell signal
410
+ if symbol not in sell_signals:
411
+ continue
412
+
413
+ signals = sell_signals[symbol]
414
+ last_row = signals.iloc[-1]
415
+
416
+ if last_row:
417
+ position = self.get_position(symbol)
418
+
419
+ if position is None:
420
+ raise OperationalException(
421
+ f"No position found for symbol {symbol} "
422
+ f"in strategy {self.strategy_id}"
423
+ )
424
+
425
+ full_symbol = (f"{symbol}/"
426
+ f"{self.context.get_trading_symbol()}")
427
+ price = self.context.get_latest_price(full_symbol)
428
+ self.create_limit_order(
429
+ target_symbol=symbol,
430
+ order_side=OrderSide.SELL,
431
+ amount=position.amount,
432
+ execute=True,
433
+ validate=True,
434
+ sync=True,
435
+ price=price
436
+ )
437
+
438
+ def apply_strategy(self, context, data):
83
439
  if self.decorated:
84
- self.decorated(algorithm=algorithm, market_data=market_data)
440
+ self.decorated(context=context, data=data)
85
441
  else:
86
442
  raise NotImplementedError("Apply strategy is not implemented")
443
+
444
+ @property
445
+ def strategy_profile(self):
446
+ return StrategyProfile(
447
+ strategy_id=self.strategy_id,
448
+ interval=self.interval,
449
+ time_unit=self.time_unit,
450
+ data_sources=self.data_sources
451
+ )
452
+
453
+ def get_take_profit_rule(self, symbol: str) -> Union[TakeProfitRule, None]:
454
+ """
455
+ Get the take profit definition for a given symbol.
456
+
457
+ Args:
458
+ symbol (str): The symbol of the asset.
459
+
460
+ Returns:
461
+ Union[TakeProfitRule, None]: The take profit rule if found,
462
+ None otherwise.
463
+ """
464
+
465
+ if self.take_profits is None or len(self.take_profits) == 0:
466
+ return None
467
+
468
+ if self.take_profit_rules_lookup == {}:
469
+ for tp in self.take_profits:
470
+ self.take_profit_rules_lookup[tp.symbol] = tp
471
+
472
+ return self.take_profit_rules_lookup.get(symbol, None)
473
+
474
+ def get_stop_loss_rule(self, symbol: str) -> Union[StopLossRule, None]:
475
+ """
476
+ Get the stop loss definition for a given symbol.
477
+
478
+ Args:
479
+ symbol (str): The symbol of the asset.
480
+
481
+ Returns:
482
+ Union[StopLossRule, None]: The stop loss rule if found,
483
+ None otherwise.
484
+ """
485
+
486
+ if self.stop_losses is None or len(self.stop_losses) == 0:
487
+ return None
488
+
489
+ if self.stop_loss_rules_lookup == {}:
490
+ for sl in self.stop_losses:
491
+ self.stop_loss_rules_lookup[sl.symbol] = sl
492
+
493
+ return self.stop_loss_rules_lookup.get(symbol, None)
494
+
495
+ def get_position_size(self, symbol: str) -> Union[PositionSize, None]:
496
+ """
497
+ Get the position size definition for a given symbol.
498
+
499
+ Args:
500
+ symbol (str): The symbol of the asset.
501
+
502
+ Returns:
503
+ Union[PositionSize, None]: The position size if found,
504
+ None otherwise.
505
+ """
506
+
507
+ if self.position_sizes is not None and len(self.position_sizes) == 0:
508
+ raise OperationalException(
509
+ f"No position size defined for symbol "
510
+ f"{symbol} in strategy "
511
+ f"{self.strategy_id}"
512
+ )
513
+
514
+ if self.position_sizes_lookup == {}:
515
+ for ps in self.position_sizes:
516
+ self.position_sizes_lookup[ps.symbol] = ps
517
+
518
+ position_size = self.position_sizes_lookup.get(symbol, None)
519
+
520
+ if position_size is None:
521
+ raise OperationalException(
522
+ f"No position size defined for symbol "
523
+ f"{symbol} in strategy "
524
+ f"{self.strategy_id}"
525
+ )
526
+
527
+ return position_size
528
+
529
+ def on_trade_closed(self, context: Context, trade: Trade):
530
+ pass
531
+
532
+ def on_trade_updated(self, context: Context, trade: Trade):
533
+ pass
534
+
535
+ def on_trade_created(self, context: Context, trade: Trade):
536
+ pass
537
+
538
+ def on_trade_opened(self, context: Context, trade: Trade):
539
+ pass
540
+
541
+ def on_trade_stop_loss_triggered(self, context: Context, trade: Trade):
542
+ pass
543
+
544
+ def on_trade_trailing_stop_loss_triggered(
545
+ self, context: Context, trade: Trade
546
+ ):
547
+ pass
548
+
549
+ def on_trade_take_profit_triggered(
550
+ self, context: Context, trade: Trade
551
+ ):
552
+ pass
553
+
554
+ def on_trade_stop_loss_updated(self, context: Context, trade: Trade):
555
+ pass
556
+
557
+ def on_trade_trailing_stop_loss_updated(
558
+ self, context: Context, trade: Trade
559
+ ):
560
+ pass
561
+
562
+ def on_trade_take_profit_updated(self, context: Context, trade: Trade):
563
+ pass
564
+
565
+ def on_trade_stop_loss_created(self, context: Context, trade: Trade):
566
+ pass
567
+
568
+ def on_trade_trailing_stop_loss_created(
569
+ self, context: Context, trade: Trade
570
+ ):
571
+ pass
572
+
573
+ def on_trade_take_profit_created(self, context: Context, trade: Trade):
574
+ pass
575
+
576
+ @property
577
+ def strategy_identifier(self):
578
+
579
+ if self.strategy_id is not None:
580
+ return self.strategy_id
581
+
582
+ return self.worker_id
583
+
584
+ def has_open_orders(
585
+ self, target_symbol=None, identifier=None, market=None
586
+ ) -> bool:
587
+ """
588
+ Check if there are open orders for a given symbol
589
+
590
+ Args:
591
+ target_symbol (str): The symbol of the asset e.g BTC if the
592
+ asset is BTC/USDT
593
+ identifier (str): The identifier of the portfolio
594
+ market (str): The market of the asset
595
+
596
+ Returns:
597
+ bool: True if there are open orders, False otherwise
598
+ """
599
+ return self.context.has_open_orders(
600
+ target_symbol=target_symbol, identifier=identifier, market=market
601
+ )
602
+
603
+ def create_limit_order(
604
+ self,
605
+ target_symbol,
606
+ price,
607
+ order_side,
608
+ amount=None,
609
+ amount_trading_symbol=None,
610
+ percentage=None,
611
+ percentage_of_portfolio=None,
612
+ percentage_of_position=None,
613
+ precision=None,
614
+ market=None,
615
+ execute=True,
616
+ validate=True,
617
+ sync=True
618
+ ) -> Order:
619
+ """
620
+ Function to create a limit order. This function will create
621
+ a limit order and execute it if the execute parameter is set to True.
622
+ If the validate parameter is set to True, the order will be validated
623
+
624
+ Args:
625
+ target_symbol: The symbol of the asset to trade
626
+ price: The price of the asset
627
+ order_side: The side of the order
628
+ amount (optional): The amount of the asset to trade
629
+ amount_trading_symbol (optional): The amount of the trading
630
+ symbol to trade
631
+ percentage (optional): The percentage of the portfolio to
632
+ allocate to the order
633
+ percentage_of_portfolio (optional): The percentage of
634
+ the portfolio to allocate to the order
635
+ percentage_of_position (optional): The percentage of
636
+ the position to allocate to the order.
637
+ (Only supported for SELL orders)
638
+ precision (optional): The precision of the amount
639
+ market (optional): The market to trade the asset
640
+ execute (optional): Default True. If set to True, the order
641
+ will be executed
642
+ validate (optional): Default True. If set to True, the order
643
+ will be validated
644
+ sync (optional): Default True. If set to True, the created
645
+ order will be synced with the portfolio of the context
646
+
647
+ Returns:
648
+ Order: Instance of the order created
649
+ """
650
+ return self.context.create_limit_order(
651
+ target_symbol=target_symbol,
652
+ price=price,
653
+ order_side=order_side,
654
+ amount=amount,
655
+ amount_trading_symbol=amount_trading_symbol,
656
+ percentage=percentage,
657
+ percentage_of_portfolio=percentage_of_portfolio,
658
+ percentage_of_position=percentage_of_position,
659
+ precision=precision,
660
+ market=market,
661
+ execute=execute,
662
+ validate=validate,
663
+ sync=sync
664
+ )
665
+
666
+ def close_position(
667
+ self, symbol, market=None, identifier=None, precision=None
668
+ ) -> Order:
669
+ """
670
+ Function to close a position. This function will close a position
671
+ by creating a market order to sell the position. If the precision
672
+ parameter is specified, the amount of the order will be rounded
673
+ down to the specified precision.
674
+
675
+ Args:
676
+ symbol: The symbol of the asset
677
+ market: The market of the asset
678
+ identifier: The identifier of the portfolio
679
+ precision: The precision of the amount
680
+
681
+ Returns:
682
+ None
683
+ """
684
+ return self.context.close_position(
685
+ symbol=symbol,
686
+ market=market,
687
+ identifier=identifier,
688
+ precision=precision
689
+ )
690
+
691
+ def get_positions(
692
+ self,
693
+ market=None,
694
+ identifier=None,
695
+ amount_gt=None,
696
+ amount_gte=None,
697
+ amount_lt=None,
698
+ amount_lte=None
699
+ ) -> List[Position]:
700
+ """
701
+ Function to get all positions. This function will return all
702
+ positions that match the specified query parameters. If the
703
+ market parameter is specified, the positions of the specified
704
+ market will be returned. If the identifier parameter is
705
+ specified, the positions of the specified portfolio will be
706
+ returned. If the amount_gt parameter is specified, the positions
707
+ with an amount greater than the specified amount will be returned.
708
+ If the amount_gte parameter is specified, the positions with an
709
+ amount greater than or equal to the specified amount will be
710
+ returned. If the amount_lt parameter is specified, the positions
711
+ with an amount less than the specified amount will be returned.
712
+ If the amount_lte parameter is specified, the positions with an
713
+ amount less than or equal to the specified amount will be returned.
714
+
715
+ Args:
716
+ market: The market of the portfolio where the positions are
717
+ identifier: The identifier of the portfolio
718
+ amount_gt: The amount of the asset must be greater than this
719
+ amount_gte: The amount of the asset must be greater than or
720
+ equal to this
721
+ amount_lt: The amount of the asset must be less than this
722
+ amount_lte: The amount of the asset must be less than or equal
723
+ to this
724
+
725
+ Returns:
726
+ List[Position]: A list of positions that match the query parameters
727
+ """
728
+ return self.context.get_positions(
729
+ market=market,
730
+ identifier=identifier,
731
+ amount_gt=amount_gt,
732
+ amount_gte=amount_gte,
733
+ amount_lt=amount_lt,
734
+ amount_lte=amount_lte
735
+ )
736
+
737
+ def get_trades(self, market=None) -> List[Trade]:
738
+ """
739
+ Function to get all trades. This function will return all trades
740
+ that match the specified query parameters. If the market parameter
741
+ is specified, the trades with the specified market will be returned.
742
+
743
+ Args:
744
+ market: The market of the asset
745
+
746
+ Returns:
747
+ List[Trade]: A list of trades that match the query parameters
748
+ """
749
+ return self.context.get_trades(market)
750
+
751
+ def get_closed_trades(self) -> List[Trade]:
752
+ """
753
+ Function to get all closed trades. This function will return all
754
+ closed trades of the context.
755
+
756
+ Returns:
757
+ List[Trade]: A list of closed trades
758
+ """
759
+ return self.context.get_closed_trades()
760
+
761
+ def get_open_trades(self, target_symbol=None, market=None) -> List[Trade]:
762
+ """
763
+ Function to get all open trades. This function will return all
764
+ open trades that match the specified query parameters. If the
765
+ target_symbol parameter is specified, the open trades with the
766
+ specified target symbol will be returned. If the market parameter
767
+ is specified, the open trades with the specified market will be
768
+ returned.
769
+
770
+ Args:
771
+ target_symbol: The symbol of the asset
772
+ market: The market of the asset
773
+
774
+ Returns:
775
+ List[Trade]: A list of open trades that match the query parameters
776
+ """
777
+ return self.context.get_open_trades(target_symbol, market)
778
+
779
+ def close_trade(self, trade, market=None, precision=None) -> None:
780
+ """
781
+ Function to close a trade. This function will close a trade by
782
+ creating a market order to sell the position. If the precision
783
+ parameter is specified, the amount of the order will be rounded
784
+ down to the specified precision.
785
+
786
+ Args:
787
+ trade: Trade - The trade to close
788
+ market: str - The market of the trade
789
+ precision: float - The precision of the amount
790
+
791
+ Returns:
792
+ None
793
+ """
794
+ self.context.close_trade(
795
+ trade=trade, market=market, precision=precision
796
+ )
797
+
798
+ def get_number_of_positions(self):
799
+ """
800
+ Returns the number of positions that have a positive amount.
801
+
802
+ Returns:
803
+ int: The number of positions
804
+ """
805
+ return self.context.get_number_of_positions()
806
+
807
+ def get_position(
808
+ self, symbol, market=None, identifier=None
809
+ ) -> Position:
810
+ """
811
+ Function to get a position. This function will return the
812
+ position that matches the specified query parameters. If the
813
+ market parameter is specified, the position of the specified
814
+ market will be returned. If the identifier parameter is
815
+ specified, the position of the specified portfolio will be
816
+ returned.
817
+
818
+ Args:
819
+ symbol: The symbol of the asset that represents the position
820
+ market: The market of the portfolio where the position is located
821
+ identifier: The identifier of the portfolio
822
+
823
+ Returns:
824
+ Position: The position that matches the query parameters
825
+ """
826
+ return self.context.get_position(
827
+ symbol=symbol,
828
+ market=market,
829
+ identifier=identifier
830
+ )
831
+
832
+ def has_position(
833
+ self,
834
+ symbol,
835
+ market=None,
836
+ identifier=None,
837
+ amount_gt=0,
838
+ amount_gte=None,
839
+ amount_lt=None,
840
+ amount_lte=None
841
+ ):
842
+ """
843
+ Function to check if a position exists. This function will return
844
+ True if a position exists, False otherwise. This function will check
845
+ if the amount > 0 condition by default.
846
+
847
+ Args:
848
+ param symbol: The symbol of the asset
849
+ param market: The market of the asset
850
+ param identifier: The identifier of the portfolio
851
+ param amount_gt: The amount of the asset must be greater than this
852
+ param amount_gte: The amount of the asset must be greater than
853
+ or equal to this
854
+ param amount_lt: The amount of the asset must be less than this
855
+ param amount_lte: The amount of the asset must be less than
856
+ or equal to this
857
+
858
+ Returns:
859
+ Boolean: True if a position exists, False otherwise
860
+ """
861
+ return self.context.has_position(
862
+ symbol=symbol,
863
+ market=market,
864
+ identifier=identifier,
865
+ amount_gt=amount_gt,
866
+ amount_gte=amount_gte,
867
+ amount_lt=amount_lt,
868
+ amount_lte=amount_lte
869
+ )
870
+
871
+ def has_balance(self, symbol, amount, market=None):
872
+ """
873
+ Function to check if the portfolio has enough balance to
874
+ create an order. This function will return True if the
875
+ portfolio has enough balance to create an order, False
876
+ otherwise.
877
+
878
+ Args:
879
+ symbol: The symbol of the asset
880
+ amount: The amount of the asset
881
+ market: The market of the asset
882
+
883
+ Returns:
884
+ Boolean: True if the portfolio has enough balance
885
+ """
886
+ return self.context.has_balance(symbol, amount, market)
887
+
888
+ def last_run(self) -> datetime:
889
+ """
890
+ Function to get the last run of the strategy
891
+
892
+ Returns:
893
+ DateTime: The last run of the strategy
894
+ """
895
+ return self.context.last_run()
896
+
897
+ def get_data_sources(self):
898
+ """
899
+ Function to get the data sources of the strategy
900
+
901
+ Returns:
902
+ List[DataSource]: The data sources of the strategy
903
+ """
904
+ return self.data_sources
905
+
906
+ def __repr__(self):
907
+ return f"<TradingStrategy(strategy_id={self.strategy_id})>"