@hoox-sh/pynets 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +17 -8
- package/dist/browser.js +89 -84
- package/dist/index.js +19482 -12937
- 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 +187 -34
- package/src/runtime/compile/emit.ts +266 -9
- package/src/runtime/compile/emit_call.ts +425 -35
- package/src/runtime/compile/runtime.ts +62 -2
- package/src/runtime/compile/strategy.ts +87 -6
- package/src/runtime/compile/types.ts +2 -0
- package/src/runtime/drawings.ts +405 -8
- package/src/runtime/interpret.ts +2201 -277
- package/src/runtime/matrix.ts +371 -42
- package/src/runtime/strategy.ts +695 -33
- package/src/runtime/ta.ts +654 -20
- package/src/runtime/timeframe.ts +199 -0
|
@@ -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,40 @@ 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
|
+
]);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Bare `ta.<attr>` auto-call whitelist. Mirrors interpret's evalTaAttr
|
|
143
|
+
* (interpret.ts) exactly: only these attributes auto-invoke a zero-arg TA
|
|
144
|
+
* kernel on a bare attribute load; anything else is na on both backends.
|
|
145
|
+
* `aroon` is deliberately absent — interpret's evalTaAttr does not handle it.
|
|
146
|
+
*/
|
|
147
|
+
export const TA_BARE_ATTRS = new Set([
|
|
148
|
+
"accdist",
|
|
149
|
+
"ad",
|
|
150
|
+
"obv",
|
|
151
|
+
"tr",
|
|
152
|
+
"vwap",
|
|
153
|
+
"pvt",
|
|
154
|
+
"vpt",
|
|
155
|
+
"nvi",
|
|
156
|
+
"pvi",
|
|
157
|
+
"wad",
|
|
158
|
+
"wvad",
|
|
159
|
+
"cmf",
|
|
160
|
+
"ao",
|
|
109
161
|
]);
|
|
110
162
|
|
|
111
163
|
const SRC_LEN = new Set([
|
|
@@ -120,8 +172,6 @@ const SRC_LEN = new Set([
|
|
|
120
172
|
"sum",
|
|
121
173
|
"dema",
|
|
122
174
|
"tema",
|
|
123
|
-
"highestbars",
|
|
124
|
-
"lowestbars",
|
|
125
175
|
"dev",
|
|
126
176
|
"variance",
|
|
127
177
|
"median",
|
|
@@ -141,6 +191,8 @@ const PLOT_FUNCS = new Set([
|
|
|
141
191
|
"hline",
|
|
142
192
|
"bgcolor",
|
|
143
193
|
"barcolor",
|
|
194
|
+
"plotcandle",
|
|
195
|
+
"plotbar",
|
|
144
196
|
]);
|
|
145
197
|
|
|
146
198
|
const STRATEGY_ACTIONS = new Set([
|
|
@@ -160,6 +212,10 @@ const STRATEGY_QUERIES = new Set([
|
|
|
160
212
|
"strategy_openprofit",
|
|
161
213
|
"strategy_opentrades",
|
|
162
214
|
"strategy_closedtrades",
|
|
215
|
+
"strategy_leverage",
|
|
216
|
+
"strategy_margin_liquidation_price",
|
|
217
|
+
"strategy_position_avg_price",
|
|
218
|
+
"strategy_initial_capital",
|
|
163
219
|
]);
|
|
164
220
|
|
|
165
221
|
const CHART_SERIES_ARR: Record<string, string> = {
|
|
@@ -232,7 +288,7 @@ export function emitCall(ctx: EmitCtx, node: Call, visit: VisitFn): string {
|
|
|
232
288
|
if (PLOT_FUNCS.has(name)) return emitPlot(ctx, name, args);
|
|
233
289
|
|
|
234
290
|
if (name === "indicator" || name === "study" || name === "library") return "null";
|
|
235
|
-
if (name === "strategy") return
|
|
291
|
+
if (name === "strategy") return emitStrategyDecl(ctx, args);
|
|
236
292
|
|
|
237
293
|
if (name === "na") {
|
|
238
294
|
if (args.pos.length === 0 && args.kw.x == null && args.kw.source == null) return "null";
|
|
@@ -275,6 +331,19 @@ export function emitCall(ctx: EmitCtx, node: Call, visit: VisitFn): string {
|
|
|
275
331
|
}
|
|
276
332
|
return "86400";
|
|
277
333
|
}
|
|
334
|
+
if (name === "timeframe_change") {
|
|
335
|
+
// Prev bar time is `time_arr[__bar_idx-1]`; bar 0 has no prev (interpret series.get(1)).
|
|
336
|
+
return `__h.timeframeChange(${pick(args, 0, ["timeframe", "tf"], "null")}, time, (__bar_idx > 0 ? time_arr[__bar_idx - 1] : null), __bar_idx)`;
|
|
337
|
+
}
|
|
338
|
+
if (name === "time_close") {
|
|
339
|
+
return `__h.timeClose(__bar_idx, time_arr)`;
|
|
340
|
+
}
|
|
341
|
+
if (name === "syminfo_prefix") {
|
|
342
|
+
if (hasArg(args, 0, ["tickerid", "symbol"])) {
|
|
343
|
+
return `__h.syminfoPrefix(${pick(args, 0, ["tickerid", "symbol"], "null")})`;
|
|
344
|
+
}
|
|
345
|
+
return `__h.syminfoPrefix()`;
|
|
346
|
+
}
|
|
278
347
|
if (name === "ticker_heikinashi" || name === "heikinashi") {
|
|
279
348
|
ctx.needsHeikinashi = true;
|
|
280
349
|
return `"__HEIKINASHI__"`;
|
|
@@ -432,6 +501,30 @@ function hasArg(args: CallArgs, index: number, names: string[]): boolean {
|
|
|
432
501
|
return index >= 0 && index < args.pos.length;
|
|
433
502
|
}
|
|
434
503
|
|
|
504
|
+
/** True when a keyword argument named one of *names* is present (interpret hasKwarg). */
|
|
505
|
+
function hasAnyKw(args: CallArgs, names: string[]): boolean {
|
|
506
|
+
return names.some((n) => Object.hasOwn(args.kw, n));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Runtime-safe `_is_period_like` gate (interpret `periodLikeCell`): only whole
|
|
511
|
+
* numbers pass and are returned as-is; everything else (na, fractional,
|
|
512
|
+
* series samples) collapses to *fb* — mirroring `periodLikeArg(...) ?? fb`.
|
|
513
|
+
* Constant args could be decided at emit time, but the runtime form is
|
|
514
|
+
* equally exact and keeps one code path for constants and series alike.
|
|
515
|
+
*/
|
|
516
|
+
function periodGate(expr: string, fb: string): string {
|
|
517
|
+
return `((__p) => (Number.isInteger(__p) ? __p : ${fb}))(${expr})`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Interpret `lenArg` / `lenOrDefault` analog: a null (na) or non-finite slot
|
|
522
|
+
* collapses to *fb* instead of reaching the kernel raw.
|
|
523
|
+
*/
|
|
524
|
+
function finiteOr(expr: string, fb: string): string {
|
|
525
|
+
return `((__v) => (__v != null && Number.isFinite(__v) ? __v : ${fb}))(${expr})`;
|
|
526
|
+
}
|
|
527
|
+
|
|
435
528
|
const DRAW_KINDS = new Set(["label", "line", "box", "table", "polyline", "linefill"]);
|
|
436
529
|
|
|
437
530
|
const ARRAY_SIGS: Record<string, string[][]> = {
|
|
@@ -452,7 +545,7 @@ const ARRAY_SIGS: Record<string, string[][]> = {
|
|
|
452
545
|
fill: [["id"], ["value"]],
|
|
453
546
|
copy: [["id"]],
|
|
454
547
|
reverse: [["id"]],
|
|
455
|
-
sort: [["id"], ["order"]],
|
|
548
|
+
sort: [["id"], ["order"], ["sort_field"]],
|
|
456
549
|
concat: [["id"], ["other", "id2"]],
|
|
457
550
|
slice: [["id"], ["index_from", "from"], ["index_to", "to"]],
|
|
458
551
|
indexof: [["id"], ["value"]],
|
|
@@ -476,10 +569,10 @@ const ARRAY_SIGS: Record<string, string[][]> = {
|
|
|
476
569
|
percentile_linear_interpolation: [["id"], ["percentage", "percent"]],
|
|
477
570
|
percentile_nearest: [["id"], ["percentage", "percent"]],
|
|
478
571
|
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"]],
|
|
572
|
+
binary_search: [["id"], ["value"], ["sort_field"]],
|
|
573
|
+
binary_search_leftmost: [["id"], ["value"], ["sort_field"]],
|
|
574
|
+
binary_search_rightmost: [["id"], ["value"], ["sort_field"]],
|
|
575
|
+
sort_indices: [["id"], ["order"], ["sort_field"]],
|
|
483
576
|
covariance: [["id"], ["id2", "other"], ["biased"]],
|
|
484
577
|
};
|
|
485
578
|
|
|
@@ -541,7 +634,8 @@ const MATRIX_SIGS: Record<string, string[][]> = {
|
|
|
541
634
|
concat: [["id"], ["other"]],
|
|
542
635
|
reshape: [["id"], ["rows"], ["columns", "cols"]],
|
|
543
636
|
reverse: [["id"]],
|
|
544
|
-
sort: [["id"], ["column", "col"], ["order"]],
|
|
637
|
+
sort: [["id"], ["column", "col"], ["order"], ["sort_field"]],
|
|
638
|
+
sort_indices: [["id"], ["column", "col"], ["order"], ["sort_field"]],
|
|
545
639
|
eigenvalues: [["id"]],
|
|
546
640
|
eigenvectors: [["id"]],
|
|
547
641
|
pow: [["id"], ["power", "n"]],
|
|
@@ -554,6 +648,26 @@ const MATRIX_SIGS: Record<string, string[][]> = {
|
|
|
554
648
|
remove_col: [["id"], ["column", "index"]],
|
|
555
649
|
remove_column: [["id"], ["column", "index"]],
|
|
556
650
|
submatrix: [["id"], ["from_row"], ["to_row"], ["from_column", "from_col"], ["to_column", "to_col"]],
|
|
651
|
+
sum_row: [["id"], ["row", "index"]],
|
|
652
|
+
avg_row: [["id"], ["row", "index"]],
|
|
653
|
+
min_row: [["id"], ["row", "index"]],
|
|
654
|
+
max_row: [["id"], ["row", "index"]],
|
|
655
|
+
mode_row: [["id"], ["row", "index"]],
|
|
656
|
+
sum_col: [["id"], ["column", "col", "index"]],
|
|
657
|
+
avg_col: [["id"], ["column", "col", "index"]],
|
|
658
|
+
min_col: [["id"], ["column", "col", "index"]],
|
|
659
|
+
max_col: [["id"], ["column", "col", "index"]],
|
|
660
|
+
mode_col: [["id"], ["column", "col", "index"]],
|
|
661
|
+
copy_row: [["id"], ["row", "index"]],
|
|
662
|
+
copy_col: [["id"], ["column", "col", "index"]],
|
|
663
|
+
fill_row: [["id"], ["row", "index"], ["value"]],
|
|
664
|
+
fill_col: [["id"], ["column", "col", "index"], ["value"]],
|
|
665
|
+
fill_diagonal: [["id"], ["value"]],
|
|
666
|
+
reverse_rows: [["id"]],
|
|
667
|
+
reverse_cols: [["id"]],
|
|
668
|
+
stdev: [["id"]],
|
|
669
|
+
variance: [["id"]],
|
|
670
|
+
mode_all: [["id"]],
|
|
557
671
|
};
|
|
558
672
|
|
|
559
673
|
function nsMethod(name: string, prefix: string): string | null {
|
|
@@ -562,6 +676,8 @@ function nsMethod(name: string, prefix: string): string | null {
|
|
|
562
676
|
}
|
|
563
677
|
|
|
564
678
|
function ctorMethod(method: string): string {
|
|
679
|
+
// Every Python `array.new*` spelling (newint, new_float, new_chart.point, …)
|
|
680
|
+
// lowers to the same PineArray constructor as `array.new`.
|
|
565
681
|
return method === "from" || method.startsWith("new") ? "new" : method;
|
|
566
682
|
}
|
|
567
683
|
|
|
@@ -891,6 +1007,26 @@ function isStrategyAction(name: string, method: string): boolean {
|
|
|
891
1007
|
);
|
|
892
1008
|
}
|
|
893
1009
|
|
|
1010
|
+
/** `strategy(...)` declaration — kwargs only, matching interpret `applyStrategyDecl`. */
|
|
1011
|
+
function emitStrategyDecl(ctx: EmitCtx, args: CallArgs): string {
|
|
1012
|
+
ctx.usesStrategy = true;
|
|
1013
|
+
const fields: string[] = [];
|
|
1014
|
+
const add = (names: string[], key: string): void => {
|
|
1015
|
+
if (hasArg(args, -1, names)) fields.push(`${key}: ${pick(args, -1, names, "null")}`);
|
|
1016
|
+
};
|
|
1017
|
+
add(["commission", "commission_value"], "commission");
|
|
1018
|
+
add(["slippage"], "slippage");
|
|
1019
|
+
add(["pyramiding"], "pyramiding");
|
|
1020
|
+
add(["avg_price_model"], "avg_price_model");
|
|
1021
|
+
add(["leverage"], "leverage");
|
|
1022
|
+
add(["margin_long"], "margin_long");
|
|
1023
|
+
add(["margin_short"], "margin_short");
|
|
1024
|
+
add(["default_qty_type"], "default_qty_type");
|
|
1025
|
+
add(["default_qty_value"], "default_qty_value");
|
|
1026
|
+
add(["initial_capital"], "initial_capital");
|
|
1027
|
+
return `__h.strategy.configure({${fields.join(", ")}})`;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
894
1030
|
function emitStrategyEntry(method: "entry" | "order", args: CallArgs): string {
|
|
895
1031
|
const id = pick(args, 0, ["id"], "null");
|
|
896
1032
|
const direction = pick(args, 1, ["direction"], "null");
|
|
@@ -926,10 +1062,22 @@ function emitStrategy(ctx: EmitCtx, name: string, args: CallArgs): string {
|
|
|
926
1062
|
return "__h.strategy.close_all()";
|
|
927
1063
|
case "exit": {
|
|
928
1064
|
ctx.usesStrategy = true;
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1065
|
+
const id = pick(args, 0, ["id"], "null");
|
|
1066
|
+
const fields: string[] = [];
|
|
1067
|
+
const add = (index: number, names: string[], key: string): void => {
|
|
1068
|
+
if (hasArg(args, index, names)) fields.push(`${key}: ${pick(args, index, names, "null")}`);
|
|
1069
|
+
};
|
|
1070
|
+
add(1, ["from_entry"], "from_entry");
|
|
1071
|
+
add(2, ["qty"], "qty");
|
|
1072
|
+
add(3, ["qty_percent"], "qty_percent");
|
|
1073
|
+
add(4, ["profit"], "profit");
|
|
1074
|
+
add(5, ["limit"], "limit");
|
|
1075
|
+
add(6, ["loss"], "loss");
|
|
1076
|
+
add(7, ["stop"], "stop");
|
|
1077
|
+
add(8, ["trail_price"], "trail_price");
|
|
1078
|
+
add(9, ["trail_points"], "trail_points");
|
|
1079
|
+
add(10, ["trail_offset"], "trail_offset");
|
|
1080
|
+
return `__h.strategy.exit(${id}, {${fields.join(", ")}})`;
|
|
933
1081
|
}
|
|
934
1082
|
case "cancel":
|
|
935
1083
|
ctx.usesStrategy = true;
|
|
@@ -943,6 +1091,10 @@ function emitStrategy(ctx: EmitCtx, name: string, args: CallArgs): string {
|
|
|
943
1091
|
case "openprofit":
|
|
944
1092
|
case "opentrades":
|
|
945
1093
|
case "closedtrades":
|
|
1094
|
+
case "leverage":
|
|
1095
|
+
case "margin_liquidation_price":
|
|
1096
|
+
case "position_avg_price":
|
|
1097
|
+
case "initial_capital":
|
|
946
1098
|
ctx.usesStrategy = true;
|
|
947
1099
|
return `__h.strategy.${method}()`;
|
|
948
1100
|
case "risk_allow_entry_in":
|
|
@@ -992,7 +1144,7 @@ function packUserFuncArgs(ctx: EmitCtx, name: string, args: CallArgs): string[]
|
|
|
992
1144
|
return packed;
|
|
993
1145
|
}
|
|
994
1146
|
|
|
995
|
-
function emitPlot(ctx: EmitCtx,
|
|
1147
|
+
function emitPlot(ctx: EmitCtx, fname: string, args: CallArgs): string {
|
|
996
1148
|
let title: string | null = constString(args.kwNodes.title);
|
|
997
1149
|
if (title == null) {
|
|
998
1150
|
for (const n of args.posNodes) {
|
|
@@ -1003,8 +1155,16 @@ function emitPlot(ctx: EmitCtx, _fname: string, args: CallArgs): string {
|
|
|
1003
1155
|
}
|
|
1004
1156
|
}
|
|
1005
1157
|
}
|
|
1006
|
-
if (title == null || title === "")
|
|
1007
|
-
|
|
1158
|
+
if (title == null || title === "") {
|
|
1159
|
+
if (fname === "plotcandle") title = "candles";
|
|
1160
|
+
else if (fname === "plotbar") title = "bars";
|
|
1161
|
+
else title = `plot_${ctx.plots.length}`;
|
|
1162
|
+
}
|
|
1163
|
+
// Python primary column for plotcandle/plotbar is close (arg 3), not open.
|
|
1164
|
+
const ohlc = fname === "plotcandle" || fname === "plotbar";
|
|
1165
|
+
const series = ohlc
|
|
1166
|
+
? pick(args, 3, ["close"], "null")
|
|
1167
|
+
: pick(args, 0, ["series", "source", "price"], "null");
|
|
1008
1168
|
const i = ctx.plots.length;
|
|
1009
1169
|
ctx.plots.push({ title });
|
|
1010
1170
|
return `plot_${i}[__bar_idx] = __h.naNum(${series})`;
|
|
@@ -1084,9 +1244,13 @@ function emitMath(name: string, args: CallArgs): string | null {
|
|
|
1084
1244
|
name === "todegrees" ||
|
|
1085
1245
|
name === "toradians" ||
|
|
1086
1246
|
name === "iff" ||
|
|
1087
|
-
name === "fixnan"
|
|
1247
|
+
name === "fixnan" ||
|
|
1248
|
+
name === "round_to_mintick";
|
|
1088
1249
|
if (!isNs && !isBareMath) return null;
|
|
1089
1250
|
|
|
1251
|
+
if (bare === "round_to_mintick") {
|
|
1252
|
+
return `__h.roundToMintick(${pick(args, 0, ["number", "x"], "null")})`;
|
|
1253
|
+
}
|
|
1090
1254
|
if (bare === "iff") {
|
|
1091
1255
|
const cond = pick(args, 0, ["condition", "cond"], "null");
|
|
1092
1256
|
const a = pick(args, 1, ["then", "if_true"], "null");
|
|
@@ -1176,6 +1340,7 @@ function taCall(method: string, site: string, parts: string[]): string {
|
|
|
1176
1340
|
|
|
1177
1341
|
function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
1178
1342
|
if (!TA_METHODS.has(method)) return "null";
|
|
1343
|
+
if (method === "ad") method = "accdist";
|
|
1179
1344
|
const site = allocSite(ctx);
|
|
1180
1345
|
const src = () => pick(args, 0, ["source", "series", "src"], CLOSE);
|
|
1181
1346
|
const len = (index: number, fb: string, names: string[] = ["length", "len", "period"]) =>
|
|
@@ -1187,8 +1352,27 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1187
1352
|
if (SRC_LEN1.has(method)) {
|
|
1188
1353
|
return taCall(method, site, [src(), len(1, "1")]);
|
|
1189
1354
|
}
|
|
1190
|
-
if (method === "highest" || method === "lowest"
|
|
1191
|
-
const defSrc = method === "lowest"
|
|
1355
|
+
if (method === "highest" || method === "lowest") {
|
|
1356
|
+
const defSrc = method === "lowest" ? LOW : HIGH;
|
|
1357
|
+
if (args.pos.length <= 1 && !hasAnyKw(args, ["source", "series"])) {
|
|
1358
|
+
// Python 1-arg period-only form (basic.py:222-246): source defaults to
|
|
1359
|
+
// the context high/low series and the length gates on _is_period_like
|
|
1360
|
+
// → non-period-like → na period 0 (interpret evalExtraTa).
|
|
1361
|
+
return taCall(method, site, [defSrc, periodGate(pick(args, 0, ["length"], "null"), "0")]);
|
|
1362
|
+
}
|
|
1363
|
+
return taCall(method, site, [src(), finiteOr(pick(args, 1, ["length"], "null"), "0")]);
|
|
1364
|
+
}
|
|
1365
|
+
if (method === "highestbars" || method === "lowestbars") {
|
|
1366
|
+
const defSrc = method === "lowestbars" ? LOW : HIGH;
|
|
1367
|
+
if (args.pos.length <= 1 && !hasAnyKw(args, ["source", "series"])) {
|
|
1368
|
+
// Python 1-arg period-only form (basic.py:248-291): context high/low
|
|
1369
|
+
// source, length gated like ta.highest (interpret evalExtraTa).
|
|
1370
|
+
return taCall(method, site, [defSrc, periodGate(pick(args, 0, ["length"], "null"), "0")]);
|
|
1371
|
+
}
|
|
1372
|
+
return taCall(method, site, [src(), finiteOr(pick(args, 1, ["length"], "null"), "0")]);
|
|
1373
|
+
}
|
|
1374
|
+
if (method === "max" || method === "min") {
|
|
1375
|
+
const defSrc = method === "min" ? LOW : HIGH;
|
|
1192
1376
|
if (args.pos.length >= 2 || Object.hasOwn(args.kw, "source") || Object.hasOwn(args.kw, "series")) {
|
|
1193
1377
|
return taCall(method, site, [src(), len(1, "14")]);
|
|
1194
1378
|
}
|
|
@@ -1224,10 +1408,24 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1224
1408
|
return `(() => { const __r = ${call}; return [__r.macd, __r.signal, __r.hist]; })()`;
|
|
1225
1409
|
}
|
|
1226
1410
|
if (method === "bb") {
|
|
1411
|
+
if (args.pos.length === 2 && !hasAnyKw(args, ["source", "series"])) {
|
|
1412
|
+
// Python period-first form (basic.py:394-401): (length, mult) with the
|
|
1413
|
+
// series defaulting to the context close. Dispatch gates slot 0 on
|
|
1414
|
+
// _is_period_like per bar (kwarg-first resolution, like interpret
|
|
1415
|
+
// callArg) — a non-period-like slot falls through to the series arm on
|
|
1416
|
+
// the raw slot-0 value, exactly like interpret.
|
|
1417
|
+
const p0Expr = pick(args, 0, ["source", "series", "length"], "null");
|
|
1418
|
+
const multPf = finiteOr(pick(args, 1, ["mult", "multiplier"], "null"), "NaN");
|
|
1419
|
+
const lenFt = finiteOr(pick(args, 1, ["length"], "null"), "0");
|
|
1420
|
+
const multFt = finiteOr(pick(args, 2, ["mult", "multiplier"], "null"), "2");
|
|
1421
|
+
const pf = `(() => { const __r = __h.ta.bb(${site}, close_arr[__bar_idx], __p0, ${multPf}); return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1422
|
+
const ft = `(() => { const __r = __h.ta.bb(${site}, __s0, ${lenFt}, ${multFt}); return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1423
|
+
return `((__p0, __s0) => (Number.isInteger(__p0) ? ${pf} : ${ft}))(${p0Expr}, ${pick(args, 0, ["source", "series"], "null")})`;
|
|
1424
|
+
}
|
|
1227
1425
|
const call = taCall("bb", site, [
|
|
1228
1426
|
src(),
|
|
1229
|
-
|
|
1230
|
-
pick(args, 2, ["mult", "multiplier"], "2"),
|
|
1427
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1428
|
+
finiteOr(pick(args, 2, ["mult", "multiplier"], "null"), "2"),
|
|
1231
1429
|
]);
|
|
1232
1430
|
return `(() => { const __r = ${call}; return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1233
1431
|
}
|
|
@@ -1245,21 +1443,52 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1245
1443
|
return `(() => { const __r = ${call}; return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1246
1444
|
}
|
|
1247
1445
|
if (method === "stoch") {
|
|
1248
|
-
if (args.
|
|
1249
|
-
|
|
1446
|
+
if (args.kw.high == null && args.pos.length <= 2) {
|
|
1447
|
+
// Python period-only / community forms (oscillators.py:58-78): %K over
|
|
1448
|
+
// the context close/high/low. Both slots gate on _is_period_like; in
|
|
1449
|
+
// the 2-arg (kLength, dPeriod) form dPeriod is ignored (Python quirk).
|
|
1450
|
+
const length =
|
|
1451
|
+
args.pos.length === 2
|
|
1452
|
+
? `(((__k, __d) => ((Number.isInteger(__k) && Number.isInteger(__d)) ? __k : 0))(${pick(args, 0, ["length"], "null")}, ${pick(args, 1, ["d", "smooth"], "null")}))`
|
|
1453
|
+
: periodGate(pick(args, 0, ["length"], "null"), "0");
|
|
1454
|
+
return taCall("stoch", site, [CLOSE, HIGH, LOW, length]);
|
|
1250
1455
|
}
|
|
1251
1456
|
return taCall("stoch", site, [
|
|
1252
1457
|
src(),
|
|
1253
1458
|
pick(args, 1, ["high"], HIGH),
|
|
1254
1459
|
pick(args, 2, ["low"], LOW),
|
|
1255
|
-
|
|
1460
|
+
finiteOr(pick(args, 3, ["length"], "null"), "0"),
|
|
1256
1461
|
]);
|
|
1257
1462
|
}
|
|
1258
1463
|
if (method === "vwma") {
|
|
1259
|
-
|
|
1464
|
+
if (args.pos.length <= 1 && !hasAnyKw(args, ["volume"])) {
|
|
1465
|
+
// Python 1-arg period-only form (basic.py:80-88): close + volume from
|
|
1466
|
+
// the chart context; length gates on _is_period_like → na period 0.
|
|
1467
|
+
return taCall("vwma", site, [CLOSE, VOL, periodGate(pick(args, 0, ["length"], "null"), "0")]);
|
|
1468
|
+
}
|
|
1469
|
+
if (args.pos.length >= 3 || hasAnyKw(args, ["volume"])) {
|
|
1470
|
+
// Python 3-arg community form (basic.py:89-100): (source, volume,
|
|
1471
|
+
// length). A period-like middle slot swaps volume back to the context
|
|
1472
|
+
// series (basic.py:93/97) — mirror interpret's per-bar swap.
|
|
1473
|
+
const vol = `((__v) => (Number.isInteger(__v) ? ${VOL} : __v))(${pick(args, 1, ["volume"], "null")})`;
|
|
1474
|
+
return taCall("vwma", site, [src(), vol, finiteOr(pick(args, 2, ["length", "period"], "null"), "0")]);
|
|
1475
|
+
}
|
|
1476
|
+
return taCall("vwma", site, [src(), VOL, finiteOr(pick(args, 1, ["length"], "null"), "0")]);
|
|
1260
1477
|
}
|
|
1261
1478
|
if (method === "cci") {
|
|
1262
|
-
|
|
1479
|
+
if (args.pos.length >= 4) {
|
|
1480
|
+
// Python legacy 4-arg form (oscillators.py:178-187): typical price of
|
|
1481
|
+
// the given (high, low, close) series. Slots resolve kwarg-first.
|
|
1482
|
+
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")})`;
|
|
1483
|
+
return taCall("cci", site, [tp, finiteOr(pick(args, 3, ["length"], "null"), "0")]);
|
|
1484
|
+
}
|
|
1485
|
+
if (args.pos.length <= 1 && !hasAnyKw(args, ["source", "series"])) {
|
|
1486
|
+
// Python 1-arg period-only form (oscillators.py:159-171): typical price
|
|
1487
|
+
// from the context high/low/close; length gates on _is_period_like.
|
|
1488
|
+
return taCall("cci", site, [TP, periodGate(pick(args, 0, ["length"], "null"), "0")]);
|
|
1489
|
+
}
|
|
1490
|
+
// Python 2-arg form (oscillators.py:172-177): CCI over the source alone.
|
|
1491
|
+
return taCall("cci", site, [src(), finiteOr(pick(args, 1, ["length"], "null"), "0")]);
|
|
1263
1492
|
}
|
|
1264
1493
|
if (method === "mfi") {
|
|
1265
1494
|
if (args.pos.length <= 1) {
|
|
@@ -1336,10 +1565,27 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1336
1565
|
]);
|
|
1337
1566
|
}
|
|
1338
1567
|
if (method === "pivothigh" || method === "pivotlow") {
|
|
1568
|
+
if (args.pos.length === 2 && !hasAnyKw(args, ["source", "series"])) {
|
|
1569
|
+
// Python period-first form (basic.py:1007-1039): both slots gate on
|
|
1570
|
+
// _is_period_like and the source defaults to the context high/low
|
|
1571
|
+
// series. A non-period-like slot falls through to the series arm on
|
|
1572
|
+
// the raw slot values (never confirms on a scalar) — mirror interpret.
|
|
1573
|
+
// Slots resolve kwarg-first (leftbars/rightbars), like interpret callArg.
|
|
1574
|
+
const defSrc = method === "pivotlow" ? LOW : HIGH;
|
|
1575
|
+
const lExpr = pick(args, 0, ["leftbars", "left"], "null");
|
|
1576
|
+
const rExpr = pick(args, 1, ["rightbars", "right"], "null");
|
|
1577
|
+
const leftFt = finiteOr(pick(args, 1, ["leftbars", "left"], "null"), "5");
|
|
1578
|
+
const rightFt = finiteOr(pick(args, 2, ["rightbars", "right"], "null"), "5");
|
|
1579
|
+
const pf = taCall(method, site, [defSrc, "__l", "__r"]);
|
|
1580
|
+
const ft = taCall(method, site, ["__s", leftFt, rightFt]);
|
|
1581
|
+
return `((__l, __r, __s) => (((Number.isInteger(__l) && Number.isInteger(__r))) ? ${pf} : ${ft}))(${lExpr}, ${rExpr}, ${pick(args, 0, ["source", "series"], "null")})`;
|
|
1582
|
+
}
|
|
1583
|
+
// Interpret srcArg: source slot names ["source", "series"], missing → na
|
|
1584
|
+
// (a bare ta.pivothigh() never confirms, like interpret).
|
|
1339
1585
|
return taCall(method, site, [
|
|
1340
|
-
|
|
1341
|
-
pick(args, 1, ["leftbars", "left"], "5"),
|
|
1342
|
-
pick(args, 2, ["rightbars", "right"], "5"),
|
|
1586
|
+
pick(args, 0, ["source", "series"], "null"),
|
|
1587
|
+
finiteOr(pick(args, 1, ["leftbars", "left"], "null"), "5"),
|
|
1588
|
+
finiteOr(pick(args, 2, ["rightbars", "right"], "null"), "5"),
|
|
1343
1589
|
]);
|
|
1344
1590
|
}
|
|
1345
1591
|
if (method === "swma") {
|
|
@@ -1355,7 +1601,10 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1355
1601
|
return taCall("willr", site, [HIGH, LOW, CLOSE, len(0, "14")]);
|
|
1356
1602
|
}
|
|
1357
1603
|
if (method === "vwap") {
|
|
1358
|
-
return taCall("vwap", site, [
|
|
1604
|
+
return taCall("vwap", site, [
|
|
1605
|
+
pick(args, 0, ["source", "series", "src"], HLC3),
|
|
1606
|
+
pick(args, 1, ["volume"], VOL),
|
|
1607
|
+
]);
|
|
1359
1608
|
}
|
|
1360
1609
|
if (method === "sar") {
|
|
1361
1610
|
return taCall("sar", site, [
|
|
@@ -1367,7 +1616,27 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1367
1616
|
]);
|
|
1368
1617
|
}
|
|
1369
1618
|
if (method === "adx") {
|
|
1370
|
-
|
|
1619
|
+
if (args.pos.length >= 4) {
|
|
1620
|
+
// Python legacy 4-arg form (common.py:718-726): (high, low, close,
|
|
1621
|
+
// length) with explicit series. Slots resolve kwarg-first.
|
|
1622
|
+
return taCall("adx", site, [
|
|
1623
|
+
pick(args, 0, ["source", "series"], "null"),
|
|
1624
|
+
pick(args, 1, ["low"], "null"),
|
|
1625
|
+
pick(args, 2, ["close"], "null"),
|
|
1626
|
+
finiteOr(pick(args, 3, ["length"], "null"), "0"),
|
|
1627
|
+
]);
|
|
1628
|
+
}
|
|
1629
|
+
if (args.pos.length === 2 && !hasAnyKw(args, ["high", "low", "close"])) {
|
|
1630
|
+
// Python 2-arg form (diLength, adxSmoothing) (common.py:709-717): both
|
|
1631
|
+
// slots gate on _is_period_like (kwarg-first resolution) and the ADX
|
|
1632
|
+
// period is the SECOND slot — diLength is accepted but unused. A
|
|
1633
|
+
// non-period-like slot → default 14.
|
|
1634
|
+
const period = `(((__di, __sm) => ((Number.isInteger(__di) && Number.isInteger(__sm)) ? __sm : 14))(${pick(args, 0, ["diLength", "length"], "null")}, ${pick(args, 1, ["adxSmoothing", "adxlen"], "null")}))`;
|
|
1635
|
+
return taCall("adx", site, [HIGH, LOW, CLOSE, period]);
|
|
1636
|
+
}
|
|
1637
|
+
// Python 1-arg period-only form gates length via _is_period_like
|
|
1638
|
+
// (common.py ~701): non-period-like → ignored → default 14 (as absent).
|
|
1639
|
+
return taCall("adx", site, [HIGH, LOW, CLOSE, periodGate(pick(args, 0, ["length"], "null"), "14")]);
|
|
1371
1640
|
}
|
|
1372
1641
|
if (method === "correlation") {
|
|
1373
1642
|
return taCall("correlation", site, [
|
|
@@ -1387,20 +1656,141 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
1387
1656
|
pick(args, 3, ["volume"], VOL),
|
|
1388
1657
|
]);
|
|
1389
1658
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1659
|
+
// `vpt` is an alias of the pvt kernel on interpret (no ta.vpt engine fn).
|
|
1660
|
+
if (method === "pvt" || method === "vpt" || method === "obv" || method === "nvi" || method === "pvi") {
|
|
1661
|
+
return taCall(method === "vpt" ? "pvt" : method, site, [
|
|
1392
1662
|
pick(args, 0, ["source", "close"], CLOSE),
|
|
1393
1663
|
pick(args, 1, ["volume"], VOL),
|
|
1394
1664
|
]);
|
|
1395
1665
|
}
|
|
1396
1666
|
if (method === "wad") {
|
|
1397
|
-
return taCall("wad", site, [
|
|
1667
|
+
return taCall("wad", site, [
|
|
1668
|
+
pick(args, 0, ["high"], HIGH),
|
|
1669
|
+
pick(args, 1, ["low"], LOW),
|
|
1670
|
+
pick(args, 2, ["close"], CLOSE),
|
|
1671
|
+
pick(args, 3, ["volume"], VOL),
|
|
1672
|
+
]);
|
|
1398
1673
|
}
|
|
1399
1674
|
if (method === "iii") {
|
|
1400
1675
|
return taCall("iii", site, [HIGH, LOW, CLOSE, VOL]);
|
|
1401
1676
|
}
|
|
1402
1677
|
if (method === "wvad") {
|
|
1403
|
-
|
|
1678
|
+
if (args.pos.length >= 4 || Object.hasOwn(args.kw, "high")) {
|
|
1679
|
+
return taCall("wvad", site, [
|
|
1680
|
+
pick(args, 0, ["high"], HIGH),
|
|
1681
|
+
pick(args, 1, ["low"], LOW),
|
|
1682
|
+
pick(args, 2, ["close"], CLOSE),
|
|
1683
|
+
pick(args, 3, ["volume"], VOL),
|
|
1684
|
+
pick(args, 4, ["length", "period"], "20"),
|
|
1685
|
+
]);
|
|
1686
|
+
}
|
|
1687
|
+
return taCall("wvad", site, [HIGH, LOW, CLOSE, VOL, pick(args, 0, ["length", "period"], "20")]);
|
|
1688
|
+
}
|
|
1689
|
+
if (method === "cmf") {
|
|
1690
|
+
if (args.pos.length >= 5) {
|
|
1691
|
+
return taCall("cmf", site, [
|
|
1692
|
+
pick(args, 1, ["high"], HIGH),
|
|
1693
|
+
pick(args, 2, ["low"], LOW),
|
|
1694
|
+
pick(args, 0, ["close", "source"], CLOSE),
|
|
1695
|
+
pick(args, 3, ["volume"], VOL),
|
|
1696
|
+
pick(args, 4, ["length", "period"], "20"),
|
|
1697
|
+
]);
|
|
1698
|
+
}
|
|
1699
|
+
return taCall("cmf", site, [HIGH, LOW, CLOSE, VOL, pick(args, 0, ["length", "period"], "20")]);
|
|
1700
|
+
}
|
|
1701
|
+
if (method === "klinger") {
|
|
1702
|
+
return taCall("klinger", site, [
|
|
1703
|
+
pick(args, 2, ["close"], CLOSE),
|
|
1704
|
+
pick(args, 3, ["volume"], VOL),
|
|
1705
|
+
pick(args, 4, ["fast_period", "fast", "fastlen"], "0"),
|
|
1706
|
+
pick(args, 5, ["slow_period", "slow", "slowlen"], "0"),
|
|
1707
|
+
]);
|
|
1708
|
+
}
|
|
1709
|
+
if (method === "ao") {
|
|
1710
|
+
// SMA(hl2, fast) − SMA(hl2, slow); high/low always come from the bar context.
|
|
1711
|
+
return taCall("ao", site, [
|
|
1712
|
+
HIGH,
|
|
1713
|
+
LOW,
|
|
1714
|
+
pick(args, 0, ["fast", "fastlen"], "5"),
|
|
1715
|
+
pick(args, 1, ["slow", "slowlen"], "34"),
|
|
1716
|
+
]);
|
|
1717
|
+
}
|
|
1718
|
+
if (method === "aroon") {
|
|
1719
|
+
// Kernel returns {down, up}; interpret exposes the tuple [down, up].
|
|
1720
|
+
const call = taCall("aroon", site, [HIGH, LOW, pick(args, 0, ["length"], "14")]);
|
|
1721
|
+
return `(() => { const __r = ${call}; return [__r.down, __r.up]; })()`;
|
|
1722
|
+
}
|
|
1723
|
+
if (method === "uo") {
|
|
1724
|
+
return taCall("uo", site, [
|
|
1725
|
+
HIGH,
|
|
1726
|
+
LOW,
|
|
1727
|
+
CLOSE,
|
|
1728
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
1729
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1730
|
+
finiteOr(pick(args, 2, ["length"], "null"), "0"),
|
|
1731
|
+
]);
|
|
1732
|
+
}
|
|
1733
|
+
if (method === "rci") {
|
|
1734
|
+
// srcArg: missing source is na, not close.
|
|
1735
|
+
return taCall("rci", site, [
|
|
1736
|
+
pick(args, 0, ["source", "series"], "null"),
|
|
1737
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1738
|
+
]);
|
|
1739
|
+
}
|
|
1740
|
+
if (method === "dpo") {
|
|
1741
|
+
return taCall("dpo", site, [CLOSE, finiteOr(pick(args, 0, ["length"], "null"), "0")]);
|
|
1742
|
+
}
|
|
1743
|
+
if (method === "kst") {
|
|
1744
|
+
return taCall("kst", site, [
|
|
1745
|
+
CLOSE,
|
|
1746
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
1747
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1748
|
+
finiteOr(pick(args, 2, ["length"], "null"), "0"),
|
|
1749
|
+
finiteOr(pick(args, 3, ["length"], "null"), "0"),
|
|
1750
|
+
]);
|
|
1751
|
+
}
|
|
1752
|
+
if (method === "stochrsi") {
|
|
1753
|
+
// Engine method is stochRsi; record already branded {stochrsi, signal}.
|
|
1754
|
+
return taCall("stochRsi", site, [
|
|
1755
|
+
CLOSE,
|
|
1756
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
1757
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1758
|
+
]);
|
|
1759
|
+
}
|
|
1760
|
+
if (method === "donchian") {
|
|
1761
|
+
return taCall("donchian", site, [HIGH, LOW, finiteOr(pick(args, 0, ["length"], "null"), "0")]);
|
|
1762
|
+
}
|
|
1763
|
+
if (method === "ichimoku") {
|
|
1764
|
+
return taCall("ichimoku", site, [
|
|
1765
|
+
HIGH,
|
|
1766
|
+
LOW,
|
|
1767
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
1768
|
+
finiteOr(pick(args, 1, ["length"], "null"), "0"),
|
|
1769
|
+
]);
|
|
1770
|
+
}
|
|
1771
|
+
if (method === "bb_pct") {
|
|
1772
|
+
return taCall("bbPct", site, [
|
|
1773
|
+
CLOSE,
|
|
1774
|
+
finiteOr(pick(args, 0, ["length"], "null"), "0"),
|
|
1775
|
+
finiteOr(pick(args, 1, ["std_dev", "mult", "std"], "null"), "2"),
|
|
1776
|
+
]);
|
|
1777
|
+
}
|
|
1778
|
+
if (method === "emv") {
|
|
1779
|
+
return taCall("emv", site, [HIGH, LOW, VOL, finiteOr(pick(args, 0, ["length"], "null"), "0")]);
|
|
1780
|
+
}
|
|
1781
|
+
if (method === "fractal") {
|
|
1782
|
+
return taCall("fractal", site, [HIGH, LOW, finiteOr(pick(args, 0, ["length"], "null"), "0")]);
|
|
1783
|
+
}
|
|
1784
|
+
if (method === "atr_stop") {
|
|
1785
|
+
// Kernel has no call-site; missing multiplier → 2 (interpret numArg).
|
|
1786
|
+
return `__h.ta.atrStop(${CLOSE}, ${pick(args, 0, ["atr", "atr_value"], "null")}, ${finiteOr(pick(args, 1, ["multiplier", "mult"], "null"), "2")})`;
|
|
1787
|
+
}
|
|
1788
|
+
if (method === "zigzag") {
|
|
1789
|
+
const call = taCall("zigzag", site, [
|
|
1790
|
+
pick(args, 0, ["source", "series"], "null"),
|
|
1791
|
+
finiteOr(pick(args, 1, ["threshold", "percent", "deviation"], "null"), "5"),
|
|
1792
|
+
]);
|
|
1793
|
+
return `(() => { const __r = ${call}; return [__r.high, __r.low, __r.dir]; })()`;
|
|
1404
1794
|
}
|
|
1405
1795
|
return "null";
|
|
1406
1796
|
}
|