investing-algorithm-framework 1.3.1__py3-none-any.whl → 7.25.6__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.
Files changed (282) hide show
  1. investing_algorithm_framework/__init__.py +195 -16
  2. investing_algorithm_framework/analysis/__init__.py +16 -0
  3. investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
  4. investing_algorithm_framework/analysis/data.py +170 -0
  5. investing_algorithm_framework/analysis/markdown.py +91 -0
  6. investing_algorithm_framework/analysis/ranking.py +298 -0
  7. investing_algorithm_framework/app/__init__.py +31 -4
  8. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  9. investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
  10. investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
  11. investing_algorithm_framework/app/app.py +2233 -264
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1724 -0
  14. investing_algorithm_framework/app/eventloop.py +620 -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 +6 -3
  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 +2 -1
  39. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  40. investing_algorithm_framework/app/stateless/exception_handler.py +1 -1
  41. investing_algorithm_framework/app/strategy.py +873 -52
  42. investing_algorithm_framework/app/task.py +5 -3
  43. investing_algorithm_framework/app/web/__init__.py +2 -1
  44. investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
  45. investing_algorithm_framework/app/web/controllers/orders.py +4 -3
  46. investing_algorithm_framework/app/web/controllers/portfolio.py +1 -1
  47. investing_algorithm_framework/app/web/controllers/positions.py +3 -3
  48. investing_algorithm_framework/app/web/create_app.py +4 -2
  49. investing_algorithm_framework/app/web/error_handler.py +1 -1
  50. investing_algorithm_framework/app/web/schemas/order.py +2 -2
  51. investing_algorithm_framework/app/web/schemas/position.py +1 -0
  52. investing_algorithm_framework/cli/__init__.py +0 -0
  53. investing_algorithm_framework/cli/cli.py +231 -0
  54. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  55. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  56. investing_algorithm_framework/cli/initialize_app.py +603 -0
  57. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  58. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  59. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  60. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  61. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  62. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  63. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  64. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  65. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  66. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  67. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  68. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  69. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  70. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  71. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  72. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  73. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  74. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  75. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  76. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  77. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  78. investing_algorithm_framework/cli/validate_backtest_checkpoints.py +197 -0
  79. investing_algorithm_framework/create_app.py +43 -9
  80. investing_algorithm_framework/dependency_container.py +121 -33
  81. investing_algorithm_framework/domain/__init__.py +109 -22
  82. investing_algorithm_framework/domain/algorithm_id.py +69 -0
  83. investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
  84. investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
  85. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
  86. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
  87. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
  88. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
  92. investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
  93. investing_algorithm_framework/domain/config.py +60 -138
  94. investing_algorithm_framework/domain/constants.py +23 -34
  95. investing_algorithm_framework/domain/data_provider.py +334 -0
  96. investing_algorithm_framework/domain/data_structures.py +42 -0
  97. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  98. investing_algorithm_framework/domain/exceptions.py +51 -1
  99. investing_algorithm_framework/domain/models/__init__.py +29 -14
  100. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  101. investing_algorithm_framework/domain/models/base_model.py +3 -1
  102. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  103. investing_algorithm_framework/domain/models/data/data_source.py +222 -0
  104. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  105. investing_algorithm_framework/domain/models/event.py +35 -0
  106. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  107. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  108. investing_algorithm_framework/domain/models/order/__init__.py +3 -4
  109. investing_algorithm_framework/domain/models/order/order.py +243 -86
  110. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  111. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  112. investing_algorithm_framework/domain/models/portfolio/__init__.py +7 -2
  113. investing_algorithm_framework/domain/models/portfolio/portfolio.py +134 -1
  114. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -37
  115. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  116. investing_algorithm_framework/domain/models/position/__init__.py +3 -2
  117. investing_algorithm_framework/domain/models/position/position.py +29 -0
  118. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  119. investing_algorithm_framework/domain/models/position/{position_cost.py → position_snapshot.py} +16 -8
  120. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  121. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  122. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  123. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  124. investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
  125. investing_algorithm_framework/domain/models/time_frame.py +94 -98
  126. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +111 -2
  128. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  129. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  130. investing_algorithm_framework/domain/models/trade/__init__.py +11 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +389 -0
  132. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  133. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  134. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  135. investing_algorithm_framework/domain/order_executor.py +112 -0
  136. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  137. investing_algorithm_framework/domain/services/__init__.py +11 -0
  138. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  139. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  140. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  141. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  142. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  143. investing_algorithm_framework/domain/strategy.py +1 -29
  144. investing_algorithm_framework/domain/utils/__init__.py +16 -4
  145. investing_algorithm_framework/domain/utils/csv.py +22 -0
  146. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  147. investing_algorithm_framework/domain/utils/dates.py +57 -0
  148. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  149. investing_algorithm_framework/domain/utils/polars.py +53 -0
  150. investing_algorithm_framework/domain/utils/random.py +29 -0
  151. investing_algorithm_framework/download_data.py +244 -0
  152. investing_algorithm_framework/infrastructure/__init__.py +39 -11
  153. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  154. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -0
  155. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  156. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  157. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  158. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +87 -13
  159. investing_algorithm_framework/infrastructure/models/__init__.py +13 -4
  160. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  161. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
  162. investing_algorithm_framework/infrastructure/models/order/order.py +73 -73
  163. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  164. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  165. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +3 -2
  166. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  167. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +57 -3
  168. investing_algorithm_framework/infrastructure/models/position/__init__.py +2 -2
  169. investing_algorithm_framework/infrastructure/models/position/position.py +16 -11
  170. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  171. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  172. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  173. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  174. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  175. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  176. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  177. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  178. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  179. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  180. investing_algorithm_framework/infrastructure/repositories/__init__.py +13 -5
  181. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  182. investing_algorithm_framework/infrastructure/repositories/order_repository.py +32 -19
  183. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
  184. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  185. investing_algorithm_framework/infrastructure/repositories/position_repository.py +47 -4
  186. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  187. investing_algorithm_framework/infrastructure/repositories/repository.py +85 -31
  188. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  189. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  190. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  191. investing_algorithm_framework/infrastructure/services/__init__.py +9 -2
  192. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  193. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -0
  194. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  195. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  196. investing_algorithm_framework/infrastructure/services/backtesting/__init__.py +9 -0
  197. investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
  198. investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
  199. investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
  200. investing_algorithm_framework/services/__init__.py +127 -10
  201. investing_algorithm_framework/services/configuration_service.py +95 -0
  202. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  203. investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
  204. investing_algorithm_framework/services/market_credential_service.py +40 -0
  205. investing_algorithm_framework/services/metrics/__init__.py +119 -0
  206. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  207. investing_algorithm_framework/services/metrics/beta.py +0 -0
  208. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  209. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  210. investing_algorithm_framework/services/metrics/drawdown.py +218 -0
  211. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  212. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  213. investing_algorithm_framework/services/metrics/generate.py +358 -0
  214. investing_algorithm_framework/services/metrics/mean_daily_return.py +84 -0
  215. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  216. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  217. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  218. investing_algorithm_framework/services/metrics/returns.py +452 -0
  219. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  220. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  221. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  222. investing_algorithm_framework/services/metrics/standard_deviation.py +156 -0
  223. investing_algorithm_framework/services/metrics/trades.py +473 -0
  224. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  225. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  226. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  227. investing_algorithm_framework/services/metrics/volatility.py +118 -0
  228. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  229. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  230. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  231. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  232. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  233. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  234. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  235. investing_algorithm_framework/services/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
  236. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  237. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  238. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  239. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  240. investing_algorithm_framework/services/positions/__init__.py +7 -0
  241. investing_algorithm_framework/services/positions/position_service.py +210 -0
  242. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  243. investing_algorithm_framework/services/repository_service.py +8 -2
  244. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  245. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -0
  246. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  247. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  248. investing_algorithm_framework/services/trade_service/__init__.py +9 -0
  249. investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
  250. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  251. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  252. investing_algorithm_framework-7.25.6.dist-info/METADATA +535 -0
  253. investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
  254. {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
  255. investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
  256. investing_algorithm_framework/app/algorithm.py +0 -410
  257. investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
  258. investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
  259. investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -76
  260. investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
  261. investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
  262. investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
  263. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  264. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -205
  265. investing_algorithm_framework/domain/singleton.py +0 -9
  266. investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
  267. investing_algorithm_framework/infrastructure/models/position/position_cost.py +0 -32
  268. investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
  269. investing_algorithm_framework/infrastructure/repositories/position_cost_repository.py +0 -16
  270. investing_algorithm_framework/infrastructure/services/market_service.py +0 -422
  271. investing_algorithm_framework/services/market_data_service.py +0 -75
  272. investing_algorithm_framework/services/order_service.py +0 -464
  273. investing_algorithm_framework/services/portfolio_service.py +0 -105
  274. investing_algorithm_framework/services/position_cost_service.py +0 -5
  275. investing_algorithm_framework/services/position_service.py +0 -50
  276. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -219
  277. investing_algorithm_framework/setup_logging.py +0 -40
  278. investing_algorithm_framework-1.3.1.dist-info/AUTHORS.md +0 -8
  279. investing_algorithm_framework-1.3.1.dist-info/METADATA +0 -172
  280. investing_algorithm_framework-1.3.1.dist-info/RECORD +0 -103
  281. investing_algorithm_framework-1.3.1.dist-info/top_level.txt +0 -1
  282. {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
@@ -0,0 +1,826 @@
1
+ import logging
2
+
3
+ from investing_algorithm_framework.domain import OrderType, OrderSide, \
4
+ OperationalException, OrderStatus, Order, random_number, INDEX_DATETIME
5
+ from investing_algorithm_framework.services.repository_service \
6
+ import RepositoryService
7
+
8
+ logger = logging.getLogger("investing_algorithm_framework")
9
+
10
+
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
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ configuration_service,
39
+ order_repository,
40
+ position_service,
41
+ portfolio_repository,
42
+ portfolio_configuration_service,
43
+ portfolio_snapshot_service,
44
+ trade_service,
45
+ portfolio_provider_lookup=None,
46
+ order_executor_lookup=None,
47
+ market_credential_service=None
48
+ ):
49
+ super(OrderService, self).__init__(order_repository)
50
+ self.configuration_service = configuration_service
51
+ self.order_repository = order_repository
52
+ self.position_service = position_service
53
+ self.portfolio_repository = portfolio_repository
54
+ self.portfolio_configuration_service = portfolio_configuration_service
55
+ self.portfolio_snapshot_service = portfolio_snapshot_service
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
60
+
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
+ """
133
+ portfolio_id = data["portfolio_id"]
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"]
153
+
154
+ if validate:
155
+ self.validate_order(data, portfolio)
156
+
157
+ del data["portfolio_id"]
158
+ data["target_symbol"] = data["target_symbol"].upper()
159
+ symbol = data["target_symbol"]
160
+ data["id"] = self._create_order_id()
161
+
162
+ order = self.repository.create(data, save=False)
163
+
164
+ if validate:
165
+ self.validate_order(data, portfolio)
166
+
167
+ if execute:
168
+ order = self.execute_order(order, portfolio)
169
+
170
+ position = self._create_position_if_not_exists(symbol, portfolio)
171
+ order.position_id = position.id
172
+ order = self.order_repository.save(order)
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)
187
+
188
+ if sync:
189
+ order = self.get(order_id)
190
+ if OrderSide.BUY.equals(order_side):
191
+ self._sync_portfolio_with_created_buy_order(order)
192
+ else:
193
+ self._sync_portfolio_with_created_sell_order(order)
194
+
195
+ order = self.get(order_id)
196
+ return order
197
+
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
+ """
221
+ previous_order = self.order_repository.get(object_id)
222
+ new_order = self.order_repository.update(object_id, data)
223
+ filled_difference = new_order.get_filled() \
224
+ - previous_order.get_filled()
225
+
226
+ if filled_difference > 0:
227
+ if OrderSide.BUY.equals(new_order.get_order_side()):
228
+ self._sync_with_buy_order_filled(previous_order, new_order)
229
+ else:
230
+ self._sync_with_sell_order_filled(previous_order, new_order)
231
+
232
+ if "status" in data:
233
+
234
+ if OrderStatus.CANCELED.equals(new_order.get_status()):
235
+ if OrderSide.BUY.equals(new_order.get_order_side()):
236
+ self._sync_with_buy_order_cancelled(new_order)
237
+ else:
238
+ self._sync_with_sell_order_cancelled(new_order)
239
+
240
+ if OrderStatus.EXPIRED.equals(new_order.get_status()):
241
+
242
+ if OrderSide.BUY.equals(new_order.get_order_side()):
243
+ self._sync_with_buy_order_expired(new_order)
244
+ else:
245
+ self._sync_with_sell_order_expired(new_order)
246
+
247
+ if OrderStatus.REJECTED.equals(new_order.get_status()):
248
+
249
+ if OrderSide.BUY.equals(new_order.get_order_side()):
250
+ self._sync_with_buy_order_rejected(new_order)
251
+ else:
252
+ self._sync_with_sell_order_expired(new_order)
253
+
254
+ return new_order
255
+
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.
261
+
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
291
+
292
+ def validate_order(self, order_data, portfolio):
293
+
294
+ if OrderSide.BUY.equals(order_data["order_side"]):
295
+ self.validate_buy_order(order_data, portfolio)
296
+ else:
297
+ self.validate_sell_order(order_data, portfolio)
298
+
299
+ if OrderType.LIMIT.equals(order_data["order_type"]):
300
+ self.validate_limit_order(order_data, portfolio)
301
+ else:
302
+ raise OperationalException(
303
+ f"Order type {order_data['order_type']} is not supported"
304
+ )
305
+
306
+ def validate_sell_order(self, order_data, portfolio):
307
+
308
+ if not self.position_service.exists(
309
+ {
310
+ "symbol": order_data["target_symbol"],
311
+ "portfolio": portfolio.id
312
+ }
313
+ ):
314
+ raise OperationalException(
315
+ "Can't add sell order to non existing position"
316
+ )
317
+
318
+ position = self.position_service\
319
+ .find(
320
+ {
321
+ "symbol": order_data["target_symbol"],
322
+ "portfolio": portfolio.id
323
+ }
324
+ )
325
+
326
+ if position.get_amount() < order_data["amount"]:
327
+ raise OperationalException(
328
+ f"Order amount {order_data['amount']} is larger " +
329
+ f"then amount of open position {position.get_amount()}"
330
+ )
331
+
332
+ if not order_data["trading_symbol"] == portfolio.trading_symbol:
333
+ raise OperationalException(
334
+ f"Can't add sell order with target "
335
+ f"symbol {order_data['target_symbol']} to "
336
+ f"portfolio with trading symbol {portfolio.trading_symbol}"
337
+ )
338
+
339
+ @staticmethod
340
+ def validate_buy_order(order_data, portfolio):
341
+
342
+ if not order_data["trading_symbol"] == portfolio.trading_symbol:
343
+ raise OperationalException(
344
+ f"Can't add buy order with trading "
345
+ f"symbol {order_data['trading_symbol']} to "
346
+ f"portfolio with trading symbol {portfolio.trading_symbol}"
347
+ )
348
+
349
+ def validate_limit_order(self, order_data, portfolio):
350
+
351
+ if OrderSide.SELL.equals(order_data["order_side"]):
352
+ amount = order_data["amount"]
353
+ position = self.position_service\
354
+ .find(
355
+ {
356
+ "portfolio": portfolio.id,
357
+ "symbol": order_data["target_symbol"]
358
+ }
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
+
367
+ if amount > position.get_amount():
368
+ raise OperationalException(
369
+ f"Order amount: {amount} {position.symbol}, is "
370
+ f"larger then position size: {position.get_amount()} "
371
+ f"{position.symbol} of the portfolio"
372
+ )
373
+ else:
374
+ total_price = order_data["amount"] * order_data["price"]
375
+ unallocated_position = self.position_service\
376
+ .find(
377
+ {
378
+ "portfolio": portfolio.id,
379
+ "symbol": portfolio.trading_symbol
380
+ }
381
+ )
382
+ unallocated_amount = unallocated_position.get_amount()
383
+
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:
392
+ raise OperationalException(
393
+ f"Order total: {total_price} "
394
+ f"{portfolio.trading_symbol}, is "
395
+ f"larger then unallocated size: {portfolio.unallocated} "
396
+ f"{portfolio.trading_symbol} of the portfolio"
397
+ )
398
+
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
+ )
410
+ else:
411
+ pending_orders = self.get_all({"status": OrderStatus.OPEN.value})
412
+
413
+ for order in pending_orders:
414
+ position = self.position_service.get(order.position_id)
415
+ portfolio = self.portfolio_repository.get(position.portfolio_id)
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
+ )
429
+ self.update(order.id, external_order.to_dict())
430
+
431
+ def _create_position_if_not_exists(self, symbol, portfolio):
432
+ if not self.position_service.exists(
433
+ {"portfolio": portfolio.id, "symbol": symbol}
434
+ ):
435
+ self.position_service \
436
+ .create({"portfolio_id": portfolio.id, "symbol": symbol})
437
+ position = self.position_service \
438
+ .find({"portfolio": portfolio.id, "symbol": symbol})
439
+ else:
440
+ position = self.position_service \
441
+ .find({"portfolio": portfolio.id, "symbol": symbol})
442
+
443
+ return position
444
+
445
+ def _sync_portfolio_with_created_buy_order(self, order):
446
+ """
447
+ Function to sync the portfolio and positions with a created buy order.
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()
461
+ self.portfolio_repository.update(
462
+ portfolio.id, {"unallocated": portfolio.get_unallocated() - size}
463
+ )
464
+
465
+ def _sync_portfolio_with_created_sell_order(self, order):
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
484
+ )
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
+
499
+ def cancel_order(self, order):
500
+ self.check_pending_orders()
501
+ order = self.order_repository.get(order.id)
502
+
503
+ if order is not None:
504
+
505
+ if OrderStatus.OPEN.equals(order.status):
506
+ portfolio = self.portfolio_repository\
507
+ .find({"position": order.position_id})
508
+ market_credential = self.market_credential_service.get(
509
+ portfolio.market
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())
516
+
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")
530
+ filled_difference = current_order.get_filled() - \
531
+ previous_order.get_filled()
532
+ filled_size = filled_difference * current_order.get_price()
533
+
534
+ if filled_difference <= 0:
535
+ return
536
+
537
+ self.position_service.update_positions_with_buy_order_filled(
538
+ current_order, filled_difference
539
+ )
540
+ position = self.position_service.get(current_order.position_id)
541
+
542
+ # Update portfolio
543
+ portfolio = self.portfolio_repository.get(position.portfolio_id)
544
+ self.portfolio_repository.update(
545
+ portfolio.id,
546
+ {
547
+ "total_cost": portfolio.get_total_cost() + filled_size,
548
+ "total_trade_volume": portfolio.get_total_trade_volume()
549
+ + filled_size,
550
+ }
551
+ )
552
+
553
+ self.trade_service.update_trade_with_buy_order(
554
+ filled_difference, current_order
555
+ )
556
+
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
+ """
571
+ filled_difference = current_order.get_filled() - \
572
+ previous_order.get_filled()
573
+ filled_size = filled_difference * current_order.get_price()
574
+
575
+ if filled_difference <= 0:
576
+ return
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
+
584
+ # Get position
585
+ position = self.position_service.get(current_order.position_id)
586
+
587
+ # Update the portfolio
588
+ portfolio = self.portfolio_repository.get(position.portfolio_id)
589
+ self.portfolio_repository.update(
590
+ portfolio.id,
591
+ {
592
+ "unallocated": portfolio.get_unallocated() + filled_size,
593
+ "total_trade_volume": portfolio.get_total_trade_volume()
594
+ + filled_size,
595
+ }
596
+ )
597
+
598
+ # Update the trading symbol position
599
+ trading_symbol_position = self.position_service.find(
600
+ {
601
+ "symbol": portfolio.trading_symbol,
602
+ "portfolio": portfolio.id
603
+ }
604
+ )
605
+ self.position_service.update(
606
+ trading_symbol_position.id,
607
+ {
608
+ "amount":
609
+ trading_symbol_position.get_amount() + filled_size
610
+ }
611
+ )
612
+
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
+ )
628
+
629
+ def _sync_with_buy_order_cancelled(self, order):
630
+ remaining = order.get_amount() - order.get_filled()
631
+ size = remaining * order.get_price()
632
+
633
+ # Add the remaining amount to the portfolio
634
+ portfolio = self.portfolio_repository.find(
635
+ {
636
+ "position": order.position_id
637
+ }
638
+ )
639
+ self.portfolio_repository.update(
640
+ portfolio.id,
641
+ {
642
+ "unallocated": portfolio.get_unallocated() + size
643
+ }
644
+ )
645
+
646
+ # Add the remaining amount to the trading symbol position
647
+ trading_symbol_position = self.position_service.find(
648
+ {
649
+ "symbol": portfolio.trading_symbol,
650
+ "portfolio": portfolio.id
651
+ }
652
+ )
653
+ self.position_service.update(
654
+ trading_symbol_position.id,
655
+ {
656
+ "amount": trading_symbol_position.get_amount() + remaining
657
+ }
658
+ )
659
+
660
+ def _sync_with_sell_order_cancelled(self, order):
661
+ remaining = order.get_amount() - order.get_filled()
662
+
663
+ # Add the remaining back to the position
664
+ position = self.position_service.get(order.position_id)
665
+ self.position_service.update(
666
+ position.id,
667
+ {
668
+ "amount": position.get_amount() + remaining
669
+ }
670
+ )
671
+ self.trade_service.update_trade_with_removed_sell_order(order)
672
+
673
+ def _sync_with_buy_order_failed(self, order):
674
+ remaining = order.get_amount() - order.get_filled()
675
+ size = remaining * order.get_price()
676
+
677
+ # Add the remaining amount to the portfolio
678
+ portfolio = self.portfolio_repository.find(
679
+ {
680
+ "position": order.position_id
681
+ }
682
+ )
683
+ self.portfolio_repository.update(
684
+ portfolio.id,
685
+ {
686
+ "unallocated": portfolio.get_unallocated() + size
687
+ }
688
+ )
689
+
690
+ # Add the remaining amount to the trading symbol position
691
+ trading_symbol_position = self.position_service.find(
692
+ {
693
+ "symbol": portfolio.trading_symbol,
694
+ "portfolio": portfolio.id
695
+ }
696
+ )
697
+ self.position_service.update(
698
+ trading_symbol_position.id,
699
+ {
700
+ "amount": trading_symbol_position.get_amount() + remaining
701
+ }
702
+ )
703
+
704
+ def _sync_with_sell_order_failed(self, order):
705
+ remaining = order.get_amount() - order.get_filled()
706
+
707
+ # Add the remaining back to the position
708
+ position = self.position_service.get(order.position_id)
709
+ self.position_service.update(
710
+ position.id,
711
+ {
712
+ "amount": position.get_amount() + remaining
713
+ }
714
+ )
715
+
716
+ self.trade_service.update_trade_with_removed_sell_order(order)
717
+
718
+ def _sync_with_buy_order_expired(self, order):
719
+ remaining = order.get_amount() - order.get_filled()
720
+ size = remaining * order.get_price()
721
+
722
+ # Add the remaining amount to the portfolio
723
+ portfolio = self.portfolio_repository.find(
724
+ {
725
+ "position": order.position_id
726
+ }
727
+ )
728
+ self.portfolio_repository.update(
729
+ portfolio.id,
730
+ {
731
+ "unallocated": portfolio.get_unallocated() + size
732
+ }
733
+ )
734
+
735
+ # Add the remaining amount to the trading symbol position
736
+ trading_symbol_position = self.position_service.find(
737
+ {
738
+ "symbol": portfolio.trading_symbol,
739
+ "portfolio": portfolio.id
740
+ }
741
+ )
742
+ self.position_service.update(
743
+ trading_symbol_position.id,
744
+ {
745
+ "amount": trading_symbol_position.get_amount() + remaining
746
+ }
747
+ )
748
+
749
+ def _sync_with_sell_order_expired(self, order):
750
+ remaining = order.get_amount() - order.get_filled()
751
+
752
+ # Add the remaining back to the position
753
+ position = self.position_service.get(order.position_id)
754
+ self.position_service.update(
755
+ position.id,
756
+ {
757
+ "amount": position.get_amount() + remaining
758
+ }
759
+ )
760
+
761
+ self.trade_service.update_trade_with_removed_sell_order(order)
762
+
763
+ def _sync_with_buy_order_rejected(self, order):
764
+ remaining = order.get_amount() - order.get_filled()
765
+ size = remaining * order.get_price()
766
+
767
+ # Add the remaining amount to the portfolio
768
+ portfolio = self.portfolio_repository.find(
769
+ {
770
+ "position": order.position_id
771
+ }
772
+ )
773
+ self.portfolio_repository.update(
774
+ portfolio.id,
775
+ {
776
+ "unallocated": portfolio.get_unallocated() + size
777
+ }
778
+ )
779
+
780
+ # Add the remaining amount to the trading symbol position
781
+ trading_symbol_position = self.position_service.find(
782
+ {
783
+ "symbol": portfolio.trading_symbol,
784
+ "portfolio": portfolio.id
785
+ }
786
+ )
787
+ self.position_service.update(
788
+ trading_symbol_position.id,
789
+ {
790
+ "amount": trading_symbol_position.get_amount() + remaining
791
+ }
792
+ )
793
+
794
+ def _sync_with_sell_order_rejected(self, order):
795
+ remaining = order.get_amount() - order.get_filled()
796
+
797
+ # Add the remaining back to the position
798
+ position = self.position_service.get(order.position_id)
799
+ self.position_service.update(
800
+ position.id,
801
+ {
802
+ "amount": position.get_amount() + remaining
803
+ }
804
+ )
805
+
806
+ self.trade_service.update_trade_with_removed_sell_order(order)
807
+
808
+ def _create_order_id(self):
809
+ """
810
+ Function to create a new order id. The function will
811
+ create a new order id and return it.
812
+
813
+ Returns:
814
+ int: The new order id
815
+ """
816
+
817
+ unique = False
818
+ order_id = None
819
+
820
+ while not unique:
821
+ order_id = random_number(12)
822
+
823
+ if not self.repository.exists({"id": order_id}):
824
+ unique = True
825
+
826
+ return order_id