wiki: one header on every page, English by default, one switch for 中文

Each section carried its own copy of the header: Home's nav had no Home,
the docs' nav had no Docs, the research post had no star or theme button,
and the generated Alpha Library pages had no header at all. Only the docs
could switch language, and they guessed Chinese from the browser while
every other page stayed English.

- partials/ holds one head block, header and footer; sync_site_chrome.py
  writes them into every page between markers and, with --check in CI,
  also requires <body data-section> (the current section is highlighted by
  CSS), the language attributes and a single ?v= token site-wide. The
  Alpha Library generator inserts the same partials.
- site.js is the shared shell (theme, stars, language, footer traffic on
  home only). English is the HTML; Chinese comes from locales/zh.json via
  data-i18n hooks, and check_i18n.py fails on a key with no Chinese or a
  Chinese string nothing uses. A URL that names a language (/docs/zh/,
  the two articles) shows it and remembers it; everything else follows the
  stored choice, English by default. theme-init.js keeps a page hidden until
  its Chinese text is in, so it never flashes English.
- English tutorial and Chinese research post added. The Chinese tutorial
  gets the corrections its translation surfaced: 462 alphas in five zoos,
  AAPL.US, the mandate caps trades per day (it has no daily loss limit),
  Shadow Account extracts up to five rules, the web UI needs Docker or a
  checkout.
- Alpha Library: the zoo pages were never deployed — content/ is
  gitignored and nothing built it, so every zoo link served the root
  redirect. The deploy now exports the manifest and renders the pages (and
  redeploys when agent/src/factors changes); the fifth zoo, fundamental, is
  listed; generated pages allow same-origin scripts only.
- Phones keep the nav as a scrollable row instead of hiding it.
This commit is contained in:
Haozhe Wu committed 2026-09-29 17:53:13 +08:00
1 parent 36485bf55b
commit a7f645f368
32 files changed
+2756 -707

No files matched your search

+15
View File
@@ -6,6 +6,7 @@ on:
- main
paths:
- "wiki/**"
- "agent/src/factors/**"
- ".github/workflows/wiki-deploy.yml"
concurrency:
@@ -23,6 +24,20 @@ jobs:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# The Alpha Library pages are generated, not committed: export the
# registry's manifest and render one page per alpha before uploading.
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: Build the Alpha Library
run: |
python -m pip install --quiet -e .
vibe-trading alpha export-manifest --out wiki/alpha-library/manifest.json --force
python wiki/scripts/build_alpha_library.py --clean
test -f wiki/alpha-library/content/index.json
- name: Deploy wiki
uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0
with:
+12 -2
View File
@@ -30,8 +30,10 @@ jobs:
- name: Check JavaScript syntax
run: |
node --check wiki/site.js
node --check wiki/main.js
node --check wiki/theme.js
node --check wiki/theme-init.js
node --check wiki/alpha-library/alpha.js
node --check wiki/docs/content.js
node --check wiki/docs/content.en.js
node --check wiki/docs/content.zh.js
@@ -41,7 +43,15 @@ jobs:
run: node wiki/scripts/check_docs_parity.mjs
- name: Check locale JSON
run: python -m json.tool wiki/locales/en.json >/dev/null
run: python -m json.tool wiki/locales/zh.json >/dev/null
- name: Check every page carries the shared header, footer and head block
run: python wiki/scripts/sync_site_chrome.py --check
- name: Check every translatable string has Chinese
run: |
python -m pip install --quiet jinja2
python wiki/scripts/check_i18n.py
- name: Check social card metadata
run: python wiki/scripts/check_social_cards.py
+49 -16
View File
@@ -16,32 +16,64 @@ Open `http://localhost:8088/home/` for the landing page and these wiki sections:
- `http://localhost:8088/alpha-library/`
- `http://localhost:8088/research-lab/`
Direct docs URLs such as `/docs/latest/getting-started/vibe-trading-overview` 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.
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.
## Docs content
## One header, two languages
The docs are one page shell in two languages. English pages live in
`docs/content.en.js` and are served at `/docs/latest/<page>`; Chinese pages live
in `docs/content.zh.js` and are served at `/docs/zh/<page>`. The header toggle
switches language and remembers the choice; a first visit from a Chinese browser
opens the Chinese pages. Both files must list the same pages and sections in the
same order — CI runs:
Every page shares one head block, header and footer, kept in `partials/` and
copied between `<!-- site-head|site-header|site-footer:start/end -->` markers:
```bash
node wiki/scripts/check_docs_parity.mjs
python3 wiki/scripts/sync_site_chrome.py # after editing a partial
python3 wiki/scripts/sync_site_chrome.py --check # CI
```
`_headers` lets browsers cache `.js` and `.css` for an hour, so the docs load
their modules with a `?v=<date>` query (`docs/index.html` → `docs/main.js` →
`docs/content.js` → the two language files). Bump it in all five places when
any of those files change, or a returning visitor can combine a new module with
a cached old one and get a blank page.
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/`.
Counts on the docs pages (data sources, engines, skills, presets, MCP tools,
**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`.
```bash
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:
```bash
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`
@@ -49,7 +81,8 @@ number.
- Output directory: `.`
- Custom domain: `vibetrading.wiki`
The site is static and needs no build step. The only dynamic piece is a small
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
-2
View File
@@ -1,6 +1,4 @@
/ /home/ 302
/docs /docs/latest/getting-started/vibe-trading-overview 302
/docs/ /docs/latest/getting-started/vibe-trading-overview 302
/tutorials /tutorials/ 302
/alpha /alpha-library/ 302
/alpha-library /alpha-library/ 302
+45
View File
@@ -0,0 +1,45 @@
// Fills the Alpha Library landing stats from content/index.json, which the
// deploy workflow generates (wiki/scripts/build_alpha_library.py).
import { storedLang } from "/site.js?v=20260929d";
const TEXT = {
en: { across: (n) => `across ${n} zoos`, alphas: (n) => `${n} alphas`, missing: "manifest not generated yet" },
zh: { across: (n) => `分布在 ${n} 个因子集`, alphas: (n) => `${n} 个因子`, missing: "尚未生成因子清单" }
};
function render(data) {
const t = TEXT[storedLang()];
const set = (id, value) => {
const el = document.getElementById(id);
if (el) el.textContent = value;
};
if (!data) {
set("stat-total-sub", t.missing);
return;
}
set("stat-total", String(data.total_alphas || 0));
set("stat-total-sub", t.across(data.zoo_count || 0));
set("stat-zoos", String(data.zoo_count || 0));
if (data.generated_at) {
const date = new Date(data.generated_at);
set("stat-generated", Number.isNaN(date.getTime()) ? "--" : date.toISOString().slice(0, 10));
}
for (const zoo of data.zoos || []) {
const el = document.querySelector(`.count[data-zoo="${String(zoo.zoo_id || "")}"]`);
if (el) el.textContent = t.alphas(zoo.count);
}
}
let data = null;
fetch("/alpha-library/content/index.json")
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
.then((json) => {
data = json;
render(data);
})
.catch(() => render(null));
// Re-render the counts in the other language when the reader switches.
document.getElementById("lang-toggle")?.addEventListener("click", () => {
setTimeout(() => render(data), 0);
});
+67 -45
View File
@@ -1,31 +1,34 @@
<!doctype html>
<html lang="en">
<html lang="en" data-lang-neutral>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self'; img-src 'self' data: https://vibetrading.wiki; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.github.com;">
<script src="/theme-init.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Alpha Library | Vibe-Trading Wiki</title>
<meta name="description" content="450+ pre-built quant alphas across four zoos: Qlib158, Kakushadze 101, GTJA191, Academic. One-line CLI to benchmark any zoo on your universe.">
<title data-i18n="alpha.title">Alpha Library | Vibe-Trading Wiki</title>
<meta name="description" content="462 pre-built quant alphas across five zoos: GTJA 191, Qlib Alpha158, Kakushadze 101, academic anomalies and point-in-time fundamentals. One command benchmarks any zoo on your universe.">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Vibe-Trading Wiki">
<meta property="og:title" content="Alpha Library">
<meta property="og:description" content="450+ pre-built quant alphas across four zoos. One-line CLI to benchmark any zoo on your universe.">
<meta property="og:description" content="462 pre-built quant alphas across five zoos. One command benchmarks any zoo on your universe.">
<meta property="og:image" content="https://vibetrading.wiki/assets/alpha-library-og.png">
<meta property="og:url" content="https://vibetrading.wiki/alpha-library/">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Alpha Library">
<meta name="twitter:description" content="450+ pre-built quant alphas across four zoos. One-line CLI to benchmark any zoo on your universe.">
<meta name="twitter:description" content="462 pre-built quant alphas across five zoos. One command benchmarks any zoo on your universe.">
<meta name="twitter:image" content="https://vibetrading.wiki/assets/alpha-library-og.png">
<link rel="canonical" href="https://vibetrading.wiki/alpha-library/">
<link rel="dns-prefetch" href="https://api.github.com">
<!-- site-head:start -->
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://api.github.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
<script type="module" src="/main.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
<!-- site-head:end -->
<script type="module" src="/main.js?v=20260929d"></script>
<script type="module" src="/alpha-library/alpha.js?v=20260929d"></script>
<style>
.alpha-hero { width: min(1180px, calc(100% - 32px)); margin: 56px auto 32px; }
.alpha-hero h1 { font-family: var(--serif); font-size: clamp(2.2rem, 4vw, 3.4rem); margin: 8px 0 14px; letter-spacing: -0.01em; }
@@ -48,63 +51,67 @@
.alpha-getstart .comment { color: var(--muted); }
</style>
</head>
<body>
<body data-section="alpha">
<noscript>
<style>.alpha-stats, .alpha-zoos { display: none; }</style>
<p style="padding:24px;color:var(--muted);">Enable JavaScript to load alpha stats.</p>
</noscript>
<!-- site-header:start -->
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">Source</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme">
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- site-header:end -->
<main>
<section class="alpha-hero">
<p class="eyebrow">Alpha Library</p>
<h1>Alpha Library.</h1>
<p>450+ pre-built quant alphas across four zoos. One-line CLI to bench any zoo on your universe.</p>
<p class="eyebrow" data-i18n="nav.alpha">Alpha Library</p>
<h1 data-i18n="alpha.heading">Alpha Library.</h1>
<p data-i18n="alpha.lede">462 pre-built quant alphas across five zoos. One command benchmarks any zoo on your universe.</p>
</section>
<dl class="alpha-stats" id="alpha-stats">
<div class="alpha-stat">
<dt>Total alphas</dt>
<dt data-i18n="alpha.stats.total">Total alphas</dt>
<dd id="stat-total">--</dd>
<span class="small" id="stat-total-sub">loading...</span>
<span class="small" id="stat-total-sub">…</span>
</div>
<div class="alpha-stat">
<dt>Zoos</dt>
<dt data-i18n="alpha.stats.zoos">Zoos</dt>
<dd id="stat-zoos">--</dd>
<span class="small">Qlib158 / Kakushadze 101 / GTJA191 / Academic</span>
<span class="small">GTJA191 / Qlib158 / Alpha101 / Academic / Fundamental</span>
</div>
<div class="alpha-stat">
<dt>Last generated</dt>
<dt data-i18n="alpha.stats.generated">Last generated</dt>
<dd id="stat-generated" style="font-size: 0.95rem;">--</dd>
<span class="small">via vibe-trading alpha export-manifest</span>
<span class="small" data-i18n="alpha.stats.generatedVia">via vibe-trading alpha export-manifest</span>
</div>
<div class="alpha-stat">
<dt>Live trading</dt>
<dd style="font-size: 0.95rem;">opt-in</dd>
<span class="small">via a broker you authorize</span>
<dt data-i18n="alpha.stats.live">Live trading</dt>
<dd style="font-size: 0.95rem;" data-i18n="alpha.stats.optIn">opt-in</dd>
<span class="small" data-i18n="alpha.stats.broker">via a broker you authorize</span>
</div>
</dl>
@@ -112,49 +119,64 @@
<a class="alpha-zoo-card" href="content/qlib158/index.html">
<span class="zoo-id">qlib158</span>
<h3>Qlib Alpha158</h3>
<p>Microsoft Qlib's 158 production alphas, ported to the panel API.</p>
<p data-i18n="alpha.zoo.qlib158">Microsoft Qlib's 158 production alphas, ported to the panel API.</p>
<span class="count" data-zoo="qlib158">--</span>
</a>
<a class="alpha-zoo-card" href="content/alpha101/index.html">
<span class="zoo-id">alpha101</span>
<h3>Kakushadze 101 Formulaic Alphas</h3>
<p>The 101 short-horizon formulaic alphas from Kakushadze (2015).</p>
<p data-i18n="alpha.zoo.alpha101">The 101 short-horizon formulaic alphas from Kakushadze (2015).</p>
<span class="count" data-zoo="alpha101">--</span>
</a>
<a class="alpha-zoo-card" href="content/gtja191/index.html">
<span class="zoo-id">gtja191</span>
<h3>GTJA 191</h3>
<p>Guotai Junan's 191 alphas — A-share microstructure &amp; volume themes.</p>
<p data-i18n="alpha.zoo.gtja191">Guotai Junan's 191 alphas — A-share microstructure &amp; volume themes.</p>
<span class="count" data-zoo="gtja191">--</span>
</a>
<a class="alpha-zoo-card" href="content/academic/index.html">
<span class="zoo-id">academic</span>
<h3>Academic Anomalies</h3>
<p>Curated alphas from the academic asset-pricing literature.</p>
<h3 data-i18n="alpha.zoo.academicName">Academic Anomalies</h3>
<p data-i18n="alpha.zoo.academic">Curated alphas from the academic asset-pricing literature.</p>
<span class="count" data-zoo="academic">--</span>
</a>
<a class="alpha-zoo-card" href="content/fundamental/index.html">
<span class="zoo-id">fundamental</span>
<h3 data-i18n="alpha.zoo.fundamentalName">Point-in-time Fundamentals</h3>
<p data-i18n="alpha.zoo.fundamental">US SEC fundamentals anchored to the filing date, so a backtest never sees a number before it was published.</p>
<span class="count" data-zoo="fundamental">--</span>
</a>
</section>
<section class="alpha-getstart">
<h2>Get started</h2>
<pre><code><span class="comment"># install</span>
<h2 data-i18n="alpha.start.title">Get started</h2>
<pre><code><span class="comment" data-i18n="alpha.start.install"># install</span>
pip install vibe-trading-ai
<span class="comment"># list every alpha in a zoo</span>
<span class="comment" data-i18n="alpha.start.list"># list every alpha in a zoo</span>
vibe-trading alpha list --zoo gtja191
<span class="comment"># benchmark a zoo on your universe and period</span>
<span class="comment" data-i18n="alpha.start.bench"># benchmark a zoo on your universe and period</span>
vibe-trading alpha bench --zoo gtja191 --universe csi300 --period 2020-2025</code></pre>
</section>
</main>
<!-- site-footer:start -->
<footer class="site-footer">
<span>Vibe-Trading Wiki</span>
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
<!-- site-footer:end -->
</body>
</html>
+7 -4
View File
@@ -1026,22 +1026,25 @@ vibe-trading playbook create premarket-brief</code></pre>
<li>Output directory: <code>.</code></li>
<li>Custom domain: <code>vibetrading.wiki</code></li>
</ul>
<p>A push to <code>main</code> that touches <code>wiki/</code> deploys it.</p>
<p>A push to <code>main</code> that touches <code>wiki/</code> or <code>agent/src/factors/</code> deploys it; the deploy generates the Alpha Library pages first.</p>
`
},
{
id: "editing",
title: "Editing the docs",
body: `
<p>English pages live in <code>wiki/docs/content.en.js</code>, Chinese pages in <code>wiki/docs/content.zh.js</code>. Both must have the same pages and sections in the same order; CI runs the check below. English is served at <code>/docs/latest/…</code>, Chinese at <code>/docs/zh/…</code>.</p>
<pre><code>node wiki/scripts/check_docs_parity.mjs</code></pre>
<p>English pages live in <code>wiki/docs/content.en.js</code>, Chinese pages in <code>wiki/docs/content.zh.js</code>. Both must have the same pages and sections in the same order. English is served at <code>/docs/latest/…</code>, Chinese at <code>/docs/zh/…</code>.</p>
<p>Every page of the site shares one header, footer and head block from <code>wiki/partials/</code>. The rest of the site is English HTML with Chinese in <code>wiki/locales/zh.json</code>. English is the default; the header button switches the whole site and remembers the choice. CI runs:</p>
<pre><code>node wiki/scripts/check_docs_parity.mjs
python3 wiki/scripts/sync_site_chrome.py --check
python3 wiki/scripts/check_i18n.py</code></pre>
`
},
{
id: "why-static",
title: "Why static",
body: `
<p>Docs, landing copy, redirects, theme and search all work as static files. No VPS, database or server process is needed; the only server-side code is an anonymous page-view counter.</p>
<p>Docs, landing copy, redirects, theme, language and search all work as static files. No VPS, database or server process is needed; the only server-side code is an anonymous page-view counter.</p>
`
}
]
+4 -10
View File
@@ -1,5 +1,5 @@
import { DOCS_STRUCTURE as EN_STRUCTURE } from "/docs/content.en.js?v=20260929";
import { DOCS_STRUCTURE as ZH_STRUCTURE } from "/docs/content.zh.js?v=20260929";
import { DOCS_STRUCTURE as EN_STRUCTURE } from "/docs/content.en.js?v=20260929d";
import { DOCS_STRUCTURE as ZH_STRUCTURE } from "/docs/content.zh.js?v=20260929d";
// One content set per language; every version alias renders the latest.
export const DOCS_DEFAULT_VERSION = "0.1.16";
@@ -22,30 +22,24 @@ export const DOCS_VERSIONS = [
export const DOCS_UI = {
en: {
siteTitle: "Vibe-Trading Docs",
brandSuffix: "Docs",
search: "Search",
searchPlaceholder: "Search docs",
version: "Version",
language: "Language",
onThisPage: "On this page",
previous: "Previous",
next: "Next",
noMatch: "No pages match that search.",
navLabel: "Documentation navigation",
nav: { home: "Home", tutorials: "Tutorials", alpha: "Alpha Library", lab: "Research Lab", source: "Source" }
navLabel: "Documentation navigation"
},
zh: {
siteTitle: "Vibe-Trading 文档",
brandSuffix: "文档",
search: "搜索",
searchPlaceholder: "搜索文档",
version: "版本",
language: "语言",
onThisPage: "本页内容",
previous: "上一页",
next: "下一页",
noMatch: "没有匹配的页面。",
navLabel: "文档导航",
nav: { home: "首页", tutorials: "教程", alpha: "因子库", lab: "研究室", source: "源码" }
navLabel: "文档导航"
}
};
+7 -4
View File
@@ -1025,22 +1025,25 @@ vibe-trading playbook create premarket-brief</code></pre>
<li>输出目录:<code>.</code></li>
<li>自定义域名:<code>vibetrading.wiki</code></li>
</ul>
<p>推送到 <code>main</code> 且改动了 <code>wiki/</code> 时自动部署。</p>
<p>推送到 <code>main</code> 且改动了 <code>wiki/</code> 或 <code>agent/src/factors/</code> 时自动部署;部署前会先生成因子库页面。</p>
`
},
{
id: "editing",
title: "编辑文档",
body: `
<p>英文页面在 <code>wiki/docs/content.en.js</code>,中文页面在 <code>wiki/docs/content.zh.js</code>。两者的页面和小节必须一一对应、顺序一致,CI 会运行下面的检查。英文地址是 <code>/docs/latest/…</code>,中文地址是 <code>/docs/zh/…</code>。</p>
<pre><code>node wiki/scripts/check_docs_parity.mjs</code></pre>
<p>英文页面在 <code>wiki/docs/content.en.js</code>,中文页面在 <code>wiki/docs/content.zh.js</code>。两者的页面和小节必须一一对应、顺序一致。英文地址是 <code>/docs/latest/…</code>,中文地址是 <code>/docs/zh/…</code>。</p>
<p>全站所有页面共用 <code>wiki/partials/</code> 里的同一套页头、页脚和 head 公共部分。其余页面的英文写在 HTML 里,中文在 <code>wiki/locales/zh.json</code>。默认英文,顶栏按钮切换整站语言并记住选择。CI 会运行:</p>
<pre><code>node wiki/scripts/check_docs_parity.mjs
python3 wiki/scripts/sync_site_chrome.py --check
python3 wiki/scripts/check_i18n.py</code></pre>
`
},
{
id: "why-static",
title: "为什么是静态站",
body: `
<p>文档、首页文案、跳转、主题和搜索都能以静态文件实现,不需要 VPS、数据库或常驻服务;唯一的服务端代码是一个匿名的访问计数器。</p>
<p>文档、首页文案、跳转、主题、语言和搜索都能以静态文件实现,不需要 VPS、数据库或常驻服务;唯一的服务端代码是一个匿名的访问计数器。</p>
`
}
]
+39 -29
View File
@@ -2,19 +2,6 @@
<html lang="en">
<head>
<meta charset="utf-8">
<script>
(function () {
try {
var stored = localStorage.getItem("vibetrading-theme");
var resolved = stored === "dark" || stored === "light"
? stored
: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", resolved);
} catch (e) {
document.documentElement.setAttribute("data-theme", "light");
}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vibe-Trading Docs</title>
<meta name="description" content="Documentation for Vibe-Trading, the natural-language finance research agent. 中文文档见 /docs/zh/。">
@@ -29,40 +16,46 @@
<link rel="alternate" hreflang="en" href="https://vibetrading.wiki/docs/latest/getting-started/vibe-trading-overview">
<link rel="alternate" hreflang="zh-CN" href="https://vibetrading.wiki/docs/zh/getting-started/vibe-trading-overview">
<link rel="alternate" hreflang="x-default" href="https://vibetrading.wiki/docs/latest/getting-started/vibe-trading-overview">
<!-- site-head:start -->
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
<link rel="stylesheet" href="/docs/styles.css?v=20260929">
<script type="module" src="/docs/main.js?v=20260929"></script>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
<!-- site-head:end -->
<link rel="stylesheet" href="/docs/styles.css?v=20260929d">
<script type="module" src="/docs/main.js?v=20260929d"></script>
</head>
<body class="docs-body">
<header class="site-header docs-header" id="site-header">
<body class="docs-body" data-section="docs">
<!-- site-header:start -->
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading <em data-ui="brandSuffix">Docs</em></span>
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/home/" data-ui-nav="home">Home</a>
<a href="/tutorials/" data-ui-nav="tutorials">Tutorials</a>
<a href="/alpha-library/" data-ui-nav="alpha">Alpha Library</a>
<a href="/research-lab/" data-ui-nav="lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading" data-ui-nav="source">Source</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<a class="lang-pill" id="lang-toggle" href="/docs/zh/getting-started/vibe-trading-overview" hreflang="zh-CN" lang="zh-CN" data-lang="zh">中文</a>
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme">
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- site-header:end -->
<main class="docs-shell">
<aside class="docs-sidebar" aria-label="Documentation navigation">
@@ -86,5 +79,22 @@
<nav id="docs-outline"></nav>
</aside>
</main>
<!-- site-footer:start -->
<footer class="site-footer">
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
<!-- site-footer:end -->
</body>
</html>
+12 -109
View File
@@ -1,17 +1,12 @@
import { initTheme } from "/theme.js";
import { initSite, setPageLang, storedLang } from "/site.js?v=20260929d";
import {
DOCS_DEFAULT_LANG,
DOCS_DEFAULT_PAGE,
DOCS_LANGUAGES,
DOCS_UI,
DOCS_VERSIONS
} from "/docs/content.js?v=20260929";
} from "/docs/content.js?v=20260929d";
const REPO = "HKUDS/Vibe-Trading";
const API = `https://api.github.com/repos/${REPO}`;
const STARS_CACHE_KEY = "vibetrading-github-stars";
const STARS_TTL_MS = 12 * 60 * 60 * 1000;
const LANG_KEY = "vibetrading-docs-lang";
const SITE = "https://vibetrading.wiki";
function stripTags(html) {
@@ -36,41 +31,6 @@ const pagesByLang = Object.fromEntries(
])
);
function formatStarCount(n) {
if (typeof n !== "number" || !Number.isFinite(n) || n < 0) return "--";
if (n < 1000) return String(Math.round(n));
if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;
return `${(n / 1_000_000).toFixed(1)}m`;
}
function initStars() {
const el = document.getElementById("star-count");
if (!el) return;
let cached = null;
try {
cached = JSON.parse(localStorage.getItem(STARS_CACHE_KEY) || "null");
} catch {
cached = null;
}
if (cached && typeof cached.count === "number") el.textContent = formatStarCount(cached.count);
if (cached && Date.now() - cached.at < STARS_TTL_MS) return;
fetch(API, { headers: { Accept: "application/vnd.github+json" } })
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
.then((data) => {
if (typeof data.stargazers_count !== "number") return;
try {
localStorage.setItem(STARS_CACHE_KEY, JSON.stringify({ count: data.stargazers_count, at: Date.now() }));
} catch {
/* ignore */
}
el.textContent = formatStarCount(data.stargazers_count);
})
.catch(() => {
if (!cached) el.textContent = "--";
});
}
function slugify(text) {
return String(text)
@@ -84,12 +44,15 @@ function langForSegment(segment) {
return match ? match[0] : DOCS_DEFAULT_LANG;
}
// The URL decides the language: /docs/zh/… is Chinese, /docs/latest/… (or an
// old version number) English. Only a URL that names no language — /docs/
// itself — falls back to the reader's stored choice, English by default.
function routeParts() {
const normalized = location.pathname.replace(/\/+$/, "");
const match = normalized.match(/^\/docs\/([^/]+)\/(.+)$/);
if (!match) {
const bare = normalized.match(/^\/docs\/([^/]+)$/);
const lang = bare ? langForSegment(bare[1]) : DOCS_DEFAULT_LANG;
const lang = bare && bare[1] !== "index.html" ? langForSegment(bare[1]) : storedLang();
return { lang, pageId: DOCS_DEFAULT_PAGE, canonical: false };
}
const lang = langForSegment(match[1]);
@@ -105,27 +68,6 @@ function resolvePage(pageId, lang) {
return pages.find((page) => page.id === pageId) || pages.find((page) => page.id === DOCS_DEFAULT_PAGE);
}
function storedLang() {
try {
const value = localStorage.getItem(LANG_KEY);
return value && value in DOCS_LANGUAGES ? value : null;
} catch {
return null;
}
}
function storeLang(lang) {
try {
localStorage.setItem(LANG_KEY, lang);
} catch {
/* ignore */
}
}
function browserLang() {
const first = (navigator.languages && navigator.languages[0]) || navigator.language || "";
return first.toLowerCase().startsWith("zh") ? "zh" : DOCS_DEFAULT_LANG;
}
function setAlternate(hreflang, href) {
let link = document.querySelector(`link[rel='alternate'][hreflang='${hreflang}']`);
@@ -156,17 +98,12 @@ function setMeta(page, lang) {
setAlternate("x-default", `${SITE}${canonicalPath(page.id, DOCS_DEFAULT_LANG)}`);
}
function applyChrome(lang, page) {
function applyChrome(lang) {
const ui = DOCS_UI[lang];
document.documentElement.lang = DOCS_LANGUAGES[lang].htmlLang;
document.querySelectorAll("[data-ui]").forEach((el) => {
const value = ui[el.getAttribute("data-ui")];
if (typeof value === "string") el.textContent = value;
});
document.querySelectorAll("[data-ui-nav]").forEach((el) => {
const value = ui.nav[el.getAttribute("data-ui-nav")];
if (typeof value === "string") el.textContent = value;
});
document.getElementById("docs-search")?.setAttribute("placeholder", ui.searchPlaceholder);
document.querySelector(".docs-sidebar")?.setAttribute("aria-label", ui.navLabel);
document.querySelector(".docs-outline")?.setAttribute("aria-label", ui.onThisPage);
@@ -177,16 +114,6 @@ function applyChrome(lang, page) {
`<option value="${item.name}">${item.label[lang] || item.name}</option>`
).join("");
}
const toggle = document.getElementById("lang-toggle");
if (toggle) {
const other = Object.keys(DOCS_LANGUAGES).find((code) => code !== lang) || DOCS_DEFAULT_LANG;
toggle.textContent = DOCS_LANGUAGES[other].label;
toggle.setAttribute("href", canonicalPath(page.id, other));
toggle.setAttribute("hreflang", DOCS_LANGUAGES[other].htmlLang);
toggle.setAttribute("lang", DOCS_LANGUAGES[other].htmlLang);
toggle.dataset.lang = other;
}
}
function navLink(page, currentId) {
@@ -277,8 +204,10 @@ function renderCurrent() {
history.replaceState({}, "", canonicalPath(page.id, lang) + location.hash);
}
current = { lang, pageId: page.id };
setPageLang(lang);
setMeta(page, lang);
applyChrome(lang, page);
applyChrome(lang);
renderNav(page.id, lang, document.getElementById("docs-search")?.value || "");
renderArticle(page, lang);
renderOutline(page);
@@ -286,23 +215,6 @@ function renderCurrent() {
document.getElementById("docs-article")?.focus({ preventScroll: true });
}
function initLanguage() {
const { lang, pageId } = routeParts();
const preferred = storedLang() || browserLang();
if (lang === DOCS_DEFAULT_LANG && preferred !== DOCS_DEFAULT_LANG) {
history.replaceState({}, "", canonicalPath(pageId, preferred) + location.hash);
}
document.getElementById("lang-toggle")?.addEventListener("click", (event) => {
const target = event.currentTarget.dataset.lang;
if (!target || !(target in DOCS_LANGUAGES)) return;
storeLang(target);
if (event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1) return;
event.preventDefault();
navigate(event.currentTarget.getAttribute("href"));
});
}
function initSearch() {
const search = document.getElementById("docs-search");
if (!search) return;
@@ -313,21 +225,12 @@ function initSearch() {
});
}
function initHeaderScroll() {
const header = document.getElementById("site-header");
if (!header) return;
const sync = () => header.classList.toggle("is-scrolled", window.scrollY > 10);
sync();
window.addEventListener("scroll", sync, { passive: true });
}
let current = { lang: DOCS_DEFAULT_LANG, pageId: DOCS_DEFAULT_PAGE };
window.addEventListener("popstate", renderCurrent);
initTheme();
initStars();
initLanguage();
initSite({ onToggle: (lang) => navigate(canonicalPath(current.pageId, lang)) });
initSearch();
initHeaderScroll();
renderCurrent();
// The sections are rendered after load, so the browser could not scroll to
// a #section in the URL on its own.
+4 -27
View File
@@ -6,15 +6,6 @@
background-size: 36px 36px, 36px 36px, auto;
}
.brand__text em {
color: var(--muted);
font-family: var(--mono);
font-size: 0.72rem;
font-style: normal;
font-weight: 600;
text-transform: uppercase;
}
.docs-shell {
display: grid;
grid-template-columns: 280px minmax(0, 1fr) 210px;
@@ -277,6 +268,10 @@
max-height: none;
}
.doc-section {
scroll-margin-top: 120px;
}
.doc-footer {
grid-template-columns: 1fr;
}
@@ -286,24 +281,6 @@
}
}
.lang-pill {
display: flex;
align-items: center;
min-height: 38px;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--surface);
color: var(--ink);
padding: 0 12px;
font-size: 0.86rem;
font-weight: 700;
white-space: nowrap;
}
.lang-pill:hover {
border-color: var(--accent);
}
.doc-section table {
width: 100%;
max-width: 860px;
+75 -86
View File
@@ -1,22 +1,9 @@
<!doctype html>
<html lang="en">
<html lang="en" data-lang-neutral>
<head>
<meta charset="utf-8">
<script>
(function () {
try {
var stored = localStorage.getItem("vibetrading-theme");
var resolved = stored === "dark" || stored === "light"
? stored
: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", resolved);
} catch (e) {
document.documentElement.setAttribute("data-theme", "light");
}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vibe-Trading Wiki - Finance Research Agent</title>
<title data-i18n="home.title">Vibe-Trading Wiki - Finance Research Agent</title>
<meta name="description" content="Vibe-Trading is a natural-language finance research agent for market data, backtesting, swarm analysis, trade journals, and research artifacts.">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta name="author" content="HKUDS">
@@ -31,38 +18,45 @@
<meta name="twitter:description" content="Natural-language finance research, backtesting, swarm analysis, and Shadow Account workflows.">
<meta name="twitter:image" content="https://vibetrading.wiki/assets/icon.png">
<link rel="canonical" href="https://vibetrading.wiki/home/">
<!-- site-head:start -->
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
<script type="module" src="/main.js?v=20260605"></script>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
<!-- site-head:end -->
<script type="module" src="/main.js?v=20260929d"></script>
</head>
<body>
<body data-section="home">
<!-- site-header:start -->
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading" data-i18n="nav.source">Source</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme">
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- site-header:end -->
<main>
<section class="hero">
@@ -72,23 +66,22 @@
<p class="hero__lead" data-i18n="hero.lead">Vibe-Trading connects natural-language prompts to market data, backtests, swarm analysis, trade-journal diagnostics, and reproducible research artifacts.</p>
<div class="hero__actions">
<a class="button button--primary" href="/docs/" data-i18n="hero.cta_docs">Read the docs</a>
<a class="button button--ghost" href="/docs/zh/getting-started/vibe-trading-overview" lang="zh-CN">中文文档</a>
<a class="button button--ghost" href="https://github.com/HKUDS/Vibe-Trading" data-i18n="hero.cta_source">View source</a>
</div>
</div>
<div class="hero__visual" aria-label="Vibe-Trading research terminal preview">
<div class="hero__visual" aria-label="Vibe-Trading research terminal preview" data-i18n-attr="aria-label=home.terminalLabel">
<div class="terminal">
<div class="terminal__bar">
<span></span><span></span><span></span>
<strong>vibe-trading</strong>
</div>
<div class="terminal__body">
<p><span class="prompt">$</span> vibe-trading run -p "Backtest BTC-USDT 20/50 MA for 2024"</p>
<p class="muted">route: crypto data -> strategy -> backtest -> run card</p>
<p><span class="ok">return</span> +18.6% <span class="warn">max drawdown</span> -7.4%</p>
<p data-i18n-html="home.term.prompt"><span class="prompt">$</span> vibe-trading run -p "Backtest BTC-USDT 20/50 MA for 2024"</p>
<p class="muted" data-i18n="home.term.route">route: crypto data -> strategy -> backtest -> run card</p>
<p data-i18n-html="home.term.result"><span class="ok">return</span> +18.6% <span class="warn">max drawdown</span> -7.4%</p>
<p><span class="prompt">$</span> vibe-trading --swarm-run investment_committee</p>
<p class="muted">workers: macro, quant, risk, catalyst</p>
<p><span class="ok">report</span> saved with evidence, metrics, and caveats</p>
<p class="muted" data-i18n="home.term.workers">workers: macro, quant, risk, catalyst</p>
<p data-i18n-html="home.term.report"><span class="ok">report</span> saved with evidence, metrics, and caveats</p>
</div>
</div>
<div class="market-strip" aria-hidden="true">
@@ -99,14 +92,14 @@
<section class="install-band" aria-labelledby="install-title">
<div class="section-head">
<p class="eyebrow">Install</p>
<h2 id="install-title">One command to start a research workspace.</h2>
<p class="eyebrow" data-i18n="home.install.eyebrow">Install</p>
<h2 id="install-title" data-i18n="home.install.title">One command to start a research workspace.</h2>
</div>
<div class="install-panel">
<div class="tabs" role="tablist" aria-label="Installation methods">
<div class="tabs" role="tablist" aria-label="Installation methods" data-i18n-attr="aria-label=home.install.tabsLabel">
<button role="tab" aria-selected="true" aria-controls="install-pypi" id="tab-pypi" data-tab="pypi">PyPI</button>
<button role="tab" aria-selected="false" aria-controls="install-uv" id="tab-uv" data-tab="uv">uv</button>
<button role="tab" aria-selected="false" aria-controls="install-source" id="tab-source" data-tab="source">Source</button>
<button role="tab" aria-selected="false" aria-controls="install-source" id="tab-source" data-tab="source" data-i18n="home.install.source">Source</button>
</div>
<pre id="install-pypi" role="tabpanel" aria-labelledby="tab-pypi"><code>pip install vibe-trading-ai
vibe-trading init
@@ -124,111 +117,107 @@ vibe-trading</code></pre>
<section class="knowledge-band" aria-labelledby="knowledge-title">
<div class="section-head">
<p class="eyebrow">Wiki</p>
<h2 id="knowledge-title">Explore Vibe-Trading.</h2>
<h2 id="knowledge-title" data-i18n="home.explore.title">Explore Vibe-Trading.</h2>
</div>
<div class="knowledge-grid">
<a class="knowledge-card" href="/docs/">
<strong>Product Docs</strong>
<strong data-i18n="home.explore.docs">Product Docs</strong>
</a>
<a class="knowledge-card" href="/tutorials/">
<strong>Hands-on AI Quant</strong>
<strong data-i18n="home.explore.tutorials">Hands-on AI Quant</strong>
</a>
<a class="knowledge-card" href="/alpha-library/">
<strong>Alpha Library</strong>
<strong data-i18n="nav.alpha">Alpha Library</strong>
</a>
<a class="knowledge-card" href="/research-lab/">
<strong>Research Lab</strong>
<strong data-i18n="nav.lab">Research Lab</strong>
</a>
</div>
</section>
<section class="workflow" aria-labelledby="workflow-title">
<div class="section-head">
<p class="eyebrow">Research loop</p>
<h2 id="workflow-title">Every answer keeps the trail inspectable.</h2>
<p class="eyebrow" data-i18n="home.loop.eyebrow">Research loop</p>
<h2 id="workflow-title" data-i18n="home.loop.title">Every answer keeps the trail inspectable.</h2>
</div>
<div class="step-grid">
<article>
<span>01</span>
<h3>Route</h3>
<p>Select skills, data sources, tools, and swarm teams from the request.</p>
<h3 data-i18n="home.loop.route">Route</h3>
<p data-i18n="home.loop.routeText">Select skills, data sources, tools, and swarm teams from the request.</p>
</article>
<article>
<span>02</span>
<h3>Ground</h3>
<p>Fetch market data, documents, URLs, broker journals, or local files at runtime.</p>
<h3 data-i18n="home.loop.ground">Ground</h3>
<p data-i18n="home.loop.groundText">Fetch market data, documents, URLs, broker journals, or local files at runtime.</p>
</article>
<article>
<span>03</span>
<h3>Test</h3>
<p>Run backtests, factor analysis, options checks, validation, and report generation.</p>
<h3 data-i18n="home.loop.test">Test</h3>
<p data-i18n="home.loop.testText">Run backtests, factor analysis, options checks, validation, and report generation.</p>
</article>
<article>
<span>04</span>
<h3>Deliver</h3>
<p>Return run cards, exports, reports, tool traces, and caveats for the next session.</p>
<h3 data-i18n="home.loop.deliver">Deliver</h3>
<p data-i18n="home.loop.deliverText">Return run cards, exports, reports, tool traces, and caveats for the next session.</p>
</article>
</div>
</section>
<section class="feature-band" aria-labelledby="features-title">
<div class="section-head">
<p class="eyebrow">Capabilities</p>
<h2 id="features-title">Built for finance research, not brokerage execution.</h2>
<p class="eyebrow" data-i18n="home.features.eyebrow">Capabilities</p>
<h2 id="features-title" data-i18n="home.features.title">Built for finance research, not brokerage execution.</h2>
</div>
<div class="feature-grid">
<article class="feature-card">
<img src="/assets/feature-self-improving-trading-agent.png" alt="Self-improving trading agent">
<h3>Agent Harness</h3>
<p>Persistent memory, session search, editable skills, context compression, and tool-batched research runs.</p>
<img src="/assets/feature-self-improving-trading-agent.png" alt="Self-improving trading agent" data-i18n-attr="alt=home.features.agentAlt">
<h3 data-i18n="home.features.agent">Agent Harness</h3>
<p data-i18n="home.features.agentText">Persistent memory, session search, editable skills, context compression, and tool-batched research runs.</p>
</article>
<article class="feature-card">
<img src="/assets/feature-cross-market-data-backtesting.png" alt="Cross-market data and backtesting">
<h3>Backtesting</h3>
<p>Eleven engines across A-shares, global, Indian, Korean and Vietnamese equities, crypto, futures, forex, cross-market composites, and options portfolios.</p>
<img src="/assets/feature-cross-market-data-backtesting.png" alt="Cross-market data and backtesting" data-i18n-attr="alt=home.features.backtestAlt">
<h3 data-i18n="home.features.backtest">Backtesting</h3>
<p data-i18n="home.features.backtestText">Eleven engines across A-shares, global, Indian, Korean and Vietnamese equities, crypto, futures, forex, cross-market composites, and options portfolios.</p>
</article>
<article class="feature-card">
<img src="/assets/feature-multi-agent-trading-teams.png" alt="Multi-agent trading teams">
<h3>Swarm Teams</h3>
<p>Investment committee, quant desk, risk review, macro, crypto, and other preset research teams.</p>
<img src="/assets/feature-multi-agent-trading-teams.png" alt="Multi-agent trading teams" data-i18n-attr="alt=home.features.swarmAlt">
<h3 data-i18n="home.features.swarm">Swarm Teams</h3>
<p data-i18n="home.features.swarmText">Investment committee, quant desk, risk review, macro, crypto, and other preset research teams.</p>
</article>
<article class="feature-card">
<img src="/assets/feature-shadow-account.png" alt="Shadow Account workflow">
<h3>Shadow Account</h3>
<p>Parse broker journals, extract implicit rules, run counterfactual backtests, and render audit reports.</p>
<img src="/assets/feature-shadow-account.png" alt="Shadow Account workflow" data-i18n-attr="alt=home.features.shadowAlt">
<h3 data-i18n="home.features.shadow">Shadow Account</h3>
<p data-i18n="home.features.shadowText">Parse broker journals, extract implicit rules, run counterfactual backtests, and render audit reports.</p>
</article>
</div>
</section>
<section class="safety-band" aria-label="Bounded autonomy">
<section class="safety-band" aria-label="Bounded autonomy" data-i18n-attr="aria-label=home.safety.label">
<div>
<p class="eyebrow">Boundary</p>
<h2>You set the limits.</h2>
<p class="eyebrow" data-i18n="home.safety.eyebrow">Boundary</p>
<h2 data-i18n="home.safety.title">You set the limits.</h2>
</div>
<p>Vibe-Trading is built for research, simulation, and backtesting. Any live trading is opt-in and read-only by default — it runs only through a broker you authorize yourself (e.g. Robinhood Agentic Trading), strictly within the limits you set, and you can halt it instantly. It holds no funds, runs no execution venue, and is not investment advice. Experimental — use at your own risk.</p>
<p data-i18n="home.safety.text">Vibe-Trading is built for research, simulation, and backtesting. Any live trading is opt-in and read-only by default — it runs only through a broker you authorize yourself (e.g. Robinhood Agentic Trading), strictly within the limits you set, and you can halt it instantly. It holds no funds, runs no execution venue, and is not investment advice. Experimental — use at your own risk.</p>
</section>
</main>
<style>
.footer-traffic[hidden] { display: none; }
.footer-traffic { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem 1.25rem; margin-top: .75rem; font-size: .85rem; opacity: .8; }
.footer-traffic .ft-item strong { font-variant-numeric: tabular-nums; font-weight: 700; }
.footer-traffic .ft-note { font-size: .72rem; opacity: .6; }
</style>
<!-- site-footer:start -->
<footer class="site-footer">
<span>Vibe-Trading Wiki</span>
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> AI agents</span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> humans</span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> installs/mo</span>
<span class="ft-note">visitors to this site · anonymous · first-party</span>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
<!-- site-footer:end -->
</body>
</html>
-13
View File
@@ -1,13 +0,0 @@
{
"nav.docs": "Docs",
"nav.tutorials": "Tutorials",
"nav.alpha": "Alpha Library",
"nav.lab": "Research Lab",
"nav.source": "Source",
"nav.pypi": "PyPI",
"hero.eyebrow": "Open-source finance research agent",
"hero.title": "Turn market questions into runnable research.",
"hero.lead": "Vibe-Trading connects natural-language prompts to market data, backtests, swarm analysis, trade-journal diagnostics, and reproducible research artifacts.",
"hero.cta_docs": "Read the docs",
"hero.cta_source": "View source"
}
+109
View File
@@ -0,0 +1,109 @@
{
"header.homeLabel": "Vibe-Trading 首页",
"header.navLabel": "主导航",
"header.themeLabel": "切换配色主题",
"nav.home": "首页",
"nav.docs": "文档",
"nav.tutorials": "教程",
"nav.alpha": "因子库",
"nav.lab": "研究室",
"footer.agents": "AI 智能体",
"footer.humans": "人类访客",
"footer.installs": "月安装量",
"footer.note": "本站访问统计 · 匿名 · 第一方",
"home.title": "Vibe-Trading Wiki - 金融研究智能体",
"hero.eyebrow": "开源金融研究智能体",
"hero.title": "把市场问题变成可运行的研究。",
"hero.lead": "Vibe-Trading 把自然语言提问接到行情数据、回测、多智能体分析、交割单诊断和可复现的研究产物上。",
"hero.cta_docs": "阅读文档",
"hero.cta_source": "查看源码",
"home.terminalLabel": "Vibe-Trading 研究终端示意",
"home.install.eyebrow": "安装",
"home.install.title": "一条命令,开一个研究工作台。",
"home.install.tabsLabel": "安装方式",
"home.install.source": "源码",
"home.explore.title": "探索 Vibe-Trading。",
"home.explore.docs": "产品文档",
"home.explore.tutorials": "AI 量化实战",
"home.loop.eyebrow": "研究闭环",
"home.loop.title": "每个答案都留下可追溯的轨迹。",
"home.loop.route": "路由",
"home.loop.routeText": "根据问题选择技能、数据源、工具和多智能体团队。",
"home.loop.ground": "取证",
"home.loop.groundText": "运行时获取行情、文档、网页、交割单或本地文件。",
"home.loop.test": "检验",
"home.loop.testText": "跑回测、因子分析、期权检查和验证,并生成报告。",
"home.loop.deliver": "交付",
"home.loop.deliverText": "交付运行卡、导出文件、报告、工具轨迹和注意事项,留给下一次会话。",
"home.features.eyebrow": "能力",
"home.features.title": "为金融研究而建,不是券商执行系统。",
"home.features.agentAlt": "会自我改进的交易智能体",
"home.features.agent": "智能体框架",
"home.features.agentText": "持久记忆、会话搜索、可编辑的技能、上下文压缩和批量工具调用的研究运行。",
"home.features.backtestAlt": "跨市场数据与回测",
"home.features.backtest": "回测",
"home.features.backtestText": "11 个回测引擎,覆盖 A 股、全球、印度、韩国和越南股票,以及加密货币、期货、外汇、跨市场组合和期权组合。",
"home.features.swarmAlt": "多智能体交易团队",
"home.features.swarm": "多智能体团队",
"home.features.swarmText": "投委会、量化台、风控评审、宏观、加密等预设研究团队。",
"home.features.shadowAlt": "影子账户流程",
"home.features.shadow": "影子账户",
"home.features.shadowText": "解析交割单,提炼隐含规则,跑反事实回测,生成审计报告。",
"home.safety.label": "有边界的自主",
"home.safety.eyebrow": "边界",
"home.safety.title": "上限由你来定。",
"home.safety.text": "Vibe-Trading 面向研究、模拟和回测。实盘交易需要你主动开启,默认只读——只通过你自己授权的券商(例如 Robinhood Agentic Trading)执行,严格限定在你设定的范围内,并且可以随时一键停止。它不托管资金,不运营交易场所,也不构成投资建议。本项目仍属实验性质,使用风险自负。",
"tutorials.title": "AI 量化实战 | Vibe-Trading Wiki",
"tutorials.heading": "AI 量化实战。",
"tutorials.lede": "把金融想法变成可检查的 Vibe-Trading 工作流的实战指南。",
"tutorials.listLabel": "教程列表",
"tutorials.beginner.tag": "入门",
"tutorials.beginner.title": "Vibe-Trading 入门教程",
"tutorials.beginner.text": "面向非金融专业读者,解释因子、策略、回测、多市场数据源、券商连接器和影子账户。",
"lab.title": "研究室 | Vibe-Trading Wiki",
"lab.heading": "研究室。",
"lab.lede": "长篇分析、可复现的回测,以及对量化想法的复盘。数据和代码公开,每个结论都只差一条命令就能复现。",
"lab.postsLabel": "最新文章",
"lab.gtja.title": "国泰君安 191 因子,2026 年还剩哪些有效?",
"lab.gtja.excerpt": "我们用沪深300 在 2018&ndash;2025 年的数据,重新检验了国泰君安 2014 年发布的 191 个短周期因子:整体 IC、各主题的存活率,以及至今仍然有效的公式——一条命令即可复现。",
"lab.gtja.meta": "<span>Alpha Zoo</span> &middot; <span>国泰君安 191</span> &middot; <span>沪深300</span> &middot; <span>8 年(2018&ndash;2025)</span>",
"alpha.title": "因子库 | Vibe-Trading Wiki",
"alpha.heading": "因子库。",
"alpha.lede": "462 个现成量化因子,分属五个因子集。一条命令即可在你的股票池上评测任意因子集。",
"alpha.stats.total": "因子总数",
"alpha.stats.zoos": "因子集",
"alpha.stats.generated": "最近生成",
"alpha.stats.generatedVia": "由 vibe-trading alpha export-manifest 生成",
"alpha.stats.live": "实盘交易",
"alpha.stats.optIn": "需主动开启",
"alpha.stats.broker": "经由你授权的券商",
"alpha.zoo.qlib158": "微软 Qlib 的 158 个生产级因子,已移植到面板 API。",
"alpha.zoo.alpha101": "Kakushadze(2015)提出的 101 个短周期公式化因子。",
"alpha.zoo.gtja191": "国泰君安 191 个因子——A 股微观结构与量价主题。",
"alpha.zoo.academic": "精选自学术资产定价文献的因子。",
"alpha.zoo.fundamental": "按披露日对齐的美股 SEC 基本面数据,回测永远看不到尚未公布的数字。",
"alpha.zoo.qlib158Name": "Qlib Alpha158",
"alpha.zoo.alpha101Name": "Kakushadze 101 公式化因子",
"alpha.zoo.gtja191Name": "国泰君安 191",
"alpha.zoo.academicName": "学术异象因子",
"alpha.zoo.fundamentalName": "时点基本面因子",
"alpha.start.title": "快速上手",
"alpha.start.install": "# 安装",
"alpha.start.list": "# 列出某个因子集的全部因子",
"alpha.start.bench": "# 在你的股票池和区间上评测一个因子集",
"alpha.page.themes": "主题与适用范围",
"alpha.page.formula": "公式",
"alpha.page.definition": "定义",
"alpha.page.notes": "说明",
"alpha.page.run": "运行",
"alpha.page.alphasUnit": "个因子",
"alpha.page.theme": "主题",
"alpha.page.universe": "适用范围",
"alpha.page.decay": "衰减期",
"alpha.page.warmup": "预热期",
"home.term.prompt": "<span class=\"prompt\">$</span> vibe-trading run -p \"回测 BTC-USDT 2024 年 20/50 均线策略\"",
"home.term.route": "路由:加密行情 -> 策略 -> 回测 -> 运行卡",
"home.term.result": "<span class=\"ok\">收益</span> +18.6% <span class=\"warn\">最大回撤</span> -7.4%",
"home.term.workers": "成员:宏观、量化、风控、催化剂",
"home.term.report": "<span class=\"ok\">报告</span>已保存,附证据、指标和注意事项"
}
+3 -115
View File
@@ -1,56 +1,6 @@
import { initTheme } from "./theme.js";
const REPO = "HKUDS/Vibe-Trading";
const API = `https://api.github.com/repos/${REPO}`;
const STARS_CACHE_KEY = "vibetrading-github-stars";
const STARS_TTL_MS = 12 * 60 * 60 * 1000;
function formatStarCount(n) {
if (typeof n !== "number" || !Number.isFinite(n) || n < 0) return "--";
if (n < 1000) return String(Math.round(n));
if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;
return `${(n / 1_000_000).toFixed(1)}m`;
}
function readStarsCache() {
try {
const raw = localStorage.getItem(STARS_CACHE_KEY);
if (!raw) return null;
const value = JSON.parse(raw);
if (typeof value.count !== "number" || typeof value.at !== "number") return null;
return value;
} catch {
return null;
}
}
function writeStarsCache(count) {
try {
localStorage.setItem(STARS_CACHE_KEY, JSON.stringify({ count, at: Date.now() }));
} catch {
/* private mode */
}
}
function initStars() {
const el = document.getElementById("star-count");
if (!el) return;
const cached = readStarsCache();
if (cached) el.textContent = formatStarCount(cached.count);
if (cached && Date.now() - cached.at < STARS_TTL_MS) return;
fetch(API, { headers: { Accept: "application/vnd.github+json" } })
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
.then((data) => {
if (typeof data.stargazers_count !== "number") return;
writeStarsCache(data.stargazers_count);
el.textContent = formatStarCount(data.stargazers_count);
})
.catch(() => {
if (!cached) el.textContent = "--";
});
}
import { initSite } from "/site.js?v=20260929d";
// Home page install tabs; a no-op on pages without them.
function initInstallTabs() {
const tabs = Array.from(document.querySelectorAll("[data-tab]"));
if (!tabs.length) return;
@@ -86,67 +36,5 @@ function initInstallTabs() {
}
}
async function applyLocale() {
try {
const response = await fetch("/locales/en.json");
if (!response.ok) return;
const messages = await response.json();
document.querySelectorAll("[data-i18n]").forEach((el) => {
const key = el.getAttribute("data-i18n");
if (key && messages[key] != null) el.textContent = messages[key];
});
document.querySelectorAll("[data-i18n-html]").forEach((el) => {
const key = el.getAttribute("data-i18n-html");
if (key && messages[key] != null) el.innerHTML = messages[key];
});
} catch {
/* static fallback text is already in the HTML */
}
}
function initHeaderScroll() {
const header = document.getElementById("site-header");
if (!header) return;
const sync = () => header.classList.toggle("is-scrolled", window.scrollY > 10);
sync();
window.addEventListener("scroll", sync, { passive: true });
}
function formatTrafficCount(n) {
n = Number(n) || 0;
if (n < 1000) return String(n);
return `${(n / 1000).toFixed(n < 10000 ? 1 : 0)}`.replace(/\.0$/, "") + "K";
}
// Render the footer traffic counter from /api/stats (agent vs human visitors to
// this site, counted server-side by the Pages middleware, plus PyPI installs).
// The block stays hidden until real data arrives, so it never shows dashes.
function initTraffic() {
const block = document.getElementById("footer-traffic");
if (!block) return;
fetch("/api/stats")
.then((r) => (r.ok ? r.json() : null))
.then((stats) => {
if (!stats) return;
const set = (id, value) => {
const el = document.getElementById(id);
if (el) el.textContent = value;
};
if (stats.web) {
set("vt-agents", formatTrafficCount(stats.web.agent));
set("vt-humans", formatTrafficCount(stats.web.human));
}
if (stats.pypi) set("vt-installs", formatTrafficCount(stats.pypi.last_month));
block.hidden = false;
})
.catch(() => {
/* stats unavailable — leave the block hidden */
});
}
initTheme();
initStars();
initSite();
initInstallTabs();
initHeaderScroll();
applyLocale();
initTraffic();
+15
View File
@@ -0,0 +1,15 @@
<footer class="site-footer">
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
+6
View File
@@ -0,0 +1,6 @@
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
+26
View File
@@ -0,0 +1,26 @@
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
+42 -39
View File
@@ -1,22 +1,9 @@
<!doctype html>
<html lang="en">
<html lang="en" data-lang-neutral>
<head>
<meta charset="utf-8">
<script>
(function () {
try {
var stored = localStorage.getItem("vibetrading-theme");
var resolved = stored === "dark" || stored === "light"
? stored
: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", resolved);
} catch (e) {
document.documentElement.setAttribute("data-theme", "light");
}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Research Lab | Vibe-Trading Wiki</title>
<title data-i18n="lab.title">Research Lab | Vibe-Trading Wiki</title>
<meta name="description" content="Research Lab for Vibe-Trading.">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta property="og:type" content="website">
@@ -26,67 +13,83 @@
<meta property="og:image" content="https://vibetrading.wiki/assets/icon.png">
<meta property="og:url" content="https://vibetrading.wiki/research-lab/">
<link rel="canonical" href="https://vibetrading.wiki/research-lab/">
<!-- site-head:start -->
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
<script type="module" src="/main.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
<!-- site-head:end -->
<script type="module" src="/main.js?v=20260929d"></script>
</head>
<body>
<body data-section="lab">
<!-- site-header:start -->
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">Source</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme">
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- site-header:end -->
<main class="section-page">
<section class="section-hero section-hero--blank">
<p class="eyebrow">Research Lab</p>
<h1>Research Lab.</h1>
<p class="section-hero__lede">Long-form analyses, replicable backtests, and post-mortems on quant ideas. Open data + open code; every claim is one CLI command away.</p>
<p class="eyebrow" data-i18n="nav.lab">Research Lab</p>
<h1 data-i18n="lab.heading">Research Lab.</h1>
<p class="section-hero__lede" data-i18n="lab.lede">Long-form analyses, replicable backtests, and post-mortems on quant ideas. Open data + open code; every claim is one CLI command away.</p>
</section>
<section class="posts-grid" aria-label="Latest posts">
<section class="posts-grid" aria-label="Latest posts" data-i18n-attr="aria-label=lab.postsLabel">
<article class="post-card">
<p class="post-card__date">2026-05-17</p>
<h2 class="post-card__title">
<a href="/research-lab/posts/alpha-191-in-2026.html">Which of the 191 GTJA alphas still work in 2026?</a>
<a href="/research-lab/posts/alpha-191-in-2026.html" data-href-zh="/research-lab/posts/alpha-191-in-2026-zh.html" data-i18n="lab.gtja.title">Which of the 191 GTJA alphas still work in 2026?</a>
</h2>
<p class="post-card__excerpt">
<p class="post-card__excerpt" data-i18n-html="lab.gtja.excerpt">
We re-ran the 191 short-horizon alphas published by Guotai Junan in 2014 against CSI 300, 2018&ndash;2025. Aggregate IC, theme survival rates, and the formulas that still hold up &mdash; reproducible with one CLI command.
</p>
<p class="post-card__meta"><span>Alpha Zoo</span> &middot; <span>GTJA 191</span> &middot; <span>CSI 300</span> &middot; <span>8 years (2018&ndash;2025)</span></p>
<p class="post-card__meta" data-i18n-html="lab.gtja.meta"><span>Alpha Zoo</span> &middot; <span>GTJA 191</span> &middot; <span>CSI 300</span> &middot; <span>8 years (2018&ndash;2025)</span></p>
</article>
</section>
</main>
<!-- site-footer:start -->
<footer class="site-footer">
<span>Vibe-Trading Wiki</span>
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
<!-- site-footer:end -->
</body>
</html>
@@ -0,0 +1,526 @@
<!DOCTYPE html>
<html lang="zh-CN" data-page-lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self'; img-src 'self' data: https://vibetrading.wiki; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.github.com;">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>国泰君安 191 因子,2026 年还剩哪些有效? | Vibe-Trading 研究实验室</title>
<meta name="description" content="对国泰君安 2014 年发布的 191 个短周期 alpha 因子做样本外检验,在沪深300、2018-2025 年区间上重新评估:还有多少存活、多少方向反转、哪些主题最经得起时间考验。">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta name="author" content="Vibe-Trading 研究实验室">
<meta property="og:type" content="article">
<meta property="og:site_name" content="Vibe-Trading Wiki">
<meta property="og:title" content="国泰君安 191 因子,2026 年还剩哪些有效?">
<meta property="og:description" content="在沪深300(2018-2025)上对 2014 年国泰君安 191 个短周期 alpha 因子做样本外 IC 复盘:存活数量汇总、分主题拆解与复现方法。">
<meta property="og:url" content="https://vibetrading.wiki/research-lab/posts/alpha-191-in-2026-zh.html">
<link rel="canonical" href="https://vibetrading.wiki/research-lab/posts/alpha-191-in-2026-zh.html">
<link rel="alternate" hreflang="en" href="/research-lab/posts/alpha-191-in-2026.html">
<link rel="alternate" hreflang="zh-CN" href="/research-lab/posts/alpha-191-in-2026-zh.html">
<link rel="alternate" hreflang="x-default" href="/research-lab/posts/alpha-191-in-2026.html">
<!-- site-head:start -->
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
<!-- site-head:end -->
<script type="module" src="/main.js?v=20260929d"></script>
<style>
.post {
width: min(760px, calc(100% - 32px));
margin: 0 auto;
padding: 72px 0 96px;
}
.post header.post-head {
margin-bottom: 48px;
border-bottom: 1px solid var(--line);
padding-bottom: 32px;
}
.post header.post-head h1 {
font-family: var(--serif);
font-size: clamp(2.2rem, 5vw, 3.6rem);
line-height: 1.05;
margin: 12px 0 16px;
letter-spacing: -0.01em;
}
.post header.post-head .byline {
color: var(--muted);
font-family: var(--mono);
font-size: 0.84rem;
letter-spacing: 0.04em;
}
.post section {
margin: 48px 0;
}
.post h2 {
font-family: var(--serif);
font-size: clamp(1.6rem, 3vw, 2.2rem);
line-height: 1.15;
margin: 0 0 16px;
}
.post h3 {
font-family: var(--sans);
font-size: 1.05rem;
font-weight: 700;
margin: 28px 0 10px;
letter-spacing: 0;
}
.post p, .post li {
color: var(--ink);
font-size: 1.04rem;
line-height: 1.7;
}
.post p.lede {
font-family: var(--serif);
font-size: 1.22rem;
color: var(--ink);
line-height: 1.55;
}
.post ul, .post ol {
padding-left: 1.4rem;
}
.post li + li {
margin-top: 8px;
}
.post code, .post pre {
font-family: var(--mono);
font-size: 0.92rem;
}
.post pre {
overflow-x: auto;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--surface-2);
padding: 14px 16px;
line-height: 1.55;
}
.post code.formula {
display: inline-block;
padding: 1px 6px;
border: 1px solid var(--line);
border-radius: 4px;
background: var(--surface-2);
}
.post pre.formula-block {
white-space: pre-wrap;
word-break: break-word;
}
.findings-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin: 24px 0;
padding: 24px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--surface-2);
}
.findings-stats .stat {
display: flex;
flex-direction: column;
gap: 4px;
}
.findings-stats .stat .label {
color: var(--muted);
font-family: var(--mono);
font-size: 0.74rem;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.findings-stats .stat .num {
font-family: var(--serif);
font-size: 2.2rem;
line-height: 1;
color: var(--accent);
}
.findings-stats .stat.reversed .num { color: var(--danger); }
.findings-stats .stat.dead .num { color: var(--muted); }
.theme-table {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
font-size: 0.96rem;
}
.theme-table th, .theme-table td {
padding: 10px 12px;
text-align: left;
border-bottom: 1px solid var(--line);
}
.theme-table th {
font-family: var(--mono);
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
.theme-table tbody tr:last-child td {
border-bottom: none;
}
.callout {
margin: 24px 0;
padding: 16px 20px;
border-left: 3px solid var(--accent);
background: var(--surface-2);
border-radius: 0 6px 6px 0;
}
.callout.warn {
border-left-color: var(--accent-2);
}
.alpha-card {
margin: 16px 0;
padding: 16px 18px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--surface);
}
.alpha-card .alpha-id {
font-family: var(--mono);
font-size: 0.84rem;
color: var(--accent);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.alpha-card .alpha-note {
margin-top: 8px;
color: var(--muted);
font-size: 0.95rem;
}
.post .cta-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 24px 0 8px;
}
.post .cta-row a {
display: inline-flex;
align-items: center;
min-height: 42px;
padding: 0 16px;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--surface);
font-weight: 600;
font-size: 0.95rem;
}
.post .cta-row a.primary {
border-color: var(--ink);
background: var(--ink);
color: var(--bg);
}
.footnote {
margin-top: 48px;
padding-top: 16px;
border-top: 1px solid var(--line);
color: var(--muted);
font-size: 0.86rem;
}
</style>
</head>
<body data-section="lab">
<!-- site-header:start -->
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- site-header:end -->
<main>
<article class="post">
<header class="post-head">
<p class="byline">研究实验室 &middot; ALPHA ZOO &middot; 样本外研究</p>
<h1>国泰君安 191 因子,2026 年还剩哪些有效?</h1>
<p class="lede">2014 年,国泰君安证券发布了一份研究报告,系统整理了面向 A 股市场的 191 个短周期 alpha 因子。十二年过去,T+1 交收制度依旧,行业轮动格局已两度翻转,散户资金流处于数十年来的高位&mdash;&mdash;这些公式里还有多少能产生可靠的信号?我们在沪深300 上、以 2018&ndash;2025 年为区间,对全部 191 个因子做了基准测试,得到的答案远比一个数字更耐人寻味。</p>
</header>
<section id="tldr">
<h2>要点速览</h2>
<p>国泰君安 2014 年发布的 191 个短周期 alpha 因子中,在沪深300、2018&ndash;2025 年区间上,只有 10 个(5%)仍能通过我们的存活筛选&mdash;&mdash;平均 IC 为正且高于 0.02、t 统计量高于 2、且至少 55% 的交易日 IC 为正。另有 15 个(8%)<em>方向反转</em>且具有统计显著性,如今成了反向信号。其余 165 个(87%)要么已衰减到不再显著,要么因缺少我们没有的行业标签而无法计算,要么触发了 NaN 占比 &gt;95% 的预热期保护。这 10 个存活因子集中在微观结构 / K 线形态一族,而不是我们原先预期的原始量价交互一族。具体数量见下文;方法刻意保守,局限性也不容忽视。</p>
<div class="findings-stats" aria-label="因子存活数量汇总">
<div class="stat alive">
<span class="label">存活</span>
<span class="num"><span class="num-alive">10</span></span>
<span>191 个因子中</span>
</div>
<div class="stat reversed">
<span class="label">反转</span>
<span class="num"><span class="num-reversed">15</span></span>
<span>191 个因子中</span>
</div>
<div class="stat dead">
<span class="label">失效</span>
<span class="num"><span class="num-dead">165</span></span>
<span>191 个因子中</span>
</div>
</div>
<p class="callout">下文数字均为对内置 gtja191 因子集实际运行 W4.a bench 的输出(沪深300,2018&ndash;2025),可用文末的 CLI 命令复现。</p>
</section>
<section id="background">
<h2>背景</h2>
<p>国泰君安 2014 年的这份研究报告&mdash;&mdash;题为《191 个短周期交易型 alpha 因子》(英文译名 <em>191 Short-Period Transactional Alpha Factors</em>)&mdash;&mdash;问世于中国量化发展的一个特殊时点。当时市场刚走出多年的横盘震荡,散户参与度正在回升,而 Kakushadze 式的“公式化 alpha”模板在此前几年已借由他 2015 年的 arXiv 预印本在国际上流行开来。回头看,国泰君安团队拿出的是迄今针对 A 股公开发布的最系统的短周期因子目录之一:191 个编号公式,每个都只是基于日频 OHLCV 与换手率的寥寥几行算子运算。</p>
<p>报告中的公式读起来像是关于市场微观结构的第一性原理假设,被翻译成了便于用 pandas 实现的算术。有些事后看来显而易见(基于排名的五日反转;成交量与收盘价的相关性);另一些则相当奇特(报告中
<code class="formula">SUMIF</code> / <code class="formula">FILTER</code> / <code class="formula">REGBETA</code>
的组合嵌套深达四到五层算子)。用现代量化的话说,它们是一个<em>先验库</em>:每个公式都编码了某个人的判断&mdash;&mdash;哪种微观结构规律可以在 1&ndash;5 天的周期上被利用。</p>
<p>这件事在 2026 年之所以重要,原因很简单:短周期 alpha 的衰减速度比任何其他类别都快。长周期的价值或质量因子不做任何调整也可能存活十年;而建立在量价交互上的 1&ndash;5 天公式大概做不到。在因子研究的尺度上,十二年的样本外几乎等同于永恒。A 股市场本身也变了:T+1 交收仍在,但机构与散户的结构已经翻转,科创板与创业板注册制改革重新定价了小盘股风险,公募基金带有高频色彩的交易执行也压缩了许多明显的均值回归窗口。</p>
<p>于是我们做了这次检验。下面先介绍方法,数字随后给出。</p>
</section>
<section id="method">
<h2>方法</h2>
<p>这次检验刻意做成<em>截面信息系数(IC)研究</em>,而不是完整回测。要回答的问题是:<em>这个 alpha 对股票的排序,是否在整个窗口内平均而言、稳健地与次日收益相关?</em>更高保真度的策略回测(含交易成本、仓位限制、行业中性化、按衰减系数加权的组合)是另一个问题,不在本文讨论范围内。</p>
<h3>股票池</h3>
<p>沪深300。流动性最好的 300 只 A 股,由指数公司每半年调整一次成分。我们在每个调仓日使用指数的现行成分股,因此股票池随指数漂移。相对于固定不变的股票池,这会带来偏差(被调出沪深300 的恰恰是流动性已经衰退的个股,这可能使 IC 估计产生偏差),但它与实际部署时使用该指数的方式一致。</p>
<h3>区间</h3>
<p>2018-01-02 至 2025-12-31。八个日历年,约 1,940 个交易日,相对 2014 年的报告完全处于样本外。窗口刻意覆盖了 2018 年的熊市、2020&ndash;2021 年流动性驱动的上涨、2022 年的回撤以及 2024&ndash;2025 年的震荡市&mdash;&mdash;因此平均 IC 是跨市场状态平均的结果,而不是挑选有利市场状态得出的。</p>
<h3>信号定义</h3>
<p>对每个 alpha 和每个交易日 <em>t</em>,我们计算股票池内每只股票的因子值,再在截面上做排名变换,映射到 <code class="formula">[0, 1]</code>。远期收益为从 <em>t</em> 日收盘到 <em>t+1</em> 日收盘的 1 日对数收益率,同样做截面排名变换。第 <em>t</em> 日的 Spearman IC 即两条排名序列的 Pearson 相关系数。我们报告所有有效交易日的平均 IC、IC 序列的 t 统计量,以及 IC 为正的交易日占比。</p>
<h3>分类标准</h3>
<ul>
<li><strong>存活。</strong>平均 IC &gt; 0.02、t-stat(IC) &gt; 2,且 IC &gt; 0 的交易日占比 &ge; 55%。三个条件须同时满足。</li>
<li><strong>反转。</strong>平均 IC &lt; &minus;0.02 且 t-stat(IC) &lt; &minus;2(即上述条件的符号翻转版)。原报告设定该因子以某一方向预测收益;我们观察到它以相反方向预测,且具有统计显著性。</li>
<li><strong>失效。</strong>其余所有情况:|平均 IC| &le; 0.02,或 |t-stat| &le; 2,或该因子无法在窗口内干净地计算(例如需要我们没有的日内逐笔数据)。</li>
</ul>
<h3>方法本身带来的局限</h3>
<p>以下三个刻意的选择限定了结果应当如何解读:</p>
<ul>
<li>IC 为<em>向前 1 日</em>,不做衰减平滑。decay-3 或 decay-5 IC(原报告偏好的周期)会得出略有不同的数字;我们会在后续文章中发布。</li>
<li>不做交易成本调整。一个高 IC、每日调仓换手率接近 100% 的因子,在扣除 5&ndash;10 bps 的现实双边成本后,实际上无法盈利。我们在 bench 产出物中把换手率与 IC 一并报告,但这里的存活分类没有考虑它。</li>
<li>不做行业中性化。报告中部分因子依赖行业映射(<code class="formula">indneutralize</code>);对这些因子,我们退而用每日截面均值替代,这是一种严格更弱的中性化。受影响的因子在逐因子输出中有标注。</li>
</ul>
<h3>所用工具</h3>
<pre><code>$ vibe-trading alpha bench --zoo gtja191 --universe csi300 \
--period 2018-2025 --top 20</code></pre>
<p>一条命令即可。输出为一份 HTML 报告,包含逐因子的 IC、t-stat、换手率和 IC 时间序列图。复现方法见文末。</p>
</section>
<section id="findings">
<h2>结果</h2>
<h3>总体存活情况</h3>
<p>再列一次核心数字,附带同样的说明:最终数字在 W4.a bench 跑完后落定:</p>
<div class="findings-stats" aria-label="因子存活数量汇总(结果部分重复展示)">
<div class="stat alive">
<span class="label">存活</span>
<span class="num"><span class="num-alive">10</span></span>
<span>通过全部三项筛选</span>
</div>
<div class="stat reversed">
<span class="label">反转</span>
<span class="num"><span class="num-reversed">15</span></span>
<span>如今以相反方向预测</span>
</div>
<div class="stat dead">
<span class="label">失效</span>
<span class="num"><span class="num-dead">165</span></span>
<span>低于显著性阈值</span>
</div>
</div>
<p>在早期的部分运行中,让我们意外的不是失效数量&mdash;&mdash;衰减人人都预料得到&mdash;&mdash;而是<em>反转</em>数量。相当一部分在 2014 年有效的因子,如今成了幅度具有统计显著性的反向指标。最简单的解读是:公式原本捕捉的行为异象(小盘股均值回归、散户的尾盘资金流)已被恰恰是这类公式所代表的系统化交易挤出,剩下的就是反向交易。</p>
<h3>分主题拆解</h3>
<p>我们按主导主题&mdash;&mdash;即因子最倚重的那组算子&mdash;&mdash;给每个 alpha 手工打了标签。这是一种有损的分类(许多因子融合了两个主题),但总体规律对标签选择是稳健的。下表数字为各主题内的存活率:</p>
<table class="theme-table" aria-label="各主题存活率">
<thead>
<tr>
<th>主题</th>
<th>定义</th>
<th>数量</th>
<th>存活率</th>
</tr>
</thead>
<tbody>
<tr>
<td>量价交互</td>
<td>成交量与收盘价、高低价振幅的相关性 / 协方差</td>
<td>81</td>
<td>5% (4/81)</td>
</tr>
<tr>
<td>短周期波动率</td>
<td>5-20 日窗口上的滚动标准差 / 振幅</td>
<td>26</td>
<td>8% (2/26)</td>
</tr>
<tr>
<td>反转</td>
<td>1-5 日周期上取负号的收益信号</td>
<td>38</td>
<td>11% (4/38)</td>
</tr>
<tr>
<td>动量</td>
<td>10-60 日周期上取正号的收益信号</td>
<td>63</td>
<td>2% (1/63)</td>
</tr>
<tr>
<td>换手 / 流动性</td>
<td>成交量比率、换手率变换</td>
<td>2</td>
<td>0% (0/2)</td>
</tr>
<tr>
<td>微观结构 / 振幅</td>
<td>开收高低价分解、日内振幅代理变量</td>
<td>18</td>
<td>22% (4/18)</td>
</tr>
</tbody>
</table>
<p>W4.a bench 的实际结果是:<em>微观结构 / 振幅</em>类因子是最突出的存活者(存活率 22%,18 个中 4 个),<em>反转</em>类次之(11%,38 个中 4 个)。我们原本预期最经得起时间考验的类别&mdash;&mdash;原始<em>量价交互</em>(5%)和<em>短周期波动率</em>(8%)&mdash;&mdash;衰减程度反而超过开收高低价分解一族。<em>动量</em>类 2%(63 个中 1 个)和规模很小的<em>换手</em>类 0%,是被套利得最干净的两组。这一规律符合如下情形下的预期:十年的系统化资金压缩了最容易的反转 / 动量交易,却没有触动那些捕捉 K 线形态效应、基于日线结构几何的因子。</p>
<h3>存活因子 Top 5</h3>
<p>窗口内平均 IC 最高的五个因子,按降序排列。每个因子以其 zoo id 和公式的转述标识;完整的算子级定义见对应 Python 模块中的 <code>__alpha_meta__["formula_latex"]</code> 字段。</p>
<article class="alpha-card">
<span class="alpha-id">gtja191_171</span>
<pre class="formula-block"><code>-1*((l-c)*(o^5))/((c-h)*(c^5))</code></pre>
<p class="alpha-note">在沪深300 / 2018&ndash;2025 窗口上,平均 IC = 0.0432,IR = 0.2690。公式逐字取自注册表(<code>gtja191_171</code> 的 <code>__alpha_meta__["formula_latex"]</code>)。</p>
</article>
<article class="alpha-card">
<span class="alpha-id">gtja191_111</span>
<pre class="formula-block"><code>sma(v*((c-l)-(h-c))/(h-l),11,2)-sma(v*((c-l)-(h-c))/(h-l),4,2)</code></pre>
<p class="alpha-note">在沪深300 / 2018&ndash;2025 窗口上,平均 IC = 0.0349,IR = 0.2232。公式逐字取自注册表(<code>gtja191_111</code> 的 <code>__alpha_meta__["formula_latex"]</code>)。</p>
</article>
<article class="alpha-card">
<span class="alpha-id">gtja191_163</span>
<pre class="formula-block"><code>rank(((-1*ret)*mean(v,20))*vwap*(high-close))</code></pre>
<p class="alpha-note">在沪深300 / 2018&ndash;2025 窗口上,平均 IC = 0.0347,IR = 0.2008。公式逐字取自注册表(<code>gtja191_163</code> 的 <code>__alpha_meta__["formula_latex"]</code>)。</p>
</article>
<article class="alpha-card">
<span class="alpha-id">gtja191_002</span>
<pre class="formula-block"><code>(-1 * DELTA(((CLOSE - LOW) - (HIGH - CLOSE)) / (HIGH - LOW), 1))</code></pre>
<p class="alpha-note">在沪深300 / 2018&ndash;2025 窗口上,平均 IC = 0.0262,IR = 0.1619。公式逐字取自注册表(<code>gtja191_002</code> 的 <code>__alpha_meta__["formula_latex"]</code>)。</p>
</article>
<article class="alpha-card">
<span class="alpha-id">gtja191_054</span>
<pre class="formula-block"><code>((-1*RANK((STD(ABS(CLOSE-OPEN),10)+(CLOSE-OPEN))+CORR(CLOSE,OPEN,10))))</code></pre>
<p class="alpha-note">在沪深300 / 2018&ndash;2025 窗口上,平均 IC = 0.0272,IR = 0.1606。公式逐字取自注册表(<code>gtja191_054</code> 的 <code>__alpha_meta__["formula_latex"]</code>)。</p>
</article>
<h3>三个著名的失效因子</h3>
<p>以下三个公式在 2014 年的报告中有效(或被声称有效),如今却稳稳落在失效或反转一栏:</p>
<article class="alpha-card">
<span class="alpha-id">gtja191_178(反转)</span>
<pre class="formula-block"><code>(c-delay(c,1))/delay(c,1)*v</code></pre>
<p class="alpha-note">平均 IC = -0.0327,IR = -0.1930。按原始 IC 计,属于 gtja191 因子集在沪深300 / 2018&ndash;2025 上表现最差的一批。</p>
</article>
<article class="alpha-card">
<span class="alpha-id">gtja191_137(反转)</span>
<pre class="formula-block"><code>见正文</code></pre>
<p class="alpha-note">平均 IC = -0.0277,IR = -0.1556。按原始 IC 计,属于 gtja191 因子集在沪深300 / 2018&ndash;2025 上表现最差的一批。</p>
</article>
<article class="alpha-card">
<span class="alpha-id">gtja191_066(反转)</span>
<pre class="formula-block"><code>(CLOSE-MEAN(CLOSE,6))/MEAN(CLOSE,6)*100</code></pre>
<p class="alpha-note">平均 IC = -0.0270,IR = -0.1377。按原始 IC 计,属于 gtja191 因子集在沪深300 / 2018&ndash;2025 上表现最差的一批。</p>
</article>
<h3>一段反思</h3>
<p>做完这类存活研究,人们容易过度概括:“衰减不可避免;公式化 alpha 已死。”我们认为这不是正确的解读。这次研究给出的结论更窄、也更有用:一份 12 年前的短周期因子目录中,仍有相当一部分能产生信号;存活者集中在可解释的主题里(量价交互、短周期波动率),失效者也同样集中在可解释的主题里(朴素反转、简单换手率变换)。至于 Kakushadze 101 公式或 Qlib 158 特征集是否会以同样的速度衰减,它几乎说明不了什么&mdash;&mdash;这两个因子集的算子词汇不同,面向的股票池也不同(分别是美股和多周期)。这留待后续工作,另行跑 bench。</p>
</section>
<section id="caveats">
<h2>局限与注意事项</h2>
<p>解读这些结果时有几项限制。每一项都不容忽视,其中任何一项都可能让核心数字变动数十个因子。</p>
<h3>1 日 IC 不等于盈利能力</h3>
<p>整个研究停留在 IC 层面,而非策略 PnL 层面。日频上统计显著的正 IC,在扣除现实的交易成本后,对应的策略仍可能亏钱,尤其当因子的日换手率很高时(国泰君安 191 因子大多如此)。请把本文当作一次<em>信号质量扫描</em>,而不是对盈利能力的论断。包含交易成本建模、仓位限制和行业中性化的正式 PnL 回测,是另一项独立的工作。</p>
<h3>仅限沪深300</h3>
<p>我们的基准测试只覆盖流动性最好的 300 只 A 股。为全 A 股票池设计的因子&mdash;&mdash;全 A 约有 5,000 只股票,流动性特征差异很大&mdash;&mdash;表现会有所不同。尤其是小盘股均值回归类因子,在沪深300 上往往比在全市场上表现更差(因为沪深300 以机构资金为主导,短周期的散户反转效应较弱),这里一些“失效”的因子在中证1000 上或许会复活。</p>
<h3>Tushare 数据范围</h3>
<p>我们的数据源(Tushare 日终 OHLCV 与换手率,外加由成交额推算的 VWAP)不含日内逐笔或订单簿信息。少数国泰君安 191 因子在原始表述中引用了日内数据(深度加权 VWAP、level-1 盘口失衡);对这些因子,我们以日频 VWAP 替代,并在逐因子输出中标注这一替换。这些因子在这里应视为<em>未经充分检验</em>,而不是“失效”。</p>
<h3>8 年窗口偏短</h3>
<p>按学术标准,8 年窗口偏短。经典的 Fama-French 论文使用 30&ndash;90 年的窗口。一些被我们标为“失效”的因子,在更长或所处时段不同的样本上可能复活&mdash;&mdash;例如,若 2026&ndash;2030 年的市场状态重新回到散户占比更高的结构,均值回归类因子可能恢复。这个标签的含义是“在本窗口内失效”,而非“永久失效”。</p>
<h3>股票池中的幸存者偏差</h3>
<p>本文使用的沪深300 成分股名单,是把<em>当前</em>的指数成分套用到整个 2018&ndash;2025 窗口,而不是按每次调仓时点重建的指数成分(point-in-time)。窗口期内退市、降档或被调出的股票不在其中;后期才纳入的股票则被回填了完整的历史价格。这是一种标准的幸存者偏差,会使 IC 估计在纯多头方向上偏高。对于以同样方式构建美股股票池的任何跨因子集比较,这一提醒更加适用&mdash;&mdash;例如,用今天的成分股名单跑标普 500,相对于真正的时点股票池会高估存活数量;那里若得出 0% 的存活比例,应理解为“衰减加上幸存者偏差把信号抵消了”,而不是该因子家族的干净失败。时点指数成分是计划中的升级。</p>
<h3>未按市场状态分组</h3>
<p>我们在整个 2018&ndash;2025 窗口上对 IC 取平均。这些因子中很多几乎肯定依赖市场状态&mdash;&mdash;在趋势市有效、在震荡市失灵,或者反过来。后续文章会按市场状态(牛市 / 熊市 / 震荡市,以 20 日指数动量划分)拆分存活数量,但本文的汇总数字是跨状态平均的。一个考虑市场状态的部署方案,或许能把一半的失效因子保留为条件信号。</p>
</section>
<section id="reproduce">
<h2>自己动手复现</h2>
<p>整个 bench 只需在开源安装之上执行一条 CLI 命令。完整源码在 GitHub 的 HKUDS 组织下;PyPI 上的包名是 <code>vibe-trading-ai</code>。</p>
<pre><code>pip install vibe-trading-ai
export TUSHARE_TOKEN=your_token_here
vibe-trading alpha bench --zoo gtja191 \
--universe csi300 \
--period 2020-2025 \
--top 20</code></pre>
<p>你会得到一份保存在 <code>~/.vibe-trading/reports/</code> 的 HTML 报告,包含逐因子的 IC、IR、衰减曲线和一张可排序的 top-N 表。同一命令也适用于 <code>--zoo alpha101</code>(Kakushadze 的 101 Formulaic Alphas,忠实于论文、据 2015 年 arXiv 预印本重写)和 <code>--zoo qlib158</code>(微软 Qlib 特征库,依 Apache-2.0 许可并注明出处使用)。跨因子集比较通过 <code>alpha compare</code> 运行。</p>
<div class="cta-row">
<a class="primary" href="https://github.com/HKUDS/Vibe-Trading">在 GitHub 查看源码</a>
<a href="/alpha-library/">浏览因子库</a>
<a href="/docs/">阅读文档</a>
</div>
<p>如果你发现某个因子的存活分类出乎意料&mdash;&mdash;尤其是我们没有列入前 5 名的存活者,或我们放进了存活一栏的著名失效因子&mdash;&mdash;欢迎附上因子 id 和你的理由提交 issue。社区也欢迎通过 pull request 贡献新的因子集、新的股票池(中证1000、纳斯达克 100、主流加密货币)或新的验证工具,流程遵循
<a href="https://github.com/HKUDS/Vibe-Trading/blob/main/CONTRIBUTING.md">CONTRIBUTING.md</a>
中的 DCO 规范。</p>
<p class="footnote">来源引用:国泰君安证券,<em>《191 个短周期交易型 alpha 因子》</em>(191 Short-Period Transactional Alpha Factors),2014 年。重新实现的
<code>agent/src/factors/zoo/gtja191/</code> 仅使用报告中的公式内容;报告的叙述性正文、样本内表格和图表均未复制。完整的出处说明见该目录下的
<code>LICENSE.md</code>。</p>
</section>
</article>
</main>
<!-- site-footer:start -->
<footer class="site-footer">
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
<!-- site-footer:end -->
</body>
</html>
+49 -17
View File
@@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-page-lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'none'; img-src 'self' data:;">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self'; img-src 'self' data: https://vibetrading.wiki; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.github.com;">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Which of the 191 GTJA alphas still work in 2026? | Vibe-Trading Research Lab</title>
<meta name="description" content="An out-of-sample look at the 191 short-horizon alphas published by Guotai Junan in 2014, re-evaluated on CSI 300 over 2018-2025. How many still survive, how many reversed sign, and which themes age best.">
@@ -14,8 +14,18 @@
<meta property="og:description" content="Out-of-sample IC review of the 2014 GTJA 191 short-horizon alphas on CSI 300, 2018-2025. Aggregate survival counts, theme breakdown, and reproducibility recipe.">
<meta property="og:url" content="https://vibetrading.wiki/research-lab/posts/alpha-191-in-2026.html">
<link rel="canonical" href="https://vibetrading.wiki/research-lab/posts/alpha-191-in-2026.html">
<link rel="alternate" hreflang="en" href="/research-lab/posts/alpha-191-in-2026.html">
<link rel="alternate" hreflang="zh-CN" href="/research-lab/posts/alpha-191-in-2026-zh.html">
<link rel="alternate" hreflang="x-default" href="/research-lab/posts/alpha-191-in-2026.html">
<!-- site-head:start -->
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="stylesheet" href="/styles.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
<!-- site-head:end -->
<script type="module" src="/main.js?v=20260929d"></script>
<style>
.post {
width: min(760px, calc(100% - 32px));
@@ -207,22 +217,35 @@
}
</style>
</head>
<body>
<header class="site-header">
<body data-section="lab">
<!-- site-header:start -->
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">Source</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- site-header:end -->
<main>
<article class="post">
@@ -605,13 +628,22 @@ vibe-trading alpha bench --zoo gtja191 \
</article>
</main>
<!-- site-footer:start -->
<footer class="site-footer">
<span>Vibe-Trading Wiki</span>
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
<!-- site-footer:end -->
</body>
</html>
+87 -39
View File
@@ -17,12 +17,15 @@ Security posture (v0.1.8):
so manifest-derived strings (``formula_latex``, ``notes``, ids, …)
cannot break out of the document.
* Each generated page carries a strict Content-Security-Policy meta:
``default-src 'self'; style-src 'self' 'unsafe-inline';
script-src 'none'; img-src 'self' data:;``.
* KaTeX is intentionally **not** loaded — ``script-src 'none'`` forbids
it. ``formula_latex`` is shown as raw escaped text inside ``<pre>``.
A future revision can swap in a server-side LaTeX→MathML pass that
keeps CSP intact.
``script-src 'self'`` — no inline script and nothing from another
origin. The only scripts are the site's own shell (theme, header,
language switch), the same files every other wiki page loads.
* KaTeX is intentionally **not** loaded. ``formula_latex`` is shown as
raw escaped text inside ``<pre>``. A future revision can swap in a
server-side LaTeX→MathML pass that keeps CSP intact.
* The header, footer and head block come from ``wiki/partials/`` (the
same markup ``sync_site_chrome.py`` writes into the hand-written
pages); labels carry ``data-i18n`` keys from ``wiki/locales/zh.json``.
* Run-time safety check (``--check-escape``, on by default) verifies a
sample page contains no unescaped ``<``/``>``/``"`` from
manifest-derived fields. The script exits non-zero on failure.
@@ -43,6 +46,11 @@ from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from markupsafe import Markup
sys.path.insert(0, str(Path(__file__).resolve().parent))
from sync_site_chrome import render_block # noqa: E402
try:
from jinja2 import Environment, select_autoescape
except ImportError as exc: # pragma: no cover — Jinja2 is a project dep
@@ -61,14 +69,19 @@ _REPO_ROOT = Path(__file__).resolve().parents[2]
_DEFAULT_MANIFEST = _REPO_ROOT / "wiki" / "alpha-library" / "manifest.json"
_DEFAULT_OUTPUT = _REPO_ROOT / "wiki" / "alpha-library" / "content"
# Strict CSP for static content pages — no scripts at all.
# Strict CSP for static content pages: same-origin scripts only, no inline.
_CSP_CONTENT = (
"default-src 'self'; "
"style-src 'self' 'unsafe-inline'; "
"script-src 'none'; "
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
"script-src 'self'; "
"img-src 'self' data:; "
"font-src 'self' https://fonts.gstatic.com; "
"connect-src 'self' https://api.github.com;"
)
# Cache-busting version of the site shell modules; matches the hand-written pages.
_SHELL_VERSION = "20260929d"
# Display names for zoos. Source of truth — do not depend on prose elsewhere.
_ZOO_DISPLAY: dict[str, dict[str, str]] = {
"qlib158": {
@@ -87,6 +100,13 @@ _ZOO_DISPLAY: dict[str, dict[str, str]] = {
"name": "Academic Anomalies",
"tagline": "Curated alphas from the academic asset-pricing literature.",
},
"fundamental": {
"name": "Point-in-time Fundamentals",
"tagline": (
"US SEC fundamentals anchored to the filing date, so a backtest never "
"sees a number before it was published."
),
},
}
@@ -141,7 +161,7 @@ def parse_manifest(path: Path) -> dict[str, Any]:
_ALPHA_PAGE_TEMPLATE = """<!doctype html>
<html lang="en">
<html lang="en" data-lang-neutral>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="{{ csp }}">
@@ -150,7 +170,8 @@ _ALPHA_PAGE_TEMPLATE = """<!doctype html>
<meta name="description" content="{{ alpha.id }} — {{ zoo_display.name }} alpha definition.">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://vibetrading.wiki/alpha-library/content/{{ zoo.zoo_id }}/{{ alpha.id }}.html">
<link rel="stylesheet" href="../../../styles.css">
{{ head_block }}
<script type="module" src="/main.js?v={{ shell_version }}"></script>
<style>
.alpha-page { width: min(880px, calc(100% - 32px)); margin: 48px auto 96px; }
.alpha-page .crumbs { color: var(--muted); font-family: var(--mono); font-size: 0.85rem; margin-bottom: 12px; }
@@ -166,29 +187,31 @@ _ALPHA_PAGE_TEMPLATE = """<!doctype html>
.alpha-page .tag { display: inline-block; border: 1px solid var(--line); border-radius: 999px; padding: 2px 10px; margin: 2px 4px 2px 0; font-size: 0.78rem; color: var(--muted); }
</style>
</head>
<body>
<body data-section="alpha">
{{ header_block }}
<main class="alpha-page">
<p class="crumbs">
<a href="../../index.html">Alpha Library</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
&nbsp;/&nbsp;
<a href="index.html">{{ zoo_display.name }}</a>
<a href="index.html" data-i18n="alpha.zoo.{{ zoo.zoo_id }}Name">{{ zoo_display.name }}</a>
&nbsp;/&nbsp;
<span>{{ alpha.id }}</span>
</p>
<h1>{{ alpha.id }}</h1>
{% if alpha.meta.nickname %}<p class="nickname">{{ alpha.meta.nickname }}</p>{% endif %}
<h2>Themes &amp; universe</h2>
<h2 data-i18n="alpha.page.themes">Themes &amp; universe</h2>
<p>
{% for t in alpha.meta.theme %}<span class="tag">{{ t }}</span>{% endfor %}
{% for u in alpha.meta.universe %}<span class="tag">{{ u }}</span>{% endfor %}
{% for f in alpha.meta.frequency %}<span class="tag">{{ f }}</span>{% endfor %}
</p>
<h2>Formula</h2>
<h2 data-i18n="alpha.page.formula">Formula</h2>
<pre class="formula"><code>{{ alpha.meta.formula_latex }}</code></pre>
<h2>Definition</h2>
<h2 data-i18n="alpha.page.definition">Definition</h2>
<dl class="meta-grid">
<div><dt>id</dt><dd>{{ alpha.id }}</dd></div>
<div><dt>zoo</dt><dd>{{ zoo.zoo_id }}</dd></div>
@@ -201,22 +224,24 @@ _ALPHA_PAGE_TEMPLATE = """<!doctype html>
</dl>
{% if alpha.meta.notes %}
<h2>Notes</h2>
<h2 data-i18n="alpha.page.notes">Notes</h2>
<p class="notes">{{ alpha.meta.notes }}</p>
{% endif %}
<h2>Run it</h2>
<h2 data-i18n="alpha.page.run">Run it</h2>
<pre class="formula"><code>pip install vibe-trading-ai
vibe-trading alpha show {{ alpha.id }}
vibe-trading alpha bench --zoo {{ zoo.zoo_id }} --universe csi300 --period 2020-2025</code></pre>
</main>
{{ footer_block }}
</body>
</html>
"""
_ZOO_PAGE_TEMPLATE = """<!doctype html>
<html lang="en">
<html lang="en" data-lang-neutral>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="{{ csp }}">
@@ -225,7 +250,8 @@ _ZOO_PAGE_TEMPLATE = """<!doctype html>
<meta name="description" content="{{ zoo_display.tagline }}">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://vibetrading.wiki/alpha-library/content/{{ zoo.zoo_id }}/index.html">
<link rel="stylesheet" href="../../../styles.css">
{{ head_block }}
<script type="module" src="/main.js?v={{ shell_version }}"></script>
<style>
.zoo-page { width: min(1100px, calc(100% - 32px)); margin: 48px auto 96px; }
.zoo-page .crumbs { color: var(--muted); font-family: var(--mono); font-size: 0.85rem; margin-bottom: 12px; }
@@ -242,20 +268,22 @@ _ZOO_PAGE_TEMPLATE = """<!doctype html>
.zoo-page .theme-cell { color: var(--muted); font-size: 0.82rem; }
</style>
</head>
<body>
<body data-section="alpha">
{{ header_block }}
<main class="zoo-page">
<p class="crumbs"><a href="../../index.html">Alpha Library</a> &nbsp;/&nbsp; <span>{{ zoo_display.name }}</span></p>
<h1>{{ zoo_display.name }} <span class="count-pill">{{ zoo.alphas | length }} alphas</span></h1>
<p class="tagline">{{ zoo_display.tagline }}</p>
<p class="crumbs"><a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a> &nbsp;/&nbsp; <span data-i18n="alpha.zoo.{{ zoo.zoo_id }}Name">{{ zoo_display.name }}</span></p>
<h1><span data-i18n="alpha.zoo.{{ zoo.zoo_id }}Name">{{ zoo_display.name }}</span> <span class="count-pill">{{ zoo.alphas | length }} <span data-i18n="alpha.page.alphasUnit">alphas</span></span></h1>
<p class="tagline" data-i18n="alpha.zoo.{{ zoo.zoo_id }}">{{ zoo_display.tagline }}</p>
<table>
<thead>
<tr>
<th>id</th>
<th>theme</th>
<th>universe</th>
<th>decay</th>
<th>warmup</th>
<th data-i18n="alpha.page.theme">theme</th>
<th data-i18n="alpha.page.universe">universe</th>
<th data-i18n="alpha.page.decay">decay</th>
<th data-i18n="alpha.page.warmup">warmup</th>
</tr>
</thead>
<tbody>
@@ -271,6 +299,8 @@ _ZOO_PAGE_TEMPLATE = """<!doctype html>
</tbody>
</table>
</main>
{{ footer_block }}
</body>
</html>
"""
@@ -290,6 +320,25 @@ def _build_env() -> Environment:
)
def _chrome() -> dict[str, Any]:
"""Return the shared head block, header and footer as trusted markup.
They come from ``wiki/partials/`` (repository files, not manifest data),
so they are marked safe and bypass autoescape; everything derived from
the manifest still goes through it.
Returns:
Template variables ``head_block``, ``header_block``, ``footer_block``
and ``shell_version``.
"""
return {
"head_block": Markup(render_block("site-head", " ")),
"header_block": Markup(render_block("site-header", " ")),
"footer_block": Markup(render_block("site-footer", " ")),
"shell_version": _SHELL_VERSION,
}
def render_alpha_page(env: Environment, alpha: dict, zoo: dict) -> str:
"""Render a single alpha detail page."""
zoo_display = _ZOO_DISPLAY.get(
@@ -302,6 +351,7 @@ def render_alpha_page(env: Environment, alpha: dict, zoo: dict) -> str:
zoo=zoo,
zoo_display=zoo_display,
csp=_CSP_CONTENT,
**_chrome(),
)
@@ -312,7 +362,7 @@ def render_zoo_page(env: Environment, zoo: dict) -> str:
{"name": zoo["zoo_id"], "tagline": ""},
)
template = env.from_string(_ZOO_PAGE_TEMPLATE)
return template.render(zoo=zoo, zoo_display=zoo_display, csp=_CSP_CONTENT)
return template.render(zoo=zoo, zoo_display=zoo_display, csp=_CSP_CONTENT, **_chrome())
def render_index_data(manifest: dict) -> dict:
@@ -357,20 +407,18 @@ def _escape_smoke(sample_html: str, alpha: dict) -> bool:
that would indicate Jinja2 autoescape was bypassed.
"""
formula = alpha.get("meta", {}).get("formula_latex", "")
if "<script" in formula.lower():
# We synthesised a hostile string into a real alpha — refuse.
return "<script" not in sample_html.lower()
# If formula contains literal angle brackets, they must be HTML-escaped
# in the output. We look for the raw ASCII characters inside the file —
# but only after we strip out our own template chrome. Cheap heuristic:
# the formula content lives inside `<pre class="formula"><code>...</code></pre>`.
# The formula lives inside `<pre class="formula"><code>...</code></pre>`;
# the page's own chrome (which loads the site scripts) is outside it.
start_tag = '<pre class="formula"><code>'
end_tag = "</code></pre>"
if start_tag in sample_html:
if start_tag not in sample_html:
return "<script" not in formula.lower()
start = sample_html.index(start_tag) + len(start_tag)
end = sample_html.index(end_tag, start)
block = sample_html[start:end]
if "<script" in formula.lower():
# We synthesised a hostile string into a real alpha — refuse.
return "<script" not in block.lower()
# autoescape should turn `<` into `&lt;` etc.
if "<" in formula and "<" in block and "&lt;" not in block:
return False
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Every translatable string on the wiki has a Chinese version, and no more.
English is the HTML itself; Chinese lives in ``wiki/locales/zh.json`` and is
applied by ``site.js`` through ``data-i18n``, ``data-i18n-html``,
``data-i18n-attr`` and ``data-href-zh``. This fails when:
* a key used on a page (hand-written, a partial, or a page the Alpha Library
generator renders) has no Chinese value — the reader would see English in
the middle of a Chinese page;
* ``zh.json`` holds a key nothing uses — a translation that silently stopped
applying after someone renamed its key;
* a ``data-href-zh`` link points at a file that does not exist;
* a generated Alpha Library page is not marked language-neutral or not in
the ``alpha`` section — it would stay English for a Chinese reader and
highlight no header item (``sync_site_chrome.py`` checks the hand-written
pages the same way).
The generator's templates are rendered from a one-alpha-per-zoo manifest, so
their keys are checked the way they ship (zoo ids are substituted into keys).
The docs are checked separately by ``check_docs_parity.mjs``.
Usage:
python wiki/scripts/check_i18n.py
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
SCRIPTS = Path(__file__).resolve().parent
WIKI = SCRIPTS.parent
sys.path.insert(0, str(SCRIPTS))
import build_alpha_library as alpha # noqa: E402
from sync_site_chrome import pages # noqa: E402
KEY_ATTR_RE = re.compile(r'data-i18n(?:-html)?="([^"]+)"')
ATTR_LIST_RE = re.compile(r'data-i18n-attr="([^"]+)"')
HREF_ZH_RE = re.compile(r'data-href-zh="([^"]+)"')
def generated_pages() -> list[str]:
"""Render one zoo page and one alpha page for every known zoo.
Returns:
The rendered HTML strings.
"""
env = alpha._build_env()
rendered = []
for zoo_id in alpha._ZOO_DISPLAY:
sample = {
"id": f"{zoo_id}_001",
"module_path": f"src.factors.zoo.{zoo_id}.alpha_001",
"meta": {"theme": ["momentum"], "universe": ["equity_cn"], "frequency": ["1d"],
"formula_latex": "rank(x)", "notes": "note", "decay_horizon": 1,
"min_warmup_bars": 1, "requires_sector": False, "columns_required": ["close"]},
}
zoo = {"zoo_id": zoo_id, "alphas": [sample]}
rendered.append(alpha.render_zoo_page(env, zoo))
rendered.append(alpha.render_alpha_page(env, sample, zoo))
return rendered
def main() -> int:
"""Compare the keys the pages use with the keys zh.json defines.
Returns:
Process exit code.
"""
zh = json.loads((WIKI / "locales" / "zh.json").read_text(encoding="utf-8"))
sources = {path.relative_to(WIKI).as_posix(): path.read_text(encoding="utf-8") for path in pages()}
sources.update({f"partials/{p.name}": p.read_text(encoding="utf-8") for p in (WIKI / "partials").glob("*.html")})
generated = {f"<generated #{i}>": html for i, html in enumerate(generated_pages())}
sources.update(generated)
used: dict[str, str] = {}
problems = []
for name, html in generated.items():
html_tag = re.search(r"<html\b[^>]*>", html)
body_tag = re.search(r"<body\b[^>]*>", html)
if not html_tag or "data-lang-neutral" not in html_tag.group(0):
problems.append(f"{name}: generated page lacks <html data-lang-neutral>")
if not body_tag or 'data-section="alpha"' not in body_tag.group(0):
problems.append(f'{name}: generated page lacks <body data-section="alpha">')
for name, text in sources.items():
for key in KEY_ATTR_RE.findall(text):
used.setdefault(key, name)
for spec in ATTR_LIST_RE.findall(text):
for pair in spec.split(","):
attr, _, key = pair.partition("=")
if not attr.strip() or not key.strip():
problems.append(f"{name}: malformed data-i18n-attr {spec!r}")
continue
used.setdefault(key.strip(), name)
for href in HREF_ZH_RE.findall(text):
if not (WIKI / href.lstrip("/")).is_file():
problems.append(f"{name}: data-href-zh points at missing {href}")
for key, where in sorted(used.items()):
value = zh.get(key)
if not isinstance(value, str) or not value.strip():
problems.append(f"{where}: no Chinese for {key!r}")
for key in sorted(set(zh) - set(used)):
problems.append(f"locales/zh.json: {key!r} is used by no page")
if problems:
print("\n".join(problems), file=sys.stderr)
return 1
print(f"i18n ok: {len(used)} keys, {len(sources)} sources")
return 0
if __name__ == "__main__":
sys.exit(main())
+237
View File
@@ -0,0 +1,237 @@
#!/usr/bin/env python3
"""Keep every wiki page on the same head block, header and footer.
The site has no build step, so the shared chrome is copied into each page
between markers. This script rewrites those blocks from ``wiki/partials/``;
``--check`` (run in CI) fails instead of writing when any page differs, and
also checks the language wiring the chrome depends on:
* ``<body data-section>`` matches the page's section, which is what highlights
the current item in the header (CSS only, no JS needed);
* a language-neutral page carries ``<html data-lang-neutral>``; an article
carries ``<html data-page-lang>`` and ``<link rel="alternate">`` to both
language versions, each of which exists and links back;
* the docs page carries neither (its script decides from the URL);
* every cache-busting ``?v=`` token (pages, site scripts, the generator) is
the one in ``partials/site-head.html``: ``_headers`` lets browsers keep
JS and CSS for an hour, so a module bumped alone can meet a cached copy of
the module it imports and leave the page blank. Bump with one replace
across ``wiki/``.
The Alpha Library pages under ``alpha-library/content/`` are generated by
``build_alpha_library.py``, which inserts the same partials itself.
Usage:
python wiki/scripts/sync_site_chrome.py # rewrite blocks
python wiki/scripts/sync_site_chrome.py --check # CI
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
WIKI = Path(__file__).resolve().parents[1]
PARTIALS = WIKI / "partials"
BLOCKS = ("site-head", "site-header", "site-footer")
SECTIONS = {
"home": "home",
"docs": "docs",
"tutorials": "tutorials",
"alpha-library": "alpha",
"research-lab": "lab",
}
# Pages whose URL fixes their language: every page in these folders except
# the folder's index.
ARTICLE_DIRS = ("tutorials", "research-lab/posts")
HTML_LANG = {"en": "en", "zh": "zh-CN"}
def pages() -> list[Path]:
"""Return every hand-written page that carries the shared chrome.
Returns:
Page paths, sorted.
"""
found = []
for path in sorted(WIKI.rglob("*.html")):
rel = path.relative_to(WIKI).as_posix()
if rel == "index.html" or rel.startswith(("partials/", "alpha-library/content/")):
continue
found.append(path)
return found
def partial(name: str) -> str:
"""Return a partial's text without its trailing newline.
Args:
name: Block name, e.g. ``site-header``.
Returns:
The partial markup.
"""
return (PARTIALS / f"{name}.html").read_text(encoding="utf-8").rstrip("\n")
def block_re(name: str) -> re.Pattern[str]:
"""Match one marked block, capturing its indentation.
Args:
name: Block name.
Returns:
A compiled pattern.
"""
return re.compile(
rf"^(?P<indent>[ \t]*)<!-- {name}:start -->\n.*?^[ \t]*<!-- {name}:end -->$",
re.S | re.M,
)
def render_block(name: str, indent: str) -> str:
"""Render a block with its markers at the given indentation.
Args:
name: Block name.
indent: Leading whitespace of the start marker.
Returns:
The block text.
"""
body = "\n".join(f"{indent}{line}" if line else "" for line in partial(name).splitlines())
return f"{indent}<!-- {name}:start -->\n{body}\n{indent}<!-- {name}:end -->"
def is_article(rel: str) -> bool:
"""Whether a page's URL fixes its language.
Args:
rel: Path relative to the wiki root.
Returns:
True for tutorial and research articles.
"""
folder, _, name = rel.rpartition("/")
return folder in ARTICLE_DIRS and name != "index.html"
def wiring_problems(path: Path, text: str) -> list[str]:
"""Check the section and language attributes of one page.
Args:
path: The page.
text: Its contents.
Returns:
Human-readable problems, empty when the page is wired correctly.
"""
rel = path.relative_to(WIKI).as_posix()
problems = []
section = SECTIONS.get(rel.split("/", 1)[0])
body = re.search(r"<body\b[^>]*>", text)
if not body or f'data-section="{section}"' not in body.group(0):
problems.append(f'{rel}: <body> needs data-section="{section}"')
html = re.search(r"<html\b[^>]*>", text)
html_tag = html.group(0) if html else ""
if rel.startswith("docs/"):
if "data-lang-neutral" in html_tag or "data-page-lang" in html_tag:
problems.append(f"{rel}: the docs decide their language from the URL; no language attribute on <html>")
return problems
if not is_article(rel):
if "data-lang-neutral" not in html_tag:
problems.append(f"{rel}: <html> needs data-lang-neutral")
return problems
declared = re.search(r'data-page-lang="(en|zh)"', html_tag)
if not declared:
problems.append(f'{rel}: <html> needs data-page-lang="en" or "zh"')
return problems
if f'lang="{HTML_LANG[declared.group(1)]}"' not in html_tag:
problems.append(f"{rel}: <html lang> disagrees with data-page-lang")
for lang, code in HTML_LANG.items():
link = re.search(rf'<link rel="alternate" hreflang="{code}" href="([^"]+)">', text)
if not link:
problems.append(f"{rel}: missing <link rel=\"alternate\" hreflang=\"{code}\">")
continue
target = WIKI / link.group(1).lstrip("/")
if not target.is_file():
problems.append(f"{rel}: alternate {code} points at missing {link.group(1)}")
elif lang == declared.group(1) and target != path:
problems.append(f"{rel}: its own-language alternate points elsewhere ({link.group(1)})")
return problems
VERSION_RE = re.compile(r'\?v=([\w-]+)|VERSION = "([\w-]+)"')
def version_problems() -> list[str]:
"""Check that every cache-busting token matches the head block's.
Returns:
Human-readable problems, empty when all tokens agree.
"""
head = re.search(r"styles\.css\?v=([\w-]+)", partial("site-head"))
if not head:
return ["partials/site-head.html: no ?v= token on styles.css"]
expected = head.group(1)
files = pages() + [
path for path in sorted(WIKI.rglob("*.js"))
if not path.relative_to(WIKI).as_posix().startswith(("alpha-library/content/", "functions/"))
] + [WIKI / "scripts" / "build_alpha_library.py"]
problems = []
for path in files:
for match in VERSION_RE.finditer(path.read_text(encoding="utf-8")):
found = match.group(1) or match.group(2)
if found != expected:
rel = path.relative_to(WIKI).as_posix()
problems.append(f"{rel}: version {found}, the head block uses {expected} (bump them together)")
return problems
def main(argv: list[str] | None = None) -> int:
"""Rewrite or check the shared chrome of every page.
Args:
argv: Command-line arguments.
Returns:
Process exit code.
"""
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--check", action="store_true", help="fail on any difference instead of writing")
args = parser.parse_args(argv)
problems: list[str] = []
changed: list[str] = []
for path in pages():
rel = path.relative_to(WIKI).as_posix()
text = path.read_text(encoding="utf-8")
updated = text
for name in BLOCKS:
matches = list(block_re(name).finditer(updated))
if len(matches) != 1:
problems.append(f"{rel}: expected one <!-- {name}:start/end --> block, found {len(matches)}")
continue
match = matches[0]
updated = updated[: match.start()] + render_block(name, match.group("indent")) + updated[match.end():]
problems.extend(wiring_problems(path, updated))
if updated != text:
changed.append(rel)
if not args.check:
path.write_text(updated, encoding="utf-8")
problems.extend(version_problems())
if args.check and changed:
problems.extend(f"{rel}: shared chrome differs from wiki/partials/ (run sync_site_chrome.py)" for rel in changed)
if problems:
print("\n".join(problems), file=sys.stderr)
return 1
verb = "checked" if args.check else f"synced ({len(changed)} rewritten)"
print(f"site chrome {verb}: {len(pages())} pages")
return 0
if __name__ == "__main__":
sys.exit(main())
+259
View File
@@ -0,0 +1,259 @@
// Shared shell for every wiki page: theme, GitHub stars, header shadow, the
// English / 中文 switch, and the footer traffic line on the home page.
//
// Language model: English is the HTML itself. Chinese comes from
// /locales/zh.json through data-i18n (text), data-i18n-html (markup),
// data-i18n-attr ("attr=key,attr=key") and data-href-zh (a link that has a
// Chinese counterpart). One stored choice, default English, never guessed
// from the browser.
//
// A page whose URL is language-neutral (<html data-lang-neutral>) renders the
// stored language. A page whose URL is language-specific (<html
// data-page-lang="en|zh">, or the docs, which pass pageLang) shows its own
// language and records it, so following a Chinese link keeps the rest of the
// site Chinese; its toggle goes to the counterpart page instead.
const LANG_KEY = "vibetrading-lang";
const LOCALE_VERSION = "20260929d";
const THEME_KEY = "vibetrading-theme";
const STARS_CACHE_KEY = "vibetrading-github-stars";
const STARS_TTL_MS = 12 * 60 * 60 * 1000;
const REPO_API = "https://api.github.com/repos/HKUDS/Vibe-Trading";
const HTML_LANG = { en: "en", zh: "zh-CN" };
const TOGGLE = {
en: { label: "中文", lang: "zh-CN", aria: "切换到中文" },
zh: { label: "English", lang: "en", aria: "Switch to English" }
};
let zhMessages = null;
let pageLang = null;
let toggleHandler = null;
export function storedLang() {
try {
return localStorage.getItem(LANG_KEY) === "zh" ? "zh" : "en";
} catch {
return "en";
}
}
export function storeLang(lang) {
try {
localStorage.setItem(LANG_KEY, lang);
} catch {
/* private mode: the choice lasts for this page only */
}
}
async function loadZh() {
if (zhMessages) return zhMessages;
const response = await fetch(`/locales/zh.json?v=${LOCALE_VERSION}`);
if (!response.ok) throw new Error(`zh.json ${response.status}`);
zhMessages = await response.json();
return zhMessages;
}
function remember(el, key, value) {
if (!(key in el.dataset)) el.dataset[key] = value;
return el.dataset[key];
}
function translateElement(el, messages) {
const text = el.getAttribute("data-i18n");
if (text) {
const original = remember(el, "i18nEn", el.textContent);
el.textContent = messages?.[text] ?? original;
}
const html = el.getAttribute("data-i18n-html");
if (html) {
const original = remember(el, "i18nEnHtml", el.innerHTML);
el.innerHTML = messages?.[html] ?? original;
}
const attrs = el.getAttribute("data-i18n-attr");
if (attrs) {
for (const pair of attrs.split(",")) {
const [attr, key] = pair.split("=").map((part) => part.trim());
if (!attr || !key) continue;
const slot = `i18nEnAttr${attr.replace(/[^a-z]/gi, "")}`;
const original = remember(el, slot, el.getAttribute(attr) ?? "");
el.setAttribute(attr, messages?.[key] ?? original);
}
}
const hrefZh = el.getAttribute("data-href-zh");
if (hrefZh) {
const original = remember(el, "i18nEnHref", el.getAttribute("href") ?? "");
el.setAttribute("href", messages ? hrefZh : original);
}
}
// Apply a language to every translatable element under `root`. English
// restores the text the HTML shipped with; a missing Chinese key falls back
// to English rather than blanking the element.
export async function translate(lang, root = document) {
let messages = null;
if (lang === "zh") {
try {
messages = await loadZh();
} catch {
messages = null;
}
}
root.querySelectorAll("[data-i18n], [data-i18n-html], [data-i18n-attr], [data-href-zh]").forEach((el) => {
translateElement(el, messages);
});
if (root === document) {
document.documentElement.lang = HTML_LANG[messages ? "zh" : "en"];
const title = document.querySelector("title[data-i18n]");
if (title) document.title = title.textContent;
}
syncToggle(messages ? "zh" : "en");
document.documentElement.removeAttribute("data-lang-pending");
}
function syncToggle(lang) {
const button = document.getElementById("lang-toggle");
if (!button) return;
const target = TOGGLE[lang];
button.textContent = target.label;
button.setAttribute("lang", target.lang);
button.setAttribute("aria-label", target.aria);
}
function counterpartHref(lang) {
const link = document.querySelector(`link[rel="alternate"][hreflang="${HTML_LANG[lang]}"]`);
return link ? link.getAttribute("href") : null;
}
function initLanguageToggle() {
const button = document.getElementById("lang-toggle");
if (!button) return;
button.addEventListener("click", () => {
const current = pageLang || storedLang();
const next = current === "zh" ? "en" : "zh";
storeLang(next);
if (toggleHandler) {
toggleHandler(next);
return;
}
if (pageLang) {
const href = counterpartHref(next);
if (href) {
location.href = href;
return;
}
}
translate(next);
});
}
// Tell the shell which language the current URL is in (docs call this on
// every in-page navigation). Records it as the reader's choice.
export function setPageLang(lang) {
pageLang = lang;
storeLang(lang);
return translate(lang);
}
function initTheme() {
const button = document.getElementById("theme-toggle");
if (!button) return;
button.addEventListener("click", () => {
const next = document.documentElement.getAttribute("data-theme") === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-theme", next);
try {
localStorage.setItem(THEME_KEY, next);
} catch {
/* ignore */
}
});
}
function formatStarCount(n) {
if (typeof n !== "number" || !Number.isFinite(n) || n < 0) return "--";
if (n < 1000) return String(Math.round(n));
if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;
return `${(n / 1_000_000).toFixed(1)}m`;
}
function initStars() {
const el = document.getElementById("star-count");
if (!el) return;
let cached = null;
try {
cached = JSON.parse(localStorage.getItem(STARS_CACHE_KEY) || "null");
} catch {
cached = null;
}
if (cached && typeof cached.count === "number") el.textContent = formatStarCount(cached.count);
if (cached && typeof cached.at === "number" && Date.now() - cached.at < STARS_TTL_MS) return;
fetch(REPO_API, { headers: { Accept: "application/vnd.github+json" } })
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
.then((data) => {
if (typeof data.stargazers_count !== "number") return;
try {
localStorage.setItem(STARS_CACHE_KEY, JSON.stringify({ count: data.stargazers_count, at: Date.now() }));
} catch {
/* ignore */
}
el.textContent = formatStarCount(data.stargazers_count);
})
.catch(() => {
if (!cached) el.textContent = "--";
});
}
function initHeaderScroll() {
const header = document.getElementById("site-header");
if (!header) return;
const sync = () => header.classList.toggle("is-scrolled", window.scrollY > 10);
sync();
window.addEventListener("scroll", sync, { passive: true });
}
function formatTrafficCount(n) {
n = Number(n) || 0;
if (n < 1000) return String(n);
return `${(n / 1000).toFixed(n < 10000 ? 1 : 0)}`.replace(/\.0$/, "") + "K";
}
// Footer traffic line (AI agents vs humans counted by the Pages middleware,
// plus PyPI installs). Home page only: /api/stats queries pypistats live, so
// asking for it on every page would multiply upstream calls for no reader.
function initTraffic() {
const block = document.getElementById("footer-traffic");
if (!block || document.body.dataset.section !== "home") return;
fetch("/api/stats")
.then((r) => (r.ok ? r.json() : null))
.then((stats) => {
if (!stats) return;
const set = (id, value) => {
const el = document.getElementById(id);
if (el) el.textContent = value;
};
if (stats.web) {
set("vt-agents", formatTrafficCount(stats.web.agent));
set("vt-humans", formatTrafficCount(stats.web.human));
}
if (stats.pypi) set("vt-installs", formatTrafficCount(stats.pypi.last_month));
block.hidden = false;
})
.catch(() => {
/* stats unavailable — the line stays hidden */
});
}
// Start the shell. `onToggle(lang)` replaces the default switch behaviour
// (the docs route to the other language's URL themselves).
export function initSite({ onToggle } = {}) {
toggleHandler = onToggle || null;
initTheme();
initStars();
initHeaderScroll();
initTraffic();
initLanguageToggle();
const declared = document.documentElement.dataset.pageLang;
if (declared === "en" || declared === "zh") return setPageLang(declared);
if (document.documentElement.hasAttribute("data-lang-neutral")) return translate(storedLang());
return Promise.resolve();
}
+83 -1
View File
@@ -130,6 +130,43 @@ button {
color: var(--ink);
}
/* The current section, from <body data-section> (no script needed). */
body[data-section="home"] .site-nav [data-nav="home"],
body[data-section="docs"] .site-nav [data-nav="docs"],
body[data-section="tutorials"] .site-nav [data-nav="tutorials"],
body[data-section="alpha"] .site-nav [data-nav="alpha"],
body[data-section="lab"] .site-nav [data-nav="lab"] {
color: var(--ink);
text-decoration: underline;
text-decoration-color: var(--accent);
text-decoration-thickness: 2px;
text-underline-offset: 8px;
}
.lang-pill {
display: flex;
align-items: center;
min-height: 38px;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--surface);
color: var(--ink);
padding: 0 12px;
font: 700 0.86rem var(--sans);
white-space: nowrap;
cursor: pointer;
}
.lang-pill:hover {
border-color: var(--accent);
}
/* A reader who chose Chinese never sees the English text flash first
(theme-init.js sets this; site.js or a 1.5 s timeout clears it). */
html[data-lang-pending] body {
visibility: hidden;
}
.site-actions {
gap: 10px;
}
@@ -536,12 +573,37 @@ pre {
font-size: 0.92rem;
}
.site-footer span {
.site-footer__name {
margin-right: auto;
color: var(--ink);
font-weight: 700;
}
.footer-traffic[hidden] {
display: none;
}
.footer-traffic {
display: flex;
flex-wrap: wrap;
align-items: baseline;
flex-basis: 100%;
gap: 0.5rem 1.25rem;
margin-bottom: 0.75rem;
font-size: 0.85rem;
opacity: 0.8;
}
.footer-traffic .ft-item strong {
font-variant-numeric: tabular-nums;
font-weight: 700;
}
.footer-traffic .ft-note {
font-size: 0.72rem;
opacity: 0.6;
}
@media (max-width: 920px) {
.site-nav {
gap: 14px;
@@ -566,7 +628,27 @@ pre {
gap: 12px;
}
.site-header__inner {
flex-wrap: wrap;
row-gap: 8px;
min-height: 0;
padding-top: 12px;
}
/* The nav drops to its own row and scrolls sideways instead of vanishing,
so every section stays one tap away on a phone. */
.site-nav {
order: 3;
width: 100%;
gap: 18px;
overflow-x: auto;
padding: 2px 0 12px;
font-size: 0.9rem;
white-space: nowrap;
scrollbar-width: none;
}
.site-nav::-webkit-scrollbar {
display: none;
}
+20 -41
View File
@@ -1,52 +1,31 @@
// Runs before first paint (a blocking external script, so pages with a strict
// CSP can use it too). Resolves the colour theme. When the page is about to be
// shown in Chinese — a Chinese article, a /docs/zh/ URL, or a language-neutral
// page (or bare /docs/) whose reader chose Chinese — it keeps the page hidden
// until site.js has swapped the English text: at most 1.5 s, so a failed load
// still shows the page.
(function () {
var root = document.documentElement;
try {
var stored = localStorage.getItem("vibetrading-theme");
var resolved = stored === "dark" || stored === "light"
? stored
: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", resolved);
root.setAttribute("data-theme", resolved);
} catch (e) {
document.documentElement.setAttribute("data-theme", "light");
root.setAttribute("data-theme", "light");
}
})();
(function () {
function hydrateAlphaStats() {
fetch("content/index.json", { cache: "default" })
.then(function (r) {
if (!r.ok) throw new Error("index.json " + r.status);
return r.json();
})
.then(function (data) {
var total = document.getElementById("stat-total");
var totalSub = document.getElementById("stat-total-sub");
var zoos = document.getElementById("stat-zoos");
var gen = document.getElementById("stat-generated");
if (total) total.textContent = String(data.total_alphas || 0);
if (totalSub) totalSub.textContent = "across " + (data.zoo_count || 0) + " zoos";
if (zoos) zoos.textContent = String(data.zoo_count || 0);
if (gen && data.generated_at) {
try { gen.textContent = new Date(data.generated_at).toISOString().slice(0, 10); }
catch (e) { gen.textContent = "unknown"; }
try {
var path = location.pathname;
var chosenZh = localStorage.getItem("vibetrading-lang") === "zh";
var bareDocs = path === "/docs" || path === "/docs/" || path === "/docs/index.html";
var zhUrl = path.indexOf("/docs/zh/") === 0 || root.getAttribute("data-page-lang") === "zh";
if (zhUrl || (chosenZh && (root.hasAttribute("data-lang-neutral") || bareDocs))) {
root.setAttribute("lang", "zh-CN");
root.setAttribute("data-lang-pending", "");
setTimeout(function () { root.removeAttribute("data-lang-pending"); }, 1500);
}
var countEls = Array.prototype.slice.call(document.querySelectorAll(".count[data-zoo]"));
(data.zoos || []).forEach(function (z) {
var zooId = String(z.zoo_id || "");
var el = countEls.find(function (candidate) {
return candidate.dataset.zoo === zooId;
});
if (el) el.textContent = z.count + " alphas";
});
})
.catch(function () {
var totalSub = document.getElementById("stat-total-sub");
if (totalSub) totalSub.textContent = "manifest not generated yet";
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", hydrateAlphaStats);
} else {
hydrateAlphaStats();
} catch (e) {
/* storage blocked: English */
}
})();
-31
View File
@@ -1,31 +0,0 @@
const STORAGE_KEY = "vibetrading-theme";
function preferredTheme() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === "dark" || stored === "light") return stored;
} catch {
/* ignore */
}
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
function applyTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
}
export function initTheme() {
applyTheme(preferredTheme());
const button = document.getElementById("theme-toggle");
if (!button) return;
button.addEventListener("click", () => {
const next = document.documentElement.getAttribute("data-theme") === "dark" ? "light" : "dark";
applyTheme(next);
try {
localStorage.setItem(STORAGE_KEY, next);
} catch {
/* ignore */
}
});
}
+42 -39
View File
@@ -1,22 +1,9 @@
<!doctype html>
<html lang="en">
<html lang="en" data-lang-neutral>
<head>
<meta charset="utf-8">
<script>
(function () {
try {
var stored = localStorage.getItem("vibetrading-theme");
var resolved = stored === "dark" || stored === "light"
? stored
: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", resolved);
} catch (e) {
document.documentElement.setAttribute("data-theme", "light");
}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hands-on AI Quant | Vibe-Trading Wiki</title>
<title data-i18n="tutorials.title">Hands-on AI Quant | Vibe-Trading Wiki</title>
<meta name="description" content="Hands-on AI Quant tutorials for Vibe-Trading.">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta property="og:type" content="website">
@@ -26,62 +13,78 @@
<meta property="og:image" content="https://vibetrading.wiki/assets/icon.png">
<meta property="og:url" content="https://vibetrading.wiki/tutorials/">
<link rel="canonical" href="https://vibetrading.wiki/tutorials/">
<!-- site-head:start -->
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
<script type="module" src="/main.js"></script>
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
<!-- site-head:end -->
<script type="module" src="/main.js?v=20260929d"></script>
</head>
<body>
<body data-section="tutorials">
<!-- site-header:start -->
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">Source</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme">
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- site-header:end -->
<main class="section-page">
<section class="section-hero">
<p class="eyebrow">Tutorials</p>
<h1>Hands-on AI Quant.</h1>
<p class="section-hero__lede">Guides for turning finance ideas into inspectable Vibe-Trading workflows.</p>
<p class="eyebrow" data-i18n="nav.tutorials">Tutorials</p>
<h1 data-i18n="tutorials.heading">Hands-on AI Quant.</h1>
<p class="section-hero__lede" data-i18n="tutorials.lede">Guides for turning finance ideas into inspectable Vibe-Trading workflows.</p>
</section>
<section class="knowledge-grid" aria-label="Tutorial list">
<a class="knowledge-card" href="/tutorials/vibe-trading-beginner-zh.html">
<strong>入门</strong>
<h3>Vibe-Trading 中文入门教程</h3>
<p>面向非金融专业读者,解释因子、策略、回测、多市场数据源、券商 connector 和 Shadow Account。</p>
<section class="knowledge-grid" aria-label="Tutorial list" data-i18n-attr="aria-label=tutorials.listLabel">
<a class="knowledge-card" href="/tutorials/vibe-trading-beginner.html" data-href-zh="/tutorials/vibe-trading-beginner-zh.html">
<strong data-i18n="tutorials.beginner.tag">Beginner</strong>
<h3 data-i18n="tutorials.beginner.title">Vibe-Trading beginner's guide</h3>
<p data-i18n="tutorials.beginner.text">For readers without a finance background: factors, strategies, backtests, multi-market data sources, broker connectors and Shadow Account.</p>
</a>
</section>
</main>
<!-- site-footer:start -->
<footer class="site-footer">
<span>Vibe-Trading Wiki</span>
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
<!-- site-footer:end -->
</body>
</html>
+49 -38
View File
@@ -1,20 +1,7 @@
<!doctype html>
<html lang="zh-CN">
<html lang="zh-CN" data-page-lang="zh">
<head>
<meta charset="utf-8">
<script>
(function () {
try {
var stored = localStorage.getItem("vibetrading-theme");
var resolved = stored === "dark" || stored === "light"
? stored
: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", resolved);
} catch (e) {
document.documentElement.setAttribute("data-theme", "light");
}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vibe-Trading 中文入门教程 | Vibe-Trading Wiki</title>
<meta name="description" content="面向非金融专业读者的 Vibe-Trading 中文入门教程:理解因子、策略、回测、多市场数据源、券商连接器与 Shadow Account。">
@@ -26,12 +13,18 @@
<meta property="og:image" content="https://vibetrading.wiki/assets/icon.png">
<meta property="og:url" content="https://vibetrading.wiki/tutorials/vibe-trading-beginner-zh.html">
<link rel="canonical" href="https://vibetrading.wiki/tutorials/vibe-trading-beginner-zh.html">
<link rel="alternate" hreflang="en" href="/tutorials/vibe-trading-beginner.html">
<link rel="alternate" hreflang="zh-CN" href="/tutorials/vibe-trading-beginner-zh.html">
<link rel="alternate" hreflang="x-default" href="/tutorials/vibe-trading-beginner.html">
<!-- site-head:start -->
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
<script type="module" src="/main.js"></script>
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
<!-- site-head:end -->
<script type="module" src="/main.js?v=20260929d"></script>
<style>
.tutorial {
width: min(940px, calc(100% - 32px));
@@ -300,31 +293,35 @@
}
</style>
</head>
<body>
<body data-section="tutorials">
<!-- site-header:start -->
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">Source</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme">
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- site-header:end -->
<main class="tutorial">
<header>
@@ -390,7 +387,7 @@
</article>
<article class="module-card">
<h3>因子层:factor / alpha</h3>
<p><code>agent/src/factors/</code> 内置 456 个 alpha。因子是给股票打分的数值信号,不是下单规则,也不是收益保证。</p>
<p><code>agent/src/factors/</code> 内置 462 个 alpha。因子是给股票打分的数值信号,不是下单规则,也不是收益保证。</p>
</article>
<article class="module-card">
<h3>回测层:backtest</h3>
@@ -426,7 +423,7 @@
<tbody>
<tr>
<td>标的 / symbol</td>
<td>你研究或交易的对象,比如 <code>AAPL</code>、<code>BTC-USDT</code>、<code>600519.SH</code>。</td>
<td>你研究或交易的对象,比如 <code>AAPL.US</code>、<code>BTC-USDT</code>、<code>600519.SH</code>。</td>
<td>回测配置里的 <code>codes</code>,券商工具里的 <code>symbol</code>。</td>
</tr>
<tr>
@@ -516,7 +513,7 @@
</tr>
<tr>
<td>mandate</td>
<td>实盘授权边界:能交易哪些标的、单笔多少、最大仓位、每日亏损限制等。</td>
<td>实盘授权边界:能交易哪些标的、单笔多少、最大总敞口、每天最多交易几笔等。</td>
<td>实盘下单路径的风控门。</td>
</tr>
</tbody>
@@ -531,7 +528,7 @@
<p>重要:因子不是策略。因子只回答“哪个标的分数更高”。策略还要回答“买多少、什么时候买、什么时候卖、交易成本多少、是否允许集中持仓、是否能成交”。</p>
</div>
<p>当前仓库的 Alpha Zoo 有 456 个 alpha,分成四类:</p>
<p>当前仓库的 Alpha Zoo 有 462 个 alpha,分成五个因子集:</p>
<table class="term-grid">
<thead>
<tr>
@@ -543,7 +540,7 @@
<tbody>
<tr>
<td><code>academic</code></td>
<td>10</td>
<td>12</td>
<td>学术风格因子,例如动量、反转、52 周高点、非流动性等,适合入门理解。</td>
</tr>
<tr>
@@ -561,6 +558,11 @@
<td>154</td>
<td>Qlib Alpha158 特征,常作为机器学习模型的输入特征。</td>
</tr>
<tr>
<td><code>fundamental</code></td>
<td>4</td>
<td>用时点正确的财务报表数据构建的因子:净资产收益率、毛利盈利能力、盈利收益率和资产增长。</td>
</tr>
</tbody>
</table>
@@ -574,7 +576,7 @@
<li><strong>value / quality</strong>:估值或质量风格,通常更偏中长期。</li>
</ul>
<p>先用 CLI 浏览,而不是直接读 456 个源码文件:</p>
<p>先用 CLI 浏览,而不是直接读 462 个源码文件:</p>
<pre><code>vibe-trading alpha list --zoo academic
vibe-trading alpha show academic_mkt_rf
vibe-trading alpha bench --zoo academic --universe sp500 --period 2020-2025 --top 10</code></pre>
@@ -681,7 +683,7 @@ vibe-trading connector check</code></pre>
<h2>8. Shadow Account</h2>
<p>Shadow Account 是“复盘你自己”的功能。它不是从网上找一个通用策略,而是读取你的交易流水,配对买入和卖出,找出你赚钱交易里反复出现的规则,再回测一个规则版的 shadow strategy。</p>
<p>当前交易流水解析支持若干常见券商导出格式和 generic CSV。它会输出持仓天数、胜率、盈亏比、回撤、处置效应、过度交易、追涨、锚定等行为诊断。随后 Shadow Account 会提取 3 到 5 条 if-then 规则,生成策略代码,跑多市场回测,最后渲染 HTML/PDF 报告。</p>
<p>当前交易流水解析支持若干常见券商导出格式和 generic CSV。它会输出持仓天数、胜率、盈亏比、回撤、处置效应、过度交易、追涨、锚定等行为诊断。随后 Shadow Account 会提取最多 5 条 if-then 规则,生成策略代码,跑多市场回测,最后渲染 HTML/PDF 报告。</p>
<pre><code>vibe-trading --upload trades_export.csv
vibe-trading run -p "Analyze my trading behavior, extract my shadow strategy, and compare it with my actual trades."</code></pre>
@@ -692,7 +694,7 @@ vibe-trading run -p "Analyze my trading behavior, extract my shadow strategy, an
<section id="learning-path">
<h2>9. 建议学习路线</h2>
<ol>
<li><strong>第一天:只跑起来。</strong> 安装、初始化、打开 CLI 或 Web UI,问一个你熟悉的问题,不连券商,不下单。</li>
<li><strong>第一天:只跑起来。</strong> 安装、初始化、打开 CLI 或 Web UI(Web UI 要用 Docker,或在源码目录执行 <code>vibe-trading setup</code> 后在 http://127.0.0.1:8899 打开;只用 <code>pip install</code> 安装时不带构建好的 Web UI),问一个你熟悉的问题,不连券商,不下单。</li>
<li><strong>第二天:只看数据。</strong> 选几个你熟悉的标的,理解 symbol、OHLCV、数据源 fallback 和缺失值。</li>
<li><strong>第三天:只看因子。</strong> 从 <code>academic</code> zoo 开始,用 <code>alpha show</code> 看公式,用 <code>alpha bench</code> 看 IC/IR,不急着交易。</li>
<li><strong>第四天:做一个简单回测。</strong> 让 agent 生成一个动量或均线策略,重点看 config、signal_engine、回测指标和交易明细。</li>
@@ -728,13 +730,22 @@ vibe-trading connector list</code></pre>
</section>
</main>
<!-- site-footer:start -->
<footer class="site-footer">
<span>Vibe-Trading Wiki</span>
<a href="/docs/">Docs</a>
<a href="/tutorials/">Tutorials</a>
<a href="/alpha-library/">Alpha Library</a>
<a href="/research-lab/">Research Lab</a>
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
<!-- site-footer:end -->
</body>
</html>
+749
View File
@@ -0,0 +1,749 @@
<!doctype html>
<html lang="en" data-page-lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vibe-Trading Beginner’s Guide | Vibe-Trading Wiki</title>
<meta name="description" content="A Vibe-Trading beginner’s guide for readers without a finance background: what factors, strategies, backtests, multi-market data sources, broker connectors and Shadow Account are.">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta property="og:type" content="article">
<meta property="og:site_name" content="Vibe-Trading Wiki">
<meta property="og:title" content="Vibe-Trading Beginner’s Guide">
<meta property="og:description" content="From finance jargon to hands-on use: helps readers without a finance background understand Vibe-Trading and start using it.">
<meta property="og:image" content="https://vibetrading.wiki/assets/icon.png">
<meta property="og:url" content="https://vibetrading.wiki/tutorials/vibe-trading-beginner.html">
<link rel="canonical" href="https://vibetrading.wiki/tutorials/vibe-trading-beginner.html">
<link rel="alternate" hreflang="en" href="/tutorials/vibe-trading-beginner.html">
<link rel="alternate" hreflang="zh-CN" href="/tutorials/vibe-trading-beginner-zh.html">
<link rel="alternate" hreflang="x-default" href="/tutorials/vibe-trading-beginner.html">
<!-- site-head:start -->
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;family=IBM+Plex+Sans:wght@400;500;600;700&amp;display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css?v=20260929d">
<script src="/theme-init.js?v=20260929d"></script>
<!-- site-head:end -->
<script type="module" src="/main.js?v=20260929d"></script>
<style>
.tutorial {
width: min(940px, calc(100% - 32px));
margin: 0 auto;
padding: 64px 0 96px;
}
.tutorial header {
border-bottom: 1px solid var(--line);
padding-bottom: 36px;
margin-bottom: 36px;
}
.tutorial h1 {
max-width: 900px;
font-size: clamp(2rem, 6vw, 5.4rem);
line-height: 1;
overflow-wrap: break-word;
}
.tutorial .lede {
max-width: 760px;
margin: 22px 0 0;
color: var(--ink);
font-family: var(--serif);
font-size: clamp(1.12rem, 2.4vw, 1.38rem);
line-height: 1.55;
}
.tutorial section {
margin: 54px 0;
scroll-margin-top: 92px;
}
.tutorial h2 {
margin-bottom: 16px;
font-size: clamp(1.8rem, 4vw, 3rem);
line-height: 1.08;
}
.tutorial h3 {
margin-top: 28px;
font-size: 1.16rem;
}
.tutorial p,
.tutorial li,
.tutorial td,
.tutorial th {
font-size: 1.02rem;
line-height: 1.72;
}
.tutorial p,
.tutorial li {
color: var(--ink);
}
.tutorial a {
color: var(--accent-3);
text-decoration: underline;
text-underline-offset: 3px;
}
.toc,
.callout,
.term-grid,
.module-grid,
.route-grid,
.workflow-flow {
border: 1px solid var(--line);
border-radius: 8px;
background: color-mix(in srgb, var(--surface) 88%, transparent);
}
.toc {
padding: 18px 20px;
}
.toc ol {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px 22px;
margin: 0;
padding-left: 1.25rem;
}
.callout {
padding: 18px 20px;
border-left: 4px solid var(--accent);
}
.callout.warning {
border-left-color: var(--accent-2);
}
.callout p {
margin: 0;
}
.module-grid,
.route-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px;
overflow: hidden;
}
.module-card,
.route-card {
background: var(--surface);
padding: 18px;
}
.module-card code,
.route-card code,
.tutorial td code,
.tutorial li code,
.tutorial p code {
font-family: var(--mono);
font-size: 0.9em;
color: var(--accent);
}
.module-card p,
.route-card p {
margin-bottom: 0;
color: var(--muted);
}
.term-grid {
width: 100%;
border-collapse: collapse;
overflow: hidden;
}
.term-grid th,
.term-grid td {
border-bottom: 1px solid var(--line);
padding: 12px 14px;
text-align: left;
vertical-align: top;
}
.term-grid th {
color: var(--muted);
font-family: var(--mono);
font-size: 0.78rem;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.term-grid tr:last-child td {
border-bottom: 0;
}
.workflow-flow {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 1px;
overflow: hidden;
margin: 24px 0;
}
.flow-step {
min-height: 132px;
background: var(--surface);
padding: 16px;
}
.flow-step span {
display: block;
margin-bottom: 20px;
color: var(--accent-2);
font-family: var(--mono);
font-size: 0.82rem;
font-weight: 600;
}
.flow-step p {
margin: 0;
color: var(--muted);
font-size: 0.95rem;
}
.tutorial pre {
margin: 18px 0;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--surface-2);
padding: 18px;
white-space: pre-wrap;
}
.tutorial pre code {
color: var(--ink);
font-family: var(--mono);
font-size: 0.92rem;
}
.meta-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 24px;
}
.meta-list span {
border: 1px solid var(--line);
border-radius: 6px;
background: var(--surface);
padding: 8px 10px;
color: var(--muted);
font-family: var(--mono);
font-size: 0.78rem;
}
@media (max-width: 800px) {
.toc ol,
.module-grid,
.route-grid,
.workflow-flow {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.tutorial {
width: min(100% - 32px, 940px);
padding-top: 44px;
}
.tutorial h1 {
font-size: clamp(2.05rem, 10vw, 2.75rem);
line-height: 1.06;
overflow-wrap: break-word;
}
.tutorial .lede {
font-size: 1rem;
}
.tutorial p,
.tutorial li,
.tutorial td,
.tutorial th {
font-size: 0.97rem;
}
.term-grid {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.term-grid th,
.term-grid td {
min-width: 160px;
}
.meta-list span {
font-size: 0.74rem;
}
}
</style>
</head>
<body data-section="tutorials">
<!-- site-header:start -->
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home" data-i18n-attr="aria-label=header.homeLabel">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary" data-i18n-attr="aria-label=header.navLabel">
<a href="/home/" data-nav="home" data-i18n="nav.home">Home</a>
<a href="/docs/" data-nav="docs" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-nav="tutorials" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-nav="alpha" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-nav="lab" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
</nav>
<div class="site-actions">
<button class="lang-pill" id="lang-toggle" type="button" lang="zh-CN" aria-label="切换到中文">中文</button>
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme" data-i18n-attr="aria-label=header.themeLabel">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- site-header:end -->
<main class="tutorial">
<header>
<p class="eyebrow">Tutorial · Beginner Guide</p>
<h1>From finance jargon to hands-on use: understanding Vibe-Trading</h1>
<p class="lede">This guide is for readers with no finance training and little trading experience. It does not teach you a “can’t-lose strategy”. Instead, it explains what factors, strategies, backtests, data sources, broker connectors and Shadow Account are in Vibe-Trading, and how to start using them in a safe order.</p>
<div class="meta-list" aria-label="Guide scope">
<span>Multi-market</span>
<span>Data sources</span>
<span>Factors / Alpha</span>
<span>Backtesting</span>
<span>Broker connectors</span>
<span>Shadow Account</span>
</div>
</header>
<nav class="toc" aria-label="Contents">
<ol>
<li><a href="#mental-model">Start with a map of the project</a></li>
<li><a href="#terms">Common finance terms in plain English</a></li>
<li><a href="#factors">Factors and the Alpha Zoo</a></li>
<li><a href="#strategy">Strategies and the Signal Engine</a></li>
<li><a href="#backtest">How backtesting works</a></li>
<li><a href="#markets">Choosing markets and data sources</a></li>
<li><a href="#connectors">Broker connectors</a></li>
<li><a href="#shadow">Shadow Account</a></li>
<li><a href="#learning-path">A suggested learning path</a></li>
<li><a href="#contribute">What you can contribute</a></li>
</ol>
</nav>
<section id="mental-model">
<h2>1. Start with a map of the project</h2>
<p>To begin with, think of Vibe-Trading as a workbench for trading research. You ask a question in plain language, and the project breaks it into steps: reading data, computing factors, writing a strategy, backtesting, reporting and connecting to accounts. It is not a broker and it never holds your money. Its real value is that every research step can be run, checked again later, and kept.</p>
<div class="workflow-flow" aria-label="Vibe-Trading workflow">
<div class="flow-step">
<span>01 Prompt</span>
<p>You ask a question, for example “backtest a momentum strategy on a set of large-cap stocks”.</p>
</div>
<div class="flow-step">
<span>02 Data</span>
<p>Loaders fetch market data for stocks, crypto, futures, forex and more.</p>
</div>
<div class="flow-step">
<span>03 Signal</span>
<p>Factor or strategy code turns the data into buy and sell signals.</p>
</div>
<div class="flow-step">
<span>04 Backtest</span>
<p>The backtest engine simulates the trades under that market’s rules.</p>
</div>
<div class="flow-step">
<span>05 Report</span>
<p>Out come metrics, charts, a report, a run card and artifacts you can check again.</p>
</div>
</div>
<div class="module-grid">
<article class="module-card">
<h3>Data layer: loader</h3>
<p><code>agent/backtest/loaders/</code> handles price and financial data. For each type of market it picks a public source, an optional source that needs a key, a source behind a broker gateway, or your own local files, and falls back to another source where one is available.</p>
</article>
<article class="module-card">
<h3>Factor layer: factor / alpha</h3>
<p><code>agent/src/factors/</code> ships 462 alphas. A factor is a numeric signal that scores stocks. It is not an order rule, and it is not a promise of returns.</p>
</article>
<article class="module-card">
<h3>Backtest layer: backtest</h3>
<p><code>agent/backtest/</code> runs simulated trading from <code>config.json</code> and <code>code/signal_engine.py</code>. Stocks, crypto, futures, forex and multi-asset portfolios each have their own engines.</p>
</article>
<article class="module-card">
<h3>Broker layer: connector</h3>
<p><code>agent/src/trading/</code> puts every broker behind the same set of profiles: account, positions, orders, quotes, historical bars, placing orders and cancelling them. This is different from a loader: a loader reads market data, while a connector reads, or acts on, your brokerage account.</p>
</article>
<article class="module-card">
<h3>Tool layer: MCP tools</h3>
<p><code>agent/src/tools/</code> exposes these capabilities as tools the agent can call, such as <code>backtest</code>, <code>factor_analysis</code>, <code>trading_positions</code> and <code>analyze_trade_journal</code>.</p>
</article>
<article class="module-card">
<h3>Review layer: Shadow Account</h3>
<p><code>agent/src/shadow_account/</code> pulls your habits out of your trade history as rules, backtests a “rule-based version of you”, and then works out where and why it differs from your real trades.</p>
</article>
</div>
</section>
<section id="terms">
<h2>2. Common finance terms in plain English</h2>
<p>Put the jargon into everyday words first, and the code becomes much easier to read. The terms below come up again and again in the README, the Alpha Zoo, backtests and broker connectors.</p>
<table class="term-grid">
<thead>
<tr>
<th>Term</th>
<th>In plain English</th>
<th>Where it lives in the project</th>
</tr>
</thead>
<tbody>
<tr>
<td>Symbol / instrument</td>
<td>The thing you research or trade, such as <code>AAPL.US</code>, <code>BTC-USDT</code> or <code>600519.SH</code>.</td>
<td><code>codes</code> in the backtest config; <code>symbol</code> in the broker tools.</td>
</tr>
<tr>
<td>Bar (candlestick) / OHLCV</td>
<td>The opening, highest, lowest and closing price, plus the volume traded, over one period of time.</td>
<td>The basic price columns a loader returns: <code>open/high/low/close/volume</code>.</td>
</tr>
<tr>
<td>VWAP</td>
<td>Volume-weighted average price. Roughly, “the average price the market actually traded at during this period”.</td>
<td>Some alphas need a <code>vwap</code> column.</td>
</tr>
<tr>
<td>amount</td>
<td>Turnover in money: the value traded, usually price times volume.</td>
<td>Used by some volume, liquidity and short-horizon factors; some stock data sources provide it.</td>
</tr>
<tr>
<td>Factor / alpha</td>
<td>A formula that scores a group of stocks. A high score might mean a stock is more worth buying, or more worth selling; that has to be checked with IC, backtests and an economic explanation.</td>
<td><code>agent/src/factors/zoo/</code>.</td>
</tr>
<tr>
<td>Strategy</td>
<td>Turns signals into trading rules: what to buy, how much, when to sell, how many positions to hold at most, and how to cut losses.</td>
<td><code>signal_engine.py</code> and the backtest config.</td>
</tr>
<tr>
<td>Signal Engine</td>
<td>The Python class that holds a strategy’s logic in this project. It reads bars or a panel and outputs target positions or buy and sell signals.</td>
<td><code>code/signal_engine.py</code> in a backtest run dir.</td>
</tr>
<tr>
<td>Backtest</td>
<td>Uses historical data to simulate “what would have happened if I had traded by these rules back then”. It can only show past performance; it cannot prove future returns.</td>
<td><code>agent/backtest/runner.py</code> and each market’s engine.</td>
</tr>
<tr>
<td>IC</td>
<td>Information Coefficient. Here it is the Spearman correlation between how a factor ranks stocks on a given day and how those stocks later rank by return. A positive IC means high-scoring stocks were more likely to go up afterwards.</td>
<td><code>compute_ic_series()</code>.</td>
</tr>
<tr>
<td>IR</td>
<td>Mean IC divided by the volatility of IC. Roughly, “how steady is this factor?”</td>
<td>One of the ranking metrics in <code>alpha bench</code>.</td>
</tr>
<tr>
<td>lookahead</td>
<td>Peeking at future data: for example, using information only known after today’s close while pretending you knew it before today’s open.</td>
<td>Factor operators forbid a negative shift; backtests execute on the next bar to reduce the risk of peeking.</td>
</tr>
<tr>
<td>PIT</td>
<td>Point-in-time: using only data that was already public and available at that moment.</td>
<td>Financial fields, Shadow Account entry context and backtest validation all stress this boundary.</td>
</tr>
<tr>
<td>warmup</td>
<td>At the start of a rolling window there is not enough data yet. For example, a 20-day moving average has no valid value for the first 19 days.</td>
<td><code>min_warmup_bars</code> in alpha metadata.</td>
</tr>
<tr>
<td>NaN</td>
<td>A missing value. In financial data a missing value is not 0; it often means a trading halt, a gap in the data source, or a window that is not full yet.</td>
<td>Factor operators keep NaN and never silently fill it with 0.</td>
</tr>
<tr>
<td>Slippage</td>
<td>The gap between the price you wanted to trade at and the price you actually got.</td>
<td>Each market engine has its own simplified slippage parameters.</td>
</tr>
<tr>
<td>Drawdown</td>
<td>How far the account’s value falls from a peak to a low. Maximum drawdown is a key measure of how painful a strategy is to live with.</td>
<td>Backtest metrics.</td>
</tr>
<tr>
<td>benchmark</td>
<td>The yardstick you compare against, such as the CSI 300, the Hang Seng Index or SPY. A strategy is judged not only on whether it made money, but also on whether it beat its yardstick.</td>
<td>The backtest report and its benchmark comparison.</td>
</tr>
<tr>
<td>paper / live</td>
<td>Paper is a simulated account; live is a real account. Be very careful with a real account.</td>
<td><code>environment</code> in a connector profile.</td>
</tr>
<tr>
<td>mandate</td>
<td>The limits you authorize for live trading: which instruments may be traded, how large a single order may be, the maximum total exposure, how many trades are allowed per day, and so on.</td>
<td>The risk gate on the live-order path.</td>
</tr>
</tbody>
</table>
</section>
<section id="factors">
<h2>3. Factors and the Alpha Zoo</h2>
<p>Factors are the part of Vibe-Trading that most often confuses readers without a finance background. Think of a factor as a “ranking formula”: every day it gives each of many stocks a score, and then you check whether the high-scoring group went on to do better.</p>
<div class="callout">
<p>Important: a factor is not a strategy. A factor only answers “which instrument scores higher”. A strategy also has to answer how much to buy, when to buy, when to sell, what the trading costs are, whether concentrated positions are allowed, and whether the orders could actually be filled.</p>
</div>
<p>The Alpha Zoo in the current repository holds 462 alphas in five zoos:</p>
<table class="term-grid">
<thead>
<tr>
<th>Zoo</th>
<th>Count</th>
<th>How to think about it</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>academic</code></td>
<td>12</td>
<td>Academic-style factors such as momentum, reversal, the 52-week high and illiquidity. A good place to start.</td>
</tr>
<tr>
<td><code>alpha101</code></td>
<td>101</td>
<td>Formulaic alphas; many of the formulas mix price, volume, ranks and rolling correlations.</td>
</tr>
<tr>
<td><code>gtja191</code></td>
<td>191</td>
<td>Short-horizon trading factors published by Guotai Junan Securities, rooted mainly in the China A-share market.</td>
</tr>
<tr>
<td><code>qlib158</code></td>
<td>154</td>
<td>Features from Qlib’s Alpha158 set, often used as inputs to machine-learning models.</td>
</tr>
<tr>
<td><code>fundamental</code></td>
<td>4</td>
<td>Factors built from point-in-time financial statements: return on equity, gross profitability, earnings yield and asset growth.</td>
</tr>
</tbody>
</table>
<p>Common themes can be read like this:</p>
<ul>
<li><strong>momentum</strong>: do instruments that have been strong lately keep being strong?</li>
<li><strong>reversal</strong>: after a big fall or a big rise, does the price swing back?</li>
<li><strong>volume</strong>: do changes in trading volume carry information about what money is doing?</li>
<li><strong>volatility</strong>: can volatility explain later returns or risk?</li>
<li><strong>liquidity / microstructure</strong>: signals about liquidity, the order book and how trades are made up.</li>
<li><strong>value / quality</strong>: valuation or quality styles, usually more medium- to long-term.</li>
</ul>
<p>Browse with the CLI first, rather than reading 462 source files straight away:</p>
<pre><code>vibe-trading alpha list --zoo academic
vibe-trading alpha show academic_mkt_rf
vibe-trading alpha bench --zoo academic --universe sp500 --period 2020-2025 --top 10</code></pre>
<p><code>alpha bench</code> tests factors on a universe of stocks. The project’s built-in classification is: <code>alive</code> when the mean IC is above 0.02, at least 55% of the IC readings are positive and the t-statistic is significant; <code>reversed</code> when the IC is significantly negative; <code>dead</code> for everything else. This is not a “you can buy it” verdict, only the first filter at the research stage.</p>
</section>
<section id="strategy">
<h2>4. Strategies and the Signal Engine</h2>
<p>A strategy turns a research idea into rules that can be executed. A very simple strategy might be: “Hold when the 20-day moving average is above the 60-day moving average; otherwise stay out of the market.” A multi-factor strategy might be: “Within a pool of stocks, combine momentum, quality and volatility into one score, buy the top 20, and rebalance once a month.”</p>
<p>In Vibe-Trading, a strategy usually ends up as a run dir:</p>
<pre><code>my_run/
config.json
code/
signal_engine.py</code></pre>
<p><code>config.json</code> tells the backtest system the symbols, the start and end dates, the data source, the bar interval and which market engine to use. <code>signal_engine.py</code> tells it what signal to produce on each bar or trading day.</p>
<div class="callout warning">
<p>The most common beginner mistake is to treat “the factor score is high” as “buy with everything I have, right now”. A real strategy also needs position limits, a rebalancing frequency, trading costs, handling for trading halts and daily price limits, risk control and a benchmark comparison.</p>
</div>
</section>
<section id="backtest">
<h2>5. How backtesting works</h2>
<p>A backtest is not a forecaster; it is a simulator of the past. Its value is that, before you put real money in, you can see what a set of rules went through in historical data: how much it made, its largest loss, how often it traded, whether it only worked in one particular year, and whether fees ate the profits.</p>
<h3>Starting a backtest</h3>
<pre><code>vibe-trading run -p "Backtest a momentum strategy on a broad equity universe from 2020 to 2025. Include benchmark comparison, drawdown, turnover, and a short explanation."</code></pre>
<p>If the agent creates a run dir, it will eventually call the <code>backtest</code> tool. That tool checks <code>config.json</code> and <code>code/signal_engine.py</code>, then runs <code>agent/backtest/runner.py</code>.</p>
<h3>Why the market engine matters</h3>
<p>Different markets do not share one set of trading rules. In the project, stocks, crypto, futures, forex and multi-asset portfolios go through different engines. Some stock markets have to handle T+1 (a share bought today cannot be sold until the next trading day), daily price limits, board lots (the minimum number of shares per order), commissions, stamp duty, settlement fees and slippage; others may allow T+0 same-day round trips, short selling or fractional shares.</p>
<p>This means you cannot judge a signal only by how accurate it is; you also have to see whether it can actually be filled under real market rules, how high the costs are, and whether positions become too concentrated. Many short-term strategies that look great fall apart once slippage and fees are added.</p>
</section>
<section id="markets">
<h2>6. Choosing markets and data sources</h2>
<p>When new users try the project for the first time, the things they mix up most are the “market”, the “data source” and the “brokerage account”. The market decides the trading rules; the data source decides where the historical data comes from; a brokerage account only comes in when you need to read a real account or place simulated or real orders.</p>
<div class="route-grid">
<article class="route-card">
<h3>Public data sources</h3>
<p>Good for getting started, ordinary price data and quick trial runs. The upside is a low barrier to entry; the downside is that coverage, rate limits, adjustment for splits and dividends, field quality and availability vary from source to source.</p>
</article>
<article class="route-card">
<h3>Optional sources that need a key</h3>
<p>Good for steadier research and richer fields, such as index constituents, financial data, fundamental fields or professional market data. The downside is that you need to apply for a token or pay.</p>
</article>
<article class="route-card">
<h3>Local data</h3>
<p>The <code>local</code> loader can read your own CSV, Parquet or DuckDB data. For cleaned historical data, offline research and reproducible experiments, local data is usually the easiest to keep under control.</p>
</article>
<article class="route-card">
<h3>Cross-market portfolios</h3>
<p>CompositeEngine supports research on portfolios that span markets. For example, when one strategy holds stocks, crypto and futures at the same time, each market is handled by its own engine.</p>
</article>
</div>
</section>
<section id="connectors">
<h2>7. Broker connectors</h2>
<p>Keep connectors and loaders apart: a loader reads market data, while a connector links to your brokerage account. Typical connector capabilities include reading the account, positions, orders, quotes and historical bars, and, under a profile that allows it, placing or cancelling orders.</p>
<pre><code>vibe-trading connector list
vibe-trading connector use &lt;profile-id&gt;
vibe-trading connector check</code></pre>
<p>When you look at a connector, don’t start by asking “can it trade automatically?”. Look at the profile’s safety properties first:</p>
<table class="term-grid">
<thead>
<tr>
<th>Profile type</th>
<th>What it means</th>
<th>When to use it</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>readonly</code></td>
<td>Only reads the account, positions, orders or quotes; it never submits an order.</td>
<td>Use this first when you connect a real account for the first time.</td>
</tr>
<tr>
<td><code>paper</code></td>
<td>Connects to a simulated or sandbox account; even when it places orders, no real money is involved.</td>
<td>For checking a strategy, order fields, fill reports and the cancel flow.</td>
</tr>
<tr>
<td><code>live</code></td>
<td>Connects to a real account. Every write action must pass the authorized limits, the kill switch, fail-closed checks and the audit log.</td>
<td>Only once you fully understand the risks and have tested with read-only and paper first.</td>
</tr>
</tbody>
</table>
<p>If you just want to learn the project, stick to read-only and paper at first. Once you can explain why every simulated order was produced, then think about connecting a real account.</p>
</section>
<section id="shadow">
<h2>8. Shadow Account</h2>
<p>Shadow Account is a feature for reviewing yourself. Instead of finding a generic strategy online, it reads your trade history, pairs up buys and sells, finds the rules that keep recurring in your profitable trades, and then backtests a rule-based shadow strategy.</p>
<p>Trade-history parsing currently supports several common broker export formats and a generic CSV. It reports behavioural diagnostics such as holding period, win rate, profit/loss ratio, drawdown, the disposition effect (holding losing positions longer than winning ones), overtrading, chasing rallies and anchoring. Shadow Account then extracts up to five if-then rules, generates strategy code, runs backtests across markets, and finally renders an HTML/PDF report.</p>
<pre><code>vibe-trading --upload trades_export.csv
vibe-trading run -p "Analyze my trading behavior, extract my shadow strategy, and compare it with my actual trades."</code></pre>
<p>If you want to add this path for a new broker, the best place to start is normalizing its official API or user-exported trade records into the project’s <code>TradeRecord</code> format, then reusing the existing Trade Journal and Shadow Account flow.</p>
</section>
<section id="learning-path">
<h2>9. A suggested learning path</h2>
<ol>
<li><strong>Day 1: just get it running.</strong> Install, initialize, and open the CLI or the Web UI (the Web UI at http://127.0.0.1:8899 comes with Docker, or with a source checkout after <code>vibe-trading setup</code>; <code>pip install</code> alone ships no built Web UI). Ask a question about something you know well. No broker connection, no orders.</li>
<li><strong>Day 2: just look at data.</strong> Pick a few instruments you know and get to grips with symbols, OHLCV, data-source fallback and missing values.</li>
<li><strong>Day 3: just look at factors.</strong> Start with the <code>academic</code> zoo: read formulas with <code>alpha show</code> and check IC/IR with <code>alpha bench</code>. No rush to trade.</li>
<li><strong>Day 4: run a simple backtest.</strong> Have the agent write a momentum or moving-average strategy, and focus on the config, signal_engine, the backtest metrics and the list of trades.</li>
<li><strong>Day 5: review your own trades.</strong> If you have a CSV exported from your broker, use Trade Journal and Shadow Account to see your behaviour profile. If you don’t have one, that’s fine: start by reading the report template and the data structures.</li>
<li><strong>Day 6 onward: connect a broker.</strong> Start with read-only and paper, confirm that the account, positions and orders read correctly, and then learn the live-trading boundaries: mandate, kill switch and fail-closed.</li>
</ol>
<p>A starter set of commands:</p>
<pre><code>pip install vibe-trading-ai
vibe-trading init
vibe-trading
vibe-trading serve --port 8899
vibe-trading alpha list --zoo academic
vibe-trading alpha bench --zoo academic --universe sp500 --period 2020-2025 --top 10
vibe-trading connector list</code></pre>
</section>
<section id="contribute">
<h2>10. What you can contribute</h2>
<p>If you want to contribute to the project, you don’t need to start with live order placement. A steadier route is to begin with docs, examples, parsers and tests.</p>
<ul>
<li><strong>Beginner docs.</strong> Explain factors, strategies, backtests, data sources and broker connectors in a way ordinary investors can follow.</li>
<li><strong>Reproducible examples.</strong> Add teaching runs across markets that include the data source, parameters, backtest metrics and risk notes, without turning them into investment advice.</li>
<li><strong>Trade-history import.</strong> If a broker’s official API or export file provides fills, orders and positions, normalize it into <code>TradeRecord</code> and plug it into Trade Journal and Shadow Account.</li>
<li><strong>Connector docs.</strong> Explain clearly how paper, live, read-only and trade profiles differ, so users don’t assume every connector can place live orders straight away.</li>
<li><strong>Safety tests.</strong> Add regression tests for the connector guard, the live mandate, paper-only limits, the path sandbox and uploaded-file parsing.</li>
</ul>
<div class="callout warning">
<p>Neither this guide nor Vibe-Trading is investment advice. Any real trade should first go through your own judgement, your broker’s confirmation, testing on a paper account, and risk control.</p>
</div>
</section>
</main>
<!-- site-footer:start -->
<footer class="site-footer">
<span class="site-footer__name">Vibe-Trading Wiki</span>
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="/tutorials/" data-i18n="nav.tutorials">Tutorials</a>
<a href="/alpha-library/" data-i18n="nav.alpha">Alpha Library</a>
<a href="/research-lab/" data-i18n="nav.lab">Research Lab</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
<div class="footer-traffic" id="footer-traffic" hidden>
<span class="ft-item">🤖 <strong id="vt-agents">–</strong> <span data-i18n="footer.agents">AI agents</span></span>
<span class="ft-item">👤 <strong id="vt-humans">–</strong> <span data-i18n="footer.humans">humans</span></span>
<span class="ft-item">📦 <strong id="vt-installs">–</strong> <span data-i18n="footer.installs">installs/mo</span></span>
<span class="ft-note" data-i18n="footer.note">visitors to this site · anonymous · first-party</span>
</div>
</footer>
<!-- site-footer:end -->
</body>
</html>