Skip to main content

@pond-ts/financial

Two things, both built on plain pond series:

  • Studies — 109 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.

Both are in TypeScript or Rust. In Rust they are part of the pond-rs crate (pond_rs::financial): every study @pond-ts/financial has, with the same numbers to the bit, and the trading calendar. The pond-rs finance studies page has the Rust details. The charts on this page are TypeScript only.

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.

FamilyCountExamples
Moving averages18sma, ema, movingAverage, macd, trix, kst
Bands10bollinger, keltner, donchian, envelope
Momentum21rsi, stochastic, williamsR, commodityChannelIndex
Trend16directionalMovement, aroon, superTrend, ichimoku
Volatility8atr, historicalVolatility, ulcerIndex
Volume18obv, moneyFlowIndex, chaikinMoneyFlow, vwap
Statistical11linearRegression, beta, correlation, zScore
Price5typicalPrice, medianPrice, balanceOfPower
Session2sessionVwap, pivotPoints (need a trading calendar, below)

The STUDIES catalog in @pond-ts/financial/catalog lists all 109 with their options and outputs.

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,
CrosshairCursor,
} 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}
>
<CrosshairCursor />
<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}>
<LineCursor />
<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.

pond-rs has not built sessionSequence / barSequence yet, so this section has no Rust tab. In Rust, tag_sessions (below) with partition_by covers the per-session work.

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​

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.