lumibot 4.1.3__tar.gz → 4.2.1__tar.gz
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 lumibot might be problematic. Click here for more details.
- {lumibot-4.1.3/lumibot.egg-info → lumibot-4.2.1}/PKG-INFO +9 -1
- {lumibot-4.1.3 → lumibot-4.2.1}/README.md +7 -0
- lumibot-4.2.1/lumibot/backtesting/__init__.py +30 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/backtesting/backtesting_broker.py +98 -18
- lumibot-4.2.1/lumibot/backtesting/databento_backtesting.py +7 -0
- lumibot-4.1.3/lumibot/backtesting/databento_backtesting.py → lumibot-4.2.1/lumibot/backtesting/databento_backtesting_pandas.py +59 -9
- lumibot-4.2.1/lumibot/backtesting/databento_backtesting_polars.py +991 -0
- lumibot-4.2.1/lumibot/backtesting/fix_debug.py +37 -0
- lumibot-4.2.1/lumibot/backtesting/thetadata_backtesting.py +12 -0
- lumibot-4.2.1/lumibot/backtesting/thetadata_backtesting_pandas.py +1167 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/alpaca.py +8 -1
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/schwab.py +12 -2
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/credentials.py +13 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/__init__.py +5 -8
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/data_source.py +6 -2
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/data_source_backtesting.py +30 -0
- lumibot-4.2.1/lumibot/data_sources/databento_data.py +7 -0
- lumibot-4.1.3/lumibot/data_sources/databento_data.py → lumibot-4.2.1/lumibot/data_sources/databento_data_pandas.py +77 -29
- lumibot-4.1.3/lumibot/data_sources/databento_data_polars_live.py → lumibot-4.2.1/lumibot/data_sources/databento_data_polars.py +15 -9
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/pandas_data.py +30 -17
- lumibot-4.2.1/lumibot/data_sources/polars_data.py +986 -0
- lumibot-4.2.1/lumibot/data_sources/polars_mixin.py +853 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/polygon_data_polars.py +5 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/yahoo_data.py +9 -2
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/yahoo_data_polars.py +5 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/__init__.py +15 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/asset.py +5 -28
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/bars.py +89 -20
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/data.py +29 -6
- lumibot-4.2.1/lumibot/entities/data_polars.py +668 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/position.py +38 -4
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/strategies/_strategy.py +2 -1
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/strategies/strategy.py +61 -49
- lumibot-4.2.1/lumibot/tools/backtest_cache.py +284 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/databento_helper.py +35 -35
- lumibot-4.2.1/lumibot/tools/databento_helper_polars.py +1257 -0
- lumibot-4.2.1/lumibot/tools/futures_roll.py +251 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/indicators.py +135 -104
- lumibot-4.2.1/lumibot/tools/polars_utils.py +142 -0
- lumibot-4.2.1/lumibot/tools/thetadata_helper.py +2074 -0
- {lumibot-4.1.3 → lumibot-4.2.1/lumibot.egg-info}/PKG-INFO +9 -1
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot.egg-info/SOURCES.txt +19 -10
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot.egg-info/requires.txt +1 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/setup.py +2 -1
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_databento.py +37 -6
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_databento_comprehensive_trading.py +8 -4
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_databento_parity.py +4 -2
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_debug_avg_fill_price.py +1 -1
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_example_strategies.py +11 -1
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_futures_edge_cases.py +3 -3
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_futures_single_trade.py +2 -2
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_futures_ultra_simple.py +2 -2
- lumibot-4.2.1/tests/backtest/test_polars_lru_eviction.py +470 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_yahoo.py +42 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_asset.py +4 -4
- lumibot-4.2.1/tests/test_backtest_cache_manager.py +149 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_backtesting_data_source_env.py +6 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_continuous_futures_resolution.py +60 -48
- lumibot-4.2.1/tests/test_data_polars_parity.py +160 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_databento_asset_validation.py +23 -5
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_databento_backtesting.py +1 -1
- lumibot-4.2.1/tests/test_databento_backtesting_polars.py +321 -0
- lumibot-4.2.1/tests/test_databento_data.py +250 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_databento_live.py +10 -10
- lumibot-4.2.1/tests/test_futures_roll.py +38 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_indicator_subplots.py +101 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_market_infinite_loop_bug.py +77 -3
- lumibot-4.2.1/tests/test_polars_resample.py +67 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_polygon_helper.py +46 -0
- lumibot-4.2.1/tests/test_thetadata_backwards_compat.py +97 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_thetadata_helper.py +222 -23
- lumibot-4.2.1/tests/test_thetadata_pandas_verification.py +186 -0
- lumibot-4.1.3/lumibot/backtesting/__init__.py +0 -16
- lumibot-4.1.3/lumibot/backtesting/thetadata_backtesting.py +0 -358
- lumibot-4.1.3/lumibot/data_sources/databento_data_polars_backtesting.py +0 -636
- lumibot-4.1.3/lumibot/data_sources/polars_mixin.py +0 -477
- lumibot-4.1.3/lumibot/tools/databento_helper_polars.py +0 -1294
- lumibot-4.1.3/lumibot/tools/databento_roll.py +0 -216
- lumibot-4.1.3/lumibot/tools/thetadata_helper.py +0 -1140
- lumibot-4.1.3/tests/backtest/check_timing_offset.py +0 -198
- lumibot-4.1.3/tests/backtest/check_volume_spike.py +0 -112
- lumibot-4.1.3/tests/backtest/comprehensive_comparison.py +0 -166
- lumibot-4.1.3/tests/backtest/debug_comparison.py +0 -91
- lumibot-4.1.3/tests/backtest/diagnose_price_difference.py +0 -97
- lumibot-4.1.3/tests/backtest/direct_api_comparison.py +0 -203
- lumibot-4.1.3/tests/backtest/root_cause_analysis.py +0 -109
- lumibot-4.1.3/tests/test_databento_data.py +0 -493
- {lumibot-4.1.3 → lumibot-4.2.1}/LICENSE +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/MANIFEST.in +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/backtesting/alpaca_backtesting.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/backtesting/alpha_vantage_backtesting.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/backtesting/ccxt_backtesting.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/backtesting/interactive_brokers_rest_backtesting.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/backtesting/pandas_backtesting.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/backtesting/polygon_backtesting.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/backtesting/yahoo_backtesting.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/bitunix.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/broker.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/ccxt.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/example_broker.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/interactive_brokers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/interactive_brokers_rest.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/projectx.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/tradier.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/brokers/tradovate.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/components/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/components/configs_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/components/drift_rebalancer_logic.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/components/grok_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/components/options_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/components/perplexity_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/components/quiver_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/components/vix_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/constants.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/alpaca_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/alpha_vantage_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/bitunix_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/ccxt_backtesting_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/ccxt_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/example_broker_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/exceptions.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/interactive_brokers_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/interactive_brokers_rest_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/projectx_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/schwab_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/tradier_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/data_sources/tradovate_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/bar.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/chains.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/dataline.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/order.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/quote.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/entities/trading_fee.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/bitunix_futures_example.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/ccxt_backtesting_example.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/classic_60_40.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/classic_60_40_config.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/crypto_50_50.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/crypto_50_50_config.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/crypto_important_functions.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/drift_rebalancer.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/forex_hold_to_expiry.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/futures_hold_to_expiry.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/lifecycle_logger.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/options_hold_to_expiry.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/schedule_function.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/simple_start_single_file.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/stock_bracket.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/stock_buy_and_hold.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/stock_diversified_leverage.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/stock_limit_and_trailing_stops.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/stock_momentum.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/stock_oco.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/strangle.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/example_strategies/test_broker_functions.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/resources/ThetaTerminal.jar +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/resources/conf.yaml +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/strategies/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/strategies/session_manager.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/strategies/strategy_executor.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/alpaca_helpers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/bitunix_helpers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/black_scholes.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/ccxt_data_store.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/debugers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/decorators.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/futures_symbols.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/helpers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/lumibot_logger.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/lumibot_time.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/pandas.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/polygon_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/polygon_helper_async.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/polygon_helper_polars_optimized.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/projectx_helpers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/schwab_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/types.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/yahoo_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/tools/yahoo_helper_polars_optimized.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/traders/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/traders/debug_log_trader.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/traders/trader.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/trading_builtins/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/trading_builtins/custom_stream.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot/trading_builtins/safe_list.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot.egg-info/dependency_links.txt +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/lumibot.egg-info/top_level.txt +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/pyproject.toml +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/setup.cfg +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/__init__.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/conftest.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/performance_tracker.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/profile_thetadata_vs_polygon.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_accuracy_verification.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_backtesting_broker_processing.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_buy_hold_quiet_logs_full_run.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_crypto_cash_regressions.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_daily_data_timestamp_comparison.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_dividends.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_failing_backtest.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_index_data_verification.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_multileg_backtest.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_pandas_backtest.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_passing_trader_into_backtest.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_polygon.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_strategy_executor.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_thetadata.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_thetadata_comprehensive.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/backtest/test_thetadata_vs_polygon.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/conftest.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/fixtures.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_alpaca.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_alpaca_auth_fix.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_alpaca_backtesting.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_alpaca_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_alpaca_helpers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_alpaca_multileg_fix.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_alpaca_oauth.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_apscheduler_warnings.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_asset_auto_expiry.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_auto_market_inference.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_backtesting_broker.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_backtesting_broker_await_close.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_backtesting_broker_time_advance.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_backtesting_crypto_cash_unit.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_backtesting_flow_control.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_backtesting_multileg_unit.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_backtesting_quiet_logs_complete.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_bars_aggregate_frequency_normalization.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_bars_aggregation_timeunits.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_bars_frequency_flex.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_botspot_handler.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_botspot_logger.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_broker_bitunix.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_broker_cleanup.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_broker_initialization.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_brokers_handle_crypto.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_cash.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_ccxt.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_ccxt_store.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_configs_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_continuous_futures.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_continuous_futures_integration.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_data_source.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_databento_auto_expiry_integration.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_databento_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_databento_timezone_fixes.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_drift_rebalancer.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_futures_integration.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_get_historical_prices.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_helpers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_integration_tests.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_interactive_brokers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_live_trading_resilience.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_logger_env_vars.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_logging.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_lumibot_logger.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_mes_symbols.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_momentum.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_options_helper.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_order.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_order_serialization.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_pandas_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_position_serialization.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_bracket_helpers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_bracket_lifecycle_unit.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_datetime_columns.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_datetime_index.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_helpers.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_lifecycle.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_lifecycle_unit.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_live_flow.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_timestep_alias.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_projectx_url_mappings.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_quiet_logs_buy_and_hold.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_quiet_logs_comprehensive.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_quiet_logs_functionality.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_quiet_logs_requirements.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_session_manager.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_strategy_methods.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_tradier.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_tradier_data.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_tradingfee.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_tradovate.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_unified_logger.py +0 -0
- {lumibot-4.1.3 → lumibot-4.2.1}/tests/test_vix_helper.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lumibot
|
|
3
|
-
Version: 4.1
|
|
3
|
+
Version: 4.2.1
|
|
4
4
|
Summary: Backtesting and Trading Library, Made by Lumiwealth
|
|
5
5
|
Home-page: https://github.com/Lumiwealth/lumibot
|
|
6
6
|
Author: Robert Grzesik
|
|
@@ -51,6 +51,7 @@ Requires-Dist: schwab-py>=1.5.0
|
|
|
51
51
|
Requires-Dist: Flask>=2.3
|
|
52
52
|
Requires-Dist: free-proxy
|
|
53
53
|
Requires-Dist: requests-oauthlib
|
|
54
|
+
Requires-Dist: boto3>=1.28.0
|
|
54
55
|
Dynamic: author
|
|
55
56
|
Dynamic: author-email
|
|
56
57
|
Dynamic: classifier
|
|
@@ -146,6 +147,13 @@ To run an individual test file, you can run the following command:
|
|
|
146
147
|
pytest tests/test_asset.py
|
|
147
148
|
```
|
|
148
149
|
|
|
150
|
+
## Remote Cache Configuration
|
|
151
|
+
|
|
152
|
+
Lumibot can mirror its local parquet caches to AWS S3 when you enable the new
|
|
153
|
+
backtest cache manager. The feature is optional and defaults to local storage.
|
|
154
|
+
To configure the environment variables, understand the key naming convention,
|
|
155
|
+
and follow the manual validation checklist, review `docs/remote_cache.md`.
|
|
156
|
+
|
|
149
157
|
### Showing Code Coverage
|
|
150
158
|
|
|
151
159
|
To show code coverage, you can run the following command:
|
|
@@ -81,6 +81,13 @@ To run an individual test file, you can run the following command:
|
|
|
81
81
|
pytest tests/test_asset.py
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
## Remote Cache Configuration
|
|
85
|
+
|
|
86
|
+
Lumibot can mirror its local parquet caches to AWS S3 when you enable the new
|
|
87
|
+
backtest cache manager. The feature is optional and defaults to local storage.
|
|
88
|
+
To configure the environment variables, understand the key naming convention,
|
|
89
|
+
and follow the manual validation checklist, review `docs/remote_cache.md`.
|
|
90
|
+
|
|
84
91
|
### Showing Code Coverage
|
|
85
92
|
|
|
86
93
|
To show code coverage, you can run the following command:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .alpaca_backtesting import AlpacaBacktesting
|
|
2
|
+
from .alpha_vantage_backtesting import AlphaVantageBacktesting
|
|
3
|
+
from .backtesting_broker import BacktestingBroker
|
|
4
|
+
from .ccxt_backtesting import CcxtBacktesting
|
|
5
|
+
from .interactive_brokers_rest_backtesting import InteractiveBrokersRESTBacktesting
|
|
6
|
+
from .pandas_backtesting import PandasDataBacktesting
|
|
7
|
+
from .polygon_backtesting import PolygonDataBacktesting
|
|
8
|
+
from .thetadata_backtesting import ThetaDataBacktesting
|
|
9
|
+
from .thetadata_backtesting_pandas import ThetaDataBacktestingPandas
|
|
10
|
+
from .yahoo_backtesting import YahooDataBacktesting
|
|
11
|
+
|
|
12
|
+
from .databento_backtesting import DataBentoDataBacktesting
|
|
13
|
+
from .databento_backtesting_pandas import DataBentoDataBacktestingPandas
|
|
14
|
+
from .databento_backtesting_polars import DataBentoDataBacktestingPolars
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AlpacaBacktesting",
|
|
18
|
+
"AlphaVantageBacktesting",
|
|
19
|
+
"BacktestingBroker",
|
|
20
|
+
"CcxtBacktesting",
|
|
21
|
+
"InteractiveBrokersRESTBacktesting",
|
|
22
|
+
"PandasDataBacktesting",
|
|
23
|
+
"PolygonDataBacktesting",
|
|
24
|
+
"ThetaDataBacktesting",
|
|
25
|
+
"ThetaDataBacktestingPandas",
|
|
26
|
+
"YahooDataBacktesting",
|
|
27
|
+
"DataBentoDataBacktesting",
|
|
28
|
+
"DataBentoDataBacktestingPandas",
|
|
29
|
+
"DataBentoDataBacktestingPolars",
|
|
30
|
+
]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import math
|
|
1
2
|
import traceback
|
|
2
3
|
import threading
|
|
3
4
|
from collections import OrderedDict
|
|
@@ -1353,17 +1354,21 @@ class BacktestingBroker(Broker):
|
|
|
1353
1354
|
|
|
1354
1355
|
# Get the OHLCV data for the asset if we're using the YAHOO, CCXT data source
|
|
1355
1356
|
data_source_name = self.data_source.SOURCE.upper()
|
|
1356
|
-
if data_source_name in ["CCXT", "YAHOO", "ALPACA", "DATABENTO"]:
|
|
1357
|
-
#
|
|
1357
|
+
if data_source_name in ["CCXT", "YAHOO", "ALPACA", "DATABENTO", "DATABENTO_POLARS"]:
|
|
1358
|
+
# Negative deltas here are intentional: _pull_source_symbol_bars subtracts the offset, so
|
|
1359
|
+
# passing -1 minute yields an effective +1 minute guard that keeps us on the previously
|
|
1360
|
+
# completed bar. See tests/*_lookahead for regression coverage.
|
|
1358
1361
|
timeshift = timedelta(minutes=-1)
|
|
1359
|
-
if data_source_name
|
|
1360
|
-
# DataBento
|
|
1362
|
+
if data_source_name in {"DATABENTO", "DATABENTO_POLARS"}:
|
|
1363
|
+
# DataBento feeds can skip minutes around maintenance windows. Giving it a two-minute
|
|
1364
|
+
# cushion mirrors the legacy Polygon behaviour and avoids falling through gaps.
|
|
1361
1365
|
timeshift = timedelta(minutes=-2)
|
|
1362
1366
|
elif data_source_name == "YAHOO":
|
|
1363
|
-
# Yahoo
|
|
1367
|
+
# Yahoo daily bars are stamped at the close (16:00). A one-day backstep keeps fills on
|
|
1368
|
+
# the previous session so we never peek at the in-progress bar.
|
|
1364
1369
|
timeshift = timedelta(days=-1)
|
|
1365
1370
|
elif data_source_name == "ALPACA":
|
|
1366
|
-
# Alpaca minute bars
|
|
1371
|
+
# Alpaca minute bars line up with our clock already; no offset needed.
|
|
1367
1372
|
timeshift = None
|
|
1368
1373
|
|
|
1369
1374
|
ohlc = self.data_source.get_historical_prices(
|
|
@@ -1373,6 +1378,23 @@ class BacktestingBroker(Broker):
|
|
|
1373
1378
|
timeshift=timeshift,
|
|
1374
1379
|
)
|
|
1375
1380
|
|
|
1381
|
+
if (
|
|
1382
|
+
ohlc is None
|
|
1383
|
+
or getattr(ohlc, "df", None) is None
|
|
1384
|
+
or (hasattr(ohlc.df, "empty") and ohlc.df.empty)
|
|
1385
|
+
):
|
|
1386
|
+
if strategy is not None:
|
|
1387
|
+
display_symbol = getattr(order.asset, "symbol", order.asset)
|
|
1388
|
+
order_identifier = getattr(order, "identifier", None)
|
|
1389
|
+
if order_identifier is None:
|
|
1390
|
+
order_identifier = getattr(order, "id", "<unknown>")
|
|
1391
|
+
strategy.log_message(
|
|
1392
|
+
f"[DIAG] No historical bars returned for {display_symbol} at {self.datetime}; "
|
|
1393
|
+
f"pending {order.order_type} id={order_identifier}",
|
|
1394
|
+
color="yellow",
|
|
1395
|
+
)
|
|
1396
|
+
continue
|
|
1397
|
+
|
|
1376
1398
|
# Handle both pandas and polars DataFrames
|
|
1377
1399
|
if hasattr(ohlc.df, 'index'): # pandas
|
|
1378
1400
|
dt = ohlc.df.index[-1]
|
|
@@ -1406,6 +1428,16 @@ class BacktestingBroker(Broker):
|
|
|
1406
1428
|
)
|
|
1407
1429
|
# Check if we got any ohlc data
|
|
1408
1430
|
if ohlc is None or ohlc.empty:
|
|
1431
|
+
if strategy is not None:
|
|
1432
|
+
display_symbol = getattr(order.asset, "symbol", order.asset)
|
|
1433
|
+
order_identifier = getattr(order, "identifier", None)
|
|
1434
|
+
if order_identifier is None:
|
|
1435
|
+
order_identifier = getattr(order, "id", "<unknown>")
|
|
1436
|
+
strategy.log_message(
|
|
1437
|
+
f"[DIAG] No pandas bars for {display_symbol} at {self.datetime}; "
|
|
1438
|
+
f"canceling {order.order_type} id={order_identifier}",
|
|
1439
|
+
color="yellow",
|
|
1440
|
+
)
|
|
1409
1441
|
self.cancel_order(order)
|
|
1410
1442
|
continue
|
|
1411
1443
|
|
|
@@ -1502,41 +1534,89 @@ class BacktestingBroker(Broker):
|
|
|
1502
1534
|
strategy=strategy,
|
|
1503
1535
|
)
|
|
1504
1536
|
else:
|
|
1537
|
+
if strategy is not None:
|
|
1538
|
+
display_symbol = getattr(order.asset, "symbol", order.asset)
|
|
1539
|
+
order_identifier = getattr(order, "identifier", None)
|
|
1540
|
+
if order_identifier is None:
|
|
1541
|
+
order_identifier = getattr(order, "id", "<unknown>")
|
|
1542
|
+
detail = (
|
|
1543
|
+
f"limit={order.limit_price}, high={high}, low={low}"
|
|
1544
|
+
if order.order_type == Order.OrderType.LIMIT
|
|
1545
|
+
else f"type={order.order_type}, high={high}, low={low}, stop={getattr(order, 'stop_price', None)}"
|
|
1546
|
+
)
|
|
1547
|
+
strategy.log_message(
|
|
1548
|
+
f"[DIAG] Order remained open for {display_symbol} ({detail}) "
|
|
1549
|
+
f"id={order_identifier} at {self.datetime}",
|
|
1550
|
+
color="yellow",
|
|
1551
|
+
)
|
|
1505
1552
|
continue
|
|
1506
1553
|
|
|
1507
1554
|
# After handling all pending orders, cash settle any residual expired contracts.
|
|
1508
1555
|
self.process_expired_option_contracts(strategy)
|
|
1509
1556
|
|
|
1557
|
+
def _coerce_price(self, value):
|
|
1558
|
+
"""Convert numeric inputs to float when possible for safe comparisons."""
|
|
1559
|
+
if value is None:
|
|
1560
|
+
return None
|
|
1561
|
+
try:
|
|
1562
|
+
return float(value)
|
|
1563
|
+
except (TypeError, ValueError):
|
|
1564
|
+
return value
|
|
1565
|
+
|
|
1566
|
+
def _is_invalid_price(self, value):
|
|
1567
|
+
"""Determine whether a price is unusable (None or NaN)."""
|
|
1568
|
+
if value is None:
|
|
1569
|
+
return True
|
|
1570
|
+
if isinstance(value, float) and math.isnan(value):
|
|
1571
|
+
return True
|
|
1572
|
+
return False
|
|
1573
|
+
|
|
1510
1574
|
def limit_order(self, limit_price, side, open_, high, low):
|
|
1511
1575
|
"""Limit order logic."""
|
|
1576
|
+
open_val = self._coerce_price(open_)
|
|
1577
|
+
high_val = self._coerce_price(high)
|
|
1578
|
+
low_val = self._coerce_price(low)
|
|
1579
|
+
limit_val = self._coerce_price(limit_price)
|
|
1580
|
+
|
|
1581
|
+
if any(self._is_invalid_price(val) for val in (open_val, high_val, low_val, limit_val)):
|
|
1582
|
+
return None
|
|
1583
|
+
|
|
1512
1584
|
# Gap Up case: Limit wasn't triggered by previous candle but current candle opens higher, fill it now
|
|
1513
|
-
if side == "sell" and
|
|
1514
|
-
return
|
|
1585
|
+
if side == "sell" and limit_val <= open_val:
|
|
1586
|
+
return open_val
|
|
1515
1587
|
|
|
1516
1588
|
# Gap Down case: Limit wasn't triggered by previous candle but current candle opens lower, fill it now
|
|
1517
|
-
if side == "buy" and
|
|
1518
|
-
return
|
|
1589
|
+
if side == "buy" and limit_val >= open_val:
|
|
1590
|
+
return open_val
|
|
1519
1591
|
|
|
1520
1592
|
# Current candle triggered limit normally
|
|
1521
|
-
if
|
|
1522
|
-
return
|
|
1593
|
+
if low_val <= limit_val <= high_val:
|
|
1594
|
+
return limit_val
|
|
1523
1595
|
|
|
1524
1596
|
# Limit has not been met
|
|
1525
1597
|
return None
|
|
1526
1598
|
|
|
1527
1599
|
def stop_order(self, stop_price, side, open_, high, low):
|
|
1528
1600
|
"""Stop order logic."""
|
|
1601
|
+
open_val = self._coerce_price(open_)
|
|
1602
|
+
high_val = self._coerce_price(high)
|
|
1603
|
+
low_val = self._coerce_price(low)
|
|
1604
|
+
stop_val = self._coerce_price(stop_price)
|
|
1605
|
+
|
|
1606
|
+
if any(self._is_invalid_price(val) for val in (open_val, high_val, low_val, stop_val)):
|
|
1607
|
+
return None
|
|
1608
|
+
|
|
1529
1609
|
# Gap Down case: Stop wasn't triggered by previous candle but current candle opens lower, fill it now
|
|
1530
|
-
if side == "sell" and
|
|
1531
|
-
return
|
|
1610
|
+
if side == "sell" and stop_val >= open_val:
|
|
1611
|
+
return open_val
|
|
1532
1612
|
|
|
1533
1613
|
# Gap Up case: Stop wasn't triggered by previous candle but current candle opens higher, fill it now
|
|
1534
|
-
if side == "buy" and
|
|
1535
|
-
return
|
|
1614
|
+
if side == "buy" and stop_val <= open_val:
|
|
1615
|
+
return open_val
|
|
1536
1616
|
|
|
1537
1617
|
# Current candle triggered stop normally
|
|
1538
|
-
if
|
|
1539
|
-
return
|
|
1618
|
+
if low_val <= stop_val <= high_val:
|
|
1619
|
+
return stop_val
|
|
1540
1620
|
|
|
1541
1621
|
# Stop has not been met
|
|
1542
1622
|
return None
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Canonical DataBento backtesting aliasing the Polars implementation."""
|
|
2
|
+
|
|
3
|
+
from .databento_backtesting_polars import DataBentoDataBacktestingPolars as DataBentoDataBacktesting
|
|
4
|
+
from .databento_backtesting_pandas import DataBentoDataBacktestingPandas
|
|
5
|
+
from .databento_backtesting_polars import DataBentoDataBacktestingPolars
|
|
6
|
+
|
|
7
|
+
__all__ = ["DataBentoDataBacktesting", "DataBentoDataBacktestingPandas", "DataBentoDataBacktestingPolars"]
|
|
@@ -7,7 +7,9 @@ from lumibot import LUMIBOT_DEFAULT_PYTZ
|
|
|
7
7
|
from lumibot.data_sources import PandasData
|
|
8
8
|
from lumibot.entities import Asset, Data
|
|
9
9
|
from lumibot.tools import databento_helper
|
|
10
|
+
from lumibot.tools.databento_helper import DataBentoAuthenticationError
|
|
10
11
|
from lumibot.tools.helpers import to_datetime_aware
|
|
12
|
+
from termcolor import colored
|
|
11
13
|
|
|
12
14
|
from lumibot.tools.lumibot_logger import get_logger
|
|
13
15
|
logger = get_logger(__name__)
|
|
@@ -15,7 +17,7 @@ logger = get_logger(__name__)
|
|
|
15
17
|
START_BUFFER = timedelta(days=5)
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
class
|
|
20
|
+
class DataBentoDataBacktestingPandas(PandasData):
|
|
19
21
|
"""
|
|
20
22
|
Backtesting implementation of DataBento data source
|
|
21
23
|
|
|
@@ -85,7 +87,7 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
85
87
|
logger.error("DataBento package not available. Please install with: pip install databento")
|
|
86
88
|
raise ImportError("DataBento package not available")
|
|
87
89
|
|
|
88
|
-
logger.
|
|
90
|
+
logger.debug(f"DataBento backtesting initialized for period: {datetime_start} to {datetime_end}")
|
|
89
91
|
|
|
90
92
|
def _check_and_clear_cache(self):
|
|
91
93
|
"""
|
|
@@ -157,8 +159,11 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
157
159
|
reference_date=self.datetime_start
|
|
158
160
|
)
|
|
159
161
|
|
|
160
|
-
logger.
|
|
162
|
+
logger.debug(f"Successfully set multiplier for {asset.symbol}: {asset.multiplier}")
|
|
161
163
|
|
|
164
|
+
except DataBentoAuthenticationError as e:
|
|
165
|
+
logger.error(colored(f"DataBento authentication failed while fetching multiplier for {asset.symbol}: {e}", "red"))
|
|
166
|
+
raise
|
|
162
167
|
except Exception as e:
|
|
163
168
|
logger.warning(f"Could not fetch multiplier for {asset.symbol}: {e}")
|
|
164
169
|
|
|
@@ -177,7 +182,7 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
177
182
|
if not assets:
|
|
178
183
|
return
|
|
179
184
|
|
|
180
|
-
logger.
|
|
185
|
+
logger.debug(f"Prefetching DataBento data for {len(assets)} assets...")
|
|
181
186
|
|
|
182
187
|
for asset in assets:
|
|
183
188
|
# Create search key for the asset
|
|
@@ -193,7 +198,7 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
193
198
|
start_datetime = self.datetime_start - START_BUFFER
|
|
194
199
|
end_datetime = self.datetime_end + timedelta(days=1)
|
|
195
200
|
|
|
196
|
-
logger.
|
|
201
|
+
logger.debug(f"Fetching {asset.symbol} data from {start_datetime.date()} to {end_datetime.date()}")
|
|
197
202
|
|
|
198
203
|
# Get data from DataBento for entire period
|
|
199
204
|
df = databento_helper.get_price_data_from_databento(
|
|
@@ -231,11 +236,14 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
231
236
|
quote=quote_asset,
|
|
232
237
|
)
|
|
233
238
|
self.pandas_data[search_asset] = data_obj
|
|
234
|
-
logger.
|
|
239
|
+
logger.debug(f"Cached {len(df)} rows for {asset.symbol}")
|
|
235
240
|
|
|
236
241
|
# Mark as prefetched
|
|
237
242
|
self._prefetched_assets.add(search_asset)
|
|
238
243
|
|
|
244
|
+
except DataBentoAuthenticationError as e:
|
|
245
|
+
logger.error(colored(f"DataBento authentication failed while prefetching {asset.symbol}: {e}", "red"))
|
|
246
|
+
raise
|
|
239
247
|
except Exception as e:
|
|
240
248
|
logger.error(f"Error prefetching data for {asset.symbol}: {str(e)}")
|
|
241
249
|
logger.error(traceback.format_exc())
|
|
@@ -317,7 +325,7 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
317
325
|
try:
|
|
318
326
|
# Only log fetch message once per asset/timestep combination
|
|
319
327
|
if log_key not in self._logged_requests:
|
|
320
|
-
logger.
|
|
328
|
+
logger.debug(f"Fetching {timestep} data for {asset_separated.symbol}")
|
|
321
329
|
self._logged_requests.add(log_key)
|
|
322
330
|
|
|
323
331
|
# Get the start datetime and timestep unit
|
|
@@ -373,6 +381,9 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
373
381
|
|
|
374
382
|
self.pandas_data[search_asset] = data_obj
|
|
375
383
|
|
|
384
|
+
except DataBentoAuthenticationError as e:
|
|
385
|
+
logger.error(colored(f"DataBento authentication failed for {asset_separated.symbol}: {e}", "red"))
|
|
386
|
+
raise
|
|
376
387
|
except Exception as e:
|
|
377
388
|
logger.error(f"Error updating pandas data for {asset_separated.symbol}: {str(e)}")
|
|
378
389
|
logger.error(traceback.format_exc())
|
|
@@ -458,6 +469,9 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
458
469
|
venue=exchange
|
|
459
470
|
)
|
|
460
471
|
|
|
472
|
+
except DataBentoAuthenticationError as e:
|
|
473
|
+
logger.error(colored(f"DataBento authentication failed while getting last price for {asset.symbol}: {e}", "red"))
|
|
474
|
+
raise
|
|
461
475
|
except Exception as e:
|
|
462
476
|
logger.error(f"Error getting last price for {asset.symbol}: {e}")
|
|
463
477
|
return None
|
|
@@ -555,6 +569,9 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
555
569
|
logger.warning(f"No data found for {asset.symbol}")
|
|
556
570
|
result[asset] = None
|
|
557
571
|
|
|
572
|
+
except DataBentoAuthenticationError as e:
|
|
573
|
+
logger.error(colored(f"DataBento authentication failed while getting bars for {asset}: {e}", "red"))
|
|
574
|
+
raise
|
|
558
575
|
except Exception as e:
|
|
559
576
|
logger.error(f"Error getting bars for {asset}: {e}")
|
|
560
577
|
result[asset] = None
|
|
@@ -616,7 +633,28 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
616
633
|
df = asset_data.df
|
|
617
634
|
|
|
618
635
|
if not df.empty:
|
|
619
|
-
#
|
|
636
|
+
# ========================================================================
|
|
637
|
+
# CRITICAL: NEGATIVE TIMESHIFT ARITHMETIC FOR LOOKAHEAD
|
|
638
|
+
# ========================================================================
|
|
639
|
+
# Negative timeshift allows broker to "peek ahead" for realistic fills.
|
|
640
|
+
#
|
|
641
|
+
# Example with timeshift=-2 at broker_dt=09:30:
|
|
642
|
+
# - Arithmetic: current_dt - timeshift
|
|
643
|
+
# = 09:30 - timedelta(minutes=-2)
|
|
644
|
+
# = 09:30 - (-2 minutes)
|
|
645
|
+
# = 09:30 + 2 minutes
|
|
646
|
+
# = 09:32
|
|
647
|
+
# - Data source returns bars up to 09:32: [..., 09:29, 09:30, 09:31, 09:32]
|
|
648
|
+
# - Broker filters to future bars (>= 09:30): [09:30, 09:31, 09:32]
|
|
649
|
+
# - Broker uses FIRST future bar (09:31) and its OPEN price for fills
|
|
650
|
+
#
|
|
651
|
+
# Why this is necessary:
|
|
652
|
+
# - Real world: Order placed at 09:30:30 fills at 09:31:00 open
|
|
653
|
+
# - Backtesting: Broker at 09:30 needs to see 09:31 bar for realistic fills
|
|
654
|
+
#
|
|
655
|
+
# DO NOT change this arithmetic! "current_dt - timeshift" with negative
|
|
656
|
+
# timeshift is CORRECT and INTENTIONAL.
|
|
657
|
+
# ========================================================================
|
|
620
658
|
shift_seconds = 0
|
|
621
659
|
if timeshift:
|
|
622
660
|
if isinstance(timeshift, int):
|
|
@@ -638,9 +676,21 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
638
676
|
|
|
639
677
|
cutoff_dt = current_dt_aware - bar_delta
|
|
640
678
|
|
|
679
|
+
# INSTRUMENTATION: Log timeshift application and filtering
|
|
680
|
+
broker_dt_orig = self.get_datetime()
|
|
681
|
+
filter_branch = "shift_seconds > 0" if shift_seconds > 0 else "shift_seconds <= 0"
|
|
682
|
+
|
|
641
683
|
# Filter data up to current backtest time (exclude current bar unless broker overrides)
|
|
642
684
|
filtered_df = df[df.index <= cutoff_dt] if shift_seconds > 0 else df[df.index < current_dt_aware]
|
|
643
685
|
|
|
686
|
+
# Log what bar we're returning
|
|
687
|
+
if not filtered_df.empty:
|
|
688
|
+
returned_bar_dt = filtered_df.index[-1]
|
|
689
|
+
logger.debug(f"[TIMESHIFT_PANDAS] asset={asset_separated.symbol} broker_dt={broker_dt_orig} "
|
|
690
|
+
f"timeshift={timeshift} shift_seconds={shift_seconds} "
|
|
691
|
+
f"shifted_dt={current_dt_aware} cutoff_dt={cutoff_dt} "
|
|
692
|
+
f"filter={filter_branch} returned_bar={returned_bar_dt}")
|
|
693
|
+
|
|
644
694
|
# Take the last 'length' bars
|
|
645
695
|
result_df = filtered_df.tail(length)
|
|
646
696
|
|
|
@@ -685,4 +735,4 @@ class DataBentoDataBacktesting(PandasData):
|
|
|
685
735
|
# Prefetch data for all assets
|
|
686
736
|
self.prefetch_data(assets, timestep)
|
|
687
737
|
|
|
688
|
-
logger.
|
|
738
|
+
logger.debug(f"Initialized DataBento backtesting with prefetched data for {len(assets)} assets")
|