investing-algorithm-framework 7.19.14__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 +197 -0
- investing_algorithm_framework/app/__init__.py +47 -0
- 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 +2204 -0
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1667 -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/__init__.py +35 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
- investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
- investing_algorithm_framework/app/strategy.py +675 -0
- investing_algorithm_framework/app/task.py +41 -0
- investing_algorithm_framework/app/web/__init__.py +5 -0
- investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
- investing_algorithm_framework/app/web/controllers/orders.py +20 -0
- investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
- investing_algorithm_framework/app/web/controllers/positions.py +18 -0
- investing_algorithm_framework/app/web/create_app.py +20 -0
- investing_algorithm_framework/app/web/error_handler.py +59 -0
- investing_algorithm_framework/app/web/responses.py +20 -0
- investing_algorithm_framework/app/web/run_strategies.py +4 -0
- investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
- investing_algorithm_framework/app/web/schemas/order.py +12 -0
- investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
- investing_algorithm_framework/app/web/schemas/position.py +15 -0
- investing_algorithm_framework/app/web/setup_cors.py +6 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +207 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -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 +54 -0
- investing_algorithm_framework/dependency_container.py +155 -0
- investing_algorithm_framework/domain/__init__.py +148 -0
- 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 +435 -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 +111 -0
- investing_algorithm_framework/domain/constants.py +83 -0
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +42 -0
- investing_algorithm_framework/domain/decimal_parsing.py +40 -0
- investing_algorithm_framework/domain/exceptions.py +112 -0
- investing_algorithm_framework/domain/models/__init__.py +43 -0
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/base_model.py +25 -0
- 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/__init__.py +5 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
- investing_algorithm_framework/domain/models/order/__init__.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +384 -0
- investing_algorithm_framework/domain/models/order/order_side.py +36 -0
- investing_algorithm_framework/domain/models/order/order_status.py +37 -0
- investing_algorithm_framework/domain/models/order/order_type.py +30 -0
- investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
- investing_algorithm_framework/domain/models/position/__init__.py +4 -0
- investing_algorithm_framework/domain/models/position/position.py +68 -0
- investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
- investing_algorithm_framework/domain/models/time_frame.py +153 -0
- investing_algorithm_framework/domain/models/time_interval.py +124 -0
- investing_algorithm_framework/domain/models/time_unit.py +149 -0
- investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
- investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
- investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
- investing_algorithm_framework/domain/models/trade/trade.py +388 -0
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/positions/__init__.py +4 -0
- investing_algorithm_framework/domain/positions/position_size.py +41 -0
- investing_algorithm_framework/domain/services/__init__.py +11 -0
- investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
- investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
- investing_algorithm_framework/domain/services/rounding_service.py +27 -0
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/stateless_actions.py +7 -0
- investing_algorithm_framework/domain/strategy.py +44 -0
- investing_algorithm_framework/domain/utils/__init__.py +27 -0
- investing_algorithm_framework/domain/utils/csv.py +104 -0
- 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 +41 -0
- investing_algorithm_framework/domain/utils/signatures.py +17 -0
- investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
- investing_algorithm_framework/domain/utils/synchronized.py +12 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +50 -0
- 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 +10 -0
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
- investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
- investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
- investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
- 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 +4 -0
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
- investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
- investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
- investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
- 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 +40 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -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 +21 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
- investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
- 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 +132 -0
- investing_algorithm_framework/services/backtesting/__init__.py +5 -0
- investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
- investing_algorithm_framework/services/configuration_service.py +96 -0
- 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 +40 -0
- 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/price_efficiency.py +57 -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 +9 -0
- investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +826 -0
- investing_algorithm_framework/services/portfolios/__init__.py +16 -0
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
- investing_algorithm_framework/services/repository_service.py +40 -0
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
- investing_algorithm_framework/services/trade_service/__init__.py +3 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
- investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
- investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
- investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
- investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
- investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -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
|