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,9 +1,12 @@
|
|
|
1
|
-
from typing import List
|
|
1
|
+
from typing import List, Any, Union
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from investing_algorithm_framework.domain.
|
|
5
|
-
|
|
4
|
+
from investing_algorithm_framework.domain.exceptions import \
|
|
5
|
+
ImproperlyConfigured
|
|
6
6
|
from investing_algorithm_framework.domain.models.time_frame import TimeFrame
|
|
7
|
+
from investing_algorithm_framework.domain.models.data.data_type import DataType
|
|
8
|
+
from investing_algorithm_framework.domain.models.data.data_source import \
|
|
9
|
+
DataSource
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class DataProvider(ABC):
|
|
@@ -13,20 +16,16 @@ class DataProvider(ABC):
|
|
|
13
16
|
algorithms.
|
|
14
17
|
|
|
15
18
|
Attributes:
|
|
16
|
-
data_type (
|
|
19
|
+
data_type (DataType): The type of data to be
|
|
17
20
|
fetched (e.g., OHLCV, TICKER, CUSTOM_DATA).
|
|
18
|
-
|
|
21
|
+
symbol (Optional[List[str]]): A list supported symbols that the
|
|
19
22
|
data provider can provide data for. The framework will use this
|
|
20
23
|
list when searching for a data provider for a specific symbol.
|
|
21
24
|
Example: ["AAPL/USD", "GOOGL/USD", "MSFT/USD"]
|
|
22
|
-
markets (Optional[List[str]]): A list supported markets that the
|
|
23
|
-
data provider can provide data for. The framework will use this
|
|
24
|
-
list when searching for a data provider for a specific market.
|
|
25
|
-
Example: ["BINANCE", "COINBASE", "KRAKEN"]
|
|
26
25
|
priority (int): The priority of the data provider. The lower the
|
|
27
26
|
number, the higher the priority. The framework will use this
|
|
28
27
|
priority when searching for a data provider for a specific symbol.
|
|
29
|
-
Example: 0 is the highest priority, 1 is the second
|
|
28
|
+
Example: 0 is the highest priority, 1 is the second-highest
|
|
30
29
|
priority, etc. This is useful when multiple data providers
|
|
31
30
|
support the same symbol or market. The framework will use the
|
|
32
31
|
data provider with the highest priority.
|
|
@@ -40,53 +39,102 @@ class DataProvider(ABC):
|
|
|
40
39
|
for the data. This is useful for data providers that support
|
|
41
40
|
saving data to a file
|
|
42
41
|
"""
|
|
42
|
+
data_type: DataType = None
|
|
43
|
+
data_provider_identifier: str = None
|
|
43
44
|
|
|
44
45
|
def __init__(
|
|
45
46
|
self,
|
|
46
|
-
|
|
47
|
+
data_provider_identifier: str = None,
|
|
48
|
+
data_type: str = None,
|
|
47
49
|
symbol: str = None,
|
|
48
50
|
market: str = None,
|
|
49
|
-
markets: list = None,
|
|
50
51
|
priority: int = 0,
|
|
51
52
|
time_frame=None,
|
|
52
53
|
window_size=None,
|
|
53
54
|
storage_path=None,
|
|
55
|
+
storage_directory=None,
|
|
56
|
+
config=None,
|
|
54
57
|
):
|
|
55
58
|
"""
|
|
56
|
-
Initializes the DataProvider
|
|
59
|
+
Initializes the DataProvider. The data provider should be defined
|
|
60
|
+
with a data_type and a data_provider_identifier. The data_type
|
|
61
|
+
should be a valid DataType, and the data_provider_identifier
|
|
62
|
+
should be a unique identifier for the data provider.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
data_provider_identifier (str): The unique identifier for the
|
|
66
|
+
data provider. This is used to identify the data provider
|
|
67
|
+
in the framework. It should be a unique string that identifies
|
|
68
|
+
the data provider. Example: "binance",
|
|
69
|
+
"coinbase", "custom_feed_data"
|
|
70
|
+
data_type (str): The type of data to be fetched
|
|
71
|
+
(e.g., "OHLCV", "TICKER", "CUSTOM_DATA")
|
|
72
|
+
symbol (str): The symbol to fetch data for. This is optional and
|
|
73
|
+
can be set later. Example: "AAPL/USD", "BTC/USD"
|
|
74
|
+
market (str): The market to fetch data for.
|
|
75
|
+
This is optional and can be set later.
|
|
76
|
+
Example: "BINANCE", "COINBASE"
|
|
77
|
+
priority (int): The priority of the data provider. The lower the
|
|
78
|
+
number, the higher the priority. This is useful when multiple
|
|
79
|
+
data providers support the same symbol or market. The framework
|
|
80
|
+
will use the data provider with the highest priority.
|
|
81
|
+
Example: 0 is the highest priority, 1 is the second-highest
|
|
82
|
+
priority, etc.
|
|
83
|
+
time_frame (str): The time frame for the data. This is optional and
|
|
84
|
+
can be set later. This is useful for data providers
|
|
85
|
+
that support multiple time frames.
|
|
86
|
+
Example: "1m", "5m", "1h", "1d"
|
|
87
|
+
window_size (int): The window size for the data. This is optional
|
|
88
|
+
and can be set later. This is useful for data providers that
|
|
89
|
+
support multiple window sizes. Example: 100, 200, 500
|
|
90
|
+
storage_path (str): The path to the storage location for the data.
|
|
91
|
+
This is optional and can be set later. This is useful for data
|
|
92
|
+
providers that support saving data to a file.
|
|
93
|
+
Example: "/path/to/data"
|
|
57
94
|
"""
|
|
58
95
|
self._data_type = None
|
|
59
96
|
self._time_frame = None
|
|
60
97
|
|
|
61
98
|
if data_type is not None:
|
|
62
|
-
self.
|
|
99
|
+
self.data_type = DataType.from_value(data_type)
|
|
63
100
|
|
|
64
101
|
if time_frame is not None:
|
|
65
102
|
self.time_frame = TimeFrame.from_value(time_frame)
|
|
66
103
|
|
|
104
|
+
if data_provider_identifier is not None:
|
|
105
|
+
self.data_provider_identifier = data_provider_identifier
|
|
106
|
+
|
|
67
107
|
self.symbol = symbol
|
|
108
|
+
|
|
109
|
+
if self.symbol is not None:
|
|
110
|
+
self.symbol = self.symbol.upper()
|
|
111
|
+
|
|
68
112
|
self.market = market
|
|
69
|
-
|
|
113
|
+
|
|
114
|
+
if self.market is not None:
|
|
115
|
+
self.market = self.market.upper()
|
|
116
|
+
|
|
70
117
|
self.priority = priority
|
|
118
|
+
self._config = config
|
|
71
119
|
self.window_size = window_size
|
|
72
120
|
self.storage_path = storage_path
|
|
121
|
+
self.storage_directory = storage_directory
|
|
73
122
|
self._market_credentials = None
|
|
74
123
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
124
|
+
# Check if the data provider is properly configured
|
|
125
|
+
if self.data_type is None:
|
|
126
|
+
raise ImproperlyConfigured(
|
|
127
|
+
"DataProvider must be initialized "
|
|
128
|
+
"with a data_type. Either pass it as a parameter in "
|
|
129
|
+
"the constructor or set it as a class attribute."
|
|
130
|
+
)
|
|
78
131
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return self._time_frame
|
|
86
|
-
|
|
87
|
-
@time_frame.setter
|
|
88
|
-
def time_frame(self, value):
|
|
89
|
-
self._time_frame = TimeFrame.from_value(value)
|
|
132
|
+
if self.data_provider_identifier is None:
|
|
133
|
+
raise ImproperlyConfigured(
|
|
134
|
+
"DataProvider must be initialized with a "
|
|
135
|
+
"data_provider_identifier. Either pass it as a parameter "
|
|
136
|
+
"in the constructor or set it as a class attribute."
|
|
137
|
+
)
|
|
90
138
|
|
|
91
139
|
@property
|
|
92
140
|
def market_credentials(self):
|
|
@@ -124,67 +172,163 @@ class DataProvider(ABC):
|
|
|
124
172
|
@abstractmethod
|
|
125
173
|
def has_data(
|
|
126
174
|
self,
|
|
127
|
-
|
|
128
|
-
symbol: str = None,
|
|
129
|
-
market: str = None,
|
|
130
|
-
time_frame: str = None,
|
|
175
|
+
data_source: DataSource,
|
|
131
176
|
start_date: datetime = None,
|
|
132
177
|
end_date: datetime = None,
|
|
133
|
-
window_size=None,
|
|
134
178
|
) -> bool:
|
|
135
179
|
"""
|
|
136
180
|
Checks if the data provider has data for the given parameters.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
data_source (DataSource): The data source specification that
|
|
184
|
+
matches a data provider.
|
|
185
|
+
start_date (datetime): The start date for the data.
|
|
186
|
+
end_date (datetime): The end date for the data.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
bool: True if the data provider has data for the given parameters,
|
|
137
190
|
"""
|
|
138
191
|
raise NotImplementedError("Subclasses should implement this method.")
|
|
139
192
|
|
|
140
193
|
@abstractmethod
|
|
141
194
|
def get_data(
|
|
142
195
|
self,
|
|
143
|
-
data_type: str = None,
|
|
144
196
|
date: datetime = None,
|
|
145
|
-
symbol: str = None,
|
|
146
|
-
market: str = None,
|
|
147
|
-
time_frame: str = None,
|
|
148
197
|
start_date: datetime = None,
|
|
149
198
|
end_date: datetime = None,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
pandas=False,
|
|
153
|
-
save: bool = True,
|
|
154
|
-
):
|
|
199
|
+
save: bool = False,
|
|
200
|
+
) -> Any:
|
|
155
201
|
"""
|
|
156
202
|
Fetches data for a given symbol and date range.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
start_date (datetime): The start date for the data.
|
|
206
|
+
end_date (datetime): The end date for the data.
|
|
207
|
+
date (datetime): The specific date for which to fetch data.
|
|
208
|
+
save (bool): Whether to save the data to the storage path.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Any: The data for the given symbol and date range.
|
|
212
|
+
This can be a DataFrame, a list, or any other data structure
|
|
213
|
+
depending on the implementation.
|
|
157
214
|
"""
|
|
158
215
|
raise NotImplementedError("Subclasses should implement this method.")
|
|
159
216
|
|
|
160
217
|
@abstractmethod
|
|
161
|
-
def
|
|
218
|
+
def prepare_backtest_data(
|
|
162
219
|
self,
|
|
163
220
|
backtest_start_date,
|
|
164
221
|
backtest_end_date,
|
|
165
|
-
symbol: str = None,
|
|
166
|
-
market: str = None,
|
|
167
|
-
time_frame: str = None,
|
|
168
|
-
window_size=None,
|
|
169
222
|
) -> None:
|
|
170
223
|
"""
|
|
171
224
|
Prepares backtest data for a given symbol and date range.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
backtest_start_date (datetime): The start date for the
|
|
228
|
+
backtest data.
|
|
229
|
+
backtest_end_date (datetime): The end date for the
|
|
230
|
+
backtest data.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
None
|
|
172
234
|
"""
|
|
173
235
|
raise NotImplementedError("Subclasses should implement this method.")
|
|
174
236
|
|
|
175
237
|
@abstractmethod
|
|
176
238
|
def get_backtest_data(
|
|
177
239
|
self,
|
|
178
|
-
|
|
179
|
-
symbol: str = None,
|
|
180
|
-
market: str = None,
|
|
181
|
-
time_frame: str = None,
|
|
240
|
+
backtest_index_date: datetime,
|
|
182
241
|
backtest_start_date: datetime = None,
|
|
183
242
|
backtest_end_date: datetime = None,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
) -> None:
|
|
243
|
+
data_source: DataSource = None,
|
|
244
|
+
) -> Any:
|
|
187
245
|
"""
|
|
188
|
-
Fetches backtest data for a given
|
|
246
|
+
Fetches backtest data for a given datasource
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
backtest_index_date (datetime): The date for which to fetch
|
|
250
|
+
backtest data.
|
|
251
|
+
backtest_start_date (datetime): The start date for the
|
|
252
|
+
backtest data.
|
|
253
|
+
backtest_end_date (datetime): The end date for the
|
|
254
|
+
backtest data.
|
|
255
|
+
data_source (Optional[DataSource]): The data source
|
|
256
|
+
specification that is used to fetch the data.
|
|
257
|
+
This param is optional and can be used to
|
|
258
|
+
help identify errors in data fetching.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Any: The data for the given symbol and date range.
|
|
262
|
+
This can be a DataFrame, a list, or any other data structure
|
|
263
|
+
depending on the implementation.
|
|
264
|
+
"""
|
|
265
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
266
|
+
|
|
267
|
+
@abstractmethod
|
|
268
|
+
def copy(self, data_source: DataSource) -> "DataProvider":
|
|
269
|
+
"""
|
|
270
|
+
Returns a copy of the data provider instance based on a
|
|
271
|
+
given data source. The data source is previously matched
|
|
272
|
+
with the 'has_data' method. Then a new instance of the data
|
|
273
|
+
provider must be registered in the framework so that each
|
|
274
|
+
data source has its own instance of the data provider.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
data_source (DataSource): The data source specification that
|
|
278
|
+
matches a data provider.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
DataProvider: A new instance of the data provider with the same
|
|
282
|
+
configuration.
|
|
283
|
+
"""
|
|
284
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
285
|
+
|
|
286
|
+
@abstractmethod
|
|
287
|
+
def get_number_of_data_points(
|
|
288
|
+
self,
|
|
289
|
+
start_date: datetime,
|
|
290
|
+
end_date: datetime,
|
|
291
|
+
) -> int:
|
|
292
|
+
"""
|
|
293
|
+
Returns the number of data points available between the
|
|
294
|
+
given start and end dates.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
start_date (datetime): The start date for the data points.
|
|
298
|
+
end_date (datetime): The end date for the data points.
|
|
299
|
+
Returns:
|
|
300
|
+
int: The number of data points available between the
|
|
301
|
+
given start and end dates.
|
|
302
|
+
"""
|
|
303
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
304
|
+
|
|
305
|
+
@abstractmethod
|
|
306
|
+
def get_missing_data_dates(
|
|
307
|
+
self,
|
|
308
|
+
start_date: datetime,
|
|
309
|
+
end_date: datetime,
|
|
310
|
+
) -> List[datetime]:
|
|
311
|
+
"""
|
|
312
|
+
Returns a list of dates for which data is missing between the
|
|
313
|
+
given start and end dates.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
start_date (datetime): The start date for checking missing data.
|
|
317
|
+
end_date (datetime): The end date for checking missing data.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
List[datetime]: A list of dates for which data is missing
|
|
321
|
+
between the given start and end dates.
|
|
322
|
+
"""
|
|
323
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
324
|
+
|
|
325
|
+
@abstractmethod
|
|
326
|
+
def get_data_source_file_path(self) -> Union[str, None]:
|
|
327
|
+
"""
|
|
328
|
+
Returns the file path for the given data source if applicable.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Union[str, None]: The file path for the data source or None
|
|
332
|
+
if not applicable.
|
|
189
333
|
"""
|
|
190
334
|
raise NotImplementedError("Subclasses should implement this method.")
|
|
@@ -49,7 +49,14 @@ class ImproperlyConfigured(Exception):
|
|
|
49
49
|
class OperationalException(Exception):
|
|
50
50
|
"""
|
|
51
51
|
Class OperationalException: Exception class indicating a problem occurred
|
|
52
|
-
during running of the investing_algorithm_framework
|
|
52
|
+
during running of the investing_algorithm_framework.
|
|
53
|
+
|
|
54
|
+
This exception is used to indicate that an error occurred during the
|
|
55
|
+
operation of the investing_algorithm_framework, such as during a backtest,
|
|
56
|
+
strategy execution, or any other operational aspect of the framework.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
message (str): The error message to be returned in the response.
|
|
53
60
|
"""
|
|
54
61
|
def __init__(self, message) -> None:
|
|
55
62
|
super(OperationalException, self).__init__(message)
|
|
@@ -77,3 +84,29 @@ class NetworkError(Exception):
|
|
|
77
84
|
"status": "error",
|
|
78
85
|
"message": self.error_message
|
|
79
86
|
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class DataError(Exception):
|
|
90
|
+
"""
|
|
91
|
+
Class DataError: Exception class indicating a problem occurred
|
|
92
|
+
during data retrieval or processing
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
message,
|
|
98
|
+
data_source_file_path: str = None,
|
|
99
|
+
number_of_missing_data_points: int = None,
|
|
100
|
+
total_number_of_data_points: int = None,
|
|
101
|
+
) -> None:
|
|
102
|
+
super(DataError, self).__init__(message)
|
|
103
|
+
self.error_message = message
|
|
104
|
+
self.data_source_file_path = data_source_file_path
|
|
105
|
+
self.number_of_missing_data_points = number_of_missing_data_points
|
|
106
|
+
self.total_number_of_data_points = total_number_of_data_points
|
|
107
|
+
|
|
108
|
+
def to_response(self):
|
|
109
|
+
return {
|
|
110
|
+
"status": "error",
|
|
111
|
+
"message": self.error_message
|
|
112
|
+
}
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
from .app_mode import AppMode
|
|
2
|
-
from .backtesting import BacktestResult, BacktestPosition, \
|
|
3
|
-
BacktestDateRange
|
|
4
2
|
from .market import MarketCredential
|
|
5
3
|
from .order import OrderStatus, OrderSide, OrderType, Order
|
|
6
4
|
from .portfolio import PortfolioConfiguration, Portfolio, PortfolioSnapshot
|
|
7
|
-
from .position import Position, PositionSnapshot
|
|
5
|
+
from .position import Position, PositionSnapshot, PositionSize
|
|
8
6
|
from .strategy_profile import StrategyProfile
|
|
9
7
|
from .time_frame import TimeFrame
|
|
10
8
|
from .time_interval import TimeInterval
|
|
11
9
|
from .time_unit import TimeUnit
|
|
12
|
-
from .trade import Trade, TradeStatus, TradeStopLoss, TradeTakeProfit
|
|
13
|
-
TradeRiskType
|
|
14
|
-
from .trading_data_types import TradingDataType
|
|
15
|
-
from .trading_time_frame import TradingTimeFrame
|
|
16
|
-
from .date_range import DateRange
|
|
17
|
-
from .market_data_type import MarketDataType
|
|
18
|
-
from .data_source import DataSource
|
|
10
|
+
from .trade import Trade, TradeStatus, TradeStopLoss, TradeTakeProfit
|
|
19
11
|
from .snapshot_interval import SnapshotInterval
|
|
20
12
|
from .event import Event
|
|
13
|
+
from .data import DataSource, DataType
|
|
14
|
+
from .risk_rules import TakeProfitRule, StopLossRule
|
|
21
15
|
|
|
22
16
|
__all__ = [
|
|
23
17
|
"OrderStatus",
|
|
@@ -27,28 +21,25 @@ __all__ = [
|
|
|
27
21
|
"TimeFrame",
|
|
28
22
|
"TimeInterval",
|
|
29
23
|
"TimeUnit",
|
|
30
|
-
"TradingTimeFrame",
|
|
31
|
-
"TradingDataType",
|
|
32
24
|
"PortfolioConfiguration",
|
|
33
25
|
"Position",
|
|
34
26
|
"Portfolio",
|
|
35
|
-
"BacktestResult",
|
|
36
27
|
"PositionSnapshot",
|
|
37
28
|
"PortfolioSnapshot",
|
|
38
29
|
"StrategyProfile",
|
|
39
|
-
"BacktestPosition",
|
|
40
30
|
"Trade",
|
|
41
31
|
"MarketCredential",
|
|
42
32
|
"TradeStatus",
|
|
43
|
-
"
|
|
33
|
+
"DataType",
|
|
34
|
+
"AppMode",
|
|
35
|
+
"DataSource",
|
|
44
36
|
"AppMode",
|
|
45
|
-
"BacktestDateRange",
|
|
46
|
-
"DateRange",
|
|
47
|
-
"MarketDataType",
|
|
48
37
|
"TradeStopLoss",
|
|
49
38
|
"TradeTakeProfit",
|
|
50
|
-
"TradeRiskType",
|
|
51
39
|
"DataSource",
|
|
52
40
|
"SnapshotInterval",
|
|
53
41
|
"Event",
|
|
42
|
+
"PositionSize",
|
|
43
|
+
"StopLossRule",
|
|
44
|
+
"TakeProfitRule",
|
|
54
45
|
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime, timezone, timedelta
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
from dateutil import parser
|
|
6
|
+
|
|
7
|
+
from investing_algorithm_framework.domain.models.time_frame import TimeFrame
|
|
8
|
+
from .data_type import DataType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class DataSource:
|
|
13
|
+
"""
|
|
14
|
+
Base class for data sources.
|
|
15
|
+
"""
|
|
16
|
+
identifier: str = None
|
|
17
|
+
data_provider_identifier: str = None
|
|
18
|
+
data_type: Union[DataType, str] = None
|
|
19
|
+
symbol: str = None
|
|
20
|
+
window_size: int = None
|
|
21
|
+
time_frame: Union[TimeFrame, str] = None
|
|
22
|
+
market: str = None
|
|
23
|
+
storage_path: str = None
|
|
24
|
+
pandas: bool = False
|
|
25
|
+
date: Union[datetime, None] = None
|
|
26
|
+
start_date: Union[datetime, None] = None
|
|
27
|
+
end_date: Union[datetime, None] = None
|
|
28
|
+
save: bool = False
|
|
29
|
+
|
|
30
|
+
def __post_init__(self):
|
|
31
|
+
# Convert data_type and time_frame to their respective enums if needed
|
|
32
|
+
if isinstance(self.data_type, str):
|
|
33
|
+
object.__setattr__(self, 'data_type',
|
|
34
|
+
DataType.from_string(self.data_type))
|
|
35
|
+
|
|
36
|
+
if isinstance(self.time_frame, str):
|
|
37
|
+
object.__setattr__(self, 'time_frame',
|
|
38
|
+
TimeFrame.from_string(self.time_frame))
|
|
39
|
+
|
|
40
|
+
start_date = self.start_date
|
|
41
|
+
end_date = self.end_date
|
|
42
|
+
|
|
43
|
+
# Parse the start_date if it is a string and
|
|
44
|
+
# make sure its set to timezone utc
|
|
45
|
+
if start_date is None:
|
|
46
|
+
|
|
47
|
+
if isinstance(self.start_date, str):
|
|
48
|
+
start_date = parser.parse(start_date)
|
|
49
|
+
|
|
50
|
+
if start_date is not None:
|
|
51
|
+
object.__setattr__(
|
|
52
|
+
self,
|
|
53
|
+
'start_date',
|
|
54
|
+
start_date.replace(tzinfo=timezone.utc)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Parse the end_date if it is a string and
|
|
58
|
+
# make sure its set to timezone utc
|
|
59
|
+
if end_date is None:
|
|
60
|
+
|
|
61
|
+
if isinstance(self.end_date, str):
|
|
62
|
+
end_date = parser.parse(end_date)
|
|
63
|
+
|
|
64
|
+
if end_date is not None:
|
|
65
|
+
object.__setattr__(
|
|
66
|
+
self,
|
|
67
|
+
'end_date',
|
|
68
|
+
end_date.replace(tzinfo=timezone.utc)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if self.market is not None:
|
|
72
|
+
object.__setattr__(self, 'market', self.market.upper())
|
|
73
|
+
|
|
74
|
+
if self.symbol is not None:
|
|
75
|
+
object.__setattr__(self, 'symbol', self.symbol.upper())
|
|
76
|
+
|
|
77
|
+
def get_identifier(self):
|
|
78
|
+
"""
|
|
79
|
+
Returns the identifier or creates a unique identifier for the
|
|
80
|
+
data source based on its attributes.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
if self.identifier is not None:
|
|
84
|
+
return self.identifier
|
|
85
|
+
|
|
86
|
+
if DataType.OHLCV.equals(self.data_type):
|
|
87
|
+
return (f"{self.data_type.value}_{self.market}_"
|
|
88
|
+
f"{self.symbol}_{self.time_frame.value}")
|
|
89
|
+
|
|
90
|
+
elif DataType.CUSTOM.equals(self.data_type):
|
|
91
|
+
identifier = "CUSTOM"
|
|
92
|
+
|
|
93
|
+
if self.symbol is not None:
|
|
94
|
+
identifier += f"_{self.symbol}"
|
|
95
|
+
|
|
96
|
+
if self.time_frame is not None:
|
|
97
|
+
identifier += f"_{self.time_frame.value}"
|
|
98
|
+
|
|
99
|
+
if self.market is not None:
|
|
100
|
+
identifier += f"_{self.market}"
|
|
101
|
+
|
|
102
|
+
if self.window_size is not None:
|
|
103
|
+
identifier += f"_{self.window_size}"
|
|
104
|
+
|
|
105
|
+
return identifier
|
|
106
|
+
|
|
107
|
+
def to_dict(self):
|
|
108
|
+
"""
|
|
109
|
+
Converts the DataSource instance to a dictionary.
|
|
110
|
+
"""
|
|
111
|
+
non_null_attributes = {
|
|
112
|
+
key: value for key, value in self.__dict__.items()
|
|
113
|
+
if value is not None
|
|
114
|
+
}
|
|
115
|
+
# Convert DataType and TimeFrame to their string representations
|
|
116
|
+
if self.data_type is not None:
|
|
117
|
+
non_null_attributes['data_type'] = self.data_type.value
|
|
118
|
+
if self.time_frame is not None:
|
|
119
|
+
non_null_attributes['time_frame'] = self.time_frame.value
|
|
120
|
+
|
|
121
|
+
return non_null_attributes
|
|
122
|
+
|
|
123
|
+
def __repr__(self):
|
|
124
|
+
return (
|
|
125
|
+
f"DataSource(identifier={self.identifier}, "
|
|
126
|
+
f"data_provider_identifier={self.data_provider_identifier}, "
|
|
127
|
+
f"data_type={self.data_type}, symbol={self.symbol}, "
|
|
128
|
+
f"window_size={self.window_size}, time_frame={self.time_frame}, "
|
|
129
|
+
f"market={self.market}, storage_path={self.storage_path}, "
|
|
130
|
+
f"pandas={self.pandas}, date={self.date}, "
|
|
131
|
+
f"start_date={self.start_date}, end_date={self.end_date}, "
|
|
132
|
+
f"save={self.save})"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def __eq__(self, other):
|
|
136
|
+
"""
|
|
137
|
+
Compares two DataSource instances for equality.
|
|
138
|
+
|
|
139
|
+
OHLCV data sources are considered equal if they have:
|
|
140
|
+
- The same data_type (OHLCV), symbol, time_frame, and market.
|
|
141
|
+
- If no market and timeframe is specified, then
|
|
142
|
+
they are considered equal for the same symbol
|
|
143
|
+
and data_type.
|
|
144
|
+
"""
|
|
145
|
+
if DataType.OHLCV.equals(self.data_type):
|
|
146
|
+
|
|
147
|
+
if other.time_frame is None and other.window_size is None:
|
|
148
|
+
return (self.data_type == other.data_type and
|
|
149
|
+
self.symbol == other.symbol)
|
|
150
|
+
elif self.time_frame is None and self.window_size is None:
|
|
151
|
+
return (self.data_type == other.data_type and
|
|
152
|
+
self.symbol == other.symbol)
|
|
153
|
+
|
|
154
|
+
return (self.time_frame == other.time_frame and
|
|
155
|
+
self.market == other.market and
|
|
156
|
+
self.symbol == other.symbol and
|
|
157
|
+
self.data_type == other.data_type)
|
|
158
|
+
|
|
159
|
+
elif DataType.CUSTOM.equals(self.data_type):
|
|
160
|
+
return (self.data_type == other.data_type and
|
|
161
|
+
self.symbol == other.symbol and
|
|
162
|
+
self.window_size == other.window_size and
|
|
163
|
+
self.time_frame == other.time_frame and
|
|
164
|
+
self.market == other.market)
|
|
165
|
+
|
|
166
|
+
elif DataType.TICKER.equals(self.data_type):
|
|
167
|
+
return (self.data_type == other.data_type and
|
|
168
|
+
self.symbol == other.symbol and
|
|
169
|
+
self.market == other.market)
|
|
170
|
+
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
def create_start_date_data(self, index_date: datetime) -> datetime:
|
|
174
|
+
|
|
175
|
+
if self.window_size is None or self.time_frame is None:
|
|
176
|
+
return index_date
|
|
177
|
+
|
|
178
|
+
return index_date - \
|
|
179
|
+
(self.window_size * timedelta(
|
|
180
|
+
minutes=self.time_frame.amount_of_minutes
|
|
181
|
+
))
|
|
182
|
+
|
|
183
|
+
def get_number_of_required_data_points(
|
|
184
|
+
self, start_date: datetime, end_date: datetime
|
|
185
|
+
) -> int:
|
|
186
|
+
"""
|
|
187
|
+
Returns the number of data points required based on the given
|
|
188
|
+
attributes of the data source. If the required number of data points
|
|
189
|
+
can't be determined, it returns None.
|
|
190
|
+
|
|
191
|
+
E.g., for OHLCV data source, it
|
|
192
|
+
calculates the number of data points needed between the
|
|
193
|
+
start_date and end_date based on the time frame.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
start_date (datetime): The start date for the data points.
|
|
197
|
+
end_date (datetime): The end date for the data points.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
int: The number of required data points, or None if it can't
|
|
201
|
+
be determined.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
if self.time_frame is None:
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
delta = end_date - start_date
|
|
208
|
+
total_minutes = delta.total_seconds() / 60
|
|
209
|
+
data_points = total_minutes / self.time_frame.amount_of_minutes
|
|
210
|
+
|
|
211
|
+
if self.window_size is not None:
|
|
212
|
+
data_points += self.window_size
|
|
213
|
+
|
|
214
|
+
return int(data_points)
|