@hoox-sh/pynets 0.2.0 → 0.4.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 +56 -0
- package/README.md +21 -12
- package/dist/browser.js +89 -84
- package/dist/index.js +21011 -12972
- 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 +1 -1
- 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 +1 -0
- package/src/runtime/array.ts +238 -68
- package/src/runtime/compile/collections.ts +191 -34
- package/src/runtime/compile/draw.ts +609 -19
- package/src/runtime/compile/emit.ts +346 -9
- package/src/runtime/compile/emit_call.ts +737 -44
- package/src/runtime/compile/runtime.ts +82 -2
- package/src/runtime/compile/strategy.ts +339 -6
- package/src/runtime/compile/types.ts +2 -0
- package/src/runtime/drawings.ts +405 -8
- package/src/runtime/interpret.ts +2352 -277
- package/src/runtime/matrix.ts +371 -42
- package/src/runtime/strategy.ts +695 -33
- package/src/runtime/ta.ts +1046 -20
- package/src/runtime/timeframe.ts +199 -0
package/src/runtime/interpret.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
Arg,
|
|
17
17
|
Assign,
|
|
18
18
|
Attribute,
|
|
19
|
+
AugAssign,
|
|
19
20
|
BoolOp,
|
|
20
21
|
Call,
|
|
21
22
|
Conditional,
|
|
@@ -25,6 +26,7 @@ import type {
|
|
|
25
26
|
TypeDef,
|
|
26
27
|
EnumDef,
|
|
27
28
|
Import,
|
|
29
|
+
Once,
|
|
28
30
|
Param,
|
|
29
31
|
ReAssign,
|
|
30
32
|
Script,
|
|
@@ -59,13 +61,18 @@ import {
|
|
|
59
61
|
import {
|
|
60
62
|
timeframeFromSeconds,
|
|
61
63
|
timeframeInSeconds,
|
|
64
|
+
timeframePeriodChanged,
|
|
62
65
|
timeframeIsDaily,
|
|
66
|
+
timeframeIsDwm,
|
|
67
|
+
timeframeIsHours,
|
|
63
68
|
timeframeIsIntraday,
|
|
69
|
+
timeframeIsMinutes,
|
|
64
70
|
timeframeIsMonthly,
|
|
71
|
+
timeframeIsSeconds,
|
|
65
72
|
timeframeIsWeekly,
|
|
66
73
|
timeframeMultiplier as tfMultiplier,
|
|
67
74
|
} from "./timeframe.ts";
|
|
68
|
-
import { DrawingBook, type DrawingEvent } from "./drawings.ts";
|
|
75
|
+
import { DrawingBook, type DrawingEvent, type DrawingKind } from "./drawings.ts";
|
|
69
76
|
import { LogBook, formatLogParts, runtimeError, type LogRecord } from "./log.ts";
|
|
70
77
|
import { MemoryProvider, type BarProvider } from "./provider.ts";
|
|
71
78
|
import {
|
|
@@ -138,10 +145,18 @@ import {
|
|
|
138
145
|
strTrim,
|
|
139
146
|
strUpper,
|
|
140
147
|
} from "./str.ts";
|
|
141
|
-
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";
|
|
142
157
|
import { PineMap } from "./map.ts";
|
|
143
158
|
import { PineMatrix } from "./matrix.ts";
|
|
144
|
-
import { TaEngine } from "./ta.ts";
|
|
159
|
+
import { TaEngine, type TaRecord } from "./ta.ts";
|
|
145
160
|
import {
|
|
146
161
|
CompileError,
|
|
147
162
|
compileEligible,
|
|
@@ -160,17 +175,28 @@ export interface OHLCVBar {
|
|
|
160
175
|
close?: number;
|
|
161
176
|
volume?: number;
|
|
162
177
|
time?: number;
|
|
178
|
+
time_close?: number;
|
|
163
179
|
}
|
|
164
180
|
|
|
165
181
|
export type InputOverrides = Record<string, number | string | boolean>;
|
|
166
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
|
+
|
|
167
191
|
export interface RuntimeOptions {
|
|
168
192
|
inputs?: InputOverrides;
|
|
169
193
|
broker?: BrokerSettings;
|
|
170
194
|
timeframe?: string | null;
|
|
171
|
-
libraries?: LibraryRegistry;
|
|
195
|
+
libraries?: LibraryRegistry | LibrarySourceSpec[];
|
|
172
196
|
/** interpret (default) | compile (JS emit) | auto (compile, fallback interpret). */
|
|
173
197
|
mode?: RuntimeMode;
|
|
198
|
+
/** Wall-clock budget in seconds. Interpret only; checked every 32 bars. */
|
|
199
|
+
timeout_seconds?: number | null;
|
|
174
200
|
}
|
|
175
201
|
|
|
176
202
|
export interface RuntimeFill {
|
|
@@ -200,6 +226,8 @@ export interface RuntimeResult {
|
|
|
200
226
|
plot_data?: Record<string, { data: Array<{ value: number | null; time?: number }> }>;
|
|
201
227
|
/** Book-level strategy scalars when the script is a strategy (or any fills ran). */
|
|
202
228
|
strategy?: StrategySummary;
|
|
229
|
+
/** True when interpret stopped after `timeout_seconds`. */
|
|
230
|
+
timed_out?: boolean;
|
|
203
231
|
error?: string;
|
|
204
232
|
error_kind?: string;
|
|
205
233
|
}
|
|
@@ -207,8 +235,17 @@ export interface RuntimeResult {
|
|
|
207
235
|
/** Eval-only: Cell, tagged tuple/array, color, or strategy.long/short strings. */
|
|
208
236
|
type TupleVal = { __tuple: true; elts: Value[] };
|
|
209
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
|
+
};
|
|
210
246
|
type Value =
|
|
211
247
|
| Cell
|
|
248
|
+
| boolean // Python True/False (e.g. `barmerge.*`); coerces to 1/0 via unwrap/valuesEq
|
|
212
249
|
| TupleVal
|
|
213
250
|
| ArrayVal
|
|
214
251
|
| Color
|
|
@@ -222,7 +259,10 @@ type Value =
|
|
|
222
259
|
| LibraryModule
|
|
223
260
|
| UdfDef
|
|
224
261
|
| EnumType
|
|
225
|
-
| EnumMember
|
|
262
|
+
| EnumMember
|
|
263
|
+
| TaRecord
|
|
264
|
+
| ChartPointVal
|
|
265
|
+
| StrategyCashAmount;
|
|
226
266
|
|
|
227
267
|
const LOOP_BREAK = Object.freeze({ __loop: "break" as const });
|
|
228
268
|
const LOOP_CONTINUE = Object.freeze({ __loop: "continue" as const });
|
|
@@ -294,6 +334,11 @@ interface Env {
|
|
|
294
334
|
barTimes: number[];
|
|
295
335
|
libraries: LibraryRegistry;
|
|
296
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>;
|
|
297
342
|
}
|
|
298
343
|
|
|
299
344
|
export class Runtime {
|
|
@@ -302,6 +347,7 @@ export class Runtime {
|
|
|
302
347
|
readonly timeframe: string | null;
|
|
303
348
|
readonly libraries: LibraryRegistry;
|
|
304
349
|
readonly mode: RuntimeMode;
|
|
350
|
+
readonly timeout_seconds: number | null;
|
|
305
351
|
|
|
306
352
|
constructor(
|
|
307
353
|
public readonly symbol = "AAPL",
|
|
@@ -310,8 +356,9 @@ export class Runtime {
|
|
|
310
356
|
this.inputs = options?.inputs ?? {};
|
|
311
357
|
this.broker = options?.broker ?? {};
|
|
312
358
|
this.timeframe = options?.timeframe ?? null;
|
|
313
|
-
this.libraries = options?.libraries
|
|
359
|
+
this.libraries = resolveLibraries(options?.libraries);
|
|
314
360
|
this.mode = options?.mode ?? "interpret";
|
|
361
|
+
this.timeout_seconds = options?.timeout_seconds ?? null;
|
|
315
362
|
}
|
|
316
363
|
|
|
317
364
|
/** Store Pine source for `import namespace/name/version`. */
|
|
@@ -348,7 +395,8 @@ export class Runtime {
|
|
|
348
395
|
inputs,
|
|
349
396
|
broker: extraOpts?.broker ?? this.broker,
|
|
350
397
|
timeframe: extraOpts?.timeframe ?? this.timeframe,
|
|
351
|
-
libraries: extraOpts?.libraries
|
|
398
|
+
libraries: extraOpts?.libraries != null ? resolveLibraries(extraOpts.libraries) : this.libraries,
|
|
399
|
+
timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds,
|
|
352
400
|
};
|
|
353
401
|
if (mode === "compile") {
|
|
354
402
|
return runCompiled(source, ohlcv, host);
|
|
@@ -456,6 +504,7 @@ export function interpretTree(
|
|
|
456
504
|
broker?: BrokerSettings;
|
|
457
505
|
timeframe?: string | null;
|
|
458
506
|
libraries?: LibraryRegistry;
|
|
507
|
+
timeout_seconds?: number | null;
|
|
459
508
|
},
|
|
460
509
|
): RuntimeResult {
|
|
461
510
|
const scriptNode = tree.kind === "Script" ? (tree as Script) : null;
|
|
@@ -491,12 +540,25 @@ export function interpretTree(
|
|
|
491
540
|
barTimes: ohlcv.map((b, i) => b.time ?? i * 60_000),
|
|
492
541
|
libraries: host?.libraries ?? new LibraryRegistry(),
|
|
493
542
|
pendingExports: new Map(),
|
|
543
|
+
onceFired: new WeakSet(),
|
|
544
|
+
maxBarsBackDecls: [],
|
|
545
|
+
alertFireBars: new Map(),
|
|
494
546
|
};
|
|
495
547
|
|
|
496
548
|
const n = ohlcv.length;
|
|
497
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;
|
|
498
555
|
try {
|
|
499
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
|
+
}
|
|
500
562
|
beginBar(env, ohlcv[i]!, i);
|
|
501
563
|
env.book.processPending(i, {
|
|
502
564
|
open: num(env.ctx.open) ?? undefined,
|
|
@@ -548,7 +610,7 @@ export function interpretTree(
|
|
|
548
610
|
series: plots,
|
|
549
611
|
plots: firstTitle ? (plots[firstTitle] ?? []) : [],
|
|
550
612
|
plot_meta,
|
|
551
|
-
count: n,
|
|
613
|
+
count: timedOut ? barsDone : n,
|
|
552
614
|
script_name,
|
|
553
615
|
script_type,
|
|
554
616
|
mode: "interpret",
|
|
@@ -561,6 +623,9 @@ export function interpretTree(
|
|
|
561
623
|
? { strategy: env.book.summary(num(env.ctx.close) ?? 0) }
|
|
562
624
|
: {}),
|
|
563
625
|
...(runError ?? {}),
|
|
626
|
+
...(timedOut
|
|
627
|
+
? { timed_out: true, error: "Script execution timed out", error_kind: "runtime" }
|
|
628
|
+
: {}),
|
|
564
629
|
};
|
|
565
630
|
}
|
|
566
631
|
|
|
@@ -604,9 +669,24 @@ function beginBar(env: Env, bar: OHLCVBar, i: number): void {
|
|
|
604
669
|
pushNamedSeries(env, "close", c);
|
|
605
670
|
pushNamedSeries(env, "volume", vol);
|
|
606
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);
|
|
607
685
|
env.ctx.bar_index = i;
|
|
608
686
|
env.ctx.last_bar_index = env.barCount - 1;
|
|
609
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;
|
|
610
690
|
|
|
611
691
|
bindDerived(env, "hl2", (h + l) / 2);
|
|
612
692
|
bindDerived(env, "hlc3", (h + l + c) / 3);
|
|
@@ -645,17 +725,23 @@ function bindDerived(env: Env, id: string, value: Cell): void {
|
|
|
645
725
|
}
|
|
646
726
|
}
|
|
647
727
|
|
|
728
|
+
function unwrapSpecialize(node: expr): expr {
|
|
729
|
+
let cur = node;
|
|
730
|
+
while (cur.kind === "Specialize") cur = cur.value;
|
|
731
|
+
return cur;
|
|
732
|
+
}
|
|
733
|
+
|
|
648
734
|
function attrPath(node: expr): string | null {
|
|
649
|
-
|
|
650
|
-
if (
|
|
651
|
-
|
|
652
|
-
|
|
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;
|
|
653
740
|
}
|
|
654
741
|
return null;
|
|
655
742
|
}
|
|
656
743
|
|
|
657
744
|
function callName(node: Call): string | null {
|
|
658
|
-
if (node.func.kind === "Name") return node.func.id;
|
|
659
745
|
return attrPath(node.func);
|
|
660
746
|
}
|
|
661
747
|
|
|
@@ -738,6 +824,8 @@ function callArg(
|
|
|
738
824
|
|
|
739
825
|
function defaultPlotTitle(fname: string): string {
|
|
740
826
|
if (fname === "hline") return "hline";
|
|
827
|
+
if (fname === "plotcandle") return "candles";
|
|
828
|
+
if (fname === "plotbar") return "bars";
|
|
741
829
|
if (fname === "plotshape") return "shape";
|
|
742
830
|
if (fname === "plotchar") return "char";
|
|
743
831
|
if (fname === "plotarrow") return "arrow";
|
|
@@ -894,6 +982,61 @@ function evalAssign(stmt: Assign, env: Env): Value {
|
|
|
894
982
|
return value;
|
|
895
983
|
}
|
|
896
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
|
+
|
|
897
1040
|
function evalReAssign(stmt: ReAssign, env: Env): Value {
|
|
898
1041
|
if (stmt.target.kind === "Tuple") {
|
|
899
1042
|
const packed = evalExpr(stmt.value, env);
|
|
@@ -924,6 +1067,21 @@ function applyStrategyDecl(stmt: { kind: string; value?: expr | null }, env: Env
|
|
|
924
1067
|
if (comm != null) settings.commission = comm;
|
|
925
1068
|
if (slip != null) settings.slippage = slip;
|
|
926
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;
|
|
927
1085
|
if (Object.keys(settings).length) env.book.configure(settings);
|
|
928
1086
|
const capital = unwrap(evalExpr(callArg(stmt.value.args, -1, ["initial_capital"]), env));
|
|
929
1087
|
if (capital != null && Number.isFinite(capital)) env.book.initialCapital = capital;
|
|
@@ -978,9 +1136,12 @@ function execStmt(
|
|
|
978
1136
|
if (stmt.kind === "ReAssign") {
|
|
979
1137
|
return evalReAssign(stmt as ReAssign, env);
|
|
980
1138
|
}
|
|
1139
|
+
if (stmt.kind === "AugAssign") {
|
|
1140
|
+
return evalAugAssign(stmt as AugAssign, env);
|
|
1141
|
+
}
|
|
981
1142
|
if (stmt.kind === "Break") throw LOOP_BREAK;
|
|
982
1143
|
if (stmt.kind === "Continue") throw LOOP_CONTINUE;
|
|
983
|
-
if (stmt.kind === "Switch" || stmt.kind === "While" || stmt.kind === "If") {
|
|
1144
|
+
if (stmt.kind === "Switch" || stmt.kind === "While" || stmt.kind === "If" || stmt.kind === "Once") {
|
|
984
1145
|
return evalExpr(stmt as unknown as expr, env);
|
|
985
1146
|
}
|
|
986
1147
|
if (stmt.kind !== "Expr" || !("value" in stmt) || stmt.value == null) return NA;
|
|
@@ -995,12 +1156,18 @@ function execStmt(
|
|
|
995
1156
|
fname === "plotchar" ||
|
|
996
1157
|
fname === "plotarrow" ||
|
|
997
1158
|
fname === "bgcolor" ||
|
|
998
|
-
fname === "barcolor"
|
|
1159
|
+
fname === "barcolor" ||
|
|
1160
|
+
fname === "plotcandle" ||
|
|
1161
|
+
fname === "plotbar"
|
|
999
1162
|
) {
|
|
1000
1163
|
const plotArgs = asArgs(value.args);
|
|
1164
|
+
const ohlc = fname === "plotcandle" || fname === "plotbar";
|
|
1001
1165
|
const title = resolvePlotTitle(plotArgs, fname);
|
|
1002
1166
|
const key = uniquifyPlotKey(env, value, title);
|
|
1003
|
-
|
|
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);
|
|
1004
1171
|
const cell = unwrap(evalExpr(seriesExpr, env));
|
|
1005
1172
|
if (!env.plots[key]) {
|
|
1006
1173
|
env.plots[key] = [];
|
|
@@ -1024,7 +1191,10 @@ function evalExpr(node: expr | undefined, env: Env): Value {
|
|
|
1024
1191
|
switch (node.kind) {
|
|
1025
1192
|
case "Name": {
|
|
1026
1193
|
const v = ctxGet(env, node.id);
|
|
1027
|
-
|
|
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;
|
|
1028
1198
|
}
|
|
1029
1199
|
case "Constant": {
|
|
1030
1200
|
const v = node.value;
|
|
@@ -1034,29 +1204,8 @@ function evalExpr(node: expr | undefined, env: Env): Value {
|
|
|
1034
1204
|
if (v === false) return 0;
|
|
1035
1205
|
return NA;
|
|
1036
1206
|
}
|
|
1037
|
-
case "BinOp":
|
|
1038
|
-
|
|
1039
|
-
const rightV = evalExpr(node.right, env);
|
|
1040
|
-
if (node.op.kind === "Add" && (typeof leftV === "string" || typeof rightV === "string")) {
|
|
1041
|
-
return stringifyVal(leftV) + stringifyVal(rightV);
|
|
1042
|
-
}
|
|
1043
|
-
const l = unwrap(leftV);
|
|
1044
|
-
const r = unwrap(rightV);
|
|
1045
|
-
if (l == null || r == null) return NA;
|
|
1046
|
-
switch (node.op.kind) {
|
|
1047
|
-
case "Add":
|
|
1048
|
-
return l + r;
|
|
1049
|
-
case "Sub":
|
|
1050
|
-
return l - r;
|
|
1051
|
-
case "Mult":
|
|
1052
|
-
return l * r;
|
|
1053
|
-
case "Div":
|
|
1054
|
-
return r === 0 ? NA : l / r;
|
|
1055
|
-
case "Mod":
|
|
1056
|
-
return r === 0 ? NA : l % r;
|
|
1057
|
-
}
|
|
1058
|
-
return NA;
|
|
1059
|
-
}
|
|
1207
|
+
case "BinOp":
|
|
1208
|
+
return applyBinOp(node.op.kind, evalExpr(node.left, env), evalExpr(node.right, env));
|
|
1060
1209
|
case "UnaryOp": {
|
|
1061
1210
|
const v = unwrap(evalExpr(node.operand, env));
|
|
1062
1211
|
if (node.op.kind === "Not") return isTruthy(v) ? 0 : 1;
|
|
@@ -1111,8 +1260,23 @@ function evalExpr(node: expr | undefined, env: Env): Value {
|
|
|
1111
1260
|
return evalWhile(node, env);
|
|
1112
1261
|
case "Switch":
|
|
1113
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
|
+
}
|
|
1114
1272
|
case "BoolOp":
|
|
1115
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);
|
|
1116
1280
|
}
|
|
1117
1281
|
}
|
|
1118
1282
|
|
|
@@ -1189,16 +1353,27 @@ function evalSwitch(node: Switch, env: Env): Value {
|
|
|
1189
1353
|
const subject = subjectPresent ? evalExpr(node.subject!, env) : null;
|
|
1190
1354
|
for (const c of cases) {
|
|
1191
1355
|
if (c.pattern == null) return execBlock(c.body, env);
|
|
1192
|
-
if (subjectPresent)
|
|
1193
|
-
const pat = evalExpr(c.pattern, env);
|
|
1194
|
-
if (valuesEq(pat, subject)) return execBlock(c.body, env);
|
|
1195
|
-
} else if (isTruthy(unwrap(evalExpr(c.pattern, env)))) {
|
|
1196
|
-
return execBlock(c.body, env);
|
|
1197
|
-
}
|
|
1356
|
+
if (switchArmMatches(subjectPresent, subject, c.pattern, env)) return execBlock(c.body, env);
|
|
1198
1357
|
}
|
|
1199
1358
|
return NA;
|
|
1200
1359
|
}
|
|
1201
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
|
+
|
|
1202
1377
|
function execBlock(body: stmt[] | stmt | null | undefined, env: Env): Value {
|
|
1203
1378
|
let last: Value = NA;
|
|
1204
1379
|
for (const s of asStmts(body)) last = execStmt(s, env);
|
|
@@ -1253,73 +1428,154 @@ function evalForIn(node: ForIn, env: Env): Value {
|
|
|
1253
1428
|
return last;
|
|
1254
1429
|
}
|
|
1255
1430
|
|
|
1256
|
-
/**
|
|
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. */
|
|
1257
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;
|
|
1258
1546
|
if (node.value.kind === "Name") {
|
|
1259
|
-
if (node.value.id === "strategy") {
|
|
1260
|
-
if (node.attr === "long" || node.attr === "short") return node.attr;
|
|
1261
|
-
if (node.attr === "position_size") return env.book.position.qty;
|
|
1262
|
-
if (node.attr === "position_avg_price") return env.book.position.avgPrice;
|
|
1263
|
-
if (node.attr === "netprofit") return env.book.realizedPnl;
|
|
1264
|
-
if (node.attr === "openprofit") {
|
|
1265
|
-
const q = env.book.position.qty;
|
|
1266
|
-
const avg = env.book.position.avgPrice;
|
|
1267
|
-
const mark = num(env.ctx.close);
|
|
1268
|
-
if (q === 0 || avg == null || mark == null) return 0;
|
|
1269
|
-
return q * (mark - avg);
|
|
1270
|
-
}
|
|
1271
|
-
if (node.attr === "equity") return env.book.equity(num(env.ctx.close) ?? 0);
|
|
1272
|
-
if (node.attr === "opentrades") return env.book.opentrades;
|
|
1273
|
-
if (node.attr === "closedtrades") return env.book.closedtrades;
|
|
1274
|
-
if (node.attr === "wintrades") return env.book.wintrades;
|
|
1275
|
-
if (node.attr === "losstrades") return env.book.losstrades;
|
|
1276
|
-
if (node.attr === "eventrades") return env.book.eventrades;
|
|
1277
|
-
if (node.attr === "grossprofit") return env.book.grossprofit;
|
|
1278
|
-
if (node.attr === "grossloss") return env.book.grossloss;
|
|
1279
|
-
if (node.attr === "avg_trade") return env.book.avgTrade();
|
|
1280
|
-
if (node.attr === "avg_winning_trade") return env.book.avgWinningTrade();
|
|
1281
|
-
if (node.attr === "avg_losing_trade") return env.book.avgLosingTrade();
|
|
1282
|
-
if (node.attr === "netprofit_percent") {
|
|
1283
|
-
return env.book.netprofitPercent(num(env.ctx.close) ?? 0);
|
|
1284
|
-
}
|
|
1285
|
-
if (node.attr === "openprofit_percent") {
|
|
1286
|
-
return env.book.openprofitPercent(num(env.ctx.close) ?? 0);
|
|
1287
|
-
}
|
|
1288
|
-
if (node.attr === "grossprofit_percent") return env.book.grossprofitPercent();
|
|
1289
|
-
if (node.attr === "grossloss_percent") return env.book.grosslossPercent();
|
|
1290
|
-
if (node.attr === "avg_trade_percent") return env.book.avgTradePercent();
|
|
1291
|
-
if (node.attr === "avg_winning_trade_percent") return env.book.avgWinningTradePercent();
|
|
1292
|
-
if (node.attr === "avg_losing_trade_percent") return env.book.avgLosingTradePercent();
|
|
1293
|
-
if (node.attr === "max_drawdown") return env.book.maxDrawdown();
|
|
1294
|
-
if (node.attr === "max_drawdown_percent") return env.book.maxDrawdownPercent();
|
|
1295
|
-
if (node.attr === "max_runup") return env.book.maxRunup();
|
|
1296
|
-
if (node.attr === "max_runup_percent") return env.book.maxRunupPercent();
|
|
1297
|
-
if (node.attr === "percent_profitable" || node.attr === "winrate") {
|
|
1298
|
-
return env.book.percentProfitable();
|
|
1299
|
-
}
|
|
1300
|
-
if (node.attr === "profitfactor" || node.attr === "profit_factor") {
|
|
1301
|
-
return env.book.profitFactor();
|
|
1302
|
-
}
|
|
1303
|
-
if (node.attr === "initial_capital") return env.book.initialCapital;
|
|
1304
|
-
if (node.attr === "commission") return 0;
|
|
1305
|
-
if (node.attr === "cash") return "cash";
|
|
1306
|
-
if (node.attr === "fixed") return "fixed";
|
|
1307
|
-
if (node.attr === "percent_of_equity") return "percent_of_equity";
|
|
1308
|
-
}
|
|
1309
1547
|
if (node.value.id === "syminfo") {
|
|
1310
1548
|
if (node.attr === "ticker" || node.attr === "tickerid") return env.symbol;
|
|
1549
|
+
if (node.attr === "prefix") return extractPrefix(env.symbol);
|
|
1311
1550
|
if (node.attr === "timezone") return "Etc/UTC";
|
|
1312
1551
|
if (node.attr === "currency") return "USD";
|
|
1313
1552
|
if (node.attr === "mintick") return 0.01;
|
|
1314
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;
|
|
1315
1557
|
}
|
|
1316
1558
|
if (node.value.id === "timeframe") {
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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;
|
|
1323
1579
|
}
|
|
1324
1580
|
if (node.value.id === "color") {
|
|
1325
1581
|
const named = colorByName(node.attr);
|
|
@@ -1358,6 +1614,14 @@ function evalAttribute(node: Attribute, env: Env): Value {
|
|
|
1358
1614
|
const taAttr = evalTaAttr(node.attr, env);
|
|
1359
1615
|
if (taAttr !== undefined) return taAttr;
|
|
1360
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
|
+
}
|
|
1361
1625
|
}
|
|
1362
1626
|
const obj = evalExpr(node.value, env);
|
|
1363
1627
|
if (obj instanceof UdtInstance) {
|
|
@@ -1377,6 +1641,33 @@ function evalAttribute(node: Attribute, env: Env): Value {
|
|
|
1377
1641
|
if (typeof exp === "function") return NA;
|
|
1378
1642
|
return exp as Value;
|
|
1379
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;
|
|
1380
1671
|
return NA;
|
|
1381
1672
|
}
|
|
1382
1673
|
|
|
@@ -1411,7 +1702,7 @@ function evalTaAttr(attr: string, env: Env): Value | undefined {
|
|
|
1411
1702
|
const src = h != null && l != null && c != null ? (h + l + c) / 3 : c;
|
|
1412
1703
|
return (ta.vwap as TaEngine["vwap"]).call(env.ta, site, src, num(env.ctx.volume));
|
|
1413
1704
|
}
|
|
1414
|
-
if (attr === "pvt" && typeof ta.pvt === "function") {
|
|
1705
|
+
if ((attr === "pvt" || attr === "vpt") && typeof ta.pvt === "function") {
|
|
1415
1706
|
return (ta.pvt as TaEngine["pvt"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
1416
1707
|
}
|
|
1417
1708
|
if (attr === "nvi" && typeof ta.nvi === "function") {
|
|
@@ -1420,6 +1711,41 @@ function evalTaAttr(attr: string, env: Env): Value | undefined {
|
|
|
1420
1711
|
if (attr === "pvi" && typeof ta.pvi === "function") {
|
|
1421
1712
|
return (ta.pvi as TaEngine["pvi"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
1422
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
|
+
}
|
|
1423
1749
|
return undefined;
|
|
1424
1750
|
}
|
|
1425
1751
|
|
|
@@ -1455,6 +1781,9 @@ function isNaVal(value: Value): boolean {
|
|
|
1455
1781
|
}
|
|
1456
1782
|
|
|
1457
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;
|
|
1458
1787
|
if (isNaVal(left) && isNaVal(right)) return true;
|
|
1459
1788
|
if (isNaVal(left) || isNaVal(right)) return false;
|
|
1460
1789
|
if (left instanceof EnumMember) return left.equals(right);
|
|
@@ -1492,20 +1821,27 @@ function cmp(left: Value, op: string, right: Value): boolean | null {
|
|
|
1492
1821
|
}
|
|
1493
1822
|
|
|
1494
1823
|
function evalCall(node: Call, env: Env): Value {
|
|
1495
|
-
|
|
1496
|
-
|
|
1824
|
+
const callee = unwrapSpecialize(node.func);
|
|
1825
|
+
if (callee.kind === "Attribute") {
|
|
1826
|
+
const obj = evalExpr(callee.value, env);
|
|
1497
1827
|
if (obj instanceof LibraryModule) {
|
|
1498
|
-
return evalLibraryMemberCall(obj,
|
|
1828
|
+
return evalLibraryMemberCall(obj, callee.attr, node, env);
|
|
1499
1829
|
}
|
|
1500
1830
|
if (obj instanceof UdtInstance) {
|
|
1501
|
-
const meth = obj.getMethod(
|
|
1831
|
+
const meth = obj.getMethod(callee.attr);
|
|
1502
1832
|
if (meth && isUdfDef(meth.body)) return evalUdf(meth.body, node, env, obj);
|
|
1503
1833
|
}
|
|
1504
|
-
if (obj instanceof UdtType &&
|
|
1834
|
+
if (obj instanceof UdtType && callee.attr === "new") {
|
|
1505
1835
|
const overrides: Record<string, unknown> = {};
|
|
1836
|
+
let pos = 0;
|
|
1506
1837
|
for (const a of asArgs(node.args)) {
|
|
1507
1838
|
const key = argKeyword(a);
|
|
1508
|
-
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);
|
|
1509
1845
|
}
|
|
1510
1846
|
return obj.newInstance(overrides);
|
|
1511
1847
|
}
|
|
@@ -1525,7 +1861,10 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1525
1861
|
return env.ta.rma(site, srcArg(node, env), lenArg(node, env, 1));
|
|
1526
1862
|
}
|
|
1527
1863
|
if (fname === "ta.atr" || fname === "atr") {
|
|
1528
|
-
|
|
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;
|
|
1529
1868
|
return env.ta.atr(site, num(env.ctx.high), num(env.ctx.low), num(env.ctx.close), period);
|
|
1530
1869
|
}
|
|
1531
1870
|
if (fname === "ta.kc" || fname === "kc") {
|
|
@@ -1542,7 +1881,12 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1542
1881
|
const mathVal = evalMathCall(fname, node, env);
|
|
1543
1882
|
if (mathVal !== undefined) return mathVal;
|
|
1544
1883
|
if (isInputBuiltin(fname)) return evalInput(node, env, fname!);
|
|
1545
|
-
if (
|
|
1884
|
+
if (
|
|
1885
|
+
isRequestBuiltin(fname) ||
|
|
1886
|
+
fname === "request.security" ||
|
|
1887
|
+
fname === "security" ||
|
|
1888
|
+
fname === "security_lower_tf"
|
|
1889
|
+
) {
|
|
1546
1890
|
return evalRequestCall(fname!, node, env);
|
|
1547
1891
|
}
|
|
1548
1892
|
const arrVal = evalArrayCall(fname, node, env);
|
|
@@ -1595,9 +1939,44 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1595
1939
|
}
|
|
1596
1940
|
const timeVal = evalTimeCall(fname, node, env);
|
|
1597
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);
|
|
1598
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).
|
|
1599
1978
|
const p = evalAsString(callArg(node.args, 0, ["timeframe", "period"]), env) ?? env.timeframe;
|
|
1600
|
-
return timeframeInSeconds(p);
|
|
1979
|
+
return timeframeInSeconds(p == null || p === "" ? "D" : p);
|
|
1601
1980
|
}
|
|
1602
1981
|
if (fname === "timeframe.from_seconds") {
|
|
1603
1982
|
const s = unwrap(evalExpr(callArg(node.args, 0, ["seconds"]), env));
|
|
@@ -1670,11 +2049,16 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1670
2049
|
const id = evalId(callArg(node.args, 0, ["id"]));
|
|
1671
2050
|
const dir = evalDirection(callArg(node.args, 1, ["direction"]), env);
|
|
1672
2051
|
const qtyArg = callArg(node.args, 2, ["qty"]);
|
|
1673
|
-
const
|
|
1674
|
-
|
|
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
|
+
}
|
|
1675
2060
|
const limit = unwrap(evalExpr(callArg(node.args, -1, ["limit"]), env));
|
|
1676
2061
|
const stop = unwrap(evalExpr(callArg(node.args, -1, ["stop"]), env));
|
|
1677
|
-
const mark = num(env.ctx.close);
|
|
1678
2062
|
env.book.placeEntry(env.barIndex, id, dir, qty, {
|
|
1679
2063
|
limit,
|
|
1680
2064
|
stop,
|
|
@@ -1688,13 +2072,40 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1688
2072
|
if (fname === "strategy.close") {
|
|
1689
2073
|
const id = evalId(callArg(node.args, 0, ["id"]));
|
|
1690
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));
|
|
1691
2077
|
if (env.book.position.qty !== 0 && mark != null) {
|
|
1692
|
-
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
|
+
});
|
|
1693
2082
|
} else env.book.close(env.barIndex, id);
|
|
1694
2083
|
return NA;
|
|
1695
2084
|
}
|
|
1696
2085
|
if (fname === "strategy.exit") {
|
|
1697
|
-
|
|
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
|
+
});
|
|
1698
2109
|
return NA;
|
|
1699
2110
|
}
|
|
1700
2111
|
if (fname === "strategy.close_all") {
|
|
@@ -1726,6 +2137,8 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1726
2137
|
}
|
|
1727
2138
|
const riskVal = evalStrategyRiskCall(fname, node, env);
|
|
1728
2139
|
if (riskVal !== undefined) return riskVal;
|
|
2140
|
+
const seriesVal = evalStrategySeriesCall(fname, node, env);
|
|
2141
|
+
if (seriesVal !== undefined) return seriesVal;
|
|
1729
2142
|
const tradeVal = evalStrategyTradeCall(fname, node, env);
|
|
1730
2143
|
if (tradeVal !== undefined) return tradeVal;
|
|
1731
2144
|
if (fname != null && fname.endsWith(".new")) {
|
|
@@ -1752,7 +2165,11 @@ function evalKc(node: Call, env: Env, site: string): Value {
|
|
|
1752
2165
|
let high = num(env.ctx.high);
|
|
1753
2166
|
let low = num(env.ctx.low);
|
|
1754
2167
|
let close = srcArg(node, env);
|
|
1755
|
-
|
|
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;
|
|
1756
2173
|
let mult = numArg(node, env, 2, ["mult", "multiplier"], 1);
|
|
1757
2174
|
if (positional.length >= 4) {
|
|
1758
2175
|
high = unwrap(evalExpr(positional[0]!.value, env));
|
|
@@ -1869,11 +2286,72 @@ function numArg(node: Call, env: Env, index: number, names: string[], fallback:
|
|
|
1869
2286
|
|
|
1870
2287
|
function unwrap(value: Value): Cell {
|
|
1871
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;
|
|
1872
2292
|
if (typeof value === "number") return Number.isFinite(value) ? value : NA;
|
|
2293
|
+
if (isStrategyCashAmount(value)) return Number.isFinite(value.value) ? value.value : NA;
|
|
1873
2294
|
// Objects (PineArray / Matrix / Color / tuples) are not cells — not na.
|
|
1874
2295
|
return NA;
|
|
1875
2296
|
}
|
|
1876
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
|
+
|
|
1877
2355
|
function formatRunError(err: unknown): string {
|
|
1878
2356
|
if (err instanceof Error) {
|
|
1879
2357
|
const msg = (err.message || err.name || "runtime error").split("\n")[0]!.trim();
|
|
@@ -1892,10 +2370,27 @@ function isPlainInputs(extra: RuntimeOptions | InputOverrides): extra is InputOv
|
|
|
1892
2370
|
!("broker" in extra) &&
|
|
1893
2371
|
!("timeframe" in extra) &&
|
|
1894
2372
|
!("libraries" in extra) &&
|
|
1895
|
-
!("mode" in extra)
|
|
2373
|
+
!("mode" in extra) &&
|
|
2374
|
+
!("timeout_seconds" in extra)
|
|
1896
2375
|
);
|
|
1897
2376
|
}
|
|
1898
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
|
+
|
|
1899
2394
|
function emptyCompileResult(
|
|
1900
2395
|
n: number,
|
|
1901
2396
|
error: string,
|
|
@@ -2028,6 +2523,7 @@ function runAuto(
|
|
|
2028
2523
|
broker?: BrokerSettings;
|
|
2029
2524
|
timeframe?: string | null;
|
|
2030
2525
|
libraries?: LibraryRegistry;
|
|
2526
|
+
timeout_seconds?: number | null;
|
|
2031
2527
|
},
|
|
2032
2528
|
): RuntimeResult {
|
|
2033
2529
|
const elig = compileEligible(source);
|
|
@@ -2081,6 +2577,16 @@ function packDrawings(book: DrawingBook): DrawingEvent[] | undefined {
|
|
|
2081
2577
|
return book.items.length === 0 ? undefined : book.items;
|
|
2082
2578
|
}
|
|
2083
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
|
+
|
|
2084
2590
|
function inputTitle(node: Call): string | null {
|
|
2085
2591
|
const named = namedStringArg(node.args, "title");
|
|
2086
2592
|
if (named != null) return named;
|
|
@@ -2168,6 +2674,153 @@ function evalStrategyRiskCall(fname: string | null, node: Call, env: Env): Value
|
|
|
2168
2674
|
return undefined;
|
|
2169
2675
|
}
|
|
2170
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
|
+
|
|
2171
2824
|
function evalStrategyTradeCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
2172
2825
|
if (fname == null || !fname.startsWith("strategy.")) return undefined;
|
|
2173
2826
|
const idx = unwrap(evalExpr(callArg(node.args, 0, ["trade_num", "trade_index", "index"]), env));
|
|
@@ -2176,32 +2829,54 @@ function evalStrategyTradeCall(fname: string | null, node: Call, env: Env): Valu
|
|
|
2176
2829
|
const book = env.book;
|
|
2177
2830
|
switch (fname) {
|
|
2178
2831
|
case "strategy.closedtrades.entry_bar_index":
|
|
2179
|
-
return book.closedEntryBar(i);
|
|
2832
|
+
return book.closedEntryBar(i) ?? 0;
|
|
2180
2833
|
case "strategy.closedtrades.entry_price":
|
|
2181
|
-
return book.closedEntryPrice(i);
|
|
2834
|
+
return book.closedEntryPrice(i) ?? 0;
|
|
2182
2835
|
case "strategy.closedtrades.exit_bar_index":
|
|
2183
|
-
return book.closedExitBar(i);
|
|
2836
|
+
return book.closedExitBar(i) ?? 0;
|
|
2184
2837
|
case "strategy.closedtrades.exit_price":
|
|
2185
|
-
return book.closedExitPrice(i);
|
|
2838
|
+
return book.closedExitPrice(i) ?? 0;
|
|
2186
2839
|
case "strategy.closedtrades.profit":
|
|
2187
|
-
return book.closedProfit(i);
|
|
2840
|
+
return book.closedProfit(i) ?? 0;
|
|
2188
2841
|
case "strategy.closedtrades.size":
|
|
2189
|
-
return book.closedSize(i);
|
|
2842
|
+
return book.closedSize(i) ?? 0;
|
|
2190
2843
|
case "strategy.closedtrades.entry_id":
|
|
2191
2844
|
case "strategy.closedtrades.exit_id":
|
|
2192
|
-
return book.closedId(i);
|
|
2845
|
+
return book.closedId(i) ?? "";
|
|
2193
2846
|
case "strategy.closedtrades.commission":
|
|
2194
|
-
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);
|
|
2195
2860
|
case "strategy.opentrades.entry_bar_index":
|
|
2196
|
-
return book.openEntryBar(i);
|
|
2861
|
+
return book.openEntryBar(i) ?? 0;
|
|
2197
2862
|
case "strategy.opentrades.entry_price":
|
|
2198
|
-
return book.openEntryPrice(i);
|
|
2863
|
+
return book.openEntryPrice(i) ?? 0;
|
|
2199
2864
|
case "strategy.opentrades.size":
|
|
2200
|
-
return book.openSize(i);
|
|
2865
|
+
return book.openSize(i) ?? 0;
|
|
2201
2866
|
case "strategy.opentrades.entry_id":
|
|
2202
|
-
return book.openId(i);
|
|
2867
|
+
return book.openId(i) ?? "";
|
|
2203
2868
|
case "strategy.opentrades.profit":
|
|
2204
|
-
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);
|
|
2205
2880
|
default:
|
|
2206
2881
|
return undefined;
|
|
2207
2882
|
}
|
|
@@ -2221,7 +2896,12 @@ function evalRequestSecurity(node: Call, env: Env): Cell {
|
|
|
2221
2896
|
}
|
|
2222
2897
|
|
|
2223
2898
|
function evalRequestCall(fname: string, node: Call, env: Env): Cell {
|
|
2224
|
-
if (
|
|
2899
|
+
if (
|
|
2900
|
+
fname === "request.security" ||
|
|
2901
|
+
fname === "request.security_lower_tf" ||
|
|
2902
|
+
fname === "security" ||
|
|
2903
|
+
fname === "security_lower_tf"
|
|
2904
|
+
) {
|
|
2225
2905
|
return evalRequestSecurity(node, env);
|
|
2226
2906
|
}
|
|
2227
2907
|
const host = { symbol: env.symbol, timeframe: env.timeframe };
|
|
@@ -2279,7 +2959,7 @@ function evalMathCall(fname: string | null, node: Call, env: Env): Cell | undefi
|
|
|
2279
2959
|
if (fname === "math.todegrees") return mathToDegrees(cellArg(node, env, 0, ["radians", "x"]));
|
|
2280
2960
|
if (fname === "math.toradians") return mathToRadians(cellArg(node, env, 0, ["degrees", "x"]));
|
|
2281
2961
|
if (fname === "math.isfinite") return mathIsFinite(cellArg(node, env, 0, ["number", "x"]));
|
|
2282
|
-
if (fname === "math.random") {
|
|
2962
|
+
if (fname === "math.random" || fname === "random") {
|
|
2283
2963
|
const a0 = callArg(node.args, 0, ["min"]);
|
|
2284
2964
|
const a1 = callArg(node.args, 1, ["max"]);
|
|
2285
2965
|
return mathRandom(
|
|
@@ -2287,7 +2967,7 @@ function evalMathCall(fname: string | null, node: Call, env: Env): Cell | undefi
|
|
|
2287
2967
|
a1 == null ? undefined : unwrap(evalExpr(a1, env)),
|
|
2288
2968
|
);
|
|
2289
2969
|
}
|
|
2290
|
-
if (fname === "math.round_to_mintick") {
|
|
2970
|
+
if (fname === "math.round_to_mintick" || fname === "round_to_mintick") {
|
|
2291
2971
|
const tick = typeof env.ctx["syminfo.mintick"] === "number" ? (env.ctx["syminfo.mintick"] as number) : 0.01;
|
|
2292
2972
|
return mathRoundToMintick(cellArg(node, env, 0, ["number", "x"]), tick);
|
|
2293
2973
|
}
|
|
@@ -2302,6 +2982,37 @@ function allCellArgs(node: Call, env: Env): Cell[] {
|
|
|
2302
2982
|
return asArgs(node.args).map((a) => unwrap(evalExpr(a.value, env)));
|
|
2303
2983
|
}
|
|
2304
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
|
+
|
|
2305
3016
|
function lenOrDefault(
|
|
2306
3017
|
node: Call,
|
|
2307
3018
|
env: Env,
|
|
@@ -2321,10 +3032,31 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2321
3032
|
const ta = env.ta as TaAny;
|
|
2322
3033
|
if (fname === "ta.highest" || fname === "highest") {
|
|
2323
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
|
+
}
|
|
2324
3046
|
return (ta.highest as TaEngine["highest"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2325
3047
|
}
|
|
2326
3048
|
if (fname === "ta.lowest" || fname === "lowest") {
|
|
2327
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
|
+
}
|
|
2328
3060
|
return (ta.lowest as TaEngine["lowest"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2329
3061
|
}
|
|
2330
3062
|
if (fname === "ta.stdev" || fname === "stdev") {
|
|
@@ -2376,6 +3108,20 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2376
3108
|
}
|
|
2377
3109
|
if (fname === "ta.bb" || fname === "bb") {
|
|
2378
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
|
+
}
|
|
2379
3125
|
const r = (ta.bb as TaEngine["bb"]).call(
|
|
2380
3126
|
env.ta,
|
|
2381
3127
|
site,
|
|
@@ -2409,6 +3155,29 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2409
3155
|
}
|
|
2410
3156
|
if (fname === "ta.vwma" || fname === "vwma") {
|
|
2411
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
|
+
}
|
|
2412
3181
|
return (ta.vwma as TaEngine["vwma"]).call(
|
|
2413
3182
|
env.ta,
|
|
2414
3183
|
site,
|
|
@@ -2419,21 +3188,43 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2419
3188
|
}
|
|
2420
3189
|
if (fname === "ta.cci" || fname === "cci") {
|
|
2421
3190
|
if (typeof ta.cci !== "function") return NA;
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
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));
|
|
2427
3216
|
}
|
|
2428
3217
|
if (fname === "ta.wpr" || fname === "wpr") {
|
|
2429
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.
|
|
2430
3221
|
return (ta.willr as TaEngine["willr"]).call(
|
|
2431
3222
|
env.ta,
|
|
2432
3223
|
site,
|
|
2433
3224
|
num(env.ctx.high),
|
|
2434
3225
|
num(env.ctx.low),
|
|
2435
3226
|
num(env.ctx.close),
|
|
2436
|
-
|
|
3227
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
2437
3228
|
);
|
|
2438
3229
|
}
|
|
2439
3230
|
if (fname === "ta.willr" || fname === "willr") {
|
|
@@ -2444,28 +3235,41 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2444
3235
|
num(env.ctx.high),
|
|
2445
3236
|
num(env.ctx.low),
|
|
2446
3237
|
num(env.ctx.close),
|
|
2447
|
-
|
|
3238
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
2448
3239
|
);
|
|
2449
3240
|
}
|
|
2450
3241
|
if (fname === "ta.stoch" || fname === "stoch") {
|
|
2451
3242
|
if (typeof ta.stoch !== "function") return NA;
|
|
2452
3243
|
const positional = asArgs(node.args).filter((a) => argKeyword(a) == null);
|
|
2453
|
-
|
|
2454
|
-
|
|
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
|
+
}
|
|
2455
3258
|
return (ta.stoch as TaEngine["stoch"]).call(
|
|
2456
3259
|
env.ta,
|
|
2457
3260
|
site,
|
|
2458
3261
|
num(env.ctx.close),
|
|
2459
3262
|
num(env.ctx.high),
|
|
2460
3263
|
num(env.ctx.low),
|
|
2461
|
-
|
|
3264
|
+
length,
|
|
2462
3265
|
);
|
|
2463
3266
|
}
|
|
3267
|
+
// reference Pine form: source, high, low, length
|
|
2464
3268
|
return (ta.stoch as TaEngine["stoch"]).call(
|
|
2465
3269
|
env.ta,
|
|
2466
3270
|
site,
|
|
2467
3271
|
srcArg(node, env),
|
|
2468
|
-
unwrap(evalExpr(
|
|
3272
|
+
unwrap(evalExpr(callArg(node.args, 1, ["high"]), env)),
|
|
2469
3273
|
unwrap(evalExpr(callArg(node.args, 2, ["low"]), env)),
|
|
2470
3274
|
lenArg(node, env, 3),
|
|
2471
3275
|
);
|
|
@@ -2533,6 +3337,16 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2533
3337
|
}
|
|
2534
3338
|
if (fname === "ta.highestbars" || fname === "highestbars") {
|
|
2535
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
|
+
}
|
|
2536
3350
|
return (ta.highestbars as TaEngine["highestbars"]).call(
|
|
2537
3351
|
env.ta,
|
|
2538
3352
|
site,
|
|
@@ -2542,6 +3356,16 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2542
3356
|
}
|
|
2543
3357
|
if (fname === "ta.lowestbars" || fname === "lowestbars") {
|
|
2544
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
|
+
}
|
|
2545
3369
|
return (ta.lowestbars as TaEngine["lowestbars"]).call(
|
|
2546
3370
|
env.ta,
|
|
2547
3371
|
site,
|
|
@@ -2598,6 +3422,20 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2598
3422
|
}
|
|
2599
3423
|
if (fname === "ta.pivothigh" || fname === "pivothigh") {
|
|
2600
3424
|
if (typeof ta.pivothigh !== "function") return NA;
|
|
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
|
+
}
|
|
2601
3439
|
return (ta.pivothigh as TaEngine["pivothigh"]).call(
|
|
2602
3440
|
env.ta,
|
|
2603
3441
|
site,
|
|
@@ -2608,6 +3446,18 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2608
3446
|
}
|
|
2609
3447
|
if (fname === "ta.pivotlow" || fname === "pivotlow") {
|
|
2610
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
|
+
}
|
|
2611
3461
|
return (ta.pivotlow as TaEngine["pivotlow"]).call(
|
|
2612
3462
|
env.ta,
|
|
2613
3463
|
site,
|
|
@@ -2624,6 +3474,8 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2624
3474
|
if (typeof ta.mfi !== "function") return NA;
|
|
2625
3475
|
const positional = asArgs(node.args).filter((a) => a.name == null);
|
|
2626
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.
|
|
2627
3479
|
return (ta.mfi as TaEngine["mfi"]).call(
|
|
2628
3480
|
env.ta,
|
|
2629
3481
|
site,
|
|
@@ -2631,7 +3483,7 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2631
3483
|
num(env.ctx.low),
|
|
2632
3484
|
num(env.ctx.close),
|
|
2633
3485
|
num(env.ctx.volume),
|
|
2634
|
-
|
|
3486
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 0,
|
|
2635
3487
|
);
|
|
2636
3488
|
}
|
|
2637
3489
|
if (positional.length === 2) {
|
|
@@ -2682,13 +3534,44 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2682
3534
|
}
|
|
2683
3535
|
if (fname === "ta.adx" || fname === "adx") {
|
|
2684
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).
|
|
2685
3568
|
return (ta.adx as TaEngine["adx"]).call(
|
|
2686
3569
|
env.ta,
|
|
2687
3570
|
site,
|
|
2688
3571
|
num(env.ctx.high),
|
|
2689
3572
|
num(env.ctx.low),
|
|
2690
3573
|
num(env.ctx.close),
|
|
2691
|
-
|
|
3574
|
+
periodLikeArg(node, env, 0, ["length"]) ?? 14,
|
|
2692
3575
|
);
|
|
2693
3576
|
}
|
|
2694
3577
|
if (fname === "ta.correlation" || fname === "correlation") {
|
|
@@ -2719,12 +3602,15 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2719
3602
|
if (typeof ta.tsi !== "function") return NA;
|
|
2720
3603
|
const positional = asArgs(node.args).filter((a) => a.name == null);
|
|
2721
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.
|
|
2722
3608
|
return (ta.tsi as TaEngine["tsi"]).call(
|
|
2723
3609
|
env.ta,
|
|
2724
3610
|
site,
|
|
2725
3611
|
num(env.ctx.close),
|
|
2726
|
-
|
|
2727
|
-
|
|
3612
|
+
periodLikeArg(node, env, 1, ["long", "long_length"]) ?? 25,
|
|
3613
|
+
periodLikeArg(node, env, 0, ["short", "short_length"]) ?? 13,
|
|
2728
3614
|
);
|
|
2729
3615
|
}
|
|
2730
3616
|
return (ta.tsi as TaEngine["tsi"]).call(
|
|
@@ -2833,27 +3719,47 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2833
3719
|
num(env.ctx.volume),
|
|
2834
3720
|
);
|
|
2835
3721
|
}
|
|
2836
|
-
if (fname === "ta.pvt" || fname === "pvt") {
|
|
3722
|
+
if (fname === "ta.pvt" || fname === "pvt" || fname === "ta.vpt" || fname === "vpt") {
|
|
2837
3723
|
if (typeof ta.pvt !== "function") return NA;
|
|
2838
3724
|
return (ta.pvt as TaEngine["pvt"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
2839
3725
|
}
|
|
2840
3726
|
if (fname === "ta.wad" || fname === "wad") {
|
|
2841
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
|
+
}
|
|
2842
3739
|
return (ta.wad as TaEngine["wad"]).call(
|
|
2843
3740
|
env.ta,
|
|
2844
3741
|
site,
|
|
2845
3742
|
num(env.ctx.high),
|
|
2846
3743
|
num(env.ctx.low),
|
|
2847
3744
|
num(env.ctx.close),
|
|
3745
|
+
num(env.ctx.volume),
|
|
2848
3746
|
);
|
|
2849
3747
|
}
|
|
2850
3748
|
if (fname === "ta.nvi" || fname === "nvi") {
|
|
2851
3749
|
if (typeof ta.nvi !== "function") return NA;
|
|
2852
|
-
|
|
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);
|
|
2853
3755
|
}
|
|
2854
3756
|
if (fname === "ta.pvi" || fname === "pvi") {
|
|
2855
3757
|
if (typeof ta.pvi !== "function") return NA;
|
|
2856
|
-
|
|
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);
|
|
2857
3763
|
}
|
|
2858
3764
|
if (fname === "ta.iii" || fname === "iii") {
|
|
2859
3765
|
if (typeof ta.iii !== "function") return NA;
|
|
@@ -2868,28 +3774,335 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
2868
3774
|
}
|
|
2869
3775
|
if (fname === "ta.wvad" || fname === "wvad") {
|
|
2870
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
|
+
}
|
|
2871
3789
|
return (ta.wvad as TaEngine["wvad"]).call(
|
|
2872
3790
|
env.ta,
|
|
2873
3791
|
site,
|
|
2874
|
-
num(env.ctx.open),
|
|
2875
3792
|
num(env.ctx.high),
|
|
2876
3793
|
num(env.ctx.low),
|
|
2877
3794
|
num(env.ctx.close),
|
|
2878
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,
|
|
2879
3800
|
);
|
|
2880
3801
|
}
|
|
2881
|
-
if (fname === "ta.
|
|
2882
|
-
if (typeof ta.
|
|
2883
|
-
const
|
|
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(
|
|
2884
3817
|
env.ta,
|
|
2885
3818
|
site,
|
|
2886
3819
|
num(env.ctx.high),
|
|
2887
3820
|
num(env.ctx.low),
|
|
2888
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,
|
|
2889
3827
|
);
|
|
2890
|
-
return tupleOf([r.pp, r.r1, r.s1, r.r2, r.s2]);
|
|
2891
3828
|
}
|
|
2892
|
-
|
|
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
|
+
}
|
|
3869
|
+
if (fname === "ta.pivot_point_levels") {
|
|
3870
|
+
if (typeof ta.pivotPoints !== "function") return NA;
|
|
3871
|
+
const r = (ta.pivotPoints as TaEngine["pivotPoints"]).call(
|
|
3872
|
+
env.ta,
|
|
3873
|
+
site,
|
|
3874
|
+
num(env.ctx.high),
|
|
3875
|
+
num(env.ctx.low),
|
|
3876
|
+
num(env.ctx.close),
|
|
3877
|
+
);
|
|
3878
|
+
return tupleOf([r.pp, r.r1, r.s1, r.r2, r.s2]);
|
|
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
|
+
}
|
|
3954
|
+
// wave10a-start
|
|
3955
|
+
const wave10aArg = (index: number, names: string[], message: string): expr => {
|
|
3956
|
+
const arg = callArg(node.args, index, names);
|
|
3957
|
+
if (arg == null) runtimeError(message);
|
|
3958
|
+
return arg;
|
|
3959
|
+
};
|
|
3960
|
+
if (fname === "ta.hammer" || fname === "hammer") {
|
|
3961
|
+
const msg = "ta.hammer() requires 4 arguments: open, high, low, close";
|
|
3962
|
+
return env.ta.hammer(
|
|
3963
|
+
unwrap(evalExpr(wave10aArg(0, ["open"], msg), env)),
|
|
3964
|
+
unwrap(evalExpr(wave10aArg(1, ["high"], msg), env)),
|
|
3965
|
+
unwrap(evalExpr(wave10aArg(2, ["low"], msg), env)),
|
|
3966
|
+
unwrap(evalExpr(wave10aArg(3, ["close"], msg), env)),
|
|
3967
|
+
);
|
|
3968
|
+
}
|
|
3969
|
+
if (fname === "ta.engulfing" || fname === "engulfing") {
|
|
3970
|
+
const msg = "ta.engulfing() requires 4 arguments: open, high, low, close";
|
|
3971
|
+
unwrap(evalExpr(wave10aArg(0, ["open"], msg), env));
|
|
3972
|
+
unwrap(evalExpr(wave10aArg(1, ["high"], msg), env));
|
|
3973
|
+
unwrap(evalExpr(wave10aArg(2, ["low"], msg), env));
|
|
3974
|
+
unwrap(evalExpr(wave10aArg(3, ["close"], msg), env));
|
|
3975
|
+
return env.ta.engulfing();
|
|
3976
|
+
}
|
|
3977
|
+
if (fname === "ta.gap_detector" || fname === "gap_detector") {
|
|
3978
|
+
const msg = "ta.gap_detector() requires 3 arguments: high, low, prev_close";
|
|
3979
|
+
return env.ta.gapDetector(
|
|
3980
|
+
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env)),
|
|
3981
|
+
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env)),
|
|
3982
|
+
unwrap(evalExpr(wave10aArg(2, ["prev_close", "prev"], msg), env)),
|
|
3983
|
+
);
|
|
3984
|
+
}
|
|
3985
|
+
if (fname === "ta.double_top_bottom" || fname === "double_top_bottom") {
|
|
3986
|
+
const msg = "ta.double_top_bottom() requires 3 arguments";
|
|
3987
|
+
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
|
|
3988
|
+
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
|
|
3989
|
+
return env.ta.doubleTopBottom(unwrap(evalExpr(wave10aArg(2, ["period", "length"], msg), env)));
|
|
3990
|
+
}
|
|
3991
|
+
if (fname === "ta.inside_bar_pattern" || fname === "inside_bar_pattern") {
|
|
3992
|
+
const msg = "ta.inside_bar_pattern() requires 2 arguments";
|
|
3993
|
+
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
|
|
3994
|
+
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
|
|
3995
|
+
return env.ta.insideBarPattern();
|
|
3996
|
+
}
|
|
3997
|
+
if (fname === "ta.macd_signal" || fname === "macd_signal") {
|
|
3998
|
+
const msg = "ta.macd_signal() requires 2 arguments: macd_line, signal_line";
|
|
3999
|
+
return env.ta.macdSignal(
|
|
4000
|
+
unwrap(evalExpr(wave10aArg(0, ["macd_line", "macd"], msg), env)),
|
|
4001
|
+
unwrap(evalExpr(wave10aArg(1, ["signal_line", "signal"], msg), env)),
|
|
4002
|
+
);
|
|
4003
|
+
}
|
|
4004
|
+
if (fname === "ta.acceleration_factor" || fname === "acceleration_factor") {
|
|
4005
|
+
const msg = "ta.acceleration_factor() requires 2 arguments";
|
|
4006
|
+
return env.ta.accelerationFactor(
|
|
4007
|
+
site,
|
|
4008
|
+
unwrap(evalExpr(wave10aArg(0, ["momentum_list", "momentum", "source", "series"], msg), env)),
|
|
4009
|
+
unwrap(evalExpr(wave10aArg(1, ["period", "length"], msg), env)),
|
|
4010
|
+
);
|
|
4011
|
+
}
|
|
4012
|
+
if (fname === "ta.stoch_smooth" || fname === "stoch_smooth") {
|
|
4013
|
+
const msg = "ta.stoch_smooth() requires 6 arguments: high, low, close, period, smooth_k, smooth_d";
|
|
4014
|
+
return env.ta.stochSmooth(
|
|
4015
|
+
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env)),
|
|
4016
|
+
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env)),
|
|
4017
|
+
unwrap(evalExpr(wave10aArg(2, ["close"], msg), env)),
|
|
4018
|
+
unwrap(evalExpr(wave10aArg(3, ["period", "length"], msg), env)),
|
|
4019
|
+
unwrap(evalExpr(wave10aArg(4, ["smooth_k", "k"], msg), env)),
|
|
4020
|
+
unwrap(evalExpr(wave10aArg(5, ["smooth_d", "d"], msg), env)),
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
// wave10a-end
|
|
4024
|
+
// wave10b-start
|
|
4025
|
+
const wave10bList = (index: number, names: string[]): Array<number | null> => {
|
|
4026
|
+
const raw = evalExpr(callArg(node.args, index, names), env);
|
|
4027
|
+
const arr = asArray(raw);
|
|
4028
|
+
if (arr == null) return [unwrap(raw)];
|
|
4029
|
+
const out: Array<number | null> = [];
|
|
4030
|
+
for (let i = 0; i < arr.size(); i++) out.push(arr.get(i));
|
|
4031
|
+
return out;
|
|
4032
|
+
};
|
|
4033
|
+
if (fname === "ta.voi" || fname === "voi") {
|
|
4034
|
+
return env.ta.voi(
|
|
4035
|
+
unwrap(evalExpr(callArg(node.args, 0, ["buy_volume", "buy"]), env)),
|
|
4036
|
+
unwrap(evalExpr(callArg(node.args, 1, ["sell_volume", "sell"]), env)),
|
|
4037
|
+
);
|
|
4038
|
+
}
|
|
4039
|
+
if (fname === "ta.bid_ask_imbalance" || fname === "bid_ask_imbalance") {
|
|
4040
|
+
return env.ta.bidAskImbalance(
|
|
4041
|
+
unwrap(evalExpr(callArg(node.args, 0, ["bid_size", "bid"]), env)),
|
|
4042
|
+
unwrap(evalExpr(callArg(node.args, 1, ["ask_size", "ask"]), env)),
|
|
4043
|
+
unwrap(evalExpr(callArg(node.args, 2, ["bid_price"]), env)),
|
|
4044
|
+
unwrap(evalExpr(callArg(node.args, 3, ["ask_price"]), env)),
|
|
4045
|
+
);
|
|
4046
|
+
}
|
|
4047
|
+
if (fname === "ta.rsi_oversold_overbought" || fname === "rsi_oversold_overbought") {
|
|
4048
|
+
const os = unwrap(evalExpr(callArg(node.args, 1, ["oversold_level", "oversold"]), env));
|
|
4049
|
+
const ob = unwrap(evalExpr(callArg(node.args, 2, ["overbought_level", "overbought"]), env));
|
|
4050
|
+
return env.ta.rsiOversoldOverbought(
|
|
4051
|
+
unwrap(evalExpr(callArg(node.args, 0, ["rsi_series", "rsi", "source"]), env)),
|
|
4052
|
+
os ?? Number.NaN,
|
|
4053
|
+
ob ?? Number.NaN,
|
|
4054
|
+
);
|
|
4055
|
+
}
|
|
4056
|
+
if (fname === "ta.rsi_divergence" || fname === "rsi_divergence") {
|
|
4057
|
+
const period = unwrap(evalExpr(callArg(node.args, 1, ["period", "length"]), env));
|
|
4058
|
+
return env.ta.rsiDivergence(wave10bList(0, ["rsi_series", "series", "source"]), period ?? 0);
|
|
4059
|
+
}
|
|
4060
|
+
if (fname === "ta.cumulative_delta" || fname === "cumulative_delta") {
|
|
4061
|
+
const period = unwrap(evalExpr(callArg(node.args, 2, ["period", "length"]), env));
|
|
4062
|
+
return env.ta.cumulativeDelta(
|
|
4063
|
+
site,
|
|
4064
|
+
unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
|
|
4065
|
+
unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env)),
|
|
4066
|
+
period ?? 0,
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
4069
|
+
if (fname === "ta.volume_momentum" || fname === "volume_momentum") {
|
|
4070
|
+
const period = unwrap(evalExpr(callArg(node.args, 1, ["period", "length"]), env));
|
|
4071
|
+
return env.ta.volumeMomentum(
|
|
4072
|
+
site,
|
|
4073
|
+
unwrap(evalExpr(callArg(node.args, 0, ["volume", "source"]), env)),
|
|
4074
|
+
period ?? 0,
|
|
4075
|
+
);
|
|
4076
|
+
}
|
|
4077
|
+
if (fname === "ta.volume_profile_high" || fname === "volume_profile_high") {
|
|
4078
|
+
return env.ta.volumeProfileHigh(
|
|
4079
|
+
wave10bList(0, ["close", "price", "source", "series"]),
|
|
4080
|
+
wave10bList(1, ["volume"]),
|
|
4081
|
+
);
|
|
4082
|
+
}
|
|
4083
|
+
if (fname === "ta.volume_profile_low" || fname === "volume_profile_low") {
|
|
4084
|
+
const period = unwrap(evalExpr(callArg(node.args, 2, ["period", "length"]), env));
|
|
4085
|
+
const levels = unwrap(evalExpr(callArg(node.args, 3, ["levels"]), env));
|
|
4086
|
+
return env.ta.volumeProfileLow(
|
|
4087
|
+
site,
|
|
4088
|
+
unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
|
|
4089
|
+
unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env)),
|
|
4090
|
+
period ?? 0,
|
|
4091
|
+
levels ?? 0,
|
|
4092
|
+
);
|
|
4093
|
+
}
|
|
4094
|
+
if (fname === "ta.volume_thrust" || fname === "volume_thrust") {
|
|
4095
|
+
const sens = unwrap(evalExpr(callArg(node.args, 3, ["sensitivity"]), env));
|
|
4096
|
+
return env.ta.volumeThrust(
|
|
4097
|
+
site,
|
|
4098
|
+
unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
|
|
4099
|
+
unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env)),
|
|
4100
|
+
unwrap(evalExpr(callArg(node.args, 2, ["volume_sma", "vol_sma", "sma"]), env)),
|
|
4101
|
+
sens ?? 0.3,
|
|
4102
|
+
);
|
|
4103
|
+
}
|
|
4104
|
+
// wave10b-end
|
|
4105
|
+
return undefined;
|
|
2893
4106
|
}
|
|
2894
4107
|
|
|
2895
4108
|
function hlc3(env: Env): Cell {
|
|
@@ -2905,37 +4118,74 @@ function asArray(value: Value): PineArray | null {
|
|
|
2905
4118
|
if (value && typeof value === "object" && "__array" in value) {
|
|
2906
4119
|
const elts = (value as ArrayVal).elts;
|
|
2907
4120
|
const arr = new PineArray();
|
|
2908
|
-
for (const e of elts) arr.push(
|
|
4121
|
+
for (const e of elts) arr.push(storeValue(e));
|
|
2909
4122
|
return arr;
|
|
2910
4123
|
}
|
|
2911
4124
|
return null;
|
|
2912
4125
|
}
|
|
2913
4126
|
|
|
4127
|
+
/** Every `array.new*` alias Python registers in `_array_builtin_map`. */
|
|
4128
|
+
const ARRAY_NEW_FNS = new Set([
|
|
4129
|
+
"array.new",
|
|
4130
|
+
"array.new_bool",
|
|
4131
|
+
"array.new_int",
|
|
4132
|
+
"array.new_float",
|
|
4133
|
+
"array.new_string",
|
|
4134
|
+
"array.new_color",
|
|
4135
|
+
"array.newbool",
|
|
4136
|
+
"array.newint",
|
|
4137
|
+
"array.newfloat",
|
|
4138
|
+
"array.newstring",
|
|
4139
|
+
"array.newcolor",
|
|
4140
|
+
"array.newlabel",
|
|
4141
|
+
"array.newline",
|
|
4142
|
+
"array.newbox",
|
|
4143
|
+
"array.newtable",
|
|
4144
|
+
"array.newpolyline",
|
|
4145
|
+
"array.newlinefill",
|
|
4146
|
+
"array.new_label",
|
|
4147
|
+
"array.new_line",
|
|
4148
|
+
"array.new_box",
|
|
4149
|
+
"array.new_table",
|
|
4150
|
+
"array.new_polyline",
|
|
4151
|
+
"array.new_linefill",
|
|
4152
|
+
"array.new_chart.point",
|
|
4153
|
+
]);
|
|
4154
|
+
|
|
4155
|
+
function isArrayNew(fname: string | null): boolean {
|
|
4156
|
+
const name = arrayBuiltin(fname);
|
|
4157
|
+
return name != null && ARRAY_NEW_FNS.has(name);
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
function arrayBuiltin(fname: string | null): string | null {
|
|
4161
|
+
if (fname == null || !fname.startsWith("array.")) return null;
|
|
4162
|
+
const lt = fname.indexOf("<");
|
|
4163
|
+
return lt >= 0 ? fname.slice(0, lt) : fname;
|
|
4164
|
+
}
|
|
4165
|
+
|
|
2914
4166
|
function evalArrayCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
fname === "array.new" ||
|
|
2918
|
-
fname === "array.new_int"
|
|
2919
|
-
) {
|
|
4167
|
+
const aname = arrayBuiltin(fname);
|
|
4168
|
+
if (isArrayNew(fname)) {
|
|
2920
4169
|
const size = unwrap(evalExpr(callArg(node.args, 0, ["size"]), env)) ?? 0;
|
|
2921
|
-
const
|
|
4170
|
+
const initialArg = callArg(node.args, 1, ["initial_value"]);
|
|
4171
|
+
const initial = initialArg == null ? undefined : storeValue(evalExpr(initialArg, env));
|
|
2922
4172
|
return new PineArray(size, initial);
|
|
2923
4173
|
}
|
|
2924
|
-
if (
|
|
4174
|
+
if (aname === "array.push") {
|
|
2925
4175
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2926
|
-
const v =
|
|
4176
|
+
const v = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
2927
4177
|
arr?.push(v);
|
|
2928
4178
|
return NA;
|
|
2929
4179
|
}
|
|
2930
|
-
if (
|
|
4180
|
+
if (aname === "array.get") {
|
|
2931
4181
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2932
4182
|
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2933
|
-
return arr ? arr.get(i ?? 0) : NA;
|
|
4183
|
+
return arr ? asArrayValue(arr.get(i ?? 0)) : NA;
|
|
2934
4184
|
}
|
|
2935
|
-
if (
|
|
4185
|
+
if (aname === "array.set") {
|
|
2936
4186
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2937
4187
|
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2938
|
-
const v =
|
|
4188
|
+
const v = storeValue(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
2939
4189
|
if (arr && i != null) arr.set(i, v);
|
|
2940
4190
|
return NA;
|
|
2941
4191
|
}
|
|
@@ -2945,7 +4195,7 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
2945
4195
|
}
|
|
2946
4196
|
if (fname === "array.pop") {
|
|
2947
4197
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2948
|
-
return arr ? arr.pop() : NA;
|
|
4198
|
+
return arr ? asArrayValue(arr.pop()) : NA;
|
|
2949
4199
|
}
|
|
2950
4200
|
if (fname === "array.clear") {
|
|
2951
4201
|
asArray(evalExpr(callArg(node.args, 0, ["id"]), env))?.clear();
|
|
@@ -2953,40 +4203,40 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
2953
4203
|
}
|
|
2954
4204
|
if (fname === "array.unshift") {
|
|
2955
4205
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2956
|
-
arr?.unshift(
|
|
4206
|
+
arr?.unshift(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env)));
|
|
2957
4207
|
return NA;
|
|
2958
4208
|
}
|
|
2959
4209
|
if (fname === "array.shift") {
|
|
2960
4210
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2961
|
-
return arr ? arr.shift() : NA;
|
|
4211
|
+
return arr ? asArrayValue(arr.shift()) : NA;
|
|
2962
4212
|
}
|
|
2963
4213
|
if (fname === "array.includes") {
|
|
2964
4214
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2965
|
-
return arr?.includes(
|
|
4215
|
+
return arr?.includes(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env))) ? 1 : 0;
|
|
2966
4216
|
}
|
|
2967
4217
|
if (fname === "array.first") {
|
|
2968
4218
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2969
|
-
return arr ? arr.first() : NA;
|
|
4219
|
+
return arr ? asArrayValue(arr.first()) : NA;
|
|
2970
4220
|
}
|
|
2971
4221
|
if (fname === "array.last") {
|
|
2972
4222
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2973
|
-
return arr ? arr.last() : NA;
|
|
4223
|
+
return arr ? asArrayValue(arr.last()) : NA;
|
|
2974
4224
|
}
|
|
2975
4225
|
if (fname === "array.insert") {
|
|
2976
4226
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2977
4227
|
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2978
|
-
const v =
|
|
4228
|
+
const v = storeValue(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
2979
4229
|
if (arr && i != null) arr.insert(i, v);
|
|
2980
4230
|
return NA;
|
|
2981
4231
|
}
|
|
2982
4232
|
if (fname === "array.remove") {
|
|
2983
4233
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2984
4234
|
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2985
|
-
return arr && i != null ? arr.remove(i) : NA;
|
|
4235
|
+
return arr && i != null ? asArrayValue(arr.remove(i)) : NA;
|
|
2986
4236
|
}
|
|
2987
4237
|
if (fname === "array.fill") {
|
|
2988
4238
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2989
|
-
arr?.fill(
|
|
4239
|
+
arr?.fill(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env)));
|
|
2990
4240
|
return NA;
|
|
2991
4241
|
}
|
|
2992
4242
|
if (fname === "array.slice") {
|
|
@@ -3006,14 +4256,13 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
3006
4256
|
}
|
|
3007
4257
|
if (fname === "array.sort") {
|
|
3008
4258
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3009
|
-
const
|
|
3010
|
-
|
|
3011
|
-
arr?.sort(order);
|
|
4259
|
+
const parsed = parseSortArgs(node, env);
|
|
4260
|
+
arr?.sort(parsed.order, parsed.sortField);
|
|
3012
4261
|
return NA;
|
|
3013
4262
|
}
|
|
3014
4263
|
if (fname === "array.indexof") {
|
|
3015
4264
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3016
|
-
return arr ? arr.indexof(
|
|
4265
|
+
return arr ? arr.indexof(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
3017
4266
|
}
|
|
3018
4267
|
if (fname === "array.avg") {
|
|
3019
4268
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
@@ -3036,19 +4285,14 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
3036
4285
|
const sep = evalAsString(callArg(node.args, 1, ["separator", "sep"]), env);
|
|
3037
4286
|
return arr ? arr.join(sep ?? ",") : NA;
|
|
3038
4287
|
}
|
|
3039
|
-
if (fname === "array.new_bool" || fname === "array.new_string") {
|
|
3040
|
-
const size = unwrap(evalExpr(callArg(node.args, 0, ["size"]), env)) ?? 0;
|
|
3041
|
-
const initial = unwrap(evalExpr(callArg(node.args, 1, ["initial_value"]), env));
|
|
3042
|
-
return new PineArray(size, initial);
|
|
3043
|
-
}
|
|
3044
4288
|
if (fname === "array.from") {
|
|
3045
4289
|
const arr = new PineArray();
|
|
3046
|
-
for (const a of asArgs(node.args)) arr.push(
|
|
4290
|
+
for (const a of asArgs(node.args)) arr.push(storeValue(evalExpr(a.value, env)));
|
|
3047
4291
|
return arr;
|
|
3048
4292
|
}
|
|
3049
4293
|
if (fname === "array.lastindexof") {
|
|
3050
4294
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3051
|
-
return arr ? arr.lastIndexOf(
|
|
4295
|
+
return arr ? arr.lastIndexOf(storeValue(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
3052
4296
|
}
|
|
3053
4297
|
if (fname === "array.concat") {
|
|
3054
4298
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
@@ -3081,15 +4325,18 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
3081
4325
|
}
|
|
3082
4326
|
if (fname === "array.binary_search") {
|
|
3083
4327
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3084
|
-
|
|
4328
|
+
const value = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
4329
|
+
return arr ? arr.binarySearch(value, evalSortField(node, env)) : NA;
|
|
3085
4330
|
}
|
|
3086
4331
|
if (fname === "array.binary_search_leftmost") {
|
|
3087
4332
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3088
|
-
|
|
4333
|
+
const value = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
4334
|
+
return arr ? arr.binarySearchLeftmost(value, evalSortField(node, env)) : NA;
|
|
3089
4335
|
}
|
|
3090
4336
|
if (fname === "array.binary_search_rightmost") {
|
|
3091
4337
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3092
|
-
|
|
4338
|
+
const value = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
4339
|
+
return arr ? arr.binarySearchRightmost(value, evalSortField(node, env)) : NA;
|
|
3093
4340
|
}
|
|
3094
4341
|
if (fname === "array.stdev") {
|
|
3095
4342
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
@@ -3125,10 +4372,9 @@ function evalArrayCall(fname: string | null, node: Call, env: Env): Value | unde
|
|
|
3125
4372
|
}
|
|
3126
4373
|
if (fname === "array.sort_indices") {
|
|
3127
4374
|
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3128
|
-
const
|
|
3129
|
-
const order = typeof orderRaw === "string" && orderRaw.toLowerCase().includes("desc") ? "desc" : "asc";
|
|
4375
|
+
const parsed = parseSortArgs(node, env);
|
|
3130
4376
|
if (!arr) return NA;
|
|
3131
|
-
const idx = arr.sortIndices(order);
|
|
4377
|
+
const idx = arr.sortIndices(parsed.order, parsed.sortField);
|
|
3132
4378
|
if (idx == null) return NA;
|
|
3133
4379
|
const out = new PineArray();
|
|
3134
4380
|
for (const i of idx) out.push(i);
|
|
@@ -3230,20 +4476,20 @@ function evalMatrixCall(fname: string | null, node: Call, env: Env): Value | und
|
|
|
3230
4476
|
const rows = unwrap(evalExpr(callArg(node.args, 0, ["rows"]), env)) ?? 0;
|
|
3231
4477
|
const cols = unwrap(evalExpr(callArg(node.args, 1, ["columns", "cols"]), env)) ?? 0;
|
|
3232
4478
|
const initialArg = callArg(node.args, 2, ["initial_value", "initial"]);
|
|
3233
|
-
const initial = initialArg == null ? undefined :
|
|
4479
|
+
const initial = initialArg == null ? undefined : storeValue(evalExpr(initialArg, env));
|
|
3234
4480
|
return new PineMatrix(rows, cols, initial);
|
|
3235
4481
|
}
|
|
3236
4482
|
if (name === "matrix.get") {
|
|
3237
4483
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3238
4484
|
const row = unwrap(evalExpr(callArg(node.args, 1, ["row"]), env));
|
|
3239
4485
|
const col = unwrap(evalExpr(callArg(node.args, 2, ["column", "col"]), env));
|
|
3240
|
-
return m ? m.get(row ?? 0, col ?? 0) : NA;
|
|
4486
|
+
return m ? asArrayValue(m.get(row ?? 0, col ?? 0)) : NA;
|
|
3241
4487
|
}
|
|
3242
4488
|
if (name === "matrix.set") {
|
|
3243
4489
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3244
4490
|
const row = unwrap(evalExpr(callArg(node.args, 1, ["row"]), env));
|
|
3245
4491
|
const col = unwrap(evalExpr(callArg(node.args, 2, ["column", "col"]), env));
|
|
3246
|
-
const v =
|
|
4492
|
+
const v = storeValue(evalExpr(callArg(node.args, 3, ["value"]), env));
|
|
3247
4493
|
if (m && row != null && col != null) m.set(row, col, v);
|
|
3248
4494
|
return NA;
|
|
3249
4495
|
}
|
|
@@ -3257,7 +4503,7 @@ function evalMatrixCall(fname: string | null, node: Call, env: Env): Value | und
|
|
|
3257
4503
|
}
|
|
3258
4504
|
if (name === "matrix.fill") {
|
|
3259
4505
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3260
|
-
const v =
|
|
4506
|
+
const v = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
3261
4507
|
m?.fill(v);
|
|
3262
4508
|
return NA;
|
|
3263
4509
|
}
|
|
@@ -3456,14 +4702,115 @@ function evalMatrixCall(fname: string | null, node: Call, env: Env): Value | und
|
|
|
3456
4702
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3457
4703
|
const col = unwrap(evalExpr(callArg(node.args, 1, ["column", "col"]), env)) ?? 0;
|
|
3458
4704
|
const orderRaw = evalExpr(callArg(node.args, 2, ["order"]), env);
|
|
3459
|
-
const order =
|
|
3460
|
-
|
|
4705
|
+
const order = isDescendingOrder(orderRaw) ? "desc" : "asc";
|
|
4706
|
+
const sortField = sortFieldValue(evalExpr(callArg(node.args, 3, ["sort_field"]), env));
|
|
4707
|
+
m?.sort(col, order, sortField);
|
|
3461
4708
|
return NA;
|
|
3462
4709
|
}
|
|
4710
|
+
if (name === "matrix.sort_indices") {
|
|
4711
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
4712
|
+
if (!m) return NA;
|
|
4713
|
+
const col = unwrap(evalExpr(callArg(node.args, 1, ["column", "col"]), env)) ?? 0;
|
|
4714
|
+
const orderRaw = evalExpr(callArg(node.args, 2, ["order"]), env);
|
|
4715
|
+
const order = isDescendingOrder(orderRaw) ? "desc" : "asc";
|
|
4716
|
+
const sortField = sortFieldValue(evalExpr(callArg(node.args, 3, ["sort_field"]), env));
|
|
4717
|
+
const idx = m.sortIndices(col, order, sortField);
|
|
4718
|
+
const out = new PineArray();
|
|
4719
|
+
for (const i of idx) out.push(i);
|
|
4720
|
+
return out;
|
|
4721
|
+
}
|
|
3463
4722
|
if (name === "matrix.median") {
|
|
3464
4723
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3465
4724
|
return m ? m.median() : NA;
|
|
3466
4725
|
}
|
|
4726
|
+
if (
|
|
4727
|
+
name === "matrix.sum_row" ||
|
|
4728
|
+
name === "matrix.avg_row" ||
|
|
4729
|
+
name === "matrix.min_row" ||
|
|
4730
|
+
name === "matrix.max_row" ||
|
|
4731
|
+
name === "matrix.mode_row"
|
|
4732
|
+
) {
|
|
4733
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4734
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["row", "index"]), env));
|
|
4735
|
+
if (!m || i == null) return NA;
|
|
4736
|
+
if (name === "matrix.sum_row") return m.sumRow(i);
|
|
4737
|
+
if (name === "matrix.avg_row") return m.avgRow(i);
|
|
4738
|
+
if (name === "matrix.min_row") return m.minRow(i);
|
|
4739
|
+
if (name === "matrix.max_row") return m.maxRow(i);
|
|
4740
|
+
return m.modeRow(i) as Value;
|
|
4741
|
+
}
|
|
4742
|
+
if (
|
|
4743
|
+
name === "matrix.sum_col" ||
|
|
4744
|
+
name === "matrix.avg_col" ||
|
|
4745
|
+
name === "matrix.min_col" ||
|
|
4746
|
+
name === "matrix.max_col" ||
|
|
4747
|
+
name === "matrix.mode_col"
|
|
4748
|
+
) {
|
|
4749
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4750
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["column", "col", "index"]), env));
|
|
4751
|
+
if (!m || i == null) return NA;
|
|
4752
|
+
if (name === "matrix.sum_col") return m.sumCol(i);
|
|
4753
|
+
if (name === "matrix.avg_col") return m.avgCol(i);
|
|
4754
|
+
if (name === "matrix.min_col") return m.minCol(i);
|
|
4755
|
+
if (name === "matrix.max_col") return m.maxCol(i);
|
|
4756
|
+
return m.modeCol(i) as Value;
|
|
4757
|
+
}
|
|
4758
|
+
if (name === "matrix.copy_row") {
|
|
4759
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4760
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["row", "index"]), env));
|
|
4761
|
+
if (!m || i == null) return NA;
|
|
4762
|
+
const arr = new PineArray();
|
|
4763
|
+
for (const v of m.copyRow(i)) arr.push(v);
|
|
4764
|
+
return arr;
|
|
4765
|
+
}
|
|
4766
|
+
if (name === "matrix.copy_col") {
|
|
4767
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4768
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["column", "col", "index"]), env));
|
|
4769
|
+
if (!m || i == null) return NA;
|
|
4770
|
+
const arr = new PineArray();
|
|
4771
|
+
for (const v of m.copyCol(i)) arr.push(v);
|
|
4772
|
+
return arr;
|
|
4773
|
+
}
|
|
4774
|
+
if (name === "matrix.fill_row") {
|
|
4775
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4776
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["row", "index"]), env));
|
|
4777
|
+
const v = storeValue(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
4778
|
+
if (m && i != null) m.fillRow(i, v);
|
|
4779
|
+
return NA;
|
|
4780
|
+
}
|
|
4781
|
+
if (name === "matrix.fill_col") {
|
|
4782
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4783
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["column", "col", "index"]), env));
|
|
4784
|
+
const v = storeValue(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
4785
|
+
if (m && i != null) m.fillCol(i, v);
|
|
4786
|
+
return NA;
|
|
4787
|
+
}
|
|
4788
|
+
if (name === "matrix.fill_diagonal") {
|
|
4789
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4790
|
+
const v = storeValue(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
4791
|
+
m?.fillDiagonal(v);
|
|
4792
|
+
return NA;
|
|
4793
|
+
}
|
|
4794
|
+
if (name === "matrix.reverse_rows") {
|
|
4795
|
+
asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env))?.reverseRows();
|
|
4796
|
+
return NA;
|
|
4797
|
+
}
|
|
4798
|
+
if (name === "matrix.reverse_cols") {
|
|
4799
|
+
asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env))?.reverseCols();
|
|
4800
|
+
return NA;
|
|
4801
|
+
}
|
|
4802
|
+
if (name === "matrix.stdev") {
|
|
4803
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4804
|
+
return m ? m.stdev() : NA;
|
|
4805
|
+
}
|
|
4806
|
+
if (name === "matrix.variance") {
|
|
4807
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4808
|
+
return m ? m.variance() : NA;
|
|
4809
|
+
}
|
|
4810
|
+
if (name === "matrix.mode_all") {
|
|
4811
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id", "matrix"]), env));
|
|
4812
|
+
return m ? (m.modeAll() as Value) : NA;
|
|
4813
|
+
}
|
|
3467
4814
|
if (name === "matrix.mode") {
|
|
3468
4815
|
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3469
4816
|
return m ? m.mode() : NA;
|
|
@@ -3483,125 +4830,808 @@ function evalMatrixCall(fname: string | null, node: Call, env: Env): Value | und
|
|
|
3483
4830
|
return undefined;
|
|
3484
4831
|
}
|
|
3485
4832
|
|
|
4833
|
+
function isChartPoint(value: Value): value is ChartPointVal {
|
|
4834
|
+
return typeof value === "object" && value !== null && (value as ChartPointVal).__chartPoint === true;
|
|
4835
|
+
}
|
|
4836
|
+
|
|
4837
|
+
function makeChartPoint(index: number | null, time: number | null, price: number | null): ChartPointVal {
|
|
4838
|
+
const x = index ?? time;
|
|
4839
|
+
return { __chartPoint: true, index, time, x, y: price, price };
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
function chartPointField(obj: ChartPointVal, attr: string): Value {
|
|
4843
|
+
if (attr === "__chartPoint" || !Object.prototype.hasOwnProperty.call(obj, attr)) return NA;
|
|
4844
|
+
const v = obj[attr as keyof ChartPointVal];
|
|
4845
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : NA;
|
|
4846
|
+
return NA;
|
|
4847
|
+
}
|
|
4848
|
+
|
|
4849
|
+
function drawingAllKind(name: string): DrawingKind | null {
|
|
4850
|
+
if (
|
|
4851
|
+
name === "line" ||
|
|
4852
|
+
name === "label" ||
|
|
4853
|
+
name === "box" ||
|
|
4854
|
+
name === "table" ||
|
|
4855
|
+
name === "polyline" ||
|
|
4856
|
+
name === "linefill"
|
|
4857
|
+
) {
|
|
4858
|
+
return name;
|
|
4859
|
+
}
|
|
4860
|
+
return null;
|
|
4861
|
+
}
|
|
4862
|
+
|
|
4863
|
+
function drawingAllArray(env: Env, kind: DrawingKind): PineArray {
|
|
4864
|
+
const arr = new PineArray();
|
|
4865
|
+
for (const id of env.drawings.allIds(kind)) arr.push(id);
|
|
4866
|
+
return arr;
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
function drawId(node: Call, env: Env): number | null {
|
|
4870
|
+
return unwrap(evalExpr(callArg(node.args, 0, ["id", "table_id", "table"]), env));
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
function drawNum(node: Call, env: Env, index: number, names: string[]): number | null {
|
|
4874
|
+
const e = callArg(node.args, index, names);
|
|
4875
|
+
return e == null ? null : unwrap(evalExpr(e, env));
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
function drawRaw(node: Call, env: Env, index: number, names: string[]): unknown {
|
|
4879
|
+
const e = callArg(node.args, index, names);
|
|
4880
|
+
if (e == null) return undefined;
|
|
4881
|
+
const v = evalExpr(e, env);
|
|
4882
|
+
if (typeof v === "string" || typeof v === "boolean" || isChartPoint(v) || v instanceof PineArray) return v;
|
|
4883
|
+
const n = unwrap(v);
|
|
4884
|
+
return n != null ? n : v;
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
function drawText(node: Call, env: Env, index: number, names: string[], fallback = ""): string {
|
|
4888
|
+
const e = callArg(node.args, index, names);
|
|
4889
|
+
if (e == null) return fallback;
|
|
4890
|
+
const s = evalAsString(e, env);
|
|
4891
|
+
if (s != null) return s;
|
|
4892
|
+
const v = evalExpr(e, env);
|
|
4893
|
+
if (typeof v === "string") return v;
|
|
4894
|
+
const n = unwrap(v);
|
|
4895
|
+
return n == null ? fallback : String(n);
|
|
4896
|
+
}
|
|
4897
|
+
|
|
4898
|
+
function putFinite(extra: Record<string, unknown>, key: string, value: number | null | undefined): void {
|
|
4899
|
+
if (value != null && Number.isFinite(value)) extra[key] = value;
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4902
|
+
function optionalCoord(node: Call, env: Env, index: number, names: string[], fallback: number): number | null {
|
|
4903
|
+
const e = callArg(node.args, index, names);
|
|
4904
|
+
if (e == null) return fallback;
|
|
4905
|
+
return unwrap(evalExpr(e, env));
|
|
4906
|
+
}
|
|
4907
|
+
|
|
4908
|
+
function pointXY(p: ChartPointVal): { x: number; y: number } {
|
|
4909
|
+
const x = p.index ?? p.time ?? p.x ?? 0;
|
|
4910
|
+
const y = p.price ?? p.y ?? 0;
|
|
4911
|
+
return { x: x ?? 0, y: y ?? 0 };
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4914
|
+
function pointsFromValue(value: Value): unknown[] {
|
|
4915
|
+
const arr = asArray(value);
|
|
4916
|
+
if (!arr) return [];
|
|
4917
|
+
return arr.toValues().filter((p) => p != null);
|
|
4918
|
+
}
|
|
4919
|
+
|
|
3486
4920
|
function evalDrawingCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
4921
|
+
if (fname == null) return undefined;
|
|
4922
|
+
const book = env.drawings;
|
|
4923
|
+
|
|
4924
|
+
if (fname === "line" || fname === "box" || fname === "label" || fname === "table" || fname === "polyline" || fname === "linefill") {
|
|
4925
|
+
const e = callArg(node.args, 0, ["id", "x"]);
|
|
4926
|
+
return e == null ? NA : evalExpr(e, env);
|
|
4927
|
+
}
|
|
4928
|
+
|
|
3487
4929
|
if (fname === "line.new") {
|
|
3488
|
-
|
|
4930
|
+
const extra: Record<string, unknown> = {};
|
|
4931
|
+
putFinite(extra, "x1", optionalCoord(node, env, 0, ["x1"], 0));
|
|
4932
|
+
putFinite(extra, "y1", optionalCoord(node, env, 1, ["y1"], 0));
|
|
4933
|
+
putFinite(extra, "x2", optionalCoord(node, env, 2, ["x2"], 0));
|
|
4934
|
+
putFinite(extra, "y2", optionalCoord(node, env, 3, ["y2"], 0));
|
|
4935
|
+
const xloc = drawRaw(node, env, 4, ["xloc"]);
|
|
4936
|
+
if (xloc !== undefined) extra.xloc = xloc;
|
|
4937
|
+
const color = drawRaw(node, env, 5, ["color"]);
|
|
4938
|
+
if (color !== undefined) extra.color = color;
|
|
4939
|
+
putFinite(extra, "width", drawNum(node, env, 6, ["width"]));
|
|
4940
|
+
const style = drawRaw(node, env, 7, ["style"]);
|
|
4941
|
+
if (style !== undefined) extra.style = style;
|
|
4942
|
+
const extend = drawRaw(node, env, 8, ["extend"]);
|
|
4943
|
+
if (extend !== undefined) extra.extend = extend;
|
|
4944
|
+
return book.lineNew(env.barIndex, extra);
|
|
3489
4945
|
}
|
|
3490
4946
|
if (fname === "label.new") {
|
|
3491
|
-
const
|
|
3492
|
-
|
|
4947
|
+
const pointKw = callArg(node.args, -1, ["point"]);
|
|
4948
|
+
const a0 = callArg(node.args, 0, ["x", "point"]);
|
|
4949
|
+
const v0 = pointKw != null ? evalExpr(pointKw, env) : a0 != null ? evalExpr(a0, env) : NA;
|
|
4950
|
+
const extra: Record<string, unknown> = {};
|
|
4951
|
+
let text: string;
|
|
4952
|
+
if (isChartPoint(v0)) {
|
|
4953
|
+
const xy = pointXY(v0);
|
|
4954
|
+
extra.x = xy.x;
|
|
4955
|
+
extra.y = xy.y;
|
|
4956
|
+
text = drawText(node, env, pointKw != null ? 0 : 1, ["text"]);
|
|
4957
|
+
} else {
|
|
4958
|
+
putFinite(extra, "x", a0 == null ? 0 : unwrap(v0));
|
|
4959
|
+
putFinite(extra, "y", optionalCoord(node, env, 1, ["y"], 0));
|
|
4960
|
+
text = drawText(node, env, 2, ["text"]);
|
|
4961
|
+
}
|
|
4962
|
+
const tooltip = drawRaw(node, env, 12, ["tooltip"]);
|
|
4963
|
+
if (tooltip !== undefined) extra.tooltip = tooltip;
|
|
4964
|
+
const style = drawRaw(node, env, 13, ["style"]);
|
|
4965
|
+
if (style !== undefined) extra.style = style;
|
|
4966
|
+
return book.labelNew(env.barIndex, text, extra);
|
|
3493
4967
|
}
|
|
3494
4968
|
if (fname === "box.new") {
|
|
3495
|
-
|
|
4969
|
+
const extra: Record<string, unknown> = {};
|
|
4970
|
+
putFinite(extra, "left", optionalCoord(node, env, 0, ["left"], 0));
|
|
4971
|
+
putFinite(extra, "top", optionalCoord(node, env, 1, ["top"], 0));
|
|
4972
|
+
putFinite(extra, "right", optionalCoord(node, env, 2, ["right"], 0));
|
|
4973
|
+
putFinite(extra, "bottom", optionalCoord(node, env, 3, ["bottom"], 0));
|
|
4974
|
+
const bgcolor = drawRaw(node, env, 6, ["bgcolor"]);
|
|
4975
|
+
if (bgcolor !== undefined) extra.bgcolor = bgcolor;
|
|
4976
|
+
const text = callArg(node.args, -1, ["text"]);
|
|
4977
|
+
if (text != null) extra.text = drawText(node, env, -1, ["text"]);
|
|
4978
|
+
return book.boxNew(env.barIndex, extra);
|
|
3496
4979
|
}
|
|
3497
4980
|
if (fname === "alert") {
|
|
3498
4981
|
const msg = evalAlertMessage(callArg(node.args, 0, ["message"]), env);
|
|
3499
|
-
|
|
4982
|
+
book.alert(env.barIndex, msg);
|
|
3500
4983
|
return NA;
|
|
3501
4984
|
}
|
|
3502
|
-
if (fname === "table.new")
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
4985
|
+
if (fname === "table.new") {
|
|
4986
|
+
const extra: Record<string, unknown> = {};
|
|
4987
|
+
const position = drawRaw(node, env, 0, ["position"]);
|
|
4988
|
+
if (position !== undefined) extra.position = position;
|
|
4989
|
+
putFinite(extra, "rows", drawNum(node, env, 1, ["rows"]));
|
|
4990
|
+
putFinite(extra, "columns", drawNum(node, env, 2, ["columns"]));
|
|
4991
|
+
return book.tableNew(env.barIndex, extra);
|
|
4992
|
+
}
|
|
4993
|
+
if (fname === "polyline.new") {
|
|
4994
|
+
const extra: Record<string, unknown> = {};
|
|
4995
|
+
const ptsExpr = callArg(node.args, 0, ["points"]);
|
|
4996
|
+
extra.points = ptsExpr == null ? [] : pointsFromValue(evalExpr(ptsExpr, env));
|
|
4997
|
+
const closed = drawRaw(node, env, 1, ["closed"]);
|
|
4998
|
+
if (closed !== undefined) extra.closed = closed;
|
|
4999
|
+
const color = drawRaw(node, env, 3, ["color"]);
|
|
5000
|
+
if (color !== undefined) extra.color = color;
|
|
5001
|
+
putFinite(extra, "width", drawNum(node, env, 4, ["width"]));
|
|
5002
|
+
return book.polylineNew(env.barIndex, extra);
|
|
5003
|
+
}
|
|
5004
|
+
if (fname === "linefill.new" || fname === "line.fill") {
|
|
5005
|
+
const a = unwrap(evalExpr(callArg(node.args, 0, ["id1", "line1"]), env));
|
|
5006
|
+
const b = unwrap(evalExpr(callArg(node.args, 1, ["id2", "line2"]), env));
|
|
5007
|
+
const extra: Record<string, unknown> = {};
|
|
5008
|
+
if (a != null) extra.id1 = a;
|
|
5009
|
+
if (b != null) extra.id2 = b;
|
|
5010
|
+
const color = drawRaw(node, env, 2, ["color"]);
|
|
5011
|
+
if (color !== undefined) extra.color = color;
|
|
5012
|
+
return book.linefillNew(env.barIndex, extra);
|
|
5013
|
+
}
|
|
5014
|
+
|
|
5015
|
+
if (fname.endsWith(".all")) {
|
|
5016
|
+
const kind = drawingAllKind(fname.slice(0, -4));
|
|
5017
|
+
if (kind != null) return drawingAllArray(env, kind);
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
if (fname === "chart.point.new" || fname === "chart.point.from_time") {
|
|
5021
|
+
const time = drawNum(node, env, 0, ["time"]);
|
|
5022
|
+
const priceExpr = callArg(node.args, 1, ["price"]);
|
|
5023
|
+
const price = priceExpr == null ? 0 : unwrap(evalExpr(priceExpr, env));
|
|
5024
|
+
if (price == null) return NA;
|
|
5025
|
+
return makeChartPoint(null, time, price);
|
|
5026
|
+
}
|
|
5027
|
+
if (fname === "chart.point.from_index") {
|
|
5028
|
+
const index = drawNum(node, env, 0, ["index"]);
|
|
5029
|
+
const priceExpr = callArg(node.args, 1, ["price"]);
|
|
5030
|
+
const price = priceExpr == null ? 0 : unwrap(evalExpr(priceExpr, env));
|
|
5031
|
+
if (price == null || index == null) return NA;
|
|
5032
|
+
return makeChartPoint(Math.trunc(index), null, price);
|
|
5033
|
+
}
|
|
5034
|
+
if (fname === "chart.point.now") {
|
|
5035
|
+
const priceExpr = callArg(node.args, 0, ["price"]);
|
|
5036
|
+
const price = priceExpr == null ? 0 : unwrap(evalExpr(priceExpr, env));
|
|
5037
|
+
if (price == null) return NA;
|
|
5038
|
+
const time = unwrap(env.ctx.time) ?? env.barTimes[env.barIndex] ?? null;
|
|
5039
|
+
return makeChartPoint(env.barIndex, time, price);
|
|
5040
|
+
}
|
|
5041
|
+
if (fname === "chart.point.copy") {
|
|
5042
|
+
const raw = evalExpr(callArg(node.args, 0, ["point"]), env);
|
|
5043
|
+
if (isChartPoint(raw)) return makeChartPoint(raw.index, raw.time, raw.price);
|
|
5044
|
+
return makeChartPoint(null, null, 0);
|
|
5045
|
+
}
|
|
5046
|
+
|
|
5047
|
+
if (fname === "line.copy" || fname === "label.copy" || fname === "box.copy" || fname === "polyline.copy") {
|
|
5048
|
+
const id = drawId(node, env);
|
|
5049
|
+
if (id == null) return NA;
|
|
5050
|
+
const copied = book.copy(id);
|
|
5051
|
+
return copied == null ? NA : copied;
|
|
5052
|
+
}
|
|
5053
|
+
|
|
5054
|
+
if (fname === "line.delete" || fname === "label.delete" || fname === "box.delete" || fname === "table.delete" || fname === "polyline.delete" || fname === "linefill.delete") {
|
|
5055
|
+
const id = drawId(node, env);
|
|
5056
|
+
if (id != null) {
|
|
5057
|
+
if (fname === "line.delete") book.lineDelete(id);
|
|
5058
|
+
else if (fname === "label.delete") book.labelDelete(id);
|
|
5059
|
+
else if (fname === "box.delete") book.boxDelete(id);
|
|
5060
|
+
else if (fname === "table.delete") book.tableDelete(id);
|
|
5061
|
+
else if (fname === "polyline.delete") book.polylineDelete(id);
|
|
5062
|
+
else book.linefillDelete(id);
|
|
5063
|
+
}
|
|
5064
|
+
return NA;
|
|
3512
5065
|
}
|
|
5066
|
+
|
|
5067
|
+
if (fname === "line.get_x1") {
|
|
5068
|
+
const id = drawId(node, env);
|
|
5069
|
+
return id != null ? book.lineGetX1(id) : NA;
|
|
5070
|
+
}
|
|
5071
|
+
if (fname === "line.get_y1") {
|
|
5072
|
+
const id = drawId(node, env);
|
|
5073
|
+
return id != null ? book.lineGetY1(id) : NA;
|
|
5074
|
+
}
|
|
5075
|
+
if (fname === "line.get_x2") {
|
|
5076
|
+
const id = drawId(node, env);
|
|
5077
|
+
return id != null ? book.lineGetX2(id) : NA;
|
|
5078
|
+
}
|
|
5079
|
+
if (fname === "line.get_y2") {
|
|
5080
|
+
const id = drawId(node, env);
|
|
5081
|
+
return id != null ? book.lineGetY2(id) : NA;
|
|
5082
|
+
}
|
|
5083
|
+
if (fname === "line.get_price") {
|
|
5084
|
+
const id = drawId(node, env);
|
|
5085
|
+
const x = drawNum(node, env, 1, ["x"]);
|
|
5086
|
+
return id != null && x != null ? book.lineGetPrice(id, x) : NA;
|
|
5087
|
+
}
|
|
5088
|
+
if (fname === "label.get_x") {
|
|
5089
|
+
const id = drawId(node, env);
|
|
5090
|
+
return id == null ? 0 : (book.labelGetX(id) ?? 0);
|
|
5091
|
+
}
|
|
5092
|
+
if (fname === "label.get_y") {
|
|
5093
|
+
const id = drawId(node, env);
|
|
5094
|
+
return id == null ? 0 : (book.labelGetY(id) ?? 0);
|
|
5095
|
+
}
|
|
5096
|
+
if (fname === "label.get_text") {
|
|
5097
|
+
const id = drawId(node, env);
|
|
5098
|
+
return id == null ? "" : (book.labelGetText(id) ?? "");
|
|
5099
|
+
}
|
|
5100
|
+
if (fname === "box.get_left") {
|
|
5101
|
+
const id = drawId(node, env);
|
|
5102
|
+
return id == null ? 0 : (book.boxGetLeft(id) ?? 0);
|
|
5103
|
+
}
|
|
5104
|
+
if (fname === "box.get_right") {
|
|
5105
|
+
const id = drawId(node, env);
|
|
5106
|
+
return id == null ? 0 : (book.boxGetRight(id) ?? 0);
|
|
5107
|
+
}
|
|
5108
|
+
if (fname === "box.get_top") {
|
|
5109
|
+
const id = drawId(node, env);
|
|
5110
|
+
return id == null ? 0 : (book.boxGetTop(id) ?? 0);
|
|
5111
|
+
}
|
|
5112
|
+
if (fname === "box.get_bottom") {
|
|
5113
|
+
const id = drawId(node, env);
|
|
5114
|
+
return id == null ? 0 : (book.boxGetBottom(id) ?? 0);
|
|
5115
|
+
}
|
|
5116
|
+
if (fname === "table.cell_get_text") {
|
|
5117
|
+
const id = drawId(node, env);
|
|
5118
|
+
const row = drawNum(node, env, 1, ["row"]);
|
|
5119
|
+
const col = drawNum(node, env, 2, ["column"]);
|
|
5120
|
+
if (id == null) return "";
|
|
5121
|
+
return book.tableCellGetText(id, col, row) ?? "";
|
|
5122
|
+
}
|
|
5123
|
+
if (fname === "linefill.get_line1") {
|
|
5124
|
+
const id = drawId(node, env);
|
|
5125
|
+
return id != null ? book.linefillGetLine1(id) : NA;
|
|
5126
|
+
}
|
|
5127
|
+
if (fname === "linefill.get_line2") {
|
|
5128
|
+
const id = drawId(node, env);
|
|
5129
|
+
return id != null ? book.linefillGetLine2(id) : NA;
|
|
5130
|
+
}
|
|
5131
|
+
if (fname === "polyline.get_points") {
|
|
5132
|
+
const id = drawId(node, env);
|
|
5133
|
+
const arr = new PineArray();
|
|
5134
|
+
if (id != null) for (const p of book.polylineGetPoints(id)) arr.push(p);
|
|
5135
|
+
return arr;
|
|
5136
|
+
}
|
|
5137
|
+
|
|
3513
5138
|
if (fname === "line.set_xy") {
|
|
3514
|
-
const id =
|
|
5139
|
+
const id = drawId(node, env);
|
|
3515
5140
|
if (id != null) {
|
|
3516
|
-
|
|
5141
|
+
book.lineSetXy(
|
|
3517
5142
|
id,
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
5143
|
+
drawNum(node, env, 1, ["x1"]) ?? 0,
|
|
5144
|
+
drawNum(node, env, 2, ["y1"]) ?? 0,
|
|
5145
|
+
drawNum(node, env, 3, ["x2"]) ?? 0,
|
|
5146
|
+
drawNum(node, env, 4, ["y2"]) ?? 0,
|
|
3522
5147
|
);
|
|
3523
5148
|
}
|
|
3524
5149
|
return NA;
|
|
3525
5150
|
}
|
|
3526
|
-
if (fname === "
|
|
3527
|
-
const id =
|
|
3528
|
-
if (id != null)
|
|
5151
|
+
if (fname === "line.set_xy1") {
|
|
5152
|
+
const id = drawId(node, env);
|
|
5153
|
+
if (id != null) book.lineSetXy1(id, drawNum(node, env, 1, ["x1", "x"]) ?? 0, drawNum(node, env, 2, ["y1", "y"]) ?? 0);
|
|
3529
5154
|
return NA;
|
|
3530
5155
|
}
|
|
3531
|
-
if (fname === "line.
|
|
3532
|
-
const id =
|
|
3533
|
-
if (id != null)
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
5156
|
+
if (fname === "line.set_xy2") {
|
|
5157
|
+
const id = drawId(node, env);
|
|
5158
|
+
if (id != null) book.lineSetXy2(id, drawNum(node, env, 1, ["x2", "x"]) ?? 0, drawNum(node, env, 2, ["y2", "y"]) ?? 0);
|
|
5159
|
+
return NA;
|
|
5160
|
+
}
|
|
5161
|
+
if (fname === "line.set_x1") {
|
|
5162
|
+
const id = drawId(node, env);
|
|
5163
|
+
if (id != null) book.lineSetX1(id, drawNum(node, env, 1, ["x1", "x"]));
|
|
5164
|
+
return NA;
|
|
5165
|
+
}
|
|
5166
|
+
if (fname === "line.set_y1") {
|
|
5167
|
+
const id = drawId(node, env);
|
|
5168
|
+
if (id != null) book.lineSetY1(id, drawNum(node, env, 1, ["y1", "y"]));
|
|
5169
|
+
return NA;
|
|
5170
|
+
}
|
|
5171
|
+
if (fname === "line.set_x2") {
|
|
5172
|
+
const id = drawId(node, env);
|
|
5173
|
+
if (id != null) book.lineSetX2(id, drawNum(node, env, 1, ["x2", "x"]));
|
|
5174
|
+
return NA;
|
|
5175
|
+
}
|
|
5176
|
+
if (fname === "line.set_y2") {
|
|
5177
|
+
const id = drawId(node, env);
|
|
5178
|
+
if (id != null) book.lineSetY2(id, drawNum(node, env, 1, ["y2", "y"]));
|
|
3538
5179
|
return NA;
|
|
3539
5180
|
}
|
|
3540
5181
|
if (fname === "line.set_color") {
|
|
3541
|
-
const id =
|
|
3542
|
-
if (id != null)
|
|
5182
|
+
const id = drawId(node, env);
|
|
5183
|
+
if (id != null) book.lineSetColor(id, drawRaw(node, env, 1, ["color"]));
|
|
3543
5184
|
return NA;
|
|
3544
5185
|
}
|
|
3545
|
-
if (fname === "line.
|
|
3546
|
-
const id =
|
|
3547
|
-
|
|
3548
|
-
return
|
|
5186
|
+
if (fname === "line.set_width") {
|
|
5187
|
+
const id = drawId(node, env);
|
|
5188
|
+
if (id != null) book.lineSetWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5189
|
+
return NA;
|
|
3549
5190
|
}
|
|
3550
|
-
if (fname === "line.
|
|
3551
|
-
const id =
|
|
3552
|
-
|
|
5191
|
+
if (fname === "line.set_style") {
|
|
5192
|
+
const id = drawId(node, env);
|
|
5193
|
+
if (id != null) book.lineSetStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5194
|
+
return NA;
|
|
3553
5195
|
}
|
|
3554
|
-
if (fname === "line.
|
|
3555
|
-
const id =
|
|
3556
|
-
|
|
5196
|
+
if (fname === "line.set_extend") {
|
|
5197
|
+
const id = drawId(node, env);
|
|
5198
|
+
if (id != null) book.lineSetExtend(id, drawRaw(node, env, 1, ["extend"]));
|
|
5199
|
+
return NA;
|
|
3557
5200
|
}
|
|
3558
|
-
if (fname === "line.
|
|
3559
|
-
const id =
|
|
3560
|
-
|
|
5201
|
+
if (fname === "line.set_xloc") {
|
|
5202
|
+
const id = drawId(node, env);
|
|
5203
|
+
if (id != null) book.lineSetXloc(id, drawRaw(node, env, 1, ["xloc"]));
|
|
5204
|
+
return NA;
|
|
3561
5205
|
}
|
|
3562
|
-
if (fname === "line.
|
|
3563
|
-
const id =
|
|
3564
|
-
|
|
5206
|
+
if (fname === "line.set_first_point") {
|
|
5207
|
+
const id = drawId(node, env);
|
|
5208
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5209
|
+
if (id != null && isChartPoint(p)) {
|
|
5210
|
+
const xy = pointXY(p);
|
|
5211
|
+
book.lineSetXy1(id, xy.x, xy.y);
|
|
5212
|
+
}
|
|
5213
|
+
return NA;
|
|
3565
5214
|
}
|
|
3566
|
-
if (fname === "
|
|
3567
|
-
const id =
|
|
3568
|
-
|
|
5215
|
+
if (fname === "line.set_second_point") {
|
|
5216
|
+
const id = drawId(node, env);
|
|
5217
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5218
|
+
if (id != null && isChartPoint(p)) {
|
|
5219
|
+
const xy = pointXY(p);
|
|
5220
|
+
book.lineSetXy2(id, xy.x, xy.y);
|
|
5221
|
+
}
|
|
5222
|
+
return NA;
|
|
5223
|
+
}
|
|
5224
|
+
|
|
5225
|
+
if (fname === "label.set_text") {
|
|
5226
|
+
const id = drawId(node, env);
|
|
5227
|
+
if (id != null) book.labelSetText(id, drawText(node, env, 1, ["text"]));
|
|
5228
|
+
return NA;
|
|
3569
5229
|
}
|
|
3570
5230
|
if (fname === "label.set_xy") {
|
|
3571
|
-
const id =
|
|
5231
|
+
const id = drawId(node, env);
|
|
5232
|
+
if (id != null) book.labelSetXy(id, drawNum(node, env, 1, ["x"]) ?? 0, drawNum(node, env, 2, ["y"]) ?? 0);
|
|
5233
|
+
return NA;
|
|
5234
|
+
}
|
|
5235
|
+
if (fname === "label.set_x") {
|
|
5236
|
+
const id = drawId(node, env);
|
|
5237
|
+
if (id != null) book.labelSetX(id, drawNum(node, env, 1, ["x"]));
|
|
5238
|
+
return NA;
|
|
5239
|
+
}
|
|
5240
|
+
if (fname === "label.set_y") {
|
|
5241
|
+
const id = drawId(node, env);
|
|
5242
|
+
if (id != null) book.labelSetY(id, drawNum(node, env, 1, ["y"]));
|
|
5243
|
+
return NA;
|
|
5244
|
+
}
|
|
5245
|
+
if (fname === "label.set_color") {
|
|
5246
|
+
const id = drawId(node, env);
|
|
5247
|
+
if (id != null) book.labelSetColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5248
|
+
return NA;
|
|
5249
|
+
}
|
|
5250
|
+
if (fname === "label.set_style") {
|
|
5251
|
+
const id = drawId(node, env);
|
|
5252
|
+
if (id != null) book.labelSetStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5253
|
+
return NA;
|
|
5254
|
+
}
|
|
5255
|
+
if (fname === "label.set_size") {
|
|
5256
|
+
const id = drawId(node, env);
|
|
5257
|
+
if (id != null) book.labelSetSize(id, drawRaw(node, env, 1, ["size"]));
|
|
5258
|
+
return NA;
|
|
5259
|
+
}
|
|
5260
|
+
if (fname === "label.set_tooltip") {
|
|
5261
|
+
const id = drawId(node, env);
|
|
5262
|
+
if (id != null) book.labelSetTooltip(id, drawRaw(node, env, 1, ["tooltip"]));
|
|
5263
|
+
return NA;
|
|
5264
|
+
}
|
|
5265
|
+
if (fname === "label.set_textalign") {
|
|
5266
|
+
const id = drawId(node, env);
|
|
5267
|
+
if (id != null) book.labelSetTextalign(id, drawRaw(node, env, 1, ["textalign", "halign"]));
|
|
5268
|
+
return NA;
|
|
5269
|
+
}
|
|
5270
|
+
if (fname === "label.set_textcolor") {
|
|
5271
|
+
const id = drawId(node, env);
|
|
5272
|
+
if (id != null) book.labelSetTextcolor(id, drawRaw(node, env, 1, ["color", "textcolor"]));
|
|
5273
|
+
return NA;
|
|
5274
|
+
}
|
|
5275
|
+
if (fname === "label.set_text_font_family") {
|
|
5276
|
+
const id = drawId(node, env);
|
|
5277
|
+
if (id != null) book.labelSetTextFontFamily(id, drawRaw(node, env, 1, ["font_family", "text_font_family"]));
|
|
5278
|
+
return NA;
|
|
5279
|
+
}
|
|
5280
|
+
if (fname === "label.set_text_halign") {
|
|
5281
|
+
const id = drawId(node, env);
|
|
5282
|
+
if (id != null) book.labelSetTextHalign(id, drawRaw(node, env, 1, ["halign", "text_halign"]));
|
|
5283
|
+
return NA;
|
|
5284
|
+
}
|
|
5285
|
+
if (fname === "label.set_text_valign") {
|
|
5286
|
+
const id = drawId(node, env);
|
|
5287
|
+
if (id != null) book.labelSetTextValign(id, drawRaw(node, env, 1, ["valign", "text_valign"]));
|
|
5288
|
+
return NA;
|
|
5289
|
+
}
|
|
5290
|
+
if (fname === "label.set_text_size") {
|
|
5291
|
+
const id = drawId(node, env);
|
|
5292
|
+
if (id != null) book.labelSetTextSize(id, drawRaw(node, env, 1, ["size", "text_size"]));
|
|
5293
|
+
return NA;
|
|
5294
|
+
}
|
|
5295
|
+
if (fname === "label.set_text_formatting") {
|
|
5296
|
+
const id = drawId(node, env);
|
|
5297
|
+
if (id != null) book.labelSetTextFormatting(id, drawRaw(node, env, 1, ["formatting", "text_formatting"]));
|
|
5298
|
+
return NA;
|
|
5299
|
+
}
|
|
5300
|
+
if (fname === "label.set_border_color") {
|
|
5301
|
+
const id = drawId(node, env);
|
|
5302
|
+
if (id != null) book.labelSetBorderColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5303
|
+
return NA;
|
|
5304
|
+
}
|
|
5305
|
+
if (fname === "label.set_border_width") {
|
|
5306
|
+
const id = drawId(node, env);
|
|
5307
|
+
if (id != null) book.labelSetBorderWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5308
|
+
return NA;
|
|
5309
|
+
}
|
|
5310
|
+
if (fname === "label.set_border_style") {
|
|
5311
|
+
const id = drawId(node, env);
|
|
5312
|
+
if (id != null) book.labelSetBorderStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5313
|
+
return NA;
|
|
5314
|
+
}
|
|
5315
|
+
if (fname === "label.set_xloc") {
|
|
5316
|
+
const id = drawId(node, env);
|
|
5317
|
+
if (id != null) book.labelSetXloc(id, drawRaw(node, env, 1, ["xloc"]));
|
|
5318
|
+
return NA;
|
|
5319
|
+
}
|
|
5320
|
+
if (fname === "label.set_yloc") {
|
|
5321
|
+
const id = drawId(node, env);
|
|
5322
|
+
if (id != null) book.labelSetYloc(id, drawRaw(node, env, 1, ["yloc"]));
|
|
5323
|
+
return NA;
|
|
5324
|
+
}
|
|
5325
|
+
if (fname === "label.set_point") {
|
|
5326
|
+
const id = drawId(node, env);
|
|
5327
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5328
|
+
if (id != null && isChartPoint(p)) {
|
|
5329
|
+
const xy = pointXY(p);
|
|
5330
|
+
book.labelSetXy(id, xy.x, xy.y);
|
|
5331
|
+
}
|
|
5332
|
+
return NA;
|
|
5333
|
+
}
|
|
5334
|
+
|
|
5335
|
+
if (fname === "box.set_lefttop" || fname === "box.set_corners") {
|
|
5336
|
+
const id = drawId(node, env);
|
|
3572
5337
|
if (id != null) {
|
|
3573
|
-
|
|
5338
|
+
book.boxSetCorners(
|
|
3574
5339
|
id,
|
|
3575
|
-
|
|
3576
|
-
|
|
5340
|
+
drawNum(node, env, 1, ["left", "x1"]) ?? 0,
|
|
5341
|
+
drawNum(node, env, 2, ["top", "y1"]) ?? 0,
|
|
5342
|
+
drawNum(node, env, 3, ["right", "x2"]) ?? 0,
|
|
5343
|
+
drawNum(node, env, 4, ["bottom", "y2"]) ?? 0,
|
|
3577
5344
|
);
|
|
3578
5345
|
}
|
|
3579
5346
|
return NA;
|
|
3580
5347
|
}
|
|
3581
|
-
if (fname === "box.
|
|
3582
|
-
const id =
|
|
5348
|
+
if (fname === "box.set_left") {
|
|
5349
|
+
const id = drawId(node, env);
|
|
5350
|
+
if (id != null) book.boxSetLeft(id, drawNum(node, env, 1, ["left"]));
|
|
5351
|
+
return NA;
|
|
5352
|
+
}
|
|
5353
|
+
if (fname === "box.set_right") {
|
|
5354
|
+
const id = drawId(node, env);
|
|
5355
|
+
if (id != null) book.boxSetRight(id, drawNum(node, env, 1, ["right"]));
|
|
5356
|
+
return NA;
|
|
5357
|
+
}
|
|
5358
|
+
if (fname === "box.set_top") {
|
|
5359
|
+
const id = drawId(node, env);
|
|
5360
|
+
if (id != null) book.boxSetTop(id, drawNum(node, env, 1, ["top"]));
|
|
5361
|
+
return NA;
|
|
5362
|
+
}
|
|
5363
|
+
if (fname === "box.set_bottom") {
|
|
5364
|
+
const id = drawId(node, env);
|
|
5365
|
+
if (id != null) book.boxSetBottom(id, drawNum(node, env, 1, ["bottom"]));
|
|
5366
|
+
return NA;
|
|
5367
|
+
}
|
|
5368
|
+
if (fname === "box.set_bgcolor") {
|
|
5369
|
+
const id = drawId(node, env);
|
|
5370
|
+
if (id != null) book.boxSetBgcolor(id, drawRaw(node, env, 1, ["color", "bgcolor"]));
|
|
5371
|
+
return NA;
|
|
5372
|
+
}
|
|
5373
|
+
if (fname === "box.set_border_color") {
|
|
5374
|
+
const id = drawId(node, env);
|
|
5375
|
+
if (id != null) book.boxSetBorderColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5376
|
+
return NA;
|
|
5377
|
+
}
|
|
5378
|
+
if (fname === "box.set_border_width") {
|
|
5379
|
+
const id = drawId(node, env);
|
|
5380
|
+
if (id != null) book.boxSetBorderWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5381
|
+
return NA;
|
|
5382
|
+
}
|
|
5383
|
+
if (fname === "box.set_border_style") {
|
|
5384
|
+
const id = drawId(node, env);
|
|
5385
|
+
if (id != null) book.boxSetBorderStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5386
|
+
return NA;
|
|
5387
|
+
}
|
|
5388
|
+
if (fname === "box.set_extend") {
|
|
5389
|
+
const id = drawId(node, env);
|
|
5390
|
+
if (id != null) book.boxSetExtend(id, drawRaw(node, env, 1, ["extend"]));
|
|
5391
|
+
return NA;
|
|
5392
|
+
}
|
|
5393
|
+
if (fname === "box.set_xloc") {
|
|
5394
|
+
const id = drawId(node, env);
|
|
3583
5395
|
if (id != null) {
|
|
3584
|
-
|
|
5396
|
+
book.boxSetLeft(id, drawNum(node, env, 1, ["left"]));
|
|
5397
|
+
book.boxSetRight(id, drawNum(node, env, 2, ["right"]));
|
|
5398
|
+
book.boxSetXloc(id, drawRaw(node, env, 3, ["xloc"]));
|
|
5399
|
+
}
|
|
5400
|
+
return NA;
|
|
5401
|
+
}
|
|
5402
|
+
if (fname === "box.set_closed") {
|
|
5403
|
+
const id = drawId(node, env);
|
|
5404
|
+
if (id != null) book.boxSetClosed(id, drawRaw(node, env, 1, ["closed"]));
|
|
5405
|
+
return NA;
|
|
5406
|
+
}
|
|
5407
|
+
if (fname === "box.set_rightbottom") {
|
|
5408
|
+
const id = drawId(node, env);
|
|
5409
|
+
if (id != null) book.boxSetRightBottom(id, drawNum(node, env, 1, ["right"]), drawNum(node, env, 2, ["bottom"]));
|
|
5410
|
+
return NA;
|
|
5411
|
+
}
|
|
5412
|
+
if (fname === "box.set_text") {
|
|
5413
|
+
const id = drawId(node, env);
|
|
5414
|
+
if (id != null) book.boxSetText(id, drawText(node, env, 1, ["text"]));
|
|
5415
|
+
return NA;
|
|
5416
|
+
}
|
|
5417
|
+
if (fname === "box.set_text_color") {
|
|
5418
|
+
const id = drawId(node, env);
|
|
5419
|
+
if (id != null) book.boxSetTextColor(id, drawRaw(node, env, 1, ["color", "text_color"]));
|
|
5420
|
+
return NA;
|
|
5421
|
+
}
|
|
5422
|
+
if (fname === "box.set_text_font_family") {
|
|
5423
|
+
const id = drawId(node, env);
|
|
5424
|
+
if (id != null) book.boxSetTextFontFamily(id, drawRaw(node, env, 1, ["font_family"]));
|
|
5425
|
+
return NA;
|
|
5426
|
+
}
|
|
5427
|
+
if (fname === "box.set_text_halign") {
|
|
5428
|
+
const id = drawId(node, env);
|
|
5429
|
+
if (id != null) book.boxSetTextHalign(id, drawRaw(node, env, 1, ["halign"]));
|
|
5430
|
+
return NA;
|
|
5431
|
+
}
|
|
5432
|
+
if (fname === "box.set_text_valign") {
|
|
5433
|
+
const id = drawId(node, env);
|
|
5434
|
+
if (id != null) book.boxSetTextValign(id, drawRaw(node, env, 1, ["valign"]));
|
|
5435
|
+
return NA;
|
|
5436
|
+
}
|
|
5437
|
+
if (fname === "box.set_text_size") {
|
|
5438
|
+
const id = drawId(node, env);
|
|
5439
|
+
if (id != null) book.boxSetTextSize(id, drawRaw(node, env, 1, ["size"]));
|
|
5440
|
+
return NA;
|
|
5441
|
+
}
|
|
5442
|
+
if (fname === "box.set_text_formatting") {
|
|
5443
|
+
const id = drawId(node, env);
|
|
5444
|
+
if (id != null) book.boxSetTextFormatting(id, drawRaw(node, env, 1, ["formatting"]));
|
|
5445
|
+
return NA;
|
|
5446
|
+
}
|
|
5447
|
+
if (fname === "box.set_text_wrap") {
|
|
5448
|
+
const id = drawId(node, env);
|
|
5449
|
+
if (id != null) book.boxSetTextWrap(id, drawRaw(node, env, 1, ["wrap"]));
|
|
5450
|
+
return NA;
|
|
5451
|
+
}
|
|
5452
|
+
if (fname === "box.set_top_left_point") {
|
|
5453
|
+
const id = drawId(node, env);
|
|
5454
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5455
|
+
if (id != null && isChartPoint(p)) {
|
|
5456
|
+
const xy = pointXY(p);
|
|
5457
|
+
book.boxSetLeftTop(id, xy.x, xy.y);
|
|
5458
|
+
}
|
|
5459
|
+
return NA;
|
|
5460
|
+
}
|
|
5461
|
+
if (fname === "box.set_bottom_right_point") {
|
|
5462
|
+
const id = drawId(node, env);
|
|
5463
|
+
const p = evalExpr(callArg(node.args, 1, ["point"]), env);
|
|
5464
|
+
if (id != null && isChartPoint(p)) {
|
|
5465
|
+
const xy = pointXY(p);
|
|
5466
|
+
book.boxSetRightBottom(id, xy.x, xy.y);
|
|
5467
|
+
}
|
|
5468
|
+
return NA;
|
|
5469
|
+
}
|
|
5470
|
+
|
|
5471
|
+
if (fname === "table.cell") {
|
|
5472
|
+
const id = drawId(node, env);
|
|
5473
|
+
const col = drawNum(node, env, 1, ["column"]);
|
|
5474
|
+
const row = drawNum(node, env, 2, ["row"]);
|
|
5475
|
+
const textArg = callArg(node.args, 3, ["text"]);
|
|
5476
|
+
const text = textArg == null ? undefined : drawText(node, env, 3, ["text"]);
|
|
5477
|
+
if (id != null && col != null && row != null) book.tableCell(id, col, row, text);
|
|
5478
|
+
return NA;
|
|
5479
|
+
}
|
|
5480
|
+
if (fname === "table.cell_set_text") {
|
|
5481
|
+
const id = drawId(node, env);
|
|
5482
|
+
const row = drawNum(node, env, 1, ["row"]);
|
|
5483
|
+
const col = drawNum(node, env, 2, ["column"]);
|
|
5484
|
+
if (id != null) book.tableCellSetText(id, col, row, drawText(node, env, 3, ["text"]));
|
|
5485
|
+
return NA;
|
|
5486
|
+
}
|
|
5487
|
+
if (
|
|
5488
|
+
fname === "table.cell_set_text_color" ||
|
|
5489
|
+
fname === "table.cell_set_bgcolor" ||
|
|
5490
|
+
fname === "table.cell_set_border_color" ||
|
|
5491
|
+
fname === "table.cell_set_border_width" ||
|
|
5492
|
+
fname === "table.cell_set_tooltip"
|
|
5493
|
+
) {
|
|
5494
|
+
const id = drawId(node, env);
|
|
5495
|
+
const row = drawNum(node, env, 1, ["row"]);
|
|
5496
|
+
const col = drawNum(node, env, 2, ["column"]);
|
|
5497
|
+
const field =
|
|
5498
|
+
fname === "table.cell_set_text_color"
|
|
5499
|
+
? "text_color"
|
|
5500
|
+
: fname === "table.cell_set_bgcolor"
|
|
5501
|
+
? "bgcolor"
|
|
5502
|
+
: fname === "table.cell_set_border_color"
|
|
5503
|
+
? "border_color"
|
|
5504
|
+
: fname === "table.cell_set_border_width"
|
|
5505
|
+
? "border_width"
|
|
5506
|
+
: "tooltip";
|
|
5507
|
+
if (id != null) book.tableCellSetField(id, col, row, field, drawRaw(node, env, 3, ["color", "width", "tooltip", "text"]));
|
|
5508
|
+
return NA;
|
|
5509
|
+
}
|
|
5510
|
+
if (
|
|
5511
|
+
fname === "table.cell_set_width" ||
|
|
5512
|
+
fname === "table.cell_set_height" ||
|
|
5513
|
+
fname === "table.cell_set_text_halign" ||
|
|
5514
|
+
fname === "table.cell_set_text_valign" ||
|
|
5515
|
+
fname === "table.cell_set_text_size" ||
|
|
5516
|
+
fname === "table.cell_set_text_font_family" ||
|
|
5517
|
+
fname === "table.cell_set_text_formatting"
|
|
5518
|
+
) {
|
|
5519
|
+
const id = drawId(node, env);
|
|
5520
|
+
const col = drawNum(node, env, 1, ["column", "col"]);
|
|
5521
|
+
const row = drawNum(node, env, 2, ["row"]);
|
|
5522
|
+
const field = fname.slice("table.cell_set_".length);
|
|
5523
|
+
if (id != null) book.tableCellSetField(id, col, row, field, drawRaw(node, env, 3, ["width", "height", "halign", "valign", "size", "font_family", "formatting"]));
|
|
5524
|
+
return NA;
|
|
5525
|
+
}
|
|
5526
|
+
if (fname === "table.set_position") {
|
|
5527
|
+
const id = drawId(node, env);
|
|
5528
|
+
if (id != null) book.tableSetPosition(id, drawRaw(node, env, 1, ["position"]));
|
|
5529
|
+
return NA;
|
|
5530
|
+
}
|
|
5531
|
+
if (fname === "table.set_bgcolor") {
|
|
5532
|
+
const id = drawId(node, env);
|
|
5533
|
+
if (id != null) book.tableSetBgcolor(id, drawRaw(node, env, 1, ["color", "bgcolor"]));
|
|
5534
|
+
return NA;
|
|
5535
|
+
}
|
|
5536
|
+
if (fname === "table.set_border_color") {
|
|
5537
|
+
const id = drawId(node, env);
|
|
5538
|
+
if (id != null) book.tableSetBorderColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5539
|
+
return NA;
|
|
5540
|
+
}
|
|
5541
|
+
if (fname === "table.set_border_width") {
|
|
5542
|
+
const id = drawId(node, env);
|
|
5543
|
+
if (id != null) book.tableSetBorderWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5544
|
+
return NA;
|
|
5545
|
+
}
|
|
5546
|
+
if (fname === "table.set_frame_color") {
|
|
5547
|
+
const id = drawId(node, env);
|
|
5548
|
+
if (id != null) book.tableSetFrameColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5549
|
+
return NA;
|
|
5550
|
+
}
|
|
5551
|
+
if (fname === "table.set_frame_width") {
|
|
5552
|
+
const id = drawId(node, env);
|
|
5553
|
+
if (id != null) book.tableSetFrameWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5554
|
+
return NA;
|
|
5555
|
+
}
|
|
5556
|
+
if (fname === "table.clear") {
|
|
5557
|
+
const id = drawId(node, env);
|
|
5558
|
+
if (id != null) {
|
|
5559
|
+
const a1 = callArg(node.args, 1, ["start_row"]);
|
|
5560
|
+
if (a1 == null) book.tableClear(id);
|
|
5561
|
+
else {
|
|
5562
|
+
book.tableClear(
|
|
5563
|
+
id,
|
|
5564
|
+
drawNum(node, env, 1, ["start_row"]),
|
|
5565
|
+
drawNum(node, env, 2, ["start_column", "start_col"]),
|
|
5566
|
+
drawNum(node, env, 3, ["end_row"]),
|
|
5567
|
+
drawNum(node, env, 4, ["end_column", "end_col"]),
|
|
5568
|
+
);
|
|
5569
|
+
}
|
|
5570
|
+
}
|
|
5571
|
+
return NA;
|
|
5572
|
+
}
|
|
5573
|
+
if (fname === "table.merge_cells") {
|
|
5574
|
+
const id = drawId(node, env);
|
|
5575
|
+
if (id != null) {
|
|
5576
|
+
book.tableMergeCells(
|
|
3585
5577
|
id,
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
5578
|
+
drawNum(node, env, 1, ["start_row"]),
|
|
5579
|
+
drawNum(node, env, 2, ["start_column", "start_col"]),
|
|
5580
|
+
drawNum(node, env, 3, ["end_row"]),
|
|
5581
|
+
drawNum(node, env, 4, ["end_column", "end_col"]),
|
|
3590
5582
|
);
|
|
3591
5583
|
}
|
|
3592
5584
|
return NA;
|
|
3593
5585
|
}
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
5586
|
+
|
|
5587
|
+
if (fname === "linefill.set_color") {
|
|
5588
|
+
const id = drawId(node, env);
|
|
5589
|
+
if (id != null) book.linefillSetColor(id, drawRaw(node, env, 1, ["color"]));
|
|
3597
5590
|
return NA;
|
|
3598
5591
|
}
|
|
3599
|
-
if (fname === "
|
|
3600
|
-
const id =
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
5592
|
+
if (fname === "polyline.set_points") {
|
|
5593
|
+
const id = drawId(node, env);
|
|
5594
|
+
if (id != null) book.polylineSetPoints(id, pointsFromValue(evalExpr(callArg(node.args, 1, ["points"]), env)));
|
|
5595
|
+
return NA;
|
|
5596
|
+
}
|
|
5597
|
+
if (fname === "polyline.set_line_color") {
|
|
5598
|
+
const id = drawId(node, env);
|
|
5599
|
+
if (id != null) book.polylineSetLineColor(id, drawRaw(node, env, 1, ["color"]));
|
|
5600
|
+
return NA;
|
|
5601
|
+
}
|
|
5602
|
+
if (fname === "polyline.set_line_width") {
|
|
5603
|
+
const id = drawId(node, env);
|
|
5604
|
+
if (id != null) book.polylineSetLineWidth(id, drawRaw(node, env, 1, ["width"]));
|
|
5605
|
+
return NA;
|
|
5606
|
+
}
|
|
5607
|
+
if (fname === "polyline.set_line_style") {
|
|
5608
|
+
const id = drawId(node, env);
|
|
5609
|
+
if (id != null) book.polylineSetLineStyle(id, drawRaw(node, env, 1, ["style"]));
|
|
5610
|
+
return NA;
|
|
5611
|
+
}
|
|
5612
|
+
if (fname === "polyline.set_fill_color") {
|
|
5613
|
+
const id = drawId(node, env);
|
|
5614
|
+
if (id != null) book.polylineSetFillColor(id, drawRaw(node, env, 1, ["fill_color", "color"]));
|
|
5615
|
+
return NA;
|
|
5616
|
+
}
|
|
5617
|
+
if (fname === "polyline.set_curved") {
|
|
5618
|
+
const id = drawId(node, env);
|
|
5619
|
+
if (id != null) book.polylineSetCurved(id, drawRaw(node, env, 1, ["curved"]));
|
|
5620
|
+
return NA;
|
|
5621
|
+
}
|
|
5622
|
+
if (fname === "polyline.set_force_overlay") {
|
|
5623
|
+
const id = drawId(node, env);
|
|
5624
|
+
if (id != null) book.polylineSetForceOverlay(id, drawRaw(node, env, 1, ["force_overlay"]));
|
|
5625
|
+
return NA;
|
|
5626
|
+
}
|
|
5627
|
+
if (fname === "polyline.set_closed") {
|
|
5628
|
+
const id = drawId(node, env);
|
|
5629
|
+
if (id != null) book.polylineSetClosed(id, drawRaw(node, env, 1, ["closed"]));
|
|
5630
|
+
return NA;
|
|
5631
|
+
}
|
|
5632
|
+
if (fname === "polyline.set_xloc") {
|
|
5633
|
+
const id = drawId(node, env);
|
|
5634
|
+
if (id != null) book.polylineSetXloc(id, drawRaw(node, env, 1, ["xloc"]));
|
|
3605
5635
|
return NA;
|
|
3606
5636
|
}
|
|
3607
5637
|
return undefined;
|
|
@@ -3949,9 +5979,54 @@ function evalTimeCall(fname: string | null, node: Call, env: Env): Value | undef
|
|
|
3949
5979
|
const ms = calendarMs(node, env);
|
|
3950
5980
|
return ms == null ? NA : timeTradingDay(ms);
|
|
3951
5981
|
}
|
|
5982
|
+
if (fname === "time_close") return barTimeClose(env);
|
|
5983
|
+
if (fname === "timenow") {
|
|
5984
|
+
const seeded = env.ctx.timenow;
|
|
5985
|
+
if (typeof seeded === "number" && Number.isFinite(seeded)) return seeded;
|
|
5986
|
+
if (typeof env.lastBarTime === "number" && Number.isFinite(env.lastBarTime)) return env.lastBarTime;
|
|
5987
|
+
const t = env.ctx.time;
|
|
5988
|
+
return typeof t === "number" && Number.isFinite(t) ? t : NA;
|
|
5989
|
+
}
|
|
3952
5990
|
return undefined;
|
|
3953
5991
|
}
|
|
3954
5992
|
|
|
5993
|
+
function barTimeClose(env: Env): number {
|
|
5994
|
+
const close = env.ctx.time_close;
|
|
5995
|
+
if (typeof close === "number" && Number.isFinite(close)) return close;
|
|
5996
|
+
const open = env.ctx.time;
|
|
5997
|
+
return typeof open === "number" && Number.isFinite(open) ? open : 0;
|
|
5998
|
+
}
|
|
5999
|
+
|
|
6000
|
+
function timeframeChange(node: Call, env: Env): Cell {
|
|
6001
|
+
const arg = callArg(node.args, 0, ["timeframe", "tf"]);
|
|
6002
|
+
if (arg == null) return 0;
|
|
6003
|
+
const raw = evalExpr(arg, env);
|
|
6004
|
+
const tf = typeof raw === "string" ? raw : typeof raw === "number" && Number.isFinite(raw) ? String(raw) : null;
|
|
6005
|
+
if (tf == null || tf.trim() === "") return 0;
|
|
6006
|
+
const curr = env.ctx.time;
|
|
6007
|
+
if (typeof curr !== "number" || !Number.isFinite(curr)) return 0;
|
|
6008
|
+
const hist = env.series.get("time");
|
|
6009
|
+
const prev = hist != null && hist.length >= 2 ? hist.get(1) : null;
|
|
6010
|
+
return timeframePeriodChanged(curr, prev, tf, env.barIndex) ? 1 : 0;
|
|
6011
|
+
}
|
|
6012
|
+
|
|
6013
|
+
/** Python `split_symbol` / `extract_prefix`: text before the first `:`, else `""`. */
|
|
6014
|
+
function extractPrefix(symbol: string): string {
|
|
6015
|
+
const s = symbol.trim();
|
|
6016
|
+
const i = s.indexOf(":");
|
|
6017
|
+
return i < 0 ? "" : s.slice(0, i);
|
|
6018
|
+
}
|
|
6019
|
+
|
|
6020
|
+
function syminfoPrefix(node: Call, env: Env): string {
|
|
6021
|
+
const arg = callArg(node.args, 0, ["tickerid", "symbol"]);
|
|
6022
|
+
if (arg == null) return extractPrefix(env.symbol);
|
|
6023
|
+
const raw = evalExpr(arg, env);
|
|
6024
|
+
if (raw instanceof TickerId) return extractPrefix(raw.symbol);
|
|
6025
|
+
if (typeof raw === "string") return extractPrefix(raw);
|
|
6026
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return extractPrefix(String(raw));
|
|
6027
|
+
return "";
|
|
6028
|
+
}
|
|
6029
|
+
|
|
3955
6030
|
function calendarMs(node: Call, env: Env): number | null {
|
|
3956
6031
|
const arg = callArg(node.args, 0, ["time", "timestamp"]);
|
|
3957
6032
|
if (arg == null) return num(env.ctx.time);
|