oakscriptjs 0.11.0 → 0.11.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/dist/cjs/array/index.d.ts +8 -2
- package/dist/cjs/array/index.d.ts.map +1 -1
- package/dist/cjs/array/index.js +2 -1
- package/dist/cjs/array/index.js.map +2 -2
- package/dist/cjs/box/index.js +6 -6
- package/dist/cjs/box/index.js.map +2 -2
- package/dist/cjs/callsite/index.d.ts.map +1 -1
- package/dist/cjs/callsite/index.js +6 -3
- package/dist/cjs/callsite/index.js.map +2 -2
- package/dist/cjs/drawing/registry.d.ts +15 -6
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +16 -2
- package/dist/cjs/drawing/registry.js.map +2 -2
- package/dist/cjs/index.d.ts +4 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -1
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/input.d.ts +9 -1
- package/dist/cjs/input.d.ts.map +1 -1
- package/dist/cjs/input.js +16 -0
- package/dist/cjs/input.js.map +2 -2
- package/dist/cjs/label/index.js +3 -3
- package/dist/cjs/label/index.js.map +2 -2
- package/dist/cjs/line/index.d.ts +10 -27
- package/dist/cjs/line/index.d.ts.map +1 -1
- package/dist/cjs/line/index.js +10 -27
- package/dist/cjs/line/index.js.map +2 -2
- package/dist/cjs/linefill/index.d.ts +3 -0
- package/dist/cjs/linefill/index.d.ts.map +1 -1
- package/dist/cjs/linefill/index.js +15 -1
- package/dist/cjs/linefill/index.js.map +3 -3
- package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
- package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
- package/dist/cjs/runtime/index.d.ts +1 -1
- package/dist/cjs/runtime/index.d.ts.map +1 -1
- package/dist/cjs/runtime/index.js +1 -0
- package/dist/cjs/runtime/index.js.map +2 -2
- package/dist/cjs/runtime/inputs.d.ts +9 -0
- package/dist/cjs/runtime/inputs.d.ts.map +1 -1
- package/dist/cjs/runtime/inputs.js +8 -0
- package/dist/cjs/runtime/inputs.js.map +2 -2
- package/dist/cjs/runtime/types.d.ts +1 -1
- 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 +66 -13
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +139 -35
- package/dist/cjs/script/index.js.map +3 -3
- package/dist/cjs/security/other.d.ts +92 -0
- package/dist/cjs/security/other.d.ts.map +1 -0
- package/dist/cjs/security/other.js +164 -0
- package/dist/cjs/security/other.js.map +7 -0
- package/dist/cjs/session/bars.d.ts +10 -1
- package/dist/cjs/session/bars.d.ts.map +1 -1
- package/dist/cjs/session/bars.js +16 -2
- package/dist/cjs/session/bars.js.map +2 -2
- package/dist/cjs/session/calendar.d.ts +4 -1
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +12 -7
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/session/context.d.ts +22 -5
- package/dist/cjs/session/context.d.ts.map +1 -1
- package/dist/cjs/session/context.js +67 -6
- package/dist/cjs/session/context.js.map +2 -2
- package/dist/cjs/str/floatingdecimal.d.ts +31 -0
- package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
- package/dist/cjs/str/floatingdecimal.js +298 -0
- package/dist/cjs/str/floatingdecimal.js.map +7 -0
- package/dist/cjs/str/index.d.ts +4 -3
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +1 -1
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +6 -4
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +3 -2
- package/dist/cjs/str/numberformat.js.map +2 -2
- package/dist/cjs/ta/index.d.ts +64 -45
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +140 -126
- package/dist/cjs/ta/index.js.map +3 -3
- package/dist/cjs/ta/limits.d.ts +11 -0
- package/dist/cjs/ta/limits.d.ts.map +1 -1
- package/dist/cjs/ta/limits.js +15 -1
- package/dist/cjs/ta/limits.js.map +2 -2
- package/dist/cjs/ta/running-sum.js +1 -1
- package/dist/cjs/ta/running-sum.js.map +2 -2
- package/dist/cjs/ta-series.d.ts +1 -1
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +1 -1
- package/dist/cjs/ta-series.js.map +2 -2
- package/dist/cjs/table/index.d.ts +10 -3
- package/dist/cjs/table/index.d.ts.map +1 -1
- package/dist/cjs/table/index.js +11 -1
- package/dist/cjs/table/index.js.map +3 -3
- package/dist/cjs/time/session.d.ts +9 -0
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +42 -14
- package/dist/cjs/time/session.js.map +3 -3
- package/dist/cjs/time/timezone.d.ts +6 -1
- package/dist/cjs/time/timezone.d.ts.map +1 -1
- package/dist/cjs/time/timezone.js +2 -2
- package/dist/cjs/time/timezone.js.map +2 -2
- package/dist/cjs/types/metadata.d.ts +1 -1
- package/dist/cjs/types/metadata.d.ts.map +1 -1
- package/dist/cjs/types/metadata.js.map +1 -1
- package/dist/esm/array/index.d.ts +8 -2
- package/dist/esm/array/index.d.ts.map +1 -1
- package/dist/esm/array/index.js +12 -3
- package/dist/esm/array/index.js.map +1 -1
- package/dist/esm/box/index.js +6 -6
- package/dist/esm/box/index.js.map +1 -1
- package/dist/esm/callsite/index.d.ts.map +1 -1
- package/dist/esm/callsite/index.js +6 -3
- package/dist/esm/callsite/index.js.map +1 -1
- package/dist/esm/drawing/registry.d.ts +15 -6
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +30 -7
- package/dist/esm/drawing/registry.js.map +1 -1
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +8 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/input.d.ts +9 -1
- package/dist/esm/input.d.ts.map +1 -1
- package/dist/esm/input.js +16 -0
- package/dist/esm/input.js.map +1 -1
- package/dist/esm/label/index.js +3 -3
- package/dist/esm/label/index.js.map +1 -1
- package/dist/esm/line/index.d.ts +10 -27
- package/dist/esm/line/index.d.ts.map +1 -1
- package/dist/esm/line/index.js +20 -64
- package/dist/esm/line/index.js.map +1 -1
- package/dist/esm/linefill/index.d.ts +3 -0
- package/dist/esm/linefill/index.d.ts.map +1 -1
- package/dist/esm/linefill/index.js +20 -1
- package/dist/esm/linefill/index.js.map +1 -1
- package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
- package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
- package/dist/esm/runtime/index.d.ts +1 -1
- package/dist/esm/runtime/index.d.ts.map +1 -1
- package/dist/esm/runtime/index.js +1 -1
- package/dist/esm/runtime/index.js.map +1 -1
- package/dist/esm/runtime/inputs.d.ts +9 -0
- package/dist/esm/runtime/inputs.d.ts.map +1 -1
- package/dist/esm/runtime/inputs.js +15 -0
- package/dist/esm/runtime/inputs.js.map +1 -1
- package/dist/esm/runtime/types.d.ts +1 -1
- package/dist/esm/runtime/types.d.ts.map +1 -1
- package/dist/esm/script/index.d.ts +66 -13
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +187 -54
- package/dist/esm/script/index.js.map +1 -1
- package/dist/esm/security/other.d.ts +92 -0
- package/dist/esm/security/other.d.ts.map +1 -0
- package/dist/esm/security/other.js +211 -0
- package/dist/esm/security/other.js.map +1 -0
- package/dist/esm/session/bars.d.ts +10 -1
- package/dist/esm/session/bars.d.ts.map +1 -1
- package/dist/esm/session/bars.js +26 -1
- package/dist/esm/session/bars.js.map +1 -1
- package/dist/esm/session/calendar.d.ts +4 -1
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +23 -7
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/session/context.d.ts +22 -5
- package/dist/esm/session/context.d.ts.map +1 -1
- package/dist/esm/session/context.js +86 -10
- package/dist/esm/session/context.js.map +1 -1
- package/dist/esm/str/floatingdecimal.d.ts +31 -0
- package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
- package/dist/esm/str/floatingdecimal.js +272 -0
- package/dist/esm/str/floatingdecimal.js.map +1 -0
- package/dist/esm/str/index.d.ts +4 -3
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +6 -5
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +6 -4
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +9 -5
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +64 -45
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +198 -228
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/limits.d.ts +11 -0
- package/dist/esm/ta/limits.d.ts.map +1 -1
- package/dist/esm/ta/limits.js +27 -2
- package/dist/esm/ta/limits.js.map +1 -1
- package/dist/esm/ta/running-sum.js +1 -1
- package/dist/esm/ta/running-sum.js.map +1 -1
- package/dist/esm/ta-series.d.ts +1 -1
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +2 -1
- package/dist/esm/ta-series.js.map +1 -1
- package/dist/esm/table/index.d.ts +10 -3
- package/dist/esm/table/index.d.ts.map +1 -1
- package/dist/esm/table/index.js +23 -3
- package/dist/esm/table/index.js.map +1 -1
- package/dist/esm/time/session.d.ts +9 -0
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +53 -15
- package/dist/esm/time/session.js.map +1 -1
- package/dist/esm/time/timezone.d.ts +6 -1
- package/dist/esm/time/timezone.d.ts.map +1 -1
- package/dist/esm/time/timezone.js +8 -2
- package/dist/esm/time/timezone.js.map +1 -1
- package/dist/esm/types/metadata.d.ts +1 -1
- package/dist/esm/types/metadata.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/array/index.ts +11 -3
- package/src/box/index.ts +6 -6
- package/src/callsite/index.ts +6 -3
- package/src/drawing/registry.ts +30 -7
- package/src/index.ts +16 -3
- package/src/input.ts +18 -1
- package/src/label/index.ts +3 -3
- package/src/line/index.ts +20 -68
- package/src/linefill/index.ts +20 -1
- package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
- package/src/runtime/index.ts +1 -0
- package/src/runtime/inputs.ts +16 -0
- package/src/runtime/types.ts +1 -1
- package/src/script/index.ts +262 -58
- package/src/security/other.ts +269 -0
- package/src/session/bars.ts +24 -1
- package/src/session/calendar.ts +26 -7
- package/src/session/context.ts +104 -10
- package/src/str/floatingdecimal.ts +268 -0
- package/src/str/index.ts +6 -5
- package/src/str/numberformat.ts +11 -6
- package/src/ta/index.ts +200 -225
- package/src/ta/limits.ts +28 -1
- package/src/ta/running-sum.ts +1 -1
- package/src/ta-series.ts +3 -2
- package/src/table/index.ts +22 -3
- package/src/time/session.ts +58 -16
- package/src/time/timezone.ts +9 -2
- package/src/types/metadata.ts +1 -1
package/src/ta/index.ts
CHANGED
|
@@ -11,7 +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
|
+
import { checkPositiveLength, checkSumLength, checkWindowLength } from './limits.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Simple Moving Average - returns the moving average (sum of last y values divided by y).
|
|
@@ -68,6 +68,7 @@ export function sma(source: Source, length: simple_int | ArrayLike<number>): ser
|
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
70
|
export function ema(source: Source, length: simple_int): series_float {
|
|
71
|
+
checkPositiveLength('ema', length, 0);
|
|
71
72
|
const result: series_float = [];
|
|
72
73
|
const len = Math.floor(length);
|
|
73
74
|
const multiplier = 2 / (len + 1);
|
|
@@ -133,7 +134,25 @@ export function ema(source: Source, length: simple_int): series_float {
|
|
|
133
134
|
* const oversold = rsi14.map(v => v < 30);
|
|
134
135
|
* ```
|
|
135
136
|
*/
|
|
136
|
-
export function rsi(source: Source, length: simple_int): series_float
|
|
137
|
+
export function rsi(source: Source, length: simple_int): series_float;
|
|
138
|
+
/**
|
|
139
|
+
* PineScript v4 `rsi(x, y)` with two series (#180): `x` and `y` are the averages of the upward and downward changes,
|
|
140
|
+
* already computed. As in PineScript: 100 when `y` is within 1e-10 of zero (also with an na `x`), else 0 when `x` is,
|
|
141
|
+
* else `100 - 100 / (1 + x / y)`: -Infinity when `x == -y` (#182), which `na()` and `nz()` treat as na while comparisons
|
|
142
|
+
* see it (`r < 30` is true). Versions 5 and 6 have the length form only.
|
|
143
|
+
*/
|
|
144
|
+
export function rsi(x: Source, y: Source): series_float;
|
|
145
|
+
export function rsi(source: Source, length: simple_int | Source): series_float {
|
|
146
|
+
if (typeof length !== 'number') {
|
|
147
|
+
return Array.from(source, (xv, i) => {
|
|
148
|
+
const x = xv ?? NaN;
|
|
149
|
+
const y = length[i] ?? NaN;
|
|
150
|
+
if (eq(y, 0)) return 100;
|
|
151
|
+
if (eq(x, 0)) return 0;
|
|
152
|
+
return 100 - 100 / (1 + x / y);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
checkPositiveLength('rsi', length, 0, 'y');
|
|
137
156
|
const result: series_float = [];
|
|
138
157
|
|
|
139
158
|
// Calculate price changes
|
|
@@ -274,14 +293,14 @@ export function stdev(
|
|
|
274
293
|
}
|
|
275
294
|
|
|
276
295
|
/**
|
|
277
|
-
* The last `length` non-na values up to bar `i` (the bar itself may be na), or null when fewer exist.
|
|
278
|
-
*
|
|
296
|
+
* The last `length` non-na values up to bar `i` (the bar itself may be na), or null when fewer exist. +/-Infinity
|
|
297
|
+
* counts as na (#183).
|
|
279
298
|
*/
|
|
280
299
|
function lastValues(source: Source, i: number, length: number): number[] | null {
|
|
281
300
|
const values: number[] = [];
|
|
282
301
|
for (let j = i; j >= 0 && values.length < length; j--) {
|
|
283
302
|
const x = source[j]!;
|
|
284
|
-
if (
|
|
303
|
+
if (Number.isFinite(x)) values.push(x);
|
|
285
304
|
}
|
|
286
305
|
return values.length === length ? values : null;
|
|
287
306
|
}
|
|
@@ -342,7 +361,7 @@ function windowExtremes(
|
|
|
342
361
|
/**
|
|
343
362
|
* The non-na values of `source` with their bars, and for each bar the position of its last non-na value (-1 when
|
|
344
363
|
* 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.
|
|
364
|
+
* ta.pivotlow keep the non-na values they receive and nothing else; +/-Infinity is skipped like na (#183).
|
|
346
365
|
*/
|
|
347
366
|
function keptValues(source: Source): { values: number[]; bars: number[]; last: Int32Array } {
|
|
348
367
|
const values: number[] = [];
|
|
@@ -350,7 +369,7 @@ function keptValues(source: Source): { values: number[]; bars: number[]; last: I
|
|
|
350
369
|
const last = new Int32Array(source.length);
|
|
351
370
|
for (let i = 0; i < source.length; i++) {
|
|
352
371
|
const x = source[i]!;
|
|
353
|
-
if (
|
|
372
|
+
if (Number.isFinite(x)) {
|
|
354
373
|
values.push(x);
|
|
355
374
|
bars.push(i);
|
|
356
375
|
}
|
|
@@ -506,6 +525,7 @@ export function crossunder(series1: Source, series2: Source): series_bool {
|
|
|
506
525
|
* ```
|
|
507
526
|
*/
|
|
508
527
|
export function change(source: Source, length: simple_int = 1): series_float {
|
|
528
|
+
checkPositiveLength('change', length, 0);
|
|
509
529
|
const result: series_float = [];
|
|
510
530
|
|
|
511
531
|
for (let i = 0; i < source.length; i++) {
|
|
@@ -618,6 +638,7 @@ export function tr(
|
|
|
618
638
|
* ```
|
|
619
639
|
*/
|
|
620
640
|
export function atr(length: simple_int, high?: Source, low?: Source, close?: Source): series_float {
|
|
641
|
+
checkPositiveLength('atr->rma', length, 0);
|
|
621
642
|
if (!high || !low || !close) {
|
|
622
643
|
throw new Error(
|
|
623
644
|
'ta.atr() requires high, low, and close series. ' +
|
|
@@ -763,6 +784,7 @@ export function supertrend(
|
|
|
763
784
|
* ```
|
|
764
785
|
*/
|
|
765
786
|
export function rma(source: Source, length: simple_int): series_float {
|
|
787
|
+
checkPositiveLength('rma', length, 0);
|
|
766
788
|
const result: series_float = [];
|
|
767
789
|
// Floor the length to match PineScript's auto-truncation of float to int
|
|
768
790
|
const len = Math.floor(length);
|
|
@@ -846,6 +868,7 @@ export function wma(source: Source, length: simple_int | ArrayLike<number>): ser
|
|
|
846
868
|
const result: series_float = [];
|
|
847
869
|
// Floor the length to match PineScript's auto-truncation of float to int
|
|
848
870
|
const fixed = typeof length === 'number' ? Math.floor(length) : NaN;
|
|
871
|
+
if (typeof length === 'number') checkPositiveLength('wma', fixed, 0);
|
|
849
872
|
// PineScript: na on a bar with an na source (also +/-Infinity) and until `len` non-na values were received;
|
|
850
873
|
// otherwise the last `len` bars, each na replaced by the previous non-na value
|
|
851
874
|
const filled: number[] = [];
|
|
@@ -861,6 +884,7 @@ export function wma(source: Source, length: simple_int | ArrayLike<number>): ser
|
|
|
861
884
|
filled.push(last);
|
|
862
885
|
// a series length: the window of this bar's length (the result of ta.wma with that fixed length)
|
|
863
886
|
const len = typeof length === 'number' ? fixed : Math.floor(length[i]!);
|
|
887
|
+
if (typeof length !== 'number') checkPositiveLength('wma', len, i);
|
|
864
888
|
if (isNa || !(len >= 1) || count < len) {
|
|
865
889
|
result.push(NaN);
|
|
866
890
|
continue;
|
|
@@ -886,9 +910,9 @@ export function wma(source: Source, length: simple_int | ArrayLike<number>): ser
|
|
|
886
910
|
* @returns Highest value series (na on the first `length - 1` bars)
|
|
887
911
|
*
|
|
888
912
|
* @remarks
|
|
889
|
-
* As in PineScript: `na` values are skipped. The result is the highest of the last `length`
|
|
890
|
-
* of them when there are fewer), also on a bar whose own value is `na`; `na` before bar
|
|
891
|
-
* value was received. A fractional length is truncated (7.5 is 7 bars).
|
|
913
|
+
* As in PineScript: `na` values (also +/-Infinity, #183) are skipped. The result is the highest of the last `length`
|
|
914
|
+
* non-na values (of all of them when there are fewer), also on a bar whose own value is `na`; `na` before bar
|
|
915
|
+
* `length - 1` and while no value was received. A fractional length is truncated (7.5 is 7 bars).
|
|
892
916
|
* `length` can be an array (a length per bar, PineScript series length), also for `ta.lowest`, `ta.highestbars`
|
|
893
917
|
* and `ta.lowestbars`: each bar uses its own length.
|
|
894
918
|
* A length above 15,000 throws the PineScript runtime error (also `ta.lowest`, `ta.highestbars`, `ta.lowestbars`).
|
|
@@ -906,9 +930,9 @@ export function highest(source: Source, length: simple_int | ArrayLike<number>):
|
|
|
906
930
|
* @returns Lowest value series (na on the first `length - 1` bars)
|
|
907
931
|
*
|
|
908
932
|
* @remarks
|
|
909
|
-
* As in PineScript: `na` values are skipped. The result is the lowest of the last `length`
|
|
910
|
-
* of them when there are fewer), also on a bar whose own value is `na`; `na` before bar
|
|
911
|
-
* value was received.
|
|
933
|
+
* As in PineScript: `na` values (also +/-Infinity, #183) are skipped. The result is the lowest of the last `length`
|
|
934
|
+
* non-na values (of all of them when there are fewer), also on a bar whose own value is `na`; `na` before bar
|
|
935
|
+
* `length - 1` and while no value was received.
|
|
912
936
|
*/
|
|
913
937
|
export function lowest(source: Source, length: simple_int | ArrayLike<number>): series_float {
|
|
914
938
|
return extremeValue(source, length, true);
|
|
@@ -994,7 +1018,7 @@ function monotonic(source: Source, length: simple_int, step: (newer: number, old
|
|
|
994
1018
|
for (let i = 0; i < source.length; i++) {
|
|
995
1019
|
const v = source[i]!;
|
|
996
1020
|
const prev = i > 0 ? source[i - 1]! : NaN;
|
|
997
|
-
if (
|
|
1021
|
+
if (Number.isFinite(v) && Number.isFinite(prev)) count = step(v, prev) ? count + 1 : 0;
|
|
998
1022
|
result.push(count >= length);
|
|
999
1023
|
}
|
|
1000
1024
|
return result;
|
|
@@ -1011,8 +1035,8 @@ function monotonic(source: Source, length: simple_int, step: (newer: number, old
|
|
|
1011
1035
|
* PineScript rules:
|
|
1012
1036
|
* - a counter of rises from the previous bar (a rise is larger than the 1e-10 tolerance); a step that is not a rise
|
|
1013
1037
|
* resets it to 0
|
|
1014
|
-
* - a step from or to an `na` bar leaves the counter unchanged (so an `na` bar, and the bar after
|
|
1015
|
-
* result of the bar before the `na`)
|
|
1038
|
+
* - a step from or to an `na` (or +/-Infinity) bar leaves the counter unchanged (so an `na` bar, and the bar after
|
|
1039
|
+
* it, keep the result of the bar before the `na`)
|
|
1016
1040
|
* - true when the counter is at least `length` (false at the start)
|
|
1017
1041
|
*
|
|
1018
1042
|
* @example
|
|
@@ -1022,6 +1046,7 @@ function monotonic(source: Source, length: simple_int, step: (newer: number, old
|
|
|
1022
1046
|
* ```
|
|
1023
1047
|
*/
|
|
1024
1048
|
export function rising(source: Source, length: simple_int): series_bool {
|
|
1049
|
+
checkPositiveLength('rising', length, 0);
|
|
1025
1050
|
return monotonic(source, length, gt);
|
|
1026
1051
|
}
|
|
1027
1052
|
|
|
@@ -1036,8 +1061,8 @@ export function rising(source: Source, length: simple_int): series_bool {
|
|
|
1036
1061
|
* PineScript rules:
|
|
1037
1062
|
* - a counter of falls from the previous bar (a fall is larger than the 1e-10 tolerance); a step that is not a fall
|
|
1038
1063
|
* resets it to 0
|
|
1039
|
-
* - a step from or to an `na` bar leaves the counter unchanged (so an `na` bar, and the bar after
|
|
1040
|
-
* result of the bar before the `na`)
|
|
1064
|
+
* - a step from or to an `na` (or +/-Infinity) bar leaves the counter unchanged (so an `na` bar, and the bar after
|
|
1065
|
+
* it, keep the result of the bar before the `na`)
|
|
1041
1066
|
* - true when the counter is at least `length` (false at the start)
|
|
1042
1067
|
*
|
|
1043
1068
|
* @example
|
|
@@ -1047,6 +1072,7 @@ export function rising(source: Source, length: simple_int): series_bool {
|
|
|
1047
1072
|
* ```
|
|
1048
1073
|
*/
|
|
1049
1074
|
export function falling(source: Source, length: simple_int): series_bool {
|
|
1075
|
+
checkPositiveLength('falling', length, 0);
|
|
1050
1076
|
return monotonic(source, length, lt);
|
|
1051
1077
|
}
|
|
1052
1078
|
|
|
@@ -1072,6 +1098,7 @@ export function falling(source: Source, length: simple_int): series_bool {
|
|
|
1072
1098
|
* ```
|
|
1073
1099
|
*/
|
|
1074
1100
|
export function roc(source: Source, length: simple_int): series_float {
|
|
1101
|
+
checkPositiveLength('roc', length, 0);
|
|
1075
1102
|
const result: series_float = [];
|
|
1076
1103
|
|
|
1077
1104
|
for (let i = 0; i < source.length; i++) {
|
|
@@ -1112,6 +1139,7 @@ export function roc(source: Source, length: simple_int): series_float {
|
|
|
1112
1139
|
* ```
|
|
1113
1140
|
*/
|
|
1114
1141
|
export function mom(source: Source, length: simple_int): series_float {
|
|
1142
|
+
checkPositiveLength('mom', length, 0);
|
|
1115
1143
|
const result: series_float = [];
|
|
1116
1144
|
|
|
1117
1145
|
for (let i = 0; i < source.length; i++) {
|
|
@@ -1151,6 +1179,7 @@ export function mom(source: Source, length: simple_int): series_float {
|
|
|
1151
1179
|
* ```
|
|
1152
1180
|
*/
|
|
1153
1181
|
export function dev(source: Source, length: simple_int): series_float {
|
|
1182
|
+
checkPositiveLength('dev', length, 0);
|
|
1154
1183
|
const len = Math.floor(length);
|
|
1155
1184
|
return sma(source, len).map((mean, i) => {
|
|
1156
1185
|
if (i < len - 1 || Number.isNaN(mean)) return NaN;
|
|
@@ -1222,7 +1251,7 @@ function varianceOf(
|
|
|
1222
1251
|
* @remarks
|
|
1223
1252
|
* - Returns the middle value when values are sorted
|
|
1224
1253
|
* - For an even length, returns the average of the two middle values
|
|
1225
|
-
* - As in PineScript: `na` values are skipped, the window holds the last `length` non-na values
|
|
1254
|
+
* - As in PineScript: `na` values (also +/-Infinity) are skipped, the window holds the last `length` non-na values
|
|
1226
1255
|
* (going back as many bars as needed), and a bar whose own value is `na` still gets a result
|
|
1227
1256
|
* - Returns NaN until `length` non-na values exist
|
|
1228
1257
|
* - More robust to outliers than mean (SMA)
|
|
@@ -1234,6 +1263,7 @@ function varianceOf(
|
|
|
1234
1263
|
* ```
|
|
1235
1264
|
*/
|
|
1236
1265
|
export function median(source: Source, length: simple_int): series_float {
|
|
1266
|
+
checkPositiveLength('median', length, 0);
|
|
1237
1267
|
const len = Math.floor(length);
|
|
1238
1268
|
const n = source.length;
|
|
1239
1269
|
const out: series_float = new Array<number>(n).fill(NaN);
|
|
@@ -1257,7 +1287,7 @@ export function median(source: Source, length: simple_int): series_float {
|
|
|
1257
1287
|
const mid = Math.floor(len / 2);
|
|
1258
1288
|
for (let i = 0; i < n; i++) {
|
|
1259
1289
|
const x = source[i]!;
|
|
1260
|
-
if (
|
|
1290
|
+
if (Number.isFinite(x)) {
|
|
1261
1291
|
kept[count++] = x;
|
|
1262
1292
|
sorted.splice(bound(x, false), 0, x); // before the equal values: the newest first
|
|
1263
1293
|
// the value leaving the window is the oldest, so the last of its equal values
|
|
@@ -1321,6 +1351,7 @@ export function swma(source: Source): series_float {
|
|
|
1321
1351
|
* ```
|
|
1322
1352
|
*/
|
|
1323
1353
|
export function vwma(source: Source, length: simple_int, volume?: Source): series_float {
|
|
1354
|
+
checkPositiveLength('vwma->sma', length, 0);
|
|
1324
1355
|
if (!volume) {
|
|
1325
1356
|
throw new Error(
|
|
1326
1357
|
'ta.vwma() requires volume series. ' +
|
|
@@ -1374,6 +1405,7 @@ export function vwma(source: Source, length: simple_int, volume?: Source): serie
|
|
|
1374
1405
|
* ```
|
|
1375
1406
|
*/
|
|
1376
1407
|
export function linreg(source: Source, length: simple_int, offset: simple_int = 0): series_float {
|
|
1408
|
+
checkPositiveLength('linreg', length, 0);
|
|
1377
1409
|
const result: series_float = [];
|
|
1378
1410
|
// Floor the length to match PineScript's auto-truncation of float to int
|
|
1379
1411
|
const len = Math.floor(length);
|
|
@@ -1443,6 +1475,7 @@ export function linreg(source: Source, length: simple_int, offset: simple_int =
|
|
|
1443
1475
|
* ```
|
|
1444
1476
|
*/
|
|
1445
1477
|
export function correlation(source1: Source, source2: Source, length: simple_int): series_float {
|
|
1478
|
+
checkPositiveLength('correlation', length, 0);
|
|
1446
1479
|
// PineScript: from compensated running sums, cov = sma(x * y) - sma(x) * sma(y), r = cov / sqrt(var(x) * var(y))
|
|
1447
1480
|
// (biased variances); each running sum skips its own na values
|
|
1448
1481
|
const product = Array.from({ length: source1.length }, (_, i) => {
|
|
@@ -1481,8 +1514,8 @@ export function correlation(source1: Source, source2: Source, length: simple_int
|
|
|
1481
1514
|
*
|
|
1482
1515
|
* PineScript rules:
|
|
1483
1516
|
* - percentrank = count(source[k] <= source, k = 1..length) * 100 / length (in this order: the PineScript bits)
|
|
1484
|
-
* - a value from bar `length` on
|
|
1485
|
-
*
|
|
1517
|
+
* - a value from bar `length` on; an `na` value in the window counts as not `<=` the current value, and an `na`
|
|
1518
|
+
* current value gives 0 (#177)
|
|
1486
1519
|
* - the comparison uses the 1e-10 tolerance
|
|
1487
1520
|
*
|
|
1488
1521
|
* @example
|
|
@@ -1493,15 +1526,16 @@ export function correlation(source1: Source, source2: Source, length: simple_int
|
|
|
1493
1526
|
* ```
|
|
1494
1527
|
*/
|
|
1495
1528
|
export function percentrank(source: Source, length: simple_int): series_float {
|
|
1529
|
+
checkPositiveLength('percentrank', length, 0);
|
|
1496
1530
|
const result: series_float = [];
|
|
1497
1531
|
|
|
1498
1532
|
for (let i = 0; i < source.length; i++) {
|
|
1499
1533
|
const currentValue = source[i]!;
|
|
1500
|
-
if (i < length
|
|
1534
|
+
if (i < length) {
|
|
1501
1535
|
result.push(NaN);
|
|
1502
1536
|
continue;
|
|
1503
1537
|
}
|
|
1504
|
-
// le() is false for an na value in the window
|
|
1538
|
+
// le() is false for an na value in the window, and for all of them when the current value is na (0)
|
|
1505
1539
|
let countLessOrEqual = 0;
|
|
1506
1540
|
for (let j = 1; j <= length; j++) {
|
|
1507
1541
|
if (le(source[i - j]!, currentValue)) countLessOrEqual++;
|
|
@@ -1541,6 +1575,7 @@ export function percentrank(source: Source, length: simple_int): series_float {
|
|
|
1541
1575
|
* \`\`\`
|
|
1542
1576
|
*/
|
|
1543
1577
|
export function cci(source: Source, length: simple_int): series_float {
|
|
1578
|
+
checkPositiveLength('cci', length, 0);
|
|
1544
1579
|
const smaValues = sma(source, length);
|
|
1545
1580
|
const devValues = dev(source, length);
|
|
1546
1581
|
let last = 0;
|
|
@@ -1566,8 +1601,9 @@ export function cci(source: Source, length: simple_int): series_float {
|
|
|
1566
1601
|
* - Measures where close is relative to the high-low range
|
|
1567
1602
|
* - Values above 80 typically indicate overbought
|
|
1568
1603
|
* - Values below 20 typically indicate oversold
|
|
1569
|
-
* - As PineScript: when the source, \`highest(high, length)\` or \`lowest(low, length)\` is \`na\`, or the range
|
|
1570
|
-
* result is the previous result of the call (\`na\` until a first value)
|
|
1604
|
+
* - As PineScript: when the source, \`highest(high, length)\` or \`lowest(low, length)\` is \`na\`, or the range and
|
|
1605
|
+
* \`source - lowest\` are both 0, the result is the previous result of the call (\`na\` until a first value); a zero
|
|
1606
|
+
* range with another source value gives +/-Infinity (plotted as \`na\`)
|
|
1571
1607
|
*
|
|
1572
1608
|
* @example
|
|
1573
1609
|
* \`\`\`typescript
|
|
@@ -1577,17 +1613,18 @@ export function cci(source: Source, length: simple_int): series_float {
|
|
|
1577
1613
|
* \`\`\`
|
|
1578
1614
|
*/
|
|
1579
1615
|
export function stoch(source: Source, high: Source, low: Source, length: simple_int): series_float {
|
|
1616
|
+
checkPositiveLength('stoch->highest', length, 0);
|
|
1580
1617
|
const result: series_float = [];
|
|
1581
1618
|
const lowestValues = lowest(low, length);
|
|
1582
1619
|
const highestValues = highest(high, length);
|
|
1583
1620
|
|
|
1584
|
-
// PineScript: an na input or a zero range
|
|
1621
|
+
// PineScript: an na (NaN) result keeps the previous one: an na input, or 0 / 0 on a zero range; a non-zero value
|
|
1622
|
+
// over a zero range gives +/-Infinity (#183)
|
|
1585
1623
|
let previous = NaN;
|
|
1586
1624
|
for (let i = 0; i < source.length; i++) {
|
|
1587
|
-
const value = source[i]!;
|
|
1588
1625
|
const lo = lowestValues[i]!;
|
|
1589
|
-
const
|
|
1590
|
-
if (!Number.isNaN(
|
|
1626
|
+
const k = (100 * (source[i]! - lo)) / (highestValues[i]! - lo);
|
|
1627
|
+
if (!Number.isNaN(k)) previous = k;
|
|
1591
1628
|
result.push(previous);
|
|
1592
1629
|
}
|
|
1593
1630
|
|
|
@@ -1629,6 +1666,7 @@ export function stoch(source: Source, high: Source, low: Source, length: simple_
|
|
|
1629
1666
|
* ```
|
|
1630
1667
|
*/
|
|
1631
1668
|
export function mfi(source: Source, length: simple_int, volume?: Source): series_float {
|
|
1669
|
+
checkPositiveLength('mfi', length, 0);
|
|
1632
1670
|
if (!volume) {
|
|
1633
1671
|
throw new Error(
|
|
1634
1672
|
'ta.mfi() requires volume series. ' +
|
|
@@ -1687,6 +1725,7 @@ export function mfi(source: Source, length: simple_int, volume?: Source): series
|
|
|
1687
1725
|
* ```
|
|
1688
1726
|
*/
|
|
1689
1727
|
export function hma(source: Source, length: simple_int): series_float {
|
|
1728
|
+
checkPositiveLength('hma', length, 0);
|
|
1690
1729
|
const halfLength = Math.floor(length / 2);
|
|
1691
1730
|
const sqrtLength = Math.floor(Math.sqrt(length));
|
|
1692
1731
|
|
|
@@ -1860,8 +1899,8 @@ function pivot(source: Source, leftbars: PivotLength, rightbars: PivotLength, is
|
|
|
1860
1899
|
* - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
|
|
1861
1900
|
* index is `i - rightbars`
|
|
1862
1901
|
* - A pivot high may equal values on its left but must be higher than all values on its right
|
|
1863
|
-
* - `na` values are skipped: the window is the last `leftbars + rightbars + 1` non-na values,
|
|
1864
|
-
* them must be on the bar `rightbars` bars back
|
|
1902
|
+
* - `na` values (also +/-Infinity) are skipped: the window is the last `leftbars + rightbars + 1` non-na values,
|
|
1903
|
+
* and the highest of them must be on the bar `rightbars` bars back
|
|
1865
1904
|
* - `leftbars` / `rightbars` can be arrays (a length per bar, PineScript series length), also for `ta.pivotlow`:
|
|
1866
1905
|
* each bar uses its own lengths
|
|
1867
1906
|
*
|
|
@@ -1900,8 +1939,8 @@ export function pivothigh(
|
|
|
1900
1939
|
* - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
|
|
1901
1940
|
* index is `i - rightbars`
|
|
1902
1941
|
* - A pivot low may equal values on its left but must be lower than all values on its right
|
|
1903
|
-
* - `na` values are skipped: the window is the last `leftbars + rightbars + 1` non-na values,
|
|
1904
|
-
* them must be on the bar `rightbars` bars back
|
|
1942
|
+
* - `na` values (also +/-Infinity) are skipped: the window is the last `leftbars + rightbars + 1` non-na values,
|
|
1943
|
+
* and the lowest of them must be on the bar `rightbars` bars back
|
|
1905
1944
|
*
|
|
1906
1945
|
* @example
|
|
1907
1946
|
* ```typescript
|
|
@@ -2093,6 +2132,7 @@ export function dmi(
|
|
|
2093
2132
|
* ```
|
|
2094
2133
|
*/
|
|
2095
2134
|
export function tsi(source: Source, shortLength: simple_int, longLength: simple_int): series_float {
|
|
2135
|
+
checkPositiveLength('tsi->ema', shortLength, 0);
|
|
2096
2136
|
const momentum: series_float = [];
|
|
2097
2137
|
|
|
2098
2138
|
// Calculate momentum (price change)
|
|
@@ -2142,6 +2182,7 @@ export function tsi(source: Source, shortLength: simple_int, longLength: simple_
|
|
|
2142
2182
|
* ```
|
|
2143
2183
|
*/
|
|
2144
2184
|
export function cmo(source: Source, length: simple_int): series_float {
|
|
2185
|
+
checkPositiveLength('cmo', length, 0);
|
|
2145
2186
|
// PineScript definition: mom = ta.change(src), sm1 = math.sum(mom >= 0 ? mom : 0, length),
|
|
2146
2187
|
// sm2 = math.sum(mom >= 0 ? 0 : -mom, length), cmo = 100 * (sm1 - sm2) / (sm1 + sm2), with the running sums of
|
|
2147
2188
|
// math.sum (bit for bit). A comparison with na is false: an na change adds 0 to sm1 and na (skipped) to sm2.
|
|
@@ -2191,6 +2232,7 @@ export function kc(
|
|
|
2191
2232
|
low?: Source,
|
|
2192
2233
|
close?: Source
|
|
2193
2234
|
): [series_float, series_float, series_float] {
|
|
2235
|
+
checkPositiveLength('kc', length, 0);
|
|
2194
2236
|
// Calculate middle band (EMA of source)
|
|
2195
2237
|
const middle = ema(source, length);
|
|
2196
2238
|
|
|
@@ -2250,6 +2292,7 @@ export function kc(
|
|
|
2250
2292
|
* ```
|
|
2251
2293
|
*/
|
|
2252
2294
|
export function bbw(source: Source, length: simple_int, mult: simple_float): series_float {
|
|
2295
|
+
checkPositiveLength('bbw', length, 0);
|
|
2253
2296
|
const [basis, upper, lower] = bb(source, length, mult);
|
|
2254
2297
|
const result: series_float = [];
|
|
2255
2298
|
|
|
@@ -2293,6 +2336,7 @@ export function bbw(source: Source, length: simple_int, mult: simple_float): ser
|
|
|
2293
2336
|
* ```
|
|
2294
2337
|
*/
|
|
2295
2338
|
export function wpr(high: Source, low: Source, close: Source, length: simple_int = 14): series_float {
|
|
2339
|
+
checkPositiveLength('wpr', length, 0);
|
|
2296
2340
|
const highs = highest(high, length);
|
|
2297
2341
|
const lows = lowest(low, length);
|
|
2298
2342
|
return Array.from({ length: close.length }, (_, i) => {
|
|
@@ -2393,6 +2437,7 @@ export function alma(
|
|
|
2393
2437
|
sigma: simple_float = 6,
|
|
2394
2438
|
floor: boolean = false
|
|
2395
2439
|
): series_float {
|
|
2440
|
+
checkPositiveLength('alma', length, 0);
|
|
2396
2441
|
const result: series_float = [];
|
|
2397
2442
|
const m = floor ? Math.floor(offset * (length - 1)) : offset * (length - 1);
|
|
2398
2443
|
const s = length / sigma;
|
|
@@ -2485,10 +2530,11 @@ export function kcw(
|
|
|
2485
2530
|
* (PineScript `ta.range(source, length)`).
|
|
2486
2531
|
*
|
|
2487
2532
|
* @remarks
|
|
2488
|
-
* As in PineScript: `na` values are skipped (the window holds the last `length` non-na
|
|
2533
|
+
* As in PineScript: `na` values (also +/-Infinity) are skipped (the window holds the last `length` non-na
|
|
2489
2534
|
* values), and a bar whose own value is `na` still gets a result.
|
|
2490
2535
|
*/
|
|
2491
2536
|
export function range(source: Source, length: simple_int): series_float {
|
|
2537
|
+
checkPositiveLength('range', length, 0);
|
|
2492
2538
|
return source.map((_, i) => {
|
|
2493
2539
|
const values = lastValues(source, i, length);
|
|
2494
2540
|
return values ? Math.max(...values) - Math.min(...values) : NaN;
|
|
@@ -2500,9 +2546,9 @@ export function range(source: Source, length: simple_int): series_float {
|
|
|
2500
2546
|
* -1 for the previous bar, and so on (as in PineScript, the offset is negative).
|
|
2501
2547
|
*
|
|
2502
2548
|
* @remarks
|
|
2503
|
-
* As in PineScript: the best of the last `length` non-na values, the newest one on equal
|
|
2504
|
-
* distance in bars to its bar, so it is below `-(length - 1)` when `na` values were
|
|
2505
|
-
* `length - 1` and while no value was received; a fractional length is truncated.
|
|
2549
|
+
* As in PineScript: the best of the last `length` non-na values (+/-Infinity skipped), the newest one on equal
|
|
2550
|
+
* values; the offset is the distance in bars to its bar, so it is below `-(length - 1)` when `na` values were
|
|
2551
|
+
* skipped. `na` before bar `length - 1` and while no value was received; a fractional length is truncated.
|
|
2506
2552
|
*/
|
|
2507
2553
|
export function highestbars(source: Source, length: simple_int | ArrayLike<number>): series_int {
|
|
2508
2554
|
return extremeOffset(source, length, false);
|
|
@@ -2513,9 +2559,9 @@ export function highestbars(source: Source, length: simple_int | ArrayLike<numbe
|
|
|
2513
2559
|
* -1 for the previous bar, and so on (as in PineScript, the offset is negative).
|
|
2514
2560
|
*
|
|
2515
2561
|
* @remarks
|
|
2516
|
-
* As in PineScript: the best of the last `length` non-na values, the newest one on equal
|
|
2517
|
-
* distance in bars to its bar, so it is below `-(length - 1)` when `na` values were
|
|
2518
|
-
* `length - 1` and while no value was received; a fractional length is truncated.
|
|
2562
|
+
* As in PineScript: the best of the last `length` non-na values (+/-Infinity skipped), the newest one on equal
|
|
2563
|
+
* values; the offset is the distance in bars to its bar, so it is below `-(length - 1)` when `na` values were
|
|
2564
|
+
* skipped. `na` before bar `length - 1` and while no value was received; a fractional length is truncated.
|
|
2519
2565
|
*/
|
|
2520
2566
|
export function lowestbars(source: Source, length: simple_int | ArrayLike<number>): series_int {
|
|
2521
2567
|
return extremeOffset(source, length, true);
|
|
@@ -2525,13 +2571,13 @@ export function lowestbars(source: Source, length: simple_int | ArrayLike<number
|
|
|
2525
2571
|
* All-time highest value of `source` up to each bar (PineScript `ta.max(source)`).
|
|
2526
2572
|
*
|
|
2527
2573
|
* @remarks
|
|
2528
|
-
* As in PineScript: `na` values are skipped, the value carries over `na` bars, and the
|
|
2574
|
+
* As in PineScript: `na` values (also +/-Infinity) are skipped, the value carries over `na` bars, and the
|
|
2529
2575
|
* result is `na` until the first non-na value.
|
|
2530
2576
|
*/
|
|
2531
2577
|
export function max(source: Source): series_float {
|
|
2532
2578
|
let best = NaN;
|
|
2533
2579
|
return source.map((x) => {
|
|
2534
|
-
if (
|
|
2580
|
+
if (Number.isFinite(x) && !(x <= best)) best = x;
|
|
2535
2581
|
return best;
|
|
2536
2582
|
});
|
|
2537
2583
|
}
|
|
@@ -2540,13 +2586,13 @@ export function max(source: Source): series_float {
|
|
|
2540
2586
|
* All-time lowest value of `source` up to each bar (PineScript `ta.min(source)`).
|
|
2541
2587
|
*
|
|
2542
2588
|
* @remarks
|
|
2543
|
-
* As in PineScript: `na` values are skipped, the value carries over `na` bars, and the
|
|
2589
|
+
* As in PineScript: `na` values (also +/-Infinity) are skipped, the value carries over `na` bars, and the
|
|
2544
2590
|
* result is `na` until the first non-na value.
|
|
2545
2591
|
*/
|
|
2546
2592
|
export function min(source: Source): series_float {
|
|
2547
2593
|
let best = NaN;
|
|
2548
2594
|
return source.map((x) => {
|
|
2549
|
-
if (
|
|
2595
|
+
if (Number.isFinite(x) && !(x >= best)) best = x;
|
|
2550
2596
|
return best;
|
|
2551
2597
|
});
|
|
2552
2598
|
}
|
|
@@ -2574,6 +2620,7 @@ export function min(source: Source): series_float {
|
|
|
2574
2620
|
* ```
|
|
2575
2621
|
*/
|
|
2576
2622
|
export function cog(source: Source, length: simple_int = 10): series_float {
|
|
2623
|
+
checkPositiveLength('cog', length, 0);
|
|
2577
2624
|
const len = Math.floor(length);
|
|
2578
2625
|
return runningSum(source, len).map((sum, i) => {
|
|
2579
2626
|
if (i < len - 1 || Number.isNaN(sum) || sum === 0) return NaN;
|
|
@@ -2601,19 +2648,20 @@ export function cog(source: Source, length: simple_int = 10): series_float {
|
|
|
2601
2648
|
*
|
|
2602
2649
|
* @remarks
|
|
2603
2650
|
* PineScript rules:
|
|
2604
|
-
* - the window is the last `length` non-`na` values (an `na` bar does not enter it, and gets the mode
|
|
2605
|
-
* window it keeps)
|
|
2651
|
+
* - the window is the last `length` non-`na` values (an `na` or +/-Infinity bar does not enter it, and gets the mode
|
|
2652
|
+
* of the window it keeps)
|
|
2606
2653
|
* - NaN until `length` non-`na` values exist
|
|
2607
2654
|
* - If no mode exists, returns the smallest value
|
|
2608
2655
|
*/
|
|
2609
2656
|
export function mode(source: Source, length: simple_int): series_float {
|
|
2657
|
+
checkPositiveLength('mode', length, 0);
|
|
2610
2658
|
const result: series_float = [];
|
|
2611
2659
|
const len = Math.floor(length);
|
|
2612
2660
|
const seen: number[] = []; // non-na values so far
|
|
2613
2661
|
|
|
2614
2662
|
for (let i = 0; i < source.length; i++) {
|
|
2615
2663
|
const v = source[i]!;
|
|
2616
|
-
if (
|
|
2664
|
+
if (Number.isFinite(v)) seen.push(v);
|
|
2617
2665
|
if (seen.length < len) {
|
|
2618
2666
|
result.push(NaN);
|
|
2619
2667
|
continue;
|
|
@@ -2684,6 +2732,7 @@ export function percentile_linear_interpolation(
|
|
|
2684
2732
|
length: simple_int,
|
|
2685
2733
|
percentage: number
|
|
2686
2734
|
): series_float {
|
|
2735
|
+
checkPositiveLength('percentile_linear_interpolation', length, 0);
|
|
2687
2736
|
return sortedWindow(source, length, (s) => {
|
|
2688
2737
|
const pos = (percentage / 100) * length - 0.5;
|
|
2689
2738
|
if (pos <= 0) return s[0]!;
|
|
@@ -2753,6 +2802,7 @@ export function percentile_nearest_rank(
|
|
|
2753
2802
|
length: simple_int,
|
|
2754
2803
|
percentage: number
|
|
2755
2804
|
): series_float {
|
|
2805
|
+
checkPositiveLength('percentile_nearest_rank', length, 0);
|
|
2756
2806
|
const rank = Math.min(length, Math.max(1, Math.ceil((percentage / 100) * length)));
|
|
2757
2807
|
return sortedWindow(source, length, (s) => s[rank - 1]!);
|
|
2758
2808
|
}
|
|
@@ -2789,6 +2839,7 @@ export function percentile_nearest_rank(
|
|
|
2789
2839
|
* where the `na` was
|
|
2790
2840
|
*/
|
|
2791
2841
|
export function rci(source: Source, length: simple_int): series_float {
|
|
2842
|
+
checkPositiveLength('rci', length, 0);
|
|
2792
2843
|
const len = Math.floor(length);
|
|
2793
2844
|
const result: series_float = [];
|
|
2794
2845
|
// PineScript keeps the values in a ring of `length + 1` slots indexed by the bar index, filled with 0 at the start
|
|
@@ -2852,31 +2903,42 @@ export function rci(source: Source, length: simple_int): series_float {
|
|
|
2852
2903
|
}
|
|
2853
2904
|
|
|
2854
2905
|
/**
|
|
2855
|
-
* Pivot Point Levels
|
|
2906
|
+
* Pivot Point Levels (PineScript `ta.pivot_point_levels`).
|
|
2856
2907
|
*
|
|
2857
|
-
*
|
|
2858
|
-
* Returns an array containing: [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5]
|
|
2908
|
+
* Returns 11 series: [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5] (one value per bar in each).
|
|
2859
2909
|
*
|
|
2860
2910
|
* @param type - Calculation type: "Traditional", "Fibonacci", "Woodie", "Classic", "DM", "Camarilla"
|
|
2861
|
-
* @param anchor - Condition that
|
|
2862
|
-
* @param developing -
|
|
2863
|
-
*
|
|
2864
|
-
* @param
|
|
2865
|
-
* @param
|
|
2866
|
-
* @param
|
|
2867
|
-
* @
|
|
2911
|
+
* @param anchor - Condition that starts a new period (`timeframe.change("W")`...)
|
|
2912
|
+
* @param developing - false: the levels of the previous period, constant until the next anchor; true: the levels of
|
|
2913
|
+
* the current period so far, recalculated on each bar
|
|
2914
|
+
* @param high - High price series
|
|
2915
|
+
* @param low - Low price series
|
|
2916
|
+
* @param close - Close price series
|
|
2917
|
+
* @param open - Open price series (Woodie and DM)
|
|
2918
|
+
* @returns 11 series of levels
|
|
2868
2919
|
*
|
|
2869
2920
|
* @example
|
|
2870
2921
|
* ```typescript
|
|
2871
2922
|
* const weekChange = [false, false, false, false, true, false, ...]; // Weekly anchor
|
|
2872
|
-
* const
|
|
2873
|
-
* //
|
|
2923
|
+
* const levels = ta.pivot_point_levels("Traditional", weekChange, false, high, low, close, open);
|
|
2924
|
+
* // levels[0]![i] is P on bar i, levels[5]![i] is R3
|
|
2874
2925
|
* ```
|
|
2875
2926
|
*
|
|
2876
2927
|
* @remarks
|
|
2877
|
-
*
|
|
2878
|
-
* -
|
|
2879
|
-
*
|
|
2928
|
+
* As in PineScript (#177):
|
|
2929
|
+
* - a period runs from an anchor bar to the bar before the next anchor; the bars before the first anchor are a
|
|
2930
|
+
* period too (from bar 0). The fixed levels use the high and low of the previous period and the close of the bar
|
|
2931
|
+
* before the anchor (na with an anchor on bar 0), and are na until the first anchor; the developing levels use
|
|
2932
|
+
* the current period's high and low and the bar's close, from bar 0
|
|
2933
|
+
* - Traditional: R3 = P * 2 + (H - 2 * L), S3 = P * 2 - (2 * H - L), R4 = P * 3 + (H - 3 * L), S4 = P * 3 - (3 * H - L),
|
|
2934
|
+
* R5 = P * 4 + (H - 4 * L), S5 = P * 4 - (4 * H - L)
|
|
2935
|
+
* - Fibonacci: P +- 0.382 / 0.618 / 1 * (H - L); R4 to S5 na
|
|
2936
|
+
* - Classic: R3 / S3 = P +- 2 * (H - L), R4 / S4 = P +- 3 * (H - L); R5 / S5 na
|
|
2937
|
+
* - Woodie: P = (H + L + 2 * open of the current period) / 4, R3 = H + 2 * (P - L), S3 = L - 2 * (H - P), R4 / S4 =
|
|
2938
|
+
* R3 + (H - L) / S3 - (H - L); R5 / S5 na. `developing` true throws (RE10128)
|
|
2939
|
+
* - DM: x = H + 2 * L + C (C < open of the period), 2 * H + L + C (C > O) or H + L + 2 * C; P = x / 4,
|
|
2940
|
+
* R1 = x / 2 - L, S1 = x / 2 - H; the other levels na
|
|
2941
|
+
* - Camarilla: C +- (H - L) * 1.1 / 12, / 6, / 4, / 2 for R1 to S4, R5 = (H / L) * C, S5 = 2 * C - R5
|
|
2880
2942
|
*/
|
|
2881
2943
|
export function pivot_point_levels(
|
|
2882
2944
|
type: series_float | string,
|
|
@@ -2887,183 +2949,96 @@ export function pivot_point_levels(
|
|
|
2887
2949
|
close?: Source,
|
|
2888
2950
|
open?: Source
|
|
2889
2951
|
): series_float[] {
|
|
2890
|
-
// For simplicity, we need to handle the case where type is a string array
|
|
2891
|
-
// But according to the docs, type is "series string", meaning it can change per bar
|
|
2892
|
-
// For this implementation, we'll support constant type strings
|
|
2893
|
-
|
|
2894
2952
|
const typeStr = typeof type === 'string' ? type : String(type[0]);
|
|
2895
|
-
const isDeveloping = typeof developing === 'boolean' ? developing : developing[0];
|
|
2896
|
-
|
|
2897
|
-
// Woodie cannot be developing
|
|
2953
|
+
const isDeveloping = typeof developing === 'boolean' ? developing : Boolean(developing[0]);
|
|
2898
2954
|
if (typeStr === 'Woodie' && isDeveloping) {
|
|
2899
|
-
throw new Error(
|
|
2955
|
+
throw new Error(
|
|
2956
|
+
'Error on bar 0: The `developing` parameter of the `ta.pivot_point_levels()` cannot be `true` when `type` is "Woodie".'
|
|
2957
|
+
);
|
|
2900
2958
|
}
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
h = lastH;
|
|
2939
|
-
l = lastL;
|
|
2940
|
-
c = lastC;
|
|
2941
|
-
o = lastO;
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
|
-
// Calculate pivot levels based on type
|
|
2945
|
-
const levels = calculatePivotLevels(typeStr, h, l, c, o);
|
|
2946
|
-
|
|
2947
|
-
// Push to results
|
|
2948
|
-
for (let j = 0; j < 11; j++) {
|
|
2949
|
-
results[j]!.push(levels[j]!);
|
|
2950
|
-
}
|
|
2959
|
+
const n = anchor.length;
|
|
2960
|
+
for (const [s, name] of [[high, 'High'], [low, 'Low'], [close, 'Close'], [open, 'Open']] as const) {
|
|
2961
|
+
if (s && s.length !== n) throw new Error(`${name} series length mismatch`);
|
|
2962
|
+
}
|
|
2963
|
+
const at = (s: Source | undefined, i: number): number => (s ? (s[i] ?? NaN) : NaN);
|
|
2964
|
+
const results: series_float[] = Array.from({ length: 11 }, () => new Array<number>(n));
|
|
2965
|
+
// the current period (from bar 0 until the first anchor) and the previous one
|
|
2966
|
+
let curH = NaN, curL = NaN, curO = NaN;
|
|
2967
|
+
let prevH = NaN, prevL = NaN, prevC = NaN, prevO = NaN;
|
|
2968
|
+
let anchored = false;
|
|
2969
|
+
for (let i = 0; i < n; i++) {
|
|
2970
|
+
const h = at(high, i), l = at(low, i);
|
|
2971
|
+
if (i === 0) {
|
|
2972
|
+
// the first period starts on bar 0: an anchor there makes bar 0 its own previous period
|
|
2973
|
+
curH = h;
|
|
2974
|
+
curL = l;
|
|
2975
|
+
curO = at(open, i);
|
|
2976
|
+
}
|
|
2977
|
+
if (anchor[i]) {
|
|
2978
|
+
prevH = curH;
|
|
2979
|
+
prevL = curL;
|
|
2980
|
+
prevC = i > 0 ? at(close, i - 1) : NaN;
|
|
2981
|
+
prevO = curO;
|
|
2982
|
+
curH = h;
|
|
2983
|
+
curL = l;
|
|
2984
|
+
curO = at(open, i);
|
|
2985
|
+
anchored = true;
|
|
2986
|
+
} else if (i > 0) {
|
|
2987
|
+
if (h > curH || Number.isNaN(curH)) curH = h;
|
|
2988
|
+
if (l < curL || Number.isNaN(curL)) curL = l;
|
|
2989
|
+
}
|
|
2990
|
+
const levels = isDeveloping
|
|
2991
|
+
? pivotLevels(typeStr, curH, curL, at(close, i), curO, at(close, i))
|
|
2992
|
+
: anchored
|
|
2993
|
+
? pivotLevels(typeStr, prevH, prevL, prevC, prevO, curO)
|
|
2994
|
+
: NA_LEVELS;
|
|
2995
|
+
for (let k = 0; k < 11; k++) results[k]![i] = levels[k]!;
|
|
2951
2996
|
}
|
|
2952
|
-
|
|
2953
2997
|
return results;
|
|
2954
2998
|
}
|
|
2955
2999
|
|
|
3000
|
+
const NA_LEVELS: readonly number[] = new Array<number>(11).fill(NaN);
|
|
3001
|
+
|
|
2956
3002
|
/**
|
|
2957
|
-
*
|
|
3003
|
+
* The 11 levels of one bar, in PineScript's operation order (bit for bit). `o` is the open of the period (DM),
|
|
3004
|
+
* `wo` the close term of Woodie (the open of the current period, or the bar's close while developing).
|
|
2958
3005
|
*/
|
|
2959
|
-
function
|
|
2960
|
-
|
|
2961
|
-
h
|
|
2962
|
-
l: number,
|
|
2963
|
-
c: number,
|
|
2964
|
-
o: number
|
|
2965
|
-
): number[] {
|
|
2966
|
-
// Return NaN array if data is insufficient
|
|
2967
|
-
if (isNaN(h) || isNaN(l) || isNaN(c)) {
|
|
2968
|
-
return Array(11).fill(NaN);
|
|
2969
|
-
}
|
|
2970
|
-
|
|
2971
|
-
const levels: number[] = Array(11).fill(NaN);
|
|
2972
|
-
|
|
2973
|
-
// Calculate pivot point (P)
|
|
2974
|
-
let P: number;
|
|
2975
|
-
|
|
2976
|
-
switch (type) {
|
|
2977
|
-
case 'Traditional':
|
|
2978
|
-
case 'Fibonacci':
|
|
2979
|
-
case 'Classic':
|
|
2980
|
-
P = (h + l + c) / 3;
|
|
2981
|
-
break;
|
|
2982
|
-
case 'Woodie':
|
|
2983
|
-
P = (h + l + 2 * c) / 4;
|
|
2984
|
-
break;
|
|
2985
|
-
case 'DM':
|
|
2986
|
-
P = (h + l + c) / 3;
|
|
2987
|
-
break;
|
|
2988
|
-
case 'Camarilla':
|
|
2989
|
-
P = (h + l + c) / 3;
|
|
2990
|
-
break;
|
|
2991
|
-
default:
|
|
2992
|
-
P = (h + l + c) / 3;
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
|
-
levels[0] = P; // P is at index 0
|
|
2996
|
-
|
|
2997
|
-
// Calculate resistance and support levels based on type
|
|
3006
|
+
function pivotLevels(type: string, h: number, l: number, c: number, o: number, wo: number): readonly number[] {
|
|
3007
|
+
const N = NaN;
|
|
3008
|
+
const r = h - l;
|
|
2998
3009
|
switch (type) {
|
|
2999
|
-
case 'Traditional':
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
levels[5] = P + (h - l); // R3
|
|
3019
|
-
levels[6] = P - (h - l); // S3
|
|
3020
|
-
levels[7] = levels[5] + 0.618 * (h - l); // R4
|
|
3021
|
-
levels[8] = levels[6] - 0.618 * (h - l); // S4
|
|
3022
|
-
levels[9] = levels[7] + 0.382 * (h - l); // R5
|
|
3023
|
-
levels[10] = levels[8] - 0.382 * (h - l); // S5
|
|
3024
|
-
break;
|
|
3025
|
-
|
|
3026
|
-
case 'Woodie':
|
|
3027
|
-
levels[1] = 2 * P - l; // R1
|
|
3028
|
-
levels[2] = 2 * P - h; // S1
|
|
3029
|
-
levels[3] = P + (h - l); // R2
|
|
3030
|
-
levels[4] = P - (h - l); // S2
|
|
3031
|
-
levels[5] = h + 2 * (P - l); // R3
|
|
3032
|
-
levels[6] = l - 2 * (h - P); // S3
|
|
3033
|
-
levels[7] = levels[5] + (h - l); // R4
|
|
3034
|
-
levels[8] = levels[6] - (h - l); // S4
|
|
3035
|
-
levels[9] = levels[7] + (h - l); // R5
|
|
3036
|
-
levels[10] = levels[8] - (h - l); // S5
|
|
3037
|
-
break;
|
|
3038
|
-
|
|
3010
|
+
case 'Traditional': {
|
|
3011
|
+
const p = (h + l + c) / 3;
|
|
3012
|
+
return [p, 2 * p - l, 2 * p - h, p + r, p - r, p * 2 + (h - 2 * l), p * 2 - (2 * h - l), p * 3 + (h - 3 * l),
|
|
3013
|
+
p * 3 - (3 * h - l), p * 4 + (h - 4 * l), p * 4 - (4 * h - l)];
|
|
3014
|
+
}
|
|
3015
|
+
case 'Fibonacci': {
|
|
3016
|
+
const p = (h + l + c) / 3;
|
|
3017
|
+
return [p, p + 0.382 * r, p - 0.382 * r, p + 0.618 * r, p - 0.618 * r, p + 1 * r, p - 1 * r, N, N, N, N];
|
|
3018
|
+
}
|
|
3019
|
+
case 'Classic': {
|
|
3020
|
+
const p = (h + l + c) / 3;
|
|
3021
|
+
return [p, 2 * p - l, 2 * p - h, p + r, p - r, p + 2 * r, p - 2 * r, p + 3 * r, p - 3 * r, N, N];
|
|
3022
|
+
}
|
|
3023
|
+
case 'Woodie': {
|
|
3024
|
+
const p = (h + l + 2 * wo) / 4;
|
|
3025
|
+
const r3 = h + 2 * (p - l);
|
|
3026
|
+
const s3 = l - 2 * (h - p);
|
|
3027
|
+
return [p, 2 * p - l, 2 * p - h, p + r, p - r, r3, s3, r3 + r, s3 - r, N, N];
|
|
3028
|
+
}
|
|
3039
3029
|
case 'DM': {
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
const newP = x / (isNaN(o) ? 4 : 5);
|
|
3043
|
-
levels[0] = newP;
|
|
3044
|
-
levels[1] = x / 2 - l; // R1
|
|
3045
|
-
levels[2] = x / 2 - h; // S1
|
|
3046
|
-
// R2-S5 remain NaN
|
|
3047
|
-
break;
|
|
3030
|
+
const x = c < o ? h + 2 * l + c : c > o ? 2 * h + l + c : h + l + 2 * c;
|
|
3031
|
+
return [x / 4, x / 2 - l, x / 2 - h, N, N, N, N, N, N, N, N];
|
|
3048
3032
|
}
|
|
3049
|
-
|
|
3050
3033
|
case 'Camarilla': {
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
levels[4] = c - range * 1.1 / 6; // S2
|
|
3056
|
-
levels[5] = c + range * 1.1 / 4; // R3
|
|
3057
|
-
levels[6] = c - range * 1.1 / 4; // S3
|
|
3058
|
-
levels[7] = c + range * 1.1 / 2; // R4
|
|
3059
|
-
levels[8] = c - range * 1.1 / 2; // S4
|
|
3060
|
-
levels[9] = h; // R5 (high)
|
|
3061
|
-
levels[10] = l; // S5 (low)
|
|
3062
|
-
break;
|
|
3034
|
+
const p = (h + l + c) / 3;
|
|
3035
|
+
const r5 = (h / l) * c;
|
|
3036
|
+
return [p, c + (r * 1.1) / 12, c - (r * 1.1) / 12, c + (r * 1.1) / 6, c - (r * 1.1) / 6, c + (r * 1.1) / 4,
|
|
3037
|
+
c - (r * 1.1) / 4, c + (r * 1.1) / 2, c - (r * 1.1) / 2, r5, 2 * c - r5];
|
|
3063
3038
|
}
|
|
3039
|
+
default:
|
|
3040
|
+
return NA_LEVELS;
|
|
3064
3041
|
}
|
|
3065
|
-
|
|
3066
|
-
return levels;
|
|
3067
3042
|
}
|
|
3068
3043
|
|
|
3069
3044
|
/**
|