Files
Haozhe Wu 36f1698e1f wiki: Chinese alpha notes, and the GTJA 191 post re-measured
Alpha Library: the 191 notes, 117 nicknames and 10 theme tags now switch
to Chinese with the rest of the site. wiki/alpha-library/i18n.zh.json keeps
each translation next to the English it came from; the generator puts the
Chinese in data-text-zh only while that English still matches the
registry, and tests/factors/test_alpha_library_translations.py fails on a
missing, stale or orphaned translation or an unnamed theme.

GTJA 191 post: the 2026-05-17 numbers came from Tushare prices that were
not split-adjusted and from today's CSI 300 roster applied to the whole
window, both fixed on 2026-08-04 (d52199ff). Re-ran the bench on the
current code (point-in-time membership, qfq prices): 8 alive, 14 reversed,
168 dead, gtja191_138 untested (99% NaN), where the first version said
10 / 15 / 165 and never accounted for the 191st. Both languages now carry
a revision note and the new counts, theme table, top 5 by IR and three
most-negative alphas, and fix what the translation surfaced or the code
contradicts: the top 5 are ranked by IR, not IC; the theme table counts
every tag an alpha carries; the reflection matches the table; the Method
universe matches the Caveats; Kakushadze's preprint came a year after the
report; forward returns are simple, not log; no GTJA formula needs sector
tags; the bench measures no turnover and draws no IC plot; five alphas use
a proxy input (named); the reproduce command covers 2018-2025. A new
caveat reports the bench's multiple-testing deflation: 3 of the 8 alive
alphas clear the IR expected from 190 null formulas.
2026-09-29 18:36:52 +08:00

238 lines
8.2 KiB
JavaScript

import { initSite, setPageLang, storedLang } from "/site.js?v=20260929e";
import {
DOCS_DEFAULT_LANG,
DOCS_DEFAULT_PAGE,
DOCS_LANGUAGES,
DOCS_UI,
DOCS_VERSIONS
} from "/docs/content.js?v=20260929e";
const SITE = "https://vibetrading.wiki";
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 slugify(text) {
return String(text)
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
}
function langForSegment(segment) {
const match = Object.entries(DOCS_LANGUAGES).find(([, meta]) => meta.segment === 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 && bare[1] !== "index.html" ? langForSegment(bare[1]) : storedLang();
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, lang) {
return `/docs/${DOCS_LANGUAGES[lang].segment}/${pageId}`;
}
function resolvePage(pageId, lang) {
const pages = pagesByLang[lang];
return pages.find((page) => page.id === pageId) || pages.find((page) => page.id === DOCS_DEFAULT_PAGE);
}
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", title);
document.querySelector('meta[property="og:description"]')?.setAttribute("content", description);
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) {
const ui = DOCS_UI[lang];
document.querySelectorAll("[data-ui]").forEach((el) => {
const value = ui[el.getAttribute("data-ui")];
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("");
}
}
function navLink(page, currentId) {
const active = page.id === currentId ? "is-active" : "";
return `<a class="${active}" data-doc-link="${page.id}">
<span>${page.title}</span>
<small>${page.description}</small>
</a>`;
}
function renderNav(currentId, lang, filter = "") {
const nav = document.getElementById("docs-nav");
if (!nav) return;
const q = filter.trim().toLowerCase();
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>
${matches.map((page) => navLink(page, currentId)).join("")}
</section>`;
}).join("");
nav.innerHTML = groups || `<p class="empty-state">${DOCS_UI[lang].noMatch}</p>`;
}
function renderOutline(page) {
const outline = document.getElementById("docs-outline");
if (!outline) return;
outline.innerHTML = page.sections.map((section) =>
`<a href="#${section.id || slugify(section.title)}">${section.title}</a>`
).join("");
}
function renderArticle(page, lang) {
const article = document.getElementById("docs-article");
if (!article) return;
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">
<p class="eyebrow">${page.group}</p>
<h1>${page.title}</h1>
<p>${page.lead}</p>
</header>
${page.sections.map((section) => `
<section id="${section.id || slugify(section.title)}" class="doc-section">
<h2>${section.title}</h2>
${section.body}
</section>
`).join("")}
<footer class="doc-footer">
${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 navigate(path) {
history.pushState({}, "", path);
renderCurrent();
window.scrollTo({ top: 0, behavior: "smooth" });
}
function bindDocLinks(lang, root = document) {
root.querySelectorAll("[data-doc-link]").forEach((link) => {
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();
navigate(canonicalPath(pageId, lang));
};
});
}
function renderCurrent() {
const { lang, pageId, canonical } = routeParts();
const page = resolvePage(pageId, lang);
if (!canonical || page.id !== pageId) {
history.replaceState({}, "", canonicalPath(page.id, lang) + location.hash);
}
current = { lang, pageId: page.id };
setPageLang(lang);
setMeta(page, lang);
applyChrome(lang);
renderNav(page.id, lang, document.getElementById("docs-search")?.value || "");
renderArticle(page, lang);
renderOutline(page);
bindDocLinks(lang);
document.getElementById("docs-article")?.focus({ preventScroll: true });
}
function initSearch() {
const search = document.getElementById("docs-search");
if (!search) return;
search.addEventListener("input", () => {
const { lang, pageId } = routeParts();
renderNav(resolvePage(pageId, lang).id, lang, search.value);
bindDocLinks(lang, document.getElementById("docs-nav") || document);
});
}
let current = { lang: DOCS_DEFAULT_LANG, pageId: DOCS_DEFAULT_PAGE };
window.addEventListener("popstate", renderCurrent);
initSite({ onToggle: (lang) => navigate(canonicalPath(current.pageId, lang)) });
initSearch();
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();