lumibot 4.2.1__py3-none-any.whl → 4.2.2__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.

@@ -26,6 +26,17 @@ CONNECTION_MAX_RETRIES = 60
26
26
  BOOT_GRACE_PERIOD = 5.0
27
27
  MAX_RESTART_ATTEMPTS = 3
28
28
 
29
+
30
+ def _resolve_asset_folder(asset_obj: Asset) -> str:
31
+ asset_type = getattr(asset_obj, "asset_type", None) or "stock"
32
+ asset_key = str(asset_type).strip().lower()
33
+ return asset_key
34
+
35
+
36
+ def _normalize_folder_component(value: str, fallback: str) -> str:
37
+ normalized = str(value or "").strip().lower().replace(" ", "_")
38
+ return normalized or fallback
39
+
29
40
  # Global process tracking for ThetaTerminal
30
41
  THETA_DATA_PROCESS = None
31
42
  THETA_DATA_PID = None
@@ -785,7 +796,11 @@ def get_trading_dates(asset: Asset, start: datetime, end: datetime):
785
796
  def build_cache_filename(asset: Asset, timespan: str, datastyle: str = "ohlc"):
786
797
  """Helper function to create the cache filename for a given asset and timespan"""
787
798
 
788
- lumibot_cache_folder = Path(LUMIBOT_CACHE_FOLDER) / CACHE_SUBFOLDER
799
+ provider_root = Path(LUMIBOT_CACHE_FOLDER) / CACHE_SUBFOLDER
800
+ asset_folder = _resolve_asset_folder(asset)
801
+ timespan_folder = _normalize_folder_component(timespan, "unknown")
802
+ datastyle_folder = _normalize_folder_component(datastyle, "default")
803
+ base_folder = provider_root / asset_folder / timespan_folder / datastyle_folder
789
804
 
790
805
  # If It's an option then also add the expiration date, strike price and right to the filename
791
806
  if asset.asset_type == "option":
@@ -799,7 +814,7 @@ def build_cache_filename(asset: Asset, timespan: str, datastyle: str = "ohlc"):
799
814
  uniq_str = asset.symbol
800
815
 
801
816
  cache_filename = f"{asset.asset_type}_{uniq_str}_{timespan}_{datastyle}.parquet"
802
- cache_file = lumibot_cache_folder / cache_filename
817
+ cache_file = base_folder / cache_filename
803
818
  return cache_file
804
819
 
805
820
 
@@ -1969,7 +1984,7 @@ def get_chains_cached(
1969
1984
  Retrieve option chain with caching (MATCHES POLYGON PATTERN).
1970
1985
 
1971
1986
  This function follows the EXACT same caching strategy as Polygon:
1972
- 1. Check cache: LUMIBOT_CACHE_FOLDER/thetadata/option_chains/{symbol}_{date}.parquet
1987
+ 1. Check cache: LUMIBOT_CACHE_FOLDER/thetadata/<asset-type>/option_chains/{symbol}_{date}.parquet
1973
1988
  2. Reuse files within RECENT_FILE_TOLERANCE_DAYS (default 7 days)
1974
1989
  3. If not found, fetch from ThetaData and save to cache
1975
1990
  4. Use pyarrow engine with snappy compression
@@ -2006,7 +2021,7 @@ def get_chains_cached(
2006
2021
  return None
2007
2022
 
2008
2023
  # 2) Build cache folder path
2009
- chain_folder = Path(LUMIBOT_CACHE_FOLDER) / "thetadata" / "option_chains"
2024
+ chain_folder = Path(LUMIBOT_CACHE_FOLDER) / "thetadata" / _resolve_asset_folder(asset) / "option_chains"
2010
2025
  chain_folder.mkdir(parents=True, exist_ok=True)
2011
2026
 
2012
2027
  # 3) Check for recent cached file (within RECENT_FILE_TOLERANCE_DAYS)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lumibot
3
- Version: 4.2.1
3
+ Version: 4.2.2
4
4
  Summary: Backtesting and Trading Library, Made by Lumiwealth
5
5
  Home-page: https://github.com/Lumiwealth/lumibot
6
6
  Author: Robert Grzesik
@@ -132,7 +132,7 @@ lumibot/tools/polygon_helper_async.py,sha256=YHDXa9kmkkn8jh7hToY6GP5etyXS9Tj-uky
132
132
  lumibot/tools/polygon_helper_polars_optimized.py,sha256=NaIZ-5Av-G2McPEKHyJ-x65W72W_Agnz4lRgvXfQp8c,30415
133
133
  lumibot/tools/projectx_helpers.py,sha256=EIemLfbG923T_RBV_i6s6A9xgs7dt0et0oCnhFwdWfA,58299
134
134
  lumibot/tools/schwab_helper.py,sha256=CXnYhgsXOIb5MgmIYOp86aLxsBF9oeVrMGrjwl_GEv0,11768
135
- lumibot/tools/thetadata_helper.py,sha256=z9x26RXazzVmne7a1AcAdxKW2IWTpOLcJ7auEVZikcE,79407
135
+ lumibot/tools/thetadata_helper.py,sha256=-FJm_NXSBJoyYLcdNQXGytMbmr-wx7F1gItnRnBUWf0,80072
136
136
  lumibot/tools/types.py,sha256=x-aQBeC6ZTN2-pUyxyo69Q0j5e0c_swdfe06kfrWSVc,1978
137
137
  lumibot/tools/yahoo_helper.py,sha256=htcKKkuktatIckVKfLc_ms0X75mXColysQhrZW244z8,19497
138
138
  lumibot/tools/yahoo_helper_polars_optimized.py,sha256=g9xBN-ReHSW4Aj9EMU_OncBXVS1HpfL8LTHit9ZxFY4,7417
@@ -142,7 +142,7 @@ lumibot/traders/trader.py,sha256=KMif3WoZtnSxA0BzoK3kvkTITNELrDFIortx1BYBv8s,967
142
142
  lumibot/trading_builtins/__init__.py,sha256=vH2QL5zLjL3slfEV1YW-BvQHtEYLCFkIWTZDfh3y8LE,87
143
143
  lumibot/trading_builtins/custom_stream.py,sha256=8_XiPT0JzyXrgnXCXoovGGUrWEfnG4ohIYMPfB_Nook,5264
144
144
  lumibot/trading_builtins/safe_list.py,sha256=IIjZOHSiZYK25A4WBts0oJaZNOJDsjZL65MOSHhE3Ig,1975
145
- lumibot-4.2.1.dist-info/licenses/LICENSE,sha256=fYhGIyxjyNXACgpNQS3xxpxDOaVOWRVxZMCRbsDv8k0,35130
145
+ lumibot-4.2.2.dist-info/licenses/LICENSE,sha256=fYhGIyxjyNXACgpNQS3xxpxDOaVOWRVxZMCRbsDv8k0,35130
146
146
  tests/__init__.py,sha256=3-VoT-nAuqMfwufd4ceN6fXaHl_zCfDCSXJOTp1ywYQ,393
147
147
  tests/conftest.py,sha256=UBw_2fx7r6TZPKus2b1Qxrzmd4bg8EEBnX1vCHUuSVA,3311
148
148
  tests/fixtures.py,sha256=wOHQsh1SGHnXe_PGi6kDWI30CS_Righi7Ig7vwSEKT4,9082
@@ -157,7 +157,7 @@ tests/test_apscheduler_warnings.py,sha256=08lzprPjKq_KAIy-_gMo2zZATpo7VPvmg_qnmS
157
157
  tests/test_asset.py,sha256=qk9giu-z3kPoFRXL6Wi0-Ly1mb7YpUNtViuLUMjaEhY,7659
158
158
  tests/test_asset_auto_expiry.py,sha256=aa0JVbAIHPKupQ6gMDk5QaDWDXV1xqHMdX513WjwWNQ,13716
159
159
  tests/test_auto_market_inference.py,sha256=NFauxzT9ZKDSjrkgHWLgcrLfJMeIj04uWvSKlZVIwqs,1717
160
- tests/test_backtest_cache_manager.py,sha256=Lkm_xnKX2L-_wS2wGZ57AghPDeZUeoS9BxcuDvwdNnw,4871
160
+ tests/test_backtest_cache_manager.py,sha256=4bZkbv-PZVFRTuKinyLFeAl03tR8rwWV81xHLsvkphI,5120
161
161
  tests/test_backtesting_broker.py,sha256=rxZGH5cgiWLmNGdI3k9fti3Fp9IOSohq8xD2E3L2UdY,13194
162
162
  tests/test_backtesting_broker_await_close.py,sha256=WbehY7E4Qet3_Mo7lpfgjmhtI9pnJPIt9mkFI15Dzho,7545
163
163
  tests/test_backtesting_broker_time_advance.py,sha256=FCv0nKG8BQlEjNft7kmQYm9M2CsLIZ0b7mWCllOHQxc,6378
@@ -234,8 +234,8 @@ tests/test_quiet_logs_requirements.py,sha256=YoUooSVLrFL8TlWPfxEiqxvSj4d8z6-qg58
234
234
  tests/test_session_manager.py,sha256=1qygN3aQ2Xe2uh4BMPm0E3V8KXLFNGq5qdL8KkZjef4,11632
235
235
  tests/test_strategy_methods.py,sha256=j9Mhr6nnG1fkiVQXnx7gLjzGbeQmwt0UbJr_4plD36o,12539
236
236
  tests/test_thetadata_backwards_compat.py,sha256=RzNLhNZNJZ2hPkEDyG-T_4mRRXh5XqavK6r-OjfRASQ,3306
237
- tests/test_thetadata_helper.py,sha256=lNUhA9VuzCZaGKuN86Zkpq3fi2Qk_R0iv1P5JdkOXNA,57966
238
- tests/test_thetadata_pandas_verification.py,sha256=MkGIci52W3gez4C6SI4SpcFlrDXmagxikNeOLmXt0yA,6547
237
+ tests/test_thetadata_helper.py,sha256=pcEPu-9kQYp4cn5xmhU1-28DfT-GRu_nUuUMb1xi7nA,58088
238
+ tests/test_thetadata_pandas_verification.py,sha256=MWUecqBY6FGFslWLRo_C5blGbom_unmXCZikAfZXLks,6553
239
239
  tests/test_tradier.py,sha256=iCEM2FTxJSzJ2oLNaRqSx05XaX_DCiMzLx1aEYPANko,33280
240
240
  tests/test_tradier_data.py,sha256=1jTxDzQtzaC42CQJVXMRMElBwExy1mVci3NFfKjjVH0,13363
241
241
  tests/test_tradingfee.py,sha256=2CBJgdU-73Ae4xuys-QkbCtpDTL9hwOUkRnCgLm4OmE,163
@@ -280,7 +280,7 @@ tests/backtest/test_thetadata.py,sha256=xWYfC9C4EhbMDb29qyZWHO3sSWaLIPzzvcMbHCt5
280
280
  tests/backtest/test_thetadata_comprehensive.py,sha256=-gN3xLJcJtlB-k4vlaK82DCZDGDmr0LNZZDzn-aN3l4,26120
281
281
  tests/backtest/test_thetadata_vs_polygon.py,sha256=dZqsrOx3u3cz-1onIO6o5BDRjI1ey7U9vIkZupfXoig,22831
282
282
  tests/backtest/test_yahoo.py,sha256=2FguUTUMC9_A20eqxnZ17rN3tT9n6hyvJHaL98QKpqY,3443
283
- lumibot-4.2.1.dist-info/METADATA,sha256=PiXutqcizL9FWqUSokKsUMfqKV3vMRzh4jjBviIV_oI,12092
284
- lumibot-4.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
285
- lumibot-4.2.1.dist-info/top_level.txt,sha256=otUnUjDFVASauEDiTiAzNgMyqQ1B6jjS3QqqP-WSx38,14
286
- lumibot-4.2.1.dist-info/RECORD,,
283
+ lumibot-4.2.2.dist-info/METADATA,sha256=yjZcnAmbXlQQj4ZEDPZNoWndZHusYMdY8nluNGVQP-0,12092
284
+ lumibot-4.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
285
+ lumibot-4.2.2.dist-info/top_level.txt,sha256=otUnUjDFVASauEDiTiAzNgMyqQ1B6jjS3QqqP-WSx38,14
286
+ lumibot-4.2.2.dist-info/RECORD,,
@@ -67,7 +67,7 @@ def _build_settings(prefix: str = "prod/cache") -> BacktestCacheSettings:
67
67
  def test_remote_key_uses_relative_cache_path(tmp_path, monkeypatch):
68
68
  cache_root = tmp_path / "cache"
69
69
  cache_root.mkdir()
70
- local_file = cache_root / "thetadata" / "bars" / "spy.parquet"
70
+ local_file = cache_root / "thetadata" / "stock" / "minute" / "ohlc" / "stock_SPY_minute_ohlc.parquet"
71
71
  local_file.parent.mkdir(parents=True, exist_ok=True)
72
72
 
73
73
  monkeypatch.setattr(backtest_cache, "LUMIBOT_CACHE_FOLDER", cache_root)
@@ -76,17 +76,17 @@ def test_remote_key_uses_relative_cache_path(tmp_path, monkeypatch):
76
76
  manager = BacktestCacheManager(settings, client_factory=lambda settings: StubS3Client())
77
77
 
78
78
  remote_key = manager.remote_key_for(local_file)
79
- assert remote_key == "stage/cache/v3/thetadata/bars/spy.parquet"
79
+ assert remote_key == "stage/cache/v3/thetadata/stock/minute/ohlc/stock_SPY_minute_ohlc.parquet"
80
80
 
81
81
 
82
82
  def test_ensure_local_file_downloads_from_s3(tmp_path, monkeypatch):
83
83
  cache_root = tmp_path / "cache"
84
84
  cache_root.mkdir()
85
- local_file = cache_root / "thetadata" / "bars" / "spy.parquet"
85
+ local_file = cache_root / "thetadata" / "stock" / "minute" / "ohlc" / "stock_SPY_minute_ohlc.parquet"
86
86
 
87
87
  monkeypatch.setattr(backtest_cache, "LUMIBOT_CACHE_FOLDER", cache_root)
88
88
 
89
- remote_key = "stage/cache/v3/thetadata/bars/spy.parquet"
89
+ remote_key = "stage/cache/v3/thetadata/stock/minute/ohlc/stock_SPY_minute_ohlc.parquet"
90
90
  objects = {("test-bucket", remote_key): b"cached-data"}
91
91
 
92
92
  stub = StubS3Client(objects)
@@ -101,7 +101,7 @@ def test_ensure_local_file_downloads_from_s3(tmp_path, monkeypatch):
101
101
  def test_ensure_local_file_handles_missing_remote(tmp_path, monkeypatch):
102
102
  cache_root = tmp_path / "cache"
103
103
  cache_root.mkdir()
104
- local_file = cache_root / "thetadata" / "bars" / "spy.parquet"
104
+ local_file = cache_root / "thetadata" / "stock" / "minute" / "ohlc" / "stock_SPY_minute_ohlc.parquet"
105
105
 
106
106
  monkeypatch.setattr(backtest_cache, "LUMIBOT_CACHE_FOLDER", cache_root)
107
107
 
@@ -116,13 +116,13 @@ def test_ensure_local_file_handles_missing_remote(tmp_path, monkeypatch):
116
116
  def test_on_local_update_uploads_file(tmp_path, monkeypatch):
117
117
  cache_root = tmp_path / "cache"
118
118
  cache_root.mkdir()
119
- local_file = cache_root / "thetadata" / "bars" / "spy.parquet"
119
+ local_file = cache_root / "thetadata" / "stock" / "minute" / "ohlc" / "stock_SPY_minute_ohlc.parquet"
120
120
  local_file.parent.mkdir(parents=True, exist_ok=True)
121
121
  local_file.write_bytes(b"new-data")
122
122
 
123
123
  monkeypatch.setattr(backtest_cache, "LUMIBOT_CACHE_FOLDER", cache_root)
124
124
 
125
- remote_key = "stage/cache/v3/thetadata/bars/spy.parquet"
125
+ remote_key = "stage/cache/v3/thetadata/stock/minute/ohlc/stock_SPY_minute_ohlc.parquet"
126
126
  stub = StubS3Client({("test-bucket", remote_key): b"old"})
127
127
  manager = BacktestCacheManager(_build_settings(prefix="stage/cache"), client_factory=lambda s: stub)
128
128
 
@@ -324,13 +324,13 @@ def test_get_trading_dates():
324
324
  def test_build_cache_filename(mocker, tmpdir, datastyle):
325
325
  asset = Asset("SPY")
326
326
  timespan = "1D"
327
- mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", tmpdir)
328
- expected = tmpdir / "thetadata" / f"stock_SPY_1D_{datastyle}.parquet"
327
+ mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", str(tmpdir))
328
+ expected = tmpdir / "thetadata" / "stock" / "1d" / datastyle / f"stock_SPY_1D_{datastyle}.parquet"
329
329
  assert thetadata_helper.build_cache_filename(asset, timespan, datastyle) == expected
330
330
 
331
331
  expire_date = datetime.date(2023, 8, 1)
332
332
  option_asset = Asset("SPY", asset_type="option", expiration=expire_date, strike=100, right="CALL")
333
- expected = tmpdir / "thetadata" / f"option_SPY_230801_100_CALL_1D_{datastyle}.parquet"
333
+ expected = tmpdir / "thetadata" / "option" / "1d" / datastyle / f"option_SPY_230801_100_CALL_1D_{datastyle}.parquet"
334
334
  assert thetadata_helper.build_cache_filename(option_asset, timespan, datastyle) == expected
335
335
 
336
336
  # Bad option asset with no expiration
@@ -427,8 +427,8 @@ def test_missing_dates():
427
427
  ],
428
428
  )
429
429
  def test_update_cache(mocker, tmpdir, df_all, df_cached, datastyle):
430
- mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", tmpdir)
431
- cache_file = Path(tmpdir / "thetadata" / f"stock_SPY_1D_{datastyle}.parquet")
430
+ mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", str(tmpdir))
431
+ cache_file = thetadata_helper.build_cache_filename(Asset("SPY"), "1D", datastyle)
432
432
 
433
433
  # Empty DataFrame of df_all, don't write cache file
434
434
  thetadata_helper.update_cache(cache_file, df_all, df_cached)
@@ -550,8 +550,9 @@ def test_get_price_data_invokes_remote_cache_manager(tmp_path, monkeypatch):
550
550
  )
551
551
  def test_load_data_from_cache(mocker, tmpdir, df_cached, datastyle):
552
552
  # Setup some basics
553
- mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", tmpdir)
554
- cache_file = Path(tmpdir / "thetadata" / f"stock_SPY_1D_{datastyle}.parquet")
553
+ mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", str(tmpdir))
554
+ asset = Asset("SPY")
555
+ cache_file = thetadata_helper.build_cache_filename(asset, "1D", datastyle)
555
556
 
556
557
  # No cache file should return None (not raise)
557
558
  assert thetadata_helper.load_cache(cache_file) is None
@@ -1371,8 +1372,8 @@ class TestThetaDataChainsCaching:
1371
1372
 
1372
1373
  # CLEAR CACHE to ensure first call downloads fresh data
1373
1374
  # This prevents cache pollution from previous tests in the suite
1374
- # Chains are stored in: LUMIBOT_CACHE_FOLDER / "thetadata" / "option_chains"
1375
- chain_folder = Path(LUMIBOT_CACHE_FOLDER) / "thetadata" / "option_chains"
1375
+ # Chains are stored in: LUMIBOT_CACHE_FOLDER / "thetadata" / "option" / "option_chains"
1376
+ chain_folder = Path(LUMIBOT_CACHE_FOLDER) / "thetadata" / "option" / "option_chains"
1376
1377
  if chain_folder.exists():
1377
1378
  # Delete all AAPL chain cache files
1378
1379
  for cache_file in chain_folder.glob("AAPL_*.parquet"):
@@ -44,7 +44,7 @@ def count_cache_files():
44
44
  cache_dir = get_cache_dir()
45
45
  if not cache_dir.exists():
46
46
  return 0
47
- return len(list(cache_dir.glob("*.parquet")))
47
+ return sum(1 for _ in cache_dir.rglob("*.parquet"))
48
48
 
49
49
 
50
50
  class WeeklyMomentumOptionsStrategy(Strategy):