Deploy marker: 9e73736f (deploy 4.5.10)
- Bitunix futures order cancellation now sends the exchange-required symbol with the order id.
Bitunix.cancel_order()now passes bothorder_idandsymbolinto the Bitunix REST client, andBitUnixClient.cancel_order()accepts either the broker's keyword shape or a Lumibot order object. This fixes cancellation failures caused by the client method expecting an order object while the broker passed only an identifier.
Deploy marker: b0da6a50 (deploy 4.5.9)
- IBKR continuous futures now tolerate IBKR-listed holiday expiration dates for the same contract month. MNQ June 2026 (
MNQM26) was computed from the synthetic equity-index anchor as2026-06-19, but IBKR lists the contract withexpirationDate/ltd=20260618; the exact-date miss was then persisted in the negative conid cache and BotSpot backtests saw no MNQ bars. LumiBot now keeps the strict no-front-month-fallback invariant, but when IBKR returns exactly one contract in the requestedYYYYMM, it resolves that conid, records both the listed date and computed-date alias, clears stale target negative-cache entries, and uses the IBKR-listed date for cont-future history fetches.
Deploy marker: eefe772f (deploy 4.5.8)
- Broker cash-event polling is hardened for real Alpaca and Tradier activity-history responses. Alpaca cash-event polling now requests non-fill account-activity categories separately (
TRANS,DIV,MISC) instead of paging through trade fills or letting dividend pages hide funding rows, safely handles wrapped/model/invalid activity rows, and was verified against real Alpaca live API-key/OAuth accounts with actualCSDdeposits andCSWwithdrawals. Tradier cash-event polling now parses the raw history response inside LumiBot so the publishedlumiwealth_tradierpackage's"history": "null"bug cannot emit live warnings likestring indices must be integers, not 'str'; direct-request pagination, wrapper fallback, null-history payloads, the exact TypeError path, and real Tradier live transfer/wire/dividend history were verified. - Cash-event warnings now identify the broker and sanitized fetch context. Strategy warnings now include the broker name/class, and Alpaca/Tradier broker adapters log sanitized activity type, page/limit, pagination, and raw-type counts so future production warnings can be diagnosed without exposing API keys.
Deploy marker: c850cd66 (deploy 4.5.7)
- AI agents no longer send unsupported
temperature=0to LiteLLM-routed providers. The ADK runtime had hardcodedtemperature=0.0for every model. OpenAI GPT-5/reasoning-class models reject custom temperature values and only allow the provider default, causing backtests to fail before the first agent call. LumiBot now keeps explicittemperature=0.0only on the Gemini-native ADK path and omits temperature for OpenAI/xAI/Anthropic/etc. LiteLLM providers. Verified with a realopenai/gpt-5.4agent call. - Tradier live cash-event polling no longer passes unsupported
page=toAccount.get_history(). The installedlumiwealth-tradierclient supportsstart_date,end_date,limit,activity_type, andsymbol, but notpage. LumiBot now detects whether the client supports pagination and falls back to a single bounded per-activity request when it does not, preventing live warnings likeAccount.get_history() got an unexpected keyword argument 'page'and allowing broker cash events to load.
Deploy marker: 61b75f24 (deploy 4.5.6)
- Canonical AI agent detail artifact is now Parquet-only and token-first. AI agent backtests now write one canonical
*_agent_detail.parquetartifact beside the normal backtest outputs. The file contains onecall_summaryrow per AI call plus event rows forthinking,text,tool_call,tool_result, andusage, with exact prompt/context fields, model output text, tool args/results, event payload JSON, input/output/total tokens, thinking tokens, cached/uncached input tokens, cache-write tokens, tool-use prompt tokens, full-call latency, and first-event latency. Pricing is intentionally excluded; the artifact records raw token facts only. - Built-in BYOK Alpaca news tool for AI agents. Added
BuiltinTools.news.alpaca_news(), which uses the user's ownALPACA_API_KEY/ALPACA_API_SECRETorAPCA_API_KEY_ID/APCA_API_SECRET_KEY, defaults the newsendtimestamp to the current simulated datetime during backtests, and returns normalized Alpaca news headlines/summaries/sources/URLs/symbols. Full content is opt-in withinclude_content=True. - Alpaca news proof script, example strategy, and live quality test. Added
scripts/run_alpaca_news_ai_proof.py, which runs a tiny real AI-agent backtest, forces the scan-first/full-article workflow, and verifies the resulting*_agent_detail.parquetcontainsalpaca_newstool calls, full article content, thinking rows when exposed by the model, and token/latency fields. Addedlumibot/example_strategies/agent_alpaca_news_builtin.pyas the recommended built-in-tool news example. Added opt-inapitestcoverage that checks Alpaca/Benzinga historical relevance and real pagination on known broad-market event windows. - Real provider prompt-cache probe. Added
scripts/run_agent_prompt_cache_probe.py, which bypasses LumiBot's replay cache and sends repeated real provider calls with the same long static prefix. The probe prints input, cached input, uncached input, output, thinking, total token counts, first-event latency, and full-call latency so provider prompt caching can be verified before deployment.
- Alpaca news tool now supports scan-first / full-read workflow without hidden truncation.
BuiltinTools.news.alpaca_news()now defaultslimit=30, supportspage_token, returnscontent_available_countandsummary_available_count, exposesrequested_end/effective_end/lookahead_clamped, clamps future backtestendtimestamps to the current simulated datetime, and returns the full article body wheninclude_content=Trueunlesscontent_max_charsis explicitly set. The tool description now teaches agents that Alpaca news is symbol/date-window retrieval rather than keyword search, recommends focused scans atlimit=10-20, broad scans atlimit=30-50, explains pagination, and lists broad-market/sector ETF proxies for less sparse historical news queries. - AI agent prompt layout is more provider-cache friendly. Runtime-specific fields such as current datetime, timezone, positions, orders, task prompt, user context, and persistent memory now stay in dynamic user-context sections instead of the static system instruction prefix. This keeps the large base prompt and tool surface stable across bars so OpenAI/xAI/Gemini provider prompt caches can reuse more of the prefix during cold backtests and live trading.
- AI agent prompt caching telemetry and routing keys. Provider cache routing is now stable for OpenAI (
prompt_cache_key+ 24h retention) and xAI (x-grok-conv-id), and LumiBot normalizes cached-token fields from provider usage payloads intocached_input_tokens/uncached_input_tokensso backtests can prove whether provider prompt caching is actually being used.
- Anthropic Claude M2 Liquidity demo. Added
lumibot/example_strategies/agent_m2_liquidity_anthropic.py, matching the Gemini/OpenAI/Grok variants withdefault_model="anthropic/claude-sonnet-4-6"andANTHROPIC_API_KEYsetup instructions.
- AI agent provider-key naming now matches product-facing env vars. Gemini examples/docs/error guidance now use
GEMINI_API_KEY, and Grok examples accept eitherXAI_API_KEYorGROK_API_KEY. LumiBot mirrorsGROK_API_KEYintoXAI_API_KEYfor LiteLLM's xAI provider when the canonical xAI env var is absent. Added regression coverage intests/test_agent_runtime_provider_keys.py.
- Multi-provider LLM support for AI agents via LiteLLM bridge. Agents can now use any LLM provider LiteLLM supports (OpenAI GPT, xAI Grok, Anthropic Claude, Groq, Mistral, Cohere, and ~100 others) by passing a provider-prefixed
default_modeltoself.agents.create(...). Gemini ids still take Google ADK's native fast path; anything else is transparently wrapped viagoogle.adk.models.lite_llm.LiteLlm. New_resolve_model_for_adk()helper inlumibot/components/agents/runtime.pydoes the routing. Addedlitellm>=1.77.0tosetup.pyinstall_requires. Replay cache already keyed on the model id (manager.py:516), so swapping providers on the same backtest produces fresh runs rather than stale cross-model replays. Three provider-specific M2 demo strategies mirror the existing Gemini demo with only thedefault_model+ provider key changed:lumibot/example_strategies/agent_m2_liquidity_openai.py(openai/gpt-5.4-mini),agent_m2_liquidity_grok.py(xai/grok-4.20-0309-reasoning = Grok 4.2), andagent_m2_liquidity_anthropic.py(anthropic/claude-sonnet-4-6). Public docs updated indocsrc/agents.rst,docsrc/agents_quickstart.rst, anddocsrc/environment_variables.rstto document supported providers and required env vars (GEMINI_API_KEY/OPENAI_API_KEY/XAI_API_KEYorGROK_API_KEY/ANTHROPIC_API_KEY). New unit testsTestResolveModelForAdkintests/test_agent_runtime_cache.pycover Gemini passthrough (6 ids incl. case and whitespace variants), LiteLlm wrapping (OpenAI, xAI, Anthropic, Groq, Mistral prefixes), and non-string passthrough. - Automatic retry on transient provider errors for AI agents.
GoogleADKRuntime.run()now wraps the ADK runner in a 3-attempt retry loop with exponential backoff (2s/4s/8s). Catches transient failures (network blips, 429 rate limits, 500/503/529 server errors, brief timeouts) and retries silently; real configuration errors (auth failures, invalid model ids, context-length-exceeded) still fail fast via a non-retryable error classifier. For the LiteLLM path,litellm.num_retries = 3is also set so the LiteLLM layer gets its own provider-aware retry (HTTP-level, 429 Retry-After aware) inside each attempt. Production agents now survive normal cloud-provider hiccups without strategy-level error handling. LiteLLM also configured withdrop_params = True(silently drops provider-unsupported params instead of raising; needed for xAI rejectingmax_completion_tokens) andsuppress_debug_info = True(mutes cosmetic "Provider List" banner for current-generation models not yet in LiteLLM's static cost registry likegpt-5.4-*andgrok-4.20-*). - Discretionary Trader demo + multi-provider parallel runner. New
lumibot/example_strategies/agent_discretionary.pyis the first max-discretion AI agent demo: one-sentence user prompt ("Make as much money as you possibly can."), no asset whitelist, shorts allowed, readsAGENT_MODELenv var so the same file runs against any supported provider (Gemini, OpenAI, xAI, Anthropic). Tools: FRED macro + Alpaca news (graceful-degrade if keys unset) + yfinance fundamentals snapshot (P/E, forward P/E, market cap, profit margins, earnings dates, analyst targets, short interest, 52W range, sector, industry) + all built-ins. Companion scriptscripts/run_discretionary_3way.pylaunches three providers in parallel with a memory watchdog (aborts all if combined RSS > 10 GB or any single process > 5 GB), per-provider log capture, and a summary JSON of tearsheet paths + metrics for post-run comparison. Docs updated indocsrc/agents_canonical_demos.rstwith the full "5 demos" list (v1 discretionary + 4 existing constrained allocators). - AI agent error classifier + live-vs-backtest error-handling semantics (AI-agent-scoped only; zero changes to main Lumibot error handling). New
_classify_agent_errorhelper inlumibot/components/agents/runtime.pybuckets every exception from an agent call intoauth/config/billing/transient/unknown. Classification uses class name match (litellm/openai/anthropic/google-genai), HTTP status code when the provider SDK attaches one, and message-substring keywords (insufficient_quota,credits,billing,payment,no credits,team doesn't have any credits) so e.g. xAI's 403 "your team doesn't have any credits yet" is correctly bucketed asbillingnotauth.GoogleADKRuntime.run()now only retriestransient+unknown; auth/config/billing errors propagate immediately instead of burning the 10-attempt 5-minute retry budget on errors that cannot self-heal.AgentHandle.run()wraps the runtime call with a classifier-aware safety net: in backtest mode,auth/config/billingre-raise loudly with a clean error banner that points at the env-var to fix and the provider billing URL (so users fix and re-run instead of getting a silent +0% tearsheet); in live mode, all categories log + skip the iteration so the bot never stops. Transient and unknown errors always skip gracefully in both modes. This is fully scoped insidelumibot/components/agents/*— the mainstrategy_executor.py,_strategy.py, broker/trader files, and_on_bot_crash/gracefully_exitbehavior are unchanged. 22 new regression tests intests/test_agent_runtime_cache.py(TestClassifyAgentError,TestAgentHandleRunClassifiedErrorBehavior) cover classifier buckets, backtest crash semantics, and live-never-crash invariants. Retry schedule retuned from 3 attempts × 14s budget (old) to 10 attempts × ~5-min budget with no single backoff exceeding 60s (2, 3, 5, 10, 20, 30, 45, 60, 60, 60). Rich error handling documentation added todocsrc/agents.rstin a new "Error Handling and Reliability" section. - AI agent model auto-populates into
strategy.parameters. Every call toself.agents.create(name=..., default_model=...)now auto-setsstrategy.parameters[f"agent_{name}_model"]to the resolved model id, which surfaces in the tearsheet's "Parameters Used" panel. Multi-agent strategies get one key per agent. Zero per-strategy code changes required — works for any existing or future agent strategy. Three regression tests inTestAgentManagerParametersAutoPopulate. - AI token usage summary + detailed audit artifacts for backtests. Every AI agent run now updates cumulative token totals directly in
strategy.parameters, so the tearsheet's "Parameters Used" panel showsagent_<name>_calls,agent_<name>_input_tokens,agent_<name>_output_tokens,agent_<name>_total_tokens,agent_<name>_thinking_tokens,agent_<name>_tool_calls,agent_<name>_cache_hits,agent_<name>_detail_csv, andagent_<name>_detail_parquetautomatically. In addition, LumiBot writes*_agent_detail.csvand*_agent_detail.parquetbeside the normal backtest artifacts, following the existing CSV-compatible / Parquet-fast contract used by trades, stats, trade_events, and indicators. The detail artifact contains one row per model event (thinking,text,tool_call,tool_result,usage) with the effective prompt, task prompt, user context, runtime context, event text, flattened tool details, warnings, trace path, and token counts for the call. This is intentionally token-only: no pricing logic, no stale model-cost tables. Gemini requests thought summaries when the SDK supports them; other providers still record thinking token counts even if they do not expose thought text. Regression coverage added intests/test_agent_runtime_cache.py.
- Agent prompt now instructs strategies on risk and drawdown discipline. New "RISK AND DRAWDOWN DISCIPLINE" block in
AgentHandle's base prompt (lumibot/components/agents/manager.py) tells generated strategies to optimize for risk-adjusted return, not headline return; explains recovery math (20% DD needs 25% gain, 50% needs 100%, 80% needs 400%); sizes positions by conviction + volatility, not just cash; cuts broken theses instead of averaging down; does not chase losses with more size; and thinks in Sharpe/Sortino/Calmar terms. Zero code-path changes; purely a behavioral shift in what agent-generated strategies do.
- WEEX broker support via CCXT. New
WEEX_CONFIGinlumibot/credentials.pywires WEEX (CCXT exchange idweex) into the broker auto-detect chain, the explicit-name branch (TRADING_BROKER=weex), and the data-source aliases (DATA_SOURCE=weex). WEEX requires three credentials (WEEX_API_KEY,WEEX_API_SECRET,WEEX_API_PASSPHRASE) and has no API sandbox, sosandboxis hard-wired toFalse. Added"weex"to the exchange-id whitelists inlumibot/brokers/ccxt.py(balance parsing at line 104 and open-orders routing at line 224) so the generic Ccxt broker accepts WEEX without raisingNotImplementedError. Initial scope is spot trading only; WEEX's primary business is USDT-margined perpetual swap, but swap support is not wired into the shared Ccxt broker today. Note that WEEX's Terms of Use exclude US and Canadian residents, documented indocsrc/brokers.ccxt.rst. New unit testtest_initialize_weex_brokerintests/test_ccxt.py.
- Docs: Coinbase section now documents CDP (Cloud Developer Platform) private-key auth.
COINBASE_CONFIGalready supported PEM private keys via thesecretfield (CCXT auto-detects PEM vs HMAC), but the docs still described the legacy HMAC + passphrase flow. Updateddocsrc/brokers.ccxt.rstto explain thatCOINBASE_API_KEY_NAMEholds the org-qualified identifier from the CDP JSON andCOINBASE_PRIVATE_KEYholds the full PEM block (multi-line,-----BEGIN EC PRIVATE KEY-----...-----END EC PRIVATE KEY-----).COINBASE_API_PASSPHRASEis now documented as legacy-only.
Data.iter_count()with a microsecond-precision index returned incorrect cursor positions._index_values_nsassumed the pandasDatetimeIndexwas nanosecond-precision, but pandas 2.x can build microsecond-precision indexes from ISO strings. Normalize to nanoseconds explicitly so thenp.searchsorted(index_ns, now_ns)cursor math works regardless of pandas' chosen unit. Regression test intests/test_data_iter_count_microsecond_index.py.
Strategy.get_last_price()returnedNoneat sim_time=00:00 on 24/7 markets, breaking strategy rebalance logic. When a strategy's iteration fires at midnight (e.g.MARKET="24/7"plus daily sleeptime), the daily-shortcut'slength=1, timeshift=0slice returns empty because no day bar exists exactly at sim_time=00:00. Without a retry the shortcut falls through tobroker.get_last_price, which on the IBKR REST backtest path also fails at non-market-hours timestamps. Net result: strategies seeNoneon every iteration and the rebalance path'sif last_price is None: continueskip means no orders are ever placed. Reported by Rob on the 2026-04-19 local IBKR backtest: Alpha Picks ran for 31 simulated days atVal: $100,000with zero trades. Fix: when the length=1 slice comes back empty, retryget_historical_prices(length=5, timestep="day")and return the close of the last row withindex <= sim_time. No guard, no rejection of other slices — this ONLY triggers when the length=1 result is honestly empty. Matches Rob's stated semantics: "if we have no bar at sim_time, the price hasn't changed since the last known bar." New regression testtest_forward_fill_returns_last_prior_bar_when_length1_emptyintests/test_get_last_price_sim_time_safety.pycovers the 24/7 midnight case.- Tearsheet cumulative-returns plot started the Strategy line below the Benchmark line at the left edge. QuantStats computes cumulative returns as
(1 + returns).cumprod() - 1, so the first plotted point equals the first day's return. When the strategy bought at open and closed with a small drawdown on day 1, the Strategy line began at e.g.-4%while the Benchmark's firstpct_changewas NaN→0 and started at0%. Rob reported this as a recurring visual artifact on Alpha Picks tearsheets (multiple sightings over months). Two-part fix: (1)lumibot/tools/indicators.py::_prepare_tearsheet_returnsnow prepends a day-0 anchor row (first_index − 1 day) with both returns = 0 whenever the first real row has a non-zero return; (2) passmatch_dates=Falsetoqs.reports.htmlat both call sites increate_tearsheet. Without the second part, QuantStats'_match_datesexplicitly drops leading zero-return rows (loc = max(returns.ne(0).idxmax(), benchmark.ne(0).idxmax()), truncates both series toloc:) and deletes our anchor before it reaches the plot. With both parts, the Strategy and Benchmark lines share a common 0% starting point on the left edge. Regression testtest_tearsheet_prepends_anchor_when_first_day_has_non_zero_returnintests/test_tearsheet.pypins the anchor prepend; thematch_dates=Falsechange is validated by visual tearsheet inspection. - IBKR helper:
get_price_data()crashed with'str' object has no attribute 'symbol'when callers passed a bare symbol string instead of anAsset. Observed in the 2026-04-19 local Alpha Picks IBKR backtest: upstream path leaked a string through, producing misleading log spam"IBKR history fetch failed for None/USD timestep=minute ..."that obscured the real bug during investigation. Fix: defensive coercion at the top oflumibot/tools/ibkr_helper.py::get_price_data— ifassetis a string, wrap it inAsset(symbol=asset)before proceeding. One-line safety net at a system boundary; the log message becomes accurate (IBKR history fetch failed for COP/USD ...) and the rest of the pipeline works.
Note: the in-flight
4.4.63work was never published to PyPI (nov4.4.63tag). Those entries are consolidated into this4.5.0release alongside theStrategy.indicatorssubsystem.
Strategy.backtest()now honoursBACKTESTING_START/BACKTESTING_ENDenv vars as overrides for hardcodedbacktesting_start/backtesting_endargs. Strategies typically pin dates in theirif __name__ == "__main__":block for local dev convenience. When the same code runs in a managed container (bot_manager backtest ECS task, BotSpot MCPstart_backtest, etc.) the orchestrator sets these env vars from the caller's requested date range — previously those env vars did nothing, the hardcoded datetimes won, and MCP callers had no runtime control over the window. New precedence: env var (if present and parseable) > passed arg. Accepts ISO date (YYYY-MM-DD) or full ISO datetime; malformed values are logged and the passed arg is preserved (no silent fallback to the epoch ornow). Unit tests intests/test_strategy_backtest_env_override.pycover both shapes, empty-string handling, parse failures, and the no-env-set baseline.Strategy.indicatorssubsystem. New per-strategy indicator accessor that computes any indicator over the full bar series once, then hands back the value at the current bar in O(1) on every subsequent call. Eliminates the per-iterationdf.rolling(...).apply(...)full-history recompute pattern that dominated wall-time on indicator-heavy strategies. Exposes the entire pandas-ta-classic surface (~130 indicators) viaself.indicators.<indicator>(asset, **kwargs)passthrough plusself.indicators.custom(name, fn, asset, **kwargs)for user-defined indicators. Same API for backtest and live. Seedocsrc/indicators.rstfor the full reference and migration guide.LUMIBOT_CACHE_MISS_DEBUGenv var gates opt-in[CACHE_MISS]/[FETCH]warning traces inibkr_helper.pyfor auditing why the cache layer decided to hit the network. Documented indocs/ENV_VARS.mdanddocsrc/environment_variables.rst.
Strategy.indicators: per-bar lookup now O(log N) viasearchsorted._latest_scalar/_latest_rowno longer allocate a.loc[:now]slice on every call; they compute the integer position of the most-recent bar ≤ now withIndex.searchsorted(now, side="right") - 1andilocdirectly. Collapses the per-iteration cost of a memoized indicator from a full label-compare slice to a binary search + one integer index. Observed impact ontqqq_medianbenchmark (13-year daily): 54.71s → 5.59s end-to-end (~9.8x faster per-iteration lookup — the memoized compute was already one-shot; this fixes the hot-path indexing cost).Strategy.indicators: cache keyed on full-history fingerprint._dispatchnow tags each memoized result with(len(df), df.index[-1])and recomputes transparently when the underlying bar series grows (routed/live prefetch). Prevents stale indicator output when the data source extends the frame after the first call.Strategy.indicators: prefetch-aware full-history fallback._full_historynow tries_data_storefirst, then — if empty — callsget_historical_prices(length=self._fallback_length)purely to trigger the routed/live adapter's prefetch path, and re-reads the now-populated store. This makes the indicator subsystem work correctly on routed/IBKR backtests where_data_storeis populated lazily on first access._fallback_lengthreduced from 100_000 to 10_000 to avoid pandas ns-timestamp overflow (100k daily bars ≈ year 1573, below the pandas epoch).lumibot/backtesting/routed_backtesting.py: narrow short-circuit in_IbkrRoutingAdapter.update_pandas_dataskips IBKR's 1-minute prefetch forcont_future/futurequote-lookup calls when coarser non-day cadence (15-minute, 30-minute, hour, …) is already loaded. Affectsget_quote(asset)default-timestep lookups during backtests. Observed impact onmes_ema_15mbenchmark: 26.6s → 7.03s (3.78x) on warm cache; results byte-identical.lumibot/data_sources/pandas_data.py: module-level_USD_FOREX = Asset("USD", "forex")singleton reused byfind_asset_in_data_store()to avoid constructing a fresh default-quoteAsseton every call (~200K calls/backtest). Missing-asset warnings now gate to first-encounter per(asset, timestep)to avoid flooding 1-minute backtest logs with redundant warnings.
- CRITICAL:
Strategy.get_last_price()daily-cadence shortcut had a look-ahead read that returned future bars on IBKR / ThetaData / routed backtests. The shortcut atlumibot/strategies/_strategy.py::get_last_pricecalledself.get_historical_prices(asset, length=2, timestep="day", timeshift=-1, ...)and then tookfloat(bars.df["close"].iloc[-1]).Data.get_bars(length=2, timestep="day", timeshift=-1)computesend_row = iter_count + 2,start_row = iter_countand returns rows[iter_count, iter_count + 1]— i.e. the bar AT sim-time AND the next bar after it — soiloc[-1]picked up the future bar. In a backtest the full history exists ahead of the sim clock, so when the underlying frame also contained bars past the backtest window (e.g. a shared S3 cache row stamped at wall-clock "today") the shortcut returned today's close for a 2022 sim-time. Observed incident 2026-04-17 on BotSpot Stock Alpha Picks (backtestf2d9ca86, window 2022-07-01 → 2022-08-01):get_last_price(COP)returned $97.43 (real-now close) instead of the 2022-07-01 close of $90.98, polluting position sizing across every symbol. Fill prices were unaffected (historical-bars fill path is separately sim-time-safe); only theget_last_priceshortcut leaked. Fix: change the shortcut tolength=1with no timeshift soData.get_barsreturns exactly the single bar atiter_count— guaranteed to be at or before sim_time regardless of what the frame contains past it. New regression coverage intests/test_get_last_price_sim_time_safety.py(7 cases): polluted-frame reproduction replaying the exact Alpha Picks COP/NUE symptom, dense-frame sanity, sim-time-before-any-bar fall-through, and a whitebox guard on thelength=1, timeshift in (None, 0)call signature so a future edit can't silently restorelength=2, timeshift=-1. The existingtest_strategy_methods.py::test_get_last_price_prefers_day_bars_for_routed_backtesting_daily_cadencetest — which had been pinning the buggyiloc[-1]return of a two-row frame as "correct" — is rewritten to assert the correct sim-time-safe behaviour. Operationally, the shared IBKR daily stock/index S3 cache was wiped on dev + prod (stock/index day + minute; futures/crypto/option preserved per theibkr/future/rule) to clear any lingering wall-clock-stamped rows that may have been written by earlier buggy code paths. - CRITICAL: IBKR stock/index daily pagination cap was too tight, producing flat-price backtests over multi-year windows.
IBKR_STOCK_INDEX_DAILY_MAX_PERIODwas hardcoded to"180d", which made_fetch_history_between_datesrequest only ~125 bars per call against an endpoint that caps responses at 1000. In practice the backward-walking pagination loop completed in a single iteration: the initial fetch near real-now filled the cache with 180 days of recent data, and the coverage check for every subsequent simulation date declared that "real bars exist" and stopped re-fetching. The strategy then received today's prices for every historical simulation date, producing flat-VIX / flat-SPY / flat-RUT series across the entire backtest window. Observed incident: Peter Credit Spreads backtesta83663bd-...(2018-01-01 → 2026-04-16) hadVIXconstant at14.2for all 2,010 simulated days (stddev=0) and max drawdown of-94%. Production verification against IBKR Client Portal REST on 2026-04-17:period=5yreturns the full 1000-bar cap (~4 trading years of daily data),period=2yreturns 323 bars,period=180dreturned only ~125 bars. Fix: raiseIBKR_STOCK_INDEX_DAILY_MAX_PERIODto"5y"so each call gets the maximum usable response and the backward-pagination loop covers 8-year windows in ~2 iterations per symbol/source. Regression tests intests/test_ibkr_helper_unit.pylock the new value in place. End-to-end verified locally against Peter Credit Spreads: VIX now has 497 distinct values (9.15–82.69) over 2018-01 → 2020-06 with the March 2020 COVID spike to 82.69 captured correctly; strategy max drawdown collapses from-94%(broken) to-5%(working, regime filter actually firing). - Backtesting: persist placeholder markers for IBKR terminal pagination-empty responses.
_fetch_history_between_datesraises"IBKR history pagination returned empty data before covering the requested window"when IBKR serves partial-then-empty responses (common for entitlement/stitching gaps like CONT_FUTURE 1-minute Trades). The error was caught but_is_terminal_no_data_errordid not match the message, so no placeholder marker was written and every backtest refetched the same ~23s empty-result call. Fix: extend_is_terminal_no_data_errorinlumibot/tools/ibkr_helper.pyto match the pagination-empty tokens, so_record_missing_windowpersists amissing=Truemarker and_window_is_placeholder_coveredshort-circuits on subsequent runs. Observed impact onvband_mnq_mes_1mbenchmark: 55.8s → 2.94s (18.98x) on warm cache; all results byte-identical. - CRITICAL: IBKR backward pagination discarded all earlier chunks on a single empty mid-walk page, breaking CME futures backtests.
_fetch_history_between_dateswalks backwards fromend_dtinperiod-sized windows (default1000minfor 1-min bars). CME futures markets have a weekly weekend close (Fri 4pm CT → Sun 5pm CT) and a nightly maintenance break (4–5pm CT); when a1000minwindow lands entirely in a no-trading range, IBKR correctly returns{"data": []}even though the surrounding bars are valid. A previous fix (3c5cbb5c, 2026-03-05 "ibkr: keep paged history chunks when later page is empty") changed the empty-page handler toif chunks: breakso earlier chunks survived, but the WIP checkpointaf8df88b(2026-03-30, pre-4.4.57 branch reconcile) silently reverted it back toraise RuntimeError("IBKR history pagination returned empty data before covering the requested window ..."). The regression was then released onversion/4.4.57+ and carried intoversion/4.5.0. Impact: everyget_historical_prices()call for CME futures (MES/MNQ/ES/NQ/…) that needed to walk past a weekend would raise, and the resulting empty-frame return meant the strategy saw no bars for every subsequent iteration. Observed inmes_ema_15mbenchmark onversion/4.5.0: 209 pagination-empty errors in a single 6-month backtest, strategy unable to trade after bar 1, backtest runs to 40-min wall-clock cap without completing. Fix: restore theif chunks: breakbehaviour in both theif not data:andif df.empty:branches of the backward-pagination loop, exactly matching3c5cbb5c. New regression testtest_ibkr_fetch_history_keeps_chunks_when_mid_paging_page_is_emptyintests/test_ibkr_crypto_daily_series.pyforces three mocked history calls where call 3 returns empty, and asserts that the two prior chunks are returned (verified to raiseRuntimeErrorwithout the fix, pass with it).
- CRITICAL: Flat-price fabrication bug in backtesting fills.
BacktestingBroker._pandas_fill_pathpreviously pickeddf_original.iloc[-1:]when the "bars at or after self.datetime" filter came back empty, ORdf_original.iloc[0]when the filter caught rows far in the future. Both paths could silently price every fill at a single bar's OPEN that had nothing to do with the simulation time, producing deterministic constant prices across multi-year backtests. Observed incident: BotSpot "Alpha Picks" backtest where 72 of 84 stocks froze at the OPEN of 2026-04-10 for every fill across 2022-2024. The fix rejects future bars outside a narrow window (2 days for daily, 2 hours for hourly, 5 minutes for minute), prefers the most recent bar at or before the simulation time, and refuses to fabricate a fill when no bar exists within a reasonable window ofself.datetime. A second-layermax_fill_distancesanity check (7 days for daily, 1 day for intraday) hard-rejects any fill price derived from a bar too far from the simulation clock, cancelling the offending iteration rather than producing a garbage fill. Provider-agnostic; applies to every data source that flows throughPANDAS/InteractiveBrokersRESTfill paths (ThetaData, IBKR, Polygon, etc.). - IBKR history loading now fails open by returning available real bars instead of synthesizing an empty dataset when the cache refresh leaves the requested window underfilled (commit
5de1362a, April 14). - Interactive Brokers REST backtesting data source now prefers an already-loaded daily stock/index series for
get_last_price/get_quotebefore triggering a separate intraday minute fetch, which avoids unnecessary VIX/USD midpoint history requests during daily-cadence backtests (commit5de1362a, April 14). Order.to_dict()now emitsidentifierandavg_fill_priceconsistently so downstream consumers that rely on these fields on serialized orders no longer see missing keys after backtest-time fills (commite3cb48fe, April 15).- Release the IBKR downloader fail-closed hotfix from the corrected commit so CI and PyPI ship the same behavior validated locally (merged from dev).
- README restructured for visitor conversion: Quick Start code block, competitor comparison table (LumiBot is the only open-source Python trading library with options support), supported brokers & data sources matrix, migration guide from Backtrader (merged from dev).
- PyPI metadata overhaul: added 20 search keywords and 12 classifiers (Financial, Investment, AI, Python 3.10-3.12) after being previously empty. Updated package description for search discoverability (merged from dev).
- Author email updated to rob@botspot.trade; added project_urls for documentation, Discord, and BotSpot platform (merged from dev).
docs/MIGRATING_FROM_BACKTRADER.md: concept mapping and side-by-side code examples for users switching from the now-unmaintained Backtrader library (merged from dev).- Release readiness script now verifies that every referenced artifact file actually exists on disk (not just that
artifact_pathis non-empty), preventing stale pointer references from passing the deployment gate (commit323b6488, April 12). - LumiBot matrix runner emits
lumibot_runner_state.jsoncheckpoint files during long runs so in-flight case state is inspectable during multi-hour acceptance runs (commit6944e22c, April 12).
Release bookkeeping only. No functional code changes from 4.4.60.
- Data Downloader queue client now uses a dedicated configurable connect-timeout budget instead of a hardcoded
5s, which prevents IBKR/VIX history refreshes from failing closed on slow downloader connections. - IBKR history loading now fails closed when a refresh leaves the requested window underfilled, so stale cached slices are no longer returned as if they were complete history.
@agent_tooldecorator now auto-includes function source code in tool descriptions, giving AI agents full visibility into parameters, defaults, and implementation details without manual documentation.AgentHandlenow always merges built-in tools with custom user tools (previously custom tools replaced built-ins).- Four new canonical agent demo strategies: M2 Liquidity (FRED data), Macro Risk (Alpaca bars), Momentum Allocator (Alpaca bars + news), and News Sentiment (Alpaca news). These replace the previous stress-test examples with production-quality patterns.
- Version logged at startup (
LumiBot v{version} starting) vialogger.infofor CloudWatch/backtest/live log visibility. - Version included in backtest
settings.jsonartifacts (lumibot_versionfield) for post-deploy verification. - Auto-create next version branch job in release workflow to prevent team-blocking delays after a release.
- Post-deployment verification steps documented in
DEPLOYMENT.md.
- Improved lookahead-bias guardrails in agent system prompts: agents must now explicitly set end-date bounds on ALL temporal tool parameters, not just known ones.
- Major documentation refresh: agents quickstart, canonical demos, observability, FAQ, getting started page with agent framework introduction.
BACKTESTING_QUIET_LOGSenv var parsing was broken (comparing string toNone); now correctly parses boolean-like strings (true,1,yes,on).- Removed contradictory
set_console_log_level("ERROR")call whenquiet_logs=Falseintrader.py. - IBKR pagination test assertions updated to match current behavior.
.gitignorefix for deployment reliability.
- Bump
quantstats-lumidependency to>=1.1.3,<1.2.0so tearsheet consumers require the renamedWorst 1-Month Returnrow and the latest machine-readable contract.
- Backtest console print settings no longer get silently overwritten when
lumibot_loggerre-applies log levels during a backtest run. (PR #981 — @davidlatte) - Tearsheet summary artifact compatibility with
quantstats-lumimachine-readable metric contract (typed scalar values, no%string leakage in JSON scalar values). - Removed the duplicate
cash_financing_rates()strategy hook so cash financing now uses a single public interface centered onset_cash_financing_rates(...). - Backtest stats, plots, and tearsheet inputs now subtract external cashflows from returns, so deposits and withdrawals no longer distort
total_return, CAGR, or other performance metrics. - Backtest runners now honor caller-provided
plot_file_htmlandtrades_filepaths instead of silently writing trade artifacts to the defaultlogs/directory.
- Tradier stock shorting support:
sell_shortandbuy_to_coverorder sides now map correctly so short-selling equities works on Tradier. (PR #976 — @brettelliot) - AI trading agent framework:
self.agents.create(...)inside strategies with DuckDB query tools, agentic backtesting with replay cache, and external MCP server mounting. New modules underlumibot/components/agents/. - End-to-end tearsheet custom-metrics proof coverage for real backtest runs that generate both
tearsheet.htmlandtearsheet_metrics.json. - Backtest cash-accounting coverage for
adjust_cash,deposit_cash,withdraw_cash, and strategy-managed financing-rate updates. - Normalized
cash_eventslive payload support in LumiBot for Alpaca and Tradier, including stable event IDs, retry-safe pending emission, and bounded payload serialization. - Period-delta cash columns in
stats.csv(cash_*_period) for manual inspection of deposits, withdrawals, financing accruals, and cashflow-adjusted return math. - Cash-event rows in
trades.csv/ parquet and cash-event markers intrades.html, including deposits, withdrawals, and financing credits/debits.
- Expanded public documentation for
tearsheet_custom_metrics(...), including parameter structure, full examples, literal-scalar unit behavior, and release-order guidance for QuantStats/LumiBot metric changes. - Added public documentation for strategy cash accounting, financing lifecycle usage, broker cash-event normalization, and broker-specific limitations for Alpaca and Tradier.
BACKTESTING_PARAMETERSenvironment variable support for parameter injection in backtest runs.- Machine-readable
*_tearsheet_metrics.jsonartifacts (summary-first) with placeholder output on insufficient/degenerate returns. - New strategy lifecycle hook
tearsheet_custom_metrics(...)for appending custom metrics to tearsheet HTML and JSON artifacts. - Regression coverage for multi-timeframe day-timestep stock lookup and tearsheet metrics/custom-hook passthrough.
- Backtest analysis and trader APIs now accept
tearsheet_metrics_file; default output filename is*_tearsheet_metrics.json. - QuantStats
metrics_jsongeneration now runs insummary_onlymode and forwards custom metrics to both HTML and JSON outputs. - Documentation updates for tearsheet metrics/lifecycle hooks and TradingFee guidance (
per_contract_feeusage).
- Day-timestep asset lookup regression for multi-timeframe stock/index backtests (including minute->day fallback paths where appropriate).
- IBKR stale no-data cache reuse now forces refresh when requested windows extend beyond cached coverage.
- ProjectX order processing race-condition and tracking hardening merged from
dev.
Deploy marker: 15e8e268 ("deploy 4.4.55")
TradingFeenow supportsper_contract_feefor broker-style option commissions charged per contract.- Regression tests for
per_contract_feeinitialization and trade-cost calculations in backtesting.
TradingFeefee fields now coerce throughDecimal(str(...))for stable decimal handling across float inputs.
- Backtesting trade-cost calculations now apply
per_contract_fee * quantityfor taker and maker fee paths (market,stop,limit,stop_limit,smart_limit).
- Regression tests for daily-cadence datasource seeding in
StrategyExecutor, routed1Dtimestep normalization, put-delta normalization/model-path strike selection, and IBKR equity corporate-action cache reuse. - Regression tests for IBKR paged-history retention when later pages are empty, plus option valuation fallback coverage for off-session stale mark scenarios.
- Daily-cadence backtests now seed datasource cadence to
dayduring strategy initialization to avoid first-lookup minute prefetch blowups. Strategy.get_last_price()now consistently prefers daily bars for stock/index assets in daily backtest cadence, including routed IBKR stock/index paths.- Routed backtesting now treats day-like timestep aliases (
1D,1day, etc.) as daily cadence for non-Theta last-price/quote reads. - ThetaData daily option fetches now prefetch forward in bounded chunks (capped by expiration/end) to reduce repeated downloader round-trips during long runs.
- Option helper strike selection now normalizes absolute delta inputs by option side and uses a fast model-based strike pick for Theta daily option backtests.
- IBKR equity corporate-action enrichment now uses Yahoo history with coverage hints (
last_needed_datetime) and date-bucket cache keys for stable reuse. - Backtest artifact export now always writes CSV/parquet outputs for trades/stats/indicators/trade-events regardless of
show_plotmode.
- Guarded option MTM valuation against off-session stale marks that could cause transient portfolio-value drops in backtests.
- Fixed IBKR history pagination to preserve already-fetched chunks when a subsequent page returns empty.
- Refreshed acceptance baseline metrics for
aapl_deep_dip_callsandleaps_alpha_picks_shortto match current deterministic CI outputs. - Updated
test_classic_60_40drift-rebalancer expectations to the corrected daily-cadence fill quantities.
- Regression tests for Yahoo corporate-actions helpers (
get_symbol_actions,get_symbols_actions) and IBKR daily equity action enrichment. - Regression test for routed IBKR daily stock prefetch to guarantee full lookback warmup coverage.
- Production-readiness harness (
scripts/ibkr_theta_prod_readiness.py) now defaults SPX stress windows to 3 months (2025-01-01through2025-03-31) with a longer timeout. - Prod-like runner (
scripts/run_backtest_prodlike.py) now supports--perf-modefor cleaner runtime benchmarking without plot/indicator/progress noise. - Routed IBKR daily stock/index prefetch now uses the computed bar lookback window (
start_datetime) instead of a short calendar cap from backtest start. - Acceptance performance history records were refreshed for ongoing regression tracking.
- Deployment runbook now documents local-timeout fallback and explicit review of local-only commit ranges before release.
- Yahoo helper typo in corporate-actions paths (
get_symbol_actions/get_symbols_actions) that prevented IBKR equity split/dividend enrichment from loading actions. - Acceptance gate hardening: apply a bounded, case-scoped tolerance override for
ibkr_crypto_acceptance_btc_usdmetric jitter (CI/provider-data drift) to reduce false negatives. - Router benchmark stats now prefer routed datasource bars for stock benchmarks and only fall back to Yahoo on router fetch failure (removes flaky Yahoo-first behavior in CI).
- Option lifecycle event support in backtesting for option expiration outcomes:
assigned,exercised, andexpired(in addition tocash_settled). - Regression coverage for equity/ETF physical settlement and index cash settlement paths at expiration.
- Opt-in early-assignment heuristic model for short ITM, physically-settled options (
strategy.parameters:option_early_assignment_enabled,option_early_assignment_max_dte_days,option_early_assignment_max_extrinsic).
- Options expiration behavior now follows broker-style settlement defaults:
- Equity/ETF options settle physically at expiration (short ITM -> assignment, long ITM -> exercise when account constraints allow).
- Index options settle to cash at intrinsic value.
- Trade artifacts now preserve option-expiration lifecycle statuses in
trades.csv/trades.parquetandtrade_eventsexports so downstream consumers can render assignment/exercise/cash-settlement explicitly.
- ThetaData daily options MTM: prefer snapshot quote marks over stale day marks, and allow forward-fill when snapshot data is unavailable.
- ThetaData backtesting: keep intraday index minute/hour fetch bounds aligned to the simulation timestamp instead of forcing full-window end coverage.
- Long ITM equity option expirations now avoid unrealistic forced delivery when account constraints are not met; these contracts expire unexercised in backtests.
- Acceptance baselines: refresh
aapl_deep_dip_callsandleaps_alpha_picks_shortmetrics to match current option settlement behavior.
- Indicators HTML: improve subplot scaling so indicator panels render with sane proportions across mixed plots.
- Indicators export: make HTML export non-fatal so backtests still complete if HTML rendering fails.
- Acceptance baselines: refresh 0DTE backdoor baseline metrics and timing metadata to match current provider data revisions.
- Acceptance CI: allow a bounded queue-fill threshold for
spx_short_straddle_reprowhile keeping strict queue-free checks for other ThetaData acceptance cases.
- Backtesting artifacts: add
LUMIBOT_BACKTEST_PARQUET_MODEwithrequiredcontract mode (fail-fast on parquet export failures) and structured parquet export logs (rows/cols/bytes/duration, coerced columns).
- Indicators: always emit
*_indicators.csv+*_indicators.parquet, even when a strategy produced no markers/lines/OHLC (empty indicators = valid artifact). - Trade events: always emit
*_trade_events.csv+*_trade_events.parquet(empty events = valid artifact).
- Stats: stop embedding raw
Assetobjects in thepositionsstats snapshot; sanitize object-ish stats columns before parquet export to preventConversion failed for column positions with type object.
- Backtesting artifacts: emit Parquet siblings for
*_indicators.csv,*_trades.csv,*_stats.csv, and*_trade_events.csv(zstd + PyArrow). CSV remains the compatibility layer.
- Tradier: support OAuth payload + access token refresh; add runtime notes for the refresh flow.
- Tests: mark DataBento backtest coverage as
apitestso the default CI suite stays deterministic without vendor credentials. - Docs: clarify auto-expiry futures behavior and IBKR crypto roots.
- Data: handle
timeshift=Nonein Data bars. - Futures (auto-expiry): make selection roll-aware and harden IBKR conid negative cache behavior.
- Backtesting: support
BACKTESTING_BUDGETenvironment override for strategy backtest cash/budget.
- Downloader: rename the downloader queue client module from
thetadata_queue_clienttodata_downloader_queue_client(provider-agnostic naming).
- IBKR: parse seconds-style timesteps (e.g.
20S) for history requests where supported. - IBKR crypto futures: harden continuous futures expiration selection in backtesting.
- Logging: avoid stale env-driven logger levels by re-applying Lumibot logging configuration on each
get_logger()call (reduces test flakiness when env vars toggle).
- Backtesting routing: when
futures/futureis configured, defaultcont_futureto the same provider soAssetType.CONT_FUTUREdoes not fall back todefault. - Backtesting performance: default per-asset fetch throttling (
sleep_time) to 0 for backtesting data sources (keeps live default throttling unchanged). - Backtesting performance: bound
get_trading_days()calendar initialization to the backtest date window to avoid building decades of unused schedules.
- Release: include
lumibot/resources/ThetaTerminal.jarin the PyPI wheel/sdist (required by BotManager and ThetaData setup). - Backtesting:
BacktestingBroker.process_pending_orders()now accepts both iterable order buckets and legacy buckets that exposeget_list().
- Charting:
Strategy.add_ohlc()andStrategy.get_ohlc_df()for exporting OHLC (candlestick) indicator series. - Indicators:
plot_indicators()now supports OHLC series in*_indicators.htmland exportstype=ohlcrows in*_indicators.csv. - Docs: add seconds-level backtesting guidance and expand seconds-mode notes.
- Charting:
Strategy.add_line()now returns the appended dict (consistent with other chart helpers). - Docs: recommend
add_ohlc()for plotting price bars andadd_line()for single-value indicators.
- Release: correct PyPI packaging so
lumibot==4.4.44includesStrategy.add_ohlc()(the published4.4.43wheel was missing it).
NOTE: The PyPI lumibot==4.4.43 artifact was published from an older commit and does not include the changes
listed below. Upgrade to lumibot==4.4.44.
- Charting:
Strategy.add_ohlc()andStrategy.get_ohlc_df()for exporting OHLC (candlestick) indicator series. - Indicators:
plot_indicators()now supports OHLC series in*_indicators.htmland exportstype=ohlcrows in*_indicators.csv. - Docs: add seconds-level backtesting guidance and expand seconds-mode notes.
- Charting:
Strategy.add_line()now returns the appended dict (consistent with other chart helpers). - Docs: recommend
add_ohlc()for plotting price bars andadd_line()for single-value indicators.
- Backtest executor safe-sleep overload now applies only in backtests and uses real sleep outside backtesting.
NOTE: The PyPI lumibot==4.4.42 artifact was published from an older commit and does not include the changes
listed below. Upgrade to lumibot==4.4.43.
- Charting:
Strategy.add_ohlc()andStrategy.get_ohlc_df()for exporting OHLC (candlestick) indicator series. - Indicators:
plot_indicators()now supports OHLC series in*_indicators.htmland exportstype=ohlcrows in*_indicators.csv. - Docs: add seconds-level backtesting guidance and expand seconds-mode notes.
- Charting:
Strategy.add_line()now returns the appended dict (consistent with other chart helpers). - Docs: recommend
add_ohlc()for plotting price bars andadd_line()for single-value indicators.
- Backtest executor safe-sleep overload now applies only in backtests and uses real sleep outside backtesting.
- Tests: add regression coverage for futures calendar spreads (same root symbol, different expirations) to prevent margin/PnL ledger collisions.
- Docs: add investigation notes for ThetaData stale-loop behavior and futures “ghost PnL” equity spikes.
- Backtesting helpers: cache trading calendar schedules by year and slice to the requested window to reduce repeated schedule computations.
- ThetaData: avoid eager debug string building in hot paths unless debug logging is enabled.
- ThetaData backtesting: normalize legacy/externally-warmed
prefetch_completemetadata before cache validation to prevent per-bar STALE/REFRESH thrash. - ThetaData backtesting (day): treat
tail_missing_permanent=Trueas satisfying end-coverage validation to prevent per-bar STALE→REFRESH loops on warm caches. - Backtesting futures: include expiration in futures margin/PnL ledger keys so calendar spreads (same root, different expiries) don't incorrectly net margin/realized PnL, preventing “ghost PnL” equity spikes.
- ThetaData backtesting: coverage-based
prefetch_completecomputation + tests to prevent per-bar STALE/REFRESH thrash when cached datasets are incomplete.
- Yahoo helper: when S3 backtest cache is enabled, hydrate cached pickles before falling back to live Yahoo fetches; upload pickles to the cache on write.
- ThetaData EOD: enforce the provider's 365-day window limit per request and keep progress tracking consistent with chunked downloads.
- Backtesting router (IBKR futures/cont_future/crypto): prefetch full backtest window once per series and slice from memory to avoid per-iteration history fetches (major warm-cache speedup).
- Indicators: prevent
plot_indicators()hovertext generation from crashing whendetail_textis missing/NaN/NA (e.g., mixed indicator points with and withoutdetail_text).
- IBKR futures: automatic exchange resolution for futures and continuous futures (via downloader secdef search) with persisted root→exchange cache.
- IBKR futures: regression/unit coverage for exchange routing, per-call exchange overrides, and conid registry bulk updates.
- Backtesting router: accept
futures/cont_futuresroute-key aliases for convenience (maps tofuture/cont_future).
- IBKR futures: honor call-time
exchange=overrides consistently forget_historical_prices,get_last_price, andget_quote(live + backtesting), and include exchange in cache keys to avoid cross-exchange contamination. - IBKR futures conid registry: bulk-ingest
trsrv/futuresresponses and harden S3 persistence with merge-before-upload retry to avoid lost updates under concurrent backtests. - Continuous futures: add roll rules for COMEX micro gold (
MGC) and NYMEX crude oil (CL/MCL) and fix monthly roll selection to avoid hanging on already-rolled contract months.
Deploy marker: 174875a8 ("chore: start 4.4.37")
- Backtesting: support
timestep="hour"in pandas-backed history requests (Data.get_bars()), used by routed backtesting (e.g., IBKR futures/crypto). - ThetaData backtesting: proxy missing NDX underlying/index bars/quotes via scaled
QQQso NDX options strategies have a usable underlying series. - ThetaData (downloader): normalize v3 row-style and nested option-history payloads so option quotes/chains parse correctly and caches hydrate instead of looping.
- ThetaData: stop incorrectly scaling legitimate high strikes (e.g., NDX ~ 18,000) during chain-building; only de-scale clearly thousandths-encoded payloads.
- Backtesting progress: fix progress-bar throttling keying for non-terminal sinks (prevents intermittent missing output under test runners/log capture).
- Backtesting stats: fix
cagr()/volatility()crash during end-of-run stats generation when returns index uses non-nanosecond datetime dtypes (e.g.,datetime64[us]/datetime64[s]).
- IBKR futures backtesting: accelerate intraday resampling paths to avoid repeated per-iteration recomputation for timesteps like 5minute/15minute/30minute.
- ThetaData EOD: treat all-zero OHLC rows as missing placeholders to prevent one-day portfolio valuation cliffs.
- IBKR futures backtesting: cut downloader roundtrips by caching history windows across iterations and preferring native bar sizes (e.g., 15-min) when available.
- IBKR futures: add acceptance backtest strategy covering market/limit/stop/stop-limit/trailing/smart-limit and OCO/OTO/bracket semantics.
- IBKR futures: add parity/apitest helpers + scripts to compare IBKR runs against stored DataBento artifact baselines.
- Docs: add IBKR futures backtesting notes and DataBento parity guidance.
- IBKR futures backtesting: interpret
get_last_price(dt)as the last completed bar close (avoid lookahead bias). - Continuous futures (IBKR): stitch rolled segments with a 1-minute overlap and deterministic de-duplication.
- US futures gap handling (IBKR): replace flaky calendar logic with a simple rule-based “closed interval” detector to reduce repeated downloader fetches.
- SMART_LIMIT (live): avoid scanning full tracked order history in the background loop by using the broker’s active-order fast path, preventing high RSS growth in accounts with large historical order lists.
- Backtesting (router): make dataset lookup timestep-aware so minute requests don’t accidentally resolve to daily Data objects, and routed crypto assets passed as
(base, quote)work reliably. - Backtesting (router): refactor multi-provider routing to a provider registry + adapters (no hard-coded branching), add
alpaca/ccxtsupport, and allow CCXT exchange-id aliases likecoinbase/kraken(case/sep-insensitive). - IBKR (crypto): normalize daily timestep handling (
day/1d/1day) so crypto daily bars consistently use the derived-daily path. - ThetaData: prevent acceptance backtests from hitting the downloader queue by enforcing CI-only warm-cache guardrails consistently (local runs behave like GitHub CI).
- ThetaData: treat session close as “complete coverage” for index minute OHLC to avoid perpetual STALE→REFRESH loops when backtest end dates are represented as midnight.
- Backtest cache (S3): speed up warm-cache hydration by streaming small objects via
get_objectinstead ofdownload_filetransfer manager overhead.
- Runtime telemetry: lightweight memory/health JSON lines (
LUMIBOT_TELEMETRY ...) for diagnosing OOMs in long-running live workers. - Broker API smoke apitests: basic Alpaca and Tradier connectivity + order lifecycle checks (paper/live as available).
- Live (Tradier): treat
submitted/open/newas equivalent to reduce repeated NEW events under polling; bound live trade-event history to avoid unbounded memory growth in long-running workers. - Live (Tradier): avoid heavy DataFrame copy chains when cleaning orders; skip ingesting large historical closed order lists on the first poll to prevent startup memory spikes in accounts with long histories.
Deploy marker: d5c6b730 ("deploy 4.4.31")
- SMART_LIMIT: live matrix apitests + runner scripts; expanded unit coverage for edge cases.
- Investigations/docs: production endpoint breakdown notes and an expanded backtesting performance playbook.
- ThetaData: per-asset download progress reporting for option-chain strike scans (exposed via
download_status).
- Acceptance backtests now run in CI (no longer marked
apitest); baselines were refreshed for LEAPS + MELI; CI caps were raised for long full-year strategies due to runner variability. - CI policy: use pytest markers (not env vars) for opt-in/slow tests; some slow ThetaData backtest tests were made opt-in, then re-enabled once bounded.
- Backtests under pytest no longer auto-open HTML artifacts (plots/indicators/tearsheets) in a browser.
- Strategy collaboration workflow: clarified “shared version branch” conventions.
- ThetaData: reduced option-chain fanout and improved warm-cache parity (reuse chain cache under constraints; prefetch strikes only for head+tail expirations when unconstrained; bounded intraday chain defaults).
- ThetaData: improved intraday cache coverage and corrected daily option MTM behavior.
- Polygon: reduced split-cache rate limit thrash.
- SMART_LIMIT: hardened behavior for quote/stream failures.
- Backtesting progress: improved per-asset
download_statusfor clearer “what is downloading” diagnostics.
⚠️ Removed ThetaData chain default-horizon env vars (THETADATA_CHAIN_DEFAULT_MAX_DAYS_OUT*). Chain default horizons are now fixed and covered by tests.- Removed the short-lived
LUMIBOT_DISABLE_UIenv var (useSHOW_PLOT/SHOW_INDICATORS/SHOW_TEARSHEET+ pytest non-interactive behavior instead).
Version bump marker: 76b31467 ("Docs/tests: normalize artifacts + bump version")
- Backtesting performance playbook and production/local parity notes.
LUMIBOT_DISABLE_DOTENVto disable recursive.envscanning in prod-like runs.
- ThetaData: filtered intraday parquet loads to reduce memory footprint; daily option MTM fixes.
Deploy marker: b8c6a839 ("deploy 4.4.29")
- Prevent production backtests from OOM-like hard exits (
ERROR_CODE_CRASH) when refreshing multi-year intraday ThetaData caches by avoiding deep copies during cache load/write and trimming non-option intraday frames in-memory.
- Production backtest runner script (
scripts/run_backtest_prod.py) plus investigation docs for NVDA/SPX accuracy, parity, and startup latency.
- ThetaData missing-day detection for intraday caches across UTC midnights (prevents “every other trading day missing” forward-fill storms).
- Backtesting: improved intraday fills and cache end handling; deterministic drift ordering for rebalances.
- Reduced peak memory usage for ThetaData backtests and tear sheet generation to avoid OOM crashes in production.
- ThetaData: cache snapshot quotes per session and fetch full-session option quote snapshots to reduce downloader fanout.
- Clamp future backtest end dates instead of failing.
Deploy marker: b7f83088 ("Deploy 4.4.25")
- Public documentation page for environment variables (
docsrc/environment_variables.rst) plus engineering notes (docs/ENV_VARS.md). - Backtest audit telemetry can be preserved in a separate
*_trade_events.csvartifact (seeLUMIBOT_BACKTEST_AUDIT). - Investigation docs for ThetaData corporate actions and performance.
- ThetaData option chain defaults are now bounded to reduce cold-cache request fanout (configurable via
THETADATA_CHAIN_DEFAULT_MAX_DAYS_OUT*).
- OptionsHelper delta-to-strike selection fast path to prevent per-strike quote storms (SPX Copy2/Copy3 slowness).
- Prevent backtest tear sheet generation from crashing on degenerate/flat returns (NVDA end-of-run failures).
- Reduce ThetaData corporate action request thrash via memoization/negative caching.
- Normalize ThetaData intraday bars for corporate actions in backtests so option strikes and underlying prices stay in the same split-adjusted space (NVDA split issues).
- Improve ThetaData snapshot quote selection near the session open to avoid missing NBBO due to end-of-minute timestamps.
- Fixed ThetaData EOD corrections by fetching a real 09:30–09:31 minute window for each trading day, preventing zero-length requests and the resulting terminal hangs.
- Logged the active downloader base URL whenever remote mode is enabled to make it obvious in backtest logs which data path is being used.
- Added regression tests covering the custom session window override plus the fallback path when Theta rejects an invalid minute range.