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.
- investing_algorithm_framework/__init__.py +168 -45
- investing_algorithm_framework/app/__init__.py +32 -1
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1933 -589
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1725 -0
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +4 -2
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +664 -84
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +226 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/create_app.py +40 -6
- investing_algorithm_framework/dependency_container.py +72 -56
- investing_algorithm_framework/domain/__init__.py +71 -47
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +59 -91
- investing_algorithm_framework/domain/constants.py +13 -38
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +3 -2
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +17 -12
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
- investing_algorithm_framework/domain/models/order/order.py +77 -83
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +12 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +37 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +66 -2
- investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
- investing_algorithm_framework/domain/models/trade/trade.py +295 -171
- investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/services/__init__.py +2 -9
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +12 -7
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +29 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +31 -18
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
- investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
- investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/services/__init__.py +113 -16
- investing_algorithm_framework/services/backtesting/__init__.py +0 -7
- investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
- investing_algorithm_framework/services/configuration_service.py +77 -11
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/services/market_credential_service.py +16 -1
- investing_algorithm_framework/services/metrics/__init__.py +114 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +181 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +3 -1
- investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +407 -326
- investing_algorithm_framework/services/portfolios/__init__.py +3 -1
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/repository_service.py +8 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
- investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -1105
- investing_algorithm_framework/domain/graphs.py +0 -382
- investing_algorithm_framework/domain/metrics/__init__.py +0 -6
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -472
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
- investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
- investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
- investing_algorithm_framework/services/backtesting/graphs.py +0 -61
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
- investing_algorithm_framework/services/position_service.py +0 -31
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
- investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
- investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
- /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {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,
|
|
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
|
-
|
|
22
|
-
position_repository,
|
|
40
|
+
position_service,
|
|
23
41
|
portfolio_repository,
|
|
24
42
|
portfolio_configuration_service,
|
|
25
43
|
portfolio_snapshot_service,
|
|
26
|
-
|
|
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.
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
127
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
382
|
+
unallocated_amount = unallocated_position.get_amount()
|
|
258
383
|
|
|
259
|
-
if
|
|
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
|
|
268
|
-
|
|
269
|
-
if
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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.
|
|
414
|
+
position = self.position_service.get(order.position_id)
|
|
310
415
|
portfolio = self.portfolio_repository.get(position.portfolio_id)
|
|
311
|
-
|
|
312
|
-
.
|
|
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.
|
|
432
|
+
if not self.position_service.exists(
|
|
317
433
|
{"portfolio": portfolio.id, "symbol": symbol}
|
|
318
434
|
):
|
|
319
|
-
self.
|
|
435
|
+
self.position_service \
|
|
320
436
|
.create({"portfolio_id": portfolio.id, "symbol": symbol})
|
|
321
|
-
position = self.
|
|
437
|
+
position = self.position_service \
|
|
322
438
|
.find({"portfolio": portfolio.id, "symbol": symbol})
|
|
323
439
|
else:
|
|
324
|
-
position = self.
|
|
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
|
-
|
|
331
|
-
portfolio
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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.
|
|
369
|
-
|
|
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
|
-
|
|
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
|
-
|
|
381
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
605
|
+
self.position_service.update(
|
|
432
606
|
trading_symbol_position.id,
|
|
433
607
|
{
|
|
434
|
-
"amount":
|
|
435
|
-
|
|
608
|
+
"amount":
|
|
609
|
+
trading_symbol_position.get_amount() + filled_size
|
|
436
610
|
}
|
|
437
611
|
)
|
|
438
612
|
|
|
439
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
477
|
-
self.
|
|
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.
|
|
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.
|
|
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.
|
|
520
|
-
self.
|
|
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.
|
|
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.
|
|
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.
|
|
563
|
-
self.
|
|
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.
|
|
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.
|
|
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.
|
|
606
|
-
self.
|
|
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
|
-
|
|
614
|
-
"""
|
|
615
|
-
Close trades for an executed sell order.
|
|
806
|
+
self.trade_service.update_trade_with_removed_sell_order(order)
|
|
616
807
|
|
|
617
|
-
|
|
808
|
+
def _create_order_id(self):
|
|
618
809
|
"""
|
|
619
|
-
|
|
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
|
-
|
|
675
|
-
|
|
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
|
-
|
|
710
|
-
|
|
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
|
-
|
|
820
|
+
while not unique:
|
|
821
|
+
order_id = random_number(12)
|
|
721
822
|
|
|
722
|
-
|
|
723
|
-
|
|
823
|
+
if not self.repository.exists({"id": order_id}):
|
|
824
|
+
unique = True
|
|
724
825
|
|
|
725
|
-
|
|
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
|