investing-algorithm-framework 6.9.1__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 (192) hide show
  1. investing_algorithm_framework/__init__.py +147 -44
  2. investing_algorithm_framework/app/__init__.py +23 -6
  3. investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
  4. investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
  5. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  6. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  7. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  8. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  9. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  10. investing_algorithm_framework/app/app.py +1322 -707
  11. investing_algorithm_framework/app/context.py +196 -88
  12. investing_algorithm_framework/app/eventloop.py +590 -0
  13. investing_algorithm_framework/app/reporting/__init__.py +16 -5
  14. investing_algorithm_framework/app/reporting/ascii.py +57 -202
  15. investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
  16. investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
  17. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  18. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  19. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
  20. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  21. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  22. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
  23. investing_algorithm_framework/app/reporting/generate.py +100 -114
  24. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
  25. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
  26. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
  27. investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
  28. investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
  29. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
  30. investing_algorithm_framework/app/strategy.py +315 -175
  31. investing_algorithm_framework/app/task.py +5 -3
  32. investing_algorithm_framework/cli/cli.py +30 -12
  33. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
  34. investing_algorithm_framework/cli/initialize_app.py +20 -1
  35. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
  36. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  37. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  38. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
  39. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
  40. investing_algorithm_framework/create_app.py +3 -5
  41. investing_algorithm_framework/dependency_container.py +25 -39
  42. investing_algorithm_framework/domain/__init__.py +45 -38
  43. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  44. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  45. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  46. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  47. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  48. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  49. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  50. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  51. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  52. investing_algorithm_framework/domain/config.py +27 -0
  53. investing_algorithm_framework/domain/constants.py +6 -34
  54. investing_algorithm_framework/domain/data_provider.py +200 -56
  55. investing_algorithm_framework/domain/exceptions.py +34 -1
  56. investing_algorithm_framework/domain/models/__init__.py +10 -19
  57. investing_algorithm_framework/domain/models/base_model.py +0 -6
  58. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  59. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  60. investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
  61. investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
  62. investing_algorithm_framework/domain/models/order/order.py +34 -13
  63. investing_algorithm_framework/domain/models/order/order_status.py +1 -1
  64. investing_algorithm_framework/domain/models/order/order_type.py +1 -1
  65. investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
  66. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
  67. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
  68. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  69. investing_algorithm_framework/domain/models/position/position.py +9 -0
  70. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  71. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  72. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  73. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  74. investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
  75. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  76. investing_algorithm_framework/domain/models/time_frame.py +7 -0
  77. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  78. investing_algorithm_framework/domain/models/time_unit.py +63 -1
  79. investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
  80. investing_algorithm_framework/domain/models/trade/trade.py +56 -32
  81. investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
  82. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
  83. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
  84. investing_algorithm_framework/domain/order_executor.py +19 -0
  85. investing_algorithm_framework/domain/portfolio_provider.py +20 -1
  86. investing_algorithm_framework/domain/services/__init__.py +0 -13
  87. investing_algorithm_framework/domain/strategy.py +1 -29
  88. investing_algorithm_framework/domain/utils/__init__.py +5 -1
  89. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  90. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  91. investing_algorithm_framework/domain/utils/polars.py +17 -14
  92. investing_algorithm_framework/download_data.py +40 -10
  93. investing_algorithm_framework/infrastructure/__init__.py +13 -25
  94. investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
  95. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
  96. investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
  97. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  98. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  99. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
  100. investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
  101. investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
  102. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
  103. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
  104. investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
  105. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  106. investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
  107. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
  108. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
  109. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
  110. investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
  111. investing_algorithm_framework/services/__init__.py +105 -8
  112. investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
  113. investing_algorithm_framework/services/configuration_service.py +14 -4
  114. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  115. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  116. investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
  117. investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
  118. investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
  119. investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
  120. investing_algorithm_framework/services/metrics/generate.py +358 -0
  121. investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
  122. investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
  123. investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
  124. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  125. investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
  126. investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
  127. investing_algorithm_framework/services/metrics/trades.py +500 -0
  128. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  129. investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
  130. investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
  131. investing_algorithm_framework/services/order_service/order_service.py +9 -71
  132. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
  133. investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
  134. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
  135. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
  136. investing_algorithm_framework/services/repository_service.py +5 -2
  137. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  138. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  139. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  140. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  141. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  142. investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
  143. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  144. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  145. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  146. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
  147. investing_algorithm_framework/app/reporting/evaluation.py +0 -243
  148. investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
  149. investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
  150. investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
  151. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
  152. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
  153. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  154. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
  155. investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
  156. investing_algorithm_framework/domain/models/data_source.py +0 -21
  157. investing_algorithm_framework/domain/models/date_range.py +0 -64
  158. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
  159. investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
  160. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  161. investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
  162. investing_algorithm_framework/domain/services/market_service.py +0 -153
  163. investing_algorithm_framework/domain/services/observable.py +0 -51
  164. investing_algorithm_framework/domain/services/observer.py +0 -19
  165. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
  166. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
  167. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
  168. investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
  169. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  170. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
  171. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  172. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  173. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
  174. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
  175. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
  176. investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
  177. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
  178. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
  179. investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
  180. /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
  181. /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
  182. /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
  183. /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
  184. /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
  185. /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
  186. /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
  187. /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
  188. /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
  189. /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
  190. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  191. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
  192. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,113 @@
1
+ from typing import List, Dict
2
+
3
+ import polars as pl
4
+
5
+ from investing_algorithm_framework.domain import OrderSide, OrderStatus, \
6
+ Trade, Order
7
+ from .trade_order_evaluator import TradeOrderEvaluator
8
+
9
+
10
+ class BacktestTradeOrderEvaluator(TradeOrderEvaluator):
11
+
12
+ def evaluate(
13
+ self,
14
+ open_trades: List[Trade],
15
+ open_orders: List[Order],
16
+ ohlcv_data: Dict[str, pl.DataFrame]
17
+ ):
18
+ """
19
+ Evaluate trades and orders based on OHLCV data.
20
+
21
+ Args:
22
+ open_orders (List[Order]): List of open Order objects.
23
+ open_trades (List[Trade]): List of open Trade objects.
24
+ ohlcv_data (dict[str, pl.DataFrame]): Mapping of
25
+ symbol -> OHLCV Polars DataFrame.
26
+
27
+ Returns:
28
+ List[dict]: Updated trades with latest prices and execution status.
29
+ """
30
+ # First check pending orders
31
+ for open_order in open_orders:
32
+ data = ohlcv_data.get(open_order.symbol)
33
+ self._check_has_executed(open_order, data)
34
+
35
+ if len(open_trades) > 0:
36
+ for open_trade in open_trades:
37
+ data = ohlcv_data[open_trade.symbol]
38
+
39
+ if data is None or data.is_empty():
40
+ continue
41
+
42
+ # Get last row of data
43
+ last_row = data.tail(1)
44
+ update_data = {
45
+ "last_reported_price": last_row["Close"][0],
46
+ "last_reported_price_datetime": last_row["Datetime"][0],
47
+ "updated_at": last_row["Datetime"][0]
48
+ }
49
+ open_trade.update(update_data)
50
+
51
+ self.trade_service.save_all(open_trades)
52
+ self._check_take_profits()
53
+ self._check_stop_losses()
54
+
55
+ def _check_has_executed(self, order, ohlcv_df):
56
+ """
57
+ Check if the order has been executed based on OHLCV data.
58
+
59
+ BUY ORDER filled Rules:
60
+ - Only uses prices after the last update_at of the order.
61
+ - If the lowest low price of the series is below or equal
62
+ to the order price, e.g. if you buy asset at price 100
63
+ and the low price of the series is 99, then the order is filled.
64
+
65
+ SELL ORDER filled Rules:
66
+ - Only uses prices after the last update_at of the order.
67
+ - If the highest high price of the series is above or equal
68
+ to the order price, e.g. if you sell asset at price 100
69
+ and the high price of the series is 101, then the order is filled.
70
+
71
+ Args:
72
+ order (Order): Order.
73
+ ohlcv_df (pl.DataFrame): OHLCV DataFrame for the symbol.
74
+
75
+ Returns:
76
+ None
77
+ """
78
+
79
+ if ohlcv_df.is_empty():
80
+ return
81
+
82
+ # Extract attributes from the order object
83
+ updated_at = order.updated_at
84
+ order_side = order.order_side
85
+ order_price = order.price
86
+ ohlcv_data_after_order = ohlcv_df.filter(
87
+ pl.col('Datetime') >= updated_at
88
+ )
89
+
90
+ if ohlcv_data_after_order.is_empty():
91
+ return
92
+
93
+ if OrderSide.BUY.equals(order_side):
94
+ # Check if the low price drops below or equals the order price
95
+ if (ohlcv_data_after_order['Low'] <= order_price).any():
96
+ self.order_service.update(
97
+ order.id, {
98
+ 'status': OrderStatus.CLOSED.value,
99
+ 'remaining': 0,
100
+ 'filled': order.amount
101
+ }
102
+ )
103
+
104
+ elif OrderSide.SELL.equals(order_side):
105
+ # Check if the high price goes above or equals the order price
106
+ if (ohlcv_data_after_order['High'] >= order_price).any():
107
+ self.order_service.update(
108
+ order.id, {
109
+ 'status': OrderStatus.CLOSED.value,
110
+ 'remaining': 0,
111
+ 'filled': order.amount
112
+ }
113
+ )
@@ -0,0 +1,51 @@
1
+ from typing import List, Dict
2
+
3
+ import polars as pl
4
+
5
+ from investing_algorithm_framework.domain import Trade, Order, INDEX_DATETIME
6
+ from .trade_order_evaluator import TradeOrderEvaluator
7
+
8
+
9
+ class DefaultTradeOrderEvaluator(TradeOrderEvaluator):
10
+
11
+ def evaluate(
12
+ self,
13
+ open_trades: List[Trade],
14
+ open_orders: List[Order],
15
+ ohlcv_data: Dict[str, pl.DataFrame]
16
+ ):
17
+ """
18
+ Evaluate trades and orders based on OHLCV data.
19
+
20
+ Args:
21
+ open_orders (List[Order]): List of open Order objects.
22
+ open_trades (List[Trade]): List of open Trade objects.
23
+ ohlcv_data (dict[str, pl.DataFrame]): Mapping of
24
+ symbol -> OHLCV Polars DataFrame.
25
+
26
+ Returns:
27
+ List[dict]: Updated trades with latest prices and execution status.
28
+ """
29
+ self.order_service.check_pending_orders()
30
+ current_date = self.configuration_service.config[INDEX_DATETIME]
31
+
32
+ if len(open_trades) > 0:
33
+ for open_trade in open_trades:
34
+ data = ohlcv_data[open_trade.symbol]
35
+
36
+ if data is None or data.is_empty():
37
+ continue
38
+
39
+ # Get last row of data
40
+ last_row = data.tail(1)
41
+ last_row_date = last_row["Datetime"][0]
42
+ update_data = {
43
+ "last_reported_price": last_row["Close"][0],
44
+ "last_reported_price_datetime": last_row_date,
45
+ "updated_at": current_date
46
+ }
47
+ open_trade.update(update_data)
48
+
49
+ self.trade_service.save_all(open_trades)
50
+ self._check_take_profits()
51
+ self._check_stop_losses()
@@ -0,0 +1,80 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Dict
3
+ import polars as pl
4
+ from investing_algorithm_framework.domain import Trade, Order, INDEX_DATETIME
5
+
6
+
7
+ class TradeOrderEvaluator(ABC):
8
+
9
+ def __init__(
10
+ self,
11
+ trade_service,
12
+ trade_stop_loss_service,
13
+ trade_take_profit_service,
14
+ order_service,
15
+ configuration_service=None
16
+ ):
17
+ self.trade_service = trade_service
18
+ self.trade_stop_loss_service = trade_stop_loss_service
19
+ self.trade_take_profit_service = trade_take_profit_service
20
+ self.order_service = order_service
21
+ self.configuration_service = configuration_service
22
+
23
+ @abstractmethod
24
+ def evaluate(
25
+ self,
26
+ open_trades: List[Trade],
27
+ open_orders: List[Order],
28
+ ohlcv_data: Dict[str, pl.DataFrame]
29
+ ):
30
+ """
31
+ Evaluate trades and orders based on OHLCV data. This
32
+ function is responsible for updating open orders and open trades.
33
+ The evaluation process includes checking if orders have been executed
34
+ and updating the trades with the latest prices and execution status.
35
+ Additionally, it may trigger stop-loss and take-profit orders
36
+ based on the current market conditions.
37
+
38
+ Args:
39
+ open_trades (List[Trade]): List of open Trade objects.
40
+ open_orders (List[Order]): List of open Order objects.
41
+ ohlcv_data (dict[str, pl.DataFrame]): Mapping of
42
+ symbol -> OHLCV Polars DataFrame.
43
+
44
+ Returns:
45
+ List[dict]: Updated trades with latest prices and execution status.
46
+ """
47
+ pass
48
+
49
+ def _check_take_profits(self):
50
+ current_date = self.configuration_service.config[INDEX_DATETIME]
51
+ take_profits_orders_data = self.trade_service \
52
+ .get_triggered_take_profit_orders()
53
+
54
+ for take_profit_order in take_profits_orders_data:
55
+ take_profits = take_profit_order["take_profits"]
56
+ self.order_service.create(take_profit_order)
57
+ self.trade_take_profit_service.mark_triggered(
58
+ [
59
+ take_profit.get("take_profit_id")
60
+ for take_profit in take_profits
61
+ ],
62
+ trigger_date=current_date
63
+ )
64
+
65
+ def _check_stop_losses(self):
66
+ current_date = self.configuration_service.config[INDEX_DATETIME]
67
+ stop_losses_orders_data = self.trade_service \
68
+ .get_triggered_stop_loss_orders()
69
+
70
+ for stop_loss_order in stop_losses_orders_data:
71
+ stop_losses = stop_loss_order["stop_losses"]
72
+
73
+ self.order_service.create(stop_loss_order)
74
+ self.trade_stop_loss_service.mark_triggered(
75
+ [
76
+ stop_loss.get("stop_loss_id") for stop_loss in
77
+ stop_losses
78
+ ],
79
+ trigger_date=current_date
80
+ )
@@ -1,3 +1,9 @@
1
1
  from .trade_service import TradeService
2
+ from .trade_stop_loss_service import TradeStopLossService
3
+ from .trade_take_profit_service import TradeTakeProfitService
2
4
 
3
- __all__ = ["TradeService"]
5
+ __all__ = [
6
+ "TradeService",
7
+ "TradeStopLossService",
8
+ "TradeTakeProfitService"
9
+ ]
@@ -4,9 +4,9 @@ from queue import PriorityQueue
4
4
  from typing import Union
5
5
 
6
6
  from investing_algorithm_framework.domain import OrderStatus, TradeStatus, \
7
- Trade, OperationalException, TradeRiskType, OrderType, \
8
- OrderSide, MarketDataType, Environment, ENVIRONMENT, PeekableQueue, \
9
- BACKTESTING_INDEX_DATETIME, random_number, random_string
7
+ Trade, OperationalException, OrderType, TradeTakeProfit, \
8
+ TradeStopLoss, OrderSide, Environment, ENVIRONMENT, PeekableQueue, \
9
+ DataType, INDEX_DATETIME, random_number, random_string
10
10
  from investing_algorithm_framework.services.repository_service import \
11
11
  RepositoryService
12
12
 
@@ -29,14 +29,12 @@ class TradeService(RepositoryService):
29
29
  trade_take_profit_repository,
30
30
  position_repository,
31
31
  portfolio_repository,
32
- market_data_source_service,
33
32
  configuration_service,
34
33
  order_metadata_repository
35
34
  ):
36
35
  super(TradeService, self).__init__(trade_repository)
37
36
  self.order_repository = order_repository
38
37
  self.portfolio_repository = portfolio_repository
39
- self.market_data_source_service = market_data_source_service
40
38
  self.position_repository = position_repository
41
39
  self.configuration_service = configuration_service
42
40
  self.trade_stop_loss_repository = trade_stop_loss_repository
@@ -249,7 +247,7 @@ class TradeService(RepositoryService):
249
247
 
250
248
  if Environment.BACKTEST.equals(environment):
251
249
  last_reported_price_date = \
252
- config[BACKTESTING_INDEX_DATETIME]
250
+ config[INDEX_DATETIME]
253
251
  else:
254
252
  last_reported_price_date = \
255
253
  datetime.now(tz=timezone.utc)
@@ -383,12 +381,21 @@ class TradeService(RepositoryService):
383
381
  Args:
384
382
  sell_order: Order object representing the sell order that has
385
383
  been created
384
+ trades: List of Trade objects representing the trades that
385
+ are associated with the sell order. Default is None.
386
+ stop_losses: List of StopLoss objects representing the stop
387
+ losses that are associated with the sell order. Default
388
+ is None.
389
+ take_profits: List of TakeProfit objects representing the take
390
+ profits that are associated with the sell order. Default
391
+ is None.
386
392
 
387
393
  Returns:
388
394
  None
389
395
  """
390
396
  sell_order_id = sell_order.id
391
397
  sell_price = sell_order.price
398
+ sell_amount = sell_order.amount
392
399
 
393
400
  if (trades is None or len(trades) == 0) \
394
401
  and (stop_losses is None or len(stop_losses) == 0) \
@@ -433,10 +440,11 @@ class TradeService(RepositoryService):
433
440
  position.cost -= cost
434
441
  self.position_repository.save(position)
435
442
 
436
- # Update the net gain and net size of the portfolio
443
+ # Update the net gain, net size of the portfolio
437
444
  portfolio = self.portfolio_repository.get(position.portfolio_id)
438
445
  portfolio.total_net_gain += net_gain
439
446
  portfolio.net_size += net_gain
447
+ portfolio.total_revenue += sell_price * sell_amount
440
448
  self.portfolio_repository.save(portfolio)
441
449
 
442
450
  def update_trade_with_removed_sell_order(
@@ -599,7 +607,14 @@ class TradeService(RepositoryService):
599
607
  function will update all the metadata objects that where
600
608
  created by the sell order.
601
609
 
610
+ Args:
611
+ filled_difference: float representing the difference between
612
+ the filled amount of the sell order and the filled amount
613
+ of the trade
614
+ sell_order: Order object representing the sell order
602
615
 
616
+ Returns:
617
+ Trade object
603
618
  """
604
619
  # Update all metadata objects
605
620
  metadata_objects = self.order_metadata_repository.get_all({
@@ -702,7 +717,7 @@ class TradeService(RepositoryService):
702
717
  meta_data = market_data["metadata"]
703
718
 
704
719
  for open_trade in open_trades:
705
- ohlcv_meta_data = meta_data[MarketDataType.OHLCV]
720
+ ohlcv_meta_data = meta_data[DataType.OHLCV]
706
721
 
707
722
  if open_trade.symbol not in ohlcv_meta_data:
708
723
  continue
@@ -728,9 +743,10 @@ class TradeService(RepositoryService):
728
743
  self,
729
744
  trade,
730
745
  percentage: float,
731
- trade_risk_type: TradeRiskType = TradeRiskType.FIXED,
746
+ trailing: bool = False,
732
747
  sell_percentage: float = 100,
733
- ):
748
+ created_at: datetime = None
749
+ ) -> TradeStopLoss:
734
750
  """
735
751
  Function to add a stop loss to a trade.
736
752
 
@@ -751,10 +767,12 @@ class TradeService(RepositoryService):
751
767
  trade: Trade object representing the trade
752
768
  percentage: float representing the percentage of the open price
753
769
  that the stop loss should be set at
754
- trade_risk_type (TradeRiskType): The type of the stop loss, fixed
755
- or trailing
770
+ trailing (bool): representing whether the stop loss is a
771
+ trailing stop loss or not. Default is False.
756
772
  sell_percentage: float representing the percentage of the trade
757
- that should be sold if the stop loss is triggered
773
+ that should be sold if the stop loss is triggered.
774
+ created_at: datetime representing the creation date of the
775
+ stop loss. If None, the current datetime will be used.
758
776
 
759
777
  Returns:
760
778
  None
@@ -775,12 +793,14 @@ class TradeService(RepositoryService):
775
793
 
776
794
  creation_data = {
777
795
  "trade_id": trade.id,
778
- "trade_risk_type": TradeRiskType.from_value(trade_risk_type).value,
796
+ "trailing": trailing,
779
797
  "percentage": percentage,
780
798
  "open_price": trade.open_price,
781
799
  "total_amount_trade": trade.amount,
782
800
  "sell_percentage": sell_percentage,
783
- "active": True
801
+ "active": True,
802
+ "created_at": created_at if created_at is not None
803
+ else datetime.now(tz=timezone.utc)
784
804
  }
785
805
  return self.trade_stop_loss_repository.create(creation_data)
786
806
 
@@ -788,9 +808,10 @@ class TradeService(RepositoryService):
788
808
  self,
789
809
  trade,
790
810
  percentage: float,
791
- trade_risk_type: TradeRiskType = TradeRiskType.FIXED,
811
+ trailing: bool = False,
792
812
  sell_percentage: float = 100,
793
- ) -> None:
813
+ created_at: datetime = None
814
+ ) -> TradeTakeProfit:
794
815
  """
795
816
  Function to add a take profit to a trade. This function will add a
796
817
  take profit to the specified trade. If the take profit is triggered,
@@ -811,12 +832,16 @@ class TradeService(RepositoryService):
811
832
 
812
833
  Args:
813
834
  trade: Trade object representing the trade
814
- percentage: float representing the percentage of the open price
815
- that the stop loss should be set at
816
- trade_risk_type (TradeRiskType): The type of the stop loss, fixed
817
- or trailing
818
- sell_percentage: float representing the percentage of the trade
835
+ percentage (float): representing the percentage of the open price
836
+ that the stop loss should be set at. This must be a positive
837
+ number, e.g. 5 for 5%, or 10 for 10%.
838
+ trailing (bool): representing whether the take profit is a
839
+ trailing take profit or not. Default is False.
840
+ sell_percentage (float): representing the percentage of the trade
819
841
  that should be sold if the stop loss is triggered
842
+ created_at (datetime): datetime representing the creation
843
+ date of the take profit. If None, the current datetime
844
+ will be used.
820
845
 
821
846
  Returns:
822
847
  None
@@ -834,15 +859,16 @@ class TradeService(RepositoryService):
834
859
  "Combined sell percentages of stop losses belonging "
835
860
  "to trade exceeds 100."
836
861
  )
837
-
838
862
  creation_data = {
839
863
  "trade_id": trade.id,
840
- "trade_risk_type": TradeRiskType.from_value(trade_risk_type).value,
864
+ "trailing": trailing,
841
865
  "percentage": percentage,
842
866
  "open_price": trade.open_price,
843
867
  "total_amount_trade": trade.amount,
844
868
  "sell_percentage": sell_percentage,
845
- "active": True
869
+ "active": True,
870
+ "created_at": created_at if created_at is not None
871
+ else datetime.now(tz=timezone.utc)
846
872
  }
847
873
  return self.trade_take_profit_repository.create(creation_data)
848
874
 
@@ -854,7 +880,6 @@ class TradeService(RepositoryService):
854
880
  Returns:
855
881
  List of trade ids
856
882
  """
857
- triggered_stop_losses = {}
858
883
  sell_orders_data = []
859
884
  query = {"status": TradeStatus.OPEN.value}
860
885
  open_trades = self.get_all(query)
@@ -954,9 +979,7 @@ class TradeService(RepositoryService):
954
979
 
955
980
  Returns:
956
981
  List of trade objects. A trade object is a dictionary
957
-
958
982
  """
959
- triggered_take_profits = {}
960
983
  sell_orders_data = []
961
984
  query = {"status": TradeStatus.OPEN.value}
962
985
  open_trades = self.get_all(query)
@@ -974,7 +997,6 @@ class TradeService(RepositoryService):
974
997
  continue
975
998
 
976
999
  for take_profit in open_trade.take_profits:
977
-
978
1000
  if (
979
1001
  take_profit.active and
980
1002
  take_profit.has_triggered(open_trade.last_reported_price)
@@ -0,0 +1,39 @@
1
+ import logging
2
+ from datetime import datetime
3
+
4
+ from investing_algorithm_framework.services.repository_service import \
5
+ RepositoryService
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class TradeStopLossService(RepositoryService):
11
+
12
+ def mark_triggered(
13
+ self,
14
+ stop_loss_ids,
15
+ trigger_date: datetime
16
+ ) -> None:
17
+ """
18
+ Mark stop losses as triggered.
19
+
20
+ Args:
21
+ stop_loss_ids (List[str]): List of stop loss IDs to
22
+ mark as triggered.
23
+ trigger_date (datetime): The date when the stop loss
24
+ was triggered.
25
+
26
+ Returns:
27
+ None
28
+ """
29
+ update_data = {
30
+ "triggered": True,
31
+ "triggered_at": trigger_date,
32
+ "updated_at": trigger_date
33
+ }
34
+
35
+ for id in stop_loss_ids:
36
+ try:
37
+ self.update(id, update_data)
38
+ except Exception as e:
39
+ logger.error(f"Error marking stop loss {id} as triggered: {e}")
@@ -0,0 +1,41 @@
1
+ import logging
2
+ from datetime import datetime
3
+
4
+ from investing_algorithm_framework.services.repository_service import \
5
+ RepositoryService
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class TradeTakeProfitService(RepositoryService):
11
+
12
+ def mark_triggered(
13
+ self,
14
+ take_profit_ids,
15
+ trigger_date: datetime
16
+ ) -> None:
17
+ """
18
+ Mark take profits as triggered.
19
+
20
+ Args:
21
+ take_profit_ids (List[str]): List of take profit IDs to
22
+ mark as triggered.
23
+ trigger_date (datetime): The date and time when the
24
+ take profits were triggered.
25
+
26
+ Returns:
27
+ None
28
+ """
29
+ update_data = {
30
+ "triggered": True,
31
+ "triggered_at": trigger_date,
32
+ "updated_at": trigger_date
33
+ }
34
+
35
+ for id in take_profit_ids:
36
+ try:
37
+ self.update(id, update_data)
38
+ except Exception as e:
39
+ logger.error(
40
+ f"Error marking take profit {id} as triggered: {e}"
41
+ )