docs(wiki): rewrite the docs for 0.1.16, in English and Chinese
The docs still described 0.1.11: seven engines, six data sources, a Quick start whose `vibe-trading serve` step opens no UI after a PyPI install. Every count on the pages is now measured from the code (28 sources, 11 engines, 90 skills, 30 presets, 74 MCP tools, 18 brokers, 462 alphas), and five pages are new: Troubleshooting (no_progress, "0 steps", cut figures, bare US tickers), Checked numbers, Alpha Zoo, Brokers and live trading, Web UI and API. Chinese pages are served at /docs/zh/<page>. The header toggle switches language and remembers it; a first visit from a Chinese browser opens the Chinese pages. The version selector offered 0.1.7-0.1.11 while rendering one content set; it now lists only 0.1.16. wiki/scripts/check_docs_parity.mjs (run in CI) fails when the two languages differ in pages, sections, order, empty fields or internal links. A #section in a docs URL now scrolls to it. Landing page: "Seven engines" -> eleven, the uv tab no longer ends in `serve`, and a 中文文档 button.
This commit is contained in:
1 parent
e1dbea8ad3
commit
8a6d1934bc
11 files changed
+2484
-473
No files matched your search
File diff suppressed because it is too large.
Load diff
+43
-392
@@ -1,400 +1,51 @@
|
||||
export const DOCS_DEFAULT_VERSION = "0.1.11";
|
||||
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";
|
||||
|
||||
// One content set per language; every version alias renders the latest.
|
||||
export const DOCS_DEFAULT_VERSION = "0.1.16";
|
||||
export const DOCS_LATEST_ALIAS = "latest";
|
||||
export const DOCS_DEFAULT_PAGE = "getting-started/vibe-trading-overview";
|
||||
export const DOCS_DEFAULT_LANG = "en";
|
||||
|
||||
// `segment` is the first path segment after /docs/: /docs/latest/<page> is
|
||||
// English, /docs/zh/<page> is Chinese. Any other segment (an old version
|
||||
// number) resolves to English.
|
||||
export const DOCS_LANGUAGES = {
|
||||
en: { segment: DOCS_LATEST_ALIAS, htmlLang: "en", label: "English", structure: EN_STRUCTURE },
|
||||
zh: { segment: "zh", htmlLang: "zh-CN", label: "中文", structure: ZH_STRUCTURE }
|
||||
};
|
||||
|
||||
export const DOCS_VERSIONS = [
|
||||
{ name: "0.1.11", label: "0.1.11 (latest)" },
|
||||
{ name: "0.1.10", label: "0.1.10" },
|
||||
{ name: "0.1.9", label: "0.1.9" },
|
||||
{ name: "0.1.8", label: "0.1.8" },
|
||||
{ name: "0.1.7", label: "0.1.7" }
|
||||
{ name: DOCS_DEFAULT_VERSION, label: { en: `${DOCS_DEFAULT_VERSION} (latest)`, zh: `${DOCS_DEFAULT_VERSION}(最新)` } }
|
||||
];
|
||||
|
||||
export const DOCS_STRUCTURE = [
|
||||
{
|
||||
id: "getting-started",
|
||||
label: "Getting started",
|
||||
pages: [
|
||||
{
|
||||
id: "getting-started/vibe-trading-overview",
|
||||
title: "Vibe-Trading overview",
|
||||
description: "What Vibe-Trading is, where it fits, and what boundary it keeps.",
|
||||
lead: "Vibe-Trading is an open-source finance research workspace that turns natural-language prompts into market-data pulls, backtests, reports, and reusable research context.",
|
||||
sections: [
|
||||
{
|
||||
id: "what-it-is",
|
||||
title: "What it is",
|
||||
body: `
|
||||
<p>Vibe-Trading connects an agent loop to finance tools: data loaders, strategy generation, backtest engines, document readers, trade-journal analysis, persistent memory, and multi-agent research teams.</p>
|
||||
<p>The goal is not to replace judgment. The goal is to make every research step runnable, inspectable, and easy to repeat.</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "research-only",
|
||||
title: "Boundaries",
|
||||
body: `
|
||||
<p>Vibe-Trading is built for research, simulation, backtesting, and audit trails. 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), within the limits you set, and you can halt it instantly. It holds no funds, runs no execution venue, and is not investment advice.</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "capabilities",
|
||||
title: "Core capabilities",
|
||||
body: `
|
||||
<ul>
|
||||
<li>Natural-language CLI and web workflows.</li>
|
||||
<li>Seven backtest engines across equities, crypto, futures, forex, composites, and options portfolios.</li>
|
||||
<li>Market data routing across Tushare, OKX, yfinance, AKShare, CCXT, and Futu.</li>
|
||||
<li>Trade Journal and Shadow Account workflows for behavior diagnostics.</li>
|
||||
<li>Swarm presets for committee-style research reviews.</li>
|
||||
<li>MCP tools for Claude Desktop, OpenClaw, Cursor, and other MCP clients.</li>
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "getting-started/quick-start",
|
||||
title: "Quick start",
|
||||
description: "Install Vibe-Trading, initialize configuration, and run the first research task.",
|
||||
lead: "The fastest path is PyPI install, interactive setup, then either CLI research or the local web UI.",
|
||||
sections: [
|
||||
{
|
||||
id: "install",
|
||||
title: "Install",
|
||||
body: `
|
||||
<pre><code>pip install vibe-trading-ai
|
||||
vibe-trading init
|
||||
vibe-trading</code></pre>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "first-run",
|
||||
title: "First run",
|
||||
body: `
|
||||
<pre><code>vibe-trading run -p "Backtest a BTC-USDT 20/50 moving-average strategy for 2024, summarize return and drawdown, then export the report"</code></pre>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "web-ui",
|
||||
title: "Open the web UI",
|
||||
body: `
|
||||
<pre><code>vibe-trading serve --port 8899</code></pre>
|
||||
<p>The local web UI is useful when you want uploaded files, streaming swarm progress, Settings, and generated artifacts in one place.</p>
|
||||
`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "getting-started/configuration",
|
||||
title: "Configuration",
|
||||
description: "Provider, model, market-data, and deployment settings.",
|
||||
lead: "Vibe-Trading keeps secrets and deployment-specific choices in environment variables or local Settings, not in source files.",
|
||||
sections: [
|
||||
{
|
||||
id: "env",
|
||||
title: "Environment file",
|
||||
body: `
|
||||
<pre><code>LANGCHAIN_PROVIDER=deepseek
|
||||
LANGCHAIN_MODEL_NAME=deepseek-v4-pro
|
||||
TUSHARE_TOKEN=your-token
|
||||
TIMEOUT_SECONDS=2400</code></pre>
|
||||
<p>Run <code>vibe-trading init</code> to bootstrap the local configuration interactively.</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "keys",
|
||||
title: "Keys and data sources",
|
||||
body: `
|
||||
<p>Many HK, US, crypto, document, journal, and static analysis workflows work without paid market-data keys. A-share fundamental enrichment and some provider-specific workflows need their matching credentials.</p>
|
||||
<p>For non-local API or web deployments, configure <code>API_AUTH_KEY</code> and send requests with <code>Authorization: Bearer <key></code>.</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "models",
|
||||
title: "Model choice",
|
||||
body: `
|
||||
<p>Agent quality depends on tool use. Prefer strong tool-calling models for long research runs, swarms, and multi-step backtests. Avoid small distilled models for workflows where fabricated answers are costly.</p>
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
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" }
|
||||
},
|
||||
{
|
||||
id: "core-concepts",
|
||||
label: "Core concepts",
|
||||
pages: [
|
||||
{
|
||||
id: "core-concepts/research-workflow",
|
||||
title: "Research workflow",
|
||||
description: "How a Vibe-Trading run moves from prompt to evidence.",
|
||||
lead: "A good run routes the request, grounds it in data, executes tools, validates outputs, and leaves artifacts behind.",
|
||||
sections: [
|
||||
{
|
||||
id: "pipeline",
|
||||
title: "Pipeline",
|
||||
body: `
|
||||
<ol>
|
||||
<li><strong>Plan:</strong> choose relevant skills, tools, data sources, and swarm presets.</li>
|
||||
<li><strong>Ground:</strong> fetch market bars, documents, URLs, broker journals, or local files at runtime.</li>
|
||||
<li><strong>Execute:</strong> run backtests, factor analysis, options checks, exports, or report generation.</li>
|
||||
<li><strong>Validate:</strong> attach metrics, benchmark comparison, Monte Carlo, Bootstrap, Walk-Forward, warnings, and run cards when applicable.</li>
|
||||
<li><strong>Deliver:</strong> return the answer plus inspectable artifacts.</li>
|
||||
</ol>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "artifacts",
|
||||
title: "Artifacts",
|
||||
body: `
|
||||
<p>Backtests and research runs can produce reports, charts, generated strategy files, run metadata, and reusable context. The artifact trail matters because finance research often needs later inspection.</p>
|
||||
`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "core-concepts/backtesting",
|
||||
title: "Backtesting",
|
||||
description: "Market coverage, engines, metrics, and validation tools.",
|
||||
lead: "Vibe-Trading backtests daily and minute strategies across multiple asset classes, then keeps outputs auditable with metrics and run cards.",
|
||||
sections: [
|
||||
{
|
||||
id: "engines",
|
||||
title: "Engines",
|
||||
body: `
|
||||
<ul>
|
||||
<li>China A-share, global equity, crypto, China futures, global futures, forex, and composite engines.</li>
|
||||
<li>Options portfolio engine for option strategy research.</li>
|
||||
<li>Minute intervals including 1m, 5m, 15m, 30m, 1H, 4H, and 1D where supported by the data source.</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "validation",
|
||||
title: "Validation",
|
||||
body: `
|
||||
<p>Research runs can include benchmark comparison, Monte Carlo, Bootstrap confidence intervals, Walk-Forward validation, and run cards. Treat these as evidence helpers, not guarantees.</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "example",
|
||||
title: "Example",
|
||||
body: `
|
||||
<pre><code>vibe-trading run -p "Backtest an equal-weight SPY and BTC-USDT momentum rotation strategy for 2024 with benchmark comparison"</code></pre>
|
||||
`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "core-concepts/swarm-teams",
|
||||
title: "Swarm teams",
|
||||
description: "Preset research teams for committee-style analysis.",
|
||||
lead: "Swarm presets turn a research question into a small DAG of specialist workers, then stream progress and persist the final report.",
|
||||
sections: [
|
||||
{
|
||||
id: "presets",
|
||||
title: "Presets",
|
||||
body: `
|
||||
<p>Vibe-Trading includes 30 presets such as investment committee, quant strategy desk, crypto trading desk, macro rates and FX desk, and risk committee.</p>
|
||||
<pre><code>vibe-trading --swarm-presets
|
||||
vibe-trading --swarm-run investment_committee '{"topic":"BTC outlook"}'</code></pre>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "keys",
|
||||
title: "Model requirements",
|
||||
body: `
|
||||
<p>Most MCP tools work without an LLM key after install. <code>run_swarm</code> needs an LLM provider because it spawns internal worker agents.</p>
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "tools",
|
||||
label: "Tools",
|
||||
pages: [
|
||||
{
|
||||
id: "tools/data-sources",
|
||||
title: "Data sources",
|
||||
description: "How Vibe-Trading routes symbols and market data providers.",
|
||||
lead: "Data routing is provider-aware: mixed symbols can use <code>source=\"auto\"</code> while each market keeps its own data rules.",
|
||||
sections: [
|
||||
{
|
||||
id: "providers",
|
||||
title: "Providers",
|
||||
body: `
|
||||
<ul>
|
||||
<li>Tushare for China market and fundamental workflows when configured.</li>
|
||||
<li>OKX and CCXT for crypto symbols such as <code>BTC-USDT</code>.</li>
|
||||
<li>yfinance for global equities and common benchmarks.</li>
|
||||
<li>AKShare and Futu for additional China, Hong Kong, and market-specific coverage.</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "symbols",
|
||||
title: "Symbol conventions",
|
||||
body: `
|
||||
<p>Crypto pairs use uppercase hyphen format, for example <code>BTC-USDT</code>. Mixed-market research should prefer automatic source routing where possible.</p>
|
||||
`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "tools/shadow-account",
|
||||
title: "Shadow Account",
|
||||
description: "Turn a broker journal into behavior diagnostics and a counterfactual strategy path.",
|
||||
lead: "Shadow Account starts with your real trading records, extracts recurring rules, and compares actual trades with a rule-based shadow strategy.",
|
||||
sections: [
|
||||
{
|
||||
id: "flow",
|
||||
title: "Workflow",
|
||||
body: `
|
||||
<ol>
|
||||
<li>Read a broker export from supported formats or a generic CSV.</li>
|
||||
<li>Profile holding time, win rate, drawdown, PnL ratio, and behavior signals.</li>
|
||||
<li>Extract recurring if-then strategy rules.</li>
|
||||
<li>Run a shadow backtest and attribute delta-PnL.</li>
|
||||
<li>Render an HTML/PDF audit report.</li>
|
||||
</ol>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "example",
|
||||
title: "Example",
|
||||
body: `
|
||||
<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>
|
||||
`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "tools/finance-skills",
|
||||
title: "Finance skills",
|
||||
description: "Reusable finance knowledge modules loaded into research runs.",
|
||||
lead: "Skills keep domain knowledge close to the agent without hardcoding every behavior into the core loop.",
|
||||
sections: [
|
||||
{
|
||||
id: "library",
|
||||
title: "Library",
|
||||
body: `
|
||||
<p>Vibe-Trading bundles specialized skills across data sources, strategy generation, analysis, options, reporting, tools, and risk workflows.</p>
|
||||
<p>Good prompts can ask the agent to load or create skills for repeated research patterns.</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "examples",
|
||||
title: "Examples",
|
||||
body: `
|
||||
<ul>
|
||||
<li>Dividend analysis and yield-trap checks.</li>
|
||||
<li>A-share pre-ST risk screening.</li>
|
||||
<li>vn.py export and Pine Script export workflows.</li>
|
||||
<li>Factor research, macro analysis, and technical patterns.</li>
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "reference",
|
||||
label: "Reference",
|
||||
pages: [
|
||||
{
|
||||
id: "reference/cli",
|
||||
title: "CLI reference",
|
||||
description: "Common commands for local research, web UI, memory, swarms, and files.",
|
||||
lead: "The CLI is the fastest operator surface for repeatable research tasks.",
|
||||
sections: [
|
||||
{
|
||||
id: "commands",
|
||||
title: "Common commands",
|
||||
body: `
|
||||
<pre><code>vibe-trading
|
||||
vibe-trading init
|
||||
vibe-trading run -p "your research prompt"
|
||||
vibe-trading --upload report.pdf
|
||||
vibe-trading memory list
|
||||
vibe-trading serve --port 8899
|
||||
vibe-trading-mcp</code></pre>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "interactive",
|
||||
title: "Interactive mode",
|
||||
body: `
|
||||
<p>Interactive mode supports slash commands for recent runs, swarm presets, memory, and research navigation.</p>
|
||||
`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "reference/mcp-server",
|
||||
title: "MCP server",
|
||||
description: "Expose Vibe-Trading tools to MCP-compatible clients.",
|
||||
lead: "The MCP server runs as a stdio subprocess and exposes Vibe-Trading tools to agent clients.",
|
||||
sections: [
|
||||
{
|
||||
id: "start",
|
||||
title: "Start",
|
||||
body: `
|
||||
<pre><code>vibe-trading-mcp</code></pre>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "config",
|
||||
title: "Client config",
|
||||
body: `
|
||||
<pre><code>{
|
||||
"mcpServers": {
|
||||
"vibe-trading": {
|
||||
"command": "vibe-trading-mcp"
|
||||
}
|
||||
zh: {
|
||||
siteTitle: "Vibe-Trading 文档",
|
||||
brandSuffix: "文档",
|
||||
search: "搜索",
|
||||
searchPlaceholder: "搜索文档",
|
||||
version: "版本",
|
||||
language: "语言",
|
||||
onThisPage: "本页内容",
|
||||
previous: "上一页",
|
||||
next: "下一页",
|
||||
noMatch: "没有匹配的页面。",
|
||||
navLabel: "文档导航",
|
||||
nav: { home: "首页", tutorials: "教程", alpha: "因子库", lab: "研究室", source: "源码" }
|
||||
}
|
||||
}</code></pre>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "tools",
|
||||
title: "Tool surface",
|
||||
body: `
|
||||
<p>The server exposes tools for skills, market data, backtesting, factor analysis, options, web/document reading, trade journals, Shadow Account, and swarm runs.</p>
|
||||
`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "reference/cloudflare-pages",
|
||||
title: "Cloudflare Pages",
|
||||
description: "Deploy this wiki without running a server.",
|
||||
lead: "The wiki is static. Cloudflare Pages can serve it directly from the repository.",
|
||||
sections: [
|
||||
{
|
||||
id: "settings",
|
||||
title: "Pages settings",
|
||||
body: `
|
||||
<ul>
|
||||
<li>Project root: <code>wiki</code></li>
|
||||
<li>Build command: leave empty</li>
|
||||
<li>Output directory: <code>.</code></li>
|
||||
<li>Custom domain: <code>vibetrading.wiki</code></li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "why-static",
|
||||
title: "Why static",
|
||||
body: `
|
||||
<p>Docs, landing copy, redirects, theme state, and client-side search all work as static files. No VPS, database, or server process is required.</p>
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
+16
-12
@@ -17,7 +17,7 @@
|
||||
</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.">
|
||||
<meta name="description" content="Documentation for Vibe-Trading, the natural-language finance research agent. 中文文档见 /docs/zh/。">
|
||||
<meta name="robots" content="index, follow, max-image-preview:large">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:site_name" content="Vibe-Trading Docs">
|
||||
@@ -26,29 +26,33 @@
|
||||
<meta property="og:image" content="https://vibetrading.wiki/assets/icon.png">
|
||||
<meta property="og:url" content="https://vibetrading.wiki/docs/latest/getting-started/vibe-trading-overview">
|
||||
<link rel="canonical" href="https://vibetrading.wiki/docs/latest/getting-started/vibe-trading-overview">
|
||||
<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">
|
||||
<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">
|
||||
<script type="module" src="/docs/main.js"></script>
|
||||
<link rel="stylesheet" href="/docs/styles.css?v=20260929">
|
||||
<script type="module" src="/docs/main.js?v=20260929"></script>
|
||||
</head>
|
||||
<body class="docs-body">
|
||||
<header class="site-header docs-header" id="site-header">
|
||||
<div class="site-header__inner">
|
||||
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
|
||||
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
|
||||
<span class="brand__text">Vibe-Trading <em>Docs</em></span>
|
||||
<span class="brand__text">Vibe-Trading <em data-ui="brandSuffix">Docs</em></span>
|
||||
</a>
|
||||
<nav class="site-nav" aria-label="Primary">
|
||||
<a href="/home/">Home</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
@@ -64,11 +68,11 @@
|
||||
<aside class="docs-sidebar" aria-label="Documentation navigation">
|
||||
<div class="docs-sidebar__tools">
|
||||
<label class="search-box">
|
||||
<span>Search</span>
|
||||
<span data-ui="search">Search</span>
|
||||
<input id="docs-search" type="search" placeholder="Search docs">
|
||||
</label>
|
||||
<label class="version-box">
|
||||
<span>Version</span>
|
||||
<span data-ui="version">Version</span>
|
||||
<select id="version-select"></select>
|
||||
</label>
|
||||
</div>
|
||||
@@ -78,7 +82,7 @@
|
||||
<article class="docs-article" id="docs-article" tabindex="-1"></article>
|
||||
|
||||
<aside class="docs-outline" aria-label="On this page">
|
||||
<p>On this page</p>
|
||||
<p data-ui="onThisPage">On this page</p>
|
||||
<nav id="docs-outline"></nav>
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
+178
-67
@@ -1,19 +1,39 @@
|
||||
import { initTheme } from "/theme.js";
|
||||
import {
|
||||
DOCS_DEFAULT_LANG,
|
||||
DOCS_DEFAULT_PAGE,
|
||||
DOCS_DEFAULT_VERSION,
|
||||
DOCS_LATEST_ALIAS,
|
||||
DOCS_STRUCTURE,
|
||||
DOCS_LANGUAGES,
|
||||
DOCS_UI,
|
||||
DOCS_VERSIONS
|
||||
} from "/docs/content.js";
|
||||
} from "/docs/content.js?v=20260929";
|
||||
|
||||
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";
|
||||
|
||||
const allPages = DOCS_STRUCTURE.flatMap((group) =>
|
||||
group.pages.map((page) => ({ ...page, group: group.label }))
|
||||
function stripTags(html) {
|
||||
return String(html).replace(/<[^>]+>/g, " ").replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
const pagesByLang = Object.fromEntries(
|
||||
Object.entries(DOCS_LANGUAGES).map(([lang, { structure }]) => [
|
||||
lang,
|
||||
structure.flatMap((group) =>
|
||||
group.pages.map((page) => ({
|
||||
...page,
|
||||
group: group.label,
|
||||
searchText: [
|
||||
page.title,
|
||||
page.description,
|
||||
page.lead,
|
||||
...page.sections.map((section) => `${section.title} ${stripTags(section.body)}`)
|
||||
].join(" ").toLowerCase()
|
||||
}))
|
||||
)
|
||||
])
|
||||
);
|
||||
|
||||
function formatStarCount(n) {
|
||||
@@ -59,59 +79,138 @@ function slugify(text) {
|
||||
.replace(/^-|-$/g, "");
|
||||
}
|
||||
|
||||
function langForSegment(segment) {
|
||||
const match = Object.entries(DOCS_LANGUAGES).find(([, meta]) => meta.segment === segment);
|
||||
return match ? match[0] : DOCS_DEFAULT_LANG;
|
||||
}
|
||||
|
||||
function routeParts() {
|
||||
const normalized = location.pathname.replace(/\/+$/, "");
|
||||
if (!normalized || normalized === "/docs" || normalized === "/docs/index.html") {
|
||||
return { version: DOCS_LATEST_ALIAS, pageId: DOCS_DEFAULT_PAGE };
|
||||
}
|
||||
const match = normalized.match(/^\/docs\/([^/]+)\/(.+)$/);
|
||||
if (!match) return { version: DOCS_LATEST_ALIAS, pageId: DOCS_DEFAULT_PAGE };
|
||||
return { version: match[1], pageId: match[2] || DOCS_DEFAULT_PAGE };
|
||||
if (!match) {
|
||||
const bare = normalized.match(/^\/docs\/([^/]+)$/);
|
||||
const lang = bare ? langForSegment(bare[1]) : DOCS_DEFAULT_LANG;
|
||||
return { lang, pageId: DOCS_DEFAULT_PAGE, canonical: false };
|
||||
}
|
||||
const lang = langForSegment(match[1]);
|
||||
return { lang, pageId: match[2], canonical: match[1] === DOCS_LANGUAGES[lang].segment };
|
||||
}
|
||||
|
||||
function canonicalPath(pageId, version = DOCS_LATEST_ALIAS) {
|
||||
return `/docs/${version}/${pageId}`;
|
||||
function canonicalPath(pageId, lang) {
|
||||
return `/docs/${DOCS_LANGUAGES[lang].segment}/${pageId}`;
|
||||
}
|
||||
|
||||
function resolvePage(pageId) {
|
||||
return allPages.find((page) => page.id === pageId) || allPages.find((page) => page.id === DOCS_DEFAULT_PAGE);
|
||||
function resolvePage(pageId, lang) {
|
||||
const pages = pagesByLang[lang];
|
||||
return pages.find((page) => page.id === pageId) || pages.find((page) => page.id === DOCS_DEFAULT_PAGE);
|
||||
}
|
||||
|
||||
function setMeta(page, version) {
|
||||
document.title = `${page.title} - Vibe-Trading Docs`;
|
||||
const description = page.description || page.lead || "Vibe-Trading documentation.";
|
||||
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}']`);
|
||||
if (!link) {
|
||||
link = document.createElement("link");
|
||||
link.rel = "alternate";
|
||||
link.hreflang = hreflang;
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
link.href = href;
|
||||
}
|
||||
|
||||
function setMeta(page, lang) {
|
||||
const ui = DOCS_UI[lang];
|
||||
const title = `${page.title} - ${ui.siteTitle}`;
|
||||
const description = page.description || page.lead;
|
||||
document.title = title;
|
||||
document.querySelector('meta[name="description"]')?.setAttribute("content", description);
|
||||
document.querySelector('meta[property="og:title"]')?.setAttribute("content", `${page.title} - Vibe-Trading Docs`);
|
||||
document.querySelector('meta[property="og:title"]')?.setAttribute("content", title);
|
||||
document.querySelector('meta[property="og:description"]')?.setAttribute("content", description);
|
||||
const canonical = `https://vibetrading.wiki${canonicalPath(page.id, version)}`;
|
||||
document.querySelector('meta[property="og:site_name"]')?.setAttribute("content", ui.siteTitle);
|
||||
const canonical = `${SITE}${canonicalPath(page.id, lang)}`;
|
||||
document.querySelector('meta[property="og:url"]')?.setAttribute("content", canonical);
|
||||
document.querySelector("link[rel='canonical']")?.setAttribute("href", canonical);
|
||||
for (const [code, meta] of Object.entries(DOCS_LANGUAGES)) {
|
||||
setAlternate(meta.htmlLang, `${SITE}${canonicalPath(page.id, code)}`);
|
||||
}
|
||||
setAlternate("x-default", `${SITE}${canonicalPath(page.id, DOCS_DEFAULT_LANG)}`);
|
||||
}
|
||||
|
||||
function applyChrome(lang, page) {
|
||||
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);
|
||||
|
||||
const select = document.getElementById("version-select");
|
||||
if (select) {
|
||||
select.innerHTML = DOCS_VERSIONS.map((item) =>
|
||||
`<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) {
|
||||
const active = page.id === currentId ? "is-active" : "";
|
||||
return `<a class="${active}" href="${canonicalPath(page.id)}" data-doc-link="${page.id}">
|
||||
return `<a class="${active}" data-doc-link="${page.id}">
|
||||
<span>${page.title}</span>
|
||||
<small>${page.description}</small>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
function renderNav(currentId, filter = "") {
|
||||
function renderNav(currentId, lang, filter = "") {
|
||||
const nav = document.getElementById("docs-nav");
|
||||
if (!nav) return;
|
||||
const q = filter.trim().toLowerCase();
|
||||
const groups = DOCS_STRUCTURE.map((group) => {
|
||||
const pages = group.pages.filter((page) => {
|
||||
if (!q) return true;
|
||||
return `${page.title} ${page.description} ${page.lead}`.toLowerCase().includes(q);
|
||||
});
|
||||
if (!pages.length) return "";
|
||||
const pages = pagesByLang[lang];
|
||||
const groups = DOCS_LANGUAGES[lang].structure.map((group) => {
|
||||
const matches = pages.filter((page) => page.group === group.label && (!q || page.searchText.includes(q)));
|
||||
if (!matches.length) return "";
|
||||
return `<section>
|
||||
<h2>${group.label}</h2>
|
||||
${pages.map((page) => navLink(page, currentId)).join("")}
|
||||
${matches.map((page) => navLink(page, currentId)).join("")}
|
||||
</section>`;
|
||||
}).join("");
|
||||
nav.innerHTML = groups || `<p class="empty-state">No pages match that search.</p>`;
|
||||
nav.innerHTML = groups || `<p class="empty-state">${DOCS_UI[lang].noMatch}</p>`;
|
||||
}
|
||||
|
||||
function renderOutline(page) {
|
||||
@@ -122,13 +221,15 @@ function renderOutline(page) {
|
||||
).join("");
|
||||
}
|
||||
|
||||
function renderArticle(page, version) {
|
||||
function renderArticle(page, lang) {
|
||||
const article = document.getElementById("docs-article");
|
||||
if (!article) return;
|
||||
|
||||
const index = allPages.findIndex((candidate) => candidate.id === page.id);
|
||||
const previous = index > 0 ? allPages[index - 1] : null;
|
||||
const next = index < allPages.length - 1 ? allPages[index + 1] : null;
|
||||
const ui = DOCS_UI[lang];
|
||||
const pages = pagesByLang[lang];
|
||||
const index = pages.findIndex((candidate) => candidate.id === page.id);
|
||||
const previous = index > 0 ? pages[index - 1] : null;
|
||||
const next = index < pages.length - 1 ? pages[index + 1] : null;
|
||||
|
||||
article.innerHTML = `
|
||||
<header class="doc-hero">
|
||||
@@ -143,65 +244,72 @@ function renderArticle(page, version) {
|
||||
</section>
|
||||
`).join("")}
|
||||
<footer class="doc-footer">
|
||||
${previous ? `<a href="${canonicalPath(previous.id, version)}" data-doc-link="${previous.id}"><span>Previous</span><strong>${previous.title}</strong></a>` : "<span></span>"}
|
||||
${next ? `<a href="${canonicalPath(next.id, version)}" data-doc-link="${next.id}"><span>Next</span><strong>${next.title}</strong></a>` : "<span></span>"}
|
||||
${previous ? `<a data-doc-link="${previous.id}"><span>${ui.previous}</span><strong>${previous.title}</strong></a>` : "<span></span>"}
|
||||
${next ? `<a data-doc-link="${next.id}"><span>${ui.next}</span><strong>${next.title}</strong></a>` : "<span></span>"}
|
||||
</footer>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderVersionSelect(version) {
|
||||
const select = document.getElementById("version-select");
|
||||
if (!select) return;
|
||||
select.innerHTML = DOCS_VERSIONS.map((item) =>
|
||||
`<option value="${item.name}" ${item.name === DOCS_DEFAULT_VERSION ? "selected" : ""}>${item.label}</option>`
|
||||
).join("");
|
||||
select.addEventListener("change", () => {
|
||||
const { pageId } = routeParts();
|
||||
navigate(canonicalPath(pageId, DOCS_LATEST_ALIAS));
|
||||
});
|
||||
}
|
||||
|
||||
function navigate(path) {
|
||||
history.pushState({}, "", path);
|
||||
renderCurrent();
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
|
||||
function bindDocLinks(root = document) {
|
||||
function bindDocLinks(lang, root = document) {
|
||||
root.querySelectorAll("[data-doc-link]").forEach((link) => {
|
||||
link.addEventListener("click", (event) => {
|
||||
const pageId = link.getAttribute("data-doc-link");
|
||||
if (!pageId) return;
|
||||
link.setAttribute("href", canonicalPath(pageId, lang));
|
||||
link.onclick = (event) => {
|
||||
if (event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1) return;
|
||||
event.preventDefault();
|
||||
const pageId = link.getAttribute("data-doc-link");
|
||||
if (!pageId) return;
|
||||
navigate(canonicalPath(pageId));
|
||||
});
|
||||
navigate(canonicalPath(pageId, lang));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function renderCurrent() {
|
||||
const { version, pageId } = routeParts();
|
||||
const resolvedVersion = version === DOCS_LATEST_ALIAS ? DOCS_LATEST_ALIAS : DOCS_DEFAULT_VERSION;
|
||||
const page = resolvePage(pageId);
|
||||
const { lang, pageId, canonical } = routeParts();
|
||||
const page = resolvePage(pageId, lang);
|
||||
|
||||
if (location.pathname === "/docs" || location.pathname === "/docs/" || location.pathname === "/docs/index.html") {
|
||||
history.replaceState({}, "", canonicalPath(page.id, DOCS_LATEST_ALIAS));
|
||||
if (!canonical || page.id !== pageId) {
|
||||
history.replaceState({}, "", canonicalPath(page.id, lang) + location.hash);
|
||||
}
|
||||
|
||||
setMeta(page, resolvedVersion);
|
||||
renderNav(page.id, document.getElementById("docs-search")?.value || "");
|
||||
renderArticle(page, resolvedVersion);
|
||||
setMeta(page, lang);
|
||||
applyChrome(lang, page);
|
||||
renderNav(page.id, lang, document.getElementById("docs-search")?.value || "");
|
||||
renderArticle(page, lang);
|
||||
renderOutline(page);
|
||||
bindDocLinks(document);
|
||||
bindDocLinks(lang);
|
||||
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;
|
||||
search.addEventListener("input", () => {
|
||||
const { pageId } = routeParts();
|
||||
renderNav(resolvePage(pageId).id, search.value);
|
||||
bindDocLinks(document.getElementById("docs-nav") || document);
|
||||
const { lang, pageId } = routeParts();
|
||||
renderNav(resolvePage(pageId, lang).id, lang, search.value);
|
||||
bindDocLinks(lang, document.getElementById("docs-nav") || document);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -217,7 +325,10 @@ window.addEventListener("popstate", renderCurrent);
|
||||
|
||||
initTheme();
|
||||
initStars();
|
||||
renderVersionSelect(DOCS_DEFAULT_VERSION);
|
||||
initLanguage();
|
||||
initSearch();
|
||||
initHeaderScroll();
|
||||
renderCurrent();
|
||||
// The sections are rendered after load, so the browser could not scroll to
|
||||
// a #section in the URL on its own.
|
||||
if (location.hash) document.getElementById(decodeURIComponent(location.hash.slice(1)))?.scrollIntoView();
|
||||
@@ -141,6 +141,7 @@
|
||||
.doc-section {
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 34px 0;
|
||||
scroll-margin-top: 80px;
|
||||
}
|
||||
|
||||
.doc-section h2 {
|
||||
@@ -284,3 +285,65 @@
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
margin: 18px 0 0;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.94rem;
|
||||
}
|
||||
|
||||
.doc-section th,
|
||||
.doc-section td {
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 9px 12px 9px 0;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.doc-section th {
|
||||
color: var(--ink);
|
||||
font-family: var(--mono);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.doc-section td {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.doc-section td code {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 4px;
|
||||
background: var(--surface-2);
|
||||
color: var(--ink);
|
||||
padding: 0.12em 0.3em;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.doc-section table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user