@pond-ts/financial
Two things, both built on plain pond series:
- Studies — twenty technical indicators (
sma,bollinger,rsi,macd,atr,stochastic,vwap, …), each a method you chain on a barTimeSeriesto append its columns, verified against a pandas oracle (the named indicators against TA-Lib as well). - A trading calendar — a session schedule (open/close instants, holidays,
intraday breaks) plus the query surface pond's data ops and
@pond-ts/charts' trading-time axis need. One class,TradingCalendar.
Install — npm install @pond-ts/financial pond-ts. Peer-depends on
pond-ts; the pond packages release together, so keep their ranges in
step.
Studies
A study takes a series and options and returns the series with more columns
on it: it reads a column (default 'close', or the named high / low /
close / volume inputs for the multi-input ones), computes over bar-count
periods, and appends its
output column(s) with the warm-up rows left undefined so the result lines
up on the source's time axis. Studies compose because each returns the
widened series. Opt in to the fluent surface once and chain them:
import '@pond-ts/financial/fluent';
const bars = candles
.bollinger({ period: 20 })
.ema({ period: 10 })
.rsi({ period: 14 })
.macd({ fastPeriod: 12, slowPeriod: 26, signalPeriod: 9 })
.vwap({ period: 20 });
// + bbUpper / bbMiddle / bbLower, ema, rsi, macdLine / macdSignal / macdHist, vwap
Every study is also a plain function for code that would rather not augment
TimeSeries — rsi(ema(bollinger(candles, { period: 20 }), { period: 10 }))
gives the same series as the first three links of that chain.
| Group | Studies |
|---|---|
| Averages, bands | sma, ema, bollinger, envelope, donchian, vwap |
| Oscillators | rsi, macd, stochastic, williamsR, momentum, percentChange, zScore, obv |
| Volatility | atr, historicalVolatility, rollingStdev, rollingMin, rollingMax, rollingPercentile |
Every study is verified bar-for-bar against a pandas oracle before it ships,
and the eight named indicators (rsi, macd, atr, stochastic,
williamsR, momentum, percentChange, obv) against TA-Lib as well; the
handful of deliberate
differences (a flat window is undefined, not 0; %K starts at its own
first valid bar; a gap in a running sum propagates) are documented on each
study's docstring. The full table with what each one appends and computes, and
a worked oscillator-panel chart, is on the
Financial charts page.
The candle below is calendar-aware
Weekends genuinely don't exist in the data below — every bar is stamped at
a real trading session's open, with no bar for Saturday or Sunday. Handing
the calendar to <ChartContainer> collapses that closed time from the
axis too, so trading days sit flush against each other instead of a gap
opening up every weekend:
import {
Candlestick,
ChartContainer,
ChartRow,
Layers,
YAxis,
} from '@pond-ts/charts';
import { useSiteChartTheme } from '@site/src/theme/useSiteChartTheme';
import { marketBars, sessionWindow } from './lib/financial-fixtures';
export default function FinancialCalendarChart() {
const theme = useSiteChartTheme();
const set = marketBars();
const { range, bars } = sessionWindow(set, 30);
return (
<ChartContainer
range={range}
width={560}
theme={theme}
calendar={set.calendar}
cursor="crosshair"
>
<ChartRow height={220}>
<YAxis id="price" side="right" format="$,.0f" width={50} />
<Layers>
<Candlestick series={bars} as={set.symbol} showOHLC />
</Layers>
</ChartRow>
</ChartContainer>
);
}
<ChartContainer range={range} width={560} theme={theme} calendar={cal}>
<ChartRow height={220}>
<YAxis id="price" side="right" format="$,.0f" />
<Layers>
<Candlestick series={series} as="demo" showOHLC />
</Layers>
</ChartRow>
</ChartContainer>
calendar is the only new prop — everything else is the same
ChartContainer/ChartRow/Layers/Candlestick vocabulary from the
Learn charts track. No package coupling: the chart
only needs an object shaped like { discontinuities(): ... }, which
TradingCalendar happens to be.
Building a calendar
Two constructors, both first-class — pick whichever shape your data starts in:
import { TradingCalendar } from '@pond-ts/financial';
// From hours + a date range — a calendar that *generates* its sessions.
const cal = TradingCalendar.fromRules(
{ timeZone: 'America/New_York', open: '09:30', close: '16:00' },
{ from: '2026-01-05', to: '2026-02-13' },
);
// From an explicit session list — for a feed that already hands you
// dated open/close instants (a vendor calendar, a holiday API).
const cal2 = TradingCalendar.fromSessions(sessions);
SessionRules also takes weekmask (default Monday–Friday), breaks
(a lunch), holidays (dates with no session), and earlyCloses
(per-date override closes) — every hour is resolved DST-correctly
against the IANA timeZone you give it, not a fixed UTC offset.
Querying the calendar
cal.sessions(); // every session, ascending
cal.sessionOn('2026-01-06'); // the session on a date, or undefined
cal.isTradingDay('2026-01-10'); // false — a Saturday
cal.sessionContaining(instant); // the session an instant falls in
cal.isOpen(instant); // inside a session AND not inside a break
Feeding a chart-ready bucketing grid
sessionSequence/barSequence return a pond BoundedSequence — feed it
straight to .aggregate/.materialize and every bucket lands on a real
trading session, with no weekend/holiday buckets and no bucket spanning a
market closure:
const dailyBars = tickSeries.aggregate(cal.sessionSequence(), {
price: { from: 'price', using: 'last' },
});
const fiveMinBars = tickSeries.aggregate(cal.barSequence('5m'), {
price: { from: 'price', using: 'last' },
});
barSequence bars are session-aligned — they never span a session
boundary or an intraday break, and the final bar of each tradeable
segment truncates at the close rather than overrunning it.
Tagging events with their session
tagSessions appends a session-id column — use it as a partitionBy key
so stateful ops (rolling, fill, cumulative folds) don't bridge across
a session boundary a raw time-based window would silently cross:
const tagged = cal.tagSessions(series); // adds a "session" column
const smoothed = tagged
.partitionBy('session')
.rolling('5m', { price: 'avg' })
.collect();
Where to go next
- API reference — the generated, full-width reference: every method, option, and type.
- Learn charts — the tutorial track this page's chart reuses every primitive from.
- Resizable multi-panel layout — the canonical financial shape: a price chart over an indicator panel.
This page is a quickstart, not the full financial-charting guide — a
flagship end-to-end guide (OHLC data → aggregate rollups → Candlestick
variants → volume row → the forming-bar live pattern) is on the roadmap.
See PLAN.md's
"Docs site wave" for what's shipped and what's next.