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,13 +1,14 @@
1
1
  import logging
2
2
 
3
3
  from sqlalchemy import create_engine, StaticPool
4
+ from sqlalchemy import inspect
4
5
  from sqlalchemy.orm import DeclarativeBase, sessionmaker
5
6
 
6
7
  from investing_algorithm_framework.domain import SQLALCHEMY_DATABASE_URI, \
7
8
  OperationalException
8
9
 
9
10
  Session = sessionmaker()
10
- logger = logging.getLogger(__name__)
11
+ logger = logging.getLogger("investing_algorithm_framework")
11
12
 
12
13
 
13
14
  class SQLAlchemyAdapter:
@@ -19,18 +20,11 @@ class SQLAlchemyAdapter:
19
20
  raise OperationalException("SQLALCHEMY_DATABASE_URI not set")
20
21
 
21
22
  global Session
22
-
23
- if app.config[SQLALCHEMY_DATABASE_URI] != "sqlite:///:memory:":
24
- engine = create_engine(
25
- app.config[SQLALCHEMY_DATABASE_URI],
26
- connect_args={'check_same_thread': False},
27
- poolclass=StaticPool
28
- )
29
- else:
30
- engine = create_engine(
31
- app.config[SQLALCHEMY_DATABASE_URI],
32
- )
33
-
23
+ engine = create_engine(
24
+ app.config[SQLALCHEMY_DATABASE_URI],
25
+ connect_args={'check_same_thread': False},
26
+ poolclass=StaticPool
27
+ )
34
28
  Session.configure(bind=engine)
35
29
 
36
30
 
@@ -45,9 +39,89 @@ def setup_sqlalchemy(app, throw_exception_if_not_set=True):
45
39
  return app
46
40
 
47
41
 
42
+
48
43
  class SQLBaseModel(DeclarativeBase):
49
44
  pass
50
45
 
51
46
 
52
47
  def create_all_tables():
53
48
  SQLBaseModel.metadata.create_all(bind=Session().bind)
49
+
50
+
51
+ from sqlalchemy import event
52
+ from sqlalchemy.orm import mapper
53
+ from datetime import timezone
54
+
55
+ def clear_db(db_uri):
56
+ """
57
+ Clear the database by dropping all tables.
58
+ This is useful for testing purposes.
59
+
60
+ Args:
61
+ db_uri (str): The database URI to connect to.
62
+
63
+ Returns:
64
+ None
65
+ """
66
+ # Drop all tables before deleting file
67
+ try:
68
+ engine = create_engine(db_uri)
69
+ inspector = inspect(engine)
70
+ if inspector.get_table_names():
71
+ logger.info("Dropping all tables in backtest database")
72
+ SQLBaseModel.metadata.drop_all(bind=engine)
73
+ except Exception as e:
74
+ logger.error(f"Error dropping tables: {e}")
75
+
76
+ # # Clear mappers (if using classical mappings)
77
+ # try:
78
+ # clear_mappers()
79
+ # except Exception:
80
+ # pass # ignore if not needed
81
+
82
+
83
+ @event.listens_for(mapper, "load")
84
+ def attach_utc_timezone_on_load(target, context):
85
+ """
86
+ For each model instance loaded from the database,
87
+ this function will check if one of the following attributes are
88
+ present: created_at, updated_at, closed_at, opened_at, triggered_at.
89
+ If so, it will check if these datetime
90
+ attributes are timezone-naive and, if so, will set them to UTC.
91
+
92
+ Its documented in the contributing guide (https://coding-kitties.github
93
+ .io/investing-algorithm-framework/Contributing%20Guide/contributing)
94
+ that each datetime attribute should be utc timezone-aware.
95
+
96
+ Args:
97
+ target: The model instance being loaded from the database.
98
+ context: The context in which the event is being handled.
99
+
100
+ Returns:
101
+ None
102
+ """
103
+ # This will apply to every model instance loaded from the DB
104
+ if hasattr(target, "created_at"):
105
+ dt = getattr(target, "created_at")
106
+ if dt and dt.tzinfo is None:
107
+ target.created_at = dt.replace(tzinfo=timezone.utc)
108
+
109
+ if hasattr(target, "updated_at"):
110
+ dt = getattr(target, "updated_at")
111
+ if dt and dt.tzinfo is None:
112
+ target.updated_at = dt.replace(tzinfo=timezone.utc)
113
+
114
+ if hasattr(target, "closed_at"):
115
+ dt = getattr(target, "closed_at")
116
+ if dt and dt.tzinfo is None:
117
+ target.closed_at = dt.replace(tzinfo=timezone.utc)
118
+
119
+ if hasattr(target, "opened_at"):
120
+ dt = getattr(target, "opened_at")
121
+ if dt and dt.tzinfo is None:
122
+ target.opened_at = dt.replace(tzinfo=timezone.utc)
123
+
124
+ if hasattr(target, "triggered_at"):
125
+ dt = getattr(target, "triggered_at")
126
+ if dt and dt.tzinfo is None:
127
+ target.triggered_at = dt.replace(tzinfo=timezone.utc)
@@ -1,7 +1,16 @@
1
- from .order import SQLOrder, SQLOrderFee
2
- from .portfolio import SQLPortfolio
3
- from .position import SQLPosition, SQLPositionCost
1
+ from .order import SQLOrder, SQLOrderMetadata
2
+ from .portfolio import SQLPortfolio, SQLPortfolioSnapshot
3
+ from .position import SQLPosition, SQLPositionSnapshot
4
+ from .trades import SQLTrade, SQLTradeStopLoss, SQLTradeTakeProfit
4
5
 
5
6
  __all__ = [
6
- "SQLOrder", "SQLPosition", "SQLPortfolio", "SQLPositionCost", "SQLOrderFee"
7
+ "SQLOrder",
8
+ "SQLPosition",
9
+ "SQLPortfolio",
10
+ "SQLPositionSnapshot",
11
+ "SQLPortfolioSnapshot",
12
+ "SQLTrade",
13
+ "SQLTradeStopLoss",
14
+ "SQLTradeTakeProfit",
15
+ "SQLOrderMetadata",
7
16
  ]
@@ -0,0 +1,14 @@
1
+ from decimal import Decimal, getcontext
2
+
3
+
4
+ def count_number_of_decimals(value) -> int:
5
+ value = str(value)
6
+ if "." in value:
7
+ return len(value) - value.index(".") - 1
8
+ else:
9
+ return 0
10
+
11
+
12
+ def parse_decimal(value) -> Decimal:
13
+ getcontext().prec = count_number_of_decimals(value)
14
+ return Decimal(value)
@@ -1,4 +1,4 @@
1
1
  from .order import SQLOrder
2
- from .order_fee import SQLOrderFee
2
+ from .order_metadata import SQLOrderMetadata
3
3
 
4
- __all__ = ["SQLOrder", "SQLOrderFee"]
4
+ __all__ = ["SQLOrder", "SQLOrderMetadata"]
@@ -1,111 +1,105 @@
1
1
  import logging
2
- from datetime import datetime
2
+ from datetime import datetime, timezone
3
3
 
4
- from sqlalchemy import Column, Integer, String, DateTime, Float, ForeignKey
4
+ from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Float
5
5
  from sqlalchemy.orm import relationship
6
6
 
7
- from investing_algorithm_framework.domain.models import OrderType, \
8
- OrderSide, Order, OrderStatus, OrderFee
7
+ from investing_algorithm_framework.domain import OrderType, \
8
+ OrderSide, Order, OrderStatus
9
9
  from investing_algorithm_framework.infrastructure.database import SQLBaseModel
10
10
  from investing_algorithm_framework.infrastructure.models.model_extension \
11
11
  import SQLAlchemyModelExtension
12
+ from investing_algorithm_framework.infrastructure.models.\
13
+ order_trade_association import order_trade_association
12
14
 
13
- logger = logging.getLogger(__name__)
15
+ logger = logging.getLogger("investing_algorithm_framework")
14
16
 
15
17
 
16
- class SQLOrder(SQLBaseModel, Order, SQLAlchemyModelExtension):
18
+ def utcnow():
19
+ return datetime.now(tz=timezone.utc)
20
+
21
+
22
+ class SQLOrder(Order, SQLBaseModel, SQLAlchemyModelExtension):
23
+ """
24
+ SQLOrder model based on the Order domain model.
25
+ """
17
26
  __tablename__ = "orders"
18
27
  id = Column(Integer, primary_key=True, unique=True)
19
28
  external_id = Column(Integer)
20
29
  target_symbol = Column(String)
21
30
  trading_symbol = Column(String)
22
- side = Column(String, nullable=False, default=OrderSide.BUY.value)
23
- type = Column(String, nullable=False, default=OrderType.LIMIT.value)
31
+ order_side = Column(String, nullable=False, default=OrderSide.BUY.value)
32
+ order_type = Column(String, nullable=False, default=OrderType.LIMIT.value)
33
+ trades = relationship(
34
+ 'SQLTrade', secondary=order_trade_association, back_populates='orders'
35
+ )
24
36
  price = Column(Float)
25
37
  amount = Column(Float)
26
- filled_amount = Column(Float)
27
- remaining_amount = Column(Float)
28
- cost = Column(Float)
29
- status = Column(String)
38
+ remaining = Column(Float, default=None)
39
+ filled = Column(Float, default=None)
40
+ cost = Column(Float, default=0)
41
+ status = Column(String, default=OrderStatus.CREATED.value)
30
42
  position_id = Column(Integer, ForeignKey('positions.id'))
31
43
  position = relationship("SQLPosition", back_populates="orders")
32
- created_at = Column(DateTime, default=datetime.utcnow)
33
- updated_at = Column(DateTime, default=datetime.utcnow)
34
- trade_closed_at = Column(DateTime, default=None)
35
- trade_closed_price = Column(Float, default=None)
36
- net_gain = Column(Float, default=0)
37
- fee = relationship(
38
- "SQLOrderFee",
39
- uselist=False,
40
- back_populates="order",
41
- cascade="all, delete"
44
+ created_at = Column(DateTime(timezone=True), default=utcnow)
45
+ updated_at = Column(
46
+ DateTime(timezone=True), default=utcnow, onupdate=utcnow
47
+ )
48
+ order_fee = Column(Float, default=None)
49
+ order_fee_currency = Column(String, default=None)
50
+ order_fee_rate = Column(Float, default=None)
51
+ sell_order_metadata_id = Column(Integer, ForeignKey('orders.id'))
52
+ order_metadata = relationship(
53
+ 'SQLOrderMetadata', back_populates='order'
42
54
  )
43
55
 
44
- def __init__(
45
- self,
46
- side,
47
- type,
48
- status,
49
- amount,
50
- position_id=None,
51
- target_symbol=None,
52
- trading_symbol=None,
53
- external_id=None,
54
- price=None,
55
- created_at=None,
56
- updated_at=None,
57
- trade_closed_at=None,
58
- trade_closed_price=None,
59
- net_gain=0,
60
- filled_amount=None,
61
- remaining_amount=None,
62
- cost=None,
63
- fee=None
64
- ):
65
- super().__init__(
66
- target_symbol=target_symbol,
67
- trading_symbol=trading_symbol,
68
- type=type,
69
- side=side,
70
- status=status,
71
- amount=amount,
72
- price=price,
73
- external_id=external_id,
74
- position_id=position_id,
75
- net_gain=net_gain,
76
- trade_closed_at=trade_closed_at,
77
- trade_closed_price=trade_closed_price,
78
- created_at=created_at,
79
- updated_at=updated_at,
80
- filled_amount=filled_amount,
81
- remaining_amount=remaining_amount,
82
- cost=cost,
83
- fee=fee
84
- )
56
+ def update(self, data):
57
+
58
+ if "status" in data and data["status"] is not None:
59
+ data["status"] = OrderStatus.from_value(data["status"]).value
60
+
61
+ super().update(data)
85
62
 
86
63
  @staticmethod
87
64
  def from_order(order):
88
65
  return SQLOrder(
89
66
  external_id=order.external_id,
90
67
  amount=order.get_amount(),
68
+ filled=order.get_filled(),
69
+ remaining=order.get_remaining(),
91
70
  price=order.price,
92
- type=order.get_type(),
93
- side=order.get_side(),
71
+ order_type=order.get_order_type(),
72
+ order_side=order.get(),
94
73
  status=order.get_status(),
95
74
  target_symbol=order.get_target_symbol(),
96
75
  trading_symbol=order.get_trading_symbol(),
97
76
  created_at=order.get_created_at(),
98
77
  updated_at=order.get_updated_at(),
99
- trade_closed_at=order.get_trade_closed_at(),
100
- trade_closed_price=order.get_trade_closed_price(),
101
- net_gain=order.get_net_gain(),
102
78
  )
103
79
 
104
80
  @staticmethod
105
81
  def from_ccxt_order(ccxt_order):
82
+ """
83
+ Create an Order object from a CCXT order object
84
+
85
+ Args:
86
+ ccxt_order: CCXT order object
87
+
88
+ Returns:
89
+ Order: Order object
90
+ """
106
91
  status = OrderStatus.from_value(ccxt_order["status"])
107
92
  target_symbol = ccxt_order.get("symbol").split("/")[0]
108
93
  trading_symbol = ccxt_order.get("symbol").split("/")[1]
94
+ ccxt_fee = ccxt_order.get("fee", None)
95
+ order_fee = None
96
+ order_fee_rate = None
97
+ order_fee_currency = None
98
+
99
+ if ccxt_fee is not None:
100
+ order_fee = ccxt_fee.get("cost", None)
101
+ order_fee_rate = ccxt_fee.get("rate", None)
102
+ order_fee_currency = ccxt_fee.get("currency", None)
109
103
 
110
104
  return Order(
111
105
  external_id=ccxt_order.get("id", None),
@@ -114,11 +108,17 @@ class SQLOrder(SQLBaseModel, Order, SQLAlchemyModelExtension):
114
108
  price=ccxt_order.get("price", None),
115
109
  amount=ccxt_order.get("amount", None),
116
110
  status=status,
117
- type=ccxt_order.get("type", None),
118
- side=ccxt_order.get("side", None),
119
- filled_amount=ccxt_order.get("filled", None),
120
- remaining_amount=ccxt_order.get("remaining", None),
111
+ order_type=ccxt_order.get("type", None),
112
+ order_side=ccxt_order.get("side", None),
113
+ filled=ccxt_order.get("filled", None),
114
+ remaining=ccxt_order.get("remaining", None),
121
115
  cost=ccxt_order.get("cost", None),
122
- fee=OrderFee.from_ccxt_fee(ccxt_order.get("fee", None)),
116
+ order_fee=order_fee,
117
+ order_fee_rate=order_fee_rate,
118
+ order_fee_currency=order_fee_currency,
123
119
  created_at=ccxt_order.get("datetime", None),
124
120
  )
121
+
122
+ def __lt__(self, other):
123
+ # Define the less-than comparison based on created_at attribute
124
+ return self.created_at < other.created_at
@@ -0,0 +1,44 @@
1
+ import logging
2
+
3
+ from sqlalchemy import Column, Integer, ForeignKey, Float
4
+ from sqlalchemy.orm import relationship
5
+
6
+ from investing_algorithm_framework.infrastructure.database import SQLBaseModel
7
+ from investing_algorithm_framework.infrastructure.models.model_extension \
8
+ import SQLAlchemyModelExtension
9
+
10
+ logger = logging.getLogger("investing_algorithm_framework")
11
+
12
+
13
+ class SQLOrderMetadata(SQLBaseModel, SQLAlchemyModelExtension):
14
+ __tablename__ = "sql_order_metadata"
15
+ id = Column(Integer, primary_key=True, unique=True)
16
+ order_id = Column(Integer, ForeignKey('orders.id'))
17
+ order = relationship('SQLOrder', back_populates='order_metadata')
18
+ trade_id = Column(Integer)
19
+ stop_loss_id = Column(Integer)
20
+ take_profit_id = Column(Integer)
21
+ amount = Column(Float)
22
+ amount_pending = Column(Float)
23
+
24
+ def __init__(
25
+ self,
26
+ order_id,
27
+ amount,
28
+ amount_pending,
29
+ trade_id=None,
30
+ stop_loss_id=None,
31
+ take_profit_id=None,
32
+ ):
33
+ self.order_id = order_id
34
+ self.trade_id = trade_id
35
+ self.stop_loss_id = stop_loss_id
36
+ self.take_profit_id = take_profit_id
37
+ self.amount = amount
38
+ self.amount_pending = amount_pending
39
+
40
+ def __repr__(self):
41
+ return f"<SQLOrderMetadata(id={self.id}, order_id={self.order_id}, " \
42
+ f"trade_id={self.trade_id}, stop_loss_id={self.stop_loss_id}, "\
43
+ f"take_profit_id={self.take_profit_id}, amount={self.amount}, "\
44
+ f"amount_pending={self.amount_pending})>"
@@ -0,0 +1,10 @@
1
+ from sqlalchemy import Table, Column, Integer, ForeignKey
2
+ from investing_algorithm_framework.infrastructure.database import SQLBaseModel
3
+
4
+ # Association table
5
+ order_trade_association = Table(
6
+ 'order_trade', # Table name
7
+ SQLBaseModel.metadata,
8
+ Column('order_id', Integer, ForeignKey('orders.id'), primary_key=True),
9
+ Column('trade_id', Integer, ForeignKey('trades.id'), primary_key=True)
10
+ )
@@ -1,3 +1,4 @@
1
- from .portfolio import SQLPortfolio
1
+ from .sql_portfolio import SQLPortfolio
2
+ from .portfolio_snapshot import SQLPortfolioSnapshot
2
3
 
3
- __all__ = ['SQLPortfolio']
4
+ __all__ = ['SQLPortfolio', "SQLPortfolioSnapshot"]
@@ -0,0 +1,37 @@
1
+ from sqlalchemy import Column, Integer, String, DateTime, Float
2
+ from sqlalchemy.orm import relationship
3
+
4
+ from investing_algorithm_framework.domain import PortfolioSnapshot
5
+ from investing_algorithm_framework.infrastructure.database import SQLBaseModel
6
+ from investing_algorithm_framework.infrastructure.models.model_extension \
7
+ import SQLAlchemyModelExtension
8
+
9
+
10
+ class SQLPortfolioSnapshot(
11
+ PortfolioSnapshot, SQLBaseModel, SQLAlchemyModelExtension
12
+ ):
13
+ """
14
+ SQLAlchemy model for portfolio snapshots.
15
+
16
+ Portfolio snapshots represent the state of a portfolio at a specific
17
+ point in time.
18
+ """
19
+ __tablename__ = "portfolio_snapshots"
20
+ id = Column(Integer, primary_key=True)
21
+ portfolio_id = Column(String, nullable=False)
22
+ trading_symbol = Column(String, nullable=False)
23
+ pending_value = Column(Float, nullable=False, default=0)
24
+ unallocated = Column(Float, nullable=False, default=0)
25
+ net_size = Column(Float, nullable=False, default=0)
26
+ total_net_gain = Column(Float, nullable=False, default=0)
27
+ total_revenue = Column(Float, nullable=False, default=0)
28
+ total_cost = Column(Float, nullable=False, default=0)
29
+ total_value = Column(Float, nullable=False, default=0)
30
+ cash_flow = Column(Float, nullable=False, default=0)
31
+ created_at = Column(DateTime, nullable=False, default=0)
32
+ position_snapshots = relationship(
33
+ "SQLPositionSnapshot",
34
+ back_populates="portfolio_snapshot",
35
+ lazy="dynamic",
36
+ cascade="all,delete",
37
+ )
@@ -1,4 +1,6 @@
1
- from sqlalchemy import Column, Integer, String, Float
1
+ from datetime import datetime, timezone
2
+
3
+ from sqlalchemy import Column, Integer, String, DateTime, Float, Boolean
2
4
  from sqlalchemy import UniqueConstraint
3
5
  from sqlalchemy.orm import relationship
4
6
  from sqlalchemy.orm import validates
@@ -9,7 +11,7 @@ from investing_algorithm_framework.infrastructure.models.model_extension \
9
11
  import SQLAlchemyModelExtension
10
12
 
11
13
 
12
- class SQLPortfolio(SQLBaseModel, Portfolio, SQLAlchemyModelExtension):
14
+ class SQLPortfolio(Portfolio, SQLBaseModel, SQLAlchemyModelExtension):
13
15
  __tablename__ = "portfolios"
14
16
  id = Column(Integer, primary_key=True)
15
17
  identifier = Column(String, nullable=False, unique=True)
@@ -18,8 +20,10 @@ class SQLPortfolio(SQLBaseModel, Portfolio, SQLAlchemyModelExtension):
18
20
  total_revenue = Column(Float, nullable=False, default=0)
19
21
  total_cost = Column(Float, nullable=False, default=0)
20
22
  total_net_gain = Column(Float, nullable=False, default=0)
23
+ total_trade_volume = Column(Float, nullable=False, default=0)
21
24
  net_size = Column(Float, nullable=False, default=0)
22
25
  unallocated = Column(Float, nullable=False, default=0)
26
+ initial_balance = Column(Float, nullable=True)
23
27
  market = Column(String, nullable=False)
24
28
  positions = relationship(
25
29
  "SQLPosition",
@@ -27,6 +31,10 @@ class SQLPortfolio(SQLBaseModel, Portfolio, SQLAlchemyModelExtension):
27
31
  lazy="dynamic",
28
32
  cascade="all,delete",
29
33
  )
34
+ created_at = Column(DateTime, nullable=False)
35
+ updated_at = Column(DateTime, nullable=False)
36
+ initialized = Column(Boolean, nullable=False, default=False)
37
+
30
38
  __table_args__ = (
31
39
  UniqueConstraint(
32
40
  'trading_symbol',
@@ -43,11 +51,27 @@ class SQLPortfolio(SQLBaseModel, Portfolio, SQLAlchemyModelExtension):
43
51
  raise ValueError("{} is write-once".format(key))
44
52
  return value
45
53
 
46
- def __init__(self, trading_symbol, market, unallocated, identifier=None):
54
+ def __init__(
55
+ self,
56
+ trading_symbol,
57
+ market,
58
+ unallocated,
59
+ initialized,
60
+ initial_balance=None,
61
+ identifier=None,
62
+ created_at=None,
63
+ updated_at=None,
64
+ ):
47
65
 
48
66
  if identifier is None:
49
67
  identifier = market
50
68
 
69
+ if created_at is None:
70
+ created_at = datetime.now(tz=timezone.utc)
71
+
72
+ if updated_at is None:
73
+ updated_at = datetime.now(tz=timezone.utc)
74
+
51
75
  super().__init__(
52
76
  trading_symbol=trading_symbol,
53
77
  market=market,
@@ -57,4 +81,34 @@ class SQLPortfolio(SQLBaseModel, Portfolio, SQLAlchemyModelExtension):
57
81
  realized=0,
58
82
  total_revenue=0,
59
83
  total_cost=0,
84
+ created_at=created_at,
85
+ updated_at=updated_at,
86
+ initialized=initialized,
87
+ initial_balance=initial_balance,
60
88
  )
89
+
90
+ def update(self, data):
91
+
92
+ if "net_size" in data:
93
+ self.net_size = data.pop("net_size")
94
+
95
+ if "unallocated" in data:
96
+ self.unallocated = data.pop("unallocated")
97
+
98
+ if "realized" in data:
99
+ self.realized = data.pop("realized")
100
+
101
+ if "total_revenue" in data:
102
+ self.total_revenue = data.pop("total_revenue")
103
+
104
+ if "total_trade_volume" in data:
105
+ self.total_trade_volume = data.pop("total_trade_volume")
106
+
107
+ if "total_cost" in data:
108
+ self.total_cost = data.pop("total_cost")
109
+
110
+ if "total_net_gain" in data:
111
+ self.total_net_gain = data.pop("total_net_gain")
112
+
113
+ super().update(data)
114
+ return self
@@ -1,4 +1,4 @@
1
1
  from .position import SQLPosition
2
- from .position_cost import SQLPositionCost
2
+ from .position_snapshot import SQLPositionSnapshot
3
3
 
4
- __all__ = ["SQLPosition", "SQLPositionCost"]
4
+ __all__ = ["SQLPosition", "SQLPositionSnapshot"]
@@ -1,11 +1,11 @@
1
- from sqlalchemy import Column, Integer, String, Float, ForeignKey
1
+ from sqlalchemy import Column, Integer, String, ForeignKey, Float
2
2
  from sqlalchemy import UniqueConstraint
3
3
  from sqlalchemy.orm import relationship, validates
4
4
 
5
+ from investing_algorithm_framework.domain import Position
5
6
  from investing_algorithm_framework.infrastructure.database import SQLBaseModel
6
7
  from investing_algorithm_framework.infrastructure.models.model_extension \
7
8
  import SQLAlchemyModelExtension
8
- from investing_algorithm_framework.domain import Position
9
9
 
10
10
 
11
11
  class SQLPosition(SQLBaseModel, Position, SQLAlchemyModelExtension):
@@ -13,18 +13,13 @@ class SQLPosition(SQLBaseModel, Position, SQLAlchemyModelExtension):
13
13
  id = Column(Integer, primary_key=True, unique=True)
14
14
  symbol = Column(String)
15
15
  amount = Column(Float)
16
+ cost = Column(Float)
16
17
  orders = relationship(
17
18
  "SQLOrder",
18
19
  back_populates="position",
19
20
  lazy="dynamic",
20
21
  cascade="all, delete-orphan"
21
22
  )
22
- position_costs = relationship(
23
- "SQLPositionCost",
24
- back_populates="position",
25
- lazy="dynamic",
26
- cascade="all, delete-orphan"
27
- )
28
23
  portfolio_id = Column(Integer, ForeignKey('portfolios.id'))
29
24
  portfolio = relationship("SQLPortfolio", back_populates="positions")
30
25
  __table_args__ = (
@@ -32,19 +27,19 @@ class SQLPosition(SQLBaseModel, Position, SQLAlchemyModelExtension):
32
27
  'symbol', 'portfolio_id', name='_symbol_portfolio_uc'
33
28
  ),
34
29
  )
35
- _cost = 0
36
30
 
37
31
  def __init__(
38
32
  self,
39
33
  symbol,
40
34
  amount=0,
41
- portfolio_id=None
35
+ cost=0,
36
+ portfolio_id=None,
42
37
  ):
43
38
  super(SQLPosition, self).__init__()
44
39
  self.symbol = symbol
45
40
  self.amount = amount
46
41
  self.portfolio_id = portfolio_id
47
- self.cost = 0
42
+ self.cost = cost
48
43
 
49
44
  @validates('id', 'symbol')
50
45
  def _write_once(self, key, value):
@@ -53,6 +48,16 @@ class SQLPosition(SQLBaseModel, Position, SQLAlchemyModelExtension):
53
48
  raise ValueError("{} is write-once".format(key))
54
49
  return value
55
50
 
51
+ def update(self, data):
52
+
53
+ if 'amount' in data:
54
+ self.amount = data.pop('amount')
55
+
56
+ if 'cost' in data:
57
+ self.cost = data.pop('cost')
58
+
59
+ super(SQLPosition, self).update(data)
60
+
56
61
  @property
57
62
  def ccxt_symbol(self):
58
63
  return f"{self.symbol}/{self.portfolio.trading_symbol}"
@@ -0,0 +1,23 @@
1
+ from sqlalchemy import Column, Integer, String, ForeignKey, Float
2
+ from sqlalchemy.orm import relationship
3
+
4
+ from investing_algorithm_framework.domain import PositionSnapshot
5
+ from investing_algorithm_framework.infrastructure.database import SQLBaseModel
6
+ from investing_algorithm_framework.infrastructure.models.model_extension \
7
+ import SQLAlchemyModelExtension
8
+
9
+
10
+ class SQLPositionSnapshot(
11
+ SQLBaseModel, PositionSnapshot, SQLAlchemyModelExtension
12
+ ):
13
+ __tablename__ = "position_snapshots"
14
+ id = Column(Integer, primary_key=True, unique=True)
15
+ symbol = Column(String)
16
+ amount = Column(Float)
17
+ cost = Column(Float)
18
+ portfolio_snapshot_id = Column(
19
+ Integer, ForeignKey('portfolio_snapshots.id')
20
+ )
21
+ portfolio_snapshot = relationship(
22
+ "SQLPortfolioSnapshot", back_populates="position_snapshots"
23
+ )