Skip to content

feat: add guarded vn.py simulated market data integration#118

Merged
FlyM1ss merged 17 commits into
mainfrom
feat/vnpy-simulated-market-data
Jul 16, 2026
Merged

feat: add guarded vn.py simulated market data integration#118
FlyM1ss merged 17 commits into
mainfrom
feat/vnpy-simulated-market-data

Conversation

@MrParamecium

Copy link
Copy Markdown
Collaborator

Summary

  • add a MarketDataProvider boundary with Alpaca as the unchanged default and an opt-in vn.py simulation provider
  • adapt real vn.py BarData objects into validated OHLCV frames and run deterministic DJIA-30 backtests offline
  • wire source selection through CLI, engine, API, run metadata, and dashboard with feature gating and explicit provenance
  • keep baselines offline when bars are supplied, force LLM off for simulation runs, and document the Loop 1 workflow

Scope

This PR covers simulated market data only. It does not connect TWS, IB Gateway, vnpy_ib, live quotes, or real order execution.

Safety

  • Alpaca remains the default when data_source is omitted
  • vn.py is an optional dependency in requirements-vnpy.txt
  • ENABLE_VNPY_SIMULATION=true is required to expose and use simulation
  • simulation errors do not silently fall back to Alpaca
  • every generated run records its data source

Test plan

  • python -m pytest dashboard/backend/tests/ -q (1032 passed, 4 skipped)
  • node --check dashboard/frontend/app.js
  • focused vn.py, API, and frontend tests (122 passed)

@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentic-trading-lab Ready Ready Preview, Comment Jul 16, 2026 2:33am

CI installs requirements.txt only, so vnpy is absent there. Three test
modules imported it at module scope, which raises during *collection* and
aborts the whole pytest session -- 0 of ~1030 tests ran, and because the
Render deploy hook gates on backend tests passing on main, that also blocks
prod deploys.

Gate them the way the optional discord dep already is (importorskip), and
skip only the two engine cases that build a provider so the argparse --help
contract test keeps running where vnpy is absent -- that CLI surface ships
in a vnpy-less deploy, so it should be verified in one.

test_enabled_simulation_is_passed_to_background_runner had the same latent
assumption: it asserted 200 while the endpoint correctly returns 503 when
find_spec("vnpy") is None, so it only passed on a machine with vnpy
installed. Substitute validate_market_data_source (real allow-list + feature
gate, minus the dependency probe) so the pass-through wiring is verified on
CI too; the probe itself stays covered by the dedicated 503 test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@FlyM1ss FlyM1ss self-assigned this Jul 16, 2026
FlyM1ss and others added 3 commits July 16, 2026 10:25
Widening _llm_run_metadata() into a shared _run_metadata() for all three
runs kept the LLM-shaped keys in it. run_agent_backtest() runs first, so by
the time the baselines call it, use_llm / prompt_adaptations / pipeline are
populated -- a Buy & Hold row that made zero model calls was recording
llm_max_output_tokens plus a full copy of the agent's pipeline, duplicated
across three rows per backtest.

Split it: _run_metadata() is provenance only (baselines), and
_agent_run_metadata() adds the effective LLM/pipeline config on top.

The wiring guard needed retargeting too -- "metadata=self._run_metadata()"
still appears in engine.py because the baselines call it, so the old grep
would have kept passing while no longer guarding the agent run it names.

Also restore the blank line before run_agent_backtest (PEP 8 E301).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
backtest_status["data_source"] was set by the background runner and never
read by anything, and it was the one key the finally block didn't reset, so
it held the last run's source indefinitely. A run's provenance is already
durable in the agent_runs metadata column and returned by the run endpoints
and POST /backtest/run, so this was dead state, not a second source of truth.

Document ENABLE_VNPY_SIMULATION in .env.example next to the other env knobs,
and record the optional-dep test rule in CLAUDE.md so the next optional
dependency doesn't rediscover the collection-abort failure mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The source-text guards pinned exact CSS/JS formatting, so a reformat or a
quote-style change would turn them red with no behavior change. Match
declarations inside a brace-counted @media block, and identifiers inside
expressions, instead of literal substrings.

Also record what these cannot do: CSS text proves a rule exists, not that
anything renders. test_mobile_backtest_keeps_setup_controls_visible passed
while the setup panel stayed hidden from 901-1200px (a pre-existing
.left-panel display:none at max-width:1200px that the 900px rules never
reach), so the old name overstated it. Renamed and documented.

Split the "option is not hardcoded in markup" assertion into its own case --
it guards the feature gate, not the presence of controls.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@FlyM1ss

FlyM1ss commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Reviewed, and pushed fixes rather than sending it back — 4 commits, f96173c..7720fc4. Backend tests now green on CI: 1025 passed, 8 skipped (was 2 errors, 0 tests run).

Was blocking

CI was red, and had never run this PR's tests at all. Three modules import vnpy at module scope; that raises during collection, which doesn't fail one module — it aborts the session (Interrupted: 2 errors during collection, 0 of ~1030 tests ran). CI installs requirements.txt only. And since the Render deploy hook gates on backend tests passing on main, merging it red would have blocked prod deploys too.

Gated them the way the optional discord dep already is (importorskip), skipping only the two engine cases that build a provider — the argparse --help contract test keeps running where vn.py is absent, since that CLI surface ships in a vn.py-less deploy.

test_enabled_simulation_is_passed_to_background_runner had the same latent assumption: it asserted 200 while the endpoint correctly returns 503 when find_spec("vnpy") is None, so it only ever passed on a machine with vn.py installed.

Also fixed

  • Baseline rows carried the agent's LLM config. _run_metadata() was shared by all three runs, but run_agent_backtest() runs first — so by the time Buy & Hold and DJIA called it, they stamped llm_max_output_tokens plus a full copy of the agent's pipeline onto rows that made zero model calls. Split into _run_metadata() (provenance) and _agent_run_metadata(). The wiring guard needed retargeting too — metadata=self._run_metadata() still appears in the source, so the old grep would have kept passing while no longer guarding the agent run it names.
  • backtest_status["data_source"] was write-only and the one key finally never reset. Removed — provenance is already durable in agent_runs.metadata.
  • .env.example documents ENABLE_VNPY_SIMULATION; CLAUDE.md records the optional-dep test rule so the next one doesn't rediscover the collection-abort.
  • Frontend guards match structure instead of exact CSS/JS formatting (a reformat used to redden them).

Left for you

  1. 901–1200px: the setup panel — including the new Market Data selector — is hidden by a pre-existing .left-panel { display: none } at max-width: 1200px; the 900px rules never reach that band. Pre-existing, not introduced here, so I left it: fixing it means redesigning a laptop layout, which wants its own PR.
  2. Long windows go flat. _hourly_return switches regime at bar index 24/50/78, then emits ~±0.03%/hr noise — about 11 trading days of signal. /backtest/run defaults to 2026-01-01→2026-05-07 (~90 days), so most of that curve is a flat line. The doc's 2026-04-01→2026-04-23 is right; worth surfacing in the notice text so the default doesn't mislead.
  3. The integration guide is Chinese-only while the other docs/integrations/* are English — fine if deliberate.

Correction to my first read

I initially flagged 7754511 as unrelated scope creep. It isn't: the new control lives inside .left-panel, which is hidden below 1200px, so unhiding it was forced by the feature, not tacked on. My error.

The boundary itself is good work — three-tier gate mapping cleanly to 422/403/503, laziness proven in a subprocess rather than by reading, SHA-256 instead of hash() so determinism survives a restart, and an adapter validated against real BarData rather than a fixture built from its own field names.

@FlyM1ss

FlyM1ss commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Merged as 8d00abd. CI green on main, deploy hook fired, prod verified: /config/features{"vnpy_simulation_enabled": false} (200 proves the deploy carried this code; false proves the simulation isn't exposed where numbers are read as real).

The three follow-ups above are now tracked, since notes on a merged PR don't get re-read:

One correction to my note above: after bar 78 the return is noise * 2, so the flat tail is ±0.06%/hr, not ±0.03%. Zero-mean either way — the point stands, the number was wrong. #128 has it right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants