investing-algorithm-framework 1.5__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 (276) hide show
  1. investing_algorithm_framework/__init__.py +192 -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 +29 -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 +2220 -379
  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/time_metrics_table.py +80 -0
  31. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  32. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  33. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  34. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  35. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +6 -3
  36. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
  37. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
  38. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  39. investing_algorithm_framework/app/strategy.py +867 -60
  40. investing_algorithm_framework/app/task.py +5 -3
  41. investing_algorithm_framework/app/web/__init__.py +2 -1
  42. investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
  43. investing_algorithm_framework/app/web/controllers/orders.py +3 -2
  44. investing_algorithm_framework/app/web/controllers/positions.py +2 -2
  45. investing_algorithm_framework/app/web/create_app.py +4 -2
  46. investing_algorithm_framework/app/web/schemas/position.py +1 -0
  47. investing_algorithm_framework/cli/__init__.py +0 -0
  48. investing_algorithm_framework/cli/cli.py +231 -0
  49. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  50. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  51. investing_algorithm_framework/cli/initialize_app.py +603 -0
  52. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  53. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  54. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  55. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  56. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  57. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  58. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  59. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  60. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  61. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  62. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  63. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  64. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  65. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  66. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  67. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  68. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  69. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  70. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  71. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  72. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  73. investing_algorithm_framework/cli/validate_backtest_checkpoints.py +197 -0
  74. investing_algorithm_framework/create_app.py +40 -7
  75. investing_algorithm_framework/dependency_container.py +100 -47
  76. investing_algorithm_framework/domain/__init__.py +97 -30
  77. investing_algorithm_framework/domain/algorithm_id.py +69 -0
  78. investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
  79. investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
  80. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
  81. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
  82. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
  83. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  84. investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
  85. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  86. investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
  87. investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
  88. investing_algorithm_framework/domain/config.py +59 -136
  89. investing_algorithm_framework/domain/constants.py +18 -37
  90. investing_algorithm_framework/domain/data_provider.py +334 -0
  91. investing_algorithm_framework/domain/data_structures.py +42 -0
  92. investing_algorithm_framework/domain/exceptions.py +51 -1
  93. investing_algorithm_framework/domain/models/__init__.py +26 -19
  94. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  95. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  96. investing_algorithm_framework/domain/models/data/data_source.py +222 -0
  97. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  98. investing_algorithm_framework/domain/models/event.py +35 -0
  99. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  100. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  101. investing_algorithm_framework/domain/models/order/__init__.py +3 -4
  102. investing_algorithm_framework/domain/models/order/order.py +198 -65
  103. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  104. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  105. investing_algorithm_framework/domain/models/portfolio/__init__.py +6 -2
  106. investing_algorithm_framework/domain/models/portfolio/portfolio.py +98 -3
  107. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -43
  108. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
  109. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  110. investing_algorithm_framework/domain/models/position/position.py +20 -0
  111. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  112. investing_algorithm_framework/domain/models/position/position_snapshot.py +0 -2
  113. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  114. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  115. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  116. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  117. investing_algorithm_framework/domain/models/strategy_profile.py +19 -141
  118. investing_algorithm_framework/domain/models/time_frame.py +94 -98
  119. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  120. investing_algorithm_framework/domain/models/time_unit.py +66 -2
  121. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  122. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  123. investing_algorithm_framework/domain/models/trade/__init__.py +11 -0
  124. investing_algorithm_framework/domain/models/trade/trade.py +389 -0
  125. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  126. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  127. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  128. investing_algorithm_framework/domain/order_executor.py +112 -0
  129. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  130. investing_algorithm_framework/domain/services/__init__.py +11 -0
  131. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  132. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  133. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  134. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  135. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  136. investing_algorithm_framework/domain/strategy.py +1 -29
  137. investing_algorithm_framework/domain/utils/__init__.py +15 -5
  138. investing_algorithm_framework/domain/utils/csv.py +22 -0
  139. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  140. investing_algorithm_framework/domain/utils/dates.py +57 -0
  141. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  142. investing_algorithm_framework/domain/utils/polars.py +53 -0
  143. investing_algorithm_framework/domain/utils/random.py +29 -0
  144. investing_algorithm_framework/download_data.py +244 -0
  145. investing_algorithm_framework/infrastructure/__init__.py +37 -11
  146. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  147. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -0
  148. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  149. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  150. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  151. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
  152. investing_algorithm_framework/infrastructure/models/__init__.py +7 -3
  153. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
  154. investing_algorithm_framework/infrastructure/models/order/order.py +53 -53
  155. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  156. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  157. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
  158. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -2
  159. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -6
  160. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +3 -1
  161. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  162. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  163. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  164. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  165. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  166. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  167. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  168. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  169. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  170. investing_algorithm_framework/infrastructure/repositories/__init__.py +10 -4
  171. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  172. investing_algorithm_framework/infrastructure/repositories/order_repository.py +16 -5
  173. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
  174. investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
  175. investing_algorithm_framework/infrastructure/repositories/repository.py +84 -30
  176. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  177. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  178. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  179. investing_algorithm_framework/infrastructure/services/__init__.py +9 -4
  180. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  181. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -0
  182. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  183. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  184. investing_algorithm_framework/infrastructure/services/backtesting/__init__.py +9 -0
  185. investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
  186. investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
  187. investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
  188. investing_algorithm_framework/services/__init__.py +123 -15
  189. investing_algorithm_framework/services/configuration_service.py +77 -11
  190. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  191. investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
  192. investing_algorithm_framework/services/market_credential_service.py +40 -0
  193. investing_algorithm_framework/services/metrics/__init__.py +119 -0
  194. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  195. investing_algorithm_framework/services/metrics/beta.py +0 -0
  196. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  197. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  198. investing_algorithm_framework/services/metrics/drawdown.py +218 -0
  199. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  200. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  201. investing_algorithm_framework/services/metrics/generate.py +358 -0
  202. investing_algorithm_framework/services/metrics/mean_daily_return.py +84 -0
  203. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  204. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  205. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  206. investing_algorithm_framework/services/metrics/returns.py +452 -0
  207. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  208. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  209. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  210. investing_algorithm_framework/services/metrics/standard_deviation.py +156 -0
  211. investing_algorithm_framework/services/metrics/trades.py +473 -0
  212. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  213. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  214. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  215. investing_algorithm_framework/services/metrics/volatility.py +118 -0
  216. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  217. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  218. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  219. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  220. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  221. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  222. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  223. investing_algorithm_framework/services/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
  224. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  225. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  226. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  227. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  228. investing_algorithm_framework/services/positions/__init__.py +7 -0
  229. investing_algorithm_framework/services/positions/position_service.py +210 -0
  230. investing_algorithm_framework/services/repository_service.py +8 -2
  231. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  232. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -0
  233. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  234. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  235. investing_algorithm_framework/services/trade_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
  237. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  238. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  239. investing_algorithm_framework-7.25.6.dist-info/METADATA +535 -0
  240. investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
  241. {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
  242. investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
  243. investing_algorithm_framework/app/algorithm.py +0 -630
  244. investing_algorithm_framework/domain/models/backtest_profile.py +0 -414
  245. investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
  246. investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
  247. investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -105
  248. investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
  249. investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
  250. investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
  251. investing_algorithm_framework/domain/models/trade.py +0 -78
  252. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  253. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  254. investing_algorithm_framework/domain/singleton.py +0 -9
  255. investing_algorithm_framework/domain/utils/backtesting.py +0 -82
  256. investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
  257. investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
  258. investing_algorithm_framework/infrastructure/services/market_backtest_service.py +0 -360
  259. investing_algorithm_framework/infrastructure/services/market_service.py +0 -410
  260. investing_algorithm_framework/infrastructure/services/performance_service.py +0 -192
  261. investing_algorithm_framework/services/backtest_service.py +0 -268
  262. investing_algorithm_framework/services/market_data_service.py +0 -77
  263. investing_algorithm_framework/services/order_backtest_service.py +0 -122
  264. investing_algorithm_framework/services/order_service.py +0 -752
  265. investing_algorithm_framework/services/portfolio_service.py +0 -164
  266. investing_algorithm_framework/services/portfolio_snapshot_service.py +0 -68
  267. investing_algorithm_framework/services/position_cost_service.py +0 -5
  268. investing_algorithm_framework/services/position_service.py +0 -63
  269. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -225
  270. investing_algorithm_framework-1.5.dist-info/AUTHORS.md +0 -8
  271. investing_algorithm_framework-1.5.dist-info/METADATA +0 -230
  272. investing_algorithm_framework-1.5.dist-info/RECORD +0 -119
  273. investing_algorithm_framework-1.5.dist-info/top_level.txt +0 -1
  274. /investing_algorithm_framework/{infrastructure/services/performance_backtest_service.py → app/reporting/tables/stop_loss_table.py} +0 -0
  275. /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
  276. {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
@@ -0,0 +1,182 @@
1
+ import logging
2
+
3
+ from investing_algorithm_framework.domain import OperationalException, \
4
+ AbstractPortfolioSyncService, ENVIRONMENT, Environment
5
+ from investing_algorithm_framework.services.trade_service import TradeService
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class PortfolioSyncService(AbstractPortfolioSyncService):
11
+ """
12
+ Service to sync the portfolio with the exchange.
13
+
14
+ This service will sync the portfolio with the exchange
15
+
16
+ Attributes:
17
+ trade_service: TradeService object
18
+ configuration_service: ConfigurationService object
19
+ order_service: OrderService object
20
+ position_repository: PositionRepository object
21
+ portfolio_repository: PortfolioRepository object
22
+ market_credential_service: MarketCredentialService object
23
+ portfolio_configuration_service: PortfolioConfigurationService object
24
+ portfolio_provider_lookup: PortfolioProviderLookup object
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ trade_service: TradeService,
30
+ configuration_service,
31
+ order_service,
32
+ position_repository,
33
+ portfolio_repository,
34
+ portfolio_configuration_service,
35
+ market_credential_service,
36
+ portfolio_provider_lookup
37
+ ):
38
+ self.trade_service = trade_service
39
+ self.configuration_service = configuration_service
40
+ self.order_service = order_service
41
+ self.position_repository = position_repository
42
+ self.portfolio_repository = portfolio_repository
43
+ self.market_credential_service = market_credential_service
44
+ self.portfolio_configuration_service = portfolio_configuration_service
45
+ self.portfolio_provider_lookup = portfolio_provider_lookup
46
+
47
+ def sync_unallocated(self, portfolio):
48
+ """
49
+ Method to sync the unallocated balance of the portfolio with the
50
+ available balance on the exchange. This method will retrieve the
51
+ available balance of the portfolio from the exchange and update the
52
+ unallocated balance of the portfolio accordingly.
53
+
54
+ If the portfolio already exists (exists in the database),
55
+ then a check is done if the exchange has the available
56
+ balance of the portfolio unallocated balance. If the exchange
57
+ does not have the available balance of the portfolio,
58
+ an OperationalException will be raised.
59
+
60
+ If the portfolio does not exist, the portfolio will be created with
61
+ the unallocated balance of the portfolio set to the available
62
+ balance on the exchange. If also a initial balance is set in
63
+ the portfolio configuration, the unallocated balance will be set
64
+ to the initial balance (given the balance is available on
65
+ the exchange). If the initial balance is not set, the
66
+ unallocated balance will be set to the available balance
67
+ on the exchange.
68
+
69
+ Args:
70
+ portfolio: Portfolio object
71
+
72
+ Returns:
73
+ Portfolio object
74
+ """
75
+ market_credential = self.market_credential_service.get(
76
+ portfolio.market
77
+ )
78
+
79
+ if market_credential is None:
80
+ raise OperationalException(
81
+ f"No market credential found for market "
82
+ f"{portfolio.market}. Cannot sync unallocated amount."
83
+ )
84
+
85
+ portfolio_provider = self.portfolio_provider_lookup\
86
+ .get_portfolio_provider(portfolio.market)
87
+ position = portfolio_provider.get_position(
88
+ portfolio, portfolio.trading_symbol, market_credential
89
+ )
90
+
91
+ if not portfolio.initialized:
92
+ # Check if the portfolio has an initial balance set
93
+ if portfolio.initial_balance is not None:
94
+ available = position.amount
95
+
96
+ if portfolio.initial_balance > available:
97
+ raise OperationalException(
98
+ "The initial balance of the " +
99
+ "portfolio configuration " +
100
+ f"({portfolio.initial_balance} "
101
+ f"{portfolio.trading_symbol}) is more " +
102
+ "than the available balance on the exchange. " +
103
+ "Please make sure that the initial balance of " +
104
+ "the portfolio configuration is less " +
105
+ "than the available balance on the " +
106
+ f"exchange {available} {portfolio.trading_symbol}."
107
+ )
108
+ else:
109
+ unallocated = portfolio.initial_balance
110
+ else:
111
+ # If the portfolio does not have an initial balance
112
+ # set, get the available balance on the exchange
113
+ if position is None:
114
+ raise OperationalException(
115
+ f"There is no available balance on the exchange for "
116
+ f"{portfolio.trading_symbol.upper()} on market "
117
+ f"{portfolio.market}. Please make sure that you have "
118
+ f"an available balance on the exchange for "
119
+ f"{portfolio.trading_symbol.upper()} on market "
120
+ f"{portfolio.market}."
121
+ )
122
+ else:
123
+ unallocated = position.amount
124
+
125
+ update_data = {
126
+ "unallocated": unallocated,
127
+ "net_size": unallocated,
128
+ "initialized": True
129
+ }
130
+ portfolio = self.portfolio_repository.update(
131
+ portfolio.id, update_data
132
+ )
133
+
134
+ # Update also a trading symbol position
135
+ trading_symbol_position = self.position_repository.find(
136
+ {
137
+ "symbol": portfolio.trading_symbol,
138
+ "portfolio_id": portfolio.id
139
+ }
140
+ )
141
+ self.position_repository.update(
142
+ trading_symbol_position.id, {"amount": unallocated}
143
+ )
144
+
145
+ else:
146
+ # Check if the portfolio unallocated balance is
147
+ # available on the exchange
148
+ if portfolio.unallocated > 0:
149
+ if position is None or portfolio.unallocated > position.amount:
150
+ raise OperationalException(
151
+ f"Out of sync: the unallocated balance"
152
+ " of the exiting portfolio is more than the available"
153
+ " balance on the exchange. Please make sure"
154
+ " that you have at least "
155
+ f"{portfolio.unallocated}"
156
+ f" {portfolio.trading_symbol.upper()} available"
157
+ " on the exchange."
158
+ )
159
+
160
+ return portfolio
161
+
162
+ def sync_orders(self, portfolio):
163
+ """
164
+ Function to sync all local orders with the orders on the exchange.
165
+ This method will go over all local open orders and check if they are
166
+ changed on the exchange. If they are, the local order will be
167
+ updated to match the status on the exchange.
168
+
169
+ Args:
170
+ portfolio: Portfolio object
171
+
172
+ Returns:
173
+ None
174
+ """
175
+
176
+ config = self.configuration_service.get_config()
177
+
178
+ if ENVIRONMENT in config \
179
+ and Environment.BACKTEST.equals(config[ENVIRONMENT]):
180
+ return
181
+
182
+ self.order_service.check_pending_orders(portfolio)
@@ -0,0 +1,7 @@
1
+ from .position_service import PositionService
2
+ from .position_snapshot_service import PositionSnapshotService
3
+
4
+ __all__ = [
5
+ "PositionService",
6
+ "PositionSnapshotService"
7
+ ]
@@ -0,0 +1,210 @@
1
+ import logging
2
+
3
+ from investing_algorithm_framework.services.repository_service import \
4
+ RepositoryService
5
+
6
+
7
+ logger = logging.getLogger("investing_algorithm_framework")
8
+
9
+
10
+ class PositionService(RepositoryService):
11
+
12
+ def __init__(self, repository, portfolio_repository):
13
+ """
14
+ Initialize the PositionService.
15
+
16
+ Args:
17
+ repository (Repository): The repository to use for storing
18
+ positions.
19
+ portfolio_repository (Repository): The repository to use for
20
+ storing portfolios.
21
+ """
22
+ super().__init__(repository)
23
+ self.portfolio_repository = portfolio_repository
24
+
25
+ def update(self, position_id, data):
26
+ """
27
+ Function to update a position.
28
+
29
+ Args:
30
+ position_id (str): The id of the position to update.
31
+ data (dict): The data to update the position with.
32
+
33
+ Returns:
34
+ Position: The updated position.
35
+ """
36
+ position = self.get(position_id)
37
+ logger.info(
38
+ f"Updating position {position_id} ({position.get_symbol()}) "
39
+ f"with data: {data}"
40
+ )
41
+ return super().update(position_id, data)
42
+
43
+ def update_positions_with_created_buy_order(self, order):
44
+ """
45
+ Function to update positions with created order.
46
+ If the order is filled then also the amount of the position
47
+ is updated.
48
+
49
+ Args:
50
+ order (Order): The order that has been created.
51
+
52
+ Returns:
53
+ None
54
+ """
55
+ position = self.get(order.position_id)
56
+ portfolio = self.portfolio_repository.get(position.portfolio_id)
57
+ size = order.get_size()
58
+ filled = order.get_filled()
59
+
60
+ logger.info(
61
+ f"Syncing trading symbol {portfolio.get_trading_symbol()} "
62
+ "position with created buy "
63
+ f"order {order.get_id()} with size {size}"
64
+ )
65
+ trading_symbol_position = self.find(
66
+ {
67
+ "portfolio": portfolio.id,
68
+ "symbol": portfolio.trading_symbol
69
+ }
70
+ )
71
+ self.update(
72
+ trading_symbol_position.id,
73
+ {
74
+ "amount": trading_symbol_position.get_amount() - size
75
+ }
76
+ )
77
+
78
+ if filled > 0:
79
+ logger.info(
80
+ f"Syncing position {position.get_symbol()} with created buy "
81
+ f"order {order.get_id()} with filled size {order.get_filled()}"
82
+ )
83
+ self.update(
84
+ position.id,
85
+ {
86
+ "amount": position.get_amount() + order.get_filled(),
87
+ "cost": position.get_cost() + size,
88
+ }
89
+ )
90
+
91
+ def update_positions_with_buy_order_filled(self, order, filled_amount):
92
+ """
93
+ Function to update positions with filled order.
94
+
95
+ Args:
96
+ order (Order): The order that has been filled.
97
+ filled_amount (float): The amount that has been filled.
98
+
99
+ Returns:
100
+ None
101
+ """
102
+ # Calculate the filled size
103
+ filled_size = filled_amount * order.get_price()
104
+
105
+ if filled_amount <= 0:
106
+ return
107
+
108
+ logger.info(
109
+ f"Syncing position with filled buy "
110
+ f"order {order.get_id()} with filled amount "
111
+ f"{filled_amount}"
112
+ )
113
+
114
+ # Update the position
115
+ position = self.get(order.position_id)
116
+ self.update(
117
+ position.id,
118
+ {
119
+ "amount": position.get_amount() + filled_amount,
120
+ "cost":
121
+ position.get_cost() + filled_size
122
+ }
123
+ )
124
+
125
+ def update_positions_with_created_sell_order(self, order):
126
+ """
127
+ Function to update positions with created order.
128
+ If the order is filled then also the amount of the position
129
+ is updated.
130
+
131
+ Args:
132
+ order (Order): The order that has been created.
133
+
134
+ Returns:
135
+ None
136
+ """
137
+ position = self.get(order.position_id)
138
+ portfolio = self.portfolio_repository.get(position.portfolio_id)
139
+ filled = order.get_filled()
140
+ filled_size = filled * order.get_price()
141
+
142
+ logger.info(
143
+ f"Syncing position {position.get_symbol()} "
144
+ "with created sell "
145
+ f"order {order.get_id()} with amount {order.get_amount()}"
146
+ )
147
+ self.update(
148
+ position.id,
149
+ {
150
+ "amount": position.get_amount() - order.get_amount(),
151
+ }
152
+ )
153
+
154
+ if filled > 0:
155
+
156
+ logger.info(
157
+ f"Syncing trading symbol {portfolio.get_trading_symbol()} "
158
+ "position with created sell "
159
+ f"order {order.get_id()} with filled size {filled_size}"
160
+ )
161
+ trading_symbol_position = self.find(
162
+ {
163
+ "portfolio": portfolio.id,
164
+ "symbol": portfolio.trading_symbol
165
+ }
166
+ )
167
+ self.update(
168
+ trading_symbol_position.id,
169
+ {
170
+ "amount":
171
+ trading_symbol_position.get_amount() + filled_size
172
+ }
173
+ )
174
+
175
+ def update_positions_with_sell_filled_order(self, order, filled_amount):
176
+ """
177
+ Function to update positions with filled order.
178
+
179
+ Args:
180
+ order:
181
+ filled_amount:
182
+
183
+ Returns:
184
+
185
+ """
186
+ position = self.get(order.position_id)
187
+ portfolio = self.portfolio_repository.get(position.portfolio_id)
188
+ trading_symbol_position = self.find(
189
+ {
190
+ "portfolio": portfolio.id,
191
+ "symbol": portfolio.trading_symbol
192
+ }
193
+ )
194
+ filled_size = filled_amount * order.get_price()
195
+
196
+ logger.info(
197
+ "Syncing trading symbol position "
198
+ f"{portfolio.get_trading_symbol()} "
199
+ f"with filled sell "
200
+ f"order {order.get_id()} with filled size "
201
+ f"{filled_size} {portfolio.get_trading_symbol()}"
202
+ )
203
+ # Update the trading symbol position
204
+ self.update(
205
+ trading_symbol_position.id,
206
+ {
207
+ "amount":
208
+ trading_symbol_position.get_amount() + filled_size
209
+ }
210
+ )
@@ -3,8 +3,8 @@ class RepositoryService:
3
3
  def __init__(self, repository):
4
4
  self.repository = repository
5
5
 
6
- def create(self, data):
7
- return self.repository.create(data)
6
+ def create(self, data, save=True):
7
+ return self.repository.create(data, save=save)
8
8
 
9
9
  def get(self, object_id):
10
10
  return self.repository.get(object_id)
@@ -32,3 +32,9 @@ class RepositoryService:
32
32
 
33
33
  def exists(self, query_params):
34
34
  return self.repository.exists(query_params)
35
+
36
+ def save(self, object):
37
+ return self.repository.save(object)
38
+
39
+ def save_all(self, objects):
40
+ return self.repository.save_objects(objects)
@@ -0,0 +1,9 @@
1
+ from .trade_order_evaluator import TradeOrderEvaluator
2
+ from .backtest_trade_oder_evaluator import BacktestTradeOrderEvaluator
3
+ from .default_trade_order_evaluator import DefaultTradeOrderEvaluator
4
+
5
+ __all__ = [
6
+ "TradeOrderEvaluator",
7
+ "BacktestTradeOrderEvaluator",
8
+ "DefaultTradeOrderEvaluator"
9
+ ]
@@ -0,0 +1,117 @@
1
+ from typing import List, Dict
2
+
3
+ import polars as pl
4
+
5
+ from investing_algorithm_framework.domain import OrderSide, OrderStatus, \
6
+ Trade, Order
7
+ from .trade_order_evaluator import TradeOrderEvaluator
8
+
9
+
10
+ class BacktestTradeOrderEvaluator(TradeOrderEvaluator):
11
+
12
+ def evaluate(
13
+ self,
14
+ open_trades: List[Trade],
15
+ open_orders: List[Order],
16
+ ohlcv_data: Dict[str, pl.DataFrame]
17
+ ):
18
+ """
19
+ Evaluate trades and orders based on OHLCV data.
20
+
21
+ Args:
22
+ open_orders (List[Order]): List of open Order objects.
23
+ open_trades (List[Trade]): List of open Trade objects.
24
+ ohlcv_data (dict[str, pl.DataFrame]): Mapping of
25
+ symbol -> OHLCV Polars DataFrame.
26
+
27
+ Returns:
28
+ List[dict]: Updated trades with latest prices and execution status.
29
+ """
30
+ # First check pending orders
31
+ for open_order in open_orders:
32
+ data = ohlcv_data.get(open_order.symbol)
33
+
34
+ if data is None or data.is_empty():
35
+ continue
36
+
37
+ self._check_has_executed(open_order, data)
38
+
39
+ if len(open_trades) > 0:
40
+ for open_trade in open_trades:
41
+ data = ohlcv_data.get(open_trade.symbol)
42
+
43
+ if data is None or data.is_empty():
44
+ continue
45
+
46
+ # Get last row of data
47
+ last_row = data.tail(1)
48
+ update_data = {
49
+ "last_reported_price": last_row["Close"][0],
50
+ "last_reported_price_datetime": last_row["Datetime"][0],
51
+ "updated_at": last_row["Datetime"][0]
52
+ }
53
+ open_trade.update(update_data)
54
+
55
+ self.trade_service.save_all(open_trades)
56
+ self._check_take_profits()
57
+ self._check_stop_losses()
58
+
59
+ def _check_has_executed(self, order, ohlcv_df):
60
+ """
61
+ Check if the order has been executed based on OHLCV data.
62
+
63
+ BUY ORDER filled Rules:
64
+ - Only uses prices after the last update_at of the order.
65
+ - If the lowest low price of the series is below or equal
66
+ to the order price, e.g. if you buy asset at price 100
67
+ and the low price of the series is 99, then the order is filled.
68
+
69
+ SELL ORDER filled Rules:
70
+ - Only uses prices after the last update_at of the order.
71
+ - If the highest high price of the series is above or equal
72
+ to the order price, e.g. if you sell asset at price 100
73
+ and the high price of the series is 101, then the order is filled.
74
+
75
+ Args:
76
+ order (Order): Order.
77
+ ohlcv_df (pl.DataFrame): OHLCV DataFrame for the symbol.
78
+
79
+ Returns:
80
+ None
81
+ """
82
+
83
+ if ohlcv_df.is_empty():
84
+ return
85
+
86
+ # Extract attributes from the order object
87
+ updated_at = order.updated_at
88
+ order_side = order.order_side
89
+ order_price = order.price
90
+ ohlcv_data_after_order = ohlcv_df.filter(
91
+ pl.col('Datetime') >= updated_at
92
+ )
93
+
94
+ if ohlcv_data_after_order.is_empty():
95
+ return
96
+
97
+ if OrderSide.BUY.equals(order_side):
98
+ # Check if the low price drops below or equals the order price
99
+ if (ohlcv_data_after_order['Low'] <= order_price).any():
100
+ self.order_service.update(
101
+ order.id, {
102
+ 'status': OrderStatus.CLOSED.value,
103
+ 'remaining': 0,
104
+ 'filled': order.amount
105
+ }
106
+ )
107
+
108
+ elif OrderSide.SELL.equals(order_side):
109
+ # Check if the high price goes above or equals the order price
110
+ if (ohlcv_data_after_order['High'] >= order_price).any():
111
+ self.order_service.update(
112
+ order.id, {
113
+ 'status': OrderStatus.CLOSED.value,
114
+ 'remaining': 0,
115
+ 'filled': order.amount
116
+ }
117
+ )
@@ -0,0 +1,51 @@
1
+ from typing import List, Dict
2
+
3
+ import polars as pl
4
+
5
+ from investing_algorithm_framework.domain import Trade, Order, INDEX_DATETIME
6
+ from .trade_order_evaluator import TradeOrderEvaluator
7
+
8
+
9
+ class DefaultTradeOrderEvaluator(TradeOrderEvaluator):
10
+
11
+ def evaluate(
12
+ self,
13
+ open_trades: List[Trade],
14
+ open_orders: List[Order],
15
+ ohlcv_data: Dict[str, pl.DataFrame]
16
+ ):
17
+ """
18
+ Evaluate trades and orders based on OHLCV data.
19
+
20
+ Args:
21
+ open_orders (List[Order]): List of open Order objects.
22
+ open_trades (List[Trade]): List of open Trade objects.
23
+ ohlcv_data (dict[str, pl.DataFrame]): Mapping of
24
+ symbol -> OHLCV Polars DataFrame.
25
+
26
+ Returns:
27
+ List[dict]: Updated trades with latest prices and execution status.
28
+ """
29
+ self.order_service.check_pending_orders()
30
+ current_date = self.configuration_service.config[INDEX_DATETIME]
31
+
32
+ if len(open_trades) > 0:
33
+ for open_trade in open_trades:
34
+ data = ohlcv_data.get(open_trade.symbol)
35
+
36
+ if data is None or data.is_empty():
37
+ continue
38
+
39
+ # Get last row of data
40
+ last_row = data.tail(1)
41
+ last_row_date = last_row["Datetime"][0]
42
+ update_data = {
43
+ "last_reported_price": last_row["Close"][0],
44
+ "last_reported_price_datetime": last_row_date,
45
+ "updated_at": current_date
46
+ }
47
+ open_trade.update(update_data)
48
+
49
+ self.trade_service.save_all(open_trades)
50
+ self._check_take_profits()
51
+ self._check_stop_losses()
@@ -0,0 +1,80 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Dict
3
+ import polars as pl
4
+ from investing_algorithm_framework.domain import Trade, Order, INDEX_DATETIME
5
+
6
+
7
+ class TradeOrderEvaluator(ABC):
8
+
9
+ def __init__(
10
+ self,
11
+ trade_service,
12
+ trade_stop_loss_service,
13
+ trade_take_profit_service,
14
+ order_service,
15
+ configuration_service=None
16
+ ):
17
+ self.trade_service = trade_service
18
+ self.trade_stop_loss_service = trade_stop_loss_service
19
+ self.trade_take_profit_service = trade_take_profit_service
20
+ self.order_service = order_service
21
+ self.configuration_service = configuration_service
22
+
23
+ @abstractmethod
24
+ def evaluate(
25
+ self,
26
+ open_trades: List[Trade],
27
+ open_orders: List[Order],
28
+ ohlcv_data: Dict[str, pl.DataFrame]
29
+ ):
30
+ """
31
+ Evaluate trades and orders based on OHLCV data. This
32
+ function is responsible for updating open orders and open trades.
33
+ The evaluation process includes checking if orders have been executed
34
+ and updating the trades with the latest prices and execution status.
35
+ Additionally, it may trigger stop-loss and take-profit orders
36
+ based on the current market conditions.
37
+
38
+ Args:
39
+ open_trades (List[Trade]): List of open Trade objects.
40
+ open_orders (List[Order]): List of open Order objects.
41
+ ohlcv_data (dict[str, pl.DataFrame]): Mapping of
42
+ symbol -> OHLCV Polars DataFrame.
43
+
44
+ Returns:
45
+ List[dict]: Updated trades with latest prices and execution status.
46
+ """
47
+ pass
48
+
49
+ def _check_take_profits(self):
50
+ current_date = self.configuration_service.config[INDEX_DATETIME]
51
+ take_profits_orders_data = self.trade_service \
52
+ .get_triggered_take_profit_orders()
53
+
54
+ for take_profit_order in take_profits_orders_data:
55
+ take_profits = take_profit_order["take_profits"]
56
+ self.order_service.create(take_profit_order)
57
+ self.trade_take_profit_service.mark_triggered(
58
+ [
59
+ take_profit.get("take_profit_id")
60
+ for take_profit in take_profits
61
+ ],
62
+ trigger_date=current_date
63
+ )
64
+
65
+ def _check_stop_losses(self):
66
+ current_date = self.configuration_service.config[INDEX_DATETIME]
67
+ stop_losses_orders_data = self.trade_service \
68
+ .get_triggered_stop_loss_orders()
69
+
70
+ for stop_loss_order in stop_losses_orders_data:
71
+ stop_losses = stop_loss_order["stop_losses"]
72
+
73
+ self.order_service.create(stop_loss_order)
74
+ self.trade_stop_loss_service.mark_triggered(
75
+ [
76
+ stop_loss.get("stop_loss_id") for stop_loss in
77
+ stop_losses
78
+ ],
79
+ trigger_date=current_date
80
+ )
@@ -0,0 +1,9 @@
1
+ from .trade_service import TradeService
2
+ from .trade_stop_loss_service import TradeStopLossService
3
+ from .trade_take_profit_service import TradeTakeProfitService
4
+
5
+ __all__ = [
6
+ "TradeService",
7
+ "TradeStopLossService",
8
+ "TradeTakeProfitService"
9
+ ]