oakscriptjs 0.10.1 → 0.11.1
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.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 +84 -0
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +153 -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 +33 -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.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 +10 -27
- package/dist/cjs/line/index.d.ts.map +1 -1
- package/dist/cjs/line/index.js +11 -22
- 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/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/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 +91 -21
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +207 -19
- 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 +24 -1
- package/dist/cjs/session/bars.d.ts.map +1 -1
- package/dist/cjs/session/bars.js +56 -7
- package/dist/cjs/session/bars.js.map +3 -3
- package/dist/cjs/session/calendar.d.ts +13 -2
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +39 -9
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/session/context.d.ts +76 -0
- package/dist/cjs/session/context.d.ts.map +1 -0
- package/dist/cjs/session/context.js +114 -0
- package/dist/cjs/session/context.js.map +7 -0
- 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 -1
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +2 -1
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +7 -3
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +4 -3
- package/dist/cjs/str/numberformat.js.map +2 -2
- package/dist/cjs/ta/index.d.ts +67 -34
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +202 -145
- package/dist/cjs/ta/index.js.map +2 -2
- package/dist/cjs/ta/limits.d.ts +35 -0
- package/dist/cjs/ta/limits.d.ts.map +1 -0
- package/dist/cjs/ta/limits.js +64 -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 +8 -8
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +10 -8
- package/dist/cjs/ta-series.js.map +2 -2
- package/dist/cjs/table/index.d.ts +3 -1
- package/dist/cjs/table/index.d.ts.map +1 -1
- package/dist/cjs/table/index.js +1 -0
- package/dist/cjs/table/index.js.map +3 -3
- 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 +26 -1
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +94 -12
- 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 +28 -4
- 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.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 +84 -0
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +260 -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 +13 -2
- 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.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 +10 -27
- package/dist/esm/line/index.d.ts.map +1 -1
- package/dist/esm/line/index.js +22 -60
- 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/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/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 +91 -21
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +259 -28
- 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 +24 -1
- package/dist/esm/session/bars.d.ts.map +1 -1
- package/dist/esm/session/bars.js +75 -6
- package/dist/esm/session/bars.js.map +1 -1
- package/dist/esm/session/calendar.d.ts +13 -2
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +54 -10
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/session/context.d.ts +76 -0
- package/dist/esm/session/context.d.ts.map +1 -0
- package/dist/esm/session/context.js +124 -0
- package/dist/esm/session/context.js.map +1 -0
- 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 -1
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +9 -2
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +7 -3
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +11 -5
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +67 -34
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +284 -240
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/limits.d.ts +35 -0
- package/dist/esm/ta/limits.d.ts.map +1 -0
- package/dist/esm/ta/limits.js +67 -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 +8 -8
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +11 -8
- package/dist/esm/ta-series.js.map +1 -1
- package/dist/esm/table/index.d.ts +3 -1
- package/dist/esm/table/index.d.ts.map +1 -1
- package/dist/esm/table/index.js +6 -1
- package/dist/esm/table/index.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 +26 -1
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +141 -16
- 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 +28 -4
- 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 +8 -2
- package/src/callsite/index.ts +150 -100
- package/src/drawing/registry.ts +278 -5
- package/src/index.ts +44 -1
- package/src/input.ts +18 -1
- package/src/label/index.ts +4 -1
- package/src/line/index.ts +22 -64
- package/src/linefill/index.ts +20 -1
- package/src/math/index.ts +2 -0
- package/src/polyline/index.ts +3 -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 +355 -34
- package/src/security/other.ts +269 -0
- package/src/session/bars.ts +70 -6
- package/src/session/calendar.ts +60 -10
- package/src/session/context.ts +162 -0
- package/src/str/floatingdecimal.ts +268 -0
- package/src/str/index.ts +8 -2
- package/src/str/numberformat.ts +13 -6
- package/src/ta/index.ts +304 -248
- package/src/ta/limits.ts +74 -0
- package/src/ta/running-sum.ts +2 -0
- package/src/ta-series.ts +19 -16
- package/src/table/index.ts +4 -1
- package/src/time/index.ts +6 -3
- package/src/time/session.ts +137 -15
- package/src/time/timezone.ts +9 -2
- package/src/types/metadata.ts +28 -4
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 { checkPositiveLength, 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
|
|
|
@@ -66,6 +68,7 @@ export function sma(source: Source, length: simple_int | ArrayLike<number>): ser
|
|
|
66
68
|
* ```
|
|
67
69
|
*/
|
|
68
70
|
export function ema(source: Source, length: simple_int): series_float {
|
|
71
|
+
checkPositiveLength('ema', length, 0);
|
|
69
72
|
const result: series_float = [];
|
|
70
73
|
const len = Math.floor(length);
|
|
71
74
|
const multiplier = 2 / (len + 1);
|
|
@@ -131,7 +134,25 @@ export function ema(source: Source, length: simple_int): series_float {
|
|
|
131
134
|
* const oversold = rsi14.map(v => v < 30);
|
|
132
135
|
* ```
|
|
133
136
|
*/
|
|
134
|
-
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)`, na when that divides by zero (`x == -y`). Versions 5 and 6 have the length form only.
|
|
142
|
+
*/
|
|
143
|
+
export function rsi(x: Source, y: Source): series_float;
|
|
144
|
+
export function rsi(source: Source, length: simple_int | Source): series_float {
|
|
145
|
+
if (typeof length !== 'number') {
|
|
146
|
+
return Array.from(source, (xv, i) => {
|
|
147
|
+
const x = xv ?? NaN;
|
|
148
|
+
const y = length[i] ?? NaN;
|
|
149
|
+
if (eq(y, 0)) return 100;
|
|
150
|
+
if (eq(x, 0)) return 0;
|
|
151
|
+
const r = 100 - 100 / (1 + x / y);
|
|
152
|
+
return Number.isFinite(r) ? r : NaN;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
checkPositiveLength('rsi', length, 0, 'y');
|
|
135
156
|
const result: series_float = [];
|
|
136
157
|
|
|
137
158
|
// Calculate price changes
|
|
@@ -234,6 +255,7 @@ export function bb(
|
|
|
234
255
|
length: simple_int,
|
|
235
256
|
mult: simple_float
|
|
236
257
|
): [series_float, series_float, series_float] {
|
|
258
|
+
checkSumLength('ta.bb', length, 0);
|
|
237
259
|
const basis = sma(series, length);
|
|
238
260
|
const dev = stdev(series, length);
|
|
239
261
|
|
|
@@ -285,14 +307,20 @@ function lastValues(source: Source, i: number, length: number): number[] | null
|
|
|
285
307
|
|
|
286
308
|
/**
|
|
287
309
|
* 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`
|
|
310
|
+
* the oldest highest (or lowest) value (the newest one with `newest`), or -1 when the window is empty. The window
|
|
311
|
+
* stops at the first `na` value going back (the callers pass the non-na values only, see {@link keptValues}), and a
|
|
312
|
+
* fractional length is truncated (7.5 is 7 bars), as PineScript does.
|
|
313
|
+
* `zeros[i]` tells whether the window holds a +0 (highest) or a -0 (lowest), the zero that `Math.max` / `Math.min`
|
|
314
|
+
* return when the extreme is 0 (not kept with `newest`).
|
|
292
315
|
*/
|
|
293
|
-
function windowExtremes(
|
|
316
|
+
function windowExtremes(
|
|
317
|
+
source: Source,
|
|
318
|
+
length: number,
|
|
319
|
+
lowest: boolean,
|
|
320
|
+
newest = false
|
|
321
|
+
): { at: Int32Array; zeros: Uint8Array } {
|
|
294
322
|
const n = source.length;
|
|
295
|
-
const span = length > 0 ? Math.
|
|
323
|
+
const span = length > 0 ? Math.trunc(length) : 0; // PineScript casts the length to int
|
|
296
324
|
const at = new Int32Array(n).fill(-1);
|
|
297
325
|
const zeros = new Uint8Array(n);
|
|
298
326
|
const queue = new Int32Array(n); // bar indices; values from best to worst, the oldest first among equal values
|
|
@@ -310,8 +338,10 @@ function windowExtremes(source: Source, length: number, lowest: boolean): { at:
|
|
|
310
338
|
zeroCount = 0;
|
|
311
339
|
continue;
|
|
312
340
|
}
|
|
313
|
-
// drop the values that the new one beats; equal values stay (the oldest wins ties)
|
|
314
|
-
while (tail > head
|
|
341
|
+
// drop the values that the new one beats; equal values stay (the oldest wins ties), or go with `newest`
|
|
342
|
+
while (tail > head) {
|
|
343
|
+
const last = source[queue[tail - 1]!]!;
|
|
344
|
+
if (!(lowest ? last > x : last < x) && !(newest && last === x)) break;
|
|
315
345
|
if (isPreferredZero(queue[--tail]!)) zeroCount--;
|
|
316
346
|
}
|
|
317
347
|
queue[tail++] = i;
|
|
@@ -328,20 +358,79 @@ function windowExtremes(source: Source, length: number, lowest: boolean): { at:
|
|
|
328
358
|
return { at, zeros };
|
|
329
359
|
}
|
|
330
360
|
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
361
|
+
/**
|
|
362
|
+
* The non-na values of `source` with their bars, and for each bar the position of its last non-na value (-1 when
|
|
363
|
+
* there is none yet). PineScript's ta.highest / ta.lowest / ta.highestbars / ta.lowestbars / ta.pivothigh /
|
|
364
|
+
* ta.pivotlow keep the non-na values they receive and nothing else.
|
|
365
|
+
*/
|
|
366
|
+
function keptValues(source: Source): { values: number[]; bars: number[]; last: Int32Array } {
|
|
367
|
+
const values: number[] = [];
|
|
368
|
+
const bars: number[] = [];
|
|
369
|
+
const last = new Int32Array(source.length);
|
|
370
|
+
for (let i = 0; i < source.length; i++) {
|
|
371
|
+
const x = source[i]!;
|
|
372
|
+
if (!Number.isNaN(x)) {
|
|
373
|
+
values.push(x);
|
|
374
|
+
bars.push(i);
|
|
375
|
+
}
|
|
376
|
+
last[i] = values.length - 1;
|
|
377
|
+
}
|
|
378
|
+
return { values, bars, last };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* ta.highest / ta.lowest / ta.highestbars / ta.lowestbars with a series length (a length per bar): on each bar the
|
|
383
|
+
* best of the last `length[i]` non-na values (the newest one on ties), na before bar `length[i] - 1`, as PineScript
|
|
384
|
+
* (measured with a length that changes on each bar, with and without na in the source).
|
|
385
|
+
*/
|
|
386
|
+
function extremeSeriesLength(source: Source, lengths: ArrayLike<number>, lowest: boolean, offsets: boolean): number[] {
|
|
387
|
+
const name = (lowest ? 'lowest' : 'highest') + (offsets ? 'bars' : '');
|
|
388
|
+
const { values, bars, last } = keptValues(source);
|
|
389
|
+
return Array.from(source, (_, i) => {
|
|
390
|
+
const len = Math.trunc(lengths[i]!);
|
|
391
|
+
checkWindowLength(name, len, i);
|
|
392
|
+
const k = last[i]!;
|
|
393
|
+
if (!(len >= 1) || i < len - 1 || k < 0) return NaN;
|
|
394
|
+
let best = k;
|
|
395
|
+
for (let j = k - 1; j >= 0 && j > k - len; j--) {
|
|
396
|
+
if (lowest ? values[j]! < values[best]! : values[j]! > values[best]!) best = j;
|
|
397
|
+
}
|
|
398
|
+
return offsets ? -(i - bars[best]!) : values[best]!;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Offset to the best of the last `length` non-na values (the newest one on ties): the distance in bars, so below
|
|
404
|
+
* `-(length - 1)` after an na value. na before bar `length - 1` and while there is no value.
|
|
405
|
+
*/
|
|
406
|
+
function extremeOffset(source: Source, length: simple_int | ArrayLike<number>, lowest: boolean): series_int {
|
|
407
|
+
if (typeof length !== 'number') return extremeSeriesLength(source, length, lowest, true);
|
|
408
|
+
checkWindowLength(lowest ? 'lowestbars' : 'highestbars', length, 0);
|
|
409
|
+
const { values, bars, last } = keptValues(source);
|
|
410
|
+
const { at } = windowExtremes(values, length, lowest, true);
|
|
411
|
+
const len = Math.trunc(length);
|
|
412
|
+
// -(i - bar): -0 when the extreme is the current bar, as the offset -j of the window scan
|
|
413
|
+
return source.map((_, i) => {
|
|
414
|
+
const k = last[i]!;
|
|
415
|
+
return i < len - 1 || k < 0 || at[k]! < 0 ? NaN : -(i - bars[at[k]!]!);
|
|
416
|
+
});
|
|
336
417
|
}
|
|
337
418
|
|
|
338
|
-
/**
|
|
339
|
-
|
|
340
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Best of the last `length` non-na values (of all of them when there are fewer), with the zero sign of `Math.max` /
|
|
421
|
+
* `Math.min`. na before bar `length - 1` and while there is no value.
|
|
422
|
+
*/
|
|
423
|
+
function extremeValue(source: Source, length: simple_int | ArrayLike<number>, lowest: boolean): series_float {
|
|
424
|
+
if (typeof length !== 'number') return extremeSeriesLength(source, length, lowest, false);
|
|
425
|
+
checkWindowLength(lowest ? 'lowest' : 'highest', length, 0);
|
|
426
|
+
const { values, last } = keptValues(source);
|
|
427
|
+
const { at, zeros } = windowExtremes(values, length, lowest);
|
|
428
|
+
const len = Math.trunc(length);
|
|
341
429
|
return source.map((_, i) => {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
430
|
+
const k = last[i]!;
|
|
431
|
+
if (i < len - 1 || k < 0 || at[k]! < 0) return NaN;
|
|
432
|
+
const v = values[at[k]!]!;
|
|
433
|
+
return v === 0 && zeros[k] ? (lowest ? -0 : 0) : v;
|
|
345
434
|
});
|
|
346
435
|
}
|
|
347
436
|
|
|
@@ -436,6 +525,7 @@ export function crossunder(series1: Source, series2: Source): series_bool {
|
|
|
436
525
|
* ```
|
|
437
526
|
*/
|
|
438
527
|
export function change(source: Source, length: simple_int = 1): series_float {
|
|
528
|
+
checkPositiveLength('change', length, 0);
|
|
439
529
|
const result: series_float = [];
|
|
440
530
|
|
|
441
531
|
for (let i = 0; i < source.length; i++) {
|
|
@@ -548,6 +638,7 @@ export function tr(
|
|
|
548
638
|
* ```
|
|
549
639
|
*/
|
|
550
640
|
export function atr(length: simple_int, high?: Source, low?: Source, close?: Source): series_float {
|
|
641
|
+
checkPositiveLength('atr->rma', length, 0);
|
|
551
642
|
if (!high || !low || !close) {
|
|
552
643
|
throw new Error(
|
|
553
644
|
'ta.atr() requires high, low, and close series. ' +
|
|
@@ -693,6 +784,7 @@ export function supertrend(
|
|
|
693
784
|
* ```
|
|
694
785
|
*/
|
|
695
786
|
export function rma(source: Source, length: simple_int): series_float {
|
|
787
|
+
checkPositiveLength('rma', length, 0);
|
|
696
788
|
const result: series_float = [];
|
|
697
789
|
// Floor the length to match PineScript's auto-truncation of float to int
|
|
698
790
|
const len = Math.floor(length);
|
|
@@ -763,6 +855,8 @@ function wmaAdd(a: number, b: number): number {
|
|
|
763
855
|
* - Summed from the oldest value to the newest; as PineScript, a partial sum `s + t` with
|
|
764
856
|
* `|s + t| <= 1e-10 * max(1, |s| + |t|)` is 0 (a cancellation residue, or values below 1e-10)
|
|
765
857
|
* - More responsive to recent price changes than SMA
|
|
858
|
+
* - `length` can be an array (a length per bar, PineScript series length): each bar gives the result of its own
|
|
859
|
+
* length (`na` for a length below 1)
|
|
766
860
|
*
|
|
767
861
|
* @example
|
|
768
862
|
* ```typescript
|
|
@@ -770,10 +864,11 @@ function wmaAdd(a: number, b: number): number {
|
|
|
770
864
|
* // WMA gives more weight to recent prices
|
|
771
865
|
* ```
|
|
772
866
|
*/
|
|
773
|
-
export function wma(source: Source, length: simple_int): series_float {
|
|
867
|
+
export function wma(source: Source, length: simple_int | ArrayLike<number>): series_float {
|
|
774
868
|
const result: series_float = [];
|
|
775
869
|
// Floor the length to match PineScript's auto-truncation of float to int
|
|
776
|
-
const
|
|
870
|
+
const fixed = typeof length === 'number' ? Math.floor(length) : NaN;
|
|
871
|
+
if (typeof length === 'number') checkPositiveLength('wma', fixed, 0);
|
|
777
872
|
// PineScript: na on a bar with an na source (also +/-Infinity) and until `len` non-na values were received;
|
|
778
873
|
// otherwise the last `len` bars, each na replaced by the previous non-na value
|
|
779
874
|
const filled: number[] = [];
|
|
@@ -787,7 +882,10 @@ export function wma(source: Source, length: simple_int): series_float {
|
|
|
787
882
|
count++;
|
|
788
883
|
}
|
|
789
884
|
filled.push(last);
|
|
790
|
-
|
|
885
|
+
// a series length: the window of this bar's length (the result of ta.wma with that fixed length)
|
|
886
|
+
const len = typeof length === 'number' ? fixed : Math.floor(length[i]!);
|
|
887
|
+
if (typeof length !== 'number') checkPositiveLength('wma', len, i);
|
|
888
|
+
if (isNa || !(len >= 1) || count < len) {
|
|
791
889
|
result.push(NaN);
|
|
792
890
|
continue;
|
|
793
891
|
}
|
|
@@ -812,11 +910,15 @@ export function wma(source: Source, length: simple_int): series_float {
|
|
|
812
910
|
* @returns Highest value series (na on the first `length - 1` bars)
|
|
813
911
|
*
|
|
814
912
|
* @remarks
|
|
815
|
-
* As in PineScript:
|
|
816
|
-
*
|
|
913
|
+
* As in PineScript: `na` values are skipped. The result is the highest of the last `length` non-na values (of all
|
|
914
|
+
* of them when there are fewer), also on a bar whose own value is `na`; `na` before bar `length - 1` and while no
|
|
915
|
+
* value was received. A fractional length is truncated (7.5 is 7 bars).
|
|
916
|
+
* `length` can be an array (a length per bar, PineScript series length), also for `ta.lowest`, `ta.highestbars`
|
|
917
|
+
* and `ta.lowestbars`: each bar uses its own length.
|
|
918
|
+
* A length above 15,000 throws the PineScript runtime error (also `ta.lowest`, `ta.highestbars`, `ta.lowestbars`).
|
|
817
919
|
* PineScript `ta.highest(length)` (source = high) is `ta.highest(length)` in the script API.
|
|
818
920
|
*/
|
|
819
|
-
export function highest(source: Source, length: simple_int): series_float {
|
|
921
|
+
export function highest(source: Source, length: simple_int | ArrayLike<number>): series_float {
|
|
820
922
|
return extremeValue(source, length, false);
|
|
821
923
|
}
|
|
822
924
|
|
|
@@ -828,10 +930,11 @@ export function highest(source: Source, length: simple_int): series_float {
|
|
|
828
930
|
* @returns Lowest value series (na on the first `length - 1` bars)
|
|
829
931
|
*
|
|
830
932
|
* @remarks
|
|
831
|
-
* As in PineScript:
|
|
832
|
-
*
|
|
933
|
+
* As in PineScript: `na` values are skipped. The result is the lowest of the last `length` non-na values (of all
|
|
934
|
+
* of them when there are fewer), also on a bar whose own value is `na`; `na` before bar `length - 1` and while no
|
|
935
|
+
* value was received.
|
|
833
936
|
*/
|
|
834
|
-
export function lowest(source: Source, length: simple_int): series_float {
|
|
937
|
+
export function lowest(source: Source, length: simple_int | ArrayLike<number>): series_float {
|
|
835
938
|
return extremeValue(source, length, true);
|
|
836
939
|
}
|
|
837
940
|
|
|
@@ -943,6 +1046,7 @@ function monotonic(source: Source, length: simple_int, step: (newer: number, old
|
|
|
943
1046
|
* ```
|
|
944
1047
|
*/
|
|
945
1048
|
export function rising(source: Source, length: simple_int): series_bool {
|
|
1049
|
+
checkPositiveLength('rising', length, 0);
|
|
946
1050
|
return monotonic(source, length, gt);
|
|
947
1051
|
}
|
|
948
1052
|
|
|
@@ -968,6 +1072,7 @@ export function rising(source: Source, length: simple_int): series_bool {
|
|
|
968
1072
|
* ```
|
|
969
1073
|
*/
|
|
970
1074
|
export function falling(source: Source, length: simple_int): series_bool {
|
|
1075
|
+
checkPositiveLength('falling', length, 0);
|
|
971
1076
|
return monotonic(source, length, lt);
|
|
972
1077
|
}
|
|
973
1078
|
|
|
@@ -993,6 +1098,7 @@ export function falling(source: Source, length: simple_int): series_bool {
|
|
|
993
1098
|
* ```
|
|
994
1099
|
*/
|
|
995
1100
|
export function roc(source: Source, length: simple_int): series_float {
|
|
1101
|
+
checkPositiveLength('roc', length, 0);
|
|
996
1102
|
const result: series_float = [];
|
|
997
1103
|
|
|
998
1104
|
for (let i = 0; i < source.length; i++) {
|
|
@@ -1033,6 +1139,7 @@ export function roc(source: Source, length: simple_int): series_float {
|
|
|
1033
1139
|
* ```
|
|
1034
1140
|
*/
|
|
1035
1141
|
export function mom(source: Source, length: simple_int): series_float {
|
|
1142
|
+
checkPositiveLength('mom', length, 0);
|
|
1036
1143
|
const result: series_float = [];
|
|
1037
1144
|
|
|
1038
1145
|
for (let i = 0; i < source.length; i++) {
|
|
@@ -1072,6 +1179,7 @@ export function mom(source: Source, length: simple_int): series_float {
|
|
|
1072
1179
|
* ```
|
|
1073
1180
|
*/
|
|
1074
1181
|
export function dev(source: Source, length: simple_int): series_float {
|
|
1182
|
+
checkPositiveLength('dev', length, 0);
|
|
1075
1183
|
const len = Math.floor(length);
|
|
1076
1184
|
return sma(source, len).map((mean, i) => {
|
|
1077
1185
|
if (i < len - 1 || Number.isNaN(mean)) return NaN;
|
|
@@ -1127,6 +1235,7 @@ function varianceOf(
|
|
|
1127
1235
|
return biased ? result : result.map((v, i) => (Math.floor(length[i]!) > 1 ? v : NaN));
|
|
1128
1236
|
}
|
|
1129
1237
|
const len = Math.floor(length);
|
|
1238
|
+
checkSumLength(name, len, 0);
|
|
1130
1239
|
// PineScript: the last `len` non-na values (also +/-Infinity skipped), as ta.sma
|
|
1131
1240
|
if ((biased ? len : len - 1) <= 0) return source.map(() => NaN);
|
|
1132
1241
|
return runningVariance(source, len, biased);
|
|
@@ -1154,6 +1263,7 @@ function varianceOf(
|
|
|
1154
1263
|
* ```
|
|
1155
1264
|
*/
|
|
1156
1265
|
export function median(source: Source, length: simple_int): series_float {
|
|
1266
|
+
checkPositiveLength('median', length, 0);
|
|
1157
1267
|
const len = Math.floor(length);
|
|
1158
1268
|
const n = source.length;
|
|
1159
1269
|
const out: series_float = new Array<number>(n).fill(NaN);
|
|
@@ -1241,6 +1351,7 @@ export function swma(source: Source): series_float {
|
|
|
1241
1351
|
* ```
|
|
1242
1352
|
*/
|
|
1243
1353
|
export function vwma(source: Source, length: simple_int, volume?: Source): series_float {
|
|
1354
|
+
checkPositiveLength('vwma->sma', length, 0);
|
|
1244
1355
|
if (!volume) {
|
|
1245
1356
|
throw new Error(
|
|
1246
1357
|
'ta.vwma() requires volume series. ' +
|
|
@@ -1294,6 +1405,7 @@ export function vwma(source: Source, length: simple_int, volume?: Source): serie
|
|
|
1294
1405
|
* ```
|
|
1295
1406
|
*/
|
|
1296
1407
|
export function linreg(source: Source, length: simple_int, offset: simple_int = 0): series_float {
|
|
1408
|
+
checkPositiveLength('linreg', length, 0);
|
|
1297
1409
|
const result: series_float = [];
|
|
1298
1410
|
// Floor the length to match PineScript's auto-truncation of float to int
|
|
1299
1411
|
const len = Math.floor(length);
|
|
@@ -1363,6 +1475,7 @@ export function linreg(source: Source, length: simple_int, offset: simple_int =
|
|
|
1363
1475
|
* ```
|
|
1364
1476
|
*/
|
|
1365
1477
|
export function correlation(source1: Source, source2: Source, length: simple_int): series_float {
|
|
1478
|
+
checkPositiveLength('correlation', length, 0);
|
|
1366
1479
|
// PineScript: from compensated running sums, cov = sma(x * y) - sma(x) * sma(y), r = cov / sqrt(var(x) * var(y))
|
|
1367
1480
|
// (biased variances); each running sum skips its own na values
|
|
1368
1481
|
const product = Array.from({ length: source1.length }, (_, i) => {
|
|
@@ -1401,8 +1514,8 @@ export function correlation(source1: Source, source2: Source, length: simple_int
|
|
|
1401
1514
|
*
|
|
1402
1515
|
* PineScript rules:
|
|
1403
1516
|
* - percentrank = count(source[k] <= source, k = 1..length) * 100 / length (in this order: the PineScript bits)
|
|
1404
|
-
* - a value from bar `length` on
|
|
1405
|
-
*
|
|
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)
|
|
1406
1519
|
* - the comparison uses the 1e-10 tolerance
|
|
1407
1520
|
*
|
|
1408
1521
|
* @example
|
|
@@ -1413,15 +1526,16 @@ export function correlation(source1: Source, source2: Source, length: simple_int
|
|
|
1413
1526
|
* ```
|
|
1414
1527
|
*/
|
|
1415
1528
|
export function percentrank(source: Source, length: simple_int): series_float {
|
|
1529
|
+
checkPositiveLength('percentrank', length, 0);
|
|
1416
1530
|
const result: series_float = [];
|
|
1417
1531
|
|
|
1418
1532
|
for (let i = 0; i < source.length; i++) {
|
|
1419
1533
|
const currentValue = source[i]!;
|
|
1420
|
-
if (i < length
|
|
1534
|
+
if (i < length) {
|
|
1421
1535
|
result.push(NaN);
|
|
1422
1536
|
continue;
|
|
1423
1537
|
}
|
|
1424
|
-
// 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)
|
|
1425
1539
|
let countLessOrEqual = 0;
|
|
1426
1540
|
for (let j = 1; j <= length; j++) {
|
|
1427
1541
|
if (le(source[i - j]!, currentValue)) countLessOrEqual++;
|
|
@@ -1461,6 +1575,7 @@ export function percentrank(source: Source, length: simple_int): series_float {
|
|
|
1461
1575
|
* \`\`\`
|
|
1462
1576
|
*/
|
|
1463
1577
|
export function cci(source: Source, length: simple_int): series_float {
|
|
1578
|
+
checkPositiveLength('cci', length, 0);
|
|
1464
1579
|
const smaValues = sma(source, length);
|
|
1465
1580
|
const devValues = dev(source, length);
|
|
1466
1581
|
let last = 0;
|
|
@@ -1497,6 +1612,7 @@ export function cci(source: Source, length: simple_int): series_float {
|
|
|
1497
1612
|
* \`\`\`
|
|
1498
1613
|
*/
|
|
1499
1614
|
export function stoch(source: Source, high: Source, low: Source, length: simple_int): series_float {
|
|
1615
|
+
checkPositiveLength('stoch->highest', length, 0);
|
|
1500
1616
|
const result: series_float = [];
|
|
1501
1617
|
const lowestValues = lowest(low, length);
|
|
1502
1618
|
const highestValues = highest(high, length);
|
|
@@ -1549,6 +1665,7 @@ export function stoch(source: Source, high: Source, low: Source, length: simple_
|
|
|
1549
1665
|
* ```
|
|
1550
1666
|
*/
|
|
1551
1667
|
export function mfi(source: Source, length: simple_int, volume?: Source): series_float {
|
|
1668
|
+
checkPositiveLength('mfi', length, 0);
|
|
1552
1669
|
if (!volume) {
|
|
1553
1670
|
throw new Error(
|
|
1554
1671
|
'ta.mfi() requires volume series. ' +
|
|
@@ -1607,6 +1724,7 @@ export function mfi(source: Source, length: simple_int, volume?: Source): series
|
|
|
1607
1724
|
* ```
|
|
1608
1725
|
*/
|
|
1609
1726
|
export function hma(source: Source, length: simple_int): series_float {
|
|
1727
|
+
checkPositiveLength('hma', length, 0);
|
|
1610
1728
|
const halfLength = Math.floor(length / 2);
|
|
1611
1729
|
const sqrtLength = Math.floor(Math.sqrt(length));
|
|
1612
1730
|
|
|
@@ -1738,35 +1856,31 @@ export function sar(
|
|
|
1738
1856
|
return result;
|
|
1739
1857
|
}
|
|
1740
1858
|
|
|
1859
|
+
/** A pivot length: one value, or a value per bar (PineScript series length). */
|
|
1860
|
+
type PivotLength = simple_int | ArrayLike<number>;
|
|
1861
|
+
|
|
1741
1862
|
/**
|
|
1742
1863
|
* Pivot detection as PineScript computes it.
|
|
1743
1864
|
*
|
|
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;
|
|
1865
|
+
* The function keeps the non-na values it receives. On each bar it takes the last `leftbars + rightbars + 1` of them
|
|
1866
|
+
* (all of them when there are fewer) and finds the highest (the newest one on equal values; the lowest for a pivot
|
|
1867
|
+
* low): there is a pivot when that value is on the bar `rightbars` bars back, and its value is returned on this bar,
|
|
1868
|
+
* 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
|
|
1869
|
+
* right. `na` before bar `leftbars + rightbars`.
|
|
1870
|
+
*/
|
|
1871
|
+
function pivot(source: Source, leftbars: PivotLength, rightbars: PivotLength, isHigh: boolean): series_float {
|
|
1872
|
+
const { values, bars, last } = keptValues(source);
|
|
1873
|
+
return Array.from(source, (_, i) => {
|
|
1874
|
+
// a series length: the lengths of the current bar
|
|
1875
|
+
const left = Math.trunc(typeof leftbars === 'number' ? leftbars : leftbars[i]!);
|
|
1876
|
+
const right = Math.trunc(typeof rightbars === 'number' ? rightbars : rightbars[i]!);
|
|
1877
|
+
const k = last[i]!;
|
|
1878
|
+
if (!(i >= left + right) || k < 0) return NaN;
|
|
1879
|
+
let best = k;
|
|
1880
|
+
for (let j = k - 1; j >= 0 && j >= k - left - right; j--) {
|
|
1881
|
+
if (isHigh ? values[j]! > values[best]! : values[j]! < values[best]!) best = j;
|
|
1882
|
+
}
|
|
1883
|
+
return bars[best] === i - right ? values[best]! : NaN;
|
|
1770
1884
|
});
|
|
1771
1885
|
}
|
|
1772
1886
|
|
|
@@ -1784,7 +1898,10 @@ function pivot(source: Source, leftbars: simple_int, rightbars: simple_int, isHi
|
|
|
1784
1898
|
* - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
|
|
1785
1899
|
* index is `i - rightbars`
|
|
1786
1900
|
* - A pivot high may equal values on its left but must be higher than all values on its right
|
|
1787
|
-
* -
|
|
1901
|
+
* - `na` values are skipped: the window is the last `leftbars + rightbars + 1` non-na values, and the highest of
|
|
1902
|
+
* them must be on the bar `rightbars` bars back
|
|
1903
|
+
* - `leftbars` / `rightbars` can be arrays (a length per bar, PineScript series length), also for `ta.pivotlow`:
|
|
1904
|
+
* each bar uses its own lengths
|
|
1788
1905
|
*
|
|
1789
1906
|
* @example
|
|
1790
1907
|
* ```typescript
|
|
@@ -1794,8 +1911,8 @@ function pivot(source: Source, leftbars: simple_int, rightbars: simple_int, isHi
|
|
|
1794
1911
|
*/
|
|
1795
1912
|
export function pivothigh(
|
|
1796
1913
|
sourceOrLeftbars: Source | simple_int,
|
|
1797
|
-
leftbarsOrRightbars:
|
|
1798
|
-
rightbars?:
|
|
1914
|
+
leftbarsOrRightbars: PivotLength,
|
|
1915
|
+
rightbars?: PivotLength,
|
|
1799
1916
|
high?: Source
|
|
1800
1917
|
): series_float {
|
|
1801
1918
|
if (rightbars === undefined) {
|
|
@@ -1821,7 +1938,8 @@ export function pivothigh(
|
|
|
1821
1938
|
* - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
|
|
1822
1939
|
* index is `i - rightbars`
|
|
1823
1940
|
* - A pivot low may equal values on its left but must be lower than all values on its right
|
|
1824
|
-
* -
|
|
1941
|
+
* - `na` values are skipped: the window is the last `leftbars + rightbars + 1` non-na values, and the lowest of
|
|
1942
|
+
* them must be on the bar `rightbars` bars back
|
|
1825
1943
|
*
|
|
1826
1944
|
* @example
|
|
1827
1945
|
* ```typescript
|
|
@@ -1831,8 +1949,8 @@ export function pivothigh(
|
|
|
1831
1949
|
*/
|
|
1832
1950
|
export function pivotlow(
|
|
1833
1951
|
sourceOrLeftbars: Source | simple_int,
|
|
1834
|
-
leftbarsOrRightbars:
|
|
1835
|
-
rightbars?:
|
|
1952
|
+
leftbarsOrRightbars: PivotLength,
|
|
1953
|
+
rightbars?: PivotLength,
|
|
1836
1954
|
low?: Source
|
|
1837
1955
|
): series_float {
|
|
1838
1956
|
if (rightbars === undefined) {
|
|
@@ -2013,6 +2131,7 @@ export function dmi(
|
|
|
2013
2131
|
* ```
|
|
2014
2132
|
*/
|
|
2015
2133
|
export function tsi(source: Source, shortLength: simple_int, longLength: simple_int): series_float {
|
|
2134
|
+
checkPositiveLength('tsi->ema', shortLength, 0);
|
|
2016
2135
|
const momentum: series_float = [];
|
|
2017
2136
|
|
|
2018
2137
|
// Calculate momentum (price change)
|
|
@@ -2062,6 +2181,7 @@ export function tsi(source: Source, shortLength: simple_int, longLength: simple_
|
|
|
2062
2181
|
* ```
|
|
2063
2182
|
*/
|
|
2064
2183
|
export function cmo(source: Source, length: simple_int): series_float {
|
|
2184
|
+
checkPositiveLength('cmo', length, 0);
|
|
2065
2185
|
// PineScript definition: mom = ta.change(src), sm1 = math.sum(mom >= 0 ? mom : 0, length),
|
|
2066
2186
|
// sm2 = math.sum(mom >= 0 ? 0 : -mom, length), cmo = 100 * (sm1 - sm2) / (sm1 + sm2), with the running sums of
|
|
2067
2187
|
// math.sum (bit for bit). A comparison with na is false: an na change adds 0 to sm1 and na (skipped) to sm2.
|
|
@@ -2111,6 +2231,7 @@ export function kc(
|
|
|
2111
2231
|
low?: Source,
|
|
2112
2232
|
close?: Source
|
|
2113
2233
|
): [series_float, series_float, series_float] {
|
|
2234
|
+
checkPositiveLength('kc', length, 0);
|
|
2114
2235
|
// Calculate middle band (EMA of source)
|
|
2115
2236
|
const middle = ema(source, length);
|
|
2116
2237
|
|
|
@@ -2170,6 +2291,7 @@ export function kc(
|
|
|
2170
2291
|
* ```
|
|
2171
2292
|
*/
|
|
2172
2293
|
export function bbw(source: Source, length: simple_int, mult: simple_float): series_float {
|
|
2294
|
+
checkPositiveLength('bbw', length, 0);
|
|
2173
2295
|
const [basis, upper, lower] = bb(source, length, mult);
|
|
2174
2296
|
const result: series_float = [];
|
|
2175
2297
|
|
|
@@ -2213,6 +2335,7 @@ export function bbw(source: Source, length: simple_int, mult: simple_float): ser
|
|
|
2213
2335
|
* ```
|
|
2214
2336
|
*/
|
|
2215
2337
|
export function wpr(high: Source, low: Source, close: Source, length: simple_int = 14): series_float {
|
|
2338
|
+
checkPositiveLength('wpr', length, 0);
|
|
2216
2339
|
const highs = highest(high, length);
|
|
2217
2340
|
const lows = lowest(low, length);
|
|
2218
2341
|
return Array.from({ length: close.length }, (_, i) => {
|
|
@@ -2313,6 +2436,7 @@ export function alma(
|
|
|
2313
2436
|
sigma: simple_float = 6,
|
|
2314
2437
|
floor: boolean = false
|
|
2315
2438
|
): series_float {
|
|
2439
|
+
checkPositiveLength('alma', length, 0);
|
|
2316
2440
|
const result: series_float = [];
|
|
2317
2441
|
const m = floor ? Math.floor(offset * (length - 1)) : offset * (length - 1);
|
|
2318
2442
|
const s = length / sigma;
|
|
@@ -2409,6 +2533,7 @@ export function kcw(
|
|
|
2409
2533
|
* values), and a bar whose own value is `na` still gets a result.
|
|
2410
2534
|
*/
|
|
2411
2535
|
export function range(source: Source, length: simple_int): series_float {
|
|
2536
|
+
checkPositiveLength('range', length, 0);
|
|
2412
2537
|
return source.map((_, i) => {
|
|
2413
2538
|
const values = lastValues(source, i, length);
|
|
2414
2539
|
return values ? Math.max(...values) - Math.min(...values) : NaN;
|
|
@@ -2420,10 +2545,11 @@ export function range(source: Source, length: simple_int): series_float {
|
|
|
2420
2545
|
* -1 for the previous bar, and so on (as in PineScript, the offset is negative).
|
|
2421
2546
|
*
|
|
2422
2547
|
* @remarks
|
|
2423
|
-
* As in PineScript:
|
|
2424
|
-
*
|
|
2548
|
+
* As in PineScript: the best of the last `length` non-na values, the newest one on equal values; the offset is the
|
|
2549
|
+
* distance in bars to its bar, so it is below `-(length - 1)` when `na` values were skipped. `na` before bar
|
|
2550
|
+
* `length - 1` and while no value was received; a fractional length is truncated.
|
|
2425
2551
|
*/
|
|
2426
|
-
export function highestbars(source: Source, length: simple_int): series_int {
|
|
2552
|
+
export function highestbars(source: Source, length: simple_int | ArrayLike<number>): series_int {
|
|
2427
2553
|
return extremeOffset(source, length, false);
|
|
2428
2554
|
}
|
|
2429
2555
|
|
|
@@ -2432,10 +2558,11 @@ export function highestbars(source: Source, length: simple_int): series_int {
|
|
|
2432
2558
|
* -1 for the previous bar, and so on (as in PineScript, the offset is negative).
|
|
2433
2559
|
*
|
|
2434
2560
|
* @remarks
|
|
2435
|
-
* As in PineScript:
|
|
2436
|
-
*
|
|
2561
|
+
* As in PineScript: the best of the last `length` non-na values, the newest one on equal values; the offset is the
|
|
2562
|
+
* distance in bars to its bar, so it is below `-(length - 1)` when `na` values were skipped. `na` before bar
|
|
2563
|
+
* `length - 1` and while no value was received; a fractional length is truncated.
|
|
2437
2564
|
*/
|
|
2438
|
-
export function lowestbars(source: Source, length: simple_int): series_int {
|
|
2565
|
+
export function lowestbars(source: Source, length: simple_int | ArrayLike<number>): series_int {
|
|
2439
2566
|
return extremeOffset(source, length, true);
|
|
2440
2567
|
}
|
|
2441
2568
|
|
|
@@ -2492,6 +2619,7 @@ export function min(source: Source): series_float {
|
|
|
2492
2619
|
* ```
|
|
2493
2620
|
*/
|
|
2494
2621
|
export function cog(source: Source, length: simple_int = 10): series_float {
|
|
2622
|
+
checkPositiveLength('cog', length, 0);
|
|
2495
2623
|
const len = Math.floor(length);
|
|
2496
2624
|
return runningSum(source, len).map((sum, i) => {
|
|
2497
2625
|
if (i < len - 1 || Number.isNaN(sum) || sum === 0) return NaN;
|
|
@@ -2525,6 +2653,7 @@ export function cog(source: Source, length: simple_int = 10): series_float {
|
|
|
2525
2653
|
* - If no mode exists, returns the smallest value
|
|
2526
2654
|
*/
|
|
2527
2655
|
export function mode(source: Source, length: simple_int): series_float {
|
|
2656
|
+
checkPositiveLength('mode', length, 0);
|
|
2528
2657
|
const result: series_float = [];
|
|
2529
2658
|
const len = Math.floor(length);
|
|
2530
2659
|
const seen: number[] = []; // non-na values so far
|
|
@@ -2602,6 +2731,7 @@ export function percentile_linear_interpolation(
|
|
|
2602
2731
|
length: simple_int,
|
|
2603
2732
|
percentage: number
|
|
2604
2733
|
): series_float {
|
|
2734
|
+
checkPositiveLength('percentile_linear_interpolation', length, 0);
|
|
2605
2735
|
return sortedWindow(source, length, (s) => {
|
|
2606
2736
|
const pos = (percentage / 100) * length - 0.5;
|
|
2607
2737
|
if (pos <= 0) return s[0]!;
|
|
@@ -2671,6 +2801,7 @@ export function percentile_nearest_rank(
|
|
|
2671
2801
|
length: simple_int,
|
|
2672
2802
|
percentage: number
|
|
2673
2803
|
): series_float {
|
|
2804
|
+
checkPositiveLength('percentile_nearest_rank', length, 0);
|
|
2674
2805
|
const rank = Math.min(length, Math.max(1, Math.ceil((percentage / 100) * length)));
|
|
2675
2806
|
return sortedWindow(source, length, (s) => s[rank - 1]!);
|
|
2676
2807
|
}
|
|
@@ -2707,6 +2838,7 @@ export function percentile_nearest_rank(
|
|
|
2707
2838
|
* where the `na` was
|
|
2708
2839
|
*/
|
|
2709
2840
|
export function rci(source: Source, length: simple_int): series_float {
|
|
2841
|
+
checkPositiveLength('rci', length, 0);
|
|
2710
2842
|
const len = Math.floor(length);
|
|
2711
2843
|
const result: series_float = [];
|
|
2712
2844
|
// PineScript keeps the values in a ring of `length + 1` slots indexed by the bar index, filled with 0 at the start
|
|
@@ -2770,31 +2902,42 @@ export function rci(source: Source, length: simple_int): series_float {
|
|
|
2770
2902
|
}
|
|
2771
2903
|
|
|
2772
2904
|
/**
|
|
2773
|
-
* Pivot Point Levels
|
|
2905
|
+
* Pivot Point Levels (PineScript `ta.pivot_point_levels`).
|
|
2774
2906
|
*
|
|
2775
|
-
*
|
|
2776
|
-
* Returns an array containing: [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5]
|
|
2907
|
+
* Returns 11 series: [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5] (one value per bar in each).
|
|
2777
2908
|
*
|
|
2778
2909
|
* @param type - Calculation type: "Traditional", "Fibonacci", "Woodie", "Classic", "DM", "Camarilla"
|
|
2779
|
-
* @param anchor - Condition that
|
|
2780
|
-
* @param developing -
|
|
2781
|
-
*
|
|
2782
|
-
* @param
|
|
2783
|
-
* @param
|
|
2784
|
-
* @param
|
|
2785
|
-
* @
|
|
2910
|
+
* @param anchor - Condition that starts a new period (`timeframe.change("W")`...)
|
|
2911
|
+
* @param developing - false: the levels of the previous period, constant until the next anchor; true: the levels of
|
|
2912
|
+
* the current period so far, recalculated on each bar
|
|
2913
|
+
* @param high - High price series
|
|
2914
|
+
* @param low - Low price series
|
|
2915
|
+
* @param close - Close price series
|
|
2916
|
+
* @param open - Open price series (Woodie and DM)
|
|
2917
|
+
* @returns 11 series of levels
|
|
2786
2918
|
*
|
|
2787
2919
|
* @example
|
|
2788
2920
|
* ```typescript
|
|
2789
2921
|
* const weekChange = [false, false, false, false, true, false, ...]; // Weekly anchor
|
|
2790
|
-
* const
|
|
2791
|
-
* //
|
|
2922
|
+
* const levels = ta.pivot_point_levels("Traditional", weekChange, false, high, low, close, open);
|
|
2923
|
+
* // levels[0]![i] is P on bar i, levels[5]![i] is R3
|
|
2792
2924
|
* ```
|
|
2793
2925
|
*
|
|
2794
2926
|
* @remarks
|
|
2795
|
-
*
|
|
2796
|
-
* -
|
|
2797
|
-
*
|
|
2927
|
+
* As in PineScript (#177):
|
|
2928
|
+
* - a period runs from an anchor bar to the bar before the next anchor; the bars before the first anchor are a
|
|
2929
|
+
* period too (from bar 0). The fixed levels use the high and low of the previous period and the close of the bar
|
|
2930
|
+
* before the anchor (na with an anchor on bar 0), and are na until the first anchor; the developing levels use
|
|
2931
|
+
* the current period's high and low and the bar's close, from bar 0
|
|
2932
|
+
* - 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),
|
|
2933
|
+
* R5 = P * 4 + (H - 4 * L), S5 = P * 4 - (4 * H - L)
|
|
2934
|
+
* - Fibonacci: P +- 0.382 / 0.618 / 1 * (H - L); R4 to S5 na
|
|
2935
|
+
* - Classic: R3 / S3 = P +- 2 * (H - L), R4 / S4 = P +- 3 * (H - L); R5 / S5 na
|
|
2936
|
+
* - Woodie: P = (H + L + 2 * open of the current period) / 4, R3 = H + 2 * (P - L), S3 = L - 2 * (H - P), R4 / S4 =
|
|
2937
|
+
* R3 + (H - L) / S3 - (H - L); R5 / S5 na. `developing` true throws (RE10128)
|
|
2938
|
+
* - 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,
|
|
2939
|
+
* R1 = x / 2 - L, S1 = x / 2 - H; the other levels na
|
|
2940
|
+
* - Camarilla: C +- (H - L) * 1.1 / 12, / 6, / 4, / 2 for R1 to S4, R5 = (H / L) * C, S5 = 2 * C - R5
|
|
2798
2941
|
*/
|
|
2799
2942
|
export function pivot_point_levels(
|
|
2800
2943
|
type: series_float | string,
|
|
@@ -2805,183 +2948,96 @@ export function pivot_point_levels(
|
|
|
2805
2948
|
close?: Source,
|
|
2806
2949
|
open?: Source
|
|
2807
2950
|
): series_float[] {
|
|
2808
|
-
// For simplicity, we need to handle the case where type is a string array
|
|
2809
|
-
// But according to the docs, type is "series string", meaning it can change per bar
|
|
2810
|
-
// For this implementation, we'll support constant type strings
|
|
2811
|
-
|
|
2812
2951
|
const typeStr = typeof type === 'string' ? type : String(type[0]);
|
|
2813
|
-
const isDeveloping = typeof developing === 'boolean' ? developing : developing[0];
|
|
2814
|
-
|
|
2815
|
-
// Woodie cannot be developing
|
|
2952
|
+
const isDeveloping = typeof developing === 'boolean' ? developing : Boolean(developing[0]);
|
|
2816
2953
|
if (typeStr === 'Woodie' && isDeveloping) {
|
|
2817
|
-
throw new Error(
|
|
2954
|
+
throw new Error(
|
|
2955
|
+
'Error on bar 0: The `developing` parameter of the `ta.pivot_point_levels()` cannot be `true` when `type` is "Woodie".'
|
|
2956
|
+
);
|
|
2818
2957
|
}
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
h = lastH;
|
|
2857
|
-
l = lastL;
|
|
2858
|
-
c = lastC;
|
|
2859
|
-
o = lastO;
|
|
2860
|
-
}
|
|
2861
|
-
|
|
2862
|
-
// Calculate pivot levels based on type
|
|
2863
|
-
const levels = calculatePivotLevels(typeStr, h, l, c, o);
|
|
2864
|
-
|
|
2865
|
-
// Push to results
|
|
2866
|
-
for (let j = 0; j < 11; j++) {
|
|
2867
|
-
results[j]!.push(levels[j]!);
|
|
2868
|
-
}
|
|
2958
|
+
const n = anchor.length;
|
|
2959
|
+
for (const [s, name] of [[high, 'High'], [low, 'Low'], [close, 'Close'], [open, 'Open']] as const) {
|
|
2960
|
+
if (s && s.length !== n) throw new Error(`${name} series length mismatch`);
|
|
2961
|
+
}
|
|
2962
|
+
const at = (s: Source | undefined, i: number): number => (s ? (s[i] ?? NaN) : NaN);
|
|
2963
|
+
const results: series_float[] = Array.from({ length: 11 }, () => new Array<number>(n));
|
|
2964
|
+
// the current period (from bar 0 until the first anchor) and the previous one
|
|
2965
|
+
let curH = NaN, curL = NaN, curO = NaN;
|
|
2966
|
+
let prevH = NaN, prevL = NaN, prevC = NaN, prevO = NaN;
|
|
2967
|
+
let anchored = false;
|
|
2968
|
+
for (let i = 0; i < n; i++) {
|
|
2969
|
+
const h = at(high, i), l = at(low, i);
|
|
2970
|
+
if (i === 0) {
|
|
2971
|
+
// the first period starts on bar 0: an anchor there makes bar 0 its own previous period
|
|
2972
|
+
curH = h;
|
|
2973
|
+
curL = l;
|
|
2974
|
+
curO = at(open, i);
|
|
2975
|
+
}
|
|
2976
|
+
if (anchor[i]) {
|
|
2977
|
+
prevH = curH;
|
|
2978
|
+
prevL = curL;
|
|
2979
|
+
prevC = i > 0 ? at(close, i - 1) : NaN;
|
|
2980
|
+
prevO = curO;
|
|
2981
|
+
curH = h;
|
|
2982
|
+
curL = l;
|
|
2983
|
+
curO = at(open, i);
|
|
2984
|
+
anchored = true;
|
|
2985
|
+
} else if (i > 0) {
|
|
2986
|
+
if (h > curH || Number.isNaN(curH)) curH = h;
|
|
2987
|
+
if (l < curL || Number.isNaN(curL)) curL = l;
|
|
2988
|
+
}
|
|
2989
|
+
const levels = isDeveloping
|
|
2990
|
+
? pivotLevels(typeStr, curH, curL, at(close, i), curO, at(close, i))
|
|
2991
|
+
: anchored
|
|
2992
|
+
? pivotLevels(typeStr, prevH, prevL, prevC, prevO, curO)
|
|
2993
|
+
: NA_LEVELS;
|
|
2994
|
+
for (let k = 0; k < 11; k++) results[k]![i] = levels[k]!;
|
|
2869
2995
|
}
|
|
2870
|
-
|
|
2871
2996
|
return results;
|
|
2872
2997
|
}
|
|
2873
2998
|
|
|
2999
|
+
const NA_LEVELS: readonly number[] = new Array<number>(11).fill(NaN);
|
|
3000
|
+
|
|
2874
3001
|
/**
|
|
2875
|
-
*
|
|
3002
|
+
* The 11 levels of one bar, in PineScript's operation order (bit for bit). `o` is the open of the period (DM),
|
|
3003
|
+
* `wo` the close term of Woodie (the open of the current period, or the bar's close while developing).
|
|
2876
3004
|
*/
|
|
2877
|
-
function
|
|
2878
|
-
|
|
2879
|
-
h
|
|
2880
|
-
l: number,
|
|
2881
|
-
c: number,
|
|
2882
|
-
o: number
|
|
2883
|
-
): number[] {
|
|
2884
|
-
// Return NaN array if data is insufficient
|
|
2885
|
-
if (isNaN(h) || isNaN(l) || isNaN(c)) {
|
|
2886
|
-
return Array(11).fill(NaN);
|
|
2887
|
-
}
|
|
2888
|
-
|
|
2889
|
-
const levels: number[] = Array(11).fill(NaN);
|
|
2890
|
-
|
|
2891
|
-
// Calculate pivot point (P)
|
|
2892
|
-
let P: number;
|
|
2893
|
-
|
|
2894
|
-
switch (type) {
|
|
2895
|
-
case 'Traditional':
|
|
2896
|
-
case 'Fibonacci':
|
|
2897
|
-
case 'Classic':
|
|
2898
|
-
P = (h + l + c) / 3;
|
|
2899
|
-
break;
|
|
2900
|
-
case 'Woodie':
|
|
2901
|
-
P = (h + l + 2 * c) / 4;
|
|
2902
|
-
break;
|
|
2903
|
-
case 'DM':
|
|
2904
|
-
P = (h + l + c) / 3;
|
|
2905
|
-
break;
|
|
2906
|
-
case 'Camarilla':
|
|
2907
|
-
P = (h + l + c) / 3;
|
|
2908
|
-
break;
|
|
2909
|
-
default:
|
|
2910
|
-
P = (h + l + c) / 3;
|
|
2911
|
-
}
|
|
2912
|
-
|
|
2913
|
-
levels[0] = P; // P is at index 0
|
|
2914
|
-
|
|
2915
|
-
// Calculate resistance and support levels based on type
|
|
3005
|
+
function pivotLevels(type: string, h: number, l: number, c: number, o: number, wo: number): readonly number[] {
|
|
3006
|
+
const N = NaN;
|
|
3007
|
+
const r = h - l;
|
|
2916
3008
|
switch (type) {
|
|
2917
|
-
case 'Traditional':
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
levels[5] = P + (h - l); // R3
|
|
2937
|
-
levels[6] = P - (h - l); // S3
|
|
2938
|
-
levels[7] = levels[5] + 0.618 * (h - l); // R4
|
|
2939
|
-
levels[8] = levels[6] - 0.618 * (h - l); // S4
|
|
2940
|
-
levels[9] = levels[7] + 0.382 * (h - l); // R5
|
|
2941
|
-
levels[10] = levels[8] - 0.382 * (h - l); // S5
|
|
2942
|
-
break;
|
|
2943
|
-
|
|
2944
|
-
case 'Woodie':
|
|
2945
|
-
levels[1] = 2 * P - l; // R1
|
|
2946
|
-
levels[2] = 2 * P - h; // S1
|
|
2947
|
-
levels[3] = P + (h - l); // R2
|
|
2948
|
-
levels[4] = P - (h - l); // S2
|
|
2949
|
-
levels[5] = h + 2 * (P - l); // R3
|
|
2950
|
-
levels[6] = l - 2 * (h - P); // S3
|
|
2951
|
-
levels[7] = levels[5] + (h - l); // R4
|
|
2952
|
-
levels[8] = levels[6] - (h - l); // S4
|
|
2953
|
-
levels[9] = levels[7] + (h - l); // R5
|
|
2954
|
-
levels[10] = levels[8] - (h - l); // S5
|
|
2955
|
-
break;
|
|
2956
|
-
|
|
3009
|
+
case 'Traditional': {
|
|
3010
|
+
const p = (h + l + c) / 3;
|
|
3011
|
+
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),
|
|
3012
|
+
p * 3 - (3 * h - l), p * 4 + (h - 4 * l), p * 4 - (4 * h - l)];
|
|
3013
|
+
}
|
|
3014
|
+
case 'Fibonacci': {
|
|
3015
|
+
const p = (h + l + c) / 3;
|
|
3016
|
+
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];
|
|
3017
|
+
}
|
|
3018
|
+
case 'Classic': {
|
|
3019
|
+
const p = (h + l + c) / 3;
|
|
3020
|
+
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];
|
|
3021
|
+
}
|
|
3022
|
+
case 'Woodie': {
|
|
3023
|
+
const p = (h + l + 2 * wo) / 4;
|
|
3024
|
+
const r3 = h + 2 * (p - l);
|
|
3025
|
+
const s3 = l - 2 * (h - p);
|
|
3026
|
+
return [p, 2 * p - l, 2 * p - h, p + r, p - r, r3, s3, r3 + r, s3 - r, N, N];
|
|
3027
|
+
}
|
|
2957
3028
|
case 'DM': {
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
const newP = x / (isNaN(o) ? 4 : 5);
|
|
2961
|
-
levels[0] = newP;
|
|
2962
|
-
levels[1] = x / 2 - l; // R1
|
|
2963
|
-
levels[2] = x / 2 - h; // S1
|
|
2964
|
-
// R2-S5 remain NaN
|
|
2965
|
-
break;
|
|
3029
|
+
const x = c < o ? h + 2 * l + c : c > o ? 2 * h + l + c : h + l + 2 * c;
|
|
3030
|
+
return [x / 4, x / 2 - l, x / 2 - h, N, N, N, N, N, N, N, N];
|
|
2966
3031
|
}
|
|
2967
|
-
|
|
2968
3032
|
case 'Camarilla': {
|
|
2969
|
-
const
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
levels[4] = c - range * 1.1 / 6; // S2
|
|
2974
|
-
levels[5] = c + range * 1.1 / 4; // R3
|
|
2975
|
-
levels[6] = c - range * 1.1 / 4; // S3
|
|
2976
|
-
levels[7] = c + range * 1.1 / 2; // R4
|
|
2977
|
-
levels[8] = c - range * 1.1 / 2; // S4
|
|
2978
|
-
levels[9] = h; // R5 (high)
|
|
2979
|
-
levels[10] = l; // S5 (low)
|
|
2980
|
-
break;
|
|
3033
|
+
const p = (h + l + c) / 3;
|
|
3034
|
+
const r5 = (h / l) * c;
|
|
3035
|
+
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,
|
|
3036
|
+
c - (r * 1.1) / 4, c + (r * 1.1) / 2, c - (r * 1.1) / 2, r5, 2 * c - r5];
|
|
2981
3037
|
}
|
|
3038
|
+
default:
|
|
3039
|
+
return NA_LEVELS;
|
|
2982
3040
|
}
|
|
2983
|
-
|
|
2984
|
-
return levels;
|
|
2985
3041
|
}
|
|
2986
3042
|
|
|
2987
3043
|
/**
|