investing-algorithm-framework 1.3.1__py3-none-any.whl → 7.25.6__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.
- investing_algorithm_framework/__init__.py +195 -16
- investing_algorithm_framework/analysis/__init__.py +16 -0
- investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
- investing_algorithm_framework/analysis/data.py +170 -0
- investing_algorithm_framework/analysis/markdown.py +91 -0
- investing_algorithm_framework/analysis/ranking.py +298 -0
- investing_algorithm_framework/app/__init__.py +31 -4
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
- investing_algorithm_framework/app/app.py +2233 -264
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1724 -0
- investing_algorithm_framework/app/eventloop.py +620 -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/action_handlers/__init__.py +6 -3
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/stateless/exception_handler.py +1 -1
- investing_algorithm_framework/app/strategy.py +873 -52
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
- investing_algorithm_framework/app/web/controllers/orders.py +4 -3
- investing_algorithm_framework/app/web/controllers/portfolio.py +1 -1
- investing_algorithm_framework/app/web/controllers/positions.py +3 -3
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/app/web/error_handler.py +1 -1
- investing_algorithm_framework/app/web/schemas/order.py +2 -2
- investing_algorithm_framework/app/web/schemas/position.py +1 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +231 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -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/cli/validate_backtest_checkpoints.py +197 -0
- investing_algorithm_framework/create_app.py +43 -9
- investing_algorithm_framework/dependency_container.py +121 -33
- investing_algorithm_framework/domain/__init__.py +109 -22
- investing_algorithm_framework/domain/algorithm_id.py +69 -0
- investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
- investing_algorithm_framework/domain/config.py +60 -138
- investing_algorithm_framework/domain/constants.py +23 -34
- 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 +51 -1
- investing_algorithm_framework/domain/models/__init__.py +29 -14
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/base_model.py +3 -1
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +222 -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 +3 -4
- investing_algorithm_framework/domain/models/order/order.py +243 -86
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/__init__.py +7 -2
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +134 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -37
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
- investing_algorithm_framework/domain/models/position/__init__.py +3 -2
- investing_algorithm_framework/domain/models/position/position.py +29 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/position/{position_cost.py → position_snapshot.py} +16 -8
- 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 +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
- investing_algorithm_framework/domain/models/time_frame.py +94 -98
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +111 -2
- 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 +11 -0
- investing_algorithm_framework/domain/models/trade/trade.py +389 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -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/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +16 -4
- investing_algorithm_framework/domain/utils/csv.py +22 -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 +29 -0
- investing_algorithm_framework/download_data.py +244 -0
- investing_algorithm_framework/infrastructure/__init__.py +39 -11
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -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 +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +87 -13
- investing_algorithm_framework/infrastructure/models/__init__.py +13 -4
- investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
- investing_algorithm_framework/infrastructure/models/order/order.py +73 -73
- 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 +3 -2
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +57 -3
- investing_algorithm_framework/infrastructure/models/position/__init__.py +2 -2
- investing_algorithm_framework/infrastructure/models/position/position.py +16 -11
- 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 +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -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 +13 -5
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +32 -19
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +47 -4
- investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +85 -31
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +9 -2
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -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/infrastructure/services/backtesting/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
- investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
- investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
- investing_algorithm_framework/services/__init__.py +127 -10
- investing_algorithm_framework/services/configuration_service.py +95 -0
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
- investing_algorithm_framework/services/market_credential_service.py +40 -0
- investing_algorithm_framework/services/metrics/__init__.py +119 -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 +218 -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 +84 -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 +156 -0
- investing_algorithm_framework/services/metrics/trades.py +473 -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 +118 -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/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
- 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 +8 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -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 +9 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
- 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.25.6.dist-info/METADATA +535 -0
- investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
- {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
- investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -410
- investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
- investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
- investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -76
- investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
- investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
- investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -205
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
- investing_algorithm_framework/infrastructure/models/position/position_cost.py +0 -32
- investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
- investing_algorithm_framework/infrastructure/repositories/position_cost_repository.py +0 -16
- investing_algorithm_framework/infrastructure/services/market_service.py +0 -422
- investing_algorithm_framework/services/market_data_service.py +0 -75
- investing_algorithm_framework/services/order_service.py +0 -464
- investing_algorithm_framework/services/portfolio_service.py +0 -105
- investing_algorithm_framework/services/position_cost_service.py +0 -5
- investing_algorithm_framework/services/position_service.py +0 -50
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -219
- investing_algorithm_framework/setup_logging.py +0 -40
- investing_algorithm_framework-1.3.1.dist-info/AUTHORS.md +0 -8
- investing_algorithm_framework-1.3.1.dist-info/METADATA +0 -172
- investing_algorithm_framework-1.3.1.dist-info/RECORD +0 -103
- investing_algorithm_framework-1.3.1.dist-info/top_level.txt +0 -1
- {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
|
@@ -1,28 +1,72 @@
|
|
|
1
|
-
from
|
|
1
|
+
from .analysis import generate_rolling_backtest_windows, \
|
|
2
|
+
select_backtest_date_ranges, rank_results, create_weights, \
|
|
3
|
+
get_missing_timeseries_data_entries, fill_missing_timeseries_data, \
|
|
4
|
+
create_markdown_table
|
|
5
|
+
from .app import App, Algorithm, \
|
|
6
|
+
TradingStrategy, StatelessAction, Task, AppHook, Context, \
|
|
7
|
+
add_html_report, BacktestReport, \
|
|
8
|
+
pretty_print_trades, pretty_print_positions, \
|
|
9
|
+
pretty_print_orders, pretty_print_backtest, \
|
|
10
|
+
get_equity_curve_with_drawdown_chart, \
|
|
11
|
+
get_rolling_sharpe_ratio_chart, get_monthly_returns_heatmap_chart, \
|
|
12
|
+
get_yearly_returns_bar_chart, get_entry_and_exit_signals, \
|
|
13
|
+
get_ohlcv_data_completeness_chart, get_equity_curve_chart
|
|
14
|
+
from .domain import ApiException, combine_backtests, PositionSize, \
|
|
15
|
+
OrderType, OperationalException, OrderStatus, OrderSide, tqdm, \
|
|
16
|
+
TimeUnit, TimeInterval, Order, Portfolio, Backtest, DataError, \
|
|
17
|
+
Position, TimeFrame, INDEX_DATETIME, MarketCredential, TakeProfitRule, \
|
|
18
|
+
PortfolioConfiguration, RESOURCE_DIRECTORY, AWS_LAMBDA_LOGGING_CONFIG, \
|
|
19
|
+
Trade, APP_MODE, AppMode, DATETIME_FORMAT, load_backtests_from_directory, \
|
|
20
|
+
BacktestDateRange, convert_polars_to_pandas, BacktestRun, \
|
|
21
|
+
DEFAULT_LOGGING_CONFIG, DataType, DataProvider, StopLossRule, \
|
|
22
|
+
TradeStatus, generate_backtest_summary_metrics, generate_algorithm_id, \
|
|
23
|
+
APPLICATION_DIRECTORY, DataSource, OrderExecutor, PortfolioProvider, \
|
|
24
|
+
SnapshotInterval, AWS_S3_STATE_BUCKET_NAME, BacktestEvaluationFocus, \
|
|
25
|
+
save_backtests_to_directory, BacktestMetrics
|
|
26
|
+
from .infrastructure import AzureBlobStorageStateHandler, \
|
|
27
|
+
CSVOHLCVDataProvider, CCXTOHLCVDataProvider, PandasOHLCVDataProvider, \
|
|
28
|
+
AWSS3StorageStateHandler
|
|
2
29
|
from .create_app import create_app
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
from .download_data import download, download_v2, DownloadResult, \
|
|
31
|
+
create_data_storage_path
|
|
32
|
+
from .services import get_annual_volatility, get_sortino_ratio, \
|
|
33
|
+
get_drawdown_series, get_max_drawdown, get_equity_curve, \
|
|
34
|
+
get_price_efficiency_ratio, get_sharpe_ratio, \
|
|
35
|
+
get_profit_factor, get_cumulative_profit_factor_series, \
|
|
36
|
+
get_rolling_profit_factor_series, get_cagr, \
|
|
37
|
+
get_standard_deviation_returns, get_standard_deviation_downside_returns, \
|
|
38
|
+
get_max_drawdown_absolute, get_total_return, get_exposure_ratio, \
|
|
39
|
+
get_average_trade_duration, get_win_rate, get_win_loss_ratio, \
|
|
40
|
+
get_calmar_ratio, get_trade_frequency, get_yearly_returns, \
|
|
41
|
+
get_monthly_returns, get_best_year, get_best_month, get_worst_year, \
|
|
42
|
+
get_worst_month, get_best_trade, get_worst_trade, \
|
|
43
|
+
get_average_yearly_return, get_average_trade_gain, \
|
|
44
|
+
get_average_trade_loss, get_average_monthly_return, \
|
|
45
|
+
get_percentage_winning_months, get_max_drawdown_duration, \
|
|
46
|
+
get_max_daily_drawdown, get_trades_per_day, \
|
|
47
|
+
get_trades_per_year, get_average_monthly_return_losing_months, \
|
|
48
|
+
get_average_monthly_return_winning_months, get_percentage_winning_years, \
|
|
49
|
+
get_rolling_sharpe_ratio, create_backtest_metrics, get_total_growth, \
|
|
50
|
+
get_total_loss, get_cumulative_exposure, get_median_trade_return, \
|
|
51
|
+
get_average_trade_return, get_risk_free_rate_us, get_cumulative_return, \
|
|
52
|
+
get_cumulative_return_series, get_current_average_trade_return, \
|
|
53
|
+
get_current_average_trade_gain, get_current_average_trade_duration, \
|
|
54
|
+
get_current_average_trade_loss, get_negative_trades, \
|
|
55
|
+
get_positive_trades, get_number_of_trades, get_current_win_rate, \
|
|
56
|
+
get_current_win_loss_ratio, create_backtest_metrics_for_backtest, \
|
|
57
|
+
TradeTakeProfitService, TradeStopLossService
|
|
58
|
+
|
|
11
59
|
|
|
12
60
|
__all__ = [
|
|
61
|
+
"Algorithm",
|
|
13
62
|
"RESOURCE_DIRECTORY",
|
|
14
63
|
"App",
|
|
64
|
+
"AppHook",
|
|
15
65
|
"create_app",
|
|
16
66
|
"ApiException",
|
|
17
|
-
"TradingDataType",
|
|
18
|
-
"OrderBook",
|
|
19
|
-
"Ticker",
|
|
20
|
-
"TradingTimeFrame",
|
|
21
|
-
"OHLCV",
|
|
22
67
|
"OrderType",
|
|
23
68
|
"OrderStatus",
|
|
24
69
|
"OrderSide",
|
|
25
|
-
"Config",
|
|
26
70
|
"PortfolioConfiguration",
|
|
27
71
|
"TimeUnit",
|
|
28
72
|
"TimeInterval",
|
|
@@ -31,5 +75,140 @@ __all__ = [
|
|
|
31
75
|
"Portfolio",
|
|
32
76
|
"Position",
|
|
33
77
|
"StatelessAction",
|
|
34
|
-
"Task"
|
|
78
|
+
"Task",
|
|
79
|
+
"pretty_print_backtest",
|
|
80
|
+
"INDEX_DATETIME",
|
|
81
|
+
"Trade",
|
|
82
|
+
"TimeFrame",
|
|
83
|
+
"MarketCredential",
|
|
84
|
+
"OperationalException",
|
|
85
|
+
"APP_MODE",
|
|
86
|
+
"AppMode",
|
|
87
|
+
"DATETIME_FORMAT",
|
|
88
|
+
"Backtest",
|
|
89
|
+
"BacktestDateRange",
|
|
90
|
+
"convert_polars_to_pandas",
|
|
91
|
+
"AzureBlobStorageStateHandler",
|
|
92
|
+
"DEFAULT_LOGGING_CONFIG",
|
|
93
|
+
"BacktestReport",
|
|
94
|
+
"TradeStatus",
|
|
95
|
+
"Context",
|
|
96
|
+
"APPLICATION_DIRECTORY",
|
|
97
|
+
"download",
|
|
98
|
+
"pretty_print_orders",
|
|
99
|
+
"pretty_print_trades",
|
|
100
|
+
"pretty_print_positions",
|
|
101
|
+
"DataSource",
|
|
102
|
+
"OrderExecutor",
|
|
103
|
+
"PortfolioProvider",
|
|
104
|
+
"SnapshotInterval",
|
|
105
|
+
"add_html_report",
|
|
106
|
+
"AWSS3StorageStateHandler",
|
|
107
|
+
"AWS_S3_STATE_BUCKET_NAME",
|
|
108
|
+
"AWS_LAMBDA_LOGGING_CONFIG",
|
|
109
|
+
'select_backtest_date_ranges',
|
|
110
|
+
'DataType',
|
|
111
|
+
'CSVOHLCVDataProvider',
|
|
112
|
+
"CCXTOHLCVDataProvider",
|
|
113
|
+
"DataProvider",
|
|
114
|
+
"get_annual_volatility",
|
|
115
|
+
"get_sortino_ratio",
|
|
116
|
+
"get_drawdown_series",
|
|
117
|
+
"get_max_drawdown",
|
|
118
|
+
"get_equity_curve",
|
|
119
|
+
"get_price_efficiency_ratio",
|
|
120
|
+
"get_sharpe_ratio",
|
|
121
|
+
"get_profit_factor",
|
|
122
|
+
"get_cumulative_profit_factor_series",
|
|
123
|
+
"get_rolling_profit_factor_series",
|
|
124
|
+
"get_sharpe_ratio",
|
|
125
|
+
"get_cagr",
|
|
126
|
+
"get_standard_deviation_returns",
|
|
127
|
+
"get_standard_deviation_downside_returns",
|
|
128
|
+
"get_max_drawdown_absolute",
|
|
129
|
+
"get_total_return",
|
|
130
|
+
"get_exposure_ratio",
|
|
131
|
+
"get_cumulative_exposure",
|
|
132
|
+
"get_average_trade_duration",
|
|
133
|
+
"get_win_rate",
|
|
134
|
+
"get_win_loss_ratio",
|
|
135
|
+
"get_calmar_ratio",
|
|
136
|
+
"get_trade_frequency",
|
|
137
|
+
"get_yearly_returns",
|
|
138
|
+
"get_monthly_returns",
|
|
139
|
+
"get_best_year",
|
|
140
|
+
"get_best_month",
|
|
141
|
+
"get_worst_year",
|
|
142
|
+
"get_worst_month",
|
|
143
|
+
"get_best_trade",
|
|
144
|
+
"get_worst_trade",
|
|
145
|
+
"get_average_yearly_return",
|
|
146
|
+
"get_average_trade_gain",
|
|
147
|
+
"get_average_trade_loss",
|
|
148
|
+
"get_average_monthly_return",
|
|
149
|
+
"get_percentage_winning_months",
|
|
150
|
+
"get_average_trade_duration",
|
|
151
|
+
"get_trade_frequency",
|
|
152
|
+
"get_win_rate",
|
|
153
|
+
"get_win_loss_ratio",
|
|
154
|
+
"get_calmar_ratio",
|
|
155
|
+
"get_max_drawdown_absolute",
|
|
156
|
+
"get_max_drawdown_duration",
|
|
157
|
+
"get_max_daily_drawdown",
|
|
158
|
+
"get_trades_per_day",
|
|
159
|
+
"get_trades_per_year",
|
|
160
|
+
"get_average_monthly_return_losing_months",
|
|
161
|
+
"get_average_monthly_return_winning_months",
|
|
162
|
+
"get_percentage_winning_years",
|
|
163
|
+
"get_rolling_sharpe_ratio",
|
|
164
|
+
"create_backtest_metrics",
|
|
165
|
+
"PandasOHLCVDataProvider",
|
|
166
|
+
"get_equity_curve_with_drawdown_chart",
|
|
167
|
+
"get_rolling_sharpe_ratio_chart",
|
|
168
|
+
"get_monthly_returns_heatmap_chart",
|
|
169
|
+
"get_yearly_returns_bar_chart",
|
|
170
|
+
"get_ohlcv_data_completeness_chart",
|
|
171
|
+
"rank_results",
|
|
172
|
+
"create_weights",
|
|
173
|
+
"get_entry_and_exit_signals",
|
|
174
|
+
"BacktestEvaluationFocus",
|
|
175
|
+
"combine_backtests",
|
|
176
|
+
"PositionSize",
|
|
177
|
+
"get_median_trade_return",
|
|
178
|
+
"get_average_trade_return",
|
|
179
|
+
"get_risk_free_rate_us",
|
|
180
|
+
"get_cumulative_return",
|
|
181
|
+
"get_cumulative_return_series",
|
|
182
|
+
"get_total_loss",
|
|
183
|
+
"get_total_growth",
|
|
184
|
+
"generate_backtest_summary_metrics",
|
|
185
|
+
"get_equity_curve_chart",
|
|
186
|
+
"get_current_win_rate",
|
|
187
|
+
"get_current_win_loss_ratio",
|
|
188
|
+
"get_current_average_trade_loss",
|
|
189
|
+
"get_current_average_trade_duration",
|
|
190
|
+
"get_current_average_trade_gain",
|
|
191
|
+
"get_current_average_trade_return",
|
|
192
|
+
"get_negative_trades",
|
|
193
|
+
"get_positive_trades",
|
|
194
|
+
"get_number_of_trades",
|
|
195
|
+
"BacktestRun",
|
|
196
|
+
"load_backtests_from_directory",
|
|
197
|
+
"save_backtests_to_directory",
|
|
198
|
+
"DataError",
|
|
199
|
+
"create_backtest_metrics_for_backtest",
|
|
200
|
+
"TakeProfitRule",
|
|
201
|
+
"StopLossRule",
|
|
202
|
+
"TradeStopLossService",
|
|
203
|
+
"TradeTakeProfitService",
|
|
204
|
+
"generate_algorithm_id",
|
|
205
|
+
"BacktestMetrics",
|
|
206
|
+
"generate_rolling_backtest_windows",
|
|
207
|
+
"tqdm",
|
|
208
|
+
"get_missing_timeseries_data_entries",
|
|
209
|
+
"fill_missing_timeseries_data",
|
|
210
|
+
"create_markdown_table",
|
|
211
|
+
"download_v2",
|
|
212
|
+
"DownloadResult",
|
|
213
|
+
"create_data_storage_path"
|
|
35
214
|
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .markdown import create_markdown_table
|
|
2
|
+
from .backtest_data_ranges import select_backtest_date_ranges, \
|
|
3
|
+
generate_rolling_backtest_windows
|
|
4
|
+
from .ranking import create_weights, rank_results
|
|
5
|
+
from .data import fill_missing_timeseries_data, \
|
|
6
|
+
get_missing_timeseries_data_entries
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"create_markdown_table",
|
|
10
|
+
"select_backtest_date_ranges",
|
|
11
|
+
"generate_rolling_backtest_windows",
|
|
12
|
+
"create_weights",
|
|
13
|
+
"rank_results",
|
|
14
|
+
"fill_missing_timeseries_data",
|
|
15
|
+
"get_missing_timeseries_data_entries",
|
|
16
|
+
]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from logging import getLogger
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import List, Dict, Union
|
|
5
|
+
from datetime import timezone
|
|
6
|
+
|
|
7
|
+
from investing_algorithm_framework.domain import BacktestDateRange, \
|
|
8
|
+
OperationalException
|
|
9
|
+
|
|
10
|
+
logger = getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def select_backtest_date_ranges(
|
|
14
|
+
df: pd.DataFrame, window: Union[str, int] = '365D'
|
|
15
|
+
) -> List[BacktestDateRange]:
|
|
16
|
+
"""
|
|
17
|
+
Identifies the best upturn, worst downturn, and sideways periods
|
|
18
|
+
for the given window duration. This allows you to quickly select
|
|
19
|
+
interesting periods for backtesting.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
df (pd.DataFrame): DataFrame with a DateTime index
|
|
23
|
+
and 'Close' column.
|
|
24
|
+
window (Union[str, int]): Duration of the window
|
|
25
|
+
to analyze. Can be a string like '365D' or an
|
|
26
|
+
integer representing days.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List[BacktestDateRange]: List of BacktestDateRange
|
|
30
|
+
objects representing the best upturn, worst
|
|
31
|
+
downturn, and most sideways periods.
|
|
32
|
+
"""
|
|
33
|
+
df = df.copy()
|
|
34
|
+
df = df.sort_index()
|
|
35
|
+
|
|
36
|
+
if isinstance(window, int):
|
|
37
|
+
window = pd.Timedelta(days=window)
|
|
38
|
+
elif isinstance(window, str):
|
|
39
|
+
window = pd.to_timedelta(window)
|
|
40
|
+
else:
|
|
41
|
+
raise OperationalException("window must be a string or integer")
|
|
42
|
+
|
|
43
|
+
# Check if the window is larger than the DataFrame
|
|
44
|
+
if len(df) == 0:
|
|
45
|
+
raise OperationalException("DataFrame is empty")
|
|
46
|
+
|
|
47
|
+
if df.index[-1] - df.index[0] < window:
|
|
48
|
+
raise OperationalException(
|
|
49
|
+
"Window duration is larger than the data duration"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if len(df) < 2 or df.index[-1] - df.index[0] < window:
|
|
53
|
+
raise OperationalException(
|
|
54
|
+
"DataFrame must contain at least two rows and span "
|
|
55
|
+
"the full window duration"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
best_upturn = {
|
|
59
|
+
"name": "UpTurn", "return": float('-inf'), "start": None, "end": None
|
|
60
|
+
}
|
|
61
|
+
worst_downturn = {
|
|
62
|
+
"name": "DownTurn", "return": float('inf'), "start": None, "end": None
|
|
63
|
+
}
|
|
64
|
+
most_sideways = {
|
|
65
|
+
"name": "SideWays",
|
|
66
|
+
"volatility": float('inf'),
|
|
67
|
+
"return": None,
|
|
68
|
+
"start": None,
|
|
69
|
+
"end": None
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for i in range(len(df)):
|
|
73
|
+
start_time = df.index[i]
|
|
74
|
+
end_time = start_time + window
|
|
75
|
+
window_df = df[(df.index >= start_time) & (df.index <= end_time)]
|
|
76
|
+
|
|
77
|
+
if len(window_df) < 2 or (window_df.index[-1] - start_time) < window:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
start_price = window_df['Close'].iloc[0]
|
|
81
|
+
end_price = window_df['Close'].iloc[-1]
|
|
82
|
+
ret = (end_price / start_price) - 1 # relative return
|
|
83
|
+
volatility = window_df['Close'].std()
|
|
84
|
+
|
|
85
|
+
# Ensure datetime for BacktestDateRange and with timezone utc
|
|
86
|
+
start_time = pd.Timestamp(start_time).to_pydatetime()
|
|
87
|
+
start_time = start_time.replace(tzinfo=timezone.utc)
|
|
88
|
+
end_time = pd.Timestamp(window_df.index[-1]).to_pydatetime()
|
|
89
|
+
end_time = end_time.replace(tzinfo=timezone.utc)
|
|
90
|
+
|
|
91
|
+
if ret > best_upturn["return"]:
|
|
92
|
+
best_upturn.update(
|
|
93
|
+
{"return": ret, "start": start_time, "end": end_time}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if ret < worst_downturn["return"]:
|
|
97
|
+
worst_downturn.update(
|
|
98
|
+
{"return": ret, "start": start_time, "end": end_time}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if volatility < most_sideways["volatility"]:
|
|
102
|
+
most_sideways.update({
|
|
103
|
+
"return": ret,
|
|
104
|
+
"volatility": volatility,
|
|
105
|
+
"start": start_time,
|
|
106
|
+
"end": end_time
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
return [
|
|
110
|
+
BacktestDateRange(
|
|
111
|
+
start_date=best_upturn['start'],
|
|
112
|
+
end_date=best_upturn['end'],
|
|
113
|
+
name=best_upturn['name']
|
|
114
|
+
),
|
|
115
|
+
BacktestDateRange(
|
|
116
|
+
start_date=worst_downturn['start'],
|
|
117
|
+
end_date=worst_downturn['end'],
|
|
118
|
+
name=worst_downturn['name']
|
|
119
|
+
),
|
|
120
|
+
BacktestDateRange(
|
|
121
|
+
start_date=most_sideways['start'],
|
|
122
|
+
end_date=most_sideways['end'],
|
|
123
|
+
name=most_sideways['name']
|
|
124
|
+
)
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def generate_rolling_backtest_windows(
|
|
129
|
+
start_date: datetime,
|
|
130
|
+
end_date: datetime,
|
|
131
|
+
train_days: int = 365,
|
|
132
|
+
test_days: int = 90,
|
|
133
|
+
step_days: int = 90,
|
|
134
|
+
gap_days: int = 0,
|
|
135
|
+
) -> List[Dict[str, BacktestDateRange]]:
|
|
136
|
+
"""
|
|
137
|
+
Generate rolling windows for walk-forward backtesting.
|
|
138
|
+
|
|
139
|
+
This function creates training and testing date ranges for
|
|
140
|
+
time-series backtesting, avoiding look-ahead bias and providing
|
|
141
|
+
realistic out-of-sample performance estimates.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
start_date (datetime): The starting date for the first
|
|
145
|
+
training window.
|
|
146
|
+
end_date (datetime): The ending date for the last
|
|
147
|
+
testing window.
|
|
148
|
+
train_days (int): Number of days in the training window.
|
|
149
|
+
test_days (int): Number of days in the testing window.
|
|
150
|
+
step_days (int): Number of days to step forward for the next window.
|
|
151
|
+
gap_days (int): Number of days to skip between train and test windows.
|
|
152
|
+
Useful to avoid look-ahead bias in indicators
|
|
153
|
+
with lag (e.g., 26 for MACD). Default is 0 (no gap).
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
List[Dict[str, BacktestDateRange]]: A list of dictionaries containing:
|
|
157
|
+
- "train_range": BacktestDateRange for training period
|
|
158
|
+
- "test_range": BacktestDateRange for testing period
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
>>> windows = generate_rolling_backtest_windows(
|
|
162
|
+
... start_date=datetime(2021, 1, 1, tzinfo=timezone.utc),
|
|
163
|
+
... end_date=datetime(2024, 12, 31, tzinfo=timezone.utc),
|
|
164
|
+
... train_days=365,
|
|
165
|
+
... test_days=90,
|
|
166
|
+
... step_days=90,
|
|
167
|
+
... gap_days=30
|
|
168
|
+
... )
|
|
169
|
+
"""
|
|
170
|
+
windows = []
|
|
171
|
+
current_start = start_date
|
|
172
|
+
max_iterations = 10000 # Safety limit to prevent infinite loops
|
|
173
|
+
iteration = 0
|
|
174
|
+
|
|
175
|
+
while iteration < max_iterations:
|
|
176
|
+
iteration += 1
|
|
177
|
+
train_start = current_start
|
|
178
|
+
train_end = train_start + pd.Timedelta(days=train_days)
|
|
179
|
+
test_start = train_end + pd.Timedelta(days=gap_days)
|
|
180
|
+
test_end = test_start + pd.Timedelta(days=test_days)
|
|
181
|
+
|
|
182
|
+
if test_end > end_date:
|
|
183
|
+
break
|
|
184
|
+
|
|
185
|
+
train_backtest_date_range = BacktestDateRange(
|
|
186
|
+
name=f"train_window_{iteration}",
|
|
187
|
+
start_date=train_start,
|
|
188
|
+
end_date=train_end
|
|
189
|
+
)
|
|
190
|
+
test_backtest_date_range = BacktestDateRange(
|
|
191
|
+
name=f"test_window_{iteration}",
|
|
192
|
+
start_date=test_start,
|
|
193
|
+
end_date=test_end
|
|
194
|
+
)
|
|
195
|
+
windows.append({
|
|
196
|
+
"train_range": train_backtest_date_range,
|
|
197
|
+
"test_range": test_backtest_date_range,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
current_start += pd.Timedelta(days=step_days)
|
|
201
|
+
|
|
202
|
+
return windows
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import polars as pl
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def fill_missing_timeseries_data(
|
|
7
|
+
data: Union[pd.DataFrame, pl.DataFrame, str],
|
|
8
|
+
missing_dates: list = None,
|
|
9
|
+
start_date=None,
|
|
10
|
+
end_date=None,
|
|
11
|
+
save_to_file: bool = False,
|
|
12
|
+
file_path: str = None
|
|
13
|
+
) -> Union[pd.DataFrame, pl.DataFrame]:
|
|
14
|
+
"""
|
|
15
|
+
Fill missing dates in time-series data using a hybrid approach:
|
|
16
|
+
forward-fill by default, backward-fill only when missing dates
|
|
17
|
+
are at the start of the data.
|
|
18
|
+
|
|
19
|
+
This function handles missing rows (dates) in time-series data,
|
|
20
|
+
not just missing values within existing rows. All columns are
|
|
21
|
+
duplicated from the adjacent row.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
data (pd.DataFrame | pl.DataFrame | str): Time-series data as a
|
|
25
|
+
DataFrame or path to a CSV file. Index should be datetime.
|
|
26
|
+
missing_dates (list, optional): List of datetime objects representing
|
|
27
|
+
specific missing dates to fill. If None and start_date/end_date
|
|
28
|
+
are provided, missing dates will be auto-detected.
|
|
29
|
+
start_date (datetime, optional): Start date to check for missing dates.
|
|
30
|
+
end_date (datetime, optional): End date to check for missing dates.
|
|
31
|
+
save_to_file (bool): If True, save the result back to the CSV file.
|
|
32
|
+
file_path (str, optional): Path to save/load the CSV file.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
pd.DataFrame | pl.DataFrame: DataFrame with missing dates filled.
|
|
36
|
+
"""
|
|
37
|
+
is_polars = False
|
|
38
|
+
|
|
39
|
+
# Load data if file path is provided
|
|
40
|
+
if isinstance(data, str):
|
|
41
|
+
file_path = data
|
|
42
|
+
df = pd.read_csv(file_path, index_col=0, parse_dates=True)
|
|
43
|
+
elif isinstance(data, pl.DataFrame):
|
|
44
|
+
is_polars = True
|
|
45
|
+
# Convert polars to pandas for processing
|
|
46
|
+
df = data.to_pandas()
|
|
47
|
+
if 'Datetime' in df.columns:
|
|
48
|
+
df = df.set_index('Datetime')
|
|
49
|
+
df.index = pd.to_datetime(df.index)
|
|
50
|
+
else:
|
|
51
|
+
df = data.copy()
|
|
52
|
+
|
|
53
|
+
# Store the index name
|
|
54
|
+
index_name = df.index.name if df.index.name else 'Datetime'
|
|
55
|
+
|
|
56
|
+
rows_to_add = []
|
|
57
|
+
|
|
58
|
+
# Determine which dates need to be filled
|
|
59
|
+
if missing_dates is not None and len(missing_dates) > 0:
|
|
60
|
+
# Convert index to set of timestamps for proper comparison
|
|
61
|
+
existing_timestamps = set(pd.to_datetime(df.index))
|
|
62
|
+
# Deduplicate missing_dates and filter out existing dates
|
|
63
|
+
seen = set()
|
|
64
|
+
dates_to_fill = []
|
|
65
|
+
for d in missing_dates:
|
|
66
|
+
dt = pd.to_datetime(d)
|
|
67
|
+
if dt not in existing_timestamps and dt not in seen:
|
|
68
|
+
dates_to_fill.append(d)
|
|
69
|
+
seen.add(dt)
|
|
70
|
+
elif start_date is not None and end_date is not None:
|
|
71
|
+
dates_to_fill = get_missing_timeseries_data_entries(
|
|
72
|
+
df, start=start_date, end=end_date, freq='D'
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
dates_to_fill = []
|
|
76
|
+
|
|
77
|
+
# Fill missing dates
|
|
78
|
+
for missing_timestamp in dates_to_fill:
|
|
79
|
+
# Find the index position where this timestamp should be inserted
|
|
80
|
+
position = df[df.index < missing_timestamp].shape[0]
|
|
81
|
+
|
|
82
|
+
# Edge case: missing timestamp is BEFORE all existing data
|
|
83
|
+
if position == 0:
|
|
84
|
+
# Use the FIRST row (backward fill)
|
|
85
|
+
if len(df) > 0:
|
|
86
|
+
next_row = df.iloc[0]
|
|
87
|
+
new_row = next_row.to_dict()
|
|
88
|
+
else:
|
|
89
|
+
continue # No data to fill from
|
|
90
|
+
else:
|
|
91
|
+
# Normal case: use previous row (forward fill)
|
|
92
|
+
prev_row = df.iloc[position - 1]
|
|
93
|
+
new_row = prev_row.to_dict()
|
|
94
|
+
|
|
95
|
+
rows_to_add.append({index_name: missing_timestamp, **new_row})
|
|
96
|
+
|
|
97
|
+
# Add new rows if any
|
|
98
|
+
if rows_to_add:
|
|
99
|
+
new_df = pd.DataFrame(rows_to_add).set_index(index_name)
|
|
100
|
+
df = pd.concat([df, new_df]).sort_index()
|
|
101
|
+
|
|
102
|
+
# Save to file if requested
|
|
103
|
+
if save_to_file and file_path:
|
|
104
|
+
df.to_csv(file_path)
|
|
105
|
+
|
|
106
|
+
# Convert back to polars if input was polars
|
|
107
|
+
if is_polars:
|
|
108
|
+
df = df.reset_index()
|
|
109
|
+
return pl.from_pandas(df)
|
|
110
|
+
|
|
111
|
+
return df
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_missing_timeseries_data_entries(
|
|
115
|
+
data: Union[pd.DataFrame, pl.DataFrame, str],
|
|
116
|
+
start=None,
|
|
117
|
+
end=None,
|
|
118
|
+
freq: str = None
|
|
119
|
+
):
|
|
120
|
+
"""
|
|
121
|
+
Identify missing timestamps in a time series.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
data (pd.DataFrame | pl.DataFrame | str): Time-series data as a
|
|
125
|
+
DataFrame or path to a CSV file. Index should be datetime.
|
|
126
|
+
start (datetime, optional): The start datetime for the expected range.
|
|
127
|
+
If None, uses the first timestamp in the data.
|
|
128
|
+
end (datetime, optional): The end datetime for the expected range.
|
|
129
|
+
If None, uses the last timestamp in the data.
|
|
130
|
+
freq (str, optional): Frequency string (e.g., 'D' for daily, 'H' for
|
|
131
|
+
hourly). If None, will be inferred from the data.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
list: A list of missing timestamps within the specified range.
|
|
135
|
+
"""
|
|
136
|
+
# Load data if file path is provided
|
|
137
|
+
if isinstance(data, str):
|
|
138
|
+
df = pd.read_csv(data, index_col=0, parse_dates=True)
|
|
139
|
+
elif isinstance(data, pl.DataFrame):
|
|
140
|
+
df = data.to_pandas()
|
|
141
|
+
if 'Datetime' in df.columns:
|
|
142
|
+
df = df.set_index('Datetime')
|
|
143
|
+
df.index = pd.to_datetime(df.index)
|
|
144
|
+
else:
|
|
145
|
+
df = data
|
|
146
|
+
|
|
147
|
+
# Get existing timestamps from the index
|
|
148
|
+
existing_timestamps = pd.to_datetime(df.index)
|
|
149
|
+
|
|
150
|
+
# Use data bounds if start/end not provided
|
|
151
|
+
if start is None:
|
|
152
|
+
start = existing_timestamps.min()
|
|
153
|
+
if end is None:
|
|
154
|
+
end = existing_timestamps.max()
|
|
155
|
+
|
|
156
|
+
# Infer frequency if not provided
|
|
157
|
+
if freq is None:
|
|
158
|
+
freq = df.index.inferred_freq
|
|
159
|
+
if freq is None and len(df) >= 2:
|
|
160
|
+
diff = df.index[1] - df.index[0]
|
|
161
|
+
freq = diff
|
|
162
|
+
elif freq is None:
|
|
163
|
+
freq = 'D' # Default to daily
|
|
164
|
+
|
|
165
|
+
expected_timestamps = pd.date_range(start=start, end=end, freq=freq)
|
|
166
|
+
|
|
167
|
+
# Find missing by checking which expected timestamps are not in existing
|
|
168
|
+
missing_mask = ~expected_timestamps.isin(existing_timestamps)
|
|
169
|
+
missing_timestamps = expected_timestamps[missing_mask].tolist()
|
|
170
|
+
return missing_timestamps
|