investing-algorithm-framework 7.19.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of investing-algorithm-framework might be problematic. Click here for more details.

Files changed (260) hide show
  1. investing_algorithm_framework/__init__.py +197 -0
  2. investing_algorithm_framework/app/__init__.py +47 -0
  3. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  4. investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
  5. investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
  6. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  7. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  8. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  9. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  10. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  11. investing_algorithm_framework/app/app.py +2204 -0
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1667 -0
  14. investing_algorithm_framework/app/eventloop.py +590 -0
  15. investing_algorithm_framework/app/reporting/__init__.py +27 -0
  16. investing_algorithm_framework/app/reporting/ascii.py +921 -0
  17. investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
  18. investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
  19. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  20. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  21. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
  22. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  23. investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
  24. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  25. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
  26. investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
  27. investing_algorithm_framework/app/reporting/generate.py +185 -0
  28. investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
  29. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
  30. investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
  31. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  32. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  33. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  34. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  35. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  36. investing_algorithm_framework/app/stateless/__init__.py +35 -0
  37. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
  38. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
  39. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
  40. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
  41. investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
  42. investing_algorithm_framework/app/strategy.py +675 -0
  43. investing_algorithm_framework/app/task.py +41 -0
  44. investing_algorithm_framework/app/web/__init__.py +5 -0
  45. investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
  46. investing_algorithm_framework/app/web/controllers/orders.py +20 -0
  47. investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
  48. investing_algorithm_framework/app/web/controllers/positions.py +18 -0
  49. investing_algorithm_framework/app/web/create_app.py +20 -0
  50. investing_algorithm_framework/app/web/error_handler.py +59 -0
  51. investing_algorithm_framework/app/web/responses.py +20 -0
  52. investing_algorithm_framework/app/web/run_strategies.py +4 -0
  53. investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
  54. investing_algorithm_framework/app/web/schemas/order.py +12 -0
  55. investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
  56. investing_algorithm_framework/app/web/schemas/position.py +15 -0
  57. investing_algorithm_framework/app/web/setup_cors.py +6 -0
  58. investing_algorithm_framework/cli/__init__.py +0 -0
  59. investing_algorithm_framework/cli/cli.py +207 -0
  60. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
  61. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  62. investing_algorithm_framework/cli/initialize_app.py +603 -0
  63. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  64. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  65. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  66. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  67. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  68. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  69. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  70. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  71. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  72. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  73. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  74. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  75. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  76. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  77. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  78. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  79. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  80. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  81. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  82. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  83. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  84. investing_algorithm_framework/create_app.py +54 -0
  85. investing_algorithm_framework/dependency_container.py +155 -0
  86. investing_algorithm_framework/domain/__init__.py +148 -0
  87. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  88. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  92. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  93. investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
  94. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  95. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  96. investing_algorithm_framework/domain/config.py +111 -0
  97. investing_algorithm_framework/domain/constants.py +83 -0
  98. investing_algorithm_framework/domain/data_provider.py +334 -0
  99. investing_algorithm_framework/domain/data_structures.py +42 -0
  100. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  101. investing_algorithm_framework/domain/exceptions.py +112 -0
  102. investing_algorithm_framework/domain/models/__init__.py +43 -0
  103. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  104. investing_algorithm_framework/domain/models/base_model.py +25 -0
  105. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  106. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  107. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  108. investing_algorithm_framework/domain/models/event.py +35 -0
  109. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  110. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  111. investing_algorithm_framework/domain/models/order/__init__.py +6 -0
  112. investing_algorithm_framework/domain/models/order/order.py +384 -0
  113. investing_algorithm_framework/domain/models/order/order_side.py +36 -0
  114. investing_algorithm_framework/domain/models/order/order_status.py +37 -0
  115. investing_algorithm_framework/domain/models/order/order_type.py +30 -0
  116. investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
  117. investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
  118. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
  119. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  120. investing_algorithm_framework/domain/models/position/__init__.py +4 -0
  121. investing_algorithm_framework/domain/models/position/position.py +68 -0
  122. investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
  123. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  124. investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
  125. investing_algorithm_framework/domain/models/time_frame.py +153 -0
  126. investing_algorithm_framework/domain/models/time_interval.py +124 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +149 -0
  128. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  129. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  130. investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +388 -0
  132. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
  133. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  134. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
  135. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
  136. investing_algorithm_framework/domain/order_executor.py +112 -0
  137. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  138. investing_algorithm_framework/domain/positions/__init__.py +4 -0
  139. investing_algorithm_framework/domain/positions/position_size.py +41 -0
  140. investing_algorithm_framework/domain/services/__init__.py +11 -0
  141. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  142. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  143. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  144. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  145. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  146. investing_algorithm_framework/domain/stateless_actions.py +7 -0
  147. investing_algorithm_framework/domain/strategy.py +44 -0
  148. investing_algorithm_framework/domain/utils/__init__.py +27 -0
  149. investing_algorithm_framework/domain/utils/csv.py +104 -0
  150. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  151. investing_algorithm_framework/domain/utils/dates.py +57 -0
  152. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  153. investing_algorithm_framework/domain/utils/polars.py +53 -0
  154. investing_algorithm_framework/domain/utils/random.py +41 -0
  155. investing_algorithm_framework/domain/utils/signatures.py +17 -0
  156. investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
  157. investing_algorithm_framework/domain/utils/synchronized.py +12 -0
  158. investing_algorithm_framework/download_data.py +108 -0
  159. investing_algorithm_framework/infrastructure/__init__.py +50 -0
  160. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  161. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  162. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  163. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  164. investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
  165. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
  166. investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
  167. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  168. investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
  169. investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
  170. investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
  171. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  172. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  173. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
  174. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  175. investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
  176. investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
  177. investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
  178. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  179. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  180. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  181. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
  182. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
  183. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  184. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  185. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  186. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  187. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  188. investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
  189. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  190. investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
  191. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
  192. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  193. investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
  194. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  195. investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
  196. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  197. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
  198. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
  199. investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
  200. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  201. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  202. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  203. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  204. investing_algorithm_framework/services/__init__.py +132 -0
  205. investing_algorithm_framework/services/backtesting/__init__.py +5 -0
  206. investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
  207. investing_algorithm_framework/services/configuration_service.py +96 -0
  208. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  209. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  210. investing_algorithm_framework/services/market_credential_service.py +40 -0
  211. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  212. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  213. investing_algorithm_framework/services/metrics/beta.py +0 -0
  214. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  215. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  216. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  217. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  218. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  219. investing_algorithm_framework/services/metrics/generate.py +358 -0
  220. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  221. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  222. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  223. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  224. investing_algorithm_framework/services/metrics/returns.py +452 -0
  225. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  226. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  227. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  228. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  229. investing_algorithm_framework/services/metrics/trades.py +500 -0
  230. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  231. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  232. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  233. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  234. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  235. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  237. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  238. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  239. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  240. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  241. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
  242. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  243. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  244. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  245. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  246. investing_algorithm_framework/services/positions/__init__.py +7 -0
  247. investing_algorithm_framework/services/positions/position_service.py +210 -0
  248. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  249. investing_algorithm_framework/services/repository_service.py +40 -0
  250. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  251. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
  252. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
  253. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
  254. investing_algorithm_framework/services/trade_service/__init__.py +3 -0
  255. investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
  256. investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
  257. investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
  258. investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
  259. investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
  260. investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,21 @@
1
+ from .order_repository import SQLOrderRepository
2
+ from .order_metadata_repository import SQLOrderMetadataRepository
3
+ from .portfolio_repository import SQLPortfolioRepository
4
+ from .portfolio_snapshot_repository import SQLPortfolioSnapshotRepository
5
+ from .position_repository import SQLPositionRepository
6
+ from .position_snapshot_repository import SQLPositionSnapshotRepository
7
+ from .trade_repository import SQLTradeRepository
8
+ from .trade_stop_loss_repository import SQLTradeStopLossRepository
9
+ from .trade_take_profit_repository import SQLTradeTakeProfitRepository
10
+
11
+ __all__ = [
12
+ "SQLOrderRepository",
13
+ "SQLPositionRepository",
14
+ "SQLPositionSnapshotRepository",
15
+ "SQLPortfolioRepository",
16
+ "SQLPortfolioSnapshotRepository",
17
+ "SQLTradeRepository",
18
+ "SQLTradeTakeProfitRepository",
19
+ "SQLTradeStopLossRepository",
20
+ "SQLOrderMetadataRepository"
21
+ ]
@@ -0,0 +1,17 @@
1
+ from investing_algorithm_framework.infrastructure.models import \
2
+ SQLOrderMetadata
3
+ from .repository import Repository
4
+
5
+
6
+ class SQLOrderMetadataRepository(Repository):
7
+ base_class = SQLOrderMetadata
8
+ DEFAULT_NOT_FOUND_MESSAGE = "The requested order metadata was not found"
9
+
10
+ def _apply_query_params(self, db, query, query_params):
11
+
12
+ if "order_id" in query_params:
13
+ query = query.filter(
14
+ SQLOrderMetadata.order_id == query_params["order_id"]
15
+ )
16
+
17
+ return query
@@ -0,0 +1,96 @@
1
+ from investing_algorithm_framework.domain import OrderStatus, OrderType, \
2
+ OrderSide
3
+ from investing_algorithm_framework.infrastructure.models import SQLOrder, \
4
+ SQLPosition, SQLPortfolio
5
+ from .repository import Repository
6
+
7
+
8
+ class SQLOrderRepository(Repository):
9
+ base_class = SQLOrder
10
+ DEFAULT_NOT_FOUND_MESSAGE = "The requested order was not found"
11
+
12
+ def _apply_query_params(self, db, query, query_params):
13
+ id_query_param = self.get_query_param("id", query_params)
14
+ external_id_query_param = self.get_query_param(
15
+ "external_id", query_params
16
+ )
17
+ portfolio_query_param = self.get_query_param(
18
+ "portfolio_id", query_params
19
+ )
20
+ side_query_param = self.get_query_param("order_side", query_params)
21
+ type_query_param = self.get_query_param("order_type", query_params)
22
+ status_query_param = self.get_query_param("status", query_params)
23
+ price_query_param = self.get_query_param("price", query_params)
24
+ amount_query_param = self.get_query_param("amount", query_params)
25
+ position_query_param = self.get_query_param(
26
+ "position", query_params, many=True
27
+ )
28
+ target_symbol_query_param = self.get_query_param(
29
+ "target_symbol", query_params
30
+ )
31
+ trading_symbol_query_param = self.get_query_param(
32
+ "trading_symbol", query_params
33
+ )
34
+ order_by_created_at_asc = self.get_query_param(
35
+ "order_by_created_at_asc", query_params
36
+ )
37
+
38
+ if id_query_param:
39
+ query = query.filter_by(id=id_query_param)
40
+
41
+ if portfolio_query_param is not None:
42
+ portfolio = db.query(SQLPortfolio).filter_by(
43
+ id=portfolio_query_param
44
+ ).first()
45
+
46
+ if portfolio:
47
+ positions = db.query(SQLPosition).filter_by(
48
+ portfolio_id=portfolio.id
49
+ ).all()
50
+ position_ids = [p.id for p in positions]
51
+ query = query.filter(SQLOrder.position_id.in_(position_ids))
52
+ else:
53
+ query = query.filter_by(id=None)
54
+
55
+ if external_id_query_param:
56
+ query = query.filter_by(external_id=external_id_query_param)
57
+
58
+ if side_query_param:
59
+ order_side = OrderSide.from_value(side_query_param)
60
+ query = query.filter_by(order_side=order_side.value)
61
+
62
+ if type_query_param:
63
+ order_type = OrderType.from_value(type_query_param)
64
+ query = query.filter_by(order_type=order_type.value)
65
+
66
+ if status_query_param:
67
+ status = OrderStatus.from_value(status_query_param)
68
+ query = query.filter_by(status=status.value)
69
+
70
+ if price_query_param:
71
+ query = query.filter(SQLOrder.price == price_query_param)
72
+
73
+ if amount_query_param:
74
+ query = query.filter_by(amount=amount_query_param)
75
+
76
+ if position_query_param:
77
+ query = query.filter(SQLOrder.position_id.in_(
78
+ position_query_param)
79
+ )
80
+
81
+ if target_symbol_query_param:
82
+ query = query.filter(
83
+ SQLOrder.target_symbol == target_symbol_query_param
84
+ )
85
+
86
+ if trading_symbol_query_param:
87
+ query = query.filter(
88
+ SQLOrder.trading_symbol == trading_symbol_query_param
89
+ )
90
+
91
+ if order_by_created_at_asc:
92
+ query = query.order_by(SQLOrder.created_at.asc())
93
+ else:
94
+ query = query.order_by(SQLOrder.created_at.desc())
95
+
96
+ return query
@@ -0,0 +1,30 @@
1
+ from investing_algorithm_framework.infrastructure.models import SQLPortfolio, \
2
+ SQLPosition
3
+ from .repository import Repository
4
+
5
+
6
+ class SQLPortfolioRepository(Repository):
7
+ base_class = SQLPortfolio
8
+ DEFAULT_NOT_FOUND_MESSAGE = "Portfolio not found"
9
+
10
+ def _apply_query_params(self, db, query, query_params):
11
+ id_query_param = query_params.get("id")
12
+ market_query_param = query_params.get("market")
13
+ identifier_query_param = query_params.get("identifier")
14
+ position_query_param = query_params.get("position")
15
+
16
+ if id_query_param:
17
+ query = query.filter_by(id=id_query_param)
18
+
19
+ if market_query_param:
20
+ query = query.filter_by(market=market_query_param.upper())
21
+
22
+ if identifier_query_param:
23
+ query = query.filter_by(identifier=identifier_query_param.upper())
24
+
25
+ if position_query_param:
26
+ position = db.query(SQLPosition)\
27
+ .filter_by(id=position_query_param).first()
28
+ query = query.filter_by(id=position.portfolio_id)
29
+
30
+ return query
@@ -0,0 +1,56 @@
1
+ from investing_algorithm_framework.infrastructure.models import \
2
+ SQLPortfolioSnapshot
3
+ from .repository import Repository
4
+
5
+
6
+ class SQLPortfolioSnapshotRepository(Repository):
7
+ base_class = SQLPortfolioSnapshot
8
+ DEFAULT_NOT_FOUND_MESSAGE = "Portfolio snapshot not found"
9
+
10
+ def _apply_query_params(self, db, query, query_params):
11
+ portfolio_id_query_param = self.get_query_param(
12
+ "portfolio_id", query_params
13
+ )
14
+ created_at_query_param = self.get_query_param(
15
+ "created_at", query_params
16
+ )
17
+ created_at_gt_query_param = self.get_query_param(
18
+ "created_at_gt", query_params
19
+ )
20
+ created_at_gte_query_param = self.get_query_param(
21
+ "created_at_gte", query_params
22
+ )
23
+ created_at_lt_query_param = self.get_query_param(
24
+ "created_at_lt", query_params
25
+ )
26
+ created_at_lte_query_param = self.get_query_param(
27
+ "created_at_lte", query_params
28
+ )
29
+
30
+ if portfolio_id_query_param is not None:
31
+ query = query.filter_by(portfolio_id=portfolio_id_query_param)
32
+
33
+ if created_at_query_param is not None:
34
+ query = query.filter_by(created_at=created_at_query_param)
35
+
36
+ if created_at_gt_query_param is not None:
37
+ query = query.filter(
38
+ SQLPortfolioSnapshot.created_at > created_at_gt_query_param
39
+ )
40
+
41
+ if created_at_gte_query_param is not None:
42
+ query = query.filter(
43
+ SQLPortfolioSnapshot.created_at >= created_at_gt_query_param
44
+ )
45
+
46
+ if created_at_lt_query_param is not None:
47
+ query = query.filter(
48
+ SQLPortfolioSnapshot.created_at < created_at_lt_query_param
49
+ )
50
+
51
+ if created_at_lte_query_param is not None:
52
+ query = query.filter(
53
+ SQLPortfolioSnapshot.created_at <= created_at_lte_query_param
54
+ )
55
+
56
+ return query
@@ -0,0 +1,66 @@
1
+ from sqlalchemy import cast, Numeric, Float
2
+
3
+ from investing_algorithm_framework.infrastructure.models import SQLPosition
4
+ from .repository import Repository
5
+
6
+
7
+ class SQLPositionRepository(Repository):
8
+ base_class = SQLPosition
9
+ DEFAULT_NOT_FOUND_MESSAGE = "Position not found"
10
+
11
+ def _apply_query_params(self, db, query, query_params):
12
+ id_query_param = self.get_query_param("id", query_params)
13
+ amount_query_param = self.get_query_param("amount", query_params)
14
+ symbol_query_param = self.get_query_param("symbol", query_params)
15
+ portfolio_query_param = self.get_query_param("portfolio", query_params)
16
+ amount_gt_query_param = self.get_query_param("amount_gt", query_params)
17
+ amount_gte_query_param = self.get_query_param(
18
+ "amount_gte", query_params
19
+ )
20
+ amount_lt_query_param = self.get_query_param("amount_lt", query_params)
21
+ amount_lte_query_param = self.get_query_param(
22
+ "amount_lte", query_params
23
+ )
24
+ order_id_query_param = self.get_query_param("order_id", query_params)
25
+
26
+ if id_query_param:
27
+ query = query.filter_by(id=id_query_param)
28
+
29
+ if amount_query_param:
30
+ query = query.filter(
31
+ cast(SQLPosition.amount, Float) == amount_query_param
32
+ )
33
+
34
+ if symbol_query_param:
35
+ query = query.filter_by(symbol=symbol_query_param)
36
+
37
+ if portfolio_query_param is not None:
38
+ query = query.filter_by(portfolio_id=portfolio_query_param)
39
+
40
+ if amount_gt_query_param is not None:
41
+ query = query.filter(
42
+ cast(SQLPosition.amount, Numeric) > amount_gt_query_param
43
+ )
44
+
45
+ if amount_gte_query_param is not None:
46
+ query = query.filter(
47
+ cast(SQLPosition.amount, Numeric) >= amount_gte_query_param
48
+ )
49
+
50
+ if amount_lt_query_param is not None:
51
+ query = query.filter(
52
+ cast(SQLPosition.amount, Numeric) < amount_lt_query_param
53
+ )
54
+
55
+ if amount_lte_query_param:
56
+ query = query.filter(
57
+ cast(SQLPosition.amount, Numeric) <= amount_lte_query_param
58
+ )
59
+ # Filter by order_id, orders is a one-to-many relationship
60
+ # with 3 position
61
+ if order_id_query_param:
62
+ query = query.filter(
63
+ SQLPosition.orders.any(id=order_id_query_param)
64
+ )
65
+
66
+ return query
@@ -0,0 +1,21 @@
1
+ from investing_algorithm_framework.infrastructure.models import \
2
+ SQLPositionSnapshot
3
+ from .repository import Repository
4
+
5
+
6
+ class SQLPositionSnapshotRepository(Repository):
7
+ base_class = SQLPositionSnapshot
8
+ DEFAULT_NOT_FOUND_MESSAGE = "Position snapshot not found"
9
+
10
+ def _apply_query_params(self, db, query, query_params):
11
+ portfolio_snapshot_query_param = self.get_query_param(
12
+ "portfolio_snapshot", query_params
13
+ )
14
+
15
+ if portfolio_snapshot_query_param is not None:
16
+ query = query\
17
+ .filter_by(
18
+ portfolio_snapshot_id=portfolio_snapshot_query_param
19
+ )
20
+
21
+ return query
@@ -0,0 +1,299 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from typing import Callable
4
+ from dateutil.parser import parse
5
+
6
+ from sqlalchemy.exc import SQLAlchemyError
7
+ from werkzeug.datastructures import MultiDict
8
+
9
+ from investing_algorithm_framework.domain import OperationalException, \
10
+ DEFAULT_PAGE_VALUE, DEFAULT_PER_PAGE_VALUE
11
+ from investing_algorithm_framework.infrastructure.database import Session
12
+
13
+ logger = logging.getLogger("investing_algorithm_framework")
14
+
15
+
16
+ def convert_datetime_fields(data, datetime_fields):
17
+ for field in datetime_fields:
18
+ if field in data and isinstance(data[field], str):
19
+ try:
20
+ data[field] = parse(data[field])
21
+ except Exception:
22
+ pass # Ignore if not a valid datetime string
23
+ return data
24
+
25
+
26
+ class Repository(ABC):
27
+ base_class: Callable
28
+ DEFAULT_NOT_FOUND_MESSAGE = "The requested resource was not found"
29
+ DEFAULT_PER_PAGE = DEFAULT_PER_PAGE_VALUE
30
+ DEFAULT_PAGE = DEFAULT_PAGE_VALUE
31
+
32
+ def create(self, data, save=True):
33
+ created_object = self.base_class(**data)
34
+ if save:
35
+ with Session() as db:
36
+ try:
37
+ db.add(created_object)
38
+ db.commit()
39
+ return self.get(created_object.id)
40
+ except SQLAlchemyError as e:
41
+ logger.error(e)
42
+ db.rollback()
43
+ raise OperationalException("Error creating object")
44
+
45
+ return created_object
46
+
47
+ def update(self, object_id, data):
48
+ # List all datetime fields for your model
49
+ datetime_fields = [
50
+ "created_at", "updated_at", "closed_at", "opened_at"
51
+ ]
52
+ data = convert_datetime_fields(data, datetime_fields)
53
+ with Session() as db:
54
+ try:
55
+ update_object = self.get(object_id)
56
+ update_object.update(data)
57
+ db.add(update_object)
58
+ db.commit()
59
+ return self.get(object_id)
60
+ except SQLAlchemyError as e:
61
+ logger.error(e)
62
+ db.rollback()
63
+ raise OperationalException("Error updating object")
64
+
65
+ def update_all(self, query_params, data):
66
+
67
+ with Session() as db:
68
+ try:
69
+ selection = self.get_all(query_params)
70
+
71
+ for item in selection:
72
+ try:
73
+ item.update(db, data)
74
+ except SQLAlchemyError as e:
75
+ logger.error(e)
76
+ db.rollback()
77
+
78
+ db.commit()
79
+
80
+ except SQLAlchemyError as e:
81
+ logger.error(e)
82
+ db.rollback()
83
+ raise OperationalException("Error updating object")
84
+
85
+ def delete(self, object_id):
86
+
87
+ with Session() as db:
88
+ try:
89
+ delete_object = self.get(object_id)
90
+ db.delete(delete_object)
91
+ db.commit()
92
+ return delete_object
93
+ except SQLAlchemyError as e:
94
+ logger.error(e)
95
+ db.rollback()
96
+ raise OperationalException("Error deleting object")
97
+
98
+ def delete_all(self, query_params):
99
+
100
+ with Session() as db:
101
+ if query_params is None:
102
+ raise OperationalException("No parameters are required")
103
+
104
+ try:
105
+ query_set = db.query(self.base_class)
106
+ query_set = self.apply_query_params(
107
+ db, query_set, query_params
108
+ )
109
+
110
+ for item in query_set.all():
111
+ item.delete(db)
112
+ db.commit()
113
+
114
+ except SQLAlchemyError as e:
115
+ logger.error(e)
116
+ db.rollback()
117
+ raise OperationalException("Error deleting all objects")
118
+
119
+ def get_all(self, query_params=None):
120
+ query_params = MultiDict(query_params)
121
+
122
+ with Session() as db:
123
+ try:
124
+ query_set = db.query(self.base_class)
125
+ query_set = self.apply_query_params(
126
+ db, query_set, query_params
127
+ )
128
+ return query_set.all()
129
+ except SQLAlchemyError as e:
130
+ logger.error(e)
131
+ raise OperationalException("Error getting all objects")
132
+
133
+ def get(self, object_id):
134
+
135
+ with Session() as db:
136
+ match = db.query(self.base_class).filter_by(id=object_id) \
137
+ .first()
138
+
139
+ if not match:
140
+ raise OperationalException(
141
+ self.DEFAULT_NOT_FOUND_MESSAGE
142
+ )
143
+
144
+ return match
145
+
146
+ @abstractmethod
147
+ def _apply_query_params(self, db, query, query_params):
148
+ raise NotImplementedError()
149
+
150
+ def apply_query_params(self, db, query, query_params):
151
+
152
+ if query_params is not None:
153
+ query_params = MultiDict(query_params)
154
+ query = self._apply_query_params(db, query, query_params)
155
+
156
+ return query
157
+
158
+ def exists(self, query_params):
159
+ with Session() as db:
160
+ try:
161
+ query = db.query(self.base_class)
162
+ query = self.apply_query_params(db, query, query_params)
163
+ return query.first() is not None
164
+ except SQLAlchemyError as e:
165
+ logger.error(e)
166
+ raise OperationalException("Error checking if object exists")
167
+
168
+ def find(self, query_params):
169
+
170
+ if query_params is None or len(query_params) == 0:
171
+ raise OperationalException("Find requires query parameters")
172
+
173
+ with Session() as db:
174
+ try:
175
+ query = db.query(self.base_class)
176
+ query = self.apply_query_params(db, query, query_params)
177
+ result = query.first()
178
+
179
+ if result is None:
180
+ raise OperationalException(self.DEFAULT_NOT_FOUND_MESSAGE)
181
+
182
+ return result
183
+ except SQLAlchemyError as e:
184
+ logger.error(e)
185
+ raise OperationalException(self.DEFAULT_NOT_FOUND_MESSAGE)
186
+
187
+ def count(self, query_params=None):
188
+
189
+ with Session() as db:
190
+ try:
191
+ query = db.query(self.base_class)
192
+ query = self.apply_query_params(db, query, query_params)
193
+ return query.count()
194
+ except SQLAlchemyError as e:
195
+ logger.error(e)
196
+ raise OperationalException("Error counting objects")
197
+
198
+ def normalize_query_param(self, value):
199
+ """
200
+ Given a non-flattened query parameter value,
201
+ and if the value is a list only containing 1 item,
202
+ then the value is flattened.
203
+
204
+ :param value: a value from a query parameter
205
+ :return: a normalized query parameter value
206
+ """
207
+ return value if len(value) > 1 else value[0]
208
+
209
+ def is_query_param_present(self, key, params, throw_exception=False):
210
+ query_params = self.normalize_query(params)
211
+
212
+ if key not in query_params:
213
+
214
+ if not throw_exception:
215
+ return False
216
+
217
+ raise OperationalException(f"{key} is not specified")
218
+ else:
219
+ return True
220
+
221
+ def normalize_query(self, params):
222
+ """
223
+ Converts query parameters from only containing one value for
224
+ each parameter, to include parameters with multiple values as lists.
225
+
226
+ :param params: a flask query parameters data structure
227
+ :return: a dict of normalized query parameters
228
+ """
229
+ if isinstance(params, MultiDict):
230
+ params = params.to_dict(flat=False)
231
+
232
+ return {k: self.normalize_query_param(v) for k, v in params.items()}
233
+
234
+ def get_query_param(self, key, params, default=None, many=False):
235
+ boolean_array = ["true", "false"]
236
+
237
+ if params is None or key not in params:
238
+ return default
239
+
240
+ params = self.normalize_query(params)
241
+ selection = params.get(key, default)
242
+
243
+ if not isinstance(selection, list):
244
+
245
+ if selection is None:
246
+ selection = []
247
+ else:
248
+ selection = [selection]
249
+
250
+ new_selection = []
251
+
252
+ for index, selected in enumerate(selection):
253
+
254
+ if isinstance(selected, str) and selected.lower() in boolean_array:
255
+ new_selection.append(selected.lower() == "true")
256
+ else:
257
+ new_selection.append(selected)
258
+
259
+ if not many:
260
+
261
+ if len(new_selection) == 0:
262
+ return None
263
+
264
+ return new_selection[0]
265
+
266
+ return new_selection
267
+
268
+ def save(self, object_to_save):
269
+ """
270
+ Save an object to the database with SQLAlchemy.
271
+
272
+ Args:
273
+ object_to_save: instance of the object to save.
274
+
275
+ Returns:
276
+ Object: The saved object.
277
+ """
278
+ with Session() as db:
279
+ try:
280
+ db.add(object_to_save)
281
+ db.commit()
282
+ return self.get(object_to_save.id)
283
+ except SQLAlchemyError as e:
284
+ logger.error(e)
285
+ db.rollback()
286
+ raise OperationalException("Error saving object")
287
+
288
+ def save_objects(self, objects):
289
+
290
+ with Session() as db:
291
+ try:
292
+ for object in objects:
293
+ db.add(object)
294
+ db.commit()
295
+ return objects
296
+ except SQLAlchemyError as e:
297
+ logger.error(e)
298
+ db.rollback()
299
+ raise OperationalException("Error saving objects")
@@ -0,0 +1,71 @@
1
+ import logging
2
+ from sqlalchemy.exc import SQLAlchemyError
3
+
4
+ from investing_algorithm_framework.domain import TradeStatus, ApiException
5
+ from investing_algorithm_framework.infrastructure.models import SQLPosition, \
6
+ SQLPortfolio, SQLTrade, SQLOrder
7
+ from investing_algorithm_framework.infrastructure.database import Session
8
+
9
+ from .repository import Repository
10
+
11
+ logger = logging.getLogger("investing_algorithm_framework")
12
+
13
+
14
+ class SQLTradeRepository(Repository):
15
+ base_class = SQLTrade
16
+ DEFAULT_NOT_FOUND_MESSAGE = "The requested trade was not found"
17
+
18
+ def _apply_query_params(self, db, query, query_params):
19
+ portfolio_query_param = self.get_query_param(
20
+ "portfolio_id", query_params
21
+ )
22
+ status_query_param = self.get_query_param("status", query_params)
23
+ target_symbol = self.get_query_param(
24
+ "target_symbol", query_params
25
+ )
26
+ trading_symbol = self.get_query_param("trading_symbol", query_params)
27
+ order_id_query_param = self.get_query_param("order_id", query_params)
28
+
29
+ if order_id_query_param:
30
+ query = query.filter(SQLTrade.orders.any(id=order_id_query_param))
31
+
32
+ if portfolio_query_param is not None:
33
+ portfolio = db.query(SQLPortfolio).filter_by(
34
+ id=portfolio_query_param
35
+ ).first()
36
+
37
+ if portfolio is None:
38
+ raise ApiException("Portfolio not found")
39
+
40
+ # Query trades belonging to the portfolio
41
+ query = db.query(SQLTrade).join(SQLOrder, SQLTrade.orders) \
42
+ .join(SQLPosition, SQLOrder.position_id == SQLPosition.id) \
43
+ .filter(SQLPosition.portfolio_id == portfolio.id)
44
+
45
+ if status_query_param:
46
+ status = TradeStatus.from_value(status_query_param)
47
+ # Explicitly filter on SQLTrade.status
48
+ query = query.filter(SQLTrade.status == status.value)
49
+
50
+ if target_symbol:
51
+ # Explicitly filter on SQLTrade.target_symbol
52
+ query = query.filter(SQLTrade.target_symbol == target_symbol)
53
+
54
+ if trading_symbol:
55
+ # Explicitly filter on SQLTrade.trading_symbol
56
+ query = query.filter(SQLTrade.trading_symbol == trading_symbol)
57
+
58
+ return query
59
+
60
+ def add_order_to_trade(self, trade, order):
61
+ with Session() as db:
62
+ try:
63
+ db.add(order)
64
+ db.add(trade)
65
+ trade.orders.append(order)
66
+ db.commit()
67
+ return trade
68
+ except SQLAlchemyError as e:
69
+ logger.error(f"Error saving trade: {e}")
70
+ db.rollback()
71
+ raise ApiException("Error saving trade")