@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
|
@@ -13,6 +13,18 @@ const LOW = "low_arr[__bar_idx]";
|
|
|
13
13
|
const CLOSE = "close_arr[__bar_idx]";
|
|
14
14
|
const VOL = "vol_arr[__bar_idx]";
|
|
15
15
|
const OPEN = "open_arr[__bar_idx]";
|
|
16
|
+
// Interpret's bare `ta.vwap` sources hlc3 = (h+l+c)/3, falling back to close
|
|
17
|
+
// when any component is na — mirror that exactly on the compile side.
|
|
18
|
+
const HLC3 =
|
|
19
|
+
"((high_arr[__bar_idx] != null && low_arr[__bar_idx] != null && close_arr[__bar_idx] != null)" +
|
|
20
|
+
" ? (high_arr[__bar_idx] + low_arr[__bar_idx] + close_arr[__bar_idx]) / 3" +
|
|
21
|
+
" : close_arr[__bar_idx])";
|
|
22
|
+
// Interpret `hlc3(env)` for the ta.cci period-only form: falls back through
|
|
23
|
+
// close → high → low when any component is na (interpret.ts hlc3).
|
|
24
|
+
const TP =
|
|
25
|
+
"((high_arr[__bar_idx] != null && low_arr[__bar_idx] != null && close_arr[__bar_idx] != null)" +
|
|
26
|
+
" ? (high_arr[__bar_idx] + low_arr[__bar_idx] + close_arr[__bar_idx]) / 3" +
|
|
27
|
+
" : (close_arr[__bar_idx] ?? high_arr[__bar_idx] ?? low_arr[__bar_idx]))";
|
|
16
28
|
|
|
17
29
|
const NS = new Set([
|
|
18
30
|
"ta",
|
|
@@ -87,13 +99,19 @@ const TA_METHODS = new Set([
|
|
|
87
99
|
"cum",
|
|
88
100
|
"range",
|
|
89
101
|
"accdist",
|
|
102
|
+
"ad",
|
|
90
103
|
"pvt",
|
|
104
|
+
"vpt",
|
|
91
105
|
"wad",
|
|
92
106
|
"nvi",
|
|
93
107
|
"pvi",
|
|
94
108
|
"wvad",
|
|
109
|
+
"cmf",
|
|
110
|
+
"klinger",
|
|
95
111
|
"iii",
|
|
96
112
|
"obv",
|
|
113
|
+
"ao",
|
|
114
|
+
"aroon",
|
|
97
115
|
"highestbars",
|
|
98
116
|
"lowestbars",
|
|
99
117
|
"dev",
|
|
@@ -106,6 +124,57 @@ const TA_METHODS = new Set([
|
|
|
106
124
|
"bbw",
|
|
107
125
|
"max",
|
|
108
126
|
"min",
|
|
127
|
+
"uo",
|
|
128
|
+
"rci",
|
|
129
|
+
"dpo",
|
|
130
|
+
"kst",
|
|
131
|
+
"stochrsi",
|
|
132
|
+
"donchian",
|
|
133
|
+
"ichimoku",
|
|
134
|
+
"bb_pct",
|
|
135
|
+
"emv",
|
|
136
|
+
"fractal",
|
|
137
|
+
"atr_stop",
|
|
138
|
+
"zigzag",
|
|
139
|
+
"hammer",
|
|
140
|
+
"gap_detector",
|
|
141
|
+
"macd_signal",
|
|
142
|
+
"acceleration_factor",
|
|
143
|
+
"stoch_smooth",
|
|
144
|
+
"engulfing",
|
|
145
|
+
"inside_bar_pattern",
|
|
146
|
+
"double_top_bottom",
|
|
147
|
+
"voi",
|
|
148
|
+
"bid_ask_imbalance",
|
|
149
|
+
"rsi_oversold_overbought",
|
|
150
|
+
"rsi_divergence",
|
|
151
|
+
"cumulative_delta",
|
|
152
|
+
"volume_momentum",
|
|
153
|
+
"volume_profile_high",
|
|
154
|
+
"volume_profile_low",
|
|
155
|
+
"volume_thrust",
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Bare `ta.<attr>` auto-call whitelist. Mirrors interpret's evalTaAttr
|
|
160
|
+
* (interpret.ts) exactly: only these attributes auto-invoke a zero-arg TA
|
|
161
|
+
* kernel on a bare attribute load; anything else is na on both backends.
|
|
162
|
+
* `aroon` is deliberately absent — interpret's evalTaAttr does not handle it.
|
|
163
|
+
*/
|
|
164
|
+
export const TA_BARE_ATTRS = new Set([
|
|
165
|
+
"accdist",
|
|
166
|
+
"ad",
|
|
167
|
+
"obv",
|
|
168
|
+
"tr",
|
|
169
|
+
"vwap",
|
|
170
|
+
"pvt",
|
|
171
|
+
"vpt",
|
|
172
|
+
"nvi",
|
|
173
|
+
"pvi",
|
|
174
|
+
"wad",
|
|
175
|
+
"wvad",
|
|
176
|
+
"cmf",
|
|
177
|
+
"ao",
|
|
109
178
|
]);
|
|
110
179
|
|
|
111
180
|
const SRC_LEN = new Set([
|
|
@@ -120,8 +189,6 @@ const SRC_LEN = new Set([
|
|
|
120
189
|
"sum",
|
|
121
190
|
"dema",
|
|
122
191
|
"tema",
|
|
123
|
-
"highestbars",
|
|
124
|
-
"lowestbars",
|
|
125
192
|
"dev",
|
|
126
193
|
"variance",
|
|
127
194
|
"median",
|
|
@@ -141,6 +208,8 @@ const PLOT_FUNCS = new Set([
|
|
|
141
208
|
"hline",
|
|
142
209
|
"bgcolor",
|
|
143
210
|
"barcolor",
|
|
211
|
+
"plotcandle",
|
|
212
|
+
"plotbar",
|
|
144
213
|
]);
|
|
145
214
|
|
|
146
215
|
const STRATEGY_ACTIONS = new Set([
|
|
@@ -160,6 +229,10 @@ const STRATEGY_QUERIES = new Set([
|
|
|
160
229
|
"strategy_openprofit",
|
|
161
230
|
"strategy_opentrades",
|
|
162
231
|
"strategy_closedtrades",
|
|
232
|
+
"strategy_leverage",
|
|
233
|
+
"strategy_margin_liquidation_price",
|
|
234
|
+
"strategy_position_avg_price",
|
|
235
|
+
"strategy_initial_capital",
|
|
163
236
|
]);
|
|
164
237
|
|
|
165
238
|
const CHART_SERIES_ARR: Record<string, string> = {
|
|
@@ -232,7 +305,7 @@ export function emitCall(ctx: EmitCtx, node: Call, visit: VisitFn): string {
|
|
|
232
305
|
if (PLOT_FUNCS.has(name)) return emitPlot(ctx, name, args);
|
|
233
306
|
|
|
234
307
|
if (name === "indicator" || name === "study" || name === "library") return "null";
|
|
235
|
-
if (name === "strategy") return
|
|
308
|
+
if (name === "strategy") return emitStrategyDecl(ctx, args);
|
|
236
309
|
|
|
237
310
|
if (name === "na") {
|
|
238
311
|
if (args.pos.length === 0 && args.kw.x == null && args.kw.source == null) return "null";
|
|
@@ -275,6 +348,19 @@ export function emitCall(ctx: EmitCtx, node: Call, visit: VisitFn): string {
|
|
|
275
348
|
}
|
|
276
349
|
return "86400";
|
|
277
350
|
}
|
|
351
|
+
if (name === "timeframe_change") {
|
|
352
|
+
// Prev bar time is `time_arr[__bar_idx-1]`; bar 0 has no prev (interpret series.get(1)).
|
|
353
|
+
return `__h.timeframeChange(${pick(args, 0, ["timeframe", "tf"], "null")}, time, (__bar_idx > 0 ? time_arr[__bar_idx - 1] : null), __bar_idx)`;
|
|
354
|
+
}
|
|
355
|
+
if (name === "time_close") {
|
|
356
|
+
return `__h.timeClose(__bar_idx, time_arr)`;
|
|
357
|
+
}
|
|
358
|
+
if (name === "syminfo_prefix") {
|
|
359
|
+
if (hasArg(args, 0, ["tickerid", "symbol"])) {
|
|
360
|
+
return `__h.syminfoPrefix(${pick(args, 0, ["tickerid", "symbol"], "null")})`;
|
|
361
|
+
}
|
|
362
|
+
return `__h.syminfoPrefix()`;
|
|
363
|
+
}
|
|
278
364
|
if (name === "ticker_heikinashi" || name === "heikinashi") {
|
|
279
365
|
ctx.needsHeikinashi = true;
|
|
280
366
|
return `"__HEIKINASHI__"`;
|
|
@@ -298,7 +384,15 @@ export function emitCall(ctx: EmitCtx, node: Call, visit: VisitFn): string {
|
|
|
298
384
|
return `(() => { throw new Error(String(${msg})); })()`;
|
|
299
385
|
}
|
|
300
386
|
|
|
301
|
-
if (name === "
|
|
387
|
+
if (name === "max_bars_back") return "null";
|
|
388
|
+
if (name === "timenow") return "__h.timenow(time_arr)";
|
|
389
|
+
if (name === "alertcondition") {
|
|
390
|
+
ctx.usesDrawings = true;
|
|
391
|
+
const cond = pick(args, 0, ["condition"], "null");
|
|
392
|
+
const title = hasArg(args, 1, ["title"]) ? pick(args, 1, ["title"], `"Alert"`) : `"Alert"`;
|
|
393
|
+
const message = hasArg(args, 2, ["message"]) ? pick(args, 2, ["message"], `"Alert"`) : `"Alert"`;
|
|
394
|
+
return `__h.draw.alertcondition(__bar_idx, ${cond}, ${title}, ${message})`;
|
|
395
|
+
}
|
|
302
396
|
if (name === "str_tostring" || name === "tostring") {
|
|
303
397
|
const x = pick(args, 0, ["value", "source", "x"], "null");
|
|
304
398
|
return `String(${x} ?? "")`;
|
|
@@ -432,6 +526,43 @@ function hasArg(args: CallArgs, index: number, names: string[]): boolean {
|
|
|
432
526
|
return index >= 0 && index < args.pos.length;
|
|
433
527
|
}
|
|
434
528
|
|
|
529
|
+
/** True when a keyword argument named one of *names* is present (interpret hasKwarg). */
|
|
530
|
+
function hasAnyKw(args: CallArgs, names: string[]): boolean {
|
|
531
|
+
return names.some((n) => Object.hasOwn(args.kw, n));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Runtime-safe `_is_period_like` gate (interpret `periodLikeCell`): only whole
|
|
536
|
+
* numbers pass and are returned as-is; everything else (na, fractional,
|
|
537
|
+
* series samples) collapses to *fb* — mirroring `periodLikeArg(...) ?? fb`.
|
|
538
|
+
* Constant args could be decided at emit time, but the runtime form is
|
|
539
|
+
* equally exact and keeps one code path for constants and series alike.
|
|
540
|
+
*/
|
|
541
|
+
function periodGate(expr: string, fb: string): string {
|
|
542
|
+
return `((__p) => (Number.isInteger(__p) ? __p : ${fb}))(${expr})`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Interpret `lenArg` / `lenOrDefault` analog: a null (na) or non-finite slot
|
|
547
|
+
* collapses to *fb* instead of reaching the kernel raw.
|
|
548
|
+
*/
|
|
549
|
+
function finiteOr(expr: string, fb: string): string {
|
|
550
|
+
return `((__v) => (__v != null && Number.isFinite(__v) ? __v : ${fb}))(${expr})`;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** Interpret `unwrap`: bool → 1/0, non-finite → null. */
|
|
554
|
+
function cellOf(expr: string): string {
|
|
555
|
+
return `((__c) => (__c == null ? null : typeof __c === "boolean" ? (__c ? 1 : 0) : (typeof __c === "number" && Number.isFinite(__c) ? __c : null)))(${expr})`;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Interpret `wave10bList`: a Pine array becomes its cells; a scalar is a
|
|
560
|
+
* one-element list. Bar-mode series stay one element.
|
|
561
|
+
*/
|
|
562
|
+
function cellList(expr: string): string {
|
|
563
|
+
return `(() => { const __v = ${expr}; const __cell = (e) => (e == null ? null : typeof e === "boolean" ? (e ? 1 : 0) : (typeof e === "number" && Number.isFinite(e) ? e : null)); if (__v != null && typeof __v.size === "function" && typeof __v.get === "function") { const __n = __v.size(); const __o = []; for (let __i = 0; __i < __n; __i++) __o.push(__cell(__v.get(__i))); return __o; } return [__cell(__v)]; })()`;
|
|
564
|
+
}
|
|
565
|
+
|
|
435
566
|
const DRAW_KINDS = new Set(["label", "line", "box", "table", "polyline", "linefill"]);
|
|
436
567
|
|
|
437
568
|
const ARRAY_SIGS: Record<string, string[][]> = {
|
|
@@ -452,7 +583,7 @@ const ARRAY_SIGS: Record<string, string[][]> = {
|
|
|
452
583
|
fill: [["id"], ["value"]],
|
|
453
584
|
copy: [["id"]],
|
|
454
585
|
reverse: [["id"]],
|
|
455
|
-
sort: [["id"], ["order"]],
|
|
586
|
+
sort: [["id"], ["order"], ["sort_field"]],
|
|
456
587
|
concat: [["id"], ["other", "id2"]],
|
|
457
588
|
slice: [["id"], ["index_from", "from"], ["index_to", "to"]],
|
|
458
589
|
indexof: [["id"], ["value"]],
|
|
@@ -476,10 +607,10 @@ const ARRAY_SIGS: Record<string, string[][]> = {
|
|
|
476
607
|
percentile_linear_interpolation: [["id"], ["percentage", "percent"]],
|
|
477
608
|
percentile_nearest: [["id"], ["percentage", "percent"]],
|
|
478
609
|
percentile_nearest_rank: [["id"], ["percentage", "percent"]],
|
|
479
|
-
binary_search: [["id"], ["value"]],
|
|
480
|
-
binary_search_leftmost: [["id"], ["value"]],
|
|
481
|
-
binary_search_rightmost: [["id"], ["value"]],
|
|
482
|
-
sort_indices: [["id"], ["order"]],
|
|
610
|
+
binary_search: [["id"], ["value"], ["sort_field"]],
|
|
611
|
+
binary_search_leftmost: [["id"], ["value"], ["sort_field"]],
|
|
612
|
+
binary_search_rightmost: [["id"], ["value"], ["sort_field"]],
|
|
613
|
+
sort_indices: [["id"], ["order"], ["sort_field"]],
|
|
483
614
|
covariance: [["id"], ["id2", "other"], ["biased"]],
|
|
484
615
|
};
|
|
485
616
|
|
|
@@ -541,7 +672,8 @@ const MATRIX_SIGS: Record<string, string[][]> = {
|
|
|
541
672
|
concat: [["id"], ["other"]],
|
|
542
673
|
reshape: [["id"], ["rows"], ["columns", "cols"]],
|
|
543
674
|
reverse: [["id"]],
|
|
544
|
-
sort: [["id"], ["column", "col"], ["order"]],
|
|
675
|
+
sort: [["id"], ["column", "col"], ["order"], ["sort_field"]],
|
|
676
|
+
sort_indices: [["id"], ["column", "col"], ["order"], ["sort_field"]],
|
|
545
677
|
eigenvalues: [["id"]],
|
|
546
678
|
eigenvectors: [["id"]],
|
|
547
679
|
pow: [["id"], ["power", "n"]],
|
|
@@ -554,6 +686,26 @@ const MATRIX_SIGS: Record<string, string[][]> = {
|
|
|
554
686
|
remove_col: [["id"], ["column", "index"]],
|
|
555
687
|
remove_column: [["id"], ["column", "index"]],
|
|
556
688
|
submatrix: [["id"], ["from_row"], ["to_row"], ["from_column", "from_col"], ["to_column", "to_col"]],
|
|
689
|
+
sum_row: [["id"], ["row", "index"]],
|
|
690
|
+
avg_row: [["id"], ["row", "index"]],
|
|
691
|
+
min_row: [["id"], ["row", "index"]],
|
|
692
|
+
max_row: [["id"], ["row", "index"]],
|
|
693
|
+
mode_row: [["id"], ["row", "index"]],
|
|
694
|
+
sum_col: [["id"], ["column", "col", "index"]],
|
|
695
|
+
avg_col: [["id"], ["column", "col", "index"]],
|
|
696
|
+
min_col: [["id"], ["column", "col", "index"]],
|
|
697
|
+
max_col: [["id"], ["column", "col", "index"]],
|
|
698
|
+
mode_col: [["id"], ["column", "col", "index"]],
|
|
699
|
+
copy_row: [["id"], ["row", "index"]],
|
|
700
|
+
copy_col: [["id"], ["column", "col", "index"]],
|
|
701
|
+
fill_row: [["id"], ["row", "index"], ["value"]],
|
|
702
|
+
fill_col: [["id"], ["column", "col", "index"], ["value"]],
|
|
703
|
+
fill_diagonal: [["id"], ["value"]],
|
|
704
|
+
reverse_rows: [["id"]],
|
|
705
|
+
reverse_cols: [["id"]],
|
|
706
|
+
stdev: [["id"]],
|
|
707
|
+
variance: [["id"]],
|
|
708
|
+
mode_all: [["id"]],
|
|
557
709
|
};
|
|
558
710
|
|
|
559
711
|
function nsMethod(name: string, prefix: string): string | null {
|
|
@@ -562,6 +714,8 @@ function nsMethod(name: string, prefix: string): string | null {
|
|
|
562
714
|
}
|
|
563
715
|
|
|
564
716
|
function ctorMethod(method: string): string {
|
|
717
|
+
// Every Python `array.new*` spelling (newint, new_float, new_chart.point, …)
|
|
718
|
+
// lowers to the same PineArray constructor as `array.new`.
|
|
565
719
|
return method === "from" || method.startsWith("new") ? "new" : method;
|
|
566
720
|
}
|
|
567
721
|
|
|
@@ -588,6 +742,10 @@ function emitHostCall(ns: string, method: string, args: CallArgs, params: string
|
|
|
588
742
|
function emitArray(name: string, args: CallArgs): string | null {
|
|
589
743
|
const raw = nsMethod(name, "array_");
|
|
590
744
|
if (raw == null) return null;
|
|
745
|
+
if (raw === "from") {
|
|
746
|
+
const parts = args.pos.length > 0 ? args.pos : Object.values(args.kw);
|
|
747
|
+
return `__h.array.from([${parts.join(", ")}])`;
|
|
748
|
+
}
|
|
591
749
|
const method = ctorMethod(raw);
|
|
592
750
|
const params = ARRAY_SIGS[method];
|
|
593
751
|
if (params == null) return null;
|
|
@@ -629,11 +787,24 @@ const DRAW_METHODS: Record<string, Record<string, string[][]>> = {
|
|
|
629
787
|
label: {
|
|
630
788
|
set_text: [["id"], ["text"]],
|
|
631
789
|
set_xy: [["id"], ["x"], ["y"]],
|
|
790
|
+
set_x: [["id"], ["x"]],
|
|
791
|
+
set_y: [["id"], ["y"]],
|
|
632
792
|
set_color: [["id"], ["color"]],
|
|
633
793
|
set_style: [["id"], ["style"]],
|
|
634
794
|
set_size: [["id"], ["size"]],
|
|
635
795
|
set_tooltip: [["id"], ["tooltip"]],
|
|
636
796
|
set_textalign: [["id"], ["textalign"]],
|
|
797
|
+
set_textcolor: [["id"], ["color", "textcolor"]],
|
|
798
|
+
set_text_font_family: [["id"], ["font_family", "text_font_family"]],
|
|
799
|
+
set_text_halign: [["id"], ["halign", "text_halign"]],
|
|
800
|
+
set_text_valign: [["id"], ["valign", "text_valign"]],
|
|
801
|
+
set_text_size: [["id"], ["size", "text_size"]],
|
|
802
|
+
set_text_formatting: [["id"], ["formatting", "text_formatting"]],
|
|
803
|
+
set_border_color: [["id"], ["color"]],
|
|
804
|
+
set_border_width: [["id"], ["width"]],
|
|
805
|
+
set_border_style: [["id"], ["style"]],
|
|
806
|
+
set_xloc: [["id"], ["xloc"]],
|
|
807
|
+
set_yloc: [["id"], ["yloc"]],
|
|
637
808
|
delete: [["id"]],
|
|
638
809
|
get_text: [["id"]],
|
|
639
810
|
get_x: [["id"]],
|
|
@@ -641,12 +812,17 @@ const DRAW_METHODS: Record<string, Record<string, string[][]>> = {
|
|
|
641
812
|
},
|
|
642
813
|
line: {
|
|
643
814
|
set_xy: [["id"], ["x1"], ["y1"], ["x2"], ["y2"]],
|
|
644
|
-
set_xy1: [["id"], ["x1"], ["y1"]],
|
|
645
|
-
set_xy2: [["id"], ["x2"], ["y2"]],
|
|
815
|
+
set_xy1: [["id"], ["x1", "x"], ["y1", "y"]],
|
|
816
|
+
set_xy2: [["id"], ["x2", "x"], ["y2", "y"]],
|
|
817
|
+
set_x1: [["id"], ["x1", "x"]],
|
|
818
|
+
set_y1: [["id"], ["y1", "y"]],
|
|
819
|
+
set_x2: [["id"], ["x2", "x"]],
|
|
820
|
+
set_y2: [["id"], ["y2", "y"]],
|
|
646
821
|
set_color: [["id"], ["color"]],
|
|
647
822
|
set_width: [["id"], ["width"]],
|
|
648
823
|
set_style: [["id"], ["style"]],
|
|
649
824
|
set_extend: [["id"], ["extend"]],
|
|
825
|
+
set_xloc: [["id"], ["xloc"]],
|
|
650
826
|
delete: [["id"]],
|
|
651
827
|
get_price: [["id"], ["x"]],
|
|
652
828
|
get_x1: [["id"]],
|
|
@@ -657,44 +833,144 @@ const DRAW_METHODS: Record<string, Record<string, string[][]>> = {
|
|
|
657
833
|
box: {
|
|
658
834
|
set_corners: [["id"], ["left", "x1"], ["top", "y1"], ["right", "x2"], ["bottom", "y2"]],
|
|
659
835
|
set_lefttop: [["id"], ["left", "x1"], ["top", "y1"], ["right", "x2"], ["bottom", "y2"]],
|
|
836
|
+
set_left: [["id"], ["left"]],
|
|
837
|
+
set_right: [["id"], ["right"]],
|
|
838
|
+
set_top: [["id"], ["top"]],
|
|
839
|
+
set_bottom: [["id"], ["bottom"]],
|
|
660
840
|
set_bgcolor: [["id"], ["bgcolor", "color"]],
|
|
661
841
|
set_border_color: [["id"], ["color", "border_color"]],
|
|
842
|
+
set_border_width: [["id"], ["width"]],
|
|
843
|
+
set_border_style: [["id"], ["style"]],
|
|
662
844
|
set_text: [["id"], ["text"]],
|
|
845
|
+
set_text_color: [["id"], ["color", "text_color"]],
|
|
846
|
+
set_text_font_family: [["id"], ["font_family"]],
|
|
847
|
+
set_text_halign: [["id"], ["halign"]],
|
|
848
|
+
set_text_valign: [["id"], ["valign"]],
|
|
849
|
+
set_text_size: [["id"], ["size"]],
|
|
850
|
+
set_text_formatting: [["id"], ["formatting"]],
|
|
851
|
+
set_text_wrap: [["id"], ["wrap"]],
|
|
663
852
|
set_extend: [["id"], ["extend"]],
|
|
853
|
+
set_closed: [["id"], ["closed"]],
|
|
854
|
+
set_rightbottom: [["id"], ["right"], ["bottom"]],
|
|
855
|
+
get_left: [["id"]],
|
|
856
|
+
get_right: [["id"]],
|
|
857
|
+
get_top: [["id"]],
|
|
858
|
+
get_bottom: [["id"]],
|
|
664
859
|
delete: [["id"]],
|
|
665
860
|
},
|
|
666
861
|
table: {
|
|
667
862
|
cell: [["id"], ["column", "col"], ["row"], ["text"]],
|
|
668
|
-
|
|
863
|
+
// Interpret positional order is (id, row, column, text), not column-first.
|
|
864
|
+
cell_set_text: [["id"], ["row"], ["column", "col"], ["text"]],
|
|
865
|
+
cell_get_text: [["id"], ["row"], ["column", "col"]],
|
|
866
|
+
cell_set_text_color: [["id"], ["row"], ["column", "col"], ["color", "text"]],
|
|
867
|
+
cell_set_bgcolor: [["id"], ["row"], ["column", "col"], ["color"]],
|
|
868
|
+
cell_set_border_color: [["id"], ["row"], ["column", "col"], ["color"]],
|
|
869
|
+
cell_set_border_width: [["id"], ["row"], ["column", "col"], ["width"]],
|
|
870
|
+
cell_set_tooltip: [["id"], ["row"], ["column", "col"], ["tooltip", "text"]],
|
|
871
|
+
cell_set_width: [["id"], ["column", "col"], ["row"], ["width"]],
|
|
872
|
+
cell_set_height: [["id"], ["column", "col"], ["row"], ["height"]],
|
|
873
|
+
cell_set_text_halign: [["id"], ["column", "col"], ["row"], ["halign"]],
|
|
874
|
+
cell_set_text_valign: [["id"], ["column", "col"], ["row"], ["valign"]],
|
|
875
|
+
cell_set_text_size: [["id"], ["column", "col"], ["row"], ["size"]],
|
|
876
|
+
cell_set_text_font_family: [["id"], ["column", "col"], ["row"], ["font_family"]],
|
|
877
|
+
cell_set_text_formatting: [["id"], ["column", "col"], ["row"], ["formatting"]],
|
|
669
878
|
set_position: [["id"], ["position"]],
|
|
879
|
+
set_bgcolor: [["id"], ["color", "bgcolor"]],
|
|
880
|
+
set_border_color: [["id"], ["color"]],
|
|
881
|
+
set_border_width: [["id"], ["width"]],
|
|
882
|
+
set_frame_color: [["id"], ["color"]],
|
|
883
|
+
set_frame_width: [["id"], ["width"]],
|
|
884
|
+
merge_cells: [["id"], ["start_row"], ["start_column", "start_col"], ["end_row"], ["end_column", "end_col"]],
|
|
670
885
|
delete: [["id"]],
|
|
671
886
|
},
|
|
672
887
|
polyline: {
|
|
673
888
|
delete: [["id"]],
|
|
889
|
+
get_points: [["id"]],
|
|
890
|
+
set_points: [["id"], ["points"]],
|
|
891
|
+
set_line_color: [["id"], ["color"]],
|
|
892
|
+
set_line_width: [["id"], ["width"]],
|
|
893
|
+
set_line_style: [["id"], ["style"]],
|
|
894
|
+
set_fill_color: [["id"], ["fill_color", "color"]],
|
|
895
|
+
set_curved: [["id"], ["curved"]],
|
|
896
|
+
set_force_overlay: [["id"], ["force_overlay"]],
|
|
897
|
+
set_closed: [["id"], ["closed"]],
|
|
898
|
+
set_xloc: [["id"], ["xloc"]],
|
|
674
899
|
},
|
|
675
900
|
linefill: {
|
|
676
901
|
set_color: [["id"], ["color"]],
|
|
677
902
|
delete: [["id"]],
|
|
903
|
+
get_line1: [["id"]],
|
|
904
|
+
get_line2: [["id"]],
|
|
678
905
|
},
|
|
679
906
|
};
|
|
680
907
|
|
|
908
|
+
function emitChartPoint(name: string, args: CallArgs): string | null {
|
|
909
|
+
const meth = name.slice("chart_point_".length);
|
|
910
|
+
if (meth === "new" || meth === "from_time") {
|
|
911
|
+
return `__h.draw.point_new(${pick(args, 0, ["time"], "null")}, ${pick(args, 1, ["price"], "0")})`;
|
|
912
|
+
}
|
|
913
|
+
if (meth === "from_index") {
|
|
914
|
+
return `__h.draw.point_from_index(${pick(args, 0, ["index"], "null")}, ${pick(args, 1, ["price"], "0")})`;
|
|
915
|
+
}
|
|
916
|
+
if (meth === "now") {
|
|
917
|
+
return `__h.draw.point_now(__bar_idx, time, ${pick(args, 0, ["price"], "0")})`;
|
|
918
|
+
}
|
|
919
|
+
if (meth === "copy") {
|
|
920
|
+
return `__h.draw.point_copy(${pick(args, 0, ["point"], "null")})`;
|
|
921
|
+
}
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
|
|
681
925
|
function emitDraw(ctx: EmitCtx, name: string, methodSrc: expr | null, args: CallArgs): string | null {
|
|
682
926
|
if (name === "alert" && methodSrc == null) {
|
|
683
927
|
ctx.usesDrawings = true;
|
|
684
928
|
return `__h.draw.alert(__bar_idx, ${pick(args, 0, ["message"], "null")})`;
|
|
685
929
|
}
|
|
930
|
+
if (name.startsWith("chart_point_")) {
|
|
931
|
+
const point = emitChartPoint(name, args);
|
|
932
|
+
if (point != null) ctx.usesDrawings = true;
|
|
933
|
+
return point;
|
|
934
|
+
}
|
|
686
935
|
const parsed = drawKindAndMethod(name, methodSrc);
|
|
687
936
|
if (parsed == null) return null;
|
|
688
937
|
const { kind, method } = parsed;
|
|
689
938
|
let code: string | null = null;
|
|
690
|
-
if (method === "
|
|
939
|
+
if (method === "all") {
|
|
940
|
+
code = `__h.draw.all(${JSON.stringify(kind)})`;
|
|
941
|
+
} else if (method === "copy" && (kind === "line" || kind === "label" || kind === "box" || kind === "polyline")) {
|
|
942
|
+
code = `__h.draw.copy(${pick(args, 0, ["id"], "null")})`;
|
|
943
|
+
} else if (method === "new") {
|
|
691
944
|
if (kind === "label") {
|
|
692
|
-
code = `__h.draw.label_new(__bar_idx, ${pick(args, 2, ["text"], "null")})`;
|
|
945
|
+
code = `__h.draw.label_new(__bar_idx, ${pick(args, 2, ["text"], "null")}, ${pick(args, 0, ["x"], "0")}, ${pick(args, 1, ["y"], "0")})`;
|
|
946
|
+
} else if (kind === "line") {
|
|
947
|
+
code = `__h.draw.line_new(__bar_idx, ${pick(args, 0, ["x1"], "0")}, ${pick(args, 1, ["y1"], "0")}, ${pick(args, 2, ["x2"], "0")}, ${pick(args, 3, ["y2"], "0")})`;
|
|
948
|
+
} else if (kind === "box") {
|
|
949
|
+
code = `__h.draw.box_new(__bar_idx, ${pick(args, 0, ["left"], "0")}, ${pick(args, 1, ["top"], "0")}, ${pick(args, 2, ["right"], "0")}, ${pick(args, 3, ["bottom"], "0")})`;
|
|
950
|
+
} else if (kind === "polyline") {
|
|
951
|
+
code = `__h.draw.polyline_new(__bar_idx, ${pick(args, 0, ["points"], "null")})`;
|
|
693
952
|
} else if (kind === "linefill") {
|
|
694
|
-
|
|
953
|
+
const color = hasArg(args, 2, ["color"]) ? pick(args, 2, ["color"], "null") : "null";
|
|
954
|
+
code = `__h.draw.linefill_new(__bar_idx, ${pick(args, 0, ["id1", "line1"], "null")}, ${pick(args, 1, ["id2", "line2"], "null")}, ${color})`;
|
|
695
955
|
} else if (DRAW_KINDS.has(kind)) {
|
|
696
956
|
code = `__h.draw.${kind}_new(__bar_idx)`;
|
|
697
957
|
}
|
|
958
|
+
} else if (kind === "line" && method === "fill") {
|
|
959
|
+
const color = hasArg(args, 2, ["color"]) ? pick(args, 2, ["color"], "null") : "null";
|
|
960
|
+
code = `__h.draw.linefill_new(__bar_idx, ${pick(args, 0, ["id1", "line1"], "null")}, ${pick(args, 1, ["id2", "line2"], "null")}, ${color})`;
|
|
961
|
+
} else if (
|
|
962
|
+
(kind === "line" && (method === "set_first_point" || method === "set_second_point")) ||
|
|
963
|
+
(kind === "label" && method === "set_point") ||
|
|
964
|
+
(kind === "box" && (method === "set_top_left_point" || method === "set_bottom_right_point"))
|
|
965
|
+
) {
|
|
966
|
+
code = `__h.draw.${kind}_${method}(${pick(args, 0, ["id"], "null")}, ${pick(args, 1, ["point"], "null")})`;
|
|
967
|
+
} else if (kind === "box" && method === "set_xloc") {
|
|
968
|
+
code = `__h.draw.box_set_xloc(${pick(args, 0, ["id"], "null")}, ${pick(args, 1, ["left"], "null")}, ${pick(args, 2, ["right"], "null")}, ${pick(args, 3, ["xloc"], "null")})`;
|
|
969
|
+
} else if (kind === "table" && method === "clear") {
|
|
970
|
+
const id = pick(args, 0, ["id", "table_id"], "null");
|
|
971
|
+
code = hasArg(args, 1, ["start_row"])
|
|
972
|
+
? `__h.draw.table_clear(${id}, ${pick(args, 1, ["start_row"], "null")}, ${pick(args, 2, ["start_column", "start_col"], "null")}, ${pick(args, 3, ["end_row"], "null")}, ${pick(args, 4, ["end_column", "end_col"], "null")})`
|
|
973
|
+
: `__h.draw.table_clear(${id})`;
|
|
698
974
|
} else {
|
|
699
975
|
const aliased = kind === "box" && method === "set_lefttop" ? "set_corners" : method;
|
|
700
976
|
const params = DRAW_METHODS[kind]?.[method] ?? DRAW_METHODS[kind]?.[aliased];
|
|
@@ -739,6 +1015,7 @@ function emitUdt(ctx: EmitCtx, name: string, methodSrc: expr | null, args: CallA
|
|
|
739
1015
|
|
|
740
1016
|
const SECURITY_FUNCS = new Set([
|
|
741
1017
|
"security",
|
|
1018
|
+
"security_lower_tf",
|
|
742
1019
|
"request_security",
|
|
743
1020
|
"request_security_lower_tf",
|
|
744
1021
|
"request_seed",
|
|
@@ -891,6 +1168,27 @@ function isStrategyAction(name: string, method: string): boolean {
|
|
|
891
1168
|
);
|
|
892
1169
|
}
|
|
893
1170
|
|
|
1171
|
+
/** `strategy(...)` declaration — kwargs only, matching interpret `applyStrategyDecl`. */
|
|
1172
|
+
function emitStrategyDecl(ctx: EmitCtx, args: CallArgs): string {
|
|
1173
|
+
ctx.usesStrategy = true;
|
|
1174
|
+
const fields: string[] = [];
|
|
1175
|
+
const add = (names: string[], key: string): void => {
|
|
1176
|
+
if (hasArg(args, -1, names)) fields.push(`${key}: ${pick(args, -1, names, "null")}`);
|
|
1177
|
+
};
|
|
1178
|
+
add(["commission", "commission_value"], "commission");
|
|
1179
|
+
add(["slippage"], "slippage");
|
|
1180
|
+
add(["pyramiding"], "pyramiding");
|
|
1181
|
+
add(["avg_price_model"], "avg_price_model");
|
|
1182
|
+
add(["leverage"], "leverage");
|
|
1183
|
+
add(["margin_long"], "margin_long");
|
|
1184
|
+
add(["margin_short"], "margin_short");
|
|
1185
|
+
add(["default_qty_type"], "default_qty_type");
|
|
1186
|
+
add(["default_qty_value"], "default_qty_value");
|
|
1187
|
+
add(["initial_capital"], "initial_capital");
|
|
1188
|
+
add(["currency"], "currency");
|
|
1189
|
+
return `__h.strategy.configure({${fields.join(", ")}})`;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
894
1192
|
function emitStrategyEntry(method: "entry" | "order", args: CallArgs): string {
|
|
895
1193
|
const id = pick(args, 0, ["id"], "null");
|
|
896
1194
|
const direction = pick(args, 1, ["direction"], "null");
|
|
@@ -926,10 +1224,22 @@ function emitStrategy(ctx: EmitCtx, name: string, args: CallArgs): string {
|
|
|
926
1224
|
return "__h.strategy.close_all()";
|
|
927
1225
|
case "exit": {
|
|
928
1226
|
ctx.usesStrategy = true;
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1227
|
+
const id = pick(args, 0, ["id"], "null");
|
|
1228
|
+
const fields: string[] = [];
|
|
1229
|
+
const add = (index: number, names: string[], key: string): void => {
|
|
1230
|
+
if (hasArg(args, index, names)) fields.push(`${key}: ${pick(args, index, names, "null")}`);
|
|
1231
|
+
};
|
|
1232
|
+
add(1, ["from_entry"], "from_entry");
|
|
1233
|
+
add(2, ["qty"], "qty");
|
|
1234
|
+
add(3, ["qty_percent"], "qty_percent");
|
|
1235
|
+
add(4, ["profit"], "profit");
|
|
1236
|
+
add(5, ["limit"], "limit");
|
|
1237
|
+
add(6, ["loss"], "loss");
|
|
1238
|
+
add(7, ["stop"], "stop");
|
|
1239
|
+
add(8, ["trail_price"], "trail_price");
|
|
1240
|
+
add(9, ["trail_points"], "trail_points");
|
|
1241
|
+
add(10, ["trail_offset"], "trail_offset");
|
|
1242
|
+
return `__h.strategy.exit(${id}, {${fields.join(", ")}})`;
|
|
933
1243
|
}
|
|
934
1244
|
case "cancel":
|
|
935
1245
|
ctx.usesStrategy = true;
|
|
@@ -943,8 +1253,54 @@ function emitStrategy(ctx: EmitCtx, name: string, args: CallArgs): string {
|
|
|
943
1253
|
case "openprofit":
|
|
944
1254
|
case "opentrades":
|
|
945
1255
|
case "closedtrades":
|
|
1256
|
+
case "leverage":
|
|
1257
|
+
case "margin_liquidation_price":
|
|
1258
|
+
case "position_avg_price":
|
|
1259
|
+
case "initial_capital":
|
|
1260
|
+
case "cash":
|
|
1261
|
+
case "position_entry_name":
|
|
1262
|
+
case "account_currency":
|
|
1263
|
+
case "max_contracts_held_all":
|
|
1264
|
+
case "max_contracts_held_long":
|
|
1265
|
+
case "max_contracts_held_short":
|
|
1266
|
+
case "wintrades":
|
|
1267
|
+
case "losstrades":
|
|
1268
|
+
case "eventrades":
|
|
1269
|
+
case "grossprofit":
|
|
1270
|
+
case "grossloss":
|
|
1271
|
+
case "avg_trade":
|
|
1272
|
+
case "avg_winning_trade":
|
|
1273
|
+
case "avg_losing_trade":
|
|
1274
|
+
case "netprofit_percent":
|
|
1275
|
+
case "openprofit_percent":
|
|
1276
|
+
case "grossprofit_percent":
|
|
1277
|
+
case "grossloss_percent":
|
|
1278
|
+
case "avg_trade_percent":
|
|
1279
|
+
case "avg_winning_trade_percent":
|
|
1280
|
+
case "avg_losing_trade_percent":
|
|
1281
|
+
case "max_drawdown":
|
|
1282
|
+
case "max_drawdown_percent":
|
|
1283
|
+
case "max_runup":
|
|
1284
|
+
case "max_runup_percent":
|
|
1285
|
+
case "percent_profitable":
|
|
1286
|
+
case "winrate":
|
|
1287
|
+
case "profitfactor":
|
|
1288
|
+
case "profit_factor":
|
|
1289
|
+
case "capital_held":
|
|
1290
|
+
case "closedtrades_first_index":
|
|
946
1291
|
ctx.usesStrategy = true;
|
|
947
1292
|
return `__h.strategy.${method}()`;
|
|
1293
|
+
case "commission":
|
|
1294
|
+
return "0";
|
|
1295
|
+
case "default_entry_qty":
|
|
1296
|
+
ctx.usesStrategy = true;
|
|
1297
|
+
return hasArg(args, 0, ["percent_equity"])
|
|
1298
|
+
? `__h.strategy.default_entry_qty(${pick(args, 0, ["percent_equity"], "null")})`
|
|
1299
|
+
: `__h.strategy.default_entry_qty()`;
|
|
1300
|
+
case "convert_to_account":
|
|
1301
|
+
case "convert_to_symbol":
|
|
1302
|
+
ctx.usesStrategy = true;
|
|
1303
|
+
return hasArg(args, 0, ["value"]) ? `__h.strategy.convert_to(${pick(args, 0, ["value"], "null")})` : "1";
|
|
948
1304
|
case "risk_allow_entry_in":
|
|
949
1305
|
ctx.usesStrategy = true;
|
|
950
1306
|
return `__h.strategy.risk_allow_entry_in(${pick(args, 0, ["value"], "null")})`;
|
|
@@ -954,9 +1310,33 @@ function emitStrategy(ctx: EmitCtx, name: string, args: CallArgs): string {
|
|
|
954
1310
|
case "risk_max_drawdown":
|
|
955
1311
|
ctx.usesStrategy = true;
|
|
956
1312
|
return `__h.strategy.risk_max_drawdown(${pick(args, 0, ["value"], "null")}, ${pick(args, 1, ["type"], "null")})`;
|
|
957
|
-
default:
|
|
1313
|
+
default: {
|
|
1314
|
+
if (method.startsWith("direction_")) {
|
|
1315
|
+
const tail = method.slice("direction_".length);
|
|
1316
|
+
if (tail === "long" || tail === "short" || tail === "all") return JSON.stringify(tail);
|
|
1317
|
+
}
|
|
1318
|
+
if (method.startsWith("oca_")) {
|
|
1319
|
+
const tail = method.slice("oca_".length);
|
|
1320
|
+
if (tail === "none" || tail === "cancel" || tail === "reduce") return JSON.stringify(tail);
|
|
1321
|
+
}
|
|
1322
|
+
if (method.startsWith("commission_")) {
|
|
1323
|
+
const tail = method.slice("commission_".length);
|
|
1324
|
+
if (tail === "percent" || tail === "cash_per_order" || tail === "cash_per_contract") {
|
|
1325
|
+
return JSON.stringify(tail);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
if (method.startsWith("closedtrades_") || method.startsWith("opentrades_")) {
|
|
1329
|
+
ctx.usesStrategy = true;
|
|
1330
|
+
if (method === "opentrades_capital_held") return "__h.strategy.capital_held()";
|
|
1331
|
+
if (method === "closedtrades_first_index") return "__h.strategy.closedtrades_first_index()";
|
|
1332
|
+
const idx = hasArg(args, 0, ["trade_num", "trade_index", "index"])
|
|
1333
|
+
? pick(args, 0, ["trade_num", "trade_index", "index"], "0")
|
|
1334
|
+
: "0";
|
|
1335
|
+
return `__h.strategy.${method}(${idx})`;
|
|
1336
|
+
}
|
|
958
1337
|
if (isStrategyAction(name, method)) ctx.usesStrategy = true;
|
|
959
1338
|
return "null";
|
|
1339
|
+
}
|
|
960
1340
|
}
|
|
961
1341
|
}
|
|
962
1342
|
|
|
@@ -992,7 +1372,7 @@ function packUserFuncArgs(ctx: EmitCtx, name: string, args: CallArgs): string[]
|
|
|
992
1372
|
return packed;
|
|
993
1373
|
}
|
|
994
1374
|
|
|
995
|
-
function emitPlot(ctx: EmitCtx,
|
|
1375
|
+
function emitPlot(ctx: EmitCtx, fname: string, args: CallArgs): string {
|
|
996
1376
|
let title: string | null = constString(args.kwNodes.title);
|
|
997
1377
|
if (title == null) {
|
|
998
1378
|
for (const n of args.posNodes) {
|
|
@@ -1003,11 +1383,19 @@ function emitPlot(ctx: EmitCtx, _fname: string, args: CallArgs): string {
|
|
|
1003
1383
|
}
|
|
1004
1384
|
}
|
|
1005
1385
|
}
|
|
1006
|
-
if (title == null || title === "")
|
|
1007
|
-
|
|
1386
|
+
if (title == null || title === "") {
|
|
1387
|
+
if (fname === "plotcandle") title = "candles";
|
|
1388
|
+
else if (fname === "plotbar") title = "bars";
|
|
1389
|
+
else title = `plot_${ctx.plots.length}`;
|
|
1390
|
+
}
|
|
1391
|
+
// Python primary column for plotcandle/plotbar is close (arg 3), not open.
|
|
1392
|
+
const ohlc = fname === "plotcandle" || fname === "plotbar";
|
|
1393
|
+
const series = ohlc
|
|
1394
|
+
? pick(args, 3, ["close"], "null")
|
|
1395
|
+
: pick(args, 0, ["series", "source", "price"], "null");
|
|
1008
1396
|
const i = ctx.plots.length;
|
|
1009
1397
|
ctx.plots.push({ title });
|
|
1010
|
-
return `plot_${i}[__bar_idx] = __h.
|
|
1398
|
+
return `plot_${i}[__bar_idx] = __h.plotNum(${series})`;
|
|
1011
1399
|
}
|
|
1012
1400
|
|
|
1013
1401
|
function emitColor(name: string, args: CallArgs): string | null {
|
|
@@ -1084,9 +1472,13 @@ function emitMath(name: string, args: CallArgs): string | null {
|
|
|
1084
1472
|
name === "todegrees" ||
|
|
1085
1473
|
name === "toradians" ||
|
|
1086
1474
|
name === "iff" ||
|
|
1087
|
-
name === "fixnan"
|
|
1475
|
+
name === "fixnan" ||
|
|
1476
|
+
name === "round_to_mintick";
|
|
1088
1477
|
if (!isNs && !isBareMath) return null;
|
|
1089
1478
|
|
|
1479
|
+
if (bare === "round_to_mintick") {
|
|
1480
|
+
return `__h.roundToMintick(${pick(args, 0, ["number", "x"], "null")})`;
|
|
1481
|
+
}
|
|
1090
1482
|
if (bare === "iff") {
|
|
1091
1483
|
const cond = pick(args, 0, ["condition", "cond"], "null");
|
|
1092
1484
|
const a = pick(args, 1, ["then", "if_true"], "null");
|
|
@@ -1176,6 +1568,7 @@ function taCall(method: string, site: string, parts: string[]): string {
|
|
|
1176
1568
|
|
|
1177
1569
|
function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
1178
1570
|
if (!TA_METHODS.has(method)) return "null";
|
|
1571
|
+
if (method === "ad") method = "accdist";
|
|
1179
1572
|
const site = allocSite(ctx);
|
|
1180
1573
|
const src = () => pick(args, 0, ["source", "series", "src"], CLOSE);
|
|
1181
1574
|
const len = (index: number, fb: string, names: string[] = ["length", "len", "period"]) =>
|
|
@@ -1187,8 +1580,27 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1187
1580
|
if (SRC_LEN1.has(method)) {
|
|
1188
1581
|
return taCall(method, site, [src(), len(1, "1")]);
|
|
1189
1582
|
}
|
|
1190
|
-
if (method === "highest" || method === "lowest"
|
|
1191
|
-
const defSrc = method === "lowest"
|
|
1583
|
+
if (method === "highest" || method === "lowest") {
|
|
1584
|
+
const defSrc = method === "lowest" ? LOW : HIGH;
|
|
1585
|
+
if (args.pos.length <= 1 && !hasAnyKw(args, ["source", "series"])) {
|
|
1586
|
+
// Python 1-arg period-only form (basic.py:222-246): source defaults to
|
|
1587
|
+
// the context high/low series and the length gates on _is_period_like
|
|
1588
|
+
// → non-period-like → na period 0 (interpret evalExtraTa).
|
|
1589
|
+
return taCall(method, site, [defSrc, periodGate(pick(args, 0, ["length"], "null"), "0")]);
|
|
1590
|
+
}
|
|
1591
|
+
return taCall(method, site, [src(), finiteOr(pick(args, 1, ["length"], "null"), "0")]);
|
|
1592
|
+
}
|
|
1593
|
+
if (method === "highestbars" || method === "lowestbars") {
|
|
1594
|
+
const defSrc = method === "lowestbars" ? LOW : HIGH;
|
|
1595
|
+
if (args.pos.length <= 1 && !hasAnyKw(args, ["source", "series"])) {
|
|
1596
|
+
// Python 1-arg period-only form (basic.py:248-291): context high/low
|
|
1597
|
+
// source, length gated like ta.highest (interpret evalExtraTa).
|
|
1598
|
+
return taCall(method, site, [defSrc, periodGate(pick(args, 0, ["length"], "null"), "0")]);
|
|
1599
|
+
}
|
|
1600
|
+
return taCall(method, site, [src(), finiteOr(pick(args, 1, ["length"], "null"), "0")]);
|
|
1601
|
+
}
|
|
1602
|
+
if (method === "max" || method === "min") {
|
|
1603
|
+
const defSrc = method === "min" ? LOW : HIGH;
|
|
1192
1604
|
if (args.pos.length >= 2 || Object.hasOwn(args.kw, "source") || Object.hasOwn(args.kw, "series")) {
|
|
1193
1605
|
return taCall(method, site, [src(), len(1, "14")]);
|
|
1194
1606
|
}
|
|
@@ -1224,10 +1636,24 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1224
1636
|
return `(() => { const __r = ${call}; return [__r.macd, __r.signal, __r.hist]; })()`;
|
|
1225
1637
|
}
|
|
1226
1638
|
if (method === "bb") {
|
|
1639
|
+
if (args.pos.length === 2 && !hasAnyKw(args, ["source", "series"])) {
|
|
1640
|
+
// Python period-first form (basic.py:394-401): (length, mult) with the
|
|
1641
|
+
// series defaulting to the context close. Dispatch gates slot 0 on
|
|
1642
|
+
// _is_period_like per bar (kwarg-first resolution, like interpret
|
|
1643
|
+
// callArg) — a non-period-like slot falls through to the series arm on
|
|
1644
|
+
// the raw slot-0 value, exactly like interpret.
|
|
1645
|
+
const p0Expr = pick(args, 0, ["source", "series", "length"], "null");
|
|
1646
|
+
const multPf = finiteOr(pick(args, 1, ["mult", "multiplier"], "null"), "NaN");
|
|
1647
|
+
const lenFt = finiteOr(pick(args, 1, ["length"], "null"), "0");
|
|
1648
|
+
const multFt = finiteOr(pick(args, 2, ["mult", "multiplier"], "null"), "2");
|
|
1649
|
+
const pf = `(() => { const __r = __h.ta.bb(${site}, close_arr[__bar_idx], __p0, ${multPf}); return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1650
|
+
const ft = `(() => { const __r = __h.ta.bb(${site}, __s0, ${lenFt}, ${multFt}); return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1651
|
+
return `((__p0, __s0) => (Number.isInteger(__p0) ? ${pf} : ${ft}))(${p0Expr}, ${pick(args, 0, ["source", "series"], "null")})`;
|
|
1652
|
+
}
|
|
1227
1653
|
const call = taCall("bb", site, [
|
|
1228
1654
|
src(),
|
|
1229
|
-
|
|
1230
|
-
pick(args, 2, ["mult", "multiplier"], "2"),
|
|
1655
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1656
|
+
finiteOr(pick(args, 2, ["mult", "multiplier"], "null"), "2"),
|
|
1231
1657
|
]);
|
|
1232
1658
|
return `(() => { const __r = ${call}; return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1233
1659
|
}
|
|
@@ -1245,21 +1671,52 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1245
1671
|
return `(() => { const __r = ${call}; return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1246
1672
|
}
|
|
1247
1673
|
if (method === "stoch") {
|
|
1248
|
-
if (args.
|
|
1249
|
-
|
|
1674
|
+
if (args.kw.high == null && args.pos.length <= 2) {
|
|
1675
|
+
// Python period-only / community forms (oscillators.py:58-78): %K over
|
|
1676
|
+
// the context close/high/low. Both slots gate on _is_period_like; in
|
|
1677
|
+
// the 2-arg (kLength, dPeriod) form dPeriod is ignored (Python quirk).
|
|
1678
|
+
const length =
|
|
1679
|
+
args.pos.length === 2
|
|
1680
|
+
? `(((__k, __d) => ((Number.isInteger(__k) && Number.isInteger(__d)) ? __k : 0))(${pick(args, 0, ["length"], "null")}, ${pick(args, 1, ["d", "smooth"], "null")}))`
|
|
1681
|
+
: periodGate(pick(args, 0, ["length"], "null"), "0");
|
|
1682
|
+
return taCall("stoch", site, [CLOSE, HIGH, LOW, length]);
|
|
1250
1683
|
}
|
|
1251
1684
|
return taCall("stoch", site, [
|
|
1252
1685
|
src(),
|
|
1253
1686
|
pick(args, 1, ["high"], HIGH),
|
|
1254
1687
|
pick(args, 2, ["low"], LOW),
|
|
1255
|
-
|
|
1688
|
+
finiteOr(pick(args, 3, ["length"], "null"), "0"),
|
|
1256
1689
|
]);
|
|
1257
1690
|
}
|
|
1258
1691
|
if (method === "vwma") {
|
|
1259
|
-
|
|
1692
|
+
if (args.pos.length <= 1 && !hasAnyKw(args, ["volume"])) {
|
|
1693
|
+
// Python 1-arg period-only form (basic.py:80-88): close + volume from
|
|
1694
|
+
// the chart context; length gates on _is_period_like → na period 0.
|
|
1695
|
+
return taCall("vwma", site, [CLOSE, VOL, periodGate(pick(args, 0, ["length"], "null"), "0")]);
|
|
1696
|
+
}
|
|
1697
|
+
if (args.pos.length >= 3 || hasAnyKw(args, ["volume"])) {
|
|
1698
|
+
// Python 3-arg community form (basic.py:89-100): (source, volume,
|
|
1699
|
+
// length). A period-like middle slot swaps volume back to the context
|
|
1700
|
+
// series (basic.py:93/97) — mirror interpret's per-bar swap.
|
|
1701
|
+
const vol = `((__v) => (Number.isInteger(__v) ? ${VOL} : __v))(${pick(args, 1, ["volume"], "null")})`;
|
|
1702
|
+
return taCall("vwma", site, [src(), vol, finiteOr(pick(args, 2, ["length", "period"], "null"), "0")]);
|
|
1703
|
+
}
|
|
1704
|
+
return taCall("vwma", site, [src(), VOL, finiteOr(pick(args, 1, ["length"], "null"), "0")]);
|
|
1260
1705
|
}
|
|
1261
1706
|
if (method === "cci") {
|
|
1262
|
-
|
|
1707
|
+
if (args.pos.length >= 4) {
|
|
1708
|
+
// Python legacy 4-arg form (oscillators.py:178-187): typical price of
|
|
1709
|
+
// the given (high, low, close) series. Slots resolve kwarg-first.
|
|
1710
|
+
const tp = `((__h, __l, __c) => ((__h != null && __l != null && __c != null) ? (__h + __l + __c) / 3 : __c))(${pick(args, 0, ["high"], "null")}, ${pick(args, 1, ["low"], "null")}, ${pick(args, 2, ["close"], "null")})`;
|
|
1711
|
+
return taCall("cci", site, [tp, finiteOr(pick(args, 3, ["length"], "null"), "0")]);
|
|
1712
|
+
}
|
|
1713
|
+
if (args.pos.length <= 1 && !hasAnyKw(args, ["source", "series"])) {
|
|
1714
|
+
// Python 1-arg period-only form (oscillators.py:159-171): typical price
|
|
1715
|
+
// from the context high/low/close; length gates on _is_period_like.
|
|
1716
|
+
return taCall("cci", site, [TP, periodGate(pick(args, 0, ["length"], "null"), "0")]);
|
|
1717
|
+
}
|
|
1718
|
+
// Python 2-arg form (oscillators.py:172-177): CCI over the source alone.
|
|
1719
|
+
return taCall("cci", site, [src(), finiteOr(pick(args, 1, ["length"], "null"), "0")]);
|
|
1263
1720
|
}
|
|
1264
1721
|
if (method === "mfi") {
|
|
1265
1722
|
if (args.pos.length <= 1) {
|
|
@@ -1336,10 +1793,27 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1336
1793
|
]);
|
|
1337
1794
|
}
|
|
1338
1795
|
if (method === "pivothigh" || method === "pivotlow") {
|
|
1796
|
+
if (args.pos.length === 2 && !hasAnyKw(args, ["source", "series"])) {
|
|
1797
|
+
// Python period-first form (basic.py:1007-1039): both slots gate on
|
|
1798
|
+
// _is_period_like and the source defaults to the context high/low
|
|
1799
|
+
// series. A non-period-like slot falls through to the series arm on
|
|
1800
|
+
// the raw slot values (never confirms on a scalar) — mirror interpret.
|
|
1801
|
+
// Slots resolve kwarg-first (leftbars/rightbars), like interpret callArg.
|
|
1802
|
+
const defSrc = method === "pivotlow" ? LOW : HIGH;
|
|
1803
|
+
const lExpr = pick(args, 0, ["leftbars", "left"], "null");
|
|
1804
|
+
const rExpr = pick(args, 1, ["rightbars", "right"], "null");
|
|
1805
|
+
const leftFt = finiteOr(pick(args, 1, ["leftbars", "left"], "null"), "5");
|
|
1806
|
+
const rightFt = finiteOr(pick(args, 2, ["rightbars", "right"], "null"), "5");
|
|
1807
|
+
const pf = taCall(method, site, [defSrc, "__l", "__r"]);
|
|
1808
|
+
const ft = taCall(method, site, ["__s", leftFt, rightFt]);
|
|
1809
|
+
return `((__l, __r, __s) => (((Number.isInteger(__l) && Number.isInteger(__r))) ? ${pf} : ${ft}))(${lExpr}, ${rExpr}, ${pick(args, 0, ["source", "series"], "null")})`;
|
|
1810
|
+
}
|
|
1811
|
+
// Interpret srcArg: source slot names ["source", "series"], missing → na
|
|
1812
|
+
// (a bare ta.pivothigh() never confirms, like interpret).
|
|
1339
1813
|
return taCall(method, site, [
|
|
1340
|
-
|
|
1341
|
-
pick(args, 1, ["leftbars", "left"], "5"),
|
|
1342
|
-
pick(args, 2, ["rightbars", "right"], "5"),
|
|
1814
|
+
pick(args, 0, ["source", "series"], "null"),
|
|
1815
|
+
finiteOr(pick(args, 1, ["leftbars", "left"], "null"), "5"),
|
|
1816
|
+
finiteOr(pick(args, 2, ["rightbars", "right"], "null"), "5"),
|
|
1343
1817
|
]);
|
|
1344
1818
|
}
|
|
1345
1819
|
if (method === "swma") {
|
|
@@ -1355,7 +1829,10 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1355
1829
|
return taCall("willr", site, [HIGH, LOW, CLOSE, len(0, "14")]);
|
|
1356
1830
|
}
|
|
1357
1831
|
if (method === "vwap") {
|
|
1358
|
-
return taCall("vwap", site, [
|
|
1832
|
+
return taCall("vwap", site, [
|
|
1833
|
+
pick(args, 0, ["source", "series", "src"], HLC3),
|
|
1834
|
+
pick(args, 1, ["volume"], VOL),
|
|
1835
|
+
]);
|
|
1359
1836
|
}
|
|
1360
1837
|
if (method === "sar") {
|
|
1361
1838
|
return taCall("sar", site, [
|
|
@@ -1367,7 +1844,27 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1367
1844
|
]);
|
|
1368
1845
|
}
|
|
1369
1846
|
if (method === "adx") {
|
|
1370
|
-
|
|
1847
|
+
if (args.pos.length >= 4) {
|
|
1848
|
+
// Python legacy 4-arg form (common.py:718-726): (high, low, close,
|
|
1849
|
+
// length) with explicit series. Slots resolve kwarg-first.
|
|
1850
|
+
return taCall("adx", site, [
|
|
1851
|
+
pick(args, 0, ["source", "series"], "null"),
|
|
1852
|
+
pick(args, 1, ["low"], "null"),
|
|
1853
|
+
pick(args, 2, ["close"], "null"),
|
|
1854
|
+
finiteOr(pick(args, 3, ["length"], "null"), "0"),
|
|
1855
|
+
]);
|
|
1856
|
+
}
|
|
1857
|
+
if (args.pos.length === 2 && !hasAnyKw(args, ["high", "low", "close"])) {
|
|
1858
|
+
// Python 2-arg form (diLength, adxSmoothing) (common.py:709-717): both
|
|
1859
|
+
// slots gate on _is_period_like (kwarg-first resolution) and the ADX
|
|
1860
|
+
// period is the SECOND slot — diLength is accepted but unused. A
|
|
1861
|
+
// non-period-like slot → default 14.
|
|
1862
|
+
const period = `(((__di, __sm) => ((Number.isInteger(__di) && Number.isInteger(__sm)) ? __sm : 14))(${pick(args, 0, ["diLength", "length"], "null")}, ${pick(args, 1, ["adxSmoothing", "adxlen"], "null")}))`;
|
|
1863
|
+
return taCall("adx", site, [HIGH, LOW, CLOSE, period]);
|
|
1864
|
+
}
|
|
1865
|
+
// Python 1-arg period-only form gates length via _is_period_like
|
|
1866
|
+
// (common.py ~701): non-period-like → ignored → default 14 (as absent).
|
|
1867
|
+
return taCall("adx", site, [HIGH, LOW, CLOSE, periodGate(pick(args, 0, ["length"], "null"), "14")]);
|
|
1371
1868
|
}
|
|
1372
1869
|
if (method === "correlation") {
|
|
1373
1870
|
return taCall("correlation", site, [
|
|
@@ -1387,20 +1884,216 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1387
1884
|
pick(args, 3, ["volume"], VOL),
|
|
1388
1885
|
]);
|
|
1389
1886
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1887
|
+
// `vpt` is an alias of the pvt kernel on interpret (no ta.vpt engine fn).
|
|
1888
|
+
if (method === "pvt" || method === "vpt" || method === "obv" || method === "nvi" || method === "pvi") {
|
|
1889
|
+
return taCall(method === "vpt" ? "pvt" : method, site, [
|
|
1392
1890
|
pick(args, 0, ["source", "close"], CLOSE),
|
|
1393
1891
|
pick(args, 1, ["volume"], VOL),
|
|
1394
1892
|
]);
|
|
1395
1893
|
}
|
|
1396
1894
|
if (method === "wad") {
|
|
1397
|
-
return taCall("wad", site, [
|
|
1895
|
+
return taCall("wad", site, [
|
|
1896
|
+
pick(args, 0, ["high"], HIGH),
|
|
1897
|
+
pick(args, 1, ["low"], LOW),
|
|
1898
|
+
pick(args, 2, ["close"], CLOSE),
|
|
1899
|
+
pick(args, 3, ["volume"], VOL),
|
|
1900
|
+
]);
|
|
1398
1901
|
}
|
|
1399
1902
|
if (method === "iii") {
|
|
1400
1903
|
return taCall("iii", site, [HIGH, LOW, CLOSE, VOL]);
|
|
1401
1904
|
}
|
|
1402
1905
|
if (method === "wvad") {
|
|
1403
|
-
|
|
1906
|
+
if (args.pos.length >= 4 || Object.hasOwn(args.kw, "high")) {
|
|
1907
|
+
return taCall("wvad", site, [
|
|
1908
|
+
pick(args, 0, ["high"], HIGH),
|
|
1909
|
+
pick(args, 1, ["low"], LOW),
|
|
1910
|
+
pick(args, 2, ["close"], CLOSE),
|
|
1911
|
+
pick(args, 3, ["volume"], VOL),
|
|
1912
|
+
pick(args, 4, ["length", "period"], "20"),
|
|
1913
|
+
]);
|
|
1914
|
+
}
|
|
1915
|
+
return taCall("wvad", site, [HIGH, LOW, CLOSE, VOL, pick(args, 0, ["length", "period"], "20")]);
|
|
1916
|
+
}
|
|
1917
|
+
if (method === "cmf") {
|
|
1918
|
+
if (args.pos.length >= 5) {
|
|
1919
|
+
return taCall("cmf", site, [
|
|
1920
|
+
pick(args, 1, ["high"], HIGH),
|
|
1921
|
+
pick(args, 2, ["low"], LOW),
|
|
1922
|
+
pick(args, 0, ["close", "source"], CLOSE),
|
|
1923
|
+
pick(args, 3, ["volume"], VOL),
|
|
1924
|
+
pick(args, 4, ["length", "period"], "20"),
|
|
1925
|
+
]);
|
|
1926
|
+
}
|
|
1927
|
+
return taCall("cmf", site, [HIGH, LOW, CLOSE, VOL, pick(args, 0, ["length", "period"], "20")]);
|
|
1928
|
+
}
|
|
1929
|
+
if (method === "klinger") {
|
|
1930
|
+
return taCall("klinger", site, [
|
|
1931
|
+
pick(args, 2, ["close"], CLOSE),
|
|
1932
|
+
pick(args, 3, ["volume"], VOL),
|
|
1933
|
+
pick(args, 4, ["fast_period", "fast", "fastlen"], "0"),
|
|
1934
|
+
pick(args, 5, ["slow_period", "slow", "slowlen"], "0"),
|
|
1935
|
+
]);
|
|
1936
|
+
}
|
|
1937
|
+
if (method === "ao") {
|
|
1938
|
+
// SMA(hl2, fast) − SMA(hl2, slow); high/low always come from the bar context.
|
|
1939
|
+
return taCall("ao", site, [
|
|
1940
|
+
HIGH,
|
|
1941
|
+
LOW,
|
|
1942
|
+
pick(args, 0, ["fast", "fastlen"], "5"),
|
|
1943
|
+
pick(args, 1, ["slow", "slowlen"], "34"),
|
|
1944
|
+
]);
|
|
1945
|
+
}
|
|
1946
|
+
if (method === "aroon") {
|
|
1947
|
+
// Kernel returns {down, up}; interpret exposes the tuple [down, up].
|
|
1948
|
+
const call = taCall("aroon", site, [HIGH, LOW, pick(args, 0, ["length"], "14")]);
|
|
1949
|
+
return `(() => { const __r = ${call}; return [__r.down, __r.up]; })()`;
|
|
1950
|
+
}
|
|
1951
|
+
if (method === "uo") {
|
|
1952
|
+
return taCall("uo", site, [
|
|
1953
|
+
HIGH,
|
|
1954
|
+
LOW,
|
|
1955
|
+
CLOSE,
|
|
1956
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
1957
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1958
|
+
finiteOr(pick(args, 2, ["length"], "null"), "0"),
|
|
1959
|
+
]);
|
|
1960
|
+
}
|
|
1961
|
+
if (method === "rci") {
|
|
1962
|
+
// srcArg: missing source is na, not close.
|
|
1963
|
+
return taCall("rci", site, [
|
|
1964
|
+
pick(args, 0, ["source", "series"], "null"),
|
|
1965
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1966
|
+
]);
|
|
1967
|
+
}
|
|
1968
|
+
if (method === "dpo") {
|
|
1969
|
+
return taCall("dpo", site, [CLOSE, finiteOr(pick(args, 0, ["length"], "null"), "0")]);
|
|
1970
|
+
}
|
|
1971
|
+
if (method === "kst") {
|
|
1972
|
+
return taCall("kst", site, [
|
|
1973
|
+
CLOSE,
|
|
1974
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
1975
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1976
|
+
finiteOr(pick(args, 2, ["length"], "null"), "0"),
|
|
1977
|
+
finiteOr(pick(args, 3, ["length"], "null"), "0"),
|
|
1978
|
+
]);
|
|
1979
|
+
}
|
|
1980
|
+
if (method === "stochrsi") {
|
|
1981
|
+
// Engine method is stochRsi; record already branded {stochrsi, signal}.
|
|
1982
|
+
return taCall("stochRsi", site, [
|
|
1983
|
+
CLOSE,
|
|
1984
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
1985
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1986
|
+
]);
|
|
1987
|
+
}
|
|
1988
|
+
if (method === "donchian") {
|
|
1989
|
+
return taCall("donchian", site, [HIGH, LOW, finiteOr(pick(args, 0, ["length"], "null"), "0")]);
|
|
1990
|
+
}
|
|
1991
|
+
if (method === "ichimoku") {
|
|
1992
|
+
return taCall("ichimoku", site, [
|
|
1993
|
+
HIGH,
|
|
1994
|
+
LOW,
|
|
1995
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
1996
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1997
|
+
]);
|
|
1998
|
+
}
|
|
1999
|
+
if (method === "bb_pct") {
|
|
2000
|
+
return taCall("bbPct", site, [
|
|
2001
|
+
CLOSE,
|
|
2002
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
2003
|
+
finiteOr(pick(args, 1, ["std_dev", "mult", "std"], "null"), "2"),
|
|
2004
|
+
]);
|
|
2005
|
+
}
|
|
2006
|
+
if (method === "emv") {
|
|
2007
|
+
return taCall("emv", site, [HIGH, LOW, VOL, finiteOr(pick(args, 0, ["length"], "null"), "0")]);
|
|
2008
|
+
}
|
|
2009
|
+
if (method === "fractal") {
|
|
2010
|
+
return taCall("fractal", site, [HIGH, LOW, finiteOr(pick(args, 0, ["length"], "null"), "0")]);
|
|
2011
|
+
}
|
|
2012
|
+
if (method === "atr_stop") {
|
|
2013
|
+
// Kernel has no call-site; missing multiplier → 2 (interpret numArg).
|
|
2014
|
+
return `__h.ta.atrStop(${CLOSE}, ${pick(args, 0, ["atr", "atr_value"], "null")}, ${finiteOr(pick(args, 1, ["multiplier", "mult"], "null"), "2")})`;
|
|
2015
|
+
}
|
|
2016
|
+
if (method === "zigzag") {
|
|
2017
|
+
const call = taCall("zigzag", site, [
|
|
2018
|
+
pick(args, 0, ["source", "series"], "null"),
|
|
2019
|
+
finiteOr(pick(args, 1, ["threshold", "percent", "deviation"], "null"), "5"),
|
|
2020
|
+
]);
|
|
2021
|
+
return `(() => { const __r = ${call}; return [__r.high, __r.low, __r.dir]; })()`;
|
|
2022
|
+
}
|
|
2023
|
+
if (method === "hammer") {
|
|
2024
|
+
return `__h.ta.hammer(${cellOf(pick(args, 0, ["open"], "null"))}, ${cellOf(pick(args, 1, ["high"], "null"))}, ${cellOf(pick(args, 2, ["low"], "null"))}, ${cellOf(pick(args, 3, ["close"], "null"))})`;
|
|
2025
|
+
}
|
|
2026
|
+
if (method === "gap_detector") {
|
|
2027
|
+
return `__h.ta.gapDetector(${cellOf(pick(args, 0, ["high"], "null"))}, ${cellOf(pick(args, 1, ["low"], "null"))}, ${cellOf(pick(args, 2, ["prev_close", "prev"], "null"))})`;
|
|
2028
|
+
}
|
|
2029
|
+
if (method === "macd_signal") {
|
|
2030
|
+
return `__h.ta.macdSignal(${cellOf(pick(args, 0, ["macd_line", "macd"], "null"))}, ${cellOf(pick(args, 1, ["signal_line", "signal"], "null"))})`;
|
|
2031
|
+
}
|
|
2032
|
+
if (method === "acceleration_factor") {
|
|
2033
|
+
return taCall("accelerationFactor", site, [
|
|
2034
|
+
cellOf(pick(args, 0, ["momentum_list", "momentum", "source", "series"], "null")),
|
|
2035
|
+
cellOf(pick(args, 1, ["period", "length"], "null")),
|
|
2036
|
+
]);
|
|
2037
|
+
}
|
|
2038
|
+
if (method === "stoch_smooth") {
|
|
2039
|
+
return `__h.ta.stochSmooth(${cellOf(pick(args, 0, ["high"], "null"))}, ${cellOf(pick(args, 1, ["low"], "null"))}, ${cellOf(pick(args, 2, ["close"], "null"))}, ${cellOf(pick(args, 3, ["period", "length"], "null"))}, ${cellOf(pick(args, 4, ["smooth_k", "k"], "null"))}, ${cellOf(pick(args, 5, ["smooth_d", "d"], "null"))})`;
|
|
2040
|
+
}
|
|
2041
|
+
if (method === "engulfing") {
|
|
2042
|
+
// Bar-mode sentinel. Args are still evaluated (interpret reads all four).
|
|
2043
|
+
return `(() => { ${cellOf(pick(args, 0, ["open"], "null"))}; ${cellOf(pick(args, 1, ["high"], "null"))}; ${cellOf(pick(args, 2, ["low"], "null"))}; ${cellOf(pick(args, 3, ["close"], "null"))}; return __h.ta.engulfing(); })()`;
|
|
2044
|
+
}
|
|
2045
|
+
if (method === "inside_bar_pattern") {
|
|
2046
|
+
return `(() => { ${cellOf(pick(args, 0, ["high"], "null"))}; ${cellOf(pick(args, 1, ["low"], "null"))}; return __h.ta.insideBarPattern(); })()`;
|
|
2047
|
+
}
|
|
2048
|
+
if (method === "double_top_bottom") {
|
|
2049
|
+
return `(() => { ${cellOf(pick(args, 0, ["high"], "null"))}; ${cellOf(pick(args, 1, ["low"], "null"))}; return __h.ta.doubleTopBottom(${cellOf(pick(args, 2, ["period", "length"], "null"))}); })()`;
|
|
2050
|
+
}
|
|
2051
|
+
if (method === "voi") {
|
|
2052
|
+
return `__h.ta.voi(${cellOf(pick(args, 0, ["buy_volume", "buy"], "null"))}, ${cellOf(pick(args, 1, ["sell_volume", "sell"], "null"))})`;
|
|
2053
|
+
}
|
|
2054
|
+
if (method === "bid_ask_imbalance") {
|
|
2055
|
+
return `__h.ta.bidAskImbalance(${cellOf(pick(args, 0, ["bid_size", "bid"], "null"))}, ${cellOf(pick(args, 1, ["ask_size", "ask"], "null"))}, ${cellOf(pick(args, 2, ["bid_price"], "null"))}, ${cellOf(pick(args, 3, ["ask_price"], "null"))})`;
|
|
2056
|
+
}
|
|
2057
|
+
if (method === "rsi_oversold_overbought") {
|
|
2058
|
+
return `__h.ta.rsiOversoldOverbought(${cellOf(pick(args, 0, ["rsi_series", "rsi", "source"], "null"))}, ${cellOf(pick(args, 1, ["oversold_level", "oversold"], "null"))}, ${cellOf(pick(args, 2, ["overbought_level", "overbought"], "null"))})`;
|
|
2059
|
+
}
|
|
2060
|
+
if (method === "rsi_divergence") {
|
|
2061
|
+
// Missing period is 0 (interpret `period ?? 0`), not the engine's null → na.
|
|
2062
|
+
const period = `((__n) => (__n == null ? 0 : __n))(${cellOf(pick(args, 1, ["period", "length"], "null"))})`;
|
|
2063
|
+
return `__h.ta.rsiDivergence(${cellList(pick(args, 0, ["rsi_series", "series", "source"], "null"))}, ${period})`;
|
|
2064
|
+
}
|
|
2065
|
+
if (method === "cumulative_delta") {
|
|
2066
|
+
return taCall("cumulativeDelta", site, [
|
|
2067
|
+
cellOf(pick(args, 0, ["close", "source"], "null")),
|
|
2068
|
+
cellOf(pick(args, 1, ["volume"], "null")),
|
|
2069
|
+
cellOf(pick(args, 2, ["period", "length"], "null")),
|
|
2070
|
+
]);
|
|
2071
|
+
}
|
|
2072
|
+
if (method === "volume_momentum") {
|
|
2073
|
+
return taCall("volumeMomentum", site, [
|
|
2074
|
+
cellOf(pick(args, 0, ["volume", "source"], "null")),
|
|
2075
|
+
cellOf(pick(args, 1, ["period", "length"], "null")),
|
|
2076
|
+
]);
|
|
2077
|
+
}
|
|
2078
|
+
if (method === "volume_profile_high") {
|
|
2079
|
+
return `__h.ta.volumeProfileHigh(${cellList(pick(args, 0, ["close", "price", "source", "series"], "null"))}, ${cellList(pick(args, 1, ["volume"], "null"))})`;
|
|
2080
|
+
}
|
|
2081
|
+
if (method === "volume_profile_low") {
|
|
2082
|
+
return taCall("volumeProfileLow", site, [
|
|
2083
|
+
cellOf(pick(args, 0, ["close", "source"], "null")),
|
|
2084
|
+
cellOf(pick(args, 1, ["volume"], "null")),
|
|
2085
|
+
cellOf(pick(args, 2, ["period", "length"], "null")),
|
|
2086
|
+
cellOf(pick(args, 3, ["levels"], "null")),
|
|
2087
|
+
]);
|
|
2088
|
+
}
|
|
2089
|
+
if (method === "volume_thrust") {
|
|
2090
|
+
const sens = cellOf(pick(args, 3, ["sensitivity"], "null"));
|
|
2091
|
+
return taCall("volumeThrust", site, [
|
|
2092
|
+
cellOf(pick(args, 0, ["close", "source"], "null")),
|
|
2093
|
+
cellOf(pick(args, 1, ["volume"], "null")),
|
|
2094
|
+
cellOf(pick(args, 2, ["volume_sma", "vol_sma", "sma"], "null")),
|
|
2095
|
+
`((__n) => (__n == null ? 0.3 : __n))(${sens})`,
|
|
2096
|
+
]);
|
|
1404
2097
|
}
|
|
1405
2098
|
return "null";
|
|
1406
2099
|
}
|