| name | charting |
| type | capability |
| description | Build financially-correct charts from company fundamentals — pick the right chart for the question, normalise units (one unit per axis, $B/$M scaling, %-vs-$ dual axes, rebasing for comparisons), and apply finance conventions (green beat / red miss, accounting negatives, dashed estimates, dark totals). A thin Python+Polars layer normalises already-available data into a chart contract; a TypeScript layer emits Highcharts options and renders a self-contained HTML page. Covers revenue trends & YoY, segment mix, margins, dividends, earnings surprise, estimate-vs-reported, revenue→net-income waterfalls, and price (candlestick/Stock). Use whenever the user wants to chart, plot, graph, or visualize financial data. Triggers: "chart revenue over time", "plot YoY growth with event flags", "revenue breakdown by segment", "dividend history and yield", "earnings surprise chart", "estimate vs reported", "waterfall from revenue to net income", "margins over time", "candlestick / price chart", "compare two companies' revenue", "show visual breakdown of income statement"
|
| requires | ["analyst-kit-core"] |
Preamble (run first)
PLAN MODE EXCEPTION — ALWAYS RUN: this block only reads state and writes to ~/.analyst-kit/.
_AK="$(cat ~/.analyst-kit/core-path 2>/dev/null)"
if [ ! -x "$_AK/bin/analyst-kit-preamble" ]; then
for d in ~/.claude/skills/analyst-kit-core .claude/skills/analyst-kit-core ~/.codex/skills/analyst-kit-core .codex/skills/analyst-kit-core; do
[ -x "$d/bin/analyst-kit-preamble" ] && _AK="$d" && break
done
[ -x "$_AK/bin/analyst-kit-preamble" ] || _AK="$(find ~/.claude/plugins -maxdepth 6 -type d -name analyst-kit-core 2>/dev/null | head -1)"
[ -n "$_AK" ] && { mkdir -p ~/.analyst-kit; printf '%s' "$_AK" > ~/.analyst-kit/core-path; } || true
fi
[ -x "$_AK/bin/analyst-kit-preamble" ] && "$_AK/bin/analyst-kit-preamble" --skill charting 2>/dev/null || echo "AK_CORE: not found (continue without runtime)"
Read the echoed state and act. Skip ALL bullets below if DEDUP: yes or AK_CORE: not found:
DISABLED: yes → this skill is turned off because a required API key isn't
configured. Do not run it. Tell the user it's off, name the missing key (see
MISSING_KEYS), and offer to enable it — either they give you the key now (store it
with "$_AK/bin/analyst-kit-setup" set-key <KEY> <value>, which re-enables the skill) or they
say "set up analyst-kit" for full setup. Then stop; do not attempt the skill's work.
- First run —
ONBOARDED: no → orient the user once, then run
"$_AK/bin/analyst-kit-setup" finish (this covers the telemetry notice, so skip the
TEL_PROMPTED bullet this turn):
- Data home — tell the user Analyst Kit keeps config, API keys, local usage analytics, and
a learnings log together in one folder (default
~/.analyst-kit); offer to move it with
"$_AK/bin/analyst-kit-setup" home <dir>.
- Telemetry (a notice, not a question) — usage telemetry is on by default: only
skill name, duration, outcome, and version, tagged with a per-machine device id
derived from the hardware/OS, never repo names, paths, tickers, or content; opt
out anytime by asking to turn Analyst Kit telemetry off.
- Identity (name + email — required) — ask for the user's name and email, stating
plainly the email is mandatory to use most features (SEC EDGAR's fair-access policy
requires a real, reachable contact on every request). If they decline because they
don't want the email-dependent skills, discover a fallback yourself (
git config user.email, gh api user, …) and pass it with --fallback in step 5.
- Offer full setup — ask if they'd like to configure API keys for all skills now.
If yes, Read
"$_AK/references/intro.md" and follow it. If no, continue — you'll
ask for a key only when a skill needs one.
- Run
"$_AK/bin/analyst-kit-setup" finish --name "<name>" --email "<email>" (add
--fallback for a discovered email; run plain finish only if no email could be
found at all). If it echoes INVALID_EMAIL, re-ask and run it again.
- User asks to set up Analyst Kit at any time (e.g. "set up analyst-kit", "help me set up Analyst Kit", "configure all skills") → Read
"$_AK/references/intro.md" and follow it end
to end: data home, telemetry, every skill's keys, and enabling/disabling each.
TEL_PROMPTED: no (returning user) → give the telemetry notice once, then run
"$_AK/bin/analyst-kit-setup" ack-telemetry.
- If the user asks to turn telemetry off (now or anytime): before flipping it, make a
sincere case once — telemetry is what tells the maintainers which skills break, which
run slow, and where users get stuck, so keeping it on directly improves their
experience; it never includes their data. Offer
"$_AK/bin/analyst-kit-config" set telemetry anonymous (drops the device id) as a middle
ground. If they still want out, run "$_AK/bin/analyst-kit-config" set telemetry off
immediately and without further argument.
- Before running any script that needs an API key: source both the data home's
.env
and the current directory's .env into the shell so scripts can read stored keys:
set -a; [ -f "$AK_HOME/.env" ] && . "$AK_HOME/.env"; [ -f "./.env" ] && . "./.env"; set +a
(where $AK_HOME is the path printed on the AK_HOME: line above). Full lookup order —
always check these before concluding a key is missing or asking the user:
- current shell environment
$AK_HOME/.env (keys stored by analyst-kit-setup)
.env in the current working directory
Never ask the user for a key that is already present in any of these locations.
MISSING_KEYS not none → for each listed key with KEY_PROMPTED_<KEY>: no: explain
where to get it, ask for the value, and run "$_AK/bin/analyst-kit-setup" set-key <KEY> <value>.
If declined, run "$_AK/bin/analyst-kit-setup" skip-key <KEY> (this disables the skills that
need it) and continue — never block the skill.
UPGRADE: UPGRADE_AVAILABLE <old> <new> → say "Analyst Kit skills is available
(you have ) — update?". If yes: Read "$_AK/references/upgrade.md" and
follow it. If declined: run "$_AK/bin/analyst-kit-update-check" --snooze <new>.
LEARNINGS entries shown → these are past mistakes/preferences for this user;
respect them and do not repeat logged pitfalls.
Then proceed with the skill. At the very end, run the Completion block at the bottom
of this file.
Charting — Highcharts charts from financial data
Build financially-correct charts: a thin Python + Polars layer normalises already-available
financial data into a chart contract; a TypeScript layer turns the contract into
Highcharts options and a self-contained HTML page.
This is a working default, not a requirement. Use the builders and the emitted options
as-is, or take the options object and adapt it to your needs — you don't have to build a chart
from scratch. It exists to save you tokens and give you something correct to start from.
Library: Highcharts — Stock for time-series (navigator, range selector, flags),
Core for fundamentals. Highcharts mechanics you'll need:
references/highcharts-notes.md. React integration:
references/highcharts-react.md.
Pipeline
financial data ─► Polars: load · validate · normalise ─► chart contract (JSON) ─► TS: buildOptions ─► Highcharts options ─► renderChartPage ─► self-contained .html
(assumed present) (adapt freely)
Data is assumed to be available (produced by an upstream step). The Polars layer only
loads, validates, and normalises it for charting — it does not fetch. Numbers cross the
boundary already scaled; the TS layer formats and draws.
The chart contract (what the TS layer consumes)
{
"title": "AAPL — revenue & net margin",
"subtitle": "FY2021–FY2025",
"axis": { "type": "category", "categories": ["FY21","FY22","FY23","FY24","FY25"] },
"yAxes": [
{ "id": "money", "name": "Amount", "unit": "B", "currency": "$" },
{ "id": "pct", "name": "Net margin", "percent": true, "opposite": true },
{ "id": "eps", "name": "EPS", "currency": "$", "decimals": 2 }
],
"series": [
{ "name": "Revenue", "kind": "column", "yAxis": "money", "role": "primary", "data": [365.8, 394.3, 383.3, 391.0, 416.2] },
{ "name": "Net margin", "kind": "line", "yAxis": "pct", "role": "neutral", "data": [25.9, 25.3, 25.3, 24.0, 26.9] }
],
"flags": [ { "x": "FY24", "title": "Event" } ],
"meta": { "chart": "revenueMargins", "variant": "stacked", "stock": false }
}
role → color in the TS layer: primary · secondary · neutral · positive ·
negative · estimate · total · segment · signed · waterfall.
kind → Highcharts series type: line · column · area · arearange (shaded
band, e.g. Bollinger — points are [ts, low, high]) · waterfall · candlestick.
yAxes[] carry unit / currency / percent / decimals (see the units table).
An optional opts object passes Highcharts yAxis options through verbatim — set
top/height percentages to stack panes (price / volume / RSI / MACD on one chart)
or plotBands for indicator zones.
meta: variant (stacked|percent|grouped) drives column stacking; stock: true
selects Highcharts Stock (navigator + range selector); zeroLine: true draws a 0 baseline.
Use it
Agent fast-path — two commands, no setup, no hand-written code. python3 (with polars)
produces the contract; node renders it — no bun/tsx install required (node is always
present; the launcher uses bun/tsx if found, else node, installing bun only as a last
resort). Go raw records → finished HTML without writing any Python/TS/HTML yourself:
python3 -m pipeline.cli yoy data.json --metrics revenue,bookings --lag 4 -o contract.json
node scripts/render.mjs contract.json chart.html
data.json is [{"date": "YYYY-MM-DD", "revenue": 655300000, "bookings": 773800000}, …]
with absolute values — the pipeline scales units, labels quarters, trims the lag window,
and colors series. Metric columns are arbitrary, so non-GAAP KPIs (bookings, DAUs, ARR)
work the same as GAAP fields. For other chart shapes, call the builders from Python and
render the same way.
Dev setup (only for hacking on the skill itself):
pip install -r requirements.txt
npm install
python -m pipeline.cli
npm run examples
from pipeline import charts
contract = charts.revenue_margins(income_records)
import { renderChartPage, buildOptions } from "@analyst-kit/charting";
const html = renderChartPage(contract);
const htmlCdn = renderChartPage(contract, { cdnScripts: true });
const opts = buildOptions(contract);
file:// rule — use the default (inlined) mode for anything opened directly. The
default renderChartPage / render.mjs output inlines Highcharts, so the page works
offline from file:// and in a headless PDF. The --cdn / cdnScripts: true mode emits
<script src> tags and only works when served over http(s) — opened from file:// it
renders a blank chart ("Highcharts is not defined"). Use --cdn only for a lightweight
browser preview behind a local server, never for a file you double-click or PDF.
CDN rule for hand-written HTML: if you write <script src> tags directly, always use
https://cdn.jsdelivr.net/npm/highcharts@12/ — never code.highcharts.com, which returns
403 for headless browser requests and will produce a blank chart in the PDF.
The vendored scripts are also available at vendor/highcharts/ relative to the skill root.
Intent → chart
| The question | Chart | Builder | Notes |
|---|
| How has X trended? growth? | line / YoY line | revenue_trend, revenue_yoy | per-series shift for lead/lag; event flags for context |
| Growth of several metrics? (incl. non-GAAP KPIs) | YoY % lines | metrics_yoy | arbitrary metric columns (bookings, DAUs…); CLI: pipeline.cli yoy |
| What happened when? | line + event flags | revenue_trend(flags=…) | caller-provided dashed vertical markers |
| Segment size over time? | stacked bar | segments(variant="stacked") | total + composition |
| Segment mix shift? | 100% stacked bar | segments(variant="percent") | reads mix even as total grows |
| Compare segment sizes? | grouped bar | segments(variant="grouped") | |
| Revenue → profit? | waterfall | waterfall | native Highcharts, reconciles to net income |
| Dividend history and yield? | dual-axis bar+line | dividend_yield | $/share bars + yield % line |
| Performance and profitability? | dual-axis bars+line | revenue_margins | revenue/NI bars + margin % line |
| Beat or miss? | sign-colored bars | surprise | green beat / red miss, zero baseline |
| Estimate vs reported? | grouped bars | estimate_vs_reported | estimate muted, reported bold |
| Price action + events? | candlestick / line | price | Stock: navigator + rangeSelector + flags |
| Compare two unequal companies? | rebased line | compare_rebased | both start at 100 → relative growth |
Units conventions
Numbers are formatted by data type, not blindly. Full table:
references/data-units.md. Headlines: one unit per axis ($B/$M/$K
by max value), EPS / per-share always 2 dp ($1.00), negatives in accounting
parentheses (($2.5M)), percent with one dp.
Rules
- One unit per axis (scaled + labelled in the axis name and ticks). Two units → two axes.
- Color carries meaning — green up / red down, estimate grey-dashed, total slate, segments
from a colorblind-safe palette. →
references/aesthetics.md
- Compact, accounting labels — see the units table; tooltip always available.
- Missing = gap (
null), not zero; a never-reported segment is a real 0.
- Compare like with like — rebase unequal companies to 100; growth uses YoY, not levels.
- Compute, don't reason; render, don't hand-write — growth/margins/rebasing come from
the Polars pipeline (
pipeline.cli yoy, process.yoy/margin/rebase) and the page comes
from scripts/render.ts. LLM arithmetic and hand-rolled Highcharts boilerplate are
slower and error-prone; the pipeline is deterministic and tested.
Chart selection detail: references/chart-types.md.
Files
charting/
SKILL.md CHARTING.md
pipeline/ process.py charts.py cli.py ← thin Python + Polars (load/validate/normalise)
src/ types.ts format.ts theme.ts render.ts charts/build.ts index.ts ← TS (Highcharts)
references/ chart-types · data-units · aesthetics · highcharts-notes · highcharts-react
tests/ data/*.json (dummy records) · contracts/*.json (generated) · *.test.ts · test_*.py
examples/ *.html (generated)
requirements.txt package.json tsconfig.json
Tests — two suites, offline
python -m pytest tests -q
npm test
Both run against the dummy data in tests/ (no network). They assert the things a financial
chart must get right — correct axis unit, sign-driven color, a waterfall that reconciles to net
income, two units on two axes, YoY math + leading gaps, EPS 2-dp / accounting formatting.
Regenerate contracts with python -m pipeline.cli.
Completion (run last)
Audit before you deliver. If this run produced a research deliverable carrying
quantitative or factual claims — a deep dive, thematic/value-chain map, technical
call, company wiki, or financial model — you MUST verify it before presenting it.
If a research-auditor subagent is available (the analyst-kit plugin ships one),
invoke it via the Task tool, handing it the draft and the data artifacts you used;
on a runtime without subagents, run the same checks yourself. Resolve every
CRITICAL finding and disclose any UNVERIFIED ones; never deliver on a FAIL
verdict without fixing it first. Skip this only for pure data-fetch/utility runs
with no analytical claims.
PLAN MODE EXCEPTION — ALWAYS RUN: writes only to ~/.analyst-kit/. Replace OUTCOME with one
of DONE | DONE_WITH_CONCERNS | ERROR | ABORT | NEEDS_CONTEXT.
_AK="$(cat ~/.analyst-kit/core-path 2>/dev/null)"
[ -x "$_AK/bin/analyst-kit-log" ] && "$_AK/bin/analyst-kit-log" end --skill charting --outcome OUTCOME 2>/dev/null || true
If this session surfaced a durable pattern, pitfall, or user preference that would save
5+ minutes next time (not obvious, not a transient error), also log it:
"$_AK/bin/analyst-kit-learn" add '{"skill":"charting","type":"pitfall|pattern|preference","ticker":"<optional>","insight":"<one line>","confidence":7,"ts":"<iso8601 utc>"}'