Repository Public
1#!/usr/bin/env node2// Generates the charts encyclopedia pages of the docs from the catalogue (src/charts/catalog — single source of truth):3// content/guides/charts-indicators.mdx index by category, thumbnails → /docs/charts/indicators4// content/guides/charts-ind-<id>.mdx one sheet per indicator (60) → /docs/charts/indicators/<id>5// content/guides/charts-tools.mdx index by group → /docs/charts/tools6// content/guides/charts-tool-<id>.mdx one sheet per drawing tool (42) → /docs/charts/tools/<id>7// src/docs/guides.charts.js the GUIDES entries (literal objects: scripts/prerender.mjs parses them)8// Idempotent; run with `npm run charts:docs` after editing the catalogue (the output is committed so `vite dev` needs nothing).910import { mkdirSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs'11import { dirname, join, resolve } from 'node:path'12import { fileURLToPath } from 'node:url'13import indicators, { INDICATOR_ENTRIES } from '../src/charts/catalog/indicators/all.js'14import tools, { TOOL_ENTRIES } from '../src/charts/catalog/tools.js'15import { INDICATOR_CATEGORY_LABELS, INDICATOR_CATEGORY_ORDER, TOOL_GROUP_LABELS, TOOL_GROUP_ORDER, thumbUrl } from '../src/charts/catalog/index.js'16import { REGISTRY } from '../src/charts/indicators/index.js'1718const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')19const GUIDES_DIR = join(ROOT, 'content', 'guides')20mkdirSync(GUIDES_DIR, { recursive: true })2122/* ─── escaping ─── */23// Prose inside MDX: braces and angle brackets are JSX syntax; keep everything else literal.24const md = s => String(s).replace(/\\/g, '\\\\').replace(/[{}]/g, m => `\\${m}`).replace(/</g, '<').replace(/>/g, '>')25const cell = s => md(s).replace(/\|/g, '\\|').replace(/\n/g, ' ')26const js = s => `'${String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, ' ')}'`27const fence = s => `\`\`\`text\n${String(s).replace(/```/g, "'''")}\n\`\`\``28const fmtVal = v => (v == null ? '—' : Array.isArray(v) ? v.join(', ') : typeof v === 'object' ? JSON.stringify(v) : String(v))2930const indName = id => indicators[id]?.name || id31const toolName = id => tools[id]?.name || id32const link = (kind, id) => `[${md(kind === 'tool' ? toolName(id) : indName(id))}](/docs/charts/${kind === 'tool' ? 'tools' : 'indicators'}/${id})`33const pairLink = id => (tools[id] ? link('tool', id) : link('indicator', id))34const list = items => items.map(s => `- ${md(s)}`).join('\n')3536/* ─── one indicator sheet ─── */37function indicatorPage(e) {38 const spec = REGISTRY[e.id]39 const title = e.aka?.[0] && e.aka[0] !== e.name ? `${e.aka[0]} — ${e.name}` : e.name40 const paramsLabel = spec ? spec.title(spec.defaults) : e.name41 const rows = e.defaults.map(([name, value, why]) => `| \`${name}\` | \`${cell(fmtVal(value))}\` | ${cell(why)} |`).join('\n')42 return `export const meta = { title: ${js(title)}, description: ${js(e.oneLiner)}, image: ${js(thumbUrl('indicator', e.id, 'dark', 'lg'))} }4344# ${md(e.name)}4546<p className="lead">${md(e.oneLiner)}</p>4748<ChartFigure kind="indicator" id="${e.id}" caption="${cell(paramsLabel)} with its default settings on a daily chart — rendered by the HF Market Data charting engine." />4950<CatalogMeta kind="indicator" id="${e.id}" aka={${JSON.stringify(e.aka)}} category=${JSON.stringify(INDICATOR_CATEGORY_LABELS[e.category] || e.category)} pane=${JSON.stringify(e.pane === 'main' ? 'Overlay on the price pane' : 'Own pane under the price')} />5152## What it measures5354${md(e.summary)}5556## Formula5758${fence(e.formula.text)}5960${e.formula.vars.length ? `| Symbol | Meaning |\n| --- | --- |\n${e.formula.vars.map(([k, m]) => `| \`${cell(k)}\` | ${cell(m)} |`).join('\n')}` : ''}6162## How to read it6364${list(e.howToRead)}6566## Default settings6768${e.defaults.length ? `| Input | Default | Why |\n| --- | --- | --- |\n${rows}` : 'This indicator has no inputs — there is nothing to tune.'}6970Every input is editable live in **⚙ Settings → Inputs**; **Reset defaults** restores the values above.7172## Best for7374| | |75| --- | --- |76| **Timeframes** | ${cell(e.bestFor.timeframes)} |77| **Markets** | ${cell(e.bestFor.markets)} |78${e.bestFor.note ? `| **Note** | ${cell(e.bestFor.note)} |` : ''}7980## Pairs well with8182${e.pairsWith.map(id => `- ${pairLink(id)} — ${md(indicators[id]?.oneLiner || '')}`).join('\n')}8384## Pitfalls8586${list(e.pitfalls)}8788## History8990${md(e.history)}9192## See also9394${[...e.related.map(r => `- [${md(r.label)}](${r.href})`), `- [All indicators](/docs/charts/indicators) · [Drawing tools](/docs/charts/tools) · [Charting guide](/docs/charts)`].join('\n')}95`96}9798/* ─── one tool sheet ─── */99function toolPage(e) {100 const clicks = e.points === Infinity ? 'free-form' : `${e.points} point${e.points > 1 ? 's' : ''}`101 return `export const meta = { title: ${js(e.name)}, description: ${js(e.oneLiner)}, image: ${js(thumbUrl('tool', e.id, 'dark', 'lg'))} }102103# ${md(e.name)}104105<p className="lead">${md(e.oneLiner)}</p>106107<ChartFigure kind="tool" id="${e.id}" caption="${cell(e.name)} placed on a daily chart — rendered by the HF Market Data charting engine." />108109<CatalogMeta kind="tool" id="${e.id}" aka={${JSON.stringify(e.aka)}} category=${JSON.stringify(TOOL_GROUP_LABELS[e.group] || e.group)} pane=${JSON.stringify(clicks)}${e.shortcut ? ` shortcut=${JSON.stringify(e.shortcut)}` : ''} />110111## What it is112113${md(e.summary)}114115## What it is for116117${md(e.whatFor)}118119## How to draw it120121${e.howToDraw.map((s, i) => `${i + 1}. ${md(s)}`).join('\n')}122123${e.shortcut ? `Keyboard: press <kbd>${e.shortcut}</kbd> to arm the tool, <kbd>Esc</kbd> to go back to the cursor. ` : 'Pick it in the drawing bar (left of the chart) or its flyout; <kbd>Esc</kbd> goes back to the cursor. '}Turn on **magnet** to snap anchors to open / high / low / close; **stay in drawing mode** keeps the tool armed after each drawing. Selecting a drawing opens the floating properties bar (colour, width, style, fill, text, lock, duplicate, delete).124125## Reading tips126127${list(e.readingTips)}128129## Example130131${md(e.example)}132133## Pairs well with134135${e.pairsWith.map(id => `- ${pairLink(id)} — ${md((tools[id] || indicators[id])?.oneLiner || '')}`).join('\n')}136137## Pitfalls138139${list(e.pitfalls)}140141## History142143${md(e.history)}144145## See also146147${[...e.related.map(r => `- [${md(r.label)}](${r.href})`), `- [All drawing tools](/docs/charts/tools) · [Indicators](/docs/charts/indicators) · [Charting guide](/docs/charts)`].join('\n')}148`149}150151/* ─── index pages ─── */152function indicatorsIndex() {153 const sections = INDICATOR_CATEGORY_ORDER.map(cat => {154 const items = INDICATOR_ENTRIES.filter(e => e.category === cat).map(e => ({ id: e.id, name: e.name, oneLiner: e.oneLiner, aka: e.aka[0] }))155 return `## ${INDICATOR_CATEGORY_LABELS[cat]}\n\n<CatalogGrid kind="indicator" items={${JSON.stringify(items)}} />`156 })157 return `export const meta = { title: 'Indicator encyclopedia', description: 'Every one of the ${INDICATOR_ENTRIES.length} indicators of the HF Market Data charts explained and illustrated: what it measures, the formula, how to read it, why the defaults, pitfalls and history — each with a chart rendered by our own engine.' }158159# Indicator encyclopedia160161<p className="lead">${INDICATOR_ENTRIES.length} indicators, each explained like a serious trading manual — what it measures, the exact formula, how to read it, why the default settings are what they are, what it pairs with, where it fails — and illustrated with the same synthetic daily chart rendered by our own engine, in your theme.</p>162163Every indicator is one click away in the [charts](/charts) — open the **Indicator library** (<kbd>⌘</kbd> <kbd>I</kbd>) for the same sheets next to the chart, or use the *Open in charts* button on each page. See also the [drawing tools](/docs/charts/tools) and the [charting guide](/docs/charts).164165${sections.join('\n\n')}166`167}168169function toolsIndex() {170 const sections = TOOL_GROUP_ORDER.map(g => {171 const items = TOOL_ENTRIES.filter(e => e.group === g).map(e => ({ id: e.id, name: e.name, oneLiner: e.oneLiner, shortcut: e.shortcut }))172 return `## ${TOOL_GROUP_LABELS[g]}\n\n<CatalogGrid kind="tool" items={${JSON.stringify(items)}} />`173 })174 return `export const meta = { title: 'Drawing tools encyclopedia', description: 'The ${TOOL_ENTRIES.length} drawing tools of the HF Market Data charts explained and illustrated: what each one is for, how to draw it click by click, how to read it, worked examples, pitfalls and history.' }175176# Drawing tools encyclopedia177178<p className="lead">${TOOL_ENTRIES.length} drawing tools — lines and channels, Fibonacci and Gann, shapes, notes, measurements, positions and patterns — each with its purpose, the exact click sequence, reading tips, a worked example and the traps to avoid, illustrated by our own engine on the same daily chart.</p>179180The tools live in the vertical bar on the left of the [charts](/charts): each group button remembers the tool you used last, the chevron (or a right-click, or a long press) opens the full group. Hovering a tool shows the same illustration and steps as here. See also the [indicators](/docs/charts/indicators) and the [charting guide](/docs/charts).181182${sections.join('\n\n')}183`184}185186/* ─── registry entries (literal objects so prerender.mjs can parse them with its regex) ─── */187function guidesModule() {188 const entry = ({ slug, title, file, summary, keywords, image }) => ` { slug: ${js(slug)}, title: ${js(title)}, section: 'charts', file: ${js(file)}, summary: ${js(summary)}, hidden: ${!!image}, keywords: ${js(keywords)}${image ? `, image: ${js(image)}` : ''} },`189 const lines = [190 entry({ slug: 'charts/indicators', title: 'Indicator encyclopedia', file: 'charts-indicators', summary: `${INDICATOR_ENTRIES.length} indicators explained and illustrated: formula, reading, defaults, pitfalls.`, keywords: 'indicators encyclopedia technical analysis' }),191 ...INDICATOR_ENTRIES.map(e => entry({ slug: `charts/indicators/${e.id}`, title: e.name, file: `charts-ind-${e.id}`, summary: e.oneLiner, keywords: [e.id, ...e.aka, INDICATOR_CATEGORY_LABELS[e.category], 'indicator'].join(' '), image: thumbUrl('indicator', e.id, 'dark', 'lg') })),192 entry({ slug: 'charts/tools', title: 'Drawing tools encyclopedia', file: 'charts-tools', summary: `${TOOL_ENTRIES.length} drawing tools explained and illustrated: purpose, how to draw, reading tips, pitfalls.`, keywords: 'drawing tools fibonacci gann pitchfork patterns' }),193 ...TOOL_ENTRIES.map(e => entry({ slug: `charts/tools/${e.id}`, title: e.name, file: `charts-tool-${e.id}`, summary: e.oneLiner, keywords: [e.id, ...e.aka, TOOL_GROUP_LABELS[e.group], 'drawing tool'].join(' '), image: thumbUrl('tool', e.id, 'dark', 'lg') })),194 ]195 return `// GENERATED by scripts/charts-docs.mjs from src/charts/catalog — do not edit by hand (npm run charts:docs).196// Docs entries of the charts encyclopedia: two index pages (in the nav) + one sheet per indicator / tool (hidden from197// the nav, present in search, prerender and the sitemap). Literal objects: scripts/prerender.mjs parses this file.198export const CHARTS_GUIDES = [199${lines.join('\n')}200]201`202}203204/* ─── write ─── */205for (const f of readdirSync(GUIDES_DIR)) if (/^charts-(ind|tool)-.+\.mdx$/.test(f)) unlinkSync(join(GUIDES_DIR, f))206let count = 0207const write = (name, text) => { writeFileSync(join(GUIDES_DIR, name), text); count++ }208write('charts-indicators.mdx', indicatorsIndex())209write('charts-tools.mdx', toolsIndex())210for (const e of INDICATOR_ENTRIES) write(`charts-ind-${e.id}.mdx`, indicatorPage(e))211for (const e of TOOL_ENTRIES) write(`charts-tool-${e.id}.mdx`, toolPage(e))212writeFileSync(join(ROOT, 'src', 'docs', 'guides.charts.js'), guidesModule())213console.log(`charts docs: ${count} MDX pages (${INDICATOR_ENTRIES.length} indicators, ${TOOL_ENTRIES.length} tools, 2 indexes) + src/docs/guides.charts.js`)214