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
@@ -2,10 +2,8 @@ import logging
2
2
 
3
3
  import polars as pl
4
4
 
5
- from investing_algorithm_framework.domain import BACKTESTING_INDEX_DATETIME, \
6
- OrderStatus, OrderSide, Order, MarketDataType
7
- from investing_algorithm_framework.services.market_data_source_service \
8
- import BacktestMarketDataSourceService
5
+ from investing_algorithm_framework.domain import INDEX_DATETIME, \
6
+ OrderStatus, OrderSide, Order, DataType
9
7
  from .order_service import OrderService
10
8
 
11
9
  logger = logging.getLogger("investing_algorithm_framework")
@@ -22,7 +20,6 @@ class OrderBacktestService(OrderService):
22
20
  portfolio_configuration_service,
23
21
  portfolio_snapshot_service,
24
22
  configuration_service,
25
- market_data_source_service: BacktestMarketDataSourceService,
26
23
  ):
27
24
  super().__init__(
28
25
  configuration_service=configuration_service,
@@ -34,7 +31,6 @@ class OrderBacktestService(OrderService):
34
31
  trade_service=trade_service,
35
32
  )
36
33
  self.configuration_service = configuration_service
37
- self.market_data_source_service = market_data_source_service
38
34
 
39
35
  def create(self, data, execute=True, validate=True, sync=True) -> Order:
40
36
  """
@@ -51,10 +47,9 @@ class OrderBacktestService(OrderService):
51
47
  Order: Created order object
52
48
  """
53
49
  config = self.configuration_service.get_config()
54
-
55
50
  # Make sure the created_at is set to the current backtest time
56
- data["created_at"] = config[BACKTESTING_INDEX_DATETIME]
57
- data["updated_at"] = config[BACKTESTING_INDEX_DATETIME]
51
+ data["created_at"] = config[INDEX_DATETIME]
52
+ data["updated_at"] = config[INDEX_DATETIME]
58
53
  # Call super to have standard behavior
59
54
  return super(OrderBacktestService, self)\
60
55
  .create(data, execute, validate, sync)
@@ -64,7 +59,7 @@ class OrderBacktestService(OrderService):
64
59
  order.remaining = order.get_amount()
65
60
  order.filled = 0
66
61
  order.updated_at = self.configuration_service.config[
67
- BACKTESTING_INDEX_DATETIME
62
+ INDEX_DATETIME
68
63
  ]
69
64
  return order
70
65
 
@@ -85,7 +80,7 @@ class OrderBacktestService(OrderService):
85
80
  meta_data = market_data["metadata"]
86
81
 
87
82
  for order in pending_orders:
88
- ohlcv_meta_data = meta_data[MarketDataType.OHLCV]
83
+ ohlcv_meta_data = meta_data[DataType.OHLCV]
89
84
 
90
85
  if order.get_symbol() not in ohlcv_meta_data:
91
86
  continue
@@ -106,7 +101,7 @@ class OrderBacktestService(OrderService):
106
101
  "filled": order.get_amount(),
107
102
  "remaining": 0,
108
103
  "updated_at": self.configuration_service
109
- .config[BACKTESTING_INDEX_DATETIME]
104
+ .config[INDEX_DATETIME]
110
105
  }
111
106
  )
112
107
 
@@ -123,11 +118,11 @@ class OrderBacktestService(OrderService):
123
118
  "status": OrderStatus.CANCELED.value,
124
119
  "remaining": 0,
125
120
  "updated_at": self.configuration_service
126
- .config[BACKTESTING_INDEX_DATETIME]
121
+ .config[INDEX_DATETIME]
127
122
  }
128
123
  )
129
124
 
130
- def has_executed(self, order, ohlcv_data_frame):
125
+ def has_executed(self, order, ohlcv_data_frame: pl.DataFrame):
131
126
  """
132
127
  Check if the order has executed based on the OHLCV data.
133
128
 
@@ -135,7 +130,7 @@ class OrderBacktestService(OrderService):
135
130
  order price. Example: If the order price is 1000 and the low price
136
131
  drops below or equals 1000, the order is executed. This simulates the
137
132
  situation where a buyer is willing to pay a higher price than the
138
- the lowest price in the ohlcv data.
133
+ lowest price in the ohlcv data.
139
134
 
140
135
  A sell order is executed if the high price goes above or equals the
141
136
  order price. Example: If the order price is 1000 and the high price
@@ -143,27 +138,22 @@ class OrderBacktestService(OrderService):
143
138
  situation where a seller is willing to accept a higher price for its
144
139
  sell order.
145
140
 
146
- :param order: Order object
147
- :param ohlcv_data_frame: OHLCV data frame
148
- :return: True if the order has executed, False otherwise
141
+ Args:
142
+ order: Order object
143
+ ohlcv_data_frame: OHLCV data frame
144
+ True if the order has executed, False otherwise
145
+
146
+ Returns:
147
+ bool: True if the order has executed, False otherwise
149
148
  """
150
149
 
151
150
  # Extract attributes from the order object
152
151
  created_at = order.get_created_at()
153
152
  order_side = order.get_order_side()
154
153
  order_price = order.get_price()
155
- column_type = ohlcv_data_frame['Datetime'].dtype
156
-
157
- if isinstance(column_type, pl.Datetime):
158
- ohlcv_data_after_order = ohlcv_data_frame.filter(
159
- pl.col('Datetime') >= created_at
160
- )
161
- else:
162
- ohlcv_data_after_order = ohlcv_data_frame.filter(
163
- pl.col('Datetime') >= created_at.strftime(
164
- self.configuration_service.config["DATETIME_FORMAT"]
165
- )
166
- )
154
+ ohlcv_data_after_order = ohlcv_data_frame.filter(
155
+ pl.col('Datetime') >= created_at
156
+ )
167
157
 
168
158
  # Check if the order execution conditions are met
169
159
  if OrderSide.BUY.equals(order_side):
@@ -182,7 +172,7 @@ class OrderBacktestService(OrderService):
182
172
 
183
173
  if created_at is None:
184
174
  created_at = self.configuration_service \
185
- .config[BACKTESTING_INDEX_DATETIME]
175
+ .config[INDEX_DATETIME]
186
176
 
187
177
  super(OrderBacktestService, self)\
188
178
  .create_snapshot(portfolio_id, created_at=created_at)
@@ -1,19 +1,14 @@
1
1
  import logging
2
- from datetime import datetime
3
- from typing import List
4
-
5
- from dateutil.tz import tzutc
6
2
 
7
3
  from investing_algorithm_framework.domain import OrderType, OrderSide, \
8
- OperationalException, OrderStatus, Order, OrderExecutor, random_number, \
9
- Observable, Event
4
+ OperationalException, OrderStatus, Order, random_number, INDEX_DATETIME
10
5
  from investing_algorithm_framework.services.repository_service \
11
6
  import RepositoryService
12
7
 
13
8
  logger = logging.getLogger("investing_algorithm_framework")
14
9
 
15
10
 
16
- class OrderService(RepositoryService, Observable):
11
+ class OrderService(RepositoryService):
17
12
  """
18
13
  Service to manage orders. This service will use the provided
19
14
  order executors to execute the orders. The order service is
@@ -52,8 +47,6 @@ class OrderService(RepositoryService, Observable):
52
47
  market_credential_service=None
53
48
  ):
54
49
  super(OrderService, self).__init__(order_repository)
55
- # Call the observable constructor
56
- Observable.__init__(self)
57
50
  self.configuration_service = configuration_service
58
51
  self.order_repository = order_repository
59
52
  self.position_service = position_service
@@ -62,36 +55,9 @@ class OrderService(RepositoryService, Observable):
62
55
  self.portfolio_snapshot_service = portfolio_snapshot_service
63
56
  self.market_credential_service = market_credential_service
64
57
  self.trade_service = trade_service
65
- self._order_executors = None
66
58
  self._order_executor_lookup = order_executor_lookup
67
59
  self._portfolio_provider_lookup = portfolio_provider_lookup
68
60
 
69
- @property
70
- def order_executors(self) -> List[OrderExecutor]:
71
- """
72
- Returns the order executors for the order service.
73
- """
74
- return self._order_executors
75
-
76
- @order_executors.setter
77
- def order_executors(self, value) -> None:
78
- """
79
- Sets the order executors for the order service.
80
- """
81
- self._order_executors = value
82
-
83
- def get_order_executor(self, market) -> OrderExecutor:
84
- """
85
- Returns the order executor for the given market.
86
-
87
- Args:
88
- market (str): The market for which to get the order executor.
89
-
90
- Returns:
91
- OrderExecutor: The order executor for the given market.
92
- """
93
- return self._order_executor_lookup.get_order_executor(market)
94
-
95
61
  def create(self, data, execute=True, validate=True, sync=True) -> Order:
96
62
  """
97
63
  Function to create an order. The function will create the order and
@@ -189,6 +155,7 @@ class OrderService(RepositoryService, Observable):
189
155
  self.validate_order(data, portfolio)
190
156
 
191
157
  del data["portfolio_id"]
158
+ data["target_symbol"] = data["target_symbol"].upper()
192
159
  symbol = data["target_symbol"]
193
160
  data["id"] = self._create_order_id()
194
161
 
@@ -225,11 +192,6 @@ class OrderService(RepositoryService, Observable):
225
192
  else:
226
193
  self._sync_portfolio_with_created_sell_order(order)
227
194
 
228
- self.notify_observers(
229
- Event.ORDER_CREATED,
230
- {"portfolio_id": portfolio.id, "created_at": order.created_at}
231
- )
232
- # self.create_snapshot(portfolio.id, created_at=created_at)
233
195
  order = self.get(order_id)
234
196
  return order
235
197
 
@@ -310,7 +272,8 @@ class OrderService(RepositoryService, Observable):
310
272
  f"and price {order.get_price()}"
311
273
  )
312
274
 
313
- order_executor = self.get_order_executor(portfolio.market)
275
+ order_executor = self._order_executor_lookup\
276
+ .get_order_executor(portfolio.market)
314
277
  market_credential = self.market_credential_service.get(
315
278
  portfolio.market
316
279
  )
@@ -322,7 +285,8 @@ class OrderService(RepositoryService, Observable):
322
285
  order.set_status(external_order.get_status())
323
286
  order.set_filled(external_order.get_filled())
324
287
  order.set_remaining(external_order.get_remaining())
325
- order.updated_at = datetime.now(tz=tzutc())
288
+ config = self.configuration_service.config
289
+ order.updated_at = config[INDEX_DATETIME]
326
290
  return order
327
291
 
328
292
  def validate_order(self, order_data, portfolio):
@@ -544,7 +508,8 @@ class OrderService(RepositoryService, Observable):
544
508
  market_credential = self.market_credential_service.get(
545
509
  portfolio.market
546
510
  )
547
- order_executor = self.get_order_executor(portfolio.market)
511
+ order_executor = self._order_executor_lookup\
512
+ .get_order_executor(portfolio.market)
548
513
  order = order_executor\
549
514
  .cancel_order(portfolio, order, market_credential)
550
515
  self.update(order.id, order.to_dict())
@@ -840,33 +805,6 @@ class OrderService(RepositoryService, Observable):
840
805
 
841
806
  self.trade_service.update_trade_with_removed_sell_order(order)
842
807
 
843
- # def create_snapshot(self, portfolio_id, created_at=None):
844
- #
845
- # if created_at is None:
846
- # created_at = datetime.now(tz=tzutc())
847
- #
848
- # portfolio = self.portfolio_repository.get(portfolio_id)
849
- # pending_orders = self.get_all(
850
- # {
851
- # "order_side": OrderSide.BUY.value,
852
- # "status": OrderStatus.OPEN.value,
853
- # "portfolio_id": portfolio.id
854
- # }
855
- # )
856
- # created_orders = self.get_all(
857
- # {
858
- # "order_side": OrderSide.BUY.value,
859
- # "status": OrderStatus.CREATED.value,
860
- # "portfolio_id": portfolio.id
861
- # }
862
- # )
863
- # return self.portfolio_snapshot_service.create_snapshot(
864
- # portfolio,
865
- # pending_orders=pending_orders,
866
- # created_orders=created_orders,
867
- # created_at=created_at
868
- # )
869
-
870
808
  def _create_order_id(self):
871
809
  """
872
810
  Function to create a new order id. The function will
@@ -54,9 +54,7 @@ class PortfolioProviderLookup:
54
54
  None
55
55
  """
56
56
  matches = []
57
-
58
57
  for portfolio_provider in self.portfolio_providers:
59
-
60
58
  if portfolio_provider.supports_market(market):
61
59
  matches.append(portfolio_provider)
62
60
 
@@ -2,8 +2,7 @@ import logging
2
2
  from datetime import datetime
3
3
 
4
4
  from investing_algorithm_framework.domain import OperationalException, \
5
- MarketCredentialService, Portfolio, \
6
- Environment, ENVIRONMENT, Observable, Event
5
+ MarketCredentialService, Portfolio, Environment, ENVIRONMENT
7
6
  from investing_algorithm_framework.services.configuration_service import \
8
7
  ConfigurationService
9
8
  from investing_algorithm_framework.services.repository_service \
@@ -12,7 +11,7 @@ from investing_algorithm_framework.services.repository_service \
12
11
  logger = logging.getLogger("investing_algorithm_framework")
13
12
 
14
13
 
15
- class PortfolioService(RepositoryService, Observable):
14
+ class PortfolioService(RepositoryService):
16
15
  """
17
16
  Service to manage portfolios. This service will sync the portfolios with
18
17
  the exchange balances and orders. It will also create portfolios based on
@@ -31,8 +30,6 @@ class PortfolioService(RepositoryService, Observable):
31
30
  portfolio_provider_lookup
32
31
  ):
33
32
  super().__init__(repository=portfolio_repository)
34
- # Call the observable constructor
35
- Observable.__init__(self)
36
33
  self.configuration_service = configuration_service
37
34
  self.market_credential_service = market_credential_service
38
35
  self.portfolio_configuration_service = portfolio_configuration_service
@@ -79,7 +76,7 @@ class PortfolioService(RepositoryService, Observable):
79
76
  {"identifier": identifier}
80
77
  )
81
78
  return portfolio
82
-
79
+ data["initial_balance"] = unallocated
83
80
  portfolio = super(PortfolioService, self).create(data)
84
81
  self.position_service.create(
85
82
  {
@@ -89,13 +86,6 @@ class PortfolioService(RepositoryService, Observable):
89
86
  "cost": unallocated
90
87
  }
91
88
  )
92
- self.notify_observers(
93
- Event.PORTFOLIO_CREATED,
94
- {
95
- "portfolio_id": portfolio.id,
96
- "created_at": portfolio.created_at
97
- }
98
- )
99
89
  return portfolio
100
90
 
101
91
  def create_portfolio_from_configuration(
@@ -1,22 +1,13 @@
1
- from datetime import datetime, timezone
2
-
3
- from investing_algorithm_framework.domain import Observer, Event, \
4
- SNAPSHOT_INTERVAL, SnapshotInterval, OrderStatus, \
5
- PortfolioSnapshot, Environment, ENVIRONMENT, BACKTESTING_INDEX_DATETIME
1
+ from investing_algorithm_framework.domain import OrderStatus, \
2
+ PortfolioSnapshot
6
3
  from investing_algorithm_framework.services.repository_service import \
7
4
  RepositoryService
8
5
 
9
6
 
10
- class PortfolioSnapshotService(RepositoryService, Observer):
7
+ class PortfolioSnapshotService(RepositoryService):
11
8
  """
12
9
  Service to manage portfolio snapshots. This service will create snapshots
13
10
  of the portfolio at specific intervals or based on certain events.
14
-
15
- This service implements the Observer interface to listen for events.
16
- This can be used to register this service to an observable object
17
- and let it create snapshots based on the events that occur in the system.
18
- For example, it can create a snapshot of the portfolio
19
- at the end of each strategy iteration or when a trade is closed.
20
11
  """
21
12
 
22
13
  def __init__(
@@ -26,55 +17,24 @@ class PortfolioSnapshotService(RepositoryService, Observer):
26
17
  order_repository,
27
18
  position_repository,
28
19
  position_snapshot_service,
29
- datasource_service,
30
- configuration_service
20
+ data_provider_service,
31
21
  ):
32
22
  self.order_repository = order_repository
33
23
  self.position_snapshot_service = position_snapshot_service
34
24
  self.portfolio_repository = portfolio_repository
35
25
  self.position_repository = position_repository
36
- self.configuration_service = configuration_service
37
- self.datasource_service = datasource_service
26
+ self.data_provider_service = data_provider_service
38
27
  super(PortfolioSnapshotService, self).__init__(repository)
39
28
 
40
- def notify(self, event_type: Event, payload):
41
- """
42
- Update the portfolio snapshot based on the event type and payload.
43
-
44
- Args:
45
- event_type: The type of event that occurred.
46
- payload: The data associated with the event.
47
- """
48
- config = self.configuration_service.get_config()
49
- snapshot_interval = config[SNAPSHOT_INTERVAL]
50
-
51
- if Event.STRATEGY_RUN.equals(event_type) and \
52
- SnapshotInterval.STRATEGY_ITERATION.equals(snapshot_interval):
53
- created_at = payload.get("created_at")
54
- for portfolio in self.portfolio_repository.get_all():
55
- self.create_snapshot(portfolio, created_at=created_at)
56
-
57
- elif Event.TRADE_CLOSED.equals(event_type) and \
58
- SnapshotInterval.TRADE_CLOSE.equals(snapshot_interval):
59
- portfolio_id = payload.get("portfolio_id")
60
- created_at = payload.get("created_at")
61
- portfolio = self.portfolio_repository.get(portfolio_id)
62
- self.create_snapshot(portfolio=portfolio, created_at=created_at)
63
-
64
- elif Event.PORTFOLIO_CREATED.equals(event_type):
65
- portfolio_id = payload.get("portfolio_id")
66
- created_at = payload.get("created_at")
67
- portfolio = self.portfolio_repository.get(portfolio_id)
68
- self.create_snapshot(portfolio=portfolio, created_at=created_at)
69
-
70
- # elif Event.ORDER_CREATED.equals(event_type):
71
- # portfolio_id = payload.get("portfolio_id")
72
- # portfolio = self.portfolio_repository.get(portfolio_id)
73
- # created_at = payload.get("created_at")
74
- # self.create_snapshot(portfolio=portfolio, created_at=created_at)
75
-
76
29
  def create_snapshot(
77
- self, portfolio, created_at=None, cash_flow=0
30
+ self,
31
+ portfolio,
32
+ created_at,
33
+ cash_flow=0,
34
+ created_orders=None,
35
+ open_orders=None,
36
+ positions=None,
37
+ save=True
78
38
  ) -> PortfolioSnapshot:
79
39
  """
80
40
  Function to create a snapshot of the portfolio. During
@@ -93,52 +53,67 @@ class PortfolioSnapshotService(RepositoryService, Observer):
93
53
  and time will be used.
94
54
  cash_flow (float, optional): The cash flow to include
95
55
  in the snapshot.
56
+ created_orders (list, optional): A list of created orders
57
+ to consider when calculating the pending value.
58
+ open_orders (list, optional): A list of open orders
59
+ to consider when calculating the pending value.
60
+ positions (list, optional): A list of positions to consider
61
+ when calculating the total value of the portfolio.
62
+ save (bool, optional): Whether to save the snapshot to
63
+ the database.
96
64
 
97
65
  Returns:
98
-
66
+ PortfolioSnapshot: The created snapshot of the portfolio.
99
67
  """
100
68
  pending_value = 0
101
- pending_orders = self.order_repository.get_all(
102
- {
103
- "status": OrderStatus.OPEN.value,
104
- "portfolio_id": portfolio.id
105
- }
106
- )
107
- created_orders = self.order_repository.get_all(
108
- {
109
- "status": OrderStatus.CREATED.value,
110
- "portfolio_id": portfolio.id
111
- }
112
- )
113
-
114
- if created_orders is not None:
115
- for order in created_orders:
116
- pending_value += order.get_price() * order.get_amount()
117
-
118
- if pending_orders is not None:
119
- for order in pending_orders:
120
- pending_value += order.get_price() * order.get_remaining()
121
-
122
- if created_at is None:
123
- config = self.configuration_service.get_config()
124
-
125
- if Environment.BACKTEST.equals(config[ENVIRONMENT]):
126
- created_at = config[BACKTESTING_INDEX_DATETIME]
127
- else:
128
- created_at = datetime.now(tz=timezone.utc)
69
+ pending_symbols = set()
70
+ allocated = 0
71
+
72
+ if open_orders is None:
73
+ open_orders = self.order_repository.get_all(
74
+ {
75
+ "status": OrderStatus.OPEN.value,
76
+ "portfolio_id": portfolio.id
77
+ }
78
+ )
79
+
80
+ if created_orders is None:
81
+ created_orders = self.order_repository.get_all(
82
+ {
83
+ "status": OrderStatus.CREATED.value,
84
+ "portfolio_id": portfolio.id
85
+ }
86
+ )
87
+
88
+ if positions is None:
89
+ positions = self.position_repository.get_all(
90
+ {"portfolio": portfolio.id}
91
+ )
92
+
93
+ for order in created_orders:
94
+ pending_value += order.get_price() * order.get_amount()
95
+ pending_symbols.add(order.get_symbol())
96
+
97
+ for order in open_orders:
98
+ pending_value += order.get_price() * order.get_remaining()
99
+ pending_symbols.add(order.get_symbol())
129
100
 
130
101
  total_value = portfolio.get_unallocated() + pending_value
131
102
 
132
103
  for position in \
133
104
  self.position_repository.get_all({"portfolio": portfolio.id}):
134
- symbol = \
135
- f"{position.get_symbol()}/{portfolio.get_trading_symbol()}"
136
105
 
137
106
  if position.get_symbol() != portfolio.get_trading_symbol():
138
- ticker = self.datasource_service.get_ticker(symbol)
139
-
107
+ symbol_pair = f"{position.get_symbol()}/" \
108
+ f"{portfolio.get_trading_symbol()}"
109
+ ticker = self.data_provider_service.get_ticker_data(
110
+ symbol=symbol_pair,
111
+ market=portfolio.market,
112
+ date=created_at
113
+ )
140
114
  # Calculate the position worth
141
115
  position_worth = position.get_amount() * ticker["bid"]
116
+ allocated += position_worth
142
117
  total_value += position_worth
143
118
 
144
119
  data = {
@@ -154,16 +129,7 @@ class PortfolioSnapshotService(RepositoryService, Observer):
154
129
  "created_at": created_at,
155
130
  "total_value": total_value,
156
131
  }
157
- snapshot = self.create(data)
158
- positions = self.position_repository.get_all(
159
- {"portfolio": portfolio.id}
160
- )
161
-
162
- for position in positions:
163
- self.position_snapshot_service.create_snapshot(
164
- snapshot.id, position
165
- )
166
-
132
+ snapshot = self.create(data, save=save)
167
133
  return snapshot
168
134
 
169
135
  def get_latest_snapshot(self, portfolio_id):
@@ -20,7 +20,6 @@ class PortfolioSyncService(AbstractPortfolioSyncService):
20
20
  position_repository: PositionRepository object
21
21
  portfolio_repository: PortfolioRepository object
22
22
  market_credential_service: MarketCredentialService object
23
- market_service: MarketService object
24
23
  portfolio_configuration_service: PortfolioConfigurationService object
25
24
  portfolio_provider_lookup: PortfolioProviderLookup object
26
25
  """
@@ -34,7 +33,6 @@ class PortfolioSyncService(AbstractPortfolioSyncService):
34
33
  portfolio_repository,
35
34
  portfolio_configuration_service,
36
35
  market_credential_service,
37
- market_service,
38
36
  portfolio_provider_lookup
39
37
  ):
40
38
  self.trade_service = trade_service
@@ -43,7 +41,6 @@ class PortfolioSyncService(AbstractPortfolioSyncService):
43
41
  self.position_repository = position_repository
44
42
  self.portfolio_repository = portfolio_repository
45
43
  self.market_credential_service = market_credential_service
46
- self.market_service = market_service
47
44
  self.portfolio_configuration_service = portfolio_configuration_service
48
45
  self.portfolio_provider_lookup = portfolio_provider_lookup
49
46
 
@@ -3,8 +3,8 @@ class RepositoryService:
3
3
  def __init__(self, repository):
4
4
  self.repository = repository
5
5
 
6
- def create(self, data):
7
- return self.repository.create(data)
6
+ def create(self, data, save=True):
7
+ return self.repository.create(data, save=save)
8
8
 
9
9
  def get(self, object_id):
10
10
  return self.repository.get(object_id)
@@ -35,3 +35,6 @@ class RepositoryService:
35
35
 
36
36
  def save(self, object):
37
37
  return self.repository.save(object)
38
+
39
+ def save_all(self, objects):
40
+ return self.repository.save_objects(objects)
@@ -0,0 +1,9 @@
1
+ from .trade_order_evaluator import TradeOrderEvaluator
2
+ from .backtest_trade_oder_evaluator import BacktestTradeOrderEvaluator
3
+ from .default_trade_order_evaluator import DefaultTradeOrderEvaluator
4
+
5
+ __all__ = [
6
+ "TradeOrderEvaluator",
7
+ "BacktestTradeOrderEvaluator",
8
+ "DefaultTradeOrderEvaluator"
9
+ ]