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,334 @@
|
|
|
1
|
+
from typing import List, Any, Union
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from investing_algorithm_framework.domain.exceptions import \
|
|
5
|
+
ImproperlyConfigured
|
|
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
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DataProvider(ABC):
|
|
13
|
+
"""
|
|
14
|
+
Abstract base class for data providers. The DataProvider class
|
|
15
|
+
is responsible for fetching and preparing data for trading
|
|
16
|
+
algorithms.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
data_type (DataType): The type of data to be
|
|
20
|
+
fetched (e.g., OHLCV, TICKER, CUSTOM_DATA).
|
|
21
|
+
symbol (Optional[List[str]]): A list supported symbols that the
|
|
22
|
+
data provider can provide data for. The framework will use this
|
|
23
|
+
list when searching for a data provider for a specific symbol.
|
|
24
|
+
Example: ["AAPL/USD", "GOOGL/USD", "MSFT/USD"]
|
|
25
|
+
priority (int): The priority of the data provider. The lower the
|
|
26
|
+
number, the higher the priority. The framework will use this
|
|
27
|
+
priority when searching for a data provider for a specific symbol.
|
|
28
|
+
Example: 0 is the highest priority, 1 is the second-highest
|
|
29
|
+
priority, etc. This is useful when multiple data providers
|
|
30
|
+
support the same symbol or market. The framework will use the
|
|
31
|
+
data provider with the highest priority.
|
|
32
|
+
time_frame (Optional[str]): The time frame for the data. This is
|
|
33
|
+
useful for data providers that support multiple time frames.
|
|
34
|
+
Example: "1m", "5m", "1h", "1d"
|
|
35
|
+
window_size (Optional[int]): The window size for the data. This is
|
|
36
|
+
useful for data providers that support multiple window sizes.
|
|
37
|
+
Example: 100, 200, 500
|
|
38
|
+
storage_path (Optional[str]): The path to the storage location
|
|
39
|
+
for the data. This is useful for data providers that support
|
|
40
|
+
saving data to a file
|
|
41
|
+
"""
|
|
42
|
+
data_type: DataType = None
|
|
43
|
+
data_provider_identifier: str = None
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
data_provider_identifier: str = None,
|
|
48
|
+
data_type: str = None,
|
|
49
|
+
symbol: str = None,
|
|
50
|
+
market: str = None,
|
|
51
|
+
priority: int = 0,
|
|
52
|
+
time_frame=None,
|
|
53
|
+
window_size=None,
|
|
54
|
+
storage_path=None,
|
|
55
|
+
storage_directory=None,
|
|
56
|
+
config=None,
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
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"
|
|
94
|
+
"""
|
|
95
|
+
self._data_type = None
|
|
96
|
+
self._time_frame = None
|
|
97
|
+
|
|
98
|
+
if data_type is not None:
|
|
99
|
+
self.data_type = DataType.from_value(data_type)
|
|
100
|
+
|
|
101
|
+
if time_frame is not None:
|
|
102
|
+
self.time_frame = TimeFrame.from_value(time_frame)
|
|
103
|
+
|
|
104
|
+
if data_provider_identifier is not None:
|
|
105
|
+
self.data_provider_identifier = data_provider_identifier
|
|
106
|
+
|
|
107
|
+
self.symbol = symbol
|
|
108
|
+
|
|
109
|
+
if self.symbol is not None:
|
|
110
|
+
self.symbol = self.symbol.upper()
|
|
111
|
+
|
|
112
|
+
self.market = market
|
|
113
|
+
|
|
114
|
+
if self.market is not None:
|
|
115
|
+
self.market = self.market.upper()
|
|
116
|
+
|
|
117
|
+
self.priority = priority
|
|
118
|
+
self._config = config
|
|
119
|
+
self.window_size = window_size
|
|
120
|
+
self.storage_path = storage_path
|
|
121
|
+
self.storage_directory = storage_directory
|
|
122
|
+
self._market_credentials = None
|
|
123
|
+
|
|
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
|
+
)
|
|
131
|
+
|
|
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
|
+
)
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def market_credentials(self):
|
|
141
|
+
"""
|
|
142
|
+
Returns the market credentials for the data provider.
|
|
143
|
+
"""
|
|
144
|
+
return self._market_credentials
|
|
145
|
+
|
|
146
|
+
@market_credentials.setter
|
|
147
|
+
def market_credentials(self, value: List):
|
|
148
|
+
"""
|
|
149
|
+
Sets the market credentials for the data provider.
|
|
150
|
+
"""
|
|
151
|
+
self._market_credentials = value
|
|
152
|
+
|
|
153
|
+
def get_credential(self, market: str):
|
|
154
|
+
"""
|
|
155
|
+
Returns the credentials for the given market.
|
|
156
|
+
"""
|
|
157
|
+
if self.market_credentials is None:
|
|
158
|
+
return None
|
|
159
|
+
for credential in self.market_credentials:
|
|
160
|
+
if credential.market == market:
|
|
161
|
+
return credential
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def config(self):
|
|
166
|
+
return self._config
|
|
167
|
+
|
|
168
|
+
@config.setter
|
|
169
|
+
def config(self, value):
|
|
170
|
+
self._config = value
|
|
171
|
+
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def has_data(
|
|
174
|
+
self,
|
|
175
|
+
data_source: DataSource,
|
|
176
|
+
start_date: datetime = None,
|
|
177
|
+
end_date: datetime = None,
|
|
178
|
+
) -> bool:
|
|
179
|
+
"""
|
|
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,
|
|
190
|
+
"""
|
|
191
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
192
|
+
|
|
193
|
+
@abstractmethod
|
|
194
|
+
def get_data(
|
|
195
|
+
self,
|
|
196
|
+
date: datetime = None,
|
|
197
|
+
start_date: datetime = None,
|
|
198
|
+
end_date: datetime = None,
|
|
199
|
+
save: bool = False,
|
|
200
|
+
) -> Any:
|
|
201
|
+
"""
|
|
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.
|
|
214
|
+
"""
|
|
215
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
216
|
+
|
|
217
|
+
@abstractmethod
|
|
218
|
+
def prepare_backtest_data(
|
|
219
|
+
self,
|
|
220
|
+
backtest_start_date,
|
|
221
|
+
backtest_end_date,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""
|
|
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
|
|
234
|
+
"""
|
|
235
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
236
|
+
|
|
237
|
+
@abstractmethod
|
|
238
|
+
def get_backtest_data(
|
|
239
|
+
self,
|
|
240
|
+
backtest_index_date: datetime,
|
|
241
|
+
backtest_start_date: datetime = None,
|
|
242
|
+
backtest_end_date: datetime = None,
|
|
243
|
+
data_source: DataSource = None,
|
|
244
|
+
) -> Any:
|
|
245
|
+
"""
|
|
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.
|
|
333
|
+
"""
|
|
334
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class PeekableQueue:
|
|
2
|
+
def __init__(self, items=[]):
|
|
3
|
+
self.queue = items
|
|
4
|
+
self.index = 0
|
|
5
|
+
|
|
6
|
+
def enqueue(self, item):
|
|
7
|
+
self.queue.append(item)
|
|
8
|
+
|
|
9
|
+
def dequeue(self):
|
|
10
|
+
if not self.is_empty():
|
|
11
|
+
return self.queue.pop(0)
|
|
12
|
+
else:
|
|
13
|
+
raise IndexError("Queue is empty")
|
|
14
|
+
|
|
15
|
+
def peek(self):
|
|
16
|
+
if not self.is_empty():
|
|
17
|
+
return self.queue[0]
|
|
18
|
+
else:
|
|
19
|
+
raise IndexError("Queue is empty")
|
|
20
|
+
|
|
21
|
+
def is_empty(self):
|
|
22
|
+
return len(self.queue) == 0
|
|
23
|
+
|
|
24
|
+
def __len__(self):
|
|
25
|
+
return len(self.queue)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def size(self):
|
|
29
|
+
return len(self.queue)
|
|
30
|
+
|
|
31
|
+
def __iter__(self):
|
|
32
|
+
self.index = 0
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
def __next__(self):
|
|
36
|
+
if self.index < len(self.queue):
|
|
37
|
+
result = self.queue[self.index]
|
|
38
|
+
self.index += 1
|
|
39
|
+
return result
|
|
40
|
+
else:
|
|
41
|
+
self.index = 0 # Reset index for next iteration
|
|
42
|
+
raise StopIteration
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from decimal import Decimal, getcontext
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def count_number_of_decimals(value) -> int:
|
|
5
|
+
value = str(value)
|
|
6
|
+
if "." in value:
|
|
7
|
+
return len(value) - value.index(".") - 1
|
|
8
|
+
else:
|
|
9
|
+
return 0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_decimal(value) -> Decimal:
|
|
13
|
+
getcontext().prec = count_number_of_decimals(value)
|
|
14
|
+
return Decimal(value)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_decimal_to_string(decimal, precision=None):
|
|
18
|
+
|
|
19
|
+
if decimal is None:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
if isinstance(decimal, str):
|
|
23
|
+
return decimal
|
|
24
|
+
|
|
25
|
+
value_str = str(Decimal(decimal))
|
|
26
|
+
|
|
27
|
+
if precision is None:
|
|
28
|
+
return value_str
|
|
29
|
+
|
|
30
|
+
value_decimal = Decimal(value_str)
|
|
31
|
+
value_with_precision = format(value_decimal, f'.{precision}f')
|
|
32
|
+
return value_with_precision
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def parse_string_to_decimal(value):
|
|
36
|
+
|
|
37
|
+
if value is None:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
return Decimal(value)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
class ApiException(Exception):
|
|
2
|
+
|
|
3
|
+
def __init__(self, message: str = None, status_code: int = 400):
|
|
4
|
+
super(ApiException, self).__init__(message)
|
|
5
|
+
self._message = message
|
|
6
|
+
self._status_code = status_code
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def error_message(self):
|
|
10
|
+
|
|
11
|
+
if self._message is None:
|
|
12
|
+
return "An error occurred"
|
|
13
|
+
|
|
14
|
+
return self._message
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def status_code(self):
|
|
18
|
+
|
|
19
|
+
if self._status_code is None:
|
|
20
|
+
return 500
|
|
21
|
+
|
|
22
|
+
return self._status_code
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PermissionDeniedApiException(ApiException):
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super(PermissionDeniedApiException, self).__init__(
|
|
29
|
+
message="Permission denied", status_code=401
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ImproperlyConfigured(Exception):
|
|
34
|
+
"""
|
|
35
|
+
Class ImproperlyConfigured: Exception class indicating a problem with
|
|
36
|
+
the configuration of the investing_algorithm_framework
|
|
37
|
+
"""
|
|
38
|
+
def __init__(self, message) -> None:
|
|
39
|
+
super(ImproperlyConfigured, self).__init__(message)
|
|
40
|
+
self.error_message = message
|
|
41
|
+
|
|
42
|
+
def to_response(self):
|
|
43
|
+
return {
|
|
44
|
+
"status": "error",
|
|
45
|
+
"message": self.error_message
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OperationalException(Exception):
|
|
50
|
+
"""
|
|
51
|
+
Class OperationalException: Exception class indicating a problem occurred
|
|
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.
|
|
60
|
+
"""
|
|
61
|
+
def __init__(self, message) -> None:
|
|
62
|
+
super(OperationalException, self).__init__(message)
|
|
63
|
+
self.error_message = message
|
|
64
|
+
|
|
65
|
+
def to_response(self):
|
|
66
|
+
return {
|
|
67
|
+
"status": "error",
|
|
68
|
+
"message": self.error_message
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class NetworkError(Exception):
|
|
73
|
+
"""
|
|
74
|
+
Class NetworkError: Exception class indicating a problem occurred
|
|
75
|
+
during making a netwok request
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, message) -> None:
|
|
79
|
+
super(NetworkError, self).__init__(message)
|
|
80
|
+
self.error_message = message
|
|
81
|
+
|
|
82
|
+
def to_response(self):
|
|
83
|
+
return {
|
|
84
|
+
"status": "error",
|
|
85
|
+
"message": self.error_message
|
|
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
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .app_mode import AppMode
|
|
2
|
+
from .market import MarketCredential
|
|
3
|
+
from .order import OrderStatus, OrderSide, OrderType, Order
|
|
4
|
+
from .portfolio import PortfolioConfiguration, Portfolio, PortfolioSnapshot
|
|
5
|
+
from .position import Position, PositionSnapshot
|
|
6
|
+
from .strategy_profile import StrategyProfile
|
|
7
|
+
from .time_frame import TimeFrame
|
|
8
|
+
from .time_interval import TimeInterval
|
|
9
|
+
from .time_unit import TimeUnit
|
|
10
|
+
from .trade import Trade, TradeStatus, TradeStopLoss, TradeTakeProfit, \
|
|
11
|
+
TradeRiskType
|
|
12
|
+
from .snapshot_interval import SnapshotInterval
|
|
13
|
+
from .event import Event
|
|
14
|
+
from .data import DataSource, DataType
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"OrderStatus",
|
|
18
|
+
"OrderSide",
|
|
19
|
+
"OrderType",
|
|
20
|
+
"Order",
|
|
21
|
+
"TimeFrame",
|
|
22
|
+
"TimeInterval",
|
|
23
|
+
"TimeUnit",
|
|
24
|
+
"PortfolioConfiguration",
|
|
25
|
+
"Position",
|
|
26
|
+
"Portfolio",
|
|
27
|
+
"PositionSnapshot",
|
|
28
|
+
"PortfolioSnapshot",
|
|
29
|
+
"StrategyProfile",
|
|
30
|
+
"Trade",
|
|
31
|
+
"MarketCredential",
|
|
32
|
+
"TradeStatus",
|
|
33
|
+
"DataType",
|
|
34
|
+
"AppMode",
|
|
35
|
+
"DataSource",
|
|
36
|
+
"AppMode",
|
|
37
|
+
"TradeStopLoss",
|
|
38
|
+
"TradeTakeProfit",
|
|
39
|
+
"TradeRiskType",
|
|
40
|
+
"DataSource",
|
|
41
|
+
"SnapshotInterval",
|
|
42
|
+
"Event",
|
|
43
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AppMode(Enum):
|
|
5
|
+
STATELESS = "STATELESS"
|
|
6
|
+
DEFAULT = "DEFAULT"
|
|
7
|
+
WEB = "WEB"
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def from_string(value: str):
|
|
11
|
+
|
|
12
|
+
if isinstance(value, str):
|
|
13
|
+
for status in AppMode:
|
|
14
|
+
|
|
15
|
+
if value.upper() == status.value:
|
|
16
|
+
return status
|
|
17
|
+
|
|
18
|
+
raise ValueError("Could not convert value to AppMode")
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def from_value(value):
|
|
22
|
+
|
|
23
|
+
if isinstance(value, AppMode):
|
|
24
|
+
for status in AppMode:
|
|
25
|
+
|
|
26
|
+
if value == status:
|
|
27
|
+
return status
|
|
28
|
+
elif isinstance(value, str):
|
|
29
|
+
return AppMode.from_string(value)
|
|
30
|
+
|
|
31
|
+
raise ValueError(f"Could not convert value {value} to AppMode")
|
|
32
|
+
|
|
33
|
+
def equals(self, other):
|
|
34
|
+
return AppMode.from_value(other) == self
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class BaseModel:
|
|
2
|
+
|
|
3
|
+
def repr(self, **fields) -> str:
|
|
4
|
+
"""
|
|
5
|
+
Helper for __repr__
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
field_strings = []
|
|
9
|
+
at_least_one_attached_attribute = False
|
|
10
|
+
|
|
11
|
+
for key, field in fields.items():
|
|
12
|
+
field_strings.append(f'{key}={field!r}')
|
|
13
|
+
at_least_one_attached_attribute = True
|
|
14
|
+
|
|
15
|
+
if at_least_one_attached_attribute:
|
|
16
|
+
return f"<{self.__class__.__name__}({','.join(field_strings)})>"
|
|
17
|
+
|
|
18
|
+
return f"<{self.__class__.__name__} {id(self)}>"
|
|
19
|
+
|
|
20
|
+
def update(self, data):
|
|
21
|
+
|
|
22
|
+
for attr, value in data.items():
|
|
23
|
+
|
|
24
|
+
if value is not None:
|
|
25
|
+
setattr(self, attr, value)
|