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,124 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from .time_frame import TimeFrame
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TimeInterval(Enum):
|
|
7
|
+
CURRENT = "CURRENT"
|
|
8
|
+
MINUTES_ONE = "MINUTES_ONE"
|
|
9
|
+
MINUTES_FIFTEEN = "MINUTES_FIFTEEN"
|
|
10
|
+
HOURS_ONE = "HOURS_ONE"
|
|
11
|
+
HOURS_FOUR = "HOURS_FOUR"
|
|
12
|
+
DAYS_ONE = "DAYS_ONE"
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def from_string(value: str):
|
|
16
|
+
|
|
17
|
+
if isinstance(value, str):
|
|
18
|
+
|
|
19
|
+
for entry in TimeInterval:
|
|
20
|
+
|
|
21
|
+
if value.upper() == entry.value:
|
|
22
|
+
return entry
|
|
23
|
+
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f"Could not convert {value} to TimeInterval"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def from_ohlcv_data_file(file_path: str):
|
|
30
|
+
"""
|
|
31
|
+
Extracts the time interval from the file name of an OHLCV data file.
|
|
32
|
+
The file name should contain the time interval in the format
|
|
33
|
+
'symbol_timeinterval.csv'.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
file_path (str): The file path of the OHLCV data file.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
TimeInterval: The extracted time interval.
|
|
40
|
+
"""
|
|
41
|
+
if not isinstance(file_path, str):
|
|
42
|
+
raise ValueError("File path must be a string.")
|
|
43
|
+
|
|
44
|
+
parts = file_path.split('_')
|
|
45
|
+
if len(parts) < 2:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"File name does not contain a valid time interval."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
time_interval_str = parts[-1].split('.')[0].upper()
|
|
51
|
+
try:
|
|
52
|
+
return TimeInterval.from_string(time_interval_str)
|
|
53
|
+
except ValueError:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"Could not extract time interval from "
|
|
56
|
+
f"file name: {file_path}. "
|
|
57
|
+
"Expected format 'symbol_timeinterval.csv', "
|
|
58
|
+
f"got '{time_interval_str}'."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def equals(self, other):
|
|
62
|
+
|
|
63
|
+
if isinstance(other, Enum):
|
|
64
|
+
return self.value == other.value
|
|
65
|
+
|
|
66
|
+
else:
|
|
67
|
+
return TimeInterval.from_string(other) == self
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def from_time_frame(time_frame):
|
|
71
|
+
|
|
72
|
+
if TimeFrame.CURRENT.equals(time_frame):
|
|
73
|
+
return TimeInterval.CURRENT
|
|
74
|
+
elif TimeFrame.ONE_HOUR.equals(time_frame):
|
|
75
|
+
return TimeInterval.MINUTES_ONE
|
|
76
|
+
elif TimeFrame.ONE_DAY.equals(time_frame):
|
|
77
|
+
return TimeInterval.MINUTES_FIFTEEN
|
|
78
|
+
elif TimeFrame.ONE_WEEK.equals(time_frame):
|
|
79
|
+
return TimeInterval.HOURS_ONE
|
|
80
|
+
elif TimeFrame.ONE_MONTH.equals(time_frame):
|
|
81
|
+
return TimeInterval.HOURS_FOUR
|
|
82
|
+
elif TimeFrame.ONE_YEAR.equals(time_frame):
|
|
83
|
+
return TimeInterval.DAYS_ONE
|
|
84
|
+
else:
|
|
85
|
+
raise NotImplementedError(
|
|
86
|
+
f"Timeframe {time_frame} not implemented"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def amount_of_data_points(self):
|
|
90
|
+
|
|
91
|
+
if TimeInterval.CURRENT.equals(self):
|
|
92
|
+
return 1
|
|
93
|
+
elif TimeInterval.MINUTES_ONE.equals(self):
|
|
94
|
+
return 60
|
|
95
|
+
elif TimeInterval.MINUTES_FIFTEEN.equals(self):
|
|
96
|
+
return 96
|
|
97
|
+
elif TimeInterval.HOURS_ONE.equals(self):
|
|
98
|
+
return 168
|
|
99
|
+
elif TimeInterval.HOURS_FOUR.equals(self):
|
|
100
|
+
return 168
|
|
101
|
+
elif TimeInterval.DAYS_ONE.equals(self):
|
|
102
|
+
return 365
|
|
103
|
+
else:
|
|
104
|
+
raise NotImplementedError(f"Timeframe {self} not implemented")
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def time_frame(self):
|
|
108
|
+
from investing_algorithm_framework.domain.models.time_frame import \
|
|
109
|
+
TimeFrame
|
|
110
|
+
|
|
111
|
+
if TimeInterval.MINUTES_ONE.equals(self):
|
|
112
|
+
return TimeFrame.ONE_HOUR
|
|
113
|
+
elif TimeInterval.MINUTES_FIFTEEN.equals(self):
|
|
114
|
+
return TimeFrame.ONE_DAY
|
|
115
|
+
elif TimeInterval.HOURS_ONE.equals(self):
|
|
116
|
+
return TimeFrame.ONE_WEEK
|
|
117
|
+
elif TimeInterval.HOURS_FOUR.equals(self):
|
|
118
|
+
return TimeFrame.ONE_MONTH
|
|
119
|
+
elif TimeInterval.DAYS_ONE.equals(self):
|
|
120
|
+
return TimeFrame.ONE_YEAR
|
|
121
|
+
else:
|
|
122
|
+
raise NotImplementedError(
|
|
123
|
+
f"TimeInterval {self.value} not implemented"
|
|
124
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from investing_algorithm_framework.domain.exceptions import \
|
|
4
|
+
OperationalException
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TimeUnit(Enum):
|
|
8
|
+
"""
|
|
9
|
+
Enum class the represents a time unit such as
|
|
10
|
+
second, minute, hour or day. This can class
|
|
11
|
+
can be used to specify time specification within
|
|
12
|
+
the framework.
|
|
13
|
+
"""
|
|
14
|
+
SECOND = "SECOND"
|
|
15
|
+
MINUTE = "MINUTE"
|
|
16
|
+
HOUR = "HOUR"
|
|
17
|
+
DAY = "DAY"
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def from_string(value: str):
|
|
21
|
+
|
|
22
|
+
if isinstance(value, str):
|
|
23
|
+
|
|
24
|
+
for entry in TimeUnit:
|
|
25
|
+
|
|
26
|
+
if value.upper() == entry.value:
|
|
27
|
+
return entry
|
|
28
|
+
|
|
29
|
+
raise OperationalException(
|
|
30
|
+
f"Could not convert string {value} to time unit"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
raise OperationalException(
|
|
34
|
+
f"Could not convert value {value} to time unit," +
|
|
35
|
+
" please make sure that the value is either of type string or" +
|
|
36
|
+
f"TimeUnit. Its current type is {type(value)}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def from_ohlcv_data_file(file_path: str):
|
|
41
|
+
"""
|
|
42
|
+
Extracts the time unit from the file name of an OHLCV data file.
|
|
43
|
+
The file name should contain the time unit in the
|
|
44
|
+
format 'symbol_timeunit.csv'.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
file_path (str): The file path of the OHLCV data file.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
TimeUnit: The extracted time unit.
|
|
51
|
+
"""
|
|
52
|
+
if not isinstance(file_path, str):
|
|
53
|
+
raise OperationalException(
|
|
54
|
+
"File path must be a string."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
parts = file_path.split('_')
|
|
58
|
+
if len(parts) < 2:
|
|
59
|
+
raise OperationalException(
|
|
60
|
+
"File name does not contain a valid time unit."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
time_unit_str = parts[-1].split('.')[0].upper()
|
|
64
|
+
try:
|
|
65
|
+
return TimeUnit.from_string(time_unit_str)
|
|
66
|
+
except ValueError:
|
|
67
|
+
raise OperationalException(
|
|
68
|
+
f"Could not extract time unit from file name: {file_path}. "
|
|
69
|
+
"Expected format 'symbol_timeunit.csv', "
|
|
70
|
+
f"got '{time_unit_str}'."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def equals(self, other):
|
|
74
|
+
|
|
75
|
+
if isinstance(other, Enum):
|
|
76
|
+
return self.value == other.value
|
|
77
|
+
else:
|
|
78
|
+
return TimeUnit.from_string(other) == self
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def from_value(value):
|
|
82
|
+
|
|
83
|
+
if isinstance(value, TimeUnit):
|
|
84
|
+
|
|
85
|
+
for entry in TimeUnit:
|
|
86
|
+
|
|
87
|
+
if entry == value:
|
|
88
|
+
return entry
|
|
89
|
+
|
|
90
|
+
return TimeUnit.from_string(value)
|
|
91
|
+
|
|
92
|
+
def create_date(self, start_date, interval):
|
|
93
|
+
|
|
94
|
+
if TimeUnit.SECOND.equals(self):
|
|
95
|
+
return timedelta(minutes=interval)
|
|
96
|
+
elif TimeUnit.MINUTE.equals(self):
|
|
97
|
+
return timedelta(minutes=interval)
|
|
98
|
+
elif TimeUnit.HOUR.equals(self):
|
|
99
|
+
return timedelta(hours=interval)
|
|
100
|
+
elif TimeUnit.DAY.equals(self):
|
|
101
|
+
return timedelta(days=interval)
|
|
102
|
+
|
|
103
|
+
raise ValueError(f"Unsupported time unit: {self}")
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def single_name(self):
|
|
107
|
+
|
|
108
|
+
if TimeUnit.SECOND.equals(self.value):
|
|
109
|
+
return "second"
|
|
110
|
+
|
|
111
|
+
if TimeUnit.MINUTE.equals(self.value):
|
|
112
|
+
return "minute"
|
|
113
|
+
|
|
114
|
+
if TimeUnit.HOUR.equals(self.value):
|
|
115
|
+
return "hour"
|
|
116
|
+
|
|
117
|
+
if TimeUnit.DAY.equals(self.value):
|
|
118
|
+
return "day"
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def plural_name(self):
|
|
122
|
+
|
|
123
|
+
if TimeUnit.SECOND.equals(self.value):
|
|
124
|
+
return "seconds"
|
|
125
|
+
|
|
126
|
+
if TimeUnit.MINUTE.equals(self.value):
|
|
127
|
+
return "minutes"
|
|
128
|
+
|
|
129
|
+
if TimeUnit.HOUR.equals(self.value):
|
|
130
|
+
return "hours"
|
|
131
|
+
|
|
132
|
+
if TimeUnit.DAY.equals(self.value):
|
|
133
|
+
return "days"
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def amount_of_minutes(self):
|
|
137
|
+
if TimeUnit.SECOND.equals(self):
|
|
138
|
+
return 1 / 60
|
|
139
|
+
|
|
140
|
+
if TimeUnit.MINUTE.equals(self):
|
|
141
|
+
return 1
|
|
142
|
+
|
|
143
|
+
if TimeUnit.HOUR.equals(self):
|
|
144
|
+
return 60
|
|
145
|
+
|
|
146
|
+
if TimeUnit.DAY.equals(self):
|
|
147
|
+
return 60 * 24
|
|
148
|
+
|
|
149
|
+
raise ValueError(f"Unsupported time unit: {self}")
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Trace:
|
|
2
|
+
"""
|
|
3
|
+
Represents a trace of a trading strategy. A trace contains
|
|
4
|
+
data that has been generated by a trading strategy during its
|
|
5
|
+
execution.
|
|
6
|
+
|
|
7
|
+
The data can be used to analyze the performance of the trading after
|
|
8
|
+
it has been executed. Usually, the data contains metrics that
|
|
9
|
+
have been generated by the trading strategy during its execution,
|
|
10
|
+
and the signals that have been generated.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
strategy_id: str,
|
|
16
|
+
symbol: str,
|
|
17
|
+
data,
|
|
18
|
+
drop_duplicates=True
|
|
19
|
+
):
|
|
20
|
+
self.strategy_id = strategy_id
|
|
21
|
+
self.symbol = symbol
|
|
22
|
+
self.data = data
|
|
23
|
+
self.drop_duplicates = drop_duplicates
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .trade import Trade
|
|
2
|
+
from .trade_status import TradeStatus
|
|
3
|
+
from .trade_stop_loss import TradeStopLoss
|
|
4
|
+
from .trade_take_profit import TradeTakeProfit
|
|
5
|
+
from .trade_risk_type import TradeRiskType
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Trade",
|
|
9
|
+
"TradeStatus",
|
|
10
|
+
"TradeStopLoss",
|
|
11
|
+
"TradeTakeProfit",
|
|
12
|
+
"TradeRiskType",
|
|
13
|
+
]
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
from dateutil.parser import parse
|
|
2
|
+
from datetime import timezone
|
|
3
|
+
|
|
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
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Trade(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
Trade model
|
|
17
|
+
|
|
18
|
+
A trade is a combination of a buy and sell order that has been opened or
|
|
19
|
+
closed.
|
|
20
|
+
|
|
21
|
+
A trade is considered opened when a buy order is executed and there is
|
|
22
|
+
no corresponding sell order. A trade is considered closed when a sell
|
|
23
|
+
order is executed and the amount of the sell order is equal or larger
|
|
24
|
+
to the amount of the buy order.
|
|
25
|
+
|
|
26
|
+
A single sell order can close multiple buy orders. Also, a single
|
|
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.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
id,
|
|
53
|
+
orders,
|
|
54
|
+
target_symbol,
|
|
55
|
+
trading_symbol,
|
|
56
|
+
closed_at,
|
|
57
|
+
opened_at,
|
|
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,
|
|
74
|
+
):
|
|
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)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def closed_prices(self):
|
|
133
|
+
return [
|
|
134
|
+
order.price for order in self.orders
|
|
135
|
+
if order.order_side == OrderSide.SELL.value
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def buy_order(self):
|
|
140
|
+
|
|
141
|
+
if self.orders is None:
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
return [
|
|
145
|
+
order for order in self.orders
|
|
146
|
+
if order.order_side == OrderSide.BUY.value
|
|
147
|
+
][0]
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def symbol(self):
|
|
151
|
+
return f"{self.target_symbol.upper()}/{self.trading_symbol.upper()}"
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def duration(self):
|
|
155
|
+
"""
|
|
156
|
+
Calculate the duration of the trade in hours.
|
|
157
|
+
|
|
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
|
|
165
|
+
|
|
166
|
+
if self.opened_at is None:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
if self.updated_at is None:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
diff = self.updated_at - self.opened_at
|
|
173
|
+
return diff.total_seconds() / 3600
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def size(self):
|
|
177
|
+
return self.amount * self.open_price
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def change(self):
|
|
181
|
+
"""
|
|
182
|
+
Property to calculate the change in value of the trade.
|
|
183
|
+
|
|
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
|
|
189
|
+
|
|
190
|
+
if self.last_reported_price is None:
|
|
191
|
+
return 0
|
|
192
|
+
|
|
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
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def net_gain_absolute(self):
|
|
204
|
+
|
|
205
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
206
|
+
return self.net_gain
|
|
207
|
+
else:
|
|
208
|
+
gain = 0
|
|
209
|
+
|
|
210
|
+
if self.last_reported_price is not None:
|
|
211
|
+
gain = (
|
|
212
|
+
self.available_amount *
|
|
213
|
+
(self.last_reported_price - self.open_price)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
gain += self.net_gain
|
|
217
|
+
return gain
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def net_gain_percentage(self):
|
|
221
|
+
|
|
222
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
223
|
+
|
|
224
|
+
if self.cost != 0:
|
|
225
|
+
return (self.net_gain / self.cost) * 100
|
|
226
|
+
|
|
227
|
+
else:
|
|
228
|
+
gain = 0
|
|
229
|
+
|
|
230
|
+
if self.last_reported_price is not None:
|
|
231
|
+
gain = (
|
|
232
|
+
self.available_amount *
|
|
233
|
+
(self.last_reported_price - self.open_price)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
gain += self.net_gain
|
|
237
|
+
|
|
238
|
+
if self.cost != 0:
|
|
239
|
+
return (gain / self.cost) * 100
|
|
240
|
+
|
|
241
|
+
return 0
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def percentage_change(self):
|
|
245
|
+
|
|
246
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
247
|
+
|
|
248
|
+
if self.cost != 0:
|
|
249
|
+
return (self.net_gain / self.cost) * 100
|
|
250
|
+
|
|
251
|
+
if self.last_reported_price is None:
|
|
252
|
+
return 0
|
|
253
|
+
|
|
254
|
+
cost = self.available_amount * self.open_price
|
|
255
|
+
gain = (self.available_amount * self.last_reported_price) - cost
|
|
256
|
+
gain += self.net_gain
|
|
257
|
+
|
|
258
|
+
if cost != 0:
|
|
259
|
+
return (gain / cost) * 100
|
|
260
|
+
|
|
261
|
+
return 0
|
|
262
|
+
|
|
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
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
"id": self.id,
|
|
280
|
+
"orders": [
|
|
281
|
+
order.to_dict(datetime_format=datetime_format)
|
|
282
|
+
for order in self.orders
|
|
283
|
+
],
|
|
284
|
+
"target_symbol": self.target_symbol,
|
|
285
|
+
"trading_symbol": self.trading_symbol,
|
|
286
|
+
"status": self.status,
|
|
287
|
+
"amount": self.amount,
|
|
288
|
+
"remaining": self.remaining if self.remaining is not None else 0,
|
|
289
|
+
"open_price": self.open_price,
|
|
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 {},
|
|
307
|
+
}
|
|
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
|
+
|
|
366
|
+
def __repr__(self):
|
|
367
|
+
return self.repr(
|
|
368
|
+
id=self.id,
|
|
369
|
+
symbol=self.symbol,
|
|
370
|
+
target_symbol=self.target_symbol,
|
|
371
|
+
trading_symbol=self.trading_symbol,
|
|
372
|
+
status=self.status,
|
|
373
|
+
amount=self.amount,
|
|
374
|
+
available_amount=self.available_amount,
|
|
375
|
+
filled_amount=self.filled_amount,
|
|
376
|
+
remaining=self.remaining,
|
|
377
|
+
open_price=self.open_price,
|
|
378
|
+
opened_at=self.opened_at,
|
|
379
|
+
closed_at=self.closed_at,
|
|
380
|
+
net_gain=self.net_gain,
|
|
381
|
+
last_reported_price=self.last_reported_price,
|
|
382
|
+
updated_at=self.updated_at,
|
|
383
|
+
metadata=self.metadata,
|
|
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
|