lumibot 4.1.2__py3-none-any.whl → 4.2.0__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 lumibot might be problematic. Click here for more details.
- lumibot/backtesting/__init__.py +19 -5
- lumibot/backtesting/backtesting_broker.py +98 -18
- lumibot/backtesting/databento_backtesting.py +5 -686
- lumibot/backtesting/databento_backtesting_pandas.py +738 -0
- lumibot/backtesting/databento_backtesting_polars.py +860 -546
- lumibot/backtesting/fix_debug.py +37 -0
- lumibot/backtesting/thetadata_backtesting.py +9 -355
- lumibot/backtesting/thetadata_backtesting_pandas.py +1178 -0
- lumibot/brokers/alpaca.py +8 -1
- lumibot/brokers/schwab.py +12 -2
- lumibot/credentials.py +13 -0
- lumibot/data_sources/__init__.py +5 -8
- lumibot/data_sources/data_source.py +6 -2
- lumibot/data_sources/data_source_backtesting.py +30 -0
- lumibot/data_sources/databento_data.py +5 -390
- lumibot/data_sources/databento_data_pandas.py +440 -0
- lumibot/data_sources/databento_data_polars.py +15 -9
- lumibot/data_sources/pandas_data.py +30 -17
- lumibot/data_sources/polars_data.py +986 -0
- lumibot/data_sources/polars_mixin.py +472 -96
- lumibot/data_sources/polygon_data_polars.py +5 -0
- lumibot/data_sources/yahoo_data.py +9 -2
- lumibot/data_sources/yahoo_data_polars.py +5 -0
- lumibot/entities/__init__.py +15 -0
- lumibot/entities/asset.py +5 -28
- lumibot/entities/bars.py +89 -20
- lumibot/entities/data.py +29 -6
- lumibot/entities/data_polars.py +668 -0
- lumibot/entities/position.py +38 -4
- lumibot/strategies/_strategy.py +31 -9
- lumibot/strategies/strategy.py +61 -49
- lumibot/tools/backtest_cache.py +284 -0
- lumibot/tools/databento_helper.py +65 -42
- lumibot/tools/databento_helper_polars.py +748 -778
- lumibot/tools/futures_roll.py +251 -0
- lumibot/tools/indicators.py +135 -104
- lumibot/tools/polars_utils.py +142 -0
- lumibot/tools/thetadata_helper.py +1068 -134
- {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/METADATA +9 -1
- {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/RECORD +72 -148
- tests/backtest/test_databento.py +37 -6
- tests/backtest/test_databento_comprehensive_trading.py +70 -87
- tests/backtest/test_databento_parity.py +31 -7
- tests/backtest/test_debug_avg_fill_price.py +1 -1
- tests/backtest/test_example_strategies.py +11 -1
- tests/backtest/test_futures_edge_cases.py +96 -63
- tests/backtest/test_futures_single_trade.py +2 -2
- tests/backtest/test_futures_ultra_simple.py +2 -2
- tests/backtest/test_polars_lru_eviction.py +470 -0
- tests/backtest/test_yahoo.py +42 -0
- tests/test_asset.py +4 -4
- tests/test_backtest_cache_manager.py +149 -0
- tests/test_backtesting_data_source_env.py +50 -10
- tests/test_continuous_futures_resolution.py +60 -48
- tests/test_data_polars_parity.py +160 -0
- tests/test_databento_asset_validation.py +23 -5
- tests/test_databento_backtesting.py +1 -1
- tests/test_databento_backtesting_polars.py +312 -192
- tests/test_databento_data.py +220 -463
- tests/test_databento_helper.py +6 -1
- tests/test_databento_live.py +10 -10
- tests/test_futures_roll.py +38 -0
- tests/test_indicator_subplots.py +101 -0
- tests/test_market_infinite_loop_bug.py +77 -3
- tests/test_polars_resample.py +67 -0
- tests/test_polygon_helper.py +46 -0
- tests/test_thetadata_backwards_compat.py +97 -0
- tests/test_thetadata_helper.py +222 -23
- tests/test_thetadata_pandas_verification.py +186 -0
- lumibot/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/__pycache__/constants.cpython-312.pyc +0 -0
- lumibot/__pycache__/credentials.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/alpaca_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/alpha_vantage_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/backtesting_broker.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/ccxt_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/databento_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/interactive_brokers_rest_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/pandas_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/polygon_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/thetadata_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/yahoo_backtesting.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/alpaca.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/bitunix.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/broker.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/ccxt.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/example_broker.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/interactive_brokers.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/interactive_brokers_rest.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/projectx.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/schwab.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/tradier.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/tradovate.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/alpaca_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/alpha_vantage_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/bitunix_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/ccxt_backtesting_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/ccxt_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/data_source.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/data_source_backtesting.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/databento_data_polars_backtesting.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/databento_data_polars_live.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/example_broker_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/exceptions.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/interactive_brokers_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/interactive_brokers_rest_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/pandas_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/polars_mixin.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/polygon_data_polars.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/projectx_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/schwab_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/tradier_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/tradovate_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/yahoo_data_polars.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/asset.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/bar.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/bars.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/chains.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/data.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/dataline.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/order.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/position.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/quote.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/trading_fee.cpython-312.pyc +0 -0
- lumibot/example_strategies/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/example_strategies/__pycache__/test_broker_functions.cpython-312-pytest-8.4.1.pyc +0 -0
- lumibot/strategies/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/strategies/__pycache__/_strategy.cpython-312.pyc +0 -0
- lumibot/strategies/__pycache__/strategy.cpython-312.pyc +0 -0
- lumibot/strategies/__pycache__/strategy_executor.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/alpaca_helpers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/bitunix_helpers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/black_scholes.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/ccxt_data_store.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/databento_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/databento_helper_polars.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/debugers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/decorators.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/helpers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/indicators.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/lumibot_logger.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/pandas.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/polygon_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/polygon_helper_async.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/polygon_helper_polars_optimized.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/projectx_helpers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/schwab_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/thetadata_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/types.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/yahoo_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/yahoo_helper_polars_optimized.cpython-312.pyc +0 -0
- lumibot/traders/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/traders/__pycache__/trader.cpython-312.pyc +0 -0
- lumibot/trading_builtins/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/trading_builtins/__pycache__/custom_stream.cpython-312.pyc +0 -0
- lumibot/trading_builtins/__pycache__/safe_list.cpython-312.pyc +0 -0
- {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/WHEEL +0 -0
- {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/licenses/LICENSE +0 -0
- {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/top_level.txt +0 -0
|
@@ -9,12 +9,18 @@ from decimal import Decimal
|
|
|
9
9
|
import pandas as pd
|
|
10
10
|
from lumibot import LUMIBOT_CACHE_FOLDER
|
|
11
11
|
from lumibot.entities import Asset
|
|
12
|
-
from lumibot.tools import
|
|
12
|
+
from lumibot.tools import futures_roll
|
|
13
|
+
from termcolor import colored
|
|
13
14
|
|
|
14
15
|
# Set up module-specific logger
|
|
15
16
|
from lumibot.tools.lumibot_logger import get_logger
|
|
16
17
|
logger = get_logger(__name__)
|
|
17
18
|
|
|
19
|
+
|
|
20
|
+
class DataBentoAuthenticationError(RuntimeError):
|
|
21
|
+
"""Raised when DataBento rejects authentication credentials."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
18
24
|
# DataBento imports (will be installed as dependency)
|
|
19
25
|
try:
|
|
20
26
|
import databento as db
|
|
@@ -161,11 +167,16 @@ class DataBentoClient:
|
|
|
161
167
|
continue
|
|
162
168
|
else:
|
|
163
169
|
logger.error(f"DataBento authentication failed after {self.max_retries} retries")
|
|
170
|
+
raise DataBentoAuthenticationError(
|
|
171
|
+
f"DataBento authentication failed after {self.max_retries} retries: {str(e)}"
|
|
172
|
+
) from e
|
|
164
173
|
|
|
165
174
|
# For non-auth errors, don't retry - fail fast
|
|
166
|
-
logger.error(
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
logger.error(
|
|
176
|
+
"DATABENTO_API_ERROR: DataBento API error: %s | Symbols: %s, Start: %s, End: %s",
|
|
177
|
+
str(e), symbols, start, end
|
|
178
|
+
)
|
|
179
|
+
raise
|
|
169
180
|
|
|
170
181
|
# This should never be reached, but just in case
|
|
171
182
|
raise Exception(f"DataBento request failed after {self.max_retries} retries")
|
|
@@ -593,13 +604,29 @@ def _filter_front_month_rows_pandas(
|
|
|
593
604
|
if df.empty or "symbol" not in df.columns or schedule is None:
|
|
594
605
|
return df
|
|
595
606
|
|
|
607
|
+
index_tz = getattr(df.index, "tz", None)
|
|
608
|
+
|
|
609
|
+
def _align(ts: datetime | pd.Timestamp | None) -> pd.Timestamp | None:
|
|
610
|
+
if ts is None:
|
|
611
|
+
return None
|
|
612
|
+
ts_pd = pd.Timestamp(ts)
|
|
613
|
+
if index_tz is None:
|
|
614
|
+
return ts_pd.tz_localize(None) if ts_pd.tz is not None else ts_pd
|
|
615
|
+
if ts_pd.tz is None:
|
|
616
|
+
ts_pd = ts_pd.tz_localize(index_tz)
|
|
617
|
+
else:
|
|
618
|
+
ts_pd = ts_pd.tz_convert(index_tz)
|
|
619
|
+
return ts_pd
|
|
620
|
+
|
|
596
621
|
mask = pd.Series(False, index=df.index)
|
|
597
622
|
for symbol, start_dt, end_dt in schedule:
|
|
598
623
|
cond = df["symbol"] == symbol
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if
|
|
602
|
-
cond &= df.index
|
|
624
|
+
start_aligned = _align(start_dt)
|
|
625
|
+
end_aligned = _align(end_dt)
|
|
626
|
+
if start_aligned is not None:
|
|
627
|
+
cond &= df.index >= start_aligned
|
|
628
|
+
if end_aligned is not None:
|
|
629
|
+
cond &= df.index < end_aligned
|
|
603
630
|
mask |= cond
|
|
604
631
|
|
|
605
632
|
filtered = df.loc[mask]
|
|
@@ -783,15 +810,31 @@ def get_price_data_from_databento(
|
|
|
783
810
|
start_naive = start.replace(tzinfo=None) if start.tzinfo is not None else start
|
|
784
811
|
end_naive = end.replace(tzinfo=None) if end.tzinfo is not None else end
|
|
785
812
|
|
|
786
|
-
|
|
813
|
+
roll_asset = asset
|
|
814
|
+
if asset.asset_type == Asset.AssetType.FUTURE and not asset.expiration:
|
|
815
|
+
roll_asset = Asset(asset.symbol, Asset.AssetType.CONT_FUTURE)
|
|
816
|
+
|
|
817
|
+
if roll_asset.asset_type == Asset.AssetType.CONT_FUTURE:
|
|
787
818
|
schedule_start = start
|
|
788
|
-
symbols =
|
|
789
|
-
|
|
819
|
+
symbols = futures_roll.resolve_symbols_for_range(
|
|
820
|
+
roll_asset,
|
|
821
|
+
schedule_start,
|
|
822
|
+
end,
|
|
823
|
+
year_digits=1,
|
|
824
|
+
)
|
|
825
|
+
front_symbol = futures_roll.resolve_symbol_for_datetime(
|
|
826
|
+
roll_asset,
|
|
827
|
+
reference_date or start,
|
|
828
|
+
year_digits=1,
|
|
829
|
+
)
|
|
790
830
|
if front_symbol not in symbols:
|
|
791
831
|
symbols.insert(0, front_symbol)
|
|
792
832
|
else:
|
|
793
833
|
schedule_start = start
|
|
794
|
-
front_symbol = _format_futures_symbol_for_databento(
|
|
834
|
+
front_symbol = _format_futures_symbol_for_databento(
|
|
835
|
+
asset,
|
|
836
|
+
reference_date=reference_date or start,
|
|
837
|
+
)
|
|
795
838
|
symbols = [front_symbol]
|
|
796
839
|
|
|
797
840
|
# Ensure multiplier is populated using the first contract.
|
|
@@ -856,6 +899,13 @@ def get_price_data_from_databento(
|
|
|
856
899
|
end=end_naive,
|
|
857
900
|
**kwargs,
|
|
858
901
|
)
|
|
902
|
+
except DataBentoAuthenticationError as exc:
|
|
903
|
+
auth_msg = colored(
|
|
904
|
+
f"❌ DataBento authentication failed while requesting {symbol}: {exc}",
|
|
905
|
+
"red"
|
|
906
|
+
)
|
|
907
|
+
logger.error(auth_msg)
|
|
908
|
+
raise
|
|
859
909
|
except Exception as exc:
|
|
860
910
|
logger.warning(f"Error fetching {symbol} from DataBento: {exc}")
|
|
861
911
|
continue
|
|
@@ -877,38 +927,11 @@ def get_price_data_from_databento(
|
|
|
877
927
|
combined = pd.concat(frames, axis=0)
|
|
878
928
|
combined.sort_index(inplace=True)
|
|
879
929
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
def get_definition(symbol_code: str) -> Optional[Dict]:
|
|
883
|
-
nonlocal definition_client
|
|
884
|
-
cache_key = (symbol_code, dataset)
|
|
885
|
-
if cache_key in _INSTRUMENT_DEFINITION_CACHE:
|
|
886
|
-
return _INSTRUMENT_DEFINITION_CACHE[cache_key]
|
|
887
|
-
if definition_client is None:
|
|
888
|
-
try:
|
|
889
|
-
definition_client = DataBentoClient(api_key=api_key)
|
|
890
|
-
except Exception as exc:
|
|
891
|
-
logger.warning(f"Unable to create DataBento definition client: {exc}")
|
|
892
|
-
return None
|
|
893
|
-
try:
|
|
894
|
-
definition = definition_client.get_instrument_definition(
|
|
895
|
-
dataset=dataset,
|
|
896
|
-
symbol=symbol_code,
|
|
897
|
-
reference_date=reference_date or start,
|
|
898
|
-
)
|
|
899
|
-
except Exception as exc:
|
|
900
|
-
logger.warning(f"Failed to fetch definition for {symbol_code}: {exc}")
|
|
901
|
-
return None
|
|
902
|
-
if definition:
|
|
903
|
-
_INSTRUMENT_DEFINITION_CACHE[cache_key] = definition
|
|
904
|
-
return definition
|
|
905
|
-
|
|
906
|
-
schedule = databento_roll.build_roll_schedule(
|
|
907
|
-
asset,
|
|
930
|
+
schedule = futures_roll.build_roll_schedule(
|
|
931
|
+
roll_asset,
|
|
908
932
|
schedule_start,
|
|
909
933
|
end,
|
|
910
|
-
|
|
911
|
-
roll_days=databento_roll.ROLL_DAYS_BEFORE_EXPIRATION,
|
|
934
|
+
year_digits=1,
|
|
912
935
|
)
|
|
913
936
|
|
|
914
937
|
if schedule:
|