chart-indicator
GitHub为OpenAlgo图表终端构建自定义JS技术指标,涵盖创建、移植和调试。严格遵循草稿验证后安装流程,确保与Python策略指标区分,避免静默失败。
Trigger Scenarios
Install
npx skills add marketcalls/openalgo --skill chart-indicator -g -y
SKILL.md
Frontmatter
{
"name": "chart-indicator",
"description": "Build a custom indicator for the OpenAlgo \/trading charting terminal (openalgo-charts). Use when asked to create, port, or debug a chart indicator, overlay, oscillator, band, or on-chart signal, including porting a study written for another charting platform. Writes a plain-JS descriptor into strategies\/indicators\/, but only after it validates against the real library. This is the chart path, not the Python openalgo.ta path used from strategies and scanners.",
"allowed-tools": "Read, Write, Edit, Bash, Glob, Grep",
"argument-hint": "[indicator name or a study to port]"
}
Custom chart indicators for /trading
Build an indicator for the charting terminal. It becomes a picker entry with a generated settings dialog, a legend row and saved-layout persistence, with no build step and no restart.
This is the chart (JavaScript) path. It has nothing to do with the Python
openalgo.ta indicators used from strategies, scanners and backtests: different
language, different runtime, different API. If the request is for a Python
indicator, this skill is the wrong one.
The one rule
Never write a file into strategies/indicators/ directly. That folder is
imported by the live chart, and the runtime fails silently in the ways that
matter most: a column that is one element short, or a plot key that does not
match what calc returns, draws nothing at all and raises nothing anywhere.
Always: write to a scratch path, validate, install on a pass.
# 1. draft to a scratch file (never the indicators folder)
# e.g. <scratchpad>/my_indicator.js
# 2. validate against the real openalgo-charts build
node .claude/skills/chart-indicator/validate.mjs <scratch>/my_indicator.js
# 3. only on PASSED, install it
node .claude/skills/chart-indicator/validate.mjs <scratch>/my_indicator.js --install
--install copies into strategies/indicators/ only when there are zero
errors, and exits 1 otherwise. If validation fails, fix the draft and re-run.
Do not install a failing indicator, and do not weaken the validator to get a
pass. Report warnings to the user rather than silently accepting them.
Never run npm install for this. The full frontend tree is 560 MB across
521 packages; the validator needs two ES modules totalling 368 KB. It finds them
itself, in this order: frontend/node_modules/openalgo-charts if a React
developer already has it, then its own .cache/, then it fetches just that one
package at the version pinned in frontend/package.json. openalgo-charts has
zero dependencies, so that is one small download, about a second, cached after.
If the fetch fails (no network, npm unavailable), say so and offer the choice: fix connectivity, or install without the pre-flight check and rely on the chart's own validation, which reports the same structural problems as toasts when the indicator loads. Do not silently skip validation.
Workflow
- Read the request. If it is a study from another platform, read it fully and identify: what is plotted, what is a signal, what state carries across bars, and what resets per day or per session.
- Load the context you need.
reference/contract.mdfor the descriptor shape and the runtime's exact behaviour,reference/api.mdfor what is available inside the module,reference/pitfalls.mdfor the traps. Readreference/pitfalls.mdbefore writing anything; most first drafts fail on something in it. - Pick the closest example in
examples/and work from it:simple_zscore.js— one pane, one plot, rolling window, levels, rangeintermediate_keltner_squeeze.js— several plots,fills,colorBy, a second price scale, a boolean that hides part of the drawingshaded_trend_zone.js— shading between two series, Supertrend and HalfTrend style, where the ribbon flips sides and recolours with the trendcomplex_session_vwap.js— per-session state,markerswith a signal latch,table,calcTail, zone-aware day boundaries
- Draft to scratch. Validate. Iterate until it passes.
- Install, then tell the user to reopen the indicator picker on
/trading. No page reload is needed: the catalogue re-reads the folder every time the picker opens, and an edited file is re-imported because the URL carries the file's modification time. A reload is only needed for a chart that was already open before the app itself changed.
Two layers of validation
validate.mjs is a pre-flight check, and it is the one that can refuse to
install. The chart validates again at load time, in the browser, where the
library already is: it checks the descriptor before it reaches the catalogue,
and wraps calc so its first result is measured against the bars. Anything
wrong surfaces as a toast naming the file.
That second layer is why a trader with no Node.js at all still gets told what is wrong instead of an indicator that quietly draws nothing.
What the file has to look like
Plain JavaScript. Nothing compiles it: no TypeScript, no JSX, no imports. The module default-exports one function and is handed the whole charting API.
export default function ({ registerIndicator, sourceValues, sma, nulls }) {
registerIndicator({
id: 'my-thing', // unique slug; prefix your own to avoid overriding a built-in
name: 'My Thing', // picker and legend
category: 'Custom', // groups it in the picker rail
placement: 'onchart', // 'onchart' overlays price, 'pane' gets its own pane
inputs: [ ... ], // becomes the settings dialog
plots: [ ... ], // each key must appear in what calc returns
calc(bars, settings, store) {
return { /* one array per plot key, exactly bars.length long */ }
},
})
}
A bar is { time, open, high, low, close, volume } with time in UTC
seconds.
The four things that go wrong most
Full list in reference/pitfalls.md. These four account for most failures:
- Column length. Every array must be exactly
bars.length. Short arrays do not error, they just stop drawing partway. - Warmup. Use
null(ornulls(...)on a helper's NaN output). A0puts a spike at the bottom of the pane and wrecks autoscale. nasemantics. Script languages with a not-available value treat every comparison against it as false. In JavaScript5 > nullis true. Guard withx != nullor signals fire through the warmup gap.- Marker anchoring.
aboveBar/belowBaranchor to this indicator's own plot line, not to the candle. To place a label relative to a bar, useposition: 'atPrice'with an explicit price.
Do not
- Add colour or line-width inputs. The chart generates colour, opacity,
thickness, line style and plot style per plot automatically, seeded from each
plot's
style. Your own width input becomes a second control that disagrees. - Reuse a built-in id unless overriding it is the actual intent. Custom modules register last, so they win. The validator warns on this.
- Assume the browser's local time. Use
zonedDayIndex/utcSecondsToZonedPartswith a zone, defaulting toDEFAULT_TIMEZONE.
Where things live
| Path | |
|---|---|
strategies/indicators/*.js |
installed indicators, gitignored, never pushed |
.claude/skills/chart-indicator/validate.mjs |
the gate |
.claude/skills/chart-indicator/examples/ |
three validated worked examples |
.claude/skills/chart-indicator/reference/ |
contract, API surface, pitfalls |
docs/custom-indicators.md |
the user-facing guide |
blueprints/custom_indicators.py |
serves the folder to the chart |
frontend/src/lib/trading/customIndicators.ts |
the loader |
Indicators are loaded over HTTP at runtime, not bundled, so they survive
git pull and need no rebuild. They run with full access to the logged-in
session: treat an indicator file from an untrusted source as you would any
script you are about to run.
Version History
- 849ae2c Current 2026-08-28 05:10


