Financial charts
Financial charting in pond is two things that meet on the plot: a library of studies (technical indicators, built on pond as plain series transforms) and the chart assembly that draws them — candles, a volume pane, a session-aware axis, an OHLC readout. This page leads with the studies, then assembles the chart around them.
import {
BandChart,
Candlestick,
ChartContainer,
ChartRow,
Layers,
LineChart,
YAxis,
} from '@pond-ts/charts';
import '@pond-ts/financial/fluent';
import { useSiteChartTheme } from '@site/src/theme/useSiteChartTheme';
import { marketBars, sessionWindow } from './lib/financial-fixtures';
/** Studies **append** columns to a bar `TimeSeries` and return the widened
* series — so they chain, and you draw their output as ordinary chart
* layers. Importing `@pond-ts/financial/fluent` mounts them as methods:
* `.bollinger()` adds `bbUpper`/`bbMiddle`/`bbLower`, `.ema()` adds `ema`,
* and the band and line just read those columns over the same candles.
*
* Prices are **modelled**, not measured — see `lib/financial-fixtures.ts`.
* The window is the last 120 sessions of that year, cropped *before* the
* studies run so the warm-up rows are the window's own. */
export default function ChartsFinancialStudies({ width }: { width: number }) {
const theme = useSiteChartTheme();
const set = marketBars();
const { range, bars } = sessionWindow(set, 120);
const study = bars.bollinger({ period: 20 }).ema({ period: 10 });
return (
<ChartContainer
range={range}
width={width}
theme={theme}
calendar={set.calendar}
cursor="crosshair"
>
<ChartRow height={240}>
<YAxis id="price" side="right" format={set.priceFormat} width={62} />
<Layers>
<BandChart
series={study}
lower="bbLower"
upper="bbUpper"
axis="price"
as="inner"
/>
<LineChart series={study} column="ema" axis="price" as="secondary" />
<Candlestick series={bars} as={set.symbol} showOHLC gap={1} />
</Layers>
</ChartRow>
</ChartContainer>
);
}
Above: daily candles with a Bollinger band and an EMA overlaid — each
one a study that appended its columns to the bar series, drawn as an ordinary
BandChart / LineChart layer.
The prices are modelled, not measured — every chart on this page draws the same 251-session year as the Gallery's finance cards, generated by the process model in the fixture header (regimes, volatility clustering, opening gaps, a real NYSE session calendar). Market data is not redistributable, so no real feed is used; the calendar the axis collapses is the real one.
The studies library
A study is a pure function (series, options) => series that appends one
or more columns to a bar TimeSeries and returns the widened series. They're
thin assemblies over a small rolling kernel — they add vocabulary, not new
math — and each is checked against an independent reference (a pandas/TA-Lib
"oracle") before it ships.
Three conventions make the corpus uniform and composable:
column— which field to read (default'close'). No study hard-codesclose, so a study can run over any numeric column — including another study's output (ChartIQ's "Field" idea).output/prefix— what to name the result column(s), so overlays don't collide.- Bar-count periods, length-preserving warm-up —
periodis a number of bars; the warm-up rows areundefined(the chart renders that as a clean gap, so a moving average simply starts once it has enough history). For a rolling study that is the firstperiod − 1rows; the difference-built ones cost one more — see the warm-up note under the tables.
import '@pond-ts/financial/fluent';
// each study appends its columns and returns the widened series, so they chain
const bars = candles.bollinger({ period: 20 }).ema({ period: 10 });
// bars now has bbUpper / bbMiddle / bbLower (from bollinger) + ema (from ema)
That is the fluent form — one import '@pond-ts/financial/fluent' mounts
every study as a method on TimeSeries, and it is what the examples on this
page use. Each study is also a plain function, (series, options) => series,
for code that would rather not augment the prototype:
ema(bollinger(candles, { period: 20 }), { period: 10 }) is the same series.
What's here today
Twenty studies, every one verified against a pandas oracle before it
shipped — and the eight named indicators (rsi, macd, atr, stochastic,
williamsR, momentum, percentChange, obv) bar-for-bar against TA-Lib
too. Each is a batch transform (TimeSeries → TimeSeries + columns). Grouped
by where they are drawn:
Overlays — drawn on the price row
| Study | Appends | Computes |
|---|---|---|
sma | sma | Simple moving average (mean of the last period bars). |
ema | ema | Exponential moving average (α = 2/(period+1)). |
bollinger | bbMiddle / bbUpper / bbLower | SMA ± stdDev×population σ (default 2). |
envelope | envMiddle / envUpper / envLower | Moving-average envelope, middle × (1 ± percent/100). |
donchian | dcUpper / dcLower / dcMiddle | Highest high / lowest low over period bars, and the midpoint. |
vwap | vwap | Rolling volume-weighted typical price, Σ tp·v / Σ v; period is required. |
Oscillators — drawn on their own row (the example below)
| Study | Appends | Computes |
|---|---|---|
rsi | rsi | Wilder's Relative Strength Index, 0–100 (default 14). |
macd | macdLine / macdSignal / macdHist | EMA(fast) − EMA(slow), its EMA signal, and the difference (12/26/9). |
stochastic | stochK / stochD | %K = where the close sits in the kPeriod range, smoothed by slowing; %D its SMA. slowing: 1 is the fast stochastic. |
williamsR | williamsR | Williams %R, −100..0 — stochastic({ slowing: 1 }).K − 100. |
momentum | momentum | value − value[period bars ago] (default 10). |
percentChange | pctChange | Rate of change vs periods bars ago, in percent (TA-Lib's ROC). |
zScore | zscore | (value − SMA) / σ, standardized deviation. |
obv | obv | On-Balance Volume — the running total of volume signed by the close change. No period. |
Volatility and range statistics
| Study | Appends | Computes |
|---|---|---|
atr | atr | Wilder's Average True Range, in price units (default 14). |
historicalVolatility | hv | Annualised σ of log returns (annualize bars per year, default 252). |
rollingStdev | stdev | Rolling population standard deviation. |
rollingMin | min | Rolling minimum (one Donchian edge). |
rollingMax | max | Rolling maximum (the other). |
rollingPercentile | p{q} | Rolling q-th percentile (linear interpolation). |
Multi-input studies (atr, stochastic, williamsR, donchian, obv,
vwap) name each bar column they read — high, low, close, volume —
with the conventional defaults, so they run over any column layout the same
way column lets a single-input study run over any field.
Two conventions worth knowing before you draw them: warm-up length is the
first period − 1 rows for a rolling study, but one more (period) for the
difference-built ones (rsi, atr, momentum, historicalVolatility, and
percentChange over its periods), kPeriod + slowing − 2 for %K, and
each macd column from its own first valid bar; obv has no warm-up at all.
And a flat window is undefined, not
0 — rsi, stochastic and williamsR all report no position when there is
no range to have a position in (TA-Lib returns 0 there; the package's
docstrings record every such delta).
Oscillators in their own rows
An oscillator is just another column on the same series, drawn on its own
<ChartRow> with its own <YAxis>. RSI gets a fixed min={0} max={100}
axis and two <Baseline>s at 30 and 70; MACD gets its line and signal on an
auto-scaled axis around a zero baseline. The window (160 sessions, October
to June) spans the year's −23.5% correction, which is where an oscillator
earns its row: RSI drops below 30 in two clusters during the sell-off (late
December, late March) and above 70 on the run-up either side, and the MACD
line crosses its signal four times:
The crosshair reads all three rows at once — the OHLC readout on the candles, and each oscillator's value on its own axis.
What's coming
The corpus assessment maps ~124 ChartIQ studies onto ~11 rolling kernels, so
the library extends cheaply, and Phase 1 breadth (the oscillators and volume
studies above) has landed. Still open in that phase: ATR bands (Keltner-style
close ± k·ATR) and the session-anchored VWAP — the rolling vwap above
is a window, not the intraday desk's running line, which needs a session reset.
A later phase covers the stateful ones (PSAR, SuperTrend, ATR trailing stop,
ZigZag) and the other session-anchored studies (pivot points). Adding one is a
short, oracle-verified assembly — see the studies/README.md pattern in the
package.
Assembling the chart
The chart around the studies is the standard @pond-ts/charts composition —
there's no separate "financial mode", just the pieces:
import {
Candlestick,
ChartContainer,
ChartRow,
Layers,
YAxis,
} from '@pond-ts/charts';
import { useSiteChartTheme } from '@site/src/theme/useSiteChartTheme';
import { marketBars, sessionWindow } from './lib/financial-fixtures';
/** Financial terminal: daily OHLC candles on a session calendar, with the
* crosshair cursor and the axis-pill OHLC readout — first-class support, not
* a bar-chart hack. Prices are **modelled**, not measured — see
* `lib/financial-fixtures.ts`. */
export default function GalleryFinancial({ width }: { width: number }) {
const theme = useSiteChartTheme();
const set = marketBars();
const { range, bars } = sessionWindow(set, 60);
return (
<ChartContainer
range={range}
width={width}
theme={theme}
calendar={set.calendar}
cursor="crosshair"
>
<ChartRow height={220}>
<YAxis id="price" side="right" format={set.priceFormat} width={62} />
<Layers>
<Candlestick series={bars} as={set.symbol} showOHLC gap={1} />
</Layers>
</ChartRow>
</ChartContainer>
);
}
-
Candles —
<Candlestick>reads the four OHLC columns;showOHLCfans the full open/high/low/close to the readout. -
Crosshair —
cursor="crosshair"on<ChartContainer>gives the trading-terminal reticle with on-axis pills. -
A volume pane — a second
<ChartRow>with a<BarChart>on thevolumecolumn and its own axis:<ChartRow height={110}><YAxis id="vol" side="right" format=",.0s" /><Layers><BarChart series={bars} column="volume" axis="vol" /></Layers></ChartRow> -
A session-aware axis — hand
<ChartContainer>acalendarand closed-market gaps collapse; see the Trading-time axis. -
A live price pill — a
YAxisIndicatordriven bycreateLiveValuepins the last price to the axis edge without re-rendering the chart; see Axis indicators & live values.
Coming from TradingView
If you're arriving from TradingView / ChartIQ, the vocabulary maps like this:
| TradingView / ChartIQ | pond-ts |
|---|---|
| Indicator / Study | the studies library (sma, bollinger, …) |
| Field (indicator input) | the column option (or the named high / low / close / volume inputs) |
| Candles / OHLC bars | <Candlestick> |
| Bar / Candle / Hollow style | <Candlestick variant> |
| Volume sub-pane | a second <ChartRow> with <BarChart column="volume"> |
| Pane / sub-chart | a <ChartRow> (Layout) |
| Crosshair | cursor="crosshair" |
| OHLC legend / data window | <Candlestick showOHLC> |
| Last-price line / label | YAxisIndicator + createLiveValue |
| Session separator | sessionDividers on <ChartContainer> |
| Regular trading hours (no gaps) | the calendar prop + a TradingCalendar |
See also
- @pond-ts/financial — the package side: the studies list,
the fluent opt-in, and constructing a
TradingCalendar. - Candlestick · Trading-time axis — the two primitives this hub assembles.
- Storybook:
Charts/Candlestick→ ScenarioPriceVolume — the price + volume two-row scenario. - The generated API reference — every study's full signature.