@hoox-sh/pynets 0.1.0 → 0.3.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/CHANGELOG.md +59 -0
- package/README.md +20 -9
- package/dist/browser.js +95 -34
- package/dist/index.d.ts +1 -1
- package/dist/index.js +23002 -12427
- package/package.json +1 -1
- package/src/ast/builder.ts +235 -12
- package/src/ast/nodes.ts +114 -4
- package/src/ast/unparse.ts +192 -17
- package/src/cli/rich.ts +4 -3
- package/src/cli.ts +29 -7
- package/src/generated/PinescriptLexer.interp +5 -1
- package/src/generated/PinescriptLexer.tokens +97 -95
- package/src/generated/PinescriptLexer.ts +512 -502
- package/src/generated/PinescriptParser.interp +8 -1
- package/src/generated/PinescriptParser.tokens +97 -95
- package/src/generated/PinescriptParser.ts +1907 -1287
- package/src/generated/PinescriptParserVisitor.ts +36 -1
- package/src/index.ts +22 -0
- package/src/runtime/array.ts +238 -68
- package/src/runtime/compile/collections.ts +492 -0
- package/src/runtime/compile/draw.ts +181 -0
- package/src/runtime/compile/emit.ts +1421 -0
- package/src/runtime/compile/emit_call.ts +1796 -0
- package/src/runtime/compile/emit_udf.ts +326 -0
- package/src/runtime/compile/engine.ts +265 -0
- package/src/runtime/compile/helpers.ts +140 -0
- package/src/runtime/compile/index.ts +33 -0
- package/src/runtime/compile/runtime.ts +357 -0
- package/src/runtime/compile/strategy.ts +250 -0
- package/src/runtime/compile/types.ts +127 -0
- package/src/runtime/compile/udt_runtime.ts +50 -0
- package/src/runtime/drawings.ts +405 -8
- package/src/runtime/interpret.ts +2387 -281
- package/src/runtime/matrix.ts +371 -42
- package/src/runtime/strategy.ts +695 -33
- package/src/runtime/ta.ts +654 -20
- package/src/runtime/timeframe.ts +199 -0
package/src/runtime/interpret.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* arrays, color/str, strategy events + position series, input*, math.*,
|
|
8
8
|
* request.security, UDF FunctionDef, matrix.*, extra TA, drawings/alert,
|
|
9
9
|
* derived prices, calendar, Pine na-compare.
|
|
10
|
-
* Not a port of runtime/host.py — same public envelope
|
|
10
|
+
* Not a port of runtime/host.py — same public envelope.
|
|
11
|
+
* Compile is JS emit in ./compile/ (mode compile|auto).
|
|
11
12
|
*/
|
|
12
13
|
import { parse } from "../ast/helper.ts";
|
|
13
14
|
import type {
|
|
@@ -15,6 +16,7 @@ import type {
|
|
|
15
16
|
Arg,
|
|
16
17
|
Assign,
|
|
17
18
|
Attribute,
|
|
19
|
+
AugAssign,
|
|
18
20
|
BoolOp,
|
|
19
21
|
Call,
|
|
20
22
|
Conditional,
|
|
@@ -24,6 +26,7 @@ import type {
|
|
|
24
26
|
TypeDef,
|
|
25
27
|
EnumDef,
|
|
26
28
|
Import,
|
|
29
|
+
Once,
|
|
27
30
|
Param,
|
|
28
31
|
ReAssign,
|
|
29
32
|
Script,
|
|
@@ -58,13 +61,18 @@ import {
|
|
|
58
61
|
import {
|
|
59
62
|
timeframeFromSeconds,
|
|
60
63
|
timeframeInSeconds,
|
|
64
|
+
timeframePeriodChanged,
|
|
61
65
|
timeframeIsDaily,
|
|
66
|
+
timeframeIsDwm,
|
|
67
|
+
timeframeIsHours,
|
|
62
68
|
timeframeIsIntraday,
|
|
69
|
+
timeframeIsMinutes,
|
|
63
70
|
timeframeIsMonthly,
|
|
71
|
+
timeframeIsSeconds,
|
|
64
72
|
timeframeIsWeekly,
|
|
65
73
|
timeframeMultiplier as tfMultiplier,
|
|
66
74
|
} from "./timeframe.ts";
|
|
67
|
-
import { DrawingBook, type DrawingEvent } from "./drawings.ts";
|
|
75
|
+
import { DrawingBook, type DrawingEvent, type DrawingKind } from "./drawings.ts";
|
|
68
76
|
import { LogBook, formatLogParts, runtimeError, type LogRecord } from "./log.ts";
|
|
69
77
|
import { MemoryProvider, type BarProvider } from "./provider.ts";
|
|
70
78
|
import {
|
|
@@ -137,10 +145,24 @@ import {
|
|
|
137
145
|
strTrim,
|
|
138
146
|
strUpper,
|
|
139
147
|
} from "./str.ts";
|
|
140
|
-
import {
|
|
148
|
+
import {
|
|
149
|
+
StrategyBook,
|
|
150
|
+
isStrategyCashAmount,
|
|
151
|
+
strategyCashAmount,
|
|
152
|
+
type BrokerSettings,
|
|
153
|
+
type StrategyCashAmount,
|
|
154
|
+
type StrategyEvent,
|
|
155
|
+
type StrategySummary,
|
|
156
|
+
} from "./strategy.ts";
|
|
141
157
|
import { PineMap } from "./map.ts";
|
|
142
158
|
import { PineMatrix } from "./matrix.ts";
|
|
143
|
-
import { TaEngine } from "./ta.ts";
|
|
159
|
+
import { TaEngine, type TaRecord } from "./ta.ts";
|
|
160
|
+
import {
|
|
161
|
+
CompileError,
|
|
162
|
+
compileEligible,
|
|
163
|
+
compileScript,
|
|
164
|
+
} from "./compile/index.ts";
|
|
165
|
+
import type { RuntimeMode } from "./compile/types.ts";
|
|
144
166
|
|
|
145
167
|
export type { DrawingEvent };
|
|
146
168
|
|
|
@@ -153,15 +175,28 @@ export interface OHLCVBar {
|
|
|
153
175
|
close?: number;
|
|
154
176
|
volume?: number;
|
|
155
177
|
time?: number;
|
|
178
|
+
time_close?: number;
|
|
156
179
|
}
|
|
157
180
|
|
|
158
181
|
export type InputOverrides = Record<string, number | string | boolean>;
|
|
159
182
|
|
|
183
|
+
/** One library source, same shape as Python `Runtime.run(..., libraries=[{...}])`. */
|
|
184
|
+
export type LibrarySourceSpec = {
|
|
185
|
+
namespace: string;
|
|
186
|
+
name: string;
|
|
187
|
+
version?: number;
|
|
188
|
+
source: string;
|
|
189
|
+
};
|
|
190
|
+
|
|
160
191
|
export interface RuntimeOptions {
|
|
161
192
|
inputs?: InputOverrides;
|
|
162
193
|
broker?: BrokerSettings;
|
|
163
194
|
timeframe?: string | null;
|
|
164
|
-
libraries?: LibraryRegistry;
|
|
195
|
+
libraries?: LibraryRegistry | LibrarySourceSpec[];
|
|
196
|
+
/** interpret (default) | compile (JS emit) | auto (compile, fallback interpret). */
|
|
197
|
+
mode?: RuntimeMode;
|
|
198
|
+
/** Wall-clock budget in seconds. Interpret only; checked every 32 bars. */
|
|
199
|
+
timeout_seconds?: number | null;
|
|
165
200
|
}
|
|
166
201
|
|
|
167
202
|
export interface RuntimeFill {
|
|
@@ -180,7 +215,9 @@ export interface RuntimeResult {
|
|
|
180
215
|
count: number;
|
|
181
216
|
script_name: string | null;
|
|
182
217
|
script_type: string;
|
|
183
|
-
mode: "interpret";
|
|
218
|
+
mode: "interpret" | "compile";
|
|
219
|
+
auto_backend?: "interpret" | "compile";
|
|
220
|
+
compile_fallback_reason?: string;
|
|
184
221
|
events?: StrategyEvent[];
|
|
185
222
|
fills?: RuntimeFill[];
|
|
186
223
|
drawings?: DrawingEvent[];
|
|
@@ -189,6 +226,8 @@ export interface RuntimeResult {
|
|
|
189
226
|
plot_data?: Record<string, { data: Array<{ value: number | null; time?: number }> }>;
|
|
190
227
|
/** Book-level strategy scalars when the script is a strategy (or any fills ran). */
|
|
191
228
|
strategy?: StrategySummary;
|
|
229
|
+
/** True when interpret stopped after `timeout_seconds`. */
|
|
230
|
+
timed_out?: boolean;
|
|
192
231
|
error?: string;
|
|
193
232
|
error_kind?: string;
|
|
194
233
|
}
|
|
@@ -196,8 +235,17 @@ export interface RuntimeResult {
|
|
|
196
235
|
/** Eval-only: Cell, tagged tuple/array, color, or strategy.long/short strings. */
|
|
197
236
|
type TupleVal = { __tuple: true; elts: Value[] };
|
|
198
237
|
type ArrayVal = { __array: true; elts: Value[] };
|
|
238
|
+
type ChartPointVal = {
|
|
239
|
+
__chartPoint: true;
|
|
240
|
+
index: number | null;
|
|
241
|
+
time: number | null;
|
|
242
|
+
x: number | null;
|
|
243
|
+
y: number | null;
|
|
244
|
+
price: number | null;
|
|
245
|
+
};
|
|
199
246
|
type Value =
|
|
200
247
|
| Cell
|
|
248
|
+
| boolean // Python True/False (e.g. `barmerge.*`); coerces to 1/0 via unwrap/valuesEq
|
|
201
249
|
| TupleVal
|
|
202
250
|
| ArrayVal
|
|
203
251
|
| Color
|
|
@@ -211,7 +259,10 @@ type Value =
|
|
|
211
259
|
| LibraryModule
|
|
212
260
|
| UdfDef
|
|
213
261
|
| EnumType
|
|
214
|
-
| EnumMember
|
|
262
|
+
| EnumMember
|
|
263
|
+
| TaRecord
|
|
264
|
+
| ChartPointVal
|
|
265
|
+
| StrategyCashAmount;
|
|
215
266
|
|
|
216
267
|
const LOOP_BREAK = Object.freeze({ __loop: "break" as const });
|
|
217
268
|
const LOOP_CONTINUE = Object.freeze({ __loop: "continue" as const });
|
|
@@ -283,6 +334,11 @@ interface Env {
|
|
|
283
334
|
barTimes: number[];
|
|
284
335
|
libraries: LibraryRegistry;
|
|
285
336
|
pendingExports: Map<string, Value>;
|
|
337
|
+
onceFired: WeakSet<object>;
|
|
338
|
+
/** Python `_max_bars_back_decls` — recorded only; evaluation is unbounded. */
|
|
339
|
+
maxBarsBackDecls: Array<{ var: Value; num: Value }>;
|
|
340
|
+
/** Python `_alert_fire_bars` keys for `alertcondition` once_per_bar. */
|
|
341
|
+
alertFireBars: Map<string, number>;
|
|
286
342
|
}
|
|
287
343
|
|
|
288
344
|
export class Runtime {
|
|
@@ -290,6 +346,8 @@ export class Runtime {
|
|
|
290
346
|
readonly broker: BrokerSettings;
|
|
291
347
|
readonly timeframe: string | null;
|
|
292
348
|
readonly libraries: LibraryRegistry;
|
|
349
|
+
readonly mode: RuntimeMode;
|
|
350
|
+
readonly timeout_seconds: number | null;
|
|
293
351
|
|
|
294
352
|
constructor(
|
|
295
353
|
public readonly symbol = "AAPL",
|
|
@@ -298,7 +356,9 @@ export class Runtime {
|
|
|
298
356
|
this.inputs = options?.inputs ?? {};
|
|
299
357
|
this.broker = options?.broker ?? {};
|
|
300
358
|
this.timeframe = options?.timeframe ?? null;
|
|
301
|
-
this.libraries = options?.libraries
|
|
359
|
+
this.libraries = resolveLibraries(options?.libraries);
|
|
360
|
+
this.mode = options?.mode ?? "interpret";
|
|
361
|
+
this.timeout_seconds = options?.timeout_seconds ?? null;
|
|
302
362
|
}
|
|
303
363
|
|
|
304
364
|
/** Store Pine source for `import namespace/name/version`. */
|
|
@@ -328,14 +388,24 @@ export class Runtime {
|
|
|
328
388
|
};
|
|
329
389
|
}
|
|
330
390
|
const extraOpts = extra && !isPlainInputs(extra) ? extra : undefined;
|
|
391
|
+
const mode = extraOpts?.mode ?? this.mode;
|
|
392
|
+
const inputs = mergeInputs(this.inputs, extra);
|
|
393
|
+
const host = {
|
|
394
|
+
symbol: this.symbol,
|
|
395
|
+
inputs,
|
|
396
|
+
broker: extraOpts?.broker ?? this.broker,
|
|
397
|
+
timeframe: extraOpts?.timeframe ?? this.timeframe,
|
|
398
|
+
libraries: extraOpts?.libraries != null ? resolveLibraries(extraOpts.libraries) : this.libraries,
|
|
399
|
+
timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds,
|
|
400
|
+
};
|
|
401
|
+
if (mode === "compile") {
|
|
402
|
+
return runCompiled(source, ohlcv, host);
|
|
403
|
+
}
|
|
404
|
+
if (mode === "auto") {
|
|
405
|
+
return runAuto(source, tree, ohlcv, host);
|
|
406
|
+
}
|
|
331
407
|
try {
|
|
332
|
-
return interpretTree(tree, ohlcv,
|
|
333
|
-
symbol: this.symbol,
|
|
334
|
-
inputs: mergeInputs(this.inputs, extra),
|
|
335
|
-
broker: extraOpts?.broker ?? this.broker,
|
|
336
|
-
timeframe: extraOpts?.timeframe ?? this.timeframe,
|
|
337
|
-
libraries: extraOpts?.libraries ?? this.libraries,
|
|
338
|
-
});
|
|
408
|
+
return interpretTree(tree, ohlcv, host);
|
|
339
409
|
} catch (err) {
|
|
340
410
|
return {
|
|
341
411
|
series: {},
|
|
@@ -434,6 +504,7 @@ export function interpretTree(
|
|
|
434
504
|
broker?: BrokerSettings;
|
|
435
505
|
timeframe?: string | null;
|
|
436
506
|
libraries?: LibraryRegistry;
|
|
507
|
+
timeout_seconds?: number | null;
|
|
437
508
|
},
|
|
438
509
|
): RuntimeResult {
|
|
439
510
|
const scriptNode = tree.kind === "Script" ? (tree as Script) : null;
|
|
@@ -469,12 +540,25 @@ export function interpretTree(
|
|
|
469
540
|
barTimes: ohlcv.map((b, i) => b.time ?? i * 60_000),
|
|
470
541
|
libraries: host?.libraries ?? new LibraryRegistry(),
|
|
471
542
|
pendingExports: new Map(),
|
|
543
|
+
onceFired: new WeakSet(),
|
|
544
|
+
maxBarsBackDecls: [],
|
|
545
|
+
alertFireBars: new Map(),
|
|
472
546
|
};
|
|
473
547
|
|
|
474
548
|
const n = ohlcv.length;
|
|
475
549
|
let runError: { error: string; error_kind: string } | undefined;
|
|
550
|
+
let timedOut = false;
|
|
551
|
+
let barsDone = n;
|
|
552
|
+
const timeoutSec = host?.timeout_seconds;
|
|
553
|
+
const deadline =
|
|
554
|
+
timeoutSec != null && Number.isFinite(timeoutSec) ? performance.now() + timeoutSec * 1000 : null;
|
|
476
555
|
try {
|
|
477
556
|
for (let i = 0; i < n; i++) {
|
|
557
|
+
if (deadline != null && (i & 31) === 0 && performance.now() > deadline) {
|
|
558
|
+
timedOut = true;
|
|
559
|
+
barsDone = i;
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
478
562
|
beginBar(env, ohlcv[i]!, i);
|
|
479
563
|
env.book.processPending(i, {
|
|
480
564
|
open: num(env.ctx.open) ?? undefined,
|
|
@@ -526,7 +610,7 @@ export function interpretTree(
|
|
|
526
610
|
series: plots,
|
|
527
611
|
plots: firstTitle ? (plots[firstTitle] ?? []) : [],
|
|
528
612
|
plot_meta,
|
|
529
|
-
count: n,
|
|
613
|
+
count: timedOut ? barsDone : n,
|
|
530
614
|
script_name,
|
|
531
615
|
script_type,
|
|
532
616
|
mode: "interpret",
|
|
@@ -539,6 +623,9 @@ export function interpretTree(
|
|
|
539
623
|
? { strategy: env.book.summary(num(env.ctx.close) ?? 0) }
|
|
540
624
|
: {}),
|
|
541
625
|
...(runError ?? {}),
|
|
626
|
+
...(timedOut
|
|
627
|
+
? { timed_out: true, error: "Script execution timed out", error_kind: "runtime" }
|
|
628
|
+
: {}),
|
|
542
629
|
};
|
|
543
630
|
}
|
|
544
631
|
|
|
@@ -582,9 +669,24 @@ function beginBar(env: Env, bar: OHLCVBar, i: number): void {
|
|
|
582
669
|
pushNamedSeries(env, "close", c);
|
|
583
670
|
pushNamedSeries(env, "volume", vol);
|
|
584
671
|
pushNamedSeries(env, "time", t);
|
|
672
|
+
// Explicit `time_close` wins. Otherwise Python's host: next bar open
|
|
673
|
+
// (0 counts as missing), and the last bar is this open + 1 day.
|
|
674
|
+
const explicitClose = bar.time_close;
|
|
675
|
+
let tClose = t;
|
|
676
|
+
if (typeof explicitClose === "number" && Number.isFinite(explicitClose)) {
|
|
677
|
+
tClose = explicitClose;
|
|
678
|
+
} else if (i + 1 < env.barCount) {
|
|
679
|
+
const next = env.barTimes[i + 1] ?? 0;
|
|
680
|
+
tClose = next !== 0 ? next : t;
|
|
681
|
+
} else {
|
|
682
|
+
tClose = Math.trunc(t) + 86_400_000;
|
|
683
|
+
}
|
|
684
|
+
pushNamedSeries(env, "time_close", tClose);
|
|
585
685
|
env.ctx.bar_index = i;
|
|
586
686
|
env.ctx.last_bar_index = env.barCount - 1;
|
|
587
687
|
env.ctx.last_bar_time = env.lastBarTime;
|
|
688
|
+
// Python `_builtin_timenow`: host ctx, else dataset last bar time (no wall-clock).
|
|
689
|
+
env.ctx.timenow = env.lastBarTime;
|
|
588
690
|
|
|
589
691
|
bindDerived(env, "hl2", (h + l) / 2);
|
|
590
692
|
bindDerived(env, "hlc3", (h + l + c) / 3);
|
|
@@ -623,17 +725,23 @@ function bindDerived(env: Env, id: string, value: Cell): void {
|
|
|
623
725
|
}
|
|
624
726
|
}
|
|
625
727
|
|
|
728
|
+
function unwrapSpecialize(node: expr): expr {
|
|
729
|
+
let cur = node;
|
|
730
|
+
while (cur.kind === "Specialize") cur = cur.value;
|
|
731
|
+
return cur;
|
|
732
|
+
}
|
|
733
|
+
|
|
626
734
|
function attrPath(node: expr): string | null {
|
|
627
|
-
|
|
628
|
-
if (
|
|
629
|
-
|
|
630
|
-
|
|
735
|
+
const n = unwrapSpecialize(node);
|
|
736
|
+
if (n.kind === "Name") return n.id;
|
|
737
|
+
if (n.kind === "Attribute") {
|
|
738
|
+
const base = attrPath(n.value);
|
|
739
|
+
return base ? `${base}.${n.attr}` : n.attr;
|
|
631
740
|
}
|
|
632
741
|
return null;
|
|
633
742
|
}
|
|
634
743
|
|
|
635
744
|
function callName(node: Call): string | null {
|
|
636
|
-
if (node.func.kind === "Name") return node.func.id;
|
|
637
745
|
return attrPath(node.func);
|
|
638
746
|
}
|
|
639
747
|
|
|
@@ -716,6 +824,8 @@ function callArg(
|
|
|
716
824
|
|
|
717
825
|
function defaultPlotTitle(fname: string): string {
|
|
718
826
|
if (fname === "hline") return "hline";
|
|
827
|
+
if (fname === "plotcandle") return "candles";
|
|
828
|
+
if (fname === "plotbar") return "bars";
|
|
719
829
|
if (fname === "plotshape") return "shape";
|
|
720
830
|
if (fname === "plotchar") return "char";
|
|
721
831
|
if (fname === "plotarrow") return "arrow";
|
|
@@ -872,6 +982,61 @@ function evalAssign(stmt: Assign, env: Env): Value {
|
|
|
872
982
|
return value;
|
|
873
983
|
}
|
|
874
984
|
|
|
985
|
+
function applyBinOp(kind: string, leftV: Value, rightV: Value): Value {
|
|
986
|
+
// Python _safe_op: any na operand propagates na, before the string-concat
|
|
987
|
+
// fast path — otherwise `"a" + na` would stringify to "ana" instead of na.
|
|
988
|
+
// Checked on the raw values: strings are valid Add operands (unwrap maps
|
|
989
|
+
// them to NA as non-cells).
|
|
990
|
+
if (leftV == null || rightV == null) return NA;
|
|
991
|
+
if (kind === "Add" && (typeof leftV === "string" || typeof rightV === "string")) {
|
|
992
|
+
return stringifyVal(leftV) + stringifyVal(rightV);
|
|
993
|
+
}
|
|
994
|
+
const l = unwrap(leftV);
|
|
995
|
+
const r = unwrap(rightV);
|
|
996
|
+
if (l == null || r == null) return NA;
|
|
997
|
+
switch (kind) {
|
|
998
|
+
case "Add":
|
|
999
|
+
return l + r;
|
|
1000
|
+
case "Sub":
|
|
1001
|
+
return l - r;
|
|
1002
|
+
case "Mult":
|
|
1003
|
+
return l * r;
|
|
1004
|
+
case "Div":
|
|
1005
|
+
return r === 0 ? NA : l / r;
|
|
1006
|
+
case "Mod":
|
|
1007
|
+
return r === 0 ? NA : l % r;
|
|
1008
|
+
case "BitAnd":
|
|
1009
|
+
return Math.trunc(l) & Math.trunc(r);
|
|
1010
|
+
case "BitOr":
|
|
1011
|
+
return Math.trunc(l) | Math.trunc(r);
|
|
1012
|
+
case "BitXor":
|
|
1013
|
+
return Math.trunc(l) ^ Math.trunc(r);
|
|
1014
|
+
case "LShift":
|
|
1015
|
+
return Math.trunc(l) << Math.trunc(r);
|
|
1016
|
+
case "RShift":
|
|
1017
|
+
return Math.trunc(l) >> Math.trunc(r);
|
|
1018
|
+
}
|
|
1019
|
+
return NA;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function evalAugAssign(stmt: AugAssign, env: Env): Value {
|
|
1023
|
+
if (stmt.target.kind === "Attribute") {
|
|
1024
|
+
const obj = evalExpr(stmt.target.value, env);
|
|
1025
|
+
const value = evalExpr(stmt.value, env);
|
|
1026
|
+
if (obj instanceof UdtInstance) {
|
|
1027
|
+
obj.set(stmt.target.attr, value);
|
|
1028
|
+
return value;
|
|
1029
|
+
}
|
|
1030
|
+
return NA;
|
|
1031
|
+
}
|
|
1032
|
+
if (stmt.target.kind !== "Name") return NA;
|
|
1033
|
+
const current = ctxGet(env, stmt.target.id);
|
|
1034
|
+
if (current === undefined) return NA;
|
|
1035
|
+
const result = applyBinOp(stmt.op.kind, current, evalExpr(stmt.value, env));
|
|
1036
|
+
bindName(env, stmt.target.id, result);
|
|
1037
|
+
return result;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
875
1040
|
function evalReAssign(stmt: ReAssign, env: Env): Value {
|
|
876
1041
|
if (stmt.target.kind === "Tuple") {
|
|
877
1042
|
const packed = evalExpr(stmt.value, env);
|
|
@@ -902,6 +1067,21 @@ function applyStrategyDecl(stmt: { kind: string; value?: expr | null }, env: Env
|
|
|
902
1067
|
if (comm != null) settings.commission = comm;
|
|
903
1068
|
if (slip != null) settings.slippage = slip;
|
|
904
1069
|
if (pyr != null) settings.pyramiding = Math.trunc(pyr);
|
|
1070
|
+
const apm = evalExpr(callArg(stmt.value.args, -1, ["avg_price_model"]), env);
|
|
1071
|
+
if (typeof apm === "string") settings.avg_price_model = apm;
|
|
1072
|
+
const lev = unwrap(evalExpr(callArg(stmt.value.args, -1, ["leverage"]), env));
|
|
1073
|
+
if (lev != null) settings.leverage = lev;
|
|
1074
|
+
const ml = unwrap(evalExpr(callArg(stmt.value.args, -1, ["margin_long"]), env));
|
|
1075
|
+
if (ml != null) settings.margin_long = ml;
|
|
1076
|
+
const ms = unwrap(evalExpr(callArg(stmt.value.args, -1, ["margin_short"]), env));
|
|
1077
|
+
if (ms != null) settings.margin_short = ms;
|
|
1078
|
+
const dqt = evalExpr(callArg(stmt.value.args, -1, ["default_qty_type"]), env);
|
|
1079
|
+
if (typeof dqt === "string") settings.default_qty_type = dqt;
|
|
1080
|
+
else if (isStrategyCashAmount(dqt)) settings.default_qty_type = "cash";
|
|
1081
|
+
const dqv = unwrap(evalExpr(callArg(stmt.value.args, -1, ["default_qty_value"]), env));
|
|
1082
|
+
if (dqv != null) settings.default_qty_value = dqv;
|
|
1083
|
+
const cur = evalExpr(callArg(stmt.value.args, -1, ["currency"]), env);
|
|
1084
|
+
if (typeof cur === "string" && cur !== "") settings.currency = cur;
|
|
905
1085
|
if (Object.keys(settings).length) env.book.configure(settings);
|
|
906
1086
|
const capital = unwrap(evalExpr(callArg(stmt.value.args, -1, ["initial_capital"]), env));
|
|
907
1087
|
if (capital != null && Number.isFinite(capital)) env.book.initialCapital = capital;
|
|
@@ -956,9 +1136,12 @@ function execStmt(
|
|
|
956
1136
|
if (stmt.kind === "ReAssign") {
|
|
957
1137
|
return evalReAssign(stmt as ReAssign, env);
|
|
958
1138
|
}
|
|
1139
|
+
if (stmt.kind === "AugAssign") {
|
|
1140
|
+
return evalAugAssign(stmt as AugAssign, env);
|
|
1141
|
+
}
|
|
959
1142
|
if (stmt.kind === "Break") throw LOOP_BREAK;
|
|
960
1143
|
if (stmt.kind === "Continue") throw LOOP_CONTINUE;
|
|
961
|
-
if (stmt.kind === "Switch" || stmt.kind === "While" || stmt.kind === "If") {
|
|
1144
|
+
if (stmt.kind === "Switch" || stmt.kind === "While" || stmt.kind === "If" || stmt.kind === "Once") {
|
|
962
1145
|
return evalExpr(stmt as unknown as expr, env);
|
|
963
1146
|
}
|
|
964
1147
|
if (stmt.kind !== "Expr" || !("value" in stmt) || stmt.value == null) return NA;
|
|
@@ -973,12 +1156,18 @@ function execStmt(
|
|
|
973
1156
|
fname === "plotchar" ||
|
|
974
1157
|
fname === "plotarrow" ||
|
|
975
1158
|
fname === "bgcolor" ||
|
|
976
|
-
fname === "barcolor"
|
|
1159
|
+
fname === "barcolor" ||
|
|
1160
|
+
fname === "plotcandle" ||
|
|
1161
|
+
fname === "plotbar"
|
|
977
1162
|
) {
|
|
978
1163
|
const plotArgs = asArgs(value.args);
|
|
1164
|
+
const ohlc = fname === "plotcandle" || fname === "plotbar";
|
|
979
1165
|
const title = resolvePlotTitle(plotArgs, fname);
|
|
980
1166
|
const key = uniquifyPlotKey(env, value, title);
|
|
981
|
-
|
|
1167
|
+
// Python primary column is close (arg 3), not open.
|
|
1168
|
+
const seriesExpr = ohlc
|
|
1169
|
+
? callArg(value.args, 3, ["close"])
|
|
1170
|
+
: (callArg(value.args, 0, ["series", "source"]) ?? plotArgs[0]?.value);
|
|
982
1171
|
const cell = unwrap(evalExpr(seriesExpr, env));
|
|
983
1172
|
if (!env.plots[key]) {
|
|
984
1173
|
env.plots[key] = [];
|
|
@@ -1002,7 +1191,10 @@ function evalExpr(node: expr | undefined, env: Env): Value {
|
|
|
1002
1191
|
switch (node.kind) {
|
|
1003
1192
|
case "Name": {
|
|
1004
1193
|
const v = ctxGet(env, node.id);
|
|
1005
|
-
|
|
1194
|
+
if (v !== undefined) return v;
|
|
1195
|
+
// Python `_MATH_CONSTANTS`: omitted 1T quotes stay na.
|
|
1196
|
+
if (node.id === "bid" || node.id === "ask") return NA;
|
|
1197
|
+
return NA;
|
|
1006
1198
|
}
|
|
1007
1199
|
case "Constant": {
|
|
1008
1200
|
const v = node.value;
|
|
@@ -1012,29 +1204,8 @@ function evalExpr(node: expr | undefined, env: Env): Value {
|
|
|
1012
1204
|
if (v === false) return 0;
|
|
1013
1205
|
return NA;
|
|
1014
1206
|
}
|
|
1015
|
-
case "BinOp":
|
|
1016
|
-
|
|
1017
|
-
const rightV = evalExpr(node.right, env);
|
|
1018
|
-
if (node.op.kind === "Add" && (typeof leftV === "string" || typeof rightV === "string")) {
|
|
1019
|
-
return stringifyVal(leftV) + stringifyVal(rightV);
|
|
1020
|
-
}
|
|
1021
|
-
const l = unwrap(leftV);
|
|
1022
|
-
const r = unwrap(rightV);
|
|
1023
|
-
if (l == null || r == null) return NA;
|
|
1024
|
-
switch (node.op.kind) {
|
|
1025
|
-
case "Add":
|
|
1026
|
-
return l + r;
|
|
1027
|
-
case "Sub":
|
|
1028
|
-
return l - r;
|
|
1029
|
-
case "Mult":
|
|
1030
|
-
return l * r;
|
|
1031
|
-
case "Div":
|
|
1032
|
-
return r === 0 ? NA : l / r;
|
|
1033
|
-
case "Mod":
|
|
1034
|
-
return r === 0 ? NA : l % r;
|
|
1035
|
-
}
|
|
1036
|
-
return NA;
|
|
1037
|
-
}
|
|
1207
|
+
case "BinOp":
|
|
1208
|
+
return applyBinOp(node.op.kind, evalExpr(node.left, env), evalExpr(node.right, env));
|
|
1038
1209
|
case "UnaryOp": {
|
|
1039
1210
|
const v = unwrap(evalExpr(node.operand, env));
|
|
1040
1211
|
if (node.op.kind === "Not") return isTruthy(v) ? 0 : 1;
|
|
@@ -1089,8 +1260,23 @@ function evalExpr(node: expr | undefined, env: Env): Value {
|
|
|
1089
1260
|
return evalWhile(node, env);
|
|
1090
1261
|
case "Switch":
|
|
1091
1262
|
return evalSwitch(node, env);
|
|
1263
|
+
case "Once": {
|
|
1264
|
+
const onceNode = node as Once;
|
|
1265
|
+
if (env.onceFired.has(onceNode)) return NA;
|
|
1266
|
+
const t = onceNode.test == null ? 1 : unwrap(evalExpr(onceNode.test, env));
|
|
1267
|
+
if (!isTruthy(t)) return NA;
|
|
1268
|
+
for (const s of onceNode.body) execStmt(s, env);
|
|
1269
|
+
env.onceFired.add(onceNode);
|
|
1270
|
+
return NA;
|
|
1271
|
+
}
|
|
1092
1272
|
case "BoolOp":
|
|
1093
1273
|
return evalBoolOp(node, env);
|
|
1274
|
+
case "Qualify":
|
|
1275
|
+
return evalExpr(node.value, env);
|
|
1276
|
+
case "Specialize":
|
|
1277
|
+
return evalExpr(node.value, env);
|
|
1278
|
+
case "AugAssign":
|
|
1279
|
+
return evalAugAssign(node, env);
|
|
1094
1280
|
}
|
|
1095
1281
|
}
|
|
1096
1282
|
|
|
@@ -1167,16 +1353,27 @@ function evalSwitch(node: Switch, env: Env): Value {
|
|
|
1167
1353
|
const subject = subjectPresent ? evalExpr(node.subject!, env) : null;
|
|
1168
1354
|
for (const c of cases) {
|
|
1169
1355
|
if (c.pattern == null) return execBlock(c.body, env);
|
|
1170
|
-
if (subjectPresent)
|
|
1171
|
-
const pat = evalExpr(c.pattern, env);
|
|
1172
|
-
if (valuesEq(pat, subject)) return execBlock(c.body, env);
|
|
1173
|
-
} else if (isTruthy(unwrap(evalExpr(c.pattern, env)))) {
|
|
1174
|
-
return execBlock(c.body, env);
|
|
1175
|
-
}
|
|
1356
|
+
if (switchArmMatches(subjectPresent, subject, c.pattern, env)) return execBlock(c.body, env);
|
|
1176
1357
|
}
|
|
1177
1358
|
return NA;
|
|
1178
1359
|
}
|
|
1179
1360
|
|
|
1361
|
+
/** Python `_switch_case_matches`: multi-value arms (`1, 2 =>`) match any element. */
|
|
1362
|
+
function switchArmMatches(
|
|
1363
|
+
subjectPresent: boolean,
|
|
1364
|
+
subject: Value | null,
|
|
1365
|
+
pattern: expr,
|
|
1366
|
+
env: Env,
|
|
1367
|
+
): boolean {
|
|
1368
|
+
const pat = evalExpr(pattern, env);
|
|
1369
|
+
if (isTuple(pat)) {
|
|
1370
|
+
if (!subjectPresent) return pat.elts.some((p) => isTruthy(unwrap(p)));
|
|
1371
|
+
return pat.elts.some((p) => valuesEq(p, subject as Value));
|
|
1372
|
+
}
|
|
1373
|
+
if (subjectPresent) return valuesEq(pat, subject as Value);
|
|
1374
|
+
return isTruthy(unwrap(pat));
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1180
1377
|
function execBlock(body: stmt[] | stmt | null | undefined, env: Env): Value {
|
|
1181
1378
|
let last: Value = NA;
|
|
1182
1379
|
for (const s of asStmts(body)) last = execStmt(s, env);
|
|
@@ -1231,73 +1428,154 @@ function evalForIn(node: ForIn, env: Env): Value {
|
|
|
1231
1428
|
return last;
|
|
1232
1429
|
}
|
|
1233
1430
|
|
|
1234
|
-
/**
|
|
1431
|
+
/**
|
|
1432
|
+
* Python `_MATH_CONSTANTS` static dotted keys (base.py).
|
|
1433
|
+
* `barmerge.*` are Python True/False booleans (ast/evaluator/base.py ~92),
|
|
1434
|
+
* matching compile (emit.ts) — plotting coerces to 1/0 on both backends.
|
|
1435
|
+
* Dynamic series (`timeframe.*` flags, `syminfo.ticker`, `bid`/`ask`) stay in Name blocks.
|
|
1436
|
+
*/
|
|
1437
|
+
/* Exported for direct unit tests (typeof / parity assertions); not part of the package API. */
|
|
1438
|
+
export const MATH_CONSTANTS: Record<string, Value> = {
|
|
1439
|
+
"math.pi": Math.PI,
|
|
1440
|
+
"math.e": Math.E,
|
|
1441
|
+
"math.phi": (1 + Math.sqrt(5)) / 2,
|
|
1442
|
+
"math.rphi": 2 / (1 + Math.sqrt(5)),
|
|
1443
|
+
"format.mintick": "mintick",
|
|
1444
|
+
"format.percent": "percent",
|
|
1445
|
+
"format.volume": "volume",
|
|
1446
|
+
"format.price": "price",
|
|
1447
|
+
"text.formatting.none": "",
|
|
1448
|
+
"text.formatting.bold": "bold",
|
|
1449
|
+
"text.formatting.italic": "italic",
|
|
1450
|
+
"text.formatting.bold_italic": "bold italic",
|
|
1451
|
+
"size.auto": "auto",
|
|
1452
|
+
"size.tiny": 8,
|
|
1453
|
+
"size.small": 10,
|
|
1454
|
+
"size.normal": 12,
|
|
1455
|
+
"size.large": 16,
|
|
1456
|
+
"size.huge": 20,
|
|
1457
|
+
"order.ascending": 1,
|
|
1458
|
+
"order.descending": -1,
|
|
1459
|
+
"barmerge.gaps_on": true,
|
|
1460
|
+
"barmerge.gaps_off": false,
|
|
1461
|
+
"barmerge.lookahead_on": true,
|
|
1462
|
+
"barmerge.lookahead_off": false,
|
|
1463
|
+
"shape.arrowup": "arrowup",
|
|
1464
|
+
"shape.arrowdown": "arrowdown",
|
|
1465
|
+
"shape.circle": "circle",
|
|
1466
|
+
"shape.cross": "cross",
|
|
1467
|
+
"shape.diamond": "diamond",
|
|
1468
|
+
"shape.flag": "flag",
|
|
1469
|
+
"shape.labelup": "labelup",
|
|
1470
|
+
"shape.labeldown": "labeldown",
|
|
1471
|
+
"shape.square": "square",
|
|
1472
|
+
"shape.triangledown": "triangledown",
|
|
1473
|
+
"shape.triangleup": "triangleup",
|
|
1474
|
+
"shape.xcross": "xcross",
|
|
1475
|
+
"location.abovebar": "abovebar",
|
|
1476
|
+
"location.belowbar": "belowbar",
|
|
1477
|
+
"location.top": "top",
|
|
1478
|
+
"location.bottom": "bottom",
|
|
1479
|
+
"location.absolute": "absolute",
|
|
1480
|
+
"xloc.bar_index": "bar_index",
|
|
1481
|
+
"xloc.bar_time": "bar_time",
|
|
1482
|
+
"yloc.price": "price",
|
|
1483
|
+
"yloc.abovebar": "abovebar",
|
|
1484
|
+
"yloc.belowbar": "belowbar",
|
|
1485
|
+
"extend.none": "none",
|
|
1486
|
+
"extend.left": "left",
|
|
1487
|
+
"extend.right": "right",
|
|
1488
|
+
"extend.both": "both",
|
|
1489
|
+
// Pine display.* is a bitfield: none=0 pane=1 data_window=2 price_scale=4
|
|
1490
|
+
// status_line=8 all=15. Integers so `display.pane + display.data_window` works.
|
|
1491
|
+
"display.none": 0,
|
|
1492
|
+
"display.pane": 1,
|
|
1493
|
+
"display.all": 15,
|
|
1494
|
+
"display.data_window": 2,
|
|
1495
|
+
"display.price_scale": 4,
|
|
1496
|
+
"display.status_line": 8,
|
|
1497
|
+
"position.top_left": "top_left",
|
|
1498
|
+
"position.top_center": "top_center",
|
|
1499
|
+
"position.top_right": "top_right",
|
|
1500
|
+
"position.middle_left": "middle_left",
|
|
1501
|
+
"position.middle_center": "middle_center",
|
|
1502
|
+
"position.middle_right": "middle_right",
|
|
1503
|
+
"position.bottom_left": "bottom_left",
|
|
1504
|
+
"position.bottom_center": "bottom_center",
|
|
1505
|
+
"position.bottom_right": "bottom_right",
|
|
1506
|
+
"hline.style_solid": "solid",
|
|
1507
|
+
"hline.style_dashed": "dashed",
|
|
1508
|
+
"hline.style_dotted": "dotted",
|
|
1509
|
+
// Python plotting.py PlotStyle / `_builtin_plot_linestyle_*`.
|
|
1510
|
+
"plot.linestyle_solid": "linestyle_solid",
|
|
1511
|
+
"plot.linestyle_dashed": "linestyle_dashed",
|
|
1512
|
+
"plot.linestyle_dotted": "linestyle_dotted",
|
|
1513
|
+
"dayofweek.sunday": 1,
|
|
1514
|
+
"dayofweek.monday": 2,
|
|
1515
|
+
"dayofweek.tuesday": 3,
|
|
1516
|
+
"dayofweek.wednesday": 4,
|
|
1517
|
+
"dayofweek.thursday": 5,
|
|
1518
|
+
"dayofweek.friday": 6,
|
|
1519
|
+
"dayofweek.saturday": 7,
|
|
1520
|
+
"month.january": 1,
|
|
1521
|
+
"month.february": 2,
|
|
1522
|
+
"month.march": 3,
|
|
1523
|
+
"month.april": 4,
|
|
1524
|
+
"month.may": 5,
|
|
1525
|
+
"month.june": 6,
|
|
1526
|
+
"month.july": 7,
|
|
1527
|
+
"month.august": 8,
|
|
1528
|
+
"month.september": 9,
|
|
1529
|
+
"month.october": 10,
|
|
1530
|
+
"month.november": 11,
|
|
1531
|
+
"month.december": 12,
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
function mathConstant(node: Attribute): Value | undefined {
|
|
1535
|
+
const path = attrPath(node);
|
|
1536
|
+
if (path == null || !Object.prototype.hasOwnProperty.call(MATH_CONSTANTS, path)) return undefined;
|
|
1537
|
+
return MATH_CONSTANTS[path];
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
/** `strategy.long` / `strategy.short` → "long"/"short"; dotted `_MATH_CONSTANTS`; other attrs are na. */
|
|
1235
1541
|
function evalAttribute(node: Attribute, env: Env): Value {
|
|
1542
|
+
const constant = mathConstant(node);
|
|
1543
|
+
if (constant !== undefined) return constant;
|
|
1544
|
+
const strat = evalStrategyAttr(node, env);
|
|
1545
|
+
if (strat !== undefined) return strat;
|
|
1236
1546
|
if (node.value.kind === "Name") {
|
|
1237
|
-
if (node.value.id === "strategy") {
|
|
1238
|
-
if (node.attr === "long" || node.attr === "short") return node.attr;
|
|
1239
|
-
if (node.attr === "position_size") return env.book.position.qty;
|
|
1240
|
-
if (node.attr === "position_avg_price") return env.book.position.avgPrice;
|
|
1241
|
-
if (node.attr === "netprofit") return env.book.realizedPnl;
|
|
1242
|
-
if (node.attr === "openprofit") {
|
|
1243
|
-
const q = env.book.position.qty;
|
|
1244
|
-
const avg = env.book.position.avgPrice;
|
|
1245
|
-
const mark = num(env.ctx.close);
|
|
1246
|
-
if (q === 0 || avg == null || mark == null) return 0;
|
|
1247
|
-
return q * (mark - avg);
|
|
1248
|
-
}
|
|
1249
|
-
if (node.attr === "equity") return env.book.equity(num(env.ctx.close) ?? 0);
|
|
1250
|
-
if (node.attr === "opentrades") return env.book.opentrades;
|
|
1251
|
-
if (node.attr === "closedtrades") return env.book.closedtrades;
|
|
1252
|
-
if (node.attr === "wintrades") return env.book.wintrades;
|
|
1253
|
-
if (node.attr === "losstrades") return env.book.losstrades;
|
|
1254
|
-
if (node.attr === "eventrades") return env.book.eventrades;
|
|
1255
|
-
if (node.attr === "grossprofit") return env.book.grossprofit;
|
|
1256
|
-
if (node.attr === "grossloss") return env.book.grossloss;
|
|
1257
|
-
if (node.attr === "avg_trade") return env.book.avgTrade();
|
|
1258
|
-
if (node.attr === "avg_winning_trade") return env.book.avgWinningTrade();
|
|
1259
|
-
if (node.attr === "avg_losing_trade") return env.book.avgLosingTrade();
|
|
1260
|
-
if (node.attr === "netprofit_percent") {
|
|
1261
|
-
return env.book.netprofitPercent(num(env.ctx.close) ?? 0);
|
|
1262
|
-
}
|
|
1263
|
-
if (node.attr === "openprofit_percent") {
|
|
1264
|
-
return env.book.openprofitPercent(num(env.ctx.close) ?? 0);
|
|
1265
|
-
}
|
|
1266
|
-
if (node.attr === "grossprofit_percent") return env.book.grossprofitPercent();
|
|
1267
|
-
if (node.attr === "grossloss_percent") return env.book.grosslossPercent();
|
|
1268
|
-
if (node.attr === "avg_trade_percent") return env.book.avgTradePercent();
|
|
1269
|
-
if (node.attr === "avg_winning_trade_percent") return env.book.avgWinningTradePercent();
|
|
1270
|
-
if (node.attr === "avg_losing_trade_percent") return env.book.avgLosingTradePercent();
|
|
1271
|
-
if (node.attr === "max_drawdown") return env.book.maxDrawdown();
|
|
1272
|
-
if (node.attr === "max_drawdown_percent") return env.book.maxDrawdownPercent();
|
|
1273
|
-
if (node.attr === "max_runup") return env.book.maxRunup();
|
|
1274
|
-
if (node.attr === "max_runup_percent") return env.book.maxRunupPercent();
|
|
1275
|
-
if (node.attr === "percent_profitable" || node.attr === "winrate") {
|
|
1276
|
-
return env.book.percentProfitable();
|
|
1277
|
-
}
|
|
1278
|
-
if (node.attr === "profitfactor" || node.attr === "profit_factor") {
|
|
1279
|
-
return env.book.profitFactor();
|
|
1280
|
-
}
|
|
1281
|
-
if (node.attr === "initial_capital") return env.book.initialCapital;
|
|
1282
|
-
if (node.attr === "commission") return 0;
|
|
1283
|
-
if (node.attr === "cash") return "cash";
|
|
1284
|
-
if (node.attr === "fixed") return "fixed";
|
|
1285
|
-
if (node.attr === "percent_of_equity") return "percent_of_equity";
|
|
1286
|
-
}
|
|
1287
1547
|
if (node.value.id === "syminfo") {
|
|
1288
1548
|
if (node.attr === "ticker" || node.attr === "tickerid") return env.symbol;
|
|
1549
|
+
if (node.attr === "prefix") return extractPrefix(env.symbol);
|
|
1289
1550
|
if (node.attr === "timezone") return "Etc/UTC";
|
|
1290
1551
|
if (node.attr === "currency") return "USD";
|
|
1291
1552
|
if (node.attr === "mintick") return 0.01;
|
|
1292
1553
|
if (node.attr === "type") return "stock";
|
|
1554
|
+
if (node.attr === "isin") return "";
|
|
1555
|
+
if (node.attr === "current_contract") return NA;
|
|
1556
|
+
if (node.attr === "main_tickerid") return env.symbol;
|
|
1293
1557
|
}
|
|
1294
1558
|
if (node.value.id === "timeframe") {
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1559
|
+
// Python SoT: with no configured timeframe the evaluator's static
|
|
1560
|
+
// "Chart timeframe defaults (daily)" table applies (base.py) and
|
|
1561
|
+
// `_chart_period` / `_period_flags` resolve the chart to daily —
|
|
1562
|
+
// period "D", isdaily/isdwm true, everything else false. Normalize
|
|
1563
|
+
// null/empty to "D" so derivation lands on that table; a configured
|
|
1564
|
+
// timeframe keeps the dynamic helpers unchanged.
|
|
1565
|
+
const tf = env.timeframe == null || env.timeframe === "" ? "D" : env.timeframe;
|
|
1566
|
+
if (node.attr === "period") return tf;
|
|
1567
|
+
if (node.attr === "main_period") return tf;
|
|
1568
|
+
if (node.attr === "multiplier") return tfMultiplier(tf);
|
|
1569
|
+
if (node.attr === "isintraday") return timeframeIsIntraday(tf) ? 1 : 0;
|
|
1570
|
+
if (node.attr === "isdaily") return timeframeIsDaily(tf) ? 1 : 0;
|
|
1571
|
+
if (node.attr === "isweekly") return timeframeIsWeekly(tf) ? 1 : 0;
|
|
1572
|
+
if (node.attr === "ismonthly") return timeframeIsMonthly(tf) ? 1 : 0;
|
|
1573
|
+
if (node.attr === "isseconds" || node.attr === "isinseconds") {
|
|
1574
|
+
return timeframeIsSeconds(tf) ? 1 : 0;
|
|
1575
|
+
}
|
|
1576
|
+
if (node.attr === "isminutes") return timeframeIsMinutes(tf) ? 1 : 0;
|
|
1577
|
+
if (node.attr === "ishours") return timeframeIsHours(tf) ? 1 : 0;
|
|
1578
|
+
if (node.attr === "isdwm") return timeframeIsDwm(tf) ? 1 : 0;
|
|
1301
1579
|
}
|
|
1302
1580
|
if (node.value.id === "color") {
|
|
1303
1581
|
const named = colorByName(node.attr);
|
|
@@ -1336,6 +1614,14 @@ function evalAttribute(node: Attribute, env: Env): Value {
|
|
|
1336
1614
|
const taAttr = evalTaAttr(node.attr, env);
|
|
1337
1615
|
if (taAttr !== undefined) return taAttr;
|
|
1338
1616
|
}
|
|
1617
|
+
if (node.value.id === "order") {
|
|
1618
|
+
if (node.attr === "ascending") return 1;
|
|
1619
|
+
if (node.attr === "descending") return -1;
|
|
1620
|
+
}
|
|
1621
|
+
if (node.attr === "all") {
|
|
1622
|
+
const kind = drawingAllKind(node.value.id);
|
|
1623
|
+
if (kind != null) return drawingAllArray(env, kind);
|
|
1624
|
+
}
|
|
1339
1625
|
}
|
|
1340
1626
|
const obj = evalExpr(node.value, env);
|
|
1341
1627
|
if (obj instanceof UdtInstance) {
|
|
@@ -1355,6 +1641,33 @@ function evalAttribute(node: Attribute, env: Env): Value {
|
|
|
1355
1641
|
if (typeof exp === "function") return NA;
|
|
1356
1642
|
return exp as Value;
|
|
1357
1643
|
}
|
|
1644
|
+
if (isTaRecord(obj)) return taRecordField(obj, node.attr);
|
|
1645
|
+
if (isChartPoint(obj)) return chartPointField(obj, node.attr);
|
|
1646
|
+
return NA;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
function isTaRecord(value: Value): value is TaRecord {
|
|
1650
|
+
return typeof value === "object" && value !== null && (value as TaRecord).__taRecord === true;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/** Brand-gated field read. Booleans plot as 1/0. Class instances never reach here. */
|
|
1654
|
+
function taRecordField(obj: TaRecord, attr: string): Value {
|
|
1655
|
+
if (
|
|
1656
|
+
obj instanceof UdtInstance ||
|
|
1657
|
+
obj instanceof EnumType ||
|
|
1658
|
+
obj instanceof LibraryModule ||
|
|
1659
|
+
obj instanceof PineArray ||
|
|
1660
|
+
obj instanceof PineMap ||
|
|
1661
|
+
obj instanceof PineMatrix
|
|
1662
|
+
) {
|
|
1663
|
+
return NA;
|
|
1664
|
+
}
|
|
1665
|
+
if (!Object.prototype.hasOwnProperty.call(obj, attr)) return NA;
|
|
1666
|
+
const v = obj[attr];
|
|
1667
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
1668
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : NA;
|
|
1669
|
+
if (v === null) return NA;
|
|
1670
|
+
if (typeof v === "string") return v;
|
|
1358
1671
|
return NA;
|
|
1359
1672
|
}
|
|
1360
1673
|
|
|
@@ -1389,7 +1702,7 @@ function evalTaAttr(attr: string, env: Env): Value | undefined {
|
|
|
1389
1702
|
const src = h != null && l != null && c != null ? (h + l + c) / 3 : c;
|
|
1390
1703
|
return (ta.vwap as TaEngine["vwap"]).call(env.ta, site, src, num(env.ctx.volume));
|
|
1391
1704
|
}
|
|
1392
|
-
if (attr === "pvt" && typeof ta.pvt === "function") {
|
|
1705
|
+
if ((attr === "pvt" || attr === "vpt") && typeof ta.pvt === "function") {
|
|
1393
1706
|
return (ta.pvt as TaEngine["pvt"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
1394
1707
|
}
|
|
1395
1708
|
if (attr === "nvi" && typeof ta.nvi === "function") {
|
|
@@ -1398,6 +1711,41 @@ function evalTaAttr(attr: string, env: Env): Value | undefined {
|
|
|
1398
1711
|
if (attr === "pvi" && typeof ta.pvi === "function") {
|
|
1399
1712
|
return (ta.pvi as TaEngine["pvi"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
1400
1713
|
}
|
|
1714
|
+
if (attr === "wad" && typeof ta.wad === "function") {
|
|
1715
|
+
return (ta.wad as TaEngine["wad"]).call(
|
|
1716
|
+
env.ta,
|
|
1717
|
+
site,
|
|
1718
|
+
num(env.ctx.high),
|
|
1719
|
+
num(env.ctx.low),
|
|
1720
|
+
num(env.ctx.close),
|
|
1721
|
+
num(env.ctx.volume),
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
if (attr === "wvad" && typeof ta.wvad === "function") {
|
|
1725
|
+
return (ta.wvad as TaEngine["wvad"]).call(
|
|
1726
|
+
env.ta,
|
|
1727
|
+
site,
|
|
1728
|
+
num(env.ctx.high),
|
|
1729
|
+
num(env.ctx.low),
|
|
1730
|
+
num(env.ctx.close),
|
|
1731
|
+
num(env.ctx.volume),
|
|
1732
|
+
20,
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
if (attr === "cmf" && typeof ta.cmf === "function") {
|
|
1736
|
+
return (ta.cmf as TaEngine["cmf"]).call(
|
|
1737
|
+
env.ta,
|
|
1738
|
+
site,
|
|
1739
|
+
num(env.ctx.high),
|
|
1740
|
+
num(env.ctx.low),
|
|
1741
|
+
num(env.ctx.close),
|
|
1742
|
+
num(env.ctx.volume),
|
|
1743
|
+
20,
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
if (attr === "ao") {
|
|
1747
|
+
return env.ta.ao(site, num(env.ctx.high), num(env.ctx.low), 5, 34);
|
|
1748
|
+
}
|
|
1401
1749
|
return undefined;
|
|
1402
1750
|
}
|
|
1403
1751
|
|
|
@@ -1433,6 +1781,9 @@ function isNaVal(value: Value): boolean {
|
|
|
1433
1781
|
}
|
|
1434
1782
|
|
|
1435
1783
|
function valuesEq(left: Value, right: Value): boolean {
|
|
1784
|
+
// Python True/False compare as 1/0 (True == 1), e.g. `barmerge.gaps_on == 1`.
|
|
1785
|
+
if (typeof left === "boolean") left = left ? 1 : 0;
|
|
1786
|
+
if (typeof right === "boolean") right = right ? 1 : 0;
|
|
1436
1787
|
if (isNaVal(left) && isNaVal(right)) return true;
|
|
1437
1788
|
if (isNaVal(left) || isNaVal(right)) return false;
|
|
1438
1789
|
if (left instanceof EnumMember) return left.equals(right);
|
|
@@ -1470,20 +1821,27 @@ function cmp(left: Value, op: string, right: Value): boolean | null {
|
|
|
1470
1821
|
}
|
|
1471
1822
|
|
|
1472
1823
|
function evalCall(node: Call, env: Env): Value {
|
|
1473
|
-
|
|
1474
|
-
|
|
1824
|
+
const callee = unwrapSpecialize(node.func);
|
|
1825
|
+
if (callee.kind === "Attribute") {
|
|
1826
|
+
const obj = evalExpr(callee.value, env);
|
|
1475
1827
|
if (obj instanceof LibraryModule) {
|
|
1476
|
-
return evalLibraryMemberCall(obj,
|
|
1828
|
+
return evalLibraryMemberCall(obj, callee.attr, node, env);
|
|
1477
1829
|
}
|
|
1478
1830
|
if (obj instanceof UdtInstance) {
|
|
1479
|
-
const meth = obj.getMethod(
|
|
1831
|
+
const meth = obj.getMethod(callee.attr);
|
|
1480
1832
|
if (meth && isUdfDef(meth.body)) return evalUdf(meth.body, node, env, obj);
|
|
1481
1833
|
}
|
|
1482
|
-
if (obj instanceof UdtType &&
|
|
1834
|
+
if (obj instanceof UdtType && callee.attr === "new") {
|
|
1483
1835
|
const overrides: Record<string, unknown> = {};
|
|
1836
|
+
let pos = 0;
|
|
1484
1837
|
for (const a of asArgs(node.args)) {
|
|
1485
1838
|
const key = argKeyword(a);
|
|
1486
|
-
if (key)
|
|
1839
|
+
if (key) {
|
|
1840
|
+
overrides[key] = evalExpr(a.value, env);
|
|
1841
|
+
continue;
|
|
1842
|
+
}
|
|
1843
|
+
const field = obj.fields[pos++];
|
|
1844
|
+
if (field) overrides[field.name] = evalExpr(a.value, env);
|
|
1487
1845
|
}
|
|
1488
1846
|
return obj.newInstance(overrides);
|
|
1489
1847
|
}
|
|
@@ -1503,7 +1861,10 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1503
1861
|
return env.ta.rma(site, srcArg(node, env), lenArg(node, env, 1));
|
|
1504
1862
|
}
|
|
1505
1863
|
if (fname === "ta.atr" || fname === "atr") {
|
|
1506
|
-
|
|
1864
|
+
// Python 1-arg period-only form gates the length on _is_period_like
|
|
1865
|
+
// (volatility.py ~54 / basic.py ~420): non-period-like → ignored → same
|
|
1866
|
+
// as the arg being absent (na period 0 → na kernel).
|
|
1867
|
+
const period = periodLikeArg(node, env, 0, ["length"]) ?? 0;
|
|
1507
1868
|
return env.ta.atr(site, num(env.ctx.high), num(env.ctx.low), num(env.ctx.close), period);
|
|
1508
1869
|
}
|
|
1509
1870
|
if (fname === "ta.kc" || fname === "kc") {
|
|
@@ -1520,7 +1881,12 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1520
1881
|
const mathVal = evalMathCall(fname, node, env);
|
|
1521
1882
|
if (mathVal !== undefined) return mathVal;
|
|
1522
1883
|
if (isInputBuiltin(fname)) return evalInput(node, env, fname!);
|
|
1523
|
-
if (
|
|
1884
|
+
if (
|
|
1885
|
+
isRequestBuiltin(fname) ||
|
|
1886
|
+
fname === "request.security" ||
|
|
1887
|
+
fname === "security" ||
|
|
1888
|
+
fname === "security_lower_tf"
|
|
1889
|
+
) {
|
|
1524
1890
|
return evalRequestCall(fname!, node, env);
|
|
1525
1891
|
}
|
|
1526
1892
|
const arrVal = evalArrayCall(fname, node, env);
|
|
@@ -1573,9 +1939,44 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1573
1939
|
}
|
|
1574
1940
|
const timeVal = evalTimeCall(fname, node, env);
|
|
1575
1941
|
if (timeVal !== undefined) return timeVal;
|
|
1942
|
+
if (fname === "max_bars_back") {
|
|
1943
|
+
const varExpr = callArg(node.args, 0, ["var"]);
|
|
1944
|
+
const numExpr = callArg(node.args, 1, ["num"]);
|
|
1945
|
+
env.maxBarsBackDecls.push({
|
|
1946
|
+
var: varExpr == null ? NA : evalExpr(varExpr, env),
|
|
1947
|
+
num: numExpr == null ? 0 : evalExpr(numExpr, env),
|
|
1948
|
+
});
|
|
1949
|
+
return NA;
|
|
1950
|
+
}
|
|
1951
|
+
if (fname === "alertcondition") {
|
|
1952
|
+
const condExpr = callArg(node.args, 0, ["condition"]);
|
|
1953
|
+
if (condExpr == null) runtimeError("alertcondition() requires at least a condition argument");
|
|
1954
|
+
const raw = evalExpr(condExpr, env);
|
|
1955
|
+
const condition = isTruthy(unwrap(raw));
|
|
1956
|
+
let title = "Alert";
|
|
1957
|
+
let message = "Alert";
|
|
1958
|
+
const titleExpr = callArg(node.args, 1, ["title"]);
|
|
1959
|
+
const msgExpr = callArg(node.args, 2, ["message"]);
|
|
1960
|
+
if (titleExpr != null) {
|
|
1961
|
+
const t = evalExpr(titleExpr, env);
|
|
1962
|
+
if (t != null) title = typeof t === "string" ? t : unwrap(t) == null ? title : String(unwrap(t));
|
|
1963
|
+
}
|
|
1964
|
+
if (msgExpr != null) {
|
|
1965
|
+
const m = evalExpr(msgExpr, env);
|
|
1966
|
+
if (m != null) message = typeof m === "string" ? m : unwrap(m) == null ? message : String(unwrap(m));
|
|
1967
|
+
}
|
|
1968
|
+
if (condition) fireAlertCondition(env, title, message);
|
|
1969
|
+
return NA;
|
|
1970
|
+
}
|
|
1971
|
+
if (fname === "timeframe.change") return timeframeChange(node, env);
|
|
1972
|
+
if (fname === "syminfo.prefix") return syminfoPrefix(node, env);
|
|
1576
1973
|
if (fname === "timeframe.in_seconds") {
|
|
1974
|
+
// Python SoT (timeframe.py:189-190): None / "" map to "D" (daily 86400)
|
|
1975
|
+
// before parsing — same default compile applies (runtime.ts
|
|
1976
|
+
// timeframeInSecondsOrDaily). A configured chart timeframe ("5") keeps
|
|
1977
|
+
// its own duration via the env fallback (pinned in interpret_wave6).
|
|
1577
1978
|
const p = evalAsString(callArg(node.args, 0, ["timeframe", "period"]), env) ?? env.timeframe;
|
|
1578
|
-
return timeframeInSeconds(p);
|
|
1979
|
+
return timeframeInSeconds(p == null || p === "" ? "D" : p);
|
|
1579
1980
|
}
|
|
1580
1981
|
if (fname === "timeframe.from_seconds") {
|
|
1581
1982
|
const s = unwrap(evalExpr(callArg(node.args, 0, ["seconds"]), env));
|
|
@@ -1648,11 +2049,16 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1648
2049
|
const id = evalId(callArg(node.args, 0, ["id"]));
|
|
1649
2050
|
const dir = evalDirection(callArg(node.args, 1, ["direction"]), env);
|
|
1650
2051
|
const qtyArg = callArg(node.args, 2, ["qty"]);
|
|
1651
|
-
const
|
|
1652
|
-
|
|
2052
|
+
const mark = num(env.ctx.close);
|
|
2053
|
+
let qty: number;
|
|
2054
|
+
if (qtyArg == null) {
|
|
2055
|
+
qty = env.book.resolveDefaultQty(mark ?? 0);
|
|
2056
|
+
} else {
|
|
2057
|
+
const qtyRaw = unwrap(evalExpr(qtyArg, env));
|
|
2058
|
+
qty = qtyRaw == null || !Number.isFinite(qtyRaw) ? Number.NaN : qtyRaw;
|
|
2059
|
+
}
|
|
1653
2060
|
const limit = unwrap(evalExpr(callArg(node.args, -1, ["limit"]), env));
|
|
1654
2061
|
const stop = unwrap(evalExpr(callArg(node.args, -1, ["stop"]), env));
|
|
1655
|
-
const mark = num(env.ctx.close);
|
|
1656
2062
|
env.book.placeEntry(env.barIndex, id, dir, qty, {
|
|
1657
2063
|
limit,
|
|
1658
2064
|
stop,
|
|
@@ -1666,13 +2072,40 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1666
2072
|
if (fname === "strategy.close") {
|
|
1667
2073
|
const id = evalId(callArg(node.args, 0, ["id"]));
|
|
1668
2074
|
const mark = num(env.ctx.close);
|
|
2075
|
+
const qtyExpr = callArg(node.args, 1, ["qty"]);
|
|
2076
|
+
const qtyRaw = qtyExpr == null ? undefined : unwrap(evalExpr(qtyExpr, env));
|
|
1669
2077
|
if (env.book.position.qty !== 0 && mark != null) {
|
|
1670
|
-
env.book.fillClose(env.barIndex, id, mark, {
|
|
2078
|
+
env.book.fillClose(env.barIndex, id, mark, {
|
|
2079
|
+
time: num(env.ctx.time) ?? undefined,
|
|
2080
|
+
qty: qtyRaw ?? undefined,
|
|
2081
|
+
});
|
|
1671
2082
|
} else env.book.close(env.barIndex, id);
|
|
1672
2083
|
return NA;
|
|
1673
2084
|
}
|
|
1674
2085
|
if (fname === "strategy.exit") {
|
|
1675
|
-
|
|
2086
|
+
const id = evalId(callArg(node.args, 0, ["id"]));
|
|
2087
|
+
const fromStr = evalAsString(callArg(node.args, 1, ["from_entry"]), env);
|
|
2088
|
+
const fromEntry = fromStr != null && fromStr !== "" ? fromStr : null;
|
|
2089
|
+
env.book.placeExit(env.barIndex, id, {
|
|
2090
|
+
from_entry: fromEntry,
|
|
2091
|
+
qty: unwrap(evalExpr(callArg(node.args, 2, ["qty"]), env)),
|
|
2092
|
+
qty_percent: unwrap(evalExpr(callArg(node.args, 3, ["qty_percent"]), env)),
|
|
2093
|
+
profit: unwrap(evalExpr(callArg(node.args, 4, ["profit"]), env)),
|
|
2094
|
+
limit: unwrap(evalExpr(callArg(node.args, 5, ["limit"]), env)),
|
|
2095
|
+
loss: unwrap(evalExpr(callArg(node.args, 6, ["loss"]), env)),
|
|
2096
|
+
stop: unwrap(evalExpr(callArg(node.args, 7, ["stop"]), env)),
|
|
2097
|
+
trail_price: unwrap(evalExpr(callArg(node.args, 8, ["trail_price"]), env)),
|
|
2098
|
+
trail_points: unwrap(evalExpr(callArg(node.args, 9, ["trail_points"]), env)),
|
|
2099
|
+
trail_offset: unwrap(evalExpr(callArg(node.args, 10, ["trail_offset"]), env)),
|
|
2100
|
+
price: num(env.ctx.close) ?? undefined,
|
|
2101
|
+
time: num(env.ctx.time) ?? undefined,
|
|
2102
|
+
ohlc: {
|
|
2103
|
+
open: num(env.ctx.open) ?? undefined,
|
|
2104
|
+
high: num(env.ctx.high) ?? undefined,
|
|
2105
|
+
low: num(env.ctx.low) ?? undefined,
|
|
2106
|
+
close: num(env.ctx.close) ?? undefined,
|
|
2107
|
+
},
|
|
2108
|
+
});
|
|
1676
2109
|
return NA;
|
|
1677
2110
|
}
|
|
1678
2111
|
if (fname === "strategy.close_all") {
|
|
@@ -1704,6 +2137,8 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1704
2137
|
}
|
|
1705
2138
|
const riskVal = evalStrategyRiskCall(fname, node, env);
|
|
1706
2139
|
if (riskVal !== undefined) return riskVal;
|
|
2140
|
+
const seriesVal = evalStrategySeriesCall(fname, node, env);
|
|
2141
|
+
if (seriesVal !== undefined) return seriesVal;
|
|
1707
2142
|
const tradeVal = evalStrategyTradeCall(fname, node, env);
|
|
1708
2143
|
if (tradeVal !== undefined) return tradeVal;
|
|
1709
2144
|
if (fname != null && fname.endsWith(".new")) {
|
|
@@ -1730,7 +2165,11 @@ function evalKc(node: Call, env: Env, site: string): Value {
|
|
|
1730
2165
|
let high = num(env.ctx.high);
|
|
1731
2166
|
let low = num(env.ctx.low);
|
|
1732
2167
|
let close = srcArg(node, env);
|
|
1733
|
-
|
|
2168
|
+
// Python 3-arg reference form dispatches on _is_period_like(args[1])
|
|
2169
|
+
// (volatility.py ~250 / basic.py ~451): a non-period-like length is ignored
|
|
2170
|
+
// → same as absent (na period 0). The legacy 4-arg form below coerces via
|
|
2171
|
+
// _expect_int (fractional floors) and stays ungated.
|
|
2172
|
+
let length = periodLikeArg(node, env, 1, ["length"]) ?? 0;
|
|
1734
2173
|
let mult = numArg(node, env, 2, ["mult", "multiplier"], 1);
|
|
1735
2174
|
if (positional.length >= 4) {
|
|
1736
2175
|
high = unwrap(evalExpr(positional[0]!.value, env));
|
|
@@ -1847,11 +2286,72 @@ function numArg(node: Call, env: Env, index: number, names: string[], fallback:
|
|
|
1847
2286
|
|
|
1848
2287
|
function unwrap(value: Value): Cell {
|
|
1849
2288
|
if (value == null) return NA;
|
|
2289
|
+
// Python True/False (e.g. `barmerge.*`) coerce to 1/0 like compile's `num()`
|
|
2290
|
+
// (True == 1) — the interpret Value pipeline is number-centric.
|
|
2291
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
1850
2292
|
if (typeof value === "number") return Number.isFinite(value) ? value : NA;
|
|
2293
|
+
if (isStrategyCashAmount(value)) return Number.isFinite(value.value) ? value.value : NA;
|
|
1851
2294
|
// Objects (PineArray / Matrix / Color / tuples) are not cells — not na.
|
|
1852
2295
|
return NA;
|
|
1853
2296
|
}
|
|
1854
2297
|
|
|
2298
|
+
/** Store array slots as-is; only coerce non-finite numbers to `na`. */
|
|
2299
|
+
function storeValue(value: Value): unknown {
|
|
2300
|
+
if (value == null) return null;
|
|
2301
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
2302
|
+
return value;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
function asArrayValue(value: unknown): Value {
|
|
2306
|
+
return value as Value;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/** Python `_parse_sort_args` — `(id)`, `(id, order)`, `(id, order, sort_field)`, or bare field. */
|
|
2310
|
+
function parseSortArgs(node: Call, env: Env): { order: "asc" | "desc"; sortField: unknown } {
|
|
2311
|
+
const orderExpr = callArg(node.args, 1, ["order"]);
|
|
2312
|
+
const fieldExpr = callArg(node.args, 2, ["sort_field"]);
|
|
2313
|
+
const second = orderExpr == null ? undefined : evalExpr(orderExpr, env);
|
|
2314
|
+
const third = fieldExpr == null ? undefined : evalExpr(fieldExpr, env);
|
|
2315
|
+
if (fieldExpr != null) {
|
|
2316
|
+
return { order: isDescendingOrder(second) ? "desc" : "asc", sortField: sortFieldValue(third) };
|
|
2317
|
+
}
|
|
2318
|
+
if (orderExpr == null) return { order: "asc", sortField: null };
|
|
2319
|
+
if (second == null) return { order: "asc", sortField: null };
|
|
2320
|
+
if (typeof second === "string") {
|
|
2321
|
+
const low = second.toLowerCase();
|
|
2322
|
+
if (low === "ascending" || low === "descending" || low === "asc" || low === "desc") {
|
|
2323
|
+
return { order: isDescendingOrder(second) ? "desc" : "asc", sortField: null };
|
|
2324
|
+
}
|
|
2325
|
+
return { order: "asc", sortField: second };
|
|
2326
|
+
}
|
|
2327
|
+
if (typeof second === "number" && Number.isFinite(second)) {
|
|
2328
|
+
if (second === 1 || second === -1) {
|
|
2329
|
+
return { order: isDescendingOrder(second) ? "desc" : "asc", sortField: null };
|
|
2330
|
+
}
|
|
2331
|
+
return { order: "asc", sortField: second };
|
|
2332
|
+
}
|
|
2333
|
+
return { order: isDescendingOrder(second) ? "desc" : "asc", sortField: null };
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
function isDescendingOrder(orderArg: Value | undefined): boolean {
|
|
2337
|
+
if (orderArg == null) return false;
|
|
2338
|
+
if (typeof orderArg === "number") return orderArg < 0;
|
|
2339
|
+
if (typeof orderArg === "string") return orderArg.toLowerCase().includes("desc");
|
|
2340
|
+
return false;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
function sortFieldValue(value: Value | undefined): unknown {
|
|
2344
|
+
if (value == null) return null;
|
|
2345
|
+
if (typeof value === "string") return value;
|
|
2346
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
2347
|
+
return null;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
function evalSortField(node: Call, env: Env): unknown {
|
|
2351
|
+
const raw = evalExpr(callArg(node.args, 2, ["sort_field"]), env);
|
|
2352
|
+
return sortFieldValue(raw);
|
|
2353
|
+
}
|
|
2354
|
+
|
|
1855
2355
|
function formatRunError(err: unknown): string {
|
|
1856
2356
|
if (err instanceof Error) {
|
|
1857
2357
|
const msg = (err.message || err.name || "runtime error").split("\n")[0]!.trim();
|
|
@@ -1865,7 +2365,185 @@ function formatRunError(err: unknown): string {
|
|
|
1865
2365
|
}
|
|
1866
2366
|
|
|
1867
2367
|
function isPlainInputs(extra: RuntimeOptions | InputOverrides): extra is InputOverrides {
|
|
1868
|
-
return
|
|
2368
|
+
return (
|
|
2369
|
+
!("inputs" in extra) &&
|
|
2370
|
+
!("broker" in extra) &&
|
|
2371
|
+
!("timeframe" in extra) &&
|
|
2372
|
+
!("libraries" in extra) &&
|
|
2373
|
+
!("mode" in extra) &&
|
|
2374
|
+
!("timeout_seconds" in extra)
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
function resolveLibraries(libs?: LibraryRegistry | LibrarySourceSpec[] | null): LibraryRegistry {
|
|
2379
|
+
if (libs instanceof LibraryRegistry) return libs;
|
|
2380
|
+
const reg = new LibraryRegistry();
|
|
2381
|
+
if (libs == null) return reg;
|
|
2382
|
+
for (const lib of libs) {
|
|
2383
|
+
if (lib == null || typeof lib !== "object") continue;
|
|
2384
|
+
const ns = String(lib.namespace ?? "");
|
|
2385
|
+
const name = String(lib.name ?? "");
|
|
2386
|
+
const source = String(lib.source ?? "");
|
|
2387
|
+
const rawVer = lib.version;
|
|
2388
|
+
const ver = typeof rawVer === "number" && Number.isFinite(rawVer) ? Math.trunc(rawVer) : 1;
|
|
2389
|
+
if (ns && name && source) reg.registerSource(ns, name, ver, source);
|
|
2390
|
+
}
|
|
2391
|
+
return reg;
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
function emptyCompileResult(
|
|
2395
|
+
n: number,
|
|
2396
|
+
error: string,
|
|
2397
|
+
extra?: Pick<RuntimeResult, "auto_backend" | "compile_fallback_reason">,
|
|
2398
|
+
): RuntimeResult {
|
|
2399
|
+
return {
|
|
2400
|
+
series: {},
|
|
2401
|
+
plots: [],
|
|
2402
|
+
plot_meta: [],
|
|
2403
|
+
count: n,
|
|
2404
|
+
script_name: null,
|
|
2405
|
+
script_type: "indicator",
|
|
2406
|
+
mode: "compile",
|
|
2407
|
+
error,
|
|
2408
|
+
error_kind: "compile",
|
|
2409
|
+
...extra,
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
function barsToColumns(ohlcv: OHLCVBar[]): {
|
|
2414
|
+
open: Array<number | null>;
|
|
2415
|
+
high: Array<number | null>;
|
|
2416
|
+
low: Array<number | null>;
|
|
2417
|
+
close: Array<number | null>;
|
|
2418
|
+
volume: Array<number | null>;
|
|
2419
|
+
time: Array<number | null>;
|
|
2420
|
+
} {
|
|
2421
|
+
const n = ohlcv.length;
|
|
2422
|
+
const open: Array<number | null> = new Array(n);
|
|
2423
|
+
const high: Array<number | null> = new Array(n);
|
|
2424
|
+
const low: Array<number | null> = new Array(n);
|
|
2425
|
+
const close: Array<number | null> = new Array(n);
|
|
2426
|
+
const volume: Array<number | null> = new Array(n);
|
|
2427
|
+
const time: Array<number | null> = new Array(n);
|
|
2428
|
+
for (let i = 0; i < n; i++) {
|
|
2429
|
+
const b = ohlcv[i]!;
|
|
2430
|
+
const c = b.close ?? null;
|
|
2431
|
+
close[i] = c;
|
|
2432
|
+
open[i] = b.open ?? c;
|
|
2433
|
+
high[i] = b.high ?? c;
|
|
2434
|
+
low[i] = b.low ?? c;
|
|
2435
|
+
volume[i] = b.volume ?? 1;
|
|
2436
|
+
time[i] = b.time ?? i * 60_000;
|
|
2437
|
+
}
|
|
2438
|
+
return { open, high, low, close, volume, time };
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
function packCompiled(
|
|
2442
|
+
source: string,
|
|
2443
|
+
ohlcv: OHLCVBar[],
|
|
2444
|
+
inputs?: InputOverrides,
|
|
2445
|
+
libraries?: LibraryRegistry,
|
|
2446
|
+
): RuntimeResult {
|
|
2447
|
+
const compiled = compileScript(source, {
|
|
2448
|
+
getLibrary: (ns, name, ver) => libraries?.getSource(ns, name, ver) ?? null,
|
|
2449
|
+
});
|
|
2450
|
+
const cols = barsToColumns(ohlcv);
|
|
2451
|
+
// compileScript().run() is the raw dict (engine CompileHostResult may already strip).
|
|
2452
|
+
const raw = compiled.run(cols.open, cols.high, cols.low, cols.close, cols.volume, cols.time, {
|
|
2453
|
+
inputs,
|
|
2454
|
+
}) as Record<
|
|
2455
|
+
string,
|
|
2456
|
+
unknown
|
|
2457
|
+
> & {
|
|
2458
|
+
__events?: StrategyEvent[];
|
|
2459
|
+
__fills?: Array<{ bar: number; id: string; side: "buy" | "sell"; qty: number; price: number; type?: "fill" }>;
|
|
2460
|
+
__position_size?: number;
|
|
2461
|
+
__netprofit?: number;
|
|
2462
|
+
__equity?: number;
|
|
2463
|
+
__strategy?: StrategySummary;
|
|
2464
|
+
__drawings?: DrawingEvent[];
|
|
2465
|
+
__logs?: LogRecord[];
|
|
2466
|
+
};
|
|
2467
|
+
const events = raw.__events;
|
|
2468
|
+
const fillsIn = raw.__fills;
|
|
2469
|
+
const strategy = raw.__strategy;
|
|
2470
|
+
const drawings = raw.__drawings;
|
|
2471
|
+
const logs = raw.__logs;
|
|
2472
|
+
const series: Record<string, Array<number | null>> = {};
|
|
2473
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2474
|
+
if (key.startsWith("__")) continue;
|
|
2475
|
+
series[key] = value as Array<number | null>;
|
|
2476
|
+
}
|
|
2477
|
+
const plot_meta = compiled.plots;
|
|
2478
|
+
const first = plot_meta[0]?.title;
|
|
2479
|
+
return {
|
|
2480
|
+
series,
|
|
2481
|
+
plots: first != null ? (series[first] ?? []) : [],
|
|
2482
|
+
plot_meta,
|
|
2483
|
+
count: ohlcv.length,
|
|
2484
|
+
script_name: null,
|
|
2485
|
+
script_type: events != null || strategy != null ? "strategy" : "indicator",
|
|
2486
|
+
mode: "compile",
|
|
2487
|
+
...(events != null ? { events } : {}),
|
|
2488
|
+
...(fillsIn != null
|
|
2489
|
+
? {
|
|
2490
|
+
fills: fillsIn.map((f) =>
|
|
2491
|
+
f.type === "fill" ? (f as RuntimeFill) : { type: "fill" as const, ...f },
|
|
2492
|
+
),
|
|
2493
|
+
}
|
|
2494
|
+
: {}),
|
|
2495
|
+
...(strategy != null ? { strategy } : {}),
|
|
2496
|
+
...(drawings != null ? { drawings } : {}),
|
|
2497
|
+
...(logs != null ? { logs } : {}),
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
function runCompiled(
|
|
2502
|
+
source: string,
|
|
2503
|
+
ohlcv: OHLCVBar[],
|
|
2504
|
+
host: { inputs?: InputOverrides; libraries?: LibraryRegistry },
|
|
2505
|
+
): RuntimeResult {
|
|
2506
|
+
const elig = compileEligible(source);
|
|
2507
|
+
if (!elig.ok) return emptyCompileResult(ohlcv.length, elig.reason ?? "ineligible");
|
|
2508
|
+
try {
|
|
2509
|
+
return packCompiled(source, ohlcv, host.inputs, host.libraries);
|
|
2510
|
+
} catch (err) {
|
|
2511
|
+
const msg = err instanceof CompileError ? err.message : formatRunError(err);
|
|
2512
|
+
return emptyCompileResult(ohlcv.length, msg);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
function runAuto(
|
|
2517
|
+
source: string,
|
|
2518
|
+
tree: AST,
|
|
2519
|
+
ohlcv: OHLCVBar[],
|
|
2520
|
+
host: {
|
|
2521
|
+
symbol?: string;
|
|
2522
|
+
inputs?: InputOverrides;
|
|
2523
|
+
broker?: BrokerSettings;
|
|
2524
|
+
timeframe?: string | null;
|
|
2525
|
+
libraries?: LibraryRegistry;
|
|
2526
|
+
timeout_seconds?: number | null;
|
|
2527
|
+
},
|
|
2528
|
+
): RuntimeResult {
|
|
2529
|
+
const elig = compileEligible(source);
|
|
2530
|
+
if (elig.ok) {
|
|
2531
|
+
try {
|
|
2532
|
+
const compiled = packCompiled(source, ohlcv, host.inputs, host.libraries);
|
|
2533
|
+
compiled.auto_backend = "compile";
|
|
2534
|
+
return compiled;
|
|
2535
|
+
} catch (err) {
|
|
2536
|
+
const reason = err instanceof CompileError ? err.message : formatRunError(err);
|
|
2537
|
+
const out = interpretTree(tree, ohlcv, host);
|
|
2538
|
+
out.auto_backend = "interpret";
|
|
2539
|
+
out.compile_fallback_reason = reason;
|
|
2540
|
+
return out;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
const out = interpretTree(tree, ohlcv, host);
|
|
2544
|
+
out.auto_backend = "interpret";
|
|
2545
|
+
out.compile_fallback_reason = elig.reason;
|
|
2546
|
+
return out;
|
|
1869
2547
|
}
|
|
1870
2548
|
|
|
1871
2549
|
function mergeInputs(
|
|
@@ -1899,6 +2577,16 @@ function packDrawings(book: DrawingBook): DrawingEvent[] | undefined {
|
|
|
1899
2577
|
return book.items.length === 0 ? undefined : book.items;
|
|
1900
2578
|
}
|
|
1901
2579
|
|
|
2580
|
+
/** Python `_emit_alert(..., source="alertcondition", freq=once_per_bar)`. */
|
|
2581
|
+
function fireAlertCondition(env: Env, title: string, message: string): void {
|
|
2582
|
+
const key = `alertcondition|${title}|${message}|once_per_bar`;
|
|
2583
|
+
if (env.alertFireBars.get(key) === env.barIndex) return;
|
|
2584
|
+
env.alertFireBars.set(key, env.barIndex);
|
|
2585
|
+
env.drawings.alert(env.barIndex, message);
|
|
2586
|
+
const ev = env.drawings.items[env.drawings.items.length - 1];
|
|
2587
|
+
if (ev != null) ev.extra = { source: "alertcondition", title, freq: "once_per_bar" };
|
|
2588
|
+
}
|
|
2589
|
+
|
|
1902
2590
|
function inputTitle(node: Call): string | null {
|
|
1903
2591
|
const named = namedStringArg(node.args, "title");
|
|
1904
2592
|
if (named != null) return named;
|
|
@@ -1986,6 +2674,153 @@ function evalStrategyRiskCall(fname: string | null, node: Call, env: Env): Value
|
|
|
1986
2674
|
return undefined;
|
|
1987
2675
|
}
|
|
1988
2676
|
|
|
2677
|
+
function evalStrategyAttr(node: Attribute, env: Env): Value | undefined {
|
|
2678
|
+
const path = attrPath(node);
|
|
2679
|
+
if (path == null || !path.startsWith("strategy.")) return undefined;
|
|
2680
|
+
return strategySeriesValue(path, env);
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
/** Zero-arg `strategy.*` series / constants, plus identity FX stubs. */
|
|
2684
|
+
function evalStrategySeriesCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
2685
|
+
if (fname == null || !fname.startsWith("strategy.")) return undefined;
|
|
2686
|
+
if (fname === "strategy.convert_to_account" || fname === "strategy.convert_to_symbol") {
|
|
2687
|
+
const arg = callArg(node.args, 0, ["value"]);
|
|
2688
|
+
if (arg == null) return 1;
|
|
2689
|
+
return unwrap(evalExpr(arg, env));
|
|
2690
|
+
}
|
|
2691
|
+
if (fname === "strategy.default_entry_qty") {
|
|
2692
|
+
const arg = callArg(node.args, 0, ["percent_equity"]);
|
|
2693
|
+
const pct = arg == null ? 100 : unwrap(evalExpr(arg, env));
|
|
2694
|
+
if (pct == null || !Number.isFinite(pct)) return NA;
|
|
2695
|
+
return (env.book.initialCapital * (pct / 100)) / 100;
|
|
2696
|
+
}
|
|
2697
|
+
if (fname === "strategy.opentrades.capital_held") return env.book.capitalHeld();
|
|
2698
|
+
return strategySeriesValue(fname, env);
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
function strategySeriesValue(path: string, env: Env): Value | undefined {
|
|
2702
|
+
const book = env.book;
|
|
2703
|
+
const mark = num(env.ctx.close) ?? 0;
|
|
2704
|
+
switch (path) {
|
|
2705
|
+
case "strategy.long":
|
|
2706
|
+
case "strategy.direction.long":
|
|
2707
|
+
return "long";
|
|
2708
|
+
case "strategy.short":
|
|
2709
|
+
case "strategy.direction.short":
|
|
2710
|
+
return "short";
|
|
2711
|
+
case "strategy.direction.all":
|
|
2712
|
+
return "all";
|
|
2713
|
+
case "strategy.position_size":
|
|
2714
|
+
return book.position.qty;
|
|
2715
|
+
case "strategy.position_avg_price":
|
|
2716
|
+
return book.position.avgPrice;
|
|
2717
|
+
case "strategy.position_entry_name":
|
|
2718
|
+
return book.position_entry_name;
|
|
2719
|
+
case "strategy.netprofit":
|
|
2720
|
+
return book.netprofit();
|
|
2721
|
+
case "strategy.openprofit":
|
|
2722
|
+
return book.openprofit(mark);
|
|
2723
|
+
case "strategy.equity":
|
|
2724
|
+
return book.equity(mark);
|
|
2725
|
+
case "strategy.opentrades":
|
|
2726
|
+
return book.opentrades;
|
|
2727
|
+
case "strategy.closedtrades":
|
|
2728
|
+
return book.closedtrades;
|
|
2729
|
+
case "strategy.closedtrades.first_index":
|
|
2730
|
+
return book.closedtrades_first_index;
|
|
2731
|
+
case "strategy.wintrades":
|
|
2732
|
+
return book.wintrades;
|
|
2733
|
+
case "strategy.losstrades":
|
|
2734
|
+
return book.losstrades;
|
|
2735
|
+
case "strategy.eventrades":
|
|
2736
|
+
return book.eventrades;
|
|
2737
|
+
case "strategy.grossprofit":
|
|
2738
|
+
return book.grossprofit;
|
|
2739
|
+
case "strategy.grossloss":
|
|
2740
|
+
return book.grossloss;
|
|
2741
|
+
case "strategy.avg_trade":
|
|
2742
|
+
return book.avgTrade();
|
|
2743
|
+
case "strategy.avg_winning_trade":
|
|
2744
|
+
return book.avgWinningTrade();
|
|
2745
|
+
case "strategy.avg_losing_trade":
|
|
2746
|
+
return book.avgLosingTrade();
|
|
2747
|
+
case "strategy.netprofit_percent":
|
|
2748
|
+
return book.netprofitPercent(mark);
|
|
2749
|
+
case "strategy.openprofit_percent":
|
|
2750
|
+
return book.openprofitPercent(mark);
|
|
2751
|
+
case "strategy.grossprofit_percent":
|
|
2752
|
+
return book.grossprofitPercent();
|
|
2753
|
+
case "strategy.grossloss_percent":
|
|
2754
|
+
return book.grosslossPercent();
|
|
2755
|
+
case "strategy.avg_trade_percent":
|
|
2756
|
+
return book.avgTradePercent();
|
|
2757
|
+
case "strategy.avg_winning_trade_percent":
|
|
2758
|
+
return book.avgWinningTradePercent();
|
|
2759
|
+
case "strategy.avg_losing_trade_percent":
|
|
2760
|
+
return book.avgLosingTradePercent();
|
|
2761
|
+
case "strategy.max_drawdown":
|
|
2762
|
+
return book.maxDrawdown();
|
|
2763
|
+
case "strategy.max_drawdown_percent":
|
|
2764
|
+
return book.maxDrawdownPercent();
|
|
2765
|
+
case "strategy.max_runup":
|
|
2766
|
+
return book.maxRunup();
|
|
2767
|
+
case "strategy.max_runup_percent":
|
|
2768
|
+
return book.maxRunupPercent();
|
|
2769
|
+
case "strategy.percent_profitable":
|
|
2770
|
+
case "strategy.winrate":
|
|
2771
|
+
return book.percentProfitable();
|
|
2772
|
+
case "strategy.profitfactor":
|
|
2773
|
+
case "strategy.profit_factor":
|
|
2774
|
+
return book.profitFactor();
|
|
2775
|
+
case "strategy.initial_capital":
|
|
2776
|
+
return book.initialCapital;
|
|
2777
|
+
case "strategy.commission":
|
|
2778
|
+
return 0;
|
|
2779
|
+
case "strategy.cash":
|
|
2780
|
+
return strategyCashAmount(book.cash(mark));
|
|
2781
|
+
case "strategy.fixed":
|
|
2782
|
+
return "fixed";
|
|
2783
|
+
case "strategy.percent_of_equity":
|
|
2784
|
+
return "percent_of_equity";
|
|
2785
|
+
case "strategy.leverage":
|
|
2786
|
+
return book.leverage;
|
|
2787
|
+
case "strategy.margin_liquidation_price":
|
|
2788
|
+
return book.marginLiquidationPrice();
|
|
2789
|
+
case "strategy.avg_price_stock":
|
|
2790
|
+
return "stock";
|
|
2791
|
+
case "strategy.avg_price_futures":
|
|
2792
|
+
return "futures";
|
|
2793
|
+
case "strategy.avg_price_inverse":
|
|
2794
|
+
return "inverse";
|
|
2795
|
+
case "strategy.account_currency":
|
|
2796
|
+
return book.account_currency;
|
|
2797
|
+
case "strategy.default_entry_qty":
|
|
2798
|
+
return (book.initialCapital * (100 / 100)) / 100;
|
|
2799
|
+
case "strategy.max_contracts_held_all":
|
|
2800
|
+
return book.maxContractsHeldAll();
|
|
2801
|
+
case "strategy.max_contracts_held_long":
|
|
2802
|
+
return book.maxContractsHeldLong();
|
|
2803
|
+
case "strategy.max_contracts_held_short":
|
|
2804
|
+
return book.maxContractsHeldShort();
|
|
2805
|
+
case "strategy.opentrades.capital_held":
|
|
2806
|
+
return book.capitalHeld();
|
|
2807
|
+
case "strategy.oca.none":
|
|
2808
|
+
return "none";
|
|
2809
|
+
case "strategy.oca.cancel":
|
|
2810
|
+
return "cancel";
|
|
2811
|
+
case "strategy.oca.reduce":
|
|
2812
|
+
return "reduce";
|
|
2813
|
+
case "strategy.commission.percent":
|
|
2814
|
+
return "percent";
|
|
2815
|
+
case "strategy.commission.cash_per_order":
|
|
2816
|
+
return "cash_per_order";
|
|
2817
|
+
case "strategy.commission.cash_per_contract":
|
|
2818
|
+
return "cash_per_contract";
|
|
2819
|
+
default:
|
|
2820
|
+
return undefined;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
|
|
1989
2824
|
function evalStrategyTradeCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
1990
2825
|
if (fname == null || !fname.startsWith("strategy.")) return undefined;
|
|
1991
2826
|
const idx = unwrap(evalExpr(callArg(node.args, 0, ["trade_num", "trade_index", "index"]), env));
|
|
@@ -1994,32 +2829,54 @@ function evalStrategyTradeCall(fname: string | null, node: Call, env: Env): Valu
|
|
|
1994
2829
|
const book = env.book;
|
|
1995
2830
|
switch (fname) {
|
|
1996
2831
|
case "strategy.closedtrades.entry_bar_index":
|
|
1997
|
-
return book.closedEntryBar(i);
|
|
2832
|
+
return book.closedEntryBar(i) ?? 0;
|
|
1998
2833
|
case "strategy.closedtrades.entry_price":
|
|
1999
|
-
return book.closedEntryPrice(i);
|
|
2834
|
+
return book.closedEntryPrice(i) ?? 0;
|
|
2000
2835
|
case "strategy.closedtrades.exit_bar_index":
|
|
2001
|
-
return book.closedExitBar(i);
|
|
2836
|
+
return book.closedExitBar(i) ?? 0;
|
|
2002
2837
|
case "strategy.closedtrades.exit_price":
|
|
2003
|
-
return book.closedExitPrice(i);
|
|
2838
|
+
return book.closedExitPrice(i) ?? 0;
|
|
2004
2839
|
case "strategy.closedtrades.profit":
|
|
2005
|
-
return book.closedProfit(i);
|
|
2840
|
+
return book.closedProfit(i) ?? 0;
|
|
2006
2841
|
case "strategy.closedtrades.size":
|
|
2007
|
-
return book.closedSize(i);
|
|
2842
|
+
return book.closedSize(i) ?? 0;
|
|
2008
2843
|
case "strategy.closedtrades.entry_id":
|
|
2009
2844
|
case "strategy.closedtrades.exit_id":
|
|
2010
|
-
return book.closedId(i);
|
|
2845
|
+
return book.closedId(i) ?? "";
|
|
2011
2846
|
case "strategy.closedtrades.commission":
|
|
2012
|
-
return book.closedCommission(i);
|
|
2847
|
+
return book.closedCommission(i) ?? 0;
|
|
2848
|
+
case "strategy.closedtrades.entry_time":
|
|
2849
|
+
return book.closedEntryTime(i);
|
|
2850
|
+
case "strategy.closedtrades.exit_time":
|
|
2851
|
+
return book.closedExitTime(i);
|
|
2852
|
+
case "strategy.closedtrades.entry_comment":
|
|
2853
|
+
return book.closedEntryComment(i);
|
|
2854
|
+
case "strategy.closedtrades.exit_comment":
|
|
2855
|
+
return book.closedExitComment(i);
|
|
2856
|
+
case "strategy.closedtrades.max_drawdown":
|
|
2857
|
+
return book.closedMaxDrawdown(i);
|
|
2858
|
+
case "strategy.closedtrades.max_runup":
|
|
2859
|
+
return book.closedMaxRunup(i);
|
|
2013
2860
|
case "strategy.opentrades.entry_bar_index":
|
|
2014
|
-
return book.openEntryBar(i);
|
|
2861
|
+
return book.openEntryBar(i) ?? 0;
|
|
2015
2862
|
case "strategy.opentrades.entry_price":
|
|
2016
|
-
return book.openEntryPrice(i);
|
|
2863
|
+
return book.openEntryPrice(i) ?? 0;
|
|
2017
2864
|
case "strategy.opentrades.size":
|
|
2018
|
-
return book.openSize(i);
|
|
2865
|
+
return book.openSize(i) ?? 0;
|
|
2019
2866
|
case "strategy.opentrades.entry_id":
|
|
2020
|
-
return book.openId(i);
|
|
2867
|
+
return book.openId(i) ?? "";
|
|
2021
2868
|
case "strategy.opentrades.profit":
|
|
2022
|
-
return book.openProfit(i, mark);
|
|
2869
|
+
return book.openProfit(i, mark) ?? 0;
|
|
2870
|
+
case "strategy.opentrades.entry_time":
|
|
2871
|
+
return book.openEntryTime(i);
|
|
2872
|
+
case "strategy.opentrades.entry_comment":
|
|
2873
|
+
return book.openEntryComment(i);
|
|
2874
|
+
case "strategy.opentrades.commission":
|
|
2875
|
+
return book.openCommission(i);
|
|
2876
|
+
case "strategy.opentrades.max_drawdown":
|
|
2877
|
+
return book.openMaxDrawdown(i);
|
|
2878
|
+
case "strategy.opentrades.max_runup":
|
|
2879
|
+
return book.openMaxRunup(i);
|
|
2023
2880
|
default:
|
|
2024
2881
|
return undefined;
|
|
2025
2882
|
}
|
|
@@ -2039,7 +2896,12 @@ function evalRequestSecurity(node: Call, env: Env): Cell {
|
|
|
2039
2896
|
}
|
|
2040
2897
|
|
|
2041
2898
|
function evalRequestCall(fname: string, node: Call, env: Env): Cell {
|
|
2042
|
-
if (
|
|
2899
|
+
if (
|
|
2900
|
+
fname === "request.security" ||
|
|
2901
|
+
fname === "request.security_lower_tf" ||
|
|
2902
|
+
fname === "security" ||
|
|
2903
|
+
fname === "security_lower_tf"
|
|
2904
|
+
) {
|
|
2043
2905
|
return evalRequestSecurity(node, env);
|
|
2044
2906
|
}
|
|
2045
2907
|
const host = { symbol: env.symbol, timeframe: env.timeframe };
|
|
@@ -2097,7 +2959,7 @@ function evalMathCall(fname: string | null, node: Call, env: Env): Cell | undefi
|
|
|
2097
2959
|
if (fname === "math.todegrees") return mathToDegrees(cellArg(node, env, 0, ["radians", "x"]));
|
|
2098
2960
|
if (fname === "math.toradians") return mathToRadians(cellArg(node, env, 0, ["degrees", "x"]));
|
|
2099
2961
|
if (fname === "math.isfinite") return mathIsFinite(cellArg(node, env, 0, ["number", "x"]));
|
|
2100
|
-
if (fname === "math.random") {
|
|
2962
|
+
if (fname === "math.random" || fname === "random") {
|
|
2101
2963
|
const a0 = callArg(node.args, 0, ["min"]);
|
|
2102
2964
|
const a1 = callArg(node.args, 1, ["max"]);
|
|
2103
2965
|
return mathRandom(
|
|
@@ -2105,7 +2967,7 @@ function evalMathCall(fname: string | null, node: Call, env: Env): Cell | undefi
|
|
|
2105
2967
|
a1 == null ? undefined : unwrap(evalExpr(a1, env)),
|
|
2106
2968
|
);
|
|
2107
2969
|
}
|
|
2108
|
-
if (fname === "math.round_to_mintick") {
|
|
2970
|
+
if (fname === "math.round_to_mintick" || fname === "round_to_mintick") {
|
|
2109
2971
|
const tick = typeof env.ctx["syminfo.mintick"] === "number" ? (env.ctx["syminfo.mintick"] as number) : 0.01;
|
|
2110
2972
|
return mathRoundToMintick(cellArg(node, env, 0, ["number", "x"]), tick);
|
|
2111
2973
|
}
|
|
@@ -2120,6 +2982,37 @@ function allCellArgs(node: Call, env: Env): Cell[] {
|
|
|
2120
2982
|
return asArgs(node.args).map((a) => unwrap(evalExpr(a.value, env)));
|
|
2121
2983
|
}
|
|
2122
2984
|
|
|
2985
|
+
/**
|
|
2986
|
+
* Python `_is_period_like` (pynescript core.py ~3427) at the cell level: only
|
|
2987
|
+
* whole numbers are period-like (7.0 → 7; 7.5 / na / non-numbers fail). TA
|
|
2988
|
+
* handlers gate period-only / period-first overload forms on this check — a
|
|
2989
|
+
* non-period-like length arg is IGNORED and the call behaves exactly as if
|
|
2990
|
+
* the arg were absent (builtin default or na-period path; same call-site
|
|
2991
|
+
* state key/reset behavior). Returns null when the arg must be treated as
|
|
2992
|
+
* absent; a passing whole number is returned as-is.
|
|
2993
|
+
*/
|
|
2994
|
+
function periodLikeCell(v: Cell): number | null {
|
|
2995
|
+
return typeof v === "number" && Number.isInteger(v) ? v : null;
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
/** `periodLikeCell` over call arg *index* (positional or kwarg *names*). */
|
|
2999
|
+
function periodLikeArg(node: Call, env: Env, index: number, names: string[]): number | null {
|
|
3000
|
+
return periodLikeCell(unwrap(evalExpr(callArg(node.args, index, names), env)));
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
/** Positional (non-kwarg) argument count — detects period-only/period-first forms. */
|
|
3004
|
+
function positionalCount(node: Call): number {
|
|
3005
|
+
return asArgs(node.args).filter((a) => argKeyword(a) == null).length;
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
/** True when a keyword argument named one of *names* is present. */
|
|
3009
|
+
function hasKwarg(node: Call, names: string[]): boolean {
|
|
3010
|
+
return asArgs(node.args).some((a) => {
|
|
3011
|
+
const kw = argKeyword(a);
|
|
3012
|
+
return kw != null && names.includes(kw);
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
|
|
2123
3016
|
function lenOrDefault(
|
|
2124
3017
|
node: Call,
|
|
2125
3018
|
env: Env,
|
|
@@ -2139,10 +3032,31 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2139
3032
|
const ta = env.ta as TaAny;
|
|
2140
3033
|
if (fname === "ta.highest" || fname === "highest") {
|
|
2141
3034
|
if (typeof ta.highest !== "function") return NA;
|
|
3035
|
+
if (positionalCount(node) <= 1 && !hasKwarg(node, ["source", "series"])) {
|
|
3036
|
+
// Python 1-arg period-only form (basic.py:222-233 via _expect_series
|
|
3037
|
+
// allow_period_only): source defaults to the context high series and
|
|
3038
|
+
// the length gates on _is_period_like → non-period-like → na period 0.
|
|
3039
|
+
return (ta.highest as TaEngine["highest"]).call(
|
|
3040
|
+
env.ta,
|
|
3041
|
+
site,
|
|
3042
|
+
num(env.ctx.high),
|
|
3043
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
2142
3046
|
return (ta.highest as TaEngine["highest"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2143
3047
|
}
|
|
2144
3048
|
if (fname === "ta.lowest" || fname === "lowest") {
|
|
2145
3049
|
if (typeof ta.lowest !== "function") return NA;
|
|
3050
|
+
if (positionalCount(node) <= 1 && !hasKwarg(node, ["source", "series"])) {
|
|
3051
|
+
// Python 1-arg period-only form (basic.py:235-246): source defaults to
|
|
3052
|
+
// the context low series; length gated like ta.highest.
|
|
3053
|
+
return (ta.lowest as TaEngine["lowest"]).call(
|
|
3054
|
+
env.ta,
|
|
3055
|
+
site,
|
|
3056
|
+
num(env.ctx.low),
|
|
3057
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
3058
|
+
);
|
|
3059
|
+
}
|
|
2146
3060
|
return (ta.lowest as TaEngine["lowest"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2147
3061
|
}
|
|
2148
3062
|
if (fname === "ta.stdev" || fname === "stdev") {
|
|
@@ -2194,6 +3108,20 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2194
3108
|
}
|
|
2195
3109
|
if (fname === "ta.bb" || fname === "bb") {
|
|
2196
3110
|
if (typeof ta.bb !== "function") return NA;
|
|
3111
|
+
if (
|
|
3112
|
+
positionalCount(node) === 2 &&
|
|
3113
|
+
!hasKwarg(node, ["source", "series"]) &&
|
|
3114
|
+
periodLikeArg(node, env, 0, ["source", "series", "length"]) != null
|
|
3115
|
+
) {
|
|
3116
|
+
// Python period-first form (basic.py:394-401): (length, mult) with the
|
|
3117
|
+
// series defaulting to the context close. Dispatch gates slot 0 on
|
|
3118
|
+
// _is_period_like, so a series first slot stays on the 3-arg form.
|
|
3119
|
+
const length = periodLikeArg(node, env, 0, ["source", "series", "length"])!;
|
|
3120
|
+
const multRaw = unwrap(evalExpr(callArg(node.args, 1, ["mult", "multiplier"]), env));
|
|
3121
|
+
const mult = typeof multRaw === "number" && Number.isFinite(multRaw) ? multRaw : Number.NaN;
|
|
3122
|
+
const r = (ta.bb as TaEngine["bb"]).call(env.ta, site, num(env.ctx.close), length, mult);
|
|
3123
|
+
return tupleOf([r.mid, r.up, r.lo]);
|
|
3124
|
+
}
|
|
2197
3125
|
const r = (ta.bb as TaEngine["bb"]).call(
|
|
2198
3126
|
env.ta,
|
|
2199
3127
|
site,
|
|
@@ -2227,6 +3155,29 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2227
3155
|
}
|
|
2228
3156
|
if (fname === "ta.vwma" || fname === "vwma") {
|
|
2229
3157
|
if (typeof ta.vwma !== "function") return NA;
|
|
3158
|
+
if (positionalCount(node) <= 1 && !hasKwarg(node, ["volume"])) {
|
|
3159
|
+
// Python 1-arg period-only form (basic.py:80-88): close + volume from
|
|
3160
|
+
// the chart context; length gates on _is_period_like → non-period-like
|
|
3161
|
+
// → na period 0.
|
|
3162
|
+
return (ta.vwma as TaEngine["vwma"]).call(
|
|
3163
|
+
env.ta,
|
|
3164
|
+
site,
|
|
3165
|
+
num(env.ctx.close),
|
|
3166
|
+
num(env.ctx.volume),
|
|
3167
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
if (positionalCount(node) >= 3 || hasKwarg(node, ["volume"])) {
|
|
3171
|
+
// Python 3-arg community form (basic.py:89-100): (source, volume,
|
|
3172
|
+
// length). A period-like middle slot swaps volume back to the context
|
|
3173
|
+
// series (basic.py:93/97) — cells are last samples, mirroring
|
|
3174
|
+
// _is_period_like's series-last-sample check.
|
|
3175
|
+
const periodRaw = unwrap(evalExpr(callArg(node.args, 2, ["length", "period"]), env));
|
|
3176
|
+
const period = typeof periodRaw === "number" && Number.isFinite(periodRaw) ? periodRaw : 0;
|
|
3177
|
+
const volLike = periodLikeArg(node, env, 1, ["volume"]) != null;
|
|
3178
|
+
const vol = volLike ? num(env.ctx.volume) : unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env));
|
|
3179
|
+
return (ta.vwma as TaEngine["vwma"]).call(env.ta, site, srcArg(node, env), vol, period);
|
|
3180
|
+
}
|
|
2230
3181
|
return (ta.vwma as TaEngine["vwma"]).call(
|
|
2231
3182
|
env.ta,
|
|
2232
3183
|
site,
|
|
@@ -2237,21 +3188,43 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2237
3188
|
}
|
|
2238
3189
|
if (fname === "ta.cci" || fname === "cci") {
|
|
2239
3190
|
if (typeof ta.cci !== "function") return NA;
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
3191
|
+
if (positionalCount(node) >= 4) {
|
|
3192
|
+
// Python legacy 4-arg form (oscillators.py:178-187): typical price of
|
|
3193
|
+
// the given (high, low, close) series.
|
|
3194
|
+
const h = unwrap(evalExpr(callArg(node.args, 0, ["high"]), env));
|
|
3195
|
+
const l = unwrap(evalExpr(callArg(node.args, 1, ["low"]), env));
|
|
3196
|
+
const c = unwrap(evalExpr(callArg(node.args, 2, ["close"]), env));
|
|
3197
|
+
const tp = h != null && l != null && c != null ? (h + l + c) / 3 : c;
|
|
3198
|
+
return (ta.cci as TaEngine["cci"]).call(env.ta, site, tp, lenArg(node, env, 3));
|
|
3199
|
+
}
|
|
3200
|
+
if (positionalCount(node) <= 1 && !hasKwarg(node, ["source", "series"])) {
|
|
3201
|
+
// Python 1-arg period-only form (oscillators.py:159-171): typical price
|
|
3202
|
+
// from the context high/low/close (hlc3 fallback); length gates on
|
|
3203
|
+
// _is_period_like → non-period-like → na period 0.
|
|
3204
|
+
return (ta.cci as TaEngine["cci"]).call(
|
|
3205
|
+
env.ta,
|
|
3206
|
+
site,
|
|
3207
|
+
hlc3(env),
|
|
3208
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
3209
|
+
);
|
|
3210
|
+
}
|
|
3211
|
+
// Python 2-arg form (oscillators.py:172-177): CCI over the source itself
|
|
3212
|
+
// (_cci(series, series, series, period) → tp = series). SoT correction:
|
|
3213
|
+
// the previous blend of context high/low into the source matched no
|
|
3214
|
+
// Python dispatch arm.
|
|
3215
|
+
return (ta.cci as TaEngine["cci"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2245
3216
|
}
|
|
2246
3217
|
if (fname === "ta.wpr" || fname === "wpr") {
|
|
2247
3218
|
if (typeof ta.willr !== "function") return NA;
|
|
3219
|
+
// Python 1-arg period-only form gates length via _is_period_like
|
|
3220
|
+
// (oscillators.py ~198): non-period-like → ignored → na period 0.
|
|
2248
3221
|
return (ta.willr as TaEngine["willr"]).call(
|
|
2249
3222
|
env.ta,
|
|
2250
3223
|
site,
|
|
2251
3224
|
num(env.ctx.high),
|
|
2252
3225
|
num(env.ctx.low),
|
|
2253
3226
|
num(env.ctx.close),
|
|
2254
|
-
|
|
3227
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
2255
3228
|
);
|
|
2256
3229
|
}
|
|
2257
3230
|
if (fname === "ta.willr" || fname === "willr") {
|
|
@@ -2262,28 +3235,41 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2262
3235
|
num(env.ctx.high),
|
|
2263
3236
|
num(env.ctx.low),
|
|
2264
3237
|
num(env.ctx.close),
|
|
2265
|
-
|
|
3238
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
2266
3239
|
);
|
|
2267
3240
|
}
|
|
2268
3241
|
if (fname === "ta.stoch" || fname === "stoch") {
|
|
2269
3242
|
if (typeof ta.stoch !== "function") return NA;
|
|
2270
3243
|
const positional = asArgs(node.args).filter((a) => argKeyword(a) == null);
|
|
2271
|
-
|
|
2272
|
-
|
|
3244
|
+
if (!hasKwarg(node, ["high"]) && positional.length <= 2) {
|
|
3245
|
+
let length: number;
|
|
3246
|
+
if (positional.length === 2) {
|
|
3247
|
+
// Python 2-arg community form (kLength, dPeriod) — both slots gate on
|
|
3248
|
+
// _is_period_like (oscillators.py:73-78); dPeriod is ignored and %K
|
|
3249
|
+
// is returned over the context close/high/low.
|
|
3250
|
+
const k = periodLikeArg(node, env, 0, ["length"]);
|
|
3251
|
+
const d = periodLikeArg(node, env, 1, ["d", "smooth"]);
|
|
3252
|
+
length = k != null && d != null ? k : 0;
|
|
3253
|
+
} else {
|
|
3254
|
+
// Python 1-arg period-only form gates length via _is_period_like
|
|
3255
|
+
// (oscillators.py ~58): non-period-like → ignored → na period 0.
|
|
3256
|
+
length = periodLikeArg(node, env, 0, ["length"]) ?? 0;
|
|
3257
|
+
}
|
|
2273
3258
|
return (ta.stoch as TaEngine["stoch"]).call(
|
|
2274
3259
|
env.ta,
|
|
2275
3260
|
site,
|
|
2276
3261
|
num(env.ctx.close),
|
|
2277
3262
|
num(env.ctx.high),
|
|
2278
3263
|
num(env.ctx.low),
|
|
2279
|
-
|
|
3264
|
+
length,
|
|
2280
3265
|
);
|
|
2281
3266
|
}
|
|
3267
|
+
// reference Pine form: source, high, low, length
|
|
2282
3268
|
return (ta.stoch as TaEngine["stoch"]).call(
|
|
2283
3269
|
env.ta,
|
|
2284
3270
|
site,
|
|
2285
3271
|
srcArg(node, env),
|
|
2286
|
-
unwrap(evalExpr(
|
|
3272
|
+
unwrap(evalExpr(callArg(node.args, 1, ["high"]), env)),
|
|
2287
3273
|
unwrap(evalExpr(callArg(node.args, 2, ["low"]), env)),
|
|
2288
3274
|
lenArg(node, env, 3),
|
|
2289
3275
|
);
|
|
@@ -2351,6 +3337,16 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2351
3337
|
}
|
|
2352
3338
|
if (fname === "ta.highestbars" || fname === "highestbars") {
|
|
2353
3339
|
if (typeof ta.highestbars !== "function") return NA;
|
|
3340
|
+
if (positionalCount(node) <= 1 && !hasKwarg(node, ["source", "series"])) {
|
|
3341
|
+
// Python 1-arg period-only form (basic.py:248-269): source defaults to
|
|
3342
|
+
// the context high series; length gates on _is_period_like.
|
|
3343
|
+
return (ta.highestbars as TaEngine["highestbars"]).call(
|
|
3344
|
+
env.ta,
|
|
3345
|
+
site,
|
|
3346
|
+
num(env.ctx.high),
|
|
3347
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
3348
|
+
);
|
|
3349
|
+
}
|
|
2354
3350
|
return (ta.highestbars as TaEngine["highestbars"]).call(
|
|
2355
3351
|
env.ta,
|
|
2356
3352
|
site,
|
|
@@ -2360,6 +3356,16 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2360
3356
|
}
|
|
2361
3357
|
if (fname === "ta.lowestbars" || fname === "lowestbars") {
|
|
2362
3358
|
if (typeof ta.lowestbars !== "function") return NA;
|
|
3359
|
+
if (positionalCount(node) <= 1 && !hasKwarg(node, ["source", "series"])) {
|
|
3360
|
+
// Python 1-arg period-only form (basic.py:271-291): source defaults to
|
|
3361
|
+
// the context low series; length gates like ta.highestbars.
|
|
3362
|
+
return (ta.lowestbars as TaEngine["lowestbars"]).call(
|
|
3363
|
+
env.ta,
|
|
3364
|
+
site,
|
|
3365
|
+
num(env.ctx.low),
|
|
3366
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
2363
3369
|
return (ta.lowestbars as TaEngine["lowestbars"]).call(
|
|
2364
3370
|
env.ta,
|
|
2365
3371
|
site,
|
|
@@ -2416,7 +3422,21 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2416
3422
|
}
|
|
2417
3423
|
if (fname === "ta.pivothigh" || fname === "pivothigh") {
|
|
2418
3424
|
if (typeof ta.pivothigh !== "function") return NA;
|
|
2419
|
-
|
|
3425
|
+
if (
|
|
3426
|
+
positionalCount(node) === 2 &&
|
|
3427
|
+
!hasKwarg(node, ["source", "series"]) &&
|
|
3428
|
+
periodLikeArg(node, env, 0, ["leftbars", "left"]) != null &&
|
|
3429
|
+
periodLikeArg(node, env, 1, ["rightbars", "right"]) != null
|
|
3430
|
+
) {
|
|
3431
|
+
// Python period-first form (basic.py:1007-1010): both slots gate on
|
|
3432
|
+
// _is_period_like and the source defaults to the context high series.
|
|
3433
|
+
// A non-period-like slot errors in Python; the repo gated-form
|
|
3434
|
+
// convention treats it as absent → the call falls through to na.
|
|
3435
|
+
const left = periodLikeArg(node, env, 0, ["leftbars", "left"])!;
|
|
3436
|
+
const right = periodLikeArg(node, env, 1, ["rightbars", "right"])!;
|
|
3437
|
+
return (ta.pivothigh as TaEngine["pivothigh"]).call(env.ta, site, num(env.ctx.high), left, right);
|
|
3438
|
+
}
|
|
3439
|
+
return (ta.pivothigh as TaEngine["pivothigh"]).call(
|
|
2420
3440
|
env.ta,
|
|
2421
3441
|
site,
|
|
2422
3442
|
srcArg(node, env),
|
|
@@ -2426,6 +3446,18 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2426
3446
|
}
|
|
2427
3447
|
if (fname === "ta.pivotlow" || fname === "pivotlow") {
|
|
2428
3448
|
if (typeof ta.pivotlow !== "function") return NA;
|
|
3449
|
+
if (
|
|
3450
|
+
positionalCount(node) === 2 &&
|
|
3451
|
+
!hasKwarg(node, ["source", "series"]) &&
|
|
3452
|
+
periodLikeArg(node, env, 0, ["leftbars", "left"]) != null &&
|
|
3453
|
+
periodLikeArg(node, env, 1, ["rightbars", "right"]) != null
|
|
3454
|
+
) {
|
|
3455
|
+
// Python period-first form (basic.py:1036-1039): source defaults to
|
|
3456
|
+
// the context low series; slot gating mirrors ta.pivothigh.
|
|
3457
|
+
const left = periodLikeArg(node, env, 0, ["leftbars", "left"])!;
|
|
3458
|
+
const right = periodLikeArg(node, env, 1, ["rightbars", "right"])!;
|
|
3459
|
+
return (ta.pivotlow as TaEngine["pivotlow"]).call(env.ta, site, num(env.ctx.low), left, right);
|
|
3460
|
+
}
|
|
2429
3461
|
return (ta.pivotlow as TaEngine["pivotlow"]).call(
|
|
2430
3462
|
env.ta,
|
|
2431
3463
|
site,
|
|
@@ -2442,6 +3474,8 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2442
3474
|
if (typeof ta.mfi !== "function") return NA;
|
|
2443
3475
|
const positional = asArgs(node.args).filter((a) => a.name == null);
|
|
2444
3476
|
if (positional.length <= 1) {
|
|
3477
|
+
// Python 1-arg period-only form gates length via _is_period_like
|
|
3478
|
+
// (volume.py ~108): non-period-like → ignored → na period 0.
|
|
2445
3479
|
return (ta.mfi as TaEngine["mfi"]).call(
|
|
2446
3480
|
env.ta,
|
|
2447
3481
|
site,
|
|
@@ -2449,7 +3483,7 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2449
3483
|
num(env.ctx.low),
|
|
2450
3484
|
num(env.ctx.close),
|
|
2451
3485
|
num(env.ctx.volume),
|
|
2452
|
-
|
|
3486
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
2453
3487
|
);
|
|
2454
3488
|
}
|
|
2455
3489
|
if (positional.length === 2) {
|
|
@@ -2500,13 +3534,44 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2500
3534
|
}
|
|
2501
3535
|
if (fname === "ta.adx" || fname === "adx") {
|
|
2502
3536
|
if (typeof ta.adx !== "function") return NA;
|
|
3537
|
+
if (positionalCount(node) >= 4) {
|
|
3538
|
+
// Python legacy 4-arg form (common.py:718-726): (high, low, close,
|
|
3539
|
+
// length) with explicit series.
|
|
3540
|
+
return (ta.adx as TaEngine["adx"]).call(
|
|
3541
|
+
env.ta,
|
|
3542
|
+
site,
|
|
3543
|
+
srcArg(node, env),
|
|
3544
|
+
unwrap(evalExpr(callArg(node.args, 1, ["low"]), env)),
|
|
3545
|
+
unwrap(evalExpr(callArg(node.args, 2, ["close"]), env)),
|
|
3546
|
+
lenArg(node, env, 3),
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
if (positionalCount(node) === 2 && !hasKwarg(node, ["high", "low", "close"])) {
|
|
3550
|
+
// Python 2-arg form (diLength, adxSmoothing) (common.py:709-717): both
|
|
3551
|
+
// slots gate on _is_period_like and the ADX period is the SECOND slot —
|
|
3552
|
+
// diLength is accepted but unused by the kernel dispatch. A
|
|
3553
|
+
// non-period-like slot → treated as absent → default 14 (same
|
|
3554
|
+
// convention as the 1-arg form below).
|
|
3555
|
+
const di = periodLikeArg(node, env, 0, ["diLength", "length"]);
|
|
3556
|
+
const smooth = periodLikeArg(node, env, 1, ["adxSmoothing", "adxlen"]);
|
|
3557
|
+
return (ta.adx as TaEngine["adx"]).call(
|
|
3558
|
+
env.ta,
|
|
3559
|
+
site,
|
|
3560
|
+
num(env.ctx.high),
|
|
3561
|
+
num(env.ctx.low),
|
|
3562
|
+
num(env.ctx.close),
|
|
3563
|
+
di != null && smooth != null ? smooth : 14,
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3566
|
+
// Python 1-arg period-only form gates length via _is_period_like
|
|
3567
|
+
// (common.py ~701): non-period-like → ignored → default 14 (as absent).
|
|
2503
3568
|
return (ta.adx as TaEngine["adx"]).call(
|
|
2504
3569
|
env.ta,
|
|
2505
3570
|
site,
|
|
2506
3571
|
num(env.ctx.high),
|
|
2507
3572
|
num(env.ctx.low),
|
|
2508
3573
|
num(env.ctx.close),
|
|
2509
|
-
|
|
3574
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 14,
|
|
2510
3575
|
);
|
|
2511
3576
|
}
|
|
2512
3577
|
if (fname === "ta.correlation" || fname === "correlation") {
|
|
@@ -2537,12 +3602,15 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2537
3602
|
if (typeof ta.tsi !== "function") return NA;
|
|
2538
3603
|
const positional = asArgs(node.args).filter((a) => a.name == null);
|
|
2539
3604
|
if (positional.length === 2) {
|
|
3605
|
+
// Python 2-arg period-first form gates BOTH slots via _is_period_like
|
|
3606
|
+
// (oscillators.py ~299): args[0]=short, args[1]=long; a non-period-like
|
|
3607
|
+
// slot is ignored → TS default (13/25), as if the slot were absent.
|
|
2540
3608
|
return (ta.tsi as TaEngine["tsi"]).call(
|
|
2541
3609
|
env.ta,
|
|
2542
3610
|
site,
|
|
2543
3611
|
num(env.ctx.close),
|
|
2544
|
-
|
|
2545
|
-
|
|
3612
|
+
periodLikeArg(node, env, 1, ["long", "long_length"]) ?? 25,
|
|
3613
|
+
periodLikeArg(node, env, 0, ["short", "short_length"]) ?? 13,
|
|
2546
3614
|
);
|
|
2547
3615
|
}
|
|
2548
3616
|
return (ta.tsi as TaEngine["tsi"]).call(
|
|
@@ -2651,27 +3719,47 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2651
3719
|
num(env.ctx.volume),
|
|
2652
3720
|
);
|
|
2653
3721
|
}
|
|
2654
|
-
if (fname === "ta.pvt" || fname === "pvt") {
|
|
3722
|
+
if (fname === "ta.pvt" || fname === "pvt" || fname === "ta.vpt" || fname === "vpt") {
|
|
2655
3723
|
if (typeof ta.pvt !== "function") return NA;
|
|
2656
3724
|
return (ta.pvt as TaEngine["pvt"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
2657
3725
|
}
|
|
2658
3726
|
if (fname === "ta.wad" || fname === "wad") {
|
|
2659
3727
|
if (typeof ta.wad !== "function") return NA;
|
|
3728
|
+
const positional = asArgs(node.args).filter((a) => argKeyword(a) == null);
|
|
3729
|
+
if (positional.length >= 4) {
|
|
3730
|
+
return (ta.wad as TaEngine["wad"]).call(
|
|
3731
|
+
env.ta,
|
|
3732
|
+
site,
|
|
3733
|
+
unwrap(evalExpr(callArg(node.args, 0, ["high"]), env)),
|
|
3734
|
+
unwrap(evalExpr(callArg(node.args, 1, ["low"]), env)),
|
|
3735
|
+
unwrap(evalExpr(callArg(node.args, 2, ["close"]), env)),
|
|
3736
|
+
unwrap(evalExpr(callArg(node.args, 3, ["volume"]), env)),
|
|
3737
|
+
);
|
|
3738
|
+
}
|
|
2660
3739
|
return (ta.wad as TaEngine["wad"]).call(
|
|
2661
3740
|
env.ta,
|
|
2662
3741
|
site,
|
|
2663
3742
|
num(env.ctx.high),
|
|
2664
3743
|
num(env.ctx.low),
|
|
2665
3744
|
num(env.ctx.close),
|
|
3745
|
+
num(env.ctx.volume),
|
|
2666
3746
|
);
|
|
2667
3747
|
}
|
|
2668
3748
|
if (fname === "ta.nvi" || fname === "nvi") {
|
|
2669
3749
|
if (typeof ta.nvi !== "function") return NA;
|
|
2670
|
-
|
|
3750
|
+
const cArg = callArg(node.args, 0, ["source", "close"]);
|
|
3751
|
+
const vArg = callArg(node.args, 1, ["volume"]);
|
|
3752
|
+
const close = cArg == null ? num(env.ctx.close) : unwrap(evalExpr(cArg, env));
|
|
3753
|
+
const volume = vArg == null ? num(env.ctx.volume) : unwrap(evalExpr(vArg, env));
|
|
3754
|
+
return (ta.nvi as TaEngine["nvi"]).call(env.ta, site, close, volume);
|
|
2671
3755
|
}
|
|
2672
3756
|
if (fname === "ta.pvi" || fname === "pvi") {
|
|
2673
3757
|
if (typeof ta.pvi !== "function") return NA;
|
|
2674
|
-
|
|
3758
|
+
const cArg = callArg(node.args, 0, ["source", "close"]);
|
|
3759
|
+
const vArg = callArg(node.args, 1, ["volume"]);
|
|
3760
|
+
const close = cArg == null ? num(env.ctx.close) : unwrap(evalExpr(cArg, env));
|
|
3761
|
+
const volume = vArg == null ? num(env.ctx.volume) : unwrap(evalExpr(vArg, env));
|
|
3762
|
+
return (ta.pvi as TaEngine["pvi"]).call(env.ta, site, close, volume);
|
|
2675
3763
|
}
|
|
2676
3764
|
if (fname === "ta.iii" || fname === "iii") {
|
|
2677
3765
|
if (typeof ta.iii !== "function") return NA;
|
|
@@ -2686,16 +3774,98 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2686
3774
|
}
|
|
2687
3775
|
if (fname === "ta.wvad" || fname === "wvad") {
|
|
2688
3776
|
if (typeof ta.wvad !== "function") return NA;
|
|
3777
|
+
const positional = asArgs(node.args).filter((a) => argKeyword(a) == null);
|
|
3778
|
+
if (positional.length >= 4) {
|
|
3779
|
+
return (ta.wvad as TaEngine["wvad"]).call(
|
|
3780
|
+
env.ta,
|
|
3781
|
+
site,
|
|
3782
|
+
unwrap(evalExpr(callArg(node.args, 0, ["high"]), env)),
|
|
3783
|
+
unwrap(evalExpr(callArg(node.args, 1, ["low"]), env)),
|
|
3784
|
+
unwrap(evalExpr(callArg(node.args, 2, ["close"]), env)),
|
|
3785
|
+
unwrap(evalExpr(callArg(node.args, 3, ["volume"]), env)),
|
|
3786
|
+
lenOrDefault(node, env, 4, ["length", "period"], 20),
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
2689
3789
|
return (ta.wvad as TaEngine["wvad"]).call(
|
|
2690
3790
|
env.ta,
|
|
2691
3791
|
site,
|
|
2692
|
-
num(env.ctx.open),
|
|
2693
3792
|
num(env.ctx.high),
|
|
2694
3793
|
num(env.ctx.low),
|
|
2695
3794
|
num(env.ctx.close),
|
|
2696
3795
|
num(env.ctx.volume),
|
|
3796
|
+
// Python 1-arg period-only form gates length via _is_period_like
|
|
3797
|
+
// (volume.py ~586/~620): non-period-like → ignored → default 20 (0-arg
|
|
3798
|
+
// fallback), exactly as if the arg were absent.
|
|
3799
|
+
periodLikeArg(node, env, 0, ["length", "period"]) ?? 20,
|
|
3800
|
+
);
|
|
3801
|
+
}
|
|
3802
|
+
if (fname === "ta.cmf" || fname === "cmf") {
|
|
3803
|
+
if (typeof ta.cmf !== "function") return NA;
|
|
3804
|
+
const positional = asArgs(node.args).filter((a) => argKeyword(a) == null);
|
|
3805
|
+
if (positional.length >= 5) {
|
|
3806
|
+
return (ta.cmf as TaEngine["cmf"]).call(
|
|
3807
|
+
env.ta,
|
|
3808
|
+
site,
|
|
3809
|
+
unwrap(evalExpr(callArg(node.args, 1, ["high"]), env)),
|
|
3810
|
+
unwrap(evalExpr(callArg(node.args, 2, ["low"]), env)),
|
|
3811
|
+
unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
|
|
3812
|
+
unwrap(evalExpr(callArg(node.args, 3, ["volume"]), env)),
|
|
3813
|
+
lenOrDefault(node, env, 4, ["length", "period"], 20),
|
|
3814
|
+
);
|
|
3815
|
+
}
|
|
3816
|
+
return (ta.cmf as TaEngine["cmf"]).call(
|
|
3817
|
+
env.ta,
|
|
3818
|
+
site,
|
|
3819
|
+
num(env.ctx.high),
|
|
3820
|
+
num(env.ctx.low),
|
|
3821
|
+
num(env.ctx.close),
|
|
3822
|
+
num(env.ctx.volume),
|
|
3823
|
+
// Python 1-arg period-only form gates length via _is_period_like
|
|
3824
|
+
// (volume.py ~659/~681): non-period-like → ignored → default 20, as
|
|
3825
|
+
// if the arg were absent.
|
|
3826
|
+
periodLikeArg(node, env, 0, ["length", "period"]) ?? 20,
|
|
2697
3827
|
);
|
|
2698
3828
|
}
|
|
3829
|
+
if (fname === "ta.klinger" || fname === "klinger") {
|
|
3830
|
+
if (typeof ta.klinger !== "function") return NA;
|
|
3831
|
+
return (ta.klinger as TaEngine["klinger"]).call(
|
|
3832
|
+
env.ta,
|
|
3833
|
+
site,
|
|
3834
|
+
unwrap(evalExpr(callArg(node.args, 2, ["close"]), env)),
|
|
3835
|
+
unwrap(evalExpr(callArg(node.args, 3, ["volume"]), env)),
|
|
3836
|
+
lenOrDefault(node, env, 4, ["fast_period", "fast", "fastlen"], 0),
|
|
3837
|
+
lenOrDefault(node, env, 5, ["slow_period", "slow", "slowlen"], 0),
|
|
3838
|
+
);
|
|
3839
|
+
}
|
|
3840
|
+
if (fname === "ta.ao" || fname === "ta.aroon") {
|
|
3841
|
+
// Python parity (pynescript base.py / oscillators.py):
|
|
3842
|
+
// "ao"/"aroon" are absent from _TA_KWARG_ORDERS and their handlers carry no
|
|
3843
|
+
// _KWARG_ORDER, so kwargs fall through the legacy path of
|
|
3844
|
+
// _merge_kwargs_into_args (base.py ~603): kwarg VALUES are appended
|
|
3845
|
+
// positionally after the positional args, in kwarg iteration order —
|
|
3846
|
+
// the names are ignored (ta.ao(slow=10) binds fast=10; ta.ao(len=8)
|
|
3847
|
+
// binds fast=8; ta.aroon(3, length=5) binds length=3).
|
|
3848
|
+
const list = asArgs(node.args);
|
|
3849
|
+
const merged: Cell[] = [];
|
|
3850
|
+
for (const a of list) {
|
|
3851
|
+
if (argKeyword(a) == null) merged.push(unwrap(evalExpr(a.value, env)));
|
|
3852
|
+
}
|
|
3853
|
+
for (const a of list) {
|
|
3854
|
+
if (argKeyword(a) != null) merged.push(unwrap(evalExpr(argValue(a), env)));
|
|
3855
|
+
}
|
|
3856
|
+
// Python handlers gate each length slot on _is_period_like (core.py ~3427):
|
|
3857
|
+
// only whole numbers pass (7.0 → 7; 7.5 / bool / na / non-numbers fail).
|
|
3858
|
+
// A non-period-like arg is IGNORED and the builtin default is used.
|
|
3859
|
+
const periodLike = periodLikeCell;
|
|
3860
|
+
if (fname === "ta.ao") {
|
|
3861
|
+
const fast = merged.length >= 1 ? periodLike(merged[0]!) : null;
|
|
3862
|
+
const slow = merged.length >= 2 ? periodLike(merged[1]!) : null;
|
|
3863
|
+
return env.ta.ao(site, num(env.ctx.high), num(env.ctx.low), fast ?? 5, slow ?? 34);
|
|
3864
|
+
}
|
|
3865
|
+
const length = merged.length >= 1 ? periodLike(merged[0]!) : null;
|
|
3866
|
+
const r = env.ta.aroon(site, num(env.ctx.high), num(env.ctx.low), length ?? 14);
|
|
3867
|
+
return tupleOf([r.down, r.up]);
|
|
3868
|
+
}
|
|
2699
3869
|
if (fname === "ta.pivot_point_levels") {
|
|
2700
3870
|
if (typeof ta.pivotPoints !== "function") return NA;
|
|
2701
3871
|
const r = (ta.pivotPoints as TaEngine["pivotPoints"]).call(
|
|
@@ -2707,6 +3877,80 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2707
3877
|
);
|
|
2708
3878
|
return tupleOf([r.pp, r.r1, r.s1, r.r2, r.s2]);
|
|
2709
3879
|
}
|
|
3880
|
+
if (fname === "ta.uo" || fname === "uo") {
|
|
3881
|
+
return env.ta.uo(
|
|
3882
|
+
site,
|
|
3883
|
+
num(env.ctx.high),
|
|
3884
|
+
num(env.ctx.low),
|
|
3885
|
+
num(env.ctx.close),
|
|
3886
|
+
lenArg(node, env, 0),
|
|
3887
|
+
lenArg(node, env, 1),
|
|
3888
|
+
lenArg(node, env, 2),
|
|
3889
|
+
);
|
|
3890
|
+
}
|
|
3891
|
+
if (fname === "ta.rci" || fname === "rci") {
|
|
3892
|
+
return env.ta.rci(site, srcArg(node, env), lenArg(node, env, 1));
|
|
3893
|
+
}
|
|
3894
|
+
if (fname === "ta.dpo" || fname === "dpo") {
|
|
3895
|
+
return env.ta.dpo(site, num(env.ctx.close), lenArg(node, env, 0));
|
|
3896
|
+
}
|
|
3897
|
+
if (fname === "ta.kst" || fname === "kst") {
|
|
3898
|
+
return env.ta.kst(
|
|
3899
|
+
site,
|
|
3900
|
+
num(env.ctx.close),
|
|
3901
|
+
lenArg(node, env, 0),
|
|
3902
|
+
lenArg(node, env, 1),
|
|
3903
|
+
lenArg(node, env, 2),
|
|
3904
|
+
lenArg(node, env, 3),
|
|
3905
|
+
);
|
|
3906
|
+
}
|
|
3907
|
+
if (fname === "ta.stochrsi" || fname === "stochrsi") {
|
|
3908
|
+
return env.ta.stochRsi(site, num(env.ctx.close), lenArg(node, env, 0), lenArg(node, env, 1));
|
|
3909
|
+
}
|
|
3910
|
+
if (fname === "ta.donchian" || fname === "donchian") {
|
|
3911
|
+
return env.ta.donchian(site, num(env.ctx.high), num(env.ctx.low), lenArg(node, env, 0));
|
|
3912
|
+
}
|
|
3913
|
+
if (fname === "ta.ichimoku" || fname === "ichimoku") {
|
|
3914
|
+
return env.ta.ichimoku(
|
|
3915
|
+
site,
|
|
3916
|
+
num(env.ctx.high),
|
|
3917
|
+
num(env.ctx.low),
|
|
3918
|
+
lenArg(node, env, 0),
|
|
3919
|
+
lenArg(node, env, 1),
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
if (fname === "ta.bb_pct" || fname === "bb_pct") {
|
|
3923
|
+
return env.ta.bbPct(
|
|
3924
|
+
site,
|
|
3925
|
+
num(env.ctx.close),
|
|
3926
|
+
lenArg(node, env, 0),
|
|
3927
|
+
numArg(node, env, 1, ["std_dev", "mult", "std"], 2),
|
|
3928
|
+
);
|
|
3929
|
+
}
|
|
3930
|
+
if (fname === "ta.emv" || fname === "emv") {
|
|
3931
|
+
return env.ta.emv(
|
|
3932
|
+
site,
|
|
3933
|
+
num(env.ctx.high),
|
|
3934
|
+
num(env.ctx.low),
|
|
3935
|
+
num(env.ctx.volume),
|
|
3936
|
+
lenArg(node, env, 0),
|
|
3937
|
+
);
|
|
3938
|
+
}
|
|
3939
|
+
if (fname === "ta.fractal" || fname === "fractal") {
|
|
3940
|
+
return env.ta.fractal(site, num(env.ctx.high), num(env.ctx.low), lenArg(node, env, 0));
|
|
3941
|
+
}
|
|
3942
|
+
if (fname === "ta.atr_stop" || fname === "atr_stop") {
|
|
3943
|
+
const atr = unwrap(evalExpr(callArg(node.args, 0, ["atr", "atr_value"]), env));
|
|
3944
|
+
const multRaw = unwrap(evalExpr(callArg(node.args, 1, ["multiplier", "mult"]), env));
|
|
3945
|
+
const mult = multRaw == null || !Number.isFinite(multRaw) ? 2 : multRaw;
|
|
3946
|
+
return env.ta.atrStop(num(env.ctx.close), atr, mult);
|
|
3947
|
+
}
|
|
3948
|
+
if (fname === "ta.zigzag" || fname === "zigzag") {
|
|
3949
|
+
const thRaw = unwrap(evalExpr(callArg(node.args, 1, ["threshold", "percent", "deviation"]), env));
|
|
3950
|
+
const th = thRaw == null || !Number.isFinite(thRaw) ? 5 : thRaw;
|
|
3951
|
+
const z = env.ta.zigzag(site, srcArg(node, env), th);
|
|
3952
|
+
return tupleOf([z.high, z.low, z.dir]);
|
|
3953
|
+
}
|
|
2710
3954
|
return undefined;
|
|
2711
3955
|
}
|
|
2712
3956
|
|
|
@@ -2723,37 +3967,74 @@ function asArray(value: Value): PineArray | null {
|
|
|
2723
3967
|
if (value && typeof value === "object" && "__array" in value) {
|
|
2724
3968
|
const elts = (value as ArrayVal).elts;
|
|
2725
3969
|
const arr = new PineArray();
|
|
2726
|
-
for (const e of elts) arr.push(
|
|
3970
|
+
for (const e of elts) arr.push(storeValue(e));
|
|
2727
3971
|
return arr;
|
|
2728
3972
|
}
|
|
2729
3973
|
return null;
|
|
2730
3974
|
}
|
|
2731
3975
|
|
|
3976
|
+
/** Every `array.new*` alias Python registers in `_array_builtin_map`. */
|
|
3977
|
+
const ARRAY_NEW_FNS = new Set([
|
|
3978
|
+
"array.new",
|
|
3979
|
+
"array.new_bool",
|
|
3980
|
+
"array.new_int",
|
|
3981
|
+
"array.new_float",
|
|
3982
|
+
"array.new_string",
|
|
3983
|
+
"array.new_color",
|
|
3984
|
+
"array.newbool",
|
|
3985
|
+
"array.newint",
|
|
3986
|
+
"array.newfloat",
|
|
3987
|
+
"array.newstring",
|
|
3988
|
+
"array.newcolor",
|
|
3989
|
+
"array.newlabel",
|
|
3990
|
+
"array.newline",
|
|
3991
|
+
"array.newbox",
|
|
3992
|
+
"array.newtable",
|
|
3993
|
+
"array.newpolyline",
|
|
3994
|
+
"array.newlinefill",
|
|
3995
|
+
"array.new_label",
|
|
3996
|
+
"array.new_line",
|
|
3997
|
+
"array.new_box",
|
|
3998
|
+
"array.new_table",
|
|
3999
|
+
"array.new_polyline",
|
|
4000
|
+
"array.new_linefill",
|
|
4001
|
+
"array.new_chart.point",
|
|
4002
|
+
]);
|
|
4003
|
+
|
|
4004
|
+
function isArrayNew(fname: string | null): boolean {
|
|
4005
|
+
const name = arrayBuiltin(fname);
|
|
4006
|
+
return name != null && ARRAY_NEW_FNS.has(name);
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
function arrayBuiltin(fname: string | null): string | null {
|
|
4010
|
+
if (fname == null || !fname.startsWith("array.")) return null;
|
|
4011
|
+
const lt = fname.indexOf("<");
|
|
4012
|
+
return lt >= 0 ? fname.slice(0, lt) : fname;
|
|
4013
|
+
}
|
|
4014
|
+
|
|
2732
4015
|
function evalArrayCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
fname === "array.new" ||
|
|
2736
|
-
fname === "array.new_int"
|
|
2737
|
-
) {
|
|
4016
|
+
const aname = arrayBuiltin(fname);
|
|
4017
|
+
if (isArrayNew(fname)) {
|
|
2738
4018
|
const size = unwrap(evalExpr(callArg(node.args, 0, ["size"]), env)) ?? 0;
|
|
2739
|
-
const
|
|
4019
|
+
const initialArg = callArg(node.args, 1, ["initial_value"]);
|
|
4020
|
+
const initial = initialArg == null ? undefined : storeValue(evalExpr(initialArg, env));
|
|
2740
4021
|
return new PineArray(size, initial);
|
|
2741
4022
|
}
|
|
2742
|
-
if (
|
|
4023
|
+
if (aname === "array.push") {
|
|
2743
4024
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2744
|
-
const v =
|
|
4025
|
+
const v = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
2745
4026
|
arr?.push(v);
|
|
2746
4027
|
return NA;
|
|
2747
4028
|
}
|
|
2748
|
-
if (
|
|
4029
|
+
if (aname === "array.get") {
|
|
2749
4030
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2750
4031
|
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2751
|
-
return arr ? arr.get(i ?? 0) : NA;
|
|
4032
|
+
return arr ? asArrayValue(arr.get(i ?? 0)) : NA;
|
|
2752
4033
|
}
|
|
2753
|
-
if (
|
|
4034
|
+
if (aname === "array.set") {
|
|
2754
4035
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2755
4036
|
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2756
|
-
const v =
|
|
4037
|
+
const v = storeValue(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
2757
4038
|
if (arr && i != null) arr.set(i, v);
|
|
2758
4039
|
return NA;
|
|
2759
4040
|
}
|
|
@@ -2763,7 +4044,7 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
2763
4044
|
}
|
|
2764
4045
|
if (fname === "array.pop") {
|
|
2765
4046
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2766
|
-
return arr ? arr.pop() : NA;
|
|
4047
|
+
return arr ? asArrayValue(arr.pop()) : NA;
|
|
2767
4048
|
}
|
|
2768
4049
|
if (fname === "array.clear") {
|
|
2769
4050
|
asArray(evalExpr(callArg(node.args, 0, ["id"]), env))?.clear();
|
|
@@ -2771,40 +4052,40 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
2771
4052
|
}
|
|
2772
4053
|
if (fname === "array.unshift") {
|
|
2773
4054
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2774
|
-
arr?.unshift(
|
|
4055
|
+
arr?.unshift(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env)));
|
|
2775
4056
|
return NA;
|
|
2776
4057
|
}
|
|
2777
4058
|
if (fname === "array.shift") {
|
|
2778
4059
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2779
|
-
return arr ? arr.shift() : NA;
|
|
4060
|
+
return arr ? asArrayValue(arr.shift()) : NA;
|
|
2780
4061
|
}
|
|
2781
4062
|
if (fname === "array.includes") {
|
|
2782
4063
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2783
|
-
return arr?.includes(
|
|
4064
|
+
return arr?.includes(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env))) ? 1 : 0;
|
|
2784
4065
|
}
|
|
2785
4066
|
if (fname === "array.first") {
|
|
2786
4067
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2787
|
-
return arr ? arr.first() : NA;
|
|
4068
|
+
return arr ? asArrayValue(arr.first()) : NA;
|
|
2788
4069
|
}
|
|
2789
4070
|
if (fname === "array.last") {
|
|
2790
4071
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2791
|
-
return arr ? arr.last() : NA;
|
|
4072
|
+
return arr ? asArrayValue(arr.last()) : NA;
|
|
2792
4073
|
}
|
|
2793
4074
|
if (fname === "array.insert") {
|
|
2794
4075
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2795
4076
|
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2796
|
-
const v =
|
|
4077
|
+
const v = storeValue(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
2797
4078
|
if (arr && i != null) arr.insert(i, v);
|
|
2798
4079
|
return NA;
|
|
2799
4080
|
}
|
|
2800
4081
|
if (fname === "array.remove") {
|
|
2801
4082
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2802
4083
|
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2803
|
-
return arr && i != null ? arr.remove(i) : NA;
|
|
4084
|
+
return arr && i != null ? asArrayValue(arr.remove(i)) : NA;
|
|
2804
4085
|
}
|
|
2805
4086
|
if (fname === "array.fill") {
|
|
2806
4087
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2807
|
-
arr?.fill(
|
|
4088
|
+
arr?.fill(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env)));
|
|
2808
4089
|
return NA;
|
|
2809
4090
|
}
|
|
2810
4091
|
if (fname === "array.slice") {
|
|
@@ -2824,14 +4105,13 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
2824
4105
|
}
|
|
2825
4106
|
if (fname === "array.sort") {
|
|
2826
4107
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2827
|
-
const
|
|
2828
|
-
|
|
2829
|
-
arr?.sort(order);
|
|
4108
|
+
const parsed = parseSortArgs(node, env);
|
|
4109
|
+
arr?.sort(parsed.order, parsed.sortField);
|
|
2830
4110
|
return NA;
|
|
2831
4111
|
}
|
|
2832
4112
|
if (fname === "array.indexof") {
|
|
2833
4113
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2834
|
-
return arr ? arr.indexof(
|
|
4114
|
+
return arr ? arr.indexof(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
2835
4115
|
}
|
|
2836
4116
|
if (fname === "array.avg") {
|
|
2837
4117
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
@@ -2854,19 +4134,14 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
2854
4134
|
const sep = evalAsString(callArg(node.args, 1, ["separator", "sep"]), env);
|
|
2855
4135
|
return arr ? arr.join(sep ?? ",") : NA;
|
|
2856
4136
|
}
|
|
2857
|
-
if (fname === "array.new_bool" || fname === "array.new_string") {
|
|
2858
|
-
const size = unwrap(evalExpr(callArg(node.args, 0, ["size"]), env)) ?? 0;
|
|
2859
|
-
const initial = unwrap(evalExpr(callArg(node.args, 1, ["initial_value"]), env));
|
|
2860
|
-
return new PineArray(size, initial);
|
|
2861
|
-
}
|
|
2862
4137
|
if (fname === "array.from") {
|
|
2863
4138
|
const arr = new PineArray();
|
|
2864
|
-
for (const a of asArgs(node.args)) arr.push(
|
|
4139
|
+
for (const a of asArgs(node.args)) arr.push(storeValue(evalExpr(a.value, env)));
|
|
2865
4140
|
return arr;
|
|
2866
4141
|
}
|
|
2867
4142
|
if (fname === "array.lastindexof") {
|
|
2868
4143
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2869
|
-
return arr ? arr.lastIndexOf(
|
|
4144
|
+
return arr ? arr.lastIndexOf(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
2870
4145
|
}
|
|
2871
4146
|
if (fname === "array.concat") {
|
|
2872
4147
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
@@ -2899,15 +4174,18 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
2899
4174
|
}
|
|
2900
4175
|
if (fname === "array.binary_search") {
|
|
2901
4176
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2902
|
-
|
|
4177
|
+
const value = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
4178
|
+
return arr ? arr.binarySearch(value, evalSortField(node, env)) : NA;
|
|
2903
4179
|
}
|
|
2904
4180
|
if (fname === "array.binary_search_leftmost") {
|
|
2905
4181
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2906
|
-
|
|
4182
|
+
const value = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
4183
|
+
return arr ? arr.binarySearchLeftmost(value, evalSortField(node, env)) : NA;
|
|
2907
4184
|
}
|
|
2908
4185
|
if (fname === "array.binary_search_rightmost") {
|
|
2909
4186
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2910
|
-
|
|
4187
|
+
const value = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
4188
|
+
return arr ? arr.binarySearchRightmost(value, evalSortField(node, env)) : NA;
|
|
2911
4189
|
}
|
|
2912
4190
|
if (fname === "array.stdev") {
|
|
2913
4191
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
@@ -2943,10 +4221,9 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
2943
4221
|
}
|
|
2944
4222
|
if (fname === "array.sort_indices") {
|
|
2945
4223
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2946
|
-
const
|
|
2947
|
-
const order = typeof orderRaw === "string" && orderRaw.toLowerCase().includes("desc") ? "desc" : "asc";
|
|
4224
|
+
const parsed = parseSortArgs(node, env);
|
|
2948
4225
|
if (!arr) return NA;
|
|
2949
|
-
const idx = arr.sortIndices(order);
|
|
4226
|
+
const idx = arr.sortIndices(parsed.order, parsed.sortField);
|
|
2950
4227
|
if (idx == null) return NA;
|
|
2951
4228
|
const out = new PineArray();
|
|
2952
4229
|
for (const i of idx) out.push(i);
|
|
@@ -3048,20 +4325,20 @@ function evalMatrixCall(fname: string | null, node: Call, env: Env): Value | und
|
|
|
3048
4325
|
const rows = unwrap(evalExpr(callArg(node.args, 0, ["rows"]), env)) ?? 0;
|
|
3049
4326
|
const cols = unwrap(evalExpr(callArg(node.args, 1, ["columns", "cols"]), env)) ?? 0;
|
|
3050
4327
|
const initialArg = callArg(node.args, 2, ["initial_value", "initial"]);
|
|
3051
|
-
const initial = initialArg == null ? undefined :
|
|
4328
|
+
const initial = initialArg == null ? undefined : storeValue(evalExpr(initialArg, env));
|
|
3052
4329
|
return new PineMatrix(rows, cols, initial);
|
|
3053
4330
|
}
|
|
3054
4331
|
if (name === "matrix.get") {
|
|
3055
4332
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3056
4333
|
const row = unwrap(evalExpr(callArg(node.args, 1, ["row"]), env));
|
|
3057
4334
|
const col = unwrap(evalExpr(callArg(node.args, 2, ["column", "col"]), env));
|
|
3058
|
-
return m ? m.get(row ?? 0, col ?? 0) : NA;
|
|
4335
|
+
return m ? asArrayValue(m.get(row ?? 0, col ?? 0)) : NA;
|
|
3059
4336
|
}
|
|
3060
4337
|
if (name === "matrix.set") {
|
|
3061
4338
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3062
4339
|
const row = unwrap(evalExpr(callArg(node.args, 1, ["row"]), env));
|
|
3063
4340
|
const col = unwrap(evalExpr(callArg(node.args, 2, ["column", "col"]), env));
|
|
3064
|
-
const v =
|
|
4341
|
+
const v = storeValue(evalExpr(callArg(node.args, 3, ["value"]), env));
|
|
3065
4342
|
if (m && row != null && col != null) m.set(row, col, v);
|
|
3066
4343
|
return NA;
|
|
3067
4344
|
}
|
|
@@ -3075,7 +4352,7 @@ function evalMatrixCall(fname: string | null, node: Call, env: Env): Value | und
|
|
|
3075
4352
|
}
|
|
3076
4353
|
if (name === "matrix.fill") {
|
|
3077
4354
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3078
|
-
const v =
|
|
4355
|
+
const v = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
3079
4356
|
m?.fill(v);
|
|
3080
4357
|
return NA;
|
|
3081
4358
|
}
|
|
@@ -3274,14 +4551,115 @@ function evalMatrixCall(fname: string | null, node: Call, env: Env): Value | und
|
|
|
3274
4551
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3275
4552
|
const col = unwrap(evalExpr(callArg(node.args, 1, ["column", "col"]), env)) ?? 0;
|
|
3276
4553
|
const orderRaw = evalExpr(callArg(node.args, 2, ["order"]), env);
|
|
3277
|
-
const order =
|
|
3278
|
-
|
|
4554
|
+
const order = isDescendingOrder(orderRaw) ? "desc" : "asc";
|
|
4555
|
+
const sortField = sortFieldValue(evalExpr(callArg(node.args, 3, ["sort_field"]), env));
|
|
4556
|
+
m?.sort(col, order, sortField);
|
|
3279
4557
|
return NA;
|
|
3280
4558
|
}
|
|
4559
|
+
if (name === "matrix.sort_indices") {
|
|
4560
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
4561
|
+
if (!m) return NA;
|
|
4562
|
+
const col = unwrap(evalExpr(callArg(node.args, 1, ["column", "col"]), env)) ?? 0;
|
|
4563
|
+
const orderRaw = evalExpr(callArg(node.args, 2, ["order"]), env);
|
|
4564
|
+
const order = isDescendingOrder(orderRaw) ? "desc" : "asc";
|
|
4565
|
+
const sortField = sortFieldValue(evalExpr(callArg(node.args, 3, ["sort_field"]), env));
|
|
4566
|
+
const idx = m.sortIndices(col, order, sortField);
|
|
4567
|
+
const out = new PineArray();
|
|
4568
|
+
for (const i of idx) out.push(i);
|
|
4569
|
+
return out;
|
|
4570
|
+
}
|
|
3281
4571
|
if (name === "matrix.median") {
|
|
3282
4572
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3283
4573
|
return m ? m.median() : NA;
|
|
3284
4574
|
}
|
|
4575
|
+
if (
|
|
4576
|
+
name === "matrix.sum_row" ||
|
|
4577
|
+
name === "matrix.avg_row" ||
|
|
4578
|
+
name === "matrix.min_row" ||
|
|
4579
|
+
name === "matrix.max_row" ||
|
|
4580
|
+
name === "matrix.mode_row"
|
|
4581
|
+
) {
|
|
4582
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4583
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["row", "index"]), env));
|
|
4584
|
+
if (!m || i == null) return NA;
|
|
4585
|
+
if (name === "matrix.sum_row") return m.sumRow(i);
|
|
4586
|
+
if (name === "matrix.avg_row") return m.avgRow(i);
|
|
4587
|
+
if (name === "matrix.min_row") return m.minRow(i);
|
|
4588
|
+
if (name === "matrix.max_row") return m.maxRow(i);
|
|
4589
|
+
return m.modeRow(i) as Value;
|
|
4590
|
+
}
|
|
4591
|
+
if (
|
|
4592
|
+
name === "matrix.sum_col" ||
|
|
4593
|
+
name === "matrix.avg_col" ||
|
|
4594
|
+
name === "matrix.min_col" ||
|
|
4595
|
+
name === "matrix.max_col" ||
|
|
4596
|
+
name === "matrix.mode_col"
|
|
4597
|
+
) {
|
|
4598
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4599
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["column", "col", "index"]), env));
|
|
4600
|
+
if (!m || i == null) return NA;
|
|
4601
|
+
if (name === "matrix.sum_col") return m.sumCol(i);
|
|
4602
|
+
if (name === "matrix.avg_col") return m.avgCol(i);
|
|
4603
|
+
if (name === "matrix.min_col") return m.minCol(i);
|
|
4604
|
+
if (name === "matrix.max_col") return m.maxCol(i);
|
|
4605
|
+
return m.modeCol(i) as Value;
|
|
4606
|
+
}
|
|
4607
|
+
if (name === "matrix.copy_row") {
|
|
4608
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4609
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["row", "index"]), env));
|
|
4610
|
+
if (!m || i == null) return NA;
|
|
4611
|
+
const arr = new PineArray();
|
|
4612
|
+
for (const v of m.copyRow(i)) arr.push(v);
|
|
4613
|
+
return arr;
|
|
4614
|
+
}
|
|
4615
|
+
if (name === "matrix.copy_col") {
|
|
4616
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4617
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["column", "col", "index"]), env));
|
|
4618
|
+
if (!m || i == null) return NA;
|
|
4619
|
+
const arr = new PineArray();
|
|
4620
|
+
for (const v of m.copyCol(i)) arr.push(v);
|
|
4621
|
+
return arr;
|
|
4622
|
+
}
|
|
4623
|
+
if (name === "matrix.fill_row") {
|
|
4624
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4625
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["row", "index"]), env));
|
|
4626
|
+
const v = storeValue(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
4627
|
+
if (m && i != null) m.fillRow(i, v);
|
|
4628
|
+
return NA;
|
|
4629
|
+
}
|
|
4630
|
+
if (name === "matrix.fill_col") {
|
|
4631
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4632
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["column", "col", "index"]), env));
|
|
4633
|
+
const v = storeValue(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
4634
|
+
if (m && i != null) m.fillCol(i, v);
|
|
4635
|
+
return NA;
|
|
4636
|
+
}
|
|
4637
|
+
if (name === "matrix.fill_diagonal") {
|
|
4638
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4639
|
+
const v = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
4640
|
+
m?.fillDiagonal(v);
|
|
4641
|
+
return NA;
|
|
4642
|
+
}
|
|
4643
|
+
if (name === "matrix.reverse_rows") {
|
|
4644
|
+
asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env))?.reverseRows();
|
|
4645
|
+
return NA;
|
|
4646
|
+
}
|
|
4647
|
+
if (name === "matrix.reverse_cols") {
|
|
4648
|
+
asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env))?.reverseCols();
|
|
4649
|
+
return NA;
|
|
4650
|
+
}
|
|
4651
|
+
if (name === "matrix.stdev") {
|
|
4652
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4653
|
+
return m ? m.stdev() : NA;
|
|
4654
|
+
}
|
|
4655
|
+
if (name === "matrix.variance") {
|
|
4656
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4657
|
+
return m ? m.variance() : NA;
|
|
4658
|
+
}
|
|
4659
|
+
if (name === "matrix.mode_all") {
|
|
4660
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4661
|
+
return m ? (m.modeAll() as Value) : NA;
|
|
4662
|
+
}
|
|
3285
4663
|
if (name === "matrix.mode") {
|
|
3286
4664
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3287
4665
|
return m ? m.mode() : NA;
|
|
@@ -3301,125 +4679,808 @@ function evalMatrixCall(fname: string | null, node: Call, env: Env): Value | und
|
|
|
3301
4679
|
return undefined;
|
|
3302
4680
|
}
|
|
3303
4681
|
|
|
4682
|
+
function isChartPoint(value: Value): value is ChartPointVal {
|
|
4683
|
+
return typeof value === "object" && value !== null && (value as ChartPointVal).__chartPoint === true;
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4686
|
+
function makeChartPoint(index: number | null, time: number | null, price: number | null): ChartPointVal {
|
|
4687
|
+
const x = index ?? time;
|
|
4688
|
+
return { __chartPoint: true, index, time, x, y: price, price };
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
function chartPointField(obj: ChartPointVal, attr: string): Value {
|
|
4692
|
+
if (attr === "__chartPoint" || !Object.prototype.hasOwnProperty.call(obj, attr)) return NA;
|
|
4693
|
+
const v = obj[attr as keyof ChartPointVal];
|
|
4694
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : NA;
|
|
4695
|
+
return NA;
|
|
4696
|
+
}
|
|
4697
|
+
|
|
4698
|
+
function drawingAllKind(name: string): DrawingKind | null {
|
|
4699
|
+
if (
|
|
4700
|
+
name === "line" ||
|
|
4701
|
+
name === "label" ||
|
|
4702
|
+
name === "box" ||
|
|
4703
|
+
name === "table" ||
|
|
4704
|
+
name === "polyline" ||
|
|
4705
|
+
name === "linefill"
|
|
4706
|
+
) {
|
|
4707
|
+
return name;
|
|
4708
|
+
}
|
|
4709
|
+
return null;
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4712
|
+
function drawingAllArray(env: Env, kind: DrawingKind): PineArray {
|
|
4713
|
+
const arr = new PineArray();
|
|
4714
|
+
for (const id of env.drawings.allIds(kind)) arr.push(id);
|
|
4715
|
+
return arr;
|
|
4716
|
+
}
|
|
4717
|
+
|
|
4718
|
+
function drawId(node: Call, env: Env): number | null {
|
|
4719
|
+
return unwrap(evalExpr(callArg(node.args, 0, ["id", "table_id", "table"]), env));
|
|
4720
|
+
}
|
|
4721
|
+
|
|
4722
|
+
function drawNum(node: Call, env: Env, index: number, names: string[]): number | null {
|
|
4723
|
+
const e = callArg(node.args, index, names);
|
|
4724
|
+
return e == null ? null : unwrap(evalExpr(e, env));
|
|
4725
|
+
}
|
|
4726
|
+
|
|
4727
|
+
function drawRaw(node: Call, env: Env, index: number, names: string[]): unknown {
|
|
4728
|
+
const e = callArg(node.args, index, names);
|
|
4729
|
+
if (e == null) return undefined;
|
|
4730
|
+
const v = evalExpr(e, env);
|
|
4731
|
+
if (typeof v === "string" || typeof v === "boolean" || isChartPoint(v) || v instanceof PineArray) return v;
|
|
4732
|
+
const n = unwrap(v);
|
|
4733
|
+
return n != null ? n : v;
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4736
|
+
function drawText(node: Call, env: Env, index: number, names: string[], fallback = ""): string {
|
|
4737
|
+
const e = callArg(node.args, index, names);
|
|
4738
|
+
if (e == null) return fallback;
|
|
4739
|
+
const s = evalAsString(e, env);
|
|
4740
|
+
if (s != null) return s;
|
|
4741
|
+
const v = evalExpr(e, env);
|
|
4742
|
+
if (typeof v === "string") return v;
|
|
4743
|
+
const n = unwrap(v);
|
|
4744
|
+
return n == null ? fallback : String(n);
|
|
4745
|
+
}
|
|
4746
|
+
|
|
4747
|
+
function putFinite(extra: Record<string, unknown>, key: string, value: number | null | undefined): void {
|
|
4748
|
+
if (value != null && Number.isFinite(value)) extra[key] = value;
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4751
|
+
function optionalCoord(node: Call, env: Env, index: number, names: string[], fallback: number): number | null {
|
|
4752
|
+
const e = callArg(node.args, index, names);
|
|
4753
|
+
if (e == null) return fallback;
|
|
4754
|
+
return unwrap(evalExpr(e, env));
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4757
|
+
function pointXY(p: ChartPointVal): { x: number; y: number } {
|
|
4758
|
+
const x = p.index ?? p.time ?? p.x ?? 0;
|
|
4759
|
+
const y = p.price ?? p.y ?? 0;
|
|
4760
|
+
return { x: x ?? 0, y: y ?? 0 };
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
function pointsFromValue(value: Value): unknown[] {
|
|
4764
|
+
const arr = asArray(value);
|
|
4765
|
+
if (!arr) return [];
|
|
4766
|
+
return arr.toValues().filter((p) => p != null);
|
|
4767
|
+
}
|
|
4768
|
+
|
|
3304
4769
|
function evalDrawingCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
4770
|
+
if (fname == null) return undefined;
|
|
4771
|
+
const book = env.drawings;
|
|
4772
|
+
|
|
4773
|
+
if (fname === "line" || fname === "box" || fname === "label" || fname === "table" || fname === "polyline" || fname === "linefill") {
|
|
4774
|
+
const e = callArg(node.args, 0, ["id", "x"]);
|
|
4775
|
+
return e == null ? NA : evalExpr(e, env);
|
|
4776
|
+
}
|
|
4777
|
+
|
|
3305
4778
|
if (fname === "line.new") {
|
|
3306
|
-
|
|
4779
|
+
const extra: Record<string, unknown> = {};
|
|
4780
|
+
putFinite(extra, "x1", optionalCoord(node, env, 0, ["x1"], 0));
|
|
4781
|
+
putFinite(extra, "y1", optionalCoord(node, env, 1, ["y1"], 0));
|
|
4782
|
+
putFinite(extra, "x2", optionalCoord(node, env, 2, ["x2"], 0));
|
|
4783
|
+
putFinite(extra, "y2", optionalCoord(node, env, 3, ["y2"], 0));
|
|
4784
|
+
const xloc = drawRaw(node, env, 4, ["xloc"]);
|
|
4785
|
+
if (xloc !== undefined) extra.xloc = xloc;
|
|
4786
|
+
const color = drawRaw(node, env, 5, ["color"]);
|
|
4787
|
+
if (color !== undefined) extra.color = color;
|
|
4788
|
+
putFinite(extra, "width", drawNum(node, env, 6, ["width"]));
|
|
4789
|
+
const style = drawRaw(node, env, 7, ["style"]);
|
|
4790
|
+
if (style !== undefined) extra.style = style;
|
|
4791
|
+
const extend = drawRaw(node, env, 8, ["extend"]);
|
|
4792
|
+
if (extend !== undefined) extra.extend = extend;
|
|
4793
|
+
return book.lineNew(env.barIndex, extra);
|
|
3307
4794
|
}
|
|
3308
4795
|
if (fname === "label.new") {
|
|
3309
|
-
const
|
|
3310
|
-
|
|
4796
|
+
const pointKw = callArg(node.args, -1, ["point"]);
|
|
4797
|
+
const a0 = callArg(node.args, 0, ["x", "point"]);
|
|
4798
|
+
const v0 = pointKw != null ? evalExpr(pointKw, env) : a0 != null ? evalExpr(a0, env) : NA;
|
|
4799
|
+
const extra: Record<string, unknown> = {};
|
|
4800
|
+
let text: string;
|
|
4801
|
+
if (isChartPoint(v0)) {
|
|
4802
|
+
const xy = pointXY(v0);
|
|
4803
|
+
extra.x = xy.x;
|
|
4804
|
+
extra.y = xy.y;
|
|
4805
|
+
text = drawText(node, env, pointKw != null ? 0 : 1, ["text"]);
|
|
4806
|
+
} else {
|
|
4807
|
+
putFinite(extra, "x", a0 == null ? 0 : unwrap(v0));
|
|
4808
|
+
putFinite(extra, "y", optionalCoord(node, env, 1, ["y"], 0));
|
|
4809
|
+
text = drawText(node, env, 2, ["text"]);
|
|
4810
|
+
}
|
|
4811
|
+
const tooltip = drawRaw(node, env, 12, ["tooltip"]);
|
|
4812
|
+
if (tooltip !== undefined) extra.tooltip = tooltip;
|
|
4813
|
+
const style = drawRaw(node, env, 13, ["style"]);
|
|
4814
|
+
if (style !== undefined) extra.style = style;
|
|
4815
|
+
return book.labelNew(env.barIndex, text, extra);
|
|
3311
4816
|
}
|
|
3312
4817
|
if (fname === "box.new") {
|
|
3313
|
-
|
|
4818
|
+
const extra: Record<string, unknown> = {};
|
|
4819
|
+
putFinite(extra, "left", optionalCoord(node, env, 0, ["left"], 0));
|
|
4820
|
+
putFinite(extra, "top", optionalCoord(node, env, 1, ["top"], 0));
|
|
4821
|
+
putFinite(extra, "right", optionalCoord(node, env, 2, ["right"], 0));
|
|
4822
|
+
putFinite(extra, "bottom", optionalCoord(node, env, 3, ["bottom"], 0));
|
|
4823
|
+
const bgcolor = drawRaw(node, env, 6, ["bgcolor"]);
|
|
4824
|
+
if (bgcolor !== undefined) extra.bgcolor = bgcolor;
|
|
4825
|
+
const text = callArg(node.args, -1, ["text"]);
|
|
4826
|
+
if (text != null) extra.text = drawText(node, env, -1, ["text"]);
|
|
4827
|
+
return book.boxNew(env.barIndex, extra);
|
|
3314
4828
|
}
|
|
3315
4829
|
if (fname === "alert") {
|
|
3316
4830
|
const msg = evalAlertMessage(callArg(node.args, 0, ["message"]), env);
|
|
3317
|
-
|
|
4831
|
+
book.alert(env.barIndex, msg);
|
|
3318
4832
|
return NA;
|
|
3319
4833
|
}
|
|
3320
|
-
if (fname === "table.new")
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
4834
|
+
if (fname === "table.new") {
|
|
4835
|
+
const extra: Record<string, unknown> = {};
|
|
4836
|
+
const position = drawRaw(node, env, 0, ["position"]);
|
|
4837
|
+
if (position !== undefined) extra.position = position;
|
|
4838
|
+
putFinite(extra, "rows", drawNum(node, env, 1, ["rows"]));
|
|
4839
|
+
putFinite(extra, "columns", drawNum(node, env, 2, ["columns"]));
|
|
4840
|
+
return book.tableNew(env.barIndex, extra);
|
|
4841
|
+
}
|
|
4842
|
+
if (fname === "polyline.new") {
|
|
4843
|
+
const extra: Record<string, unknown> = {};
|
|
4844
|
+
const ptsExpr = callArg(node.args, 0, ["points"]);
|
|
4845
|
+
extra.points = ptsExpr == null ? [] : pointsFromValue(evalExpr(ptsExpr, env));
|
|
4846
|
+
const closed = drawRaw(node, env, 1, ["closed"]);
|
|
4847
|
+
if (closed !== undefined) extra.closed = closed;
|
|
4848
|
+
const color = drawRaw(node, env, 3, ["color"]);
|
|
4849
|
+
if (color !== undefined) extra.color = color;
|
|
4850
|
+
putFinite(extra, "width", drawNum(node, env, 4, ["width"]));
|
|
4851
|
+
return book.polylineNew(env.barIndex, extra);
|
|
4852
|
+
}
|
|
4853
|
+
if (fname === "linefill.new" || fname === "line.fill") {
|
|
4854
|
+
const a = unwrap(evalExpr(callArg(node.args, 0, ["id1", "line1"]), env));
|
|
4855
|
+
const b = unwrap(evalExpr(callArg(node.args, 1, ["id2", "line2"]), env));
|
|
4856
|
+
const extra: Record<string, unknown> = {};
|
|
4857
|
+
if (a != null) extra.id1 = a;
|
|
4858
|
+
if (b != null) extra.id2 = b;
|
|
4859
|
+
const color = drawRaw(node, env, 2, ["color"]);
|
|
4860
|
+
if (color !== undefined) extra.color = color;
|
|
4861
|
+
return book.linefillNew(env.barIndex, extra);
|
|
4862
|
+
}
|
|
4863
|
+
|
|
4864
|
+
if (fname.endsWith(".all")) {
|
|
4865
|
+
const kind = drawingAllKind(fname.slice(0, -4));
|
|
4866
|
+
if (kind != null) return drawingAllArray(env, kind);
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
if (fname === "chart.point.new" || fname === "chart.point.from_time") {
|
|
4870
|
+
const time = drawNum(node, env, 0, ["time"]);
|
|
4871
|
+
const priceExpr = callArg(node.args, 1, ["price"]);
|
|
4872
|
+
const price = priceExpr == null ? 0 : unwrap(evalExpr(priceExpr, env));
|
|
4873
|
+
if (price == null) return NA;
|
|
4874
|
+
return makeChartPoint(null, time, price);
|
|
4875
|
+
}
|
|
4876
|
+
if (fname === "chart.point.from_index") {
|
|
4877
|
+
const index = drawNum(node, env, 0, ["index"]);
|
|
4878
|
+
const priceExpr = callArg(node.args, 1, ["price"]);
|
|
4879
|
+
const price = priceExpr == null ? 0 : unwrap(evalExpr(priceExpr, env));
|
|
4880
|
+
if (price == null || index == null) return NA;
|
|
4881
|
+
return makeChartPoint(Math.trunc(index), null, price);
|
|
4882
|
+
}
|
|
4883
|
+
if (fname === "chart.point.now") {
|
|
4884
|
+
const priceExpr = callArg(node.args, 0, ["price"]);
|
|
4885
|
+
const price = priceExpr == null ? 0 : unwrap(evalExpr(priceExpr, env));
|
|
4886
|
+
if (price == null) return NA;
|
|
4887
|
+
const time = unwrap(env.ctx.time) ?? env.barTimes[env.barIndex] ?? null;
|
|
4888
|
+
return makeChartPoint(env.barIndex, time, price);
|
|
4889
|
+
}
|
|
4890
|
+
if (fname === "chart.point.copy") {
|
|
4891
|
+
const raw = evalExpr(callArg(node.args, 0, ["point"]), env);
|
|
4892
|
+
if (isChartPoint(raw)) return makeChartPoint(raw.index, raw.time, raw.price);
|
|
4893
|
+
return makeChartPoint(null, null, 0);
|
|
4894
|
+
}
|
|
4895
|
+
|
|
4896
|
+
if (fname === "line.copy" || fname === "label.copy" || fname === "box.copy" || fname === "polyline.copy") {
|
|
4897
|
+
const id = drawId(node, env);
|
|
4898
|
+
if (id == null) return NA;
|
|
4899
|
+
const copied = book.copy(id);
|
|
4900
|
+
return copied == null ? NA : copied;
|
|
4901
|
+
}
|
|
4902
|
+
|
|
4903
|
+
if (fname === "line.delete" || fname === "label.delete" || fname === "box.delete" || fname === "table.delete" || fname === "polyline.delete" || fname === "linefill.delete") {
|
|
4904
|
+
const id = drawId(node, env);
|
|
4905
|
+
if (id != null) {
|
|
4906
|
+
if (fname === "line.delete") book.lineDelete(id);
|
|
4907
|
+
else if (fname === "label.delete") book.labelDelete(id);
|
|
4908
|
+
else if (fname === "box.delete") book.boxDelete(id);
|
|
4909
|
+
else if (fname === "table.delete") book.tableDelete(id);
|
|
4910
|
+
else if (fname === "polyline.delete") book.polylineDelete(id);
|
|
4911
|
+
else book.linefillDelete(id);
|
|
4912
|
+
}
|
|
4913
|
+
return NA;
|
|
4914
|
+
}
|
|
4915
|
+
|
|
4916
|
+
if (fname === "line.get_x1") {
|
|
4917
|
+
const id = drawId(node, env);
|
|
4918
|
+
return id != null ? book.lineGetX1(id) : NA;
|
|
4919
|
+
}
|
|
4920
|
+
if (fname === "line.get_y1") {
|
|
4921
|
+
const id = drawId(node, env);
|
|
4922
|
+
return id != null ? book.lineGetY1(id) : NA;
|
|
4923
|
+
}
|
|
4924
|
+
if (fname === "line.get_x2") {
|
|
4925
|
+
const id = drawId(node, env);
|
|
4926
|
+
return id != null ? book.lineGetX2(id) : NA;
|
|
4927
|
+
}
|
|
4928
|
+
if (fname === "line.get_y2") {
|
|
4929
|
+
const id = drawId(node, env);
|
|
4930
|
+
return id != null ? book.lineGetY2(id) : NA;
|
|
4931
|
+
}
|
|
4932
|
+
if (fname === "line.get_price") {
|
|
4933
|
+
const id = drawId(node, env);
|
|
4934
|
+
const x = drawNum(node, env, 1, ["x"]);
|
|
4935
|
+
return id != null && x != null ? book.lineGetPrice(id, x) : NA;
|
|
4936
|
+
}
|
|
4937
|
+
if (fname === "label.get_x") {
|
|
4938
|
+
const id = drawId(node, env);
|
|
4939
|
+
return id == null ? 0 : (book.labelGetX(id) ?? 0);
|
|
3330
4940
|
}
|
|
4941
|
+
if (fname === "label.get_y") {
|
|
4942
|
+
const id = drawId(node, env);
|
|
4943
|
+
return id == null ? 0 : (book.labelGetY(id) ?? 0);
|
|
4944
|
+
}
|
|
4945
|
+
if (fname === "label.get_text") {
|
|
4946
|
+
const id = drawId(node, env);
|
|
4947
|
+
return id == null ? "" : (book.labelGetText(id) ?? "");
|
|
4948
|
+
}
|
|
4949
|
+
if (fname === "box.get_left") {
|
|
4950
|
+
const id = drawId(node, env);
|
|
4951
|
+
return id == null ? 0 : (book.boxGetLeft(id) ?? 0);
|
|
4952
|
+
}
|
|
4953
|
+
if (fname === "box.get_right") {
|
|
4954
|
+
const id = drawId(node, env);
|
|
4955
|
+
return id == null ? 0 : (book.boxGetRight(id) ?? 0);
|
|
4956
|
+
}
|
|
4957
|
+
if (fname === "box.get_top") {
|
|
4958
|
+
const id = drawId(node, env);
|
|
4959
|
+
return id == null ? 0 : (book.boxGetTop(id) ?? 0);
|
|
4960
|
+
}
|
|
4961
|
+
if (fname === "box.get_bottom") {
|
|
4962
|
+
const id = drawId(node, env);
|
|
4963
|
+
return id == null ? 0 : (book.boxGetBottom(id) ?? 0);
|
|
4964
|
+
}
|
|
4965
|
+
if (fname === "table.cell_get_text") {
|
|
4966
|
+
const id = drawId(node, env);
|
|
4967
|
+
const row = drawNum(node, env, 1, ["row"]);
|
|
4968
|
+
const col = drawNum(node, env, 2, ["column"]);
|
|
4969
|
+
if (id == null) return "";
|
|
4970
|
+
return book.tableCellGetText(id, col, row) ?? "";
|
|
4971
|
+
}
|
|
4972
|
+
if (fname === "linefill.get_line1") {
|
|
4973
|
+
const id = drawId(node, env);
|
|
4974
|
+
return id != null ? book.linefillGetLine1(id) : NA;
|
|
4975
|
+
}
|
|
4976
|
+
if (fname === "linefill.get_line2") {
|
|
4977
|
+
const id = drawId(node, env);
|
|
4978
|
+
return id != null ? book.linefillGetLine2(id) : NA;
|
|
4979
|
+
}
|
|
4980
|
+
if (fname === "polyline.get_points") {
|
|
4981
|
+
const id = drawId(node, env);
|
|
4982
|
+
const arr = new PineArray();
|
|
4983
|
+
if (id != null) for (const p of book.polylineGetPoints(id)) arr.push(p);
|
|
4984
|
+
return arr;
|
|
4985
|
+
}
|
|
4986
|
+
|
|
3331
4987
|
if (fname === "line.set_xy") {
|
|
3332
|
-
const id =
|
|
4988
|
+
const id = drawId(node, env);
|
|
3333
4989
|
if (id != null) {
|
|
3334
|
-
|
|
4990
|
+
book.lineSetXy(
|
|
3335
4991
|
id,
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
4992
|
+
drawNum(node, env, 1, ["x1"]) ?? 0,
|
|
4993
|
+
drawNum(node, env, 2, ["y1"]) ?? 0,
|
|
4994
|
+
drawNum(node, env, 3, ["x2"]) ?? 0,
|
|
4995
|
+
drawNum(node, env, 4, ["y2"]) ?? 0,
|
|
3340
4996
|
);
|
|
3341
4997
|
}
|
|
3342
4998
|
return NA;
|
|
3343
4999
|
}
|
|
3344
|
-
if (fname === "
|
|
3345
|
-
const id =
|
|
3346
|
-
if (id != null)
|
|
5000
|
+
if (fname === "line.set_xy1") {
|
|
5001
|
+
const id = drawId(node, env);
|
|
5002
|
+
if (id != null) book.lineSetXy1(id, drawNum(node, env, 1, ["x1", "x"]) ?? 0, drawNum(node, env, 2, ["y1", "y"]) ?? 0);
|
|
3347
5003
|
return NA;
|
|
3348
5004
|
}
|
|
3349
|
-
if (fname === "line.
|
|
3350
|
-
const id =
|
|
3351
|
-
if (id != null)
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
5005
|
+
if (fname === "line.set_xy2") {
|
|
5006
|
+
const id = drawId(node, env);
|
|
5007
|
+
if (id != null) book.lineSetXy2(id, drawNum(node, env, 1, ["x2", "x"]) ?? 0, drawNum(node, env, 2, ["y2", "y"]) ?? 0);
|
|
5008
|
+
return NA;
|
|
5009
|
+
}
|
|
5010
|
+
if (fname === "line.set_x1") {
|
|
5011
|
+
const id = drawId(node, env);
|
|
5012
|
+
if (id != null) book.lineSetX1(id, drawNum(node, env, 1, ["x1", "x"]));
|
|
5013
|
+
return NA;
|
|
5014
|
+
}
|
|
5015
|
+
if (fname === "line.set_y1") {
|
|
5016
|
+
const id = drawId(node, env);
|
|
5017
|
+
if (id != null) book.lineSetY1(id, drawNum(node, env, 1, ["y1", "y"]));
|
|
5018
|
+
return NA;
|
|
5019
|
+
}
|
|
5020
|
+
if (fname === "line.set_x2") {
|
|
5021
|
+
const id = drawId(node, env);
|
|
5022
|
+
if (id != null) book.lineSetX2(id, drawNum(node, env, 1, ["x2", "x"]));
|
|
5023
|
+
return NA;
|
|
5024
|
+
}
|
|
5025
|
+
if (fname === "line.set_y2") {
|
|
5026
|
+
const id = drawId(node, env);
|
|
5027
|
+
if (id != null) book.lineSetY2(id, drawNum(node, env, 1, ["y2", "y"]));
|
|
3356
5028
|
return NA;
|
|
3357
5029
|
}
|
|
3358
5030
|
if (fname === "line.set_color") {
|
|
3359
|
-
const id =
|
|
3360
|
-
if (id != null)
|
|
5031
|
+
const id = drawId(node, env);
|
|
5032
|
+
if (id != null) book.lineSetColor(id, drawRaw(node, env, 1, ["color"]));
|
|
3361
5033
|
return NA;
|
|
3362
5034
|
}
|
|
3363
|
-
if (fname === "line.
|
|
3364
|
-
const id =
|
|
3365
|
-
|
|
3366
|
-
return
|
|
5035
|
+
if (fname === "line.set_width") {
|
|
5036
|
+
const id = drawId(node, env);
|
|
5037
|
+
if (id != null) book.lineSetWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5038
|
+
return NA;
|
|
3367
5039
|
}
|
|
3368
|
-
if (fname === "line.
|
|
3369
|
-
const id =
|
|
3370
|
-
|
|
5040
|
+
if (fname === "line.set_style") {
|
|
5041
|
+
const id = drawId(node, env);
|
|
5042
|
+
if (id != null) book.lineSetStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5043
|
+
return NA;
|
|
3371
5044
|
}
|
|
3372
|
-
if (fname === "line.
|
|
3373
|
-
const id =
|
|
3374
|
-
|
|
5045
|
+
if (fname === "line.set_extend") {
|
|
5046
|
+
const id = drawId(node, env);
|
|
5047
|
+
if (id != null) book.lineSetExtend(id, drawRaw(node, env, 1, ["extend"]));
|
|
5048
|
+
return NA;
|
|
3375
5049
|
}
|
|
3376
|
-
if (fname === "line.
|
|
3377
|
-
const id =
|
|
3378
|
-
|
|
5050
|
+
if (fname === "line.set_xloc") {
|
|
5051
|
+
const id = drawId(node, env);
|
|
5052
|
+
if (id != null) book.lineSetXloc(id, drawRaw(node, env, 1, ["xloc"]));
|
|
5053
|
+
return NA;
|
|
3379
5054
|
}
|
|
3380
|
-
if (fname === "line.
|
|
3381
|
-
const id =
|
|
3382
|
-
|
|
5055
|
+
if (fname === "line.set_first_point") {
|
|
5056
|
+
const id = drawId(node, env);
|
|
5057
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5058
|
+
if (id != null && isChartPoint(p)) {
|
|
5059
|
+
const xy = pointXY(p);
|
|
5060
|
+
book.lineSetXy1(id, xy.x, xy.y);
|
|
5061
|
+
}
|
|
5062
|
+
return NA;
|
|
3383
5063
|
}
|
|
3384
|
-
if (fname === "
|
|
3385
|
-
const id =
|
|
3386
|
-
|
|
5064
|
+
if (fname === "line.set_second_point") {
|
|
5065
|
+
const id = drawId(node, env);
|
|
5066
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5067
|
+
if (id != null && isChartPoint(p)) {
|
|
5068
|
+
const xy = pointXY(p);
|
|
5069
|
+
book.lineSetXy2(id, xy.x, xy.y);
|
|
5070
|
+
}
|
|
5071
|
+
return NA;
|
|
5072
|
+
}
|
|
5073
|
+
|
|
5074
|
+
if (fname === "label.set_text") {
|
|
5075
|
+
const id = drawId(node, env);
|
|
5076
|
+
if (id != null) book.labelSetText(id, drawText(node, env, 1, ["text"]));
|
|
5077
|
+
return NA;
|
|
3387
5078
|
}
|
|
3388
5079
|
if (fname === "label.set_xy") {
|
|
3389
|
-
const id =
|
|
5080
|
+
const id = drawId(node, env);
|
|
5081
|
+
if (id != null) book.labelSetXy(id, drawNum(node, env, 1, ["x"]) ?? 0, drawNum(node, env, 2, ["y"]) ?? 0);
|
|
5082
|
+
return NA;
|
|
5083
|
+
}
|
|
5084
|
+
if (fname === "label.set_x") {
|
|
5085
|
+
const id = drawId(node, env);
|
|
5086
|
+
if (id != null) book.labelSetX(id, drawNum(node, env, 1, ["x"]));
|
|
5087
|
+
return NA;
|
|
5088
|
+
}
|
|
5089
|
+
if (fname === "label.set_y") {
|
|
5090
|
+
const id = drawId(node, env);
|
|
5091
|
+
if (id != null) book.labelSetY(id, drawNum(node, env, 1, ["y"]));
|
|
5092
|
+
return NA;
|
|
5093
|
+
}
|
|
5094
|
+
if (fname === "label.set_color") {
|
|
5095
|
+
const id = drawId(node, env);
|
|
5096
|
+
if (id != null) book.labelSetColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5097
|
+
return NA;
|
|
5098
|
+
}
|
|
5099
|
+
if (fname === "label.set_style") {
|
|
5100
|
+
const id = drawId(node, env);
|
|
5101
|
+
if (id != null) book.labelSetStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5102
|
+
return NA;
|
|
5103
|
+
}
|
|
5104
|
+
if (fname === "label.set_size") {
|
|
5105
|
+
const id = drawId(node, env);
|
|
5106
|
+
if (id != null) book.labelSetSize(id, drawRaw(node, env, 1, ["size"]));
|
|
5107
|
+
return NA;
|
|
5108
|
+
}
|
|
5109
|
+
if (fname === "label.set_tooltip") {
|
|
5110
|
+
const id = drawId(node, env);
|
|
5111
|
+
if (id != null) book.labelSetTooltip(id, drawRaw(node, env, 1, ["tooltip"]));
|
|
5112
|
+
return NA;
|
|
5113
|
+
}
|
|
5114
|
+
if (fname === "label.set_textalign") {
|
|
5115
|
+
const id = drawId(node, env);
|
|
5116
|
+
if (id != null) book.labelSetTextalign(id, drawRaw(node, env, 1, ["textalign", "halign"]));
|
|
5117
|
+
return NA;
|
|
5118
|
+
}
|
|
5119
|
+
if (fname === "label.set_textcolor") {
|
|
5120
|
+
const id = drawId(node, env);
|
|
5121
|
+
if (id != null) book.labelSetTextcolor(id, drawRaw(node, env, 1, ["color", "textcolor"]));
|
|
5122
|
+
return NA;
|
|
5123
|
+
}
|
|
5124
|
+
if (fname === "label.set_text_font_family") {
|
|
5125
|
+
const id = drawId(node, env);
|
|
5126
|
+
if (id != null) book.labelSetTextFontFamily(id, drawRaw(node, env, 1, ["font_family", "text_font_family"]));
|
|
5127
|
+
return NA;
|
|
5128
|
+
}
|
|
5129
|
+
if (fname === "label.set_text_halign") {
|
|
5130
|
+
const id = drawId(node, env);
|
|
5131
|
+
if (id != null) book.labelSetTextHalign(id, drawRaw(node, env, 1, ["halign", "text_halign"]));
|
|
5132
|
+
return NA;
|
|
5133
|
+
}
|
|
5134
|
+
if (fname === "label.set_text_valign") {
|
|
5135
|
+
const id = drawId(node, env);
|
|
5136
|
+
if (id != null) book.labelSetTextValign(id, drawRaw(node, env, 1, ["valign", "text_valign"]));
|
|
5137
|
+
return NA;
|
|
5138
|
+
}
|
|
5139
|
+
if (fname === "label.set_text_size") {
|
|
5140
|
+
const id = drawId(node, env);
|
|
5141
|
+
if (id != null) book.labelSetTextSize(id, drawRaw(node, env, 1, ["size", "text_size"]));
|
|
5142
|
+
return NA;
|
|
5143
|
+
}
|
|
5144
|
+
if (fname === "label.set_text_formatting") {
|
|
5145
|
+
const id = drawId(node, env);
|
|
5146
|
+
if (id != null) book.labelSetTextFormatting(id, drawRaw(node, env, 1, ["formatting", "text_formatting"]));
|
|
5147
|
+
return NA;
|
|
5148
|
+
}
|
|
5149
|
+
if (fname === "label.set_border_color") {
|
|
5150
|
+
const id = drawId(node, env);
|
|
5151
|
+
if (id != null) book.labelSetBorderColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5152
|
+
return NA;
|
|
5153
|
+
}
|
|
5154
|
+
if (fname === "label.set_border_width") {
|
|
5155
|
+
const id = drawId(node, env);
|
|
5156
|
+
if (id != null) book.labelSetBorderWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5157
|
+
return NA;
|
|
5158
|
+
}
|
|
5159
|
+
if (fname === "label.set_border_style") {
|
|
5160
|
+
const id = drawId(node, env);
|
|
5161
|
+
if (id != null) book.labelSetBorderStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5162
|
+
return NA;
|
|
5163
|
+
}
|
|
5164
|
+
if (fname === "label.set_xloc") {
|
|
5165
|
+
const id = drawId(node, env);
|
|
5166
|
+
if (id != null) book.labelSetXloc(id, drawRaw(node, env, 1, ["xloc"]));
|
|
5167
|
+
return NA;
|
|
5168
|
+
}
|
|
5169
|
+
if (fname === "label.set_yloc") {
|
|
5170
|
+
const id = drawId(node, env);
|
|
5171
|
+
if (id != null) book.labelSetYloc(id, drawRaw(node, env, 1, ["yloc"]));
|
|
5172
|
+
return NA;
|
|
5173
|
+
}
|
|
5174
|
+
if (fname === "label.set_point") {
|
|
5175
|
+
const id = drawId(node, env);
|
|
5176
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5177
|
+
if (id != null && isChartPoint(p)) {
|
|
5178
|
+
const xy = pointXY(p);
|
|
5179
|
+
book.labelSetXy(id, xy.x, xy.y);
|
|
5180
|
+
}
|
|
5181
|
+
return NA;
|
|
5182
|
+
}
|
|
5183
|
+
|
|
5184
|
+
if (fname === "box.set_lefttop" || fname === "box.set_corners") {
|
|
5185
|
+
const id = drawId(node, env);
|
|
3390
5186
|
if (id != null) {
|
|
3391
|
-
|
|
5187
|
+
book.boxSetCorners(
|
|
3392
5188
|
id,
|
|
3393
|
-
|
|
3394
|
-
|
|
5189
|
+
drawNum(node, env, 1, ["left", "x1"]) ?? 0,
|
|
5190
|
+
drawNum(node, env, 2, ["top", "y1"]) ?? 0,
|
|
5191
|
+
drawNum(node, env, 3, ["right", "x2"]) ?? 0,
|
|
5192
|
+
drawNum(node, env, 4, ["bottom", "y2"]) ?? 0,
|
|
3395
5193
|
);
|
|
3396
5194
|
}
|
|
3397
5195
|
return NA;
|
|
3398
5196
|
}
|
|
3399
|
-
if (fname === "box.
|
|
3400
|
-
const id =
|
|
5197
|
+
if (fname === "box.set_left") {
|
|
5198
|
+
const id = drawId(node, env);
|
|
5199
|
+
if (id != null) book.boxSetLeft(id, drawNum(node, env, 1, ["left"]));
|
|
5200
|
+
return NA;
|
|
5201
|
+
}
|
|
5202
|
+
if (fname === "box.set_right") {
|
|
5203
|
+
const id = drawId(node, env);
|
|
5204
|
+
if (id != null) book.boxSetRight(id, drawNum(node, env, 1, ["right"]));
|
|
5205
|
+
return NA;
|
|
5206
|
+
}
|
|
5207
|
+
if (fname === "box.set_top") {
|
|
5208
|
+
const id = drawId(node, env);
|
|
5209
|
+
if (id != null) book.boxSetTop(id, drawNum(node, env, 1, ["top"]));
|
|
5210
|
+
return NA;
|
|
5211
|
+
}
|
|
5212
|
+
if (fname === "box.set_bottom") {
|
|
5213
|
+
const id = drawId(node, env);
|
|
5214
|
+
if (id != null) book.boxSetBottom(id, drawNum(node, env, 1, ["bottom"]));
|
|
5215
|
+
return NA;
|
|
5216
|
+
}
|
|
5217
|
+
if (fname === "box.set_bgcolor") {
|
|
5218
|
+
const id = drawId(node, env);
|
|
5219
|
+
if (id != null) book.boxSetBgcolor(id, drawRaw(node, env, 1, ["color", "bgcolor"]));
|
|
5220
|
+
return NA;
|
|
5221
|
+
}
|
|
5222
|
+
if (fname === "box.set_border_color") {
|
|
5223
|
+
const id = drawId(node, env);
|
|
5224
|
+
if (id != null) book.boxSetBorderColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5225
|
+
return NA;
|
|
5226
|
+
}
|
|
5227
|
+
if (fname === "box.set_border_width") {
|
|
5228
|
+
const id = drawId(node, env);
|
|
5229
|
+
if (id != null) book.boxSetBorderWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5230
|
+
return NA;
|
|
5231
|
+
}
|
|
5232
|
+
if (fname === "box.set_border_style") {
|
|
5233
|
+
const id = drawId(node, env);
|
|
5234
|
+
if (id != null) book.boxSetBorderStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5235
|
+
return NA;
|
|
5236
|
+
}
|
|
5237
|
+
if (fname === "box.set_extend") {
|
|
5238
|
+
const id = drawId(node, env);
|
|
5239
|
+
if (id != null) book.boxSetExtend(id, drawRaw(node, env, 1, ["extend"]));
|
|
5240
|
+
return NA;
|
|
5241
|
+
}
|
|
5242
|
+
if (fname === "box.set_xloc") {
|
|
5243
|
+
const id = drawId(node, env);
|
|
5244
|
+
if (id != null) {
|
|
5245
|
+
book.boxSetLeft(id, drawNum(node, env, 1, ["left"]));
|
|
5246
|
+
book.boxSetRight(id, drawNum(node, env, 2, ["right"]));
|
|
5247
|
+
book.boxSetXloc(id, drawRaw(node, env, 3, ["xloc"]));
|
|
5248
|
+
}
|
|
5249
|
+
return NA;
|
|
5250
|
+
}
|
|
5251
|
+
if (fname === "box.set_closed") {
|
|
5252
|
+
const id = drawId(node, env);
|
|
5253
|
+
if (id != null) book.boxSetClosed(id, drawRaw(node, env, 1, ["closed"]));
|
|
5254
|
+
return NA;
|
|
5255
|
+
}
|
|
5256
|
+
if (fname === "box.set_rightbottom") {
|
|
5257
|
+
const id = drawId(node, env);
|
|
5258
|
+
if (id != null) book.boxSetRightBottom(id, drawNum(node, env, 1, ["right"]), drawNum(node, env, 2, ["bottom"]));
|
|
5259
|
+
return NA;
|
|
5260
|
+
}
|
|
5261
|
+
if (fname === "box.set_text") {
|
|
5262
|
+
const id = drawId(node, env);
|
|
5263
|
+
if (id != null) book.boxSetText(id, drawText(node, env, 1, ["text"]));
|
|
5264
|
+
return NA;
|
|
5265
|
+
}
|
|
5266
|
+
if (fname === "box.set_text_color") {
|
|
5267
|
+
const id = drawId(node, env);
|
|
5268
|
+
if (id != null) book.boxSetTextColor(id, drawRaw(node, env, 1, ["color", "text_color"]));
|
|
5269
|
+
return NA;
|
|
5270
|
+
}
|
|
5271
|
+
if (fname === "box.set_text_font_family") {
|
|
5272
|
+
const id = drawId(node, env);
|
|
5273
|
+
if (id != null) book.boxSetTextFontFamily(id, drawRaw(node, env, 1, ["font_family"]));
|
|
5274
|
+
return NA;
|
|
5275
|
+
}
|
|
5276
|
+
if (fname === "box.set_text_halign") {
|
|
5277
|
+
const id = drawId(node, env);
|
|
5278
|
+
if (id != null) book.boxSetTextHalign(id, drawRaw(node, env, 1, ["halign"]));
|
|
5279
|
+
return NA;
|
|
5280
|
+
}
|
|
5281
|
+
if (fname === "box.set_text_valign") {
|
|
5282
|
+
const id = drawId(node, env);
|
|
5283
|
+
if (id != null) book.boxSetTextValign(id, drawRaw(node, env, 1, ["valign"]));
|
|
5284
|
+
return NA;
|
|
5285
|
+
}
|
|
5286
|
+
if (fname === "box.set_text_size") {
|
|
5287
|
+
const id = drawId(node, env);
|
|
5288
|
+
if (id != null) book.boxSetTextSize(id, drawRaw(node, env, 1, ["size"]));
|
|
5289
|
+
return NA;
|
|
5290
|
+
}
|
|
5291
|
+
if (fname === "box.set_text_formatting") {
|
|
5292
|
+
const id = drawId(node, env);
|
|
5293
|
+
if (id != null) book.boxSetTextFormatting(id, drawRaw(node, env, 1, ["formatting"]));
|
|
5294
|
+
return NA;
|
|
5295
|
+
}
|
|
5296
|
+
if (fname === "box.set_text_wrap") {
|
|
5297
|
+
const id = drawId(node, env);
|
|
5298
|
+
if (id != null) book.boxSetTextWrap(id, drawRaw(node, env, 1, ["wrap"]));
|
|
5299
|
+
return NA;
|
|
5300
|
+
}
|
|
5301
|
+
if (fname === "box.set_top_left_point") {
|
|
5302
|
+
const id = drawId(node, env);
|
|
5303
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5304
|
+
if (id != null && isChartPoint(p)) {
|
|
5305
|
+
const xy = pointXY(p);
|
|
5306
|
+
book.boxSetLeftTop(id, xy.x, xy.y);
|
|
5307
|
+
}
|
|
5308
|
+
return NA;
|
|
5309
|
+
}
|
|
5310
|
+
if (fname === "box.set_bottom_right_point") {
|
|
5311
|
+
const id = drawId(node, env);
|
|
5312
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5313
|
+
if (id != null && isChartPoint(p)) {
|
|
5314
|
+
const xy = pointXY(p);
|
|
5315
|
+
book.boxSetRightBottom(id, xy.x, xy.y);
|
|
5316
|
+
}
|
|
5317
|
+
return NA;
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5320
|
+
if (fname === "table.cell") {
|
|
5321
|
+
const id = drawId(node, env);
|
|
5322
|
+
const col = drawNum(node, env, 1, ["column"]);
|
|
5323
|
+
const row = drawNum(node, env, 2, ["row"]);
|
|
5324
|
+
const textArg = callArg(node.args, 3, ["text"]);
|
|
5325
|
+
const text = textArg == null ? undefined : drawText(node, env, 3, ["text"]);
|
|
5326
|
+
if (id != null && col != null && row != null) book.tableCell(id, col, row, text);
|
|
5327
|
+
return NA;
|
|
5328
|
+
}
|
|
5329
|
+
if (fname === "table.cell_set_text") {
|
|
5330
|
+
const id = drawId(node, env);
|
|
5331
|
+
const row = drawNum(node, env, 1, ["row"]);
|
|
5332
|
+
const col = drawNum(node, env, 2, ["column"]);
|
|
5333
|
+
if (id != null) book.tableCellSetText(id, col, row, drawText(node, env, 3, ["text"]));
|
|
5334
|
+
return NA;
|
|
5335
|
+
}
|
|
5336
|
+
if (
|
|
5337
|
+
fname === "table.cell_set_text_color" ||
|
|
5338
|
+
fname === "table.cell_set_bgcolor" ||
|
|
5339
|
+
fname === "table.cell_set_border_color" ||
|
|
5340
|
+
fname === "table.cell_set_border_width" ||
|
|
5341
|
+
fname === "table.cell_set_tooltip"
|
|
5342
|
+
) {
|
|
5343
|
+
const id = drawId(node, env);
|
|
5344
|
+
const row = drawNum(node, env, 1, ["row"]);
|
|
5345
|
+
const col = drawNum(node, env, 2, ["column"]);
|
|
5346
|
+
const field =
|
|
5347
|
+
fname === "table.cell_set_text_color"
|
|
5348
|
+
? "text_color"
|
|
5349
|
+
: fname === "table.cell_set_bgcolor"
|
|
5350
|
+
? "bgcolor"
|
|
5351
|
+
: fname === "table.cell_set_border_color"
|
|
5352
|
+
? "border_color"
|
|
5353
|
+
: fname === "table.cell_set_border_width"
|
|
5354
|
+
? "border_width"
|
|
5355
|
+
: "tooltip";
|
|
5356
|
+
if (id != null) book.tableCellSetField(id, col, row, field, drawRaw(node, env, 3, ["color", "width", "tooltip", "text"]));
|
|
5357
|
+
return NA;
|
|
5358
|
+
}
|
|
5359
|
+
if (
|
|
5360
|
+
fname === "table.cell_set_width" ||
|
|
5361
|
+
fname === "table.cell_set_height" ||
|
|
5362
|
+
fname === "table.cell_set_text_halign" ||
|
|
5363
|
+
fname === "table.cell_set_text_valign" ||
|
|
5364
|
+
fname === "table.cell_set_text_size" ||
|
|
5365
|
+
fname === "table.cell_set_text_font_family" ||
|
|
5366
|
+
fname === "table.cell_set_text_formatting"
|
|
5367
|
+
) {
|
|
5368
|
+
const id = drawId(node, env);
|
|
5369
|
+
const col = drawNum(node, env, 1, ["column", "col"]);
|
|
5370
|
+
const row = drawNum(node, env, 2, ["row"]);
|
|
5371
|
+
const field = fname.slice("table.cell_set_".length);
|
|
5372
|
+
if (id != null) book.tableCellSetField(id, col, row, field, drawRaw(node, env, 3, ["width", "height", "halign", "valign", "size", "font_family", "formatting"]));
|
|
5373
|
+
return NA;
|
|
5374
|
+
}
|
|
5375
|
+
if (fname === "table.set_position") {
|
|
5376
|
+
const id = drawId(node, env);
|
|
5377
|
+
if (id != null) book.tableSetPosition(id, drawRaw(node, env, 1, ["position"]));
|
|
5378
|
+
return NA;
|
|
5379
|
+
}
|
|
5380
|
+
if (fname === "table.set_bgcolor") {
|
|
5381
|
+
const id = drawId(node, env);
|
|
5382
|
+
if (id != null) book.tableSetBgcolor(id, drawRaw(node, env, 1, ["color", "bgcolor"]));
|
|
5383
|
+
return NA;
|
|
5384
|
+
}
|
|
5385
|
+
if (fname === "table.set_border_color") {
|
|
5386
|
+
const id = drawId(node, env);
|
|
5387
|
+
if (id != null) book.tableSetBorderColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5388
|
+
return NA;
|
|
5389
|
+
}
|
|
5390
|
+
if (fname === "table.set_border_width") {
|
|
5391
|
+
const id = drawId(node, env);
|
|
5392
|
+
if (id != null) book.tableSetBorderWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5393
|
+
return NA;
|
|
5394
|
+
}
|
|
5395
|
+
if (fname === "table.set_frame_color") {
|
|
5396
|
+
const id = drawId(node, env);
|
|
5397
|
+
if (id != null) book.tableSetFrameColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5398
|
+
return NA;
|
|
5399
|
+
}
|
|
5400
|
+
if (fname === "table.set_frame_width") {
|
|
5401
|
+
const id = drawId(node, env);
|
|
5402
|
+
if (id != null) book.tableSetFrameWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5403
|
+
return NA;
|
|
5404
|
+
}
|
|
5405
|
+
if (fname === "table.clear") {
|
|
5406
|
+
const id = drawId(node, env);
|
|
5407
|
+
if (id != null) {
|
|
5408
|
+
const a1 = callArg(node.args, 1, ["start_row"]);
|
|
5409
|
+
if (a1 == null) book.tableClear(id);
|
|
5410
|
+
else {
|
|
5411
|
+
book.tableClear(
|
|
5412
|
+
id,
|
|
5413
|
+
drawNum(node, env, 1, ["start_row"]),
|
|
5414
|
+
drawNum(node, env, 2, ["start_column", "start_col"]),
|
|
5415
|
+
drawNum(node, env, 3, ["end_row"]),
|
|
5416
|
+
drawNum(node, env, 4, ["end_column", "end_col"]),
|
|
5417
|
+
);
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
return NA;
|
|
5421
|
+
}
|
|
5422
|
+
if (fname === "table.merge_cells") {
|
|
5423
|
+
const id = drawId(node, env);
|
|
3401
5424
|
if (id != null) {
|
|
3402
|
-
|
|
5425
|
+
book.tableMergeCells(
|
|
3403
5426
|
id,
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
5427
|
+
drawNum(node, env, 1, ["start_row"]),
|
|
5428
|
+
drawNum(node, env, 2, ["start_column", "start_col"]),
|
|
5429
|
+
drawNum(node, env, 3, ["end_row"]),
|
|
5430
|
+
drawNum(node, env, 4, ["end_column", "end_col"]),
|
|
3408
5431
|
);
|
|
3409
5432
|
}
|
|
3410
5433
|
return NA;
|
|
3411
5434
|
}
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
5435
|
+
|
|
5436
|
+
if (fname === "linefill.set_color") {
|
|
5437
|
+
const id = drawId(node, env);
|
|
5438
|
+
if (id != null) book.linefillSetColor(id, drawRaw(node, env, 1, ["color"]));
|
|
3415
5439
|
return NA;
|
|
3416
5440
|
}
|
|
3417
|
-
if (fname === "
|
|
3418
|
-
const id =
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
5441
|
+
if (fname === "polyline.set_points") {
|
|
5442
|
+
const id = drawId(node, env);
|
|
5443
|
+
if (id != null) book.polylineSetPoints(id, pointsFromValue(evalExpr(callArg(node.args, 1, ["points"]), env)));
|
|
5444
|
+
return NA;
|
|
5445
|
+
}
|
|
5446
|
+
if (fname === "polyline.set_line_color") {
|
|
5447
|
+
const id = drawId(node, env);
|
|
5448
|
+
if (id != null) book.polylineSetLineColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5449
|
+
return NA;
|
|
5450
|
+
}
|
|
5451
|
+
if (fname === "polyline.set_line_width") {
|
|
5452
|
+
const id = drawId(node, env);
|
|
5453
|
+
if (id != null) book.polylineSetLineWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5454
|
+
return NA;
|
|
5455
|
+
}
|
|
5456
|
+
if (fname === "polyline.set_line_style") {
|
|
5457
|
+
const id = drawId(node, env);
|
|
5458
|
+
if (id != null) book.polylineSetLineStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5459
|
+
return NA;
|
|
5460
|
+
}
|
|
5461
|
+
if (fname === "polyline.set_fill_color") {
|
|
5462
|
+
const id = drawId(node, env);
|
|
5463
|
+
if (id != null) book.polylineSetFillColor(id, drawRaw(node, env, 1, ["fill_color", "color"]));
|
|
5464
|
+
return NA;
|
|
5465
|
+
}
|
|
5466
|
+
if (fname === "polyline.set_curved") {
|
|
5467
|
+
const id = drawId(node, env);
|
|
5468
|
+
if (id != null) book.polylineSetCurved(id, drawRaw(node, env, 1, ["curved"]));
|
|
5469
|
+
return NA;
|
|
5470
|
+
}
|
|
5471
|
+
if (fname === "polyline.set_force_overlay") {
|
|
5472
|
+
const id = drawId(node, env);
|
|
5473
|
+
if (id != null) book.polylineSetForceOverlay(id, drawRaw(node, env, 1, ["force_overlay"]));
|
|
5474
|
+
return NA;
|
|
5475
|
+
}
|
|
5476
|
+
if (fname === "polyline.set_closed") {
|
|
5477
|
+
const id = drawId(node, env);
|
|
5478
|
+
if (id != null) book.polylineSetClosed(id, drawRaw(node, env, 1, ["closed"]));
|
|
5479
|
+
return NA;
|
|
5480
|
+
}
|
|
5481
|
+
if (fname === "polyline.set_xloc") {
|
|
5482
|
+
const id = drawId(node, env);
|
|
5483
|
+
if (id != null) book.polylineSetXloc(id, drawRaw(node, env, 1, ["xloc"]));
|
|
3423
5484
|
return NA;
|
|
3424
5485
|
}
|
|
3425
5486
|
return undefined;
|
|
@@ -3767,9 +5828,54 @@ function evalTimeCall(fname: string | null, node: Call, env: Env): Value | undef
|
|
|
3767
5828
|
const ms = calendarMs(node, env);
|
|
3768
5829
|
return ms == null ? NA : timeTradingDay(ms);
|
|
3769
5830
|
}
|
|
5831
|
+
if (fname === "time_close") return barTimeClose(env);
|
|
5832
|
+
if (fname === "timenow") {
|
|
5833
|
+
const seeded = env.ctx.timenow;
|
|
5834
|
+
if (typeof seeded === "number" && Number.isFinite(seeded)) return seeded;
|
|
5835
|
+
if (typeof env.lastBarTime === "number" && Number.isFinite(env.lastBarTime)) return env.lastBarTime;
|
|
5836
|
+
const t = env.ctx.time;
|
|
5837
|
+
return typeof t === "number" && Number.isFinite(t) ? t : NA;
|
|
5838
|
+
}
|
|
3770
5839
|
return undefined;
|
|
3771
5840
|
}
|
|
3772
5841
|
|
|
5842
|
+
function barTimeClose(env: Env): number {
|
|
5843
|
+
const close = env.ctx.time_close;
|
|
5844
|
+
if (typeof close === "number" && Number.isFinite(close)) return close;
|
|
5845
|
+
const open = env.ctx.time;
|
|
5846
|
+
return typeof open === "number" && Number.isFinite(open) ? open : 0;
|
|
5847
|
+
}
|
|
5848
|
+
|
|
5849
|
+
function timeframeChange(node: Call, env: Env): Cell {
|
|
5850
|
+
const arg = callArg(node.args, 0, ["timeframe", "tf"]);
|
|
5851
|
+
if (arg == null) return 0;
|
|
5852
|
+
const raw = evalExpr(arg, env);
|
|
5853
|
+
const tf = typeof raw === "string" ? raw : typeof raw === "number" && Number.isFinite(raw) ? String(raw) : null;
|
|
5854
|
+
if (tf == null || tf.trim() === "") return 0;
|
|
5855
|
+
const curr = env.ctx.time;
|
|
5856
|
+
if (typeof curr !== "number" || !Number.isFinite(curr)) return 0;
|
|
5857
|
+
const hist = env.series.get("time");
|
|
5858
|
+
const prev = hist != null && hist.length >= 2 ? hist.get(1) : null;
|
|
5859
|
+
return timeframePeriodChanged(curr, prev, tf, env.barIndex) ? 1 : 0;
|
|
5860
|
+
}
|
|
5861
|
+
|
|
5862
|
+
/** Python `split_symbol` / `extract_prefix`: text before the first `:`, else `""`. */
|
|
5863
|
+
function extractPrefix(symbol: string): string {
|
|
5864
|
+
const s = symbol.trim();
|
|
5865
|
+
const i = s.indexOf(":");
|
|
5866
|
+
return i < 0 ? "" : s.slice(0, i);
|
|
5867
|
+
}
|
|
5868
|
+
|
|
5869
|
+
function syminfoPrefix(node: Call, env: Env): string {
|
|
5870
|
+
const arg = callArg(node.args, 0, ["tickerid", "symbol"]);
|
|
5871
|
+
if (arg == null) return extractPrefix(env.symbol);
|
|
5872
|
+
const raw = evalExpr(arg, env);
|
|
5873
|
+
if (raw instanceof TickerId) return extractPrefix(raw.symbol);
|
|
5874
|
+
if (typeof raw === "string") return extractPrefix(raw);
|
|
5875
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return extractPrefix(String(raw));
|
|
5876
|
+
return "";
|
|
5877
|
+
}
|
|
5878
|
+
|
|
3773
5879
|
function calendarMs(node: Call, env: Env): number | null {
|
|
3774
5880
|
const arg = callArg(node.args, 0, ["time", "timestamp"]);
|
|
3775
5881
|
if (arg == null) return num(env.ctx.time);
|