Expand description
§pond-rs
Time series analytics for Rust: rolling windows, time and calendar buckets (time-zone and daylight-saving aware), joins, per-entity partitions, 109 technical-analysis studies, trading calendars, and live series that update as events arrive. Arrow and JSON in and out.
pond-rs is the Rust implementation of pond. The
TypeScript packages (pond-ts, @pond-ts/financial) and this crate
give the same results, bit for bit: a parity harness checks every
operation against output written by pond-ts. Pick the language that
fits the program; the numbers do not change.
[dependencies]
pond-rs = "0.1"§Where to start
| You want to… | Start at |
|---|---|
| Build a series, or read one from Arrow or JSON | TimeSeries::new, TimeSeries::read_ipc, TimeSeries::from_json |
| Roll a window over it | TimeSeries::rolling, TimeSeries::rolling_with |
| Bucket it by minute, day, month… in a time zone | TimeSeries::aggregate with Sequence::every or Sequence::calendar |
| Do the same per host, symbol or user | TimeSeries::partition_by |
| Line several series up | TimeSeries::join, TimeSeries::align, TimeSeries::materialize |
| Compute RSI, Bollinger bands, VWAP… | financial |
| Process events as they arrive | live (the live feature) |
§What is in the crate
- Series. A
TimeSeriesis keyed by time, by time ranges or by intervals labelled with numbers or text. Value columns hold numbers, exact decimals, text, true/false values or arrays, and may have gaps (Column,ColumnData). Keys are always in order. - Windows and buckets.
TimeSeries::rollingover a span of time or a count of rows (trailing, leading or centred),TimeSeries::aggregateinto fixed-length or calendar buckets,TimeSeries::reduceover the whole series, with the built-inReducers (count, sum, avg, min, max, percentiles, stdev, unique, top, …). - Row by row.
TimeSeries::diff,rate,pct_change,shift,cumulative,fill;TimeSeries::smooth(EMA, moving average, loess),TimeSeries::baselineandoutliers, a fold of your own withTimeSeries::scan. - Picking rows.
TimeSeries::filter,slice,within,trim,before,after,tail, and lookups by key:TimeSeries::bisect,at_or_before,nearest,find. - Reshaping.
TimeSeries::join/join_many,concat,pivot_by_group,collapse,dedupe,align,materialize; histograms withTimeSeries::by_column; rows keyed by a number column instead of time withTimeSeries::by_value(ValueSeries). - Per entity.
TimeSeries::partition_byruns any of the above per group, on several cores. - In and out. Arrow IPC files and record batches
(
TimeSeries::read_ipc,TimeSeries::write_ipc,TimeSeries::to_arrow); pond’s JSON formats (rows, columns, chart points); times written as text withparse_time/parse_time_in. - Finance.
financial: 109 studies (moving averages, bands, momentum, trend, volatility, volume, statistics, price, per session), each checked against a pandas / TA-Lib oracle, trading calendars, and acatalogdescribing every study for menus and agents. - Live.
live: aLiveSeriestakes events as they arrive and keeps filters, rolling windows, bucket aggregations and per-entity partitions up to date, returning what each push caused.
Every call returns Result; a PondError names the call and the
column, so the input can be fixed without reading pond-rs source.
§Cargo features
| Feature | Default | What it does |
|---|---|---|
parallel | on | Independent output columns, partitions and loess rows run on several cores (rayon). Results are the same bits either way. Does nothing on wasm32. |
live | on | The live module. |
With default-features = false you get the batch core and
financial, on one thread.
§Threads and memory
Threads. With the parallel feature (on by default; not on wasm32),
the output columns of one rolling or aggregate call are computed on
several cores (rayon’s pool; RAYON_NUM_THREADS sets its size). Each
column is still computed by one thread, in order, so results are the same
bits on any number of threads. Partitions of a PartitionedSeries run
on several cores the same way: each partition is computed by one thread.
Loess fits each row on its own, so its rows are spread over the cores.
Memory. Columns hold their values in Arrow buffers. Reading a
gap-free Float64 field, a Utf8 / Dictionary<Int32, Utf8> field or a
Boolean field from a one-batch Arrow file uses the file’s memory
(or a Decimal128 field) uses the file’s memory without copying (the
file’s bytes stay in memory while the column lives), and writing hands
each column’s buffer to Arrow.
§Example
use pond_rs::{Agg, Column, Reducer, Sequence, TimeSeries, parse_time_in};
let series = TimeSeries::new(
"cpu",
vec![0, 20_000, 40_000, 60_000, 80_000],
vec![Column::optional("load", [Some(0.5), Some(0.7), None, Some(0.9), Some(0.4)])],
)?;
let per_minute = series.aggregate(
&Sequence::every("1m")?,
&[
Agg::new("load_avg", "load", Reducer::Avg),
Agg::new("load_n", "load", Reducer::Count),
],
)?;
assert_eq!(per_minute.len(), 2);
assert_eq!(per_minute.column("load_n")?.numbers()?.get(0), Some(2.0)); // the gap is not counted
// Calendar days in New York: the day clocks spring forward is 23 hours.
let days = Sequence::calendar("day", "America/New_York")?;
let day = parse_time_in("2026-03-08", "America/New_York")?; // its start, as epoch ms
let keys = days.buckets_overlapping(day, day)?;
assert_eq!(keys.end[0] - keys.begin[0], 23 * 3_600_000);§Results that are not a series
Latest state per entity. A
ranked table (one row per symbol, sorted by score) is not a
TimeSeries, which is always in time order with its key first. Reduce
each partition’s last window with TimeSeries::tail and
TimeSeries::reduce, then write the table with the re-exported
arrow_array and arrow_ipc:
use std::sync::Arc;
use pond_rs::arrow_array::{ArrayRef, Float64Array, RecordBatch, StringArray};
use pond_rs::{Agg, Column, Reducer, TimeSeries, Value};
let bars = TimeSeries::new(
"bars",
vec![0, 0, 60_000, 60_000, 120_000, 120_000],
vec![
Column::required("symbol", vec!["A", "B", "A", "B", "A", "B"]),
Column::required("close", vec![10.0, 50.0, 11.0, 50.0, 14.0, 49.0]),
],
)?;
let mut scores: Vec<(String, f64)> = Vec::new();
for (key, part) in bars.partition_by(&["symbol"])?.iter() {
let symbol = key[0].as_ref().and_then(Value::as_str).unwrap_or("");
let last = part.tail("2m")?.reduce(&[
Agg::new("mean", "close", Reducer::Avg),
Agg::new("close", "close", Reducer::Last),
])?;
if let (Some(mean), Some(close)) = (last.number("mean")?, last.number("close")?) {
scores.push((symbol.to_string(), close - mean));
}
}
scores.sort_by(|a, b| b.1.abs().total_cmp(&a.1.abs()));
let table = RecordBatch::try_from_iter([
("symbol", Arc::new(StringArray::from_iter_values(scores.iter().map(|s| &s.0))) as ArrayRef),
("score", Arc::new(scores.iter().map(|s| s.1).collect::<Float64Array>()) as ArrayRef),
])
.expect("columns of one length");
assert_eq!(table.num_rows(), 2);
// tail("2m") keeps rows after 0: A's mean is 12.5, its close 14.
assert_eq!(scores[0], ("A".to_string(), 1.5));
// Write it with arrow_ipc::writer::FileWriter, as TimeSeries::write_ipc does.Re-exports§
pub use arrow_array;pub use arrow_ipc;pub use arrow_schema;pub use serde_json;
Modules§
- financial
- Technical-analysis studies for price bars (pond-ts
@pond-ts/financial): moving averages and the studies built on them (MACD, price oscillator, Guppy, rainbow, TRIX, Schaff, Coppock, KST, …), bands (Bollinger, Keltner, STARC, ATR, Donchian, high-low, prime number, envelope), momentum (RSI, stochastics, Williams %R, Chande momentum, momentum, percent change, intraday momentum, awesome and ultimate oscillators, CCI, Fisher transform, psychological line, pretty good and prime number oscillators, QStick, relative vigor, centre of gravity, Chande forecast, true strength), trend (directional movement, Aroon, vortex, vertical horizontal filter, random walk index, SuperTrend, parabolic SAR, ATR trailing stop, ZigZag, Ichimoku, Elder impulse and ray, trend intensity, RAVI, swing index and its running total), rolling statistics and the z-score so far, each a method onTimeSeriesthat adds its number columns and keeps every row. Also trading calendars (TradingCalendar,SessionRules): the sessions an exchange trades, their lookups, and session tagging. - live
live - Live series: events pushed one batch at a time, and nodes built on them
that update as events arrive (pond-ts
LiveSeries,LiveView,LiveAggregation).
Structs§
- Agg
- One mapping entry: write
reducer(source)to the output columnoutput. - Align
- Options for
TimeSeries::align.Align::default()is pond-ts’s default: hold, sample each bucket at its start, over the series’ own time range. - Array
Column - A column of arrays (pond-ts
kind: 'array'): each row holds a list of numbers, text and true/false values, mixed freely, or is a gap. An empty list is a value, not a gap. Numbers must be finite. - Array
Options - Where
TimeSeries::array_aggregateandTimeSeries::array_explodewrite, and what kind of column they write (pond-ts’s{ as, kind }).ArrayOptions::default()replaces the array column in place and works the kind out from the values. - Boolean
Column - A column of true/false values (pond-ts
kind: 'boolean'). A gap is an Arrow null. Held as an ArrowBooleanarray (one bit per row). - Change
- Options for
TimeSeries::diff_with,TimeSeries::rate_withandTimeSeries::pct_change_with.Change::default()is pond-ts’s default: the first row kept, each named column replaced by its change. - Column
- A named value column: numbers, text or true/false values. Handed to
TimeSeries::new, and read back withTimeSeries::column. - Column
Def - One schema entry.
required: falsemeans the column may have gaps. - Decimal
- One exact decimal:
value × 10^-scale, soDecimal { value: 125, scale: 2 }is1.25. Prints as a decimal (1.25). Equality comparesvalueandscaleas stored, so1.25at scale 2 is not equal to1.250at scale 3 (a column has one scale, so within a column this never arises). - Decimal
Column - A column of exact decimals: Arrow
Decimal128(precision, scale). Each value is a whole number (i128) and the column has one scale, so1.25at scale 2 is stored as125. A gap is an Arrow null. - Event
Key - One key: a point in time, a time range or a labelled interval, in
epoch milliseconds, as pond-ts
Time/TimeRange/Interval. Used to look rows up (TimeSeries::bisectand friends) and given byRow::key. A plaini64converts to a point in time. (A series’ whole key column is aKey.) - Fill
Limits - Caps on which runs of gaps
TimeSeries::fillfills. A run that breaks a cap is left entirely unfilled, never partly (pond-ts{ limit, maxGap }). - Float64
Column - A column of
f64values where any non-finite value (NaN,±inf) is a gap. Reading a gap givesNone. - Interval
Keys - Interval keys: row
icovers[begin[i], end[i])and is labelledlabel[i](for buckets made by a sequence, the label is the start). - Join
- Options for
TimeSeries::joinandTimeSeries::join_many. - Json
Options - Options for
TimeSeries::from_json. - Materialize
- Options for
TimeSeries::materialize.Materialize::default()is pond-ts’s default: the last row of each bucket, the bucket’s start as the row’s time, over the series’ own time range. - Partitioned
Series - A series split into partitions by the values of one or more columns:
TimeSeries::partition_by. Operations run on each partition separately (on several cores with theparallelfeature) and return anotherPartitionedSeries;PartitionedSeries::collectputs the rows back into one series. - Pivot
- How
TimeSeries::pivot_by_grouptreats rows that share a time and a group, and which groups become columns. - Reduced
- What
TimeSeries::reducegives: one value per output, read by name with the same calls as aRow, or as aValuewithReduced::get. A name not among the outputs, or an output of another kind, is an error;Ok(None)is a gap. - Rolling
- Options for
TimeSeries::rolling_withandTimeSeries::rolling_rows.Rolling::default()is pond-ts’s default: a trailing window, every row given a value. - Rolling
Over - Options for
TimeSeries::rolling_over.RollingOver::default()is pond-ts’s default: a trailing window ending at each bucket’s start, over the series’ own time range, every bucket given a value. - Row
- One row of a series, handed to
TimeSeries::filter’s closure and yielded byTimeSeries::rows. Values are read by column name; a name the series does not have, or a column of another kind, is an error. - Sequence
- An unbounded grid of buckets. Realised over a concrete range by
Sequence::buckets_overlapping. Build one withSequence::everyorSequence::calendar; the constructors are the only way in, so everySequenceis valid (a positive step, a known zone, a week start 1–7). - Smooth
- How
TimeSeries::smoothsmooths a column, with its options. Build one withSmooth::ema,Smooth::ema_span,Smooth::moving_averageorSmooth::loess, then add options: - String
Column - A column of text values (pond-ts
kind: 'string'). A gap is an Arrow null; an empty string is a value, not a gap. - Table
- Columns of equal length without a key: what
TimeSeries::by_columnandTimeSeries::rolling_by_columngive (pond-ts gives an array of records). Read a column by name, as on a series. - Time
Range Keys - Time-range keys (pond-ts
timeRange): rowispansbegin[i]toend[i], epoch milliseconds. - Time
Series - A time series: a name, a key column and value columns of equal length.
- Value
Series - A series keyed by a number axis instead of time (pond-ts
ValueSeries): a cumulative distance, a strike price, a frequency. The axis has no gaps, every value is finite, and it never goes down (repeats are allowed). The value columns are as in aTimeSeries.
Enums§
- Align
Method - How
TimeSeries::alignfills each grid point. - Alignment
- Where a rolling window sits around each row (pond-ts
alignment). - Arrow
Key - Which fields of an Arrow table hold the key, for
TimeSeries::from_arrow. A plain&strorStringmeans a time key of that name, sofrom_arrow(name, batches, "time")reads a time-keyed table. - At
- Where
TimeSeries::as_timeplaces each row’s new time within its interval (pond-tsasTime({ at })). - Bins
- How
TimeSeries::by_columncuts a number column into bins (pond-ts{ width, origin }or{ edges, inclusive }). - Calendar
Unit - A calendar unit for
Sequence::calendar. - Column
Data - What a value column holds: numbers, exact decimals, text, true/false values, or arrays.
- Column
Kind - What a schema column holds.
- Fill
- How
TimeSeries::fillfills a run of gaps. - First
Row - Whether
TimeSeries::diff,TimeSeries::rateandTimeSeries::pct_changekeep the first row, which has no row before it (pond-ts{ drop }). - Join
Type - Which rows a
TimeSeries::joinkeeps (pond-tstype). - Keep
- What
TimeSeries::dedupedoes with rows that share a key. - Key
- The key column.
- KeyOrder
- What
TimeSeries::from_jsondoes when a row’s key sorts before the row above it, as pond-tsfromJSON({ ordering }). - Label
- One interval label: a number or text, as pond-ts allows. (A whole
column of them is
Labels.) - Labels
- Interval labels: numbers or text, one kind for the whole key, as in
pond-ts. Every row has a label (no gaps). Build them with
.into()fromVec<f64>,Vec<i64>,Vec<&str>orVec<String>. - OnConflict
- What to do when a value column name appears in more than one series
(pond-ts
onConflict). - Pick
- Which row
TimeSeries::materializetakes from a bucket with several. - Pond
Error - What went wrong. Every message names the call and, where there is one, the column, so the caller can fix the input without reading pond-rs source.
- Reducer
- A built-in reducer.
- RowFormat
- How
TimeSeries::to_jsonwrites each row, as pond-tstoJSON({ rowFormat }). - Sample
- How
TimeSeries::samplepicks rows. - Scalar
- One element of an array cell: a number, text or a true/false value, as
pond-ts’s
ScalarValue. Numbers must be finite (checked when a series is built). - Value
- One value of any column kind: a
Fill::Valueliteral, or one part of a partition key (PartitionedSeries), whereNonestands for a gap.
Constants§
- MAX_
TIME_ MS - Largest key time, in epoch milliseconds, either side of 1970: the range
of a JavaScript
Date(so of pond-ts), 100 million days. Keeping keys in it leaves room for any window or step arithmetic ini64. Checked on every series built from outside data; a derived series can go one bucket past it (aggregateof a row near the limit gives a bucket whose end is beyond it), as pond-ts’s does. - VERSION
- The version of this crate.
Functions§
- parse_
duration - Parse a duration string to whole milliseconds.
- parse_
time - A time written as text, as epoch milliseconds, read the way pond-ts’s
Time.parse(text)reads it. Same asparse_time_inwith"UTC". - parse_
time_ in - A time written as text, as epoch milliseconds, read the way pond-ts’s
Time.parse(text, { timeZone })reads it: - tzdb_
version - The version of the tz database bundled into pond-rs, e.g.
"2026e".
Type Aliases§
- Partition
Key - A partition key: one entry per partition column,
Nonefor a gap. - Result
- Shorthand for
Result<T, PondError>.