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
@@ -1,13 +1,14 @@
1
- from datetime import datetime
2
- from typing import List
1
+ from dateutil.parser import parse
2
+ from datetime import timezone
3
3
 
4
- import polars as pl
5
- from polars import DataFrame
6
-
7
- from investing_algorithm_framework.domain.constants import DATETIME_FORMAT
8
- from investing_algorithm_framework.domain.exceptions import \
9
- OperationalException
10
4
  from investing_algorithm_framework.domain.models.base_model import BaseModel
5
+ from investing_algorithm_framework.domain.models.order import OrderSide, Order
6
+ from investing_algorithm_framework.domain.models.trade.trade_status import \
7
+ TradeStatus
8
+ from investing_algorithm_framework.domain.models.trade.trade_stop_loss import \
9
+ TradeStopLoss
10
+ from investing_algorithm_framework.domain.models.trade\
11
+ .trade_take_profit import TradeTakeProfit
11
12
 
12
13
 
13
14
  class Trade(BaseModel):
@@ -24,241 +25,364 @@ class Trade(BaseModel):
24
25
 
25
26
  A single sell order can close multiple buy orders. Also, a single
26
27
  buy order can be closed by multiple sell orders.
28
+
29
+ Attributes:
30
+ orders: str, the id of the buy order
31
+ target_symbol: str, the target symbol of the trade
32
+ trading_symbol: str, the trading symbol of the trade
33
+ closed_at: datetime, the datetime when the trade was closed
34
+ amount: float, the amount of the trade
35
+ available_amount: float, the available amount of the trade
36
+ remaining: float, the remaining amount that is not filled by the
37
+ buy order that opened the trade.
38
+ filled_amount: float, the filled amount of the trade by the buy
39
+ order that opened the trade.
40
+ net_gain: float, the net gain of the trade
41
+ last_reported_price: float, the last reported price of the trade
42
+ last_reported_price_datetime: datetime, the datetime when the last
43
+ reported price was reported
44
+ updated_at: datetime, the datetime when the trade was last updated
45
+ status: str, the status of the trade
46
+ metadata: dict, the metadata of the trade, this can be used to store
47
+ additional information about the trade.
27
48
  """
49
+
28
50
  def __init__(
29
51
  self,
30
- buy_order_id,
52
+ id,
53
+ orders,
31
54
  target_symbol,
32
55
  trading_symbol,
33
- amount,
34
- open_price,
56
+ closed_at,
35
57
  opened_at,
36
- closed_price=None,
37
- closed_at=None,
38
- current_price=None,
39
- sell_order_id=None,
58
+ open_price,
59
+ amount,
60
+ available_amount,
61
+ cost,
62
+ remaining,
63
+ filled_amount,
64
+ status,
65
+ net_gain=0,
66
+ last_reported_price=None,
67
+ last_reported_price_datetime=None,
68
+ high_water_mark=None,
69
+ high_water_mark_datetime=None,
70
+ updated_at=None,
71
+ stop_losses=None,
72
+ take_profits=None,
73
+ metadata=None,
40
74
  ):
41
- self._target_symbol = target_symbol
42
- self._trading_symbol = trading_symbol
43
- self._amount = amount
44
- self._open_price = open_price
45
- self._closed_price = closed_price
46
- self._closed_at = closed_at
47
- self._opened_at = opened_at
48
- self._trading_symbol = trading_symbol
49
- self._current_price = current_price
50
- self._buy_order_id = buy_order_id
51
- self._sell_order_id = sell_order_id,
75
+ self.id = id
76
+ self.orders = orders
77
+ self.target_symbol = target_symbol
78
+ self.trading_symbol = trading_symbol
79
+ self.closed_at = closed_at
80
+ self.opened_at = opened_at
81
+ self.open_price = open_price
82
+ self.amount = amount
83
+ self.available_amount = available_amount
84
+ self.cost = cost
85
+ self.remaining = remaining
86
+ self.filled_amount = filled_amount
87
+ self.net_gain = net_gain
88
+ self.last_reported_price = last_reported_price
89
+ self.last_reported_price_datetime = last_reported_price_datetime
90
+ self.high_water_mark = high_water_mark
91
+ self.high_water_mark_datetime = high_water_mark_datetime
92
+ self.status = TradeStatus.from_value(status).value
93
+ self.updated_at = updated_at
94
+ self.stop_losses = stop_losses
95
+ self.take_profits = take_profits
96
+ self.metadata = metadata if metadata is not None else {}
97
+
98
+ def update(self, data):
99
+
100
+ if "status" in data:
101
+ self.status = TradeStatus.from_value(data["status"]).value
102
+
103
+ if TradeStatus.CLOSED.equals(self.status):
104
+
105
+ # Set all stop losses to inactive
106
+ if self.stop_losses is not None:
107
+ for stop_loss in self.stop_losses:
108
+ stop_loss.active = False
109
+
110
+ # set all take profits to inactive
111
+ if self.take_profits is not None:
112
+ for take_profit in self.take_profits:
113
+ take_profit.active = False
114
+
115
+ if "last_reported_price" in data:
116
+ self.last_reported_price = data["last_reported_price"]
117
+
118
+ if self.high_water_mark is None:
119
+ self.high_water_mark = data["last_reported_price"]
120
+ self.high_water_mark_datetime = \
121
+ data["last_reported_price_datetime"]
122
+ else:
123
+
124
+ if data["last_reported_price"] > self.high_water_mark:
125
+ self.high_water_mark = data["last_reported_price"]
126
+ self.high_water_mark_datetime = \
127
+ data["last_reported_price_datetime"]
128
+
129
+ return super().update(data)
52
130
 
53
131
  @property
54
- def buy_order_id(self):
55
- return self._buy_order_id
132
+ def closed_prices(self):
133
+ return [
134
+ order.price for order in self.orders
135
+ if order.order_side == OrderSide.SELL.value
136
+ ]
56
137
 
57
138
  @property
58
- def sell_order_id(self):
59
- return self._sell_order_id
139
+ def buy_order(self):
60
140
 
61
- @property
62
- def target_symbol(self):
63
- return self._target_symbol
141
+ if self.orders is None:
142
+ return
64
143
 
65
- @property
66
- def trading_symbol(self):
67
- return self._trading_symbol
144
+ return [
145
+ order for order in self.orders
146
+ if order.order_side == OrderSide.BUY.value
147
+ ][0]
68
148
 
69
149
  @property
70
150
  def symbol(self):
71
- return f"{self.target_symbol}/{self.trading_symbol}"
72
-
73
- def get_symbol(self):
74
- return f"{self.target_symbol}/{self.trading_symbol}"
151
+ return f"{self.target_symbol.upper()}/{self.trading_symbol.upper()}"
75
152
 
76
153
  @property
77
- def amount(self):
78
- return self._amount
79
-
80
- def get_amount(self):
81
- return self.amount
154
+ def duration(self):
155
+ """
156
+ Calculate the duration of the trade in hours.
82
157
 
83
- @property
84
- def open_price(self):
85
- return self._open_price
158
+ Returns:
159
+ float: The duration of the trade in hours.
160
+ """
161
+ if TradeStatus.CLOSED.equals(self.status):
162
+ # Get the total hours between the closed and opened datetime
163
+ diff = self.closed_at - self.opened_at
164
+ return diff.total_seconds() / 3600
86
165
 
87
- @property
88
- def closed_price(self):
89
- return self._closed_price
166
+ if self.opened_at is None:
167
+ return None
90
168
 
91
- @property
92
- def closed_at(self):
93
- return self._closed_at
169
+ if self.updated_at is None:
170
+ return None
94
171
 
95
- @property
96
- def opened_at(self):
97
- return self._opened_at
172
+ diff = self.updated_at - self.opened_at
173
+ return diff.total_seconds() / 3600
98
174
 
99
175
  @property
100
176
  def size(self):
101
177
  return self.amount * self.open_price
102
178
 
103
179
  @property
104
- def status(self):
105
- return "CLOSED" if self.closed_at else "OPEN"
180
+ def change(self):
181
+ """
182
+ Property to calculate the change in value of the trade.
106
183
 
107
- @property
108
- def net_gain(self):
184
+ This is the difference between the current value of the trade
185
+ and the cost of the trade.
186
+ """
187
+ if TradeStatus.CLOSED.equals(self.status):
188
+ return self.net_gain
109
189
 
110
- if self.closed_at is None:
190
+ if self.last_reported_price is None:
111
191
  return 0
112
192
 
113
- return self.amount * (self.closed_price - self.open_price)
193
+ if self.remaining is None or self.remaining == 0:
194
+ amount = self.amount
195
+ else:
196
+ amount = self.amount - self.remaining
197
+
198
+ cost = amount * self.open_price
199
+ gain = (amount * self.last_reported_price) - cost
200
+ return gain
114
201
 
115
202
  @property
116
- def net_gain_percentage(self):
203
+ def net_gain_absolute(self):
117
204
 
118
- if self.closed_at is None:
119
- return 0
205
+ if TradeStatus.CLOSED.equals(self.status):
206
+ return self.net_gain
207
+ else:
208
+ gain = 0
120
209
 
121
- return self.net_gain / self.size * 100
210
+ if self.last_reported_price is not None:
211
+ gain = (
212
+ self.available_amount *
213
+ (self.last_reported_price - self.open_price)
214
+ )
122
215
 
123
- @property
124
- def duration(self):
125
- closed_at = self.closed_at
216
+ gain += self.net_gain
217
+ return gain
126
218
 
127
- if closed_at is None:
128
- closed_at = datetime.utcnow()
219
+ @property
220
+ def net_gain_percentage(self):
129
221
 
130
- return (closed_at - self.opened_at).total_seconds() / 3600
222
+ if TradeStatus.CLOSED.equals(self.status):
131
223
 
132
- @property
133
- def current_price(self):
134
- return self._current_price
224
+ if self.cost != 0:
225
+ return (self.net_gain / self.cost) * 100
135
226
 
136
- @current_price.setter
137
- def current_price(self, current_price):
138
- self._current_price = current_price
227
+ else:
228
+ gain = 0
139
229
 
140
- @property
141
- def value(self):
230
+ if self.last_reported_price is not None:
231
+ gain = (
232
+ self.available_amount *
233
+ (self.last_reported_price - self.open_price)
234
+ )
142
235
 
143
- if self.closed_at is not None:
144
- return self.amount * self.closed_price
236
+ gain += self.net_gain
145
237
 
146
- if self.current_price is None:
147
- return None
238
+ if self.cost != 0:
239
+ return (gain / self.cost) * 100
148
240
 
149
- return self.amount * self.current_price
241
+ return 0
150
242
 
151
243
  @property
152
244
  def percentage_change(self):
153
245
 
154
- if self.closed_at is not None or self.value is None:
155
- return 0
156
- value = self.value
157
- return (value - self.size) / self.size * 100
158
-
159
- def get_percentage_change(self):
160
- return self.percentage_change
246
+ if TradeStatus.CLOSED.equals(self.status):
161
247
 
162
- @property
163
- def absolute_change(self):
248
+ if self.cost != 0:
249
+ return (self.net_gain / self.cost) * 100
164
250
 
165
- if self.closed_at is None:
251
+ if self.last_reported_price is None:
166
252
  return 0
167
253
 
168
- value = self.value
169
- return value - self.size
254
+ cost = self.available_amount * self.open_price
255
+ gain = (self.available_amount * self.last_reported_price) - cost
256
+ gain += self.net_gain
170
257
 
171
- def get_absolute_change(self):
172
- return self.absolute_change
258
+ if cost != 0:
259
+ return (gain / cost) * 100
173
260
 
174
- def is_manual_stop_loss_trigger(
175
- self,
176
- current_price,
177
- stop_loss_percentage,
178
- prices: List[float] = None,
179
- ohlcv_df: DataFrame = None
180
- ):
181
- """
182
- Function to check if the stop loss is triggered for a given trade.
183
-
184
- You can use either the prices list or the ohlcv_df DataFrame to
185
- calculate the stop loss. The dataframe needs to be a Polars
186
- DataFrame with the following columns: "Datetime" and "Close".
187
-
188
- You can use the default CCXTOHLCVMarketDataSource to get the ohlcv_df
189
- DataFrame.
190
-
191
- Stop loss is triggered when the current price is lower than the
192
- calculated stop loss price. The stop loss price is calculated by
193
- taking the highest price of the given range. If the highest price
194
- is lower than the open price, the stop loss price is calculated by
195
- taking the open price and subtracting the stop loss percentage.
196
- If the highest price is higher than the open price, the stop loss
197
- price is calculated by taking the open price and adding the stop
198
- loss percentage.
199
- """
261
+ return 0
200
262
 
201
- if prices is None and ohlcv_df is None:
202
- raise OperationalException(
203
- "Either prices or a polars ohlcv dataframe must be provided"
204
- )
263
+ def to_dict(self, datetime_format=None):
264
+ def ensure_iso(value):
265
+ if hasattr(value, "isoformat"):
266
+ if value.tzinfo is None:
267
+ value = value.replace(tzinfo=timezone.utc)
268
+ return value.isoformat()
269
+ return value
270
+
271
+ opened_at = ensure_iso(self.opened_at) if self.opened_at else None
272
+ closed_at = ensure_iso(self.closed_at) if self.closed_at else None
273
+ updated_at = ensure_iso(self.updated_at) if self.updated_at else None
274
+
275
+ # Ensure status is a string
276
+ self.status = TradeStatus.from_value(self.status).value
205
277
 
206
- if current_price < self.open_price:
207
- stop_loss_price = self.open_price * \
208
- (1 - stop_loss_percentage / 100)
209
- return current_price <= stop_loss_price
210
- else:
211
- # If dataframes are provided, we use the dataframe to calculate
212
- # the stop loss price
213
- if ohlcv_df is not None:
214
- column_type = ohlcv_df['Datetime'].dtype
215
-
216
- if isinstance(column_type, pl.Datetime):
217
- filtered_df = ohlcv_df.filter(
218
- pl.col('Datetime') >= self.opened_at
219
- )
220
- else:
221
- filtered_df = ohlcv_df.filter(
222
- pl.col('Datetime') >= self.opened_at.strftime(
223
- DATETIME_FORMAT
224
- )
225
- )
226
-
227
- prices = filtered_df['Close'].to_numpy()
228
-
229
- highest_price = max(prices)
230
- stop_loss_price = highest_price * (1 - stop_loss_percentage / 100)
231
- return current_price <= stop_loss_price
232
-
233
- def to_dict(self):
234
278
  return {
279
+ "id": self.id,
280
+ "orders": [
281
+ order.to_dict(datetime_format=datetime_format)
282
+ for order in self.orders
283
+ ],
235
284
  "target_symbol": self.target_symbol,
236
285
  "trading_symbol": self.trading_symbol,
237
286
  "status": self.status,
238
287
  "amount": self.amount,
288
+ "remaining": self.remaining if self.remaining is not None else 0,
239
289
  "open_price": self.open_price,
240
- "current_price": self.current_price,
241
- "closed_price": self.closed_price,
242
- "opened_at": self.opened_at.strftime(DATETIME_FORMAT)
243
- if self.opened_at else None,
244
- "closed_at": self.closed_at.strftime(DATETIME_FORMAT)
245
- if self.closed_at else None,
246
- "change": self.percentage_change,
247
- "absolute_change": self.absolute_change,
290
+ "last_reported_price": self.last_reported_price,
291
+ "opened_at": opened_at,
292
+ "closed_at": closed_at,
293
+ "updated_at": updated_at,
294
+ "net_gain": self.net_gain if self.net_gain is not None else 0,
295
+ "cost": self.cost if self.cost is not None else 0,
296
+ "stop_losses": [
297
+ stop_loss.to_dict(datetime_format=datetime_format)
298
+ for stop_loss in self.stop_losses
299
+ ] if self.stop_losses else None,
300
+ "take_profits": [
301
+ take_profit.to_dict(datetime_format=datetime_format)
302
+ for take_profit in self.take_profits
303
+ ] if self.take_profits else None,
304
+ "filled_amount": self.filled_amount,
305
+ "available_amount": self.available_amount,
306
+ "metadata": self.metadata if self.metadata else {},
248
307
  }
249
308
 
309
+ @staticmethod
310
+ def from_dict(data):
311
+ opened_at = None
312
+ closed_at = None
313
+ updated_at = None
314
+ stop_losses = None
315
+ take_profits = None
316
+ orders = None
317
+
318
+ if "opened_at" in data and data["opened_at"] is not None:
319
+ opened_at = parse(data["opened_at"])
320
+
321
+ if "closed_at" in data and data["closed_at"] is not None:
322
+ closed_at = parse(data["closed_at"])
323
+
324
+ if "updated_at" in data and data["updated_at"] is not None:
325
+ updated_at = parse(data["updated_at"])
326
+
327
+ if "stop_losses" in data and data["stop_losses"] is not None:
328
+ stop_losses = [
329
+ TradeStopLoss.from_dict(stop_loss)
330
+ for stop_loss in data["stop_losses"]
331
+ ]
332
+
333
+ if "take_profits" in data and data["take_profits"] is not None:
334
+ take_profits = [
335
+ TradeTakeProfit.from_dict(take_profit)
336
+ for take_profit in data["take_profits"]
337
+ ]
338
+
339
+ if "orders" in data and data["orders"] is not None:
340
+ orders = [
341
+ Order.from_dict(order)
342
+ for order in data["orders"]
343
+ ]
344
+ return Trade(
345
+ id=data.get("id", None),
346
+ orders=orders,
347
+ target_symbol=data["target_symbol"],
348
+ trading_symbol=data["trading_symbol"],
349
+ amount=data["amount"],
350
+ open_price=data["open_price"],
351
+ opened_at=opened_at,
352
+ closed_at=closed_at,
353
+ filled_amount=data.get("filled_amount", 0),
354
+ available_amount=data.get("available_amount", 0),
355
+ remaining=data.get("remaining", 0),
356
+ net_gain=data.get("net_gain", 0),
357
+ last_reported_price=data.get("last_reported_price"),
358
+ status=TradeStatus.from_value(data["status"]).value,
359
+ cost=data.get("cost", 0),
360
+ updated_at=updated_at,
361
+ stop_losses=stop_losses,
362
+ take_profits=take_profits,
363
+ metadata=data.get("metadata", {}),
364
+ )
365
+
250
366
  def __repr__(self):
251
367
  return self.repr(
368
+ id=self.id,
369
+ symbol=self.symbol,
252
370
  target_symbol=self.target_symbol,
253
371
  trading_symbol=self.trading_symbol,
254
372
  status=self.status,
255
373
  amount=self.amount,
374
+ available_amount=self.available_amount,
375
+ filled_amount=self.filled_amount,
376
+ remaining=self.remaining,
256
377
  open_price=self.open_price,
257
- current_price=self.current_price,
258
- closed_price=self.closed_price,
259
378
  opened_at=self.opened_at,
260
379
  closed_at=self.closed_at,
261
- value=self.value,
262
- change=self.percentage_change,
263
- absolute_change=self.absolute_change,
380
+ net_gain=self.net_gain,
381
+ last_reported_price=self.last_reported_price,
382
+ updated_at=self.updated_at,
383
+ metadata=self.metadata,
264
384
  )
385
+
386
+ def __lt__(self, other):
387
+ # Define the less-than comparison based on created_at attribute
388
+ return self.opened_at < other.opened_at
@@ -1,7 +1,10 @@
1
1
  from enum import Enum
2
+ from investing_algorithm_framework.domain.exceptions import \
3
+ OperationalException
2
4
 
3
5
 
4
6
  class TradeStatus(Enum):
7
+ CREATED = "CREATED"
5
8
  OPEN = "OPEN"
6
9
  CLOSED = "CLOSED"
7
10
 
@@ -14,7 +17,9 @@ class TradeStatus(Enum):
14
17
  if value.upper() == status.value:
15
18
  return status
16
19
 
17
- raise ValueError("Could not convert value to TradeStatus")
20
+ raise OperationalException(
21
+ f"Could not convert value: '{value}' to TradeStatus"
22
+ )
18
23
 
19
24
  @staticmethod
20
25
  def from_value(value):
@@ -27,7 +32,9 @@ class TradeStatus(Enum):
27
32
  elif isinstance(value, str):
28
33
  return TradeStatus.from_string(value)
29
34
 
30
- raise ValueError("Could not convert value to TradeStatus")
35
+ raise OperationalException(
36
+ f"Could not convert value: {value} to TradeStatus"
37
+ )
31
38
 
32
39
  def equals(self, other):
33
40
  return TradeStatus.from_value(other) == self