investing-algorithm-framework 3.7.0__py3-none-any.whl → 7.19.15__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 (256) hide show
  1. investing_algorithm_framework/__init__.py +168 -45
  2. investing_algorithm_framework/app/__init__.py +32 -1
  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 +1933 -589
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1725 -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/action_handlers/__init__.py +4 -2
  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 +1 -1
  39. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  40. investing_algorithm_framework/app/strategy.py +664 -84
  41. investing_algorithm_framework/app/task.py +5 -3
  42. investing_algorithm_framework/app/web/__init__.py +2 -1
  43. investing_algorithm_framework/app/web/create_app.py +4 -2
  44. investing_algorithm_framework/cli/__init__.py +0 -0
  45. investing_algorithm_framework/cli/cli.py +226 -0
  46. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  47. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  48. investing_algorithm_framework/cli/initialize_app.py +603 -0
  49. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  50. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  51. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  52. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  53. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  54. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  55. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  56. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  57. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  58. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  59. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  60. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  61. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  62. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  63. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  64. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  65. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  66. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  67. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  68. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  69. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  70. investing_algorithm_framework/create_app.py +40 -6
  71. investing_algorithm_framework/dependency_container.py +72 -56
  72. investing_algorithm_framework/domain/__init__.py +71 -47
  73. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  74. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  75. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  76. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  77. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  78. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  79. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  80. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  81. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  82. investing_algorithm_framework/domain/config.py +59 -91
  83. investing_algorithm_framework/domain/constants.py +13 -38
  84. investing_algorithm_framework/domain/data_provider.py +334 -0
  85. investing_algorithm_framework/domain/data_structures.py +3 -2
  86. investing_algorithm_framework/domain/exceptions.py +51 -1
  87. investing_algorithm_framework/domain/models/__init__.py +17 -12
  88. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  89. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  90. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  91. investing_algorithm_framework/domain/models/event.py +35 -0
  92. investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
  93. investing_algorithm_framework/domain/models/order/order.py +77 -83
  94. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  95. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  96. investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
  97. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
  98. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
  99. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  100. investing_algorithm_framework/domain/models/position/position.py +12 -0
  101. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  102. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  103. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  104. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  105. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  106. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  107. investing_algorithm_framework/domain/models/time_frame.py +37 -0
  108. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  109. investing_algorithm_framework/domain/models/time_unit.py +66 -2
  110. investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
  111. investing_algorithm_framework/domain/models/trade/trade.py +295 -171
  112. investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
  113. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  114. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  115. investing_algorithm_framework/domain/order_executor.py +112 -0
  116. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  117. investing_algorithm_framework/domain/services/__init__.py +2 -9
  118. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
  119. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  120. investing_algorithm_framework/domain/strategy.py +1 -29
  121. investing_algorithm_framework/domain/utils/__init__.py +12 -7
  122. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  123. investing_algorithm_framework/domain/utils/dates.py +57 -0
  124. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  125. investing_algorithm_framework/domain/utils/polars.py +53 -0
  126. investing_algorithm_framework/domain/utils/random.py +29 -0
  127. investing_algorithm_framework/download_data.py +108 -0
  128. investing_algorithm_framework/infrastructure/__init__.py +31 -18
  129. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  130. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  131. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  132. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  133. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  134. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
  135. investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
  136. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
  137. investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
  138. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  139. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  140. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
  141. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
  142. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
  143. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  144. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  145. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  146. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  147. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  148. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  149. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  150. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  151. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  152. investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
  153. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  154. investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
  155. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
  156. investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
  157. investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
  158. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  159. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  160. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  161. investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
  162. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  163. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  164. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  165. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  166. investing_algorithm_framework/services/__init__.py +113 -16
  167. investing_algorithm_framework/services/backtesting/__init__.py +0 -7
  168. investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
  169. investing_algorithm_framework/services/configuration_service.py +77 -11
  170. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  171. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  172. investing_algorithm_framework/services/market_credential_service.py +16 -1
  173. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  174. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  175. investing_algorithm_framework/services/metrics/beta.py +0 -0
  176. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  177. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  178. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  179. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  180. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  181. investing_algorithm_framework/services/metrics/generate.py +358 -0
  182. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  183. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  184. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  185. investing_algorithm_framework/services/metrics/returns.py +452 -0
  186. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  187. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  188. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  189. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  190. investing_algorithm_framework/services/metrics/trades.py +500 -0
  191. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  192. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  193. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  194. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  195. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  196. investing_algorithm_framework/services/order_service/__init__.py +3 -1
  197. investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
  198. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  199. investing_algorithm_framework/services/order_service/order_service.py +407 -326
  200. investing_algorithm_framework/services/portfolios/__init__.py +3 -1
  201. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
  202. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
  203. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  204. investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
  205. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
  206. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
  207. investing_algorithm_framework/services/positions/__init__.py +7 -0
  208. investing_algorithm_framework/services/positions/position_service.py +210 -0
  209. investing_algorithm_framework/services/repository_service.py +8 -2
  210. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  211. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  212. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  213. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  214. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  215. investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
  216. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  217. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  218. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  219. investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
  220. investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
  221. investing_algorithm_framework/app/algorithm.py +0 -1105
  222. investing_algorithm_framework/domain/graphs.py +0 -382
  223. investing_algorithm_framework/domain/metrics/__init__.py +0 -6
  224. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
  225. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
  226. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  227. investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
  228. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
  229. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  230. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  231. investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
  232. investing_algorithm_framework/domain/services/market_service.py +0 -153
  233. investing_algorithm_framework/domain/singleton.py +0 -9
  234. investing_algorithm_framework/domain/utils/backtesting.py +0 -472
  235. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
  236. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
  237. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
  238. investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
  239. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  240. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
  241. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  242. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  243. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
  244. investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
  245. investing_algorithm_framework/services/backtesting/graphs.py +0 -61
  246. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
  247. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
  248. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
  249. investing_algorithm_framework/services/position_service.py +0 -31
  250. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
  251. investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
  252. investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
  253. /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
  254. /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
  255. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  256. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
@@ -3,6 +3,7 @@ from .portfolio_configuration_service import PortfolioConfigurationService
3
3
  from .portfolio_service import PortfolioService
4
4
  from .portfolio_snapshot_service import PortfolioSnapshotService
5
5
  from .portfolio_sync_service import PortfolioSyncService
6
+ from .portfolio_provider_lookup import PortfolioProviderLookup
6
7
 
7
8
  __all__ = [
8
9
  "PortfolioConfigurationService",
@@ -10,5 +11,6 @@ __all__ = [
10
11
  "PortfolioSnapshotService",
11
12
  "PortfolioService",
12
13
  "PortfolioSnapshotService",
13
- "BacktestPortfolioService"
14
+ "BacktestPortfolioService",
15
+ "PortfolioProviderLookup"
14
16
  ]
@@ -1,4 +1,7 @@
1
- from investing_algorithm_framework.domain import PortfolioConfiguration
1
+ from datetime import datetime
2
+
3
+ from investing_algorithm_framework.domain import PortfolioConfiguration, \
4
+ OperationalException
2
5
  from .portfolio_service import PortfolioService
3
6
 
4
7
 
@@ -9,12 +12,43 @@ class BacktestPortfolioService(PortfolioService):
9
12
  not check if the initial balance is present on the exchange or broker.
10
13
  """
11
14
  def create_portfolio_from_configuration(
12
- self, portfolio_configuration: PortfolioConfiguration
15
+ self,
16
+ portfolio_configuration: PortfolioConfiguration,
17
+ initial_amount=None,
18
+ created_at: datetime = None
13
19
  ):
20
+ """
21
+ Wil create a portfolio from a portfolio configuration for backtesting.
22
+
23
+ Args:
24
+ portfolio_configuration (PortfolioConfiguration):
25
+ Portfolio configuration to create the portfolio from
26
+ initial_amount (Decimal): Initial balance for the portfolio
27
+ created_at (datetime): The date and time when the portfolio
28
+ is created. If not provided, the current date and time
29
+ will be used.
30
+
31
+ Returns:
32
+ Portfolio: The created portfolio
33
+ """
34
+ amount = portfolio_configuration.initial_balance
35
+
36
+ if initial_amount is not None:
37
+ amount = initial_amount
38
+
39
+ if amount is None:
40
+ raise OperationalException(
41
+ "Initial amount is required as a parameter or the " +
42
+ "'initial_balance' attribute needs to be set on the "
43
+ "portfolio configuration before running the backtest."
44
+ )
45
+
14
46
  data = {
15
47
  "identifier": portfolio_configuration.identifier,
16
48
  "market": portfolio_configuration.market,
17
49
  "trading_symbol": portfolio_configuration.trading_symbol,
18
- "unallocated": portfolio_configuration.initial_balance,
50
+ "unallocated": amount,
51
+ "initialized": True,
52
+ "created_at": created_at
19
53
  }
20
54
  return self.create(data)
@@ -6,6 +6,11 @@ logger = logging.getLogger(__name__)
6
6
 
7
7
 
8
8
  class PortfolioConfigurationService:
9
+ """
10
+ Service to manage portfolio configurations. This service will
11
+ manage the portfolio configurations that the user has
12
+ registered in the app.
13
+ """
9
14
 
10
15
  def __init__(self, portfolio_repository, position_repository):
11
16
  self.portfolio_repository = portfolio_repository
@@ -17,14 +22,22 @@ class PortfolioConfigurationService:
17
22
 
18
23
  def get(self, identifier):
19
24
  portfolio_configuration = next(
20
- (portfolio_configuration for portfolio_configuration in
21
- self.portfolio_configurations if
22
- portfolio_configuration.identifier == identifier.lower()),
25
+ (
26
+ portfolio_configuration for portfolio_configuration in
27
+ self.portfolio_configurations
28
+ if portfolio_configuration.identifier.upper() ==
29
+ identifier.upper()),
23
30
  None
24
31
  )
25
32
 
26
- if portfolio_configuration is None:
27
- raise ApiException('Portfolio configuration not found', 404)
33
+ # if portfolio_configuration is None:
34
+ # raise ApiException(
35
+ # f'Portfolio configuration not '
36
+ # f'found for {identifier}'
37
+ # " Please make sure that you have registered a portfolio "
38
+ # "configuration for the portfolio you are trying to use",
39
+ # 404
40
+ # )
28
41
 
29
42
  return portfolio_configuration
30
43
 
@@ -36,14 +49,15 @@ class PortfolioConfigurationService:
36
49
  return next(
37
50
  (portfolio_configuration for portfolio_configuration in
38
51
  self.portfolio_configurations if
39
- portfolio_configuration.market == market.lower()),
52
+ portfolio_configuration.market.upper() == market.upper()),
40
53
  None
41
54
  )
42
55
  elif identifier is not None:
43
56
  return next(
44
57
  (portfolio_configuration for portfolio_configuration in
45
- self.portfolio_configurations if
46
- portfolio_configuration.identifier == identifier.lower()),
58
+ self.portfolio_configurations if
59
+ portfolio_configuration.identifier.upper()
60
+ == identifier.upper()),
47
61
  None
48
62
  )
49
63
  elif market is None and identifier is None:
@@ -0,0 +1,106 @@
1
+ import logging
2
+ from collections import defaultdict
3
+ from typing import List, Union
4
+
5
+ from investing_algorithm_framework.domain import ImproperlyConfigured, \
6
+ PortfolioProvider
7
+
8
+ logger = logging.getLogger("investing_algorithm_framework")
9
+
10
+
11
+ class PortfolioProviderLookup:
12
+ """
13
+ Efficient lookup for portfolio providers based on market in O(1) time.
14
+ """
15
+ def __init__(self):
16
+ self.portfolio_providers = []
17
+
18
+ # These will be our lookup tables
19
+ self.portfolio_provider_lookup = defaultdict()
20
+
21
+ def add_portfolio_provider(self, portfolio_provider: PortfolioProvider):
22
+ """
23
+ Add a portfolio provider to the lookup table.
24
+
25
+ Args:
26
+ portfolio_provider (PortfolioProvider): The portfolio provider
27
+ to be added.
28
+
29
+ Returns:
30
+ None
31
+ """
32
+ self.portfolio_providers.append(portfolio_provider)
33
+
34
+ def register_portfolio_provider_for_market(self, market) -> None:
35
+ """
36
+ Register a portfolio provider for a specific market.
37
+ This method will create a lookup table for efficient access to
38
+ portfolio providers based on market. It will use the
39
+ portfolio providers that are currently registered in the
40
+ portfolio_providers list. The lookup table will be a dictionary
41
+ where the key is the market and the value is the portfolio provider.
42
+
43
+ This method will also check if the portfolio provider supports
44
+ the market. If no portfolio provider is found for the market,
45
+ it will raise an ImproperlyConfigured exception.
46
+
47
+ If multiple portfolio providers are found for the market,
48
+ it will sort them by priority and pick the best one.
49
+
50
+ Args:
51
+ market:
52
+
53
+ Returns:
54
+ None
55
+ """
56
+ matches = []
57
+ for portfolio_provider in self.portfolio_providers:
58
+ if portfolio_provider.supports_market(market):
59
+ matches.append(portfolio_provider)
60
+
61
+ if len(matches) == 0:
62
+ raise ImproperlyConfigured(
63
+ f"No portfolio provider found for market "
64
+ f"{market}. Cannot configure portfolio."
65
+ f" Please make sure that you have registered a portfolio "
66
+ f"provider for the market you are trying to use"
67
+ )
68
+
69
+ # Sort by priority and pick the best one
70
+ best_provider = sorted(matches, key=lambda x: x.priority)[0]
71
+ self.portfolio_provider_lookup[market] = best_provider
72
+
73
+ def get_portfolio_provider(self, market) -> Union[PortfolioProvider, None]:
74
+ """
75
+ Get the portfolio provider for a specific market.
76
+ This method will return the portfolio provider for the given market.
77
+ If no portfolio provider is found, it will return None.
78
+
79
+ Args:
80
+ market:
81
+
82
+ Returns:
83
+ PortfolioProvider: The portfolio provider for the given market.
84
+ """
85
+ return self.portfolio_provider_lookup.get(market, None)
86
+
87
+ def get_all(self) -> List[PortfolioProvider]:
88
+ """
89
+ Get all portfolio providers.
90
+ This method will return all portfolio providers that are currently
91
+ registered in the portfolio_providers list.
92
+
93
+ Returns:
94
+ List[PortfolioProvider]: A list of all portfolio providers.
95
+ """
96
+ return self.portfolio_providers
97
+
98
+ def reset(self):
99
+ """
100
+ Function to reset the order executor lookup table
101
+
102
+ Returns:
103
+ None
104
+ """
105
+ self.portfolio_provider_lookup = defaultdict()
106
+ self.portfolio_providers = []
@@ -1,8 +1,8 @@
1
1
  import logging
2
2
  from datetime import datetime
3
3
 
4
- from investing_algorithm_framework.domain import OrderSide, OrderStatus, \
5
- OperationalException, MarketService, MarketCredentialService, Portfolio
4
+ from investing_algorithm_framework.domain import OperationalException, \
5
+ MarketCredentialService, Portfolio, Environment, ENVIRONMENT
6
6
  from investing_algorithm_framework.services.configuration_service import \
7
7
  ConfigurationService
8
8
  from investing_algorithm_framework.services.repository_service \
@@ -21,22 +21,22 @@ class PortfolioService(RepositoryService):
21
21
  def __init__(
22
22
  self,
23
23
  configuration_service: ConfigurationService,
24
- market_service: MarketService,
25
24
  market_credential_service: MarketCredentialService,
26
25
  order_service,
27
- portfolio_repository,
28
26
  portfolio_configuration_service,
29
27
  portfolio_snapshot_service,
30
- position_service
28
+ position_service,
29
+ portfolio_repository,
30
+ portfolio_provider_lookup
31
31
  ):
32
+ super().__init__(repository=portfolio_repository)
32
33
  self.configuration_service = configuration_service
33
34
  self.market_credential_service = market_credential_service
34
- self.market_service = market_service
35
35
  self.portfolio_configuration_service = portfolio_configuration_service
36
36
  self.order_service = order_service
37
37
  self.portfolio_snapshot_service = portfolio_snapshot_service
38
38
  self.position_service = position_service
39
- super(PortfolioService, self).__init__(portfolio_repository)
39
+ self.portfolio_provider_lookup = portfolio_provider_lookup
40
40
 
41
41
  def find(self, query_params):
42
42
  portfolio = self.repository.find(query_params)
@@ -47,6 +47,36 @@ class PortfolioService(RepositoryService):
47
47
 
48
48
  def create(self, data):
49
49
  unallocated = data.get("unallocated", 0)
50
+ market = data.get("market")
51
+
52
+ # Check if the app is in backtest mode
53
+ config = self.configuration_service.get_config()
54
+ environment = config[ENVIRONMENT]
55
+
56
+ if not Environment.BACKTEST.equals(environment):
57
+ # Check if there is a market credential
58
+ # for the portfolio configuration
59
+ market_credential = self.market_credential_service.get(market)
60
+
61
+ if market_credential is None:
62
+ raise OperationalException(
63
+ f"No market credential found for market "
64
+ f"{market}. Cannot "
65
+ f"initialize portfolio configuration."
66
+ )
67
+
68
+ identifier = data.get("identifier")
69
+ # Check if the portfolio already exists
70
+ # If the portfolio already exists, return the portfolio after checking
71
+ # the unallocated balance of the portfolio on the exchange
72
+ if identifier is not None and self.repository.exists(
73
+ {"identifier": identifier}
74
+ ):
75
+ portfolio = self.repository.find(
76
+ {"identifier": identifier}
77
+ )
78
+ return portfolio
79
+ data["initial_balance"] = unallocated
50
80
  portfolio = super(PortfolioService, self).create(data)
51
81
  self.position_service.create(
52
82
  {
@@ -56,30 +86,13 @@ class PortfolioService(RepositoryService):
56
86
  "cost": unallocated
57
87
  }
58
88
  )
59
- self.create_snapshot(portfolio.id, created_at=portfolio.created_at)
60
89
  return portfolio
61
90
 
62
- def create_snapshot(self, portfolio_id, created_at=None):
63
-
64
- if created_at is None:
65
- created_at = datetime.utcnow()
66
-
67
- portfolio = self.get(portfolio_id)
68
- pending_orders = self.order_service.get_all(
69
- {
70
- "order_side": OrderSide.BUY.value,
71
- "status": OrderStatus.OPEN.value,
72
- "portfolio_id": portfolio.id
73
- }
74
- )
75
- return self.portfolio_snapshot_service.create_snapshot(
76
- portfolio,
77
- pending_orders=pending_orders,
78
- created_at=created_at
79
- )
80
-
81
91
  def create_portfolio_from_configuration(
82
- self, portfolio_configuration
92
+ self,
93
+ portfolio_configuration,
94
+ initial_amount=None,
95
+ created_at: datetime = None
83
96
  ) -> Portfolio:
84
97
  """
85
98
  Method to create a portfolio based on a portfolio configuration.
@@ -87,6 +100,17 @@ class PortfolioService(RepositoryService):
87
100
  provided. If the portfolio already exists, it will be returned.
88
101
 
89
102
  If the portfolio does not exist, it will be created.
103
+
104
+ Args:
105
+ portfolio_configuration (PortfolioConfiguration)
106
+ Portfolio configuration to create the portfolio from
107
+ initial_amount (Decimal): Initial balance for the portfolio
108
+ created_at (datetime): The date and time when the portfolio
109
+ is created. If not provided, the current date and time
110
+ will be used.
111
+
112
+ Returns:
113
+ Portfolio: Portfolio created from the configuration
90
114
  """
91
115
  logger.info("Creating portfolios")
92
116
 
@@ -117,4 +141,48 @@ class PortfolioService(RepositoryService):
117
141
  portfolio = Portfolio.from_portfolio_configuration(
118
142
  portfolio_configuration
119
143
  )
144
+ data = portfolio.to_dict()
145
+
146
+ if created_at is not None:
147
+ data["created_at"] = created_at
148
+
149
+ if initial_amount is not None:
150
+ data["unallocated"] = initial_amount
151
+
152
+ self.create(data)
153
+
120
154
  return portfolio
155
+
156
+ def update_portfolio_with_filled_order(self, order, filled_amount) -> None:
157
+ """
158
+ Function to update the portfolio with filled order.
159
+
160
+ Args:
161
+ order: Order object
162
+ filled_amount: float
163
+
164
+ Returns:
165
+ None
166
+ """
167
+ filled_size = filled_amount * order.get_price()
168
+
169
+ if filled_size <= 0:
170
+ return
171
+
172
+ logger.info(
173
+ f"Syncing portfolio with filled sell "
174
+ f"order {order.get_id()} with filled amount "
175
+ f"{filled_amount}"
176
+ )
177
+
178
+ position = self.position_service.get(order.position_id)
179
+ portfolio = self.get(position.portfolio_id)
180
+
181
+ self.update(
182
+ portfolio.id,
183
+ {
184
+ "unallocated": portfolio.get_unallocated() + filled_size,
185
+ "total_trade_volume":
186
+ portfolio.get_total_trade_volume() + filled_size,
187
+ }
188
+ )
@@ -1,67 +1,136 @@
1
- from datetime import datetime
2
-
1
+ from investing_algorithm_framework.domain import OrderStatus, \
2
+ PortfolioSnapshot
3
3
  from investing_algorithm_framework.services.repository_service import \
4
4
  RepositoryService
5
5
 
6
6
 
7
7
  class PortfolioSnapshotService(RepositoryService):
8
+ """
9
+ Service to manage portfolio snapshots. This service will create snapshots
10
+ of the portfolio at specific intervals or based on certain events.
11
+ """
8
12
 
9
13
  def __init__(
10
14
  self,
11
15
  repository,
16
+ portfolio_repository,
17
+ order_repository,
12
18
  position_repository,
13
- position_snapshot_service
19
+ position_snapshot_service,
20
+ data_provider_service,
14
21
  ):
22
+ self.order_repository = order_repository
15
23
  self.position_snapshot_service = position_snapshot_service
24
+ self.portfolio_repository = portfolio_repository
16
25
  self.position_repository = position_repository
26
+ self.data_provider_service = data_provider_service
17
27
  super(PortfolioSnapshotService, self).__init__(repository)
18
28
 
19
29
  def create_snapshot(
20
30
  self,
21
31
  portfolio,
22
- pending_orders=None,
32
+ created_at,
33
+ cash_flow=0,
23
34
  created_orders=None,
24
- created_at=None,
25
- cash_flow=0
26
- ):
35
+ open_orders=None,
36
+ positions=None,
37
+ save=True
38
+ ) -> PortfolioSnapshot:
39
+ """
40
+ Function to create a snapshot of the portfolio. During
41
+ creation, it will calculate the pending value of the
42
+ portfolio based on the pending orders and created orders. Also,
43
+ it will calculate the total value of the portfolio based on the
44
+ current positions and the unallocated cash. It will do this by
45
+ fetching the current ticker prices for each position in the portfolio.
46
+ This function will also create position snapshots for each position
47
+ in the portfolio and associate them with the snapshot.
48
+
49
+ Args:
50
+ portfolio (Portfolio): The portfolio to create a snapshot for.
51
+ created_at (datetime, optional): The date and time when the
52
+ snapshot was created. If not provided, the current date
53
+ and time will be used.
54
+ cash_flow (float, optional): The cash flow to include
55
+ in the snapshot.
56
+ created_orders (list, optional): A list of created orders
57
+ to consider when calculating the pending value.
58
+ open_orders (list, optional): A list of open orders
59
+ to consider when calculating the pending value.
60
+ positions (list, optional): A list of positions to consider
61
+ when calculating the total value of the portfolio.
62
+ save (bool, optional): Whether to save the snapshot to
63
+ the database.
64
+
65
+ Returns:
66
+ PortfolioSnapshot: The created snapshot of the portfolio.
67
+ """
27
68
  pending_value = 0
69
+ pending_symbols = set()
70
+ allocated = 0
71
+
72
+ if open_orders is None:
73
+ open_orders = self.order_repository.get_all(
74
+ {
75
+ "status": OrderStatus.OPEN.value,
76
+ "portfolio_id": portfolio.id
77
+ }
78
+ )
79
+
80
+ if created_orders is None:
81
+ created_orders = self.order_repository.get_all(
82
+ {
83
+ "status": OrderStatus.CREATED.value,
84
+ "portfolio_id": portfolio.id
85
+ }
86
+ )
28
87
 
29
- if created_orders is not None:
30
- for order in created_orders:
31
- pending_value += order.get_price() * order.get_amount()
88
+ if positions is None:
89
+ positions = self.position_repository.get_all(
90
+ {"portfolio": portfolio.id}
91
+ )
92
+
93
+ for order in created_orders:
94
+ pending_value += order.get_price() * order.get_amount()
95
+ pending_symbols.add(order.get_symbol())
96
+
97
+ for order in open_orders:
98
+ pending_value += order.get_price() * order.get_remaining()
99
+ pending_symbols.add(order.get_symbol())
100
+
101
+ total_value = portfolio.get_unallocated() + pending_value
32
102
 
33
- if pending_orders is not None:
34
- for order in pending_orders:
35
- pending_value += order.get_price() * order.get_remaining()
103
+ for position in \
104
+ self.position_repository.get_all({"portfolio": portfolio.id}):
36
105
 
37
- if created_at is None:
38
- created_at = datetime.now()
106
+ if position.get_symbol() != portfolio.get_trading_symbol():
107
+ symbol_pair = f"{position.get_symbol()}/" \
108
+ f"{portfolio.get_trading_symbol()}"
109
+ ticker = self.data_provider_service.get_ticker_data(
110
+ symbol=symbol_pair,
111
+ market=portfolio.market,
112
+ date=created_at
113
+ )
114
+ # Calculate the position worth
115
+ position_worth = position.get_amount() * ticker["bid"]
116
+ allocated += position_worth
117
+ total_value += position_worth
39
118
 
40
119
  data = {
41
120
  "portfolio_id": portfolio.id,
42
121
  "trading_symbol": portfolio.trading_symbol,
43
122
  "pending_value": pending_value,
44
123
  "unallocated": portfolio.unallocated,
124
+ "net_size": portfolio.net_size,
45
125
  "total_net_gain": portfolio.total_net_gain,
46
126
  "total_revenue": portfolio.total_revenue,
47
127
  "total_cost": portfolio.total_cost,
48
128
  "cash_flow": cash_flow,
49
129
  "created_at": created_at,
130
+ "total_value": total_value,
50
131
  }
51
- snapshot = self.create(data)
52
- positions = self.position_repository.get_all(
53
- {"portfolio": portfolio.id}
54
- )
55
-
56
- for position in positions:
57
- self.position_snapshot_service.create_snapshot(
58
- snapshot.id, position
59
- )
60
-
132
+ snapshot = self.create(data, save=save)
61
133
  return snapshot
62
134
 
63
135
  def get_latest_snapshot(self, portfolio_id):
64
136
  pass
65
-
66
- def get_snapshots(self, portfolio_id, start_date=None, end_date=None):
67
- pass