Lumibot Spike — Reproducing One autoinv Strategy in Lumibot's Backtest to Greenlight Framework Adoption
The question
Spike: rewrite one autoinv strategy (the buy-and-hold benchmark or a small mechanical rule) as a Lumibot Strategy subclass, run it through Lumibot's backtest, and compare results to autoinv's Backtest engine. If equivalent within noise, greenlight the framework adoption. (Context: validation gate for the build-vs-buy recommendation made in the 2026-05-22 library-comparison brief; estimated 1-2 hour spike; unblocks 4-8 weeks of avoided execution-plumbing work.)
What we already know (from the vault)
- The build-vs-buy recommendation already exists and is conditional on exactly this spike. [[2026-05-22-alpaca-paper-trading-harness-library-comparison]] recommends adopting Lumibot's
Strategy+Broker+Traderabstractions while keeping autoinv's bias-audit / permutation-test / Brier discipline layer, and explicitly lists this spike as the open follow-up that greenlights the swap. "Migration cost estimate: 1-2 sessions." - autoinv is package-shaped and the adoption decision is reversible. [[01-projects/automated-investing/index]] — the
autoinvevent-driven spine lives in the private repoRayDataCo/automated-investing(engine, data, validation, metrics, portfolio, pricing) plus Phase-1 additions:guard.py(fail-closed no-lookahead guard),backtest.py,report.py,feed.py,strategies/momentum.py, and a 58-test suite. There is an existing momentum strategy and an honest result (SPY MA100 underperformed buy-and-hold) to use as the comparison fixture. - autoinv's engine is modeled on the Halls-Moore event-driven backtester. [[2026-04-10-halls-moore-algo-trading]] — Ch. 13 event-driven architecture; the no-lookahead guard is independently verified (incremental causal replay + multi-truncation + future-tail perturbation). This is the fill/bar-timing model Lumibot must be reconciled against.
- The strategy-vs-execution boundary is load-bearing and must survive adoption. The comparison brief notes founder authorizes via /decisions/ and Ray executes against locked parameters ([[feedback_paper_trade_deploy_authorization]]); any library that auto-routes orders without an explicit authorization gate is disqualified — so the spike is backtest-only, no live broker.
- autoinv already runs custom yfinance-backed backtests with explicit drivers. [[2026-05-17-memory-cycle-v1-walk-forward]] and the power-cycle reruns establish the existing harness baseline (yfinance daily bars, custom Python driver) the spike must match.
What the web says
- Lumibot strategies are
Strategysubclasses driven by lifecycle hooks:initialize()(setself.sleeptime, parameters),on_trading_iteration()(the main loop body, re-run everysleeptime), plusbefore_market_opens/after_market_closesand fill hooks.self.sleeptime = "1D"gives daily iteration. (Lumibot lifecycle docs) - The backtest fill model is bar-based and forward-filling:
process_pending_orders()"evaluate[s] the open orders at the beginning of every new bar." Market orders fill at the open of the bar following submission; limit/stop orders are evaluated against the bar's OHLC range. (Lumibot BacktestingBroker docs) - Data sources are pluggable: Yahoo Finance (free, daily, "suitable for longer-term strategies… not ideal for intraday"), Polygon.io (intraday + EOD), ThetaData (recommended, SIP-quality), DataBento, Alpaca, and Pandas/CSV. Bar granularity is set via
timestep(daily or minute). (Lumibot how-to-backtest) - Fees are explicit and configurable via the
TradingFeeentity;calculate_trade_cost()applies strategy-specific fees per order. Slippage is referenced ("Trading Slippage", "Smart Limit Config") but defaults are not documented — must be verified empirically in the spike. (Lumibot backtesting overview) - Backtest output is a tearsheet HTML (equity curve, Sharpe, drawdown, benchmark compare) plus a trades file (asset, qty, price, timestamp) and an indicators file. These are per-run file artifacts, not a persistent cross-run registry — the registry remains autoinv-specific scaffolding. (Lumibot how-to-backtest)
- Lumibot is positioned as "backtestable AI trading agents" with the same
Strategyclass running across backtest / paper / live by config flag — the backtest-to-live parity that the comparison brief flagged as the core reason to adopt. (Lumibot GitHub)
Convergences and contradictions
- Convergence: Lumibot's actual docs confirm the comparison brief's central claim —
Strategy+ lifecycle hooks +BacktestingBroker+ configurableTradingFeemap cleanly onto what autoinv would otherwise build asBrokerageExecutionHandler+ fills reconciliation. The buy-and-hold and momentum fixtures both express trivially ason_trading_iterationbodies. - Contradiction (the real risk): the comparison brief treated "equivalent within noise" as a formality; the docs reveal a concrete fill-convention mismatch. Lumibot fills market orders at the next bar's open; autoinv's Halls-Moore-derived engine and its no-lookahead guard may fill at the same-bar close or next-bar open depending on how
backtest.pyis wired. On daily bars this one-bar offset plus any open-vs-close choice is the single most likely source of a non-noise return delta — and it is a real modeling difference, not a bug, so "within noise" must be defined to tolerate it or the comparison must align conventions first. - Convergence (cost discipline): docs confirm Yahoo daily backtesting is free and matches autoinv's existing yfinance daily baseline, so the spike needs no paid data tier — consistent with the brief's no-paid-software-without-evaluation note and the license follow-up.
Synthesis for RDCO
Port the buy-and-hold benchmark first, not the momentum rule. Buy-and-hold has one fill event at the start and one mark-to-market at the end, so it isolates the two variables that actually matter for equivalence — data alignment and the entry fill price — without entangling them in signal-timing logic. The momentum rule (SPY MA100) is the better second fixture precisely because it generates many fills and will surface the next-bar-open offset; run it only after buy-and-hold passes, as the stress test. Use the same single liquid ticker (SPY), the same explicit date window, the same yfinance/Yahoo daily bars on both engines, fees set to zero on both, and slippage forced to zero on both. Hold all four knobs fixed so the only residual difference is each engine's internal fill-and-mark convention.
Define "equivalent within noise" operationally before running, not after. Three nested checks, in order of strictness: (1) final-return delta — absolute difference in total return over the window ≤ 25 bps for buy-and-hold and ≤ 50 bps for the momentum rule (buy-and-hold should be near-exact since it is one round-trip; a large delta there means a data-alignment bug, not noise); (2) equity-curve correlation — Pearson r ≥ 0.999 on the daily equity series after aligning timestamps, which catches drift that a single endpoint comparison hides; (3) trade-by-trade reconciliation for the momentum rule — same number of fills, same fill dates, fill prices matching within one bar's open-vs-close gap. Passing (1) and (2) on buy-and-hold plus (1) and (3) on momentum is the greenlight. Note explicitly: a known, explainable constant offset from the next-bar-open fill convention is not a fail — it is a convention difference to document and standardize on (Lumibot's is the more conservative, live-realistic choice), whereas an unexplained or drifting delta is a fail.
The top three places equivalence breaks, in priority order: (1) Fill timing — Lumibot's next-bar-open market fill vs autoinv's convention; this is the headline risk and the reason buy-and-hold goes first. (2) Bar/timestamp alignment — yfinance adjusted-close vs raw-close, dividend/split handling, and the exact first/last bar each engine includes in the window; an off-by-one bar at either boundary masquerades as a return delta. (3) Implicit costs — Lumibot's undocumented default slippage and any minimum-fee assumption must be forced to zero, or autoinv's frictionless benchmark will look like it "beats" Lumibot purely on costs. Cash handling (fractional vs whole shares, starting-cash defaults) is a fourth, lower-priority watch — pin starting cash and share-rounding identically on both.
Go/no-go framing for build-vs-buy. GREENLIGHT adoption if buy-and-hold reconciles near-exactly and the momentum rule's residual delta is fully attributable to the documented next-bar-open fill convention (i.e. the engines agree once conventions are aligned). NO-GO / escalate if there is an unexplained return or equity-curve divergence that survives zeroing fees and slippage and aligning data — that would mean Lumibot's backtest semantics are opaque or subtly different in a way that undermines the backtest-to-live parity that is the entire reason to adopt it. A middle outcome (equivalent only after convention alignment) is still a GREENLIGHT but with a required deliverable: a short "convention map" note documenting that autoinv standardizes on Lumibot's fill timing going forward, so historical autoinv backtests are re-run before they are trusted. This spike is genuinely 1-2 hours of work gating a 4-8 week plumbing decision — a high-leverage validation gate; the failure cost of a false greenlight is low (decision is reversible, autoinv is small) but the failure cost of a silent semantic mismatch is high (every future paper-trade inherits it), which is why the equity-curve correlation and trade-by-trade checks are non-negotiable, not just the headline return delta.
Open follow-ups
- What is Lumibot's actual default slippage on a Yahoo daily backtest with no
TradingFeeconfigured — zero, or a hidden bps assumption? (Empirically measure in the spike; it determines check #3.) - Does autoinv's
backtest.pyfill at same-bar close or next-bar open, and does theguard.pyno-lookahead guard already enforce next-bar execution? (Read the repo before the spike to pre-align conventions.) - Does Lumibot read Alpaca/Yahoo credentials from environment variables (compatible with the 1Password
alpaca-env-paper.shwrapper) or require a config file on disk (conflicts with no-secrets-on-disk discipline)? - Lumibot license + Lumiwealth commercial-tier check — is any broker/data integration gated behind a paid tier, and is the OSS license permissive for personal-license RDCO use?
- After greenlight: does the autoinv strategy-candidate handoff schema become a Lumibot
Strategysubclass + YAML + discipline-gate spec, as the comparison brief's Implication 2 proposed?
Related
- [[2026-05-22-alpaca-paper-trading-harness-library-comparison]]
- [[01-projects/automated-investing/index]]
- [[2026-04-10-halls-moore-algo-trading]]
- [[2026-05-17-memory-cycle-v1-walk-forward]]
- [[project_investing_markov_capital_cycle]]
- [[feedback_paper_trade_deploy_authorization]]
Sources
- Vault:
06-reference/research/2026-05-22-alpaca-paper-trading-harness-library-comparison.md— the build-vs-buy recommendation this spike validates - Vault:
01-projects/automated-investing/index.md— repo location, engine spine, existing momentum + buy-and-hold fixtures - Vault:
06-reference/2026-04-10-halls-moore-algo-trading.md— event-driven engine architecture autoinv is modeled on - Vault:
01-projects/investing/backtests/2026-05-17-memory-cycle-v1-walk-forward.md— existing yfinance daily backtest baseline - Lumibot lifecycle / on_trading_iteration: https://lumibot.lumiwealth.com/lifecycle_methods.on_trading_iteration.html
- Lumibot BacktestingBroker (fill model, fees): https://lumibot.lumiwealth.com/lumibot.backtesting.html
- Lumibot how-to-backtest (data sources, output): https://lumibot.lumiwealth.com/backtesting.how_to_backtest.html
- Lumibot GitHub (backtest-to-live parity): https://github.com/Lumiwealth/lumibot