Repository navigation
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
The py3.11 CI job sat in 'Run tests' for 25+ minutes while py3.12 finished in six. A per-test timeout turns a silent hang into a named failure with the hung thread's stack so the root cause can be fixed; the CI job timeout stays as the backstop.
Security (P0): authorize on the ASGI scope path with root_path normalization instead of request.url.path. Starlette 0.40's BadHost parsing turned `Host: x/..` into `/../api/config`, which skipped the token/loopback gate while the router still matched `/api/config`; the raw-ASGI regression harness shows 200-with-data before and 403/401 after for every bad-Host variant, root_path deployment, encoded path, docs, static and the WS handshake. A new outermost RequestGuard validates Host (optional QUANT_ALLOWED_HOSTS), caps bodies at 10 MB, rate-limits compute endpoints per client, and runs before metrics/auth/CORS/routing. Token comparison is timing-safe; a latent 422 serialization 500 is fixed. Security (P1/P2): sweep/parallel-sweep/research windows are bounded; /api/core/store/* and audit scope by X-Tenant-Id with cross-tenant reads requiring loopback + explicit all_tenants; delete_position requires a tenant; HTML reports escape data and script JSON; retention archives neutralize CSV formulas; config-version redaction is pattern-based; WS accepts only the bearer subprotocol (query token removed, breaking); QMT passwords are rejected in request bodies; Tushare forces HTTPS; python-multipart and lightgbm are bumped to fixed versions. Corporate actions: providers expose a canonical split/cash-dividend feed (synthetic deterministic, Tushare dividend, Baostock query_dividend_data), DataPipeline exposes it, and the paper broker/ExecutionEngine/LiveEngine/ LiveRunner/dual-track PaperBroker apply actions once per (asset, ex_date) on the ex-date (split quantity/cost, dividend cash, T+1, persisted markers, stock-lot reseed); real brokers never apply locally. The web engine, CLI trade, daemon and ValidatedProvider are wired, and every path reports corporate_actions_applied. Testing: pytest-timeout (600s/test) turns hangs into stack dumps. All test directories pass individually (3,282 passed, 3 skipped); a single-process full run hit host memory pressure (180 MB free from other sessions), so CI runs the authoritative full suite.
- fastapi 0.128 -> 0.157.0, starlette 0.40 -> 1.9.0 (BadHost GHSA-86qp-5c8j-p5mr, StaticFiles UNC and form-parse DoS fixed upstream). The P0 middleware fix remains defense-in-depth. API/core/utils/cli suites pass on the new stack locally. - axios ^1.7 -> ^1.20.0 in the frontend; production build passes. echarts 6 is a major chart-API migration and stays scheduled separately. - mypy fixes surfaced by the new import graph: EventStore buffer Optional annotation, queue/handler-name annotations, bucket boundaries float, metrics dict value type, audit record Any, and an explicit EXCHANGE_TZ re-export on the core.store shim.
The test activated the process-wide RiskMonitor, whose persistence wrote a KILL switch into data/trading.db; every later live run restored it and blocked orders. Inject a fresh monitor backed by a temp store and route the module's default resolution to it. The existing polluted row is left for the operator to deactivate (no deletion without explicit approval).
…ls POST-only
RequestGuard now validates any browser Origin against the request Host
(scheme/host/port, default-port normalized) or QUANT_ALLOWED_ORIGINS before
routing/auth; an unlisted Origin gets 403 (WS close 1008), duplicate or
opaque origins are rejected, and requests without Origin (curl/server-to-
server) are unaffected. This closes the loopback-mode CSRF hole where a
malicious page could POST to 127.0.0.1.
/ws-quotes/{start,stop} and /l2/{start,stop} change server state, so they are
POST-only now; GET returns 405 and performs nothing (the /ws-quotes/{code}
catch-all needed an explicit GET stub). Frontend wrappers updated and the
production build passes. QUANT_ALLOWED_ORIGINS documented in .env.example,
including the Vite dev cross-origin flow and TLS-proxy scheme caveat.
Verified: tests/test_api 328 passed; ruff clean; frontend build passes.
Update the endpoint tables and Round 11 handover; QUANT_ALLOWED_ORIGINS is documented in .env.example.
…d uv.lock Verification found the security bumps were reversed: requirements pinned python-multipart 0.0.32 / lightgbm 4.6.0 but pyproject still had 0.0.6 / 4.5.0, and CI's `pip install -e .` (without --no-deps) downgraded them. pyproject now matches, CI installs both editable packages with --no-deps, and uv.lock is regenerated (starlette 1.9.0, urllib3 2.8.0, python-multipart 0.0.32). Add floors for certifi>=2026.10.19 (compromised-CA advisory) and urllib3>=2.8.0.
- One-sided date overrides are merged with the config before the 10-year span check (end_date=2100-12-31 alone used to expand the default 2021 start to 29,218 days); GET analysis/monitor endpoints join the per-client rate limit. - The token-less loopback bypass now also requires a loopback Host name (localhost/127.0.0.1/::1/testserver or an explicit allowlist entry), so a DNS-rebound page presenting Host=attacker.example from 127.0.0.1 gets 403 with no handler execution. Missing Host is rejected when QUANT_ALLOWED_HOSTS is configured. - Large request bodies (>64 KB) on protected routes are authenticated before buffering; unauthenticated clients no longer make the server read 10 MB. - /api/core/events filters audit.* payloads (use the tenant-filtered /api/core/audit); Prometheus method labels are allowlisted; QMT `server` is operator config (QMT_SERVER) and rejected in request bodies; webhook failure logs strip URL credentials; config redaction covers list-valued secrets; responses carry nosniff/DENY/referrer-policy headers. Verified: tests/test_api 341 passed; utils/risk/core/monitoring 773 passed; ruff clean.
The OMS kept every order and filled blotter entry for the process lifetime (the audit accepted 1,000 unthrottled orders) and /api/oms/* had no admission control. Completed orders are now pruned to 90% of MAX_ORDERS once the book exceeds 10k, active orders are never evicted, the blotter is trimmed on the same rule, and /api/oms/order, /api/oms/fill and /api/trading/order join the per-client token bucket.
- /api/core/store/stats reports db_name instead of the absolute path. - /api/portfolio/import is capped at 10,000 rows (a 100k-row CSV was accepted under the 10 MB body cap). - L2 GET endpoints no longer lazily start the provider (a drive-by <img> could start threads); they return 503 pointing at POST /api/l2/start. - WebSocket admission reserves its slot before the first await, so concurrent handshakes can no longer overshoot the 100-client cap. - Config redaction detects secret query parameters (token/api_key/etc.) and redacts them inside lists; HTML responses carry a CSP that allows only self plus the Google Fonts origins used by index.html.
Red-team follow-up:
- The token-less WebSocket branch now requires a loopback Host (or an
explicit QUANT_ALLOWED_HOSTS entry), matching the HTTP guard; a
DNS-rebound page from 127.0.0.1 presenting attacker.example is closed 1008
before accept (regression tests for both cases).
- Portfolio import requires exactly {code, hold_vol} per row; disjoint keys
used to become 40k pandas columns and an 8M-cell frame.
- /api/execution/smart-route and /api/trading/run-once join the per-client
token bucket.
- OMS refuses new orders at the active-order cap (instead of growing
unboundedly), caps fills per order at 1,000, and the API bounds ticker
(32), strategy (64) and quantity (100M).
- The audit-event filter normalizes topic names (audit./AUDIT/whitespace) and
drops every audit-shaped topic from /api/core/events.
- Webhook failure logs keep only scheme+host(+port): Slack/Discord tokens
live in the path and were still logged; an invalid port no longer raises.
- Swagger/ReDoc are exempt from the SPA CSP (they load cdn.jsdelivr.net);
/ws-quotes reads no longer lazily start the provider.
Verified: tests/test_api 349, execution+risk 450, ruff clean.
…ocale The em dash in the xgboost comment made pip's locale decoder fail on Windows (UnicodeDecodeError); CI's UTF-8 environment was unaffected.
- ECharts 5.6 -> 6.1 (XSS advisory GHSA-fgmj-fm8m-jvvx); production build passes and the terminal renders four canvases with no console errors. - The WebSocket URL prefers 127.0.0.1 when the page is opened via localhost (browsers resolve localhost to ::1 first, and WebSockets do not fall back to IPv4 like HTTP does). Origin validation treats localhost and 127.0.0.1/::1 as the same loopback origin only when both ends are loopback, so a DNS-rebound attacker host is still rejected. - Research-context builds are single-flight per key: correlation + ic-decay (or a reload during the first build) share one analytics slot instead of exhausting both and returning 429. - The UI retries 429 GETs up to four times with exponential backoff, so the first 40-60 s context build no longer leaves a panel blank until reload. - Loopback UI clients with a loopback Host are exempt from the GET research bucket (remote clients and rebound hosts are not).
Document that the default provider is synthetic (not live quotes), the three real-time paths (engine event stream, market/live-portfolio/WS quotes, real research providers with incremental updates), the token/origin requirements, and the measured Baostock/network caveat. Counts: 3,342 cases.
- purge_gap delays each test window by N trading days after train end - embargo removes N trading days after prior folds' test windows from training - both default to 0, preserving the historical fold layout exactly - fold results expose train_days (training days after embargo removal)
…andoned Dev 2019-2022 Sharpe 1.10, OOS 2023-2025 0.275 vs CB equal-weight 0.382; 2026 holdout -5.13% vs -4.34%; 30/50bps excess flips negative. Per pre-registration the direction is dropped without retuning. Grid enumeration is 18 configs (prereg text said 12; corrected), DSR N=18, nothing significant.
- deduplicate QMT fills, use execution VWAP, and recover unknown submissions - reject stale quotes and unknown account/order books; suppress duplicate intents - make impact ADV causal with safe fallbacks and fee-aware cash funding - rebuild forward targets with the complete PIT ST list (v3) - reject zero-fill inception, preserve target hash prefixes and exchange sessions - add regression coverage across execution, backtest, tools, and live trading
Uses Sina latest/previous-close quotes to show intraday target returns and indicative NAV without writing or altering the official close-based forward ledgers. Includes continuous snapshot mode and unit tests.
Turnover: 32 positions, NAV 1998328.40, -0.0836%, costs 1671.60 CNY. EP+turnover: 31 positions, NAV 1998386.68, -0.0807%, costs 1613.32 CNY. 600094 was limit-up and correctly blocked; no stale assets or target hash changes.
Persist end-of-day positions, reference prices and cash in each rolling summary. The realtime monitor now marks the official book to live quotes and only falls back to target weights for legacy ledgers. Includes book/NAV reconciliation and valuation tests.
- require the latest expected exchange session on weekdays and weekends - fail when refreshed assets in the current target/book are stale, and on any month-end refresh failure - catch up earlier missing month-ends using only the prior target book - write stock and merged parquet files atomically
Cover stale ticks, fill dedup/VWAP, partial-cancellation cumulative snapshots, submission timeout recovery, kill-switch delayed sells, fail-closed account queries, and fee-aware funding. All seven drills pass; real miniQMT certification remains explicitly false.
A single provider error no longer ends the trading-day monitor or publishes stale data as fresh. Errors are logged and the next interval is attempted; three consecutive failures stop the run. Includes live three-snapshot verification and regression tests.
… two Retry and reject non-empty stale responses instead of treating them as fresh. The first run covered only 106/300 names and materially overstated NAV. After per-name repair plus spot-confirmed, adjustment-scaled rows, 299/300 names are current and 002717 is confirmed suspended. Independent first-book recomputation matches exactly: turnover NAV 1952009.91 and EP+turnover NAV 1956791.96, with zero stale target assets.
…ebalance The October month-end (2026-10-30) is the first real rolling rebalance. Two rounds of adversarial review found one blocker and several major gaps; this commit closes them: - Blocker: the dated ST snapshot was only captured after the data gates. If Friday's run exits 2 on a gate, no st_codes_20261030.txt exists and Monday's overdue catch-up refuses to use the current ST list, so the November rebalance would stall permanently. The snapshot is now taken at the very start of a month-end session, before any gate, and an exception there cannot abort the refresh. - A zeroed or degraded spot feed could have reclassified every failure as suspended and nullified the month-end gates. Suspension confirmation now requires a market-wide frame (>=100 codes), >=50% of codes trading, exactly zero volume across every row of the code with no missing values, and a bounded reclassification count; anything doubtful keeps the failure. - The any-failure month-end gate now also applies to overdue catch-up runs that can actually be generated (dated PIT snapshot present). - Target files are trusted only if they parse, match their filename date, and carry non-empty weights; corrupt targets are regenerated and ignored by the overdue scan. daily_signal writes targets atomically so a killed process cannot strand a truncated book. - The ledger rebuilds even when target generation is blocked. - Spot fetches get a 90s wall-clock deadline, at most two attempts, and a ten-minute in-process failure memo so nested retries cannot multiply full-market paginations; one successful pagination is shared per run. - ST reuse re-validates file shape and uses the same floor family as the writer; the ST list gets a plausibility floor. Strategy inputs, costs, constraints and ledger directories are unchanged. tools tests: 176 passed. A --skip-refetch rebuild on the production path reproduced both ledgers byte-identically, and the independent readback still verifies target identities with zero stale assets.
The account holder must verify identity, sign the quant agreements, download the broker's QMT installer and log in with their own credentials; everything after that (xtquant wiring, config, read-only connectivity checks, simulation-account certification drills, report and runbook updates) is handled by the operator. Passwords never leave the client and are never shared. Includes a fallback path: for a monthly strategy the system can emit an order list for manual entry, which needs no broker API access at all.
The pre-registered window only means something if the running code and targets cannot change unnoticed. The guard recomputes sha256[:16] for the frozen code, the inception targets and the PIT ST list, pins every forward target the first time it is seen, and keeps a row-count plus prefix hash per ledger so past NAV rows cannot be edited after the fact. Any drift exits non-zero for a scheduled task to alarm on. All eight registered artifacts match the freeze manifest today; drift detection was verified by deliberately corrupting a pinned hash.
An independent overfitting audit showed the guard covered only 5 of the modules that actually determine targets and NAV. It now pins 13 code/data files (optimizers, covariance, constraints, factor processing/evaluation, metrics, exchange calendar, industry map), the two inception targets and the PIT ST list, hashes with LF normalization so git autocrlf cannot cause cross-machine false alarms, and refuses to rebaseline its state once drift is detected so alarms persist until a person resolves them. The audit also verified the main path is clean: injecting future rows leaves the frozen 2026-10-23 targets bit-identical, prev-weight chaining rejects future books, valuation ffill is causal, and both ledgers reproduce to within 4e-7 CNY. Remaining blockers and majors (valuation vintage refresh before the November month-end, a pinned evaluation spec, 2027 calendar, benchmark ST exclusion, ledger rebuild comparison) are recorded with owners and deadlines in FREEZE_MANIFEST section 8.
Both strategies beat the equal-weight universe today: turnover +1.736% (cum -0.705%) and EP+turnover +1.831% (cum -0.369%) versus benchmark +1.614% (cum -1.338%). The 002717 suspension reclassification ran in production for the first time: the stale daily bar was confirmed untraded by spot volume and did not block the ledger. The freeze guard reported the two ledgers' first three lines as rewritten; git diff shows strategy NAV, returns, drawdown, positions and costs byte-identical, with only the 2026-10-27 benchmark daily return moving by 2.2e-7 after the suspended name left the benchmark cross-section. Reviewed, recorded in the manifest, and the guard baseline re-accepted with --accept (the flag exists precisely so a reviewed adjustment is explicit rather than silent).
The overfitting audit found the valuation store had not advanced past 2026-10-24: the vendor samples PE(TTM)/PB per stock on its own irregular dates, and no scheduled task refreshed them, so EP inputs were being ffilled from an ever-staler snapshot and would reach a ~37-day vintage at the November month-end, outside the p90 = 18 days seen in research. tools/fetch_valuation.py appends only vendor-dated rows (never later than today), writes per asset atomically, rebuilds the merged panel restricted to the price universe, reports per-asset vintage, and exits non-zero when more than 5% of assets fail or the post-refresh median vintage exceeds 21 days. The daily task now runs it between the freeze guard and the forward update; on a month-end session a failed refresh skips target generation instead of producing a stale EP book. Factor definitions, parameters, constraints and the frozen inception targets are unchanged: this restores the data supply the research relied on, and is recorded as v3.2 in the freeze manifest. The tool is added to the freeze guard; 17 artifacts verify. Four regression tests cover append-only semantics, causality, idempotence, failure thresholds and universe filtering.
A broad down day (universe equal-weight -2.44%) with both books holding up: turnover -1.331% (cum -2.03%) and EP+turnover -0.898% (cum -1.26%) versus benchmark cum -3.74%. Cumulative excess now +1.72pp and +2.48pp. Zero stale assets, target identities verified, freeze guard green, refetch had one expected suspended name (002717) reclassified by spot volume. The 17:15 scheduled session had again been terminated mid-command by the environment, so the ledger was produced by the manual rerun of the same pipeline (valuation refresh then forward_update) at 19:03; the new 19:40 fallback task exists for exactly this case going forward.
Recomputes the user's securities account from Sina quotes and reconciles to the broker app to the cent (value 2351.80, pnl -558.30 on 2026-10-29), tracks bucket weights, and alerts on face-value delisting proximity for the distressed single stock (now 1.15), single-day moves >=5%, single position >20% and tech-bucket weight >85%. Daily snapshot task scheduled for weekday close.
…day five Both strategies generated their 2026-10-30 target from the PIT ST snapshot captured earlier that day (197 codes, zero ST in either book) with weights summing to 1.0, a 5% single-name cap and only 1.8% one-way turnover versus the October book: turnover 35 names (+2), EP+turnover 33 (+1). Execution is pending at Monday's close per the frozen schedule. Day five stayed defensive in a falling market (universe -2.87%): turnover -1.03% to 1,939,198.11 (cum -3.04%) and EP+turnover -1.11% to 1,952,848.17 (cum -2.36%), lifting cumulative excess to +3.47pp and +4.15pp. Zero stale assets, both target identities verified, and the freeze guard passes with all four targets pinned.
Both books rolled into their 2026-10-30 targets at today's close: turnover now holds 35 names and EP+turnover 33, with rebalance costs of 212.91 and 205.84 CNY for the 1.8% one-way turnover. Six sessions and two rebalances are on record with zero pending targets and zero stale assets. On a strong up day (universe +2.41%) the defensive October book lagged: turnover +1.70% to 1,972,179.44 (cum -1.39%) and EP+turnover +1.39% to 1,979,890.41 (cum -1.01%), leaving cumulative excess at +2.87pp and +3.25pp versus the benchmark's -4.26%. Freeze guard green with all four targets pinned.
Closes audit item B3: the 3/6-month evaluation previously had no executable definition of its metrics and conflicting thresholds across documents. EVALUATION_SPEC_2026-11.md now pins the evaluation dates, the window start, the Sharpe formula (primary rf=0, secondary rf=3%), the benchmarks (B1 ST-excluded investable EW rebuilt from Baostock PIT isST; B2 the ledger column), the decision rule (6-month: Sharpe >= 0.2 and positive excess vs both B1 and B2; 3-month diagnostic only), confidence intervals and the multiplicity treatment. tools/forward_evaluation.py is the only allowed report generator: it reads only the append-only nav.csv ledgers, never summary.json, and writes a hashed report. Both files are added to the freeze guard. End-to-end validated today on the six sessions so far (B1 rebuild succeeded; excess figures match the ledger columns).
The 17:15 scheduled attempt was again refused by the mixed-date gate (55 of 300 names unpublished at 16:53); the 17:45 conditional retry found a complete panel and produced the ledger with a single expected failure (the suspended 002717). Both new November books rose on a down day (universe -0.87%): turnover +0.99% to 1,991,749.98 (cum -0.41%) and EP+turnover +1.18% to 2,003,300.78 (cum +0.17%), lifting cumulative excess to +4.68pp and +5.26pp. Seven sessions, two rebalances, zero stale assets, target identities verified.
Both November books crossed into positive cumulative territory even as a +2.45% rally day favored the market: turnover +0.73% to 2,006,329.71 (cum +0.32%) and EP+turnover +0.42% to 2,011,672.89 (cum +0.58%) versus the benchmark's -2.76%, leaving cumulative excess at +3.08pp and +3.34pp. Eight sessions, two rebalances, zero stale assets; the guard verifies all 19 frozen artifacts.
The 17:04 attempt was refused by the mixed-date gate (22 names still unpublished); the 17:46 conditional retry completed the ledger. On another strong up day (universe +2.26%) the books again rose less but kept building cumulative lead: turnover +1.50% to 2,036,456.22 (cum +1.82%) and EP+turnover +1.32% to 2,038,319.42 (cum +1.92%) versus benchmark cum -0.56%, leaving excess at +2.38pp and +2.47pp. Nine sessions, two rebalances, zero stale assets.
A risk-off Friday: the universe fell 1.69% (benchmark cum -2.24%) and both books held up far better, turnover -0.38% to 2,028,664.33 (cum +1.43%) and EP+turnover -0.37% to 2,030,738.74 (cum +1.54%), keeping cumulative excess at +3.67pp and +3.77pp. Ten sessions, two rebalances, zero stale assets, target identities verified. The 16:54 and 17:51 attempts were refused by the mixed-date gate (27 then 7 names unpublished); the 18:42 conditional retry completed the ledger. A post-close spot snapshot had shown +0.17%/+0.15% indicatively - another reminder that only official daily bars are authoritative.
The first weekly overfit-watch run reported a clean week (guard green, nine commits touching no frozen path, four target hashes matching, ledgers consistent with their summaries) and flagged two pre-week commits, 0ffcf61 and 692a8d1, that had edited the section 6 hash line in place during the 10-26/27 defect-fix days. Recorded as the same class already disclosed under m1: both code versions were superseded by v3.1 and are now hash-pinned; no version bump or interruption is needed, and this note closes the item.
First clean first-attempt run in a week: one expected failure (suspended 002717), no gate rejection. The universe was flat-to-down (benchmark cum -2.11%) while both books drifted slightly lower but kept their lead: turnover -0.01% to 2,028,475.64 (cum +1.42%) and EP+turnover -0.23% to 2,025,997.74 (cum +1.30%), leaving cumulative excess at +3.53pp and +3.41pp. Eleven sessions, two rebalances, zero stale assets.
The 16:54 attempt was refused by the mixed-date gate (59 names unpublished); the 17:42 conditional retry completed the ledger. The market rallied 1.62% and the books again lagged but stayed positive: turnover +0.32% to 2,034,976.58 (cum +1.75%) and EP+turnover +0.04% to 2,026,756.48 (cum +1.34%) versus benchmark cum -0.52%. The cumulative excess narrowed accordingly to +2.27pp and +1.86pp - the expected give-back on a strong up day. Twelve sessions, two rebalances, zero stale assets.
The scheduled daily and evening-fallback sessions did not complete during the day; the fallback task fired when the app was reopened at 22:14 and produced the ledger through its normal flow (valuation refresh, refetch, rebuild). A manual duplicate launch was detected and stopped to avoid double-fetching Sina. Another strong up day (benchmark +1.09%) with the books lagging again but positive: turnover +0.28% to 2,040,743.23 (cum +2.04%) and EP+turnover +0.09% to 2,028,588.86 (cum +1.43%). Thirteen sessions, two rebalances, zero stale assets, guard green.
The 16:55 attempt was refused by the mixed-date gate (30 names unpublished) and the 17:20 scheduled session died with an API 405 before doing any work; the 19:07 manual rerun produced the ledger with a single expected failure (suspended 002717). A down day (benchmark -1.45%) where the books again lost less: turnover -1.35% to 2,013,172.48 (cum +0.66%) and EP+turnover -1.02% to 2,007,879.61 (cum +0.39%) versus benchmark cum -0.90%, excess +1.56pp and +1.29pp. Fourteen sessions, two rebalances, zero stale assets, guard green.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A multi-agent audit of backtest accounting, live trading, risk, the API and hot paths. Highlights:
Verification:
pytest tests1732 passed / 3 skipped,ruff check .clean.2026-10-24 Update: research discipline, data PIT, cost realism, live accounting
isSTand Tusharenamechange; data-cache schemas bumped.psd_wrap), removing the silent equal-weight fallbacks seen when the estimation window is shorter than the universe.tools/real_data_research.pyreproduces the cached-panel research end to end (factors -> walk-forward -> net-of-cost report).docs/research/REAL_DATA_EVIDENCE_2026-10-24.md.Targeted test suites are green; CI will run the full matrix.
2026-10-25 Update 3: decision memo, orthogonal screen, full-suite verification
2026-10-25 Update 4: wide pool, true 2026 holdout, forward paper
Update 5 — external-review round (2026-10-25)
Metric conventions. Re-audited Sharpe/DSR/IR on all saved OOS series. The platform's custom Sharpe
(CAGR-3%)/volis 0.03–0.05 below the standard annualized Sharpesqrt(252)*mean(daily excess)/std(daily excess); DSR already used the standard convention internally, so its p-values are unchanged. Reports now lead with the standard Sharpe.Benchmark matrix (same pool). OOS 2020-02-11..2025-12-31: strategy 15.5% CAGR vs daily-EW 15.3%, market-cap-weighted 8.6%, constant-share buy&hold 13.2%. The edge over mcap/buy&hold comes from lower volatility (defensive tilt); versus same-pool daily EW the net edge is close to zero.
Cost stress (frozen strategy, same code path). impact 10bps baseline vs punitive fixed slippage 30/50bps: standard Sharpe 0.474 / 0.441 / 0.415; cumulative excess vs daily EW +2.4% / -2.4% / -6.0%. Absolute returns are not cost-fragile (monthly, turnover-capped), but the net advantage over EW does not survive conservative costs — an explicit negative result against the review's acceptance criterion.
Freeze. Strategies frozen in
docs/research/FREEZE_MANIFEST_2026-10-25.md(sha256 of targets/code/config) with evaluation and termination rules; tagforward-freeze-2026-10-23. Historical data defect ledger (15 items) indocs/research/DATA_DEFECT_LEDGER_2026-10-25.md.New return source, pre-registered: ETF momentum rotation. Pre-registration written before any backtest (
docs/research/PREREG_ETF_ROTATION_2026-10-25.md); 15 liquid A-share ETFs, 12-1 momentum, top-3, inverse-vol, cash defensive leg, monthly rebalance. Results (docs/research/ETF_ROTATION_RESULTS_2026-10-25.md):Data fixes found during the pilot (all documented): Sina ETF prices carry unadjusted share conversions (159928 4:1, 2021) and missing distributions (510500, 2022); switched total returns to official Eastmoney NAV growth. Eastmoney timestamps are Beijing midnight — parsing as UTC introduced a one-day lookahead (lag-correlation check: 0.978 at lag+1 before, 0.98 at lag 0 after the fix). The square-root impact rule explodes on zero/stale ADV (a -15x daily return appeared in placebo runs) — switched to causal 60-day median ADV.
Net position after this round: the stock strategies stay in forward paper (frozen); the ETF direction is frozen; no real money. The 90-day plan is in
docs/research/DEPLOYMENT_DECISION_2026-10-25.md§8.Addendum — PIT valuation replication. Wide-pool EP was rebuilt from Baostock daily
peTTM/pbMRQ(published-report basis, 99.97% coverage; rank correlation with the Baidu panel 0.98/0.996). Rerunning variants A/B unchanged: EP Rank IC 0.0196 → 0.0204, Sharpe 0.497 → 0.479 and 0.674 → 0.644. The EP-adds-nothing conclusion is not an artifact of the valuation source (report section 22; defect ledger #3 closed).Addendum 2 — PIT ST replication (material). The 101-pool ST mask was previously static/all-False. Rebuilt from Baostock daily
isST: 22 of 101 names had ST episodes (7,859 asset-days; ranges match public history). Under a production-faithful ST policy the frozen strategy's standard Sharpe falls from 0.474 to 0.375 (CAGR 9.27% → 7.71%), and its cumulative excess versus the investable equal-weight benchmark flips from +2.4% to -11.2%; the ST-free EW benchmark itself is stronger (10.9% CAGR, Sharpe 0.453) because ST names were net losers. Since the production pipeline already filters ST, the earlier 0.49 overstated the deployable strategy. Deployment decision is unchanged (no real money); the evidence bar is lower. Report section 23, ledger #2.Addendum 3 — live target ST contamination fixed (freeze v2). Auditing the frozen 2026-10-23 forward book found 5 of 40 holdings were ST as of that date (002168, 002650, 300419, 603189, 603825):
daily_signal.pyhad no ST filter and the wide panel carries nois_stcolumn. Fix:--exclude-codes-filedrops a PIT exclusion list before construction and records it in provenance/diagnostics (unit-tested);forward_update.pymoved intotools/and now snapshots the current Sina ST list at month end, failing closed when the snapshot is unavailable. Both 2026-10-23 targets were rebuilt with the PIT ST list (v1 files preserved, not deleted); the freeze manifest is upgraded to v2 with new hashes and tagforward-freeze-2026-10-25. The forward ledger had not started, so v2 is a starting point, not a post-hoc edit of tracked records. Report section 25.Update 6 — live-execution safety and forward v3 (2026-10-26)
Live/QMT safeguards. Stale cached snapshots are no longer re-certified as fresh; QMT ticks require a valid recent
timetaginterpreted as Asia/Shanghai. Working orders suppress duplicate intents, including kill-switch de-risking, and order generation uses an authoritative order-book check rather than a stale cache. QMT fills are deduplicated bytraded_idacross callbacks and resyncs, with quantity-weighted VWAP; cumulative order snapshots and incremental trades are never added. Submission-outcome-unknown orders stay pending and are recovered by a durable local intent instrategy_name, including the resync/exception race. Position/account query failures now fail closed instead of looking like flat positions or usable cash.Backtest correctness. Invalid ADV/volatility uses per-asset fixed-slippage fallback instead of a CNY-1 denominator; impact ADV is now the causal 60-session median shifted one day. Buy funding includes transaction costs, and cash/NAV/fee reconciliation debits the same cost exactly once. Iceberg no longer emits zero-share slices.
Forward integrity (v3). The complete 2026-10-23 PIT ST snapshot contains 13 names, not only the five excluded by v2. Active targets were rebuilt (33 turnover-only and 32 EP+turnover holdings); v1/v2 are preserved under
superseded/. Empty/partial/malformed ST inputs fail closed. A zero-fill initial rebalance cannot publish an all-cash ledger, zero-CNY buys no longer create phantom positions, target schedules must remain an unchanged hash prefix, and execution follows the exchange calendar. A stale daily cache now fails with an explicit "provider has not published the current close" error.Verification. 3,942 passed / 23 skipped across all 24 test directories (batched with bounded BLAS threads); Ruff and
git diff --checkpass. A scratch-only first-execution dry-run succeeded for both v3 books (33 and 32 buys, no blocked buys). Recomputed deployable baseline after the cost fixes: standard Sharpe 0.387, CAGR 7.89%, cumulative excess vs investable EW -9.6%; the no-real-money decision is unchanged. No real forward NAV is claimed in this update.Addendum — first real forward session recorded. On 2026-10-26 the official close-based ledgers started successfully after the provider published the daily bars. Turnover-only: 32 filled positions, NAV 1,998,328.40 (-0.0836%), costs 1,671.60 CNY. EP+turnover: 31 filled positions, NAV 1,998,386.68 (-0.0807%), costs 1,613.32 CNY.
600094was correctly blocked as limit-up and remains cash; stale assets 0 and all target hashes verified. This first session is close-entry/cost accounting only, so no holding-period benchmark conclusion is drawn. A separatetools/forward_realtime.pynow provides indicative intraday target monitoring (3 unit tests) without altering the official close ledger.Addendum — realtime now uses the actual book. Rolling summaries now persist end-of-day positions, reference prices and cash.
tools/forward_realtime.pymarks those official positions to live quotes (basis=official_book) and only falls back to target weights for legacy ledgers. Latest indicative values: turnover 1,998,650.51 (+0.0161% vs the official close NAV) and EP+turnover 2,003,818.78 (+0.2718%); these remain intraday references, not official NAV. Book/NAV reconciliation and valuation tests pass (66 tests in the affected modules).Addendum — forward data reliability review. Weekend/holiday runs now require the latest expected exchange session (not merely accept any stale cache). Per-asset refresh failures cannot be hidden by one fresh row: failures affecting the current target/book, or any failure before a month-end signal, abort the update. Earlier missing month-ends are detected and catch-up uses only the prior target book, never future weights. Stock and merged parquet writes are atomic. The daily read-back validator now supports later sessions and pending targets instead of hard-coding inception day. Affected tools suite: 149 passed.
Addendum — executable QMT safety drills and runbook.
tools/qmt_safety_drill.pynow runs seven production-code fault-injection scenarios: stale ticks/timezones, fill dedup/VWAP, partial-cancellation cumulative snapshots, submission-timeout recovery races, kill-switch delayed sells, fail-closed account/order books, and fee-aware cash funding. All seven pass, plus 566 tests across trading/live/tools. A newdocs/QMT_SAFETY_DRILL.mdgives the exact pre-open, intraday, close, and read-back commands.xtquant/miniQMT is not installed here, so the report explicitly setsreal_miniqmt_certified=false; tomorrow's documented default is real-data forward paper, not real-money orders.Addendum — forward day two, after a real data-integrity repair. The first 2026-10-27 refresh produced current rows for only 106/300 names; non-empty stale responses were being accepted as fresh, materially overstating NAV. Per-name retries plus a saved, spot-confirmed fallback (with adjustment-scaled OHLC and turnover recomputation) brought 299/300 names current; 002717 is confirmed suspended. The mixed-date outputs were archived and recomputed, with first-day rows unchanged and an independent first-book recomputation matching exactly. Corrected results: turnover NAV 1,952,009.91 (-2.40% since inception) vs equal-weight benchmark -2.91%; EP+turnover NAV 1,956,791.96 (-2.16%) vs -2.91%. Zero stale target assets. The updater now retries/rejects stale responses and fails on watched-asset or widespread refresh failures.