investing-algorithm-framework 6.9.1__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 +147 -44
- investing_algorithm_framework/app/__init__.py +23 -6
- investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
- 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 +1322 -707
- investing_algorithm_framework/app/context.py +196 -88
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +16 -5
- investing_algorithm_framework/app/reporting/ascii.py +57 -202
- investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
- investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
- 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 +11 -26
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
- investing_algorithm_framework/app/reporting/generate.py +100 -114
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
- investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
- investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
- investing_algorithm_framework/app/strategy.py +315 -175
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/cli/cli.py +30 -12
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
- investing_algorithm_framework/cli/initialize_app.py +20 -1
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
- 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_requirements.txt.template +2 -2
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
- investing_algorithm_framework/create_app.py +3 -5
- investing_algorithm_framework/dependency_container.py +25 -39
- investing_algorithm_framework/domain/__init__.py +45 -38
- 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 +27 -0
- investing_algorithm_framework/domain/constants.py +6 -34
- investing_algorithm_framework/domain/data_provider.py +200 -56
- investing_algorithm_framework/domain/exceptions.py +34 -1
- investing_algorithm_framework/domain/models/__init__.py +10 -19
- investing_algorithm_framework/domain/models/base_model.py +0 -6
- 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/{market_data_type.py → data/data_type.py} +7 -7
- investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +34 -13
- investing_algorithm_framework/domain/models/order/order_status.py +1 -1
- investing_algorithm_framework/domain/models/order/order_type.py +1 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +9 -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 +0 -1
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +7 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +63 -1
- investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
- investing_algorithm_framework/domain/models/trade/trade.py +56 -32
- investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
- investing_algorithm_framework/domain/order_executor.py +19 -0
- investing_algorithm_framework/domain/portfolio_provider.py +20 -1
- investing_algorithm_framework/domain/services/__init__.py +0 -13
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +5 -1
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +17 -14
- investing_algorithm_framework/download_data.py +40 -10
- investing_algorithm_framework/infrastructure/__init__.py +13 -25
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
- investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
- 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 +81 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
- investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
- investing_algorithm_framework/services/__init__.py +105 -8
- investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
- investing_algorithm_framework/services/configuration_service.py +14 -4
- 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/{app/reporting → services}/metrics/__init__.py +48 -17
- investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
- investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
- investing_algorithm_framework/services/order_service/order_service.py +9 -71
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
- investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
- investing_algorithm_framework/services/repository_service.py +5 -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 +51 -29
- 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-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
- investing_algorithm_framework/app/reporting/evaluation.py +0 -243
- investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
- investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
- investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
- investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
- investing_algorithm_framework/domain/models/data_source.py +0 -21
- investing_algorithm_framework/domain/models/date_range.py +0 -64
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/services/observable.py +0 -51
- investing_algorithm_framework/domain/services/observer.py +0 -19
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
- investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
- 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 -322
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
- investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
- investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
- /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
|
@@ -1,141 +1,25 @@
|
|
|
1
|
-
from
|
|
2
|
-
from .time_unit import TimeUnit
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class StrategyProfile(BaseModel):
|
|
6
|
-
|
|
7
|
-
def __init__(
|
|
8
|
-
self,
|
|
9
|
-
strategy_id=None,
|
|
10
|
-
interval=None,
|
|
11
|
-
time_unit=None,
|
|
12
|
-
trading_time_frame=None,
|
|
13
|
-
trading_time_frame_start_date=None,
|
|
14
|
-
symbols=None,
|
|
15
|
-
market=None,
|
|
16
|
-
backtest_start_date_data=None,
|
|
17
|
-
backtest_data_index_date=None,
|
|
18
|
-
trading_data_type=None,
|
|
19
|
-
trading_data_types=None,
|
|
20
|
-
market_data_sources=None,
|
|
21
|
-
):
|
|
22
|
-
self._strategy_id = strategy_id
|
|
23
|
-
self._interval = interval
|
|
24
|
-
self._time_unit = time_unit
|
|
25
|
-
self._number_of_runs = 0
|
|
26
|
-
self._trading_time_frame = trading_time_frame
|
|
27
|
-
self._trading_time_frame_start_date = trading_time_frame_start_date
|
|
28
|
-
self._backtest_start_date_data = backtest_start_date_data
|
|
29
|
-
self._backtest_data_index_date = backtest_data_index_date
|
|
30
|
-
self._symbols = symbols
|
|
31
|
-
self._market = market
|
|
32
|
-
self._trading_data_type = trading_data_type
|
|
33
|
-
self._trading_data_types = trading_data_types
|
|
34
|
-
self._market_data_sources = market_data_sources
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def strategy_id(self):
|
|
38
|
-
return self._strategy_id
|
|
39
|
-
|
|
40
|
-
@strategy_id.setter
|
|
41
|
-
def strategy_id(self, strategy_id):
|
|
42
|
-
self._strategy_id = strategy_id
|
|
43
|
-
|
|
44
|
-
@property
|
|
45
|
-
def interval(self):
|
|
46
|
-
return self._interval
|
|
47
|
-
|
|
48
|
-
@interval.setter
|
|
49
|
-
def interval(self, value):
|
|
50
|
-
self._interval = value
|
|
51
|
-
|
|
52
|
-
@property
|
|
53
|
-
def time_unit(self):
|
|
54
|
-
return self._time_unit
|
|
55
|
-
|
|
56
|
-
@time_unit.setter
|
|
57
|
-
def time_unit(self, value):
|
|
58
|
-
self._time_unit = value
|
|
59
|
-
|
|
60
|
-
@property
|
|
61
|
-
def symbols(self):
|
|
62
|
-
return self._symbols
|
|
63
|
-
|
|
64
|
-
@property
|
|
65
|
-
def trading_time_frame(self):
|
|
66
|
-
return self._trading_time_frame
|
|
67
|
-
|
|
68
|
-
@property
|
|
69
|
-
def trading_time_frame_start_date(self):
|
|
70
|
-
return self._trading_time_frame_start_date
|
|
71
|
-
|
|
72
|
-
@property
|
|
73
|
-
def number_of_runs(self):
|
|
74
|
-
return self._number_of_runs
|
|
75
|
-
|
|
76
|
-
@property
|
|
77
|
-
def market(self):
|
|
78
|
-
return self._market
|
|
1
|
+
from dataclasses import dataclass
|
|
79
2
|
|
|
80
|
-
|
|
81
|
-
def symbols(self, value):
|
|
82
|
-
self._symbols = value
|
|
83
|
-
|
|
84
|
-
@market.setter
|
|
85
|
-
def market(self, value):
|
|
86
|
-
self._market = value
|
|
87
|
-
|
|
88
|
-
@number_of_runs.setter
|
|
89
|
-
def number_of_runs(self, value):
|
|
90
|
-
self._number_of_runs = value
|
|
91
|
-
|
|
92
|
-
@trading_time_frame.setter
|
|
93
|
-
def trading_time_frame(self, value):
|
|
94
|
-
self._trading_time_frame = value
|
|
95
|
-
|
|
96
|
-
@property
|
|
97
|
-
def backtest_start_date_data(self):
|
|
98
|
-
return self._backtest_start_date_data
|
|
99
|
-
|
|
100
|
-
@backtest_start_date_data.setter
|
|
101
|
-
def backtest_start_date_data(self, value):
|
|
102
|
-
self._backtest_start_date_data = value
|
|
103
|
-
|
|
104
|
-
@property
|
|
105
|
-
def backtest_data_index_date(self):
|
|
106
|
-
return self._backtest_data_index_date
|
|
107
|
-
|
|
108
|
-
@backtest_data_index_date.setter
|
|
109
|
-
def backtest_data_index_date(self, value):
|
|
110
|
-
self._backtest_data_index_date = value
|
|
111
|
-
|
|
112
|
-
@property
|
|
113
|
-
def trading_data_type(self):
|
|
114
|
-
return self._trading_data_type
|
|
115
|
-
|
|
116
|
-
@trading_data_type.setter
|
|
117
|
-
def trading_data_type(self, value):
|
|
118
|
-
self._trading_data_type = value
|
|
119
|
-
|
|
120
|
-
@property
|
|
121
|
-
def trading_data_types(self):
|
|
122
|
-
|
|
123
|
-
if self.trading_data_type is not None:
|
|
124
|
-
return [self.trading_data_type]
|
|
125
|
-
|
|
126
|
-
return self._trading_data_types
|
|
127
|
-
|
|
128
|
-
@trading_data_types.setter
|
|
129
|
-
def trading_data_types(self, value):
|
|
130
|
-
self._trading_data_types = value
|
|
3
|
+
from .time_unit import TimeUnit
|
|
131
4
|
|
|
132
|
-
@property
|
|
133
|
-
def market_data_sources(self):
|
|
134
|
-
return self._market_data_sources
|
|
135
5
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class StrategyProfile:
|
|
8
|
+
"""
|
|
9
|
+
StrategyProfile class that represents the profile of a trading strategy.
|
|
10
|
+
"""
|
|
11
|
+
strategy_id: str = None
|
|
12
|
+
interval: int = None
|
|
13
|
+
time_unit: TimeUnit = None
|
|
14
|
+
trading_time_frame: str = None
|
|
15
|
+
trading_time_frame_start_date: str = None
|
|
16
|
+
backtest_start_date_data: str = None
|
|
17
|
+
backtest_data_index_date: str = None
|
|
18
|
+
symbols: list = None
|
|
19
|
+
market: str = None
|
|
20
|
+
trading_data_type: str = None
|
|
21
|
+
trading_data_types: list = None
|
|
22
|
+
data_sources: list = None
|
|
139
23
|
|
|
140
24
|
def get_runs_per_day(self):
|
|
141
25
|
|
|
@@ -147,19 +31,3 @@ class StrategyProfile(BaseModel):
|
|
|
147
31
|
return 1440 / self.interval
|
|
148
32
|
else:
|
|
149
33
|
return 24 / self.interval
|
|
150
|
-
|
|
151
|
-
def __repr__(self):
|
|
152
|
-
return self.repr(
|
|
153
|
-
strategy_id=self._strategy_id,
|
|
154
|
-
number_of_runs=self._number_of_runs,
|
|
155
|
-
trading_time_frame=self._trading_time_frame,
|
|
156
|
-
trading_time_frame_start_date=self._trading_time_frame_start_date,
|
|
157
|
-
symbols=self._symbols,
|
|
158
|
-
time_unit=self.time_unit,
|
|
159
|
-
interval=self.interval,
|
|
160
|
-
market=self._market,
|
|
161
|
-
backtest_start_date_data=self._backtest_start_date_data,
|
|
162
|
-
backtest_data_index_date=self._backtest_data_index_date,
|
|
163
|
-
trading_data_type=self._trading_data_type,
|
|
164
|
-
trading_data_types=self._trading_data_types,
|
|
165
|
-
)
|
|
@@ -124,6 +124,13 @@ class TimeFrame(Enum):
|
|
|
124
124
|
if self.equals(TimeFrame.ONE_MONTH):
|
|
125
125
|
return 40320
|
|
126
126
|
|
|
127
|
+
if self.equals(TimeFrame.ONE_YEAR):
|
|
128
|
+
return 525600
|
|
129
|
+
|
|
130
|
+
raise ValueError(
|
|
131
|
+
f"Could not determine amount of minutes for {self.value}"
|
|
132
|
+
)
|
|
133
|
+
|
|
127
134
|
# Add comparison methods for ordering
|
|
128
135
|
def __lt__(self, other):
|
|
129
136
|
if isinstance(other, TimeFrame):
|
|
@@ -25,6 +25,39 @@ class TimeInterval(Enum):
|
|
|
25
25
|
f"Could not convert {value} to TimeInterval"
|
|
26
26
|
)
|
|
27
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
|
+
|
|
28
61
|
def equals(self, other):
|
|
29
62
|
|
|
30
63
|
if isinstance(other, Enum):
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
from datetime import timedelta
|
|
2
2
|
from enum import Enum
|
|
3
|
+
from investing_algorithm_framework.domain.exceptions import \
|
|
4
|
+
OperationalException
|
|
3
5
|
|
|
4
6
|
|
|
5
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
|
+
"""
|
|
6
14
|
SECOND = "SECOND"
|
|
7
15
|
MINUTE = "MINUTE"
|
|
8
16
|
HOUR = "HOUR"
|
|
@@ -18,12 +26,50 @@ class TimeUnit(Enum):
|
|
|
18
26
|
if value.upper() == entry.value:
|
|
19
27
|
return entry
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
raise OperationalException(
|
|
30
|
+
f"Could not convert string {value} to time unit"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
raise OperationalException(
|
|
22
34
|
f"Could not convert value {value} to time unit," +
|
|
23
35
|
" please make sure that the value is either of type string or" +
|
|
24
36
|
f"TimeUnit. Its current type is {type(value)}"
|
|
25
37
|
)
|
|
26
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
|
+
|
|
27
73
|
def equals(self, other):
|
|
28
74
|
|
|
29
75
|
if isinstance(other, Enum):
|
|
@@ -85,3 +131,19 @@ class TimeUnit(Enum):
|
|
|
85
131
|
|
|
86
132
|
if TimeUnit.DAY.equals(self.value):
|
|
87
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}")
|
|
@@ -2,12 +2,10 @@ from .trade import Trade
|
|
|
2
2
|
from .trade_status import TradeStatus
|
|
3
3
|
from .trade_stop_loss import TradeStopLoss
|
|
4
4
|
from .trade_take_profit import TradeTakeProfit
|
|
5
|
-
from .trade_risk_type import TradeRiskType
|
|
6
5
|
|
|
7
6
|
__all__ = [
|
|
8
7
|
"Trade",
|
|
9
8
|
"TradeStatus",
|
|
10
9
|
"TradeStopLoss",
|
|
11
10
|
"TradeTakeProfit",
|
|
12
|
-
"TradeRiskType",
|
|
13
11
|
]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from dateutil.parser import parse
|
|
2
|
+
from datetime import timezone
|
|
2
3
|
|
|
3
4
|
from investing_algorithm_framework.domain.models.base_model import BaseModel
|
|
4
5
|
from investing_algorithm_framework.domain.models.order import OrderSide, Order
|
|
@@ -40,9 +41,10 @@ class Trade(BaseModel):
|
|
|
40
41
|
last_reported_price: float, the last reported price of the trade
|
|
41
42
|
last_reported_price_datetime: datetime, the datetime when the last
|
|
42
43
|
reported price was reported
|
|
43
|
-
created_at: datetime, the datetime when the trade was created
|
|
44
44
|
updated_at: datetime, the datetime when the trade was last updated
|
|
45
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.
|
|
46
48
|
"""
|
|
47
49
|
|
|
48
50
|
def __init__(
|
|
@@ -68,6 +70,7 @@ class Trade(BaseModel):
|
|
|
68
70
|
updated_at=None,
|
|
69
71
|
stop_losses=None,
|
|
70
72
|
take_profits=None,
|
|
73
|
+
metadata=None,
|
|
71
74
|
):
|
|
72
75
|
self.id = id
|
|
73
76
|
self.orders = orders
|
|
@@ -86,15 +89,16 @@ class Trade(BaseModel):
|
|
|
86
89
|
self.last_reported_price_datetime = last_reported_price_datetime
|
|
87
90
|
self.high_water_mark = high_water_mark
|
|
88
91
|
self.high_water_mark_datetime = high_water_mark_datetime
|
|
89
|
-
self.status = status
|
|
92
|
+
self.status = TradeStatus.from_value(status).value
|
|
90
93
|
self.updated_at = updated_at
|
|
91
94
|
self.stop_losses = stop_losses
|
|
92
95
|
self.take_profits = take_profits
|
|
96
|
+
self.metadata = metadata if metadata is not None else {}
|
|
93
97
|
|
|
94
98
|
def update(self, data):
|
|
95
99
|
|
|
96
100
|
if "status" in data:
|
|
97
|
-
self.status = TradeStatus.from_value(data["status"])
|
|
101
|
+
self.status = TradeStatus.from_value(data["status"]).value
|
|
98
102
|
|
|
99
103
|
if TradeStatus.CLOSED.equals(self.status):
|
|
100
104
|
|
|
@@ -174,16 +178,25 @@ class Trade(BaseModel):
|
|
|
174
178
|
|
|
175
179
|
@property
|
|
176
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
|
+
"""
|
|
177
187
|
if TradeStatus.CLOSED.equals(self.status):
|
|
178
|
-
|
|
179
|
-
return self.net_gain - cost
|
|
188
|
+
return self.net_gain
|
|
180
189
|
|
|
181
190
|
if self.last_reported_price is None:
|
|
182
191
|
return 0
|
|
183
192
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
187
200
|
return gain
|
|
188
201
|
|
|
189
202
|
@property
|
|
@@ -196,7 +209,7 @@ class Trade(BaseModel):
|
|
|
196
209
|
|
|
197
210
|
if self.last_reported_price is not None:
|
|
198
211
|
gain = (
|
|
199
|
-
self.
|
|
212
|
+
self.available_amount *
|
|
200
213
|
(self.last_reported_price - self.open_price)
|
|
201
214
|
)
|
|
202
215
|
|
|
@@ -216,7 +229,7 @@ class Trade(BaseModel):
|
|
|
216
229
|
|
|
217
230
|
if self.last_reported_price is not None:
|
|
218
231
|
gain = (
|
|
219
|
-
self.
|
|
232
|
+
self.available_amount *
|
|
220
233
|
(self.last_reported_price - self.open_price)
|
|
221
234
|
)
|
|
222
235
|
|
|
@@ -231,31 +244,36 @@ class Trade(BaseModel):
|
|
|
231
244
|
def percentage_change(self):
|
|
232
245
|
|
|
233
246
|
if TradeStatus.CLOSED.equals(self.status):
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
247
|
+
|
|
248
|
+
if self.cost != 0:
|
|
249
|
+
return (self.net_gain / self.cost) * 100
|
|
237
250
|
|
|
238
251
|
if self.last_reported_price is None:
|
|
239
252
|
return 0
|
|
240
253
|
|
|
241
|
-
cost = self.
|
|
242
|
-
gain = (self.
|
|
254
|
+
cost = self.available_amount * self.open_price
|
|
255
|
+
gain = (self.available_amount * self.last_reported_price) - cost
|
|
243
256
|
gain += self.net_gain
|
|
244
|
-
|
|
257
|
+
|
|
258
|
+
if cost != 0:
|
|
259
|
+
return (gain / cost) * 100
|
|
260
|
+
|
|
261
|
+
return 0
|
|
245
262
|
|
|
246
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
|
|
247
270
|
|
|
248
|
-
if
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if self.updated_at else None
|
|
255
|
-
else:
|
|
256
|
-
opened_at = self.opened_at
|
|
257
|
-
closed_at = self.closed_at
|
|
258
|
-
updated_at = self.updated_at
|
|
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
|
|
259
277
|
|
|
260
278
|
return {
|
|
261
279
|
"id": self.id,
|
|
@@ -267,14 +285,14 @@ class Trade(BaseModel):
|
|
|
267
285
|
"trading_symbol": self.trading_symbol,
|
|
268
286
|
"status": self.status,
|
|
269
287
|
"amount": self.amount,
|
|
270
|
-
"remaining": self.remaining,
|
|
288
|
+
"remaining": self.remaining if self.remaining is not None else 0,
|
|
271
289
|
"open_price": self.open_price,
|
|
272
290
|
"last_reported_price": self.last_reported_price,
|
|
273
291
|
"opened_at": opened_at,
|
|
274
292
|
"closed_at": closed_at,
|
|
275
293
|
"updated_at": updated_at,
|
|
276
|
-
"net_gain": self.net_gain,
|
|
277
|
-
"cost": self.cost,
|
|
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,
|
|
278
296
|
"stop_losses": [
|
|
279
297
|
stop_loss.to_dict(datetime_format=datetime_format)
|
|
280
298
|
for stop_loss in self.stop_losses
|
|
@@ -283,6 +301,9 @@ class Trade(BaseModel):
|
|
|
283
301
|
take_profit.to_dict(datetime_format=datetime_format)
|
|
284
302
|
for take_profit in self.take_profits
|
|
285
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 {},
|
|
286
307
|
}
|
|
287
308
|
|
|
288
309
|
@staticmethod
|
|
@@ -320,7 +341,6 @@ class Trade(BaseModel):
|
|
|
320
341
|
Order.from_dict(order)
|
|
321
342
|
for order in data["orders"]
|
|
322
343
|
]
|
|
323
|
-
|
|
324
344
|
return Trade(
|
|
325
345
|
id=data.get("id", None),
|
|
326
346
|
orders=orders,
|
|
@@ -335,16 +355,18 @@ class Trade(BaseModel):
|
|
|
335
355
|
remaining=data.get("remaining", 0),
|
|
336
356
|
net_gain=data.get("net_gain", 0),
|
|
337
357
|
last_reported_price=data.get("last_reported_price"),
|
|
338
|
-
status=data["status"],
|
|
358
|
+
status=TradeStatus.from_value(data["status"]).value,
|
|
339
359
|
cost=data.get("cost", 0),
|
|
340
360
|
updated_at=updated_at,
|
|
341
361
|
stop_losses=stop_losses,
|
|
342
362
|
take_profits=take_profits,
|
|
363
|
+
metadata=data.get("metadata", {}),
|
|
343
364
|
)
|
|
344
365
|
|
|
345
366
|
def __repr__(self):
|
|
346
367
|
return self.repr(
|
|
347
368
|
id=self.id,
|
|
369
|
+
symbol=self.symbol,
|
|
348
370
|
target_symbol=self.target_symbol,
|
|
349
371
|
trading_symbol=self.trading_symbol,
|
|
350
372
|
status=self.status,
|
|
@@ -357,6 +379,8 @@ class Trade(BaseModel):
|
|
|
357
379
|
closed_at=self.closed_at,
|
|
358
380
|
net_gain=self.net_gain,
|
|
359
381
|
last_reported_price=self.last_reported_price,
|
|
382
|
+
updated_at=self.updated_at,
|
|
383
|
+
metadata=self.metadata,
|
|
360
384
|
)
|
|
361
385
|
|
|
362
386
|
def __lt__(self, other):
|
|
@@ -1,4 +1,6 @@
|
|
|
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):
|
|
@@ -15,7 +17,9 @@ class TradeStatus(Enum):
|
|
|
15
17
|
if value.upper() == status.value:
|
|
16
18
|
return status
|
|
17
19
|
|
|
18
|
-
raise
|
|
20
|
+
raise OperationalException(
|
|
21
|
+
f"Could not convert value: '{value}' to TradeStatus"
|
|
22
|
+
)
|
|
19
23
|
|
|
20
24
|
@staticmethod
|
|
21
25
|
def from_value(value):
|
|
@@ -28,7 +32,9 @@ class TradeStatus(Enum):
|
|
|
28
32
|
elif isinstance(value, str):
|
|
29
33
|
return TradeStatus.from_string(value)
|
|
30
34
|
|
|
31
|
-
raise
|
|
35
|
+
raise OperationalException(
|
|
36
|
+
f"Could not convert value: {value} to TradeStatus"
|
|
37
|
+
)
|
|
32
38
|
|
|
33
39
|
def equals(self, other):
|
|
34
40
|
return TradeStatus.from_value(other) == self
|