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,8 +3,6 @@ from enum import Enum
3
3
 
4
4
  class OrderType(Enum):
5
5
  LIMIT = 'LIMIT'
6
- MARKET = 'MARKET'
7
- STOP_LOSS_LIMIT = "STOP_LOSS_LIMIT"
8
6
 
9
7
  @staticmethod
10
8
  def from_string(value: str):
@@ -15,7 +13,7 @@ class OrderType(Enum):
15
13
  if value.upper() == order_type.value:
16
14
  return order_type
17
15
 
18
- raise ValueError("Could not convert value to OrderType")
16
+ raise ValueError(f"Could not convert value: {value} to OrderType")
19
17
 
20
18
  @staticmethod
21
19
  def from_value(value):
@@ -2,6 +2,37 @@ from investing_algorithm_framework.domain.models.base_model import BaseModel
2
2
 
3
3
 
4
4
  class Portfolio(BaseModel):
5
+ """
6
+ Portfolio base class.
7
+
8
+ A portfolio is a collection of positions that are managed by an algorithm.
9
+
10
+ Attributes:
11
+ * identifier: str, unique identifier of the portfolio
12
+ * trading_symbol: str, trading symbol of the portfolio
13
+ * unallocated: float, the size of the trading symbol that is not
14
+ allocated. For example, if the trading symbol is USDT and the unallocated
15
+ is 1000, it means that the portfolio has 1000 USDT that is not
16
+ allocated to any position.
17
+ * net_size: float, net size of the portfolio is the initial balance of the
18
+ portfolio plus the all the net gains of the trades. The
19
+ * realized: float, the realized gain of the portfolio is the sum of all the
20
+ realized gains of the trades.
21
+ * total_revenue: float, the total revenue of the portfolio is the sum
22
+ of all the orders (price * size)
23
+ * total_cost: float, the total cost of the portfolio is the sum of all the
24
+ costs of the trades (price * size (for buy orders)
25
+ or -price * size (for sell orders))
26
+ * total_net_gain: float, the total net gain of the portfolio is the sum of
27
+ all the net gains of the trades
28
+ * total_trade_volume: float, the total trade volume of the
29
+ portfolio is the sum of all the sizes of the trades
30
+ * market: str, the market of the portfolio (e.g. BITVAVO, BINANCE)
31
+ * created_at: datetime, the datetime when the portfolio was created
32
+ * updated_at: datetime, the datetime when the portfolio was last updated
33
+ * initialized: bool, whether the portfolio is initialized or not
34
+ * initial_balance: float, the initial balance of the portfolio
35
+ """
5
36
 
6
37
  def __init__(
7
38
  self,
@@ -9,6 +40,7 @@ class Portfolio(BaseModel):
9
40
  trading_symbol,
10
41
  net_size,
11
42
  unallocated,
43
+ initial_balance,
12
44
  market,
13
45
  realized=0,
14
46
  total_revenue=0,
@@ -16,21 +48,25 @@ class Portfolio(BaseModel):
16
48
  total_net_gain=0,
17
49
  total_trade_volume=0,
18
50
  created_at=None,
19
- updated_at=None
51
+ updated_at=None,
52
+ initialized=False,
20
53
  ):
21
54
  self.identifier = identifier
22
55
  self.updated_at = None
23
56
  self.trading_symbol = trading_symbol.upper()
24
57
  self.net_size = net_size
25
58
  self.unallocated = unallocated
59
+ self.initial_balance = initial_balance
26
60
  self.realized = realized
27
61
  self.total_revenue = total_revenue
28
62
  self.total_cost = total_cost
29
63
  self.total_net_gain = total_net_gain
30
64
  self.total_trade_volume = total_trade_volume
31
- self.market = market
65
+ self.market = market.upper()
32
66
  self.created_at = created_at
33
67
  self.updated_at = updated_at
68
+ self.initialized = initialized
69
+ self._allocated = None
34
70
 
35
71
  def __repr__(self):
36
72
  return self.repr(
@@ -42,6 +78,7 @@ class Portfolio(BaseModel):
42
78
  total_revenue=self.total_revenue,
43
79
  total_cost=self.total_cost,
44
80
  market=self.market,
81
+ initial_balance=self.initial_balance
45
82
  )
46
83
 
47
84
  def get_identifier(self):
@@ -80,12 +117,53 @@ class Portfolio(BaseModel):
80
117
  def get_market(self):
81
118
  return self.market
82
119
 
120
+ def get_initial_balance(self):
121
+ return self.initial_balance
122
+
123
+ @property
124
+ def allocated(self):
125
+
126
+ if self._allocated is None:
127
+ return 0.0
128
+
129
+ return self._allocated
130
+
131
+ @allocated.setter
132
+ def allocated(self, value):
133
+ self._allocated = value
134
+
83
135
  @staticmethod
84
136
  def from_portfolio_configuration(portfolio_configuration):
137
+ """
138
+ Function to create a portfolio from a portfolio configuration
139
+
140
+ We assume that a portfolio that is created from a configuration
141
+ is always un initialized.
142
+
143
+ Args:
144
+ portfolio_configuration: PortfolioConfiguration
145
+
146
+ Returns:
147
+ Portfolio
148
+ """
85
149
  return Portfolio(
86
150
  identifier=portfolio_configuration.identifier,
87
151
  trading_symbol=portfolio_configuration.trading_symbol,
88
152
  unallocated=portfolio_configuration.initial_balance,
89
153
  net_size=portfolio_configuration.initial_balance,
90
- market=portfolio_configuration.market
154
+ market=portfolio_configuration.market,
155
+ initial_balance=portfolio_configuration.initial_balance,
156
+ initialized=False
91
157
  )
158
+
159
+ def to_dict(self):
160
+ return {
161
+ "trading_symbol": self.trading_symbol,
162
+ "market": self.market,
163
+ "unallocated": self.unallocated,
164
+ "identifier": self.identifier,
165
+ "created_at": self.created_at,
166
+ "updated_at": self.updated_at,
167
+ "initialized": self.initialized,
168
+ "initial_balance": self.initial_balance,
169
+ }
@@ -6,6 +6,23 @@ from investing_algorithm_framework.domain.models.base_model import BaseModel
6
6
 
7
7
 
8
8
  class PortfolioConfiguration(BaseModel):
9
+ """
10
+ This class represents a portfolio configuration. It is used to
11
+ configure the portfolio that the user wants to create.
12
+
13
+ Attributes:
14
+ - market: The market where the portfolio will be created
15
+ - trading_symbol: The trading symbol of the portfolio
16
+ - track_from: The date from which the portfolio will be tracked
17
+ - identifier: The identifier of the portfolio
18
+ - initial_balance: The initial balance of the portfolio
19
+
20
+ For backtesting, a portfolio configuration is used to create a
21
+ portfolio that will be used to simulate the trading of the algorithm. if
22
+ the user does not provide an initial balance, the portfolio will be created
23
+ with a balance of according to the initial balanace of
24
+ the PortfolioConfiguration class.
25
+ """
9
26
 
10
27
  def __init__(
11
28
  self,
@@ -16,13 +33,19 @@ class PortfolioConfiguration(BaseModel):
16
33
  initial_balance=None,
17
34
  ):
18
35
  self._market = market
36
+
37
+ if self._market is not None:
38
+ self._market = self._market.upper()
39
+
19
40
  self._track_from = None
20
41
  self._trading_symbol = trading_symbol.upper()
21
42
  self._identifier = identifier
22
43
  self._initial_balance = initial_balance
23
44
 
24
45
  if self.identifier is None:
25
- self._identifier = market.lower()
46
+ self._identifier = market.upper()
47
+ else:
48
+ self._identifier = identifier.upper()
26
49
 
27
50
  if track_from:
28
51
  self._track_from = parse(track_from)
@@ -35,8 +58,8 @@ class PortfolioConfiguration(BaseModel):
35
58
  @property
36
59
  def market(self):
37
60
 
38
- if hasattr(self._market, "lower"):
39
- return self._market.lower()
61
+ if hasattr(self._market, "upper"):
62
+ return self._market.upper()
40
63
 
41
64
  return self._market
42
65
 
@@ -1,3 +1,7 @@
1
+ from datetime import timezone
2
+
3
+ from dateutil import parser
4
+
1
5
  from investing_algorithm_framework.domain.models.base_model import BaseModel
2
6
 
3
7
 
@@ -5,16 +9,19 @@ class PortfolioSnapshot(BaseModel):
5
9
 
6
10
  def __init__(
7
11
  self,
8
- portfolio_id,
9
- trading_symbol,
10
- pending_value,
11
- unallocated,
12
- total_net_gain,
13
- total_revenue,
14
- total_cost,
15
- cash_flow,
16
- created_at,
17
- position_snapshots=None
12
+ portfolio_id=None,
13
+ trading_symbol=None,
14
+ pending_value=None,
15
+ unallocated=None,
16
+ net_size=None,
17
+ total_net_gain=None,
18
+ total_revenue=None,
19
+ total_cost=None,
20
+ total_value=None,
21
+ cash_flow=None,
22
+ created_at=None,
23
+ position_snapshots=None,
24
+ metadata=None,
18
25
  ):
19
26
  self.portfolio_id = portfolio_id
20
27
  self.trading_symbol = trading_symbol
@@ -22,9 +29,19 @@ class PortfolioSnapshot(BaseModel):
22
29
  self.unallocated = unallocated
23
30
  self.total_net_gain = total_net_gain
24
31
  self.total_revenue = total_revenue
32
+ self.total_value = total_value if total_value is not None else 0.0
33
+ self.net_size = net_size
25
34
  self.total_cost = total_cost
26
35
  self.cash_flow = cash_flow
27
- self.created_at = created_at
36
+ self.metadata = metadata if metadata is not None else {}
37
+
38
+ if created_at is not None and isinstance(created_at, str):
39
+ self.created_at = parser.parse(created_at)
40
+ else:
41
+ self.created_at = created_at
42
+
43
+ # Make sure that created_at is a timezone aware datetime object
44
+ self.created_at = self.created_at.replace(tzinfo=timezone.utc)
28
45
 
29
46
  if position_snapshots is None:
30
47
  position_snapshots = []
@@ -67,6 +84,12 @@ class PortfolioSnapshot(BaseModel):
67
84
  def set_total_revenue(self, total_revenue):
68
85
  self.total_revenue = total_revenue
69
86
 
87
+ def get_total_value(self):
88
+ return self.total_value
89
+
90
+ def set_total_value(self, total_value):
91
+ self.total_value = total_value
92
+
70
93
  def get_total_cost(self):
71
94
  return self.total_cost
72
95
 
@@ -102,10 +125,84 @@ class PortfolioSnapshot(BaseModel):
102
125
  portfolio_id=self.portfolio_id,
103
126
  created_at=self.created_at.strftime("%Y-%m-%d %H:%M:%S"),
104
127
  trading_symbol=self.trading_symbol,
128
+ net_size=self.net_size,
105
129
  unallocated=self.unallocated,
106
130
  pending_value=self.pending_value,
107
131
  total_net_gain=self.total_net_gain,
108
132
  total_revenue=self.total_revenue,
109
133
  total_cost=self.total_cost,
110
134
  cash_flow=self.cash_flow,
135
+ metadata=self.metadata,
136
+ )
137
+
138
+ def to_dict(self, datetime_format=None):
139
+ """
140
+ Convert the portfolio snapshot object to a dictionary
141
+
142
+ Args:
143
+ datetime_format (str): The format to use for the datetime fields.
144
+ If None, the datetime fields will be returned as is.
145
+ Defaults to None.
146
+
147
+ Returns:
148
+ dict: A dictionary representation of the portfolio snapshot object.
149
+ """
150
+ def ensure_iso(value):
151
+ if hasattr(value, "isoformat"):
152
+ if value.tzinfo is None:
153
+ value = value.replace(tzinfo=timezone.utc)
154
+ return value.isoformat()
155
+ return value
156
+
157
+ created_at = ensure_iso(self.created_at) if self.created_at else None
158
+
159
+ return {
160
+ "metadata": self.metadata if self.metadata else {},
161
+ "portfolio_id": self.portfolio_id if self.portfolio_id else "",
162
+ "trading_symbol": self.trading_symbol
163
+ if self.trading_symbol else "",
164
+ "pending_value": self.pending_value if self.pending_value else 0.0,
165
+ "unallocated": self.unallocated if self.unallocated else 0.0,
166
+ "total_net_gain": self.total_net_gain
167
+ if self.total_net_gain else 0.0,
168
+ "total_revenue": self.total_revenue if self.total_revenue else 0.0,
169
+ "total_cost": self.total_cost if self.total_cost else 0.0,
170
+ "cash_flow": self.cash_flow if self.cash_flow else 0.0,
171
+ "net_size": self.net_size if self.net_size else 0.0,
172
+ "created_at": created_at if created_at else "",
173
+ "total_value": self.total_value if self.total_value else 0.0,
174
+ }
175
+
176
+ @staticmethod
177
+ def from_dict(data):
178
+ """
179
+ Create a PortfolioSnapshot object from a dictionary.
180
+
181
+ Args:
182
+ data (dict): A dictionary containing the portfolio snapshot data.
183
+
184
+ Returns:
185
+ PortfolioSnapshot: An instance of PortfolioSnapshot.
186
+ """
187
+ created_at_str = data.get("created_at")
188
+ created_at = parser.parse(created_at_str)
189
+
190
+ # Ensure created_at is timezone aware
191
+ created_at = created_at.replace(tzinfo=timezone.utc)
192
+
193
+ return PortfolioSnapshot(
194
+ net_size=data.get("net_size", 0.0),
195
+ created_at=created_at,
196
+ total_value=data.get("total_value", 0.0),
197
+ trading_symbol=data.get(
198
+ "trading_symbol", None
199
+ ),
200
+ portfolio_id=data.get("portfolio_id", None),
201
+ pending_value=data.get("pending_value", 0.0),
202
+ unallocated=data.get("unallocated", 0.0),
203
+ total_net_gain=data.get("total_net_gain", 0.0),
204
+ total_revenue=data.get("total_revenue", 0.0),
205
+ total_cost=data.get("total_cost", 0.0),
206
+ cash_flow=data.get("cash_flow", 0.0),
207
+ metadata=data.get("metadata", {})
111
208
  )
@@ -1,4 +1,5 @@
1
1
  from .position import Position
2
2
  from .position_snapshot import PositionSnapshot
3
+ from .position_size import PositionSize
3
4
 
4
- __all__ = ["Position", "PositionSnapshot"]
5
+ __all__ = ["Position", "PositionSnapshot", "PositionSize"]
@@ -2,6 +2,9 @@ from investing_algorithm_framework.domain.models.base_model import BaseModel
2
2
 
3
3
 
4
4
  class Position(BaseModel):
5
+ """
6
+ This class represents a position in a portfolio.
7
+ """
5
8
 
6
9
  def __init__(
7
10
  self,
@@ -47,6 +50,15 @@ class Position(BaseModel):
47
50
  "portfolio_id": self.portfolio_id,
48
51
  }
49
52
 
53
+ @staticmethod
54
+ def from_dict(data: dict):
55
+ return Position(
56
+ symbol=data.get("symbol"),
57
+ amount=data.get("amount", 0),
58
+ cost=data.get("cost", 0),
59
+ portfolio_id=data.get("portfolio_id"),
60
+ )
61
+
50
62
  def __repr__(self):
51
63
  return self.repr(
52
64
  symbol=self.symbol,
@@ -0,0 +1,41 @@
1
+ from typing import Optional
2
+ from investing_algorithm_framework.domain.exceptions import \
3
+ OperationalException
4
+
5
+
6
+ class PositionSize:
7
+ """
8
+ Defines how much capital to allocate to a specific symbol.
9
+ """
10
+
11
+ def __init__(
12
+ self,
13
+ symbol: str,
14
+ percentage_of_portfolio: Optional[float] = None,
15
+ fixed_amount: Optional[float] = None
16
+ ):
17
+ self.symbol = symbol
18
+ self.percentage_of_portfolio = percentage_of_portfolio
19
+ self.fixed_amount = fixed_amount
20
+
21
+ def get_size(self, portfolio, asset_price) -> float:
22
+ """
23
+ Calculate size in currency/units.
24
+ """
25
+ if self.fixed_amount is not None:
26
+ return self.fixed_amount
27
+ elif self.percentage_of_portfolio is not None:
28
+ total_value = portfolio.get_unallocated() + portfolio.allocated
29
+ return total_value * (self.percentage_of_portfolio / 100)
30
+ else:
31
+ raise OperationalException(
32
+ "A position size object must have either a fixed amount or a "
33
+ "percentage of the portfolio defined."
34
+ )
35
+
36
+ def __repr__(self) -> str:
37
+ return (
38
+ f"PositionSize(symbol={self.symbol}, "
39
+ f"percentage_of_portfolio={self.percentage_of_portfolio}, "
40
+ f"fixed_amount={self.fixed_amount})"
41
+ )
@@ -0,0 +1,7 @@
1
+ from .stop_loss_rule import StopLossRule
2
+ from .take_profit_rule import TakeProfitRule
3
+
4
+ __all__ = [
5
+ "StopLossRule",
6
+ "TakeProfitRule",
7
+ ]
@@ -0,0 +1,51 @@
1
+ class StopLossRule:
2
+ """
3
+ A rule that defines when to trigger a stop loss based
4
+ on a specified threshold such as a percentage drop in price or
5
+ a fixed amount.
6
+
7
+ if trade_risk_type is fixed, the stop loss price is calculated as follows:
8
+ You buy an asset at $100.
9
+ You set a 5% stop loss, meaning you will sell if
10
+ the price drops to $95.
11
+ If the price rises to $120, the stop loss is not triggered.
12
+ But if the price keeps falling to $95, the stop loss triggers,
13
+ and you exit with a $5 loss.
14
+
15
+ if trade_risk_type is trailing, the stop loss price is
16
+ calculated as follows:
17
+ You buy an asset at $100.
18
+ You set a 5% trailing stop loss, meaning you will sell if
19
+ the price drops 5% from its peak at $96
20
+ If the price rises to $120, the stop loss adjusts
21
+ to $114 (5% below $120).
22
+ If the price falls to $114, the position is
23
+ closed, securing a $14 profit.
24
+ But if the price keeps rising to $150, the stop
25
+ loss moves up to $142.50.
26
+ If the price drops from $150 to $142.50, the stop
27
+ loss triggers, and you exit with a $42.50 profit.
28
+
29
+ Attributes:
30
+ - percentage_threshold (float): The percentage drop in price
31
+ that triggers the stop loss.
32
+ - trailing (bool): Indicates whether the stop loss is trailing
33
+ or fixed.
34
+ - sell_percentage (float): The percentage of the position to sell
35
+ when the stop loss is triggered.
36
+ - symbol (str): The symbol of the asset the stop loss rule
37
+ applies to. Symbol is defined as the target symbol
38
+ (the asset being traded) combined with the trading symbol
39
+ (the asset used to trade the target symbol), e.g., 'BTC-EUR'.
40
+ """
41
+ def __init__(
42
+ self,
43
+ percentage_threshold: float,
44
+ sell_percentage: float,
45
+ symbol: str,
46
+ trailing: bool = False,
47
+ ):
48
+ self.percentage_threshold = percentage_threshold
49
+ self.trailing = trailing
50
+ self.sell_percentage = sell_percentage
51
+ self.symbol = symbol
@@ -0,0 +1,55 @@
1
+ class TakeProfitRule:
2
+ """
3
+ A rule that defines when to trigger a take profit based
4
+ on a specified threshold such as a percentage gain in price or
5
+ a fixed amount.
6
+
7
+ if trailing is set to true, the take profit price is
8
+ calculated as follows:
9
+ You buy an asset at $100.
10
+ You set a 5% take profit, meaning you will sell if the price
11
+ rises to $105.
12
+ If the price rises to $120, the take profit triggers,
13
+ and you exit with a $20 profit.
14
+ But if the price keeps falling below $105, the take profit is not
15
+ triggered.
16
+
17
+ if trailing is set to true, the take profit price is
18
+ calculated as follows:
19
+ You buy an asset at $100.
20
+ You set a 5% trailing take profit, the moment the price rises
21
+ 5% the initial take profit mark will be set. This means you
22
+ will set the take_profit_price initially at none and
23
+ only if the price hits $105, you will set the
24
+ take_profit_price to $105.
25
+ if the price drops below $105, the take profit is triggered.
26
+ If the price rises to $120, the take profit adjusts to
27
+ $114 (5% below $120).
28
+ If the price falls to $114, the position is closed,
29
+ securing a $14 profit.
30
+ But if the price keeps rising to $150, the take profit
31
+ moves up to $142.50.
32
+
33
+
34
+ Attributes:
35
+ - percentage_threshold (float): The percentage gain in price
36
+ that triggers the stop loss.
37
+ - trailing (bool): Indicates whether the take profit is trailing
38
+ or fixed.
39
+ - sell_percentage (float): The percentage of the position to sell
40
+ when the take profit is triggered.
41
+ - symbol (str): The symbol of the asset the take profit rule
42
+ applies to. Symbol is defined as only the target symbol.
43
+ So for example, 'BTC' in 'BTC-EUR' or 'META' in 'META-USD'.
44
+ """
45
+ def __init__(
46
+ self,
47
+ percentage_threshold: float,
48
+ sell_percentage: float,
49
+ symbol: str,
50
+ trailing: bool = False,
51
+ ):
52
+ self.percentage_threshold = percentage_threshold
53
+ self.trailing = trailing
54
+ self.sell_percentage = sell_percentage
55
+ self.symbol = symbol
@@ -0,0 +1,45 @@
1
+ from enum import Enum
2
+
3
+
4
+ class SnapshotInterval(Enum):
5
+ STRATEGY_ITERATION = "STRATEGY_ITERATION"
6
+ DAILY = "DAILY"
7
+
8
+ @staticmethod
9
+ def from_string(value: str):
10
+
11
+ if isinstance(value, str):
12
+
13
+ for entry in SnapshotInterval:
14
+
15
+ if value.upper() == entry.value:
16
+ return entry
17
+
18
+ raise ValueError(
19
+ f"Could not convert {value} to SnapshotInterval"
20
+ )
21
+ return None
22
+
23
+ @staticmethod
24
+ def from_value(value):
25
+
26
+ if isinstance(value, str):
27
+ return SnapshotInterval.from_string(value)
28
+
29
+ if isinstance(value, SnapshotInterval):
30
+
31
+ for entry in SnapshotInterval:
32
+
33
+ if value == entry:
34
+ return entry
35
+
36
+ raise ValueError(
37
+ f"Could not convert {value} to SnapshotInterval"
38
+ )
39
+
40
+ def equals(self, other):
41
+
42
+ if isinstance(other, Enum):
43
+ return self.value == other.value
44
+ else:
45
+ return SnapshotInterval.from_string(other) == self