investing-algorithm-framework 3.7.0__py3-none-any.whl → 7.19.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +168 -45
- investing_algorithm_framework/app/__init__.py +32 -1
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1933 -589
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1725 -0
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +4 -2
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +664 -84
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +226 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/create_app.py +40 -6
- investing_algorithm_framework/dependency_container.py +72 -56
- investing_algorithm_framework/domain/__init__.py +71 -47
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +59 -91
- investing_algorithm_framework/domain/constants.py +13 -38
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +3 -2
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +17 -12
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
- investing_algorithm_framework/domain/models/order/order.py +77 -83
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +12 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +37 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +66 -2
- investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
- investing_algorithm_framework/domain/models/trade/trade.py +295 -171
- investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/services/__init__.py +2 -9
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +12 -7
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +29 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +31 -18
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
- investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
- investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/services/__init__.py +113 -16
- investing_algorithm_framework/services/backtesting/__init__.py +0 -7
- investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
- investing_algorithm_framework/services/configuration_service.py +77 -11
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/services/market_credential_service.py +16 -1
- investing_algorithm_framework/services/metrics/__init__.py +114 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +181 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +3 -1
- investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +407 -326
- investing_algorithm_framework/services/portfolios/__init__.py +3 -1
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/repository_service.py +8 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
- investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -1105
- investing_algorithm_framework/domain/graphs.py +0 -382
- investing_algorithm_framework/domain/metrics/__init__.py +0 -6
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -472
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
- investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
- investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
- investing_algorithm_framework/services/backtesting/graphs.py +0 -61
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
- investing_algorithm_framework/services/position_service.py +0 -31
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
- investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
- investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
- /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
1
|
+
from dateutil.parser import parse
|
|
2
|
+
from datetime import timezone
|
|
3
3
|
|
|
4
|
-
import polars as pl
|
|
5
|
-
from polars import DataFrame
|
|
6
|
-
|
|
7
|
-
from investing_algorithm_framework.domain.constants import DATETIME_FORMAT
|
|
8
|
-
from investing_algorithm_framework.domain.exceptions import \
|
|
9
|
-
OperationalException
|
|
10
4
|
from investing_algorithm_framework.domain.models.base_model import BaseModel
|
|
5
|
+
from investing_algorithm_framework.domain.models.order import OrderSide, Order
|
|
6
|
+
from investing_algorithm_framework.domain.models.trade.trade_status import \
|
|
7
|
+
TradeStatus
|
|
8
|
+
from investing_algorithm_framework.domain.models.trade.trade_stop_loss import \
|
|
9
|
+
TradeStopLoss
|
|
10
|
+
from investing_algorithm_framework.domain.models.trade\
|
|
11
|
+
.trade_take_profit import TradeTakeProfit
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class Trade(BaseModel):
|
|
@@ -24,241 +25,364 @@ class Trade(BaseModel):
|
|
|
24
25
|
|
|
25
26
|
A single sell order can close multiple buy orders. Also, a single
|
|
26
27
|
buy order can be closed by multiple sell orders.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
orders: str, the id of the buy order
|
|
31
|
+
target_symbol: str, the target symbol of the trade
|
|
32
|
+
trading_symbol: str, the trading symbol of the trade
|
|
33
|
+
closed_at: datetime, the datetime when the trade was closed
|
|
34
|
+
amount: float, the amount of the trade
|
|
35
|
+
available_amount: float, the available amount of the trade
|
|
36
|
+
remaining: float, the remaining amount that is not filled by the
|
|
37
|
+
buy order that opened the trade.
|
|
38
|
+
filled_amount: float, the filled amount of the trade by the buy
|
|
39
|
+
order that opened the trade.
|
|
40
|
+
net_gain: float, the net gain of the trade
|
|
41
|
+
last_reported_price: float, the last reported price of the trade
|
|
42
|
+
last_reported_price_datetime: datetime, the datetime when the last
|
|
43
|
+
reported price was reported
|
|
44
|
+
updated_at: datetime, the datetime when the trade was last updated
|
|
45
|
+
status: str, the status of the trade
|
|
46
|
+
metadata: dict, the metadata of the trade, this can be used to store
|
|
47
|
+
additional information about the trade.
|
|
27
48
|
"""
|
|
49
|
+
|
|
28
50
|
def __init__(
|
|
29
51
|
self,
|
|
30
|
-
|
|
52
|
+
id,
|
|
53
|
+
orders,
|
|
31
54
|
target_symbol,
|
|
32
55
|
trading_symbol,
|
|
33
|
-
|
|
34
|
-
open_price,
|
|
56
|
+
closed_at,
|
|
35
57
|
opened_at,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
open_price,
|
|
59
|
+
amount,
|
|
60
|
+
available_amount,
|
|
61
|
+
cost,
|
|
62
|
+
remaining,
|
|
63
|
+
filled_amount,
|
|
64
|
+
status,
|
|
65
|
+
net_gain=0,
|
|
66
|
+
last_reported_price=None,
|
|
67
|
+
last_reported_price_datetime=None,
|
|
68
|
+
high_water_mark=None,
|
|
69
|
+
high_water_mark_datetime=None,
|
|
70
|
+
updated_at=None,
|
|
71
|
+
stop_losses=None,
|
|
72
|
+
take_profits=None,
|
|
73
|
+
metadata=None,
|
|
40
74
|
):
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
self.
|
|
45
|
-
self.
|
|
46
|
-
self.
|
|
47
|
-
self.
|
|
48
|
-
self.
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
51
|
-
self.
|
|
75
|
+
self.id = id
|
|
76
|
+
self.orders = orders
|
|
77
|
+
self.target_symbol = target_symbol
|
|
78
|
+
self.trading_symbol = trading_symbol
|
|
79
|
+
self.closed_at = closed_at
|
|
80
|
+
self.opened_at = opened_at
|
|
81
|
+
self.open_price = open_price
|
|
82
|
+
self.amount = amount
|
|
83
|
+
self.available_amount = available_amount
|
|
84
|
+
self.cost = cost
|
|
85
|
+
self.remaining = remaining
|
|
86
|
+
self.filled_amount = filled_amount
|
|
87
|
+
self.net_gain = net_gain
|
|
88
|
+
self.last_reported_price = last_reported_price
|
|
89
|
+
self.last_reported_price_datetime = last_reported_price_datetime
|
|
90
|
+
self.high_water_mark = high_water_mark
|
|
91
|
+
self.high_water_mark_datetime = high_water_mark_datetime
|
|
92
|
+
self.status = TradeStatus.from_value(status).value
|
|
93
|
+
self.updated_at = updated_at
|
|
94
|
+
self.stop_losses = stop_losses
|
|
95
|
+
self.take_profits = take_profits
|
|
96
|
+
self.metadata = metadata if metadata is not None else {}
|
|
97
|
+
|
|
98
|
+
def update(self, data):
|
|
99
|
+
|
|
100
|
+
if "status" in data:
|
|
101
|
+
self.status = TradeStatus.from_value(data["status"]).value
|
|
102
|
+
|
|
103
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
104
|
+
|
|
105
|
+
# Set all stop losses to inactive
|
|
106
|
+
if self.stop_losses is not None:
|
|
107
|
+
for stop_loss in self.stop_losses:
|
|
108
|
+
stop_loss.active = False
|
|
109
|
+
|
|
110
|
+
# set all take profits to inactive
|
|
111
|
+
if self.take_profits is not None:
|
|
112
|
+
for take_profit in self.take_profits:
|
|
113
|
+
take_profit.active = False
|
|
114
|
+
|
|
115
|
+
if "last_reported_price" in data:
|
|
116
|
+
self.last_reported_price = data["last_reported_price"]
|
|
117
|
+
|
|
118
|
+
if self.high_water_mark is None:
|
|
119
|
+
self.high_water_mark = data["last_reported_price"]
|
|
120
|
+
self.high_water_mark_datetime = \
|
|
121
|
+
data["last_reported_price_datetime"]
|
|
122
|
+
else:
|
|
123
|
+
|
|
124
|
+
if data["last_reported_price"] > self.high_water_mark:
|
|
125
|
+
self.high_water_mark = data["last_reported_price"]
|
|
126
|
+
self.high_water_mark_datetime = \
|
|
127
|
+
data["last_reported_price_datetime"]
|
|
128
|
+
|
|
129
|
+
return super().update(data)
|
|
52
130
|
|
|
53
131
|
@property
|
|
54
|
-
def
|
|
55
|
-
return
|
|
132
|
+
def closed_prices(self):
|
|
133
|
+
return [
|
|
134
|
+
order.price for order in self.orders
|
|
135
|
+
if order.order_side == OrderSide.SELL.value
|
|
136
|
+
]
|
|
56
137
|
|
|
57
138
|
@property
|
|
58
|
-
def
|
|
59
|
-
return self._sell_order_id
|
|
139
|
+
def buy_order(self):
|
|
60
140
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return self._target_symbol
|
|
141
|
+
if self.orders is None:
|
|
142
|
+
return
|
|
64
143
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
144
|
+
return [
|
|
145
|
+
order for order in self.orders
|
|
146
|
+
if order.order_side == OrderSide.BUY.value
|
|
147
|
+
][0]
|
|
68
148
|
|
|
69
149
|
@property
|
|
70
150
|
def symbol(self):
|
|
71
|
-
return f"{self.target_symbol}/{self.trading_symbol}"
|
|
72
|
-
|
|
73
|
-
def get_symbol(self):
|
|
74
|
-
return f"{self.target_symbol}/{self.trading_symbol}"
|
|
151
|
+
return f"{self.target_symbol.upper()}/{self.trading_symbol.upper()}"
|
|
75
152
|
|
|
76
153
|
@property
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def get_amount(self):
|
|
81
|
-
return self.amount
|
|
154
|
+
def duration(self):
|
|
155
|
+
"""
|
|
156
|
+
Calculate the duration of the trade in hours.
|
|
82
157
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
158
|
+
Returns:
|
|
159
|
+
float: The duration of the trade in hours.
|
|
160
|
+
"""
|
|
161
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
162
|
+
# Get the total hours between the closed and opened datetime
|
|
163
|
+
diff = self.closed_at - self.opened_at
|
|
164
|
+
return diff.total_seconds() / 3600
|
|
86
165
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return self._closed_price
|
|
166
|
+
if self.opened_at is None:
|
|
167
|
+
return None
|
|
90
168
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return self._closed_at
|
|
169
|
+
if self.updated_at is None:
|
|
170
|
+
return None
|
|
94
171
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return self._opened_at
|
|
172
|
+
diff = self.updated_at - self.opened_at
|
|
173
|
+
return diff.total_seconds() / 3600
|
|
98
174
|
|
|
99
175
|
@property
|
|
100
176
|
def size(self):
|
|
101
177
|
return self.amount * self.open_price
|
|
102
178
|
|
|
103
179
|
@property
|
|
104
|
-
def
|
|
105
|
-
|
|
180
|
+
def change(self):
|
|
181
|
+
"""
|
|
182
|
+
Property to calculate the change in value of the trade.
|
|
106
183
|
|
|
107
|
-
|
|
108
|
-
|
|
184
|
+
This is the difference between the current value of the trade
|
|
185
|
+
and the cost of the trade.
|
|
186
|
+
"""
|
|
187
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
188
|
+
return self.net_gain
|
|
109
189
|
|
|
110
|
-
if self.
|
|
190
|
+
if self.last_reported_price is None:
|
|
111
191
|
return 0
|
|
112
192
|
|
|
113
|
-
|
|
193
|
+
if self.remaining is None or self.remaining == 0:
|
|
194
|
+
amount = self.amount
|
|
195
|
+
else:
|
|
196
|
+
amount = self.amount - self.remaining
|
|
197
|
+
|
|
198
|
+
cost = amount * self.open_price
|
|
199
|
+
gain = (amount * self.last_reported_price) - cost
|
|
200
|
+
return gain
|
|
114
201
|
|
|
115
202
|
@property
|
|
116
|
-
def
|
|
203
|
+
def net_gain_absolute(self):
|
|
117
204
|
|
|
118
|
-
if self.
|
|
119
|
-
return
|
|
205
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
206
|
+
return self.net_gain
|
|
207
|
+
else:
|
|
208
|
+
gain = 0
|
|
120
209
|
|
|
121
|
-
|
|
210
|
+
if self.last_reported_price is not None:
|
|
211
|
+
gain = (
|
|
212
|
+
self.available_amount *
|
|
213
|
+
(self.last_reported_price - self.open_price)
|
|
214
|
+
)
|
|
122
215
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
closed_at = self.closed_at
|
|
216
|
+
gain += self.net_gain
|
|
217
|
+
return gain
|
|
126
218
|
|
|
127
|
-
|
|
128
|
-
|
|
219
|
+
@property
|
|
220
|
+
def net_gain_percentage(self):
|
|
129
221
|
|
|
130
|
-
|
|
222
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
131
223
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return self._current_price
|
|
224
|
+
if self.cost != 0:
|
|
225
|
+
return (self.net_gain / self.cost) * 100
|
|
135
226
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
self._current_price = current_price
|
|
227
|
+
else:
|
|
228
|
+
gain = 0
|
|
139
229
|
|
|
140
|
-
|
|
141
|
-
|
|
230
|
+
if self.last_reported_price is not None:
|
|
231
|
+
gain = (
|
|
232
|
+
self.available_amount *
|
|
233
|
+
(self.last_reported_price - self.open_price)
|
|
234
|
+
)
|
|
142
235
|
|
|
143
|
-
|
|
144
|
-
return self.amount * self.closed_price
|
|
236
|
+
gain += self.net_gain
|
|
145
237
|
|
|
146
|
-
|
|
147
|
-
|
|
238
|
+
if self.cost != 0:
|
|
239
|
+
return (gain / self.cost) * 100
|
|
148
240
|
|
|
149
|
-
return
|
|
241
|
+
return 0
|
|
150
242
|
|
|
151
243
|
@property
|
|
152
244
|
def percentage_change(self):
|
|
153
245
|
|
|
154
|
-
if
|
|
155
|
-
return 0
|
|
156
|
-
value = self.value
|
|
157
|
-
return (value - self.size) / self.size * 100
|
|
158
|
-
|
|
159
|
-
def get_percentage_change(self):
|
|
160
|
-
return self.percentage_change
|
|
246
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
161
247
|
|
|
162
|
-
|
|
163
|
-
|
|
248
|
+
if self.cost != 0:
|
|
249
|
+
return (self.net_gain / self.cost) * 100
|
|
164
250
|
|
|
165
|
-
if self.
|
|
251
|
+
if self.last_reported_price is None:
|
|
166
252
|
return 0
|
|
167
253
|
|
|
168
|
-
|
|
169
|
-
|
|
254
|
+
cost = self.available_amount * self.open_price
|
|
255
|
+
gain = (self.available_amount * self.last_reported_price) - cost
|
|
256
|
+
gain += self.net_gain
|
|
170
257
|
|
|
171
|
-
|
|
172
|
-
|
|
258
|
+
if cost != 0:
|
|
259
|
+
return (gain / cost) * 100
|
|
173
260
|
|
|
174
|
-
|
|
175
|
-
self,
|
|
176
|
-
current_price,
|
|
177
|
-
stop_loss_percentage,
|
|
178
|
-
prices: List[float] = None,
|
|
179
|
-
ohlcv_df: DataFrame = None
|
|
180
|
-
):
|
|
181
|
-
"""
|
|
182
|
-
Function to check if the stop loss is triggered for a given trade.
|
|
183
|
-
|
|
184
|
-
You can use either the prices list or the ohlcv_df DataFrame to
|
|
185
|
-
calculate the stop loss. The dataframe needs to be a Polars
|
|
186
|
-
DataFrame with the following columns: "Datetime" and "Close".
|
|
187
|
-
|
|
188
|
-
You can use the default CCXTOHLCVMarketDataSource to get the ohlcv_df
|
|
189
|
-
DataFrame.
|
|
190
|
-
|
|
191
|
-
Stop loss is triggered when the current price is lower than the
|
|
192
|
-
calculated stop loss price. The stop loss price is calculated by
|
|
193
|
-
taking the highest price of the given range. If the highest price
|
|
194
|
-
is lower than the open price, the stop loss price is calculated by
|
|
195
|
-
taking the open price and subtracting the stop loss percentage.
|
|
196
|
-
If the highest price is higher than the open price, the stop loss
|
|
197
|
-
price is calculated by taking the open price and adding the stop
|
|
198
|
-
loss percentage.
|
|
199
|
-
"""
|
|
261
|
+
return 0
|
|
200
262
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
263
|
+
def to_dict(self, datetime_format=None):
|
|
264
|
+
def ensure_iso(value):
|
|
265
|
+
if hasattr(value, "isoformat"):
|
|
266
|
+
if value.tzinfo is None:
|
|
267
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
268
|
+
return value.isoformat()
|
|
269
|
+
return value
|
|
270
|
+
|
|
271
|
+
opened_at = ensure_iso(self.opened_at) if self.opened_at else None
|
|
272
|
+
closed_at = ensure_iso(self.closed_at) if self.closed_at else None
|
|
273
|
+
updated_at = ensure_iso(self.updated_at) if self.updated_at else None
|
|
274
|
+
|
|
275
|
+
# Ensure status is a string
|
|
276
|
+
self.status = TradeStatus.from_value(self.status).value
|
|
205
277
|
|
|
206
|
-
if current_price < self.open_price:
|
|
207
|
-
stop_loss_price = self.open_price * \
|
|
208
|
-
(1 - stop_loss_percentage / 100)
|
|
209
|
-
return current_price <= stop_loss_price
|
|
210
|
-
else:
|
|
211
|
-
# If dataframes are provided, we use the dataframe to calculate
|
|
212
|
-
# the stop loss price
|
|
213
|
-
if ohlcv_df is not None:
|
|
214
|
-
column_type = ohlcv_df['Datetime'].dtype
|
|
215
|
-
|
|
216
|
-
if isinstance(column_type, pl.Datetime):
|
|
217
|
-
filtered_df = ohlcv_df.filter(
|
|
218
|
-
pl.col('Datetime') >= self.opened_at
|
|
219
|
-
)
|
|
220
|
-
else:
|
|
221
|
-
filtered_df = ohlcv_df.filter(
|
|
222
|
-
pl.col('Datetime') >= self.opened_at.strftime(
|
|
223
|
-
DATETIME_FORMAT
|
|
224
|
-
)
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
prices = filtered_df['Close'].to_numpy()
|
|
228
|
-
|
|
229
|
-
highest_price = max(prices)
|
|
230
|
-
stop_loss_price = highest_price * (1 - stop_loss_percentage / 100)
|
|
231
|
-
return current_price <= stop_loss_price
|
|
232
|
-
|
|
233
|
-
def to_dict(self):
|
|
234
278
|
return {
|
|
279
|
+
"id": self.id,
|
|
280
|
+
"orders": [
|
|
281
|
+
order.to_dict(datetime_format=datetime_format)
|
|
282
|
+
for order in self.orders
|
|
283
|
+
],
|
|
235
284
|
"target_symbol": self.target_symbol,
|
|
236
285
|
"trading_symbol": self.trading_symbol,
|
|
237
286
|
"status": self.status,
|
|
238
287
|
"amount": self.amount,
|
|
288
|
+
"remaining": self.remaining if self.remaining is not None else 0,
|
|
239
289
|
"open_price": self.open_price,
|
|
240
|
-
"
|
|
241
|
-
"
|
|
242
|
-
"
|
|
243
|
-
|
|
244
|
-
"
|
|
245
|
-
if self.
|
|
246
|
-
"
|
|
247
|
-
|
|
290
|
+
"last_reported_price": self.last_reported_price,
|
|
291
|
+
"opened_at": opened_at,
|
|
292
|
+
"closed_at": closed_at,
|
|
293
|
+
"updated_at": updated_at,
|
|
294
|
+
"net_gain": self.net_gain if self.net_gain is not None else 0,
|
|
295
|
+
"cost": self.cost if self.cost is not None else 0,
|
|
296
|
+
"stop_losses": [
|
|
297
|
+
stop_loss.to_dict(datetime_format=datetime_format)
|
|
298
|
+
for stop_loss in self.stop_losses
|
|
299
|
+
] if self.stop_losses else None,
|
|
300
|
+
"take_profits": [
|
|
301
|
+
take_profit.to_dict(datetime_format=datetime_format)
|
|
302
|
+
for take_profit in self.take_profits
|
|
303
|
+
] if self.take_profits else None,
|
|
304
|
+
"filled_amount": self.filled_amount,
|
|
305
|
+
"available_amount": self.available_amount,
|
|
306
|
+
"metadata": self.metadata if self.metadata else {},
|
|
248
307
|
}
|
|
249
308
|
|
|
309
|
+
@staticmethod
|
|
310
|
+
def from_dict(data):
|
|
311
|
+
opened_at = None
|
|
312
|
+
closed_at = None
|
|
313
|
+
updated_at = None
|
|
314
|
+
stop_losses = None
|
|
315
|
+
take_profits = None
|
|
316
|
+
orders = None
|
|
317
|
+
|
|
318
|
+
if "opened_at" in data and data["opened_at"] is not None:
|
|
319
|
+
opened_at = parse(data["opened_at"])
|
|
320
|
+
|
|
321
|
+
if "closed_at" in data and data["closed_at"] is not None:
|
|
322
|
+
closed_at = parse(data["closed_at"])
|
|
323
|
+
|
|
324
|
+
if "updated_at" in data and data["updated_at"] is not None:
|
|
325
|
+
updated_at = parse(data["updated_at"])
|
|
326
|
+
|
|
327
|
+
if "stop_losses" in data and data["stop_losses"] is not None:
|
|
328
|
+
stop_losses = [
|
|
329
|
+
TradeStopLoss.from_dict(stop_loss)
|
|
330
|
+
for stop_loss in data["stop_losses"]
|
|
331
|
+
]
|
|
332
|
+
|
|
333
|
+
if "take_profits" in data and data["take_profits"] is not None:
|
|
334
|
+
take_profits = [
|
|
335
|
+
TradeTakeProfit.from_dict(take_profit)
|
|
336
|
+
for take_profit in data["take_profits"]
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
if "orders" in data and data["orders"] is not None:
|
|
340
|
+
orders = [
|
|
341
|
+
Order.from_dict(order)
|
|
342
|
+
for order in data["orders"]
|
|
343
|
+
]
|
|
344
|
+
return Trade(
|
|
345
|
+
id=data.get("id", None),
|
|
346
|
+
orders=orders,
|
|
347
|
+
target_symbol=data["target_symbol"],
|
|
348
|
+
trading_symbol=data["trading_symbol"],
|
|
349
|
+
amount=data["amount"],
|
|
350
|
+
open_price=data["open_price"],
|
|
351
|
+
opened_at=opened_at,
|
|
352
|
+
closed_at=closed_at,
|
|
353
|
+
filled_amount=data.get("filled_amount", 0),
|
|
354
|
+
available_amount=data.get("available_amount", 0),
|
|
355
|
+
remaining=data.get("remaining", 0),
|
|
356
|
+
net_gain=data.get("net_gain", 0),
|
|
357
|
+
last_reported_price=data.get("last_reported_price"),
|
|
358
|
+
status=TradeStatus.from_value(data["status"]).value,
|
|
359
|
+
cost=data.get("cost", 0),
|
|
360
|
+
updated_at=updated_at,
|
|
361
|
+
stop_losses=stop_losses,
|
|
362
|
+
take_profits=take_profits,
|
|
363
|
+
metadata=data.get("metadata", {}),
|
|
364
|
+
)
|
|
365
|
+
|
|
250
366
|
def __repr__(self):
|
|
251
367
|
return self.repr(
|
|
368
|
+
id=self.id,
|
|
369
|
+
symbol=self.symbol,
|
|
252
370
|
target_symbol=self.target_symbol,
|
|
253
371
|
trading_symbol=self.trading_symbol,
|
|
254
372
|
status=self.status,
|
|
255
373
|
amount=self.amount,
|
|
374
|
+
available_amount=self.available_amount,
|
|
375
|
+
filled_amount=self.filled_amount,
|
|
376
|
+
remaining=self.remaining,
|
|
256
377
|
open_price=self.open_price,
|
|
257
|
-
current_price=self.current_price,
|
|
258
|
-
closed_price=self.closed_price,
|
|
259
378
|
opened_at=self.opened_at,
|
|
260
379
|
closed_at=self.closed_at,
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
380
|
+
net_gain=self.net_gain,
|
|
381
|
+
last_reported_price=self.last_reported_price,
|
|
382
|
+
updated_at=self.updated_at,
|
|
383
|
+
metadata=self.metadata,
|
|
264
384
|
)
|
|
385
|
+
|
|
386
|
+
def __lt__(self, other):
|
|
387
|
+
# Define the less-than comparison based on created_at attribute
|
|
388
|
+
return self.opened_at < other.opened_at
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
+
from investing_algorithm_framework.domain.exceptions import \
|
|
3
|
+
OperationalException
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class TradeStatus(Enum):
|
|
7
|
+
CREATED = "CREATED"
|
|
5
8
|
OPEN = "OPEN"
|
|
6
9
|
CLOSED = "CLOSED"
|
|
7
10
|
|
|
@@ -14,7 +17,9 @@ class TradeStatus(Enum):
|
|
|
14
17
|
if value.upper() == status.value:
|
|
15
18
|
return status
|
|
16
19
|
|
|
17
|
-
raise
|
|
20
|
+
raise OperationalException(
|
|
21
|
+
f"Could not convert value: '{value}' to TradeStatus"
|
|
22
|
+
)
|
|
18
23
|
|
|
19
24
|
@staticmethod
|
|
20
25
|
def from_value(value):
|
|
@@ -27,7 +32,9 @@ class TradeStatus(Enum):
|
|
|
27
32
|
elif isinstance(value, str):
|
|
28
33
|
return TradeStatus.from_string(value)
|
|
29
34
|
|
|
30
|
-
raise
|
|
35
|
+
raise OperationalException(
|
|
36
|
+
f"Could not convert value: {value} to TradeStatus"
|
|
37
|
+
)
|
|
31
38
|
|
|
32
39
|
def equals(self, other):
|
|
33
40
|
return TradeStatus.from_value(other) == self
|