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,16 @@
1
+ from .backtest_portfolio_service import BacktestPortfolioService
2
+ from .portfolio_configuration_service import PortfolioConfigurationService
3
+ from .portfolio_service import PortfolioService
4
+ from .portfolio_snapshot_service import PortfolioSnapshotService
5
+ from .portfolio_sync_service import PortfolioSyncService
6
+ from .portfolio_provider_lookup import PortfolioProviderLookup
7
+
8
+ __all__ = [
9
+ "PortfolioConfigurationService",
10
+ "PortfolioSyncService",
11
+ "PortfolioSnapshotService",
12
+ "PortfolioService",
13
+ "PortfolioSnapshotService",
14
+ "BacktestPortfolioService",
15
+ "PortfolioProviderLookup"
16
+ ]
@@ -0,0 +1,54 @@
1
+ from datetime import datetime
2
+
3
+ from investing_algorithm_framework.domain import PortfolioConfiguration, \
4
+ OperationalException
5
+ from .portfolio_service import PortfolioService
6
+
7
+
8
+ class BacktestPortfolioService(PortfolioService):
9
+ """
10
+ BacktestPortfolioService is a subclass of PortfolioService.
11
+ It is used to create a portfolio for backtesting. This class does
12
+ not check if the initial balance is present on the exchange or broker.
13
+ """
14
+ def create_portfolio_from_configuration(
15
+ self,
16
+ portfolio_configuration: PortfolioConfiguration,
17
+ initial_amount=None,
18
+ created_at: datetime = None
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
+
46
+ data = {
47
+ "identifier": portfolio_configuration.identifier,
48
+ "market": portfolio_configuration.market,
49
+ "trading_symbol": portfolio_configuration.trading_symbol,
50
+ "unallocated": amount,
51
+ "initialized": True,
52
+ "created_at": created_at
53
+ }
54
+ return self.create(data)
@@ -0,0 +1,75 @@
1
+ import logging
2
+
3
+ from investing_algorithm_framework.domain import ApiException
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
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
+ """
14
+
15
+ def __init__(self, portfolio_repository, position_repository):
16
+ self.portfolio_repository = portfolio_repository
17
+ self.position_repository = position_repository
18
+ self.portfolio_configurations = []
19
+
20
+ def add(self, portfolio_configuration):
21
+ self.portfolio_configurations.append(portfolio_configuration)
22
+
23
+ def get(self, identifier):
24
+ portfolio_configuration = next(
25
+ (
26
+ portfolio_configuration for portfolio_configuration in
27
+ self.portfolio_configurations
28
+ if portfolio_configuration.identifier.upper() ==
29
+ identifier.upper()),
30
+ None
31
+ )
32
+
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
+ # )
41
+
42
+ return portfolio_configuration
43
+
44
+ def find(self, query_params):
45
+ market = query_params.get("market", None)
46
+ identifier = query_params.get("market", None)
47
+
48
+ if market is not None:
49
+ return next(
50
+ (portfolio_configuration for portfolio_configuration in
51
+ self.portfolio_configurations if
52
+ portfolio_configuration.market.upper() == market.upper()),
53
+ None
54
+ )
55
+ elif identifier is not None:
56
+ return next(
57
+ (portfolio_configuration for portfolio_configuration in
58
+ self.portfolio_configurations if
59
+ portfolio_configuration.identifier.upper()
60
+ == identifier.upper()),
61
+ None
62
+ )
63
+ elif market is None and identifier is None:
64
+ return self.portfolio_configurations[0]
65
+ else:
66
+ raise ApiException('Portfolio configuration not found', 404)
67
+
68
+ def get_all(self):
69
+ return self.portfolio_configurations
70
+
71
+ def count(self):
72
+ return len(self.portfolio_configurations)
73
+
74
+ def clear(self):
75
+ self.portfolio_configurations = []
@@ -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 = []
@@ -0,0 +1,188 @@
1
+ import logging
2
+ from datetime import datetime
3
+
4
+ from investing_algorithm_framework.domain import OperationalException, \
5
+ MarketCredentialService, Portfolio, Environment, ENVIRONMENT
6
+ from investing_algorithm_framework.services.configuration_service import \
7
+ ConfigurationService
8
+ from investing_algorithm_framework.services.repository_service \
9
+ import RepositoryService
10
+
11
+ logger = logging.getLogger("investing_algorithm_framework")
12
+
13
+
14
+ class PortfolioService(RepositoryService):
15
+ """
16
+ Service to manage portfolios. This service will sync the portfolios with
17
+ the exchange balances and orders. It will also create portfolios based on
18
+ the portfolio configurations registered by the user
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ configuration_service: ConfigurationService,
24
+ market_credential_service: MarketCredentialService,
25
+ order_service,
26
+ portfolio_configuration_service,
27
+ portfolio_snapshot_service,
28
+ position_service,
29
+ portfolio_repository,
30
+ portfolio_provider_lookup
31
+ ):
32
+ super().__init__(repository=portfolio_repository)
33
+ self.configuration_service = configuration_service
34
+ self.market_credential_service = market_credential_service
35
+ self.portfolio_configuration_service = portfolio_configuration_service
36
+ self.order_service = order_service
37
+ self.portfolio_snapshot_service = portfolio_snapshot_service
38
+ self.position_service = position_service
39
+ self.portfolio_provider_lookup = portfolio_provider_lookup
40
+
41
+ def find(self, query_params):
42
+ portfolio = self.repository.find(query_params)
43
+ portfolio_configuration = self.portfolio_configuration_service\
44
+ .get(portfolio.identifier)
45
+ portfolio.configuration = portfolio_configuration
46
+ return portfolio
47
+
48
+ def create(self, data):
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
80
+ portfolio = super(PortfolioService, self).create(data)
81
+ self.position_service.create(
82
+ {
83
+ "symbol": portfolio.get_trading_symbol(),
84
+ "amount": unallocated,
85
+ "portfolio_id": portfolio.id,
86
+ "cost": unallocated
87
+ }
88
+ )
89
+ return portfolio
90
+
91
+ def create_portfolio_from_configuration(
92
+ self,
93
+ portfolio_configuration,
94
+ initial_amount=None,
95
+ created_at: datetime = None
96
+ ) -> Portfolio:
97
+ """
98
+ Method to create a portfolio based on a portfolio configuration.
99
+ This method will create a portfolio based on the configuration
100
+ provided. If the portfolio already exists, it will be returned.
101
+
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
114
+ """
115
+ logger.info("Creating portfolios")
116
+
117
+ # Check if there is a market credential for the portfolio configuration
118
+ market_credential = self.market_credential_service.get(
119
+ portfolio_configuration.market
120
+ )
121
+
122
+ if market_credential is None:
123
+ raise OperationalException(
124
+ f"No market credential found for market "
125
+ f"{portfolio_configuration.market}. Cannot "
126
+ f"initialize portfolio configuration."
127
+ )
128
+
129
+ # Check if the portfolio already exists
130
+ # If the portfolio already exists, return the portfolio after checking
131
+ # the unallocated balance of the portfolio on the exchange
132
+ if self.repository.exists(
133
+ {"identifier": portfolio_configuration.identifier}
134
+ ):
135
+ portfolio = self.repository.find(
136
+ {"identifier": portfolio_configuration.identifier}
137
+ )
138
+ return portfolio
139
+ else:
140
+ # Create a new portfolio
141
+ portfolio = Portfolio.from_portfolio_configuration(
142
+ portfolio_configuration
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
+
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
+ )
@@ -0,0 +1,136 @@
1
+ from investing_algorithm_framework.domain import OrderStatus, \
2
+ PortfolioSnapshot
3
+ from investing_algorithm_framework.services.repository_service import \
4
+ RepositoryService
5
+
6
+
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
+ """
12
+
13
+ def __init__(
14
+ self,
15
+ repository,
16
+ portfolio_repository,
17
+ order_repository,
18
+ position_repository,
19
+ position_snapshot_service,
20
+ data_provider_service,
21
+ ):
22
+ self.order_repository = order_repository
23
+ self.position_snapshot_service = position_snapshot_service
24
+ self.portfolio_repository = portfolio_repository
25
+ self.position_repository = position_repository
26
+ self.data_provider_service = data_provider_service
27
+ super(PortfolioSnapshotService, self).__init__(repository)
28
+
29
+ def create_snapshot(
30
+ self,
31
+ portfolio,
32
+ created_at,
33
+ cash_flow=0,
34
+ created_orders=None,
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
+ """
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
+ )
87
+
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
102
+
103
+ for position in \
104
+ self.position_repository.get_all({"portfolio": portfolio.id}):
105
+
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
118
+
119
+ data = {
120
+ "portfolio_id": portfolio.id,
121
+ "trading_symbol": portfolio.trading_symbol,
122
+ "pending_value": pending_value,
123
+ "unallocated": portfolio.unallocated,
124
+ "net_size": portfolio.net_size,
125
+ "total_net_gain": portfolio.total_net_gain,
126
+ "total_revenue": portfolio.total_revenue,
127
+ "total_cost": portfolio.total_cost,
128
+ "cash_flow": cash_flow,
129
+ "created_at": created_at,
130
+ "total_value": total_value,
131
+ }
132
+ snapshot = self.create(data, save=save)
133
+ return snapshot
134
+
135
+ def get_latest_snapshot(self, portfolio_id):
136
+ pass