lumibot 4.1.3__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 +2 -1
- lumibot/strategies/strategy.py +61 -49
- lumibot/tools/backtest_cache.py +284 -0
- lumibot/tools/databento_helper.py +35 -35
- lumibot/tools/databento_helper_polars.py +738 -775
- 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.3.dist-info → lumibot-4.2.0.dist-info}/METADATA +9 -1
- {lumibot-4.1.3.dist-info → lumibot-4.2.0.dist-info}/RECORD +71 -147
- tests/backtest/test_databento.py +37 -6
- tests/backtest/test_databento_comprehensive_trading.py +8 -4
- tests/backtest/test_databento_parity.py +4 -2
- 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 +3 -3
- 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 +6 -0
- 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_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.3.dist-info → lumibot-4.2.0.dist-info}/WHEEL +0 -0
- {lumibot-4.1.3.dist-info → lumibot-4.2.0.dist-info}/licenses/LICENSE +0 -0
- {lumibot-4.1.3.dist-info → lumibot-4.2.0.dist-info}/top_level.txt +0 -0
|
@@ -7,6 +7,11 @@ This implementation:
|
|
|
7
7
|
4. Efficient caching with parquet files
|
|
8
8
|
5. Vectorized operations only
|
|
9
9
|
"""
|
|
10
|
+
# NOTE: This module is intentionally disabled. The DataBento Polars migration only
|
|
11
|
+
# supports Polars for DataBento; other data sources must use the pandas implementations.
|
|
12
|
+
raise RuntimeError('Yahoo/Polygon Polars backends are not production-ready; use the pandas data sources instead.')
|
|
13
|
+
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
from datetime import datetime, timedelta
|
|
12
17
|
from decimal import Decimal
|
lumibot/entities/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ from .bar import Bar
|
|
|
5
5
|
from .bars import Bars as _BarsBase
|
|
6
6
|
from .chains import Chains
|
|
7
7
|
from .data import Data as _DataBase
|
|
8
|
+
from .data_polars import DataPolars
|
|
8
9
|
from .dataline import Dataline
|
|
9
10
|
from .order import Order
|
|
10
11
|
from .position import Position
|
|
@@ -14,3 +15,17 @@ from .trading_fee import TradingFee
|
|
|
14
15
|
# Use base implementations directly
|
|
15
16
|
Bars = _BarsBase
|
|
16
17
|
Data = _DataBase
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Asset",
|
|
20
|
+
"AssetsMapping",
|
|
21
|
+
"Bar",
|
|
22
|
+
"Bars",
|
|
23
|
+
"Chains",
|
|
24
|
+
"Data",
|
|
25
|
+
"DataPolars",
|
|
26
|
+
"Dataline",
|
|
27
|
+
"Order",
|
|
28
|
+
"Position",
|
|
29
|
+
"Quote",
|
|
30
|
+
"TradingFee",
|
|
31
|
+
]
|
lumibot/entities/asset.py
CHANGED
|
@@ -715,35 +715,12 @@ class Asset:
|
|
|
715
715
|
# logger = logging.getLogger(__name__)
|
|
716
716
|
# logger.info(f"[CONTRACT RESOLUTION] symbol={self.symbol}, reference_date={reference_date}, month={reference_date.month}, day={reference_date.day}")
|
|
717
717
|
|
|
718
|
-
|
|
719
|
-
current_year = reference_date.year
|
|
720
|
-
current_day = reference_date.day
|
|
718
|
+
from lumibot.tools import futures_roll
|
|
721
719
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
elif current_month >= 10:
|
|
727
|
-
target_month = 12
|
|
728
|
-
target_year = current_year
|
|
729
|
-
elif current_month == 9 and current_day >= 15:
|
|
730
|
-
target_month = 12
|
|
731
|
-
target_year = current_year
|
|
732
|
-
elif current_month >= 7:
|
|
733
|
-
target_month = 9
|
|
734
|
-
target_year = current_year
|
|
735
|
-
elif current_month == 6 and current_day >= 15:
|
|
736
|
-
target_month = 9
|
|
737
|
-
target_year = current_year
|
|
738
|
-
elif current_month >= 4:
|
|
739
|
-
target_month = 6
|
|
740
|
-
target_year = current_year
|
|
741
|
-
elif current_month == 3 and current_day >= 15:
|
|
742
|
-
target_month = 6
|
|
743
|
-
target_year = current_year
|
|
744
|
-
else:
|
|
745
|
-
target_month = 3
|
|
746
|
-
target_year = current_year
|
|
720
|
+
target_year, target_month = futures_roll.determine_contract_year_month(
|
|
721
|
+
self.symbol,
|
|
722
|
+
reference_date,
|
|
723
|
+
)
|
|
747
724
|
|
|
748
725
|
month_code = FUTURES_MONTH_CODES.get(target_month, "U")
|
|
749
726
|
base_contract = f"{self.symbol}{month_code}"
|
lumibot/entities/bars.py
CHANGED
|
@@ -179,8 +179,9 @@ class Bars:
|
|
|
179
179
|
self.quote = quote
|
|
180
180
|
self._raw = raw
|
|
181
181
|
self._return_polars = return_polars
|
|
182
|
-
#
|
|
182
|
+
# Caches for on-demand conversions to avoid repeated expensive copies
|
|
183
183
|
self._polars_cache = None
|
|
184
|
+
self._pandas_cache = None
|
|
184
185
|
self._tzinfo = self._normalize_tzinfo(tzinfo)
|
|
185
186
|
|
|
186
187
|
# Check if empty
|
|
@@ -204,9 +205,20 @@ class Bars:
|
|
|
204
205
|
pl.col("close").pct_change().alias("return")
|
|
205
206
|
])
|
|
206
207
|
|
|
208
|
+
if "datetime" in df.columns and self._tzinfo is not None:
|
|
209
|
+
target_tz = getattr(self._tzinfo, "zone", None) or getattr(self._tzinfo, "key", None)
|
|
210
|
+
if target_tz:
|
|
211
|
+
current_dtype = df.schema.get("datetime")
|
|
212
|
+
current_tz = getattr(current_dtype, "time_zone", None)
|
|
213
|
+
if current_tz != target_tz:
|
|
214
|
+
df = df.with_columns(
|
|
215
|
+
pl.col("datetime").dt.convert_time_zone(target_tz)
|
|
216
|
+
)
|
|
217
|
+
|
|
207
218
|
if return_polars:
|
|
208
219
|
# Keep as polars
|
|
209
220
|
self._df = df
|
|
221
|
+
self._pandas_cache = None
|
|
210
222
|
else:
|
|
211
223
|
# Convert to pandas and track the conversion
|
|
212
224
|
tracker = PolarsConversionTracker()
|
|
@@ -219,6 +231,7 @@ class Bars:
|
|
|
219
231
|
self._df = self._df.set_index(col_name)
|
|
220
232
|
break
|
|
221
233
|
self._apply_timezone()
|
|
234
|
+
self._pandas_cache = None
|
|
222
235
|
else:
|
|
223
236
|
# Already pandas, keep it as is
|
|
224
237
|
self._df = df
|
|
@@ -231,19 +244,38 @@ class Bars:
|
|
|
231
244
|
self._df["return"] = df["close"].pct_change()
|
|
232
245
|
|
|
233
246
|
self._apply_timezone()
|
|
247
|
+
if self._return_polars:
|
|
248
|
+
self._pandas_cache = self._df
|
|
249
|
+
self._df = self._convert_pandas_to_polars(self._df)
|
|
250
|
+
self._polars_cache = None
|
|
234
251
|
|
|
235
252
|
@property
|
|
236
253
|
def df(self):
|
|
237
|
-
"""Return the DataFrame"""
|
|
238
|
-
return self.
|
|
254
|
+
"""Return the active DataFrame representation based on return_polars flag."""
|
|
255
|
+
return self.polars_df if self._return_polars else self.pandas_df
|
|
239
256
|
|
|
240
257
|
@df.setter
|
|
241
258
|
def df(self, value):
|
|
242
|
-
"""Allow setting the DataFrame"""
|
|
259
|
+
"""Allow setting the DataFrame while keeping caches in sync."""
|
|
243
260
|
self._df = value
|
|
244
|
-
# Invalidate cached converted forms when df changes
|
|
245
261
|
self._polars_cache = None
|
|
246
|
-
self.
|
|
262
|
+
self._pandas_cache = None
|
|
263
|
+
|
|
264
|
+
if isinstance(value, pd.DataFrame):
|
|
265
|
+
self._apply_timezone()
|
|
266
|
+
if self._return_polars:
|
|
267
|
+
self._pandas_cache = value
|
|
268
|
+
self._df = self._convert_pandas_to_polars(value)
|
|
269
|
+
elif isinstance(value, pl.DataFrame) and not self._return_polars:
|
|
270
|
+
tracker = PolarsConversionTracker()
|
|
271
|
+
tracker.track_conversion(self.asset.symbol if hasattr(self.asset, 'symbol') else str(self.asset))
|
|
272
|
+
pandas_df = value.to_pandas()
|
|
273
|
+
for col_name in ['datetime', 'timestamp', 'date', 'time']:
|
|
274
|
+
if col_name in pandas_df.columns:
|
|
275
|
+
pandas_df = pandas_df.set_index(col_name)
|
|
276
|
+
break
|
|
277
|
+
self._df = pandas_df
|
|
278
|
+
self._apply_timezone()
|
|
247
279
|
|
|
248
280
|
@property
|
|
249
281
|
def polars_df(self):
|
|
@@ -254,12 +286,29 @@ class Bars:
|
|
|
254
286
|
# Convert pandas to polars once and cache
|
|
255
287
|
if self._polars_cache is not None:
|
|
256
288
|
return self._polars_cache
|
|
257
|
-
|
|
258
|
-
self._polars_cache = pl.from_pandas(self._df.reset_index())
|
|
259
|
-
else:
|
|
260
|
-
self._polars_cache = pl.from_pandas(self._df)
|
|
289
|
+
self._polars_cache = self._convert_pandas_to_polars(self._df)
|
|
261
290
|
return self._polars_cache
|
|
262
291
|
|
|
292
|
+
@property
|
|
293
|
+
def pandas_df(self):
|
|
294
|
+
"""Return as Pandas DataFrame, converting on demand if required."""
|
|
295
|
+
if isinstance(self._df, pd.DataFrame):
|
|
296
|
+
return self._df
|
|
297
|
+
if self._pandas_cache is not None:
|
|
298
|
+
return self._pandas_cache
|
|
299
|
+
|
|
300
|
+
tracker = PolarsConversionTracker()
|
|
301
|
+
tracker.track_conversion(self.asset.symbol if hasattr(self.asset, 'symbol') else str(self.asset))
|
|
302
|
+
|
|
303
|
+
pandas_df = self._df.to_pandas()
|
|
304
|
+
for col_name in ['datetime', 'timestamp', 'date', 'time']:
|
|
305
|
+
if col_name in pandas_df.columns:
|
|
306
|
+
pandas_df = pandas_df.set_index(col_name)
|
|
307
|
+
break
|
|
308
|
+
|
|
309
|
+
self._pandas_cache = self._apply_timezone(pandas_df)
|
|
310
|
+
return self._pandas_cache
|
|
311
|
+
|
|
263
312
|
def __repr__(self):
|
|
264
313
|
return repr(self.df)
|
|
265
314
|
|
|
@@ -268,7 +317,12 @@ class Bars:
|
|
|
268
317
|
|
|
269
318
|
def __len__(self):
|
|
270
319
|
"""Return the number of bars (rows) in the DataFrame"""
|
|
271
|
-
|
|
320
|
+
if isinstance(self._df, pl.DataFrame):
|
|
321
|
+
return self._df.height
|
|
322
|
+
if isinstance(self._df, pd.DataFrame):
|
|
323
|
+
return len(self._df)
|
|
324
|
+
df = self.df
|
|
325
|
+
return df.height if isinstance(df, pl.DataFrame) else len(df)
|
|
272
326
|
|
|
273
327
|
@property
|
|
274
328
|
def empty(self):
|
|
@@ -285,24 +339,39 @@ class Bars:
|
|
|
285
339
|
return pytz.timezone(tzinfo)
|
|
286
340
|
return tzinfo
|
|
287
341
|
|
|
288
|
-
def _apply_timezone(self):
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
342
|
+
def _apply_timezone(self, df=None):
|
|
343
|
+
target_df = self._df if df is None else df
|
|
344
|
+
if not isinstance(target_df, pd.DataFrame):
|
|
345
|
+
return target_df
|
|
346
|
+
if not isinstance(target_df.index, pd.DatetimeIndex):
|
|
347
|
+
return target_df
|
|
293
348
|
|
|
294
349
|
tz = self._tzinfo or LUMIBOT_DEFAULT_PYTZ
|
|
295
350
|
if isinstance(tz, str):
|
|
296
351
|
tz = pytz.timezone(tz)
|
|
297
352
|
|
|
298
353
|
try:
|
|
299
|
-
if
|
|
300
|
-
|
|
354
|
+
if target_df.index.tz is None:
|
|
355
|
+
target_df.index = target_df.index.tz_localize(tz)
|
|
301
356
|
else:
|
|
302
|
-
|
|
357
|
+
target_df.index = target_df.index.tz_convert(tz)
|
|
303
358
|
self._tzinfo = tz
|
|
304
359
|
except Exception:
|
|
305
|
-
|
|
360
|
+
return target_df
|
|
361
|
+
|
|
362
|
+
if df is None:
|
|
363
|
+
self._df = target_df
|
|
364
|
+
return target_df
|
|
365
|
+
|
|
366
|
+
def _convert_pandas_to_polars(self, pandas_df: pd.DataFrame) -> pl.DataFrame:
|
|
367
|
+
"""Convert a pandas DataFrame (possibly with datetime index) to Polars."""
|
|
368
|
+
df_to_convert = pandas_df.copy()
|
|
369
|
+
if isinstance(df_to_convert.index, pd.DatetimeIndex):
|
|
370
|
+
df_to_convert = df_to_convert.reset_index()
|
|
371
|
+
first_col = df_to_convert.columns[0]
|
|
372
|
+
if first_col != "datetime":
|
|
373
|
+
df_to_convert = df_to_convert.rename(columns={first_col: "datetime"})
|
|
374
|
+
return pl.from_pandas(df_to_convert)
|
|
306
375
|
|
|
307
376
|
@classmethod
|
|
308
377
|
def parse_bar_list(cls, bar_list, source, asset):
|
lumibot/entities/data.py
CHANGED
|
@@ -411,6 +411,14 @@ class Data:
|
|
|
411
411
|
|
|
412
412
|
length = kwargs.get("length", 1)
|
|
413
413
|
timeshift = kwargs.get("timeshift", 0)
|
|
414
|
+
|
|
415
|
+
if isinstance(timeshift, datetime.timedelta):
|
|
416
|
+
if self.timestep == "day":
|
|
417
|
+
timeshift = int(timeshift.total_seconds() / (24 * 3600))
|
|
418
|
+
else:
|
|
419
|
+
timeshift = int(timeshift.total_seconds() / 60)
|
|
420
|
+
kwargs["timeshift"] = timeshift
|
|
421
|
+
|
|
414
422
|
data_index = i + 1 - length - timeshift
|
|
415
423
|
is_data = data_index >= 0
|
|
416
424
|
if not is_data:
|
|
@@ -437,8 +445,8 @@ class Data:
|
|
|
437
445
|
The number of periods to get the last price.
|
|
438
446
|
timestep : str
|
|
439
447
|
The frequency of the data to get the last price.
|
|
440
|
-
timeshift : int
|
|
441
|
-
The number of periods to shift the data.
|
|
448
|
+
timeshift : int | datetime.timedelta
|
|
449
|
+
The number of periods to shift the data, or a timedelta that will be converted to periods.
|
|
442
450
|
|
|
443
451
|
Returns
|
|
444
452
|
-------
|
|
@@ -462,8 +470,8 @@ class Data:
|
|
|
462
470
|
The number of periods to get the last price.
|
|
463
471
|
timestep : str
|
|
464
472
|
The frequency of the data to get the last price.
|
|
465
|
-
timeshift : int
|
|
466
|
-
The number of periods to shift the data.
|
|
473
|
+
timeshift : int | datetime.timedelta
|
|
474
|
+
The number of periods to shift the data, or a timedelta that will be converted to periods.
|
|
467
475
|
|
|
468
476
|
Returns
|
|
469
477
|
-------
|
|
@@ -547,12 +555,27 @@ class Data:
|
|
|
547
555
|
|
|
548
556
|
"""
|
|
549
557
|
|
|
550
|
-
|
|
558
|
+
if isinstance(timeshift, datetime.timedelta):
|
|
559
|
+
if self.timestep == "day":
|
|
560
|
+
timeshift = int(timeshift.total_seconds() / (24 * 3600))
|
|
561
|
+
else:
|
|
562
|
+
timeshift = int(timeshift.total_seconds() / 60)
|
|
563
|
+
|
|
551
564
|
end_row = self.get_iter_count(dt) - timeshift
|
|
552
|
-
start_row = end_row - length
|
|
553
565
|
|
|
566
|
+
data_len = len(next(iter(self.datalines.values())).dataline) if self.datalines else 0
|
|
567
|
+
if end_row > data_len:
|
|
568
|
+
end_row = data_len
|
|
569
|
+
if end_row < 0:
|
|
570
|
+
end_row = 0
|
|
571
|
+
|
|
572
|
+
start_row = end_row - length
|
|
554
573
|
if start_row < 0:
|
|
555
574
|
start_row = 0
|
|
575
|
+
if start_row > end_row:
|
|
576
|
+
start_row = end_row
|
|
577
|
+
if start_row == end_row and end_row > 0:
|
|
578
|
+
start_row = max(0, end_row - 1)
|
|
556
579
|
|
|
557
580
|
# Cast both start_row and end_row to int
|
|
558
581
|
start_row = int(start_row)
|