oakscriptjs 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/array/index.d.ts +84 -27
- package/dist/cjs/array/index.d.ts.map +1 -1
- package/dist/cjs/array/index.js +235 -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/index.d.ts +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- 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/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/script/index.d.ts +15 -31
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +32 -0
- package/dist/cjs/script/index.js.map +2 -2
- 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/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/esm/array/index.d.ts +84 -27
- package/dist/esm/array/index.d.ts.map +1 -1
- package/dist/esm/array/index.js +373 -111
- 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/index.d.ts +1 -1
- package/dist/esm/index.js +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/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/script/index.d.ts +15 -31
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +44 -1
- 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/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/package.json +1 -1
- package/src/array/index.ts +352 -127
- package/src/callsite/index.ts +147 -3
- package/src/color/index.ts +15 -5
- package/src/index.ts +1 -1
- 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/runtime/series.ts +22 -1
- package/src/script/index.ts +53 -1
- 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/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/math/index.ts
CHANGED
|
@@ -8,16 +8,17 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Known difference: `log10`, `exp`, `log`, `pow`, `sin`, `cos`, `tan` use JavaScript's `Math`. PineScript uses
|
|
10
10
|
* other algorithms, whose results differ by 1 or 2 units in the last place on part of the inputs (`log10` about 4 %,
|
|
11
|
-
* `exp` about 9 %, `sin` / `cos` / `tan` about 3 %, `log` 0.4 %, `pow` 0.05 % of random inputs).
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* `exp` about 9 %, `sin` / `cos` / `tan` about 3 %, `log` 0.4 %, `pow` 0.05 % of random inputs). This is visible
|
|
12
|
+
* only when a script amplifies these tiny differences (e.g. `ma > ma[1]` on values of 1e195); `sqrt`, `atan`,
|
|
13
|
+
* `asin`, `acos` are equal. A constant argument (`math.log10(2)`) is computed before the script runs, with other
|
|
14
|
+
* rules: see `constant`.
|
|
14
15
|
*
|
|
15
16
|
* @version 6
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import type { float, int, simple_int, series_float } from '../types/index.js';
|
|
19
20
|
import { Series } from '../runtime/series.js';
|
|
20
|
-
import { runningSum } from '../ta/running-sum.js';
|
|
21
|
+
import { runningSum, runningSumSeries } from '../ta/running-sum.js';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Returns the absolute value of a number.
|
|
@@ -147,16 +148,23 @@ export function floor(value: float | Series): int | Series {
|
|
|
147
148
|
* @remarks
|
|
148
149
|
* - Rounds to nearest integer by default
|
|
149
150
|
* - With precision, rounds to that many decimal places
|
|
150
|
-
* - Uses "round half up" strategy (0.5 rounds to 1)
|
|
151
151
|
* - If value is a Series, returns a Series; if value is a number, returns a number
|
|
152
152
|
*
|
|
153
|
+
* PineScript rules:
|
|
154
|
+
* - ties go away from zero (2.5 -> 3, -2.5 -> -3, -0.5 -> -1)
|
|
155
|
+
* - without a precision, or with a precision <= 0, the result is the integer rounding (1234.5678 @ -2 -> 1235)
|
|
156
|
+
* - a positive precision rounds only the fraction: `int + round(frac * scale) / scale` with
|
|
157
|
+
* `scale = 1 / 10^-precision` (precision above 16 acts as 16); the tie is decided on the exact value of
|
|
158
|
+
* `frac * scale` with a 1e-10 tolerance (1.005 @ 2 -> 1.01, 0.285 @ 2 -> 0.29, -1.45 @ 1 -> -1.5)
|
|
159
|
+
* - an infinite value gives na
|
|
160
|
+
*
|
|
153
161
|
* @example
|
|
154
162
|
* ```typescript
|
|
155
163
|
* math.round(4.2) // Returns: 4
|
|
156
164
|
* math.round(4.5) // Returns: 5
|
|
157
|
-
* math.round(
|
|
165
|
+
* math.round(-2.5) // Returns: -3
|
|
158
166
|
* math.round(4.567, 2) // Returns: 4.57
|
|
159
|
-
* math.round(
|
|
167
|
+
* math.round(1.005, 2) // Returns: 1.01
|
|
160
168
|
* ```
|
|
161
169
|
*/
|
|
162
170
|
export function round(value: Series, precision?: int): Series;
|
|
@@ -165,30 +173,118 @@ export function round(value: float | Series, precision?: int): float | Series {
|
|
|
165
173
|
if (value instanceof Series) {
|
|
166
174
|
const bars = value.bars;
|
|
167
175
|
const valueArray = value.toArray();
|
|
168
|
-
const length = bars.length;
|
|
169
|
-
|
|
170
176
|
const result: number[] = [];
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const v = valueArray[i] ?? NaN;
|
|
174
|
-
result.push(Math.round(v));
|
|
175
|
-
}
|
|
176
|
-
} else {
|
|
177
|
-
const multiplier = Math.pow(10, precision);
|
|
178
|
-
for (let i = 0; i < length; i++) {
|
|
179
|
-
const v = valueArray[i] ?? NaN;
|
|
180
|
-
result.push(Math.round(v * multiplier) / multiplier);
|
|
181
|
-
}
|
|
177
|
+
for (let i = 0; i < bars.length; i++) {
|
|
178
|
+
result.push(roundValue(valueArray[i] ?? NaN, precision));
|
|
182
179
|
}
|
|
183
|
-
|
|
184
180
|
return Series.fromArray(bars, result);
|
|
185
181
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
return roundValue(value, precision);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** `0.5 - 1e-10` as the exact fraction TIE_HALF / TIE_SCALE: the tie threshold of round / round_to_mintick. */
|
|
186
|
+
const TIE_SCALE = 10n ** 10n;
|
|
187
|
+
const TIE_HALF = 5n * 10n ** 9n - 1n;
|
|
188
|
+
/** From 2^52 up every double is an integer. */
|
|
189
|
+
const INT_LIMIT = 2 ** 52;
|
|
190
|
+
|
|
191
|
+
/** Exact value of a finite double >= 0 as `[numerator, denominator]`. */
|
|
192
|
+
function exactRatio(x: number): [bigint, bigint] {
|
|
193
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
194
|
+
view.setFloat64(0, x);
|
|
195
|
+
const hi = view.getUint32(0);
|
|
196
|
+
const lo = view.getUint32(4);
|
|
197
|
+
const expField = (hi >>> 20) & 0x7ff;
|
|
198
|
+
let mantissa = (BigInt(hi & 0xfffff) << 32n) | BigInt(lo);
|
|
199
|
+
let exp = -1074;
|
|
200
|
+
if (expField !== 0) {
|
|
201
|
+
mantissa |= 1n << 52n;
|
|
202
|
+
exp = expField - 1075;
|
|
203
|
+
}
|
|
204
|
+
return exp >= 0 ? [mantissa << BigInt(exp), 1n] : [mantissa, 1n << BigInt(-exp)];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** True when `num / den - units` (exact) is >= 0.5 - 1e-10. */
|
|
208
|
+
function reachesTie(num: bigint, den: bigint, units: bigint): boolean {
|
|
209
|
+
return (num - units * den) * TIE_SCALE >= TIE_HALF * den;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** `10^-p`: the exact literal for an integer p (correctly rounded), Math.pow otherwise. */
|
|
213
|
+
function tenToMinus(p: number): number {
|
|
214
|
+
return Number.isInteger(p) ? Number(`1e-${p}`) : Math.pow(10, -p);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** PineScript math.round of one value (see `round`). */
|
|
218
|
+
function roundValue(value: number, precision?: number): number {
|
|
219
|
+
if (!Number.isFinite(value)) return NaN;
|
|
220
|
+
const negative = value < 0;
|
|
221
|
+
const magnitude = negative ? -value : value;
|
|
222
|
+
if (precision === undefined || Number.isNaN(precision) || precision <= 0) {
|
|
223
|
+
if (magnitude >= INT_LIMIT) return value;
|
|
224
|
+
let units = Math.floor(magnitude);
|
|
225
|
+
if (magnitude - units >= 0.5) units++;
|
|
226
|
+
return negative ? 0 - units : units;
|
|
227
|
+
}
|
|
228
|
+
const scale = 1 / tenToMinus(Math.min(precision, 16));
|
|
229
|
+
const fraction = magnitude % 1;
|
|
230
|
+
const scaled = fraction * scale;
|
|
231
|
+
let units = Math.floor(scaled);
|
|
232
|
+
const offset = scaled - units - 0.5;
|
|
233
|
+
// Only a near-tie needs the exact product; the window covers the double's own error.
|
|
234
|
+
const slack = 1e-9 + scaled * 1.8e-15;
|
|
235
|
+
if (-slack < offset && offset < slack) {
|
|
236
|
+
const [fn, fd] = exactRatio(fraction);
|
|
237
|
+
const [sn, sd] = exactRatio(scale);
|
|
238
|
+
if (reachesTie(fn * sn, fd * sd, BigInt(units))) units++;
|
|
239
|
+
} else if (offset > 0) {
|
|
240
|
+
units++;
|
|
189
241
|
}
|
|
190
|
-
const
|
|
191
|
-
return
|
|
242
|
+
const result = magnitude - fraction + units / scale;
|
|
243
|
+
return negative ? 0 - result : result;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** 10^16: a constant keeps 16 decimals. */
|
|
247
|
+
const CONSTANT_SCALE = 10n ** 16n;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Returns the value PineScript gives to a constant float expression (not a PineScript function).
|
|
251
|
+
*
|
|
252
|
+
* @param value - The result of the constant expression, computed in JavaScript (e.g. `1 / 255`)
|
|
253
|
+
* @returns The value of the same expression in a PineScript script
|
|
254
|
+
*
|
|
255
|
+
* @remarks
|
|
256
|
+
* PineScript computes a constant expression (only literals and constants: `1 / 255`, `0.1 * 3`,
|
|
257
|
+
* `math.sqrt(0.1)`) before the script runs, and the value is then rounded. JavaScript cannot tell a constant from a
|
|
258
|
+
* series value, so wrap the constant expressions of a script with this function.
|
|
259
|
+
* - |value| >= 0.001: the exact value of the double is rounded to 16 decimals, ties to even
|
|
260
|
+
* (`1 / 255` -> 0.003921568627451, `1 / 7` -> 0.1428571428571428)
|
|
261
|
+
* - |value| < 0.001, na and infinities: unchanged
|
|
262
|
+
* - the whole expression is computed first, then rounded once (`(1 / 7) * 7` is 1)
|
|
263
|
+
* - math functions of constants use JavaScript's `Math` there: `math.constant(math.sqrt(0.1))` is the PineScript
|
|
264
|
+
* value; `math.log10` of a constant is `math.log(x) / math.log(10)` there:
|
|
265
|
+
* `math.constant(math.log(2) / math.log(10))` -> 0.3010299956639811
|
|
266
|
+
* - the same expression with a series operand (`close / 255`) is not rounded: do not wrap it
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```typescript
|
|
270
|
+
* // Pine: var const float i_255 = 1 / 255
|
|
271
|
+
* const i_255 = math.constant(1 / 255) // 0.003921568627451 (JavaScript: 0.00392156862745098)
|
|
272
|
+
* math.constant(1 / 7) // 0.1428571428571428 (JavaScript: 0.14285714285714285)
|
|
273
|
+
* math.constant(1 / 3) // 0.3333333333333333 (unchanged)
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
export function constant(value: float): float {
|
|
277
|
+
const magnitude = Math.abs(value);
|
|
278
|
+
// from 0.5 up, 16 decimals move a double by less than half its spacing: it stays the same
|
|
279
|
+
if (!(magnitude >= 1e-3 && magnitude < 0.5)) return value;
|
|
280
|
+
const [num, den] = exactRatio(magnitude);
|
|
281
|
+
const scaled = num * CONSTANT_SCALE;
|
|
282
|
+
let units = scaled / den;
|
|
283
|
+
const twiceRest = (scaled - units * den) * 2n;
|
|
284
|
+
if (twiceRest > den || (twiceRest === den && (units & 1n) === 1n)) units++;
|
|
285
|
+
const digits = units.toString().padStart(17, '0');
|
|
286
|
+
const result = Number(`${digits.slice(0, -16)}.${digits.slice(-16)}`);
|
|
287
|
+
return value < 0 ? -result : result;
|
|
192
288
|
}
|
|
193
289
|
|
|
194
290
|
/**
|
|
@@ -301,6 +397,8 @@ export function min(...values: (float | Series)[]): float | Series {
|
|
|
301
397
|
* - Calculates sum of all values divided by count
|
|
302
398
|
* - Returns NaN if no arguments provided (division by zero)
|
|
303
399
|
* - If any argument is a Series, returns a Series; otherwise returns a number
|
|
400
|
+
* - PineScript rule: with 1 or 2 values the sum is plain; with 3 values or more it is a Kahan compensated sum whose
|
|
401
|
+
* pending correction is subtracted before the division (math.avg(0.1, 0.2, 0.3) = 0.19999999999999998)
|
|
304
402
|
*
|
|
305
403
|
* @example
|
|
306
404
|
* ```typescript
|
|
@@ -313,10 +411,9 @@ export function min(...values: (float | Series)[]): float | Series {
|
|
|
313
411
|
export function avg(...values: (float | Series)[]): float | Series {
|
|
314
412
|
// Check if any value is a Series
|
|
315
413
|
const hasSeries = values.some(v => v instanceof Series);
|
|
316
|
-
|
|
414
|
+
|
|
317
415
|
if (!hasSeries) {
|
|
318
|
-
|
|
319
|
-
return (values as number[]).reduce((a, b) => a + b, 0) / values.length;
|
|
416
|
+
return avgOf(values as number[]);
|
|
320
417
|
}
|
|
321
418
|
|
|
322
419
|
// At least one Series - return a Series
|
|
@@ -334,18 +431,35 @@ export function avg(...values: (float | Series)[]): float | Series {
|
|
|
334
431
|
const nums = values.map((v, idx) =>
|
|
335
432
|
valueArrays[idx] ? valueArrays[idx]![i] ?? NaN : (v as number)
|
|
336
433
|
);
|
|
337
|
-
|
|
338
|
-
result.push(sum / nums.length);
|
|
434
|
+
result.push(avgOf(nums));
|
|
339
435
|
}
|
|
340
|
-
|
|
436
|
+
|
|
341
437
|
return Series.fromArray(bars, result);
|
|
342
438
|
}
|
|
343
439
|
|
|
440
|
+
/** PineScript math.avg of numbers: plain sum up to 2 values, Kahan sum with the correction flushed from 3. */
|
|
441
|
+
function avgOf(nums: number[]): number {
|
|
442
|
+
if (nums.length <= 2) {
|
|
443
|
+
let sum = 0;
|
|
444
|
+
for (const n of nums) sum = sum + n;
|
|
445
|
+
return sum / nums.length;
|
|
446
|
+
}
|
|
447
|
+
let sum = 0;
|
|
448
|
+
let c = 0;
|
|
449
|
+
for (const n of nums) {
|
|
450
|
+
const y = n - c;
|
|
451
|
+
const t = sum + y;
|
|
452
|
+
c = t - sum - y;
|
|
453
|
+
sum = t;
|
|
454
|
+
}
|
|
455
|
+
return (sum - c) / nums.length;
|
|
456
|
+
}
|
|
457
|
+
|
|
344
458
|
/**
|
|
345
459
|
* Returns the sum of values over a sliding window.
|
|
346
460
|
*
|
|
347
461
|
* @param source - Series of values or array
|
|
348
|
-
* @param length - Window length for summation
|
|
462
|
+
* @param length - Window length for summation, or one length per bar (a series length: array or Series)
|
|
349
463
|
* @returns Series or array with rolling sum values
|
|
350
464
|
*
|
|
351
465
|
* @remarks
|
|
@@ -355,6 +469,11 @@ export function avg(...values: (float | Series)[]): float | Series {
|
|
|
355
469
|
* - A compensated running sum, as in PineScript, so the result can differ from the exact window sum in the last
|
|
356
470
|
* bits (bit for bit with PineScript)
|
|
357
471
|
* - When passed a Series, returns a Series; when passed an array, returns an array
|
|
472
|
+
* - With a series length, PineScript moves the running sum to the new length when the length changes, with Kahan
|
|
473
|
+
* steps of the raw values: a smaller length removes the values that leave the window (oldest first); a larger
|
|
474
|
+
* length adds back the values from the one before the old window down to the one `length` values back (newest
|
|
475
|
+
* first), then the usual step removes the stored value of the one `length` values back. A bar with an `na` value
|
|
476
|
+
* gives the sum of the last `length` earlier values
|
|
358
477
|
*
|
|
359
478
|
* @example
|
|
360
479
|
* ```typescript
|
|
@@ -362,14 +481,20 @@ export function avg(...values: (float | Series)[]): float | Series {
|
|
|
362
481
|
* // Explanation: [NaN, NaN, 1+2+3, 2+3+4, 3+4+5]
|
|
363
482
|
* ```
|
|
364
483
|
*/
|
|
365
|
-
export function sum(source: Series, length: simple_int): Series;
|
|
366
|
-
export function sum(source: series_float, length: simple_int): series_float;
|
|
367
|
-
export function sum(
|
|
484
|
+
export function sum(source: Series, length: simple_int | ArrayLike<number> | Series): Series;
|
|
485
|
+
export function sum(source: series_float, length: simple_int | ArrayLike<number> | Series): series_float;
|
|
486
|
+
export function sum(
|
|
487
|
+
source: series_float | Series,
|
|
488
|
+
length: simple_int | ArrayLike<number> | Series
|
|
489
|
+
): series_float | Series {
|
|
368
490
|
// Handle Series objects - extract to array first
|
|
369
491
|
const sourceArray = source instanceof Series ? source.toArray() : source;
|
|
370
492
|
// PineScript: the sum of the last `length` non-na values (also +/-Infinity skipped); an na bar keeps the previous
|
|
371
493
|
// result. A compensated running sum, bit for bit as PineScript.
|
|
372
|
-
const result: series_float =
|
|
494
|
+
const result: series_float =
|
|
495
|
+
typeof length === 'number'
|
|
496
|
+
? runningSum(sourceArray, length)
|
|
497
|
+
: runningSumSeries(sourceArray, length instanceof Series ? length.toArray() : length, 'math.sum');
|
|
373
498
|
|
|
374
499
|
// If input was a Series, return a Series
|
|
375
500
|
if (source instanceof Series) {
|
|
@@ -916,31 +1041,63 @@ export function todegrees(radians: float | Series): float | Series {
|
|
|
916
1041
|
return radians * (180 / Math.PI);
|
|
917
1042
|
}
|
|
918
1043
|
|
|
1044
|
+
/**
|
|
1045
|
+
* PineScript's seeded generator: Java's linear congruential generator (`java.util.Random`), `nextDouble()`.
|
|
1046
|
+
* Each generator draws one number per call.
|
|
1047
|
+
*/
|
|
1048
|
+
export function seededRandom(seed: int): (min?: float, max?: float) => float {
|
|
1049
|
+
const mask = (1n << 48n) - 1n;
|
|
1050
|
+
let state = (BigInt(Math.trunc(seed)) ^ 0x5deece66dn) & mask;
|
|
1051
|
+
const next = (bits: bigint): bigint => {
|
|
1052
|
+
state = (0x5deece66dn * state + 11n) & mask;
|
|
1053
|
+
return state >> (48n - bits);
|
|
1054
|
+
};
|
|
1055
|
+
return (min = 0, max = 1) => {
|
|
1056
|
+
const hi = next(26n);
|
|
1057
|
+
const lo = next(27n);
|
|
1058
|
+
const unit = Number((hi << 27n) + lo) / 2 ** 53;
|
|
1059
|
+
return min + unit * (max - min);
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const seeded = new Map<number, (min?: float, max?: float) => float>();
|
|
1064
|
+
|
|
1065
|
+
/** Starts every seeded sequence of `math.random` again (called at the start of a script run). */
|
|
1066
|
+
export function resetRandom(): void {
|
|
1067
|
+
seeded.clear();
|
|
1068
|
+
}
|
|
1069
|
+
|
|
919
1070
|
/**
|
|
920
1071
|
* Returns a pseudo-random number.
|
|
921
1072
|
*
|
|
922
|
-
* @param min - Optional minimum value (inclusive)
|
|
923
|
-
* @param max - Optional maximum value (exclusive)
|
|
924
|
-
* @param seed - Optional seed
|
|
1073
|
+
* @param min - Optional minimum value (inclusive), default 0
|
|
1074
|
+
* @param max - Optional maximum value (exclusive), default 1
|
|
1075
|
+
* @param seed - Optional seed: a repeatable sequence, as PineScript (Java's `java.util.Random`)
|
|
925
1076
|
* @returns A random number in the specified range, or [0, 1) if no range specified
|
|
926
1077
|
*
|
|
927
1078
|
* @remarks
|
|
928
|
-
* - Without
|
|
929
|
-
* - With
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
1079
|
+
* - Without a seed: `Math.random()`
|
|
1080
|
+
* - With a seed: PineScript's sequence, one number per call (measured: the call on each bar draws the next number
|
|
1081
|
+
* of `new java.util.Random(seed).nextDouble()` from the first bar of the history). PineScript keeps one generator
|
|
1082
|
+
* per call site; here there is one generator per seed value (started again by `executeScript`): for two call sites
|
|
1083
|
+
* with the same seed use `callsite.random(seed)`
|
|
933
1084
|
*
|
|
934
1085
|
* @example
|
|
935
1086
|
* ```typescript
|
|
936
1087
|
* math.random() // Returns: value between 0 and 1 (e.g., 0.234...)
|
|
937
1088
|
* math.random(0, 10) // Returns: value between 0 and 10 (e.g., 7.45...)
|
|
938
|
-
* math.random(
|
|
1089
|
+
* math.random(0, 1, 42) // Returns: 0.7275636800328681 on the first call, as PineScript
|
|
939
1090
|
* ```
|
|
940
1091
|
*/
|
|
941
|
-
export function random(min?: float, max?: float,
|
|
942
|
-
|
|
943
|
-
|
|
1092
|
+
export function random(min?: float, max?: float, seed?: int): float {
|
|
1093
|
+
if (seed !== undefined && seed !== null && !Number.isNaN(seed)) {
|
|
1094
|
+
let gen = seeded.get(seed);
|
|
1095
|
+
if (!gen) {
|
|
1096
|
+
gen = seededRandom(seed);
|
|
1097
|
+
seeded.set(seed, gen);
|
|
1098
|
+
}
|
|
1099
|
+
return gen(min ?? 0, max ?? 1);
|
|
1100
|
+
}
|
|
944
1101
|
const rand = Math.random();
|
|
945
1102
|
if (min !== undefined && max !== undefined) {
|
|
946
1103
|
return min + rand * (max - min);
|
|
@@ -1003,9 +1160,14 @@ export function sign(value: float | Series): int | Series {
|
|
|
1003
1160
|
* - **JavaScript signature**: Requires explicit `mintick` OR use `createContext()` with syminfo
|
|
1004
1161
|
* - Rounds to the nearest value divisible by mintick
|
|
1005
1162
|
* - Returns NaN for NaN input
|
|
1006
|
-
* - Ties round up (0.5 -> 1)
|
|
1007
1163
|
* - If number is a Series, returns a Series; if number is a float, returns a float
|
|
1008
1164
|
*
|
|
1165
|
+
* PineScript rules:
|
|
1166
|
+
* - mintick is `minmove / pricescale` (here rebuilt from `mintick`: the smallest power of 10 that makes it an
|
|
1167
|
+
* integer); the position on the tick grid is `|number| * pricescale / minmove`
|
|
1168
|
+
* - ties go away from zero, decided on the exact value of that product with a 1e-10 tolerance
|
|
1169
|
+
* (-1.075 -> -1.08, 1.005 -> 1.01, 2.675 -> 2.68 at mintick 0.01)
|
|
1170
|
+
*
|
|
1009
1171
|
* @example
|
|
1010
1172
|
* ```typescript
|
|
1011
1173
|
* // Direct call with explicit mintick
|
|
@@ -1023,35 +1185,19 @@ export function round_to_mintick(number: float | Series, mintick?: float): float
|
|
|
1023
1185
|
if (number instanceof Series) {
|
|
1024
1186
|
const bars = number.bars;
|
|
1025
1187
|
const valueArray = number.toArray();
|
|
1026
|
-
const length = bars.length;
|
|
1027
|
-
|
|
1028
1188
|
if (mintick === undefined) {
|
|
1029
1189
|
throw new Error(
|
|
1030
1190
|
'math.round_to_mintick() requires mintick value. ' +
|
|
1031
1191
|
'Either pass it explicitly or use createContext({ syminfo: { mintick } }) for implicit data.'
|
|
1032
1192
|
);
|
|
1033
1193
|
}
|
|
1034
|
-
|
|
1035
1194
|
const result: number[] = [];
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
for (let i = 0; i < length; i++) {
|
|
1039
|
-
result.push(valueArray[i] ?? NaN);
|
|
1040
|
-
}
|
|
1041
|
-
} else {
|
|
1042
|
-
for (let i = 0; i < length; i++) {
|
|
1043
|
-
const v = valueArray[i];
|
|
1044
|
-
if (v === undefined || isNaN(v)) {
|
|
1045
|
-
result.push(NaN);
|
|
1046
|
-
} else {
|
|
1047
|
-
result.push(Math.round(v / mintick) * mintick);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1195
|
+
for (let i = 0; i < bars.length; i++) {
|
|
1196
|
+
result.push(roundToTick(valueArray[i] ?? NaN, mintick));
|
|
1050
1197
|
}
|
|
1051
|
-
|
|
1052
1198
|
return Series.fromArray(bars, result);
|
|
1053
1199
|
}
|
|
1054
|
-
|
|
1200
|
+
|
|
1055
1201
|
// Scalar case
|
|
1056
1202
|
if (isNaN(number)) {
|
|
1057
1203
|
return NaN;
|
|
@@ -1063,17 +1209,53 @@ export function round_to_mintick(number: float | Series, mintick?: float): float
|
|
|
1063
1209
|
'Either pass it explicitly or use createContext({ syminfo: { mintick } }) for implicit data.'
|
|
1064
1210
|
);
|
|
1065
1211
|
}
|
|
1212
|
+
return roundToTick(number, mintick);
|
|
1213
|
+
}
|
|
1066
1214
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1215
|
+
/** `[minmove, pricescale]` of a mintick: pricescale is the smallest power of 10 (up to 1e15) giving an integer. */
|
|
1216
|
+
function tickPair(mintick: number): [number, number] | null {
|
|
1217
|
+
for (let k = 0; k <= 15; k++) {
|
|
1218
|
+
const pricescale = Number(`1e${k}`);
|
|
1219
|
+
const scaled = mintick * pricescale;
|
|
1220
|
+
const minmove = Math.round(scaled);
|
|
1221
|
+
if (minmove >= 1 && Math.abs(scaled - minmove) <= 1e-9 * minmove) return [minmove, pricescale];
|
|
1069
1222
|
}
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1070
1225
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1226
|
+
/** PineScript math.round_to_mintick of one value (see `round_to_mintick`). */
|
|
1227
|
+
function roundToTick(number: number, mintick: number): number {
|
|
1228
|
+
if (!Number.isFinite(number)) return NaN;
|
|
1229
|
+
if (mintick === 0) return number;
|
|
1230
|
+
const pair = tickPair(mintick);
|
|
1231
|
+
const negative = number < 0;
|
|
1232
|
+
const magnitude = negative ? -number : number;
|
|
1233
|
+
if (pair === null) {
|
|
1234
|
+
// A tick that is no decimal fraction: the grid of the double mintick, ties away from zero.
|
|
1235
|
+
const scaled = magnitude / mintick;
|
|
1236
|
+
let units = Math.trunc(scaled);
|
|
1237
|
+
if (scaled - units >= 0.5) units++;
|
|
1238
|
+
return (negative ? 0 - units : units) * mintick;
|
|
1239
|
+
}
|
|
1240
|
+
const [minmove, pricescale] = pair;
|
|
1241
|
+
const scaled = (magnitude * pricescale) / minmove;
|
|
1242
|
+
let units = Math.trunc(scaled);
|
|
1243
|
+
const offset = scaled - units - 0.5;
|
|
1244
|
+
const slack = 1e-6 + scaled * 1.8e-15;
|
|
1245
|
+
if (-slack < offset && offset < slack) {
|
|
1246
|
+
const [n, d] = exactRatio(magnitude);
|
|
1247
|
+
const num = n * BigInt(pricescale);
|
|
1248
|
+
const den = d * BigInt(minmove);
|
|
1249
|
+
const whole = num / den;
|
|
1250
|
+
units = Number(whole) + (reachesTie(num, den, whole) ? 1 : 0);
|
|
1251
|
+
} else if (offset > 0) {
|
|
1252
|
+
units++;
|
|
1253
|
+
}
|
|
1254
|
+
return ((negative ? 0 - units : units) * minmove) / pricescale;
|
|
1073
1255
|
}
|
|
1074
1256
|
|
|
1075
1257
|
// Constants
|
|
1076
1258
|
export const pi = Math.PI;
|
|
1077
1259
|
export const e = Math.E;
|
|
1078
1260
|
export const phi = 1.618033988749895; // Golden ratio
|
|
1079
|
-
export const rphi = 0.
|
|
1261
|
+
export const rphi = 0.6180339887498948; // Reciprocal of golden ratio: 1 / phi
|
package/src/matrix/index.ts
CHANGED
|
@@ -61,6 +61,8 @@ export function new_matrix<T>(
|
|
|
61
61
|
* ```
|
|
62
62
|
*/
|
|
63
63
|
export function get<T>(id: PineMatrix<T>, row: simple_int, column: simple_int): T {
|
|
64
|
+
// PineScript: an na index gives na
|
|
65
|
+
if (Number.isNaN(row) || Number.isNaN(column)) return NaN as T;
|
|
64
66
|
if (row < 0 || row >= id.rows || column < 0 || column >= id.columns) {
|
|
65
67
|
throw new Error(`Matrix index out of bounds: [${row}, ${column}] for matrix of size [${id.rows}, ${id.columns}]`);
|
|
66
68
|
}
|
|
@@ -92,6 +94,8 @@ export function get<T>(id: PineMatrix<T>, row: simple_int, column: simple_int):
|
|
|
92
94
|
* ```
|
|
93
95
|
*/
|
|
94
96
|
export function set<T>(id: PineMatrix<T>, row: simple_int, column: simple_int, value: T): void {
|
|
97
|
+
// PineScript: an na index changes nothing
|
|
98
|
+
if (Number.isNaN(row) || Number.isNaN(column)) return;
|
|
95
99
|
if (row < 0 || row >= id.rows || column < 0 || column >= id.columns) {
|
|
96
100
|
throw new Error(`Matrix index out of bounds: [${row}, ${column}] for matrix of size [${id.rows}, ${id.columns}]`);
|
|
97
101
|
}
|
|
@@ -1884,15 +1888,8 @@ export function det(id: PineMatrix<float>): float {
|
|
|
1884
1888
|
return 1; // Empty matrix has determinant 1 by convention
|
|
1885
1889
|
}
|
|
1886
1890
|
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
if (n === 2) {
|
|
1892
|
-
return id.data[0]![0]! * id.data[1]![1]! - id.data[0]![1]! * id.data[1]![0]!;
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
// LU decomposition with partial pivoting
|
|
1891
|
+
// LU decomposition with partial pivoting, for every size (as PineScript: det([[0.1, 0.2], [0.3, 0.4]]) is
|
|
1892
|
+
// -0.019999999999999993, not the closed form's -0.01999999999999999)
|
|
1896
1893
|
// Make a copy to work with
|
|
1897
1894
|
const a: float[][] = id.data.map(row => [...row]);
|
|
1898
1895
|
let determinant = 1;
|
|
@@ -1918,9 +1915,10 @@ export function det(id: PineMatrix<float>): float {
|
|
|
1918
1915
|
swapCount++;
|
|
1919
1916
|
}
|
|
1920
1917
|
|
|
1921
|
-
//
|
|
1918
|
+
// As PineScript (LU with an absolute singularity threshold of 1e-11): det(1e-11 * I3) is 1e-33, a pivot below
|
|
1919
|
+
// 1e-11 makes the determinant 0 (det([[1, 1], [1, 1 + 1e-12]]) and det([[1..9]]) are 0)
|
|
1922
1920
|
const pivot = a[col]![col]!;
|
|
1923
|
-
if (Math.abs(pivot) <
|
|
1921
|
+
if (Math.abs(pivot) < 1e-11) {
|
|
1924
1922
|
return 0;
|
|
1925
1923
|
}
|
|
1926
1924
|
|
|
@@ -2161,51 +2159,53 @@ export function rank(id: PineMatrix<float>): int {
|
|
|
2161
2159
|
if (id.rows === 0 || id.columns === 0) {
|
|
2162
2160
|
return 0;
|
|
2163
2161
|
}
|
|
2162
|
+
// As PineScript: the number of singular values above max(rows, columns) * largest * 2^-52 (rank([[1..9]]) is 2,
|
|
2163
|
+
// rank(1e-11 * I3) is 3, rank([[1, 1], [1, 1 + 1e-12]]) is 2)
|
|
2164
|
+
const s = singularValues(id.data);
|
|
2165
|
+
const largest = Math.max(...s);
|
|
2166
|
+
if (!(largest > 0)) return 0;
|
|
2167
|
+
const tol = Math.max(id.rows, id.columns) * largest * 2 ** -52;
|
|
2168
|
+
return s.filter((v) => v > tol).length;
|
|
2169
|
+
}
|
|
2164
2170
|
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
const
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
for (let
|
|
2173
|
-
|
|
2174
|
-
let
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
for (let row = r + 1; row < m; row++) {
|
|
2199
|
-
const factor = a[row]![col]! / pivot;
|
|
2200
|
-
for (let j = col; j < n; j++) {
|
|
2201
|
-
a[row]![j] = a[row]![j]! - factor * a[r]![j]!;
|
|
2171
|
+
/** Singular values of a matrix (one-sided Jacobi rotations on the columns; accurate for small values). */
|
|
2172
|
+
function singularValues(data: float[][]): float[] {
|
|
2173
|
+
// work on the orientation with at least as many rows as columns
|
|
2174
|
+
const tall = data.length >= (data[0]?.length ?? 0);
|
|
2175
|
+
const a: float[][] = tall ? data.map((row) => [...row]) : data[0]!.map((_, j) => data.map((row) => row[j]!));
|
|
2176
|
+
const m = a.length;
|
|
2177
|
+
const n = a[0]!.length;
|
|
2178
|
+
for (let sweep = 0; sweep < 60; sweep++) {
|
|
2179
|
+
let rotated = false;
|
|
2180
|
+
for (let p = 0; p < n - 1; p++) {
|
|
2181
|
+
for (let q = p + 1; q < n; q++) {
|
|
2182
|
+
let alpha = 0;
|
|
2183
|
+
let beta = 0;
|
|
2184
|
+
let gamma = 0;
|
|
2185
|
+
for (let i = 0; i < m; i++) {
|
|
2186
|
+
const x = a[i]![p]!;
|
|
2187
|
+
const y = a[i]![q]!;
|
|
2188
|
+
alpha += x * x;
|
|
2189
|
+
beta += y * y;
|
|
2190
|
+
gamma += x * y;
|
|
2191
|
+
}
|
|
2192
|
+
if (gamma === 0 || Math.abs(gamma) <= 1e-15 * Math.sqrt(alpha * beta)) continue;
|
|
2193
|
+
rotated = true;
|
|
2194
|
+
const zeta = (beta - alpha) / (2 * gamma);
|
|
2195
|
+
const tan = Math.sign(zeta || 1) / (Math.abs(zeta) + Math.sqrt(1 + zeta * zeta));
|
|
2196
|
+
const cos = 1 / Math.sqrt(1 + tan * tan);
|
|
2197
|
+
const sin = cos * tan;
|
|
2198
|
+
for (let i = 0; i < m; i++) {
|
|
2199
|
+
const x = a[i]![p]!;
|
|
2200
|
+
const y = a[i]![q]!;
|
|
2201
|
+
a[i]![p] = cos * x - sin * y;
|
|
2202
|
+
a[i]![q] = sin * x + cos * y;
|
|
2203
|
+
}
|
|
2202
2204
|
}
|
|
2203
2205
|
}
|
|
2204
|
-
|
|
2205
|
-
r++;
|
|
2206
|
+
if (!rotated) break;
|
|
2206
2207
|
}
|
|
2207
|
-
|
|
2208
|
-
return r;
|
|
2208
|
+
return Array.from({ length: n }, (_, j) => Math.sqrt(a.reduce((sum, row) => sum + row[j]! * row[j]!, 0)));
|
|
2209
2209
|
}
|
|
2210
2210
|
|
|
2211
2211
|
/**
|
package/src/runtime/series.ts
CHANGED
|
@@ -174,6 +174,17 @@ export class BarData {
|
|
|
174
174
|
* const materialized = complex.materialize(); // Breaks closure chain
|
|
175
175
|
* ```
|
|
176
176
|
*/
|
|
177
|
+
/** Largest history reference of a script series (PineScript max_bars_back limit). */
|
|
178
|
+
export const MAX_BARS_BACK = 5000;
|
|
179
|
+
|
|
180
|
+
/** PineScript's runtime error RE10008 text. */
|
|
181
|
+
export function historyError(bar: number, barsBack: number): string {
|
|
182
|
+
return (
|
|
183
|
+
`Error on bar ${bar}: The script attempts to reference historical data that is too far from the current bar ` +
|
|
184
|
+
`(${barsBack} bars back). The history-referencing length for the expression must be a value between 0 and ${MAX_BARS_BACK}.`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
177
188
|
export class Series {
|
|
178
189
|
private extractor: SeriesExtractor;
|
|
179
190
|
private dataSource: BarData;
|
|
@@ -186,6 +197,12 @@ export class Series {
|
|
|
186
197
|
* @param data - Bar data (Bar[] or BarData)
|
|
187
198
|
* @param extractor - Function to extract/compute value for each bar
|
|
188
199
|
*/
|
|
200
|
+
/**
|
|
201
|
+
* True for PineScript's built-in price variables (open, high, low, close, volume, hl2...): their history covers
|
|
202
|
+
* every bar, so references further than 5000 bars back are allowed (measured: close[bar_index] runs).
|
|
203
|
+
*/
|
|
204
|
+
fullHistory = false;
|
|
205
|
+
|
|
189
206
|
constructor(data: Bar[] | BarData, extractor: SeriesExtractor) {
|
|
190
207
|
// Support both Bar[] and BarData for backward compatibility
|
|
191
208
|
this.dataSource = data instanceof BarData ? data : new BarData(data);
|
|
@@ -219,7 +236,9 @@ export class Series {
|
|
|
219
236
|
* @returns Series with extracted field values
|
|
220
237
|
*/
|
|
221
238
|
static fromBars(bars: Bar[] | BarData, field: 'open' | 'high' | 'low' | 'close' | 'volume'): Series {
|
|
222
|
-
|
|
239
|
+
const series = new Series(bars, (bar) => bar[field] ?? NaN);
|
|
240
|
+
series.fullHistory = true;
|
|
241
|
+
return series;
|
|
223
242
|
}
|
|
224
243
|
|
|
225
244
|
/**
|
|
@@ -516,6 +535,8 @@ export class Series {
|
|
|
516
535
|
*/
|
|
517
536
|
offset(offset: number): Series {
|
|
518
537
|
if (offset < 0) throw new RangeError(`Series.offset(${offset}): the offset cannot be negative (it would read a future bar).`);
|
|
538
|
+
// PineScript runtime error RE10008: a script series keeps at most 5000 bars of history
|
|
539
|
+
if (offset > MAX_BARS_BACK && !this.fullHistory) throw new Error(historyError(0, Math.floor(offset)));
|
|
519
540
|
return new Series(this.dataSource, (_bar, i, data) => {
|
|
520
541
|
const targetIndex = i - offset;
|
|
521
542
|
if (targetIndex < 0 || targetIndex >= data.length) {
|