investing-algorithm-framework 3.7.0__py3-none-any.whl → 7.19.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +168 -45
- investing_algorithm_framework/app/__init__.py +32 -1
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1933 -589
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1725 -0
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +4 -2
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +664 -84
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +226 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/create_app.py +40 -6
- investing_algorithm_framework/dependency_container.py +72 -56
- investing_algorithm_framework/domain/__init__.py +71 -47
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +59 -91
- investing_algorithm_framework/domain/constants.py +13 -38
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +3 -2
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +17 -12
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
- investing_algorithm_framework/domain/models/order/order.py +77 -83
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +12 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +37 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +66 -2
- investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
- investing_algorithm_framework/domain/models/trade/trade.py +295 -171
- investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/services/__init__.py +2 -9
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +12 -7
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +29 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +31 -18
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
- investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
- investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/services/__init__.py +113 -16
- investing_algorithm_framework/services/backtesting/__init__.py +0 -7
- investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
- investing_algorithm_framework/services/configuration_service.py +77 -11
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/services/market_credential_service.py +16 -1
- investing_algorithm_framework/services/metrics/__init__.py +114 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +181 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +3 -1
- investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +407 -326
- investing_algorithm_framework/services/portfolios/__init__.py +3 -1
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/repository_service.py +8 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
- investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -1105
- investing_algorithm_framework/domain/graphs.py +0 -382
- investing_algorithm_framework/domain/metrics/__init__.py +0 -6
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -472
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
- investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
- investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
- investing_algorithm_framework/services/backtesting/graphs.py +0 -61
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
- investing_algorithm_framework/services/position_service.py +0 -31
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
- investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
- investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
- /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
from dateutil.parser import parse
|
|
2
|
+
from datetime import timezone
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.domain.models.base_model import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TradeStopLoss(BaseModel):
|
|
9
|
+
"""
|
|
10
|
+
TradeStopLoss represents a stop loss strategy for a trade.
|
|
11
|
+
|
|
12
|
+
if trailing is set to False, the stop loss price is calculated as follows:
|
|
13
|
+
You buy a stock at $100.
|
|
14
|
+
You set a 5% stop loss, meaning you will sell if
|
|
15
|
+
the price drops to $95.
|
|
16
|
+
If the price rises to $120, the stop loss is not triggered.
|
|
17
|
+
But if the price keeps falling to $95, the stop loss triggers,
|
|
18
|
+
and you exit with a $5 loss.
|
|
19
|
+
|
|
20
|
+
if trailing is set to True, the stop loss price is
|
|
21
|
+
calculated as follows:
|
|
22
|
+
You buy a stock at $100.
|
|
23
|
+
You set a 5% trailing stop loss, meaning you will sell if
|
|
24
|
+
the price drops 5% from its peak at $96
|
|
25
|
+
If the price rises to $120, the stop loss adjusts
|
|
26
|
+
to $114 (5% below $120).
|
|
27
|
+
If the price falls to $114, the position is
|
|
28
|
+
closed, securing a $14 profit.
|
|
29
|
+
But if the price keeps rising to $150, the stop
|
|
30
|
+
loss moves up to $142.50.
|
|
31
|
+
If the price drops from $150 to $142.50, the stop
|
|
32
|
+
loss triggers, and you exit with a $42.50 profit.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
- trade (Trade): the trade that the take profit is for
|
|
36
|
+
- trailing (bool): whether the stop loss is trailing or fixed
|
|
37
|
+
- percentage (float): the stop loss percentage
|
|
38
|
+
- sell_percentage (float): the percentage of the trade to sell when the
|
|
39
|
+
take profit is hit. Default is 100% of the trade. If the
|
|
40
|
+
take profit percentage is lower than 100% a check must
|
|
41
|
+
be made that the combined sell percentage of all
|
|
42
|
+
take profits is less or equal than 100%.
|
|
43
|
+
- open_price (float): the price at which the trade was opened
|
|
44
|
+
- high_water_mark_date (str): the date at which the high water mark
|
|
45
|
+
was reached
|
|
46
|
+
- active (bool): whether the stop loss is active
|
|
47
|
+
- sell_amount (float): the amount to sell when the stop loss triggers
|
|
48
|
+
- sold_amount (float): the amount that has been sold
|
|
49
|
+
- high_water_mark (float) the highest price of the trade
|
|
50
|
+
- stop_loss_price (float) the price at which the stop loss triggers
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
trade_id: int,
|
|
56
|
+
percentage: float,
|
|
57
|
+
open_price: float,
|
|
58
|
+
trailing: bool = False,
|
|
59
|
+
total_amount_trade: float = None,
|
|
60
|
+
sell_percentage: float = 100,
|
|
61
|
+
active: bool = True,
|
|
62
|
+
triggered: bool = False,
|
|
63
|
+
triggered_at: datetime = None,
|
|
64
|
+
sell_prices: str = None,
|
|
65
|
+
sell_dates: str = None,
|
|
66
|
+
sell_amount: float = None,
|
|
67
|
+
high_water_mark: float = None,
|
|
68
|
+
high_water_mark_date: str = None,
|
|
69
|
+
created_at: datetime = None,
|
|
70
|
+
updated_at: datetime = None
|
|
71
|
+
):
|
|
72
|
+
self.trade_id = trade_id
|
|
73
|
+
self.trailing = trailing
|
|
74
|
+
self.percentage = percentage
|
|
75
|
+
self.triggered = triggered
|
|
76
|
+
self.triggered_at = triggered_at
|
|
77
|
+
self.sell_percentage = sell_percentage
|
|
78
|
+
self.high_water_mark = high_water_mark
|
|
79
|
+
self.high_water_mark_date = high_water_mark_date
|
|
80
|
+
self.open_price = open_price
|
|
81
|
+
self.created_at = created_at
|
|
82
|
+
self.updated_at = updated_at
|
|
83
|
+
|
|
84
|
+
if high_water_mark is None:
|
|
85
|
+
self.high_water_mark = open_price
|
|
86
|
+
self.stop_loss_price = self.open_price * \
|
|
87
|
+
(1 - (self.percentage / 100))
|
|
88
|
+
self.high_water_mark_date = created_at
|
|
89
|
+
else:
|
|
90
|
+
self.stop_loss_price = high_water_mark * \
|
|
91
|
+
(1 - (self.percentage / 100))
|
|
92
|
+
|
|
93
|
+
if sell_amount is not None:
|
|
94
|
+
self.sell_amount = sell_amount
|
|
95
|
+
else:
|
|
96
|
+
self.sell_amount = total_amount_trade * \
|
|
97
|
+
(self.sell_percentage / 100)
|
|
98
|
+
|
|
99
|
+
self.sold_amount = 0
|
|
100
|
+
self.active = active
|
|
101
|
+
self.sell_prices = sell_prices
|
|
102
|
+
self.sell_dates = sell_dates
|
|
103
|
+
|
|
104
|
+
def update_with_last_reported_price(self, current_price: float, date):
|
|
105
|
+
"""
|
|
106
|
+
Function to update the take profit price based on the last
|
|
107
|
+
reported price.
|
|
108
|
+
The take profit price is only updated when the trade risk
|
|
109
|
+
type is trailing.
|
|
110
|
+
The take profit price is updated based on the current price
|
|
111
|
+
and the percentage of the take profit.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
current_price (float): the last reported price of the trade
|
|
115
|
+
date (datetime): the date of the last reported price
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
None
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
if not self.active or self.sold_amount == self.sell_amount:
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
if not self.trailing:
|
|
125
|
+
# Check if the current price is less than the high water mark
|
|
126
|
+
if current_price > self.high_water_mark:
|
|
127
|
+
self.high_water_mark = current_price
|
|
128
|
+
return
|
|
129
|
+
else:
|
|
130
|
+
# Check if the current price is less than the stop loss price
|
|
131
|
+
if current_price <= self.stop_loss_price:
|
|
132
|
+
return
|
|
133
|
+
elif current_price > self.high_water_mark:
|
|
134
|
+
self.high_water_mark = current_price
|
|
135
|
+
self.high_water_mark_date = date
|
|
136
|
+
self.stop_loss_price = self.high_water_mark * \
|
|
137
|
+
(1 - (self.percentage / 100))
|
|
138
|
+
|
|
139
|
+
def has_triggered(self, current_price: float) -> bool:
|
|
140
|
+
"""
|
|
141
|
+
Function to check if the stop loss has triggered.
|
|
142
|
+
Function always returns False if the stop loss is not active or
|
|
143
|
+
the sold amount is equal to the sell amount.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
current_price: float - the current price of the trade
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
bool - True if the stop loss has triggered, False otherwise
|
|
150
|
+
"""
|
|
151
|
+
if not self.active or self.sold_amount == self.sell_amount:
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
if not self.trailing:
|
|
155
|
+
# Check if the current price is less than the high watermark
|
|
156
|
+
return current_price <= self.stop_loss_price
|
|
157
|
+
else:
|
|
158
|
+
# Check if the current price is less than the stop loss price
|
|
159
|
+
if current_price <= self.stop_loss_price:
|
|
160
|
+
return True
|
|
161
|
+
elif current_price > self.high_water_mark:
|
|
162
|
+
self.high_water_mark = current_price
|
|
163
|
+
self.stop_loss_price = self.high_water_mark * \
|
|
164
|
+
(1 - (self.percentage / 100))
|
|
165
|
+
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
def get_sell_amount(self) -> float:
|
|
169
|
+
"""
|
|
170
|
+
Function to calculate the amount to sell based on the
|
|
171
|
+
sell percentage and the remaining amount of the trade.
|
|
172
|
+
Keep in mind the moment the take profit triggers, the remaining
|
|
173
|
+
amount of the trade is used to calculate the sell amount.
|
|
174
|
+
If the remaining amount is smaller than the trade amount, the
|
|
175
|
+
trade stop loss stays active. The client that uses the
|
|
176
|
+
trade stop loss is responsible for setting the trade stop
|
|
177
|
+
loss to inactive.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
if not self.active:
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
return self.sell_amount - self.sold_amount
|
|
184
|
+
|
|
185
|
+
def add_sell_price(self, price: float, date: str):
|
|
186
|
+
"""
|
|
187
|
+
Function to add a sell price to the list of sell prices.
|
|
188
|
+
The sell price is added to the list of sell prices and the
|
|
189
|
+
date is added to the list of sell dates.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
price: float - the price at which the trade was sold
|
|
193
|
+
date: str - the date at which the trade was sold
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
None
|
|
197
|
+
"""
|
|
198
|
+
if self.sell_prices is None:
|
|
199
|
+
self.sell_prices = str(price)
|
|
200
|
+
self.sell_dates = str(date)
|
|
201
|
+
else:
|
|
202
|
+
self.sell_prices += f", {price}"
|
|
203
|
+
self.sell_dates += f", {date}"
|
|
204
|
+
|
|
205
|
+
def remove_sell_price(self, price: float, date: str):
|
|
206
|
+
"""
|
|
207
|
+
Function to remove a sell price from the list of sell prices.
|
|
208
|
+
The sell price is removed from the list of sell prices and the
|
|
209
|
+
date is removed from the list of sell dates.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
price: float - the price at which the trade was sold
|
|
213
|
+
date: str - the date at which the trade was sold
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
None
|
|
217
|
+
"""
|
|
218
|
+
if self.sell_prices is not None:
|
|
219
|
+
|
|
220
|
+
# Split the sell prices into a list and convert to float
|
|
221
|
+
sell_prices_list = self.sell_prices.split(", ")
|
|
222
|
+
sell_prices_list = [float(p) for p in sell_prices_list]
|
|
223
|
+
|
|
224
|
+
if price in sell_prices_list:
|
|
225
|
+
sell_prices_list.remove(price)
|
|
226
|
+
self.sell_prices = ", ".join(sell_prices_list)
|
|
227
|
+
|
|
228
|
+
if self.sell_prices == "":
|
|
229
|
+
self.sell_prices = None
|
|
230
|
+
|
|
231
|
+
# Split the sell dates into a list
|
|
232
|
+
sell_dates_list = self.sell_dates.split(", ")
|
|
233
|
+
if date in sell_dates_list:
|
|
234
|
+
sell_dates_list.remove(date)
|
|
235
|
+
self.sell_dates = ", ".join(sell_dates_list)
|
|
236
|
+
else:
|
|
237
|
+
self.sell_prices = None
|
|
238
|
+
self.sell_dates = None
|
|
239
|
+
|
|
240
|
+
def to_dict(self, datetime_format=None):
|
|
241
|
+
def ensure_iso(value):
|
|
242
|
+
|
|
243
|
+
if value is None:
|
|
244
|
+
return value
|
|
245
|
+
|
|
246
|
+
if hasattr(value, "isoformat"):
|
|
247
|
+
if value.tzinfo is None:
|
|
248
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
249
|
+
return value.isoformat()
|
|
250
|
+
return value
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
"trade_id": self.trade_id,
|
|
254
|
+
"trailing": self.trailing,
|
|
255
|
+
"percentage": self.percentage,
|
|
256
|
+
"open_price": self.open_price,
|
|
257
|
+
"sell_percentage": self.sell_percentage,
|
|
258
|
+
"high_water_mark": self.high_water_mark,
|
|
259
|
+
"high_water_mark_date": self.high_water_mark_date,
|
|
260
|
+
"triggered": self.triggered,
|
|
261
|
+
"triggered_at": ensure_iso(getattr(self, "triggered_at", None)),
|
|
262
|
+
"stop_loss_price": self.stop_loss_price,
|
|
263
|
+
"sell_amount": self.sell_amount,
|
|
264
|
+
"sold_amount": self.sold_amount,
|
|
265
|
+
"active": self.active,
|
|
266
|
+
"sell_prices": self.sell_prices,
|
|
267
|
+
"created_at": ensure_iso(self.created_at),
|
|
268
|
+
"updated_at": ensure_iso(self.updated_at)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@staticmethod
|
|
272
|
+
def from_dict(data: dict):
|
|
273
|
+
created_at = parse(data["created_at"]) \
|
|
274
|
+
if data.get("created_at") is not None else None
|
|
275
|
+
updated_at = parse(data["updated_at"]) \
|
|
276
|
+
if data.get("updated_at") is not None else None
|
|
277
|
+
triggered_at = parse(data["triggered_at"]) \
|
|
278
|
+
if data.get("triggered_at") is not None else None
|
|
279
|
+
high_water_mark_date = parse(data.get("high_water_mark_date")) \
|
|
280
|
+
if data.get("high_water_mark_date") is not None else None
|
|
281
|
+
|
|
282
|
+
# Make sure all the dates are timezone utc aware
|
|
283
|
+
if created_at and created_at.tzinfo is None:
|
|
284
|
+
created_at = created_at.replace(tzinfo=timezone.utc)
|
|
285
|
+
if updated_at and updated_at.tzinfo is None:
|
|
286
|
+
updated_at = updated_at.replace(tzinfo=timezone.utc)
|
|
287
|
+
if triggered_at and triggered_at.tzinfo is None:
|
|
288
|
+
triggered_at = triggered_at.replace(tzinfo=timezone.utc)
|
|
289
|
+
if high_water_mark_date and high_water_mark_date.tzinfo is None:
|
|
290
|
+
high_water_mark_date = high_water_mark_date.replace(
|
|
291
|
+
tzinfo=timezone.utc
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return TradeStopLoss(
|
|
295
|
+
trade_id=data.get("trade_id"),
|
|
296
|
+
trailing=data.get("trailing"),
|
|
297
|
+
percentage=data.get("percentage"),
|
|
298
|
+
open_price=data.get("open_price"),
|
|
299
|
+
total_amount_trade=data.get("sell_amount", 0) /
|
|
300
|
+
(data.get("sell_percentage", 100) / 100),
|
|
301
|
+
sell_percentage=data.get("sell_percentage", 100),
|
|
302
|
+
active=data.get("active", True),
|
|
303
|
+
sell_prices=data.get("sell_prices"),
|
|
304
|
+
sell_dates=data.get("sell_dates"),
|
|
305
|
+
sell_amount=data.get("sell_amount"),
|
|
306
|
+
high_water_mark=data.get("high_water_mark"),
|
|
307
|
+
high_water_mark_date=high_water_mark_date,
|
|
308
|
+
triggered=data.get("triggered", False),
|
|
309
|
+
triggered_at=triggered_at,
|
|
310
|
+
created_at=created_at,
|
|
311
|
+
updated_at=updated_at
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def __repr__(self):
|
|
315
|
+
return self.repr(
|
|
316
|
+
trade_id=self.trade_id,
|
|
317
|
+
trailing=self.trailing,
|
|
318
|
+
percentage=self.percentage,
|
|
319
|
+
sell_percentage=self.sell_percentage,
|
|
320
|
+
high_water_mark=self.high_water_mark,
|
|
321
|
+
high_water_mark_date=self.high_water_mark_date,
|
|
322
|
+
open_price=self.open_price,
|
|
323
|
+
stop_loss_price=self.stop_loss_price,
|
|
324
|
+
sell_amount=self.sell_amount,
|
|
325
|
+
sold_amount=self.sold_amount,
|
|
326
|
+
sell_prices=self.sell_prices,
|
|
327
|
+
active=self.active,
|
|
328
|
+
triggered=self.triggered,
|
|
329
|
+
triggered_at=self.triggered_at,
|
|
330
|
+
created_at=self.created_at,
|
|
331
|
+
updated_at=self.updated_at
|
|
332
|
+
)
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
from datetime import timezone, datetime
|
|
2
|
+
from dateutil.parser import parse
|
|
3
|
+
|
|
4
|
+
from investing_algorithm_framework.domain.models.base_model import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TradeTakeProfit(BaseModel):
|
|
8
|
+
"""
|
|
9
|
+
TradeTakeProfit represents a take profit strategy for a trade.
|
|
10
|
+
|
|
11
|
+
if trailing is set to False, the take profit price is
|
|
12
|
+
calculated as follows:
|
|
13
|
+
You buy a stock at $100.
|
|
14
|
+
You set a 5% take profit, meaning you will sell if the price
|
|
15
|
+
rises to $105.
|
|
16
|
+
If the price rises to $120, the take profit triggers,
|
|
17
|
+
and you exit with a $20 profit.
|
|
18
|
+
But if the price keeps falling below $105, the take profit is not
|
|
19
|
+
triggered.
|
|
20
|
+
|
|
21
|
+
if trailing is set to True, the take profit price is
|
|
22
|
+
calculated as follows:
|
|
23
|
+
You buy a stock at $100.
|
|
24
|
+
You set a 5% trailing take profit, the moment the price rises
|
|
25
|
+
5% the initial take profit mark will be set. This means you
|
|
26
|
+
will set the take_profit_price initially at none and
|
|
27
|
+
only if the price hits $105, you will set the
|
|
28
|
+
take_profit_price to $105.
|
|
29
|
+
if the price drops below $105, the take profit is triggered.
|
|
30
|
+
If the price rises to $120, the take profit adjusts to
|
|
31
|
+
$114 (5% below $120).
|
|
32
|
+
If the price falls to $114, the position is closed,
|
|
33
|
+
securing a $14 profit.
|
|
34
|
+
But if the price keeps rising to $150, the take profit
|
|
35
|
+
moves up to $142.50.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
- trade (Trade): the trade that the take profit is for
|
|
39
|
+
- trailing (bool): whether the take profit is trailing or fixed
|
|
40
|
+
- percentage (float): the stop loss percentage
|
|
41
|
+
- sell_percentage (float): the percentage of the trade to sell when the
|
|
42
|
+
take profit is hit. Default is 100% of the trade. If the
|
|
43
|
+
take profit percentage is lower than 100% a check must
|
|
44
|
+
be made that the combined sell percentage of all
|
|
45
|
+
take profits is less or equal than 100%.
|
|
46
|
+
- open_price (float): the price at which the trade was opened
|
|
47
|
+
- take_profit_price (float): the price at which the take profit
|
|
48
|
+
triggers
|
|
49
|
+
- high_water_mark_date (str): the date at which the high water mark
|
|
50
|
+
was reached
|
|
51
|
+
- active (bool): whether the take profit is active
|
|
52
|
+
- triggered (bool): whether the take profit has been triggered
|
|
53
|
+
- sell_amount (float): the amount to sell when the stop loss triggers
|
|
54
|
+
- sold_amount (float): the amount that has been sold
|
|
55
|
+
- high_water_mark (float) the highest price of the trade
|
|
56
|
+
- stop_loss_price (float) the price at which the stop loss triggers
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
trade_id: int,
|
|
62
|
+
percentage: float,
|
|
63
|
+
open_price: float,
|
|
64
|
+
trailing: bool = False,
|
|
65
|
+
total_amount_trade: float = None,
|
|
66
|
+
sell_percentage: float = 100,
|
|
67
|
+
active: bool = True,
|
|
68
|
+
triggered: bool = False,
|
|
69
|
+
triggered_at: datetime = None,
|
|
70
|
+
sell_prices: str = None,
|
|
71
|
+
sell_dates: str = None,
|
|
72
|
+
sell_amount: float = None,
|
|
73
|
+
high_water_mark: float = None,
|
|
74
|
+
high_water_mark_date: str = None,
|
|
75
|
+
created_at: datetime = None,
|
|
76
|
+
updated_at: datetime = None
|
|
77
|
+
):
|
|
78
|
+
self.trade_id = trade_id
|
|
79
|
+
self.trailing = trailing
|
|
80
|
+
self.percentage = percentage
|
|
81
|
+
self.sell_percentage = sell_percentage
|
|
82
|
+
self.triggered = triggered
|
|
83
|
+
self.triggered_at = triggered_at
|
|
84
|
+
self.high_water_mark = high_water_mark
|
|
85
|
+
self.high_water_mark_date = high_water_mark_date
|
|
86
|
+
self.open_price = open_price
|
|
87
|
+
self.created_at = created_at
|
|
88
|
+
self.updated_at = updated_at
|
|
89
|
+
|
|
90
|
+
if high_water_mark is None and not self.trailing:
|
|
91
|
+
self.take_profit_price = self.open_price * \
|
|
92
|
+
(1 + (self.percentage / 100))
|
|
93
|
+
else:
|
|
94
|
+
self.take_profit_price = None
|
|
95
|
+
|
|
96
|
+
if sell_amount is not None:
|
|
97
|
+
self.sell_amount = sell_amount
|
|
98
|
+
else:
|
|
99
|
+
self.sell_amount = total_amount_trade * \
|
|
100
|
+
(self.sell_percentage / 100)
|
|
101
|
+
self.sold_amount = 0
|
|
102
|
+
self.active = active
|
|
103
|
+
self.sell_prices = sell_prices
|
|
104
|
+
self.sell_dates = sell_dates
|
|
105
|
+
|
|
106
|
+
def update_with_last_reported_price(self, current_price: float, date):
|
|
107
|
+
"""
|
|
108
|
+
Function to update the take profit price based on
|
|
109
|
+
the last reported price.
|
|
110
|
+
For fixed take profits: track the high water mark when price
|
|
111
|
+
exceeds the take profit price.
|
|
112
|
+
For trailing take profits: update the take profit price based on
|
|
113
|
+
the current price and the percentage of the take profit.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
current_price: float - the last reported price of the trade
|
|
117
|
+
date: the date of the price update
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
if not self.trailing:
|
|
121
|
+
# Fixed take profit: track high watermark
|
|
122
|
+
if current_price >= self.take_profit_price:
|
|
123
|
+
if (self.high_water_mark is None
|
|
124
|
+
or current_price > self.high_water_mark):
|
|
125
|
+
self.high_water_mark = current_price
|
|
126
|
+
self.high_water_mark_date = date
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
# Trailing take profit logic
|
|
130
|
+
if self.high_water_mark is None:
|
|
131
|
+
# High water mark not set yet
|
|
132
|
+
# Calculate the initial take profit threshold
|
|
133
|
+
initial_threshold = self.open_price * (1 + (self.percentage / 100))
|
|
134
|
+
|
|
135
|
+
# Wait for price to reach the initial take profit threshold
|
|
136
|
+
if current_price >= initial_threshold:
|
|
137
|
+
# Initial threshold reached, set high watermark
|
|
138
|
+
self.high_water_mark = current_price
|
|
139
|
+
self.high_water_mark_date = date
|
|
140
|
+
# Calculate new take profit price based on high watermark
|
|
141
|
+
self.take_profit_price = self.high_water_mark * \
|
|
142
|
+
(1 - (self.percentage / 100))
|
|
143
|
+
else:
|
|
144
|
+
# High watermark is set, check for updates
|
|
145
|
+
# Check if price has risen above high watermark (adjust upward)
|
|
146
|
+
if current_price > self.high_water_mark:
|
|
147
|
+
self.high_water_mark = current_price
|
|
148
|
+
self.high_water_mark_date = date
|
|
149
|
+
# Recalculate take profit price based on new high water mark
|
|
150
|
+
new_take_profit_price = self.high_water_mark * \
|
|
151
|
+
(1 - (self.percentage / 100))
|
|
152
|
+
# Update take profit price if it's higher than current
|
|
153
|
+
if new_take_profit_price > self.take_profit_price:
|
|
154
|
+
self.take_profit_price = new_take_profit_price
|
|
155
|
+
|
|
156
|
+
def has_triggered(self, current_price: float = None) -> bool:
|
|
157
|
+
|
|
158
|
+
if not self.trailing:
|
|
159
|
+
# Fixed take profit: trigger when price reaches take_profit_price
|
|
160
|
+
return current_price >= self.take_profit_price
|
|
161
|
+
else:
|
|
162
|
+
# Trailing take profit logic
|
|
163
|
+
if self.high_water_mark is None:
|
|
164
|
+
# High water mark not set yet
|
|
165
|
+
# Calculate the initial take profit threshold
|
|
166
|
+
# (open_price * (1 + percentage))
|
|
167
|
+
initial_threshold = (self.open_price
|
|
168
|
+
* (1 + (self.percentage / 100)))
|
|
169
|
+
|
|
170
|
+
# Wait for price to reach the initial take profit threshold
|
|
171
|
+
if current_price >= initial_threshold:
|
|
172
|
+
# Initial threshold reached, set high water mark
|
|
173
|
+
self.high_water_mark = current_price
|
|
174
|
+
# Calculate new take profit price based on high water mark
|
|
175
|
+
# This is the pullback level
|
|
176
|
+
# (high_water_mark * (1 - percentage))
|
|
177
|
+
self.take_profit_price = self.high_water_mark * \
|
|
178
|
+
(1 - (self.percentage / 100))
|
|
179
|
+
# Don't trigger yet, wait for pullback
|
|
180
|
+
return False
|
|
181
|
+
else:
|
|
182
|
+
# High watermark is set, check for triggers and updates
|
|
183
|
+
|
|
184
|
+
# Check if price has pulled back below take profit
|
|
185
|
+
# price (trigger condition)
|
|
186
|
+
if current_price < self.take_profit_price:
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
# Check if price has risen above high
|
|
190
|
+
# water mark (adjust upward)
|
|
191
|
+
if current_price > self.high_water_mark:
|
|
192
|
+
self.high_water_mark = current_price
|
|
193
|
+
# Recalculate take profit price based on
|
|
194
|
+
# new high water mark
|
|
195
|
+
new_take_profit_price = self.high_water_mark * \
|
|
196
|
+
(1 - (self.percentage / 100))
|
|
197
|
+
# Update take profit price if it's higher than current
|
|
198
|
+
if new_take_profit_price > self.take_profit_price:
|
|
199
|
+
self.take_profit_price = new_take_profit_price
|
|
200
|
+
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def get_sell_amount(self) -> float:
|
|
204
|
+
"""
|
|
205
|
+
Function to calculate the amount to sell based on the
|
|
206
|
+
sell percentage and the remaining amount of the trade.
|
|
207
|
+
Keep in mind the moment the take profit triggers, the remaining
|
|
208
|
+
amount of the trade is used to calculate the sell amount.
|
|
209
|
+
If the remaining amount is smaller than the trade amount, the
|
|
210
|
+
trade stop loss stays active. The client that uses the
|
|
211
|
+
trade stop loss is responsible for setting the trade stop
|
|
212
|
+
loss to inactive.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
float - the amount to sell
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
if not self.active:
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
return self.sell_amount - self.sold_amount
|
|
222
|
+
|
|
223
|
+
def add_sell_price(self, price: float, date: str):
|
|
224
|
+
"""
|
|
225
|
+
Function to add a sell price to the list of sell prices.
|
|
226
|
+
The sell price is added to the list of sell prices and the
|
|
227
|
+
date is added to the list of sell dates.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
price (float): the price at which the trade was sold
|
|
231
|
+
date (datetime): the date at which the trade was sold
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
None
|
|
235
|
+
"""
|
|
236
|
+
if self.sell_prices is None:
|
|
237
|
+
self.sell_prices = str(price)
|
|
238
|
+
self.sell_dates = str(date)
|
|
239
|
+
else:
|
|
240
|
+
self.sell_prices += f", {price}"
|
|
241
|
+
self.sell_dates += f", {date}"
|
|
242
|
+
|
|
243
|
+
def remove_sell_price(self, price: float, date: str):
|
|
244
|
+
"""
|
|
245
|
+
Function to remove a sell price from the list of sell prices.
|
|
246
|
+
The sell price is removed from the list of sell prices and the
|
|
247
|
+
date is removed from the list of sell dates.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
price: float - the price at which the trade was sold
|
|
251
|
+
date: str - the date at which the trade was sold
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
None
|
|
255
|
+
"""
|
|
256
|
+
if self.sell_prices is not None:
|
|
257
|
+
|
|
258
|
+
# Split the sell prices into a list and convert to float
|
|
259
|
+
sell_prices_list = self.sell_prices.split(", ")
|
|
260
|
+
sell_prices_list = [float(p) for p in sell_prices_list]
|
|
261
|
+
|
|
262
|
+
if price in sell_prices_list:
|
|
263
|
+
sell_prices_list.remove(price)
|
|
264
|
+
self.sell_prices = ", ".join(sell_prices_list)
|
|
265
|
+
|
|
266
|
+
if self.sell_prices == "":
|
|
267
|
+
self.sell_prices = None
|
|
268
|
+
|
|
269
|
+
# Split the sell dates into a list
|
|
270
|
+
sell_dates_list = self.sell_dates.split(", ")
|
|
271
|
+
if date in sell_dates_list:
|
|
272
|
+
sell_dates_list.remove(date)
|
|
273
|
+
self.sell_dates = ", ".join(sell_dates_list)
|
|
274
|
+
else:
|
|
275
|
+
self.sell_prices = None
|
|
276
|
+
self.sell_dates = None
|
|
277
|
+
|
|
278
|
+
def to_dict(self, datetime_format=None):
|
|
279
|
+
def ensure_iso(value):
|
|
280
|
+
if hasattr(value, "isoformat"):
|
|
281
|
+
if value.tzinfo is None:
|
|
282
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
283
|
+
return value.isoformat()
|
|
284
|
+
return value
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
"trade_id": self.trade_id,
|
|
288
|
+
"trailing": self.trailing,
|
|
289
|
+
"percentage": self.percentage,
|
|
290
|
+
"open_price": self.open_price,
|
|
291
|
+
"sell_percentage": self.sell_percentage,
|
|
292
|
+
"high_water_mark": self.high_water_mark,
|
|
293
|
+
"take_profit_price": self.take_profit_price,
|
|
294
|
+
"sell_amount": self.sell_amount,
|
|
295
|
+
"sold_amount": self.sold_amount,
|
|
296
|
+
"active": self.active,
|
|
297
|
+
"triggered": self.triggered,
|
|
298
|
+
"triggered_at": ensure_iso(self.triggered_at),
|
|
299
|
+
"high_water_mark_date": self.high_water_mark_date,
|
|
300
|
+
"sell_prices": self.sell_prices,
|
|
301
|
+
"created_at": ensure_iso(self.created_at),
|
|
302
|
+
"updated_at": ensure_iso(self.updated_at)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@staticmethod
|
|
306
|
+
def from_dict(data: dict):
|
|
307
|
+
created_at = parse(data["created_at"]) \
|
|
308
|
+
if data.get("created_at") is not None else None
|
|
309
|
+
updated_at = parse(data["updated_at"]) \
|
|
310
|
+
if data.get("updated_at") is not None else None
|
|
311
|
+
triggered_at = parse(data["triggered_at"]) \
|
|
312
|
+
if data.get("triggered_at") is not None else None
|
|
313
|
+
high_water_mark_date = parse(data.get("high_water_mark_date")) \
|
|
314
|
+
if data.get("high_water_mark_date") is not None else None
|
|
315
|
+
|
|
316
|
+
# Make sure all the dates are timezone utc aware
|
|
317
|
+
if created_at and created_at.tzinfo is None:
|
|
318
|
+
created_at = created_at.replace(tzinfo=timezone.utc)
|
|
319
|
+
if updated_at and updated_at.tzinfo is None:
|
|
320
|
+
updated_at = updated_at.replace(tzinfo=timezone.utc)
|
|
321
|
+
if triggered_at and triggered_at.tzinfo is None:
|
|
322
|
+
triggered_at = triggered_at.replace(tzinfo=timezone.utc)
|
|
323
|
+
if high_water_mark_date and high_water_mark_date.tzinfo is None:
|
|
324
|
+
high_water_mark_date = high_water_mark_date.replace(
|
|
325
|
+
tzinfo=timezone.utc
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return TradeTakeProfit(
|
|
329
|
+
trade_id=data.get("trade_id"),
|
|
330
|
+
trailing=data.get("trailing"),
|
|
331
|
+
percentage=data.get("percentage"),
|
|
332
|
+
open_price=data.get("open_price"),
|
|
333
|
+
total_amount_trade=data.get("total_amount_trade"),
|
|
334
|
+
sell_percentage=data.get("sell_percentage", 100),
|
|
335
|
+
active=data.get("active", True),
|
|
336
|
+
triggered=data.get("triggered", False),
|
|
337
|
+
triggered_at=triggered_at,
|
|
338
|
+
sell_prices=data.get("sell_prices"),
|
|
339
|
+
sell_dates=data.get("sell_dates"),
|
|
340
|
+
sell_amount=data.get("sell_amount"),
|
|
341
|
+
high_water_mark=data.get("high_water_mark"),
|
|
342
|
+
high_water_mark_date=high_water_mark_date,
|
|
343
|
+
created_at=created_at,
|
|
344
|
+
updated_at=updated_at
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def __repr__(self):
|
|
348
|
+
return self.repr(
|
|
349
|
+
trade_id=self.trade_id,
|
|
350
|
+
trailing=self.trailing,
|
|
351
|
+
percentage=self.percentage,
|
|
352
|
+
open_price=self.open_price,
|
|
353
|
+
sell_percentage=self.sell_percentage,
|
|
354
|
+
high_water_mark=self.high_water_mark,
|
|
355
|
+
high_water_mark_date=self.high_water_mark_date,
|
|
356
|
+
triggered=self.triggered,
|
|
357
|
+
triggered_at=self.triggered_at,
|
|
358
|
+
take_profit_price=self.take_profit_price,
|
|
359
|
+
sell_amount=self.sell_amount,
|
|
360
|
+
sold_amount=self.sold_amount,
|
|
361
|
+
active=self.active,
|
|
362
|
+
sell_prices=self.sell_prices,
|
|
363
|
+
created_at=self.created_at,
|
|
364
|
+
updated_at=self.updated_at
|
|
365
|
+
)
|