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,112 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from investing_algorithm_framework.domain import Order
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OrderExecutor(ABC):
|
|
7
|
+
"""
|
|
8
|
+
Abstract base class for order executors. The OrderExecutor class is
|
|
9
|
+
responsible for executing orders in a trading algorithm.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
_priority (int): The priority of the order executor compared to
|
|
13
|
+
other order executors. The lower the number, the higher the
|
|
14
|
+
priority. The framework will use this priority when searching
|
|
15
|
+
for an order executor for a specific market.
|
|
16
|
+
_config (dict): Reference to the application configuration.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, priority=1):
|
|
20
|
+
self._priority = priority
|
|
21
|
+
self._config = None
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def priority(self):
|
|
25
|
+
"""
|
|
26
|
+
Returns the priority of the order executor.
|
|
27
|
+
"""
|
|
28
|
+
return self._priority
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def config(self):
|
|
32
|
+
"""
|
|
33
|
+
Returns the configuration of the order executor.
|
|
34
|
+
This can be used to store any configuration that is needed
|
|
35
|
+
for the order executor.
|
|
36
|
+
"""
|
|
37
|
+
return self._config
|
|
38
|
+
|
|
39
|
+
@config.setter
|
|
40
|
+
def config(self, config):
|
|
41
|
+
"""
|
|
42
|
+
Setter for the app config. This will be used to set a reference
|
|
43
|
+
to the config of the Application when the app is started.
|
|
44
|
+
"""
|
|
45
|
+
self._config = config
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def execute_order(self, portfolio, order, market_credential) -> Order:
|
|
49
|
+
"""
|
|
50
|
+
Executes an order for a given portfolio. The order executor should
|
|
51
|
+
create an order on the exchange or broker and return an order object
|
|
52
|
+
that reflects the order on the exchange or broker. This should be
|
|
53
|
+
done by setting the external_id of the order to the id of the order
|
|
54
|
+
on the exchange or broker.
|
|
55
|
+
|
|
56
|
+
!Important: This function should not throw an exception if the order
|
|
57
|
+
is not successfully executed. Instead, it should return an order
|
|
58
|
+
instance with the status set to OrderStatus.FAILED.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
order: The order to be executed
|
|
62
|
+
portfolio: The portfolio in which the order will be executed
|
|
63
|
+
market_credential: The market credential to use for the order
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Order: Instance of the executed order. The order instance
|
|
67
|
+
should copy the id of the order that has been provided as a
|
|
68
|
+
"""
|
|
69
|
+
raise NotImplementedError(
|
|
70
|
+
"Subclasses must implement this method."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def cancel_order(self, portfolio, order, market_credential) -> Order:
|
|
75
|
+
"""
|
|
76
|
+
Cancels an order for a given portfolio. The order executor should
|
|
77
|
+
cancel the order on the exchange or broker and return an order
|
|
78
|
+
object that reflects the order on the exchange or broker.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
order: The order to be canceled
|
|
82
|
+
portfolio: The portfolio in which the order was executed
|
|
83
|
+
market_credential: The market credential to use for the order
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Order: Instance of the canceled order.
|
|
87
|
+
"""
|
|
88
|
+
raise NotImplementedError(
|
|
89
|
+
"Subclasses must implement this method."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def supports_market(self, market):
|
|
94
|
+
"""
|
|
95
|
+
Checks if the order executor supports the given market.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
market: The market to check
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
bool: True if the order executor supports the market, False
|
|
102
|
+
otherwise.
|
|
103
|
+
"""
|
|
104
|
+
raise NotImplementedError(
|
|
105
|
+
"Subclasses must implement this method."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def __repr__(self):
|
|
109
|
+
"""
|
|
110
|
+
Returns a string representation of the order executor.
|
|
111
|
+
"""
|
|
112
|
+
return f"{self.__class__.__name__}(priority={self._priority})"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
from investing_algorithm_framework.domain import Order, Position
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PortfolioProvider(ABC):
|
|
8
|
+
"""
|
|
9
|
+
Abstract base class for portfolio providers. The PortfolioProvider class
|
|
10
|
+
is responsible for managing and providing access to trading portfolios.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
_priority (int): The priority of the portfolio provider compared to
|
|
14
|
+
other portfolio providers. The lower the number, the higher the
|
|
15
|
+
priority. The framework will use this priority when searching
|
|
16
|
+
for a portfolio provider for a specific symbol or market.
|
|
17
|
+
_config (dict): Reference to the application configuration.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, priority=1):
|
|
21
|
+
self._priority = priority
|
|
22
|
+
self._config = None
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def priority(self):
|
|
26
|
+
"""
|
|
27
|
+
Returns the priority of the portfolio provider.
|
|
28
|
+
"""
|
|
29
|
+
return self._priority
|
|
30
|
+
|
|
31
|
+
@priority.setter
|
|
32
|
+
def priority(self, value: int):
|
|
33
|
+
"""
|
|
34
|
+
Sets the priority of the portfolio provider.
|
|
35
|
+
"""
|
|
36
|
+
self._priority = value
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def config(self):
|
|
40
|
+
"""
|
|
41
|
+
Returns the configuration of the order executor.
|
|
42
|
+
This can be used to store any configuration that is needed
|
|
43
|
+
for the order executor.
|
|
44
|
+
"""
|
|
45
|
+
return self._config
|
|
46
|
+
|
|
47
|
+
@config.setter
|
|
48
|
+
def config(self, config):
|
|
49
|
+
"""
|
|
50
|
+
Setter for the app config. This will be used to set a reference
|
|
51
|
+
to the config of the Application when the app is started.
|
|
52
|
+
"""
|
|
53
|
+
self._config = config
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def get_order(
|
|
57
|
+
self, portfolio, order, market_credential
|
|
58
|
+
) -> Union[Order, None]:
|
|
59
|
+
"""
|
|
60
|
+
Function to get an order from the exchange or broker. The returned
|
|
61
|
+
should be an order object that reflects the current state of the
|
|
62
|
+
order on the exchange or broker.
|
|
63
|
+
|
|
64
|
+
!IMPORTANT: This function should return None if the order is
|
|
65
|
+
not found or if the order is not available on the
|
|
66
|
+
exchange or broker. Please do not throw an exception if the
|
|
67
|
+
order is not found.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
portfolio: Portfolio object
|
|
71
|
+
order: Order object from the database
|
|
72
|
+
market_credential: Market credential object
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Order: Order object reflecting the order on the exchange or broker
|
|
76
|
+
"""
|
|
77
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def get_position(
|
|
81
|
+
self, portfolio, symbol, market_credential
|
|
82
|
+
) -> Union[Position, None]:
|
|
83
|
+
"""
|
|
84
|
+
Function to get the position for a given symbol in the portfolio.
|
|
85
|
+
The returned position should be an object that reflects the current
|
|
86
|
+
state of the position on the exchange or broker.
|
|
87
|
+
|
|
88
|
+
!IMPORTANT: This function should return None if the position is
|
|
89
|
+
not found or if the position is not available on the
|
|
90
|
+
exchange or broker. Please do not throw an exception if the
|
|
91
|
+
position is not found.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
portfolio: Portfolio object
|
|
95
|
+
symbol: Symbol object
|
|
96
|
+
market_credential: MarketCredential object
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
float: Position for the given symbol in the portfolio
|
|
100
|
+
"""
|
|
101
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def supports_market(self, market) -> bool:
|
|
105
|
+
"""
|
|
106
|
+
Function to check if the market is supported by the portfolio
|
|
107
|
+
provider.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
market: Market object
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
bool: True if the market is supported, False otherwise
|
|
114
|
+
"""
|
|
115
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
116
|
+
|
|
117
|
+
def __repr__(self):
|
|
118
|
+
return f"{self.__class__.__name__}(priority={self.priority})"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from investing_algorithm_framework.domain.exceptions import \
|
|
3
|
+
OperationalException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PositionSize:
|
|
7
|
+
"""
|
|
8
|
+
Defines how much capital to allocate to a specific symbol.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
symbol: str,
|
|
14
|
+
percentage_of_portfolio: Optional[float] = None,
|
|
15
|
+
fixed_amount: Optional[float] = None
|
|
16
|
+
):
|
|
17
|
+
self.symbol = symbol
|
|
18
|
+
self.percentage_of_portfolio = percentage_of_portfolio
|
|
19
|
+
self.fixed_amount = fixed_amount
|
|
20
|
+
|
|
21
|
+
def get_size(self, portfolio, asset_price) -> float:
|
|
22
|
+
"""
|
|
23
|
+
Calculate size in currency/units.
|
|
24
|
+
"""
|
|
25
|
+
if self.fixed_amount is not None:
|
|
26
|
+
return self.fixed_amount
|
|
27
|
+
elif self.percentage_of_portfolio is not None:
|
|
28
|
+
total_value = portfolio.get_unallocated() + portfolio.allocated
|
|
29
|
+
return total_value * (self.percentage_of_portfolio / 100)
|
|
30
|
+
else:
|
|
31
|
+
raise OperationalException(
|
|
32
|
+
"A position size object must have either a fixed amount or a "
|
|
33
|
+
"percentage of the portfolio defined."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
return (
|
|
38
|
+
f"PositionSize(symbol={self.symbol}, "
|
|
39
|
+
f"percentage_of_portfolio={self.percentage_of_portfolio}, "
|
|
40
|
+
f"fixed_amount={self.fixed_amount})"
|
|
41
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .market_credential_service import MarketCredentialService
|
|
2
|
+
from .portfolios import AbstractPortfolioSyncService
|
|
3
|
+
from .rounding_service import RoundingService
|
|
4
|
+
from .state_handler import StateHandler
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"MarketCredentialService",
|
|
8
|
+
"AbstractPortfolioSyncService",
|
|
9
|
+
"RoundingService",
|
|
10
|
+
"StateHandler",
|
|
11
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from investing_algorithm_framework.domain import MarketCredential
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MarketCredentialService(ABC):
|
|
8
|
+
"""
|
|
9
|
+
This class is responsible for managing the market credentials of the app.
|
|
10
|
+
"""
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def add(self, market_data_credential: MarketCredential):
|
|
13
|
+
"""
|
|
14
|
+
Add a market credential in the repository
|
|
15
|
+
"""
|
|
16
|
+
raise NotImplementedError()
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def add_all(self, market_data_credentials: List[MarketCredential]):
|
|
20
|
+
"""
|
|
21
|
+
Add a list of market credentials in the repository
|
|
22
|
+
"""
|
|
23
|
+
raise NotImplementedError()
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def get(self, market) -> Optional[MarketCredential]:
|
|
27
|
+
"""
|
|
28
|
+
Get a market credential from the repository
|
|
29
|
+
"""
|
|
30
|
+
raise NotImplementedError()
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def get_all(self) -> List[MarketCredential]:
|
|
34
|
+
"""
|
|
35
|
+
Get all market credentials from the repository
|
|
36
|
+
"""
|
|
37
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import decimal
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RoundingService:
|
|
5
|
+
"""
|
|
6
|
+
Service to round numbers to a certain amount of decimals.
|
|
7
|
+
It will always round down.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def round_down(value, amount_of_decimals):
|
|
12
|
+
|
|
13
|
+
if RoundingService.count_decimals(value) <= amount_of_decimals:
|
|
14
|
+
return value
|
|
15
|
+
|
|
16
|
+
with decimal.localcontext() as ctx:
|
|
17
|
+
d = decimal.Decimal(value)
|
|
18
|
+
ctx.rounding = decimal.ROUND_DOWN
|
|
19
|
+
return float(round(d, amount_of_decimals))
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def count_decimals(number):
|
|
23
|
+
decimal_str = str(number)
|
|
24
|
+
if '.' in decimal_str:
|
|
25
|
+
return len(decimal_str.split('.')[1])
|
|
26
|
+
else:
|
|
27
|
+
return 0
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StateHandler(ABC):
|
|
5
|
+
"""
|
|
6
|
+
Abstract base class for state handlers.
|
|
7
|
+
|
|
8
|
+
This class defines the
|
|
9
|
+
interface for state handlers, which are responsible for
|
|
10
|
+
saving and loading state information.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def initialize(self):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the state handler.
|
|
17
|
+
"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def save(self, target_directory: str):
|
|
22
|
+
"""
|
|
23
|
+
Save the state to the specified directory.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
target_directory (str): Directory to save the state
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def load(self, target_directory: str):
|
|
32
|
+
"""
|
|
33
|
+
Load the state from the specified directory.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
target_directory (str): Directory to load the state
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from .models import TimeUnit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Strategy:
|
|
5
|
+
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
market=None,
|
|
9
|
+
time_unit=None,
|
|
10
|
+
interval=None,
|
|
11
|
+
worker_id=None,
|
|
12
|
+
symbols=None,
|
|
13
|
+
limit=None,
|
|
14
|
+
):
|
|
15
|
+
self._market = market
|
|
16
|
+
self._time_unit = TimeUnit.from_value(time_unit).value
|
|
17
|
+
self._interval = interval
|
|
18
|
+
self._symbols = symbols
|
|
19
|
+
self._limit = limit
|
|
20
|
+
self._worker_id = worker_id
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def market(self):
|
|
24
|
+
return self._market
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def time_unit(self):
|
|
28
|
+
return self._time_unit
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def interval(self):
|
|
32
|
+
return self._interval
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def symbols(self):
|
|
36
|
+
return self._symbols
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def limit(self):
|
|
40
|
+
return self._limit
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def worker_id(self):
|
|
44
|
+
return self._worker_id
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from .csv import get_total_amount_of_rows, append_dict_as_row_to_csv, \
|
|
2
|
+
add_column_headers_to_csv, csv_to_list, load_csv_into_dict
|
|
3
|
+
from .random import random_string, random_number
|
|
4
|
+
from .stoppable_thread import StoppableThread
|
|
5
|
+
from .synchronized import synchronized
|
|
6
|
+
from .polars import convert_polars_to_pandas
|
|
7
|
+
from .dates import is_timezone_aware, sync_timezones, get_timezone
|
|
8
|
+
from .jupyter_notebook_detection import is_jupyter_notebook
|
|
9
|
+
from .custom_tqdm import tqdm
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'synchronized',
|
|
13
|
+
'StoppableThread',
|
|
14
|
+
'random_string',
|
|
15
|
+
'random_number',
|
|
16
|
+
'get_total_amount_of_rows',
|
|
17
|
+
'append_dict_as_row_to_csv',
|
|
18
|
+
'add_column_headers_to_csv',
|
|
19
|
+
'csv_to_list',
|
|
20
|
+
'load_csv_into_dict',
|
|
21
|
+
'convert_polars_to_pandas',
|
|
22
|
+
'is_timezone_aware',
|
|
23
|
+
'sync_timezones',
|
|
24
|
+
'get_timezone',
|
|
25
|
+
'is_jupyter_notebook',
|
|
26
|
+
'tqdm'
|
|
27
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import shutil
|
|
3
|
+
import tempfile
|
|
4
|
+
from typing import Dict, List
|
|
5
|
+
|
|
6
|
+
from ..exceptions import OperationalException
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Function to add column headers to csv
|
|
10
|
+
def add_column_headers_to_csv(file_name: str, column_names: List[str]) -> None:
|
|
11
|
+
|
|
12
|
+
# Open file in append mode
|
|
13
|
+
with open(file_name, 'a+', newline='') as write_obj:
|
|
14
|
+
|
|
15
|
+
# Create a writer object from csv module
|
|
16
|
+
csv_writer = csv.writer(write_obj)
|
|
17
|
+
|
|
18
|
+
# Add the columns
|
|
19
|
+
csv_writer.writerow(column_names)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Function to append dict as a row to csv
|
|
23
|
+
def append_dict_as_row_to_csv(
|
|
24
|
+
file_name: str, dict_data: Dict, field_names: List[str]
|
|
25
|
+
) -> None:
|
|
26
|
+
result_dict = {field: dict_data[field] for field in field_names}
|
|
27
|
+
|
|
28
|
+
# Open file in append mode
|
|
29
|
+
with open(file_name, 'a', newline='') as write_obj:
|
|
30
|
+
# Create a writer object from csv module
|
|
31
|
+
dict_writer = csv.DictWriter(write_obj, fieldnames=field_names)
|
|
32
|
+
# Add dictionary as wor in the csv
|
|
33
|
+
dict_writer.writerow(result_dict)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_total_amount_of_rows(file_path: str):
|
|
37
|
+
file = open(file_path)
|
|
38
|
+
reader = csv.reader(file)
|
|
39
|
+
rows_count = len(list(reader))
|
|
40
|
+
file.close()
|
|
41
|
+
return rows_count
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Function to convert a csv to a list of data_provider
|
|
45
|
+
def csv_to_list(csv_path: str, strip_column_headers: bool = False) -> List:
|
|
46
|
+
data = []
|
|
47
|
+
with open(csv_path, "r") as csv_f:
|
|
48
|
+
reader = csv.reader(csv_f, delimiter=',')
|
|
49
|
+
|
|
50
|
+
for row in reader:
|
|
51
|
+
|
|
52
|
+
if row: # avoid blank lines
|
|
53
|
+
data.append(row)
|
|
54
|
+
|
|
55
|
+
# Delete columns
|
|
56
|
+
if strip_column_headers:
|
|
57
|
+
del data[0]
|
|
58
|
+
|
|
59
|
+
return data
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Function to remove a row of a csv corresponding to a particular index
|
|
63
|
+
def remove_row(csv_path: str, row_index: int) -> None:
|
|
64
|
+
|
|
65
|
+
if row_index < 0:
|
|
66
|
+
raise OperationalException('Negative index number')
|
|
67
|
+
|
|
68
|
+
_, temp_file = tempfile.mkstemp(suffix='.csv')
|
|
69
|
+
|
|
70
|
+
with open(csv_path, 'r') as inf, open(temp_file, 'w') as out_f:
|
|
71
|
+
writer = csv.writer(out_f)
|
|
72
|
+
index = 0
|
|
73
|
+
|
|
74
|
+
for row in csv.reader(inf):
|
|
75
|
+
|
|
76
|
+
# Copy every row except the index row
|
|
77
|
+
if index != row_index:
|
|
78
|
+
writer.writerow(row)
|
|
79
|
+
|
|
80
|
+
index += 1
|
|
81
|
+
|
|
82
|
+
shutil.move(temp_file, csv_path)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def load_csv_into_dict(file_path) -> Dict:
|
|
86
|
+
"""
|
|
87
|
+
Load a CSV file into a dictionary.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
file_path (str): Path to the CSV file.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
dict: Dictionary containing the data loaded from the CSV file.
|
|
94
|
+
"""
|
|
95
|
+
data_dict = {}
|
|
96
|
+
|
|
97
|
+
with open(file_path, 'r', newline='', encoding='utf-8') as csvfile:
|
|
98
|
+
csv_reader = csv.DictReader(csvfile)
|
|
99
|
+
|
|
100
|
+
for row in csv_reader:
|
|
101
|
+
for key, value in row.items():
|
|
102
|
+
data_dict[key] = value
|
|
103
|
+
|
|
104
|
+
return data_dict
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from tqdm.notebook import tqdm as tqdm_notebook
|
|
2
|
+
from tqdm import tqdm as tqdm_terminal
|
|
3
|
+
|
|
4
|
+
from .jupyter_notebook_detection import is_jupyter_notebook
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def tqdm(*args, **kwargs):
|
|
8
|
+
"""
|
|
9
|
+
Returns a tqdm progress bar that adapts to the
|
|
10
|
+
environment (Jupyter Notebook or terminal).
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
*args: Positional arguments for tqdm.
|
|
14
|
+
**kwargs: Keyword arguments for tqdm.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
tqdm object: A tqdm progress bar.
|
|
18
|
+
"""
|
|
19
|
+
if is_jupyter_notebook():
|
|
20
|
+
return tqdm_notebook(*args, **kwargs)
|
|
21
|
+
else:
|
|
22
|
+
return tqdm_terminal(*args, **kwargs)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import pytz
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_timezone_aware(dt: datetime) -> bool:
|
|
7
|
+
"""
|
|
8
|
+
Check if a datetime object is timezone-aware.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
dt (datetime): The datetime object to check.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
bool: True if the datetime is timezone-aware, False otherwise.
|
|
15
|
+
"""
|
|
16
|
+
return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_timezone(dt: datetime) -> Optional[pytz.tzinfo.BaseTzInfo]:
|
|
20
|
+
"""
|
|
21
|
+
Returns the timezone info from a datetime object.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
dt (datetime): The datetime object to check.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
pytz timezone info if available, otherwise None.
|
|
28
|
+
"""
|
|
29
|
+
if dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None:
|
|
30
|
+
return dt.tzinfo
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def sync_timezones(reference_dt: datetime, naive_dt: datetime) -> datetime:
|
|
35
|
+
"""
|
|
36
|
+
Synchronize a naive datetime with the timezone of a reference datetime.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
reference_dt (datetime): A timezone-aware datetime to
|
|
40
|
+
use as a reference.
|
|
41
|
+
naive_dt (datetime): A naive datetime to be synchronized.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Datetime: A timezone-aware datetime that matches the
|
|
45
|
+
timezone of the reference.
|
|
46
|
+
"""
|
|
47
|
+
tz = get_timezone(reference_dt)
|
|
48
|
+
|
|
49
|
+
if tz is None:
|
|
50
|
+
return naive_dt
|
|
51
|
+
|
|
52
|
+
# Check if tz has a localize method (pytz)
|
|
53
|
+
if hasattr(tz, 'localize'):
|
|
54
|
+
return tz.localize(naive_dt)
|
|
55
|
+
else:
|
|
56
|
+
# fallback (e.g., zoneinfo or other)
|
|
57
|
+
return naive_dt.replace(tzinfo=tz)
|