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/array/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Mirrors PineScript's array.* functions
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { PineArray, int, float, bool, simple_int, color, Line, Box, Label, Linefill } from '../types/index.js';
|
|
6
|
+
import type { PineArray, int, float, bool, simple_int, color, Line, Box, Label, Linefill, Table } from '../types/index.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Creates a new array with initial size and default value
|
|
@@ -23,18 +23,49 @@ export function size<T>(id: PineArray<T>): int {
|
|
|
23
23
|
return id.length;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** True for a PineScript `na` element: NaN, null or undefined. */
|
|
27
|
+
function isNa(v: unknown): boolean {
|
|
28
|
+
return v === undefined || v === null || (typeof v === 'number' && Number.isNaN(v));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The `na` of the array's element type: NaN for a numeric (or empty) array, undefined otherwise. */
|
|
32
|
+
function naOf<T>(id: PineArray<T>): T {
|
|
33
|
+
return (id.length === 0 || typeof id[0] === 'number' ? NaN : undefined) as T;
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
/**
|
|
27
|
-
*
|
|
37
|
+
* Position addressed by a PineScript index: truncated, a negative index counts from the end. An index outside
|
|
38
|
+
* `-size .. size - 1 + extra` is a runtime error.
|
|
39
|
+
*/
|
|
40
|
+
function position(name: string, id: PineArray<unknown>, index: number, extra = 0): number {
|
|
41
|
+
const i = Math.trunc(index);
|
|
42
|
+
const n = id.length;
|
|
43
|
+
if (i < -n || i >= n + extra) {
|
|
44
|
+
throw new Error(`array.${name}: index ${i} is out of bounds, array size is ${n}`);
|
|
45
|
+
}
|
|
46
|
+
return i < 0 ? i + n : i;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns the element at the specified index.
|
|
51
|
+
*
|
|
52
|
+
* PineScript rules: a negative index counts from the end (`get(a, -1)` is the last element); an index outside
|
|
53
|
+
* `-size .. size - 1` is a runtime error (throws); an `na` index gives `na`.
|
|
28
54
|
*/
|
|
29
55
|
export function get<T>(id: PineArray<T>, index: simple_int): T {
|
|
30
|
-
return id
|
|
56
|
+
if (Number.isNaN(index)) return naOf(id);
|
|
57
|
+
return id[position('get', id, index)]!;
|
|
31
58
|
}
|
|
32
59
|
|
|
33
60
|
/**
|
|
34
|
-
* Sets the value of the element at the specified index
|
|
61
|
+
* Sets the value of the element at the specified index.
|
|
62
|
+
*
|
|
63
|
+
* PineScript rules: a negative index counts from the end; an index outside `-size .. size - 1` is a runtime error
|
|
64
|
+
* (throws); an `na` index does nothing.
|
|
35
65
|
*/
|
|
36
66
|
export function set<T>(id: PineArray<T>, index: simple_int, value: T): void {
|
|
37
|
-
|
|
67
|
+
if (Number.isNaN(index)) return;
|
|
68
|
+
id[position('set', id, index)] = value;
|
|
38
69
|
}
|
|
39
70
|
|
|
40
71
|
/**
|
|
@@ -73,38 +104,72 @@ export function clear<T>(id: PineArray<T>): void {
|
|
|
73
104
|
}
|
|
74
105
|
|
|
75
106
|
/**
|
|
76
|
-
* Inserts a value at the specified index
|
|
107
|
+
* Inserts a value at the specified index.
|
|
108
|
+
*
|
|
109
|
+
* PineScript rules: a negative index counts from the end and `size` appends, so `-size .. size` is valid; any other
|
|
110
|
+
* index is a runtime error (throws); an `na` index appends.
|
|
77
111
|
*/
|
|
78
112
|
export function insert<T>(id: PineArray<T>, index: simple_int, value: T): void {
|
|
79
|
-
|
|
113
|
+
if (Number.isNaN(index)) {
|
|
114
|
+
id.push(value);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
id.splice(position('insert', id, index, 1), 0, value);
|
|
80
118
|
}
|
|
81
119
|
|
|
82
120
|
/**
|
|
83
|
-
* Removes the element at the specified index
|
|
121
|
+
* Removes the element at the specified index and returns it.
|
|
122
|
+
*
|
|
123
|
+
* PineScript rules: a negative index counts from the end; an index outside `-size .. size - 1` is a runtime error
|
|
124
|
+
* (throws); an `na` index removes nothing and gives `na`.
|
|
84
125
|
*/
|
|
85
126
|
export function remove<T>(id: PineArray<T>, index: simple_int): T {
|
|
86
|
-
|
|
127
|
+
if (Number.isNaN(index)) return naOf(id);
|
|
128
|
+
return id.splice(position('remove', id, index), 1)[0]!;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** PineScript `==` of two elements: numbers are equal when |a - b| <= 1e-10; `na` never equals anything. */
|
|
132
|
+
function pineEqual(a: unknown, b: unknown): boolean {
|
|
133
|
+
if (isNa(a) || isNa(b)) return false;
|
|
134
|
+
if (a === b) return true;
|
|
135
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
136
|
+
const d = a - b;
|
|
137
|
+
return -1e-10 <= d && d <= 1e-10;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
87
140
|
}
|
|
88
141
|
|
|
89
142
|
/**
|
|
90
|
-
* Returns true if the array contains the value
|
|
143
|
+
* Returns true if the array contains the value.
|
|
144
|
+
*
|
|
145
|
+
* PineScript rules: numbers are compared with a 1e-10 tolerance (|a - b| <= 1e-10); `na` is never found.
|
|
91
146
|
*/
|
|
92
147
|
export function includes<T>(id: PineArray<T>, value: T): bool {
|
|
93
|
-
return id
|
|
148
|
+
return indexof(id, value) >= 0;
|
|
94
149
|
}
|
|
95
150
|
|
|
96
151
|
/**
|
|
97
|
-
* Returns the index of the first occurrence of the value
|
|
152
|
+
* Returns the index of the first occurrence of the value, -1 when not found.
|
|
153
|
+
*
|
|
154
|
+
* PineScript rules: numbers are compared with a 1e-10 tolerance (|a - b| <= 1e-10); `na` is never found.
|
|
98
155
|
*/
|
|
99
156
|
export function indexof<T>(id: PineArray<T>, value: T): int {
|
|
100
|
-
|
|
157
|
+
for (let i = 0; i < id.length; i++) {
|
|
158
|
+
if (pineEqual(id[i], value)) return i;
|
|
159
|
+
}
|
|
160
|
+
return -1;
|
|
101
161
|
}
|
|
102
162
|
|
|
103
163
|
/**
|
|
104
|
-
* Returns the index of the last occurrence of the value
|
|
164
|
+
* Returns the index of the last occurrence of the value, -1 when not found.
|
|
165
|
+
*
|
|
166
|
+
* PineScript rules: numbers are compared with a 1e-10 tolerance (|a - b| <= 1e-10); `na` is never found.
|
|
105
167
|
*/
|
|
106
168
|
export function lastindexof<T>(id: PineArray<T>, value: T): int {
|
|
107
|
-
|
|
169
|
+
for (let i = id.length - 1; i >= 0; i--) {
|
|
170
|
+
if (pineEqual(id[i], value)) return i;
|
|
171
|
+
}
|
|
172
|
+
return -1;
|
|
108
173
|
}
|
|
109
174
|
|
|
110
175
|
/**
|
|
@@ -115,10 +180,11 @@ export function copy<T>(id: PineArray<T>): PineArray<T> {
|
|
|
115
180
|
}
|
|
116
181
|
|
|
117
182
|
/**
|
|
118
|
-
*
|
|
183
|
+
* Appends the elements of `id2` to `id1` and returns `id1` (PineScript `array.concat` changes `id1`).
|
|
119
184
|
*/
|
|
120
185
|
export function concat<T>(id1: PineArray<T>, id2: PineArray<T>): PineArray<T> {
|
|
121
|
-
|
|
186
|
+
for (const v of Array.from(id2)) id1.push(v);
|
|
187
|
+
return id1;
|
|
122
188
|
}
|
|
123
189
|
|
|
124
190
|
/**
|
|
@@ -136,30 +202,132 @@ export function reverse<T>(id: PineArray<T>): void {
|
|
|
136
202
|
}
|
|
137
203
|
|
|
138
204
|
/**
|
|
139
|
-
* Returns
|
|
205
|
+
* Returns the elements from `index_from` (included) to `index_to` (excluded, default: the size) as a live view of
|
|
206
|
+
* the array (PineScript `array.slice`).
|
|
207
|
+
*
|
|
208
|
+
* PineScript rules:
|
|
209
|
+
* - the slice shares the elements of the original array: a change of an element of the slice changes the original
|
|
210
|
+
* array, and adding / removing elements of the slice adds / removes them in the original array, inside the slice
|
|
211
|
+
* bounds (a push to the slice inserts at the end of the slice)
|
|
212
|
+
* - `index_from` must address an element (`0 .. size - 1`) and `index_to` may be `size`; other bounds, or
|
|
213
|
+
* `index_from > index_to`, are a runtime error (throws); an `na` bound means the start / the end of the array
|
|
140
214
|
*/
|
|
141
215
|
export function slice<T>(id: PineArray<T>, index_from: simple_int, index_to?: simple_int): PineArray<T> {
|
|
142
|
-
|
|
216
|
+
const n = id.length;
|
|
217
|
+
const start = Number.isNaN(index_from) ? 0 : Math.trunc(index_from);
|
|
218
|
+
const stop = index_to === undefined || Number.isNaN(index_to) ? n : Math.trunc(index_to);
|
|
219
|
+
if (start < 0 || start >= n) throw new Error(`array.slice: index ${start} is out of bounds, array size is ${n}`);
|
|
220
|
+
if (stop < 0 || stop > n) throw new Error(`array.slice: index ${stop} is out of bounds, array size is ${n}`);
|
|
221
|
+
if (start > stop) throw new Error(`array.slice: index_from ${start} is greater than index_to ${stop}`);
|
|
222
|
+
return sliceView(id, start, stop - start);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Index of a property key that is an array index, -1 otherwise. */
|
|
226
|
+
function indexKey(p: string | symbol): number {
|
|
227
|
+
if (typeof p !== 'string') return -1;
|
|
228
|
+
const i = Number(p);
|
|
229
|
+
return Number.isInteger(i) && i >= 0 && String(i) === p ? i : -1;
|
|
143
230
|
}
|
|
144
231
|
|
|
145
232
|
/**
|
|
146
|
-
*
|
|
233
|
+
* Live view of `size` elements of `parent` from `start`: an array (a Proxy) whose element reads and writes go to
|
|
234
|
+
* `parent`, and whose size changes insert / remove elements in `parent` at the end of the view.
|
|
147
235
|
*/
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
236
|
+
function sliceView<T>(parent: PineArray<T>, start: number, size: number): PineArray<T> {
|
|
237
|
+
const resize = (target: number): void => {
|
|
238
|
+
if (target > size) parent.splice(start + size, 0, ...Array<T>(target - size).fill(undefined as T));
|
|
239
|
+
else if (target < size) parent.splice(start + target, size - target);
|
|
240
|
+
size = target;
|
|
241
|
+
};
|
|
242
|
+
return new Proxy<T[]>([], {
|
|
243
|
+
get(target, p, receiver) {
|
|
244
|
+
const i = indexKey(p);
|
|
245
|
+
if (i >= 0) return i < size ? parent[start + i] : undefined;
|
|
246
|
+
if (p === 'length') return size;
|
|
247
|
+
return Reflect.get(target, p, receiver);
|
|
248
|
+
},
|
|
249
|
+
set(target, p, value, receiver) {
|
|
250
|
+
const i = indexKey(p);
|
|
251
|
+
if (i >= 0) {
|
|
252
|
+
if (i >= size) resize(i + 1);
|
|
253
|
+
parent[start + i] = value;
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (p === 'length') {
|
|
257
|
+
resize(Number(value));
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return Reflect.set(target, p, value, receiver);
|
|
261
|
+
},
|
|
262
|
+
has(target, p) {
|
|
263
|
+
const i = indexKey(p);
|
|
264
|
+
return i >= 0 ? i < size : Reflect.has(target, p);
|
|
265
|
+
},
|
|
266
|
+
// Array methods delete the slots past the new end before they set the length, which does the removal.
|
|
267
|
+
deleteProperty(target, p) {
|
|
268
|
+
return indexKey(p) >= 0 ? true : Reflect.deleteProperty(target, p);
|
|
269
|
+
},
|
|
270
|
+
ownKeys(target) {
|
|
271
|
+
return [...Array.from({ length: size }, (_, i) => String(i)), ...Reflect.ownKeys(target)];
|
|
272
|
+
},
|
|
273
|
+
getOwnPropertyDescriptor(target, p) {
|
|
274
|
+
const i = indexKey(p);
|
|
275
|
+
if (i >= 0) {
|
|
276
|
+
return i < size ? { value: parent[start + i], writable: true, enumerable: true, configurable: true } : undefined;
|
|
277
|
+
}
|
|
278
|
+
if (p === 'length') return { value: size, writable: true, enumerable: false, configurable: false };
|
|
279
|
+
return Reflect.getOwnPropertyDescriptor(target, p);
|
|
280
|
+
},
|
|
155
281
|
});
|
|
156
282
|
}
|
|
157
283
|
|
|
284
|
+
/** Sort order of array.sort / array.sort_indices (PineScript `order.ascending` / `order.descending`). */
|
|
285
|
+
export type SortOrder = 'asc' | 'desc' | 'ascending' | 'descending';
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Positions of the elements in PineScript sorted order: ascending values, `na` after the numbers (before the
|
|
289
|
+
* strings of a string array), stable; descending is the ascending result reversed.
|
|
290
|
+
*/
|
|
291
|
+
function sortedPositions<T>(id: PineArray<T>, order: SortOrder): number[] {
|
|
292
|
+
const values: number[] = [];
|
|
293
|
+
const nas: number[] = [];
|
|
294
|
+
for (let i = 0; i < id.length; i++) (isNa(id[i]) ? nas : values).push(i);
|
|
295
|
+
values.sort((a, b) => {
|
|
296
|
+
const x = id[a] as unknown as number;
|
|
297
|
+
const y = id[b] as unknown as number;
|
|
298
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
299
|
+
});
|
|
300
|
+
const naFirst = values.length > 0 && typeof id[values[0]!] === 'string';
|
|
301
|
+
const positions = naFirst ? [...nas, ...values] : [...values, ...nas];
|
|
302
|
+
if (order === 'desc' || order === 'descending') positions.reverse();
|
|
303
|
+
return positions;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Sorts the array in place.
|
|
308
|
+
*
|
|
309
|
+
* PineScript rules: `na` goes after the numbers in ascending order and before them in descending order (the
|
|
310
|
+
* ascending result reversed); in a string array `na` goes first in ascending order.
|
|
311
|
+
*/
|
|
312
|
+
export function sort<T>(id: PineArray<T>, order: SortOrder = 'asc'): void {
|
|
313
|
+
const sorted = sortedPositions(id, order).map((p) => id[p]!);
|
|
314
|
+
for (let i = 0; i < sorted.length; i++) id[i] = sorted[i]!;
|
|
315
|
+
}
|
|
316
|
+
|
|
158
317
|
/**
|
|
159
|
-
*
|
|
318
|
+
* Sum of the non-na elements (PineScript `array.sum(id)`), added from left to right; an array without values gives NaN.
|
|
319
|
+
* As PineScript, the running sum is set to 0 after a step when its absolute value is <= 1e-10.
|
|
160
320
|
*/
|
|
161
321
|
export function sum(id: PineArray<float>): float {
|
|
162
|
-
|
|
322
|
+
let total = 0;
|
|
323
|
+
let count = 0;
|
|
324
|
+
for (const v of id) {
|
|
325
|
+
if (Number.isNaN(v)) continue;
|
|
326
|
+
count++;
|
|
327
|
+
total += v;
|
|
328
|
+
if (Math.abs(total) <= 1e-10) total = 0;
|
|
329
|
+
}
|
|
330
|
+
return count ? total : NaN;
|
|
163
331
|
}
|
|
164
332
|
|
|
165
333
|
/**
|
|
@@ -170,79 +338,115 @@ export function avg(id: PineArray<float>): float {
|
|
|
170
338
|
return values.length ? values.reduce((a, b) => a + b, 0) / values.length : NaN;
|
|
171
339
|
}
|
|
172
340
|
|
|
341
|
+
/** The `nth` value of the non-na elements sorted with `compare` (array.min / array.max). */
|
|
342
|
+
function nthValue(name: string, id: PineArray<float>, nth: number, compare: (a: number, b: number) => number): float {
|
|
343
|
+
const rank = Number.isNaN(nth) ? 0 : Math.trunc(nth);
|
|
344
|
+
const values = id.filter((v) => !Number.isNaN(v));
|
|
345
|
+
if (!values.length) return NaN;
|
|
346
|
+
if (rank < 0 || rank >= id.length) {
|
|
347
|
+
throw new Error(`array.${name}: nth ${rank} is out of bounds, array size is ${id.length}`);
|
|
348
|
+
}
|
|
349
|
+
values.sort(compare);
|
|
350
|
+
return values[Math.min(rank, values.length - 1)]!;
|
|
351
|
+
}
|
|
352
|
+
|
|
173
353
|
/**
|
|
174
354
|
* The `nth` smallest value (0 = the smallest) of the non-na elements (PineScript `array.min(id, nth)`).
|
|
175
|
-
*
|
|
355
|
+
*
|
|
356
|
+
* PineScript rules: an `nth` past the non-na values but inside the array size gives the greatest value; an `nth`
|
|
357
|
+
* outside `0 .. size - 1` is a runtime error (throws); an `na` nth counts as 0; an array without values gives NaN.
|
|
176
358
|
*/
|
|
177
359
|
export function min(id: PineArray<float>, nth: simple_int = 0): float {
|
|
178
|
-
|
|
179
|
-
return values.length ? values[Math.min(nth, values.length - 1)]! : NaN;
|
|
360
|
+
return nthValue('min', id, nth, (a, b) => a - b);
|
|
180
361
|
}
|
|
181
362
|
|
|
182
363
|
/**
|
|
183
364
|
* The `nth` greatest value (0 = the greatest) of the non-na elements (PineScript `array.max(id, nth)`).
|
|
184
|
-
*
|
|
365
|
+
*
|
|
366
|
+
* PineScript rules: an `nth` past the non-na values but inside the array size gives the smallest value; an `nth`
|
|
367
|
+
* outside `0 .. size - 1` is a runtime error (throws); an `na` nth counts as 0; an array without values gives NaN.
|
|
185
368
|
*/
|
|
186
369
|
export function max(id: PineArray<float>, nth: simple_int = 0): float {
|
|
187
|
-
|
|
188
|
-
return values.length ? values[Math.min(nth, values.length - 1)]! : NaN;
|
|
370
|
+
return nthValue('max', id, nth, (a, b) => b - a);
|
|
189
371
|
}
|
|
190
372
|
|
|
191
373
|
/**
|
|
192
|
-
*
|
|
374
|
+
* Median of the non-na elements (PineScript `array.median(id)`): the middle value, or the mean of the two middle
|
|
375
|
+
* values for an even count; an array without values gives NaN.
|
|
193
376
|
*/
|
|
194
377
|
export function median(id: PineArray<float>): float {
|
|
195
|
-
const sorted =
|
|
378
|
+
const sorted = id.filter((v) => !Number.isNaN(v)).sort((a, b) => a - b);
|
|
379
|
+
if (!sorted.length) return NaN;
|
|
196
380
|
const mid = Math.floor(sorted.length / 2);
|
|
197
381
|
return sorted.length % 2 === 0 ? (sorted[mid - 1]! + sorted[mid]!) / 2 : sorted[mid]!;
|
|
198
382
|
}
|
|
199
383
|
|
|
200
384
|
/**
|
|
201
|
-
*
|
|
385
|
+
* Most frequent non-na element (PineScript `array.mode(id)`), compared exactly.
|
|
386
|
+
*
|
|
387
|
+
* PineScript rules: with several values of the same frequency, the smallest one; an array without values gives NaN.
|
|
202
388
|
*/
|
|
203
389
|
export function mode(id: PineArray<float>): float {
|
|
204
|
-
const frequency
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
|
|
390
|
+
const frequency = new Map<float, int>();
|
|
391
|
+
for (const v of id) {
|
|
392
|
+
if (!Number.isNaN(v)) frequency.set(v, (frequency.get(v) ?? 0) + 1);
|
|
393
|
+
}
|
|
394
|
+
let best = NaN;
|
|
395
|
+
let bestCount = 0;
|
|
396
|
+
for (const [v, count] of frequency) {
|
|
397
|
+
if (count > bestCount || (count === bestCount && v < best)) {
|
|
398
|
+
best = v;
|
|
399
|
+
bestCount = count;
|
|
214
400
|
}
|
|
215
401
|
}
|
|
216
|
-
|
|
217
|
-
return mode;
|
|
402
|
+
return best;
|
|
218
403
|
}
|
|
219
404
|
|
|
220
405
|
/**
|
|
221
|
-
* Standard deviation of the non-na elements (PineScript `array.stdev(id, biased)`)
|
|
222
|
-
* `
|
|
406
|
+
* Standard deviation of the non-na elements (PineScript `array.stdev(id, biased)`): the square root of
|
|
407
|
+
* `array.variance(id, biased)`.
|
|
223
408
|
*/
|
|
224
409
|
export function stdev(id: PineArray<float>, biased: bool = true): float {
|
|
225
410
|
return Math.sqrt(variance(id, biased));
|
|
226
411
|
}
|
|
227
412
|
|
|
413
|
+
/** Sum of the values and sum of their squares, both added from the first to the last value. */
|
|
414
|
+
function moments(values: number[]): [number, number] {
|
|
415
|
+
let p = 0;
|
|
416
|
+
let q = 0;
|
|
417
|
+
for (const v of values) p += v;
|
|
418
|
+
for (const v of values) q += v * v;
|
|
419
|
+
return [p, q];
|
|
420
|
+
}
|
|
421
|
+
|
|
228
422
|
/**
|
|
229
423
|
* Variance of the non-na elements (PineScript `array.variance(id, biased)`).
|
|
230
|
-
*
|
|
424
|
+
*
|
|
425
|
+
* PineScript rule (moment form): with `p` the sum of the values, `q` the sum of their squares, `n` the count and
|
|
426
|
+
* `m = p / n`: biased (default) `max(0, q / n - m * m)`, unbiased `max(0, q / (n - 1) - (p / (n - 1)) * m)`.
|
|
427
|
+
* One value gives 0 biased and NaN unbiased; an array without values gives NaN.
|
|
231
428
|
*/
|
|
232
429
|
export function variance(id: PineArray<float>, biased: bool = true): float {
|
|
233
430
|
const values = id.filter((v) => !Number.isNaN(v));
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
431
|
+
const n = values.length;
|
|
432
|
+
if (!n) return NaN;
|
|
433
|
+
if (n < 2) return biased ? 0 : NaN;
|
|
434
|
+
const [p, q] = moments(values);
|
|
435
|
+
const mean = p / n;
|
|
436
|
+
return biased ? Math.max(0, q / n - mean * mean) : Math.max(0, q / (n - 1) - (p / (n - 1)) * mean);
|
|
238
437
|
}
|
|
239
438
|
|
|
240
439
|
/**
|
|
241
440
|
* Fills the array with the specified value
|
|
242
441
|
*/
|
|
243
442
|
export function fill<T>(id: PineArray<T>, value: T, index_from: simple_int = 0, index_to?: simple_int): void {
|
|
244
|
-
|
|
245
|
-
|
|
443
|
+
// PineScript: an na bound is the start / the end; a bound past the end is a runtime error
|
|
444
|
+
const start = index_from === undefined || index_from === null || Number.isNaN(index_from) ? 0 : index_from;
|
|
445
|
+
const end = index_to === undefined || index_to === null || Number.isNaN(index_to) ? id.length : index_to;
|
|
446
|
+
for (const i of [start, end]) {
|
|
447
|
+
if (i < 0 || i > id.length) throw new Error(`array.fill: index ${i} is out of bounds, array size is ${id.length}`);
|
|
448
|
+
}
|
|
449
|
+
for (let i = start; i < end; i++) {
|
|
246
450
|
id[i] = value;
|
|
247
451
|
}
|
|
248
452
|
}
|
|
@@ -277,6 +481,8 @@ export function from<T>(...values: T[]): PineArray<T> {
|
|
|
277
481
|
* ```
|
|
278
482
|
*/
|
|
279
483
|
export function first<T>(id: PineArray<T>): T {
|
|
484
|
+
// PineScript: a runtime error on an empty array
|
|
485
|
+
if (id.length === 0) throw new Error('Cannot call array.first() if array is empty');
|
|
280
486
|
return id[0]!;
|
|
281
487
|
}
|
|
282
488
|
|
|
@@ -297,6 +503,8 @@ export function first<T>(id: PineArray<T>): T {
|
|
|
297
503
|
* ```
|
|
298
504
|
*/
|
|
299
505
|
export function last<T>(id: PineArray<T>): T {
|
|
506
|
+
// PineScript: a runtime error on an empty array (as array.first)
|
|
507
|
+
if (id.length === 0) throw new Error('Cannot call array.last() if array is empty');
|
|
300
508
|
return id[id.length - 1]!;
|
|
301
509
|
}
|
|
302
510
|
|
|
@@ -545,6 +753,17 @@ export function new_linefill(size: simple_int = 0, initial_value?: Linefill): Pi
|
|
|
545
753
|
return new_array<Linefill>(size, initial_value);
|
|
546
754
|
}
|
|
547
755
|
|
|
756
|
+
/**
|
|
757
|
+
* Creates a new array of tables (PineScript `array.new_table`).
|
|
758
|
+
*
|
|
759
|
+
* @param size - Initial size of array (default: 0)
|
|
760
|
+
* @param initial_value - Initial value for all elements (default: undefined)
|
|
761
|
+
* @returns New table array
|
|
762
|
+
*/
|
|
763
|
+
export function new_table(size: simple_int = 0, initial_value?: Table): PineArray<Table> {
|
|
764
|
+
return new_array<Table>(size, initial_value);
|
|
765
|
+
}
|
|
766
|
+
|
|
548
767
|
/**
|
|
549
768
|
* Absolute value of array elements
|
|
550
769
|
*
|
|
@@ -752,28 +971,36 @@ export function binary_search_rightmost(id: PineArray<float>, val: float): int {
|
|
|
752
971
|
* - Unbiased (false): divides by n-1 (sample covariance)
|
|
753
972
|
* - Returns NaN if arrays are empty or have different lengths
|
|
754
973
|
* - Formula: Cov(X,Y) = E[(X - μX)(Y - μY)]
|
|
974
|
+
*
|
|
975
|
+
* PineScript rules: a pair where either value is `na` is skipped and the count is the number of pairs kept; both
|
|
976
|
+
* means first, then the sum of the products from the first to the last pair. One pair gives 0 biased, NaN unbiased.
|
|
755
977
|
*/
|
|
756
978
|
export function covariance(id1: PineArray<float>, id2: PineArray<float>, biased: bool = true): float {
|
|
757
|
-
if (id1.length === 0 || id2.length === 0) {
|
|
758
|
-
return NaN;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
979
|
if (id1.length !== id2.length) {
|
|
762
980
|
return NaN;
|
|
763
981
|
}
|
|
764
|
-
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
982
|
+
const xs: number[] = [];
|
|
983
|
+
const ys: number[] = [];
|
|
984
|
+
for (let i = 0; i < id1.length; i++) {
|
|
985
|
+
if (Number.isNaN(id1[i]!) || Number.isNaN(id2[i]!)) continue;
|
|
986
|
+
xs.push(id1[i]!);
|
|
987
|
+
ys.push(id2[i]!);
|
|
988
|
+
}
|
|
989
|
+
const n = xs.length;
|
|
990
|
+
if (!n || (!biased && n < 2)) return NaN;
|
|
991
|
+
let mean1 = 0;
|
|
992
|
+
let mean2 = 0;
|
|
993
|
+
for (let i = 0; i < n; i++) {
|
|
994
|
+
mean1 += xs[i]!;
|
|
995
|
+
mean2 += ys[i]!;
|
|
996
|
+
}
|
|
997
|
+
mean1 /= n;
|
|
998
|
+
mean2 /= n;
|
|
769
999
|
let sum = 0;
|
|
770
1000
|
for (let i = 0; i < n; i++) {
|
|
771
|
-
sum += (
|
|
1001
|
+
sum += (xs[i]! - mean1) * (ys[i]! - mean2);
|
|
772
1002
|
}
|
|
773
|
-
|
|
774
|
-
// Biased: divide by n, Unbiased: divide by n-1
|
|
775
|
-
const divisor = biased ? n : n - 1;
|
|
776
|
-
return sum / divisor;
|
|
1003
|
+
return sum / (biased ? n : n - 1);
|
|
777
1004
|
}
|
|
778
1005
|
|
|
779
1006
|
/**
|
|
@@ -799,23 +1026,28 @@ export function covariance(id1: PineArray<float>, id2: PineArray<float>, biased:
|
|
|
799
1026
|
* - Returns NaN if array is empty
|
|
800
1027
|
*
|
|
801
1028
|
* PineScript rules:
|
|
802
|
-
* - position in the sorted array: `percentage / 100
|
|
803
|
-
*
|
|
1029
|
+
* - 1-based position in the sorted array: `size * percentage / 100 + 0.5` (snapped to an integer within 1e-9),
|
|
1030
|
+
* clamped to the first / last value; between two values `lo * (1 - frac) + hi * frac`
|
|
804
1031
|
* - `na` values are sorted after all numbers; with an `na` in the array, a position between two values gives `na`
|
|
1032
|
+
* - an `na` percentage gives `na`; a percentage outside 0 .. 100 is a runtime error (throws)
|
|
805
1033
|
*/
|
|
806
1034
|
export function percentile_linear_interpolation(id: PineArray<float>, percentage: float): float {
|
|
807
|
-
if (id.length === 0) {
|
|
1035
|
+
if (id.length === 0 || Number.isNaN(percentage)) {
|
|
808
1036
|
return NaN;
|
|
809
1037
|
}
|
|
1038
|
+
checkPercentage('percentile_linear_interpolation', percentage);
|
|
810
1039
|
const s = sortedNaLast(id);
|
|
811
1040
|
const n = s.length;
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
if (pos
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1041
|
+
let pos = (n * percentage) / 100 + 0.5;
|
|
1042
|
+
const nearest = Math.round(pos);
|
|
1043
|
+
if (Math.abs(pos - nearest) < 1e-9) pos = nearest;
|
|
1044
|
+
if (pos <= 1) return s[0]!;
|
|
1045
|
+
if (pos >= n) return s[n - 1]!;
|
|
1046
|
+
const lower = Math.floor(pos);
|
|
1047
|
+
const frac = pos - lower;
|
|
1048
|
+
if (frac === 0) return s[lower - 1]!;
|
|
1049
|
+
if (Number.isNaN(s[n - 1]!)) return NaN;
|
|
1050
|
+
return s[lower - 1]! * (1 - frac) + s[lower]! * frac;
|
|
819
1051
|
}
|
|
820
1052
|
|
|
821
1053
|
/** Array values sorted ascending, `na` values after the numbers (array.percentile_*). */
|
|
@@ -824,6 +1056,13 @@ function sortedNaLast(id: PineArray<float>): number[] {
|
|
|
824
1056
|
return [...numbers, ...Array<number>(id.length - numbers.length).fill(NaN)];
|
|
825
1057
|
}
|
|
826
1058
|
|
|
1059
|
+
/** A percentage outside 0 .. 100 is a PineScript runtime error. */
|
|
1060
|
+
function checkPercentage(name: string, percentage: number): void {
|
|
1061
|
+
if (!(percentage >= 0 && percentage <= 100)) {
|
|
1062
|
+
throw new Error(`array.${name}: percentage ${percentage} must be between 0 and 100`);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
827
1066
|
/**
|
|
828
1067
|
* Percentile Nearest Rank
|
|
829
1068
|
*
|
|
@@ -847,15 +1086,18 @@ function sortedNaLast(id: PineArray<float>): number[] {
|
|
|
847
1086
|
* - Returns NaN if array is empty
|
|
848
1087
|
*
|
|
849
1088
|
* PineScript rules:
|
|
850
|
-
* - the value at rank `ceil(percentage / 100
|
|
1089
|
+
* - the value at rank `ceil(percentage * size / 100)` (clamped to 1 .. size) of the sorted array
|
|
851
1090
|
* - `na` values are sorted after all numbers (na when that rank holds an `na`)
|
|
1091
|
+
* - an `na` percentage counts as 0; a percentage outside 0 .. 100 is a runtime error (throws)
|
|
852
1092
|
*/
|
|
853
1093
|
export function percentile_nearest_rank(id: PineArray<float>, percentage: float): float {
|
|
854
1094
|
if (id.length === 0) {
|
|
855
1095
|
return NaN;
|
|
856
1096
|
}
|
|
1097
|
+
const pct = Number.isNaN(percentage) ? 0 : percentage;
|
|
1098
|
+
checkPercentage('percentile_nearest_rank', pct);
|
|
857
1099
|
const s = sortedNaLast(id);
|
|
858
|
-
const rank = Math.min(s.length, Math.max(1, Math.ceil((
|
|
1100
|
+
const rank = Math.min(s.length, Math.max(1, Math.ceil((pct * s.length) / 100)));
|
|
859
1101
|
return s[rank - 1]!;
|
|
860
1102
|
}
|
|
861
1103
|
|
|
@@ -874,35 +1116,38 @@ export function percentile_nearest_rank(id: PineArray<float>, percentage: float)
|
|
|
874
1116
|
* ```typescript
|
|
875
1117
|
* const arr = [1, 2, 3, 4, 5];
|
|
876
1118
|
* const rank = array.percentrank(arr, 2); // Value at index 2 is 3
|
|
877
|
-
* // Returns:
|
|
1119
|
+
* // Returns: 50 ((3 - 1) * 100 / (5 - 1))
|
|
878
1120
|
* ```
|
|
879
1121
|
*
|
|
880
1122
|
* @remarks
|
|
881
|
-
* - Returns percentage of elements <= the value at given index
|
|
882
1123
|
* - Range: 0 to 100
|
|
883
|
-
*
|
|
1124
|
+
*
|
|
1125
|
+
* PineScript rules:
|
|
1126
|
+
* - `(count - 1) * 100 / (size - 1)`, with `count` the number of non-na elements <= the element (exact
|
|
1127
|
+
* comparison, no 1e-10 tolerance); `na` elements count in the size
|
|
1128
|
+
* - an `na` element, a one-element array or an empty array gives NaN
|
|
1129
|
+
* - an `na` index counts as 0; an index outside `0 .. size - 1` is a runtime error (throws)
|
|
884
1130
|
*/
|
|
885
1131
|
export function percentrank(id: PineArray<float>, index: int): float {
|
|
886
|
-
|
|
1132
|
+
const i = Number.isNaN(index) ? 0 : Math.trunc(index);
|
|
1133
|
+
if (id.length === 0) {
|
|
887
1134
|
return NaN;
|
|
888
1135
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
if (
|
|
1136
|
+
if (i < 0 || i >= id.length) {
|
|
1137
|
+
throw new Error(`array.percentrank: index ${i} is out of bounds, array size is ${id.length}`);
|
|
1138
|
+
}
|
|
1139
|
+
if (id.length === 1) {
|
|
1140
|
+
return NaN;
|
|
1141
|
+
}
|
|
1142
|
+
const value = id[i]!;
|
|
1143
|
+
if (Number.isNaN(value)) {
|
|
893
1144
|
return NaN;
|
|
894
1145
|
}
|
|
895
|
-
|
|
896
|
-
// Count elements less than or equal to the value
|
|
897
1146
|
let count = 0;
|
|
898
|
-
for (
|
|
899
|
-
if (
|
|
900
|
-
count++;
|
|
901
|
-
}
|
|
1147
|
+
for (const v of id) {
|
|
1148
|
+
if (v <= value) count++;
|
|
902
1149
|
}
|
|
903
|
-
|
|
904
|
-
// Calculate percentage
|
|
905
|
-
return (count / id.length) * 100;
|
|
1150
|
+
return ((count - 1) * 100) / (id.length - 1);
|
|
906
1151
|
}
|
|
907
1152
|
|
|
908
1153
|
/**
|
|
@@ -930,21 +1175,11 @@ export function percentrank(id: PineArray<float>, index: int): float {
|
|
|
930
1175
|
* - Original array is not modified
|
|
931
1176
|
* - Returns indices that would sort the array
|
|
932
1177
|
* - Useful for maintaining correspondence with other arrays
|
|
1178
|
+
* - PineScript rule: the indices of `na` elements go where `array.sort` puts them (after the numbers in ascending
|
|
1179
|
+
* order, in their original order: `sort_indices([na, na, 5, 1])` is `[3, 2, 0, 1]`)
|
|
933
1180
|
*/
|
|
934
|
-
export function sort_indices(id: PineArray<
|
|
935
|
-
|
|
936
|
-
const indices: PineArray<int> = Array.from({ length: id.length }, (_, i) => i) as PineArray<int>;
|
|
937
|
-
|
|
938
|
-
// Sort indices based on values in original array
|
|
939
|
-
indices.sort((a, b) => {
|
|
940
|
-
if (order === 'asc') {
|
|
941
|
-
return id[a]! - id[b]!;
|
|
942
|
-
} else {
|
|
943
|
-
return id[b]! - id[a]!;
|
|
944
|
-
}
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
return indices;
|
|
1181
|
+
export function sort_indices<T>(id: PineArray<T>, order: SortOrder = 'asc'): PineArray<int> {
|
|
1182
|
+
return sortedPositions(id, order);
|
|
948
1183
|
}
|
|
949
1184
|
|
|
950
1185
|
/**
|
|
@@ -967,24 +1202,25 @@ export function sort_indices(id: PineArray<float>, order: 'asc' | 'desc' = 'asc'
|
|
|
967
1202
|
* @remarks
|
|
968
1203
|
* - Formula: z = (x - μ) / σ
|
|
969
1204
|
* - Result has mean of 0 and standard deviation of 1
|
|
970
|
-
* - Returns array of NaN if standard deviation is 0
|
|
971
1205
|
* - Useful for comparing values on different scales
|
|
1206
|
+
*
|
|
1207
|
+
* PineScript rules:
|
|
1208
|
+
* - μ and σ are the population mean and standard deviation of the non-na elements, as `array.variance` (moment
|
|
1209
|
+
* form); `na` elements stay `na`
|
|
1210
|
+
* - when σ is 0 (all values equal) every non-na element gives 1
|
|
972
1211
|
*/
|
|
973
1212
|
export function standardize(id: PineArray<float>): PineArray<float> {
|
|
974
|
-
|
|
975
|
-
|
|
1213
|
+
const values = id.filter((v) => !Number.isNaN(v));
|
|
1214
|
+
if (!values.length) {
|
|
1215
|
+
return id.map(() => NaN);
|
|
976
1216
|
}
|
|
977
|
-
|
|
978
|
-
const mean =
|
|
979
|
-
const stdDev =
|
|
980
|
-
|
|
981
|
-
// If stddev is 0, all values are the same - return array of 0s or NaN
|
|
1217
|
+
const [p, q] = moments(values);
|
|
1218
|
+
const mean = p / values.length;
|
|
1219
|
+
const stdDev = Math.sqrt(Math.max(0, q / values.length - mean * mean));
|
|
982
1220
|
if (stdDev === 0) {
|
|
983
|
-
return id.map(() =>
|
|
1221
|
+
return id.map((v) => (Number.isNaN(v) ? NaN : 1));
|
|
984
1222
|
}
|
|
985
|
-
|
|
986
|
-
// Calculate z-score for each element
|
|
987
|
-
return id.map(value => (value - mean) / stdDev) as PineArray<float>;
|
|
1223
|
+
return id.map((v) => (Number.isNaN(v) ? NaN : (v - mean) / stdDev));
|
|
988
1224
|
}
|
|
989
1225
|
|
|
990
1226
|
/**
|
|
@@ -1016,25 +1252,3 @@ export function newtype<T>(size: simple_int = 0, initial_value?: T): PineArray<T
|
|
|
1016
1252
|
return new_array<T>(size, initial_value);
|
|
1017
1253
|
}
|
|
1018
1254
|
|
|
1019
|
-
/**
|
|
1020
|
-
* ⚠️ RENDERING FUNCTIONS NOT IMPLEMENTED
|
|
1021
|
-
*
|
|
1022
|
-
* The following array constructor functions are intentionally excluded
|
|
1023
|
-
* because they create arrays for rendering objects, which are outside
|
|
1024
|
-
* the scope of this calculation-focused library:
|
|
1025
|
-
*
|
|
1026
|
-
* - `new_box()` - Creates arrays of box drawing objects
|
|
1027
|
-
* - `new_label()` - Creates arrays of label objects
|
|
1028
|
-
* - `new_line()` - Creates arrays of line drawing objects
|
|
1029
|
-
* - `new_linefill()` - Creates arrays of line fill objects
|
|
1030
|
-
* - `new_table()` - Creates arrays of table layout objects
|
|
1031
|
-
*
|
|
1032
|
-
* **Design Constraint**: This library focuses on calculation and indicator
|
|
1033
|
-
* functions only. Rendering, visualization, and UI functions require a
|
|
1034
|
-
* rendering engine and are not included.
|
|
1035
|
-
*
|
|
1036
|
-
* If you need these functions, consider:
|
|
1037
|
-
* 1. Using PineScript directly
|
|
1038
|
-
* 2. Implementing a rendering layer separately
|
|
1039
|
-
* 3. Using this library for calculations and another library for visualization
|
|
1040
|
-
*/
|