@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
|
@@ -8,6 +8,7 @@ import { parse } from "../../ast/helper.ts";
|
|
|
8
8
|
import type {
|
|
9
9
|
Assign,
|
|
10
10
|
Attribute,
|
|
11
|
+
AugAssign,
|
|
11
12
|
BinOp,
|
|
12
13
|
BoolOp,
|
|
13
14
|
Call,
|
|
@@ -23,6 +24,7 @@ import type {
|
|
|
23
24
|
If,
|
|
24
25
|
Import,
|
|
25
26
|
Name,
|
|
27
|
+
Once,
|
|
26
28
|
ReAssign,
|
|
27
29
|
Script,
|
|
28
30
|
stmt,
|
|
@@ -33,7 +35,7 @@ import type {
|
|
|
33
35
|
UnaryOp,
|
|
34
36
|
While,
|
|
35
37
|
} from "../../ast/nodes.ts";
|
|
36
|
-
import { emitCall } from "./emit_call.ts";
|
|
38
|
+
import { emitCall, TA_BARE_ATTRS } from "./emit_call.ts";
|
|
37
39
|
import { emitFunctionDef } from "./emit_udf.ts";
|
|
38
40
|
import type { EmitCtx, VisitFn } from "./types.ts";
|
|
39
41
|
|
|
@@ -47,6 +49,7 @@ const CHART_LOCALS = new Set([
|
|
|
47
49
|
"close",
|
|
48
50
|
"volume",
|
|
49
51
|
"time",
|
|
52
|
+
"time_close",
|
|
50
53
|
"bar_index",
|
|
51
54
|
"last_bar_index",
|
|
52
55
|
"na",
|
|
@@ -138,6 +141,7 @@ export function newEmitCtx(): EmitCtx {
|
|
|
138
141
|
usesStrategy: false,
|
|
139
142
|
nextSite: 0,
|
|
140
143
|
errors: [],
|
|
144
|
+
onceFlags: [],
|
|
141
145
|
currentFunc: null,
|
|
142
146
|
currentParamNames: new Set(),
|
|
143
147
|
currentSeriesParams: new Set(),
|
|
@@ -210,6 +214,9 @@ function assemble(ctx: EmitCtx, body: string[]): string {
|
|
|
210
214
|
lines.push(` const ${arr} = new Array(n_bars).fill(null);`);
|
|
211
215
|
}
|
|
212
216
|
lines.push(" const __var_inited = Object.create(null);");
|
|
217
|
+
for (const flag of ctx.onceFlags) {
|
|
218
|
+
lines.push(` let ${flag} = false;`);
|
|
219
|
+
}
|
|
213
220
|
for (const fn of ctx.functions) {
|
|
214
221
|
if (fn) lines.push(indentBlock(fn, 2));
|
|
215
222
|
}
|
|
@@ -223,6 +230,7 @@ function assemble(ctx: EmitCtx, body: string[]): string {
|
|
|
223
230
|
lines.push(" const close = close_arr[__bar_idx];");
|
|
224
231
|
lines.push(" const volume = vol_arr[__bar_idx];");
|
|
225
232
|
lines.push(" const time = time_arr[__bar_idx];");
|
|
233
|
+
lines.push(" const time_close = __h.timeClose(__bar_idx, time_arr);");
|
|
226
234
|
lines.push(" const bar_index = __bar_idx;");
|
|
227
235
|
lines.push(" const last_bar_index = n_bars - 1;");
|
|
228
236
|
lines.push(" const na = null;");
|
|
@@ -267,6 +275,7 @@ function emitNode(state: State, node: expr | stmt | null | undefined): string {
|
|
|
267
275
|
switch (node.kind) {
|
|
268
276
|
case "Assign":
|
|
269
277
|
case "ReAssign":
|
|
278
|
+
case "AugAssign":
|
|
270
279
|
case "FunctionDef":
|
|
271
280
|
case "TypeDef":
|
|
272
281
|
case "EnumDef":
|
|
@@ -274,6 +283,7 @@ function emitNode(state: State, node: expr | stmt | null | undefined): string {
|
|
|
274
283
|
case "Break":
|
|
275
284
|
case "Continue":
|
|
276
285
|
case "Expr":
|
|
286
|
+
case "Once":
|
|
277
287
|
return emitStmt(state, node);
|
|
278
288
|
default:
|
|
279
289
|
return emitExpr(state, node);
|
|
@@ -288,6 +298,8 @@ function emitStmt(state: State, node: stmt | expr): string {
|
|
|
288
298
|
return emitAssign(state, node);
|
|
289
299
|
case "ReAssign":
|
|
290
300
|
return emitReAssign(state, node);
|
|
301
|
+
case "AugAssign":
|
|
302
|
+
return emitAugAssign(state, node);
|
|
291
303
|
case "FunctionDef":
|
|
292
304
|
emitFunctionDef(state.ctx, node as FunctionDef, (s) => emitStmt(state, s));
|
|
293
305
|
if (state.ctx.currentFunc != null) state.ctx.currentFunc = null;
|
|
@@ -312,6 +324,8 @@ function emitStmt(state: State, node: stmt | expr): string {
|
|
|
312
324
|
return emitWhile(state, node);
|
|
313
325
|
case "Switch":
|
|
314
326
|
return emitSwitch(state, node, false);
|
|
327
|
+
case "Once":
|
|
328
|
+
return emitOnce(state, node);
|
|
315
329
|
default:
|
|
316
330
|
return emitExprStmt(state, node);
|
|
317
331
|
}
|
|
@@ -330,6 +344,8 @@ function emitExprStmt(state: State, value: expr | null | undefined): string {
|
|
|
330
344
|
return emitWhile(state, value);
|
|
331
345
|
case "Switch":
|
|
332
346
|
return emitSwitch(state, value, false);
|
|
347
|
+
case "Once":
|
|
348
|
+
return emitOnce(state, value);
|
|
333
349
|
default: {
|
|
334
350
|
const e = emitExpr(state, value);
|
|
335
351
|
if (!e || e === "null") return "";
|
|
@@ -373,6 +389,12 @@ function emitExpr(state: State, node: expr | null | undefined): string {
|
|
|
373
389
|
return iife(emitForIn(state, node), "null");
|
|
374
390
|
case "While":
|
|
375
391
|
return iife(emitWhile(state, node), "null");
|
|
392
|
+
case "Qualify":
|
|
393
|
+
return emitExpr(state, node.value);
|
|
394
|
+
case "Specialize":
|
|
395
|
+
return emitExpr(state, node.value);
|
|
396
|
+
case "AugAssign":
|
|
397
|
+
return emitAugAssign(state, node);
|
|
376
398
|
default:
|
|
377
399
|
return "null";
|
|
378
400
|
}
|
|
@@ -396,6 +418,8 @@ function emitName(state: State, node: Name): string {
|
|
|
396
418
|
if (CHART_LOCALS.has(id)) return id;
|
|
397
419
|
if (state.ctx.userFuncs.has(id)) return id;
|
|
398
420
|
if (state.ctx.enumTypes.has(id)) return JSON.stringify(id);
|
|
421
|
+
// Dataset last-bar time on every bar (interpret ctx.timenow / lastBarTime).
|
|
422
|
+
if (id === "timenow") return "__h.timenow(time_arr)";
|
|
399
423
|
return safeIdent(id);
|
|
400
424
|
}
|
|
401
425
|
|
|
@@ -423,11 +447,25 @@ function emitBinOp(state: State, node: BinOp): string {
|
|
|
423
447
|
return `__h.div(${left}, ${right})`;
|
|
424
448
|
case "Mod":
|
|
425
449
|
return `__h.mod(${left}, ${right})`;
|
|
450
|
+
case "BitAnd":
|
|
451
|
+
return emitBitOp(left, right, "&");
|
|
452
|
+
case "BitOr":
|
|
453
|
+
return emitBitOp(left, right, "|");
|
|
454
|
+
case "BitXor":
|
|
455
|
+
return emitBitOp(left, right, "^");
|
|
456
|
+
case "LShift":
|
|
457
|
+
return emitBitOp(left, right, "<<");
|
|
458
|
+
case "RShift":
|
|
459
|
+
return emitBitOp(left, right, ">>");
|
|
426
460
|
default:
|
|
427
461
|
return `__h.add(${left}, ${right})`;
|
|
428
462
|
}
|
|
429
463
|
}
|
|
430
464
|
|
|
465
|
+
function emitBitOp(left: string, right: string, op: "&" | "|" | "^" | "<<" | ">>"): string {
|
|
466
|
+
return `((__l, __r) => { if (__h.isNa(__l) || __h.isNa(__r)) return null; return (__h.nz(__l) | 0) ${op} (__h.nz(__r) | 0); })(${left}, ${right})`;
|
|
467
|
+
}
|
|
468
|
+
|
|
431
469
|
function emitUnaryOp(state: State, node: UnaryOp): string {
|
|
432
470
|
const operand = emitExpr(state, node.operand);
|
|
433
471
|
switch (opKind(node.op)) {
|
|
@@ -500,9 +538,62 @@ const STRATEGY_QUERY_ATTRS = new Set([
|
|
|
500
538
|
"openprofit",
|
|
501
539
|
"opentrades",
|
|
502
540
|
"closedtrades",
|
|
541
|
+
"leverage",
|
|
542
|
+
"margin_liquidation_price",
|
|
543
|
+
"position_avg_price",
|
|
544
|
+
"initial_capital",
|
|
545
|
+
"cash",
|
|
546
|
+
"position_entry_name",
|
|
547
|
+
"account_currency",
|
|
548
|
+
"default_entry_qty",
|
|
549
|
+
"max_contracts_held_all",
|
|
550
|
+
"max_contracts_held_long",
|
|
551
|
+
"max_contracts_held_short",
|
|
552
|
+
"wintrades",
|
|
553
|
+
"losstrades",
|
|
554
|
+
"eventrades",
|
|
555
|
+
"grossprofit",
|
|
556
|
+
"grossloss",
|
|
557
|
+
"avg_trade",
|
|
558
|
+
"avg_winning_trade",
|
|
559
|
+
"avg_losing_trade",
|
|
560
|
+
"netprofit_percent",
|
|
561
|
+
"openprofit_percent",
|
|
562
|
+
"grossprofit_percent",
|
|
563
|
+
"grossloss_percent",
|
|
564
|
+
"avg_trade_percent",
|
|
565
|
+
"avg_winning_trade_percent",
|
|
566
|
+
"avg_losing_trade_percent",
|
|
567
|
+
"max_drawdown",
|
|
568
|
+
"max_drawdown_percent",
|
|
569
|
+
"max_runup",
|
|
570
|
+
"max_runup_percent",
|
|
571
|
+
"percent_profitable",
|
|
572
|
+
"winrate",
|
|
573
|
+
"profitfactor",
|
|
574
|
+
"profit_factor",
|
|
503
575
|
]);
|
|
504
576
|
|
|
505
|
-
/**
|
|
577
|
+
/** Nested `strategy.*` constants (interpret `strategySeriesValue`). */
|
|
578
|
+
const STRATEGY_NESTED_CONSTS: Record<string, string> = {
|
|
579
|
+
"strategy.direction.long": "long",
|
|
580
|
+
"strategy.direction.short": "short",
|
|
581
|
+
"strategy.direction.all": "all",
|
|
582
|
+
"strategy.oca.none": "none",
|
|
583
|
+
"strategy.oca.cancel": "cancel",
|
|
584
|
+
"strategy.oca.reduce": "reduce",
|
|
585
|
+
"strategy.commission.percent": "percent",
|
|
586
|
+
"strategy.commission.cash_per_order": "cash_per_order",
|
|
587
|
+
"strategy.commission.cash_per_contract": "cash_per_contract",
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
/** Nested `strategy.*` series that are not a single identifier. */
|
|
591
|
+
const STRATEGY_NESTED_CALLS: Record<string, string> = {
|
|
592
|
+
"strategy.closedtrades.first_index": "closedtrades_first_index",
|
|
593
|
+
"strategy.opentrades.capital_held": "capital_held",
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
/** Namespaces used as call receivers or dotted constants — not UDT field access. */
|
|
506
597
|
const ATTR_NAMESPACES = new Set([
|
|
507
598
|
"ta",
|
|
508
599
|
"math",
|
|
@@ -521,8 +612,155 @@ const ATTR_NAMESPACES = new Set([
|
|
|
521
612
|
"session",
|
|
522
613
|
"chart",
|
|
523
614
|
"log",
|
|
615
|
+
"order",
|
|
616
|
+
"format",
|
|
617
|
+
"text",
|
|
618
|
+
"size",
|
|
619
|
+
"barmerge",
|
|
620
|
+
"shape",
|
|
621
|
+
"location",
|
|
622
|
+
"xloc",
|
|
623
|
+
"yloc",
|
|
624
|
+
"extend",
|
|
625
|
+
"display",
|
|
626
|
+
"position",
|
|
627
|
+
"hline",
|
|
628
|
+
"dayofweek",
|
|
629
|
+
"month",
|
|
630
|
+
]);
|
|
631
|
+
|
|
632
|
+
/** Dotted keys from Python `_MATH_CONSTANTS` (base.py). Unknown member → na. */
|
|
633
|
+
const ATTR_CONSTANTS: Record<string, string | number | boolean> = {
|
|
634
|
+
// Math constants — Python SoT `_MATH_CONSTANTS` (evaluator/base.py).
|
|
635
|
+
"math.pi": Math.PI,
|
|
636
|
+
"math.e": Math.E,
|
|
637
|
+
"math.phi": (1 + Math.sqrt(5)) / 2,
|
|
638
|
+
"math.rphi": 2 / (1 + Math.sqrt(5)),
|
|
639
|
+
"format.mintick": "mintick",
|
|
640
|
+
"format.percent": "percent",
|
|
641
|
+
"format.volume": "volume",
|
|
642
|
+
"format.price": "price",
|
|
643
|
+
"text.formatting.none": "",
|
|
644
|
+
"text.formatting.bold": "bold",
|
|
645
|
+
"text.formatting.italic": "italic",
|
|
646
|
+
"text.formatting.bold_italic": "bold italic",
|
|
647
|
+
"size.auto": "auto",
|
|
648
|
+
"size.tiny": 8,
|
|
649
|
+
"size.small": 10,
|
|
650
|
+
"size.normal": 12,
|
|
651
|
+
"size.large": 16,
|
|
652
|
+
"size.huge": 20,
|
|
653
|
+
"order.ascending": 1,
|
|
654
|
+
"order.descending": -1,
|
|
655
|
+
"barmerge.gaps_on": true,
|
|
656
|
+
"barmerge.gaps_off": false,
|
|
657
|
+
"barmerge.lookahead_on": true,
|
|
658
|
+
"barmerge.lookahead_off": false,
|
|
659
|
+
"shape.arrowup": "arrowup",
|
|
660
|
+
"shape.arrowdown": "arrowdown",
|
|
661
|
+
"shape.circle": "circle",
|
|
662
|
+
"shape.cross": "cross",
|
|
663
|
+
"shape.diamond": "diamond",
|
|
664
|
+
"shape.flag": "flag",
|
|
665
|
+
"shape.labelup": "labelup",
|
|
666
|
+
"shape.labeldown": "labeldown",
|
|
667
|
+
"shape.square": "square",
|
|
668
|
+
"shape.triangledown": "triangledown",
|
|
669
|
+
"shape.triangleup": "triangleup",
|
|
670
|
+
"shape.xcross": "xcross",
|
|
671
|
+
"location.abovebar": "abovebar",
|
|
672
|
+
"location.belowbar": "belowbar",
|
|
673
|
+
"location.top": "top",
|
|
674
|
+
"location.bottom": "bottom",
|
|
675
|
+
"location.absolute": "absolute",
|
|
676
|
+
"xloc.bar_index": "bar_index",
|
|
677
|
+
"xloc.bar_time": "bar_time",
|
|
678
|
+
"yloc.price": "price",
|
|
679
|
+
"yloc.abovebar": "abovebar",
|
|
680
|
+
"yloc.belowbar": "belowbar",
|
|
681
|
+
"extend.none": "none",
|
|
682
|
+
"extend.left": "left",
|
|
683
|
+
"extend.right": "right",
|
|
684
|
+
"extend.both": "both",
|
|
685
|
+
// Pine display.* is a bitfield: none=0 pane=1 data_window=2 price_scale=4
|
|
686
|
+
// status_line=8 all=15. Integers so `display.pane + display.data_window` works
|
|
687
|
+
// (Python SoT base.py / compiler.py — matches interpret backend).
|
|
688
|
+
"display.none": 0,
|
|
689
|
+
"display.pane": 1,
|
|
690
|
+
"display.all": 15,
|
|
691
|
+
"display.data_window": 2,
|
|
692
|
+
"display.price_scale": 4,
|
|
693
|
+
"display.status_line": 8,
|
|
694
|
+
"position.top_left": "top_left",
|
|
695
|
+
"position.top_center": "top_center",
|
|
696
|
+
"position.top_right": "top_right",
|
|
697
|
+
"position.middle_left": "middle_left",
|
|
698
|
+
"position.middle_center": "middle_center",
|
|
699
|
+
"position.middle_right": "middle_right",
|
|
700
|
+
"position.bottom_left": "bottom_left",
|
|
701
|
+
"position.bottom_center": "bottom_center",
|
|
702
|
+
"position.bottom_right": "bottom_right",
|
|
703
|
+
"hline.style_solid": "solid",
|
|
704
|
+
"hline.style_dashed": "dashed",
|
|
705
|
+
"hline.style_dotted": "dotted",
|
|
706
|
+
// Python plotting.py PlotStyle — longer than hline's "solid".
|
|
707
|
+
"plot.linestyle_solid": "linestyle_solid",
|
|
708
|
+
"plot.linestyle_dashed": "linestyle_dashed",
|
|
709
|
+
"plot.linestyle_dotted": "linestyle_dotted",
|
|
710
|
+
"dayofweek.sunday": 1,
|
|
711
|
+
"dayofweek.monday": 2,
|
|
712
|
+
"dayofweek.tuesday": 3,
|
|
713
|
+
"dayofweek.wednesday": 4,
|
|
714
|
+
"dayofweek.thursday": 5,
|
|
715
|
+
"dayofweek.friday": 6,
|
|
716
|
+
"dayofweek.saturday": 7,
|
|
717
|
+
"month.january": 1,
|
|
718
|
+
"month.february": 2,
|
|
719
|
+
"month.march": 3,
|
|
720
|
+
"month.april": 4,
|
|
721
|
+
"month.may": 5,
|
|
722
|
+
"month.june": 6,
|
|
723
|
+
"month.july": 7,
|
|
724
|
+
"month.august": 8,
|
|
725
|
+
"month.september": 9,
|
|
726
|
+
"month.october": 10,
|
|
727
|
+
"month.november": 11,
|
|
728
|
+
"month.december": 12,
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
const ATTR_CONST_NS = new Set([
|
|
732
|
+
"math",
|
|
733
|
+
"order",
|
|
734
|
+
"format",
|
|
735
|
+
"text",
|
|
736
|
+
"size",
|
|
737
|
+
"barmerge",
|
|
738
|
+
"shape",
|
|
739
|
+
"location",
|
|
740
|
+
"xloc",
|
|
741
|
+
"yloc",
|
|
742
|
+
"extend",
|
|
743
|
+
"display",
|
|
744
|
+
"position",
|
|
745
|
+
"hline",
|
|
746
|
+
"plot",
|
|
747
|
+
"dayofweek",
|
|
748
|
+
"month",
|
|
524
749
|
]);
|
|
525
750
|
|
|
751
|
+
function attrQualifiedName(node: Attribute): string | null {
|
|
752
|
+
const parts: string[] = [node.attr];
|
|
753
|
+
let cur: expr = node.value;
|
|
754
|
+
while (cur.kind === "Attribute") {
|
|
755
|
+
parts.push(cur.attr);
|
|
756
|
+
cur = cur.value;
|
|
757
|
+
}
|
|
758
|
+
if (cur.kind !== "Name") return null;
|
|
759
|
+
parts.push(cur.id);
|
|
760
|
+
parts.reverse();
|
|
761
|
+
return parts.join(".");
|
|
762
|
+
}
|
|
763
|
+
|
|
526
764
|
function emitBarstate(attr: string): string {
|
|
527
765
|
switch (attr) {
|
|
528
766
|
case "isfirst":
|
|
@@ -584,19 +822,34 @@ function emitChart(attr: string): string {
|
|
|
584
822
|
}
|
|
585
823
|
}
|
|
586
824
|
|
|
825
|
+
// Static defaults mirror the Python SoT flat table in
|
|
826
|
+
// pynescript/ast/evaluator/base.py ("Chart timeframe defaults (daily)"),
|
|
827
|
+
// cross-checked with runtime/host.py Timeframe (same values). The compile host
|
|
828
|
+
// has no timeframe context (`__h.timeframe` is undefined), so these are the
|
|
829
|
+
// effective values; interpret derives them dynamically from env.timeframe and
|
|
830
|
+
// resolves to the same daily table when no timeframe is configured — every
|
|
831
|
+
// timeframe.* member is backend-parity (commit 6977a42; pinned in
|
|
832
|
+
// test/compile_timeframe_defaults.test.ts).
|
|
587
833
|
function emitTimeframe(attr: string): string {
|
|
588
834
|
switch (attr) {
|
|
589
835
|
case "period":
|
|
590
|
-
|
|
836
|
+
case "main_period":
|
|
837
|
+
return `(__h.timeframe || "D")`;
|
|
591
838
|
case "multiplier":
|
|
592
839
|
return "1";
|
|
593
840
|
case "isintraday":
|
|
594
|
-
return "true";
|
|
595
|
-
case "isdaily":
|
|
596
841
|
case "isweekly":
|
|
597
842
|
case "ismonthly":
|
|
843
|
+
case "isseconds":
|
|
844
|
+
case "isinseconds":
|
|
845
|
+
case "isminutes":
|
|
846
|
+
case "ishours":
|
|
598
847
|
return "false";
|
|
848
|
+
case "isdaily":
|
|
849
|
+
case "isdwm":
|
|
850
|
+
return "true";
|
|
599
851
|
default:
|
|
852
|
+
// Unknown member → na (documented behavior).
|
|
600
853
|
return "null";
|
|
601
854
|
}
|
|
602
855
|
}
|
|
@@ -607,6 +860,8 @@ function emitSyminfo(attr: string): string {
|
|
|
607
860
|
case "tickerid":
|
|
608
861
|
case "root":
|
|
609
862
|
return `"SYMBOL"`;
|
|
863
|
+
case "prefix":
|
|
864
|
+
return `__h.syminfoPrefix()`;
|
|
610
865
|
default:
|
|
611
866
|
return "null";
|
|
612
867
|
}
|
|
@@ -635,14 +890,42 @@ function emitAttribute(state: State, node: Attribute): string {
|
|
|
635
890
|
if (node.value.kind === "Name" && node.value.id === "color") {
|
|
636
891
|
return `__h.colorByName(${JSON.stringify(node.attr)})`;
|
|
637
892
|
}
|
|
893
|
+
if (node.value.kind === "Name" && node.value.id === "ta") {
|
|
894
|
+
// Bare `ta.<attr>` auto-calls only for the whitelist interpret's
|
|
895
|
+
// evalTaAttr supports; everything else is na on both backends.
|
|
896
|
+
if (!TA_BARE_ATTRS.has(node.attr)) return "null";
|
|
897
|
+
return emitCall(state.ctx, { kind: "Call", func: node, args: [] }, visitOf(state));
|
|
898
|
+
}
|
|
638
899
|
if (node.value.kind === "Name" && node.value.id === "strategy") {
|
|
639
900
|
const attr = node.attr;
|
|
640
901
|
if (attr === "long") return `"long"`;
|
|
641
902
|
if (attr === "short") return `"short"`;
|
|
903
|
+
if (attr === "avg_price_stock") return `"stock"`;
|
|
904
|
+
if (attr === "avg_price_futures") return `"futures"`;
|
|
905
|
+
if (attr === "avg_price_inverse") return `"inverse"`;
|
|
906
|
+
if (attr === "fixed") return `"fixed"`;
|
|
907
|
+
if (attr === "percent_of_equity") return `"percent_of_equity"`;
|
|
908
|
+
// `strategy.commission` the series is 0; `strategy.commission.percent` is nested.
|
|
909
|
+
if (attr === "commission") return "0";
|
|
642
910
|
if (STRATEGY_QUERY_ATTRS.has(attr)) {
|
|
643
911
|
state.ctx.usesStrategy = true;
|
|
644
912
|
return `__h.strategy.${attr}()`;
|
|
645
913
|
}
|
|
914
|
+
return "null";
|
|
915
|
+
}
|
|
916
|
+
if (node.attr === "all" && node.value.kind === "Name") {
|
|
917
|
+
const kind = node.value.id;
|
|
918
|
+
if (
|
|
919
|
+
kind === "line" ||
|
|
920
|
+
kind === "label" ||
|
|
921
|
+
kind === "box" ||
|
|
922
|
+
kind === "table" ||
|
|
923
|
+
kind === "polyline" ||
|
|
924
|
+
kind === "linefill"
|
|
925
|
+
) {
|
|
926
|
+
state.ctx.usesDrawings = true;
|
|
927
|
+
return `__h.draw.all(${JSON.stringify(kind)})`;
|
|
928
|
+
}
|
|
646
929
|
}
|
|
647
930
|
if (node.value.kind === "Name") {
|
|
648
931
|
const members = state.ctx.enumTypes.get(node.value.id);
|
|
@@ -650,6 +933,23 @@ function emitAttribute(state: State, node: Attribute): string {
|
|
|
650
933
|
return JSON.stringify(node.attr);
|
|
651
934
|
}
|
|
652
935
|
}
|
|
936
|
+
const qn = attrQualifiedName(node);
|
|
937
|
+
if (qn != null) {
|
|
938
|
+
const nested = STRATEGY_NESTED_CONSTS[qn];
|
|
939
|
+
if (nested !== undefined) return JSON.stringify(nested);
|
|
940
|
+
const nestedCall = STRATEGY_NESTED_CALLS[qn];
|
|
941
|
+
if (nestedCall != null) {
|
|
942
|
+
state.ctx.usesStrategy = true;
|
|
943
|
+
return `__h.strategy.${nestedCall}()`;
|
|
944
|
+
}
|
|
945
|
+
if (qn.startsWith("strategy.") && qn.split(".").length > 2) return "null";
|
|
946
|
+
const dot = qn.indexOf(".");
|
|
947
|
+
const root = dot < 0 ? qn : qn.slice(0, dot);
|
|
948
|
+
if (ATTR_CONST_NS.has(root)) {
|
|
949
|
+
const lit = ATTR_CONSTANTS[qn];
|
|
950
|
+
return lit === undefined ? "null" : JSON.stringify(lit);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
653
953
|
const obj = emitExpr(state, node.value);
|
|
654
954
|
if (node.value.kind === "Name" && state.ctx.udtTypes.has(node.value.id)) {
|
|
655
955
|
return `__h.udtGet(${obj}, ${JSON.stringify(node.attr)})`;
|
|
@@ -772,6 +1072,21 @@ function emitReAssign(state: State, node: ReAssign): string {
|
|
|
772
1072
|
return emitStore(state, node.target, emitExpr(state, node.value), false);
|
|
773
1073
|
}
|
|
774
1074
|
|
|
1075
|
+
function emitAugAssign(state: State, node: AugAssign): string {
|
|
1076
|
+
// Attribute targets store the raw rhs — the old field is discarded
|
|
1077
|
+
// (quirk preserved from Python visit_AugAssign and interpret evalAugAssign).
|
|
1078
|
+
if (node.target.kind === "Attribute") {
|
|
1079
|
+
return emitStore(state, node.target, emitExpr(state, node.value), false);
|
|
1080
|
+
}
|
|
1081
|
+
const rhs = emitBinOp(state, {
|
|
1082
|
+
kind: "BinOp",
|
|
1083
|
+
left: node.target,
|
|
1084
|
+
op: node.op,
|
|
1085
|
+
right: node.value,
|
|
1086
|
+
});
|
|
1087
|
+
return emitStore(state, node.target, rhs, false);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
775
1090
|
function emitStore(state: State, target: expr, rhs: string, isVar: boolean): string {
|
|
776
1091
|
if (target.kind === "Name") {
|
|
777
1092
|
return storeName(state, target.id, rhs, isVar);
|
|
@@ -842,6 +1157,15 @@ function emitIfStmt(state: State, node: If, asExpr = false): string {
|
|
|
842
1157
|
return `if (${test}) {\n${indentBlock(thenBlock, 2)}\n} else {\n${indentBlock(elseBlock, 2)}\n}`;
|
|
843
1158
|
}
|
|
844
1159
|
|
|
1160
|
+
function emitOnce(state: State, node: Once): string {
|
|
1161
|
+
const flag = `__once_${state.ctx.onceFlags.length}`;
|
|
1162
|
+
state.ctx.onceFlags.push(flag);
|
|
1163
|
+
const test = node.test == null ? "true" : `__h.nz(${emitExpr(state, node.test)})`;
|
|
1164
|
+
const body = emitBranch(state, node.body, false);
|
|
1165
|
+
const inner = body ? `${indentBlock(body, 4)}\n ${flag} = true;` : ` ${flag} = true;`;
|
|
1166
|
+
return `if (!${flag}) {\n if (${test}) {\n${inner}\n }\n}`;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
845
1169
|
function emitBranch(state: State, stmts: stmt[], asExpr: boolean): string {
|
|
846
1170
|
const list = stmts ?? [];
|
|
847
1171
|
if (list.length === 0) return asExpr ? "return null;" : "";
|
|
@@ -900,6 +1224,7 @@ function isValueExpr(node: expr): boolean {
|
|
|
900
1224
|
case "ForIn":
|
|
901
1225
|
case "While":
|
|
902
1226
|
case "Switch":
|
|
1227
|
+
case "Once":
|
|
903
1228
|
return false;
|
|
904
1229
|
default:
|
|
905
1230
|
return true;
|
|
@@ -1021,10 +1346,7 @@ function emitSwitch(state: State, node: Switch, asExpr: boolean): string {
|
|
|
1021
1346
|
defaultCase = c;
|
|
1022
1347
|
continue;
|
|
1023
1348
|
}
|
|
1024
|
-
const cond =
|
|
1025
|
-
subjN != null
|
|
1026
|
-
? `__h.eq(${subjN}, ${emitExpr(state, c.pattern)})`
|
|
1027
|
-
: `__h.nz(${emitExpr(state, c.pattern)})`;
|
|
1349
|
+
const cond = emitSwitchCond(state, c.pattern, subjN);
|
|
1028
1350
|
const kw = first ? "if" : "else if";
|
|
1029
1351
|
first = false;
|
|
1030
1352
|
const body = emitBranch(state, c.body ?? [], asExpr);
|
|
@@ -1045,6 +1367,21 @@ function emitSwitch(state: State, node: Switch, asExpr: boolean): string {
|
|
|
1045
1367
|
return `{\n${indentBlock(block, 2)}\n}`;
|
|
1046
1368
|
}
|
|
1047
1369
|
|
|
1370
|
+
/** Python `_switch_case_matches`: a comma arm (`1, 2 =>`) matches any element. */
|
|
1371
|
+
function emitSwitchCond(state: State, pattern: expr, subjN: string | null): string {
|
|
1372
|
+
if (pattern.kind === "Tuple") {
|
|
1373
|
+
const parts = (pattern.elts ?? []).map((e) => emitSwitchLeaf(state, e, subjN));
|
|
1374
|
+
return parts.length ? `(${parts.join(" || ")})` : "false";
|
|
1375
|
+
}
|
|
1376
|
+
return emitSwitchLeaf(state, pattern, subjN);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function emitSwitchLeaf(state: State, pattern: expr, subjN: string | null): string {
|
|
1380
|
+
return subjN != null
|
|
1381
|
+
? `__h.eq(${subjN}, ${emitExpr(state, pattern)})`
|
|
1382
|
+
: `__h.nz(${emitExpr(state, pattern)})`;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1048
1385
|
function emitBlock(state: State, stmts: stmt[] | null | undefined): string {
|
|
1049
1386
|
const lines: string[] = [];
|
|
1050
1387
|
for (const s of stmts ?? []) {
|