量化交易研究与认证平台

YATS 是一个量化交易研究平台,提供从多源数据接入、特征计算、Walk-Forward 优化到 Deflated Sharpe Ratio 认证的全流程回测与评估,适用于量化研究员和机器学习工程师对交易策略进行严格验证。

  • by skyliquid22
  • Python
  • 本地部署
  • 2 星标
  • 更新于
在 GitHub 查看 skyliquid22/yats

使用说明

YATS — Yet Another Trading System

YATS is a trading research platform whose referee keeps saying no. It runs the full loop (multi-vendor ingestion, canonical data plane, deterministic features, walk-forward training and evaluation, shadow replay, paper execution), but the part it takes most seriously is the certification gate: honest next-bar execution, purged walk-forward optimization, and a Deflated Sharpe Ratio test with a public trial clock that counts every configuration ever tried against every new result. After 63 logged trials across PPO, supervised, feature-ablation, and risk-overlay sweeps, nothing has crossed the bar yet. The negative results are published in-repo, because a platform that can't say "no signal" rigorously can't be trusted when it eventually says "yes."

Who this is for

  • Quant researchers who want their backtests distrusted by default: every new result deflated against every trial ever burned.
  • ML people who want a leak-proof evaluation harness: purged anchored folds, lookback-aware purge buffers, next-bar fills.
  • Anyone who wants to watch a Deflated Sharpe referee reject 63 straight attempts in public, with receipts.
  • Not for anyone seeking turnkey profits: the best certified alpha here is none, and that is the point.

Quickstart (no API keys needed)

git clone <this repo> && cd yats
make demo

make demo replays the bundled demo/ dataset through the research harness (feature computation, a small purged walk-forward sweep, and a Deflated Sharpe verdict) with no vendor credentials required. See Setup with real data to plug in live vendors.

The demo install is deliberately lean: it does not pull the RL stack (torch, stable-baselines3, gymnasium live in an optional rl extra). Demo install is about 800 MB; the full research stack (uv sync --extra rl, CPU-pinned torch) is about 1.5 GB.

Run the test suite (no services required; live-DB tests skip automatically, and the RL trainer tests need the rl extra):

PYTHONPATH=.:pipelines uv run --extra rl --with pytest --with pytest-timeout \
  pytest tests -q --timeout=120 -k "not live"

1,716 tests collected; 1,684 run without live vendors/DB.

Architecture

flowchart LR
    subgraph vendors [Vendors]
        A[Alpaca<br/>OHLCV + trading]
        T[ThetaData<br/>options]
        F[financialdatasets.ai<br/>fundamentals, 13F]
    end

    vendors --> I[Ingest<br/>Dagster jobs]
    I --> R[raw_* tables<br/>append-only,<br/>vendor-shaped]
    R --> C[(canonical_*<br/>point-in-time, DEDUP-<br/>idempotent, lineage)]
    C --> FE[Features<br/>deterministic registry,<br/>lookback accounting]
    FE --> E[Experiments<br/>spec-hashed<br/>content-addressed IDs]
    E --> H[WFO / DSR harness<br/>purged folds,<br/>Deflated Sharpe + trial clock]
    H --> Q[Qualification gates<br/>hard + soft]
    Q --> P[Promotion tiers<br/>research → candidate<br/>→ production]
    P --> S[Shadow replay<br/>→ paper trading]

    M[MCP server<br/>72 tools, TypeScript] -.-> C
    M -.-> H
    D[Ops dashboard<br/>read-only FastAPI] -.-> C
  • Raw → canonical: every vendor write is append-only raw; only reconciled canonical tables (with lineage) feed anything downstream.
  • Honest execution: signals computed on bar t fill at bar t+1 (fill_timing: next_close or next_open); no same-bar fills.
  • Purged WFO: anchored expanding folds with purge = label horizon plus a feature-memory buffer, so no training fold sees leaked future data.
  • Deflated Sharpe with a trial clock: every config in every sweep increments a cumulative trial count; DSR deflates each new result against the whole history. Current clock: 63 trials (docs/research/full_span_verdicts.md).
  • MCP-native: all capabilities are exposed as MCP tools callable by agents or notebooks; a read-only dashboard sits alongside.

Anatomy of an experiment

Every run is an ExperimentSpec (research/experiments/spec.py), frozen and content-addressed: the SHA256 of its canonical JSON is the experiment_id, so changing any field is a new trial on the clock.

experiment_name: alpha1_lgbm_21d
symbols: [AMD, NFLX]          # sorted + deduped, so ordering can't fork the hash
interval: daily
feature_set: core_v1          # registry-resolved; its max lookback sizes the purge
policy: ppo                   # equal_weight | sma | ppo | sac[_*] | hierarchical
cost_config: {transaction_cost_bp: 5.0, slippage_bp: 0.0}
seed: 42                      # one seed, deterministic run
wfo_config:
  n_periods: 4
  train_window: 504           # anchored mode: window expands, never drops history
  label_horizon: 1            # bars purged at every train/test boundary
  purge_buffer: null          # null = auto from the feature set's max lookback
execution_lag_days: 1         # decide on bar t, fill on bar t+1; same-bar fills (0)
fill_timing: next_close       # are legacy-only and warn loudly: a close-time signal
                              # filled at that same close is lookahead, not execution

Results (the honest table)

Best certified alpha: none. The certification bar is DSR ≥ 0.95 after deflation over all trials ever burned.

Sweep Question Best result Verdict
2d: first WFO sweep Real alpha in the signal set with PPO? DSR 0.645 No
3d: insider/institutional A/B Do insider features help? Enriched arm uniformly worse; DSR 0.835 No
4b: regime_v2 3-arm Do market-implied regime features help? Worse overall, better final fold; DSR 0.770 No (extractor is the bottleneck)
ALPHA-1: supervised track Ridge/LightGBM vs PPO? Sharpe 1.68 on the short span; DSR 0.885 No
Full-span re-verdicts, honest execution Do results survive 6.5 years + next-bar fills? lgbm_21d: OOS Sharpe 0.905, DSR 0.943 No
Vol-targeting overlay on lgbm_21d, trial 63 Does the ALPHA-3 risk layer lift DSR? OOS Sharpe 1.134, strict DSR 0.921 vs the 0.95 bar Closest yet, still no

Trial clock at last verdict: 63 trials burned. Nothing is certified; the closest attempt sits at DSR 0.92 against the 0.95 bar. On trial 63 a naive 3-trial benchmark printed DSR 0.988, and was rejected in favor of the strict computation deflated over all 63 trials, which is exactly the self-flattering shortcut the public trial clock exists to prevent (see the addendum in full_span_verdicts.md).

An earlier era of Sharpe ≈ 2.17 results on a 2-year window died under span extension and honest fills; that post-mortem is in the full-span verdicts doc. The docs above are kept as a lab notebook: dated, immutable, and linked from every claim.

Raw sweep outputs live in docs/research/receipts/. Honest scope of the reproduction claim: re-running these sweeps requires vendor API keys and a populated database (see docs/ingestion.md); results will drift on vendor restatements, and no input-data snapshot is distributed. The receipts are the record of what was observed, not a bit-reproducible artifact. The make demo verdict, by contrast, is fully deterministic.

Setup with real data

Full vendor setup, the one-command symbol backfill, and the raw → canonical pipeline are documented in docs/ingestion.md.

cp .env.example .env       # fill in vendor keys (Alpaca, ThetaData, financialdatasets.ai)
docker compose up -d       # QuestDB
python scripts/bootstrap_db.py
PYTHONPATH=.:pipelines uv run python -m yats_pipelines.backfill \
    --symbols NFLX,AMD --start 2020-01-01

Reference material:

Troubleshooting

  • pytest hangs or deadlocks on macOS: set OMP_NUM_THREADS=1 (make test already does): torch and lightgbm each bundle their own libomp, and the duplicate OpenMP runtimes deadlock in torch's QR init.
  • live tests fail: they need a running Theta terminal / live DB; run with -k "not live" as in the Quickstart.
  • Connection refused on real-data paths: QuestDB must be up: docker compose up -d. make demo needs no services at all.
  • MCP tool calls clamped to intern: elevated roles must be operator-allowlisted via YATS_ALLOWED_ROLES (comma-separated role names, or *); anything else falls back to least privilege.
  • financialdatasets.ai data looks truncated or wrong: known vendor limits (insider trades cap at 500 rows/ticker, 13F value_usd unreliable before 2025-06, ETFs have no insider/fundamentals data) are documented in docs/ingestion.md.
  • npm test (MCP server) misbehaves: use Node ≥ 22, the version CI pins.

Roadmap (not yet built)

None of the following exists today; no dates are promised.

  • v1.1 (external LLM policies over MCP): let external agents propose allocations through the MCP tool surface, subject to the same risk contract and certification gate as any other policy.
  • Universe expansion: beyond the dev10 development universe toward the full S&P 500 list already in configs/universes/.
  • Live trading: the execution layer and kill switches exist and are paper-tested; live capital waits on a certified policy, i.e. on the referee finally saying yes.

Contributing

See CONTRIBUTING.md for dev setup, test commands, and the no-lookahead discipline every PR is held to.

License

MIT, copyright YATS contributors.