Autonomous quantitative strategy research for Chinese A-shares. An AI agent modifies a single strategy file, runs backtests on a held-out training window, and iteratively discovers profitable trading signals — inspired by karpathy/autoresearch.
⚠️ Methodology warnings before you start. Read Known Biases & Limitations before drawing any conclusions from a backtest result. The most important one — survivorship bias — is structural to the data source and not fixable inside this repo.
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ program.md │────▶│ AI Agent │────▶│ strategy.py │
│ (human goal)│ │ (autonomous) │ │ (editable) │
└─────────────┘ └──────┬───────┘ └──────┬──────┘
│ │
│ run backtest │
▼ ▼
┌────────────────────┐ ┌─────────────────┐
│ results/ │◀─│ backtest.py │
│ best_train.json │ │ (fixed) │
│ best_val.json │ │ train window │
│ test_final_*.json │ │ by default │
└────────────────────┘ └─────────────────┘
- You write
program.mdwith research goals and constraints. - Agent reads it, modifies
strategy.py, runspython backtest.py(= train split). - Backtest evaluates under realistic A-share rules (T+1, price limits, costs).
- Result compared against
best_train.json— if better, promoted; if worse, discarded. - Every 5 iterations the agent runs
--split valas a guardrail; if train improves but val doesn't, the agent backs off. - At the end of a research phase,
--split testwrites a timestamped held-out report. Editingstrategy.pybased on that number invalidates it.
# 1. Install dependencies
uv sync
# 2. Download data (~25-40 min first time, ~5,200 stocks × 12 years).
# Fetches OHLCV from Tencent then runs feature engineering.
python prepare.py
# 3. Incremental update later (only the days since last fetch)
python prepare.py --update
# 4. Run backtest on the train window (2017-01 → 2022-12)
python backtest.py
# 5. Sanity check on validation (2023-01 → 2024-06)
python backtest.py --split val
# 6. Diagnostic: all three splits side-by-side (does NOT promote any best file)
python backtest.py --split all
# 7. Final evaluation on the held-out test set (~2y)
python backtest.py --split testBy design, there is no driver script. The "agent" is whichever coding
agent you spin up inside the repo — Claude Code, Cursor, Codex CLI, an
SDK harness, etc. The entire orchestration lives in program.md, which
the agent reads as its operating manual (setup → loop → logging → stop
conditions). This mirrors the karpathy/autoresearch
design choice: keep the surface area minimal, put the loop in Markdown.
Start a session by opening a coding agent in this directory and saying something like:
"Have a look at
program.mdand let's kick off a new experiment. Do the setup first."
The agent will create an autoresearch/<tag> branch, read the in-scope
files, verify the data is prepared, and then LOOP FOREVER until you
manually stop it — committing each experiment, recording results to a
local results.tsv, reverting failures, and checking val every 5
iterations as a guardrail.
A typical overnight session at ~1 min/backtest yields ~500 experiments. You wake up to a branch with hundreds of commits and the TSV.
| File | Who Touches It | Purpose |
|---|---|---|
program.md |
Human | Research direction, constraints, split discipline |
strategy.py |
Agent | Trading signal generation logic — the only file the agent modifies |
prepare.py |
Fixed | Data pipeline: fetch → features → cache |
backtest.py |
Fixed | A-share backtesting engine + split runner |
results/research_log.md |
Human | Phase-by-phase notes on data infra, bugs, methodology |
- Source (active): Tencent
web.ifzq.gtimg.cnfor daily HFQ klines (paginated for the 640-row cap); akshare for the A-share code list (with Sina spot fallback when SZSE rate-limits). - Adjustment: HFQ (后复权), not QFQ. HFQ guarantees non-negative prices and produces a true total-return series. See
results/research_log.mdPhase 4 for the reasoning. - Frequency: Daily OHLCV + ~50 derived technical features.
- Coverage: A-shares currently listed, 2014–present (~5,200 stocks, ~11M rows). North Exchange
920xxxcodes are not in Tencent and are skipped.
This is the most important table in the repo: your source caps which strategies you can ever evaluate. All numbers verified against akshare 1.18+ in May 2026.
| Source | OHLCV | hfq/qfq | real amount |
turnover_rate |
outstanding_share |
PE/PB history | BJ 920xxx | Throughput | Thread-safe | Reliability |
|---|---|---|---|---|---|---|---|---|---|---|
| Tencent fqkline (active) | ✅ | ✅ both | ❌ approx | ❌ | ❌ | ❌ | ❌ | ~5 stocks/s | ✅ | ★★★★★ |
Sina (stock_zh_a_daily) |
✅ | ✅ both | ✅ | ✅ | ✅ | ❌ | ✅ (via spot list) | ~0.3 stocks/s | ❌ (mini-racer) | ★★★★ |
Eastmoney (stock_zh_a_hist) |
✅ | ✅ both | ✅ | ✅ | ❌ | sep. endpoint | ✅ | medium | ✅ | ★ (blocked from many CN IPs) |
| Tushare Pro (paid) | ✅ | ✅ both | ✅ | ✅ | ✅ | ✅ | ✅ | high | ✅ | paid / point cap |
| If your strategy needs… | Minimum source |
|---|---|
| Any pure-technical signal (MA, RSI, MACD, Bollinger, ATR, KDJ, momentum, volatility) | Tencent (default) |
Cross-section liquidity (real Amihud illiq), retail-vs-institutional turnover |
Sina (+ ProcessPool + ~70 min fetch) |
| Real market cap (size factor, size-neutralization) | Sina (close_raw × outstanding_share) |
| Historical PE / PB / market cap as time series | Tushare Pro (paid) — or skip |
| Point-in-time delisted universe (survivorship-bias fix) | Tushare Pro / Wind / CSMAR — no free option |
| BJ 920xxx coverage | Sina or Eastmoney (Tencent doesn't serve them) |
Tencent gives us the fastest, most reliable feed for everything that depends only on OHLCV — and that's the entire technical-analysis feature set (~45 of the ~50 features in the pipeline). Switching to Sina to unlock 4–5 more features costs ~3x fetch time and a process-pool workaround; only worth it once you have a strategy hypothesis that specifically needs them.
The switch is mechanical: drop in the _fetch_single_stock_sina template in prepare.py and swap ThreadPoolExecutor → ProcessPoolExecutor(max_workers=4).
Realistic A-share market simulation:
- T+1 settlement (signal at day-T close → trade at day-(T+1) open).
- Price-limit filtering: 10 % main board, 20 % ChiNext/STAR, 30 % BJ.
- Stamp tax 0.05 % (sell), commission 0.025 % (both sides), slippage 0.01 %.
- Suspension detection (volume = 0), liquidity filters, position-vaporization guard.
- Metrics: Sharpe, Calmar, MaxDD, Win Rate, VaR, daily/annual turnover.
Single-holdout split, enforced by --split:
| Split | Window | Purpose |
|---|---|---|
| warmup | 2014-01 → 2016-12 (3y) | Features only; no trading. Lookback for 252-day indicators. |
| train | 2017-01 → 2022-12 (6y) | Agent iterates here. best_train.json tracks improvements. |
| val | 2023-01 → 2024-06 (1.5y) | Overfitting guardrail. Auto-promotes in best_val.json. |
| test | 2024-07 → end of data (~2y) | Sacred. Each run writes test_final_<timestamp>.json; never auto-promoted. |
--split all runs all three side-by-side without touching any best file.
Read this section before you (or your agent) celebrate any backtest number.
Tencent's endpoint only serves stocks that are currently listed. Stocks that have delisted (退市) — failures, fraud blowups, *ST →退市 chains — are completely absent from the historical universe. Every backtest in this repo implicitly assumes you could have known in 2017 that today's 5,200 stocks would survive to 2026.
Empirically this lifts annual returns by roughly 2-4 % p.a. for naive long strategies and exaggerates Sharpe by 0.2-0.4 versus a delisting- inclusive universe. The direction of the bias is always favorable, and the agent loop has no way to detect it from within the data.
Mitigations (not implemented here):
- Source a "point-in-time universe" with delisted tickers from Wind / Choice / Tushare Pro / CSMAR. Re-run prepare.py against that universe.
- Be explicit in your hypothesis: "this strategy is being judged on the surviving universe and will likely overstate live PnL."
ST stocks have a 5 % daily price limit (not 10 %) and many institutions forbid trading them. The backtest's price-limit logic uses code prefix only and treats ST stocks as ordinary main-board names. If your strategy disproportionately picks ST tickers in the train window, the numbers are optimistic.
Because the pipeline approximates amount ≈ close × volume and close is
HFQ-adjusted, amount scales with the cumulative dividend factor. Any
feature derived from it (illiq, amount_ratio, etc.) is per-stock
relative only — a heavy-dividend old stock looks artificially liquid
versus a young one in cross-section comparisons.
PE, PB, market cap, turnover_rate — and anything derived from them
— are absent. Eastmoney (the only free source) blocks frequent IPs;
shipping stale fundamentals is worse than shipping none. Value/quality
strategies require an external feed.
Tencent does not serve them. The pipeline reports them as permanently failed (~316 codes). Strategies that need full A-share coverage need a secondary feed for these names.
Trading days are inferred from the union of dates that have at least one row across the universe. There is no explicit holiday calendar. Single- session vendor outages would silently look like a market holiday.
Execution is modeled at the next day's open with a flat 1 bp slippage, 0.025 % commission, and 0.05 % stamp tax. There is no participation model, no market impact, no open-call queuing. High-turnover strategies will likely underperform their backtest in live trading by more than the explicit costs alone.
MIT — use it, fork it, build on it. Let the community find alpha.