@@ -324,13 +324,13 @@ def test_get_trading_dates():
324324def test_build_cache_filename (mocker , tmpdir , datastyle ):
325325 asset = Asset ("SPY" )
326326 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"
329329 assert thetadata_helper .build_cache_filename (asset , timespan , datastyle ) == expected
330330
331331 expire_date = datetime .date (2023 , 8 , 1 )
332332 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"
334334 assert thetadata_helper .build_cache_filename (option_asset , timespan , datastyle ) == expected
335335
336336 # Bad option asset with no expiration
@@ -427,8 +427,8 @@ def test_missing_dates():
427427 ],
428428)
429429def 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 )
432432
433433 # Empty DataFrame of df_all, don't write cache file
434434 thetadata_helper .update_cache (cache_file , df_all , df_cached )
@@ -550,8 +550,9 @@ def on_local_update(self, local_path, payload=None):
550550)
551551def test_load_data_from_cache (mocker , tmpdir , df_cached , datastyle ):
552552 # 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 )
555556
556557 # No cache file should return None (not raise)
557558 assert thetadata_helper .load_cache (cache_file ) is None
@@ -1371,8 +1372,8 @@ def test_chains_cache_reuse(self):
13711372
13721373 # CLEAR CACHE to ensure first call downloads fresh data
13731374 # 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"
13761377 if chain_folder .exists ():
13771378 # Delete all AAPL chain cache files
13781379 for cache_file in chain_folder .glob ("AAPL_*.parquet" ):
@@ -1407,6 +1408,46 @@ def test_chains_cache_reuse(self):
14071408 assert time2 < time1 * 0.1 , f"Cache not working: time1={ time1 :.2f} s, time2={ time2 :.2f} s (should be 10x faster)"
14081409 print (f"✓ Cache speedup: { time1 / time2 :.1f} x faster ({ time1 :.2f} s -> { time2 :.4f} s)" )
14091410
1411+
1412+ def test_finalize_day_frame_handles_dst_fallback ():
1413+ tz = pytz .timezone ("America/New_York" )
1414+ utc = pytz .UTC
1415+ frame_index = pd .date_range (
1416+ end = tz .localize (datetime .datetime (2024 , 10 , 31 , 16 , 0 )),
1417+ periods = 5 ,
1418+ freq = "D" ,
1419+ )
1420+ frame = pd .DataFrame (
1421+ {
1422+ "open" : [100 + i for i in range (len (frame_index ))],
1423+ "high" : [101 + i for i in range (len (frame_index ))],
1424+ "low" : [99 + i for i in range (len (frame_index ))],
1425+ "close" : [100.5 + i for i in range (len (frame_index ))],
1426+ "volume" : [1000 + i for i in range (len (frame_index ))],
1427+ },
1428+ index = frame_index ,
1429+ )
1430+
1431+ data_source = ThetaDataBacktestingPandas (
1432+ datetime_start = utc .localize (datetime .datetime (2024 , 10 , 1 )),
1433+ datetime_end = utc .localize (datetime .datetime (2024 , 11 , 5 )),
1434+ username = "user" ,
1435+ password = "pass" ,
1436+ use_quote_data = False ,
1437+ )
1438+
1439+ current_dt = utc .localize (datetime .datetime (2024 , 11 , 4 , 13 , 30 ))
1440+ result = data_source ._finalize_day_frame (
1441+ frame ,
1442+ current_dt ,
1443+ requested_length = len (frame_index ),
1444+ timeshift = None ,
1445+ asset = Asset ("TSLA" ),
1446+ )
1447+
1448+ assert result is not None
1449+ assert len (result ) == len (frame_index )
1450+
14101451 def test_chains_strike_format (self ):
14111452 """Test strikes are floats (not integers) and properly converted."""
14121453 username = os .environ .get ("THETADATA_USERNAME" )
0 commit comments