ta-crypto 0.2.3 → 0.3.2

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/README.md CHANGED
@@ -33,9 +33,13 @@ const vs = vwapSession(high, low, close, volume, [1, 1]);
33
33
  ## Stateful API (streaming)
34
34
 
35
35
  ```ts
36
- import { createRSI, createVWAPSession } from "ta-crypto";
36
+ import { createSMA, createEMA, createRSI, createVWAPSession } from "ta-crypto";
37
37
 
38
+ const sma14 = createSMA(14);
39
+ const ema14 = createEMA(14);
38
40
  const rsi14 = createRSI(14);
41
+ const nextSma = sma14.next(101.25);
42
+ const nextEma = ema14.next(101.25);
39
43
  const nextRsi = rsi14.next(101.25);
40
44
 
41
45
  const vwap = createVWAPSession();
@@ -48,6 +52,42 @@ const nextVwap = vwap.next({
48
52
  });
49
53
  ```
50
54
 
55
+ Websocket-like streaming loop:
56
+
57
+ ```ts
58
+ import { createEMA, createRSI } from "ta-crypto";
59
+
60
+ const ema21 = createEMA(21);
61
+ const rsi14 = createRSI(14);
62
+
63
+ ws.on("message", (tick) => {
64
+ const price = Number(tick.last);
65
+ const e = ema21.next(price);
66
+ const r = rsi14.next(price);
67
+ if (e !== null && r !== null) {
68
+ // strategy signal path
69
+ }
70
+ });
71
+ ```
72
+
73
+ ## Examples
74
+
75
+ Real-world entry points live in `examples/`:
76
+ - `examples/rsi-strategy.mjs`
77
+ - `examples/vwap-session.mjs`
78
+ - `examples/funding-arbitrage.mjs`
79
+ - `examples/rsi-technicalindicators-check.mjs` (external reference check)
80
+
81
+ If you're using ta-crypto in production or experiments, feel free to add your example here.
82
+
83
+ Run examples:
84
+
85
+ ```bash
86
+ npm run example:all
87
+ ```
88
+
89
+ Detailed commands and expected outputs: `examples/README.md`
90
+
51
91
  ## Compatibility
52
92
 
53
93
  `ta-crypto` now ships golden tests (`test/fixtures/golden.json`) to lock behavior across releases.
@@ -75,9 +115,23 @@ Streaming parity:
75
115
 
76
116
  External parity:
77
117
 
78
- | Indicator set | Reference libs | Tolerance |
79
- | --- | --- | --- |
80
- | SMA/EMA/RSI/MACD/BBANDS/ATR/ADX | TA-Lib, pandas-ta, technicalindicators | 1e-8 |
118
+ Policy source: `scripts/compat-policy.json`
119
+
120
+ | Indicator | Burn-in | Tolerance | Blocking refs | Non-blocking refs |
121
+ | --- | --- | --- | --- | --- |
122
+ | SMA(14) | 14 | 1e-10 | TA-Lib, technicalindicators | pandas-ta |
123
+ | EMA(14) | 14 | 1e-10 | TA-Lib, technicalindicators | pandas-ta |
124
+ | RSI(14) | 28 | 5e-2 | TA-Lib, technicalindicators | pandas-ta |
125
+ | MACD(12,26,9) | 80 | 2e-2 | TA-Lib, technicalindicators | pandas-ta |
126
+ | BBANDS(20,2) | 20 | 1e-10 | TA-Lib, technicalindicators | pandas-ta |
127
+ | ATR(14) | 56 | 1.5e-1 | TA-Lib, technicalindicators | pandas-ta |
128
+ | ADX/+DI/-DI(14) | 90 | 1.5 | TA-Lib, technicalindicators | pandas-ta |
129
+
130
+ Warmup and alignment rules:
131
+ - References are left-padded to full series length before comparison.
132
+ - Comparison starts at indicator-specific burn-in and uses only overlapping non-null points.
133
+ - `TA-Lib` and `technicalindicators` are release-blocking checks in CI.
134
+ - `pandas-ta` is telemetry-only (warns when divergent or unavailable by environment).
81
135
 
82
136
  Generate/review vectors:
83
137
 
@@ -149,20 +203,49 @@ Limitations:
149
203
  - Orderflow proxies infer pressure from candle direction and volume; they are not a replacement for L2/L3 order book data.
150
204
  - Different libraries use different warmup conventions; comparisons use overlapping non-null windows.
151
205
 
152
- ## Candle Contracts
206
+ ## Typing and Inputs
153
207
 
154
- Use typed candles plus helpers:
208
+ `ta-crypto` public APIs accept two input styles:
209
+ - Primitive arrays (`number[]`) for low-level control.
210
+ - Candle objects with long keys (`open/high/low/close/volume/time`) or aliases (`o/h/l/c/v/t`).
211
+
212
+ Single-series APIs (for example `sma`, `ema`, `rsi`, `macd`, `bbands`) accept:
213
+
214
+ ```ts
215
+ import { rsi } from "ta-crypto";
216
+
217
+ const close = [101, 102, 103, 104];
218
+ const candles = [{ o: 100, h: 102, l: 99, c: 101, v: 10, t: 1 }];
219
+
220
+ rsi(close, 14);
221
+ rsi(candles, 14); // uses candle close/c
222
+ ```
223
+
224
+ OHLC/OHLCV APIs (for example `vwap`, `stoch`, `atr`, `natr`, `mfi`, `adx`) accept:
225
+
226
+ ```ts
227
+ import { atr, vwap } from "ta-crypto";
228
+
229
+ atr([102, 103], [99, 100], [101, 102], 14);
230
+ atr([{ open: 100, high: 102, low: 99, close: 101, volume: 10 }], 14);
231
+
232
+ vwap([102, 103], [99, 100], [101, 102], [10, 12]);
233
+ vwap([{ o: 100, h: 102, l: 99, c: 101, v: 10 }]);
234
+ ```
235
+
236
+ Helpers:
155
237
 
156
238
  ```ts
157
239
  import { pluckClose, toOHLCV } from "ta-crypto";
158
240
 
159
241
  const close = pluckClose(candles);
160
- const { open, high, low, close: c, volume } = toOHLCV(candles, 0);
242
+ const ohlcv = toOHLCV(candles, 0);
243
+ const ohlcvFromArrays = toOHLCV({ o: [1, 2], h: [3, 4], l: [0, 1], c: [2, 3], v: [5, 6] });
161
244
  ```
162
245
 
163
246
  Validation:
164
- - Multi-series indicators enforce equal lengths (`assertSameLength`).
165
- - Candle helpers validate finite numeric fields with index-specific error messages.
247
+ - Multi-series APIs enforce equal lengths.
248
+ - Numeric inputs are validated as finite numbers with index-aware messages when possible.
166
249
 
167
250
  ## Module Imports
168
251
 
@@ -170,7 +253,7 @@ Validation:
170
253
  import { sma } from "ta-crypto/indicators";
171
254
  import { vwapSession } from "ta-crypto/crypto";
172
255
  import { toOHLCV } from "ta-crypto/candles";
173
- import { createRSI } from "ta-crypto/stateful";
256
+ import { createSMA, createEMA, createRSI } from "ta-crypto/stateful";
174
257
  ```
175
258
 
176
259
  ## Bench (internal baseline, 10k candles)
@@ -191,6 +274,16 @@ Run locally:
191
274
  npm run bench
192
275
  ```
193
276
 
277
+ ## Contributing
278
+
279
+ ta-crypto is open to contributors.
280
+ If you use it, improve it.
281
+ If you miss something, build it.
282
+ If you're learning, this is a great codebase to practice financial math.
283
+
284
+ Start with `CONTRIBUTING.md` and `ROADMAP.md`.
285
+ If you want to contribute but don't know where to start, pick an indicator from the roadmap and add tests comparing with TA-Lib.
286
+
194
287
  ## Release
195
288
 
196
289
  Publications are gated by GitHub Actions:
@@ -211,6 +304,10 @@ npm run version:patch
211
304
  npm run release
212
305
  ```
213
306
 
307
+ ## Trust and Verification
308
+
309
+ For release integrity, compatibility policy, and verification workflow, see `docs/trust.md`.
310
+
214
311
  ## License
215
312
 
216
313
  MIT
package/dist/api.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type { Candle, OHLCVInput, PriceInput } from "./types.js";
2
+ type OHLCInput = Candle[] | OHLCVInput;
3
+ export declare function sma(input: PriceInput, length?: number): Array<number | null>;
4
+ export declare function ema(input: PriceInput, length?: number): Array<number | null>;
5
+ export declare function rma(input: PriceInput, length?: number): Array<number | null>;
6
+ export declare function bbands(input: PriceInput, length?: number, std?: number): {
7
+ basis: (number | null)[];
8
+ upper: (number | null)[];
9
+ lower: (number | null)[];
10
+ };
11
+ export declare function macd(input: PriceInput, fast?: number, slow?: number, signal?: number): {
12
+ macd: (number | null)[];
13
+ signal: (number | null)[];
14
+ histogram: (number | null)[];
15
+ };
16
+ export declare function rsi(input: PriceInput, length?: number): Array<number | null>;
17
+ export declare function logReturn(input: PriceInput, cumulative?: boolean): Array<number | null>;
18
+ export declare function percentReturn(input: PriceInput, cumulative?: boolean): Array<number | null>;
19
+ export declare function realizedVolatility(input: PriceInput, length?: number, periodsPerYear?: number): Array<number | null>;
20
+ export declare function hl2(high: number[], low: number[]): Array<number | null>;
21
+ export declare function hl2(input: OHLCInput): Array<number | null>;
22
+ export declare function hlc3(high: number[], low: number[], close: number[]): Array<number | null>;
23
+ export declare function hlc3(input: OHLCInput): Array<number | null>;
24
+ export declare function ohlc4(open: number[], high: number[], low: number[], close: number[]): Array<number | null>;
25
+ export declare function ohlc4(input: OHLCInput): Array<number | null>;
26
+ export declare function vwap(high: number[], low: number[], close: number[], volume: number[], length?: number): Array<number | null>;
27
+ export declare function vwap(input: OHLCInput, length?: number): Array<number | null>;
28
+ export declare function stoch(high: number[], low: number[], close: number[], kLength?: number, dLength?: number): {
29
+ k: Array<number | null>;
30
+ d: Array<number | null>;
31
+ };
32
+ export declare function stoch(input: OHLCInput, kLength?: number, dLength?: number): {
33
+ k: Array<number | null>;
34
+ d: Array<number | null>;
35
+ };
36
+ export declare function trueRange(high: number[], low: number[], close: number[]): Array<number | null>;
37
+ export declare function trueRange(input: OHLCInput): Array<number | null>;
38
+ export declare function atr(high: number[], low: number[], close: number[], length?: number): Array<number | null>;
39
+ export declare function atr(input: OHLCInput, length?: number): Array<number | null>;
40
+ export declare function natr(high: number[], low: number[], close: number[], length?: number): Array<number | null>;
41
+ export declare function natr(input: OHLCInput, length?: number): Array<number | null>;
42
+ export declare function obv(close: number[], volume: number[]): Array<number | null>;
43
+ export declare function obv(input: OHLCInput): Array<number | null>;
44
+ export declare function mfi(high: number[], low: number[], close: number[], volume: number[], length?: number): Array<number | null>;
45
+ export declare function mfi(input: OHLCInput, length?: number): Array<number | null>;
46
+ export declare function adx(high: number[], low: number[], close: number[], length?: number): {
47
+ adx: Array<number | null>;
48
+ plusDI: Array<number | null>;
49
+ minusDI: Array<number | null>;
50
+ };
51
+ export declare function adx(input: OHLCInput, length?: number): {
52
+ adx: Array<number | null>;
53
+ plusDI: Array<number | null>;
54
+ minusDI: Array<number | null>;
55
+ };
56
+ export declare function vwapSession(high: number[], low: number[], close: number[], volume: number[], session: Array<string | number>): Array<number | null>;
57
+ export declare function vwapSession(input: OHLCInput, session: Array<string | number>): Array<number | null>;
58
+ export declare function signedVolume(open: number[], close: number[], volume: number[]): Array<number | null>;
59
+ export declare function signedVolume(input: OHLCInput): Array<number | null>;
60
+ export declare function volumeDelta(open: number[], close: number[], volume: number[], length?: number): Array<number | null>;
61
+ export declare function volumeDelta(input: OHLCInput, length?: number): Array<number | null>;
62
+ export declare function orderflowImbalance(open: number[], close: number[], volume: number[], length?: number): Array<number | null>;
63
+ export declare function orderflowImbalance(input: OHLCInput, length?: number): Array<number | null>;
64
+ export declare function fundingRateCumulative(values: number[]): Array<number | null>;
65
+ export declare function fundingRateAPR(values: number[], periodsPerYear?: number): Array<number | null>;
66
+ export declare function volatilityRegime(input: PriceInput, length?: number, periodsPerYear?: number, lowZ?: number, highZ?: number): Array<number | null>;
67
+ export {};
package/dist/api.js ADDED
@@ -0,0 +1,241 @@
1
+ import { assertFiniteSeries } from "./core/math.js";
2
+ import { normalizePrice, toOHLCV } from "./candles.js";
3
+ import { sma as coreSma, ema as coreEma, rma as coreRma, hl2 as coreHl2, hlc3 as coreHlc3, ohlc4 as coreOhlc4, vwap as coreVwap, bbands as coreBbands } from "./core/overlap.js";
4
+ import { rsi as coreRsi, macd as coreMacd, stoch as coreStoch } from "./core/momentum.js";
5
+ import { trueRange as coreTrueRange, atr as coreAtr, natr as coreNatr } from "./core/volatility.js";
6
+ import { logReturn as coreLogReturn, percentReturn as corePercentReturn, realizedVolatility as coreRealizedVolatility } from "./core/performance.js";
7
+ import { obv as coreObv, mfi as coreMfi } from "./core/volume.js";
8
+ import { adx as coreAdx } from "./core/trend.js";
9
+ import { vwapSession as coreVwapSession, fundingRateCumulative as coreFundingRateCumulative, fundingRateAPR as coreFundingRateAPR, volatilityRegime as coreVolatilityRegime, signedVolume as coreSignedVolume, volumeDelta as coreVolumeDelta, orderflowImbalance as coreOrderflowImbalance } from "./core/crypto.js";
10
+ function isNumericArray(input) {
11
+ return Array.isArray(input) && (input.length === 0 || typeof input[0] === "number");
12
+ }
13
+ function parseHLC(input, low, close) {
14
+ if (isNumericArray(input)) {
15
+ if (!low || !close) {
16
+ throw new Error("Expected high, low, close arrays or candles/OHLCV object input");
17
+ }
18
+ assertFiniteSeries("high", input);
19
+ assertFiniteSeries("low", low);
20
+ assertFiniteSeries("close", close);
21
+ return { high: input, low, close };
22
+ }
23
+ return toOHLCV(input);
24
+ }
25
+ function parseOHLC(input, high, low, close) {
26
+ if (isNumericArray(input)) {
27
+ if (!high || !low || !close) {
28
+ throw new Error("Expected open, high, low, close arrays or candles/OHLCV object input");
29
+ }
30
+ assertFiniteSeries("open", input);
31
+ assertFiniteSeries("high", high);
32
+ assertFiniteSeries("low", low);
33
+ assertFiniteSeries("close", close);
34
+ return { open: input, high, low, close };
35
+ }
36
+ return toOHLCV(input);
37
+ }
38
+ function parseHLCV(input, low, close, volume) {
39
+ if (isNumericArray(input)) {
40
+ if (!low || !close || !volume) {
41
+ throw new Error("Expected high, low, close, volume arrays or candles/OHLCV object input");
42
+ }
43
+ assertFiniteSeries("high", input);
44
+ assertFiniteSeries("low", low);
45
+ assertFiniteSeries("close", close);
46
+ assertFiniteSeries("volume", volume);
47
+ return { high: input, low, close, volume };
48
+ }
49
+ return toOHLCV(input);
50
+ }
51
+ function parseOCV(input, close, volume) {
52
+ if (isNumericArray(input)) {
53
+ if (!close || !volume) {
54
+ throw new Error("Expected open, close, volume arrays or candles/OHLCV object input");
55
+ }
56
+ assertFiniteSeries("open", input);
57
+ assertFiniteSeries("close", close);
58
+ assertFiniteSeries("volume", volume);
59
+ return { open: input, close, volume };
60
+ }
61
+ return toOHLCV(input);
62
+ }
63
+ export function sma(input, length = 14) {
64
+ return coreSma(normalizePrice(input), length);
65
+ }
66
+ export function ema(input, length = 14) {
67
+ return coreEma(normalizePrice(input), length);
68
+ }
69
+ export function rma(input, length = 14) {
70
+ return coreRma(normalizePrice(input), length);
71
+ }
72
+ export function bbands(input, length = 20, std = 2) {
73
+ return coreBbands(normalizePrice(input), length, std);
74
+ }
75
+ export function macd(input, fast = 12, slow = 26, signal = 9) {
76
+ return coreMacd(normalizePrice(input), fast, slow, signal);
77
+ }
78
+ export function rsi(input, length = 14) {
79
+ return coreRsi(normalizePrice(input), length);
80
+ }
81
+ export function logReturn(input, cumulative = false) {
82
+ return coreLogReturn(normalizePrice(input), cumulative);
83
+ }
84
+ export function percentReturn(input, cumulative = false) {
85
+ return corePercentReturn(normalizePrice(input), cumulative);
86
+ }
87
+ export function realizedVolatility(input, length = 30, periodsPerYear = 365) {
88
+ return coreRealizedVolatility(normalizePrice(input), length, periodsPerYear);
89
+ }
90
+ export function hl2(input, low) {
91
+ if (isNumericArray(input)) {
92
+ if (!low)
93
+ throw new Error("Expected high + low arrays or candles/OHLCV object input");
94
+ return coreHl2(input, low);
95
+ }
96
+ const ohlcv = toOHLCV(input);
97
+ return coreHl2(ohlcv.high, ohlcv.low);
98
+ }
99
+ export function hlc3(input, low, close) {
100
+ const ohlcv = parseHLC(input, low, close);
101
+ return coreHlc3(ohlcv.high, ohlcv.low, ohlcv.close);
102
+ }
103
+ export function ohlc4(input, high, low, close) {
104
+ const ohlcv = parseOHLC(input, high, low, close);
105
+ return coreOhlc4(ohlcv.open, ohlcv.high, ohlcv.low, ohlcv.close);
106
+ }
107
+ export function vwap(input, lowOrLength, close, volume, length) {
108
+ if (isNumericArray(input)) {
109
+ const low = Array.isArray(lowOrLength) ? lowOrLength : undefined;
110
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
111
+ const ohlcv = parseHLCV(input, low, close, volume);
112
+ return coreVwap(ohlcv.high, ohlcv.low, ohlcv.close, ohlcv.volume, resolvedLength);
113
+ }
114
+ const ohlcv = toOHLCV(input);
115
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
116
+ return coreVwap(ohlcv.high, ohlcv.low, ohlcv.close, ohlcv.volume, resolvedLength);
117
+ }
118
+ export function stoch(input, lowOrKLength, closeOrDLength, kLength = 14, dLength = 3) {
119
+ if (isNumericArray(input)) {
120
+ const low = Array.isArray(lowOrKLength) ? lowOrKLength : undefined;
121
+ const close = Array.isArray(closeOrDLength) ? closeOrDLength : undefined;
122
+ const resolvedK = typeof lowOrKLength === "number" ? lowOrKLength : kLength;
123
+ const resolvedD = typeof closeOrDLength === "number" ? closeOrDLength : dLength;
124
+ const ohlcv = parseHLC(input, low, close);
125
+ return coreStoch(ohlcv.high, ohlcv.low, ohlcv.close, resolvedK, resolvedD);
126
+ }
127
+ const resolvedK = typeof lowOrKLength === "number" ? lowOrKLength : kLength;
128
+ const resolvedD = typeof closeOrDLength === "number" ? closeOrDLength : dLength;
129
+ const ohlcv = toOHLCV(input);
130
+ return coreStoch(ohlcv.high, ohlcv.low, ohlcv.close, resolvedK, resolvedD);
131
+ }
132
+ export function trueRange(input, low, close) {
133
+ const ohlcv = parseHLC(input, low, close);
134
+ return coreTrueRange(ohlcv.high, ohlcv.low, ohlcv.close);
135
+ }
136
+ export function atr(input, lowOrLength, close, length = 14) {
137
+ if (isNumericArray(input)) {
138
+ const low = Array.isArray(lowOrLength) ? lowOrLength : undefined;
139
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
140
+ const ohlcv = parseHLC(input, low, close);
141
+ return coreAtr(ohlcv.high, ohlcv.low, ohlcv.close, resolvedLength);
142
+ }
143
+ const ohlcv = toOHLCV(input);
144
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
145
+ return coreAtr(ohlcv.high, ohlcv.low, ohlcv.close, resolvedLength);
146
+ }
147
+ export function natr(input, lowOrLength, close, length = 14) {
148
+ if (isNumericArray(input)) {
149
+ const low = Array.isArray(lowOrLength) ? lowOrLength : undefined;
150
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
151
+ const ohlcv = parseHLC(input, low, close);
152
+ return coreNatr(ohlcv.high, ohlcv.low, ohlcv.close, resolvedLength);
153
+ }
154
+ const ohlcv = toOHLCV(input);
155
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
156
+ return coreNatr(ohlcv.high, ohlcv.low, ohlcv.close, resolvedLength);
157
+ }
158
+ export function obv(input, volume) {
159
+ if (isNumericArray(input)) {
160
+ if (!volume)
161
+ throw new Error("Expected close + volume arrays or candles/OHLCV object input");
162
+ return coreObv(input, volume);
163
+ }
164
+ const ohlcv = toOHLCV(input);
165
+ return coreObv(ohlcv.close, ohlcv.volume);
166
+ }
167
+ export function mfi(input, lowOrLength, close, volume, length = 14) {
168
+ if (isNumericArray(input)) {
169
+ const low = Array.isArray(lowOrLength) ? lowOrLength : undefined;
170
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
171
+ const ohlcv = parseHLCV(input, low, close, volume);
172
+ return coreMfi(ohlcv.high, ohlcv.low, ohlcv.close, ohlcv.volume, resolvedLength);
173
+ }
174
+ const ohlcv = toOHLCV(input);
175
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
176
+ return coreMfi(ohlcv.high, ohlcv.low, ohlcv.close, ohlcv.volume, resolvedLength);
177
+ }
178
+ export function adx(input, lowOrLength, close, length = 14) {
179
+ if (isNumericArray(input)) {
180
+ const low = Array.isArray(lowOrLength) ? lowOrLength : undefined;
181
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
182
+ const ohlcv = parseHLC(input, low, close);
183
+ return coreAdx(ohlcv.high, ohlcv.low, ohlcv.close, resolvedLength);
184
+ }
185
+ const ohlcv = toOHLCV(input);
186
+ const resolvedLength = typeof lowOrLength === "number" ? lowOrLength : length;
187
+ return coreAdx(ohlcv.high, ohlcv.low, ohlcv.close, resolvedLength);
188
+ }
189
+ export function vwapSession(input, lowOrSession, close, volume, session) {
190
+ if (isNumericArray(input)) {
191
+ if (!isNumericArray(lowOrSession)) {
192
+ throw new Error("Expected high, low, close, volume, session arrays");
193
+ }
194
+ if (!session || !close || !volume) {
195
+ throw new Error("Expected high, low, close, volume, session arrays");
196
+ }
197
+ return coreVwapSession(input, lowOrSession, close, volume, session);
198
+ }
199
+ if (!Array.isArray(lowOrSession)) {
200
+ throw new Error("session must be an array");
201
+ }
202
+ const ohlcv = toOHLCV(input);
203
+ return coreVwapSession(ohlcv.high, ohlcv.low, ohlcv.close, ohlcv.volume, lowOrSession);
204
+ }
205
+ export function signedVolume(input, close, volume) {
206
+ const ohlcv = parseOCV(input, close, volume);
207
+ return coreSignedVolume(ohlcv.open, ohlcv.close, ohlcv.volume);
208
+ }
209
+ export function volumeDelta(input, closeOrLength, volume, length = 14) {
210
+ if (isNumericArray(input)) {
211
+ const close = Array.isArray(closeOrLength) ? closeOrLength : undefined;
212
+ const resolvedLength = typeof closeOrLength === "number" ? closeOrLength : length;
213
+ const ohlcv = parseOCV(input, close, volume);
214
+ return coreVolumeDelta(ohlcv.open, ohlcv.close, ohlcv.volume, resolvedLength);
215
+ }
216
+ const ohlcv = toOHLCV(input);
217
+ const resolvedLength = typeof closeOrLength === "number" ? closeOrLength : length;
218
+ return coreVolumeDelta(ohlcv.open, ohlcv.close, ohlcv.volume, resolvedLength);
219
+ }
220
+ export function orderflowImbalance(input, closeOrLength, volume, length = 14) {
221
+ if (isNumericArray(input)) {
222
+ const close = Array.isArray(closeOrLength) ? closeOrLength : undefined;
223
+ const resolvedLength = typeof closeOrLength === "number" ? closeOrLength : length;
224
+ const ohlcv = parseOCV(input, close, volume);
225
+ return coreOrderflowImbalance(ohlcv.open, ohlcv.close, ohlcv.volume, resolvedLength);
226
+ }
227
+ const ohlcv = toOHLCV(input);
228
+ const resolvedLength = typeof closeOrLength === "number" ? closeOrLength : length;
229
+ return coreOrderflowImbalance(ohlcv.open, ohlcv.close, ohlcv.volume, resolvedLength);
230
+ }
231
+ export function fundingRateCumulative(values) {
232
+ assertFiniteSeries("values", values);
233
+ return coreFundingRateCumulative(values);
234
+ }
235
+ export function fundingRateAPR(values, periodsPerYear = 365 * 3) {
236
+ assertFiniteSeries("values", values);
237
+ return coreFundingRateAPR(values, periodsPerYear);
238
+ }
239
+ export function volatilityRegime(input, length = 30, periodsPerYear = 365, lowZ = -0.5, highZ = 0.5) {
240
+ return coreVolatilityRegime(normalizePrice(input), length, periodsPerYear, lowZ, highZ);
241
+ }
package/dist/candles.d.ts CHANGED
@@ -1,15 +1,9 @@
1
- import type { Candle } from "./types.js";
2
- export type OHLCV = {
3
- open: number[];
4
- high: number[];
5
- low: number[];
6
- close: number[];
7
- volume: number[];
8
- time: Array<number | string | Date | undefined>;
9
- };
1
+ import type { Candle, OHLCV, OHLCVInput, PriceInput } from "./types.js";
2
+ export declare function isCandleArray(input: PriceInput | OHLCVInput): input is Candle[];
3
+ export declare function normalizePrice(input: PriceInput, name?: string): number[];
10
4
  export declare function pluckOpen(candles: Candle[]): number[];
11
5
  export declare function pluckHigh(candles: Candle[]): number[];
12
6
  export declare function pluckLow(candles: Candle[]): number[];
13
7
  export declare function pluckClose(candles: Candle[]): number[];
14
8
  export declare function pluckVolume(candles: Candle[], fallback?: number): number[];
15
- export declare function toOHLCV(candles: Candle[], volumeFallback?: number): OHLCV;
9
+ export declare function toOHLCV(input: Candle[] | OHLCVInput, volumeFallback?: number): OHLCV;
package/dist/candles.js CHANGED
@@ -1,36 +1,98 @@
1
- import { assertFiniteSeries } from "./core/math.js";
1
+ import { assertFiniteSeries, assertSameLength, isNum } from "./core/math.js";
2
+ function readField(candle, index, name, alias) {
3
+ const value = candle[name] ?? candle[alias];
4
+ if (!isNum(value)) {
5
+ throw new Error(`candles[${index}].${name} (or .${alias}) must be a finite number`);
6
+ }
7
+ return value;
8
+ }
9
+ function readTime(candle) {
10
+ const value = candle.time ?? candle.t;
11
+ return value;
12
+ }
13
+ function hasOHLCVArrays(input) {
14
+ return "open" in input && "high" in input && "low" in input && "close" in input;
15
+ }
16
+ export function isCandleArray(input) {
17
+ return Array.isArray(input) && input.length > 0 && typeof input[0] !== "number";
18
+ }
19
+ export function normalizePrice(input, name = "values") {
20
+ if (Array.isArray(input) && (input.length === 0 || typeof input[0] === "number")) {
21
+ assertFiniteSeries(name, input);
22
+ return input;
23
+ }
24
+ return pluckClose(input);
25
+ }
2
26
  export function pluckOpen(candles) {
3
- const out = candles.map(c => c.open);
27
+ const out = candles.map((candle, index) => readField(candle, index, "open", "o"));
4
28
  assertFiniteSeries("open", out);
5
29
  return out;
6
30
  }
7
31
  export function pluckHigh(candles) {
8
- const out = candles.map(c => c.high);
32
+ const out = candles.map((candle, index) => readField(candle, index, "high", "h"));
9
33
  assertFiniteSeries("high", out);
10
34
  return out;
11
35
  }
12
36
  export function pluckLow(candles) {
13
- const out = candles.map(c => c.low);
37
+ const out = candles.map((candle, index) => readField(candle, index, "low", "l"));
14
38
  assertFiniteSeries("low", out);
15
39
  return out;
16
40
  }
17
41
  export function pluckClose(candles) {
18
- const out = candles.map(c => c.close);
42
+ const out = candles.map((candle, index) => readField(candle, index, "close", "c"));
19
43
  assertFiniteSeries("close", out);
20
44
  return out;
21
45
  }
22
46
  export function pluckVolume(candles, fallback = 0) {
23
- const out = candles.map(c => (c.volume === undefined ? fallback : c.volume));
47
+ if (!isNum(fallback)) {
48
+ throw new Error("volumeFallback must be a finite number");
49
+ }
50
+ const out = candles.map((candle, index) => {
51
+ const value = candle.volume ?? candle.v;
52
+ const normalized = value === undefined ? fallback : value;
53
+ if (!isNum(normalized)) {
54
+ throw new Error(`candles[${index}].volume (or .v) must be a finite number`);
55
+ }
56
+ return normalized;
57
+ });
24
58
  assertFiniteSeries("volume", out);
25
59
  return out;
26
60
  }
27
- export function toOHLCV(candles, volumeFallback = 0) {
28
- return {
29
- open: pluckOpen(candles),
30
- high: pluckHigh(candles),
31
- low: pluckLow(candles),
32
- close: pluckClose(candles),
33
- volume: pluckVolume(candles, volumeFallback),
34
- time: candles.map(c => c.time)
35
- };
61
+ export function toOHLCV(input, volumeFallback = 0) {
62
+ if (Array.isArray(input)) {
63
+ return {
64
+ open: pluckOpen(input),
65
+ high: pluckHigh(input),
66
+ low: pluckLow(input),
67
+ close: pluckClose(input),
68
+ volume: pluckVolume(input, volumeFallback),
69
+ time: input.map(c => readTime(c))
70
+ };
71
+ }
72
+ if (hasOHLCVArrays(input)) {
73
+ assertFiniteSeries("open", input.open);
74
+ assertFiniteSeries("high", input.high);
75
+ assertFiniteSeries("low", input.low);
76
+ assertFiniteSeries("close", input.close);
77
+ const volume = input.volume ?? new Array(input.close.length).fill(volumeFallback);
78
+ assertFiniteSeries("volume", volume);
79
+ assertSameLength(input.open, input.high, input.low, input.close, volume);
80
+ const time = input.time ?? new Array(input.close.length).fill(undefined);
81
+ if (time.length !== input.close.length) {
82
+ throw new Error(`All series must have the same length (expected ${input.close.length}, got ${time.length})`);
83
+ }
84
+ return { open: input.open, high: input.high, low: input.low, close: input.close, volume, time };
85
+ }
86
+ assertFiniteSeries("open", input.o);
87
+ assertFiniteSeries("high", input.h);
88
+ assertFiniteSeries("low", input.l);
89
+ assertFiniteSeries("close", input.c);
90
+ const volume = input.v ?? new Array(input.c.length).fill(volumeFallback);
91
+ assertFiniteSeries("volume", volume);
92
+ assertSameLength(input.o, input.h, input.l, input.c, volume);
93
+ const time = input.t ?? new Array(input.c.length).fill(undefined);
94
+ if (time.length !== input.c.length) {
95
+ throw new Error(`All series must have the same length (expected ${input.c.length}, got ${time.length})`);
96
+ }
97
+ return { open: input.o, high: input.h, low: input.l, close: input.c, volume, time };
36
98
  }
@@ -1,12 +1,9 @@
1
- import { assertSameLength, isNum, makeSeries, mean, stdev } from "./math.js";
1
+ import { assertSameLength, isNum, makeSeries, mean } from "./math.js";
2
+ import { rollingMean, rollingMeanStdDev } from "./rolling.js";
2
3
  export function sma(values, length = 14) {
3
- const out = makeSeries(values.length);
4
4
  if (length <= 0)
5
- return out;
6
- for (let i = length - 1; i < values.length; i++) {
7
- out[i] = mean(values, i - length + 1, i);
8
- }
9
- return out;
5
+ return makeSeries(values.length);
6
+ return rollingMean(values, length);
10
7
  }
11
8
  export function ema(values, length = 14) {
12
9
  const out = makeSeries(values.length);
@@ -90,11 +87,19 @@ export function vwap(high, low, close, volume, length) {
90
87
  return out;
91
88
  }
92
89
  export function bbands(values, length = 20, std = 2) {
93
- const basis = sma(values, length);
94
- const upper = makeSeries(values.length);
90
+ if (length <= 0) {
91
+ return {
92
+ basis: makeSeries(values.length),
93
+ upper: makeSeries(values.length),
94
+ lower: makeSeries(values.length)
95
+ };
96
+ }
97
+ const { mean: basis, stdDev: upper } = rollingMeanStdDev(values, length);
95
98
  const lower = makeSeries(values.length);
96
- for (let i = length - 1; i < values.length; i++) {
97
- const s = stdev(values, i - length + 1, i);
99
+ for (let i = 0; i < values.length; i++) {
100
+ const s = upper[i];
101
+ if (s === null)
102
+ continue;
98
103
  upper[i] = basis[i] + std * s;
99
104
  lower[i] = basis[i] - std * s;
100
105
  }
@@ -0,0 +1,62 @@
1
+ export type RollingValue = number | null;
2
+ export declare class RollingWindow {
3
+ readonly period: number;
4
+ private readonly values;
5
+ private cursor;
6
+ private count;
7
+ constructor(period: number);
8
+ get size(): number;
9
+ get ready(): boolean;
10
+ push(value: number): number | null;
11
+ reset(): void;
12
+ }
13
+ export declare class RollingSum {
14
+ private readonly window;
15
+ private total;
16
+ constructor(period: number);
17
+ get ready(): boolean;
18
+ next(value: number): RollingValue;
19
+ reset(): void;
20
+ }
21
+ export declare class RollingMean {
22
+ readonly period: number;
23
+ private readonly sum;
24
+ constructor(period: number);
25
+ next(value: number): RollingValue;
26
+ reset(): void;
27
+ }
28
+ export declare class RollingStdDev {
29
+ readonly period: number;
30
+ private readonly window;
31
+ private total;
32
+ private totalSquares;
33
+ constructor(period: number);
34
+ next(value: number): RollingValue;
35
+ reset(): void;
36
+ }
37
+ declare class RollingExtremum {
38
+ private readonly period;
39
+ private readonly compare;
40
+ private readonly deque;
41
+ private head;
42
+ private index;
43
+ constructor(period: number, compare: (candidate: number, tail: number) => boolean);
44
+ next(value: number): RollingValue;
45
+ reset(): void;
46
+ }
47
+ export declare class RollingMin extends RollingExtremum {
48
+ constructor(period: number);
49
+ }
50
+ export declare class RollingMax extends RollingExtremum {
51
+ constructor(period: number);
52
+ }
53
+ export declare function rollingSum(values: number[], period: number): RollingValue[];
54
+ export declare function rollingMean(values: number[], period: number): RollingValue[];
55
+ export declare function rollingStdDev(values: number[], period: number): RollingValue[];
56
+ export declare function rollingMeanStdDev(values: number[], period: number): {
57
+ mean: RollingValue[];
58
+ stdDev: RollingValue[];
59
+ };
60
+ export declare function rollingMin(values: number[], period: number): RollingValue[];
61
+ export declare function rollingMax(values: number[], period: number): RollingValue[];
62
+ export {};
@@ -0,0 +1,214 @@
1
+ function assertPeriod(period) {
2
+ if (!Number.isInteger(period) || period <= 0) {
3
+ throw new Error("period must be a positive integer");
4
+ }
5
+ }
6
+ function assertValue(value) {
7
+ if (!Number.isFinite(value)) {
8
+ throw new Error("value must be a finite number");
9
+ }
10
+ }
11
+ export class RollingWindow {
12
+ constructor(period) {
13
+ this.cursor = 0;
14
+ this.count = 0;
15
+ assertPeriod(period);
16
+ this.period = period;
17
+ this.values = new Array(period);
18
+ }
19
+ get size() {
20
+ return this.count;
21
+ }
22
+ get ready() {
23
+ return this.count === this.period;
24
+ }
25
+ push(value) {
26
+ assertValue(value);
27
+ const removed = this.ready ? this.values[this.cursor] : null;
28
+ this.values[this.cursor] = value;
29
+ this.cursor = (this.cursor + 1) % this.period;
30
+ if (this.count < this.period)
31
+ this.count += 1;
32
+ return removed;
33
+ }
34
+ reset() {
35
+ this.cursor = 0;
36
+ this.count = 0;
37
+ }
38
+ }
39
+ export class RollingSum {
40
+ constructor(period) {
41
+ this.total = 0;
42
+ this.window = new RollingWindow(period);
43
+ }
44
+ get ready() {
45
+ return this.window.ready;
46
+ }
47
+ next(value) {
48
+ const removed = this.window.push(value);
49
+ this.total += value - (removed ?? 0);
50
+ return this.ready ? this.total : null;
51
+ }
52
+ reset() {
53
+ this.window.reset();
54
+ this.total = 0;
55
+ }
56
+ }
57
+ export class RollingMean {
58
+ constructor(period) {
59
+ assertPeriod(period);
60
+ this.period = period;
61
+ this.sum = new RollingSum(period);
62
+ }
63
+ next(value) {
64
+ const total = this.sum.next(value);
65
+ return total === null ? null : total / this.period;
66
+ }
67
+ reset() {
68
+ this.sum.reset();
69
+ }
70
+ }
71
+ export class RollingStdDev {
72
+ constructor(period) {
73
+ this.total = 0;
74
+ this.totalSquares = 0;
75
+ assertPeriod(period);
76
+ this.period = period;
77
+ this.window = new RollingWindow(period);
78
+ }
79
+ next(value) {
80
+ const removed = this.window.push(value);
81
+ this.total += value - (removed ?? 0);
82
+ this.totalSquares += value * value - (removed === null ? 0 : removed * removed);
83
+ if (!this.window.ready)
84
+ return null;
85
+ const mean = this.total / this.period;
86
+ const variance = Math.max(0, this.totalSquares / this.period - mean * mean);
87
+ return Math.sqrt(variance);
88
+ }
89
+ reset() {
90
+ this.window.reset();
91
+ this.total = 0;
92
+ this.totalSquares = 0;
93
+ }
94
+ }
95
+ class RollingExtremum {
96
+ constructor(period, compare) {
97
+ this.deque = [];
98
+ this.head = 0;
99
+ this.index = -1;
100
+ assertPeriod(period);
101
+ this.period = period;
102
+ this.compare = compare;
103
+ }
104
+ next(value) {
105
+ assertValue(value);
106
+ this.index += 1;
107
+ const firstValid = this.index - this.period + 1;
108
+ while (this.head < this.deque.length && this.deque[this.head].index < firstValid)
109
+ this.head += 1;
110
+ while (this.deque.length > this.head && this.compare(value, this.deque[this.deque.length - 1].value)) {
111
+ this.deque.pop();
112
+ }
113
+ this.deque.push({ index: this.index, value });
114
+ if (this.head > 64 && this.head * 2 > this.deque.length) {
115
+ this.deque.splice(0, this.head);
116
+ this.head = 0;
117
+ }
118
+ return this.index + 1 < this.period ? null : this.deque[this.head].value;
119
+ }
120
+ reset() {
121
+ this.deque.length = 0;
122
+ this.head = 0;
123
+ this.index = -1;
124
+ }
125
+ }
126
+ export class RollingMin extends RollingExtremum {
127
+ constructor(period) {
128
+ super(period, (candidate, tail) => candidate <= tail);
129
+ }
130
+ }
131
+ export class RollingMax extends RollingExtremum {
132
+ constructor(period) {
133
+ super(period, (candidate, tail) => candidate >= tail);
134
+ }
135
+ }
136
+ function collect(values, engine) {
137
+ return values.map(value => engine.next(value));
138
+ }
139
+ export function rollingSum(values, period) {
140
+ assertPeriod(period);
141
+ const out = new Array(values.length).fill(null);
142
+ let total = 0;
143
+ for (let i = 0; i < values.length; i++) {
144
+ total += values[i];
145
+ if (i >= period)
146
+ total -= values[i - period];
147
+ if (i >= period - 1)
148
+ out[i] = total;
149
+ }
150
+ return out;
151
+ }
152
+ export function rollingMean(values, period) {
153
+ assertPeriod(period);
154
+ const out = new Array(values.length).fill(null);
155
+ let total = 0;
156
+ for (let i = 0; i < values.length; i++) {
157
+ total += values[i];
158
+ if (i >= period)
159
+ total -= values[i - period];
160
+ if (i >= period - 1)
161
+ out[i] = total / period;
162
+ }
163
+ return out;
164
+ }
165
+ export function rollingStdDev(values, period) {
166
+ assertPeriod(period);
167
+ const out = new Array(values.length).fill(null);
168
+ let total = 0;
169
+ let totalSquares = 0;
170
+ for (let i = 0; i < values.length; i++) {
171
+ const value = values[i];
172
+ total += value;
173
+ totalSquares += value * value;
174
+ if (i >= period) {
175
+ const removed = values[i - period];
176
+ total -= removed;
177
+ totalSquares -= removed * removed;
178
+ }
179
+ if (i >= period - 1) {
180
+ const mean = total / period;
181
+ out[i] = Math.sqrt(Math.max(0, totalSquares / period - mean * mean));
182
+ }
183
+ }
184
+ return out;
185
+ }
186
+ export function rollingMeanStdDev(values, period) {
187
+ assertPeriod(period);
188
+ const mean = new Array(values.length).fill(null);
189
+ const stdDev = new Array(values.length).fill(null);
190
+ let total = 0;
191
+ let totalSquares = 0;
192
+ for (let i = 0; i < values.length; i++) {
193
+ const value = values[i];
194
+ total += value;
195
+ totalSquares += value * value;
196
+ if (i >= period) {
197
+ const removed = values[i - period];
198
+ total -= removed;
199
+ totalSquares -= removed * removed;
200
+ }
201
+ if (i >= period - 1) {
202
+ const currentMean = total / period;
203
+ mean[i] = currentMean;
204
+ stdDev[i] = Math.sqrt(Math.max(0, totalSquares / period - currentMean * currentMean));
205
+ }
206
+ }
207
+ return { mean, stdDev };
208
+ }
209
+ export function rollingMin(values, period) {
210
+ return collect(values, new RollingMin(period));
211
+ }
212
+ export function rollingMax(values, period) {
213
+ return collect(values, new RollingMax(period));
214
+ }
package/dist/crypto.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { vwapSession, fundingRateCumulative, fundingRateAPR, volatilityRegime, signedVolume, volumeDelta, orderflowImbalance } from "./core/crypto.js";
1
+ export { vwapSession, fundingRateCumulative, fundingRateAPR, volatilityRegime, signedVolume, volumeDelta, orderflowImbalance } from "./api.js";
2
2
  export { createVWAPSession } from "./stateful.js";
package/dist/crypto.js CHANGED
@@ -1,2 +1,2 @@
1
- export { vwapSession, fundingRateCumulative, fundingRateAPR, volatilityRegime, signedVolume, volumeDelta, orderflowImbalance } from "./core/crypto.js";
1
+ export { vwapSession, fundingRateCumulative, fundingRateAPR, volatilityRegime, signedVolume, volumeDelta, orderflowImbalance } from "./api.js";
2
2
  export { createVWAPSession } from "./stateful.js";
package/dist/index.d.ts CHANGED
@@ -1,64 +1,83 @@
1
- export { sma, ema, rma, hl2, hlc3, ohlc4, vwap, bbands } from "./core/overlap.js";
2
- export { rsi, macd, stoch } from "./core/momentum.js";
3
- export { trueRange, atr, natr } from "./core/volatility.js";
4
- export { logReturn, percentReturn, realizedVolatility } from "./core/performance.js";
5
- export { obv, mfi } from "./core/volume.js";
6
- export { adx } from "./core/trend.js";
7
- export { vwapSession, fundingRateCumulative, fundingRateAPR, volatilityRegime, signedVolume, volumeDelta, orderflowImbalance } from "./core/crypto.js";
1
+ export { sma, ema, rma, hl2, hlc3, ohlc4, vwap, bbands, rsi, macd, stoch, trueRange, atr, natr, logReturn, percentReturn, realizedVolatility, obv, mfi, adx, vwapSession, fundingRateCumulative, fundingRateAPR, volatilityRegime, signedVolume, volumeDelta, orderflowImbalance } from "./api.js";
8
2
  export { pluckOpen, pluckHigh, pluckLow, pluckClose, pluckVolume, toOHLCV } from "./candles.js";
9
- export { createRSI, createVWAPSession } from "./stateful.js";
3
+ export { createSMA, createEMA, createRSI, createVWAPSession } from "./stateful.js";
10
4
  export * from "./types.js";
11
- import * as candles from "./candles.js";
12
5
  import * as stateful from "./stateful.js";
13
6
  export declare const ta: {
7
+ createSMA(period?: number): stateful.StatefulIndicator<number, number | null>;
8
+ createEMA(period?: number): stateful.StatefulIndicator<number, number | null>;
14
9
  createRSI(period?: number): stateful.StatefulIndicator<number, number | null>;
15
10
  createVWAPSession(): stateful.StatefulIndicator<stateful.VWAPSessionInput, number | null>;
11
+ isCandleArray(input: import("./types.js").PriceInput | import("./types.js").OHLCVInput): input is import("./types.js").Candle[];
12
+ normalizePrice(input: import("./types.js").PriceInput, name?: string): number[];
16
13
  pluckOpen(candles: import("./types.js").Candle[]): number[];
17
14
  pluckHigh(candles: import("./types.js").Candle[]): number[];
18
15
  pluckLow(candles: import("./types.js").Candle[]): number[];
19
16
  pluckClose(candles: import("./types.js").Candle[]): number[];
20
17
  pluckVolume(candles: import("./types.js").Candle[], fallback?: number): number[];
21
- toOHLCV(candles: import("./types.js").Candle[], volumeFallback?: number): candles.OHLCV;
22
- vwapSession(high: number[], low: number[], close: number[], volume: number[], session: Array<string | number>): Array<number | null>;
23
- fundingRateCumulative(values: number[]): Array<number | null>;
24
- fundingRateAPR(values: number[], periodsPerYear?: number): Array<number | null>;
25
- volatilityRegime(values: number[], length?: number, periodsPerYear?: number, lowZ?: number, highZ?: number): Array<number | null>;
26
- signedVolume(open: number[], close: number[], volume: number[]): Array<number | null>;
27
- volumeDelta(open: number[], close: number[], volume: number[], length?: number): Array<number | null>;
28
- orderflowImbalance(open: number[], close: number[], volume: number[], length?: number): Array<number | null>;
29
- adx(high: number[], low: number[], close: number[], length?: number): {
30
- adx: (number | null)[];
31
- plusDI: (number | null)[];
32
- minusDI: (number | null)[];
18
+ toOHLCV(input: import("./types.js").Candle[] | import("./types.js").OHLCVInput, volumeFallback?: number): import("./types.js").OHLCV;
19
+ sma(input: import("./types.js").PriceInput, length?: number): Array<number | null>;
20
+ ema(input: import("./types.js").PriceInput, length?: number): Array<number | null>;
21
+ rma(input: import("./types.js").PriceInput, length?: number): Array<number | null>;
22
+ bbands(input: import("./types.js").PriceInput, length?: number, std?: number): {
23
+ basis: (number | null)[];
24
+ upper: (number | null)[];
25
+ lower: (number | null)[];
33
26
  };
34
- obv(close: number[], volume: number[]): Array<number | null>;
35
- mfi(high: number[], low: number[], close: number[], volume: number[], length?: number): Array<number | null>;
36
- logReturn(values: number[], cumulative?: boolean): Array<number | null>;
37
- percentReturn(values: number[], cumulative?: boolean): Array<number | null>;
38
- realizedVolatility(values: number[], length?: number, periodsPerYear?: number): Array<number | null>;
39
- trueRange(high: number[], low: number[], close: number[]): Array<number | null>;
40
- atr(high: number[], low: number[], close: number[], length?: number): Array<number | null>;
41
- natr(high: number[], low: number[], close: number[], length?: number): Array<number | null>;
42
- macd(values: number[], fast?: number, slow?: number, signal?: number): {
27
+ macd(input: import("./types.js").PriceInput, fast?: number, slow?: number, signal?: number): {
43
28
  macd: (number | null)[];
44
29
  signal: (number | null)[];
45
30
  histogram: (number | null)[];
46
31
  };
47
- rsi(values: number[], length?: number): Array<number | null>;
48
- stoch(high: number[], low: number[], close: number[], kLength?: number, dLength?: number): {
49
- k: (number | null)[];
50
- d: (number | null)[];
51
- };
52
- sma(values: number[], length?: number): Array<number | null>;
53
- ema(values: number[], length?: number): Array<number | null>;
54
- rma(values: number[], length?: number): Array<number | null>;
32
+ rsi(input: import("./types.js").PriceInput, length?: number): Array<number | null>;
33
+ logReturn(input: import("./types.js").PriceInput, cumulative?: boolean): Array<number | null>;
34
+ percentReturn(input: import("./types.js").PriceInput, cumulative?: boolean): Array<number | null>;
35
+ realizedVolatility(input: import("./types.js").PriceInput, length?: number, periodsPerYear?: number): Array<number | null>;
55
36
  hl2(high: number[], low: number[]): Array<number | null>;
37
+ hl2(input: import("./types.js").OHLCVInput | import("./types.js").Candle[]): Array<number | null>;
56
38
  hlc3(high: number[], low: number[], close: number[]): Array<number | null>;
39
+ hlc3(input: import("./types.js").OHLCVInput | import("./types.js").Candle[]): Array<number | null>;
57
40
  ohlc4(open: number[], high: number[], low: number[], close: number[]): Array<number | null>;
41
+ ohlc4(input: import("./types.js").OHLCVInput | import("./types.js").Candle[]): Array<number | null>;
58
42
  vwap(high: number[], low: number[], close: number[], volume: number[], length?: number): Array<number | null>;
59
- bbands(values: number[], length?: number, std?: number): {
60
- basis: (number | null)[];
61
- upper: (number | null)[];
62
- lower: (number | null)[];
43
+ vwap(input: import("./types.js").OHLCVInput | import("./types.js").Candle[], length?: number): Array<number | null>;
44
+ stoch(high: number[], low: number[], close: number[], kLength?: number, dLength?: number): {
45
+ k: Array<number | null>;
46
+ d: Array<number | null>;
47
+ };
48
+ stoch(input: import("./types.js").OHLCVInput | import("./types.js").Candle[], kLength?: number, dLength?: number): {
49
+ k: Array<number | null>;
50
+ d: Array<number | null>;
51
+ };
52
+ trueRange(high: number[], low: number[], close: number[]): Array<number | null>;
53
+ trueRange(input: import("./types.js").OHLCVInput | import("./types.js").Candle[]): Array<number | null>;
54
+ atr(high: number[], low: number[], close: number[], length?: number): Array<number | null>;
55
+ atr(input: import("./types.js").OHLCVInput | import("./types.js").Candle[], length?: number): Array<number | null>;
56
+ natr(high: number[], low: number[], close: number[], length?: number): Array<number | null>;
57
+ natr(input: import("./types.js").OHLCVInput | import("./types.js").Candle[], length?: number): Array<number | null>;
58
+ obv(close: number[], volume: number[]): Array<number | null>;
59
+ obv(input: import("./types.js").OHLCVInput | import("./types.js").Candle[]): Array<number | null>;
60
+ mfi(high: number[], low: number[], close: number[], volume: number[], length?: number): Array<number | null>;
61
+ mfi(input: import("./types.js").OHLCVInput | import("./types.js").Candle[], length?: number): Array<number | null>;
62
+ adx(high: number[], low: number[], close: number[], length?: number): {
63
+ adx: Array<number | null>;
64
+ plusDI: Array<number | null>;
65
+ minusDI: Array<number | null>;
66
+ };
67
+ adx(input: import("./types.js").OHLCVInput | import("./types.js").Candle[], length?: number): {
68
+ adx: Array<number | null>;
69
+ plusDI: Array<number | null>;
70
+ minusDI: Array<number | null>;
63
71
  };
72
+ vwapSession(high: number[], low: number[], close: number[], volume: number[], session: Array<string | number>): Array<number | null>;
73
+ vwapSession(input: import("./types.js").OHLCVInput | import("./types.js").Candle[], session: Array<string | number>): Array<number | null>;
74
+ signedVolume(open: number[], close: number[], volume: number[]): Array<number | null>;
75
+ signedVolume(input: import("./types.js").OHLCVInput | import("./types.js").Candle[]): Array<number | null>;
76
+ volumeDelta(open: number[], close: number[], volume: number[], length?: number): Array<number | null>;
77
+ volumeDelta(input: import("./types.js").OHLCVInput | import("./types.js").Candle[], length?: number): Array<number | null>;
78
+ orderflowImbalance(open: number[], close: number[], volume: number[], length?: number): Array<number | null>;
79
+ orderflowImbalance(input: import("./types.js").OHLCVInput | import("./types.js").Candle[], length?: number): Array<number | null>;
80
+ fundingRateCumulative(values: number[]): Array<number | null>;
81
+ fundingRateAPR(values: number[], periodsPerYear?: number): Array<number | null>;
82
+ volatilityRegime(input: import("./types.js").PriceInput, length?: number, periodsPerYear?: number, lowZ?: number, highZ?: number): Array<number | null>;
64
83
  };
package/dist/index.js CHANGED
@@ -1,30 +1,12 @@
1
- export { sma, ema, rma, hl2, hlc3, ohlc4, vwap, bbands } from "./core/overlap.js";
2
- export { rsi, macd, stoch } from "./core/momentum.js";
3
- export { trueRange, atr, natr } from "./core/volatility.js";
4
- export { logReturn, percentReturn, realizedVolatility } from "./core/performance.js";
5
- export { obv, mfi } from "./core/volume.js";
6
- export { adx } from "./core/trend.js";
7
- export { vwapSession, fundingRateCumulative, fundingRateAPR, volatilityRegime, signedVolume, volumeDelta, orderflowImbalance } from "./core/crypto.js";
1
+ export { sma, ema, rma, hl2, hlc3, ohlc4, vwap, bbands, rsi, macd, stoch, trueRange, atr, natr, logReturn, percentReturn, realizedVolatility, obv, mfi, adx, vwapSession, fundingRateCumulative, fundingRateAPR, volatilityRegime, signedVolume, volumeDelta, orderflowImbalance } from "./api.js";
8
2
  export { pluckOpen, pluckHigh, pluckLow, pluckClose, pluckVolume, toOHLCV } from "./candles.js";
9
- export { createRSI, createVWAPSession } from "./stateful.js";
3
+ export { createSMA, createEMA, createRSI, createVWAPSession } from "./stateful.js";
10
4
  export * from "./types.js";
11
- import * as overlap from "./core/overlap.js";
12
- import * as momentum from "./core/momentum.js";
13
- import * as volatility from "./core/volatility.js";
14
- import * as performance from "./core/performance.js";
15
- import * as volume from "./core/volume.js";
16
- import * as trend from "./core/trend.js";
17
- import * as crypto from "./core/crypto.js";
5
+ import * as api from "./api.js";
18
6
  import * as candles from "./candles.js";
19
7
  import * as stateful from "./stateful.js";
20
8
  export const ta = {
21
- ...overlap,
22
- ...momentum,
23
- ...volatility,
24
- ...performance,
25
- ...volume,
26
- ...trend,
27
- ...crypto,
9
+ ...api,
28
10
  ...candles,
29
11
  ...stateful
30
12
  };
@@ -1,6 +1 @@
1
- export { sma, ema, rma, hl2, hlc3, ohlc4, vwap, bbands } from "./core/overlap.js";
2
- export { rsi, macd, stoch } from "./core/momentum.js";
3
- export { trueRange, atr, natr } from "./core/volatility.js";
4
- export { logReturn, percentReturn, realizedVolatility } from "./core/performance.js";
5
- export { obv, mfi } from "./core/volume.js";
6
- export { adx } from "./core/trend.js";
1
+ export { sma, ema, rma, hl2, hlc3, ohlc4, vwap, bbands, rsi, macd, stoch, trueRange, atr, natr, logReturn, percentReturn, realizedVolatility, obv, mfi, adx } from "./api.js";
@@ -1,6 +1 @@
1
- export { sma, ema, rma, hl2, hlc3, ohlc4, vwap, bbands } from "./core/overlap.js";
2
- export { rsi, macd, stoch } from "./core/momentum.js";
3
- export { trueRange, atr, natr } from "./core/volatility.js";
4
- export { logReturn, percentReturn, realizedVolatility } from "./core/performance.js";
5
- export { obv, mfi } from "./core/volume.js";
6
- export { adx } from "./core/trend.js";
1
+ export { sma, ema, rma, hl2, hlc3, ohlc4, vwap, bbands, rsi, macd, stoch, trueRange, atr, natr, logReturn, percentReturn, realizedVolatility, obv, mfi, adx } from "./api.js";
@@ -2,6 +2,8 @@ export type StatefulIndicator<TIn, TOut> = {
2
2
  next(value: TIn): TOut;
3
3
  reset(): void;
4
4
  };
5
+ export declare function createSMA(period?: number): StatefulIndicator<number, number | null>;
6
+ export declare function createEMA(period?: number): StatefulIndicator<number, number | null>;
5
7
  export declare function createRSI(period?: number): StatefulIndicator<number, number | null>;
6
8
  export type VWAPSessionInput = {
7
9
  high: number;
package/dist/stateful.js CHANGED
@@ -1,3 +1,53 @@
1
+ import { RollingMean } from "./core/rolling.js";
2
+ export function createSMA(period = 14) {
3
+ if (period <= 0) {
4
+ throw new Error("period must be > 0");
5
+ }
6
+ const rolling = new RollingMean(period);
7
+ return {
8
+ next(value) {
9
+ if (!Number.isFinite(value)) {
10
+ throw new Error("value must be a finite number");
11
+ }
12
+ return rolling.next(value);
13
+ },
14
+ reset() {
15
+ rolling.reset();
16
+ }
17
+ };
18
+ }
19
+ export function createEMA(period = 14) {
20
+ if (period <= 0) {
21
+ throw new Error("period must be > 0");
22
+ }
23
+ const k = 2 / (period + 1);
24
+ let seedSum = 0;
25
+ let seedCount = 0;
26
+ let prev = null;
27
+ return {
28
+ next(value) {
29
+ if (!Number.isFinite(value)) {
30
+ throw new Error("value must be a finite number");
31
+ }
32
+ if (prev === null) {
33
+ seedSum += value;
34
+ seedCount += 1;
35
+ if (seedCount < period) {
36
+ return null;
37
+ }
38
+ prev = seedSum / period;
39
+ return prev;
40
+ }
41
+ prev = (value - prev) * k + prev;
42
+ return prev;
43
+ },
44
+ reset() {
45
+ seedSum = 0;
46
+ seedCount = 0;
47
+ prev = null;
48
+ }
49
+ };
50
+ }
1
51
  export function createRSI(period = 14) {
2
52
  if (period <= 0) {
3
53
  throw new Error("period must be > 0");
package/dist/types.d.ts CHANGED
@@ -1,10 +1,38 @@
1
1
  export type NumericSeries = number[];
2
2
  export type Series = Array<number | null>;
3
- export type Candle = {
4
- time?: number | string | Date;
3
+ export type TimeValue = number | string | Date;
4
+ export type CandleObject = {
5
5
  open: number;
6
6
  high: number;
7
7
  low: number;
8
8
  close: number;
9
9
  volume?: number;
10
+ time?: TimeValue;
10
11
  };
12
+ export type CandleAlias = {
13
+ o: number;
14
+ h: number;
15
+ l: number;
16
+ c: number;
17
+ v?: number;
18
+ t?: TimeValue;
19
+ };
20
+ export type Candle = CandleObject | CandleAlias;
21
+ export type OHLCV = {
22
+ open: number[];
23
+ high: number[];
24
+ low: number[];
25
+ close: number[];
26
+ volume: number[];
27
+ time: Array<TimeValue | undefined>;
28
+ };
29
+ export type OHLCVAlias = {
30
+ o: number[];
31
+ h: number[];
32
+ l: number[];
33
+ c: number[];
34
+ v?: number[];
35
+ t?: Array<TimeValue | undefined>;
36
+ };
37
+ export type OHLCVInput = OHLCV | OHLCVAlias;
38
+ export type PriceInput = NumericSeries | Candle[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ta-crypto",
3
- "version": "0.2.3",
3
+ "version": "0.3.2",
4
4
  "description": "Technical analysis for crypto markets in Node.js",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,11 +50,17 @@
50
50
  "generate:golden": "npm run build && node ./scripts/generate-golden.js",
51
51
  "generate:compat": "npm run build && node ./scripts/export-compat-vectors.js",
52
52
  "bench": "npm run build && node ./scripts/bench.js",
53
+ "bench:rolling": "npm run build && node ./scripts/bench-rolling.js",
53
54
  "test": "npm run build && node --test test/*.test.mjs",
54
55
  "test:golden": "npm run build && node --test test/golden.test.mjs",
55
56
  "test:compat:technicalindicators": "npm run generate:compat && node ./scripts/compare-technicalindicators.js",
56
57
  "test:compat:python": "npm run generate:compat && python ./scripts/compare-python-refs.py",
57
- "test:compat": "npm run test:compat:technicalindicators && npm run test:compat:python"
58
+ "test:compat": "npm run test:compat:technicalindicators && npm run test:compat:python",
59
+ "example:rsi": "npm run build && node ./examples/rsi-strategy.mjs",
60
+ "example:vwap": "npm run build && node ./examples/vwap-session.mjs",
61
+ "example:funding": "npm run build && node ./examples/funding-arbitrage.mjs",
62
+ "example:rsi:compat": "npm run build && node ./examples/rsi-technicalindicators-check.mjs",
63
+ "example:all": "npm run example:rsi && npm run example:vwap && npm run example:funding && npm run example:rsi:compat"
58
64
  },
59
65
  "devDependencies": {
60
66
  "rimraf": "^5.0.5",