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,11 +1,7 @@
1
1
  import logging
2
- from datetime import datetime
3
- from queue import PriorityQueue
4
-
5
- from dateutil.tz import tzutc
6
2
 
7
3
  from investing_algorithm_framework.domain import OrderType, OrderSide, \
8
- OperationalException, OrderStatus, MarketService, Order
4
+ OperationalException, OrderStatus, Order, random_number, INDEX_DATETIME
9
5
  from investing_algorithm_framework.services.repository_service \
10
6
  import RepositoryService
11
7
 
@@ -13,80 +9,221 @@ logger = logging.getLogger("investing_algorithm_framework")
13
9
 
14
10
 
15
11
  class OrderService(RepositoryService):
12
+ """
13
+ Service to manage orders. This service will use the provided
14
+ order executors to execute the orders. The order service is
15
+ responsible for creating, updating, canceling and deleting orders.
16
+
17
+ Attributes:
18
+ configuration_service (ConfigurationService): The service
19
+ responsible for managing configurations.
20
+ order_repository (OrderRepository): The repository
21
+ responsible for managing orders.
22
+ position_service (PositionService): The service
23
+ responsible for managing positions.
24
+ portfolio_repository (PortfolioRepository): The repository
25
+ responsible for managing portfolios.
26
+ portfolio_configuration_service (PortfolioConfigurationService):
27
+ service responsible for managing portfolio configurations.
28
+ portfolio_snapshot_service (PortfolioSnapshotService):
29
+ service responsible for managing portfolio snapshots.
30
+ market_credential_service (MarketCredentialService):
31
+ service responsible for managing market credentials.
32
+ trade_service (TradeService): The service responsible for
33
+ managing trades.
34
+ """
16
35
 
17
36
  def __init__(
18
37
  self,
19
38
  configuration_service,
20
39
  order_repository,
21
- market_service: MarketService,
22
- position_repository,
40
+ position_service,
23
41
  portfolio_repository,
24
42
  portfolio_configuration_service,
25
43
  portfolio_snapshot_service,
26
- market_credential_service
44
+ trade_service,
45
+ portfolio_provider_lookup=None,
46
+ order_executor_lookup=None,
47
+ market_credential_service=None
27
48
  ):
28
49
  super(OrderService, self).__init__(order_repository)
29
50
  self.configuration_service = configuration_service
30
51
  self.order_repository = order_repository
31
- self.market_service: MarketService = market_service
32
- self.position_repository = position_repository
52
+ self.position_service = position_service
33
53
  self.portfolio_repository = portfolio_repository
34
54
  self.portfolio_configuration_service = portfolio_configuration_service
35
55
  self.portfolio_snapshot_service = portfolio_snapshot_service
36
56
  self.market_credential_service = market_credential_service
57
+ self.trade_service = trade_service
58
+ self._order_executor_lookup = order_executor_lookup
59
+ self._portfolio_provider_lookup = portfolio_provider_lookup
37
60
 
38
61
  def create(self, data, execute=True, validate=True, sync=True) -> Order:
62
+ """
63
+ Function to create an order. The function will create the order and
64
+ execute it if execute is set to True. The function will also validate
65
+ the order if validate is set to True. The function will also sync the
66
+ portfolio with the order if sync is set to True.
67
+
68
+ The following only applies if the order is a sell order:
69
+
70
+ If stop_losses, or take_profits are in the data, we assume that the
71
+ order has been created by a stop loss or take profit. We will then
72
+ create for the order one or more metadata objects with the
73
+ amount and stop loss id or take profit id. These objects can later
74
+ be used to restore the stop loss or take profit to its original state
75
+ if the order is cancelled or rejected.
76
+
77
+ If trades are in the data, we assume that the order has
78
+ been created by a closing a specific trade. We will then create for
79
+ the order one metadata object with the amount and trade id. This
80
+ objects can later be used to restore the trade to its original
81
+ state if the order is cancelled or rejected.
82
+
83
+ If there are no trades in the data, we rely on the trade service to
84
+ create the metadata objects for the order.
85
+
86
+ The metadata objects are needed because for trades, stop losses and
87
+ take profits we need to know how much of the order has been
88
+ filled at any given time. If the order is cancelled or rejected we
89
+ need to add the pending amount back to the trade, stop loss or take
90
+ profit.
91
+
92
+ Args:
93
+ data: dict - the data to create the order with. Data should have
94
+ the following format:
95
+ {
96
+ "target_symbol": str,
97
+ "trading_symbol": str,
98
+ "order_side": str,
99
+ "order_type": str,
100
+ "amount": float,
101
+ "filled" (optional): float, // If set, trades
102
+ and positions are synced
103
+ "remaining" (optional): float, // Same as filled
104
+ "price": float,
105
+ "portfolio_id": int
106
+ "stop_losses" (optional): list[dict] - list of stop
107
+ losses with the following format:
108
+ {
109
+ "stop_loss_id": float,
110
+ "amount": float
111
+ }
112
+ "take_profits" (optional): list[dict] - list of
113
+ take profits with the following format:
114
+ {
115
+ "take_profit_id": float,
116
+ "amount": float
117
+ }
118
+ "trades" (optional): list[dict] - list of trades
119
+ with the following format:
120
+ {
121
+ "trade_id": int,
122
+ "amount": float
123
+ }
124
+ }
125
+
126
+ execute: bool - if True the order will be executed
127
+ validate: bool - if True the order will be validated
128
+ sync: bool - if True the portfolio will be synced with the order
129
+
130
+ Returns:
131
+ Order: Order object
132
+ """
39
133
  portfolio_id = data["portfolio_id"]
40
134
  portfolio = self.portfolio_repository.get(portfolio_id)
135
+ trades = data.get("trades", [])
136
+ stop_losses = data.get("stop_losses", [])
137
+ take_profits = data.get("take_profits", [])
138
+
139
+ if "filled" in data:
140
+ del data["filled"]
141
+
142
+ if "remaining" in data:
143
+ del data["remaining"]
144
+
145
+ if "trades" in data:
146
+ del data["trades"]
147
+
148
+ if "stop_losses" in data:
149
+ del data["stop_losses"]
150
+
151
+ if "take_profits" in data:
152
+ del data["take_profits"]
41
153
 
42
154
  if validate:
43
155
  self.validate_order(data, portfolio)
44
156
 
45
157
  del data["portfolio_id"]
158
+ data["target_symbol"] = data["target_symbol"].upper()
46
159
  symbol = data["target_symbol"]
160
+ data["id"] = self._create_order_id()
161
+
162
+ order = self.repository.create(data, save=False)
47
163
 
48
164
  if validate:
49
165
  self.validate_order(data, portfolio)
50
166
 
167
+ if execute:
168
+ order = self.execute_order(order, portfolio)
169
+
51
170
  position = self._create_position_if_not_exists(symbol, portfolio)
52
- data["position_id"] = position.id
53
- data["remaining"] = data["amount"]
54
- data["status"] = OrderStatus.CREATED.value
55
- order = self.order_repository.create(data)
171
+ order.position_id = position.id
172
+ order = self.order_repository.save(order)
56
173
  order_id = order.id
174
+ order_side = order.order_side
175
+
176
+ if OrderSide.SELL.equals(order_side):
177
+ # Create order metadata if there is a key in the data
178
+ # for trades, stop_losses or take_profits
179
+ self.trade_service.create_order_metadata_with_trade_context(
180
+ sell_order=order,
181
+ trades=trades,
182
+ stop_losses=stop_losses,
183
+ take_profits=take_profits
184
+ )
185
+ else:
186
+ self.trade_service.create_trade_from_buy_order(order)
57
187
 
58
188
  if sync:
59
- if OrderSide.BUY.equals(order.get_order_side()):
189
+ order = self.get(order_id)
190
+ if OrderSide.BUY.equals(order_side):
60
191
  self._sync_portfolio_with_created_buy_order(order)
61
192
  else:
62
193
  self._sync_portfolio_with_created_sell_order(order)
63
194
 
64
- self.create_snapshot(portfolio.id, created_at=order.created_at)
65
-
66
- if execute:
67
- portfolio.configuration = self.portfolio_configuration_service\
68
- .get(portfolio.identifier)
69
- self.execute_order(order_id, portfolio)
70
-
195
+ order = self.get(order_id)
71
196
  return order
72
197
 
73
198
  def update(self, object_id, data):
199
+ """
200
+ Function to update an order. The function will update the order and
201
+ sync the portfolio, position and trades if the order has been filled.
202
+
203
+ If the order has been cancelled, expired or rejected the function will
204
+ sync the portfolio, position, trades, stop losses, and
205
+ take profits with the order.
206
+
207
+ Args:
208
+ object_id: int - the id of the order to update
209
+ data: dict - the data to update the order with
210
+ the following format:
211
+ {
212
+ "amount": float,
213
+ "filled" (optional): float,
214
+ "remaining" (optional): float,
215
+ "status" (optional): str,
216
+ }
217
+
218
+ Returns:
219
+ Order: Order object that has been updated
220
+ """
74
221
  previous_order = self.order_repository.get(object_id)
75
- trading_symbol_position = self.position_repository.find(
76
- {
77
- "id": previous_order.position_id,
78
- "symbol": previous_order.trading_symbol
79
- }
80
- )
81
- portfolio = self.portfolio_repository.get(
82
- trading_symbol_position.portfolio_id
83
- )
84
222
  new_order = self.order_repository.update(object_id, data)
85
223
  filled_difference = new_order.get_filled() \
86
224
  - previous_order.get_filled()
87
225
 
88
- if filled_difference:
89
-
226
+ if filled_difference > 0:
90
227
  if OrderSide.BUY.equals(new_order.get_order_side()):
91
228
  self._sync_with_buy_order_filled(previous_order, new_order)
92
229
  else:
@@ -95,7 +232,6 @@ class OrderService(RepositoryService):
95
232
  if "status" in data:
96
233
 
97
234
  if OrderStatus.CANCELED.equals(new_order.get_status()):
98
-
99
235
  if OrderSide.BUY.equals(new_order.get_order_side()):
100
236
  self._sync_with_buy_order_cancelled(new_order)
101
237
  else:
@@ -115,65 +251,43 @@ class OrderService(RepositoryService):
115
251
  else:
116
252
  self._sync_with_sell_order_expired(new_order)
117
253
 
118
- if "updated_at" in data:
119
- created_at = data["updated_at"]
120
- else:
121
- created_at = datetime.now(tz=tzutc())
122
-
123
- self.create_snapshot(portfolio.id, created_at=created_at)
124
254
  return new_order
125
255
 
126
- def execute_order(self, order_id, portfolio):
127
- order = self.get(order_id)
256
+ def execute_order(self, order, portfolio) -> Order:
257
+ """
258
+ Function to execute an order. The function will execute the order
259
+ with a matching order executor. The function will also update
260
+ the order attributes with the external order attributes.
128
261
 
129
- try:
130
- if OrderType.LIMIT.equals(order.get_order_type()):
131
-
132
- if OrderSide.BUY.equals(order.get_order_side()):
133
- external_order = self.market_service\
134
- .create_limit_buy_order(
135
- target_symbol=order.get_target_symbol(),
136
- trading_symbol=order.get_trading_symbol(),
137
- amount=order.get_amount(),
138
- price=order.get_price(),
139
- market=portfolio.get_market()
140
- )
141
- else:
142
- external_order = self.market_service\
143
- .create_limit_sell_order(
144
- target_symbol=order.get_target_symbol(),
145
- trading_symbol=order.get_trading_symbol(),
146
- amount=order.get_amount(),
147
- price=order.get_price(),
148
- market=portfolio.get_market()
149
- )
150
- else:
151
- if OrderSide.BUY.equals(order.get_order_side()):
152
- raise OperationalException(
153
- "Market buy order not supported"
154
- )
155
- else:
156
- external_order = self.market_service\
157
- .create_market_sell_order(
158
- target_symbol=order.get_target_symbol(),
159
- trading_symbol=order.get_trading_symbol(),
160
- amount=order.get_amount(),
161
- market=portfolio.get_market()
162
- )
163
-
164
- data = external_order.to_dict()
165
- data["status"] = OrderStatus.OPEN.value
166
- data["updated_at"] = datetime.now(tz=tzutc())
167
- return self.update(order_id, data)
168
- except Exception as e:
169
- logger.error("Error executing order: {}".format(e))
170
- return self.update(
171
- order_id,
172
- {
173
- "status": OrderStatus.REJECTED.value,
174
- "updated_at": datetime.now(tz=tzutc())
175
- }
176
- )
262
+ Args:
263
+ order: Order object representing the order to be executed
264
+ portfolio: Portfolio object representing the portfolio in which
265
+
266
+ Returns:
267
+ order: Order object representing the executed order
268
+ """
269
+ logger.info(
270
+ f"Executing order {order.get_symbol()} with "
271
+ f"amount {order.get_amount()} "
272
+ f"and price {order.get_price()}"
273
+ )
274
+
275
+ order_executor = self._order_executor_lookup\
276
+ .get_order_executor(portfolio.market)
277
+ market_credential = self.market_credential_service.get(
278
+ portfolio.market
279
+ )
280
+ external_order = order_executor.execute_order(
281
+ portfolio, order, market_credential
282
+ )
283
+ logger.info(f"Executed order: {external_order.to_dict()}")
284
+ order.set_external_id(external_order.get_external_id())
285
+ order.set_status(external_order.get_status())
286
+ order.set_filled(external_order.get_filled())
287
+ order.set_remaining(external_order.get_remaining())
288
+ config = self.configuration_service.config
289
+ order.updated_at = config[INDEX_DATETIME]
290
+ return order
177
291
 
178
292
  def validate_order(self, order_data, portfolio):
179
293
 
@@ -185,10 +299,13 @@ class OrderService(RepositoryService):
185
299
  if OrderType.LIMIT.equals(order_data["order_type"]):
186
300
  self.validate_limit_order(order_data, portfolio)
187
301
  else:
188
- self.validate_market_order(order_data, portfolio)
302
+ raise OperationalException(
303
+ f"Order type {order_data['order_type']} is not supported"
304
+ )
189
305
 
190
306
  def validate_sell_order(self, order_data, portfolio):
191
- if not self.position_repository.exists(
307
+
308
+ if not self.position_service.exists(
192
309
  {
193
310
  "symbol": order_data["target_symbol"],
194
311
  "portfolio": portfolio.id
@@ -198,7 +315,7 @@ class OrderService(RepositoryService):
198
315
  "Can't add sell order to non existing position"
199
316
  )
200
317
 
201
- position = self.position_repository\
318
+ position = self.position_service\
202
319
  .find(
203
320
  {
204
321
  "symbol": order_data["target_symbol"],
@@ -208,7 +325,8 @@ class OrderService(RepositoryService):
208
325
 
209
326
  if position.get_amount() < order_data["amount"]:
210
327
  raise OperationalException(
211
- "Order amount is larger then amount of open position"
328
+ f"Order amount {order_data['amount']} is larger " +
329
+ f"then amount of open position {position.get_amount()}"
212
330
  )
213
331
 
214
332
  if not order_data["trading_symbol"] == portfolio.trading_symbol:
@@ -232,13 +350,20 @@ class OrderService(RepositoryService):
232
350
 
233
351
  if OrderSide.SELL.equals(order_data["order_side"]):
234
352
  amount = order_data["amount"]
235
- position = self.position_repository\
353
+ position = self.position_service\
236
354
  .find(
237
355
  {
238
356
  "portfolio": portfolio.id,
239
357
  "symbol": order_data["target_symbol"]
240
358
  }
241
359
  )
360
+
361
+ if amount <= 0:
362
+ raise OperationalException(
363
+ f"Order amount: {amount} {position.symbol}, is "
364
+ f"less or equal to 0"
365
+ )
366
+
242
367
  if amount > position.get_amount():
243
368
  raise OperationalException(
244
369
  f"Order amount: {amount} {position.symbol}, is "
@@ -247,16 +372,23 @@ class OrderService(RepositoryService):
247
372
  )
248
373
  else:
249
374
  total_price = order_data["amount"] * order_data["price"]
250
- unallocated_position = self.position_repository\
375
+ unallocated_position = self.position_service\
251
376
  .find(
252
377
  {
253
378
  "portfolio": portfolio.id,
254
379
  "symbol": portfolio.trading_symbol
255
380
  }
256
381
  )
257
- amount = unallocated_position.get_amount()
382
+ unallocated_amount = unallocated_position.get_amount()
258
383
 
259
- if amount < total_price:
384
+ if unallocated_amount is None:
385
+ raise OperationalException(
386
+ "Unallocated amount of the portfolio is None" +
387
+ "can't validate limit order. Please check if " +
388
+ "the portfolio configuration is correct"
389
+ )
390
+
391
+ if unallocated_amount < total_price:
260
392
  raise OperationalException(
261
393
  f"Order total: {total_price} "
262
394
  f"{portfolio.trading_symbol}, is "
@@ -264,98 +396,106 @@ class OrderService(RepositoryService):
264
396
  f"{portfolio.trading_symbol} of the portfolio"
265
397
  )
266
398
 
267
- def validate_market_order(self, order_data, portfolio):
268
-
269
- if OrderSide.BUY.equals(order_data["order_side"]):
270
-
271
- if "amount" not in order_data:
272
- raise OperationalException(
273
- f"Market order needs an amount specified in the trading "
274
- f"symbol {order_data['trading_symbol']}"
275
- )
276
-
277
- if order_data['amount'] > portfolio.unallocated:
278
- raise OperationalException(
279
- f"Market order amount "
280
- f"{order_data['amount']}"
281
- f"{portfolio.trading_symbol.upper()} is larger then "
282
- f"unallocated {portfolio.unallocated} "
283
- f"{portfolio.trading_symbol.upper()}"
284
- )
399
+ def check_pending_orders(self, portfolio=None):
400
+ """
401
+ Function to check if
402
+ """
403
+ if portfolio is not None:
404
+ pending_orders = self.get_all(
405
+ {
406
+ "status": OrderStatus.OPEN.value,
407
+ "portfolio_id": portfolio.id
408
+ }
409
+ )
285
410
  else:
286
- position = self.position_repository\
287
- .find(
288
- {
289
- "symbol": order_data["target_symbol"],
290
- "portfolio": portfolio.id
291
- }
292
- )
293
-
294
- if position is None:
295
- raise OperationalException(
296
- "Can't add market sell order to non existing position"
297
- )
298
-
299
- if order_data['amount'] > position.get_amount():
300
- raise OperationalException(
301
- "Sell order amount larger then position size"
302
- )
303
-
304
- def check_pending_orders(self):
305
- pending_orders = self.get_all({"status": OrderStatus.OPEN.value})
306
- logger.info(f"Checking {len(pending_orders)} open orders")
411
+ pending_orders = self.get_all({"status": OrderStatus.OPEN.value})
307
412
 
308
413
  for order in pending_orders:
309
- position = self.position_repository.get(order.position_id)
414
+ position = self.position_service.get(order.position_id)
310
415
  portfolio = self.portfolio_repository.get(position.portfolio_id)
311
- external_order = self.market_service\
312
- .get_order(order, market=portfolio.get_market())
416
+ portfolio_provider = self._portfolio_provider_lookup\
417
+ .get_portfolio_provider(portfolio.market)
418
+ market_credential = self.market_credential_service.get(
419
+ portfolio.market
420
+ )
421
+ logger.info(
422
+ f"Checking {order.get_order_side()} order {order.get_id()} "
423
+ f"with external id: {order.get_external_id()} "
424
+ f"at market {portfolio.market}"
425
+ )
426
+ external_order = portfolio_provider.get_order(
427
+ portfolio, order, market_credential
428
+ )
313
429
  self.update(order.id, external_order.to_dict())
314
430
 
315
431
  def _create_position_if_not_exists(self, symbol, portfolio):
316
- if not self.position_repository.exists(
432
+ if not self.position_service.exists(
317
433
  {"portfolio": portfolio.id, "symbol": symbol}
318
434
  ):
319
- self.position_repository \
435
+ self.position_service \
320
436
  .create({"portfolio_id": portfolio.id, "symbol": symbol})
321
- position = self.position_repository \
437
+ position = self.position_service \
322
438
  .find({"portfolio": portfolio.id, "symbol": symbol})
323
439
  else:
324
- position = self.position_repository \
440
+ position = self.position_service \
325
441
  .find({"portfolio": portfolio.id, "symbol": symbol})
326
442
 
327
443
  return position
328
444
 
329
445
  def _sync_portfolio_with_created_buy_order(self, order):
330
- position = self.position_repository.get(order.position_id)
331
- portfolio = self.portfolio_repository.get(position.portfolio_id)
332
- size = order.get_amount() * order.get_price()
333
- trading_symbol_position = self.position_repository.find(
334
- {
335
- "portfolio": portfolio.id,
336
- "symbol": portfolio.trading_symbol
337
- }
338
- )
446
+ """
447
+ Function to sync the portfolio and positions with a created buy order.
339
448
 
449
+ Args:
450
+ order: the order object representing the buy order
451
+
452
+ Returns:
453
+ None
454
+ """
455
+ self.position_service.update_positions_with_created_buy_order(
456
+ order
457
+ )
458
+ position = self.position_service.get(order.position_id)
459
+ portfolio = self.portfolio_repository.get(position.portfolio_id)
460
+ size = order.get_size()
340
461
  self.portfolio_repository.update(
341
462
  portfolio.id, {"unallocated": portfolio.get_unallocated() - size}
342
463
  )
343
- self.position_repository.update(
344
- trading_symbol_position.id,
345
- {
346
- "amount": trading_symbol_position.get_amount() - size
347
- }
348
- )
349
464
 
350
465
  def _sync_portfolio_with_created_sell_order(self, order):
351
- position = self.position_repository.get(order.position_id)
352
- self.position_repository.update(
353
- position.id,
354
- {
355
- "amount": position.get_amount() - order.get_amount()
356
- }
466
+ """
467
+ Function to sync the portfolio with a created sell order. The
468
+ function will subtract the amount of the order from the position and
469
+ the trade amount. If the sell order is already filled, then
470
+ the function will also update the portfolio and the
471
+ trading symbol position.
472
+
473
+ The portfolio will not be updated because the sell order has not been
474
+ filled.
475
+
476
+ Args:
477
+ order: Order object representing the sell order
478
+
479
+ Returns:
480
+ None
481
+ """
482
+ self.position_service.update_positions_with_created_sell_order(
483
+ order
357
484
  )
358
485
 
486
+ filled = order.get_filled()
487
+
488
+ if filled > 0:
489
+ position = self.position_service.get(order.position_id)
490
+ portfolio = self.portfolio_repository.get(position.portfolio_id)
491
+ size = filled * order.get_price()
492
+ self.portfolio_repository.update(
493
+ portfolio.id,
494
+ {
495
+ "unallocated": portfolio.get_unallocated() + size
496
+ }
497
+ )
498
+
359
499
  def cancel_order(self, order):
360
500
  self.check_pending_orders()
361
501
  order = self.order_repository.get(order.id)
@@ -365,28 +505,39 @@ class OrderService(RepositoryService):
365
505
  if OrderStatus.OPEN.equals(order.status):
366
506
  portfolio = self.portfolio_repository\
367
507
  .find({"position": order.position_id})
368
- self.market_service.cancel_order(
369
- order, market=portfolio.get_market()
508
+ market_credential = self.market_credential_service.get(
509
+ portfolio.market
370
510
  )
511
+ order_executor = self._order_executor_lookup\
512
+ .get_order_executor(portfolio.market)
513
+ order = order_executor\
514
+ .cancel_order(portfolio, order, market_credential)
515
+ self.update(order.id, order.to_dict())
371
516
 
372
517
  def _sync_with_buy_order_filled(self, previous_order, current_order):
518
+ """
519
+ Function to sync the portfolio, position and trades with the
520
+ filled buy order.
521
+
522
+ Args:
523
+ previous_order: the previous order object
524
+ current_order: the current order object
525
+
526
+ Returns:
527
+ None
528
+ """
529
+ logger.info("Syncing portfolio with filled buy order")
373
530
  filled_difference = current_order.get_filled() - \
374
- previous_order.get_filled()
531
+ previous_order.get_filled()
375
532
  filled_size = filled_difference * current_order.get_price()
376
533
 
377
534
  if filled_difference <= 0:
378
535
  return
379
536
 
380
- # Update position
381
- position = self.position_repository.get(current_order.position_id)
382
-
383
- self.position_repository.update(
384
- position.id,
385
- {
386
- "amount": position.get_amount() + filled_difference,
387
- "cost": position.get_cost() + filled_size,
388
- }
537
+ self.position_service.update_positions_with_buy_order_filled(
538
+ current_order, filled_difference
389
539
  )
540
+ position = self.position_service.get(current_order.position_id)
390
541
 
391
542
  # Update portfolio
392
543
  portfolio = self.portfolio_repository.get(position.portfolio_id)
@@ -399,16 +550,39 @@ class OrderService(RepositoryService):
399
550
  }
400
551
  )
401
552
 
553
+ self.trade_service.update_trade_with_buy_order(
554
+ filled_difference, current_order
555
+ )
556
+
402
557
  def _sync_with_sell_order_filled(self, previous_order, current_order):
558
+ """
559
+ Function to sync the portfolio, position and trades with the
560
+ filled sell order. The function will update the portfolio and
561
+ position with the filled amount of the order. The function will
562
+ also update the trades with the filled amount of the order.
563
+
564
+ Args:
565
+ previous_order: Order object representing the previous order
566
+ current_order: Order object representing the current order
567
+
568
+ Returns:
569
+ None
570
+ """
403
571
  filled_difference = current_order.get_filled() - \
404
- previous_order.get_filled()
572
+ previous_order.get_filled()
405
573
  filled_size = filled_difference * current_order.get_price()
406
574
 
407
575
  if filled_difference <= 0:
408
576
  return
409
577
 
578
+ logger.info(
579
+ f"Syncing portfolio with filled sell "
580
+ f"order {current_order.get_id()} with filled amount "
581
+ f"{filled_difference}"
582
+ )
583
+
410
584
  # Get position
411
- position = self.position_repository.get(current_order.position_id)
585
+ position = self.position_service.get(current_order.position_id)
412
586
 
413
587
  # Update the portfolio
414
588
  portfolio = self.portfolio_repository.get(position.portfolio_id)
@@ -422,21 +596,35 @@ class OrderService(RepositoryService):
422
596
  )
423
597
 
424
598
  # Update the trading symbol position
425
- trading_symbol_position = self.position_repository.find(
599
+ trading_symbol_position = self.position_service.find(
426
600
  {
427
601
  "symbol": portfolio.trading_symbol,
428
602
  "portfolio": portfolio.id
429
603
  }
430
604
  )
431
- self.position_repository.update(
605
+ self.position_service.update(
432
606
  trading_symbol_position.id,
433
607
  {
434
- "amount": trading_symbol_position.get_amount()
435
- + filled_size
608
+ "amount":
609
+ trading_symbol_position.get_amount() + filled_size
436
610
  }
437
611
  )
438
612
 
439
- self._close_trades(filled_difference, current_order)
613
+ # Update the position if the amount has changed
614
+ if current_order.amount != previous_order.amount:
615
+ difference = current_order.amount - previous_order.amount
616
+ cost = difference * current_order.get_price()
617
+ self.position_service.update(
618
+ position.id,
619
+ {
620
+ "amount": position.get_amount() - difference,
621
+ "cost": position.get_cost() - cost
622
+ }
623
+ )
624
+
625
+ self.trade_service.update_trade_with_filled_sell_order(
626
+ filled_difference, current_order
627
+ )
440
628
 
441
629
  def _sync_with_buy_order_cancelled(self, order):
442
630
  remaining = order.get_amount() - order.get_filled()
@@ -456,13 +644,13 @@ class OrderService(RepositoryService):
456
644
  )
457
645
 
458
646
  # Add the remaining amount to the trading symbol position
459
- trading_symbol_position = self.position_repository.find(
647
+ trading_symbol_position = self.position_service.find(
460
648
  {
461
649
  "symbol": portfolio.trading_symbol,
462
650
  "portfolio": portfolio.id
463
651
  }
464
652
  )
465
- self.position_repository.update(
653
+ self.position_service.update(
466
654
  trading_symbol_position.id,
467
655
  {
468
656
  "amount": trading_symbol_position.get_amount() + remaining
@@ -473,13 +661,14 @@ class OrderService(RepositoryService):
473
661
  remaining = order.get_amount() - order.get_filled()
474
662
 
475
663
  # Add the remaining back to the position
476
- position = self.position_repository.get(order.position_id)
477
- self.position_repository.update(
664
+ position = self.position_service.get(order.position_id)
665
+ self.position_service.update(
478
666
  position.id,
479
667
  {
480
668
  "amount": position.get_amount() + remaining
481
669
  }
482
670
  )
671
+ self.trade_service.update_trade_with_removed_sell_order(order)
483
672
 
484
673
  def _sync_with_buy_order_failed(self, order):
485
674
  remaining = order.get_amount() - order.get_filled()
@@ -499,13 +688,13 @@ class OrderService(RepositoryService):
499
688
  )
500
689
 
501
690
  # Add the remaining amount to the trading symbol position
502
- trading_symbol_position = self.position_repository.find(
691
+ trading_symbol_position = self.position_service.find(
503
692
  {
504
693
  "symbol": portfolio.trading_symbol,
505
694
  "portfolio": portfolio.id
506
695
  }
507
696
  )
508
- self.position_repository.update(
697
+ self.position_service.update(
509
698
  trading_symbol_position.id,
510
699
  {
511
700
  "amount": trading_symbol_position.get_amount() + remaining
@@ -516,14 +705,16 @@ class OrderService(RepositoryService):
516
705
  remaining = order.get_amount() - order.get_filled()
517
706
 
518
707
  # Add the remaining back to the position
519
- position = self.position_repository.get(order.position_id)
520
- self.position_repository.update(
708
+ position = self.position_service.get(order.position_id)
709
+ self.position_service.update(
521
710
  position.id,
522
711
  {
523
712
  "amount": position.get_amount() + remaining
524
713
  }
525
714
  )
526
715
 
716
+ self.trade_service.update_trade_with_removed_sell_order(order)
717
+
527
718
  def _sync_with_buy_order_expired(self, order):
528
719
  remaining = order.get_amount() - order.get_filled()
529
720
  size = remaining * order.get_price()
@@ -542,13 +733,13 @@ class OrderService(RepositoryService):
542
733
  )
543
734
 
544
735
  # Add the remaining amount to the trading symbol position
545
- trading_symbol_position = self.position_repository.find(
736
+ trading_symbol_position = self.position_service.find(
546
737
  {
547
738
  "symbol": portfolio.trading_symbol,
548
739
  "portfolio": portfolio.id
549
740
  }
550
741
  )
551
- self.position_repository.update(
742
+ self.position_service.update(
552
743
  trading_symbol_position.id,
553
744
  {
554
745
  "amount": trading_symbol_position.get_amount() + remaining
@@ -559,14 +750,16 @@ class OrderService(RepositoryService):
559
750
  remaining = order.get_amount() - order.get_filled()
560
751
 
561
752
  # Add the remaining back to the position
562
- position = self.position_repository.get(order.position_id)
563
- self.position_repository.update(
753
+ position = self.position_service.get(order.position_id)
754
+ self.position_service.update(
564
755
  position.id,
565
756
  {
566
757
  "amount": position.get_amount() + remaining
567
758
  }
568
759
  )
569
760
 
761
+ self.trade_service.update_trade_with_removed_sell_order(order)
762
+
570
763
  def _sync_with_buy_order_rejected(self, order):
571
764
  remaining = order.get_amount() - order.get_filled()
572
765
  size = remaining * order.get_price()
@@ -585,13 +778,13 @@ class OrderService(RepositoryService):
585
778
  )
586
779
 
587
780
  # Add the remaining amount to the trading symbol position
588
- trading_symbol_position = self.position_repository.find(
781
+ trading_symbol_position = self.position_service.find(
589
782
  {
590
783
  "symbol": portfolio.trading_symbol,
591
784
  "portfolio": portfolio.id
592
785
  }
593
786
  )
594
- self.position_repository.update(
787
+ self.position_service.update(
595
788
  trading_symbol_position.id,
596
789
  {
597
790
  "amount": trading_symbol_position.get_amount() + remaining
@@ -602,144 +795,32 @@ class OrderService(RepositoryService):
602
795
  remaining = order.get_amount() - order.get_filled()
603
796
 
604
797
  # Add the remaining back to the position
605
- position = self.position_repository.get(order.position_id)
606
- self.position_repository.update(
798
+ position = self.position_service.get(order.position_id)
799
+ self.position_service.update(
607
800
  position.id,
608
801
  {
609
802
  "amount": position.get_amount() + remaining
610
803
  }
611
804
  )
612
805
 
613
- def _close_trades(self, amount_to_close, sell_order):
614
- """
615
- Close trades for an executed sell order.
806
+ self.trade_service.update_trade_with_removed_sell_order(order)
616
807
 
617
- This method will c
808
+ def _create_order_id(self):
618
809
  """
619
- matching_buy_orders = self.order_repository.get_all(
620
- {
621
- "position": sell_order.position_id,
622
- "order_side": OrderSide.BUY.value,
623
- "status": OrderStatus.CLOSED.value
624
- }
625
- )
626
- matching_buy_orders = [
627
- buy_order for buy_order in matching_buy_orders
628
- if buy_order.get_trade_closed_at() is None
629
- ]
630
- order_queue = PriorityQueue()
631
-
632
- for order in matching_buy_orders:
633
- order_queue.put(order)
634
-
635
- total_net_gain = 0
636
- total_cost = 0
637
-
638
- while amount_to_close > 0 and not order_queue.empty():
639
- buy_order = order_queue.get()
640
- closed_amount = buy_order.get_trade_closed_amount()
641
-
642
- # Check if the order has been closed
643
- if closed_amount is None:
644
- closed_amount = 0
645
-
646
- available_to_close = buy_order.get_filled() - closed_amount
647
-
648
- if amount_to_close >= available_to_close:
649
- to_be_closed = available_to_close
650
- remaining = amount_to_close - to_be_closed
651
- cost = buy_order.get_price() * to_be_closed
652
- net_gain = (sell_order.get_price() - buy_order.get_price()) \
653
- * to_be_closed
654
- amount_to_close = remaining
655
- self.order_repository.update(
656
- buy_order.id,
657
- {
658
- "trade_closed_amount": buy_order.get_filled(),
659
- "trade_closed_at": sell_order.get_updated_at(),
660
- "trade_closed_price": sell_order.get_price(),
661
- "net_gain": buy_order.get_net_gain() + net_gain
662
- }
663
- )
664
- else:
665
- to_be_closed = amount_to_close
666
- net_gain = (sell_order.get_price() - buy_order.get_price()) \
667
- * to_be_closed
668
- cost = buy_order.get_price() * amount_to_close
669
- closed_amount = buy_order.get_trade_closed_amount()
670
-
671
- if closed_amount is None:
672
- closed_amount = 0
810
+ Function to create a new order id. The function will
811
+ create a new order id and return it.
673
812
 
674
- self.order_repository.update(
675
- buy_order.id,
676
- {
677
- "trade_closed_amount": closed_amount + to_be_closed,
678
- "trade_closed_price": sell_order.get_price(),
679
- "net_gain": buy_order.get_net_gain() + net_gain
680
- }
681
- )
682
- amount_to_close = 0
683
-
684
- total_net_gain += net_gain
685
- total_cost += cost
686
-
687
- position = self.position_repository.get(sell_order.position_id)
688
-
689
- # Update portfolio
690
- portfolio = self.portfolio_repository.get(position.portfolio_id)
691
- self.portfolio_repository.update(
692
- portfolio.id,
693
- {
694
- "total_net_gain": portfolio.get_total_net_gain()
695
- + total_net_gain,
696
- "total_cost": portfolio.get_total_cost() - total_cost,
697
- "net_size": portfolio.get_net_size() + total_net_gain
698
- }
699
- )
700
- # Update the position
701
- position = self.position_repository.get(sell_order.position_id)
702
- self.position_repository.update(
703
- position.id,
704
- {
705
- "cost": position.get_cost() - total_cost
706
- }
707
- )
813
+ Returns:
814
+ int: The new order id
815
+ """
708
816
 
709
- # Update the sell order
710
- self.order_repository.update(
711
- sell_order.id,
712
- {
713
- "trade_closed_amount": sell_order.get_filled(),
714
- "trade_closed_at": sell_order.get_updated_at(),
715
- "trade_closed_price": sell_order.get_price(),
716
- "net_gain": sell_order.get_net_gain() + total_net_gain
717
- }
718
- )
817
+ unique = False
818
+ order_id = None
719
819
 
720
- def create_snapshot(self, portfolio_id, created_at=None):
820
+ while not unique:
821
+ order_id = random_number(12)
721
822
 
722
- if created_at is None:
723
- created_at = datetime.now(tz=tzutc())
823
+ if not self.repository.exists({"id": order_id}):
824
+ unique = True
724
825
 
725
- portfolio = self.portfolio_repository.get(portfolio_id)
726
- pending_orders = self.get_all(
727
- {
728
- "order_side": OrderSide.BUY.value,
729
- "status": OrderStatus.OPEN.value,
730
- "portfolio_id": portfolio.id
731
- }
732
- )
733
- created_orders = self.get_all(
734
- {
735
- "order_side": OrderSide.BUY.value,
736
- "status": OrderStatus.CREATED.value,
737
- "portfolio_id": portfolio.id
738
- }
739
- )
740
- return self.portfolio_snapshot_service.create_snapshot(
741
- portfolio,
742
- pending_orders=pending_orders,
743
- created_orders=created_orders,
744
- created_at=created_at
745
- )
826
+ return order_id