lumibot 4.2.3__py3-none-any.whl → 4.2.4__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/strategies/_strategy.py +14 -11
- {lumibot-4.2.3.dist-info → lumibot-4.2.4.dist-info}/METADATA +1 -1
- {lumibot-4.2.3.dist-info → lumibot-4.2.4.dist-info}/RECORD +8 -7
- tests/test_backtesting_data_source_env.py +3 -0
- tests/test_backtesting_datetime_normalization.py +90 -0
- {lumibot-4.2.3.dist-info → lumibot-4.2.4.dist-info}/WHEEL +0 -0
- {lumibot-4.2.3.dist-info → lumibot-4.2.4.dist-info}/licenses/LICENSE +0 -0
- {lumibot-4.2.3.dist-info → lumibot-4.2.4.dist-info}/top_level.txt +0 -0
lumibot/strategies/_strategy.py
CHANGED
|
@@ -1388,12 +1388,6 @@ class _Strategy:
|
|
|
1388
1388
|
if use_other_option_source and not isinstance(optionsource_class, type):
|
|
1389
1389
|
raise ValueError(f"`optionsource_class` must be a class. You passed in {optionsource_class}")
|
|
1390
1390
|
|
|
1391
|
-
self.verify_backtest_inputs(backtesting_start, backtesting_end)
|
|
1392
|
-
|
|
1393
|
-
if not self.IS_BACKTESTABLE:
|
|
1394
|
-
get_logger(__name__).warning(f"Strategy {name + ' ' if name is not None else ''}cannot be " f"backtested at the moment")
|
|
1395
|
-
return None
|
|
1396
|
-
|
|
1397
1391
|
try:
|
|
1398
1392
|
backtesting_start = to_datetime_aware(backtesting_start)
|
|
1399
1393
|
backtesting_end = to_datetime_aware(backtesting_end)
|
|
@@ -1405,6 +1399,12 @@ class _Strategy:
|
|
|
1405
1399
|
)
|
|
1406
1400
|
return None
|
|
1407
1401
|
|
|
1402
|
+
self.verify_backtest_inputs(backtesting_start, backtesting_end)
|
|
1403
|
+
|
|
1404
|
+
if not self.IS_BACKTESTABLE:
|
|
1405
|
+
get_logger(__name__).warning(f"Strategy {name + ' ' if name is not None else ''}cannot be " f"backtested at the moment")
|
|
1406
|
+
return None
|
|
1407
|
+
|
|
1408
1408
|
if BACKTESTING_QUIET_LOGS is not None:
|
|
1409
1409
|
quiet_logs = BACKTESTING_QUIET_LOGS
|
|
1410
1410
|
|
|
@@ -1628,18 +1628,21 @@ class _Strategy:
|
|
|
1628
1628
|
if not isinstance(backtesting_end, datetime.datetime):
|
|
1629
1629
|
raise ValueError(f"`backtesting_end` must be a datetime object. You passed in {backtesting_end}")
|
|
1630
1630
|
|
|
1631
|
+
start_dt = to_datetime_aware(backtesting_start)
|
|
1632
|
+
end_dt = to_datetime_aware(backtesting_end)
|
|
1633
|
+
|
|
1631
1634
|
# Check that backtesting end is after backtesting start
|
|
1632
|
-
if
|
|
1635
|
+
if end_dt <= start_dt:
|
|
1633
1636
|
raise ValueError(
|
|
1634
1637
|
f"`backtesting_end` must be after `backtesting_start`. You passed in "
|
|
1635
|
-
f"{
|
|
1638
|
+
f"{end_dt} and {start_dt}"
|
|
1636
1639
|
)
|
|
1637
1640
|
|
|
1638
1641
|
# Check that backtesting_end is not in the future
|
|
1639
|
-
now = datetime.datetime.now(
|
|
1640
|
-
if
|
|
1642
|
+
now = datetime.datetime.now(end_dt.tzinfo) if end_dt.tzinfo else datetime.datetime.now()
|
|
1643
|
+
if end_dt > now:
|
|
1641
1644
|
raise ValueError(
|
|
1642
|
-
f"`backtesting_end` cannot be in the future. You passed in {
|
|
1645
|
+
f"`backtesting_end` cannot be in the future. You passed in {end_dt}, now is {now}"
|
|
1643
1646
|
)
|
|
1644
1647
|
|
|
1645
1648
|
def send_update_to_cloud(self):
|
|
@@ -104,7 +104,7 @@ lumibot/example_strategies/test_broker_functions.py,sha256=wnVS-M_OtzMgaXVBgshVE
|
|
|
104
104
|
lumibot/resources/ThetaTerminal.jar,sha256=K6GeeFcN8-gvyL2x5iq5pzD79KfPJvMK8iiezi3TmNQ,11834389
|
|
105
105
|
lumibot/resources/conf.yaml,sha256=rjB9-10JP7saZ_edjX5bQDGfuc3amOQTUUUr-UiMpNA,597
|
|
106
106
|
lumibot/strategies/__init__.py,sha256=jEZ95K5hG0f595EXYKWwL2_UsnWWk5Pug361PK2My2E,79
|
|
107
|
-
lumibot/strategies/_strategy.py,sha256=
|
|
107
|
+
lumibot/strategies/_strategy.py,sha256=jfKU4NpYiGJexTEeHz-pi-8NtSdcX1BXBTcjr7wsUuY,110825
|
|
108
108
|
lumibot/strategies/session_manager.py,sha256=Nze6UYNSPlCsf-tyHvtFqUeL44WSNHjwsKrIepvsyCY,12956
|
|
109
109
|
lumibot/strategies/strategy.py,sha256=toPeL5oIVWmCxBNcfXqIuTCF_EeCfIVj425PrSYImCo,170021
|
|
110
110
|
lumibot/strategies/strategy_executor.py,sha256=IrHwDOu5s3gG65dz7FL-0kllWy7COci7JFyB4iiPUrg,70801
|
|
@@ -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.
|
|
145
|
+
lumibot-4.2.4.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
|
|
@@ -162,7 +162,8 @@ tests/test_backtesting_broker.py,sha256=rxZGH5cgiWLmNGdI3k9fti3Fp9IOSohq8xD2E3L2
|
|
|
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
|
|
164
164
|
tests/test_backtesting_crypto_cash_unit.py,sha256=4EO9jVajdZNV0M7zSyp4gpR_msZFoM4x5tb-6g-mHO8,11399
|
|
165
|
-
tests/test_backtesting_data_source_env.py,sha256=
|
|
165
|
+
tests/test_backtesting_data_source_env.py,sha256=ZzpF42-tMc8qqETuy_nf43UsSZMbHtS_ivH93ZqV5P0,12460
|
|
166
|
+
tests/test_backtesting_datetime_normalization.py,sha256=8NjxiZbSIOzE4I2qDwEqkN3M7jnIdTfjPPE2Qp_0ySE,2984
|
|
166
167
|
tests/test_backtesting_flow_control.py,sha256=pBqW-fa-HnZq0apUBltalGMM-vNJ_2A5W2SoJzMK8Mg,7208
|
|
167
168
|
tests/test_backtesting_multileg_unit.py,sha256=h1DPfVuYXXx-uq6KtUjr6_nasZuXPm_5gFat1XxCKIo,6456
|
|
168
169
|
tests/test_backtesting_quiet_logs_complete.py,sha256=x-GfOiqkiUu8pYKCzB0UUacn13Nx_cPRth7_jmPY2Y8,14155
|
|
@@ -280,7 +281,7 @@ tests/backtest/test_thetadata.py,sha256=xWYfC9C4EhbMDb29qyZWHO3sSWaLIPzzvcMbHCt5
|
|
|
280
281
|
tests/backtest/test_thetadata_comprehensive.py,sha256=-gN3xLJcJtlB-k4vlaK82DCZDGDmr0LNZZDzn-aN3l4,26120
|
|
281
282
|
tests/backtest/test_thetadata_vs_polygon.py,sha256=dZqsrOx3u3cz-1onIO6o5BDRjI1ey7U9vIkZupfXoig,22831
|
|
282
283
|
tests/backtest/test_yahoo.py,sha256=2FguUTUMC9_A20eqxnZ17rN3tT9n6hyvJHaL98QKpqY,3443
|
|
283
|
-
lumibot-4.2.
|
|
284
|
-
lumibot-4.2.
|
|
285
|
-
lumibot-4.2.
|
|
286
|
-
lumibot-4.2.
|
|
284
|
+
lumibot-4.2.4.dist-info/METADATA,sha256=tTX4FY9DKXc8JjeCMImHK3NgzlR1h_U9dSvQqyOnQ28,12092
|
|
285
|
+
lumibot-4.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
286
|
+
lumibot-4.2.4.dist-info/top_level.txt,sha256=otUnUjDFVASauEDiTiAzNgMyqQ1B6jjS3QqqP-WSx38,14
|
|
287
|
+
lumibot-4.2.4.dist-info/RECORD,,
|
|
@@ -75,6 +75,9 @@ class TestBacktestingDataSourceEnv:
|
|
|
75
75
|
# Configure caplog to capture INFO level logs from lumibot.strategies._strategy
|
|
76
76
|
import logging
|
|
77
77
|
caplog.set_level(logging.INFO, logger='lumibot.strategies._strategy')
|
|
78
|
+
polygon_key = os.environ.get("POLYGON_API_KEY")
|
|
79
|
+
if not polygon_key:
|
|
80
|
+
pytest.skip("Polygon API key not configured")
|
|
78
81
|
|
|
79
82
|
with patch.dict(os.environ, {'BACKTESTING_DATA_SOURCE': 'polygon'}):
|
|
80
83
|
# Re-import credentials to pick up env change
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from lumibot.strategies import Strategy
|
|
7
|
+
from lumibot.strategies._strategy import _Strategy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MinimalStrategy(Strategy):
|
|
11
|
+
"""No-op strategy used for backtest scaffolding."""
|
|
12
|
+
|
|
13
|
+
def initialize(self):
|
|
14
|
+
self.sleeptime = "1D"
|
|
15
|
+
|
|
16
|
+
def on_trading_iteration(self):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DummyDataSource:
|
|
21
|
+
"""Lightweight datasource stub capturing the start/end datetimes."""
|
|
22
|
+
|
|
23
|
+
SOURCE = "dummy"
|
|
24
|
+
|
|
25
|
+
def __init__(self, datetime_start=None, datetime_end=None, **kwargs):
|
|
26
|
+
self.datetime_start = datetime_start
|
|
27
|
+
self.datetime_end = datetime_end
|
|
28
|
+
self._data_store = {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DummyTrader:
|
|
32
|
+
"""Trader stub that records strategies and returns canned results."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args, **kwargs):
|
|
35
|
+
self._strategies = []
|
|
36
|
+
|
|
37
|
+
def add_strategy(self, strategy):
|
|
38
|
+
self._strategies.append(strategy)
|
|
39
|
+
|
|
40
|
+
def run_all(self, **_kwargs):
|
|
41
|
+
return {strategy.name: {"dummy": True} for strategy in self._strategies}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _EarlyExit(Exception):
|
|
45
|
+
"""Signal to stop run_backtest after the datasource is constructed."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_verify_backtest_inputs_accepts_mixed_timezones():
|
|
49
|
+
"""Regression: verify_backtest_inputs must not crash on naive vs aware inputs."""
|
|
50
|
+
naive_start = datetime.datetime(2025, 1, 1)
|
|
51
|
+
aware_end = datetime.datetime(2025, 9, 30, tzinfo=datetime.timezone.utc)
|
|
52
|
+
|
|
53
|
+
# Should not raise
|
|
54
|
+
_Strategy.verify_backtest_inputs(naive_start, aware_end)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_run_backtest_normalizes_mixed_timezones():
|
|
58
|
+
"""Strategy.run_backtest should normalize naive/aware datetimes before validation."""
|
|
59
|
+
naive_start = datetime.datetime(2025, 1, 1)
|
|
60
|
+
aware_end = datetime.datetime(2025, 9, 30, tzinfo=datetime.timezone.utc)
|
|
61
|
+
|
|
62
|
+
captured = {}
|
|
63
|
+
|
|
64
|
+
class CapturingDataSource(DummyDataSource):
|
|
65
|
+
def __init__(self, datetime_start=None, datetime_end=None, **kwargs):
|
|
66
|
+
super().__init__(datetime_start=datetime_start, datetime_end=datetime_end, **kwargs)
|
|
67
|
+
captured["start"] = self.datetime_start
|
|
68
|
+
captured["end"] = self.datetime_end
|
|
69
|
+
|
|
70
|
+
def broker_factory(data_source, *args, **kwargs):
|
|
71
|
+
captured["data_source"] = data_source
|
|
72
|
+
raise _EarlyExit
|
|
73
|
+
|
|
74
|
+
with patch("lumibot.strategies._strategy.BacktestingBroker", side_effect=broker_factory), \
|
|
75
|
+
patch("lumibot.strategies._strategy.Trader", DummyTrader):
|
|
76
|
+
with pytest.raises(_EarlyExit):
|
|
77
|
+
MinimalStrategy.run_backtest(
|
|
78
|
+
CapturingDataSource,
|
|
79
|
+
backtesting_start=naive_start,
|
|
80
|
+
backtesting_end=aware_end,
|
|
81
|
+
show_plot=False,
|
|
82
|
+
show_tearsheet=False,
|
|
83
|
+
show_indicators=False,
|
|
84
|
+
show_progress_bar=False,
|
|
85
|
+
save_logfile=False,
|
|
86
|
+
save_stats_file=False,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
assert "start" in captured and captured["start"].tzinfo is not None
|
|
90
|
+
assert "end" in captured and captured["end"].tzinfo is not None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|