Skip to main content

@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 bar TimeSeries to 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.

Installnpm 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 TimeSeriesrsi(ema(bollinger(candles, { period: 20 }), { period: 10 })) gives the same series as the first three links of that chain.

GroupStudies
Averages, bandssma, ema, bollinger, envelope, donchian, vwap
Oscillatorsrsi, macd, stochastic, williamsR, momentum, percentChange, zScore, obv
Volatilityatr, 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:

src/examples/financial-calendar-chart.tsx
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.
note

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.