oakscriptjs 0.10.0 → 0.11.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/dist/cjs/box/index.d.ts.map +1 -1
- package/dist/cjs/box/index.js +7 -1
- package/dist/cjs/box/index.js.map +2 -2
- package/dist/cjs/callsite/index.d.ts +73 -32
- package/dist/cjs/callsite/index.d.ts.map +1 -1
- package/dist/cjs/callsite/index.js +69 -65
- package/dist/cjs/callsite/index.js.map +2 -2
- package/dist/cjs/drawing/registry.d.ts +75 -0
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +139 -6
- package/dist/cjs/drawing/registry.js.map +2 -2
- package/dist/cjs/index.d.ts +8 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +30 -1
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/label/index.d.ts.map +1 -1
- package/dist/cjs/label/index.js +3 -0
- package/dist/cjs/label/index.js.map +2 -2
- package/dist/cjs/line/index.d.ts.map +1 -1
- package/dist/cjs/line/index.js +7 -1
- package/dist/cjs/line/index.js.map +2 -2
- package/dist/cjs/math/index.d.ts.map +1 -1
- package/dist/cjs/math/index.js +2 -0
- package/dist/cjs/math/index.js.map +2 -2
- package/dist/cjs/polyline/index.d.ts.map +1 -1
- package/dist/cjs/polyline/index.js +3 -0
- package/dist/cjs/polyline/index.js.map +2 -2
- package/dist/cjs/runtime/index.d.ts +2 -1
- package/dist/cjs/runtime/index.d.ts.map +1 -1
- package/dist/cjs/runtime/index.js +2 -0
- package/dist/cjs/runtime/index.js.map +2 -2
- package/dist/cjs/runtime/input-active.d.ts +27 -0
- package/dist/cjs/runtime/input-active.d.ts.map +1 -0
- package/dist/cjs/runtime/input-active.js +44 -0
- package/dist/cjs/runtime/input-active.js.map +7 -0
- package/dist/cjs/runtime/types.d.ts +29 -2
- package/dist/cjs/runtime/types.d.ts.map +1 -1
- package/dist/cjs/runtime/types.js.map +1 -1
- package/dist/cjs/script/index.d.ts +53 -17
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +70 -11
- package/dist/cjs/script/index.js.map +3 -3
- package/dist/cjs/session/bars.d.ts +15 -1
- package/dist/cjs/session/bars.d.ts.map +1 -1
- package/dist/cjs/session/bars.js +41 -6
- package/dist/cjs/session/bars.js.map +3 -3
- package/dist/cjs/session/calendar.d.ts +9 -1
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +28 -2
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/session/context.d.ts +75 -0
- package/dist/cjs/session/context.d.ts.map +1 -0
- package/dist/cjs/session/context.js +113 -0
- package/dist/cjs/session/context.js.map +7 -0
- package/dist/cjs/str/index.d.ts +12 -4
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +13 -7
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +10 -2
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +10 -2
- package/dist/cjs/str/numberformat.js.map +2 -2
- package/dist/cjs/ta/index.d.ts +33 -17
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +70 -29
- package/dist/cjs/ta/index.js.map +2 -2
- package/dist/cjs/ta/limits.d.ts +24 -0
- package/dist/cjs/ta/limits.d.ts.map +1 -0
- package/dist/cjs/ta/limits.js +50 -0
- package/dist/cjs/ta/limits.js.map +7 -0
- package/dist/cjs/ta/running-sum.d.ts +0 -16
- package/dist/cjs/ta/running-sum.d.ts.map +1 -1
- package/dist/cjs/ta/running-sum.js +2 -0
- package/dist/cjs/ta/running-sum.js.map +2 -2
- package/dist/cjs/ta-series.d.ts +7 -7
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +9 -7
- package/dist/cjs/ta-series.js.map +2 -2
- package/dist/cjs/time/index.d.ts +5 -2
- package/dist/cjs/time/index.d.ts.map +1 -1
- package/dist/cjs/time/index.js +2 -2
- package/dist/cjs/time/index.js.map +2 -2
- package/dist/cjs/time/session.d.ts +17 -1
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +61 -7
- package/dist/cjs/time/session.js.map +2 -2
- package/dist/cjs/types/metadata.d.ts +27 -3
- package/dist/cjs/types/metadata.d.ts.map +1 -1
- package/dist/cjs/types/metadata.js.map +1 -1
- package/dist/esm/box/index.d.ts.map +1 -1
- package/dist/esm/box/index.js +8 -2
- package/dist/esm/box/index.js.map +1 -1
- package/dist/esm/callsite/index.d.ts +73 -32
- package/dist/esm/callsite/index.d.ts.map +1 -1
- package/dist/esm/callsite/index.js +143 -106
- package/dist/esm/callsite/index.js.map +1 -1
- package/dist/esm/drawing/registry.d.ts +75 -0
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +237 -6
- package/dist/esm/drawing/registry.js.map +1 -1
- package/dist/esm/index.d.ts +8 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +11 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/label/index.d.ts.map +1 -1
- package/dist/esm/label/index.js +4 -1
- package/dist/esm/label/index.js.map +1 -1
- package/dist/esm/line/index.d.ts.map +1 -1
- package/dist/esm/line/index.js +8 -2
- package/dist/esm/line/index.js.map +1 -1
- package/dist/esm/math/index.d.ts.map +1 -1
- package/dist/esm/math/index.js +3 -0
- package/dist/esm/math/index.js.map +1 -1
- package/dist/esm/polyline/index.d.ts.map +1 -1
- package/dist/esm/polyline/index.js +5 -1
- package/dist/esm/polyline/index.js.map +1 -1
- package/dist/esm/runtime/index.d.ts +2 -1
- package/dist/esm/runtime/index.d.ts.map +1 -1
- package/dist/esm/runtime/index.js +1 -0
- package/dist/esm/runtime/index.js.map +1 -1
- package/dist/esm/runtime/input-active.d.ts +27 -0
- package/dist/esm/runtime/input-active.d.ts.map +1 -0
- package/dist/esm/runtime/input-active.js +54 -0
- package/dist/esm/runtime/input-active.js.map +1 -0
- package/dist/esm/runtime/types.d.ts +29 -2
- package/dist/esm/runtime/types.d.ts.map +1 -1
- package/dist/esm/script/index.d.ts +53 -17
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +80 -13
- package/dist/esm/script/index.js.map +1 -1
- package/dist/esm/session/bars.d.ts +15 -1
- package/dist/esm/session/bars.d.ts.map +1 -1
- package/dist/esm/session/bars.js +50 -6
- package/dist/esm/session/bars.js.map +1 -1
- package/dist/esm/session/calendar.d.ts +9 -1
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +35 -3
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/session/context.d.ts +75 -0
- package/dist/esm/session/context.d.ts.map +1 -0
- package/dist/esm/session/context.js +122 -0
- package/dist/esm/session/context.js.map +1 -0
- package/dist/esm/str/index.d.ts +12 -4
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +33 -12
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +10 -2
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +16 -2
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +33 -17
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +130 -56
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/limits.d.ts +24 -0
- package/dist/esm/ta/limits.d.ts.map +1 -0
- package/dist/esm/ta/limits.js +42 -0
- package/dist/esm/ta/limits.js.map +1 -0
- package/dist/esm/ta/running-sum.d.ts +0 -16
- package/dist/esm/ta/running-sum.d.ts.map +1 -1
- package/dist/esm/ta/running-sum.js +2 -0
- package/dist/esm/ta/running-sum.js.map +1 -1
- package/dist/esm/ta-series.d.ts +7 -7
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +9 -7
- package/dist/esm/ta-series.js.map +1 -1
- package/dist/esm/time/index.d.ts +5 -2
- package/dist/esm/time/index.d.ts.map +1 -1
- package/dist/esm/time/index.js +6 -3
- package/dist/esm/time/index.js.map +1 -1
- package/dist/esm/time/session.d.ts +17 -1
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +97 -10
- package/dist/esm/time/session.js.map +1 -1
- package/dist/esm/types/metadata.d.ts +27 -3
- package/dist/esm/types/metadata.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/box/index.ts +8 -2
- package/src/callsite/index.ts +150 -100
- package/src/drawing/registry.ts +255 -5
- package/src/index.ts +36 -1
- package/src/label/index.ts +4 -1
- package/src/line/index.ts +8 -2
- package/src/math/index.ts +2 -0
- package/src/polyline/index.ts +3 -1
- package/src/runtime/index.ts +3 -0
- package/src/runtime/input-active.ts +50 -0
- package/src/runtime/types.ts +25 -2
- package/src/script/index.ts +123 -23
- package/src/session/bars.ts +47 -6
- package/src/session/calendar.ts +37 -3
- package/src/session/context.ts +160 -0
- package/src/str/index.ts +30 -12
- package/src/str/numberformat.ts +18 -3
- package/src/ta/index.ts +148 -66
- package/src/ta/limits.ts +47 -0
- package/src/ta/running-sum.ts +2 -0
- package/src/ta-series.ts +16 -14
- package/src/time/index.ts +6 -3
- package/src/time/session.ts +89 -9
- package/src/types/metadata.ts +27 -3
package/src/ta/index.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import type { series_float, series_bool, series_int, int, Source, simple_int, simple_float, simple_bool } from '../types/index.js';
|
|
12
12
|
import { eq, ge, gt, le, lt } from '../compare/index.js';
|
|
13
13
|
import { runningSum, runningSumSeries, runningVariance, runningVarianceSeries } from './running-sum.js';
|
|
14
|
+
import { checkSumLength, checkWindowLength } from './limits.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Simple Moving Average - returns the moving average (sum of last y values divided by y).
|
|
@@ -42,6 +43,7 @@ export function sma(source: Source, length: simple_int | ArrayLike<number>): ser
|
|
|
42
43
|
}
|
|
43
44
|
// Floor the length to match PineScript's auto-truncation of float to int
|
|
44
45
|
const len = Math.floor(length);
|
|
46
|
+
checkSumLength('ta.sma', len, 0);
|
|
45
47
|
return runningSum(source, len).map((sum) => sum / len);
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -234,6 +236,7 @@ export function bb(
|
|
|
234
236
|
length: simple_int,
|
|
235
237
|
mult: simple_float
|
|
236
238
|
): [series_float, series_float, series_float] {
|
|
239
|
+
checkSumLength('ta.bb', length, 0);
|
|
237
240
|
const basis = sma(series, length);
|
|
238
241
|
const dev = stdev(series, length);
|
|
239
242
|
|
|
@@ -285,14 +288,20 @@ function lastValues(source: Source, i: number, length: number): number[] | null
|
|
|
285
288
|
|
|
286
289
|
/**
|
|
287
290
|
* The extreme of the window of `length` bars ending at each bar, in one pass (monotonic deque): the bar index of
|
|
288
|
-
* the oldest highest (or lowest) value, or -1 when the window is empty.
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
* (lowest), the zero that `Math.max` / `Math.min`
|
|
291
|
+
* the oldest highest (or lowest) value (the newest one with `newest`), or -1 when the window is empty. The window
|
|
292
|
+
* stops at the first `na` value going back (the callers pass the non-na values only, see {@link keptValues}), and a
|
|
293
|
+
* fractional length is truncated (7.5 is 7 bars), as PineScript does.
|
|
294
|
+
* `zeros[i]` tells whether the window holds a +0 (highest) or a -0 (lowest), the zero that `Math.max` / `Math.min`
|
|
295
|
+
* return when the extreme is 0 (not kept with `newest`).
|
|
292
296
|
*/
|
|
293
|
-
function windowExtremes(
|
|
297
|
+
function windowExtremes(
|
|
298
|
+
source: Source,
|
|
299
|
+
length: number,
|
|
300
|
+
lowest: boolean,
|
|
301
|
+
newest = false
|
|
302
|
+
): { at: Int32Array; zeros: Uint8Array } {
|
|
294
303
|
const n = source.length;
|
|
295
|
-
const span = length > 0 ? Math.
|
|
304
|
+
const span = length > 0 ? Math.trunc(length) : 0; // PineScript casts the length to int
|
|
296
305
|
const at = new Int32Array(n).fill(-1);
|
|
297
306
|
const zeros = new Uint8Array(n);
|
|
298
307
|
const queue = new Int32Array(n); // bar indices; values from best to worst, the oldest first among equal values
|
|
@@ -310,8 +319,10 @@ function windowExtremes(source: Source, length: number, lowest: boolean): { at:
|
|
|
310
319
|
zeroCount = 0;
|
|
311
320
|
continue;
|
|
312
321
|
}
|
|
313
|
-
// drop the values that the new one beats; equal values stay (the oldest wins ties)
|
|
314
|
-
while (tail > head
|
|
322
|
+
// drop the values that the new one beats; equal values stay (the oldest wins ties), or go with `newest`
|
|
323
|
+
while (tail > head) {
|
|
324
|
+
const last = source[queue[tail - 1]!]!;
|
|
325
|
+
if (!(lowest ? last > x : last < x) && !(newest && last === x)) break;
|
|
315
326
|
if (isPreferredZero(queue[--tail]!)) zeroCount--;
|
|
316
327
|
}
|
|
317
328
|
queue[tail++] = i;
|
|
@@ -328,20 +339,79 @@ function windowExtremes(source: Source, length: number, lowest: boolean): { at:
|
|
|
328
339
|
return { at, zeros };
|
|
329
340
|
}
|
|
330
341
|
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
342
|
+
/**
|
|
343
|
+
* The non-na values of `source` with their bars, and for each bar the position of its last non-na value (-1 when
|
|
344
|
+
* there is none yet). PineScript's ta.highest / ta.lowest / ta.highestbars / ta.lowestbars / ta.pivothigh /
|
|
345
|
+
* ta.pivotlow keep the non-na values they receive and nothing else.
|
|
346
|
+
*/
|
|
347
|
+
function keptValues(source: Source): { values: number[]; bars: number[]; last: Int32Array } {
|
|
348
|
+
const values: number[] = [];
|
|
349
|
+
const bars: number[] = [];
|
|
350
|
+
const last = new Int32Array(source.length);
|
|
351
|
+
for (let i = 0; i < source.length; i++) {
|
|
352
|
+
const x = source[i]!;
|
|
353
|
+
if (!Number.isNaN(x)) {
|
|
354
|
+
values.push(x);
|
|
355
|
+
bars.push(i);
|
|
356
|
+
}
|
|
357
|
+
last[i] = values.length - 1;
|
|
358
|
+
}
|
|
359
|
+
return { values, bars, last };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* ta.highest / ta.lowest / ta.highestbars / ta.lowestbars with a series length (a length per bar): on each bar the
|
|
364
|
+
* best of the last `length[i]` non-na values (the newest one on ties), na before bar `length[i] - 1`, as PineScript
|
|
365
|
+
* (measured with a length that changes on each bar, with and without na in the source).
|
|
366
|
+
*/
|
|
367
|
+
function extremeSeriesLength(source: Source, lengths: ArrayLike<number>, lowest: boolean, offsets: boolean): number[] {
|
|
368
|
+
const name = (lowest ? 'lowest' : 'highest') + (offsets ? 'bars' : '');
|
|
369
|
+
const { values, bars, last } = keptValues(source);
|
|
370
|
+
return Array.from(source, (_, i) => {
|
|
371
|
+
const len = Math.trunc(lengths[i]!);
|
|
372
|
+
checkWindowLength(name, len, i);
|
|
373
|
+
const k = last[i]!;
|
|
374
|
+
if (!(len >= 1) || i < len - 1 || k < 0) return NaN;
|
|
375
|
+
let best = k;
|
|
376
|
+
for (let j = k - 1; j >= 0 && j > k - len; j--) {
|
|
377
|
+
if (lowest ? values[j]! < values[best]! : values[j]! > values[best]!) best = j;
|
|
378
|
+
}
|
|
379
|
+
return offsets ? -(i - bars[best]!) : values[best]!;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Offset to the best of the last `length` non-na values (the newest one on ties): the distance in bars, so below
|
|
385
|
+
* `-(length - 1)` after an na value. na before bar `length - 1` and while there is no value.
|
|
386
|
+
*/
|
|
387
|
+
function extremeOffset(source: Source, length: simple_int | ArrayLike<number>, lowest: boolean): series_int {
|
|
388
|
+
if (typeof length !== 'number') return extremeSeriesLength(source, length, lowest, true);
|
|
389
|
+
checkWindowLength(lowest ? 'lowestbars' : 'highestbars', length, 0);
|
|
390
|
+
const { values, bars, last } = keptValues(source);
|
|
391
|
+
const { at } = windowExtremes(values, length, lowest, true);
|
|
392
|
+
const len = Math.trunc(length);
|
|
393
|
+
// -(i - bar): -0 when the extreme is the current bar, as the offset -j of the window scan
|
|
394
|
+
return source.map((_, i) => {
|
|
395
|
+
const k = last[i]!;
|
|
396
|
+
return i < len - 1 || k < 0 || at[k]! < 0 ? NaN : -(i - bars[at[k]!]!);
|
|
397
|
+
});
|
|
336
398
|
}
|
|
337
399
|
|
|
338
|
-
/**
|
|
339
|
-
|
|
340
|
-
|
|
400
|
+
/**
|
|
401
|
+
* Best of the last `length` non-na values (of all of them when there are fewer), with the zero sign of `Math.max` /
|
|
402
|
+
* `Math.min`. na before bar `length - 1` and while there is no value.
|
|
403
|
+
*/
|
|
404
|
+
function extremeValue(source: Source, length: simple_int | ArrayLike<number>, lowest: boolean): series_float {
|
|
405
|
+
if (typeof length !== 'number') return extremeSeriesLength(source, length, lowest, false);
|
|
406
|
+
checkWindowLength(lowest ? 'lowest' : 'highest', length, 0);
|
|
407
|
+
const { values, last } = keptValues(source);
|
|
408
|
+
const { at, zeros } = windowExtremes(values, length, lowest);
|
|
409
|
+
const len = Math.trunc(length);
|
|
341
410
|
return source.map((_, i) => {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
411
|
+
const k = last[i]!;
|
|
412
|
+
if (i < len - 1 || k < 0 || at[k]! < 0) return NaN;
|
|
413
|
+
const v = values[at[k]!]!;
|
|
414
|
+
return v === 0 && zeros[k] ? (lowest ? -0 : 0) : v;
|
|
345
415
|
});
|
|
346
416
|
}
|
|
347
417
|
|
|
@@ -763,6 +833,8 @@ function wmaAdd(a: number, b: number): number {
|
|
|
763
833
|
* - Summed from the oldest value to the newest; as PineScript, a partial sum `s + t` with
|
|
764
834
|
* `|s + t| <= 1e-10 * max(1, |s| + |t|)` is 0 (a cancellation residue, or values below 1e-10)
|
|
765
835
|
* - More responsive to recent price changes than SMA
|
|
836
|
+
* - `length` can be an array (a length per bar, PineScript series length): each bar gives the result of its own
|
|
837
|
+
* length (`na` for a length below 1)
|
|
766
838
|
*
|
|
767
839
|
* @example
|
|
768
840
|
* ```typescript
|
|
@@ -770,10 +842,10 @@ function wmaAdd(a: number, b: number): number {
|
|
|
770
842
|
* // WMA gives more weight to recent prices
|
|
771
843
|
* ```
|
|
772
844
|
*/
|
|
773
|
-
export function wma(source: Source, length: simple_int): series_float {
|
|
845
|
+
export function wma(source: Source, length: simple_int | ArrayLike<number>): series_float {
|
|
774
846
|
const result: series_float = [];
|
|
775
847
|
// Floor the length to match PineScript's auto-truncation of float to int
|
|
776
|
-
const
|
|
848
|
+
const fixed = typeof length === 'number' ? Math.floor(length) : NaN;
|
|
777
849
|
// PineScript: na on a bar with an na source (also +/-Infinity) and until `len` non-na values were received;
|
|
778
850
|
// otherwise the last `len` bars, each na replaced by the previous non-na value
|
|
779
851
|
const filled: number[] = [];
|
|
@@ -787,7 +859,9 @@ export function wma(source: Source, length: simple_int): series_float {
|
|
|
787
859
|
count++;
|
|
788
860
|
}
|
|
789
861
|
filled.push(last);
|
|
790
|
-
|
|
862
|
+
// a series length: the window of this bar's length (the result of ta.wma with that fixed length)
|
|
863
|
+
const len = typeof length === 'number' ? fixed : Math.floor(length[i]!);
|
|
864
|
+
if (isNa || !(len >= 1) || count < len) {
|
|
791
865
|
result.push(NaN);
|
|
792
866
|
continue;
|
|
793
867
|
}
|
|
@@ -812,11 +886,15 @@ export function wma(source: Source, length: simple_int): series_float {
|
|
|
812
886
|
* @returns Highest value series (na on the first `length - 1` bars)
|
|
813
887
|
*
|
|
814
888
|
* @remarks
|
|
815
|
-
* As in PineScript:
|
|
816
|
-
*
|
|
889
|
+
* As in PineScript: `na` values are skipped. The result is the highest of the last `length` non-na values (of all
|
|
890
|
+
* of them when there are fewer), also on a bar whose own value is `na`; `na` before bar `length - 1` and while no
|
|
891
|
+
* value was received. A fractional length is truncated (7.5 is 7 bars).
|
|
892
|
+
* `length` can be an array (a length per bar, PineScript series length), also for `ta.lowest`, `ta.highestbars`
|
|
893
|
+
* and `ta.lowestbars`: each bar uses its own length.
|
|
894
|
+
* A length above 15,000 throws the PineScript runtime error (also `ta.lowest`, `ta.highestbars`, `ta.lowestbars`).
|
|
817
895
|
* PineScript `ta.highest(length)` (source = high) is `ta.highest(length)` in the script API.
|
|
818
896
|
*/
|
|
819
|
-
export function highest(source: Source, length: simple_int): series_float {
|
|
897
|
+
export function highest(source: Source, length: simple_int | ArrayLike<number>): series_float {
|
|
820
898
|
return extremeValue(source, length, false);
|
|
821
899
|
}
|
|
822
900
|
|
|
@@ -828,10 +906,11 @@ export function highest(source: Source, length: simple_int): series_float {
|
|
|
828
906
|
* @returns Lowest value series (na on the first `length - 1` bars)
|
|
829
907
|
*
|
|
830
908
|
* @remarks
|
|
831
|
-
* As in PineScript:
|
|
832
|
-
*
|
|
909
|
+
* As in PineScript: `na` values are skipped. The result is the lowest of the last `length` non-na values (of all
|
|
910
|
+
* of them when there are fewer), also on a bar whose own value is `na`; `na` before bar `length - 1` and while no
|
|
911
|
+
* value was received.
|
|
833
912
|
*/
|
|
834
|
-
export function lowest(source: Source, length: simple_int): series_float {
|
|
913
|
+
export function lowest(source: Source, length: simple_int | ArrayLike<number>): series_float {
|
|
835
914
|
return extremeValue(source, length, true);
|
|
836
915
|
}
|
|
837
916
|
|
|
@@ -1127,6 +1206,7 @@ function varianceOf(
|
|
|
1127
1206
|
return biased ? result : result.map((v, i) => (Math.floor(length[i]!) > 1 ? v : NaN));
|
|
1128
1207
|
}
|
|
1129
1208
|
const len = Math.floor(length);
|
|
1209
|
+
checkSumLength(name, len, 0);
|
|
1130
1210
|
// PineScript: the last `len` non-na values (also +/-Infinity skipped), as ta.sma
|
|
1131
1211
|
if ((biased ? len : len - 1) <= 0) return source.map(() => NaN);
|
|
1132
1212
|
return runningVariance(source, len, biased);
|
|
@@ -1738,35 +1818,31 @@ export function sar(
|
|
|
1738
1818
|
return result;
|
|
1739
1819
|
}
|
|
1740
1820
|
|
|
1821
|
+
/** A pivot length: one value, or a value per bar (PineScript series length). */
|
|
1822
|
+
type PivotLength = simple_int | ArrayLike<number>;
|
|
1823
|
+
|
|
1741
1824
|
/**
|
|
1742
1825
|
* Pivot detection as PineScript computes it.
|
|
1743
1826
|
*
|
|
1744
|
-
* The
|
|
1745
|
-
*
|
|
1746
|
-
*
|
|
1747
|
-
*
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
const
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
for (let j = 1; j
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
}
|
|
1764
|
-
for (let j = 1; j <= rightbars; j++) {
|
|
1765
|
-
const x = source[center + j]!;
|
|
1766
|
-
if (Number.isNaN(x)) break;
|
|
1767
|
-
if (beatsRight(x, v)) return NaN;
|
|
1768
|
-
}
|
|
1769
|
-
return v;
|
|
1827
|
+
* The function keeps the non-na values it receives. On each bar it takes the last `leftbars + rightbars + 1` of them
|
|
1828
|
+
* (all of them when there are fewer) and finds the highest (the newest one on equal values; the lowest for a pivot
|
|
1829
|
+
* low): there is a pivot when that value is on the bar `rightbars` bars back, and its value is returned on this bar,
|
|
1830
|
+
* so no future bar is used. A pivot high may so equal values on its left but must be higher than every value on its
|
|
1831
|
+
* right. `na` before bar `leftbars + rightbars`.
|
|
1832
|
+
*/
|
|
1833
|
+
function pivot(source: Source, leftbars: PivotLength, rightbars: PivotLength, isHigh: boolean): series_float {
|
|
1834
|
+
const { values, bars, last } = keptValues(source);
|
|
1835
|
+
return Array.from(source, (_, i) => {
|
|
1836
|
+
// a series length: the lengths of the current bar
|
|
1837
|
+
const left = Math.trunc(typeof leftbars === 'number' ? leftbars : leftbars[i]!);
|
|
1838
|
+
const right = Math.trunc(typeof rightbars === 'number' ? rightbars : rightbars[i]!);
|
|
1839
|
+
const k = last[i]!;
|
|
1840
|
+
if (!(i >= left + right) || k < 0) return NaN;
|
|
1841
|
+
let best = k;
|
|
1842
|
+
for (let j = k - 1; j >= 0 && j >= k - left - right; j--) {
|
|
1843
|
+
if (isHigh ? values[j]! > values[best]! : values[j]! < values[best]!) best = j;
|
|
1844
|
+
}
|
|
1845
|
+
return bars[best] === i - right ? values[best]! : NaN;
|
|
1770
1846
|
});
|
|
1771
1847
|
}
|
|
1772
1848
|
|
|
@@ -1784,7 +1860,10 @@ function pivot(source: Source, leftbars: simple_int, rightbars: simple_int, isHi
|
|
|
1784
1860
|
* - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
|
|
1785
1861
|
* index is `i - rightbars`
|
|
1786
1862
|
* - A pivot high may equal values on its left but must be higher than all values on its right
|
|
1787
|
-
* -
|
|
1863
|
+
* - `na` values are skipped: the window is the last `leftbars + rightbars + 1` non-na values, and the highest of
|
|
1864
|
+
* them must be on the bar `rightbars` bars back
|
|
1865
|
+
* - `leftbars` / `rightbars` can be arrays (a length per bar, PineScript series length), also for `ta.pivotlow`:
|
|
1866
|
+
* each bar uses its own lengths
|
|
1788
1867
|
*
|
|
1789
1868
|
* @example
|
|
1790
1869
|
* ```typescript
|
|
@@ -1794,8 +1873,8 @@ function pivot(source: Source, leftbars: simple_int, rightbars: simple_int, isHi
|
|
|
1794
1873
|
*/
|
|
1795
1874
|
export function pivothigh(
|
|
1796
1875
|
sourceOrLeftbars: Source | simple_int,
|
|
1797
|
-
leftbarsOrRightbars:
|
|
1798
|
-
rightbars?:
|
|
1876
|
+
leftbarsOrRightbars: PivotLength,
|
|
1877
|
+
rightbars?: PivotLength,
|
|
1799
1878
|
high?: Source
|
|
1800
1879
|
): series_float {
|
|
1801
1880
|
if (rightbars === undefined) {
|
|
@@ -1821,7 +1900,8 @@ export function pivothigh(
|
|
|
1821
1900
|
* - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
|
|
1822
1901
|
* index is `i - rightbars`
|
|
1823
1902
|
* - A pivot low may equal values on its left but must be lower than all values on its right
|
|
1824
|
-
* -
|
|
1903
|
+
* - `na` values are skipped: the window is the last `leftbars + rightbars + 1` non-na values, and the lowest of
|
|
1904
|
+
* them must be on the bar `rightbars` bars back
|
|
1825
1905
|
*
|
|
1826
1906
|
* @example
|
|
1827
1907
|
* ```typescript
|
|
@@ -1831,8 +1911,8 @@ export function pivothigh(
|
|
|
1831
1911
|
*/
|
|
1832
1912
|
export function pivotlow(
|
|
1833
1913
|
sourceOrLeftbars: Source | simple_int,
|
|
1834
|
-
leftbarsOrRightbars:
|
|
1835
|
-
rightbars?:
|
|
1914
|
+
leftbarsOrRightbars: PivotLength,
|
|
1915
|
+
rightbars?: PivotLength,
|
|
1836
1916
|
low?: Source
|
|
1837
1917
|
): series_float {
|
|
1838
1918
|
if (rightbars === undefined) {
|
|
@@ -2420,10 +2500,11 @@ export function range(source: Source, length: simple_int): series_float {
|
|
|
2420
2500
|
* -1 for the previous bar, and so on (as in PineScript, the offset is negative).
|
|
2421
2501
|
*
|
|
2422
2502
|
* @remarks
|
|
2423
|
-
* As in PineScript:
|
|
2424
|
-
*
|
|
2503
|
+
* As in PineScript: the best of the last `length` non-na values, the newest one on equal values; the offset is the
|
|
2504
|
+
* distance in bars to its bar, so it is below `-(length - 1)` when `na` values were skipped. `na` before bar
|
|
2505
|
+
* `length - 1` and while no value was received; a fractional length is truncated.
|
|
2425
2506
|
*/
|
|
2426
|
-
export function highestbars(source: Source, length: simple_int): series_int {
|
|
2507
|
+
export function highestbars(source: Source, length: simple_int | ArrayLike<number>): series_int {
|
|
2427
2508
|
return extremeOffset(source, length, false);
|
|
2428
2509
|
}
|
|
2429
2510
|
|
|
@@ -2432,10 +2513,11 @@ export function highestbars(source: Source, length: simple_int): series_int {
|
|
|
2432
2513
|
* -1 for the previous bar, and so on (as in PineScript, the offset is negative).
|
|
2433
2514
|
*
|
|
2434
2515
|
* @remarks
|
|
2435
|
-
* As in PineScript:
|
|
2436
|
-
*
|
|
2516
|
+
* As in PineScript: the best of the last `length` non-na values, the newest one on equal values; the offset is the
|
|
2517
|
+
* distance in bars to its bar, so it is below `-(length - 1)` when `na` values were skipped. `na` before bar
|
|
2518
|
+
* `length - 1` and while no value was received; a fractional length is truncated.
|
|
2437
2519
|
*/
|
|
2438
|
-
export function lowestbars(source: Source, length: simple_int): series_int {
|
|
2520
|
+
export function lowestbars(source: Source, length: simple_int | ArrayLike<number>): series_int {
|
|
2439
2521
|
return extremeOffset(source, length, true);
|
|
2440
2522
|
}
|
|
2441
2523
|
|
package/src/ta/limits.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History limits of the window functions.
|
|
3
|
+
*
|
|
4
|
+
* PineScript stops a script when `ta.highest` / `ta.lowest` / `ta.highestbars` / `ta.lowestbars` ask for more than
|
|
5
|
+
* 15,000 bars (runtime error RE10004): with a constant length on the first bar, with a length that changes on the
|
|
6
|
+
* bar where it passes the limit. `math.sum`, `ta.sma`, `ta.variance`, `ta.stdev` and `ta.bb` have the same error
|
|
7
|
+
* above 5,000 bars; the message names the function that keeps the window ('stdev'->'variance', 'bb'->'sma').
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Largest length of `ta.highest` / `ta.lowest` / `ta.highestbars` / `ta.lowestbars`. */
|
|
11
|
+
export const MAX_WINDOW_LENGTH = 15000;
|
|
12
|
+
|
|
13
|
+
/** Largest length of `math.sum` / `ta.sma` / `ta.variance` / `ta.stdev` / `ta.bb`. */
|
|
14
|
+
export const MAX_SUM_LENGTH = 5000;
|
|
15
|
+
|
|
16
|
+
/** Names of the PineScript message for the functions with the 5,000 limit ("a->b": a calls b). */
|
|
17
|
+
export const SUM_FUNCTIONS: Record<string, string> = {
|
|
18
|
+
'math.sum': 'sum',
|
|
19
|
+
'ta.sma': 'sma',
|
|
20
|
+
'ta.variance': 'variance',
|
|
21
|
+
'ta.stdev': 'stdev->variance',
|
|
22
|
+
'ta.bb': 'bb->sma',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* PineScript's runtime error RE10004 text; `bar` is the bar index of the call when it is known. `name` is the
|
|
27
|
+
* function, or a call path "stdev->variance" (printed 'stdev'->'variance').
|
|
28
|
+
*/
|
|
29
|
+
export function windowError(name: string, length: number, bar?: number, max = MAX_WINDOW_LENGTH): string {
|
|
30
|
+
const fn = name.split('->').map((part) => `'${part}'`).join('->');
|
|
31
|
+
return (
|
|
32
|
+
(bar === undefined ? '' : `Error on bar ${bar}: `) +
|
|
33
|
+
`The ${fn} function references too many historical candles (${length}), the limit is ${max}.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Throws RE10004 when the (truncated) length of a window function is above the limit. */
|
|
38
|
+
export function checkWindowLength(name: string, length: number, bar?: number, max = MAX_WINDOW_LENGTH): void {
|
|
39
|
+
const len = Math.trunc(length);
|
|
40
|
+
if (len > max) throw new Error(windowError(name, len, bar, max));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Throws RE10004 for a function of {@link SUM_FUNCTIONS} ("ta.sma", ...) with a length above 5,000. */
|
|
44
|
+
export function checkSumLength(name: string, length: number, bar?: number): void {
|
|
45
|
+
const fn = SUM_FUNCTIONS[name];
|
|
46
|
+
if (fn !== undefined) checkWindowLength(fn, length, bar, MAX_SUM_LENGTH);
|
|
47
|
+
}
|
package/src/ta/running-sum.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @internal
|
|
16
16
|
*/
|
|
17
|
+
import { checkSumLength } from './limits.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Whether the compensation `c` is (partly) lost when it is applied to `x`: `x + |c|` in floating point moves `x` by
|
|
@@ -177,6 +178,7 @@ function seriesLengths(lengths: ArrayLike<number>, count: number, name: string):
|
|
|
177
178
|
return Array.from({ length: count }, (_, i) => {
|
|
178
179
|
const len = Math.floor(lengths[i]!);
|
|
179
180
|
if (!(len >= 1)) throw new Error(`${name}: length must be at least 1 (bar ${i}: ${lengths[i]})`);
|
|
181
|
+
checkSumLength(name, len, i);
|
|
180
182
|
return len;
|
|
181
183
|
});
|
|
182
184
|
}
|
package/src/ta-series.ts
CHANGED
|
@@ -40,10 +40,10 @@ export function ema(source: Series, length: number): Series {
|
|
|
40
40
|
* @param length - Period length
|
|
41
41
|
* @returns Series with WMA values
|
|
42
42
|
*/
|
|
43
|
-
export function wma(source: Series, length: number): Series {
|
|
43
|
+
export function wma(source: Series, length: number | Series): Series {
|
|
44
44
|
const bars = source.bars as Bar[];
|
|
45
45
|
const sourceValues = source.toArray();
|
|
46
|
-
const result = taCore.wma(sourceValues, length);
|
|
46
|
+
const result = taCore.wma(sourceValues, length instanceof Series ? length.toArray() : length);
|
|
47
47
|
return Series.fromArray(bars, result);
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -333,10 +333,10 @@ export function roc(source: Series, length: number): Series {
|
|
|
333
333
|
* @param length - Period length
|
|
334
334
|
* @returns Series with highest values
|
|
335
335
|
*/
|
|
336
|
-
export function highest(source: Series, length: number): Series {
|
|
336
|
+
export function highest(source: Series, length: number | Series): Series {
|
|
337
337
|
const bars = source.bars as Bar[];
|
|
338
338
|
const sourceValues = source.toArray();
|
|
339
|
-
const result = taCore.highest(sourceValues, length);
|
|
339
|
+
const result = taCore.highest(sourceValues, length instanceof Series ? length.toArray() : length);
|
|
340
340
|
return Series.fromArray(bars, result);
|
|
341
341
|
}
|
|
342
342
|
|
|
@@ -346,10 +346,10 @@ export function highest(source: Series, length: number): Series {
|
|
|
346
346
|
* @param length - Period length
|
|
347
347
|
* @returns Series with lowest values
|
|
348
348
|
*/
|
|
349
|
-
export function lowest(source: Series, length: number): Series {
|
|
349
|
+
export function lowest(source: Series, length: number | Series): Series {
|
|
350
350
|
const bars = source.bars as Bar[];
|
|
351
351
|
const sourceValues = source.toArray();
|
|
352
|
-
const result = taCore.lowest(sourceValues, length);
|
|
352
|
+
const result = taCore.lowest(sourceValues, length instanceof Series ? length.toArray() : length);
|
|
353
353
|
return Series.fromArray(bars, result);
|
|
354
354
|
}
|
|
355
355
|
|
|
@@ -688,9 +688,10 @@ export function hma(source: Series, length: number): Series {
|
|
|
688
688
|
* @returns Series with the pivot high value on the bar where the pivot is confirmed,
|
|
689
689
|
* `rightbars` bars after the pivot bar (as in PineScript), NaN elsewhere
|
|
690
690
|
*/
|
|
691
|
-
export function pivothigh(source: Series, leftbars: number, rightbars: number): Series {
|
|
691
|
+
export function pivothigh(source: Series, leftbars: number | Series, rightbars: number | Series): Series {
|
|
692
692
|
const bars = source.bars as Bar[];
|
|
693
|
-
const
|
|
693
|
+
const arr = (v: number | Series) => (v instanceof Series ? v.toArray() : v);
|
|
694
|
+
const result = taCore.pivothigh(source.toArray(), arr(leftbars), arr(rightbars));
|
|
694
695
|
return Series.fromArray(bars, result);
|
|
695
696
|
}
|
|
696
697
|
|
|
@@ -702,9 +703,10 @@ export function pivothigh(source: Series, leftbars: number, rightbars: number):
|
|
|
702
703
|
* @returns Series with the pivot low value on the bar where the pivot is confirmed,
|
|
703
704
|
* `rightbars` bars after the pivot bar (as in PineScript), NaN elsewhere
|
|
704
705
|
*/
|
|
705
|
-
export function pivotlow(source: Series, leftbars: number, rightbars: number): Series {
|
|
706
|
+
export function pivotlow(source: Series, leftbars: number | Series, rightbars: number | Series): Series {
|
|
706
707
|
const bars = source.bars as Bar[];
|
|
707
|
-
const
|
|
708
|
+
const arr = (v: number | Series) => (v instanceof Series ? v.toArray() : v);
|
|
709
|
+
const result = taCore.pivotlow(source.toArray(), arr(leftbars), arr(rightbars));
|
|
708
710
|
return Series.fromArray(bars, result);
|
|
709
711
|
}
|
|
710
712
|
|
|
@@ -861,9 +863,9 @@ export function kcw(bars: Bar[], source: Series, length: number = 20, mult: numb
|
|
|
861
863
|
* @param length - Period length
|
|
862
864
|
* @returns Series with bar offsets to highest value
|
|
863
865
|
*/
|
|
864
|
-
export function highestbars(source: Series, length: number): Series {
|
|
866
|
+
export function highestbars(source: Series, length: number | Series): Series {
|
|
865
867
|
const bars = source.bars as Bar[];
|
|
866
|
-
const result = taCore.highestbars(source.toArray(), length);
|
|
868
|
+
const result = taCore.highestbars(source.toArray(), length instanceof Series ? length.toArray() : length);
|
|
867
869
|
return Series.fromArray(bars, result);
|
|
868
870
|
}
|
|
869
871
|
|
|
@@ -873,9 +875,9 @@ export function highestbars(source: Series, length: number): Series {
|
|
|
873
875
|
* @param length - Period length
|
|
874
876
|
* @returns Series with bar offsets to lowest value
|
|
875
877
|
*/
|
|
876
|
-
export function lowestbars(source: Series, length: number): Series {
|
|
878
|
+
export function lowestbars(source: Series, length: number | Series): Series {
|
|
877
879
|
const bars = source.bars as Bar[];
|
|
878
|
-
const result = taCore.lowestbars(source.toArray(), length);
|
|
880
|
+
const result = taCore.lowestbars(source.toArray(), length instanceof Series ? length.toArray() : length);
|
|
879
881
|
return Series.fromArray(bars, result);
|
|
880
882
|
}
|
|
881
883
|
|
package/src/time/index.ts
CHANGED
|
@@ -156,8 +156,11 @@ export function second(time: int, timezone: string): int {
|
|
|
156
156
|
* ":" (1 = Sunday ... 7 = Saturday), e.g. "0930-1600:23456" or "1000-1100,1400-1500".
|
|
157
157
|
* Without days, one period covers every day and several periods cover Monday to Friday.
|
|
158
158
|
* A period includes its start and excludes its end. An overnight period ("1800-0930")
|
|
159
|
-
* belongs to the day it ends. "0000-0000" and "24x7" cover the whole day.
|
|
159
|
+
* belongs to the day it ends. "0000-0000" and "24x7" cover the whole day. A period that ends at 0000 ends at 24:00 of
|
|
160
|
+
* the day it starts. On the days around a clock change the periods are placed as PineScript places them (see
|
|
161
|
+
* time/session.ts); `barMs`, the duration of the bars in ms, is needed for one of these rules (the day before the
|
|
162
|
+
* clocks go back).
|
|
160
163
|
*/
|
|
161
|
-
export function inSession(time: int, session: string, timezone: string): boolean {
|
|
162
|
-
return sessionContains(time, session, timezone);
|
|
164
|
+
export function inSession(time: int, session: string, timezone: string, barMs?: number): boolean {
|
|
165
|
+
return sessionContains(time, session, timezone, barMs);
|
|
163
166
|
}
|