oakscriptjs 0.9.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/array/index.d.ts +93 -50
- package/dist/cjs/array/index.d.ts.map +1 -1
- package/dist/cjs/array/index.js +239 -78
- package/dist/cjs/array/index.js.map +3 -3
- package/dist/cjs/callsite/index.d.ts +81 -2
- package/dist/cjs/callsite/index.d.ts.map +1 -1
- package/dist/cjs/callsite/index.js +62 -1
- package/dist/cjs/callsite/index.js.map +2 -2
- package/dist/cjs/color/index.d.ts +7 -3
- package/dist/cjs/color/index.d.ts.map +1 -1
- package/dist/cjs/color/index.js +6 -1
- package/dist/cjs/color/index.js.map +2 -2
- package/dist/cjs/drawing/bar-time.d.ts +24 -0
- package/dist/cjs/drawing/bar-time.d.ts.map +1 -0
- package/dist/cjs/drawing/bar-time.js +47 -0
- package/dist/cjs/drawing/bar-time.js.map +7 -0
- package/dist/cjs/drawing/output.d.ts +32 -0
- package/dist/cjs/drawing/output.d.ts.map +1 -0
- package/dist/cjs/drawing/output.js +184 -0
- package/dist/cjs/drawing/output.js.map +7 -0
- package/dist/cjs/drawing/registry.d.ts +5 -4
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +11 -2
- package/dist/cjs/drawing/registry.js.map +2 -2
- package/dist/cjs/index.d.ts +6 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +9 -2
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/label/index.d.ts.map +1 -1
- package/dist/cjs/label/index.js +2 -1
- package/dist/cjs/label/index.js.map +2 -2
- package/dist/cjs/linefill/index.d.ts.map +1 -1
- package/dist/cjs/linefill/index.js +1 -0
- package/dist/cjs/linefill/index.js.map +2 -2
- package/dist/cjs/math/index.d.ts +76 -21
- package/dist/cjs/math/index.d.ts.map +1 -1
- package/dist/cjs/math/index.js +155 -41
- package/dist/cjs/math/index.js.map +3 -3
- package/dist/cjs/matrix/index.d.ts.map +1 -1
- package/dist/cjs/matrix/index.js +42 -36
- package/dist/cjs/matrix/index.js.map +2 -2
- package/dist/cjs/polyline/index.d.ts +3 -5
- package/dist/cjs/polyline/index.d.ts.map +1 -1
- package/dist/cjs/polyline/index.js +0 -5
- package/dist/cjs/polyline/index.js.map +2 -2
- package/dist/cjs/runtime/series.d.ts +9 -0
- package/dist/cjs/runtime/series.d.ts.map +1 -1
- package/dist/cjs/runtime/series.js +16 -2
- package/dist/cjs/runtime/series.js.map +2 -2
- package/dist/cjs/runtime/types.d.ts +22 -0
- 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 +97 -36
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +173 -26
- package/dist/cjs/script/index.js.map +3 -3
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +15 -2
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/str/index.d.ts +50 -38
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +43 -15
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/messageformat.d.ts +2 -1
- package/dist/cjs/str/messageformat.d.ts.map +1 -1
- package/dist/cjs/str/messageformat.js +4 -1
- package/dist/cjs/str/messageformat.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +4 -0
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +26 -4
- package/dist/cjs/str/numberformat.js.map +3 -3
- package/dist/cjs/ta/index.d.ts +86 -43
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +173 -267
- package/dist/cjs/ta/index.js.map +3 -3
- package/dist/cjs/ta/running-sum.d.ts +54 -2
- package/dist/cjs/ta/running-sum.d.ts.map +1 -1
- package/dist/cjs/ta/running-sum.js +132 -52
- package/dist/cjs/ta/running-sum.js.map +2 -2
- package/dist/cjs/ta-series.d.ts +4 -4
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +4 -3
- package/dist/cjs/ta-series.js.map +2 -2
- package/dist/cjs/table/index.d.ts +87 -0
- package/dist/cjs/table/index.d.ts.map +1 -0
- package/dist/cjs/table/index.js +150 -0
- package/dist/cjs/table/index.js.map +7 -0
- package/dist/cjs/text/index.d.ts +17 -2
- package/dist/cjs/text/index.d.ts.map +1 -1
- package/dist/cjs/text/index.js +15 -1
- package/dist/cjs/text/index.js.map +2 -2
- package/dist/cjs/time/datestring.d.ts +1 -0
- package/dist/cjs/time/datestring.d.ts.map +1 -1
- package/dist/cjs/time/datestring.js +7 -0
- package/dist/cjs/time/datestring.js.map +2 -2
- package/dist/cjs/time/index.d.ts.map +1 -1
- package/dist/cjs/time/index.js +2 -1
- package/dist/cjs/time/index.js.map +2 -2
- package/dist/cjs/time/session.d.ts +28 -9
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +74 -28
- package/dist/cjs/time/session.js.map +2 -2
- package/dist/cjs/time/timezone.d.ts +5 -0
- package/dist/cjs/time/timezone.d.ts.map +1 -1
- package/dist/cjs/time/timezone.js +15 -1
- package/dist/cjs/time/timezone.js.map +2 -2
- package/dist/cjs/types/index.d.ts +30 -1
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +12 -0
- package/dist/cjs/types/index.js.map +2 -2
- package/dist/cjs/types/metadata.d.ts +258 -26
- 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 +93 -50
- package/dist/esm/array/index.d.ts.map +1 -1
- package/dist/esm/array/index.js +383 -133
- package/dist/esm/array/index.js.map +1 -1
- package/dist/esm/callsite/index.d.ts +81 -2
- package/dist/esm/callsite/index.d.ts.map +1 -1
- package/dist/esm/callsite/index.js +145 -3
- package/dist/esm/callsite/index.js.map +1 -1
- package/dist/esm/color/index.d.ts +7 -3
- package/dist/esm/color/index.d.ts.map +1 -1
- package/dist/esm/color/index.js +15 -3
- package/dist/esm/color/index.js.map +1 -1
- package/dist/esm/drawing/bar-time.d.ts +24 -0
- package/dist/esm/drawing/bar-time.d.ts.map +1 -0
- package/dist/esm/drawing/bar-time.js +44 -0
- package/dist/esm/drawing/bar-time.js.map +1 -0
- package/dist/esm/drawing/output.d.ts +32 -0
- package/dist/esm/drawing/output.d.ts.map +1 -0
- package/dist/esm/drawing/output.js +190 -0
- package/dist/esm/drawing/output.js.map +1 -0
- package/dist/esm/drawing/registry.d.ts +5 -4
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +12 -2
- package/dist/esm/drawing/registry.js.map +1 -1
- package/dist/esm/index.d.ts +6 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +7 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/label/index.d.ts.map +1 -1
- package/dist/esm/label/index.js +2 -1
- package/dist/esm/label/index.js.map +1 -1
- package/dist/esm/linefill/index.d.ts.map +1 -1
- package/dist/esm/linefill/index.js +3 -0
- package/dist/esm/linefill/index.js.map +1 -1
- package/dist/esm/math/index.d.ts +76 -21
- package/dist/esm/math/index.d.ts.map +1 -1
- package/dist/esm/math/index.js +227 -61
- package/dist/esm/math/index.js.map +1 -1
- package/dist/esm/matrix/index.d.ts.map +1 -1
- package/dist/esm/matrix/index.js +57 -43
- package/dist/esm/matrix/index.js.map +1 -1
- package/dist/esm/polyline/index.d.ts +3 -5
- package/dist/esm/polyline/index.d.ts.map +1 -1
- package/dist/esm/polyline/index.js +3 -9
- package/dist/esm/polyline/index.js.map +1 -1
- package/dist/esm/runtime/series.d.ts +9 -0
- package/dist/esm/runtime/series.d.ts.map +1 -1
- package/dist/esm/runtime/series.js +18 -1
- package/dist/esm/runtime/series.js.map +1 -1
- package/dist/esm/runtime/types.d.ts +22 -0
- package/dist/esm/runtime/types.d.ts.map +1 -1
- package/dist/esm/script/index.d.ts +97 -36
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +211 -22
- package/dist/esm/script/index.js.map +1 -1
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +22 -2
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/str/index.d.ts +50 -38
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +103 -52
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/messageformat.d.ts +2 -1
- package/dist/esm/str/messageformat.d.ts.map +1 -1
- package/dist/esm/str/messageformat.js +7 -2
- package/dist/esm/str/messageformat.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +4 -0
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +38 -4
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +86 -43
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +296 -348
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/running-sum.d.ts +54 -2
- package/dist/esm/ta/running-sum.d.ts.map +1 -1
- package/dist/esm/ta/running-sum.js +166 -71
- package/dist/esm/ta/running-sum.js.map +1 -1
- package/dist/esm/ta-series.d.ts +4 -4
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +5 -4
- package/dist/esm/ta-series.js.map +1 -1
- package/dist/esm/table/index.d.ts +87 -0
- package/dist/esm/table/index.d.ts.map +1 -0
- package/dist/esm/table/index.js +169 -0
- package/dist/esm/table/index.js.map +1 -0
- package/dist/esm/text/index.d.ts +17 -2
- package/dist/esm/text/index.d.ts.map +1 -1
- package/dist/esm/text/index.js +17 -2
- package/dist/esm/text/index.js.map +1 -1
- package/dist/esm/time/datestring.d.ts +1 -0
- package/dist/esm/time/datestring.d.ts.map +1 -1
- package/dist/esm/time/datestring.js +12 -0
- package/dist/esm/time/datestring.js.map +1 -1
- package/dist/esm/time/index.d.ts.map +1 -1
- package/dist/esm/time/index.js +3 -1
- package/dist/esm/time/index.js.map +1 -1
- package/dist/esm/time/session.d.ts +28 -9
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +113 -42
- package/dist/esm/time/session.js.map +1 -1
- package/dist/esm/time/timezone.d.ts +5 -0
- package/dist/esm/time/timezone.d.ts.map +1 -1
- package/dist/esm/time/timezone.js +21 -1
- package/dist/esm/time/timezone.js.map +1 -1
- package/dist/esm/types/index.d.ts +30 -1
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/types/index.js +14 -0
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm/types/metadata.d.ts +258 -26
- package/dist/esm/types/metadata.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/array/index.ts +364 -150
- package/src/callsite/index.ts +147 -3
- package/src/color/index.ts +15 -5
- package/src/drawing/bar-time.ts +42 -0
- package/src/drawing/output.ts +214 -0
- package/src/drawing/registry.ts +16 -6
- package/src/index.ts +9 -3
- package/src/label/index.ts +2 -1
- package/src/linefill/index.ts +2 -0
- package/src/math/index.ts +256 -74
- package/src/matrix/index.ts +52 -52
- package/src/polyline/index.ts +3 -12
- package/src/runtime/series.ts +22 -1
- package/src/runtime/types.ts +23 -0
- package/src/script/index.ts +307 -25
- package/src/session/calendar.ts +22 -2
- package/src/str/index.ts +95 -51
- package/src/str/messageformat.ts +7 -2
- package/src/str/numberformat.ts +39 -4
- package/src/ta/index.ts +306 -386
- package/src/ta/running-sum.ts +187 -74
- package/src/ta-series.ts +8 -7
- package/src/table/index.ts +221 -0
- package/src/text/index.ts +18 -2
- package/src/time/datestring.ts +10 -0
- package/src/time/index.ts +3 -1
- package/src/time/session.ts +97 -47
- package/src/time/timezone.ts +21 -1
- package/src/types/index.ts +32 -1
- package/src/types/metadata.ts +325 -26
package/src/ta/running-sum.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* - each bar first removes, with a Kahan step, the compensated value that was added for the value that leaves the
|
|
7
7
|
* window, then adds the new value with a Kahan step and keeps its compensated value;
|
|
8
8
|
* - when the compensation of the previous step would be lost when applied to the new value, the sum is computed
|
|
9
|
-
* again from the window values (newest first) and the compensation is reset. The test
|
|
10
|
-
*
|
|
9
|
+
* again from the window values (newest first) and the compensation is reset. The test moves the new value toward
|
|
10
|
+
* +infinity by the size of the compensation, in floating point (#136, #142).
|
|
11
11
|
*
|
|
12
12
|
* So the result can differ from the exact window sum in the last bits (e.g. a small residue on a window of zeros),
|
|
13
13
|
* as in PineScript.
|
|
@@ -15,115 +15,228 @@
|
|
|
15
15
|
* @internal
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
const view = new DataView(new ArrayBuffer(8));
|
|
19
|
-
|
|
20
|
-
/** Binary exponent of a finite non-zero number (from its bits). */
|
|
21
|
-
function exponentOf(x: number): number {
|
|
22
|
-
view.setFloat64(0, x);
|
|
23
|
-
const e = (view.getUint16(0) >> 4) & 0x7ff;
|
|
24
|
-
return e === 0 ? -1074 : e - 1023;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Distance from a finite non-zero `x` to the next number toward +infinity: the spacing of the binade of `x`, or half
|
|
29
|
-
* of it when `x` is a negative power of two (the next number up is in the binade below).
|
|
30
|
-
*/
|
|
31
|
-
function spacingUp(x: number): number {
|
|
32
|
-
const e = exponentOf(x);
|
|
33
|
-
const powerOfTwo = (view.getUint16(0) & 0xf) === 0 && view.getUint16(2) === 0 && view.getUint32(4) === 0;
|
|
34
|
-
return 2 ** (e - (x < 0 && powerOfTwo && e > -1022 ? 53 : 52));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
18
|
/**
|
|
38
|
-
* Whether the compensation `c` is (partly) lost when it is applied to `x`: `x
|
|
39
|
-
*
|
|
19
|
+
* Whether the compensation `c` is (partly) lost when it is applied to `x`: `x + |c|` in floating point moves `x` by
|
|
20
|
+
* less than `|c|`. Moving toward +infinity uses the grid above `x` (the coarser binade just below a power of two, the
|
|
21
|
+
* finer binade for a negative power of two), as PineScript (measured bit for bit, #142).
|
|
40
22
|
*/
|
|
41
23
|
function compensationLost(x: number, c: number): boolean {
|
|
42
24
|
if (c === 0 || x === 0) return false;
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const mantissa = x / ulp;
|
|
46
|
-
const f = Math.floor(q);
|
|
47
|
-
const d = q - f;
|
|
48
|
-
// parity of mantissa - f from each part: the mantissa can be 2^53 (half spacing), where mantissa - f is not exact
|
|
49
|
-
const applied = d > 0.5 ? f + 1 : d < 0.5 ? f : Math.abs(mantissa % 2) === Math.abs(f % 2) ? f : f + 1;
|
|
50
|
-
return Math.abs(applied) < Math.abs(q);
|
|
25
|
+
const size = Math.abs(c);
|
|
26
|
+
return Math.abs(x + size - x) < size;
|
|
51
27
|
}
|
|
52
28
|
|
|
53
29
|
/** A value PineScript treats as na in window functions (also +/-Infinity). */
|
|
54
|
-
function isNa(v: number | null | undefined): boolean {
|
|
30
|
+
export function isNa(v: number | null | undefined): boolean {
|
|
55
31
|
return v === undefined || v === null || !Number.isFinite(v);
|
|
56
32
|
}
|
|
57
33
|
|
|
58
34
|
/**
|
|
59
|
-
*
|
|
35
|
+
* Running sum state, one value at a time. {@link step} computes the sum with a new value from the kept state without
|
|
36
|
+
* changing it; {@link commit} keeps the last step. So several candidate values can be tried on one bar and only the
|
|
37
|
+
* last one kept (a ta.* call in a loop).
|
|
60
38
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
39
|
+
* The length can change from one step to the next (a series length). PineScript then first moves the kept sum to the
|
|
40
|
+
* new length with Kahan steps of the raw values (not the stored compensated values): a smaller length removes the
|
|
41
|
+
* values that leave the window, oldest first; a larger length adds back the values from the one before the old window
|
|
42
|
+
* down to the one `length` values back, newest first (one more than the window needs). Then the usual step runs with
|
|
43
|
+
* the new length: it removes the stored value of the one `length` values back and adds the new value.
|
|
65
44
|
*/
|
|
66
|
-
export
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
45
|
+
export class RunningSum {
|
|
46
|
+
private readonly values: number[] = [];
|
|
47
|
+
private readonly stored: number[] = [];
|
|
48
|
+
private keptLength = NaN;
|
|
49
|
+
private s = 0;
|
|
50
|
+
private c = 0;
|
|
51
|
+
private stepLength = NaN;
|
|
52
|
+
private stepX = NaN;
|
|
53
|
+
private stepY = NaN;
|
|
54
|
+
private stepS = 0;
|
|
55
|
+
private stepC = 0;
|
|
56
|
+
|
|
57
|
+
/** Kahan steps that move the kept sum from the kept length to `len`; the result goes to stepS / stepC. */
|
|
58
|
+
private resize(len: number): void {
|
|
59
|
+
const values = this.values;
|
|
60
|
+
const n = values.length;
|
|
61
|
+
const kept = this.keptLength;
|
|
62
|
+
let s = this.s;
|
|
63
|
+
let c = this.c;
|
|
64
|
+
const add = (v: number) => {
|
|
65
|
+
const y = v - c;
|
|
66
|
+
const t = s + y;
|
|
67
|
+
c = (t - s) - y;
|
|
68
|
+
s = t;
|
|
69
|
+
};
|
|
70
|
+
if (len < kept) {
|
|
71
|
+
for (let k = Math.max(0, n - kept); k <= n - len - 1; k++) add(-values[k]!);
|
|
72
|
+
} else if (len > kept) {
|
|
73
|
+
for (let k = n - kept - 1; k >= Math.max(0, n - len); k--) add(values[k]!);
|
|
87
74
|
}
|
|
88
|
-
|
|
75
|
+
this.stepS = s;
|
|
76
|
+
this.stepC = c;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The running sum of the last `length` values with the (not na) value `x` added, from the kept state; the state is
|
|
81
|
+
* unchanged until {@link commit}.
|
|
82
|
+
*
|
|
83
|
+
* @returns The sum, or NaN while fewer than `length` values exist (with `x`)
|
|
84
|
+
*/
|
|
85
|
+
step(x: number, length: number): number {
|
|
86
|
+
const len = length;
|
|
87
|
+
const values = this.values;
|
|
88
|
+
this.resize(len);
|
|
89
|
+
let s = this.stepS;
|
|
90
|
+
let c = this.stepC;
|
|
89
91
|
const c0 = c;
|
|
90
92
|
const n = values.length;
|
|
91
|
-
if (n >= len)
|
|
92
|
-
|
|
93
|
+
if (n >= len) {
|
|
94
|
+
const y = -this.stored[n - len]! - c;
|
|
95
|
+
const t = s + y;
|
|
96
|
+
c = (t - s) - y;
|
|
97
|
+
s = t;
|
|
98
|
+
}
|
|
93
99
|
if (compensationLost(x, c0)) {
|
|
94
|
-
let fresh =
|
|
95
|
-
for (let k =
|
|
100
|
+
let fresh = x;
|
|
101
|
+
for (let k = n - 1; k >= Math.max(0, n + 1 - len); k--) fresh += values[k]!;
|
|
96
102
|
s = fresh;
|
|
97
103
|
c = 0;
|
|
98
|
-
|
|
104
|
+
this.stepY = x;
|
|
99
105
|
} else {
|
|
100
|
-
|
|
106
|
+
const y = x - c;
|
|
107
|
+
const t = s + y;
|
|
108
|
+
c = (t - s) - y;
|
|
109
|
+
s = t;
|
|
110
|
+
this.stepY = y;
|
|
101
111
|
}
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
this.stepLength = len;
|
|
113
|
+
this.stepX = x;
|
|
114
|
+
this.stepS = s;
|
|
115
|
+
this.stepC = c;
|
|
116
|
+
return n + 1 >= len ? s : NaN;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The running sum of the last `length` values on a bar with an na value (no value added), from the kept state; the
|
|
121
|
+
* state is unchanged until {@link commit}. With an unchanged length, it is the previous result.
|
|
122
|
+
*
|
|
123
|
+
* @returns The sum, or NaN while fewer than `length` values exist
|
|
124
|
+
*/
|
|
125
|
+
stepNa(length: number): number {
|
|
126
|
+
this.resize(length);
|
|
127
|
+
this.stepLength = length;
|
|
128
|
+
this.stepX = NaN;
|
|
129
|
+
const n = this.values.length;
|
|
130
|
+
return n > 0 && n >= length ? this.stepS : NaN;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Keeps the state of the last {@link step} or {@link stepNa}. */
|
|
134
|
+
commit(): void {
|
|
135
|
+
if (!Number.isNaN(this.stepX)) {
|
|
136
|
+
this.values.push(this.stepX);
|
|
137
|
+
this.stored.push(this.stepY);
|
|
138
|
+
}
|
|
139
|
+
this.keptLength = this.stepLength;
|
|
140
|
+
this.s = this.stepS;
|
|
141
|
+
this.c = this.stepC;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The running sums of `source` with the length `lengthAt(i)` on bar `i`. */
|
|
146
|
+
function sumWindows(source: ArrayLike<number | null | undefined>, lengthAt: (i: number) => number): number[] {
|
|
147
|
+
const out: number[] = new Array<number>(source.length);
|
|
148
|
+
const sum = new RunningSum();
|
|
149
|
+
for (let i = 0; i < source.length; i++) {
|
|
150
|
+
const v = source[i];
|
|
151
|
+
const len = lengthAt(i);
|
|
152
|
+
out[i] = isNa(v) ? sum.stepNa(len) : sum.step(v as number, len);
|
|
153
|
+
sum.commit();
|
|
104
154
|
}
|
|
105
155
|
return out;
|
|
106
156
|
}
|
|
107
157
|
|
|
108
158
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
159
|
+
* The sum of the last `length` non-na values on each bar, as PineScript computes it.
|
|
160
|
+
*
|
|
161
|
+
* @param source - Values (na: NaN, null, undefined or +/-Infinity, skipped)
|
|
162
|
+
* @param length - Number of non-na values in the window
|
|
163
|
+
* @returns The running sum per bar: NaN until `length` non-na values exist; a bar with an na value keeps the
|
|
164
|
+
* previous result
|
|
111
165
|
*/
|
|
112
|
-
export function
|
|
166
|
+
export function runningSum(source: ArrayLike<number | null | undefined>, length: number): number[] {
|
|
167
|
+
const len = Math.floor(length);
|
|
168
|
+
if (!(len >= 1)) return new Array<number>(source.length).fill(NaN);
|
|
169
|
+
return sumWindows(source, () => len);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The values of a series length, floored, checked to be at least 1 on every bar. */
|
|
173
|
+
function seriesLengths(lengths: ArrayLike<number>, count: number, name: string): number[] {
|
|
174
|
+
if (lengths.length < count) {
|
|
175
|
+
throw new Error(`${name}: the length series has ${lengths.length} values for ${count} bars`);
|
|
176
|
+
}
|
|
177
|
+
return Array.from({ length: count }, (_, i) => {
|
|
178
|
+
const len = Math.floor(lengths[i]!);
|
|
179
|
+
if (!(len >= 1)) throw new Error(`${name}: length must be at least 1 (bar ${i}: ${lengths[i]})`);
|
|
180
|
+
return len;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The sum of the last `lengths[i]` non-na values on each bar `i` (a series length), as PineScript computes it (see
|
|
186
|
+
* {@link RunningSum}). A bar with an na value gives the sum of the last `lengths[i]` earlier values.
|
|
187
|
+
*
|
|
188
|
+
* @param name - Function name for the error messages
|
|
189
|
+
*/
|
|
190
|
+
export function runningSumSeries(
|
|
113
191
|
source: ArrayLike<number | null | undefined>,
|
|
114
|
-
|
|
192
|
+
lengths: ArrayLike<number>,
|
|
193
|
+
name = 'math.sum'
|
|
194
|
+
): number[] {
|
|
195
|
+
const lens = seriesLengths(lengths, source.length, name);
|
|
196
|
+
return sumWindows(source, (i) => lens[i]!);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Variance from the running sums of the values and of their squares; rules in {@link runningVariance}. */
|
|
200
|
+
function varianceFromSums(
|
|
201
|
+
source: ArrayLike<number | null | undefined>,
|
|
202
|
+
sumOf: (src: ArrayLike<number | null | undefined>) => number[],
|
|
203
|
+
lengthAt: (i: number) => number,
|
|
115
204
|
biased: boolean
|
|
116
205
|
): number[] {
|
|
117
|
-
const len = Math.floor(length);
|
|
118
206
|
const squares = Array.from({ length: source.length }, (_, i) => {
|
|
119
207
|
const v = source[i];
|
|
120
208
|
return isNa(v) ? NaN : (v as number) * (v as number);
|
|
121
209
|
});
|
|
122
|
-
const sums =
|
|
123
|
-
const sumSquares =
|
|
210
|
+
const sums = sumOf(source);
|
|
211
|
+
const sumSquares = sumOf(squares);
|
|
124
212
|
return sums.map((sum, i) => {
|
|
213
|
+
const len = lengthAt(i);
|
|
125
214
|
const mean = sum / len;
|
|
126
215
|
const sq = sumSquares[i]!;
|
|
127
216
|
return biased ? sq / len - mean * mean : sq / (len - 1) - (mean * sum) / (len - 1);
|
|
128
217
|
});
|
|
129
218
|
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Window variance from the running sums of the values and of their squares, as PineScript computes it:
|
|
222
|
+
* biased `sumSq / n - mean * mean`, unbiased `sumSq / (n - 1) - mean * sum / (n - 1)`, with `mean = sum / n`.
|
|
223
|
+
*/
|
|
224
|
+
export function runningVariance(
|
|
225
|
+
source: ArrayLike<number | null | undefined>,
|
|
226
|
+
length: number,
|
|
227
|
+
biased: boolean
|
|
228
|
+
): number[] {
|
|
229
|
+
const len = Math.floor(length);
|
|
230
|
+
return varianceFromSums(source, (src) => runningSum(src, len), () => len, biased);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** {@link runningVariance} with a series length (the length of bar `i` is `lengths[i]`). */
|
|
234
|
+
export function runningVarianceSeries(
|
|
235
|
+
source: ArrayLike<number | null | undefined>,
|
|
236
|
+
lengths: ArrayLike<number>,
|
|
237
|
+
biased: boolean,
|
|
238
|
+
name = 'ta.variance'
|
|
239
|
+
): number[] {
|
|
240
|
+
const lens = seriesLengths(lengths, source.length, name);
|
|
241
|
+
return varianceFromSums(source, (src) => sumWindows(src, (i) => lens[i]!), (i) => lens[i]!, biased);
|
|
242
|
+
}
|
package/src/ta-series.ts
CHANGED
|
@@ -14,10 +14,10 @@ import type { Bar } from './types/index.js';
|
|
|
14
14
|
* @param length - Period length
|
|
15
15
|
* @returns Series with SMA values
|
|
16
16
|
*/
|
|
17
|
-
export function sma(source: Series, length: number): Series {
|
|
17
|
+
export function sma(source: Series, length: number | Series): Series {
|
|
18
18
|
const bars = source.bars as Bar[];
|
|
19
19
|
const sourceValues = source.toArray();
|
|
20
|
-
const result = taCore.sma(sourceValues, length);
|
|
20
|
+
const result = taCore.sma(sourceValues, length instanceof Series ? length.toArray() : length);
|
|
21
21
|
return Series.fromArray(bars, result);
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -124,9 +124,10 @@ export function bb(source: Series, length: number, mult: number): [Series, Serie
|
|
|
124
124
|
* @param biased - true (default): divide by length; false: by length - 1
|
|
125
125
|
* @returns Series with standard deviation values
|
|
126
126
|
*/
|
|
127
|
-
export function stdev(source: Series, length: number, biased: boolean = true): Series {
|
|
127
|
+
export function stdev(source: Series, length: number | Series, biased: boolean = true): Series {
|
|
128
128
|
const bars = source.bars as Bar[];
|
|
129
|
-
|
|
129
|
+
const len = length instanceof Series ? length.toArray() : length;
|
|
130
|
+
return Series.fromArray(bars, taCore.stdev(source.toArray(), len, biased));
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
|
|
@@ -600,9 +601,9 @@ export function dev(source: Series, length: number): Series {
|
|
|
600
601
|
* @param biased - Use biased estimation (default: true)
|
|
601
602
|
* @returns Series with variance values
|
|
602
603
|
*/
|
|
603
|
-
export function variance(source: Series, length: number, biased: boolean = true): Series {
|
|
604
|
+
export function variance(source: Series, length: number | Series, biased: boolean = true): Series {
|
|
604
605
|
const bars = source.bars as Bar[];
|
|
605
|
-
const result = taCore.variance(source.toArray(), length, biased);
|
|
606
|
+
const result = taCore.variance(source.toArray(), length instanceof Series ? length.toArray() : length, biased);
|
|
606
607
|
return Series.fromArray(bars, result);
|
|
607
608
|
}
|
|
608
609
|
|
|
@@ -757,7 +758,7 @@ export function dmi(bars: Bar[], diLength: number, adxSmoothing: number): [Serie
|
|
|
757
758
|
* @param source - Source series
|
|
758
759
|
* @param shortLength - Short smoothing period
|
|
759
760
|
* @param longLength - Long smoothing period
|
|
760
|
-
* @returns Series with TSI values
|
|
761
|
+
* @returns Series with TSI values, in [-1, 1] as PineScript
|
|
761
762
|
*/
|
|
762
763
|
export function tsi(source: Series, shortLength: number, longLength: number): Series {
|
|
763
764
|
const bars = source.bars as Bar[];
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table namespace
|
|
3
|
+
* Functions for creating and filling table drawing objects.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* A table is drawn at a fixed position of the pane (`position.*`), not at a bar. Tables have no getters in
|
|
7
|
+
* PineScript; the script API returns the live tables in `result.tables`.
|
|
8
|
+
*
|
|
9
|
+
* PineScript rules:
|
|
10
|
+
* - `table.cell` defines a cell: the arguments it does not give get their default, a previous definition of the
|
|
11
|
+
* cell is replaced
|
|
12
|
+
* - `table.cell_set_*` changes one property of a cell (a cell that was not defined is created)
|
|
13
|
+
* - a column or row outside the table is a runtime error
|
|
14
|
+
* - a deleted table is `na`: its functions do nothing
|
|
15
|
+
*
|
|
16
|
+
* @version 6
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { all as allDrawings, isDeleted, register, remove } from '../drawing/registry.js';
|
|
20
|
+
import type { Table, TableCell, color } from '../types/index.js';
|
|
21
|
+
|
|
22
|
+
type TablePosition = Table['position'];
|
|
23
|
+
type HAlign = 'left' | 'center' | 'right';
|
|
24
|
+
type VAlign = 'top' | 'center' | 'bottom';
|
|
25
|
+
type FontFamily = 'default' | 'monospace';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates a new table (PineScript `table.new`).
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const t = table.new(position.top_right, 2, 3, '#131722', '#787B86', 1);
|
|
33
|
+
* table.cell(t, 0, 0, 'RSI', 0, 0, '#FFFFFF');
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function new_table(
|
|
37
|
+
position: TablePosition,
|
|
38
|
+
columns: number,
|
|
39
|
+
rows: number,
|
|
40
|
+
bgcolor?: color,
|
|
41
|
+
frame_color?: color,
|
|
42
|
+
frame_width?: number,
|
|
43
|
+
border_color?: color,
|
|
44
|
+
border_width?: number,
|
|
45
|
+
force_overlay?: boolean
|
|
46
|
+
): Table {
|
|
47
|
+
return register('table', {
|
|
48
|
+
position,
|
|
49
|
+
columns,
|
|
50
|
+
rows,
|
|
51
|
+
bgcolor,
|
|
52
|
+
frame_color,
|
|
53
|
+
frame_width,
|
|
54
|
+
border_color,
|
|
55
|
+
border_width,
|
|
56
|
+
force_overlay: force_overlay ?? false,
|
|
57
|
+
cells: new Map(),
|
|
58
|
+
merges: [],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function checkCell(caller: string, id: Table, column: number, row: number): void {
|
|
63
|
+
if (!(column >= 0 && column < id.columns && row >= 0 && row < id.rows)) {
|
|
64
|
+
throw new RangeError(
|
|
65
|
+
`${caller}: column ${column}, row ${row} is outside the table (${id.columns} columns, ${id.rows} rows).`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const key = (column: number, row: number): string => `${column},${row}`;
|
|
71
|
+
|
|
72
|
+
/** The cell at column / row, created when it was not defined. */
|
|
73
|
+
function cellOf(caller: string, id: Table, column: number, row: number): TableCell {
|
|
74
|
+
checkCell(caller, id, column, row);
|
|
75
|
+
const cells = (id.cells ??= new Map());
|
|
76
|
+
let cell = cells.get(key(column, row));
|
|
77
|
+
if (!cell) cells.set(key(column, row), (cell = { text: '' }));
|
|
78
|
+
return cell;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Defines a cell (PineScript `table.cell`): its text and properties. Omitted properties get their default.
|
|
83
|
+
*/
|
|
84
|
+
export function cell(
|
|
85
|
+
table_id: Table,
|
|
86
|
+
column: number,
|
|
87
|
+
row: number,
|
|
88
|
+
text?: string,
|
|
89
|
+
width?: number,
|
|
90
|
+
height?: number,
|
|
91
|
+
text_color?: color,
|
|
92
|
+
text_halign?: HAlign,
|
|
93
|
+
text_valign?: VAlign,
|
|
94
|
+
text_size?: string | number,
|
|
95
|
+
bgcolor?: color,
|
|
96
|
+
tooltip?: string,
|
|
97
|
+
text_font_family?: FontFamily,
|
|
98
|
+
text_formatting?: number
|
|
99
|
+
): void {
|
|
100
|
+
if (isDeleted(table_id)) return;
|
|
101
|
+
checkCell('table.cell()', table_id, column, row);
|
|
102
|
+
(table_id.cells ??= new Map()).set(key(column, row), {
|
|
103
|
+
text: text ?? '',
|
|
104
|
+
width,
|
|
105
|
+
height,
|
|
106
|
+
text_color,
|
|
107
|
+
text_halign,
|
|
108
|
+
text_valign,
|
|
109
|
+
text_size,
|
|
110
|
+
bgcolor,
|
|
111
|
+
tooltip,
|
|
112
|
+
text_font_family,
|
|
113
|
+
text_formatting,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function setCell<K extends keyof TableCell>(name: string, field: K) {
|
|
118
|
+
return (table_id: Table, column: number, row: number, value: TableCell[K]): void => {
|
|
119
|
+
if (isDeleted(table_id)) return;
|
|
120
|
+
cellOf(`table.${name}()`, table_id, column, row)[field] = value;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Sets the text of a cell. */
|
|
125
|
+
export const cell_set_text = setCell('cell_set_text', 'text');
|
|
126
|
+
/** Sets the width of a cell (% of the chart width; 0 fits the text). */
|
|
127
|
+
export const cell_set_width = setCell('cell_set_width', 'width');
|
|
128
|
+
/** Sets the height of a cell (% of the chart height; 0 fits the text). */
|
|
129
|
+
export const cell_set_height = setCell('cell_set_height', 'height');
|
|
130
|
+
/** Sets the text colour of a cell. */
|
|
131
|
+
export const cell_set_text_color = setCell('cell_set_text_color', 'text_color');
|
|
132
|
+
/** Sets the horizontal text alignment of a cell. */
|
|
133
|
+
export const cell_set_text_halign = setCell('cell_set_text_halign', 'text_halign');
|
|
134
|
+
/** Sets the vertical text alignment of a cell. */
|
|
135
|
+
export const cell_set_text_valign = setCell('cell_set_text_valign', 'text_valign');
|
|
136
|
+
/** Sets the text size of a cell. */
|
|
137
|
+
export const cell_set_text_size = setCell('cell_set_text_size', 'text_size');
|
|
138
|
+
/** Sets the background colour of a cell. */
|
|
139
|
+
export const cell_set_bgcolor = setCell('cell_set_bgcolor', 'bgcolor');
|
|
140
|
+
/** Sets the tooltip of a cell. */
|
|
141
|
+
export const cell_set_tooltip = setCell('cell_set_tooltip', 'tooltip');
|
|
142
|
+
/** Sets the font family of a cell. */
|
|
143
|
+
export const cell_set_text_font_family = setCell('cell_set_text_font_family', 'text_font_family');
|
|
144
|
+
/** Sets the text formatting of a cell (`text.format_*` flags). */
|
|
145
|
+
export const cell_set_text_formatting = setCell('cell_set_text_formatting', 'text_formatting');
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Merges the cells from start_column / start_row to end_column / end_row (PineScript `table.merge_cells`). The
|
|
149
|
+
* merged cell shows the content of the start cell.
|
|
150
|
+
*/
|
|
151
|
+
export function merge_cells(
|
|
152
|
+
table_id: Table,
|
|
153
|
+
start_column: number,
|
|
154
|
+
start_row: number,
|
|
155
|
+
end_column: number,
|
|
156
|
+
end_row: number
|
|
157
|
+
): void {
|
|
158
|
+
if (isDeleted(table_id)) return;
|
|
159
|
+
checkCell('table.merge_cells()', table_id, start_column, start_row);
|
|
160
|
+
checkCell('table.merge_cells()', table_id, end_column, end_row);
|
|
161
|
+
if (end_column < start_column || end_row < start_row) {
|
|
162
|
+
throw new RangeError('table.merge_cells(): the end cell is before the start cell.');
|
|
163
|
+
}
|
|
164
|
+
(table_id.merges ??= []).push({ start_column, start_row, end_column, end_row });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Removes the cells from start_column / start_row to end_column / end_row (PineScript `table.clear`; default end:
|
|
169
|
+
* the start cell), and the merges that start in that range.
|
|
170
|
+
*/
|
|
171
|
+
export function clear(
|
|
172
|
+
table_id: Table,
|
|
173
|
+
start_column: number,
|
|
174
|
+
start_row: number,
|
|
175
|
+
end_column: number = start_column,
|
|
176
|
+
end_row: number = start_row
|
|
177
|
+
): void {
|
|
178
|
+
if (isDeleted(table_id)) return;
|
|
179
|
+
checkCell('table.clear()', table_id, start_column, start_row);
|
|
180
|
+
checkCell('table.clear()', table_id, end_column, end_row);
|
|
181
|
+
const inside = (column: number, row: number): boolean =>
|
|
182
|
+
column >= start_column && column <= end_column && row >= start_row && row <= end_row;
|
|
183
|
+
for (let column = start_column; column <= end_column; column++) {
|
|
184
|
+
for (let row = start_row; row <= end_row; row++) table_id.cells?.delete(key(column, row));
|
|
185
|
+
}
|
|
186
|
+
if (table_id.merges) table_id.merges = table_id.merges.filter((m) => !inside(m.start_column, m.start_row));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function setTable<K extends keyof Table>(field: K) {
|
|
190
|
+
return (table_id: Table, value: Table[K]): void => {
|
|
191
|
+
if (isDeleted(table_id)) return;
|
|
192
|
+
table_id[field] = value;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Moves the table to another position (`position.*`). */
|
|
197
|
+
export const set_position = setTable('position');
|
|
198
|
+
/** Sets the background colour of the table. */
|
|
199
|
+
export const set_bgcolor = setTable('bgcolor');
|
|
200
|
+
/** Sets the colour of the outer frame. */
|
|
201
|
+
export const set_frame_color = setTable('frame_color');
|
|
202
|
+
/** Sets the width of the outer frame. */
|
|
203
|
+
export const set_frame_width = setTable('frame_width');
|
|
204
|
+
/** Sets the colour of the cell borders. */
|
|
205
|
+
export const set_border_color = setTable('border_color');
|
|
206
|
+
/** Sets the width of the cell borders. */
|
|
207
|
+
export const set_border_width = setTable('border_width');
|
|
208
|
+
|
|
209
|
+
/** Deletes the table (PineScript `table.delete`). */
|
|
210
|
+
export function delete_table(table_id: Table): void {
|
|
211
|
+
remove('table', table_id);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** All live tables, in creation order (PineScript `table.all`). A new array. */
|
|
215
|
+
export function all(): Table[] {
|
|
216
|
+
return allDrawings('table');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Alias to match PineScript API
|
|
220
|
+
export { new_table as new };
|
|
221
|
+
export { delete_table as delete };
|
package/src/text/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Text namespace
|
|
3
|
-
* PineScript `text.format_*` constants for `label.set_text_formatting` and `box.set_text_formatting
|
|
3
|
+
* PineScript `text.format_*` constants for `label.set_text_formatting` and `box.set_text_formatting`, and the
|
|
4
|
+
* `text.align_*` / `text.wrap_*` constants.
|
|
4
5
|
*
|
|
5
|
-
* The values are flags that add up, as in PineScript (`text.format_bold + text.format_italic`).
|
|
6
|
+
* The format values are flags that add up, as in PineScript (`text.format_bold + text.format_italic`).
|
|
6
7
|
* They are the values PineScript uses for drawn labels and boxes.
|
|
7
8
|
*/
|
|
8
9
|
|
|
@@ -12,3 +13,18 @@ export const format_none = 0;
|
|
|
12
13
|
export const format_bold = 1;
|
|
13
14
|
/** Italic text. */
|
|
14
15
|
export const format_italic = 2;
|
|
16
|
+
|
|
17
|
+
/** Text aligned to the left (`table.cell`, `box.new`, `label.new` textalign). */
|
|
18
|
+
export const align_left = 'left' as const;
|
|
19
|
+
/** Text centred horizontally or vertically. */
|
|
20
|
+
export const align_center = 'center' as const;
|
|
21
|
+
/** Text aligned to the right. */
|
|
22
|
+
export const align_right = 'right' as const;
|
|
23
|
+
/** Text aligned to the top (`table.cell`, `box.new` text_valign). */
|
|
24
|
+
export const align_top = 'top' as const;
|
|
25
|
+
/** Text aligned to the bottom. */
|
|
26
|
+
export const align_bottom = 'bottom' as const;
|
|
27
|
+
/** Text wrapped to the box width (`box.new` text_wrap). */
|
|
28
|
+
export const wrap_auto = 'auto' as const;
|
|
29
|
+
/** Text not wrapped. */
|
|
30
|
+
export const wrap_none = 'none' as const;
|
package/src/time/datestring.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Time zones: "UT", "UTC", "GMT", "+0530", "-05:00", "GMT+5", "UTC-0300", and the RFC 2822
|
|
11
11
|
* names EST, EDT, CST, CDT, MST, MDT, PST, PDT. After a "T" time, only numeric offsets.
|
|
12
12
|
* Without a time the time is 00:00. Without a time zone the time zone is GMT+0.
|
|
13
|
+
* "2025" and "2025-06" are the first day of the year / month. "01 Jan 2022 GMT+3" (a zone without a time) gives na.
|
|
13
14
|
* "24:00" is midnight at the end of the day.
|
|
14
15
|
*
|
|
15
16
|
* "Z", "2024-01-01T09:30:00 GMT", "24:30" and "09:30:60" are compile errors in PineScript, and
|
|
@@ -34,6 +35,7 @@ const NUMERIC_ZONE = String.raw`([+-]\d{2}(?::?\d{2})?)`;
|
|
|
34
35
|
const DATE = String.raw`(\d{4})([-/])(\d{1,2})\2(\d{1,2})`;
|
|
35
36
|
const WEEKDAY = String.raw`(?:[a-z]{3,9},?\s+)?`;
|
|
36
37
|
|
|
38
|
+
const YEAR_MONTH = /^(\d{4})(?:-(\d{1,2}))?$/;
|
|
37
39
|
const ISO_T = new RegExp(String.raw`^${DATE}t${TIME}\s*${NUMERIC_ZONE}?$`, 'i');
|
|
38
40
|
const ISO = new RegExp(String.raw`^${DATE}(?:\s+${TIME})?\s*${ZONE}?$`, 'i');
|
|
39
41
|
const DAY_FIRST = new RegExp(String.raw`^${WEEKDAY}(\d{1,2})(?:\s+|-)([a-z]+)\.?,?(?:\s+|-)(\d{4})(?:,?\s+${TIME})?\s*${ZONE}?$`, 'i');
|
|
@@ -68,12 +70,20 @@ export function parseDateString(dateString: string): number {
|
|
|
68
70
|
let rest: Array<string | undefined>;
|
|
69
71
|
|
|
70
72
|
let m: RegExpExecArray | null;
|
|
73
|
+
// "2025" and "2025-06": the first day of the year / month, 00:00 UTC (PineScript)
|
|
74
|
+
if ((m = YEAR_MONTH.exec(text))) {
|
|
75
|
+
const mo = m[2] ? Number(m[2]) : 1;
|
|
76
|
+
if (mo < 1 || mo > 12) throw new SyntaxError(`Invalid date string "${dateString}": a field is out of range`);
|
|
77
|
+
return Date.UTC(Number(m[1]), mo - 1, 1);
|
|
78
|
+
}
|
|
71
79
|
if ((m = ISO_T.exec(text) ?? ISO.exec(text))) {
|
|
72
80
|
[year, month, day] = [m[1]!, Number(m[3]), m[4]!];
|
|
73
81
|
rest = m.slice(5);
|
|
74
82
|
} else if ((m = DAY_FIRST.exec(text))) {
|
|
75
83
|
[day, month, year] = [m[1]!, monthNumber(m[2]!), m[3]!];
|
|
76
84
|
rest = m.slice(4);
|
|
85
|
+
// a time zone without a time ("01 Jan 2022 GMT+3") gives na in PineScript
|
|
86
|
+
if (rest[0] === undefined && rest[4] !== undefined) return NaN;
|
|
77
87
|
} else if ((m = MONTH_FIRST.exec(text))) {
|
|
78
88
|
[month, day, year] = [monthNumber(m[1]!), m[2]!, m[3]!];
|
|
79
89
|
rest = m.slice(4);
|
package/src/time/index.ts
CHANGED
|
@@ -61,8 +61,10 @@ export function timestamp(
|
|
|
61
61
|
export function timestamp(first: string | simple_int, ...rest: Array<simple_int | undefined>): int {
|
|
62
62
|
if (typeof first === 'string') {
|
|
63
63
|
if (rest.length === 0) return parseDateString(first);
|
|
64
|
+
// PineScript: an na field counts as 0 (day na of January 2020 is 2019-12-31)
|
|
65
|
+
const f = (v: number | undefined) => (v === undefined || v === null || Number.isNaN(v) ? 0 : v);
|
|
64
66
|
const [year, month, day, hour, minute, second] = rest as [number, number, number, number?, number?, number?];
|
|
65
|
-
return zonedToUnix(first, year, month, day, hour
|
|
67
|
+
return zonedToUnix(first, f(year), f(month), f(day), f(hour), f(minute), f(second));
|
|
66
68
|
}
|
|
67
69
|
const [month, day, hour, minute, second] = rest as [number, number, number?, number?, number?];
|
|
68
70
|
return new Date(first, month - 1, day, hour || 0, minute || 0, second || 0).getTime();
|