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/dist/esm/array/index.js
CHANGED
|
@@ -18,17 +18,47 @@ export function new_array(size = 0, initial_value) {
|
|
|
18
18
|
export function size(id) {
|
|
19
19
|
return id.length;
|
|
20
20
|
}
|
|
21
|
+
/** True for a PineScript `na` element: NaN, null or undefined. */
|
|
22
|
+
function isNa(v) {
|
|
23
|
+
return v === undefined || v === null || (typeof v === 'number' && Number.isNaN(v));
|
|
24
|
+
}
|
|
25
|
+
/** The `na` of the array's element type: NaN for a numeric (or empty) array, undefined otherwise. */
|
|
26
|
+
function naOf(id) {
|
|
27
|
+
return (id.length === 0 || typeof id[0] === 'number' ? NaN : undefined);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Position addressed by a PineScript index: truncated, a negative index counts from the end. An index outside
|
|
31
|
+
* `-size .. size - 1 + extra` is a runtime error.
|
|
32
|
+
*/
|
|
33
|
+
function position(name, id, index, extra = 0) {
|
|
34
|
+
const i = Math.trunc(index);
|
|
35
|
+
const n = id.length;
|
|
36
|
+
if (i < -n || i >= n + extra) {
|
|
37
|
+
throw new Error(`array.${name}: index ${i} is out of bounds, array size is ${n}`);
|
|
38
|
+
}
|
|
39
|
+
return i < 0 ? i + n : i;
|
|
40
|
+
}
|
|
21
41
|
/**
|
|
22
|
-
* Returns the element at the specified index
|
|
42
|
+
* Returns the element at the specified index.
|
|
43
|
+
*
|
|
44
|
+
* PineScript rules: a negative index counts from the end (`get(a, -1)` is the last element); an index outside
|
|
45
|
+
* `-size .. size - 1` is a runtime error (throws); an `na` index gives `na`.
|
|
23
46
|
*/
|
|
24
47
|
export function get(id, index) {
|
|
25
|
-
|
|
48
|
+
if (Number.isNaN(index))
|
|
49
|
+
return naOf(id);
|
|
50
|
+
return id[position('get', id, index)];
|
|
26
51
|
}
|
|
27
52
|
/**
|
|
28
|
-
* Sets the value of the element at the specified index
|
|
53
|
+
* Sets the value of the element at the specified index.
|
|
54
|
+
*
|
|
55
|
+
* PineScript rules: a negative index counts from the end; an index outside `-size .. size - 1` is a runtime error
|
|
56
|
+
* (throws); an `na` index does nothing.
|
|
29
57
|
*/
|
|
30
58
|
export function set(id, index, value) {
|
|
31
|
-
|
|
59
|
+
if (Number.isNaN(index))
|
|
60
|
+
return;
|
|
61
|
+
id[position('set', id, index)] = value;
|
|
32
62
|
}
|
|
33
63
|
/**
|
|
34
64
|
* Appends a value to the end of the array
|
|
@@ -61,34 +91,72 @@ export function clear(id) {
|
|
|
61
91
|
id.length = 0;
|
|
62
92
|
}
|
|
63
93
|
/**
|
|
64
|
-
* Inserts a value at the specified index
|
|
94
|
+
* Inserts a value at the specified index.
|
|
95
|
+
*
|
|
96
|
+
* PineScript rules: a negative index counts from the end and `size` appends, so `-size .. size` is valid; any other
|
|
97
|
+
* index is a runtime error (throws); an `na` index appends.
|
|
65
98
|
*/
|
|
66
99
|
export function insert(id, index, value) {
|
|
67
|
-
|
|
100
|
+
if (Number.isNaN(index)) {
|
|
101
|
+
id.push(value);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
id.splice(position('insert', id, index, 1), 0, value);
|
|
68
105
|
}
|
|
69
106
|
/**
|
|
70
|
-
* Removes the element at the specified index
|
|
107
|
+
* Removes the element at the specified index and returns it.
|
|
108
|
+
*
|
|
109
|
+
* PineScript rules: a negative index counts from the end; an index outside `-size .. size - 1` is a runtime error
|
|
110
|
+
* (throws); an `na` index removes nothing and gives `na`.
|
|
71
111
|
*/
|
|
72
112
|
export function remove(id, index) {
|
|
73
|
-
|
|
113
|
+
if (Number.isNaN(index))
|
|
114
|
+
return naOf(id);
|
|
115
|
+
return id.splice(position('remove', id, index), 1)[0];
|
|
116
|
+
}
|
|
117
|
+
/** PineScript `==` of two elements: numbers are equal when |a - b| <= 1e-10; `na` never equals anything. */
|
|
118
|
+
function pineEqual(a, b) {
|
|
119
|
+
if (isNa(a) || isNa(b))
|
|
120
|
+
return false;
|
|
121
|
+
if (a === b)
|
|
122
|
+
return true;
|
|
123
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
124
|
+
const d = a - b;
|
|
125
|
+
return -1e-10 <= d && d <= 1e-10;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
74
128
|
}
|
|
75
129
|
/**
|
|
76
|
-
* Returns true if the array contains the value
|
|
130
|
+
* Returns true if the array contains the value.
|
|
131
|
+
*
|
|
132
|
+
* PineScript rules: numbers are compared with a 1e-10 tolerance (|a - b| <= 1e-10); `na` is never found.
|
|
77
133
|
*/
|
|
78
134
|
export function includes(id, value) {
|
|
79
|
-
return id
|
|
135
|
+
return indexof(id, value) >= 0;
|
|
80
136
|
}
|
|
81
137
|
/**
|
|
82
|
-
* Returns the index of the first occurrence of the value
|
|
138
|
+
* Returns the index of the first occurrence of the value, -1 when not found.
|
|
139
|
+
*
|
|
140
|
+
* PineScript rules: numbers are compared with a 1e-10 tolerance (|a - b| <= 1e-10); `na` is never found.
|
|
83
141
|
*/
|
|
84
142
|
export function indexof(id, value) {
|
|
85
|
-
|
|
143
|
+
for (let i = 0; i < id.length; i++) {
|
|
144
|
+
if (pineEqual(id[i], value))
|
|
145
|
+
return i;
|
|
146
|
+
}
|
|
147
|
+
return -1;
|
|
86
148
|
}
|
|
87
149
|
/**
|
|
88
|
-
* Returns the index of the last occurrence of the value
|
|
150
|
+
* Returns the index of the last occurrence of the value, -1 when not found.
|
|
151
|
+
*
|
|
152
|
+
* PineScript rules: numbers are compared with a 1e-10 tolerance (|a - b| <= 1e-10); `na` is never found.
|
|
89
153
|
*/
|
|
90
154
|
export function lastindexof(id, value) {
|
|
91
|
-
|
|
155
|
+
for (let i = id.length - 1; i >= 0; i--) {
|
|
156
|
+
if (pineEqual(id[i], value))
|
|
157
|
+
return i;
|
|
158
|
+
}
|
|
159
|
+
return -1;
|
|
92
160
|
}
|
|
93
161
|
/**
|
|
94
162
|
* Returns a shallow copy of the array
|
|
@@ -97,10 +165,12 @@ export function copy(id) {
|
|
|
97
165
|
return [...id];
|
|
98
166
|
}
|
|
99
167
|
/**
|
|
100
|
-
*
|
|
168
|
+
* Appends the elements of `id2` to `id1` and returns `id1` (PineScript `array.concat` changes `id1`).
|
|
101
169
|
*/
|
|
102
170
|
export function concat(id1, id2) {
|
|
103
|
-
|
|
171
|
+
for (const v of Array.from(id2))
|
|
172
|
+
id1.push(v);
|
|
173
|
+
return id1;
|
|
104
174
|
}
|
|
105
175
|
/**
|
|
106
176
|
* Joins all elements into a string
|
|
@@ -115,29 +185,139 @@ export function reverse(id) {
|
|
|
115
185
|
id.reverse();
|
|
116
186
|
}
|
|
117
187
|
/**
|
|
118
|
-
* Returns
|
|
188
|
+
* Returns the elements from `index_from` (included) to `index_to` (excluded, default: the size) as a live view of
|
|
189
|
+
* the array (PineScript `array.slice`).
|
|
190
|
+
*
|
|
191
|
+
* PineScript rules:
|
|
192
|
+
* - the slice shares the elements of the original array: a change of an element of the slice changes the original
|
|
193
|
+
* array, and adding / removing elements of the slice adds / removes them in the original array, inside the slice
|
|
194
|
+
* bounds (a push to the slice inserts at the end of the slice)
|
|
195
|
+
* - `index_from` must address an element (`0 .. size - 1`) and `index_to` may be `size`; other bounds, or
|
|
196
|
+
* `index_from > index_to`, are a runtime error (throws); an `na` bound means the start / the end of the array
|
|
119
197
|
*/
|
|
120
198
|
export function slice(id, index_from, index_to) {
|
|
121
|
-
|
|
199
|
+
const n = id.length;
|
|
200
|
+
const start = Number.isNaN(index_from) ? 0 : Math.trunc(index_from);
|
|
201
|
+
const stop = index_to === undefined || Number.isNaN(index_to) ? n : Math.trunc(index_to);
|
|
202
|
+
if (start < 0 || start >= n)
|
|
203
|
+
throw new Error(`array.slice: index ${start} is out of bounds, array size is ${n}`);
|
|
204
|
+
if (stop < 0 || stop > n)
|
|
205
|
+
throw new Error(`array.slice: index ${stop} is out of bounds, array size is ${n}`);
|
|
206
|
+
if (start > stop)
|
|
207
|
+
throw new Error(`array.slice: index_from ${start} is greater than index_to ${stop}`);
|
|
208
|
+
return sliceView(id, start, stop - start);
|
|
209
|
+
}
|
|
210
|
+
/** Index of a property key that is an array index, -1 otherwise. */
|
|
211
|
+
function indexKey(p) {
|
|
212
|
+
if (typeof p !== 'string')
|
|
213
|
+
return -1;
|
|
214
|
+
const i = Number(p);
|
|
215
|
+
return Number.isInteger(i) && i >= 0 && String(i) === p ? i : -1;
|
|
122
216
|
}
|
|
123
217
|
/**
|
|
124
|
-
*
|
|
218
|
+
* Live view of `size` elements of `parent` from `start`: an array (a Proxy) whose element reads and writes go to
|
|
219
|
+
* `parent`, and whose size changes insert / remove elements in `parent` at the end of the view.
|
|
125
220
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
221
|
+
function sliceView(parent, start, size) {
|
|
222
|
+
const resize = (target) => {
|
|
223
|
+
if (target > size)
|
|
224
|
+
parent.splice(start + size, 0, ...Array(target - size).fill(undefined));
|
|
225
|
+
else if (target < size)
|
|
226
|
+
parent.splice(start + target, size - target);
|
|
227
|
+
size = target;
|
|
228
|
+
};
|
|
229
|
+
return new Proxy([], {
|
|
230
|
+
get(target, p, receiver) {
|
|
231
|
+
const i = indexKey(p);
|
|
232
|
+
if (i >= 0)
|
|
233
|
+
return i < size ? parent[start + i] : undefined;
|
|
234
|
+
if (p === 'length')
|
|
235
|
+
return size;
|
|
236
|
+
return Reflect.get(target, p, receiver);
|
|
237
|
+
},
|
|
238
|
+
set(target, p, value, receiver) {
|
|
239
|
+
const i = indexKey(p);
|
|
240
|
+
if (i >= 0) {
|
|
241
|
+
if (i >= size)
|
|
242
|
+
resize(i + 1);
|
|
243
|
+
parent[start + i] = value;
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
if (p === 'length') {
|
|
247
|
+
resize(Number(value));
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
return Reflect.set(target, p, value, receiver);
|
|
251
|
+
},
|
|
252
|
+
has(target, p) {
|
|
253
|
+
const i = indexKey(p);
|
|
254
|
+
return i >= 0 ? i < size : Reflect.has(target, p);
|
|
255
|
+
},
|
|
256
|
+
// Array methods delete the slots past the new end before they set the length, which does the removal.
|
|
257
|
+
deleteProperty(target, p) {
|
|
258
|
+
return indexKey(p) >= 0 ? true : Reflect.deleteProperty(target, p);
|
|
259
|
+
},
|
|
260
|
+
ownKeys(target) {
|
|
261
|
+
return [...Array.from({ length: size }, (_, i) => String(i)), ...Reflect.ownKeys(target)];
|
|
262
|
+
},
|
|
263
|
+
getOwnPropertyDescriptor(target, p) {
|
|
264
|
+
const i = indexKey(p);
|
|
265
|
+
if (i >= 0) {
|
|
266
|
+
return i < size ? { value: parent[start + i], writable: true, enumerable: true, configurable: true } : undefined;
|
|
267
|
+
}
|
|
268
|
+
if (p === 'length')
|
|
269
|
+
return { value: size, writable: true, enumerable: false, configurable: false };
|
|
270
|
+
return Reflect.getOwnPropertyDescriptor(target, p);
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Positions of the elements in PineScript sorted order: ascending values, `na` after the numbers (before the
|
|
276
|
+
* strings of a string array), stable; descending is the ascending result reversed.
|
|
277
|
+
*/
|
|
278
|
+
function sortedPositions(id, order) {
|
|
279
|
+
const values = [];
|
|
280
|
+
const nas = [];
|
|
281
|
+
for (let i = 0; i < id.length; i++)
|
|
282
|
+
(isNa(id[i]) ? nas : values).push(i);
|
|
283
|
+
values.sort((a, b) => {
|
|
284
|
+
const x = id[a];
|
|
285
|
+
const y = id[b];
|
|
286
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
134
287
|
});
|
|
288
|
+
const naFirst = values.length > 0 && typeof id[values[0]] === 'string';
|
|
289
|
+
const positions = naFirst ? [...nas, ...values] : [...values, ...nas];
|
|
290
|
+
if (order === 'desc' || order === 'descending')
|
|
291
|
+
positions.reverse();
|
|
292
|
+
return positions;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Sorts the array in place.
|
|
296
|
+
*
|
|
297
|
+
* PineScript rules: `na` goes after the numbers in ascending order and before them in descending order (the
|
|
298
|
+
* ascending result reversed); in a string array `na` goes first in ascending order.
|
|
299
|
+
*/
|
|
300
|
+
export function sort(id, order = 'asc') {
|
|
301
|
+
const sorted = sortedPositions(id, order).map((p) => id[p]);
|
|
302
|
+
for (let i = 0; i < sorted.length; i++)
|
|
303
|
+
id[i] = sorted[i];
|
|
135
304
|
}
|
|
136
305
|
/**
|
|
137
|
-
*
|
|
306
|
+
* Sum of the non-na elements (PineScript `array.sum(id)`), added from left to right; an array without values gives NaN.
|
|
307
|
+
* As PineScript, the running sum is set to 0 after a step when its absolute value is <= 1e-10.
|
|
138
308
|
*/
|
|
139
309
|
export function sum(id) {
|
|
140
|
-
|
|
310
|
+
let total = 0;
|
|
311
|
+
let count = 0;
|
|
312
|
+
for (const v of id) {
|
|
313
|
+
if (Number.isNaN(v))
|
|
314
|
+
continue;
|
|
315
|
+
count++;
|
|
316
|
+
total += v;
|
|
317
|
+
if (Math.abs(total) <= 1e-10)
|
|
318
|
+
total = 0;
|
|
319
|
+
}
|
|
320
|
+
return count ? total : NaN;
|
|
141
321
|
}
|
|
142
322
|
/**
|
|
143
323
|
* Average of the non-na elements (PineScript `array.avg(id)`); an array without values gives NaN.
|
|
@@ -146,72 +326,115 @@ export function avg(id) {
|
|
|
146
326
|
const values = id.filter((v) => !Number.isNaN(v));
|
|
147
327
|
return values.length ? values.reduce((a, b) => a + b, 0) / values.length : NaN;
|
|
148
328
|
}
|
|
329
|
+
/** The `nth` value of the non-na elements sorted with `compare` (array.min / array.max). */
|
|
330
|
+
function nthValue(name, id, nth, compare) {
|
|
331
|
+
const rank = Number.isNaN(nth) ? 0 : Math.trunc(nth);
|
|
332
|
+
const values = id.filter((v) => !Number.isNaN(v));
|
|
333
|
+
if (!values.length)
|
|
334
|
+
return NaN;
|
|
335
|
+
if (rank < 0 || rank >= id.length) {
|
|
336
|
+
throw new Error(`array.${name}: nth ${rank} is out of bounds, array size is ${id.length}`);
|
|
337
|
+
}
|
|
338
|
+
values.sort(compare);
|
|
339
|
+
return values[Math.min(rank, values.length - 1)];
|
|
340
|
+
}
|
|
149
341
|
/**
|
|
150
342
|
* The `nth` smallest value (0 = the smallest) of the non-na elements (PineScript `array.min(id, nth)`).
|
|
151
|
-
*
|
|
343
|
+
*
|
|
344
|
+
* PineScript rules: an `nth` past the non-na values but inside the array size gives the greatest value; an `nth`
|
|
345
|
+
* outside `0 .. size - 1` is a runtime error (throws); an `na` nth counts as 0; an array without values gives NaN.
|
|
152
346
|
*/
|
|
153
347
|
export function min(id, nth = 0) {
|
|
154
|
-
|
|
155
|
-
return values.length ? values[Math.min(nth, values.length - 1)] : NaN;
|
|
348
|
+
return nthValue('min', id, nth, (a, b) => a - b);
|
|
156
349
|
}
|
|
157
350
|
/**
|
|
158
351
|
* The `nth` greatest value (0 = the greatest) of the non-na elements (PineScript `array.max(id, nth)`).
|
|
159
|
-
*
|
|
352
|
+
*
|
|
353
|
+
* PineScript rules: an `nth` past the non-na values but inside the array size gives the smallest value; an `nth`
|
|
354
|
+
* outside `0 .. size - 1` is a runtime error (throws); an `na` nth counts as 0; an array without values gives NaN.
|
|
160
355
|
*/
|
|
161
356
|
export function max(id, nth = 0) {
|
|
162
|
-
|
|
163
|
-
return values.length ? values[Math.min(nth, values.length - 1)] : NaN;
|
|
357
|
+
return nthValue('max', id, nth, (a, b) => b - a);
|
|
164
358
|
}
|
|
165
359
|
/**
|
|
166
|
-
*
|
|
360
|
+
* Median of the non-na elements (PineScript `array.median(id)`): the middle value, or the mean of the two middle
|
|
361
|
+
* values for an even count; an array without values gives NaN.
|
|
167
362
|
*/
|
|
168
363
|
export function median(id) {
|
|
169
|
-
const sorted =
|
|
364
|
+
const sorted = id.filter((v) => !Number.isNaN(v)).sort((a, b) => a - b);
|
|
365
|
+
if (!sorted.length)
|
|
366
|
+
return NaN;
|
|
170
367
|
const mid = Math.floor(sorted.length / 2);
|
|
171
368
|
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
172
369
|
}
|
|
173
370
|
/**
|
|
174
|
-
*
|
|
371
|
+
* Most frequent non-na element (PineScript `array.mode(id)`), compared exactly.
|
|
372
|
+
*
|
|
373
|
+
* PineScript rules: with several values of the same frequency, the smallest one; an array without values gives NaN.
|
|
175
374
|
*/
|
|
176
375
|
export function mode(id) {
|
|
177
376
|
const frequency = new Map();
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
377
|
+
for (const v of id) {
|
|
378
|
+
if (!Number.isNaN(v))
|
|
379
|
+
frequency.set(v, (frequency.get(v) ?? 0) + 1);
|
|
380
|
+
}
|
|
381
|
+
let best = NaN;
|
|
382
|
+
let bestCount = 0;
|
|
383
|
+
for (const [v, count] of frequency) {
|
|
384
|
+
if (count > bestCount || (count === bestCount && v < best)) {
|
|
385
|
+
best = v;
|
|
386
|
+
bestCount = count;
|
|
186
387
|
}
|
|
187
388
|
}
|
|
188
|
-
return
|
|
389
|
+
return best;
|
|
189
390
|
}
|
|
190
391
|
/**
|
|
191
|
-
* Standard deviation of the non-na elements (PineScript `array.stdev(id, biased)`)
|
|
192
|
-
* `
|
|
392
|
+
* Standard deviation of the non-na elements (PineScript `array.stdev(id, biased)`): the square root of
|
|
393
|
+
* `array.variance(id, biased)`.
|
|
193
394
|
*/
|
|
194
395
|
export function stdev(id, biased = true) {
|
|
195
396
|
return Math.sqrt(variance(id, biased));
|
|
196
397
|
}
|
|
398
|
+
/** Sum of the values and sum of their squares, both added from the first to the last value. */
|
|
399
|
+
function moments(values) {
|
|
400
|
+
let p = 0;
|
|
401
|
+
let q = 0;
|
|
402
|
+
for (const v of values)
|
|
403
|
+
p += v;
|
|
404
|
+
for (const v of values)
|
|
405
|
+
q += v * v;
|
|
406
|
+
return [p, q];
|
|
407
|
+
}
|
|
197
408
|
/**
|
|
198
409
|
* Variance of the non-na elements (PineScript `array.variance(id, biased)`).
|
|
199
|
-
*
|
|
410
|
+
*
|
|
411
|
+
* PineScript rule (moment form): with `p` the sum of the values, `q` the sum of their squares, `n` the count and
|
|
412
|
+
* `m = p / n`: biased (default) `max(0, q / n - m * m)`, unbiased `max(0, q / (n - 1) - (p / (n - 1)) * m)`.
|
|
413
|
+
* One value gives 0 biased and NaN unbiased; an array without values gives NaN.
|
|
200
414
|
*/
|
|
201
415
|
export function variance(id, biased = true) {
|
|
202
416
|
const values = id.filter((v) => !Number.isNaN(v));
|
|
203
|
-
|
|
417
|
+
const n = values.length;
|
|
418
|
+
if (!n)
|
|
204
419
|
return NaN;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
420
|
+
if (n < 2)
|
|
421
|
+
return biased ? 0 : NaN;
|
|
422
|
+
const [p, q] = moments(values);
|
|
423
|
+
const mean = p / n;
|
|
424
|
+
return biased ? Math.max(0, q / n - mean * mean) : Math.max(0, q / (n - 1) - (p / (n - 1)) * mean);
|
|
208
425
|
}
|
|
209
426
|
/**
|
|
210
427
|
* Fills the array with the specified value
|
|
211
428
|
*/
|
|
212
429
|
export function fill(id, value, index_from = 0, index_to) {
|
|
213
|
-
|
|
214
|
-
|
|
430
|
+
// PineScript: an na bound is the start / the end; a bound past the end is a runtime error
|
|
431
|
+
const start = index_from === undefined || index_from === null || Number.isNaN(index_from) ? 0 : index_from;
|
|
432
|
+
const end = index_to === undefined || index_to === null || Number.isNaN(index_to) ? id.length : index_to;
|
|
433
|
+
for (const i of [start, end]) {
|
|
434
|
+
if (i < 0 || i > id.length)
|
|
435
|
+
throw new Error(`array.fill: index ${i} is out of bounds, array size is ${id.length}`);
|
|
436
|
+
}
|
|
437
|
+
for (let i = start; i < end; i++) {
|
|
215
438
|
id[i] = value;
|
|
216
439
|
}
|
|
217
440
|
}
|
|
@@ -244,6 +467,9 @@ export function from(...values) {
|
|
|
244
467
|
* ```
|
|
245
468
|
*/
|
|
246
469
|
export function first(id) {
|
|
470
|
+
// PineScript: a runtime error on an empty array
|
|
471
|
+
if (id.length === 0)
|
|
472
|
+
throw new Error('Cannot call array.first() if array is empty');
|
|
247
473
|
return id[0];
|
|
248
474
|
}
|
|
249
475
|
/**
|
|
@@ -263,6 +489,9 @@ export function first(id) {
|
|
|
263
489
|
* ```
|
|
264
490
|
*/
|
|
265
491
|
export function last(id) {
|
|
492
|
+
// PineScript: a runtime error on an empty array (as array.first)
|
|
493
|
+
if (id.length === 0)
|
|
494
|
+
throw new Error('Cannot call array.last() if array is empty');
|
|
266
495
|
return id[id.length - 1];
|
|
267
496
|
}
|
|
268
497
|
/**
|
|
@@ -499,6 +728,16 @@ export function new_label(size = 0, initial_value) {
|
|
|
499
728
|
export function new_linefill(size = 0, initial_value) {
|
|
500
729
|
return new_array(size, initial_value);
|
|
501
730
|
}
|
|
731
|
+
/**
|
|
732
|
+
* Creates a new array of tables (PineScript `array.new_table`).
|
|
733
|
+
*
|
|
734
|
+
* @param size - Initial size of array (default: 0)
|
|
735
|
+
* @param initial_value - Initial value for all elements (default: undefined)
|
|
736
|
+
* @returns New table array
|
|
737
|
+
*/
|
|
738
|
+
export function new_table(size = 0, initial_value) {
|
|
739
|
+
return new_array(size, initial_value);
|
|
740
|
+
}
|
|
502
741
|
/**
|
|
503
742
|
* Absolute value of array elements
|
|
504
743
|
*
|
|
@@ -701,24 +940,38 @@ export function binary_search_rightmost(id, val) {
|
|
|
701
940
|
* - Unbiased (false): divides by n-1 (sample covariance)
|
|
702
941
|
* - Returns NaN if arrays are empty or have different lengths
|
|
703
942
|
* - Formula: Cov(X,Y) = E[(X - μX)(Y - μY)]
|
|
943
|
+
*
|
|
944
|
+
* PineScript rules: a pair where either value is `na` is skipped and the count is the number of pairs kept; both
|
|
945
|
+
* means first, then the sum of the products from the first to the last pair. One pair gives 0 biased, NaN unbiased.
|
|
704
946
|
*/
|
|
705
947
|
export function covariance(id1, id2, biased = true) {
|
|
706
|
-
if (id1.length
|
|
948
|
+
if (id1.length !== id2.length) {
|
|
707
949
|
return NaN;
|
|
708
950
|
}
|
|
709
|
-
|
|
951
|
+
const xs = [];
|
|
952
|
+
const ys = [];
|
|
953
|
+
for (let i = 0; i < id1.length; i++) {
|
|
954
|
+
if (Number.isNaN(id1[i]) || Number.isNaN(id2[i]))
|
|
955
|
+
continue;
|
|
956
|
+
xs.push(id1[i]);
|
|
957
|
+
ys.push(id2[i]);
|
|
958
|
+
}
|
|
959
|
+
const n = xs.length;
|
|
960
|
+
if (!n || (!biased && n < 2))
|
|
710
961
|
return NaN;
|
|
962
|
+
let mean1 = 0;
|
|
963
|
+
let mean2 = 0;
|
|
964
|
+
for (let i = 0; i < n; i++) {
|
|
965
|
+
mean1 += xs[i];
|
|
966
|
+
mean2 += ys[i];
|
|
711
967
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
const mean2 = avg(id2);
|
|
968
|
+
mean1 /= n;
|
|
969
|
+
mean2 /= n;
|
|
715
970
|
let sum = 0;
|
|
716
971
|
for (let i = 0; i < n; i++) {
|
|
717
|
-
sum += (
|
|
972
|
+
sum += (xs[i] - mean1) * (ys[i] - mean2);
|
|
718
973
|
}
|
|
719
|
-
|
|
720
|
-
const divisor = biased ? n : n - 1;
|
|
721
|
-
return sum / divisor;
|
|
974
|
+
return sum / (biased ? n : n - 1);
|
|
722
975
|
}
|
|
723
976
|
/**
|
|
724
977
|
* Percentile Linear Interpolation
|
|
@@ -743,32 +996,45 @@ export function covariance(id1, id2, biased = true) {
|
|
|
743
996
|
* - Returns NaN if array is empty
|
|
744
997
|
*
|
|
745
998
|
* PineScript rules:
|
|
746
|
-
* - position in the sorted array: `percentage / 100
|
|
747
|
-
*
|
|
999
|
+
* - 1-based position in the sorted array: `size * percentage / 100 + 0.5` (snapped to an integer within 1e-9),
|
|
1000
|
+
* clamped to the first / last value; between two values `lo * (1 - frac) + hi * frac`
|
|
748
1001
|
* - `na` values are sorted after all numbers; with an `na` in the array, a position between two values gives `na`
|
|
1002
|
+
* - an `na` percentage gives `na`; a percentage outside 0 .. 100 is a runtime error (throws)
|
|
749
1003
|
*/
|
|
750
1004
|
export function percentile_linear_interpolation(id, percentage) {
|
|
751
|
-
if (id.length === 0) {
|
|
1005
|
+
if (id.length === 0 || Number.isNaN(percentage)) {
|
|
752
1006
|
return NaN;
|
|
753
1007
|
}
|
|
1008
|
+
checkPercentage('percentile_linear_interpolation', percentage);
|
|
754
1009
|
const s = sortedNaLast(id);
|
|
755
1010
|
const n = s.length;
|
|
756
|
-
|
|
757
|
-
|
|
1011
|
+
let pos = (n * percentage) / 100 + 0.5;
|
|
1012
|
+
const nearest = Math.round(pos);
|
|
1013
|
+
if (Math.abs(pos - nearest) < 1e-9)
|
|
1014
|
+
pos = nearest;
|
|
1015
|
+
if (pos <= 1)
|
|
758
1016
|
return s[0];
|
|
759
|
-
if (pos >= n
|
|
1017
|
+
if (pos >= n)
|
|
760
1018
|
return s[n - 1];
|
|
761
|
-
const
|
|
762
|
-
const
|
|
763
|
-
if (
|
|
764
|
-
return s[
|
|
765
|
-
|
|
1019
|
+
const lower = Math.floor(pos);
|
|
1020
|
+
const frac = pos - lower;
|
|
1021
|
+
if (frac === 0)
|
|
1022
|
+
return s[lower - 1];
|
|
1023
|
+
if (Number.isNaN(s[n - 1]))
|
|
1024
|
+
return NaN;
|
|
1025
|
+
return s[lower - 1] * (1 - frac) + s[lower] * frac;
|
|
766
1026
|
}
|
|
767
1027
|
/** Array values sorted ascending, `na` values after the numbers (array.percentile_*). */
|
|
768
1028
|
function sortedNaLast(id) {
|
|
769
1029
|
const numbers = id.filter((v) => !Number.isNaN(v)).sort((a, b) => a - b);
|
|
770
1030
|
return [...numbers, ...Array(id.length - numbers.length).fill(NaN)];
|
|
771
1031
|
}
|
|
1032
|
+
/** A percentage outside 0 .. 100 is a PineScript runtime error. */
|
|
1033
|
+
function checkPercentage(name, percentage) {
|
|
1034
|
+
if (!(percentage >= 0 && percentage <= 100)) {
|
|
1035
|
+
throw new Error(`array.${name}: percentage ${percentage} must be between 0 and 100`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
772
1038
|
/**
|
|
773
1039
|
* Percentile Nearest Rank
|
|
774
1040
|
*
|
|
@@ -792,15 +1058,18 @@ function sortedNaLast(id) {
|
|
|
792
1058
|
* - Returns NaN if array is empty
|
|
793
1059
|
*
|
|
794
1060
|
* PineScript rules:
|
|
795
|
-
* - the value at rank `ceil(percentage / 100
|
|
1061
|
+
* - the value at rank `ceil(percentage * size / 100)` (clamped to 1 .. size) of the sorted array
|
|
796
1062
|
* - `na` values are sorted after all numbers (na when that rank holds an `na`)
|
|
1063
|
+
* - an `na` percentage counts as 0; a percentage outside 0 .. 100 is a runtime error (throws)
|
|
797
1064
|
*/
|
|
798
1065
|
export function percentile_nearest_rank(id, percentage) {
|
|
799
1066
|
if (id.length === 0) {
|
|
800
1067
|
return NaN;
|
|
801
1068
|
}
|
|
1069
|
+
const pct = Number.isNaN(percentage) ? 0 : percentage;
|
|
1070
|
+
checkPercentage('percentile_nearest_rank', pct);
|
|
802
1071
|
const s = sortedNaLast(id);
|
|
803
|
-
const rank = Math.min(s.length, Math.max(1, Math.ceil((
|
|
1072
|
+
const rank = Math.min(s.length, Math.max(1, Math.ceil((pct * s.length) / 100)));
|
|
804
1073
|
return s[rank - 1];
|
|
805
1074
|
}
|
|
806
1075
|
/**
|
|
@@ -818,31 +1087,39 @@ export function percentile_nearest_rank(id, percentage) {
|
|
|
818
1087
|
* ```typescript
|
|
819
1088
|
* const arr = [1, 2, 3, 4, 5];
|
|
820
1089
|
* const rank = array.percentrank(arr, 2); // Value at index 2 is 3
|
|
821
|
-
* // Returns:
|
|
1090
|
+
* // Returns: 50 ((3 - 1) * 100 / (5 - 1))
|
|
822
1091
|
* ```
|
|
823
1092
|
*
|
|
824
1093
|
* @remarks
|
|
825
|
-
* - Returns percentage of elements <= the value at given index
|
|
826
1094
|
* - Range: 0 to 100
|
|
827
|
-
*
|
|
1095
|
+
*
|
|
1096
|
+
* PineScript rules:
|
|
1097
|
+
* - `(count - 1) * 100 / (size - 1)`, with `count` the number of non-na elements <= the element (exact
|
|
1098
|
+
* comparison, no 1e-10 tolerance); `na` elements count in the size
|
|
1099
|
+
* - an `na` element, a one-element array or an empty array gives NaN
|
|
1100
|
+
* - an `na` index counts as 0; an index outside `0 .. size - 1` is a runtime error (throws)
|
|
828
1101
|
*/
|
|
829
1102
|
export function percentrank(id, index) {
|
|
830
|
-
|
|
1103
|
+
const i = Number.isNaN(index) ? 0 : Math.trunc(index);
|
|
1104
|
+
if (id.length === 0) {
|
|
1105
|
+
return NaN;
|
|
1106
|
+
}
|
|
1107
|
+
if (i < 0 || i >= id.length) {
|
|
1108
|
+
throw new Error(`array.percentrank: index ${i} is out of bounds, array size is ${id.length}`);
|
|
1109
|
+
}
|
|
1110
|
+
if (id.length === 1) {
|
|
831
1111
|
return NaN;
|
|
832
1112
|
}
|
|
833
|
-
const value = id[
|
|
834
|
-
if (isNaN(value)) {
|
|
1113
|
+
const value = id[i];
|
|
1114
|
+
if (Number.isNaN(value)) {
|
|
835
1115
|
return NaN;
|
|
836
1116
|
}
|
|
837
|
-
// Count elements less than or equal to the value
|
|
838
1117
|
let count = 0;
|
|
839
|
-
for (
|
|
840
|
-
if (
|
|
1118
|
+
for (const v of id) {
|
|
1119
|
+
if (v <= value)
|
|
841
1120
|
count++;
|
|
842
|
-
}
|
|
843
1121
|
}
|
|
844
|
-
|
|
845
|
-
return (count / id.length) * 100;
|
|
1122
|
+
return ((count - 1) * 100) / (id.length - 1);
|
|
846
1123
|
}
|
|
847
1124
|
/**
|
|
848
1125
|
* Sort Indices
|
|
@@ -869,20 +1146,11 @@ export function percentrank(id, index) {
|
|
|
869
1146
|
* - Original array is not modified
|
|
870
1147
|
* - Returns indices that would sort the array
|
|
871
1148
|
* - Useful for maintaining correspondence with other arrays
|
|
1149
|
+
* - PineScript rule: the indices of `na` elements go where `array.sort` puts them (after the numbers in ascending
|
|
1150
|
+
* order, in their original order: `sort_indices([na, na, 5, 1])` is `[3, 2, 0, 1]`)
|
|
872
1151
|
*/
|
|
873
1152
|
export function sort_indices(id, order = 'asc') {
|
|
874
|
-
|
|
875
|
-
const indices = Array.from({ length: id.length }, (_, i) => i);
|
|
876
|
-
// Sort indices based on values in original array
|
|
877
|
-
indices.sort((a, b) => {
|
|
878
|
-
if (order === 'asc') {
|
|
879
|
-
return id[a] - id[b];
|
|
880
|
-
}
|
|
881
|
-
else {
|
|
882
|
-
return id[b] - id[a];
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
return indices;
|
|
1153
|
+
return sortedPositions(id, order);
|
|
886
1154
|
}
|
|
887
1155
|
/**
|
|
888
1156
|
* Standardize
|
|
@@ -904,21 +1172,25 @@ export function sort_indices(id, order = 'asc') {
|
|
|
904
1172
|
* @remarks
|
|
905
1173
|
* - Formula: z = (x - μ) / σ
|
|
906
1174
|
* - Result has mean of 0 and standard deviation of 1
|
|
907
|
-
* - Returns array of NaN if standard deviation is 0
|
|
908
1175
|
* - Useful for comparing values on different scales
|
|
1176
|
+
*
|
|
1177
|
+
* PineScript rules:
|
|
1178
|
+
* - μ and σ are the population mean and standard deviation of the non-na elements, as `array.variance` (moment
|
|
1179
|
+
* form); `na` elements stay `na`
|
|
1180
|
+
* - when σ is 0 (all values equal) every non-na element gives 1
|
|
909
1181
|
*/
|
|
910
1182
|
export function standardize(id) {
|
|
911
|
-
|
|
912
|
-
|
|
1183
|
+
const values = id.filter((v) => !Number.isNaN(v));
|
|
1184
|
+
if (!values.length) {
|
|
1185
|
+
return id.map(() => NaN);
|
|
913
1186
|
}
|
|
914
|
-
const
|
|
915
|
-
const
|
|
916
|
-
|
|
1187
|
+
const [p, q] = moments(values);
|
|
1188
|
+
const mean = p / values.length;
|
|
1189
|
+
const stdDev = Math.sqrt(Math.max(0, q / values.length - mean * mean));
|
|
917
1190
|
if (stdDev === 0) {
|
|
918
|
-
return id.map(() => NaN);
|
|
1191
|
+
return id.map((v) => (Number.isNaN(v) ? NaN : 1));
|
|
919
1192
|
}
|
|
920
|
-
|
|
921
|
-
return id.map(value => (value - mean) / stdDev);
|
|
1193
|
+
return id.map((v) => (Number.isNaN(v) ? NaN : (v - mean) / stdDev));
|
|
922
1194
|
}
|
|
923
1195
|
/**
|
|
924
1196
|
* New Type (User-Defined Type Array)
|
|
@@ -948,26 +1220,4 @@ export function newtype(size = 0, initial_value) {
|
|
|
948
1220
|
// Full UDT support requires implementing the type system
|
|
949
1221
|
return new_array(size, initial_value);
|
|
950
1222
|
}
|
|
951
|
-
/**
|
|
952
|
-
* ⚠️ RENDERING FUNCTIONS NOT IMPLEMENTED
|
|
953
|
-
*
|
|
954
|
-
* The following array constructor functions are intentionally excluded
|
|
955
|
-
* because they create arrays for rendering objects, which are outside
|
|
956
|
-
* the scope of this calculation-focused library:
|
|
957
|
-
*
|
|
958
|
-
* - `new_box()` - Creates arrays of box drawing objects
|
|
959
|
-
* - `new_label()` - Creates arrays of label objects
|
|
960
|
-
* - `new_line()` - Creates arrays of line drawing objects
|
|
961
|
-
* - `new_linefill()` - Creates arrays of line fill objects
|
|
962
|
-
* - `new_table()` - Creates arrays of table layout objects
|
|
963
|
-
*
|
|
964
|
-
* **Design Constraint**: This library focuses on calculation and indicator
|
|
965
|
-
* functions only. Rendering, visualization, and UI functions require a
|
|
966
|
-
* rendering engine and are not included.
|
|
967
|
-
*
|
|
968
|
-
* If you need these functions, consider:
|
|
969
|
-
* 1. Using PineScript directly
|
|
970
|
-
* 2. Implementing a rendering layer separately
|
|
971
|
-
* 3. Using this library for calculations and another library for visualization
|
|
972
|
-
*/
|
|
973
1223
|
//# sourceMappingURL=index.js.map
|