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,1083 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from queue import PriorityQueue
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from investing_algorithm_framework.domain import OrderStatus, TradeStatus, \
|
|
7
|
+
Trade, OperationalException, TradeRiskType, OrderType, \
|
|
8
|
+
OrderSide, Environment, ENVIRONMENT, PeekableQueue, DataType, \
|
|
9
|
+
INDEX_DATETIME, random_number, random_string
|
|
10
|
+
from investing_algorithm_framework.services.repository_service import \
|
|
11
|
+
RepositoryService
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TradeService(RepositoryService):
|
|
17
|
+
"""
|
|
18
|
+
Trade service class to handle trade related operations. This class
|
|
19
|
+
is responsible for creating, updating, and deleting trades. It also
|
|
20
|
+
takes care of keeping track of all sell transactions that are
|
|
21
|
+
associated with a trade.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
trade_repository,
|
|
27
|
+
order_repository,
|
|
28
|
+
trade_stop_loss_repository,
|
|
29
|
+
trade_take_profit_repository,
|
|
30
|
+
position_repository,
|
|
31
|
+
portfolio_repository,
|
|
32
|
+
configuration_service,
|
|
33
|
+
order_metadata_repository
|
|
34
|
+
):
|
|
35
|
+
super(TradeService, self).__init__(trade_repository)
|
|
36
|
+
self.order_repository = order_repository
|
|
37
|
+
self.portfolio_repository = portfolio_repository
|
|
38
|
+
self.position_repository = position_repository
|
|
39
|
+
self.configuration_service = configuration_service
|
|
40
|
+
self.trade_stop_loss_repository = trade_stop_loss_repository
|
|
41
|
+
self.trade_take_profit_repository = trade_take_profit_repository
|
|
42
|
+
self.order_metadata_repository = order_metadata_repository
|
|
43
|
+
|
|
44
|
+
def create_trade_from_buy_order(self, buy_order) -> Union[Trade, None]:
|
|
45
|
+
"""
|
|
46
|
+
Function to create a trade from a buy order. If the given buy
|
|
47
|
+
order has its status set to CANCELED, EXPIRED, or REJECTED,
|
|
48
|
+
the trade object will not be created. If the given buy
|
|
49
|
+
order has its status set to CLOSED or OPEN, the trade object
|
|
50
|
+
will be created. The amount will be set to the filled amount.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
buy_order: Order object representing the buy order
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Union[Trade, None] Representing the created trade object or None
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
if buy_order.status in \
|
|
60
|
+
[
|
|
61
|
+
OrderStatus.CANCELED.value,
|
|
62
|
+
OrderStatus.EXPIRED.value,
|
|
63
|
+
OrderStatus.REJECTED.value
|
|
64
|
+
]:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
data = {
|
|
68
|
+
"buy_order": buy_order,
|
|
69
|
+
"target_symbol": buy_order.target_symbol,
|
|
70
|
+
"trading_symbol": buy_order.trading_symbol,
|
|
71
|
+
"amount": buy_order.get_amount(),
|
|
72
|
+
"available_amount": buy_order.get_filled(),
|
|
73
|
+
"filled_amount": buy_order.get_filled(),
|
|
74
|
+
"remaining": buy_order.get_remaining(),
|
|
75
|
+
"opened_at": buy_order.created_at,
|
|
76
|
+
"cost": buy_order.get_filled() * buy_order.price
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if buy_order.get_filled() > 0:
|
|
80
|
+
data["status"] = TradeStatus.OPEN.value
|
|
81
|
+
data["cost"] = buy_order.filled * buy_order.price
|
|
82
|
+
|
|
83
|
+
return self.create(data)
|
|
84
|
+
|
|
85
|
+
def _create_trade_metadata_with_sell_order(self, sell_order):
|
|
86
|
+
"""
|
|
87
|
+
Function to create trade metadata with only a sell order.
|
|
88
|
+
This function will create all metadata objects for the trades
|
|
89
|
+
that are closed with the sell order amount.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
sell_order: Order object representing the sell order
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
None
|
|
96
|
+
"""
|
|
97
|
+
position = self.position_repository.find({
|
|
98
|
+
"order_id": sell_order.id
|
|
99
|
+
})
|
|
100
|
+
portfolio_id = position.portfolio_id
|
|
101
|
+
matching_trades = self.get_all({
|
|
102
|
+
"status": TradeStatus.OPEN.value,
|
|
103
|
+
"target_symbol": sell_order.target_symbol,
|
|
104
|
+
"portfolio_id": portfolio_id
|
|
105
|
+
})
|
|
106
|
+
updated_at = sell_order.updated_at
|
|
107
|
+
total_available_to_close = 0
|
|
108
|
+
amount_to_close = sell_order.amount
|
|
109
|
+
trade_queue = PriorityQueue()
|
|
110
|
+
sell_order_id = sell_order.id
|
|
111
|
+
sell_price = sell_order.price
|
|
112
|
+
|
|
113
|
+
for trade in matching_trades:
|
|
114
|
+
if trade.available_amount > 0:
|
|
115
|
+
total_available_to_close += trade.available_amount
|
|
116
|
+
trade_queue.put(trade)
|
|
117
|
+
|
|
118
|
+
if total_available_to_close < amount_to_close:
|
|
119
|
+
raise OperationalException(
|
|
120
|
+
"Not enough amount to close in trades."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Create order metadata object
|
|
124
|
+
while amount_to_close > 0 and not trade_queue.empty():
|
|
125
|
+
trade = trade_queue.get()
|
|
126
|
+
trade_id = trade.id
|
|
127
|
+
available_to_close = trade.available_amount
|
|
128
|
+
|
|
129
|
+
if amount_to_close >= available_to_close:
|
|
130
|
+
amount_to_close = amount_to_close - available_to_close
|
|
131
|
+
cost = trade.open_price * available_to_close
|
|
132
|
+
net_gain = (sell_price * available_to_close) - cost
|
|
133
|
+
update_data = {
|
|
134
|
+
"available_amount": 0,
|
|
135
|
+
"orders": trade.orders.append(sell_order),
|
|
136
|
+
"updated_at": updated_at,
|
|
137
|
+
"closed_at": updated_at,
|
|
138
|
+
"net_gain": trade.net_gain + net_gain
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if trade.remaining == 0:
|
|
142
|
+
update_data["status"] = TradeStatus.CLOSED.value
|
|
143
|
+
|
|
144
|
+
self.update(trade_id, update_data)
|
|
145
|
+
self.repository.add_order_to_trade(trade, sell_order)
|
|
146
|
+
|
|
147
|
+
# Create metadata object
|
|
148
|
+
self.order_metadata_repository.\
|
|
149
|
+
create({
|
|
150
|
+
"order_id": sell_order_id,
|
|
151
|
+
"trade_id": trade_id,
|
|
152
|
+
"amount": available_to_close,
|
|
153
|
+
"amount_pending": available_to_close,
|
|
154
|
+
})
|
|
155
|
+
else:
|
|
156
|
+
to_be_closed = amount_to_close
|
|
157
|
+
cost = trade.open_price * to_be_closed
|
|
158
|
+
net_gain = (sell_price * to_be_closed) - cost
|
|
159
|
+
|
|
160
|
+
self.update(
|
|
161
|
+
trade_id, {
|
|
162
|
+
"available_amount":
|
|
163
|
+
trade.available_amount - to_be_closed,
|
|
164
|
+
"orders": trade.orders.append(sell_order),
|
|
165
|
+
"updated_at": updated_at,
|
|
166
|
+
"net_gain": trade.net_gain + net_gain
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
self.repository.add_order_to_trade(trade, sell_order)
|
|
170
|
+
|
|
171
|
+
# Create an order metadata object
|
|
172
|
+
self.order_metadata_repository.\
|
|
173
|
+
create({
|
|
174
|
+
"order_id": sell_order_id,
|
|
175
|
+
"trade_id": trade_id,
|
|
176
|
+
"amount": to_be_closed,
|
|
177
|
+
"amount_pending": to_be_closed,
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
amount_to_close = 0
|
|
181
|
+
|
|
182
|
+
def _create_stop_loss_metadata_with_sell_order(
|
|
183
|
+
self, sell_order_id, stop_losses
|
|
184
|
+
):
|
|
185
|
+
"""
|
|
186
|
+
"""
|
|
187
|
+
sell_order = self.order_repository.get(sell_order_id)
|
|
188
|
+
|
|
189
|
+
for stop_loss_data in stop_losses:
|
|
190
|
+
|
|
191
|
+
self.order_metadata_repository.\
|
|
192
|
+
create({
|
|
193
|
+
"order_id": sell_order.id,
|
|
194
|
+
"stop_loss_id": stop_loss_data["stop_loss_id"],
|
|
195
|
+
"amount": stop_loss_data["amount"],
|
|
196
|
+
"amount_pending": stop_loss_data["amount"]
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
def _create_take_profit_metadata_with_sell_order(
|
|
200
|
+
self, sell_order_id, take_profits
|
|
201
|
+
):
|
|
202
|
+
"""
|
|
203
|
+
"""
|
|
204
|
+
sell_order = self.order_repository.get(sell_order_id)
|
|
205
|
+
|
|
206
|
+
for take_profit_data in take_profits:
|
|
207
|
+
|
|
208
|
+
self.order_metadata_repository.\
|
|
209
|
+
create({
|
|
210
|
+
"order_id": sell_order.id,
|
|
211
|
+
"take_profit_id": take_profit_data["take_profit_id"],
|
|
212
|
+
"amount": take_profit_data["amount"],
|
|
213
|
+
"amount_pending": take_profit_data["amount"]
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
def update(self, trade_id, data) -> Trade:
|
|
217
|
+
"""
|
|
218
|
+
Function to update a trade object. This function will update
|
|
219
|
+
the trade object with the given data.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
trade_id: int representing the id of the trade object
|
|
223
|
+
data: dict representing the data that should be updated
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Trade object
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
# Update the stop losses and take profits if last reported price
|
|
230
|
+
# is updated
|
|
231
|
+
if "last_reported_price" in data:
|
|
232
|
+
trade = self.get(trade_id)
|
|
233
|
+
stop_losses = trade.stop_losses
|
|
234
|
+
to_be_saved_stop_losses = []
|
|
235
|
+
take_profits = trade.take_profits
|
|
236
|
+
to_be_saved_take_profits = []
|
|
237
|
+
|
|
238
|
+
# Check if 'update_at' attribute is in data
|
|
239
|
+
|
|
240
|
+
if 'last_reported_price_date' in data:
|
|
241
|
+
last_reported_price_date = data["last_reported_price_date"]
|
|
242
|
+
else:
|
|
243
|
+
|
|
244
|
+
# Check if config environment has value BACKTEST
|
|
245
|
+
config = self.configuration_service.get_config()
|
|
246
|
+
environment = config[ENVIRONMENT]
|
|
247
|
+
|
|
248
|
+
if Environment.BACKTEST.equals(environment):
|
|
249
|
+
last_reported_price_date = \
|
|
250
|
+
config[INDEX_DATETIME]
|
|
251
|
+
else:
|
|
252
|
+
last_reported_price_date = \
|
|
253
|
+
datetime.now(tz=timezone.utc)
|
|
254
|
+
|
|
255
|
+
for stop_loss in stop_losses:
|
|
256
|
+
|
|
257
|
+
if stop_loss.active:
|
|
258
|
+
stop_loss.update_with_last_reported_price(
|
|
259
|
+
data["last_reported_price"], last_reported_price_date
|
|
260
|
+
)
|
|
261
|
+
to_be_saved_stop_losses.append(stop_loss)
|
|
262
|
+
|
|
263
|
+
for take_profit in take_profits:
|
|
264
|
+
|
|
265
|
+
if take_profit.active:
|
|
266
|
+
take_profit.update_with_last_reported_price(
|
|
267
|
+
data["last_reported_price"], last_reported_price_date
|
|
268
|
+
)
|
|
269
|
+
to_be_saved_take_profits.append(take_profit)
|
|
270
|
+
|
|
271
|
+
self.trade_stop_loss_repository\
|
|
272
|
+
.save_objects(to_be_saved_stop_losses)
|
|
273
|
+
|
|
274
|
+
self.trade_take_profit_repository\
|
|
275
|
+
.save_objects(to_be_saved_take_profits)
|
|
276
|
+
|
|
277
|
+
return super(TradeService, self).update(trade_id, data)
|
|
278
|
+
|
|
279
|
+
def _create_trade_metadata_with_sell_order_and_trades(
|
|
280
|
+
self, sell_order, trades
|
|
281
|
+
):
|
|
282
|
+
"""
|
|
283
|
+
Function to create trade metadata with a sell order and trades.
|
|
284
|
+
|
|
285
|
+
The metadata objects function as a link between the trades and
|
|
286
|
+
the sell order. The metadata objects are used to keep track
|
|
287
|
+
of the trades that are closed with the sell order.
|
|
288
|
+
|
|
289
|
+
A single sell order can close or partially close multiple trades.
|
|
290
|
+
Therefore it is important to keep track of the trades that are
|
|
291
|
+
closed with the sell order. The metadata objects are used to
|
|
292
|
+
keep track of this relationship.
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
"""
|
|
296
|
+
sell_order_id = sell_order.id
|
|
297
|
+
updated_at = sell_order.updated_at
|
|
298
|
+
sell_amount = sell_order.amount
|
|
299
|
+
sell_price = sell_order.price
|
|
300
|
+
|
|
301
|
+
for trade_data in trades:
|
|
302
|
+
trade = self.get(trade_data["trade_id"])
|
|
303
|
+
trade_id = trade.id
|
|
304
|
+
open_price = trade.open_price
|
|
305
|
+
old_net_gain = trade.net_gain
|
|
306
|
+
available_amount = trade.available_amount
|
|
307
|
+
filled_amount = trade.filled_amount
|
|
308
|
+
amount = trade.amount
|
|
309
|
+
|
|
310
|
+
self.order_metadata_repository.\
|
|
311
|
+
create({
|
|
312
|
+
"order_id": sell_order_id,
|
|
313
|
+
"trade_id": trade_data["trade_id"],
|
|
314
|
+
"amount": trade_data["amount"],
|
|
315
|
+
"amount_pending": trade_data["amount"]
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
# Add the sell order to the trade
|
|
319
|
+
self.repository.add_order_to_trade(trade, sell_order)
|
|
320
|
+
|
|
321
|
+
# Update the trade
|
|
322
|
+
net_gain = (sell_price * sell_amount) - open_price * sell_amount
|
|
323
|
+
available_amount = available_amount - trade_data["amount"]
|
|
324
|
+
trade_updated_data = {
|
|
325
|
+
"available_amount": available_amount,
|
|
326
|
+
"updated_at": updated_at,
|
|
327
|
+
"net_gain": old_net_gain + net_gain
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if available_amount == 0 and filled_amount == amount:
|
|
331
|
+
trade_updated_data["status"] = TradeStatus.CLOSED.value
|
|
332
|
+
trade_updated_data["closed_at"] = updated_at
|
|
333
|
+
else:
|
|
334
|
+
trade_updated_data["status"] = TradeStatus.OPEN.value
|
|
335
|
+
|
|
336
|
+
# Update the trade object
|
|
337
|
+
self.update(trade_id, trade_updated_data)
|
|
338
|
+
|
|
339
|
+
def create_order_metadata_with_trade_context(
|
|
340
|
+
self, sell_order, trades=None, stop_losses=None, take_profits=None
|
|
341
|
+
):
|
|
342
|
+
"""
|
|
343
|
+
Function to create order metadata for trade related models.
|
|
344
|
+
|
|
345
|
+
If only the sell order is provided, we assume that the sell order
|
|
346
|
+
is initiated by a client of the order service. In this case we
|
|
347
|
+
create only metadata objects for the trades based on size of the
|
|
348
|
+
sell order.
|
|
349
|
+
|
|
350
|
+
If also stop losses and take profits are provided, we assume that
|
|
351
|
+
the sell order is initiated a stop loss or take profit. In this case
|
|
352
|
+
we create metadata objects for the trades, stop losses,
|
|
353
|
+
and take profits.
|
|
354
|
+
|
|
355
|
+
If the trades param is provided, we assume that the sell order is
|
|
356
|
+
based on either a stop loss or take profit or a closing of a trade.
|
|
357
|
+
In this case we create also the metadata objects for the trades,
|
|
358
|
+
|
|
359
|
+
As part of this function, we will also update the position cost.
|
|
360
|
+
|
|
361
|
+
Scenario 1: Sell order without trades, stop losses, and take profits
|
|
362
|
+
- Use the sell amount to create all trade metadata objects
|
|
363
|
+
- Update the position cost
|
|
364
|
+
|
|
365
|
+
Scenario 2: Sell order with trades
|
|
366
|
+
- We assume that the sell amount is same as the total amount
|
|
367
|
+
of the trades
|
|
368
|
+
- Use the trades to create all trade metadata objects
|
|
369
|
+
- Update trade object remaining amount
|
|
370
|
+
- Update the position cost
|
|
371
|
+
|
|
372
|
+
Scenario 3: Sell order with trades, stop losses, and take profits
|
|
373
|
+
- We assume that the sell amount is same as the total
|
|
374
|
+
amount of the trades
|
|
375
|
+
- Use the trades to create all metadata objects
|
|
376
|
+
- Update trade object remaining amount
|
|
377
|
+
- Use the stop losses to create all metadata objects
|
|
378
|
+
- Use the take profits to create all metadata objects
|
|
379
|
+
- Update the position cost
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
sell_order: Order object representing the sell order that has
|
|
383
|
+
been created
|
|
384
|
+
trades: List of Trade objects representing the trades that
|
|
385
|
+
are associated with the sell order. Default is None.
|
|
386
|
+
stop_losses: List of StopLoss objects representing the stop
|
|
387
|
+
losses that are associated with the sell order. Default
|
|
388
|
+
is None.
|
|
389
|
+
take_profits: List of TakeProfit objects representing the take
|
|
390
|
+
profits that are associated with the sell order. Default
|
|
391
|
+
is None.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
None
|
|
395
|
+
"""
|
|
396
|
+
sell_order_id = sell_order.id
|
|
397
|
+
sell_price = sell_order.price
|
|
398
|
+
sell_amount = sell_order.amount
|
|
399
|
+
|
|
400
|
+
if (trades is None or len(trades) == 0) \
|
|
401
|
+
and (stop_losses is None or len(stop_losses) == 0) \
|
|
402
|
+
and (take_profits is None or len(take_profits) == 0):
|
|
403
|
+
self._create_trade_metadata_with_sell_order(sell_order)
|
|
404
|
+
else:
|
|
405
|
+
|
|
406
|
+
if stop_losses is not None:
|
|
407
|
+
self._create_stop_loss_metadata_with_sell_order(
|
|
408
|
+
sell_order_id, stop_losses
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
if take_profits is not None:
|
|
412
|
+
self._create_take_profit_metadata_with_sell_order(
|
|
413
|
+
sell_order_id, take_profits
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if trades is not None:
|
|
417
|
+
self._create_trade_metadata_with_sell_order_and_trades(
|
|
418
|
+
sell_order, trades
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# Retrieve all trades metadata objects
|
|
422
|
+
order_metadatas = self.order_metadata_repository.get_all({
|
|
423
|
+
"order_id": sell_order_id
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
# Update the position cost
|
|
427
|
+
position = self.position_repository.find({
|
|
428
|
+
"order_id": sell_order_id
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
# Update position
|
|
432
|
+
cost = 0
|
|
433
|
+
net_gain = 0
|
|
434
|
+
for metadata in order_metadatas:
|
|
435
|
+
if metadata.trade_id is not None:
|
|
436
|
+
trade = self.get(metadata.trade_id)
|
|
437
|
+
cost += trade.open_price * metadata.amount
|
|
438
|
+
net_gain += (sell_price * metadata.amount) - cost
|
|
439
|
+
|
|
440
|
+
position.cost -= cost
|
|
441
|
+
self.position_repository.save(position)
|
|
442
|
+
|
|
443
|
+
# Update the net gain, net size of the portfolio
|
|
444
|
+
portfolio = self.portfolio_repository.get(position.portfolio_id)
|
|
445
|
+
portfolio.total_net_gain += net_gain
|
|
446
|
+
portfolio.net_size += net_gain
|
|
447
|
+
portfolio.total_revenue += sell_price * sell_amount
|
|
448
|
+
self.portfolio_repository.save(portfolio)
|
|
449
|
+
|
|
450
|
+
def update_trade_with_removed_sell_order(
|
|
451
|
+
self, sell_order
|
|
452
|
+
) -> Trade:
|
|
453
|
+
"""
|
|
454
|
+
This function updates a trade with a removed sell order that belongs
|
|
455
|
+
to the trade. This function uses the order metadata objects to
|
|
456
|
+
update the trade object. The function will update the trade object
|
|
457
|
+
available amount, cost, and net gain. The function will also
|
|
458
|
+
update the stop loss and take profit objects that are associated
|
|
459
|
+
with the trade object. The function will update the position cost
|
|
460
|
+
and the portfolio net gain.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
sell_order (Order): Order object representing the sell order
|
|
464
|
+
that has been removed
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Trade: Trade object representing the updated trade object
|
|
468
|
+
"""
|
|
469
|
+
position_cost = 0
|
|
470
|
+
total_net_gain = 0
|
|
471
|
+
|
|
472
|
+
# Get all order metadata objects that are associated with
|
|
473
|
+
# the sell order
|
|
474
|
+
order_metadatas = self.order_metadata_repository.get_all({
|
|
475
|
+
"order_id": sell_order.id
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
for metadata in order_metadatas:
|
|
479
|
+
# If trade id is not None, update the trade object
|
|
480
|
+
if metadata.trade_id is not None:
|
|
481
|
+
trade = self.get(metadata.trade_id)
|
|
482
|
+
cost = metadata.amount_pending * trade.open_price
|
|
483
|
+
net_gain = (sell_order.price * metadata.amount_pending) - cost
|
|
484
|
+
trade.available_amount += metadata.amount_pending
|
|
485
|
+
trade.status = TradeStatus.OPEN.value
|
|
486
|
+
trade.updated_at = sell_order.updated_at
|
|
487
|
+
trade.net_gain -= net_gain
|
|
488
|
+
trade.cost += cost
|
|
489
|
+
trade = self.save(trade)
|
|
490
|
+
|
|
491
|
+
# Update the position cost
|
|
492
|
+
position_cost += cost
|
|
493
|
+
total_net_gain += net_gain
|
|
494
|
+
|
|
495
|
+
if metadata.stop_loss_id is not None:
|
|
496
|
+
stop_loss = self.trade_stop_loss_repository\
|
|
497
|
+
.get(metadata.stop_loss_id)
|
|
498
|
+
stop_loss.sold_amount -= metadata.amount_pending
|
|
499
|
+
stop_loss.remove_sell_price(
|
|
500
|
+
sell_order.price, sell_order.created_at
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
if stop_loss.sold_amount < stop_loss.sell_amount:
|
|
504
|
+
stop_loss.active = True
|
|
505
|
+
stop_loss.high_water_mark = None
|
|
506
|
+
|
|
507
|
+
self.trade_stop_loss_repository.save(stop_loss)
|
|
508
|
+
|
|
509
|
+
if metadata.take_profit_id is not None:
|
|
510
|
+
take_profit = self.trade_take_profit_repository\
|
|
511
|
+
.get(metadata.take_profit_id)
|
|
512
|
+
take_profit.sold_amount -= metadata.amount_pending
|
|
513
|
+
take_profit.remove_sell_price(
|
|
514
|
+
sell_order.price, sell_order.created_at
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
if take_profit.sold_amount < take_profit.sell_amount:
|
|
518
|
+
take_profit.active = True
|
|
519
|
+
take_profit.high_water_mark = None
|
|
520
|
+
|
|
521
|
+
self.trade_take_profit_repository.save(take_profit)
|
|
522
|
+
|
|
523
|
+
# Update the position cost
|
|
524
|
+
position = self.position_repository.find({
|
|
525
|
+
"order_id": sell_order.id
|
|
526
|
+
})
|
|
527
|
+
position.cost += position_cost
|
|
528
|
+
self.position_repository.save(position)
|
|
529
|
+
|
|
530
|
+
# Update the net gain of the portfolio
|
|
531
|
+
portfolio = self.portfolio_repository.get(position.portfolio_id)
|
|
532
|
+
portfolio.total_net_gain -= total_net_gain
|
|
533
|
+
portfolio.net_size -= total_net_gain
|
|
534
|
+
self.portfolio_repository.save(portfolio)
|
|
535
|
+
return trade
|
|
536
|
+
|
|
537
|
+
def update_trade_with_buy_order(
|
|
538
|
+
self, filled_difference, buy_order
|
|
539
|
+
) -> Trade:
|
|
540
|
+
"""
|
|
541
|
+
Function to update a trade from a buy order. This function
|
|
542
|
+
checks if a trade exists for the buy order. If the given buy
|
|
543
|
+
order has its status set to CANCLED, EXPIRED, or REJECTED, the
|
|
544
|
+
trade will object will be removed. If the given buy order has
|
|
545
|
+
its status set to CLOSED or OPEN, the amount and
|
|
546
|
+
remaining of the trade object will be updated.
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
filled_difference: float representing the difference between the
|
|
550
|
+
filled amount of the buy order and the filled amount
|
|
551
|
+
of the trade
|
|
552
|
+
buy_order: Order object representing the buy order
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
Trade object
|
|
556
|
+
"""
|
|
557
|
+
trade = self.find({"buy_order": buy_order.id})
|
|
558
|
+
filled = buy_order.get_filled()
|
|
559
|
+
amount = buy_order.get_amount()
|
|
560
|
+
|
|
561
|
+
if filled is None:
|
|
562
|
+
filled = trade.filled_amount + filled_difference
|
|
563
|
+
|
|
564
|
+
remaining = buy_order.get_remaining()
|
|
565
|
+
|
|
566
|
+
if remaining is None:
|
|
567
|
+
remaining = trade.remaining - filled_difference
|
|
568
|
+
|
|
569
|
+
if trade is None:
|
|
570
|
+
raise OperationalException(
|
|
571
|
+
"Trade does not exist for buy order."
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
status = buy_order.get_status()
|
|
575
|
+
|
|
576
|
+
if status in \
|
|
577
|
+
[
|
|
578
|
+
OrderStatus.CANCELED.value,
|
|
579
|
+
OrderStatus.EXPIRED.value,
|
|
580
|
+
OrderStatus.REJECTED.value
|
|
581
|
+
]:
|
|
582
|
+
return self.delete(trade.id)
|
|
583
|
+
|
|
584
|
+
trade = self.find({"order_id": buy_order.id})
|
|
585
|
+
updated_data = {
|
|
586
|
+
"available_amount": trade.available_amount + filled_difference,
|
|
587
|
+
"filled_amount": filled,
|
|
588
|
+
"remaining": remaining,
|
|
589
|
+
"cost": trade.cost + filled_difference * buy_order.price
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if amount != trade.amount:
|
|
593
|
+
updated_data["amount"] = amount
|
|
594
|
+
updated_data["cost"] = amount * buy_order.price
|
|
595
|
+
|
|
596
|
+
if filled_difference > 0:
|
|
597
|
+
updated_data["status"] = TradeStatus.OPEN.value
|
|
598
|
+
|
|
599
|
+
trade = self.update(trade.id, updated_data)
|
|
600
|
+
return trade
|
|
601
|
+
|
|
602
|
+
def update_trade_with_filled_sell_order(
|
|
603
|
+
self, filled_difference, sell_order
|
|
604
|
+
) -> Trade:
|
|
605
|
+
"""
|
|
606
|
+
Function to update a trade with a filled sell order. This
|
|
607
|
+
function will update all the metadata objects that where
|
|
608
|
+
created by the sell order.
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
"""
|
|
612
|
+
# Update all metadata objects
|
|
613
|
+
metadata_objects = self.order_metadata_repository.get_all({
|
|
614
|
+
"order_id": sell_order.id
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
trade_filled_difference = filled_difference
|
|
618
|
+
stop_loss_filled_difference = filled_difference
|
|
619
|
+
take_profit_filled_difference = filled_difference
|
|
620
|
+
total_amount_in_metadata = 0
|
|
621
|
+
trade_metadata_objects = []
|
|
622
|
+
|
|
623
|
+
for metadata_object in metadata_objects:
|
|
624
|
+
# Update the trade metadata object
|
|
625
|
+
if metadata_object.trade_id is not None \
|
|
626
|
+
and trade_filled_difference > 0:
|
|
627
|
+
|
|
628
|
+
trade_metadata_objects.append(metadata_object)
|
|
629
|
+
total_amount_in_metadata += metadata_object.amount
|
|
630
|
+
|
|
631
|
+
if metadata_object.amount_pending >= trade_filled_difference:
|
|
632
|
+
amount = trade_filled_difference
|
|
633
|
+
trade_filled_difference = 0
|
|
634
|
+
else:
|
|
635
|
+
amount = metadata_object.amount_pending
|
|
636
|
+
trade_filled_difference -= amount
|
|
637
|
+
|
|
638
|
+
metadata_object.amount_pending -= amount
|
|
639
|
+
self.order_metadata_repository.save(metadata_object)
|
|
640
|
+
|
|
641
|
+
if metadata_object.stop_loss_id is not None \
|
|
642
|
+
and stop_loss_filled_difference > 0:
|
|
643
|
+
|
|
644
|
+
if (
|
|
645
|
+
metadata_object.amount_pending >=
|
|
646
|
+
stop_loss_filled_difference
|
|
647
|
+
):
|
|
648
|
+
amount = stop_loss_filled_difference
|
|
649
|
+
stop_loss_filled_difference = 0
|
|
650
|
+
else:
|
|
651
|
+
amount = metadata_object.amount_pending
|
|
652
|
+
stop_loss_filled_difference -= amount
|
|
653
|
+
|
|
654
|
+
metadata_object.amount_pending -= amount
|
|
655
|
+
self.order_metadata_repository.save(metadata_object)
|
|
656
|
+
|
|
657
|
+
if metadata_object.take_profit_id is not None \
|
|
658
|
+
and take_profit_filled_difference > 0:
|
|
659
|
+
|
|
660
|
+
if (
|
|
661
|
+
metadata_object.amount_pending >=
|
|
662
|
+
take_profit_filled_difference
|
|
663
|
+
):
|
|
664
|
+
amount = take_profit_filled_difference
|
|
665
|
+
take_profit_filled_difference = 0
|
|
666
|
+
else:
|
|
667
|
+
amount = metadata_object.amount_pending
|
|
668
|
+
take_profit_filled_difference -= amount
|
|
669
|
+
|
|
670
|
+
metadata_object.amount_pending -= amount
|
|
671
|
+
self.order_metadata_repository.save(metadata_object)
|
|
672
|
+
|
|
673
|
+
# Update trade available amount if the total amount in metadata
|
|
674
|
+
# is not equal to the sell order amount
|
|
675
|
+
if total_amount_in_metadata != sell_order.amount:
|
|
676
|
+
difference = sell_order.amount - total_amount_in_metadata
|
|
677
|
+
trades = []
|
|
678
|
+
|
|
679
|
+
for metadata_object in trade_metadata_objects:
|
|
680
|
+
trade = self.get(metadata_object.trade_id)
|
|
681
|
+
trades.append(trade)
|
|
682
|
+
|
|
683
|
+
# Sort trades by created_at with the most recent first
|
|
684
|
+
trades = sorted(
|
|
685
|
+
trades,
|
|
686
|
+
key=lambda x: x.updated_at,
|
|
687
|
+
reverse=True
|
|
688
|
+
)
|
|
689
|
+
queue = PeekableQueue(trades)
|
|
690
|
+
|
|
691
|
+
while difference != 0 and not queue.is_empty():
|
|
692
|
+
trade = queue.dequeue()
|
|
693
|
+
trade.available_amount -= difference
|
|
694
|
+
self.save(trade)
|
|
695
|
+
|
|
696
|
+
def update_trades_with_market_data(self, market_data):
|
|
697
|
+
"""
|
|
698
|
+
Function to update trades with market data. This function will
|
|
699
|
+
update the last reported price and last reported price date of the
|
|
700
|
+
trade.
|
|
701
|
+
|
|
702
|
+
Args:
|
|
703
|
+
market_data: dict representing the market data
|
|
704
|
+
that will be used to update the trades
|
|
705
|
+
|
|
706
|
+
Returns:
|
|
707
|
+
None
|
|
708
|
+
"""
|
|
709
|
+
open_trades = self.get_all({"status": TradeStatus.OPEN.value})
|
|
710
|
+
meta_data = market_data["metadata"]
|
|
711
|
+
|
|
712
|
+
for open_trade in open_trades:
|
|
713
|
+
ohlcv_meta_data = meta_data[DataType.OHLCV]
|
|
714
|
+
|
|
715
|
+
if open_trade.symbol not in ohlcv_meta_data:
|
|
716
|
+
continue
|
|
717
|
+
|
|
718
|
+
timeframes = ohlcv_meta_data[open_trade.symbol].keys()
|
|
719
|
+
sorted_timeframes = sorted(timeframes)
|
|
720
|
+
most_granular_interval = sorted_timeframes[0]
|
|
721
|
+
identifier = (
|
|
722
|
+
ohlcv_meta_data[open_trade.symbol][most_granular_interval]
|
|
723
|
+
)
|
|
724
|
+
data = market_data[identifier]
|
|
725
|
+
|
|
726
|
+
# Get last row of data
|
|
727
|
+
last_row = data.tail(1)
|
|
728
|
+
update_data = {
|
|
729
|
+
"last_reported_price": last_row["Close"][0],
|
|
730
|
+
"last_reported_price_datetime": last_row["Datetime"][0],
|
|
731
|
+
"updated_at": last_row["Datetime"][0]
|
|
732
|
+
}
|
|
733
|
+
self.update(open_trade.id, update_data)
|
|
734
|
+
|
|
735
|
+
def add_stop_loss(
|
|
736
|
+
self,
|
|
737
|
+
trade,
|
|
738
|
+
percentage: float,
|
|
739
|
+
trade_risk_type: TradeRiskType = TradeRiskType.FIXED,
|
|
740
|
+
sell_percentage: float = 100,
|
|
741
|
+
):
|
|
742
|
+
"""
|
|
743
|
+
Function to add a stop loss to a trade.
|
|
744
|
+
|
|
745
|
+
Example of fixed stop loss:
|
|
746
|
+
* You buy BTC at $40,000.
|
|
747
|
+
* You set a SL of 5% → SL level at $38,000 (40,000 - 5%).
|
|
748
|
+
* BTC price increases to $42,000 → SL level remains at $38,000.
|
|
749
|
+
* BTC price drops to $38,000 → SL level reached, trade closes.
|
|
750
|
+
|
|
751
|
+
Example of trailing stop loss:
|
|
752
|
+
* You buy BTC at $40,000.
|
|
753
|
+
* You set a TSL of 5%, setting the sell price at $38,000.
|
|
754
|
+
* BTC price increases to $42,000 → New TSL level at
|
|
755
|
+
$39,900 (42,000 - 5%).
|
|
756
|
+
* BTC price drops to $39,900 → SL level reached, trade closes.
|
|
757
|
+
|
|
758
|
+
Args:
|
|
759
|
+
trade: Trade object representing the trade
|
|
760
|
+
percentage: float representing the percentage of the open price
|
|
761
|
+
that the stop loss should be set at
|
|
762
|
+
trade_risk_type (TradeRiskType): The type of the stop loss, fixed
|
|
763
|
+
or trailing
|
|
764
|
+
sell_percentage: float representing the percentage of the trade
|
|
765
|
+
that should be sold if the stop loss is triggered
|
|
766
|
+
|
|
767
|
+
Returns:
|
|
768
|
+
None
|
|
769
|
+
"""
|
|
770
|
+
trade = self.get(trade.id)
|
|
771
|
+
|
|
772
|
+
# Check if the sell percentage + the existing stop losses is
|
|
773
|
+
# greater than 100
|
|
774
|
+
existing_sell_percentage = 0
|
|
775
|
+
for stop_loss in trade.stop_losses:
|
|
776
|
+
existing_sell_percentage += stop_loss.sell_percentage
|
|
777
|
+
|
|
778
|
+
if existing_sell_percentage + sell_percentage > 100:
|
|
779
|
+
raise OperationalException(
|
|
780
|
+
"Combined sell percentages of stop losses belonging "
|
|
781
|
+
"to trade exceeds 100."
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
creation_data = {
|
|
785
|
+
"trade_id": trade.id,
|
|
786
|
+
"trade_risk_type": TradeRiskType.from_value(trade_risk_type).value,
|
|
787
|
+
"percentage": percentage,
|
|
788
|
+
"open_price": trade.open_price,
|
|
789
|
+
"total_amount_trade": trade.amount,
|
|
790
|
+
"sell_percentage": sell_percentage,
|
|
791
|
+
"active": True
|
|
792
|
+
}
|
|
793
|
+
return self.trade_stop_loss_repository.create(creation_data)
|
|
794
|
+
|
|
795
|
+
def add_take_profit(
|
|
796
|
+
self,
|
|
797
|
+
trade,
|
|
798
|
+
percentage: float,
|
|
799
|
+
trade_risk_type: TradeRiskType = TradeRiskType.FIXED,
|
|
800
|
+
sell_percentage: float = 100,
|
|
801
|
+
) -> None:
|
|
802
|
+
"""
|
|
803
|
+
Function to add a take profit to a trade. This function will add a
|
|
804
|
+
take profit to the specified trade. If the take profit is triggered,
|
|
805
|
+
the trade will be closed.
|
|
806
|
+
|
|
807
|
+
Example of take profit:
|
|
808
|
+
* You buy BTC at $40,000.
|
|
809
|
+
* You set a TP of 5% → TP level at $42,000 (40,000 + 5%).
|
|
810
|
+
* BTC rises to $42,000 → TP level reached, trade
|
|
811
|
+
closes, securing profit.
|
|
812
|
+
|
|
813
|
+
Example of trailing take profit:
|
|
814
|
+
* You buy BTC at $40,000
|
|
815
|
+
* You set a TTP of 5%, setting the sell price at $42,000.
|
|
816
|
+
* BTC rises to $42,000 → TTP level stays at $42,000.
|
|
817
|
+
* BTC rises to $45,000 → New TTP level at $42,750.
|
|
818
|
+
* BTC drops to $42,750 → Trade closes, securing profit.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
trade: Trade object representing the trade
|
|
822
|
+
percentage: float representing the percentage of the open price
|
|
823
|
+
that the stop loss should be set at. This must be a positive
|
|
824
|
+
number, e.g. 5 for 5%, or 10 for 10%.
|
|
825
|
+
trade_risk_type (TradeRiskType): The type of the stop loss, fixed
|
|
826
|
+
or trailing
|
|
827
|
+
sell_percentage: float representing the percentage of the trade
|
|
828
|
+
that should be sold if the stop loss is triggered
|
|
829
|
+
|
|
830
|
+
Returns:
|
|
831
|
+
None
|
|
832
|
+
"""
|
|
833
|
+
trade = self.get(trade.id)
|
|
834
|
+
|
|
835
|
+
# Check if the sell percentage + the existing stop losses is
|
|
836
|
+
# greater than 100
|
|
837
|
+
existing_sell_percentage = 0
|
|
838
|
+
for take_profit in trade.take_profits:
|
|
839
|
+
existing_sell_percentage += take_profit.sell_percentage
|
|
840
|
+
|
|
841
|
+
if existing_sell_percentage + sell_percentage > 100:
|
|
842
|
+
raise OperationalException(
|
|
843
|
+
"Combined sell percentages of stop losses belonging "
|
|
844
|
+
"to trade exceeds 100."
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
creation_data = {
|
|
848
|
+
"trade_id": trade.id,
|
|
849
|
+
"trade_risk_type": TradeRiskType.from_value(trade_risk_type).value,
|
|
850
|
+
"percentage": percentage,
|
|
851
|
+
"open_price": trade.open_price,
|
|
852
|
+
"total_amount_trade": trade.amount,
|
|
853
|
+
"sell_percentage": sell_percentage,
|
|
854
|
+
"active": True
|
|
855
|
+
}
|
|
856
|
+
return self.trade_take_profit_repository.create(creation_data)
|
|
857
|
+
|
|
858
|
+
def get_triggered_stop_loss_orders(self):
|
|
859
|
+
"""
|
|
860
|
+
Function to get all triggered stop loss orders. This function will
|
|
861
|
+
return a list of trade ids that have triggered stop losses.
|
|
862
|
+
|
|
863
|
+
Returns:
|
|
864
|
+
List of trade ids
|
|
865
|
+
"""
|
|
866
|
+
sell_orders_data = []
|
|
867
|
+
query = {"status": TradeStatus.OPEN.value}
|
|
868
|
+
open_trades = self.get_all(query)
|
|
869
|
+
to_be_saved_stop_loss_objects = []
|
|
870
|
+
|
|
871
|
+
# Group trades by target symbol
|
|
872
|
+
stop_losses_by_target_symbol = {}
|
|
873
|
+
|
|
874
|
+
for open_trade in open_trades:
|
|
875
|
+
triggered_stop_losses = []
|
|
876
|
+
|
|
877
|
+
for stop_loss in open_trade.stop_losses:
|
|
878
|
+
|
|
879
|
+
if (
|
|
880
|
+
stop_loss.active
|
|
881
|
+
and stop_loss.has_triggered(open_trade.last_reported_price)
|
|
882
|
+
):
|
|
883
|
+
triggered_stop_losses.append(stop_loss)
|
|
884
|
+
|
|
885
|
+
to_be_saved_stop_loss_objects.append(stop_loss)
|
|
886
|
+
|
|
887
|
+
if len(triggered_stop_losses) > 0:
|
|
888
|
+
stop_losses_by_target_symbol[open_trade] = \
|
|
889
|
+
triggered_stop_losses
|
|
890
|
+
|
|
891
|
+
for trade in stop_losses_by_target_symbol:
|
|
892
|
+
stop_losses = stop_losses_by_target_symbol[trade]
|
|
893
|
+
available_amount = trade.available_amount
|
|
894
|
+
stop_loss_que = PeekableQueue(stop_losses)
|
|
895
|
+
order_amount = 0
|
|
896
|
+
stop_loss_metadata = []
|
|
897
|
+
|
|
898
|
+
# While there is an available amount and there are stop losses
|
|
899
|
+
# to process
|
|
900
|
+
while not stop_loss_que.is_empty() and available_amount > 0:
|
|
901
|
+
stop_loss = stop_loss_que.dequeue()
|
|
902
|
+
stop_loss_sell_amount = stop_loss.get_sell_amount()
|
|
903
|
+
|
|
904
|
+
if stop_loss_sell_amount <= available_amount:
|
|
905
|
+
available_amount = available_amount - stop_loss_sell_amount
|
|
906
|
+
stop_loss.active = False
|
|
907
|
+
stop_loss.sold_amount += stop_loss_sell_amount
|
|
908
|
+
order_amount += stop_loss_sell_amount
|
|
909
|
+
else:
|
|
910
|
+
stop_loss.sold_amount += available_amount
|
|
911
|
+
|
|
912
|
+
# Deactivate stop loss if the filled amount is equal
|
|
913
|
+
# to the amount of the trade, meaning that there is
|
|
914
|
+
# nothing left to sell
|
|
915
|
+
if trade.filled_amount == trade.amount:
|
|
916
|
+
stop_loss.active = False
|
|
917
|
+
else:
|
|
918
|
+
stop_loss.active = True
|
|
919
|
+
|
|
920
|
+
order_amount += available_amount
|
|
921
|
+
stop_loss_sell_amount = available_amount
|
|
922
|
+
available_amount = 0
|
|
923
|
+
|
|
924
|
+
stop_loss_metadata.append({
|
|
925
|
+
"stop_loss_id": stop_loss.id,
|
|
926
|
+
"amount": stop_loss_sell_amount
|
|
927
|
+
})
|
|
928
|
+
stop_loss.add_sell_price(
|
|
929
|
+
trade.last_reported_price,
|
|
930
|
+
trade.last_reported_price_datetime
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
position = self.position_repository.find({
|
|
934
|
+
"order_id": trade.orders[0].id
|
|
935
|
+
})
|
|
936
|
+
portfolio_id = position.portfolio_id
|
|
937
|
+
sell_orders_data.append(
|
|
938
|
+
{
|
|
939
|
+
"target_symbol": trade.target_symbol,
|
|
940
|
+
"trading_symbol": trade.trading_symbol,
|
|
941
|
+
"amount": order_amount,
|
|
942
|
+
"price": trade.last_reported_price,
|
|
943
|
+
"order_type": OrderType.LIMIT.value,
|
|
944
|
+
"order_side": OrderSide.SELL.value,
|
|
945
|
+
"portfolio_id": portfolio_id,
|
|
946
|
+
"stop_losses": stop_loss_metadata,
|
|
947
|
+
"trades": [{
|
|
948
|
+
"trade_id": trade.id,
|
|
949
|
+
"amount": order_amount
|
|
950
|
+
}]
|
|
951
|
+
}
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
self.trade_stop_loss_repository\
|
|
955
|
+
.save_objects(to_be_saved_stop_loss_objects)
|
|
956
|
+
return sell_orders_data
|
|
957
|
+
|
|
958
|
+
def get_triggered_take_profit_orders(self):
|
|
959
|
+
"""
|
|
960
|
+
Function to get all triggered stop loss orders. This function will
|
|
961
|
+
return a list of trade ids that have triggered stop losses.
|
|
962
|
+
|
|
963
|
+
Returns:
|
|
964
|
+
List of trade objects. A trade object is a dictionary
|
|
965
|
+
"""
|
|
966
|
+
sell_orders_data = []
|
|
967
|
+
query = {"status": TradeStatus.OPEN.value}
|
|
968
|
+
open_trades = self.get_all(query)
|
|
969
|
+
to_be_saved_take_profit_objects = []
|
|
970
|
+
|
|
971
|
+
# Group trades by target symbol
|
|
972
|
+
take_profits_by_target_symbol = {}
|
|
973
|
+
|
|
974
|
+
for open_trade in open_trades:
|
|
975
|
+
triggered_take_profits = []
|
|
976
|
+
available_amount = open_trade.available_amount
|
|
977
|
+
|
|
978
|
+
# Skip if there is no available amount
|
|
979
|
+
if available_amount == 0:
|
|
980
|
+
continue
|
|
981
|
+
|
|
982
|
+
for take_profit in open_trade.take_profits:
|
|
983
|
+
|
|
984
|
+
if (
|
|
985
|
+
take_profit.active and
|
|
986
|
+
take_profit.has_triggered(open_trade.last_reported_price)
|
|
987
|
+
):
|
|
988
|
+
triggered_take_profits.append(take_profit)
|
|
989
|
+
|
|
990
|
+
to_be_saved_take_profit_objects.append(take_profit)
|
|
991
|
+
|
|
992
|
+
if len(triggered_take_profits) > 0:
|
|
993
|
+
take_profits_by_target_symbol[open_trade] = \
|
|
994
|
+
triggered_take_profits
|
|
995
|
+
|
|
996
|
+
for trade in take_profits_by_target_symbol:
|
|
997
|
+
take_profits = take_profits_by_target_symbol[trade]
|
|
998
|
+
available_amount = trade.available_amount
|
|
999
|
+
take_profit_que = PeekableQueue(take_profits)
|
|
1000
|
+
order_amount = 0
|
|
1001
|
+
take_profit_metadata = []
|
|
1002
|
+
|
|
1003
|
+
# While there is an available amount and there are take profits
|
|
1004
|
+
# to process
|
|
1005
|
+
while not take_profit_que.is_empty() and available_amount > 0:
|
|
1006
|
+
take_profit = take_profit_que.dequeue()
|
|
1007
|
+
take_profit_sell_amount = take_profit.get_sell_amount()
|
|
1008
|
+
|
|
1009
|
+
if take_profit_sell_amount <= available_amount:
|
|
1010
|
+
available_amount = available_amount - \
|
|
1011
|
+
take_profit_sell_amount
|
|
1012
|
+
take_profit.active = False
|
|
1013
|
+
take_profit.sold_amount += take_profit_sell_amount
|
|
1014
|
+
order_amount += take_profit_sell_amount
|
|
1015
|
+
else:
|
|
1016
|
+
take_profit.sold_amount += available_amount
|
|
1017
|
+
|
|
1018
|
+
# Deactivate take profit if the filled amount is equal
|
|
1019
|
+
# to the amount of the trade, meaning that there is
|
|
1020
|
+
# nothing left to sell
|
|
1021
|
+
if trade.filled_amount == trade.amount:
|
|
1022
|
+
take_profit.active = False
|
|
1023
|
+
else:
|
|
1024
|
+
take_profit.active = True
|
|
1025
|
+
|
|
1026
|
+
order_amount += available_amount
|
|
1027
|
+
take_profit_sell_amount = available_amount
|
|
1028
|
+
available_amount = 0
|
|
1029
|
+
|
|
1030
|
+
take_profit_metadata.append({
|
|
1031
|
+
"take_profit_id": take_profit.id,
|
|
1032
|
+
"amount": take_profit_sell_amount
|
|
1033
|
+
})
|
|
1034
|
+
|
|
1035
|
+
take_profit.add_sell_price(
|
|
1036
|
+
trade.last_reported_price,
|
|
1037
|
+
trade.last_reported_price_datetime
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
position = self.position_repository.find({
|
|
1041
|
+
"order_id": trade.orders[0].id
|
|
1042
|
+
})
|
|
1043
|
+
portfolio_id = position.portfolio_id
|
|
1044
|
+
sell_orders_data.append(
|
|
1045
|
+
{
|
|
1046
|
+
"target_symbol": trade.target_symbol,
|
|
1047
|
+
"trading_symbol": trade.trading_symbol,
|
|
1048
|
+
"amount": order_amount,
|
|
1049
|
+
"price": trade.last_reported_price,
|
|
1050
|
+
"order_type": OrderType.LIMIT.value,
|
|
1051
|
+
"order_side": OrderSide.SELL.value,
|
|
1052
|
+
"portfolio_id": portfolio_id,
|
|
1053
|
+
"take_profits": take_profit_metadata,
|
|
1054
|
+
"trades": [{
|
|
1055
|
+
"trade_id": trade.id,
|
|
1056
|
+
"amount": order_amount
|
|
1057
|
+
}]
|
|
1058
|
+
}
|
|
1059
|
+
)
|
|
1060
|
+
|
|
1061
|
+
self.trade_take_profit_repository\
|
|
1062
|
+
.save_objects(to_be_saved_take_profit_objects)
|
|
1063
|
+
return sell_orders_data
|
|
1064
|
+
|
|
1065
|
+
def _create_order_id(self) -> str:
|
|
1066
|
+
"""
|
|
1067
|
+
Function to create a unique order id. This function will
|
|
1068
|
+
create a unique order id based on the current time and
|
|
1069
|
+
the order id counter.
|
|
1070
|
+
|
|
1071
|
+
Returns:
|
|
1072
|
+
str: Unique order id
|
|
1073
|
+
"""
|
|
1074
|
+
unique = False
|
|
1075
|
+
order_id = None
|
|
1076
|
+
|
|
1077
|
+
while not unique:
|
|
1078
|
+
order_id = f"{random_number(8)}-{random_string(8)}"
|
|
1079
|
+
|
|
1080
|
+
if not self.exists({"order_id": order_id}):
|
|
1081
|
+
unique = True
|
|
1082
|
+
|
|
1083
|
+
return order_id
|