oakscriptjs 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/array/index.d.ts +8 -2
- package/dist/cjs/array/index.d.ts.map +1 -1
- package/dist/cjs/array/index.js +2 -1
- package/dist/cjs/array/index.js.map +2 -2
- package/dist/cjs/box/index.d.ts.map +1 -1
- package/dist/cjs/box/index.js +7 -1
- package/dist/cjs/box/index.js.map +2 -2
- package/dist/cjs/callsite/index.d.ts +73 -32
- package/dist/cjs/callsite/index.d.ts.map +1 -1
- package/dist/cjs/callsite/index.js +69 -65
- package/dist/cjs/callsite/index.js.map +2 -2
- package/dist/cjs/drawing/registry.d.ts +84 -0
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +153 -6
- package/dist/cjs/drawing/registry.js.map +2 -2
- package/dist/cjs/index.d.ts +8 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +33 -1
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/input.d.ts +9 -1
- package/dist/cjs/input.d.ts.map +1 -1
- package/dist/cjs/input.js +16 -0
- package/dist/cjs/input.js.map +2 -2
- package/dist/cjs/label/index.d.ts.map +1 -1
- package/dist/cjs/label/index.js +3 -0
- package/dist/cjs/label/index.js.map +2 -2
- package/dist/cjs/line/index.d.ts +10 -27
- package/dist/cjs/line/index.d.ts.map +1 -1
- package/dist/cjs/line/index.js +11 -22
- package/dist/cjs/line/index.js.map +2 -2
- package/dist/cjs/linefill/index.d.ts +3 -0
- package/dist/cjs/linefill/index.d.ts.map +1 -1
- package/dist/cjs/linefill/index.js +15 -1
- package/dist/cjs/linefill/index.js.map +3 -3
- package/dist/cjs/math/index.d.ts.map +1 -1
- package/dist/cjs/math/index.js +2 -0
- package/dist/cjs/math/index.js.map +2 -2
- package/dist/cjs/polyline/index.d.ts.map +1 -1
- package/dist/cjs/polyline/index.js +3 -0
- package/dist/cjs/polyline/index.js.map +2 -2
- package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
- package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
- package/dist/cjs/runtime/index.d.ts +1 -1
- package/dist/cjs/runtime/index.d.ts.map +1 -1
- package/dist/cjs/runtime/index.js +1 -0
- package/dist/cjs/runtime/index.js.map +2 -2
- package/dist/cjs/runtime/inputs.d.ts +9 -0
- package/dist/cjs/runtime/inputs.d.ts.map +1 -1
- package/dist/cjs/runtime/inputs.js +8 -0
- package/dist/cjs/runtime/inputs.js.map +2 -2
- package/dist/cjs/runtime/types.d.ts +1 -1
- package/dist/cjs/runtime/types.d.ts.map +1 -1
- package/dist/cjs/runtime/types.js.map +1 -1
- package/dist/cjs/script/index.d.ts +91 -21
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +207 -19
- package/dist/cjs/script/index.js.map +3 -3
- package/dist/cjs/security/other.d.ts +92 -0
- package/dist/cjs/security/other.d.ts.map +1 -0
- package/dist/cjs/security/other.js +164 -0
- package/dist/cjs/security/other.js.map +7 -0
- package/dist/cjs/session/bars.d.ts +24 -1
- package/dist/cjs/session/bars.d.ts.map +1 -1
- package/dist/cjs/session/bars.js +56 -7
- package/dist/cjs/session/bars.js.map +3 -3
- package/dist/cjs/session/calendar.d.ts +13 -2
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +39 -9
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/session/context.d.ts +76 -0
- package/dist/cjs/session/context.d.ts.map +1 -0
- package/dist/cjs/session/context.js +114 -0
- package/dist/cjs/session/context.js.map +7 -0
- package/dist/cjs/str/floatingdecimal.d.ts +31 -0
- package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
- package/dist/cjs/str/floatingdecimal.js +298 -0
- package/dist/cjs/str/floatingdecimal.js.map +7 -0
- package/dist/cjs/str/index.d.ts +4 -1
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +2 -1
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +7 -3
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +4 -3
- package/dist/cjs/str/numberformat.js.map +2 -2
- package/dist/cjs/ta/index.d.ts +67 -34
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +202 -145
- package/dist/cjs/ta/index.js.map +2 -2
- package/dist/cjs/ta/limits.d.ts +35 -0
- package/dist/cjs/ta/limits.d.ts.map +1 -0
- package/dist/cjs/ta/limits.js +64 -0
- package/dist/cjs/ta/limits.js.map +7 -0
- package/dist/cjs/ta/running-sum.d.ts +0 -16
- package/dist/cjs/ta/running-sum.d.ts.map +1 -1
- package/dist/cjs/ta/running-sum.js +2 -0
- package/dist/cjs/ta/running-sum.js.map +2 -2
- package/dist/cjs/ta-series.d.ts +8 -8
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +10 -8
- package/dist/cjs/ta-series.js.map +2 -2
- package/dist/cjs/table/index.d.ts +3 -1
- package/dist/cjs/table/index.d.ts.map +1 -1
- package/dist/cjs/table/index.js +1 -0
- package/dist/cjs/table/index.js.map +3 -3
- package/dist/cjs/time/index.d.ts +5 -2
- package/dist/cjs/time/index.d.ts.map +1 -1
- package/dist/cjs/time/index.js +2 -2
- package/dist/cjs/time/index.js.map +2 -2
- package/dist/cjs/time/session.d.ts +26 -1
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +94 -12
- package/dist/cjs/time/session.js.map +3 -3
- package/dist/cjs/time/timezone.d.ts +6 -1
- package/dist/cjs/time/timezone.d.ts.map +1 -1
- package/dist/cjs/time/timezone.js +2 -2
- package/dist/cjs/time/timezone.js.map +2 -2
- package/dist/cjs/types/metadata.d.ts +28 -4
- package/dist/cjs/types/metadata.d.ts.map +1 -1
- package/dist/cjs/types/metadata.js.map +1 -1
- package/dist/esm/array/index.d.ts +8 -2
- package/dist/esm/array/index.d.ts.map +1 -1
- package/dist/esm/array/index.js +12 -3
- package/dist/esm/array/index.js.map +1 -1
- package/dist/esm/box/index.d.ts.map +1 -1
- package/dist/esm/box/index.js +8 -2
- package/dist/esm/box/index.js.map +1 -1
- package/dist/esm/callsite/index.d.ts +73 -32
- package/dist/esm/callsite/index.d.ts.map +1 -1
- package/dist/esm/callsite/index.js +143 -106
- package/dist/esm/callsite/index.js.map +1 -1
- package/dist/esm/drawing/registry.d.ts +84 -0
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +260 -6
- package/dist/esm/drawing/registry.js.map +1 -1
- package/dist/esm/index.d.ts +8 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +13 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/input.d.ts +9 -1
- package/dist/esm/input.d.ts.map +1 -1
- package/dist/esm/input.js +16 -0
- package/dist/esm/input.js.map +1 -1
- package/dist/esm/label/index.d.ts.map +1 -1
- package/dist/esm/label/index.js +4 -1
- package/dist/esm/label/index.js.map +1 -1
- package/dist/esm/line/index.d.ts +10 -27
- package/dist/esm/line/index.d.ts.map +1 -1
- package/dist/esm/line/index.js +22 -60
- package/dist/esm/line/index.js.map +1 -1
- package/dist/esm/linefill/index.d.ts +3 -0
- package/dist/esm/linefill/index.d.ts.map +1 -1
- package/dist/esm/linefill/index.js +20 -1
- package/dist/esm/linefill/index.js.map +1 -1
- package/dist/esm/math/index.d.ts.map +1 -1
- package/dist/esm/math/index.js +3 -0
- package/dist/esm/math/index.js.map +1 -1
- package/dist/esm/polyline/index.d.ts.map +1 -1
- package/dist/esm/polyline/index.js +5 -1
- package/dist/esm/polyline/index.js.map +1 -1
- package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
- package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
- package/dist/esm/runtime/index.d.ts +1 -1
- package/dist/esm/runtime/index.d.ts.map +1 -1
- package/dist/esm/runtime/index.js +1 -1
- package/dist/esm/runtime/index.js.map +1 -1
- package/dist/esm/runtime/inputs.d.ts +9 -0
- package/dist/esm/runtime/inputs.d.ts.map +1 -1
- package/dist/esm/runtime/inputs.js +15 -0
- package/dist/esm/runtime/inputs.js.map +1 -1
- package/dist/esm/runtime/types.d.ts +1 -1
- package/dist/esm/runtime/types.d.ts.map +1 -1
- package/dist/esm/script/index.d.ts +91 -21
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +259 -28
- package/dist/esm/script/index.js.map +1 -1
- package/dist/esm/security/other.d.ts +92 -0
- package/dist/esm/security/other.d.ts.map +1 -0
- package/dist/esm/security/other.js +211 -0
- package/dist/esm/security/other.js.map +1 -0
- package/dist/esm/session/bars.d.ts +24 -1
- package/dist/esm/session/bars.d.ts.map +1 -1
- package/dist/esm/session/bars.js +75 -6
- package/dist/esm/session/bars.js.map +1 -1
- package/dist/esm/session/calendar.d.ts +13 -2
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +54 -10
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/session/context.d.ts +76 -0
- package/dist/esm/session/context.d.ts.map +1 -0
- package/dist/esm/session/context.js +124 -0
- package/dist/esm/session/context.js.map +1 -0
- package/dist/esm/str/floatingdecimal.d.ts +31 -0
- package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
- package/dist/esm/str/floatingdecimal.js +272 -0
- package/dist/esm/str/floatingdecimal.js.map +1 -0
- package/dist/esm/str/index.d.ts +4 -1
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +9 -2
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +7 -3
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +11 -5
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +67 -34
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +284 -240
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/limits.d.ts +35 -0
- package/dist/esm/ta/limits.d.ts.map +1 -0
- package/dist/esm/ta/limits.js +67 -0
- package/dist/esm/ta/limits.js.map +1 -0
- package/dist/esm/ta/running-sum.d.ts +0 -16
- package/dist/esm/ta/running-sum.d.ts.map +1 -1
- package/dist/esm/ta/running-sum.js +2 -0
- package/dist/esm/ta/running-sum.js.map +1 -1
- package/dist/esm/ta-series.d.ts +8 -8
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +11 -8
- package/dist/esm/ta-series.js.map +1 -1
- package/dist/esm/table/index.d.ts +3 -1
- package/dist/esm/table/index.d.ts.map +1 -1
- package/dist/esm/table/index.js +6 -1
- package/dist/esm/table/index.js.map +1 -1
- package/dist/esm/time/index.d.ts +5 -2
- package/dist/esm/time/index.d.ts.map +1 -1
- package/dist/esm/time/index.js +6 -3
- package/dist/esm/time/index.js.map +1 -1
- package/dist/esm/time/session.d.ts +26 -1
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +141 -16
- package/dist/esm/time/session.js.map +1 -1
- package/dist/esm/time/timezone.d.ts +6 -1
- package/dist/esm/time/timezone.d.ts.map +1 -1
- package/dist/esm/time/timezone.js +8 -2
- package/dist/esm/time/timezone.js.map +1 -1
- package/dist/esm/types/metadata.d.ts +28 -4
- package/dist/esm/types/metadata.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/array/index.ts +11 -3
- package/src/box/index.ts +8 -2
- package/src/callsite/index.ts +150 -100
- package/src/drawing/registry.ts +278 -5
- package/src/index.ts +44 -1
- package/src/input.ts +18 -1
- package/src/label/index.ts +4 -1
- package/src/line/index.ts +22 -64
- package/src/linefill/index.ts +20 -1
- package/src/math/index.ts +2 -0
- package/src/polyline/index.ts +3 -1
- package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
- package/src/runtime/index.ts +1 -0
- package/src/runtime/inputs.ts +16 -0
- package/src/runtime/types.ts +1 -1
- package/src/script/index.ts +355 -34
- package/src/security/other.ts +269 -0
- package/src/session/bars.ts +70 -6
- package/src/session/calendar.ts +60 -10
- package/src/session/context.ts +162 -0
- package/src/str/floatingdecimal.ts +268 -0
- package/src/str/index.ts +8 -2
- package/src/str/numberformat.ts +13 -6
- package/src/ta/index.ts +304 -248
- package/src/ta/limits.ts +74 -0
- package/src/ta/running-sum.ts +2 -0
- package/src/ta-series.ts +19 -16
- package/src/table/index.ts +4 -1
- package/src/time/index.ts +6 -3
- package/src/time/session.ts +137 -15
- package/src/time/timezone.ts +9 -2
- package/src/types/metadata.ts +28 -4
package/src/callsite/index.ts
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* - {@link whenCalled}: vectorized, when the bars of the calls are known in advance.
|
|
10
10
|
* - {@link crossover} / {@link crossunder} / {@link cross} / {@link barssince}: one stateful call site for bar
|
|
11
11
|
* loops, called only on the bars where the Pine call runs.
|
|
12
|
-
* - {@link
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* - {@link lowestByBar} / {@link highestByBar} / {@link lowestbars} / {@link highestbars} / {@link pivothigh} /
|
|
13
|
+
* {@link pivotlow}: `ta.lowest` / `ta.highest` / `ta.lowestbars` / `ta.highestbars` / `ta.pivothigh` /
|
|
14
|
+
* `ta.pivotlow` in a conditional block or a loop, also with a length that changes on each call. They keep the
|
|
15
|
+
* non-na values of their calls; {@link lowest} / {@link highest} are the same sites called with `(value, length)`.
|
|
16
|
+
* - {@link linreg}: `ta.linreg` in a conditional block, whose history is kept by bar (not by call).
|
|
16
17
|
* - {@link sum} / {@link sma}: `math.sum` / `ta.sma` in a `for` / `while` loop, called several times on one bar.
|
|
17
18
|
*
|
|
18
19
|
* Since PineScript v6, `and` / `or` are lazy: the right operand runs only when the left operand does not decide
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
import type { Source } from '../types/index.js';
|
|
46
47
|
import { linreg as linregSeries } from '../ta/index.js';
|
|
47
48
|
import { RunningSum, isNa } from '../ta/running-sum.js';
|
|
49
|
+
import { checkSumLength, checkWindowLength } from '../ta/limits.js';
|
|
48
50
|
import { seededRandom } from '../math/index.js';
|
|
49
51
|
|
|
50
52
|
/**
|
|
@@ -133,58 +135,63 @@ export function cross(): (a: number, b: number) => boolean {
|
|
|
133
135
|
};
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
/**
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
for (let b = i; b > i - length && b >= 0; b--) {
|
|
150
|
-
const v = values[b]!;
|
|
151
|
-
if (!Number.isNaN(v) && (Number.isNaN(extreme) || !better(extreme, v))) keep(v, b);
|
|
138
|
+
/** The values a ta.highest / ta.lowest / ta.pivothigh call site keeps: the non-na values of its calls, one per bar. */
|
|
139
|
+
class KeptCalls {
|
|
140
|
+
readonly values: number[] = [];
|
|
141
|
+
readonly bars: number[] = [];
|
|
142
|
+
|
|
143
|
+
/** Keeps a non-na value; a later call on the same bar (a loop) replaces the value of that bar. */
|
|
144
|
+
add(barIndex: number, value: number): void {
|
|
145
|
+
if (Number.isNaN(value)) return;
|
|
146
|
+
const last = this.values.length - 1;
|
|
147
|
+
if (last >= 0 && this.bars[last] === barIndex) this.values[last] = value;
|
|
148
|
+
else {
|
|
149
|
+
this.values.push(value);
|
|
150
|
+
this.bars.push(barIndex);
|
|
152
151
|
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Position of the best of the last `count` kept values (the newest one on ties), -1 when there is none. */
|
|
155
|
+
best(count: number, isLowest: boolean): number {
|
|
156
|
+
const last = this.values.length - 1;
|
|
157
|
+
if (last < 0 || !(count >= 1)) return -1;
|
|
158
|
+
let best = last;
|
|
159
|
+
for (let k = last - 1; k >= 0 && k > last - count; k--) {
|
|
160
|
+
if (isLowest ? this.values[k]! < this.values[best]! : this.values[k]! > this.values[best]!) best = k;
|
|
161
161
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
return best;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** One `ta.lowest` / `ta.highest` / `ta.lowestbars` / `ta.highestbars` call site; rules in {@link lowestByBar}. */
|
|
167
|
+
function extremeByBarSite(
|
|
168
|
+
isLowest: boolean,
|
|
169
|
+
offsets: boolean
|
|
170
|
+
): (barIndex: number, value: number, length: number) => number {
|
|
171
|
+
const kept = new KeptCalls();
|
|
172
|
+
const name = (isLowest ? 'lowest' : 'highest') + (offsets ? 'bars' : '');
|
|
173
|
+
return (barIndex, value, length) => {
|
|
174
|
+
checkWindowLength(name, length, barIndex);
|
|
175
|
+
kept.add(barIndex, value);
|
|
176
|
+
const len = Math.trunc(length);
|
|
177
|
+
if (barIndex < len - 1) return NaN;
|
|
178
|
+
const best = kept.best(len, isLowest);
|
|
179
|
+
if (best < 0) return NaN;
|
|
180
|
+
return offsets ? -(barIndex - kept.bars[best]!) : kept.values[best]!;
|
|
174
181
|
};
|
|
175
182
|
}
|
|
176
183
|
|
|
184
|
+
/** {@link extremeByBarSite} called with `(value, length, barIndex?)`: without a bar index, every call is a new bar. */
|
|
185
|
+
function extremeSite(isLowest: boolean): (value: number, length: number, barIndex?: number) => number {
|
|
186
|
+
const site = extremeByBarSite(isLowest, false);
|
|
187
|
+
let calls = 0;
|
|
188
|
+
return (value, length, barIndex) => site(barIndex ?? calls++, value, length);
|
|
189
|
+
}
|
|
190
|
+
|
|
177
191
|
/**
|
|
178
|
-
* One `ta.lowest(source, length)` call site with
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* PineScript rules (measured with a length that changes on each bar):
|
|
182
|
-
* - the extreme is kept with its bar; a new value that passes it replaces it; when the kept extreme is `length`
|
|
183
|
-
* calls old, the last `length` values are scanned again (na skipped, the oldest wins a tie)
|
|
184
|
-
* - a length smaller than on the previous call: the last `length` values are scanned again
|
|
185
|
-
* - a length larger than on the previous call: the values that enter the window at its old end are compared with the
|
|
186
|
-
* extreme; when one of them is na, the extreme restarts from the current value (the values in between are not read)
|
|
187
|
-
* - an na value gives na and resets the extreme; na before call `length - 1`
|
|
192
|
+
* One `ta.lowest(source, length)` call site, called with `(value, length, barIndex?)`: the same site as
|
|
193
|
+
* {@link lowestByBar} (same rules). Pass the bar index when the call does not run on every bar; without it every
|
|
194
|
+
* call counts as one bar, which is right for a call on every bar (for example with a length that changes).
|
|
188
195
|
*
|
|
189
196
|
* @example
|
|
190
197
|
* ```typescript
|
|
@@ -193,62 +200,34 @@ function extremeSite(isLowest: boolean): (value: number, length: number) => numb
|
|
|
193
200
|
* const out = closes.map((_, i) => lowPrice(i > 0 ? closes[i - 1]! : NaN, lookback[i]!));
|
|
194
201
|
* ```
|
|
195
202
|
*/
|
|
196
|
-
export function lowest(): (value: number, length: number) => number {
|
|
203
|
+
export function lowest(): (value: number, length: number, barIndex?: number) => number {
|
|
197
204
|
return extremeSite(true);
|
|
198
205
|
}
|
|
199
206
|
|
|
200
207
|
/**
|
|
201
|
-
* One `ta.highest(source, length)` call site with
|
|
202
|
-
*
|
|
208
|
+
* One `ta.highest(source, length)` call site, called with `(value, length, barIndex?)`. Same rules as
|
|
209
|
+
* {@link lowest}.
|
|
203
210
|
*/
|
|
204
|
-
export function highest(): (value: number, length: number) => number {
|
|
211
|
+
export function highest(): (value: number, length: number, barIndex?: number) => number {
|
|
205
212
|
return extremeSite(false);
|
|
206
213
|
}
|
|
207
214
|
|
|
208
|
-
/** One `ta.lowest` / `ta.highest` call site whose history is kept by bar; rules in {@link lowestByBar}. */
|
|
209
|
-
function extremeByBarSite(isLowest: boolean): (barIndex: number, value: number, length: number) => number {
|
|
210
|
-
const better = isLowest ? (a: number, b: number) => a < b : (a: number, b: number) => a > b;
|
|
211
|
-
let ring: number[] = [];
|
|
212
|
-
let extreme = NaN;
|
|
213
|
-
let extremeAt = -1;
|
|
214
|
-
return (barIndex, value, length) => {
|
|
215
|
-
const len = Math.floor(length);
|
|
216
|
-
if (ring.length !== len + 1) {
|
|
217
|
-
ring = new Array<number>(len + 1).fill(0);
|
|
218
|
-
extreme = NaN;
|
|
219
|
-
}
|
|
220
|
-
ring[barIndex % (len + 1)] = value;
|
|
221
|
-
if (Number.isNaN(extreme) || better(value, extreme)) {
|
|
222
|
-
extreme = value;
|
|
223
|
-
extremeAt = barIndex;
|
|
224
|
-
} else if (barIndex - extremeAt >= len) {
|
|
225
|
-
extreme = NaN;
|
|
226
|
-
for (let b = barIndex - len + 1; b <= barIndex; b++) {
|
|
227
|
-
const v = ring[b % (len + 1)]!;
|
|
228
|
-
if (!Number.isNaN(v) && (Number.isNaN(extreme) || better(v, extreme))) {
|
|
229
|
-
extreme = v;
|
|
230
|
-
extremeAt = b;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return barIndex < len - 1 ? NaN : extreme;
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
215
|
/**
|
|
239
|
-
* One `ta.lowest(source, length)` call site in a conditional block
|
|
240
|
-
*
|
|
216
|
+
* One `ta.lowest(source, length)` call site in a conditional block or a loop, called with
|
|
217
|
+
* `(barIndex, value, length)` on the bars (and loop turns) where the Pine call runs. The length can change from one
|
|
218
|
+
* call to the next.
|
|
241
219
|
*
|
|
242
|
-
* PineScript rules (measured
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* -
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* -
|
|
220
|
+
* PineScript rules (measured in `if` blocks, ternaries, user functions and `for` loops, with constant lengths and
|
|
221
|
+
* lengths that change, on sources with and without na):
|
|
222
|
+
* - the call site keeps the non-na values it received, each with its bar; nothing is kept for a bar without a call
|
|
223
|
+
* or for an na value
|
|
224
|
+
* - the result is na while `barIndex < length - 1` (by bar, not by call) and while no value was received
|
|
225
|
+
* - otherwise it is the lowest of the last `length` kept values, or of all of them when there are fewer: the first
|
|
226
|
+
* call after bar `length - 1` gives its own value. A call with an na value also gives this result
|
|
227
|
+
* - in a loop (several calls on one bar), a bar has one kept value: each call replaces the value of the earlier
|
|
228
|
+
* calls of that bar, so a call gives the lowest of its value and the values of the bars before
|
|
250
229
|
*
|
|
251
|
-
*
|
|
230
|
+
* Called on every bar, it equals `ta.lowest`.
|
|
252
231
|
*
|
|
253
232
|
* @example
|
|
254
233
|
* ```typescript
|
|
@@ -258,16 +237,86 @@ function extremeByBarSite(isLowest: boolean): (barIndex: number, value: number,
|
|
|
258
237
|
* ```
|
|
259
238
|
*/
|
|
260
239
|
export function lowestByBar(): (barIndex: number, value: number, length: number) => number {
|
|
261
|
-
return extremeByBarSite(true);
|
|
240
|
+
return extremeByBarSite(true, false);
|
|
262
241
|
}
|
|
263
242
|
|
|
264
243
|
/**
|
|
265
|
-
* One `ta.highest(source, length)` call site in a conditional block
|
|
266
|
-
* `(barIndex, value, length)`. Same rules as {@link lowestByBar}
|
|
267
|
-
* strictly higher).
|
|
244
|
+
* One `ta.highest(source, length)` call site in a conditional block or a loop, called with
|
|
245
|
+
* `(barIndex, value, length)`. Same rules as {@link lowestByBar}.
|
|
268
246
|
*/
|
|
269
247
|
export function highestByBar(): (barIndex: number, value: number, length: number) => number {
|
|
270
|
-
return extremeByBarSite(false);
|
|
248
|
+
return extremeByBarSite(false, false);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* One `ta.lowestbars(source, length)` call site in a conditional block or a loop, called with
|
|
253
|
+
* `(barIndex, value, length)`: the offset (0 or negative) from this bar to the bar of the lowest of the last `length`
|
|
254
|
+
* kept values, the newest one on equal values. The values are kept as in {@link lowestByBar}; the offset counts
|
|
255
|
+
* bars, not calls, so it can be below `-(length - 1)`.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* // Pine: off = cond ? ta.lowestbars(low, 10) : na
|
|
260
|
+
* const site = callsite.lowestbars();
|
|
261
|
+
* const off = lows.map((v, i) => (cond[i] ? site(i, v, 10) : NaN));
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
export function lowestbars(): (barIndex: number, value: number, length: number) => number {
|
|
265
|
+
return extremeByBarSite(true, true);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* One `ta.highestbars(source, length)` call site in a conditional block or a loop, called with
|
|
270
|
+
* `(barIndex, value, length)`. Same rules as {@link lowestbars}.
|
|
271
|
+
*/
|
|
272
|
+
export function highestbars(): (barIndex: number, value: number, length: number) => number {
|
|
273
|
+
return extremeByBarSite(false, true);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** One `ta.pivothigh` / `ta.pivotlow` call site; rules in {@link pivothigh}. */
|
|
277
|
+
function pivotSite(isLowest: boolean): (barIndex: number, value: number, leftbars: number, rightbars: number) => number {
|
|
278
|
+
const kept = new KeptCalls();
|
|
279
|
+
return (barIndex, value, leftbars, rightbars) => {
|
|
280
|
+
kept.add(barIndex, value);
|
|
281
|
+
const left = Math.trunc(leftbars);
|
|
282
|
+
const right = Math.trunc(rightbars);
|
|
283
|
+
if (!(barIndex >= left + right)) return NaN;
|
|
284
|
+
const best = kept.best(left + right + 1, isLowest);
|
|
285
|
+
return best >= 0 && kept.bars[best] === barIndex - right ? kept.values[best]! : NaN;
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* One `ta.pivothigh(source, leftbars, rightbars)` call site in a conditional block, called with
|
|
291
|
+
* `(barIndex, value, leftbars, rightbars)` on the bars where the Pine call runs. The lengths can change from one call
|
|
292
|
+
* to the next (the lengths of the current call are used).
|
|
293
|
+
*
|
|
294
|
+
* PineScript rules (measured in `if` blocks and ternaries, and on sources with na):
|
|
295
|
+
* - the call site keeps the non-na values it received, each with its bar
|
|
296
|
+
* - on a call it takes the last `leftbars + rightbars + 1` kept values (all of them when there are fewer) and finds
|
|
297
|
+
* the highest, the newest one on equal values
|
|
298
|
+
* - there is a pivot only when that value is on the bar `rightbars` BARS back (by bar index, not by call); the
|
|
299
|
+
* result is its value, na otherwise and before bar `leftbars + rightbars`
|
|
300
|
+
*
|
|
301
|
+
* Called on every bar, it equals `ta.pivothigh`.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```typescript
|
|
305
|
+
* // Pine: ph = useFilter ? ta.pivothigh(src, 5, 5) : na
|
|
306
|
+
* const site = callsite.pivothigh();
|
|
307
|
+
* const ph = src.map((v, i) => (useFilter[i] ? site(i, v, 5, 5) : NaN));
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
export function pivothigh(): (barIndex: number, value: number, leftbars: number, rightbars: number) => number {
|
|
311
|
+
return pivotSite(false);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* One `ta.pivotlow(source, leftbars, rightbars)` call site in a conditional block, called with
|
|
316
|
+
* `(barIndex, value, leftbars, rightbars)`. Same rules as {@link pivothigh} (the lowest value).
|
|
317
|
+
*/
|
|
318
|
+
export function pivotlow(): (barIndex: number, value: number, leftbars: number, rightbars: number) => number {
|
|
319
|
+
return pivotSite(true);
|
|
271
320
|
}
|
|
272
321
|
|
|
273
322
|
/**
|
|
@@ -315,6 +364,7 @@ function loopSumSite(
|
|
|
315
364
|
let pending = false;
|
|
316
365
|
return (barIndex, value, length) => {
|
|
317
366
|
const len = Math.floor(length);
|
|
367
|
+
checkSumLength(name === 'sum' ? 'math.sum' : `ta.${name}`, len, barIndex);
|
|
318
368
|
if (!(len >= 1)) throw new Error(`callsite.${name}: length must be at least 1 (got ${length})`);
|
|
319
369
|
if (barIndex !== lastBar) {
|
|
320
370
|
if (pending) {
|
package/src/drawing/registry.ts
CHANGED
|
@@ -8,6 +8,42 @@
|
|
|
8
8
|
* - when a creation brings the count above `max_*_count + 5`, the oldest objects are deleted until
|
|
9
9
|
* `max_*_count` remain (default 50). Copies count as creations. Linefills follow the same rule with
|
|
10
10
|
* `max_lines_count`; lines deleted by this rule do not delete their linefills
|
|
11
|
+
* - the object that was just created is never deleted by this rule
|
|
12
|
+
* - an object that a `var` variable of the drawing type refers to is never deleted by this rule, and it still counts
|
|
13
|
+
* (measured for lines, labels and boxes: with 9 held boxes and a maximum of 50, 9 held + 46 others stay; with 8
|
|
14
|
+
* held lines and a maximum of 5, the 8 and the newest line stay). The protection starts on the bar after the
|
|
15
|
+
* assignment: 12 `var` lines created on the first bar with a maximum of 5 lose the first 6. Only
|
|
16
|
+
* the variable itself protects: an object in a `var` array, in a field of a user type or in a variable that got
|
|
17
|
+
* another object or `na` later is deleted like any other. JavaScript has no `var` declaration to observe, so the
|
|
18
|
+
* script says it: {@link varDrawing} (a variable), or {@link holdDrawing} / {@link releaseDrawing}
|
|
19
|
+
* - linefills have the limit of `max_lines_count` with another rule for the held ones (measured on one script, two
|
|
20
|
+
* long runs): a fill is skipped when a `var` variable holds it or released it less than 5,000 bars ago (the bar
|
|
21
|
+
* of {@link setDrawingBar}); when the count is still above the maximum + 5 after that, the oldest fills are
|
|
22
|
+
* deleted until the maximum remain, the held ones too
|
|
23
|
+
* - the script deletes count (`line.delete`, `label.delete`, ...): each kind has a number `P` of deleted objects. A
|
|
24
|
+
* delete adds 1; a creation first sets `P` to 0 when it is 5 or more (3 or more for polylines)
|
|
25
|
+
* - polylines use `P` on every bar: a creation cuts when the count is above `max_polylines_count + 5 + P`, down to
|
|
26
|
+
* the maximum + `P`, and the polylines of the cut are added to `P`. A script that deletes and creates one polyline
|
|
27
|
+
* per bar therefore sees the cut 0, 1 or 2 bars late
|
|
28
|
+
* - lines, labels and boxes use `P` on the last bar only, with its value at the start of that bar: there the cut is
|
|
29
|
+
* above the maximum + 5 + `P`, down to the maximum + `P` (up to 4 more objects)
|
|
30
|
+
* - the last bar has rules of its own (measured from the count after each creation, on a closed and on an open last
|
|
31
|
+
* bar): a cut deletes the objects of the bars before first, at most those (a line takes its linefills with it);
|
|
32
|
+
* once none is left, a cut deletes objects of the last bar as on any bar. With 10 lines and a maximum of 10, 13
|
|
33
|
+
* lines on the last bar leave these 13: the cut at the 16th line deletes the 10 old ones only. Lines, labels and
|
|
34
|
+
* boxes: equal to PineScript, count after every operation and objects alive at the end, on 72 of 72 runs checked
|
|
35
|
+
* after the rule was written (and 218 of 218 end states before)
|
|
36
|
+
* - polylines on the last bar: the `P` carried from the bar before is counted twice (the first creation adds it once
|
|
37
|
+
* more, unless it sets `P` to 0), later creations do not set `P` to 0, and the polylines of the bars before that a
|
|
38
|
+
* cut deletes are added to `P`. Once none is
|
|
39
|
+
* left, `P` is at least the number of polylines of the bars before deleted on the last bar (by the count or by
|
|
40
|
+
* the script) and a cut deletes polylines of the last bar: a script that deletes its 100 polylines and creates 200
|
|
41
|
+
* on the last bar keeps them all (#176). Equal to PineScript for the polylines alive at the end on 271 of
|
|
42
|
+
* 281 probe runs; the count inside the bar can differ when the script deletes polylines of the last bar
|
|
43
|
+
*
|
|
44
|
+
* - a bar index (`xloc.bar_index`) more than 10,000 bars before the current bar stops the script (runtime error
|
|
45
|
+
* RE10026) in `line.new`, `box.new`, `label.new`, `polyline.new` and the setters of x. The current bar is the one
|
|
46
|
+
* of `eachBar` in a script run; a port with its own bar loop gives it with {@link setDrawingBar}
|
|
11
47
|
*
|
|
12
48
|
* The script API (`executeScript`) resets the registry at the start of every run and applies the
|
|
13
49
|
* `max_*_count` values of `indicator()`. Outside a script run the registry is global to the module.
|
|
@@ -50,6 +86,169 @@ let limits: Required<DrawingLimits> = {
|
|
|
50
86
|
polyline: DEFAULT_MAX_COUNT,
|
|
51
87
|
};
|
|
52
88
|
const deleted = new WeakSet<object>();
|
|
89
|
+
/** Objects held by a `var` variable, with the number of variables that hold them. */
|
|
90
|
+
let held = new WeakMap<object, number>();
|
|
91
|
+
/** Bar on which the last `var` variable released an object (linefill rule). */
|
|
92
|
+
let releasedAt = new WeakMap<object, number>();
|
|
93
|
+
/** Bars during which a released linefill is still skipped by the maximum count. */
|
|
94
|
+
const LINEFILL_RELEASE_BARS = 5000;
|
|
95
|
+
/** Bar on which a `var` variable got an object: the maximum count skips it from the next bar on. */
|
|
96
|
+
let heldAt = new WeakMap<object, number>();
|
|
97
|
+
/** Kinds with a maximum count of their own. */
|
|
98
|
+
type CountedKind = 'line' | 'label' | 'box' | 'polyline';
|
|
99
|
+
const COUNTED_KINDS: readonly CountedKind[] = ['line', 'label', 'box', 'polyline'];
|
|
100
|
+
const isCounted = (kind: DrawingKind): kind is CountedKind => (COUNTED_KINDS as readonly DrawingKind[]).includes(kind);
|
|
101
|
+
/** `P` of the module notes: objects deleted by the script (for polylines also by the maximum count). */
|
|
102
|
+
let pending: Record<CountedKind, number> = { line: 0, label: 0, box: 0, polyline: 0 };
|
|
103
|
+
/** A creation sets `P` to 0 from this value on. */
|
|
104
|
+
const PENDING_RESET: Record<CountedKind, number> = { line: 5, label: 5, box: 5, polyline: 3 };
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Marks an object as held by a `var` variable (PineScript `var line l = ...` or `l := line.new(...)` on a `var`
|
|
108
|
+
* variable): the maximum count never deletes it, and it still counts. Returns the object.
|
|
109
|
+
*/
|
|
110
|
+
export function holdDrawing<T extends object>(obj: T): T {
|
|
111
|
+
if (!held.has(obj)) {
|
|
112
|
+
if (currentBar === null) heldAt.delete(obj);
|
|
113
|
+
else heldAt.set(obj, currentBar);
|
|
114
|
+
}
|
|
115
|
+
held.set(obj, (held.get(obj) ?? 0) + 1);
|
|
116
|
+
return obj;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The `var` variable no longer refers to the object (it got another object or `na`). */
|
|
120
|
+
export function releaseDrawing(obj: object | null | undefined): void {
|
|
121
|
+
if (!obj) return;
|
|
122
|
+
const count = held.get(obj) ?? 0;
|
|
123
|
+
if (count > 1) held.set(obj, count - 1);
|
|
124
|
+
else if (held.delete(obj) && currentBar !== null) releasedAt.set(obj, currentBar);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Held by a `var` variable since a bar before the current one (or outside a bar loop). */
|
|
128
|
+
function heldBefore(obj: object): boolean {
|
|
129
|
+
if (!held.has(obj)) return false;
|
|
130
|
+
const bar = heldAt.get(obj);
|
|
131
|
+
return bar === undefined || currentBar === null || bar < currentBar;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Linefill rule: held, or released less than 5,000 bars ago. */
|
|
135
|
+
function fillProtected(obj: object): boolean {
|
|
136
|
+
if (held.has(obj)) return true;
|
|
137
|
+
const bar = releasedAt.get(obj);
|
|
138
|
+
return bar !== undefined && currentBar !== null && currentBar - bar < LINEFILL_RELEASE_BARS;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** A `var` variable of a drawing type: see {@link varDrawing}. */
|
|
142
|
+
export interface DrawingVariable<T extends object> {
|
|
143
|
+
/** The object of the variable; setting it holds the new object and releases the one before */
|
|
144
|
+
value: T | undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* A PineScript `var` variable of a drawing type (`var line l = na`). The object it refers to is not deleted by the
|
|
149
|
+
* maximum count; assigning another object (or `undefined` for `na`) releases the one before.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* // Pine: var line level = na
|
|
154
|
+
* // level := line.new(bar_index, close, bar_index + 1, close)
|
|
155
|
+
* const level = varDrawing<Line>();
|
|
156
|
+
* level.value = line.new(i, close, i + 1, close);
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export function varDrawing<T extends object>(initial?: T): DrawingVariable<T> {
|
|
160
|
+
let current: T | undefined = initial ? holdDrawing(initial) : undefined;
|
|
161
|
+
return {
|
|
162
|
+
get value() {
|
|
163
|
+
return current;
|
|
164
|
+
},
|
|
165
|
+
set value(next: T | undefined) {
|
|
166
|
+
if (next === current) return;
|
|
167
|
+
releaseDrawing(current);
|
|
168
|
+
current = next ? holdDrawing(next) : undefined;
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Largest distance, in bars, of a bar index before the current bar (PineScript runtime error RE10026 above it). */
|
|
174
|
+
export const MAX_DRAWING_BARS_BACK = 10000;
|
|
175
|
+
/** Largest distance, in bars, of a bar index after the last bar of the data (PineScript runtime error RE10020 above it). */
|
|
176
|
+
export const MAX_DRAWING_BARS_FORWARD = 500;
|
|
177
|
+
let currentBar: number | null = null;
|
|
178
|
+
let lastBarIndex: number | null = null;
|
|
179
|
+
|
|
180
|
+
/** Kinds whose allowance on the last bar is the `P` they had when the bar started. */
|
|
181
|
+
type AllowanceKind = 'line' | 'label' | 'box';
|
|
182
|
+
const isAllowanceKind = (kind: DrawingKind): kind is AllowanceKind => kind === 'line' || kind === 'label' || kind === 'box';
|
|
183
|
+
|
|
184
|
+
/** State of the last bar (see the module notes). */
|
|
185
|
+
interface LastBar {
|
|
186
|
+
bar: number;
|
|
187
|
+
/** The objects alive when the bar started (the objects of the bars before) */
|
|
188
|
+
old: Set<object>;
|
|
189
|
+
/** `P` of lines, labels and boxes when the bar started: their allowance for the whole bar */
|
|
190
|
+
allowance: Record<AllowanceKind, number>;
|
|
191
|
+
/** `P` of polylines when the bar started */
|
|
192
|
+
carried: number;
|
|
193
|
+
/** A polyline was created on this bar */
|
|
194
|
+
polyline: boolean;
|
|
195
|
+
/** Polylines of the bars before deleted on this bar (by the count or by the script) */
|
|
196
|
+
oldPolylinesGone: number;
|
|
197
|
+
}
|
|
198
|
+
let lastBar: LastBar | null = null;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Tells the drawing functions which bar is being calculated (`bar_index`), so that a bar index more than 10,000
|
|
202
|
+
* bars back throws as in PineScript; `null` turns the check off. `eachBar` of the script API calls it.
|
|
203
|
+
*
|
|
204
|
+
* The bar also dates the objects held by a `var` variable (protected from the next bar on). `last` says that it is
|
|
205
|
+
* the last bar of the data, which has rules of its own for the maximum count. `lastIndex` is the bar index of the
|
|
206
|
+
* last bar of the data (PineScript `last_bar_index`): with it, a bar index more than 500 bars after it throws, also
|
|
207
|
+
* on the bars before the last one (RE10020).
|
|
208
|
+
*/
|
|
209
|
+
export function setDrawingBar(barIndex: number | null, last: boolean = false, lastIndex?: number): void {
|
|
210
|
+
if (lastBar && !(last && barIndex === lastBar.bar)) lastBar = null;
|
|
211
|
+
currentBar = barIndex;
|
|
212
|
+
lastBarIndex = barIndex === null ? null : (lastIndex ?? (last ? barIndex : null));
|
|
213
|
+
if (barIndex !== null && last && !lastBar) {
|
|
214
|
+
const start = (kind: AllowanceKind): number => (pending[kind] >= PENDING_RESET[kind] ? 0 : pending[kind]);
|
|
215
|
+
lastBar = {
|
|
216
|
+
bar: barIndex,
|
|
217
|
+
old: new Set<object>([...live.line, ...live.label, ...live.box, ...live.polyline]),
|
|
218
|
+
allowance: { line: start('line'), label: start('label'), box: start('box') },
|
|
219
|
+
carried: pending.polyline,
|
|
220
|
+
polyline: false,
|
|
221
|
+
oldPolylinesGone: 0,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Throws the PineScript runtime error RE10026 when `x` is a bar index more than 10,000 bars before the current bar,
|
|
228
|
+
* and RE10020 when it is more than 500 bars after the last bar of the data (when {@link setDrawingBar} knows it).
|
|
229
|
+
* `method` and `variable` are the names of the PineScript message ("line.new", "x1").
|
|
230
|
+
*
|
|
231
|
+
* Returns `x`, truncated when it is a bar index: PineScript's x is an int (a box from 5537.94 starts on bar 5537,
|
|
232
|
+
* #176).
|
|
233
|
+
*/
|
|
234
|
+
export function checkBarIndex(method: string, variable: string, x: number, xloc: string | undefined): number {
|
|
235
|
+
if ((xloc ?? 'bar_index') !== 'bar_index') return x;
|
|
236
|
+
x = Math.trunc(x);
|
|
237
|
+
if (currentBar === null) return x;
|
|
238
|
+
if (lastBarIndex !== null && x - lastBarIndex > MAX_DRAWING_BARS_FORWARD) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Error on bar ${currentBar}: Objects positioned using xloc.bar_index cannot be drawn further than ` +
|
|
241
|
+
`${MAX_DRAWING_BARS_FORWARD} bars into the future.`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (currentBar - x > MAX_DRAWING_BARS_BACK) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`Error on bar ${currentBar}: Bar index value of the \`${variable}\` argument (${x}) in \`${method}()\` is too far ` +
|
|
247
|
+
'from the current bar index. Try using `time` instead.'
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return x;
|
|
251
|
+
}
|
|
53
252
|
|
|
54
253
|
/** Numeric fields that become NaN when an object is deleted, so that its getters return `na`. */
|
|
55
254
|
const COORDINATES: { [K in DrawingKind]: Array<keyof DrawingTypes[K]> } = {
|
|
@@ -69,14 +268,77 @@ function markDeleted<K extends DrawingKind>(kind: K, obj: DrawingTypes[K], expli
|
|
|
69
268
|
}
|
|
70
269
|
}
|
|
71
270
|
|
|
271
|
+
/** Objects above the maximum that the rule allows for, besides the 5: `P` of the module notes. */
|
|
272
|
+
function allowance(kind: DrawingKind): number {
|
|
273
|
+
if (kind === 'polyline') return pending.polyline;
|
|
274
|
+
return lastBar && isAllowanceKind(kind) ? lastBar.allowance[kind] : 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The maximum count after the creation of `obj`: removes the objects to delete from `list` (the oldest first; the new
|
|
279
|
+
* object and an object held by a `var` variable are skipped and still count) and returns them.
|
|
280
|
+
*/
|
|
281
|
+
function cutOldest<K extends DrawingKind>(
|
|
282
|
+
kind: K,
|
|
283
|
+
list: Array<DrawingTypes[K]>,
|
|
284
|
+
obj: DrawingTypes[K],
|
|
285
|
+
only?: (old: object) => boolean
|
|
286
|
+
): Array<DrawingTypes[K]> {
|
|
287
|
+
if (kind === 'table') return []; // tables have no maximum count
|
|
288
|
+
const max = limits[kind === 'linefill' ? 'line' : (kind as keyof DrawingLimits)] + allowance(kind);
|
|
289
|
+
if (list.length <= max + SLACK) return [];
|
|
290
|
+
const kept: Array<DrawingTypes[K]> = [];
|
|
291
|
+
const cut: Array<DrawingTypes[K]> = [];
|
|
292
|
+
let excess = list.length - max;
|
|
293
|
+
for (const old of list) {
|
|
294
|
+
if (excess > 0 && old !== obj && (!only || only(old)) && !(kind === 'linefill' ? fillProtected(old) : heldBefore(old))) {
|
|
295
|
+
excess--;
|
|
296
|
+
cut.push(old);
|
|
297
|
+
} else kept.push(old);
|
|
298
|
+
}
|
|
299
|
+
// linefills: still above the maximum + 5, the oldest go, the held ones too
|
|
300
|
+
if (kind === 'linefill' && kept.length > max + SLACK) cut.push(...kept.splice(0, kept.length - max));
|
|
301
|
+
list.length = 0;
|
|
302
|
+
list.push(...kept);
|
|
303
|
+
return cut;
|
|
304
|
+
}
|
|
305
|
+
|
|
72
306
|
/** Records a new object and deletes the oldest objects of its kind when the count is too high. */
|
|
73
307
|
export function register<K extends DrawingKind>(kind: K, obj: DrawingTypes[K]): DrawingTypes[K] {
|
|
74
308
|
const list = live[kind];
|
|
309
|
+
if (lastBar && isCounted(kind)) return registerLast(lastBar, kind, obj);
|
|
310
|
+
if (isCounted(kind) && pending[kind] >= PENDING_RESET[kind]) pending[kind] = 0;
|
|
75
311
|
list.push(obj);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
312
|
+
const cut = cutOldest(kind, list, obj);
|
|
313
|
+
if (kind === 'polyline') pending.polyline += cut.length;
|
|
314
|
+
for (const old of cut) markDeleted(kind, old, false);
|
|
315
|
+
return obj;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** An object created on the last bar (see the module notes). */
|
|
319
|
+
function registerLast<K extends DrawingKind>(bar: LastBar, kind: K, obj: DrawingTypes[K]): DrawingTypes[K] {
|
|
320
|
+
if (kind === 'polyline' && !bar.polyline) {
|
|
321
|
+
bar.polyline = true;
|
|
322
|
+
if (pending.polyline >= PENDING_RESET.polyline) pending.polyline = 0;
|
|
323
|
+
else pending.polyline += bar.carried; // counted twice
|
|
324
|
+
}
|
|
325
|
+
const list = live[kind];
|
|
326
|
+
list.push(obj);
|
|
327
|
+
const isOld = (old: object): boolean => bar.old.has(old);
|
|
328
|
+
if (list.some((old) => old !== obj && isOld(old) && !heldBefore(old))) {
|
|
329
|
+
// the objects of the bars before only; a line takes its linefills with it
|
|
330
|
+
const cut = cutOldest(kind, list, obj, isOld);
|
|
331
|
+
if (kind === 'polyline') {
|
|
332
|
+
pending.polyline += cut.length;
|
|
333
|
+
bar.oldPolylinesGone += cut.length;
|
|
334
|
+
}
|
|
335
|
+
for (const old of cut) markDeleted(kind, old, true);
|
|
336
|
+
} else {
|
|
337
|
+
// the polylines of the bars before deleted on this bar count before the cut (#176)
|
|
338
|
+
if (kind === 'polyline' && pending.polyline < bar.oldPolylinesGone) pending.polyline = bar.oldPolylinesGone;
|
|
339
|
+
const cut = cutOldest(kind, list, obj);
|
|
340
|
+
if (kind === 'polyline' && cut.length > 0) pending.polyline = bar.oldPolylinesGone;
|
|
341
|
+
for (const old of cut) markDeleted(kind, old, false);
|
|
80
342
|
}
|
|
81
343
|
return obj;
|
|
82
344
|
}
|
|
@@ -85,7 +347,11 @@ export function register<K extends DrawingKind>(kind: K, obj: DrawingTypes[K]):
|
|
|
85
347
|
export function remove<K extends DrawingKind>(kind: K, obj: DrawingTypes[K]): void {
|
|
86
348
|
const list = live[kind];
|
|
87
349
|
const index = list.indexOf(obj);
|
|
88
|
-
if (index !== -1)
|
|
350
|
+
if (index !== -1) {
|
|
351
|
+
list.splice(index, 1);
|
|
352
|
+
if (isCounted(kind)) pending[kind]++;
|
|
353
|
+
if (kind === 'polyline' && lastBar?.old.has(obj)) lastBar.oldPolylinesGone++;
|
|
354
|
+
}
|
|
89
355
|
if (!deleted.has(obj)) markDeleted(kind, obj, true);
|
|
90
356
|
}
|
|
91
357
|
|
|
@@ -102,6 +368,13 @@ export function all<K extends DrawingKind>(kind: K): Array<DrawingTypes[K]> {
|
|
|
102
368
|
/** Forgets all objects (without deleting them) and sets the maximum counts (default 50). */
|
|
103
369
|
export function resetDrawings(newLimits: DrawingLimits = {}): void {
|
|
104
370
|
for (const kind of Object.keys(live) as DrawingKind[]) live[kind].length = 0;
|
|
371
|
+
held = new WeakMap();
|
|
372
|
+
releasedAt = new WeakMap();
|
|
373
|
+
heldAt = new WeakMap();
|
|
374
|
+
pending = { line: 0, label: 0, box: 0, polyline: 0 };
|
|
375
|
+
currentBar = null;
|
|
376
|
+
lastBarIndex = null;
|
|
377
|
+
lastBar = null;
|
|
105
378
|
limits = {
|
|
106
379
|
line: newLimits.line ?? DEFAULT_MAX_COUNT,
|
|
107
380
|
label: newLimits.label ?? DEFAULT_MAX_COUNT,
|