@pond-ts/react 0.65.0 → 0.67.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.
- package/API.md +211 -34
- package/CHANGELOG.md +1111 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,9 @@ 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.
|
|
11
|
+
[Unreleased]: https://github.com/pond-ts/pond/compare/v0.67.0...HEAD
|
|
12
|
+
[0.67.0]: https://github.com/pond-ts/pond/compare/v0.66.0...v0.67.0
|
|
13
|
+
[0.66.0]: https://github.com/pond-ts/pond/compare/v0.65.0...v0.66.0
|
|
12
14
|
[0.65.0]: https://github.com/pond-ts/pond/compare/v0.64.0...v0.65.0
|
|
13
15
|
[0.64.0]: https://github.com/pond-ts/pond/compare/v0.63.0...v0.64.0
|
|
14
16
|
[0.63.0]: https://github.com/pond-ts/pond/compare/v0.62.0...v0.63.0
|
|
@@ -68,6 +70,1114 @@ include new features and type-level changes; patch bumps are strictly additive.
|
|
|
68
70
|
|
|
69
71
|
## [Unreleased]
|
|
70
72
|
|
|
73
|
+
## [0.67.0] — 2026-09-11
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- `@pond-ts/financial`: **a runtime study catalog** — `import { STUDIES } from
|
|
78
|
+
'@pond-ts/financial/catalog'`. One `StudyDescriptor` per study (all 109
|
|
79
|
+
fluent methods): the columns it reads (`inputs`, with defaults — absent
|
|
80
|
+
means required, as a `benchmark` is), its numeric and menu options
|
|
81
|
+
(`params`: `kind`, `default` or an `example` for a required one,
|
|
82
|
+
`min`/`max` — inclusive, and only where the study validates a constant
|
|
83
|
+
bound — and a `suggest` range a control is drawn on, legal throughout at
|
|
84
|
+
the other options' defaults), how it names what it appends (`naming`: `output` or `prefix`
|
|
85
|
+
and the default), the columns it appends (`outputs`, each with a `unit`
|
|
86
|
+
from a closed vocabulary — `inherit` / `delta` / `percent` / `ratio` /
|
|
87
|
+
`signal` / `volume` / `index` / `bars` — that answers whether the column
|
|
88
|
+
may share the source's axis), a `family` and one-line `summary` for a
|
|
89
|
+
picker, `anchor: 'session' | 'time'` for the two session-anchored studies
|
|
90
|
+
and `anchoredVwap` (an input the consumer supplies from context, not a
|
|
91
|
+
control), `optional: true` for an option whose absence is a switch rather
|
|
92
|
+
than a value (`balanceOfPower`'s `period`) with `requires` for a menu
|
|
93
|
+
that is only legal alongside it, and `run`. The shape is modelled on `@pond-ts/process`'s `OpDef` (`role`, `id`,
|
|
94
|
+
`unit`, `suggest` are its words) so a registry maps it rather than
|
|
95
|
+
interprets it — with two deliberate differences: an input carries its
|
|
96
|
+
`default`, and a required option carries an `example` where process
|
|
97
|
+
requires a `default`. Asked for by a consumer that was otherwise
|
|
98
|
+
hand-transcribing ~400 facts from `.d.ts` files and re-checking them per
|
|
99
|
+
release. Guarded two ways so it cannot drift from the studies:
|
|
100
|
+
`defineStudy<Options>()` classifies every key of the options interface at
|
|
101
|
+
compile time and rejects a missed or misspelt key, a default claimed on a
|
|
102
|
+
required option, a menu value outside the union, a menu `of` that omits a
|
|
103
|
+
member, or an option shape it does not know; and `test/catalog.test.ts` runs every descriptor against its
|
|
104
|
+
study — the appended columns are exactly those declared and every one
|
|
105
|
+
has a value on the fixture, stating every default explicitly changes
|
|
106
|
+
nothing, every menu value and both `suggest` endpoints run, a declared
|
|
107
|
+
`min`/`max` is accepted and one past it throws, and the catalog is exactly
|
|
108
|
+
the set of fluent methods. A separate subpath, so the main entry's tree-shaking is untouched.
|
|
109
|
+
The `API map` workflow now guards `catalog/index.ts` too.
|
|
110
|
+
- `@pond-ts/financial`: **the session-anchored studies** (corpus §6.6 / §6.9 —
|
|
111
|
+
the **G4** pair the trading calendar was gating). Both take the session as a
|
|
112
|
+
first-class input through one shared option shape, `SessionAnchorOptions`:
|
|
113
|
+
exactly one of **`sessions`** (a `TradingCalendar` or a `Session[]` — the
|
|
114
|
+
primary door — a calendar is narrowed with `sessionsInRange`, an explicit
|
|
115
|
+
list validated per call — and walked once, `O(N +
|
|
116
|
+
sessions)`) or **`session`** (the name of a session-id column, what
|
|
117
|
+
`TradingCalendar.tagSessions` appends — the door for a series already
|
|
118
|
+
partitioned by session), plus `stamped: 'open' | 'close'` on the calendar
|
|
119
|
+
door. A bar in **closed time** — between sessions, a weekend print on a 24/7
|
|
120
|
+
feed, outside the schedule — reads `undefined` in both studies. Both doors
|
|
121
|
+
run the **same** `sessionIdValues` walk `tagSessions` now runs, so they are
|
|
122
|
+
the same anchoring by construction, and a test pins the two routes equal
|
|
123
|
+
under both stamp conventions.
|
|
124
|
+
- **`sessionVwap({ sessions | session, stamped, high, low, close, volume,
|
|
125
|
+
output = 'svwap' })`** — the VWAP an intraday desk means: `Σ tp·vol / Σ vol`
|
|
126
|
+
accumulated from each session's open and **reset at the next**. This is the
|
|
127
|
+
third VWAP form `vwap` named and deliberately left open. It composes on
|
|
128
|
+
`anchoredVwap`'s arithmetic literally rather than by resemblance — both
|
|
129
|
+
studies now call one `anchoredVwapValues(typical, volume, anchors)` kernel
|
|
130
|
+
and differ only in what they pass as the anchor group. An interior gap ends
|
|
131
|
+
**that session's** line (`obv`'s rule; the two sums are blanked together so
|
|
132
|
+
a bar with volume but a missing `high` cannot bias the average) and the
|
|
133
|
+
next session open re-seeds — the reset is the recovery `anchoredVwap` makes
|
|
134
|
+
the caller do by hand. `Σ vol = 0` → `undefined`, live at the output.
|
|
135
|
+
- **`pivotPoints({ sessions | session, stamped, method = 'standard', high,
|
|
136
|
+
low, close, prefix = 'pp' })`** — each session's support/resistance ladder
|
|
137
|
+
from the **previous session's** aggregate high / low / close, held flat
|
|
138
|
+
across the session. Four formula sets, all reading the same three inputs
|
|
139
|
+
and differing in constants: `'standard'` (floor-trader), `'fibonacci'`
|
|
140
|
+
(0.382 / 0.618 / 1.000 of the range), `'woodie'` (the standard ladder over
|
|
141
|
+
the close-weighted centre `(H + L + 2C)/4`) and `'camarilla'` (Nick Scott's
|
|
142
|
+
1.1/12, 1.1/6, 1.1/4, 1.1/2, measured from the **close**, not the pivot).
|
|
143
|
+
**The column set follows `method`**: seven columns (`${prefix}Pivot`,
|
|
144
|
+
`R1–R3`, `S1–S3`) for the first three and **nine** for Camarilla, which is
|
|
145
|
+
the only set defining a fourth pair — the return type is conditional on
|
|
146
|
+
`method` rather than shipping three methods with two permanently-`undefined`
|
|
147
|
+
columns. The first session with bars and every closed-time bar read
|
|
148
|
+
`undefined`; "previous session" means the previous session **with bars in
|
|
149
|
+
this series**, not the previous entry on the calendar.
|
|
150
|
+
- Two deliberate definition deltas, both documented on the study: Woodie's
|
|
151
|
+
ships the previous-**close** centre `(H + L + 2C)/4` rather than the
|
|
152
|
+
current-open variant also in circulation, and Camarilla's levels are
|
|
153
|
+
centred on the close rather than on the pivot (which is the definition, and
|
|
154
|
+
is what makes its ladder asymmetric about `ppPivot`).
|
|
155
|
+
- Oracle: five new cases on a new **session-keyed** input
|
|
156
|
+
(`input.sessionTimes`) — the same 80 OHLCV bars re-keyed onto a real
|
|
157
|
+
09:30–16:00 America/New_York 30-minute grid over six sessions, with two
|
|
158
|
+
bars in no session. The references are pandas `groupby`-`cumsum` and
|
|
159
|
+
`groupby().agg().shift(1).reindex()`, a different formulation from our
|
|
160
|
+
sequential loops; the vitest side rebuilds the calendar from the same rules
|
|
161
|
+
rather than from a table, so a Temporal/`zoneinfo` disagreement about a
|
|
162
|
+
session boundary fails the case rather than hiding.
|
|
163
|
+
- `@pond-ts/financial`: **Ichimoku Cloud and ZigZag** (corpus §6.4) — the two
|
|
164
|
+
most-used studies left in the corpus, each shipped in the form that needs no
|
|
165
|
+
core change. Both take the uniform shape (bar columns plus a `prefix`,
|
|
166
|
+
bar-count periods, a length-preserving per-column warm-up, a fluent method)
|
|
167
|
+
and both have pandas oracle cases. One internal kernel helper rides with
|
|
168
|
+
them: **`rollingBarExtremesValues`** (the max of one array beside the min of
|
|
169
|
+
another over a strict window, in one deque walk), which the perf check
|
|
170
|
+
surfaced — Ichimoku on the single-array door spent 414 ms of its ~404 ms at
|
|
171
|
+
1M bars in six deque passes where three suffice; on the package bench the
|
|
172
|
+
study went 433.6 → 280.7 ms at 1M.
|
|
173
|
+
- **`ichimoku({ conversionPeriod = 9, basePeriod = 26, spanBPeriod = 52,
|
|
174
|
+
displacement = 26, high, low, close, prefix = 'ichi' })`** — Hosoda's five
|
|
175
|
+
lines as `ichiTenkan` / `ichiKijun` / `ichiSenkouA` / `ichiSenkouB` /
|
|
176
|
+
`ichiChikou`, each the midpoint of the highest `high` and lowest `low` of
|
|
177
|
+
its own window (the Chikou span is the close). Per-column warm-up 8 / 25 /
|
|
178
|
+
25 / 51 / 0. **`displacement` changes no value**: the study keys every
|
|
179
|
+
column to the bar it is _computed from_ and shifts nothing — the forward
|
|
180
|
+
spans have no rows past the last bar to land on (assessment gap **G5**),
|
|
181
|
+
and a pre-shifted Chikou would be a look-ahead column, the one thing no
|
|
182
|
+
other column in the package is. The new **`ichimokuOffsets(options)`**
|
|
183
|
+
returns the per-column x-offset in bars (`+displacement` on the two spans,
|
|
184
|
+
`−displacement` on Chikou, `0` on the rest) for a chart to apply — the
|
|
185
|
+
data-side half of the charts ask **C2**. Measured against the common slip
|
|
186
|
+
(taking the ranges over the close): up to 0.2281 / 0.2383 / 0.2226 /
|
|
187
|
+
0.2328 on the oracle's deliberately narrow bars.
|
|
188
|
+
- **`zigZag({ deviation = 5, high, low, prefix = 'zz' })`** — the price path
|
|
189
|
+
reduced to its swings: `zzPivot` (the pivot price on its own bar),
|
|
190
|
+
`zzDirection` (`+1` rising / `−1` falling leg) and `zzLine` (the straight
|
|
191
|
+
line between consecutive pivots). A leg turns when price retraces
|
|
192
|
+
`deviation` **percent** from the leg's running extreme — measured against
|
|
193
|
+
the peak on a fall, the trough on a rise. **Every column repaints**
|
|
194
|
+
(assessment gap **G6**): a pivot is written at the bar its extreme
|
|
195
|
+
occurred but is not known until a later bar confirms it, so none of the
|
|
196
|
+
three may be fed to a backtest unlagged. The **last leg is provisional**
|
|
197
|
+
and therefore has no pivot and no line — only a direction. A gap resets
|
|
198
|
+
the machine _and_ discards the leg in force, and no line is drawn across
|
|
199
|
+
it. The close-based fork needs no option (`{ high: 'close', low: 'close' }`)
|
|
200
|
+
and ships as its own oracle case; the absolute-deviation fork is measured
|
|
201
|
+
at a whole extra pivot on the fixture.
|
|
202
|
+
|
|
203
|
+
### Changed
|
|
204
|
+
|
|
205
|
+
- `@pond-ts/financial`: **`bollinger` draws the degenerate band on a flat
|
|
206
|
+
window** — `upper = lower = middle` where `σ = 0`, instead of blanking
|
|
207
|
+
both bands around an unbroken middle line. `undefined` now means warm-up
|
|
208
|
+
only, as it does for `keltner`'s zero-range channel. A consumer's
|
|
209
|
+
"outside the band" test is `bbUpper > bbLower`, not a hole in the data.
|
|
210
|
+
`bollingerBandwidth` is still `0` there (its numerator is forced to zero)
|
|
211
|
+
and `bollingerPercentB` still `undefined` (a genuine 0/0), and `bbWidth`
|
|
212
|
+
is now recoverable from the `bollinger` columns on every bar the bands
|
|
213
|
+
are set. Asked for by a consumer whose band over a stale stretch broke
|
|
214
|
+
into segments ([PND-BBFLAT]).
|
|
215
|
+
- `@pond-ts/financial`: **`TradingCalendar.tagSessions` is ~4.7× faster** and
|
|
216
|
+
its output is unchanged on every row. It was materializing `series.toArray()`
|
|
217
|
+
and reading `event.begin()` — one `Event` plus one data object per row, the
|
|
218
|
+
cost PR #536 removed from the study kernel — where it now reads
|
|
219
|
+
`keyColumn().begin` columnar through the shared `sessionIdValues` walk the
|
|
220
|
+
session-anchored studies use. Measured at 1M bars: **120.90 ms → 25.69 ms**.
|
|
221
|
+
The session column it appends is now a `Float64Array` rather than an
|
|
222
|
+
`Array<number | undefined>`; `withColumn` maps `NaN` to missing, so readers
|
|
223
|
+
still see `number | undefined` and the declared `TaggedSchema` is unchanged.
|
|
224
|
+
- `@pond-ts/financial`: `anchoredVwap` now runs the shared
|
|
225
|
+
`anchoredVwapValues` kernel rather than composing two `cumulativeValues`
|
|
226
|
+
passes over two blanked arrays. Bit-identical output; the kernel arithmetic
|
|
227
|
+
measures **23.51 ms → 16.62 ms** at 1M rows.
|
|
228
|
+
|
|
229
|
+
## [0.66.0] — 2026-09-07
|
|
230
|
+
|
|
231
|
+
### Added
|
|
232
|
+
|
|
233
|
+
### Changed
|
|
234
|
+
|
|
235
|
+
- `@pond-ts/financial`: **the volume and miscellaneous leftovers** (corpus
|
|
236
|
+
§6.6 / §6.4 / §6.1) — six studies in the uniform shape (bar columns plus an
|
|
237
|
+
`output` or `prefix`, bar-count periods, a length-preserving per-column
|
|
238
|
+
warm-up, a fluent method), each with oracle cases. Two internal kernel
|
|
239
|
+
helpers ride with them: `trueRangeBoundsValues` (the true high / true low
|
|
240
|
+
the true range is the width of) and `moneyFlowVolumeValues` (the `clv ·
|
|
241
|
+
volume` term the A/D line already accumulated, named so Twiggs can share
|
|
242
|
+
it).
|
|
243
|
+
- **`twiggsMoneyFlow({ period = 21, high, low, close, volume, output =
|
|
244
|
+
'tmf' })`** — Colin Twiggs' correction to Chaikin Money Flow: the close's
|
|
245
|
+
location in the bar's **true** range (so a gap bar is scored against the
|
|
246
|
+
ground it covered), Wilder-smoothed against a matching smoothing of
|
|
247
|
+
volume rather than summed over a flat window. Bounded −1…1. **F-AMBIG**
|
|
248
|
+
on the smoothing: Wilder's exponential form ships (Incredible Charts /
|
|
249
|
+
Twiggs' own published algorithm) and the window-sum fork is measured at
|
|
250
|
+
**0.0706** away at `period 21`, where TMF itself spans −0.0279…0.1493;
|
|
251
|
+
`chaikinMoneyFlow(21)` sits **0.1494** away — wider than the whole
|
|
252
|
+
reading. Warm-up is `period` (bar 0 has no true range), and an interior
|
|
253
|
+
gap in any input **ends** the reading, where CMF recovers a window later.
|
|
254
|
+
- **`tradeVolumeIndex({ minTick, column = 'close', volume, output = 'tvi'
|
|
255
|
+
})`** — tick-direction volume accumulation: `+1` above the dead band, `−1`
|
|
256
|
+
below it, and **the previous direction** on an undecided bar, which is
|
|
257
|
+
what the exchange tick rule does and what distinguishes it from `obv`
|
|
258
|
+
(which adds nothing for an unchanged close). **`minTick` is required**
|
|
259
|
+
and has no default — a fact about the instrument, like `swingIndex`'s
|
|
260
|
+
`limit`; `0`, negative and non-finite are rejected. The level starts at
|
|
261
|
+
`0`, no first direction is invented (the up-seeded vendor fork is pinned
|
|
262
|
+
by a unit test — the oracle input's first bar carries its largest move,
|
|
263
|
+
so the fork is invisible there, and the generator asserts that), and an
|
|
264
|
+
interior gap **ends** the index the way `obv` does rather than re-seeding
|
|
265
|
+
the way `negativeVolumeIndex` does.
|
|
266
|
+
- **`shinoharaIntensityRatio({ period = 26, open, high, low, close, prefix =
|
|
267
|
+
'sir' })`** → `sirStrong`, `sirWeak` — Shinohara's A and B ratios,
|
|
268
|
+
`100·Σup/Σdown` over the window: A measured against each bar's **own
|
|
269
|
+
open**, B against the **previous close**. **F-AMBIG** on the A/B naming:
|
|
270
|
+
the arithmetic is the standard pair and the `strong` / `weak` labels come
|
|
271
|
+
from the corpus' own list; the alternative convention charts them the
|
|
272
|
+
other way round, and the two lines sit **23,312.47** apart on the oracle
|
|
273
|
+
input, so the labels carry information. Per-column warm-up (25 and 26 at
|
|
274
|
+
the default). Neither ratio is bounded, and **B inverts on a gappy tape**
|
|
275
|
+
— `prevClose − low` is negative on a bar that gapped up, which is the
|
|
276
|
+
definition, not a defect (documented, with the oracle input's own
|
|
277
|
+
−22,761…7,620 range as the worked case).
|
|
278
|
+
- **`elderImpulse({ emaPeriod = 13, fastPeriod = 12, slowPeriod = 26,
|
|
279
|
+
signalPeriod = 9, column = 'close', output = 'impulse' })`** — Elder's
|
|
280
|
+
Impulse System: `+1` when the 13-bar EMA **and** the MACD histogram both
|
|
281
|
+
rise, `−1` when both fall, `0` otherwise. The column is **numeric**, not
|
|
282
|
+
the categorical colour the corpus describes — `TimeSeries.withColumn` has
|
|
283
|
+
no string door, so a study cannot append one; `+1 / 0 / −1` is signed, so
|
|
284
|
+
a chart maps it to Elder's green/blue/red in one expression. It calls
|
|
285
|
+
`macd()` under a scratch prefix rather than re-deriving the histogram, so
|
|
286
|
+
it is by construction the same MACD a caller charts beside it. Ties are
|
|
287
|
+
`0` (strict comparison on both sides), so it carries no state and needs
|
|
288
|
+
no gap rule of its own; warm-up 34 at the defaults.
|
|
289
|
+
- **`movingAverageCross({ fastPeriod = 10, slowPeriod = 30, maType = 'sma',
|
|
290
|
+
column = 'close', output = 'maCross' })`** — a **signal** column: `+1` on
|
|
291
|
+
the bar the fast average crosses above the slow one, `−1` below, `0`
|
|
292
|
+
otherwise. The averages are not emitted (that is `movingAverage`'s job);
|
|
293
|
+
what this adds is the event, which needs memory of which side the pair
|
|
294
|
+
was last on — a K6 `foldRows` machine. **The tie rule is the design**: an
|
|
295
|
+
exact tie is not a cross, a touch-and-retreat is not a cross (the machine
|
|
296
|
+
carries the last **non-zero** sign), and a crossing _through_ a tie fires
|
|
297
|
+
on the far-side bar. `maType`, not `type`, because the column is a signal
|
|
298
|
+
rather than an average. The first bar on which both averages exist
|
|
299
|
+
reports `undefined`, not `0` — it is the seed. **What a gap costs depends
|
|
300
|
+
entirely on `maType`**, and the docstring carries the measured table: at
|
|
301
|
+
the default `sma` a hole costs _nothing_ (the K2 column door counts rows),
|
|
302
|
+
at `ema` two bars, at `wma`/`hull` a window, and at `smma`/`kama` the rest
|
|
303
|
+
of the series.
|
|
304
|
+
- **`anchoredVwap({ anchor, high, low, close, volume, output = 'avwap' })`**
|
|
305
|
+
— the cumulative `Σ typicalPrice·volume / Σ volume` from a chosen bar
|
|
306
|
+
onwards: the VWAP of the execution desk, and the half of VWAP that
|
|
307
|
+
`vwap()`'s docstring named and left open. **`anchor` is required** and is
|
|
308
|
+
a `Date` or epoch **milliseconds**, not a row index — the line starts at
|
|
309
|
+
the first bar at or after it, so an anchor between bars snaps forward, one
|
|
310
|
+
before the series covers all of it, and one after the last bar leaves the
|
|
311
|
+
column empty (not an error). The **session-reset** form still waits on
|
|
312
|
+
[PND-TCAL]; what makes this one shippable today is that its anchor is a
|
|
313
|
+
user parameter, so no calendar is consulted. An interior gap **ends** the
|
|
314
|
+
line (`obv`'s cumulative rule), and the volume sum is blanked wherever
|
|
315
|
+
the price sum is so the two consume the same bars.
|
|
316
|
+
|
|
317
|
+
- `@pond-ts/financial`: **the momentum and trend leftovers** (corpus §6.3 /
|
|
318
|
+
§6.4 / §6.1) — ten studies in the uniform shape (a `column` or the bar
|
|
319
|
+
columns plus an `output` or `prefix`, bar-count periods, a length-preserving
|
|
320
|
+
per-column warm-up, a fluent method), each with oracle cases. Two internal
|
|
321
|
+
kernels ride with them: `swingIndexValues` (shared by the swing pair) and
|
|
322
|
+
`randomWalkValues` (the multi-horizon sweep the corpus flags as **G2**).
|
|
323
|
+
- **`stochasticMomentumIndex({ period = 13, longPeriod = 25, shortPeriod =
|
|
324
|
+
2, signalPeriod = 3, high, low, close, prefix = 'smi' })`** → `smi`,
|
|
325
|
+
`smiSignal` — William Blau's SMI: the close against the **midpoint** of
|
|
326
|
+
the `period`-bar range, with the distance and the half-range each
|
|
327
|
+
double-EMA smoothed before the division, ×100. Bounded −100…100 and
|
|
328
|
+
signed, where the classic stochastic reads 50 at the midpoint. Option
|
|
329
|
+
names are `trueStrengthIndex`'s (Blau's other double-smoothed
|
|
330
|
+
study, same author, same `r`/`s`). Blau's `(13, 25, 2)` ship; the short
|
|
331
|
+
`(5, 3, 3)` fork sits **108.99** away and the unsmoothed reading
|
|
332
|
+
**126.95**, on a line spanning −48.19…74.33 (measured). A flat range for
|
|
333
|
+
the whole smoothed history reads `undefined` — the guard is live via a
|
|
334
|
+
redirected `close`.
|
|
335
|
+
- **`fisherTransform({ period = 10, high, low, prefix = 'fisher' })`** →
|
|
336
|
+
`fisher`, `fisherSignal` — John Ehlers' Fisher Transform (TASC Nov 2002),
|
|
337
|
+
a `foldRows` state machine. The range is over the **median price's** own
|
|
338
|
+
extremes, not over the bars' highs and lows: that port sits **5.95** away
|
|
339
|
+
on a line spanning −4.69…7.60 (measured). `0.33/0.67`, the `±0.99` clamp
|
|
340
|
+
test with its `±0.999` replacement, and the `0.5/0.5` second smoothing are
|
|
341
|
+
Ehlers' constants, not options. The signal is the line **delayed one
|
|
342
|
+
bar**. A gap — or a flat window — resets the machine ([PND-SFOLD]).
|
|
343
|
+
- **`schaffTrendCycle({ fastPeriod = 23, slowPeriod = 50, cyclePeriod = 10,
|
|
344
|
+
column, output = 'stc' })`** — Doug Schaff's STC: a stochastic of the MACD,
|
|
345
|
+
`0.5`-smoothed, then a stochastic of that, `0.5`-smoothed again. Bounded
|
|
346
|
+
0…100; the raw double stochastic is **98.30** away (measured). Both
|
|
347
|
+
recursions are `foldRows` steps, so a gap resets them rather than
|
|
348
|
+
repeating the previous reading as the common port does — and that is
|
|
349
|
+
visible: a sustained trend pins the first stochastic, leaving the second
|
|
350
|
+
window flat and the line `undefined`, which on the package's own oracle
|
|
351
|
+
input delays the start from bar 67 to bar **74**.
|
|
352
|
+
- **`prettyGoodOscillator({ period = 14, column, high, low, close, output =
|
|
353
|
+
'pgo' })`** — Mark Johnson's PGO, `(close − SMA)/EMA(TR)`, in units of
|
|
354
|
+
average daily ranges. **F-AMBIG**: Johnson's span-EMA denominator ships
|
|
355
|
+
and the Wilder-`ATR` port is named and measured (**0.211** apart at
|
|
356
|
+
period 14 on a line spanning −3.71…4.23). `column` defaults to whatever
|
|
357
|
+
`close` resolves to, as `atrBands` does.
|
|
358
|
+
- **`swingIndex({ limit, open, high, low, close, output = 'si' })`** and
|
|
359
|
+
**`accumulativeSwingIndex({ …, output = 'asi' })`** — Wilder's 1978 swing
|
|
360
|
+
index and its running total. **`limit` (Wilder's `T`, the instrument's
|
|
361
|
+
limit move) is required** and has no default: it is a fact about the
|
|
362
|
+
instrument, every possible default silently rescales the reading, and the
|
|
363
|
+
±100 bound the study is defined by depends on it. A `limit` of 0 (or
|
|
364
|
+
negative, or non-finite) is rejected. `R = 0` — a tape frozen for two
|
|
365
|
+
bars — reads `undefined`, which for the ASI ends the level, the same rule
|
|
366
|
+
OBV follows.
|
|
367
|
+
- **`randomWalkIndex({ period = 14, high, low, close, prefix = 'rwi' })`** →
|
|
368
|
+
`rwiHigh`, `rwiLow` — E. Michael Poulos' RWI: the **maximum over horizons
|
|
369
|
+
`2 … period`** of `(high − low[−n]) / (meanTR(n)·√n)`. The denominator is
|
|
370
|
+
the `n`-bar **mean** true range, not Wilder's ATR (that variant is
|
|
371
|
+
**0.190** away at period 14; the single-horizon form **2.374**, measured).
|
|
372
|
+
Deliberately **O(N·period)** — the G2 shape no window kernel expresses —
|
|
373
|
+
with O(N) memory; 328 ms at `period 14` and 1194 ms at `period 50` on 1M
|
|
374
|
+
bars. Both columns go **negative** when every horizon fell.
|
|
375
|
+
- **`ravi({ shortPeriod = 7, longPeriod = 65, column, output = 'ravi' })`** —
|
|
376
|
+
Tushar Chande's Range Action Verification Index,
|
|
377
|
+
`100·|SMA(short) − SMA(long)|/SMA(long)`. The **absolute value** is the
|
|
378
|
+
study: it answers "is this trending" (above 3%), not "which way" — the
|
|
379
|
+
signed form is **26.98** away (measured). Scale-invariant, not
|
|
380
|
+
shift-invariant.
|
|
381
|
+
- **`trendIntensityIndex({ period = 30, maPeriod = 60, column, output =
|
|
382
|
+
'tii' })`** — M. H. Pee's TII, `100·Σpos/(Σpos + Σneg)` over the last
|
|
383
|
+
`period` deviations from a `maPeriod` SMA. **F-AMBIG**: Pee's **sums**
|
|
384
|
+
ship, weighted by how far price strayed; the common **count** form is
|
|
385
|
+
**28.85** away (measured) and would deserve its own name. Bounded 0…100,
|
|
386
|
+
warm-up `maPeriod + period − 2` = **88** bars at the defaults.
|
|
387
|
+
- **`specialK({ column, output = 'specialK' })`** — Martin Pring's Special
|
|
388
|
+
K: the KST construction extended to **twelve** weighted smoothed rates of
|
|
389
|
+
change across three groups. The thirty-six constants are the study, so
|
|
390
|
+
there are no period options (the `kst` decision). **Warm-up 724 bars** —
|
|
391
|
+
a series shorter than 725 comes back entirely `undefined`, with the row
|
|
392
|
+
count preserved.
|
|
393
|
+
- `@pond-ts/financial`: **the prime studies and Bill Williams' Market
|
|
394
|
+
Facilitation Index** (corpus §6.2 / §6.3 / §6.6) — three per-bar studies with
|
|
395
|
+
no window, no warm-up and no parameters beyond their column names.
|
|
396
|
+
- **`primeNumberBands({ high?, low?, prefix = 'pnb' })`** → `pnbUpper`,
|
|
397
|
+
`pnbLower` — the smallest prime at or above the bar's high and the largest
|
|
398
|
+
at or below its low, so the bands **contain** the bar. A step function of
|
|
399
|
+
the price _level_: it holds flat inside a prime gap and jumps across one,
|
|
400
|
+
and two instruments at the same price have identical bands.
|
|
401
|
+
- **`primeNumberOscillator({ column = 'close', output = 'pno' })`** —
|
|
402
|
+
`price − nearestPrime(price)`, positive when the price sits above the prime
|
|
403
|
+
nearest it and exactly `0` when it **is** an integer prime. Both open
|
|
404
|
+
conventions are pinned: the sign is `price − prime`, and an **equidistant
|
|
405
|
+
tie goes to the lower prime** (so `pno(6) = +1`).
|
|
406
|
+
- Both: **a price below 2 is outside the domain** and reads `undefined` — two
|
|
407
|
+
is the smallest prime, so there is nothing to bracket with. **Neither is
|
|
408
|
+
scale- nor shift-equivariant**, and the property tests assert that
|
|
409
|
+
_absence_ rather than skipping the check. **Cost grows with the price
|
|
410
|
+
magnitude**, the only operators in the package that do: measured at 1M
|
|
411
|
+
bars, ~78 ms and ~55 ms at ordinary equity prices and **~6.5 s each at
|
|
412
|
+
~1e7**. There is no sieve; the kernel documents why and what would change
|
|
413
|
+
it.
|
|
414
|
+
- **`marketFacilitationIndex({ high?, low?, volume?, output = 'bwmfi' })`** —
|
|
415
|
+
Bill Williams' `(high − low) / volume`, the raw ratio with **no `scale`
|
|
416
|
+
option** (no vendor constant is standard, unlike `easeOfMovement`'s
|
|
417
|
+
published `100_000_000`). The default output is **`bwmfi`, not `mfi`**:
|
|
418
|
+
`moneyFlowIndex` — a different indicator published under the same
|
|
419
|
+
abbreviation — already owns that name, and both must be able to sit on one
|
|
420
|
+
series. A zero-volume bar reads `undefined`; a flat bar reads `0`.
|
|
421
|
+
- `@pond-ts/financial`: **the bands and channels tail** (corpus §6.2) — four
|
|
422
|
+
studies in the uniform shape, each with oracle cases and a fluent method.
|
|
423
|
+
- **`starcBands({ period = 20, atrPeriod = 15, multiplier = 2, maType =
|
|
424
|
+
'sma', high?, low?, close?, prefix = 'starc' })`** → `starcMiddle`,
|
|
425
|
+
`starcUpper`, `starcLower` — Manning Stoller's channel: a moving average
|
|
426
|
+
of the **close** with bands a multiple of the ATR away. The close-centred
|
|
427
|
+
sibling of `keltner` (typical-price centre) and of `atrBands` (bands
|
|
428
|
+
around an existing column, no centre). It **is** `movingAverage` followed
|
|
429
|
+
by `atrBands` around that average, pinned bit-for-bit by a test; it ships
|
|
430
|
+
for the name and the centre column. Per-column warm-up: centre at the
|
|
431
|
+
MA's own bar, bands at `max(centre, ATR)`.
|
|
432
|
+
- **`highLowBands({ period = 10, percent = 1, maType = 'trima', high?, low?,
|
|
433
|
+
prefix = 'hlb' })`** → `hlbMiddle`, `hlbUpper`, `hlbLower` — a moving
|
|
434
|
+
average of the **median price** with bands `± percent`. It **is**
|
|
435
|
+
`envelope` over a `medianPrice` column, pinned bit-for-bit.
|
|
436
|
+
- **`bollingerBandwidth({ period = 20, stdDev = 2, column?, output =
|
|
437
|
+
'bbWidth' })`** — `100·(upper − lower)/middle`, the **×100** StockCharts /
|
|
438
|
+
ChartIQ form rather than Bollinger's bare ratio. A **flat window reads
|
|
439
|
+
`0`**, not `undefined`: the numerator is `2·stdDev·σ`, forced to zero,
|
|
440
|
+
over a non-zero centre — so on a flat stretch it is deliberately **not**
|
|
441
|
+
recoverable from `bollinger`'s (missing) band columns, while everywhere
|
|
442
|
+
else it is bit-for-bit.
|
|
443
|
+
- **`bollingerPercentB({ period = 20, stdDev = 2, column?, output =
|
|
444
|
+
'percentB' })`** — `(price − lower)/(upper − lower)`, the **decimal** form
|
|
445
|
+
(`0` = lower band, `1` = upper), unbounded so an out-of-band price still
|
|
446
|
+
reads. A flat window is a genuine `0/0` and reads `undefined` — the
|
|
447
|
+
opposite answer from BandWidth's on the same window, and the clearest
|
|
448
|
+
illustration of the "is the numerator _forced_ to zero?" test.
|
|
449
|
+
- `@pond-ts/financial`: **the K3 price transforms and Balance of Power**
|
|
450
|
+
(corpus §6.8) — five studies in the uniform shape (each bar-column named by
|
|
451
|
+
its own option defaulting to its `DEFAULT_OHLCV` name, an `output`, a
|
|
452
|
+
fluent method), all five **exact** against TA-Lib bar-for-bar.
|
|
453
|
+
- **`typicalPrice({ high?, low?, close?, output = 'typicalPrice' })`**
|
|
454
|
+
(`(h+l+c)/3`, TA-Lib `TYPPRICE`),
|
|
455
|
+
**`medianPrice({ high?, low?, output = 'medianPrice' })`** (`(h+l)/2`,
|
|
456
|
+
`MEDPRICE`),
|
|
457
|
+
**`weightedClose({ high?, low?, close?, output = 'weightedClose' })`**
|
|
458
|
+
(`(h+l+2c)/4`, `WCLPRICE`) and
|
|
459
|
+
**`averagePrice({ open?, high?, low?, close?, output = 'averagePrice' })`**
|
|
460
|
+
(`(o+h+l+c)/4`, `AVGPRICE`). These are the per-bar summaries `vwap`, `cci`,
|
|
461
|
+
`mfi`, `keltner` and the Awesome Oscillator derive privately, now available
|
|
462
|
+
as **columns** so a caller can chart one or run another study over it
|
|
463
|
+
(`sma({ column: 'typicalPrice' })`). **No warm-up at all** — a bar's own
|
|
464
|
+
prices are all any of them reads, so bar 0 carries a value.
|
|
465
|
+
- **`balanceOfPower({ period?, maType?, open?, high?, low?, close?, output =
|
|
466
|
+
'bop' })`** — Igor Livshin's `(close − open) / (high − low)`, the body over
|
|
467
|
+
the range, bounded −1…1. **Raw by default** (exact against TA-Lib `BOP`);
|
|
468
|
+
the optional `period` is ChartIQ's smoothed form over the shared `MaType`
|
|
469
|
+
menu. **F-AMBIG** — both conventions are published, and the one an oracle
|
|
470
|
+
can pin ships as the default. Passing `maType` without a `period` **throws**
|
|
471
|
+
rather than silently doing nothing. A **flat bar reads `0`, not
|
|
472
|
+
`undefined`**: a bar with no range traded at one price, so the numerator is
|
|
473
|
+
forced to zero (the `clvValues` rule from the volume group), which is
|
|
474
|
+
TA-Lib's answer too.
|
|
475
|
+
- `@pond-ts/financial`: **the moving-average stacks and the smoothed-momentum
|
|
476
|
+
tail** (corpus §6.1 / §6.3) — seven studies in the uniform shape (a `column`
|
|
477
|
+
plus an `output` or `prefix`, bar-count periods, a length-preserving
|
|
478
|
+
per-column warm-up, a fluent method), each with oracle cases.
|
|
479
|
+
- **`guppy({ column = 'close', type = 'ema', prefix = 'gmma' })`** — Daryl
|
|
480
|
+
Guppy's Multiple Moving Average: the **fixed twelve** averages, short
|
|
481
|
+
`3, 5, 8, 10, 12, 15` as `gmmaS3 … gmmaS15` and long
|
|
482
|
+
`30, 35, 40, 45, 50, 60` as `gmmaL30 … gmmaL60`. There is deliberately no
|
|
483
|
+
"which periods" option — the twelve are the study — but `type` takes the
|
|
484
|
+
whole shared `MaType` menu. The periods are exported as
|
|
485
|
+
`GUPPY_SHORT_PERIODS` / `GUPPY_LONG_PERIODS` so a chart can label the
|
|
486
|
+
ribbon. Each column is checked against `talib.MA` **at its own period**
|
|
487
|
+
(exact for `sma`; for `ema` the formula on TA-Lib's SMA seed plus a
|
|
488
|
+
geometric-decay check on pond's first-sample seed). Warm-up is
|
|
489
|
+
**per column** — `gmmaS3` at bar 2, `gmmaL60` at bar 59, which is 57 real
|
|
490
|
+
values of the fast ribbon a shared warm-up would have discarded.
|
|
491
|
+
- **`rainbow({ column = 'close', period = 2, type = 'sma', prefix =
|
|
492
|
+
'rainbow' })`** — Mel Widner's Rainbow Moving Average (TASC, July 1997):
|
|
493
|
+
ten **recursive** averages, each smoothing the previous one, as
|
|
494
|
+
`rainbow1 … rainbow10`. Stage `k` warms up at `k · (period − 1)`, so ten
|
|
495
|
+
2-bar averages are a ten-bar look-back. Deliberately **not** the
|
|
496
|
+
ten-increasing-lengths variant also published under the name: a recursive
|
|
497
|
+
2-bar mean is a binomial filter, and stage 10 sits **0.83** from the
|
|
498
|
+
11-bar SMA covering the same support (measured; the fixture's whole close
|
|
499
|
+
range is 19.4).
|
|
500
|
+
- **`rainbowOscillator({ column = 'close', period = 2, lookback = 10,
|
|
501
|
+
prefix = 'rbo' })`** → `rbo`, `rboUpper`, `rboLower` — ChartIQ's:
|
|
502
|
+
`100·(price − mean of the ten)/(HH − LL)` over `lookback` bars of
|
|
503
|
+
`column`, with the stack's own `max − min` as bands mirrored about zero.
|
|
504
|
+
**F-AMBIG**, so the source is named and the alternatives measured: the
|
|
505
|
+
divide-by-price form is **67.27** away and a first-average numerator
|
|
506
|
+
**49.16**, against a reading spanning −68.6…63.8. A flat `lookback`
|
|
507
|
+
window reads `undefined` — nothing forces the numerators to zero, so it
|
|
508
|
+
is a real number over zero rather than a `0/0`.
|
|
509
|
+
- **`kst({ column = 'close', signalPeriod = 9, prefix = 'kst' })`** →
|
|
510
|
+
`kst`, `kstSignal` — Martin Pring's Know Sure Thing on his intermediate
|
|
511
|
+
**daily** set: four percent rates of change (look-backs 10 / 15 / 20 /
|
|
512
|
+
30), each smoothed by a simple average (10 / 10 / 10 / 15), weighted
|
|
513
|
+
1 / 2 / 3 / 4 and summed, with a simple-average signal. The twelve
|
|
514
|
+
constants are deliberately **not** options — Pring published several KSTs
|
|
515
|
+
(short daily, weekly, monthly) and they are different indicators, not one
|
|
516
|
+
with parameters — so only `signalPeriod` is exposed. The line warms up at
|
|
517
|
+
bar 44, the signal `signalPeriod − 1` later. Scale-invariant, not
|
|
518
|
+
shift-invariant.
|
|
519
|
+
- **`priceMomentumOscillator({ column = 'close', prefix = 'pmo' })`** →
|
|
520
|
+
`pmo`, `pmoSignal` — DecisionPoint's PMO. Its two smoothing stages use
|
|
521
|
+
DecisionPoint's **custom** multiplier `α = 2/n`, which is **not** the
|
|
522
|
+
span EMA's `2/(n+1)` and is the only non-span exponential in the package:
|
|
523
|
+
measured, building both stages on the span EMA instead puts the line
|
|
524
|
+
**0.106** away on a reading whose scale is 3.906 (2.7%). The **signal**
|
|
525
|
+
is a plain span `EMA(10)` — DecisionPoint's own asymmetry, and a
|
|
526
|
+
custom-smoothed signal would sit 0.088 away. No period options; the line
|
|
527
|
+
warms up at bar 54 and the signal at 63.
|
|
528
|
+
- **`stochasticRsi({ column = 'close', rsiPeriod = 14, stochPeriod = 14,
|
|
529
|
+
kPeriod = 3, dPeriod = 3, prefix = 'stochRsi' })`** → `stochRsiK`,
|
|
530
|
+
`stochRsiD` — Chande & Kroll's Stochastic RSI, composed on the shipped
|
|
531
|
+
`rsi` rather than a private copy. **`stochRsiK` equals TA-Lib
|
|
532
|
+
`STOCHRSI`'s `fastd`** (not its `fastk`) bar-for-bar with identical
|
|
533
|
+
masks, measured to 9.9e-14; `stochRsiD` has no TA-Lib counterpart, since
|
|
534
|
+
`STOCHRSI` stops at `fastd`. Crossing the two columns is a 45-point error
|
|
535
|
+
on the oracle input, and the generator asserts both the match and the
|
|
536
|
+
mismatch. The option names are TradingView's, so **`stochPeriod` here is
|
|
537
|
+
`stochastic`'s `kPeriod`** and **`kPeriod` here is its `slowing`** — a
|
|
538
|
+
table in the docstring maps all three vocabularies.
|
|
539
|
+
- **`trueStrengthIndex({ column = 'close', longPeriod = 25, shortPeriod =
|
|
540
|
+
13, signalPeriod = 7, prefix = 'tsi' })`** → `tsi`, `tsiSignal` — William
|
|
541
|
+
Blau's TSI: the bar-over-bar change double-smoothed, over its own
|
|
542
|
+
magnitude double-smoothed the same way, ×100. Bounded −100…100 by
|
|
543
|
+
construction. The smoothing **order is the definition** — `longPeriod`
|
|
544
|
+
first, `shortPeriod` to its output — and the swap sits **15.93** away on
|
|
545
|
+
a line spanning −28.7…80.7 (measured; the generator asserts it). A
|
|
546
|
+
perfectly flat column gives a zero denominator with the numerator forced
|
|
547
|
+
to zero, a genuine `0/0` → `undefined`. Scale- **and** shift-invariant,
|
|
548
|
+
unlike `kst` and `priceMomentumOscillator`. Options are named
|
|
549
|
+
`longPeriod` / `shortPeriod`, not `long` / `short`, which read as
|
|
550
|
+
position vocabulary in a financial package.
|
|
551
|
+
- **`movingAverageDeviation({ column = 'close', period = 20, maType =
|
|
552
|
+
'sma', output = 'maDev' })`** — `price − MA`, in **price units**, over
|
|
553
|
+
the shared `MaType` menu. The corpus lists this study as "points **or**
|
|
554
|
+
percent"; the percent half is **already shipped** as `disparityIndex`,
|
|
555
|
+
and the two agree **bit for bit** (`100·maDev/MA === disparity`,
|
|
556
|
+
measured 0.0, asserted in the oracle), so only the points form ships and
|
|
557
|
+
there is no `mode` flag — two indicators behind an option is the
|
|
558
|
+
`keltner` precedent this package avoids. It is the one study in the pair
|
|
559
|
+
that is **shift-invariant** (the constant cancels between the price and
|
|
560
|
+
its own average) and the one with no division and so no
|
|
561
|
+
zero-denominator case.
|
|
562
|
+
- `@pond-ts/financial`: **the K6 stateful-fold kernel and the state-machine
|
|
563
|
+
studies** (corpus §6.4 / §6.6, gap **G2**) — one new public kernel and six
|
|
564
|
+
studies in the uniform shape (`column` / `output` or a `prefix`, bar-count
|
|
565
|
+
periods, a length-preserving warm-up, a fluent method), each with oracle
|
|
566
|
+
cases.
|
|
567
|
+
- **`foldRows(inputs, outputCount, state, step)`** (kernel **K6**,
|
|
568
|
+
[PND-SFOLD]) — a per-bar fold with carried state over several row-aligned
|
|
569
|
+
`Float64Array` columns, the one shape the rolling kernels and core's
|
|
570
|
+
single-column `scan` cannot express. One O(N·k) pass, outputs allocated
|
|
571
|
+
and **NaN-filled** by the kernel, the state object the study's own and
|
|
572
|
+
mutated in place — no per-bar allocation. Exported with its `FoldStep`
|
|
573
|
+
type. Measured at 1M bars: **11.3 ms** for a two-column fold over a no-op
|
|
574
|
+
step, against `ema()`'s 6.3 ms and a 5.2 ms floor for the same scan with
|
|
575
|
+
no callback.
|
|
576
|
+
- **A missing cell RESETS the machine.** The step is offered only complete
|
|
577
|
+
rows, with `run` = how many consecutive complete rows end here; an
|
|
578
|
+
incomplete row leaves the outputs `undefined` and restarts `run` at 1.
|
|
579
|
+
The alternative — holding the state across the gap — was rejected: a
|
|
580
|
+
Parabolic SAR that did not see a bar cannot know whether it flipped, and
|
|
581
|
+
a recursion carried on the wrong side never recovers. `run` doubles as
|
|
582
|
+
the seeded flag, which is why there is no separate `seed` hook.
|
|
583
|
+
- **`parabolicSar({ step = 0.02, maxStep = 0.2, high?, low?, prefix = 'psar' })`**
|
|
584
|
+
→ `psar` + `psarTrend` — Wilder's stop-and-reverse, **bar-for-bar
|
|
585
|
+
identical to TA-Lib `SAR`** (measured 0.0 maximum absolute difference at
|
|
586
|
+
`(0.02, 0.2)`, `(0.05, 0.5)` and `(0.01, 0.1)`, masks identical, first
|
|
587
|
+
value at bar 1). The three prose-ambiguous details — the `−DM` seed side,
|
|
588
|
+
the first bar's "yesterday is today" clamp, and the clamp against the last
|
|
589
|
+
two bars on a reversal — are pinned to TA-Lib's reading and **probed**
|
|
590
|
+
against it in the oracle generator.
|
|
591
|
+
- **`superTrend({ period = 10, multiplier = 3, high?, low?, close?, prefix = 'st' })`**
|
|
592
|
+
→ `st` + `stTrend` — Olivier Seban's ratcheting ATR band, as TradingView's
|
|
593
|
+
`ta.supertrend` implements it, on the same Wilder `atrValues` `atr` and
|
|
594
|
+
`keltner` read. The two bands are **not** emitted: `st` already is
|
|
595
|
+
whichever one is live.
|
|
596
|
+
- **`atrTrailingStop({ period = 14, multiplier = 3, high?, low?, close?, prefix = 'ats' })`**
|
|
597
|
+
→ `ats` + `atsTrend` — the close-anchored ratcheting stop (Vervoort's).
|
|
598
|
+
The **Chandelier** anchor (rolling extreme rather than the close) is a
|
|
599
|
+
different study, reachable as `donchian` + `atr`, and deliberately not a
|
|
600
|
+
knob here.
|
|
601
|
+
- **`negativeVolumeIndex` / `positiveVolumeIndex`
|
|
602
|
+
(`{ column = 'close', volume?, output = 'nvi' | 'pvi', start = 1000 }`)** —
|
|
603
|
+
Fosback's conditional cumulative indices. No period and no warm-up. A
|
|
604
|
+
**flat volume holds on both**, so the two do not partition the bars; a
|
|
605
|
+
zero previous close ends the index rather than inventing a level.
|
|
606
|
+
- **`klinger({ fastPeriod = 34, slowPeriod = 55, signalPeriod = 13, high?, low?, close?, volume?, prefix = 'kvo' })`**
|
|
607
|
+
→ `kvo` + `kvoSignal` — Klinger's **original** volume force
|
|
608
|
+
(`volume × |2 × (dm/cm − 1)| × trend × 100`, with `cm` accumulating over
|
|
609
|
+
the trend leg) through an EMA pair plus a signal EMA. **F-AMBIG**:
|
|
610
|
+
TradingView's `ta.kvo`, which drops the `dm/cm` factor entirely, is a
|
|
611
|
+
different indicator sharing the name and is documented rather than offered
|
|
612
|
+
as an option.
|
|
613
|
+
- **The three stop machines append `${prefix}` and `${prefix}Trend`**, the
|
|
614
|
+
value under the bare prefix rather than `${prefix}Line`, with `+1` meaning
|
|
615
|
+
the line sits **below** price. The side is a separate column because it is
|
|
616
|
+
not recoverable from the value — all three clamp, so the line can print
|
|
617
|
+
exactly on an extreme or on the close.
|
|
618
|
+
- `@pond-ts/financial`: **the rolling linear-regression family** (corpus §6.7,
|
|
619
|
+
plus the two §6.3 studies that hang off it) — one new public kernel and four
|
|
620
|
+
studies in the uniform shape (a `column` / `output` or `prefix`, bar-count
|
|
621
|
+
periods, a length-preserving warm-up, a fluent method), each with oracle
|
|
622
|
+
cases at two periods.
|
|
623
|
+
- **`linearRegressionValues(values, period)`** (kernel **K7**) — the rolling
|
|
624
|
+
ordinary-least-squares fit of a raw `Float64Array` against the bar index,
|
|
625
|
+
returning `{ slope, intercept, r2 }` in **one O(N) pass that is flat in
|
|
626
|
+
`period`** (measured at 1M bars: 29 ms at `period 14`, 29 ms at
|
|
627
|
+
`period 200`). `x` is deterministic, so `Σx` and `Σx²` are closed forms and
|
|
628
|
+
only `Σy`, `Σxy` and `Σy²` roll. Exported alongside its `RollingRegression`
|
|
629
|
+
result type.
|
|
630
|
+
Near-flat windows (changing by ulps, or a plateau the anchor has gone
|
|
631
|
+
stale across) are recomputed two-pass on a fresh local anchor when the
|
|
632
|
+
rolling spread falls below `1e-3` of the gross magnitude that has passed
|
|
633
|
+
through its sums since the last rebuild — the sign is not the tell — and
|
|
634
|
+
`r2` is pinned to 1; every changing window is tested against an exact
|
|
635
|
+
BigInt-rational reference (review, 2026-09-07).
|
|
636
|
+
- **`linearRegression({ period = 14, column = 'close', prefix = 'linreg' })`**
|
|
637
|
+
→ `linregValue`, `linregSlope`, `linregIntercept`, `linregAngle`,
|
|
638
|
+
`linregR2` — five readings of **one** fit, all warming up together at
|
|
639
|
+
`period − 1`. Four are **exact against TA-Lib** (`LINEARREG`,
|
|
640
|
+
`LINEARREG_SLOPE`, `LINEARREG_INTERCEPT`, `LINEARREG_ANGLE`; measured
|
|
641
|
+
≤ 1.3e-12 at `period 14`, masks identical); `linregR2` has no TA-Lib
|
|
642
|
+
equivalent and is a pandas replication.
|
|
643
|
+
- **`linregIntercept` is the fit at the window's FIRST bar** (`x = 0`),
|
|
644
|
+
TA-Lib's convention — `linregValue` is the fit at its last. On the
|
|
645
|
+
oracle input at `period 14` the two differ by up to **13.68 points** on
|
|
646
|
+
a series whose entire range is 19.4, so reading one for the other is a
|
|
647
|
+
real error rather than a rounding one.
|
|
648
|
+
- **`linregAngle` is scale-DEPENDENT**, and deliberately so: TA-Lib's
|
|
649
|
+
`LINEARREG_ANGLE` is `atan(slope)` in degrees with no normalisation, so
|
|
650
|
+
the same instrument quoted in cents reads a different angle. Stated on
|
|
651
|
+
the study and pinned by a property test that asserts scaling **moves**
|
|
652
|
+
it.
|
|
653
|
+
- **`timeSeriesForecast({ period = 14, column, output = 'tsf' })`** — the
|
|
654
|
+
same fit projected one bar past the window (`intercept + slope·period`),
|
|
655
|
+
**exact against TA-Lib `TSF`** (≤ 2.1e-13). Also known as the "time series
|
|
656
|
+
moving average" and deliberately **not** added to the K2 `MaType` menu:
|
|
657
|
+
every type there is the identity at `period 1`, and a one-bar window has
|
|
658
|
+
no slope.
|
|
659
|
+
- **`chandeForecastOscillator({ period = 14, column, output = 'cfo' })`** —
|
|
660
|
+
`100 · (price − TSF) / price` (Tushar Chande). No TA-Lib function; the
|
|
661
|
+
oracle is a pandas replication on the TA-Lib-checked `TSF`, separated from
|
|
662
|
+
the version that subtracts the in-window endpoint (0.89 at `period 14`).
|
|
663
|
+
Scale-invariant, **not** shift-invariant.
|
|
664
|
+
- **`centerOfGravity({ period = 10, column, output = 'cog' })`** — Ehlers'
|
|
665
|
+
position-weighted balance point (_Stocks & Commodities_, May 2002),
|
|
666
|
+
`−Σ(k+1)·price[i−k] / Σprice[i−k]` with the **newest** bar carrying weight
|
|
667
|
+
1. Negative and bounded `[−period, −1]` on positive prices (a
|
|
668
|
+
zero-crossing source column can read outside it), and a flat window balances at
|
|
669
|
+
`−(period+1)/2`. This is **TradingView's uncentred `ta.cog`**; Ehlers' own
|
|
670
|
+
EasyLanguage adds `(period+1)/2` to re-centre on zero, a constant offset
|
|
671
|
+
the oracle pins. It needs no new kernel: the descending weights are the
|
|
672
|
+
ascending ones subtracted from a constant, so the study is exactly
|
|
673
|
+
`(period + 1)·(WMA/(2·SMA) − 1)` over the K2 engine's `wma` and
|
|
674
|
+
`rollingMeanValues` — an identity pinned by a test against the naive
|
|
675
|
+
`O(N·period)` definition.
|
|
676
|
+
- **All four run the strict window**: a bar is emitted only when all
|
|
677
|
+
`period` cells are finite, because `x` names a _position_ and dropping a
|
|
678
|
+
cell would fit the line against the wrong abscissa (the `wma` rule). A
|
|
679
|
+
leading gap shifts the start; an interior gap blanks `period` bars and
|
|
680
|
+
then recovers.
|
|
681
|
+
- **A flat window: `slope` exactly `0`, `linregR2` `undefined`.** The
|
|
682
|
+
slope's numerator is forced to zero by the same condition that empties
|
|
683
|
+
the window, so `0` is a real reading; `R²` is a genuine `0/0` and is
|
|
684
|
+
missing. Both are decided by an O(1) count of the changes inside the
|
|
685
|
+
window rather than computed — without it the accumulator residue reads
|
|
686
|
+
`slope = −2.1e-14` and **`R² = −13.5`**, outside the statistic's own
|
|
687
|
+
range (measured).
|
|
688
|
+
- The regression periods must be **at least 2** (`n²(n²−1)/12` is `0` at
|
|
689
|
+
`n = 1`); `centerOfGravity` takes a moment rather than a fit, so it
|
|
690
|
+
accepts `period 1` and reads `−1`.
|
|
691
|
+
- `@pond-ts/financial`: **the two-series / comparison family** (corpus §6.7,
|
|
692
|
+
kernel K8) — `correlation`, `beta`, `priceRelative` and `performanceIndex`,
|
|
693
|
+
plus the public kernel `rollingBivariateValues`. Seven oracle cases; the two
|
|
694
|
+
TA-Lib-backed studies agree with `CORREL` and `BETA` bar-for-bar.
|
|
695
|
+
- **The comparison series is a `benchmark` COLUMN on the same series, never a
|
|
696
|
+
second `TimeSeries`.** The consumer aligns and joins first
|
|
697
|
+
(`align` + `TimeSeries.joinMany`), and the study reads two columns of one
|
|
698
|
+
row — exactly as `atr` reads `high`/`low`/`close`. A study that took a
|
|
699
|
+
second series would have to invent an alignment policy (hold?
|
|
700
|
+
interpolate? inner or outer?) that core already expresses, better and once
|
|
701
|
+
for the whole pipeline. A `benchmark` or `column` that is not on the
|
|
702
|
+
series **throws** rather than reading empty.
|
|
703
|
+
- **`correlation({ period = 30, column = 'close', benchmark, output = 'corr' })`**
|
|
704
|
+
→ Pearson's `r` over the two **price** columns, `= talib.CORREL`
|
|
705
|
+
bar-for-bar. TA-Lib correlates the raw inputs, not their returns; the
|
|
706
|
+
return correlation is the same study over two `percentChange` columns.
|
|
707
|
+
- **`beta({ period = 5, column = 'close', benchmark, output = 'beta' })`** →
|
|
708
|
+
`cov(returns, benchmarkReturns) / var(benchmarkReturns)` over `period`
|
|
709
|
+
one-bar returns. **Pass prices** — the returns are taken inside, as
|
|
710
|
+
`percentChangeValues(v, 1)`. Equals `talib.BETA(benchmark, close)`
|
|
711
|
+
bar-for-bar: TA-Lib regresses its **second** input on its **first**
|
|
712
|
+
(measured), so the benchmark goes first there; pond's option names remove
|
|
713
|
+
the ambiguity.
|
|
714
|
+
- **`priceRelative({ column = 'close', benchmark, output = 'priceRel' })`** →
|
|
715
|
+
the bare ratio, with **no period and no warm-up**. ChartIQ's _Price
|
|
716
|
+
Relative_ and _Relative Strength (comparative)_ are one implementation;
|
|
717
|
+
neither is Wilder's `rsi`.
|
|
718
|
+
- **`performanceIndex({ period = 20, column = 'close', benchmark, output = 'perf' })`**
|
|
719
|
+
→ each side's own `period`-bar growth, divided, so `1` is parity. The
|
|
720
|
+
**ratio** form ships (some vendors publish `× 100` or `− 1`);
|
|
721
|
+
`(perf − 1) × 100` is `percentChange(priceRelative, period)` identically,
|
|
722
|
+
which a test asserts.
|
|
723
|
+
- **`rollingBivariateValues(x, y, period)`** → `{ covariance, varianceX,
|
|
724
|
+
varianceY }`, population (`ddof = 0`), over a **strict** pair window: all
|
|
725
|
+
`period` rows of **both** columns, or nothing. Shifted-frame Welford with
|
|
726
|
+
the aligned rebuild ([PND-SHIFTFRAME] / [PND-PROCKERN]), never
|
|
727
|
+
`Σxy − ΣxΣy/n` — measured at 1e12-scale prices, the textbook form returns a
|
|
728
|
+
negative variance (a non-finite correlation) where this holds 2.4e-15.
|
|
729
|
+
Validates `period` (integer `≥ 2`) at the boundary like every public
|
|
730
|
+
kernel, and rebuilds a window on demand when a changing column's `m2`
|
|
731
|
+
falls below `1e-3` of the gross shifted magnitude that has passed through
|
|
732
|
+
it since the last rebuild, or `cxy² > m2x·m2y` past rounding slack — so
|
|
733
|
+
no near-flat window reports a false missing cell or an out-of-range
|
|
734
|
+
correlation; tested against an exact BigInt-rational reference (review,
|
|
735
|
+
2026-09-07).
|
|
736
|
+
- **Two deliberate TA-Lib deltas, both measured**: a flat window is
|
|
737
|
+
`undefined` here and `0.0` in TA-Lib (`CORREL` and `BETA` both substitute
|
|
738
|
+
zero for a zero denominator), and a zero price is a **missing** return here
|
|
739
|
+
where TA-Lib substitutes a return of `0`.
|
|
740
|
+
|
|
741
|
+
- `@pond-ts/financial`: **the Wilder directional group** (corpus §6.4) — three
|
|
742
|
+
studies and two new public kernels, in the uniform shape (redirectable bar
|
|
743
|
+
inputs, bar-count periods, length-preserving warm-up, a fluent method), each
|
|
744
|
+
with oracle cases at two periods.
|
|
745
|
+
- **`directionalMovement({ period = 14, high?, low?, close?, prefix = 'dmi' })`**
|
|
746
|
+
→ `dmiPlusDi`, `dmiMinusDi`, `dmiDx`, `dmiAdx`, `dmiAdxr`. Wilder's Directional
|
|
747
|
+
Movement System — `+DI`, `−DI`, `DX`, `ADX` and `ADXR` in **one** study
|
|
748
|
+
with a **per-column warm-up** (bars 14 / 14 / 14 / 27 / 40 at the default),
|
|
749
|
+
the `macd` precedent rather than five studies re-running the same pipeline.
|
|
750
|
+
The `DI` denominator **is** `atr()`'s array, so the two measure range
|
|
751
|
+
identically by construction.
|
|
752
|
+
- **Seeding is Wilder's, not TA-Lib's**, and this is the one delta.
|
|
753
|
+
Wilder's worksheet seeds `+DM`/`−DM`/`TR` on the mean of the first
|
|
754
|
+
`period` values (what `wilderValues` runs, and what TA-Lib's own `ATR`
|
|
755
|
+
uses); TA-Lib's `ADX` family instead seeds on the first `period − 1` and
|
|
756
|
+
takes one decayed step, so TA-Lib's `+DI` denominator disagrees with
|
|
757
|
+
TA-Lib's own `ATR` (measured on the oracle input: `1.515450` vs
|
|
758
|
+
`1.411787` at `period 14`). Warm-up masks are identical to TA-Lib's on
|
|
759
|
+
all five columns; the oracle proves the **formula** exactly by replaying
|
|
760
|
+
our pipeline on TA-Lib's seed (`≤ 2.9e-14`) and bounds the pond-seed
|
|
761
|
+
transient separately — at `period 14`, `+DI` 0.117 points at the first
|
|
762
|
+
shared bar decaying to 0.0092 by bar 79, `ADX` 0.421 → 0.0046.
|
|
763
|
+
- **`ADXR` looks back `period − 1` bars**, TA-Lib's reading and the
|
|
764
|
+
package's own bar-count convention (a `period`-bar window spans
|
|
765
|
+
`i − period + 1 … i`). The literal "`period` bars ago" reading differs
|
|
766
|
+
by up to 2.64 points at `period 14`.
|
|
767
|
+
- **`+DI + −DI = 0` → `DX = 0`**, not `undefined`: both legs are
|
|
768
|
+
non-negative, so a zero sum forces a zero numerator (the `clvValues`
|
|
769
|
+
flat-bar rule, not the `percentOfRangeValues` flat-window one). A zero
|
|
770
|
+
**true range** is a genuine `0/0` and does read `undefined`.
|
|
771
|
+
- **`aroon({ period = 25, high?, low?, prefix = 'aroon' })`** → `aroonUp`,
|
|
772
|
+
`aroonDown`, `aroonOsc`. `100 · (period − bars since the extreme) / period`
|
|
773
|
+
on each side, and their difference. **Matches TA-Lib `AROON` and
|
|
774
|
+
`AROONOSC` exactly** (delta `0`, identical masks). The window is
|
|
775
|
+
**`period + 1` bars** — `period` counts the oldest _age_ reportable, and
|
|
776
|
+
"`period` bars ago" is itself a reading — so the warm-up is `period` rows;
|
|
777
|
+
**ties go to the most recent bar** (measured against TA-Lib). Invariant to
|
|
778
|
+
any monotonic rescaling of price, not merely to scale and shift.
|
|
779
|
+
- **`vortex({ period = 14, high?, low?, close?, prefix = 'vi' })`** →
|
|
780
|
+
`viPlus`, `viMinus`. Botes & Siepman's `Σ|high − prevLow| / Σ TR` and
|
|
781
|
+
`Σ|low − prevHigh| / Σ TR`. No TA-Lib function; pandas replication, with
|
|
782
|
+
the plain-bar-range denominator asserted as a visible separation. Both
|
|
783
|
+
legs are positive and **not bounded by 1**; `Σ TR = 0` → `undefined`
|
|
784
|
+
(unlike `DX`, the numerator is not forced to zero with it).
|
|
785
|
+
- **`directionalMovementValues(high, low)`** (`kernels/directional-movement.ts`)
|
|
786
|
+
— Wilder's `+DM`/`−DM` split in one pass, with the tie and sign rules and
|
|
787
|
+
an explicit `NaN` guard (an unknown move must not read as a flat one).
|
|
788
|
+
- **`barsSinceExtremeValues(values, period, 'max' | 'min')`**
|
|
789
|
+
(`kernels/highest-lowest.ts`) — the corpus's **G3 argmax gap**: bars since
|
|
790
|
+
the window's extreme, via a **monotonic deque**, so it is O(N) and _flat in
|
|
791
|
+
`period`_. Measured at 1M bars: 26.7 ms at `period 25` and 26.5 ms at
|
|
792
|
+
`period 200`, against a naive re-scan's 78 ms and 545 ms. `aroon` costs
|
|
793
|
+
102 ms at 1M bars against `donchian`'s 247 ms — the first measured
|
|
794
|
+
evidence for the monotonic-deque fast path core's rolling min/max wants.
|
|
795
|
+
- `@pond-ts/financial`: **the volatility tail** (assessment §6.5) — seven
|
|
796
|
+
studies of range and drawdown, **none of which TA-Lib implements**, so every
|
|
797
|
+
one is a pandas replication in the oracle with its analytic first-valid bar
|
|
798
|
+
asserted _and_ a measured separation from the plausible wrong turn. Each
|
|
799
|
+
ships with a fluent method, two oracle cases, hand-computed unit values,
|
|
800
|
+
missing-cell placement tests, property tests and a perf entry.
|
|
801
|
+
- **`chaikinVolatility({ period = 10, rocPeriod = 10, high?, low?, output = 'chaikinVol' })`**
|
|
802
|
+
→ `chaikinVol`. The percent rate of change of `EMA(high − low, 10)` over
|
|
803
|
+
the last 10 bars — is the average bar getting wider? **Plain** range, not
|
|
804
|
+
true range (Chaikin's; 35.6 points from the true-range version on the
|
|
805
|
+
oracle input). Two separate periods, both defaulting to 10, because the
|
|
806
|
+
smoothing span and the look-back are different quantities. First valid at
|
|
807
|
+
`period − 1 + rocPeriod`.
|
|
808
|
+
- **`massIndex({ emaPeriod = 9, sumPeriod = 25, high?, low?, output = 'mass' })`**
|
|
809
|
+
→ `mass`. Dorsey's `Σ EMA(range)/EMA(EMA(range))` — range expansion with no
|
|
810
|
+
direction at all, which is the point of it (the 27 → 26.5 "reversal bulge"
|
|
811
|
+
warns that a trend will turn without saying which way). A **sum**, so a
|
|
812
|
+
steady market reads ≈ `sumPeriod`, not 0 or 1; the mean version is 24.1
|
|
813
|
+
away and is asserted apart. The EMA∘EMA chain follows the `trix` rule, so
|
|
814
|
+
the first valid bar is `2·emaPeriod + sumPeriod − 3` (bar 40 at the
|
|
815
|
+
defaults).
|
|
816
|
+
- **`choppinessIndex({ period = 14, high?, low?, close?, output = 'chop' })`**
|
|
817
|
+
→ `chop`, bounded `0..100`. Dreiss' `100·log10(ΣTR/(HH−LL))/log10(period)`:
|
|
818
|
+
path walked against ground covered, **high = choppy, low = trending**, and
|
|
819
|
+
silent about direction. **True** range, the package's own `trueRangeValues`
|
|
820
|
+
(asserted `== talib.TRANGE`), 7.7 points from the plain-range version. The
|
|
821
|
+
log base cancels, which the generator asserts rather than assumes.
|
|
822
|
+
`period` must be **≥ 2** — `log10(1)` is zero.
|
|
823
|
+
- **`ulcerIndex({ period = 14, column?, output = 'ulcer' })`** → `ulcer`.
|
|
824
|
+
Peter Martin's downside-only volatility: the RMS percentage drawdown from
|
|
825
|
+
the window's own highest close. Ships the **rolling (StockCharts) form**;
|
|
826
|
+
Martin's cumulative original is one number per portfolio, not a series, and
|
|
827
|
+
is a documented delta rather than an option — as is the mean-**absolute**
|
|
828
|
+
variant, which is the Pain Index (1.53 away, asserted apart). Warm-up is
|
|
829
|
+
**`2·period − 2`**: two chained windows.
|
|
830
|
+
- **`verticalHorizontalFilter({ period = 28, column?, output = 'vhf' })`** →
|
|
831
|
+
`vhf`, a **fraction** in `(0, 1]`. Adam White's `(HH−LL)/Σ|Δ|` — the
|
|
832
|
+
opposite polarity to `chop`. Both halves read the **same** column, which is
|
|
833
|
+
what makes it composable over another study's output. Warm-up is `period`,
|
|
834
|
+
not `period − 1`: `period` changes need `period + 1` values, and the
|
|
835
|
+
one-term-shorter version is asserted apart.
|
|
836
|
+
- **`gopalakrishnanRangeIndex({ period = 10, high?, low?, output = 'gapo' })`**
|
|
837
|
+
→ `gapo`. `ln(HH − LL)/ln(period)` — which _is_ the log of the window's
|
|
838
|
+
range in base `period`, since the base cancels. The **one study in the
|
|
839
|
+
batch that is not scale-invariant**: scaling every price by `k` shifts the
|
|
840
|
+
reading by exactly `ln(k)/ln(period)`, pinned as an identity rather than an
|
|
841
|
+
invariance. `period` must be **≥ 2**.
|
|
842
|
+
- **`relativeVolatilityIndex({ period = 14, stdevPeriod = 10, column?, output = 'relVol' })`**
|
|
843
|
+
→ `relVol`, bounded `0..100`. Dorsey's RSI form with the population σ of
|
|
844
|
+
the close in place of the price change, **Wilder-smoothed** (his
|
|
845
|
+
definition; TradingView's EMA-smoothed fork is 31.3 points away and
|
|
846
|
+
measured). **The column is `relVol`, not `rvi`** — that belongs to
|
|
847
|
+
`relativeVigorIndex`, a completely different study, and the two now sit on
|
|
848
|
+
one series without collision. An unchanged close counts as a **down** bar,
|
|
849
|
+
deliberately unlike `rsi`'s split, which gives a flat bar 0 on both legs.
|
|
850
|
+
|
|
851
|
+
**One new kernel**, `barRangeValues` (`kernels/typical-price.ts`, not
|
|
852
|
+
public): the plain `high − low`, shared by `chaikinVolatility` and
|
|
853
|
+
`massIndex`. It is deliberately **not** `trueRangeValues` — the Wilder family
|
|
854
|
+
(ATR, Keltner, Choppiness) takes true range and the Chaikin/Dorsey family
|
|
855
|
+
takes plain range, because that is what each author defined, and neither
|
|
856
|
+
study exposes a knob to swap them.
|
|
857
|
+
|
|
858
|
+
**Two sharp edges worth knowing.** (1) `ulcerIndex` is the first study to
|
|
859
|
+
take a **square root** of a rolling mean, and an incremental accumulator's
|
|
860
|
+
`O(ε)` residue in the mean of squares becomes `1.6e-9` in the reading — right
|
|
861
|
+
where the answer should be exactly `0` (a window at new highs). The
|
|
862
|
+
contributing bars are counted so that reading is exact; the oracle case at
|
|
863
|
+
`period 5` is what caught it. (2) `rollingValues`' answer to a **misnamed
|
|
864
|
+
column** depends on the reducer: `stdev`/`avg` take the range-exact path and
|
|
865
|
+
read all-missing, `max`/`min` fall through to core's sweep and throw. So
|
|
866
|
+
`relativeVolatilityIndex` answers empty where `ulcerIndex` and
|
|
867
|
+
`verticalHorizontalFilter` throw. Both behaviours are pinned by tests and
|
|
868
|
+
documented on the studies; the fix belongs in the kernel.
|
|
869
|
+
|
|
870
|
+
**Zero-denominator guards, where they are and are not.** `choppinessIndex`
|
|
871
|
+
and `gopalakrishnanRangeIndex` divide (or take a logarithm) at their
|
|
872
|
+
**output**, so a non-finite result would reach `withColumn` — which throws on
|
|
873
|
+
`±Infinity` — and their guards are load-bearing. `massIndex` and `ulcerIndex`
|
|
874
|
+
divide **upstream of a rolling summation**, which counts a non-finite cell as
|
|
875
|
+
missing exactly as it counts a `NaN`, so their guards were unobservable and
|
|
876
|
+
were removed after mutation testing; the behaviour (`undefined`, never an
|
|
877
|
+
infinity) is unchanged and still unit-tested. `verticalHorizontalFilter` and
|
|
878
|
+
`relativeVolatilityIndex` need none at all — a zero denominator forces a zero
|
|
879
|
+
numerator in both.
|
|
880
|
+
|
|
881
|
+
- `@pond-ts/financial`: **the volume & money-flow group** (corpus §6.6) — eight
|
|
882
|
+
studies over one new kernel, all in the uniform shape (redirectable bar
|
|
883
|
+
inputs, bar-count periods, length-preserving warm-up, a fluent method) and
|
|
884
|
+
each with oracle cases at two parameterisations where it has a period.
|
|
885
|
+
- **`accumulationDistribution({ high?, low?, close?, volume?, output = 'ad' })`**
|
|
886
|
+
→ `ad`. Chaikin's running total of `CLV × volume`, where
|
|
887
|
+
`CLV = ((close − low) − (high − close)) / (high − low)` grades _where in
|
|
888
|
+
its own range_ a bar closed. Like `obv` it has **no period** and no
|
|
889
|
+
warm-up. **Matches TA-Lib `AD` exactly** (delta `0`) on bars with a range.
|
|
890
|
+
- **`chaikinOscillator({ fastPeriod = 3, slowPeriod = 10, high?, low?, close?, volume?, output = 'chaikinOsc' })`**
|
|
891
|
+
→ `chaikinOsc`. `EMA(A/D, 3) − EMA(A/D, 10)`, the A/D line's momentum.
|
|
892
|
+
**Matches TA-Lib `ADOSC` exactly** (delta `0`, identical masks) — the one
|
|
893
|
+
EMA-family study here with **no seed delta**, because TA-Lib's own `ADOSC`
|
|
894
|
+
seeds both EMAs on the first A/D value, which is pond's convention. (The
|
|
895
|
+
oracle also asserts the SMA-seeded reconstruction is visibly different, so
|
|
896
|
+
the case pins which seed ships.)
|
|
897
|
+
- **`priceVolumeTrend({ close?, volume?, output = 'pvt' })`** → `pvt`. The
|
|
898
|
+
running total of `volume × (close − prevClose)/prevClose` — OBV's idea
|
|
899
|
+
scaled by _how far_ price moved. The **fraction**, not the percent: it
|
|
900
|
+
composes on the `ROC` kernel and divides by 100, which is the one place
|
|
901
|
+
that constant appears. **`pvt[0]` is `undefined`, not `0`** — the term
|
|
902
|
+
needs a previous close, and unlike OBV there is no vendor convention to
|
|
903
|
+
seed from; every later level is identical either way.
|
|
904
|
+
- **`chaikinMoneyFlow({ period = 20, high?, low?, close?, volume?, output = 'cmf' })`**
|
|
905
|
+
→ `cmf`. `Σ CLV·volume / Σ volume` over the window — the A/D term as a
|
|
906
|
+
**bounded** `[−1, +1]` reading. Runs on the same `rollingWeightedMeanValues`
|
|
907
|
+
kernel as `vwap`, so it inherits its edge rules; `Σ volume = 0` →
|
|
908
|
+
`undefined`.
|
|
909
|
+
- **`moneyFlowIndex({ period = 14, high?, low?, close?, volume?, output = 'mfi' })`**
|
|
910
|
+
→ `mfi`. The RSI form on raw money flow (`typical price × volume`, split
|
|
911
|
+
by the direction of the typical price). **Matches TA-Lib `MFI`** to
|
|
912
|
+
`2.8e-14` with identical masks. Warm-up is **`period` rows, not
|
|
913
|
+
`period − 1`** (the first bar has no previous typical price), the `rsi` /
|
|
914
|
+
`atr` off-by-one.
|
|
915
|
+
- **`forceIndex({ period = 13, close?, volume?, output = 'force' })`** →
|
|
916
|
+
`force`. Elder's `EMA((close − prevClose) × volume, 13)`; **`period: 1` is
|
|
917
|
+
the raw, unsmoothed force**, so no `smooth: false` flag is needed. No
|
|
918
|
+
`maType` — Elder names the EMA (the `elderRay` precedent).
|
|
919
|
+
- **`easeOfMovement({ period = 14, maType = 'sma', scale = 100_000_000, high?, low?, volume?, output = 'eom' })`**
|
|
920
|
+
→ `eom`. Arms' midpoint move divided by the box ratio
|
|
921
|
+
`(volume / scale) / (high − low)`, MA-smoothed. `scale` is StockCharts' /
|
|
922
|
+
ChartIQ's constant and is a **pure linear multiplier**, exposed because the
|
|
923
|
+
right value depends on the instrument's volume units, not on taste. A flat
|
|
924
|
+
bar and a zero-volume bar both report `undefined` (two zero denominators).
|
|
925
|
+
- **`volumeOscillator({ fastPeriod = 5, slowPeriod = 10, maType = 'sma', volume?, output = 'volOsc' })`**
|
|
926
|
+
→ `volOsc`. `100 · (MA(volume, 5) − MA(volume, 10)) / MA(volume, 10)`. This
|
|
927
|
+
is **literally `priceOscillator`'s percent mode over the volume column**, so
|
|
928
|
+
it delegates to it rather than restating the arithmetic — a test pins the
|
|
929
|
+
identity. What the wrapper adds is the name and the volume-appropriate
|
|
930
|
+
defaults (5/10/sma against 12/26/ema).
|
|
931
|
+
|
|
932
|
+
**Volume Rate of Change is not a study**: the corpus names it, but
|
|
933
|
+
`percentChange({ column: 'volume' })` _is_ its definition (and is
|
|
934
|
+
TA-Lib-verified through `ROC`), so what ships is a recipe note in API.md and
|
|
935
|
+
a test that pins it — step 0 of the studies README.
|
|
936
|
+
|
|
937
|
+
**One new kernel**, `clvValues` / `accumulationDistributionValues`
|
|
938
|
+
(`kernels/close-location.ts`), shared by A/D, the Chaikin oscillator and CMF.
|
|
939
|
+
A **flat bar (`high === low`) has a close location of exactly `0`** — its
|
|
940
|
+
numerator `(c − l) − (h − c)` is forced to zero, so `0` is the value rather
|
|
941
|
+
than a convention — and so A/D matches TA-Lib's `AD` on every bar, halts
|
|
942
|
+
included, and CMF counts the bar's volume in its denominator as every
|
|
943
|
+
conventional CMF does. A window with **no money flow at all**
|
|
944
|
+
makes `mfi` `undefined` where TA-Lib reports `0` — and TA-Lib reports `0`
|
|
945
|
+
for any window whose total flow is merely below `1.0`, measured returning `0`
|
|
946
|
+
where the answer is `100` on a rising series with `1e-9` volume.
|
|
947
|
+
|
|
948
|
+
- `@pond-ts/financial`: **the momentum tail** (assessment §6.3) — six
|
|
949
|
+
oscillators, all of them ratios rather than price-unit quantities, so all six
|
|
950
|
+
are invariant to **both** a scale factor and a constant shift (pinned by
|
|
951
|
+
property tests, which is the pair that catches a dropped normalisation).
|
|
952
|
+
Each ships with a fluent method, two oracle cases, hand-computed unit values,
|
|
953
|
+
missing-cell placement tests and a perf entry.
|
|
954
|
+
- **`chandeMomentum({ period = 14, column = 'close', output = 'cmo' })`** →
|
|
955
|
+
`cmo`, bounded `−100..100`. `100·(Σup − Σdown)/(Σup + Σdown)` over
|
|
956
|
+
**Chande's unsmoothed** window sums. **This is deliberately not TA-Lib's
|
|
957
|
+
`CMO`**, which Wilder-smooths the legs and is therefore exactly
|
|
958
|
+
`2 · rsi − 100` — a second name for a shipped column. The generator asserts
|
|
959
|
+
that identity (2.8e-14) as the reason, and measures the definition delta:
|
|
960
|
+
68.28 points at `period 14`, 131.55 at `period 5`, warm-ups identical.
|
|
961
|
+
- **`ultimateOscillator({ shortPeriod = 7, mediumPeriod = 14, longPeriod = 28, high?, low?, close?, output = 'uo' })`**
|
|
962
|
+
→ `uo`, bounded `0..100`. Williams' three-horizon blend of buying pressure
|
|
963
|
+
over true range, weighted 4/2/1. Matches **TA-Lib `ULTOSC`** to 7.1e-15 with
|
|
964
|
+
identical masks; the true range is the package's own kernel, asserted equal
|
|
965
|
+
to `talib.TRANGE` exactly. Three **named** periods (not a tuple — the
|
|
966
|
+
weights are positional), validated strictly increasing.
|
|
967
|
+
- **`commodityChannelIndex({ period = 20, high?, low?, close?, output = 'cci' })`**
|
|
968
|
+
→ `cci`. `(tp − SMA(tp))/(0.015 · meanAbsDev(tp))`, matching **TA-Lib
|
|
969
|
+
`CCI`** to 3.6e-12. Unbounded — ±100 is a convention, not a limit. A
|
|
970
|
+
zero-deviation window is `undefined` rather than TA-Lib's `0`.
|
|
971
|
+
- **`intradayMomentumIndex({ period = 14, open?, close?, output = 'imi' })`**
|
|
972
|
+
→ `imi`, bounded `0..100`. RSI's form over the candle **body**, with
|
|
973
|
+
**plain** window sums rather than Wilder smoothing (Chande's definition; the
|
|
974
|
+
corpus's "RSI form" is the ambiguity this pins). Warm-up is `period − 1`, a
|
|
975
|
+
row shorter than `rsi`/`chandeMomentum`, because a body needs no previous
|
|
976
|
+
bar. Over identical legs `imi = (cmo + 100)/2`, which a test pins.
|
|
977
|
+
- **`relativeVigorIndex({ period = 10, open?, high?, low?, close?, prefix = 'rvi' })`**
|
|
978
|
+
→ `rvi` / `rviSignal`. TradingView's definition: ratio of `SWMA(close −
|
|
979
|
+
open)` sums to `SWMA(high − low)` sums, signal a fourth SWMA. **SWMA is the
|
|
980
|
+
symmetric `(1,2,2,1)/6` filter, not the K2 engine's linear `wma(4)`** — a
|
|
981
|
+
test pins the impulse response. The first study to read all four OHLC
|
|
982
|
+
columns.
|
|
983
|
+
- **`psychologicalLine({ period = 12, column = 'close', output = 'psy' })`** →
|
|
984
|
+
`psy`, bounded `0..100` and quantised to multiples of `100/period`. An
|
|
985
|
+
unchanged close is **not** an up bar (strict `>`), and a bar with no close
|
|
986
|
+
leaves both itself and its successor undirected.
|
|
987
|
+
|
|
988
|
+
**Kernels.** Three additions, none public: `rollingMeanAbsDevValues`
|
|
989
|
+
(CCI's denominator — the package's only **O(N·period)** kernel; the cost is
|
|
990
|
+
measured, and the `O(N log period)` order-statistic form that would replace
|
|
991
|
+
it is written down rather than left to be rediscovered),
|
|
992
|
+
`symmetricWeightedValues` (the 4-bar SWMA), and `upDownLegValues` (the
|
|
993
|
+
gain/loss split, now shared by `rsi`, `chandeMomentum` and
|
|
994
|
+
`intradayMomentumIndex` — `rsi` was moved onto it, with no change to its
|
|
995
|
+
numbers).
|
|
996
|
+
|
|
997
|
+
- `@pond-ts/financial`: **five K2 consumers** — the first studies built on the
|
|
998
|
+
moving-average engine, two channels and three smoothed rates. All five take
|
|
999
|
+
the uniform shape (bar-count periods, redirectable inputs, length-preserving
|
|
1000
|
+
warm-up, a fluent method) and each has two oracle cases.
|
|
1001
|
+
- **`keltner({ period = 20, atrPeriod = 10, multiplier = 2, maType = 'ema', high?, low?, close?, prefix = 'kc' })`**
|
|
1002
|
+
→ `kcMiddle` / `kcUpper` / `kcLower`. A moving average of **typical price**
|
|
1003
|
+
with bands at ±`multiplier` × **ATR**. The variant is pinned: this is the
|
|
1004
|
+
**modern** form (Chester Keltner via Linda Raschke, and ChartIQ's default) —
|
|
1005
|
+
EMA(20) of typical price ± 2 × ATR(10). Keltner's 1960 original used ±1 ×
|
|
1006
|
+
the SMA of the **plain** high−low range; the half-width here is always
|
|
1007
|
+
_true_ range, which is a documented delta rather than an option.
|
|
1008
|
+
Warm-up is **per column** (the `macd` rule): the centre from the MA's own
|
|
1009
|
+
first bar, the bands from `max(centre, ATR)`.
|
|
1010
|
+
- **`atrBands({ period = 14, multiplier = 2, column = 'close', high?, low?, close?, prefix = 'atrb' })`**
|
|
1011
|
+
→ `atrbUpper` / `atrbLower`. **Two** columns, not three: the middle is
|
|
1012
|
+
`column` itself, already on the series. `column` (what the bands are drawn
|
|
1013
|
+
around) is separate from `close` (what the ATR is measured from), so the
|
|
1014
|
+
bands can sit on an `sma` while the volatility still comes off the raw bars.
|
|
1015
|
+
`atrbUpper === column + multiplier × atr()` **bit-for-bit** — both call one
|
|
1016
|
+
shared `atrValues` kernel — and a test pins it against the shipped `atr`.
|
|
1017
|
+
- **`qstick({ period = 8, maType = 'sma', open?, close?, output = 'qstick' })`**
|
|
1018
|
+
→ `qstick`. Chande's moving average of the candle body, `close − open`; the
|
|
1019
|
+
candlestick chart's colour, quantified, read against its zero line. The
|
|
1020
|
+
**first study to read `open`**, which joins the named bar inputs.
|
|
1021
|
+
- **`trix({ period = 15, signalPeriod = 9, column?, prefix = 'trix' })`** →
|
|
1022
|
+
`trix` / `trixSignal`. The 1-bar **percent** rate of change of a
|
|
1023
|
+
triple-smoothed EMA, plus an EMA signal (9 is ChartIQ's default; TA-Lib
|
|
1024
|
+
returns the line alone). The line takes the prefix itself, not
|
|
1025
|
+
`${prefix}Line`. It composes **three** EMA passes — TRIX wants `EMA³`, which
|
|
1026
|
+
is _not_ the menu's `tema` (`3·EMA − 3·EMA² + EMA³`), and a test pins the
|
|
1027
|
+
difference.
|
|
1028
|
+
- **`coppock({ longPeriod = 14, shortPeriod = 11, wmaPeriod = 10, column?, output = 'coppock' })`**
|
|
1029
|
+
→ `coppock`. `WMA(ROC(14) + ROC(11), 10)`. Coppock's lengths are **months**
|
|
1030
|
+
on a monthly index chart; the study is bar-count like every other here, so
|
|
1031
|
+
on daily bars it is a short-horizon oscillator and not the indicator he
|
|
1032
|
+
defined — stated on the study. The weighted average is part of the
|
|
1033
|
+
definition, so there is no `maType` knob.
|
|
1034
|
+
|
|
1035
|
+
**Oracle.** `trix` is cross-checked against **TA-Lib `TRIX`**: the formula is
|
|
1036
|
+
asserted **bit-exact on TA-Lib's own SMA seed** (2.2e-14, identical null
|
|
1037
|
+
masks — which is what catches a log-vs-percent rate of change, a dropped
|
|
1038
|
+
stage, or a `tema` substitution), with pond's first-sample EMA seed bounded as
|
|
1039
|
+
a decaying transient (8.75% of scale at the first shared bar → 0.85% over the
|
|
1040
|
+
last 20 at `period 15`, against 8.5% / 9.8% for the wrong smoothing rates).
|
|
1041
|
+
TA-Lib has no Keltner, ATR-band, QStick or Coppock function, so those four are
|
|
1042
|
+
pandas replications — but the ATR half-width and the rates of change reuse the
|
|
1043
|
+
references TA-Lib already validates, so what each case adds is the assembly
|
|
1044
|
+
and its composed warm-up, each asserted as an analytic first-valid bar. The
|
|
1045
|
+
oracle fixture gains an **`opens`** column (the previous close pulled inside
|
|
1046
|
+
its own bar; 30 of 80 bodies negative, all 80 distinct).
|
|
1047
|
+
|
|
1048
|
+
- `@pond-ts/financial`: **five price-vs-moving-average oscillators** — the
|
|
1049
|
+
first consumers of the K2 moving-average engine. All five ship with fluent
|
|
1050
|
+
methods, oracle cases at two parameterisations each, and property tests.
|
|
1051
|
+
- **`priceOscillator({ fastPeriod = 12, slowPeriod = 26, maType = 'ema',
|
|
1052
|
+
mode = 'percent', column = 'close', output = 'priceOsc' })`** — the
|
|
1053
|
+
spread between two moving averages, as a percent of the slow one
|
|
1054
|
+
(TA-Lib's **PPO**) or in price units (`mode: 'absolute'`, TA-Lib's
|
|
1055
|
+
**APO**). **Percent is the default** for two reasons: the absolute form
|
|
1056
|
+
at the default parameters is `macd()`'s line bar-for-bar (a test pins the
|
|
1057
|
+
identity), and the percent form is the scale-invariant, cross-instrument
|
|
1058
|
+
comparable one. `{ maType: 'sma', mode: 'absolute' }` matches TA-Lib's
|
|
1059
|
+
`APO(matype=0)` to `7.1e-14` with an identical mask; on `'ema'` pond's
|
|
1060
|
+
first-sample EMA seed is kept (the `macd` precedent), so the generator
|
|
1061
|
+
proves the **formula** on TA-Lib's own SMA seed (`2.8e-14`) and bounds
|
|
1062
|
+
the **seed transient** separately — 6.61% of scale at the first shared
|
|
1063
|
+
bar, 0.41% at its worst over the last 20.
|
|
1064
|
+
- **`disparityIndex({ period = 14, maType = 'sma', column, output =
|
|
1065
|
+
'disparity' })`** — `100·(price − MA)/MA`, scale-invariant. No TA-Lib
|
|
1066
|
+
function; pandas replication with the analytic first-valid bar asserted.
|
|
1067
|
+
- **`detrendedPriceOscillator({ period = 20, maType = 'sma', column,
|
|
1068
|
+
output = 'dpo' })`** — `price[i] − MA[i − shift]` with
|
|
1069
|
+
`shift = ⌊period/2⌋ + 1` (it **floors** on odd periods). This is the
|
|
1070
|
+
**non-centered** alignment (TradingView's default); StockCharts'
|
|
1071
|
+
centered `price[i − shift] − MA[i]` is a different series, not a
|
|
1072
|
+
re-plotting of this one, and is documented as such on the study. First
|
|
1073
|
+
value on bar `period − 1 + shift` (bar 30 at the defaults).
|
|
1074
|
+
- **`elderRay({ period = 13, high, low, close, prefix = 'elder' })`** —
|
|
1075
|
+
appends `elderBull` = `high − EMA(close)` and `elderBear` =
|
|
1076
|
+
`low − EMA(close)`. Elder's definition names the EMA, so there is no
|
|
1077
|
+
`maType` knob. No TA-Lib function; pandas replication on pond's EMA seed.
|
|
1078
|
+
- **`awesomeOscillator({ fastPeriod = 5, slowPeriod = 34, high, low,
|
|
1079
|
+
output = 'ao' })`** — `SMA(5) − SMA(34)` of the median price
|
|
1080
|
+
`(high + low)/2`. Both legs wait for that many finite **values** (the
|
|
1081
|
+
derived-array rule), so a gap masks the windows containing it. No TA-Lib
|
|
1082
|
+
function; pandas replication.
|
|
1083
|
+
|
|
1084
|
+
Internally this adds one kernel helper, `medianPriceValues(high, low)`,
|
|
1085
|
+
beside `typicalPriceValues` — 7.6 ms at 1M bars, and named consumers next
|
|
1086
|
+
(Alligator, Gator, High-Low Bands). It is not a public export.
|
|
1087
|
+
|
|
1088
|
+
- `@pond-ts/financial`: **the K2 moving-average engine** — one **MA-type
|
|
1089
|
+
vocabulary** shared by every study that exposes a "MA Type" input (~25 of
|
|
1090
|
+
them in the corpus assessment). New study **`movingAverage({ period, type =
|
|
1091
|
+
'sma', column = 'close', output = 'ma' })`** with fluent
|
|
1092
|
+
`.movingAverage()`, new exported type **`MaType`** and its **`MA_TYPES`**
|
|
1093
|
+
menu, and the raw-array kernel **`movingAverageValues(values, period,
|
|
1094
|
+
type)`** for studies whose input is derived rather than a column (Keltner's
|
|
1095
|
+
typical price, Coppock's WMA of two ROCs, the Price Oscillator).
|
|
1096
|
+
|
|
1097
|
+
Ten types: `sma`, `ema`, `wma` (linear weights), `smma` (Wilder/RMA),
|
|
1098
|
+
`dema` (`2·EMA − EMA²`), `tema` (`3·EMA − 3·EMA² + EMA³`), `trima`
|
|
1099
|
+
(TA-Lib's SMA-of-SMA, lengths summing to `period + 1`), `hull`
|
|
1100
|
+
(`WMA(2·WMA(n/2) − WMA(n), round √n)`), `kama` (Kaufman, TA-Lib's fast 2 /
|
|
1101
|
+
slow 30) and `zlema` (`EMA(2x − x[i−lag])`, `lag = ⌊(period−1)/2⌋` — it
|
|
1102
|
+
floors, so `zlema(10)` and `zlema(11)` share a lag). Deferred, and named as
|
|
1103
|
+
deferred: MAMA/FAMA, T3, VIDYA (needs the K6 recursion) and the
|
|
1104
|
+
time-series/regression forecast MA (kernel K7).
|
|
1105
|
+
|
|
1106
|
+
**`type: 'sma'` and `type: 'ema'` are the calls `sma()` and `ema()` already
|
|
1107
|
+
make** — routed back to `rollingValues` / `smooth('ema')`, so they are the
|
|
1108
|
+
same doubles rather than a second implementation, and stay on their
|
|
1109
|
+
accelerated paths. Pinned bit-for-bit by test.
|
|
1110
|
+
|
|
1111
|
+
**Oracle**: every type has cases at two periods. `sma` / `wma` / `trima` /
|
|
1112
|
+
`kama` match TA-Lib's `MA(matype=…)` **exactly** (≤ 5.7e-13, identical null
|
|
1113
|
+
masks; `kama` to `0`). `ema` / `dema` / `tema` keep **pond's first-sample
|
|
1114
|
+
EMA seed** rather than TA-Lib's SMA seed — the `macd` precedent, so
|
|
1115
|
+
`movingAverage({ type: 'ema' })` cannot disagree with `ema()` inside our own
|
|
1116
|
+
package. Masks are still identical; the values are a decaying seed
|
|
1117
|
+
transient, measured at 0.210% / 0.529% / 0.059% of scale at the first shared
|
|
1118
|
+
bar (period 21) falling to 0.0008% / 0.0155% / 0.0144% by bar 79, and
|
|
1119
|
+
asserted as a tail bound. `smma` / `hull` / `zlema` have no TA-Lib function
|
|
1120
|
+
and are pandas replications with their analytic warm-up asserted (`smma`
|
|
1121
|
+
also pinned bit-for-bit against the `wilderValues` kernel RSI and ATR run
|
|
1122
|
+
on).
|
|
1123
|
+
|
|
1124
|
+
**Interior gaps differ by type, deliberately** (the Wilder asymmetry, now
|
|
1125
|
+
stated per type on the kernel): the window types recover once the gap leaves
|
|
1126
|
+
the window, the `ema` family skips the missing bar and carries on, and
|
|
1127
|
+
`smma` and `kama` propagate to the end — a recursion that consumes every bar
|
|
1128
|
+
has no state to carry across a hole. `sma` is also the one type a leading
|
|
1129
|
+
gap does **not** shift, because it keeps `sma()`'s row-counting window.
|
|
1130
|
+
|
|
1131
|
+
### Changed
|
|
1132
|
+
|
|
1133
|
+
- `@pond-ts/financial`: **`envelope`'s `maType` widens from `'sma' | 'ema'` to
|
|
1134
|
+
the full `MaType` menu** and routes through the shared engine. Strictly
|
|
1135
|
+
additive for existing callers — the `sma` and `ema` centre lines are the
|
|
1136
|
+
same kernel calls as before, pinned bit-for-bit — and an unknown `maType`
|
|
1137
|
+
now throws instead of silently falling back to SMA. (On the raw-array door,
|
|
1138
|
+
`movingAverageValues(…, 'sma')` waits for `period` finite values like every
|
|
1139
|
+
other type; the column door keeps `sma()`'s rows window.)
|
|
1140
|
+
- `pond-ts`: `partitionBy(column, options)` now throws a `TypeError` on an
|
|
1141
|
+
unknown option key (e.g. `maxPartitions`, which was never implemented)
|
|
1142
|
+
instead of silently ignoring it. Only JS callers passing a key the type
|
|
1143
|
+
rejects are affected; the error names the known keys ([PND-LIVFIX]).
|
|
1144
|
+
|
|
1145
|
+
### Fixed
|
|
1146
|
+
|
|
1147
|
+
- `pond-ts` live layer, the [PND-LIVFIX] cluster — the five confirmed
|
|
1148
|
+
wrong-answer defects from the 2026-06 audits, each now pinned by a test:
|
|
1149
|
+
- **Listener errors are isolated.** A `LiveSeries` / `LiveView` listener
|
|
1150
|
+
that throws no longer aborts the push: every row still commits, the
|
|
1151
|
+
remaining listeners still run, retention still runs and `'batch'` /
|
|
1152
|
+
`'evict'` still fire; the first error is rethrown to the caller once
|
|
1153
|
+
the push has completed. Before, a throw skipped retention (the buffer
|
|
1154
|
+
sat over `maxEvents` until the next push) and left every later
|
|
1155
|
+
subscriber — a derived `filter()` view included — permanently out of
|
|
1156
|
+
sync.
|
|
1157
|
+
- **Re-entrancy.** A listener may subscribe or unsubscribe during
|
|
1158
|
+
dispatch without disturbing the fan-out (a listener added mid-event
|
|
1159
|
+
does not fire for that event), and a `push` made from inside a
|
|
1160
|
+
listener is queued and delivered after the current push, in order.
|
|
1161
|
+
Before, a re-entrant push emitted out of order, was spuriously
|
|
1162
|
+
rejected as out-of-order on the chunked backing, and broke an attached
|
|
1163
|
+
view with an error that read `[object Object]` (keys in that message
|
|
1164
|
+
now print as ISO timestamps).
|
|
1165
|
+
- **Quiet partitions evict by age.** Per-partition `maxAge` was
|
|
1166
|
+
push-driven, so a partition that stopped receiving events kept them
|
|
1167
|
+
forever. `partitionBy` now sweeps every partition against the source
|
|
1168
|
+
watermark as it advances (throttled to `maxAge / 8`), emitting
|
|
1169
|
+
`'evict'` as push-driven retention does.
|
|
1170
|
+
- **Chain-aware `LiveView.dispose()`.** Disposing the last view of
|
|
1171
|
+
`live.filter(p).map(f)` now disposes the unreachable intermediate too
|
|
1172
|
+
(a source view left with no subscribers is torn down; one with another
|
|
1173
|
+
subscriber is not). `dispose()` is idempotent and documented.
|
|
1174
|
+
- **`LiveReduce` windowed reducers on a `reorder` source with
|
|
1175
|
+
retention.** `min` / `max` / `first` / `last` reported stale or
|
|
1176
|
+
`undefined` values because their sliding-window state assumed
|
|
1177
|
+
oldest-arrived-first eviction; over a `reorder` source they now use
|
|
1178
|
+
removal-by-any-index structures (exact in any eviction order, O(n)
|
|
1179
|
+
per update, selected only on that source shape).
|
|
1180
|
+
|
|
71
1181
|
## [0.65.0] — 2026-09-06
|
|
72
1182
|
|
|
73
1183
|
### Added
|