Files
Auto-Trader/wiki
shadowinlife ee03474268 feat(factors): industry-neutralized CN value alphas + PIT SW2021 sector tag (csi300) (#1802)
* feat(bench): add horizon parameter for monthly-frequency IC revalidation

h-bar forward returns (close[t+h]/close[t]-1, no ffill — a NaN close never
bridges) sampled non-overlapping end-anchored, so the naive-iid t-stat and
the alive/dead thresholds stay honest without new statistics. Threaded
through run_bench / run_bench_strict / the alpha_bench tool + MCP wrapper /
CLI --horizon (argparse type + handler guard, exit 2 on bad input).
horizon=1 is byte-identical to the previous next-bar formula.

Follow-up #1 of issue #1789 (monthly-horizon revalidation of the 13
fundamental factors — the conventional reading for fundamental factors).

* fix(bench): exclude SEC-fundamental alphas off sp500 instead of failing the bench

--zoo fundamental --universe csi300 (the repro command printed in issue
#1789) hard-failed with 'Choose the S&P 500 universe' because the
validator rejects fund:* inputs on any non-sp500 universe, while the
mirror case (CN val:* alphas on sp500) skips cleanly per alpha. The
runners now split the selection up front, disclose the excluded ids in
the result envelope, and error only when nothing is left to bench; the
validator stays as the backstop for direct callers.

The CLI/tool JSON envelopes also state their own horizon,
forward_return_sampling and universe_excluded — a saved summary must
show the configuration its numbers came from.

* fix(bench): close review findings on horizon/exclusion disclosure

- multi-zoo CLI aggregation threads horizon / forward_return_sampling /
  universe_excluded from the sub-runs into the aggregate envelope (a saved
  all-zoos JSON now states its own horizon and reconciles its exclusions)
- alpha compare surfaces universe-excluded shortlist members as skipped
  entries with the exclusion reason instead of silently dropping them
- horizon upper bound (1.._MAX_HORIZON) enforced consistently at the CLI
  parser, the handler, and both runner entries BEFORE any panel load —
  previously only the tool/MCP capped it and an unbounded CLI run fetched
  the full panel before failing late
- non-int horizon can no longer TypeError in the entry schema construction
- categorise docstring notes the daily calibration of the |IC|>0.02 floor
  at h>1; the sampling comment no longer overclaims (residual
  autocorrelation from persistent factor exposures is not modeled)
- MCP wrapper uses _MAX_HORIZON instead of a hardcoded duplicate

Pinned by new tests: parser/handler/runner cap rejection (fetch not
called), multi-zoo disclosure aggregation, compare exclusion disclosure.

* feat(factors): industry-neutralized CN value alphas + PIT SW2021 sector tag

Three within-industry z-scored variants of the CN value factors
(fund_pb/fund_ps_ttm/fund_dividend_yield -> *_ind_neutral, zoo 475 -> 478):
raw A-share PB/PS/dy are heavily industry-confounded, so each name is
z-scored against its Shenwan L1 peers instead of the whole cross-section.

- industry_loader.py: tushare index_member_all (SW2021) membership with
  in_date/out_date -> point-in-time labels per trading day; deterministic
  overlap resolution; 2000-row truncation guard refetches per L1; degrades
  to an unmapped frame on provider failure, never invents a label.
- csi300 bench attaches panel['sector'] outside the pickled price cache,
  masked to the PIT membership cross-section, behind the same 0.9 coverage
  gate the sp500 GICS tag uses; meta discloses sector_source/coverage.
- base.zscore_by_group: the z-scoring sibling of alpha101's _ind_neutralize
  demean - no global fallback (an unlabelled cell stays NaN rather than
  wearing an industry-neutral label it did not earn).
- README x7 + SKILL.md counts (incl. the stale 462 prose sites), i18n zh/en.

Follow-up #2 of issue #1789.

* fix(loaders): index_member_all takes no src param; fetch PIT history via is_new Y+N

Live-verified endpoint contract (pinned by test): index_member_all accepts
only the documented l1/l2/l3_code, ts_code and is_new filters — an
undocumented src param silently returns an empty frame (HTTP 200, 0 rows),
which degraded the first csi300 sector attach to coverage 0.0. src now goes
to index_classify (truncation fallback) only.

The server also defaults is_new to 'Y' (current members, no out_date rows),
so the historical half ('N', ~2000 rows with out_date back to the 1990s) is
fetched separately and concatenated — without it every label would be
today's classification and the loader's PIT claim false.

Live smoke: 350/350 csi300 PIT constituents covered (100%), 0.9 gate passes.

* docs(factors): surface the SW2021 taxonomy-retroactivity caveat; pin cache immutability

Review follow-ups: the loader docstring disclosed that SW2021
classifications are applied retroactively, but no user-facing surface
did — one sentence added to each ind-neutral alpha's notes, the i18n
en/zh entries and SKILL.md (membership is point-in-time; the taxonomy
version itself is not). The sector-attach test now pins that the
original pickled panel dict is never mutated by the attach, and the
sector_coverage meta semantics (labelled-somewhere fraction, not
per-date) are documented at the attach site.

* fix(factors): align ind-neutral PB/PS with the upstream nonpositive-multiple guard

The maintenance batch (d3c76819) added a guard to fund_pb/fund_ps_ttm:
nonpositive multiples are excluded rather than ranked as cheap. The
industry-neutral variants follow suit — the within-group z-score sees
the same values.where(values > 0) pre-filter, the guard sentence leads
the notes/i18n entries, and the upstream parametrized test is mirrored
for the neutral stems.
2026-10-12 19:22:41 +08:00
..
2026-05-14 19:31:31 +08:00
2026-05-14 19:31:31 +08:00

Vibe-Trading Wiki

Static source for https://vibetrading.wiki.

Local preview

cd wiki
python3 -m http.server 8088

Open http://localhost:8088/home/ for the landing page and these wiki sections:

  • http://localhost:8088/docs/
  • http://localhost:8088/tutorials/
  • http://localhost:8088/alpha-library/
  • http://localhost:8088/research-lab/

Direct docs URLs such as /docs/latest/getting-started/vibe-trading-overview or /docs/zh/… are handled by Cloudflare Pages via _redirects. The simple Python preview server does not apply those rewrite rules, so use /docs/ as the local entry point. Build the Alpha Library first (see below) or its zoo links fall through.

One header, two languages

Every page shares one head block, header and footer, kept in partials/ and copied between <!-- site-head|site-header|site-footer:start/end --> markers:

python3 wiki/scripts/sync_site_chrome.py          # after editing a partial
python3 wiki/scripts/sync_site_chrome.py --check  # CI

The check also requires <body data-section> (it highlights the current section in the header, CSS only), the language attributes below, and one cache-busting ?v= token everywhere — _headers lets browsers keep JS and CSS for an hour, so a module bumped alone can meet a cached copy of what it imports and leave the page blank. Bump the token with one replace across wiki/.

Language. English by default, never guessed from the browser; the header button switches the whole site and the choice is stored (vibetrading-lang). site.js does the switching:

  • A page whose URL names no language (<html data-lang-neutral>: home, tutorials and research-lab indexes, the Alpha Library and its generated pages, bare /docs/) is English HTML with data-i18n / data-i18n-html / data-i18n-attr / data-href-zh hooks; Chinese comes from locales/zh.json. theme-init.js hides such a page until the Chinese text is in, so it never flashes English.
  • An article exists once per language (<html data-page-lang="en|zh"> plus <link rel="alternate" hreflang> both ways): the tutorial (vibe-trading-beginner.html / -zh.html) and the research post. Opening one records its language; the button goes to the other file.
  • The docs are one shell: English at /docs/latest/<page> from docs/content.en.js, Chinese at /docs/zh/<page> from docs/content.zh.js.
node wiki/scripts/check_docs_parity.mjs   # docs: same pages and sections in both languages
python3 wiki/scripts/check_i18n.py        # every data-i18n key has Chinese, no orphans (needs jinja2)

Counts on the pages (data sources, engines, skills, presets, MCP tools, brokers, alphas) are measured from the code of the version in docs/content.js; re-measure them for a new release rather than editing one number.

Alpha Library

alpha-library/content/ and manifest.json are generated, not committed. The deploy workflow builds them before uploading, and also redeploys when agent/src/factors/ changes:

vibe-trading alpha export-manifest --out wiki/alpha-library/manifest.json --force
python wiki/scripts/build_alpha_library.py --clean

Cloudflare Pages

  • Project root: wiki
  • Build command: leave empty
  • Output directory: .
  • Custom domain: vibetrading.wiki

The pages are static and need no build step; the deploy workflow only generates the Alpha Library (above). The only dynamic piece is a small analytics layer in functions/ (Cloudflare Pages Functions): _middleware.js classifies each page request as AI-agent / bot / human by User-Agent and counts it into a D1 database (vibetrading-analytics, binding DB), and api/stats.js serves the footer's aggregate counts alongside public PyPI and GitHub numbers. The counter is anonymous and first-party — no cookies, no per-visitor identifier, no IP retention.