@pond-ts/react 0.64.0 → 0.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/API.md +26 -13
  2. package/CHANGELOG.md +162 -1
  3. package/package.json +2 -2
package/API.md CHANGED
@@ -432,19 +432,32 @@ Series shapes (same file): `ChartSeries`, `BandSeries`, `BoxSeries`,
432
432
 
433
433
  All are pure `(series, options) → TimeSeries` appending output columns;
434
434
  `column` defaults to `'close'`; periods are bar counts; warm-up is
435
- length-preserving (`undefined` head rows).
436
-
437
- | Study | Output column(s) | Options gist | Source |
438
- | --------------------------- | ----------------------------------- | --------------------------------------------------- | -------------------------------------------------- |
439
- | `sma` | `sma` | `{ period, column?, output? }` | `packages/financial/src/studies/moving-average.ts` |
440
- | `ema` | `ema` | `{ period, column?, output? }` (α = 2/(period+1)) | `packages/financial/src/studies/moving-average.ts` |
441
- | `bollinger` | `bbMiddle`, `bbUpper`, `bbLower` | `{ period, stdDev?, column?, prefix? }` | `packages/financial/src/studies/bollinger.ts` |
442
- | `envelope` | `envMiddle`, `envUpper`, `envLower` | `{ period, percent?, maType?, column?, prefix? }` | `packages/financial/src/studies/envelope.ts` |
443
- | `rollingStdev` | `stdev` | `{ period, column?, output? }` (population, ddof=0) | `packages/financial/src/studies/rolling-stat.ts` |
444
- | `rollingMin` / `rollingMax` | `min` / `max` | `{ period, column?, output? }` (Donchian edges) | `packages/financial/src/studies/rolling-stat.ts` |
445
- | `rollingPercentile` | `p{q}` (e.g. `p90`) | `{ period, q, column?, output? }` | `packages/financial/src/studies/rolling-stat.ts` |
446
- | `zScore` | `zscore` | `{ period, column?, output? }` | `packages/financial/src/studies/z-score.ts` |
447
- | `percentChange` | `pctChange` | `{ periods?, column?, output? }` | `packages/financial/src/studies/percent-change.ts` |
435
+ length-preserving (`undefined` head rows). A **multi-input** study (`atr`)
436
+ names each input instead of taking one `column`, each defaulting to its
437
+ `DEFAULT_OHLCV` name the same "never hard-code a column" rule applied
438
+ per input.
439
+
440
+ | Study | Output column(s) | Options gist | Source |
441
+ | --------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------- | -------------------------------------------------- |
442
+ | `sma` | `sma` | `{ period, column?, output? }` | `packages/financial/src/studies/moving-average.ts` |
443
+ | `ema` | `ema` | `{ period, column?, output? }` (α = 2/(period+1)) | `packages/financial/src/studies/moving-average.ts` |
444
+ | `bollinger` | `bbMiddle`, `bbUpper`, `bbLower` | `{ period, stdDev?, column?, prefix? }` | `packages/financial/src/studies/bollinger.ts` |
445
+ | `envelope` | `envMiddle`, `envUpper`, `envLower` | `{ period, percent?, maType?, column?, prefix? }` | `packages/financial/src/studies/envelope.ts` |
446
+ | `rollingStdev` | `stdev` | `{ period, column?, output? }` (population, ddof=0) | `packages/financial/src/studies/rolling-stat.ts` |
447
+ | `rollingMin` / `rollingMax` | `min` / `max` | `{ period, column?, output? }` (one edge; `donchian` gives the channel) | `packages/financial/src/studies/rolling-stat.ts` |
448
+ | `rollingPercentile` | `p{q}` (e.g. `p90`) | `{ period, q, column?, output? }` | `packages/financial/src/studies/rolling-stat.ts` |
449
+ | `zScore` | `zscore` | `{ period, column?, output? }` | `packages/financial/src/studies/z-score.ts` |
450
+ | `percentChange` | `pctChange` | `{ periods?, column?, output? }` (= ROC; TA-Lib-verified) | `packages/financial/src/studies/percent-change.ts` |
451
+ | `rsi` | `rsi` | `{ period?, column?, output? }` (Wilder, default 14) | `packages/financial/src/studies/rsi.ts` |
452
+ | `macd` | `macdLine`, `macdSignal`, `macdHist` | `{ fastPeriod?, slowPeriod?, signalPeriod?, column?, prefix? }` (12/26/9) | `packages/financial/src/studies/macd.ts` |
453
+ | `atr` | `atr` | `{ period?, high?, low?, close?, output? }` (Wilder, default 14) | `packages/financial/src/studies/atr.ts` |
454
+ | `momentum` | `momentum` | `{ period?, column?, output? }` (`v − v[−period]`, default 10) | `packages/financial/src/studies/momentum.ts` |
455
+ | `historicalVolatility` | `hv` | `{ period?, annualize?, column?, output? }` (σ of log returns, ×√252) | `packages/financial/src/studies/volatility.ts` |
456
+ | `stochastic` | `stochK`, `stochD` | `{ kPeriod?, slowing?, dPeriod?, high?, low?, close?, prefix? }` (14/3/3; `slowing: 1` = fast) | `packages/financial/src/studies/stochastic.ts` |
457
+ | `williamsR` | `williamsR` | `{ period?, high?, low?, close?, output? }` (default 14, bounded −100..0) | `packages/financial/src/studies/williams-r.ts` |
458
+ | `donchian` | `dcUpper`, `dcLower`, `dcMiddle` | `{ period?, high?, low?, prefix? }` (default 20) | `packages/financial/src/studies/donchian.ts` |
459
+ | `obv` | `obv` | `{ close?, volume?, output? }` (no period; TA-Lib seed `volume[0]`) | `packages/financial/src/studies/obv.ts` |
460
+ | `vwap` | `vwap` | `{ period, high?, low?, close?, volume?, output? }` (rolling, typical px) | `packages/financial/src/studies/vwap.ts` |
448
461
 
449
462
  Adding a study? Follow `packages/financial/src/studies/README.md` (uniform
450
463
  shape + pandas oracle case + fluent method are all REQUIRED).
package/CHANGELOG.md CHANGED
@@ -8,7 +8,8 @@ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`,
8
8
  under a single `v*` tag, so this file covers them all. Pre-1.0: minor bumps may
9
9
  include new features and type-level changes; patch bumps are strictly additive.
10
10
 
11
- [Unreleased]: https://github.com/pond-ts/pond/compare/v0.64.0...HEAD
11
+ [Unreleased]: https://github.com/pond-ts/pond/compare/v0.65.0...HEAD
12
+ [0.65.0]: https://github.com/pond-ts/pond/compare/v0.64.0...v0.65.0
12
13
  [0.64.0]: https://github.com/pond-ts/pond/compare/v0.63.0...v0.64.0
13
14
  [0.63.0]: https://github.com/pond-ts/pond/compare/v0.62.0...v0.63.0
14
15
  [0.62.0]: https://github.com/pond-ts/pond/compare/v0.61.0...v0.62.0
@@ -67,6 +68,166 @@ include new features and type-level changes; patch bumps are strictly additive.
67
68
 
68
69
  ## [Unreleased]
69
70
 
71
+ ## [0.65.0] — 2026-09-06
72
+
73
+ ### Added
74
+
75
+ - `@pond-ts/financial`: **`stochastic(...)`**, **`williamsR(...)`** and
76
+ **`donchian(...)`** — the three range-position studies, built on one new
77
+ kernel (`highestLowestValues`: the trailing highest high / lowest low in a
78
+ single `rolling` scan) plus fluent `.stochastic()` / `.williamsR()` /
79
+ `.donchian()`. All name their bar inputs per column (`high` / `low` /
80
+ `close`, defaulting to `DEFAULT_OHLCV`), the `atr` shape.
81
+
82
+ **`stochastic`** — `{ kPeriod = 14, slowing = 3, dPeriod = 3, prefix =
83
+ 'stoch' }` appending `stochK` / `stochD`: the slow stochastic, with
84
+ `slowing: 1` giving the fast one (TA-Lib's `STOCHF`) from the same
85
+ function. Values match TA-Lib's `STOCH`/`STOCHF` to `5.7e-14` on every bar
86
+ TA-Lib emits, `%D` with an identical warm-up. Two deliberate deltas,
87
+ both documented on the study: **`%K` starts when it can** (bar 15 at the
88
+ defaults) where TA-Lib masks it back to `%D`'s first bar (17), discarding
89
+ `dPeriod − 1` real values — the `macd` precedent; and **a flat window
90
+ (`HH === LL`) is `undefined`** where TA-Lib reports `0`, which is also its
91
+ value for "close at the very bottom of a real range" — the `rsi` precedent.
92
+ The smoothing uses a new raw-array kernel, `rollingMeanValues`, that waits
93
+ for `slowing` _values_ rather than rows: a scratch-column SMA would have
94
+ put a "3-bar" `%K` on bar 13 with one value in it.
95
+
96
+ **`williamsR`** — `{ period = 14, output = 'williamsR' }`, bounded
97
+ `−100..0`. Matches TA-Lib's `WILLR` bar-for-bar to `1.4e-14` with an
98
+ identical warm-up; the only delta is the same flat-window `undefined`.
99
+ It is fast `%K − 100`, computed on the same kernel, and a test pins that
100
+ identity against `stochastic({ slowing: 1 })`.
101
+
102
+ **`donchian`** — `{ period = 20, prefix = 'dc' }` appending `dcUpper` /
103
+ `dcLower` / `dcMiddle`. `upper` is exactly `rollingMax(high)` and `lower`
104
+ exactly `rollingMin(low)` (pinned), in one scan plus the midpoint; pandas
105
+ oracle only (TA-Lib has no Donchian). Scales linearly with price, like
106
+ `atr`; a missing `high`/`low` is skipped (core's reducer policy), not
107
+ propagated.
108
+
109
+ - `@pond-ts/financial`: **`obv(...)`** and **`vwap(...)`** — the first two
110
+ **volume** studies, plus fluent `.obv()` / `.vwap()`. Both name their inputs
111
+ per column (`close` / `volume`, and `high` / `low` for VWAP), each
112
+ defaulting to its `DEFAULT_OHLCV` name, the rule `atr` established.
113
+
114
+ **`obv`** is Granville's On-Balance Volume — the running total of each bar's
115
+ volume signed by its close change — and is the first study with **no
116
+ `period`**: it is read for its shape, not its level, so there is nothing to
117
+ size a window over. It is **TA-Lib's OBV exactly**, including the seed
118
+ (`OBV[0] = volume[0]`) and an unchanged close adding nothing; the oracle
119
+ asserts equality, with an identical (empty) warm-up mask. On **interior
120
+ gaps** it deliberately departs from TA-Lib: a running sum has no local
121
+ answer for a missing term, so a missing close or volume leaves the level
122
+ `undefined` from that bar on (the same asymmetry the Wilder recursion has —
123
+ a leading gap shifts the seed, an interior one propagates). TA-Lib has no
124
+ gap semantics; measured on a seven-bar series it is 400 out of level at
125
+ the gap bar and 100 out from then on, presented as a value.
126
+
127
+ **`vwap`** is a **rolling** Volume-Weighted Average Price:
128
+ `Σ typicalPrice · volume / Σ volume` over `period` bars, typical price
129
+ `(high + low + close) / 3`. There is no TA-Lib VWAP and no single
130
+ definition, so this is a documented design choice: the rolling form fits
131
+ the package's count-window shape, and the intraday desk's _anchored_ VWAP
132
+ is not a special case of it (a count window emits only once it spans
133
+ `period` rows) — that one needs a session reset and is deferred to the
134
+ session-anchored phase. `period` is **required** (no conventional length to
135
+ default to). A window with no volume is `undefined`; a bar missing any
136
+ input is dropped from **both** sums so the ratio is never biased. Verified
137
+ against a pandas replication on a fixture whose volume spikes make a
138
+ dropped weighting visible (≥ 1.24 price units from the plain mean).
139
+
140
+ The oracle fixture gains a `volumes` input, and four kernels land under
141
+ them for the studies that follow: `cumulativeValues` (A/D line, PVT),
142
+ `signedVolumeValues`, `typicalPriceValues` (CCI, MFI, Keltner) and
143
+ `rollingWeightedMeanValues` (VWMA).
144
+
145
+ - `@pond-ts/financial`: **`atr(...)`** — Wilder's Average True Range, the third
146
+ [PND-STUDY] named indicator, plus a fluent `.atr()`. `{ period = 14,
147
+ high = 'high', low = 'low', close = 'close', output = 'atr' }`. Verified
148
+ against **TA-Lib** bar-for-bar in the oracle — exact agreement at both
149
+ periods tested, with identical warm-up.
150
+
151
+ **The first multi-input study.** Where every study so far took a single
152
+ `column`, ATR names each of its three inputs, each defaulting to its
153
+ `DEFAULT_OHLCV` name — the same "never hard-code a column" rule applied per
154
+ input rather than a new mechanism. Because the inputs are columns of one
155
+ series they are aligned by construction, so the mismatched-length error
156
+ array-based libraries must check for at every call is unrepresentable here.
157
+
158
+ Warm-up is `period` rows, not `period − 1`: true range needs a previous
159
+ close, so bar 0 has none.
160
+
161
+ - `@pond-ts/financial`: **`macd(...)`** — Moving Average Convergence/Divergence,
162
+ the second [PND-STUDY] named indicator. `{ fastPeriod = 12, slowPeriod = 26,
163
+ signalPeriod = 9, column = 'close', prefix = 'macd' }`, appending
164
+ `macdLine` / `macdSignal` / `macdHist`, plus a fluent `.macd()`.
165
+
166
+ Two deliberate deltas from TA-Lib, both documented on the study:
167
+ **each column warms up when it can** (the line at bar 25, signal and
168
+ histogram at 33) where TA-Lib masks all three back to 33, keeping eight real
169
+ values it discards; and the EMAs are **pond's own** (`α = 2/(span+1)` seeded
170
+ on the first sample — what `ema()` already ships) rather than TA-Lib's
171
+ SMA-seeded ones. The second is a _seed_ difference, so it decays: measured
172
+ at 3.8% of the line's magnitude at the first shared bar, 0.089% by bar 79.
173
+ Seeding TA-Lib's way would make `macd()` disagree with
174
+ `ema(fast) − ema(slow)` inside this package, which is the worse surprise —
175
+ and is pinned by a test.
176
+
177
+ - `@pond-ts/financial`: **`rsi(...)`** — Wilder's Relative Strength Index, the
178
+ first of the [PND-STUDY] named indicators. `{ period = 14, column = 'close',
179
+ output = 'rsi' }`, length-preserving warm-up (`period` rows, not `period − 1`
180
+ — RSI averages _differences_, so it needs one extra bar), and a fluent
181
+ `.rsi()`. Verified against **TA-Lib** bar-for-bar in the oracle fixture,
182
+ agreeing to `1.4e-14`.
183
+
184
+ Worth knowing which RSI this is: the gain/loss averages are seeded on the
185
+ arithmetic mean of the first `period` differences, then carried by Wilder's
186
+ recursion — TA-Lib's definition and Wilder's original. Implementations that
187
+ instead smooth with a plain first-sample-seeded EMA of the same `α = 1/period`
188
+ produce a **materially different** series: on this package's own oracle input
189
+ the two sit up to **7.03 RSI points** apart, and are still `0.15` apart 65
190
+ bars later. That is enough to move a reading across the conventional 70/30
191
+ thresholds, so it is a definition choice rather than a rounding detail.
192
+
193
+ - `@pond-ts/financial`: **`momentum(...)`** — the absolute `period`-bar
194
+ difference, `value − value[i − period]`. `{ period = 10, column = 'close',
195
+ output = 'momentum' }`, warm-up `period` rows, plus a fluent `.momentum()`.
196
+ Verified against **TA-Lib's `MOM`** bar-for-bar in the oracle: exact
197
+ agreement with identical warm-up masks. The additive companion to
198
+ `percentChange`'s ratio; it is in the price's units and scales with them
199
+ (pinned by a property test).
200
+
201
+ - `@pond-ts/financial`: **`historicalVolatility(...)`** — the standard
202
+ deviation of log returns over `period` bars, annualised.
203
+ `{ period = 20, annualize = 252, column = 'close', output = 'hv' }`, plus a
204
+ fluent `.historicalVolatility()`. Warm-up is `period` rows, not
205
+ `period − 1` — `period` returns need `period + 1` prices, the same
206
+ off-by-one RSI and ATR have.
207
+
208
+ TA-Lib has no HV to arbitrate the conventions, so they are pinned on the
209
+ study and in the pandas oracle (`np.log(s).diff().rolling(n).std(ddof=0)
210
+ · √annualize`): **population σ** (`ddof = 0`, the package convention shared
211
+ with Bollinger / `rollingStdev` / `zScore`; a sample σ is
212
+ `√(n/(n−1))` larger — 2.6% at 20); **log returns**, not simple; annualised
213
+ by `√annualize` with `252` the default for daily bars and an **option**
214
+ rather than a hidden constant (`1` for per-bar σ, `252·390` for one-minute
215
+ bars); and a **decimal** (`0.18` = 18%), the form volatility is consumed
216
+ in. Scale-invariant, pinned by a property test. A non-positive price has
217
+ no log return: both returns touching it are missing (explicitly, since
218
+ `ln(−4/−5)` is a finite non-answer). A leading gap shifts the start so the
219
+ first window still covers `period` real returns, matching pandas'
220
+ `min_periods` and the Wilder-family studies — the σ is computed by calling
221
+ the range-exact kernel on the returns array directly, because a scratch
222
+ column through `rollingValues` counts rows and would emit one bar early
223
+ over one return too few.
224
+
225
+ - `@pond-ts/financial`: **`percentChange` is ROC** — TA-Lib's `ROC` is the same
226
+ `(price / prevPrice − 1) × 100`, and the oracle now cross-checks the study
227
+ against it: exact agreement (`0.0`) with identical warm-up masks at both
228
+ periods. Documented on the study and in API.md; there is deliberately no
229
+ separate `roc` study.
230
+
70
231
  ## [0.64.0] — 2026-08-28
71
232
 
72
233
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.64.0",
3
+ "version": "0.65.0",
4
4
  "description": "React hooks for pond-ts live time series",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "test:runtime": "vitest run"
35
35
  },
36
36
  "peerDependencies": {
37
- "pond-ts": "^0.64.0",
37
+ "pond-ts": "^0.65.0",
38
38
  "react": "^18.0.0 || ^19.0.0"
39
39
  },
40
40
  "devDependencies": {