@hoox-sh/pynets 0.1.0 → 0.2.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 +20 -0
- package/README.md +5 -3
- package/dist/browser.js +90 -34
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4441 -411
- package/package.json +1 -1
- package/src/cli/rich.ts +4 -3
- package/src/cli.ts +29 -7
- package/src/index.ts +21 -0
- package/src/runtime/compile/collections.ts +339 -0
- package/src/runtime/compile/draw.ts +181 -0
- package/src/runtime/compile/emit.ts +1164 -0
- package/src/runtime/compile/emit_call.ts +1406 -0
- package/src/runtime/compile/emit_udf.ts +326 -0
- package/src/runtime/compile/engine.ts +265 -0
- package/src/runtime/compile/helpers.ts +140 -0
- package/src/runtime/compile/index.ts +33 -0
- package/src/runtime/compile/runtime.ts +297 -0
- package/src/runtime/compile/strategy.ts +169 -0
- package/src/runtime/compile/types.ts +125 -0
- package/src/runtime/compile/udt_runtime.ts +50 -0
- package/src/runtime/interpret.ts +192 -10
|
@@ -0,0 +1,1406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2024-2026 jango_blockchained
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*
|
|
5
|
+
* Call lowering for JS compile. Pine builtins → `__h` / `__h.ta`.
|
|
6
|
+
*/
|
|
7
|
+
import type { Attribute, Call, Constant, expr, Name } from "../../ast/nodes.ts";
|
|
8
|
+
import { emitUserFuncCall } from "./emit_udf.ts";
|
|
9
|
+
import type { EmitCtx, VisitFn } from "./types.ts";
|
|
10
|
+
|
|
11
|
+
const HIGH = "high_arr[__bar_idx]";
|
|
12
|
+
const LOW = "low_arr[__bar_idx]";
|
|
13
|
+
const CLOSE = "close_arr[__bar_idx]";
|
|
14
|
+
const VOL = "vol_arr[__bar_idx]";
|
|
15
|
+
const OPEN = "open_arr[__bar_idx]";
|
|
16
|
+
|
|
17
|
+
const NS = new Set([
|
|
18
|
+
"ta",
|
|
19
|
+
"math",
|
|
20
|
+
"str",
|
|
21
|
+
"array",
|
|
22
|
+
"matrix",
|
|
23
|
+
"map",
|
|
24
|
+
"color",
|
|
25
|
+
"strategy",
|
|
26
|
+
"input",
|
|
27
|
+
"request",
|
|
28
|
+
"ticker",
|
|
29
|
+
"timeframe",
|
|
30
|
+
"runtime",
|
|
31
|
+
"log",
|
|
32
|
+
"barstate",
|
|
33
|
+
"session",
|
|
34
|
+
"syminfo",
|
|
35
|
+
"timenow",
|
|
36
|
+
"chart",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/** TaEngine methods we emit. Unknown `ta.*` → `null`. */
|
|
40
|
+
const TA_METHODS = new Set([
|
|
41
|
+
"sma",
|
|
42
|
+
"ema",
|
|
43
|
+
"rma",
|
|
44
|
+
"rsi",
|
|
45
|
+
"atr",
|
|
46
|
+
"tr",
|
|
47
|
+
"change",
|
|
48
|
+
"mom",
|
|
49
|
+
"roc",
|
|
50
|
+
"stdev",
|
|
51
|
+
"highest",
|
|
52
|
+
"lowest",
|
|
53
|
+
"sum",
|
|
54
|
+
"cross",
|
|
55
|
+
"crossover",
|
|
56
|
+
"crossunder",
|
|
57
|
+
"macd",
|
|
58
|
+
"bb",
|
|
59
|
+
"stoch",
|
|
60
|
+
"wma",
|
|
61
|
+
"vwma",
|
|
62
|
+
"hma",
|
|
63
|
+
"cci",
|
|
64
|
+
"cmo",
|
|
65
|
+
"mfi",
|
|
66
|
+
"tsi",
|
|
67
|
+
"dmi",
|
|
68
|
+
"supertrend",
|
|
69
|
+
"alma",
|
|
70
|
+
"kama",
|
|
71
|
+
"linreg",
|
|
72
|
+
"rising",
|
|
73
|
+
"falling",
|
|
74
|
+
"barssince",
|
|
75
|
+
"valuewhen",
|
|
76
|
+
"pivothigh",
|
|
77
|
+
"pivotlow",
|
|
78
|
+
"swma",
|
|
79
|
+
"kc",
|
|
80
|
+
"willr",
|
|
81
|
+
"wpr",
|
|
82
|
+
"vwap",
|
|
83
|
+
"sar",
|
|
84
|
+
"adx",
|
|
85
|
+
"correlation",
|
|
86
|
+
"cog",
|
|
87
|
+
"cum",
|
|
88
|
+
"range",
|
|
89
|
+
"accdist",
|
|
90
|
+
"pvt",
|
|
91
|
+
"wad",
|
|
92
|
+
"nvi",
|
|
93
|
+
"pvi",
|
|
94
|
+
"wvad",
|
|
95
|
+
"iii",
|
|
96
|
+
"obv",
|
|
97
|
+
"highestbars",
|
|
98
|
+
"lowestbars",
|
|
99
|
+
"dev",
|
|
100
|
+
"variance",
|
|
101
|
+
"median",
|
|
102
|
+
"mode",
|
|
103
|
+
"percentrank",
|
|
104
|
+
"dema",
|
|
105
|
+
"tema",
|
|
106
|
+
"bbw",
|
|
107
|
+
"max",
|
|
108
|
+
"min",
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
const SRC_LEN = new Set([
|
|
112
|
+
"sma",
|
|
113
|
+
"ema",
|
|
114
|
+
"rma",
|
|
115
|
+
"rsi",
|
|
116
|
+
"stdev",
|
|
117
|
+
"wma",
|
|
118
|
+
"hma",
|
|
119
|
+
"cmo",
|
|
120
|
+
"sum",
|
|
121
|
+
"dema",
|
|
122
|
+
"tema",
|
|
123
|
+
"highestbars",
|
|
124
|
+
"lowestbars",
|
|
125
|
+
"dev",
|
|
126
|
+
"variance",
|
|
127
|
+
"median",
|
|
128
|
+
"mode",
|
|
129
|
+
"percentrank",
|
|
130
|
+
"range",
|
|
131
|
+
"cog",
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
const SRC_LEN1 = new Set(["change", "mom", "roc", "rising", "falling"]);
|
|
135
|
+
|
|
136
|
+
const PLOT_FUNCS = new Set([
|
|
137
|
+
"plot",
|
|
138
|
+
"plotshape",
|
|
139
|
+
"plotchar",
|
|
140
|
+
"plotarrow",
|
|
141
|
+
"hline",
|
|
142
|
+
"bgcolor",
|
|
143
|
+
"barcolor",
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
const STRATEGY_ACTIONS = new Set([
|
|
147
|
+
"strategy_entry",
|
|
148
|
+
"strategy_close",
|
|
149
|
+
"strategy_close_all",
|
|
150
|
+
"strategy_exit",
|
|
151
|
+
"strategy_order",
|
|
152
|
+
"strategy_cancel",
|
|
153
|
+
"strategy_cancel_all",
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
const STRATEGY_QUERIES = new Set([
|
|
157
|
+
"strategy_position_size",
|
|
158
|
+
"strategy_netprofit",
|
|
159
|
+
"strategy_equity",
|
|
160
|
+
"strategy_openprofit",
|
|
161
|
+
"strategy_opentrades",
|
|
162
|
+
"strategy_closedtrades",
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
const CHART_SERIES_ARR: Record<string, string> = {
|
|
166
|
+
close: "close_arr",
|
|
167
|
+
open: "open_arr",
|
|
168
|
+
high: "high_arr",
|
|
169
|
+
low: "low_arr",
|
|
170
|
+
volume: "vol_arr",
|
|
171
|
+
time: "time_arr",
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const MATH_UNARY: Record<string, { fn: string; guard?: string }> = {
|
|
175
|
+
abs: { fn: "Math.abs(Number(__x))" },
|
|
176
|
+
sqrt: { fn: "Math.sqrt(Number(__x))", guard: "Number(__x) < 0" },
|
|
177
|
+
log: { fn: "Math.log(Number(__x))", guard: "Number(__x) <= 0" },
|
|
178
|
+
log10: { fn: "Math.log10(Number(__x))", guard: "Number(__x) <= 0" },
|
|
179
|
+
exp: { fn: "Math.exp(Number(__x))" },
|
|
180
|
+
sign: { fn: "(Number(__x) > 0 ? 1 : Number(__x) < 0 ? -1 : 0)" },
|
|
181
|
+
floor: { fn: "Math.floor(Number(__x))" },
|
|
182
|
+
ceil: { fn: "Math.ceil(Number(__x))" },
|
|
183
|
+
sin: { fn: "Math.sin(Number(__x))" },
|
|
184
|
+
cos: { fn: "Math.cos(Number(__x))" },
|
|
185
|
+
tan: { fn: "Math.tan(Number(__x))" },
|
|
186
|
+
asin: { fn: "Math.asin(Number(__x))", guard: "Number(__x) < -1 || Number(__x) > 1" },
|
|
187
|
+
acos: { fn: "Math.acos(Number(__x))", guard: "Number(__x) < -1 || Number(__x) > 1" },
|
|
188
|
+
atan: { fn: "Math.atan(Number(__x))" },
|
|
189
|
+
todegrees: { fn: "Number(__x) * 180 / Math.PI" },
|
|
190
|
+
toradians: { fn: "Number(__x) * Math.PI / 180" },
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
interface CallArgs {
|
|
194
|
+
pos: string[];
|
|
195
|
+
kw: Record<string, string>;
|
|
196
|
+
posNodes: expr[];
|
|
197
|
+
kwNodes: Record<string, expr>;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function emitCall(ctx: EmitCtx, node: Call, visit: VisitFn): string {
|
|
201
|
+
const { name: rawName, methodSrc } = resolveCallee(node.func);
|
|
202
|
+
const args = collectArgs(node, visit);
|
|
203
|
+
|
|
204
|
+
// `import lib as alias` → alias.foo. Inlined export is a user func; else stub null.
|
|
205
|
+
// Must run before methodSrc unshift / UDT-method (do not pass alias as receiver).
|
|
206
|
+
if (methodSrc != null && methodSrc.kind === "Name") {
|
|
207
|
+
const alias = (methodSrc as Name).id;
|
|
208
|
+
if (ctx.importAliases.has(alias)) {
|
|
209
|
+
if (ctx.userFuncs.has(rawName)) {
|
|
210
|
+
return emitUserFuncCall(ctx, rawName, packUserFuncArgs(ctx, rawName, args));
|
|
211
|
+
}
|
|
212
|
+
return "null";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const drawn = emitDraw(ctx, rawName, methodSrc, args);
|
|
217
|
+
if (drawn != null) return drawn;
|
|
218
|
+
const udt = emitUdt(ctx, rawName, methodSrc, args);
|
|
219
|
+
if (udt != null) return udt;
|
|
220
|
+
const udtMethod = emitUdtMethod(ctx, rawName, methodSrc, args, visit);
|
|
221
|
+
if (udtMethod != null) return udtMethod;
|
|
222
|
+
|
|
223
|
+
if (methodSrc != null) {
|
|
224
|
+
args.pos.unshift(visit(methodSrc));
|
|
225
|
+
args.posNodes.unshift(methodSrc);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let name = rawName;
|
|
229
|
+
if (methodSrc != null && TA_METHODS.has(name)) name = `ta_${name}`;
|
|
230
|
+
if (methodSrc != null && (name === "tostring" || name === "format")) name = `str_${name}`;
|
|
231
|
+
|
|
232
|
+
if (PLOT_FUNCS.has(name)) return emitPlot(ctx, name, args);
|
|
233
|
+
|
|
234
|
+
if (name === "indicator" || name === "study" || name === "library") return "null";
|
|
235
|
+
if (name === "strategy") return "null";
|
|
236
|
+
|
|
237
|
+
if (name === "na") {
|
|
238
|
+
if (args.pos.length === 0 && args.kw.x == null && args.kw.source == null) return "null";
|
|
239
|
+
const x = pick(args, 0, ["x", "source"], "null");
|
|
240
|
+
return `(__h.isNa(${x}) ? 1 : 0)`;
|
|
241
|
+
}
|
|
242
|
+
if (name === "nz") {
|
|
243
|
+
const x = pick(args, 0, ["source", "x"], "null");
|
|
244
|
+
if (hasArg(args, 1, ["replacement", "y"])) return `__h.nz(${x}, ${pick(args, 1, ["replacement", "y"], "0")})`;
|
|
245
|
+
return `__h.nz(${x})`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (name === "input" || name.startsWith("input_")) return emitInput(ctx, args);
|
|
249
|
+
|
|
250
|
+
if (STRATEGY_ACTIONS.has(name) || STRATEGY_QUERIES.has(name) || name.startsWith("strategy_")) {
|
|
251
|
+
return emitStrategy(ctx, name, args);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (ctx.userFuncs.has(name) || ctx.funcSeriesParams.has(name)) {
|
|
255
|
+
return emitUserFuncCall(ctx, name, packUserFuncArgs(ctx, name, args));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (name === "year" || name === "month" || name === "dayofmonth" || name === "hour" || name === "minute" || name === "second" || name === "dayofweek") {
|
|
259
|
+
const t = pick(args, 0, ["time"], "time");
|
|
260
|
+
return `__h.${name}(${t})`;
|
|
261
|
+
}
|
|
262
|
+
if (name === "timestamp") {
|
|
263
|
+
const parts = args.pos.length > 0 ? args.pos : Object.values(args.kw);
|
|
264
|
+
return `__h.timestamp(${parts.join(", ")})`;
|
|
265
|
+
}
|
|
266
|
+
if (name === "weekofyear") {
|
|
267
|
+
return `__h.weekOfYear(${pick(args, 0, ["time", "timestamp"], "time")})`;
|
|
268
|
+
}
|
|
269
|
+
if (name === "time_tradingday") {
|
|
270
|
+
return `__h.timeTradingDay(${pick(args, 0, ["time", "timestamp"], "time")})`;
|
|
271
|
+
}
|
|
272
|
+
if (name === "timeframe_in_seconds") {
|
|
273
|
+
if (hasArg(args, 0, ["timeframe", "period"])) {
|
|
274
|
+
return `__h.timeframeInSeconds(${pick(args, 0, ["timeframe", "period"], "null")})`;
|
|
275
|
+
}
|
|
276
|
+
return "86400";
|
|
277
|
+
}
|
|
278
|
+
if (name === "ticker_heikinashi" || name === "heikinashi") {
|
|
279
|
+
ctx.needsHeikinashi = true;
|
|
280
|
+
return `"__HEIKINASHI__"`;
|
|
281
|
+
}
|
|
282
|
+
// ticker.new / standard / modify / renko / kagi / linebreak / pointfigure → first arg or "SYMBOL"
|
|
283
|
+
if (name.startsWith("ticker_")) {
|
|
284
|
+
return pick(args, 0, ["symbol", "ticker", "tickerid"], `"SYMBOL"`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (name === "log_info" || name === "log_warning" || name === "log_error") {
|
|
288
|
+
const level = name.slice("log_".length);
|
|
289
|
+
const parts = args.pos.length > 0 ? args.pos : Object.values(args.kw);
|
|
290
|
+
return parts.length > 0
|
|
291
|
+
? `__h.log.${level}(__bar_idx, ${parts.join(", ")})`
|
|
292
|
+
: `__h.log.${level}(__bar_idx)`;
|
|
293
|
+
}
|
|
294
|
+
if (name === "log_clear") return "__h.log.clear()";
|
|
295
|
+
|
|
296
|
+
if (name === "runtime_error") {
|
|
297
|
+
const msg = pick(args, 0, ["message", "msg"], `"runtime.error"`);
|
|
298
|
+
return `(() => { throw new Error(String(${msg})); })()`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (name === "alertcondition") return "null";
|
|
302
|
+
if (name === "str_tostring" || name === "tostring") {
|
|
303
|
+
const x = pick(args, 0, ["value", "source", "x"], "null");
|
|
304
|
+
return `String(${x} ?? "")`;
|
|
305
|
+
}
|
|
306
|
+
if (name === "str_format" || name === "format") {
|
|
307
|
+
const fmt = pick(args, 0, ["formatString", "format", "fmt"], "null");
|
|
308
|
+
const fmtFromKw =
|
|
309
|
+
Object.hasOwn(args.kw, "formatString") ||
|
|
310
|
+
Object.hasOwn(args.kw, "format") ||
|
|
311
|
+
Object.hasOwn(args.kw, "fmt");
|
|
312
|
+
const extras = fmtFromKw ? args.pos.slice() : args.pos.slice(1);
|
|
313
|
+
return extras.length > 0 ? `__h.strFormat(${fmt}, ${extras.join(", ")})` : `__h.strFormat(${fmt})`;
|
|
314
|
+
}
|
|
315
|
+
if (name === "str_format_time" || name === "format_time") {
|
|
316
|
+
return `__h.strFormatTime(${pick(args, 0, ["time", "timestamp"], "null")}, ${pick(args, 1, ["format"], "undefined")}, ${pick(args, 2, ["timezone"], "undefined")})`;
|
|
317
|
+
}
|
|
318
|
+
if (name.startsWith("str_")) {
|
|
319
|
+
const meth = name.slice(4);
|
|
320
|
+
const alias: Record<string, string> = {
|
|
321
|
+
length: "length",
|
|
322
|
+
contains: "contains",
|
|
323
|
+
startswith: "starts_with",
|
|
324
|
+
ends_with: "ends_with",
|
|
325
|
+
endswith: "ends_with",
|
|
326
|
+
lower: "lower",
|
|
327
|
+
upper: "upper",
|
|
328
|
+
replace: "replace",
|
|
329
|
+
substring: "substring",
|
|
330
|
+
tonumber: "tonumber",
|
|
331
|
+
trim: "trim",
|
|
332
|
+
};
|
|
333
|
+
const key = alias[meth];
|
|
334
|
+
if (key) {
|
|
335
|
+
const a0 = pick(args, 0, ["source", "string", "value"], "null");
|
|
336
|
+
const a1 = args.pos[1] ?? args.kw.substring ?? args.kw.str ?? args.kw.replacement ?? "undefined";
|
|
337
|
+
if (meth === "length" || meth === "lower" || meth === "upper" || meth === "tonumber" || meth === "trim") {
|
|
338
|
+
return `__h.str.${key}(${a0})`;
|
|
339
|
+
}
|
|
340
|
+
return `__h.str.${key}(${a0}, ${a1})`;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const color = emitColor(name, args);
|
|
345
|
+
if (color != null) return color;
|
|
346
|
+
|
|
347
|
+
const math = emitMath(name, args);
|
|
348
|
+
if (math != null) return math;
|
|
349
|
+
|
|
350
|
+
if (TA_METHODS.has(name) && !ctx.userFuncs.has(name)) name = `ta_${name}`;
|
|
351
|
+
if (name.startsWith("ta_")) return emitTa(ctx, name.slice(3), args);
|
|
352
|
+
|
|
353
|
+
const arr = emitArray(name, args);
|
|
354
|
+
if (arr != null) return arr;
|
|
355
|
+
const mp = emitMap(name, args);
|
|
356
|
+
if (mp != null) return mp;
|
|
357
|
+
const mx = emitMatrix(name, args);
|
|
358
|
+
if (mx != null) return mx;
|
|
359
|
+
|
|
360
|
+
const req = emitRequest(ctx, name, args);
|
|
361
|
+
if (req != null) return req;
|
|
362
|
+
|
|
363
|
+
return "null";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function resolveCallee(func: expr): { name: string; methodSrc: expr | null } {
|
|
367
|
+
func = unwrapSpecialize(func);
|
|
368
|
+
if (func.kind === "Name") return { name: (func as Name).id, methodSrc: null };
|
|
369
|
+
if (func.kind !== "Attribute") return { name: "unknown_func", methodSrc: null };
|
|
370
|
+
|
|
371
|
+
const attrNode = func as Attribute;
|
|
372
|
+
const attr = attrNode.attr;
|
|
373
|
+
const recv = unwrapSpecialize(attrNode.value);
|
|
374
|
+
|
|
375
|
+
if (recv.kind === "Name") {
|
|
376
|
+
const id = (recv as Name).id;
|
|
377
|
+
if (NS.has(id)) return { name: `${id}_${attr}`, methodSrc: null };
|
|
378
|
+
return { name: attr, methodSrc: recv };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (recv.kind === "Attribute") {
|
|
382
|
+
const mid = recv as Attribute;
|
|
383
|
+
const inner = unwrapSpecialize(mid.value);
|
|
384
|
+
if (inner.kind === "Name" && NS.has((inner as Name).id)) {
|
|
385
|
+
return { name: `${(inner as Name).id}_${mid.attr}_${attr}`, methodSrc: null };
|
|
386
|
+
}
|
|
387
|
+
return { name: attr, methodSrc: attrNode.value };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return { name: attr, methodSrc: attrNode.value };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function unwrapSpecialize(node: expr): expr {
|
|
394
|
+
const n = node as unknown as { kind?: string; value?: expr };
|
|
395
|
+
if (n.kind === "Specialize" && n.value != null) return n.value;
|
|
396
|
+
return node;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function collectArgs(node: Call, visit: VisitFn): CallArgs {
|
|
400
|
+
const pos: string[] = [];
|
|
401
|
+
const kw: Record<string, string> = {};
|
|
402
|
+
const posNodes: expr[] = [];
|
|
403
|
+
const kwNodes: Record<string, expr> = {};
|
|
404
|
+
const list = node.args ?? [];
|
|
405
|
+
for (const arg of list) {
|
|
406
|
+
const value = arg.value;
|
|
407
|
+
const emitted = visit(value);
|
|
408
|
+
const key = arg.name != null && arg.name !== "" ? arg.name : null;
|
|
409
|
+
if (key != null) {
|
|
410
|
+
kw[key] = emitted;
|
|
411
|
+
kwNodes[key] = value;
|
|
412
|
+
} else {
|
|
413
|
+
pos.push(emitted);
|
|
414
|
+
posNodes.push(value);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return { pos, kw, posNodes, kwNodes };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function pick(args: CallArgs, index: number, names: string[], fallback: string): string {
|
|
421
|
+
for (const n of names) {
|
|
422
|
+
if (Object.hasOwn(args.kw, n)) return args.kw[n]!;
|
|
423
|
+
}
|
|
424
|
+
if (index >= 0 && index < args.pos.length) return args.pos[index]!;
|
|
425
|
+
return fallback;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function hasArg(args: CallArgs, index: number, names: string[]): boolean {
|
|
429
|
+
for (const n of names) {
|
|
430
|
+
if (Object.hasOwn(args.kw, n)) return true;
|
|
431
|
+
}
|
|
432
|
+
return index >= 0 && index < args.pos.length;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const DRAW_KINDS = new Set(["label", "line", "box", "table", "polyline", "linefill"]);
|
|
436
|
+
|
|
437
|
+
const ARRAY_SIGS: Record<string, string[][]> = {
|
|
438
|
+
new: [["size"], ["initial_value", "initial"]],
|
|
439
|
+
get: [["id"], ["index"]],
|
|
440
|
+
set: [["id"], ["index"], ["value"]],
|
|
441
|
+
push: [["id"], ["value"]],
|
|
442
|
+
pop: [["id"]],
|
|
443
|
+
unshift: [["id"], ["value"]],
|
|
444
|
+
shift: [["id"]],
|
|
445
|
+
size: [["id"]],
|
|
446
|
+
includes: [["id"], ["value"]],
|
|
447
|
+
first: [["id"]],
|
|
448
|
+
last: [["id"]],
|
|
449
|
+
insert: [["id"], ["index"], ["value"]],
|
|
450
|
+
remove: [["id"], ["index"]],
|
|
451
|
+
clear: [["id"]],
|
|
452
|
+
fill: [["id"], ["value"]],
|
|
453
|
+
copy: [["id"]],
|
|
454
|
+
reverse: [["id"]],
|
|
455
|
+
sort: [["id"], ["order"]],
|
|
456
|
+
concat: [["id"], ["other", "id2"]],
|
|
457
|
+
slice: [["id"], ["index_from", "from"], ["index_to", "to"]],
|
|
458
|
+
indexof: [["id"], ["value"]],
|
|
459
|
+
lastindexof: [["id"], ["value"]],
|
|
460
|
+
avg: [["id"]],
|
|
461
|
+
min: [["id"]],
|
|
462
|
+
max: [["id"]],
|
|
463
|
+
sum: [["id"]],
|
|
464
|
+
stdev: [["id"], ["biased"]],
|
|
465
|
+
variance: [["id"], ["biased"]],
|
|
466
|
+
join: [["id"], ["separator", "sep"]],
|
|
467
|
+
every: [["id"]],
|
|
468
|
+
some: [["id"]],
|
|
469
|
+
abs: [["id"]],
|
|
470
|
+
range: [["id"]],
|
|
471
|
+
median: [["id"]],
|
|
472
|
+
mode: [["id"]],
|
|
473
|
+
standardize: [["id"]],
|
|
474
|
+
percentrank: [["id"], ["value"]],
|
|
475
|
+
percentile_linear: [["id"], ["percentage", "percent"]],
|
|
476
|
+
percentile_linear_interpolation: [["id"], ["percentage", "percent"]],
|
|
477
|
+
percentile_nearest: [["id"], ["percentage", "percent"]],
|
|
478
|
+
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"]],
|
|
483
|
+
covariance: [["id"], ["id2", "other"], ["biased"]],
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const MAP_SIGS: Record<string, string[][]> = {
|
|
487
|
+
new: [],
|
|
488
|
+
put: [["id"], ["key"], ["value"]],
|
|
489
|
+
get: [["id"], ["key"]],
|
|
490
|
+
contains: [["id"], ["key"]],
|
|
491
|
+
remove: [["id"], ["key"]],
|
|
492
|
+
clear: [["id"]],
|
|
493
|
+
size: [["id"]],
|
|
494
|
+
keys: [["id"]],
|
|
495
|
+
values: [["id"]],
|
|
496
|
+
copy: [["id"]],
|
|
497
|
+
put_all: [["id"], ["id2", "other"]],
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
const MATRIX_SIGS: Record<string, string[][]> = {
|
|
501
|
+
new: [["rows"], ["columns", "cols"], ["initial_value", "initial"]],
|
|
502
|
+
get: [["id"], ["row"], ["column", "col"]],
|
|
503
|
+
set: [["id"], ["row"], ["column", "col"], ["value"]],
|
|
504
|
+
rows: [["id"]],
|
|
505
|
+
columns: [["id"]],
|
|
506
|
+
fill: [["id"], ["value"]],
|
|
507
|
+
transpose: [["id"]],
|
|
508
|
+
copy: [["id"]],
|
|
509
|
+
inv: [["id"]],
|
|
510
|
+
pinv: [["id"]],
|
|
511
|
+
rank: [["id"]],
|
|
512
|
+
det: [["id"]],
|
|
513
|
+
trace: [["id"]],
|
|
514
|
+
elements_count: [["id"]],
|
|
515
|
+
is_square: [["id"]],
|
|
516
|
+
is_zero: [["id"]],
|
|
517
|
+
is_identity: [["id"]],
|
|
518
|
+
is_diagonal: [["id"]],
|
|
519
|
+
is_symmetric: [["id"]],
|
|
520
|
+
is_antisymmetric: [["id"]],
|
|
521
|
+
is_triangular: [["id"]],
|
|
522
|
+
is_binary: [["id"]],
|
|
523
|
+
is_stochastic: [["id"]],
|
|
524
|
+
is_antidiagonal: [["id"]],
|
|
525
|
+
mult: [["id"], ["other", "matrix_2"]],
|
|
526
|
+
row: [["id"], ["row", "index"]],
|
|
527
|
+
col: [["id"], ["column", "col", "index"]],
|
|
528
|
+
column: [["id"], ["column", "col", "index"]],
|
|
529
|
+
sum: [["id"]],
|
|
530
|
+
sum_all: [["id"]],
|
|
531
|
+
avg: [["id"]],
|
|
532
|
+
avg_all: [["id"]],
|
|
533
|
+
min: [["id"]],
|
|
534
|
+
min_all: [["id"]],
|
|
535
|
+
max: [["id"]],
|
|
536
|
+
max_all: [["id"]],
|
|
537
|
+
median: [["id"]],
|
|
538
|
+
mode: [["id"]],
|
|
539
|
+
kron: [["id"], ["other"]],
|
|
540
|
+
diff: [["id"], ["other"]],
|
|
541
|
+
concat: [["id"], ["other"]],
|
|
542
|
+
reshape: [["id"], ["rows"], ["columns", "cols"]],
|
|
543
|
+
reverse: [["id"]],
|
|
544
|
+
sort: [["id"], ["column", "col"], ["order"]],
|
|
545
|
+
eigenvalues: [["id"]],
|
|
546
|
+
eigenvectors: [["id"]],
|
|
547
|
+
pow: [["id"], ["power", "n"]],
|
|
548
|
+
swap_rows: [["id"], ["row1", "i"], ["row2", "j"]],
|
|
549
|
+
swap_columns: [["id"], ["column1", "i"], ["column2", "j"]],
|
|
550
|
+
add_row: [["id"], ["row", "index"], ["array"]],
|
|
551
|
+
add_col: [["id"], ["column", "index"], ["array"]],
|
|
552
|
+
add_column: [["id"], ["column", "index"], ["array"]],
|
|
553
|
+
remove_row: [["id"], ["row", "index"]],
|
|
554
|
+
remove_col: [["id"], ["column", "index"]],
|
|
555
|
+
remove_column: [["id"], ["column", "index"]],
|
|
556
|
+
submatrix: [["id"], ["from_row"], ["to_row"], ["from_column", "from_col"], ["to_column", "to_col"]],
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
function nsMethod(name: string, prefix: string): string | null {
|
|
560
|
+
if (!name.startsWith(prefix)) return null;
|
|
561
|
+
return name.slice(prefix.length);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function ctorMethod(method: string): string {
|
|
565
|
+
return method === "from" || method.startsWith("new") ? "new" : method;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const ARRAY_HOST_ALIAS: Record<string, string> = {
|
|
569
|
+
percentile_linear_interpolation: "percentile_linear",
|
|
570
|
+
percentile_nearest_rank: "percentile_nearest",
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const MATRIX_HOST_ALIAS: Record<string, string> = {
|
|
574
|
+
column: "col",
|
|
575
|
+
add_column: "add_col",
|
|
576
|
+
remove_column: "remove_col",
|
|
577
|
+
sum_all: "sum",
|
|
578
|
+
avg_all: "avg",
|
|
579
|
+
min_all: "min",
|
|
580
|
+
max_all: "max",
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
function emitHostCall(ns: string, method: string, args: CallArgs, params: string[][]): string {
|
|
584
|
+
const parts = params.map((names, i) => pick(args, i, names, "null"));
|
|
585
|
+
return `__h.${ns}.${method}(${parts.join(", ")})`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function emitArray(name: string, args: CallArgs): string | null {
|
|
589
|
+
const raw = nsMethod(name, "array_");
|
|
590
|
+
if (raw == null) return null;
|
|
591
|
+
const method = ctorMethod(raw);
|
|
592
|
+
const params = ARRAY_SIGS[method];
|
|
593
|
+
if (params == null) return null;
|
|
594
|
+
return emitHostCall("array", ARRAY_HOST_ALIAS[method] ?? method, args, params);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function emitMap(name: string, args: CallArgs): string | null {
|
|
598
|
+
const raw = nsMethod(name, "map_");
|
|
599
|
+
if (raw == null) return null;
|
|
600
|
+
const method = ctorMethod(raw);
|
|
601
|
+
const params = MAP_SIGS[method];
|
|
602
|
+
if (params == null) return null;
|
|
603
|
+
return emitHostCall("map", method, args, params);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function emitMatrix(name: string, args: CallArgs): string | null {
|
|
607
|
+
const raw = nsMethod(name, "matrix_");
|
|
608
|
+
if (raw == null) return null;
|
|
609
|
+
const method = ctorMethod(raw);
|
|
610
|
+
const params = MATRIX_SIGS[method];
|
|
611
|
+
if (params == null) return null;
|
|
612
|
+
return emitHostCall("matrix", MATRIX_HOST_ALIAS[method] ?? method, args, params);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function drawKindAndMethod(name: string, methodSrc: expr | null): { kind: string; method: string } | null {
|
|
616
|
+
if (methodSrc != null && methodSrc.kind === "Name") {
|
|
617
|
+
const id = (methodSrc as Name).id;
|
|
618
|
+
if (DRAW_KINDS.has(id)) return { kind: id, method: name };
|
|
619
|
+
}
|
|
620
|
+
const us = name.indexOf("_");
|
|
621
|
+
if (us > 0) {
|
|
622
|
+
const kind = name.slice(0, us);
|
|
623
|
+
if (DRAW_KINDS.has(kind)) return { kind, method: name.slice(us + 1) };
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const DRAW_METHODS: Record<string, Record<string, string[][]>> = {
|
|
629
|
+
label: {
|
|
630
|
+
set_text: [["id"], ["text"]],
|
|
631
|
+
set_xy: [["id"], ["x"], ["y"]],
|
|
632
|
+
set_color: [["id"], ["color"]],
|
|
633
|
+
set_style: [["id"], ["style"]],
|
|
634
|
+
set_size: [["id"], ["size"]],
|
|
635
|
+
set_tooltip: [["id"], ["tooltip"]],
|
|
636
|
+
set_textalign: [["id"], ["textalign"]],
|
|
637
|
+
delete: [["id"]],
|
|
638
|
+
get_text: [["id"]],
|
|
639
|
+
get_x: [["id"]],
|
|
640
|
+
get_y: [["id"]],
|
|
641
|
+
},
|
|
642
|
+
line: {
|
|
643
|
+
set_xy: [["id"], ["x1"], ["y1"], ["x2"], ["y2"]],
|
|
644
|
+
set_xy1: [["id"], ["x1"], ["y1"]],
|
|
645
|
+
set_xy2: [["id"], ["x2"], ["y2"]],
|
|
646
|
+
set_color: [["id"], ["color"]],
|
|
647
|
+
set_width: [["id"], ["width"]],
|
|
648
|
+
set_style: [["id"], ["style"]],
|
|
649
|
+
set_extend: [["id"], ["extend"]],
|
|
650
|
+
delete: [["id"]],
|
|
651
|
+
get_price: [["id"], ["x"]],
|
|
652
|
+
get_x1: [["id"]],
|
|
653
|
+
get_y1: [["id"]],
|
|
654
|
+
get_x2: [["id"]],
|
|
655
|
+
get_y2: [["id"]],
|
|
656
|
+
},
|
|
657
|
+
box: {
|
|
658
|
+
set_corners: [["id"], ["left", "x1"], ["top", "y1"], ["right", "x2"], ["bottom", "y2"]],
|
|
659
|
+
set_lefttop: [["id"], ["left", "x1"], ["top", "y1"], ["right", "x2"], ["bottom", "y2"]],
|
|
660
|
+
set_bgcolor: [["id"], ["bgcolor", "color"]],
|
|
661
|
+
set_border_color: [["id"], ["color", "border_color"]],
|
|
662
|
+
set_text: [["id"], ["text"]],
|
|
663
|
+
set_extend: [["id"], ["extend"]],
|
|
664
|
+
delete: [["id"]],
|
|
665
|
+
},
|
|
666
|
+
table: {
|
|
667
|
+
cell: [["id"], ["column", "col"], ["row"], ["text"]],
|
|
668
|
+
cell_set_text: [["id"], ["column", "col"], ["row"], ["text"]],
|
|
669
|
+
set_position: [["id"], ["position"]],
|
|
670
|
+
delete: [["id"]],
|
|
671
|
+
},
|
|
672
|
+
polyline: {
|
|
673
|
+
delete: [["id"]],
|
|
674
|
+
},
|
|
675
|
+
linefill: {
|
|
676
|
+
set_color: [["id"], ["color"]],
|
|
677
|
+
delete: [["id"]],
|
|
678
|
+
},
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
function emitDraw(ctx: EmitCtx, name: string, methodSrc: expr | null, args: CallArgs): string | null {
|
|
682
|
+
if (name === "alert" && methodSrc == null) {
|
|
683
|
+
ctx.usesDrawings = true;
|
|
684
|
+
return `__h.draw.alert(__bar_idx, ${pick(args, 0, ["message"], "null")})`;
|
|
685
|
+
}
|
|
686
|
+
const parsed = drawKindAndMethod(name, methodSrc);
|
|
687
|
+
if (parsed == null) return null;
|
|
688
|
+
const { kind, method } = parsed;
|
|
689
|
+
let code: string | null = null;
|
|
690
|
+
if (method === "new") {
|
|
691
|
+
if (kind === "label") {
|
|
692
|
+
code = `__h.draw.label_new(__bar_idx, ${pick(args, 2, ["text"], "null")})`;
|
|
693
|
+
} else if (kind === "linefill") {
|
|
694
|
+
code = `__h.draw.linefill_new(__bar_idx, ${pick(args, 0, ["id1"], "null")}, ${pick(args, 1, ["id2"], "null")})`;
|
|
695
|
+
} else if (DRAW_KINDS.has(kind)) {
|
|
696
|
+
code = `__h.draw.${kind}_new(__bar_idx)`;
|
|
697
|
+
}
|
|
698
|
+
} else {
|
|
699
|
+
const aliased = kind === "box" && method === "set_lefttop" ? "set_corners" : method;
|
|
700
|
+
const params = DRAW_METHODS[kind]?.[method] ?? DRAW_METHODS[kind]?.[aliased];
|
|
701
|
+
if (params != null) {
|
|
702
|
+
code = emitHostCall("draw", `${kind}_${aliased}`, args, params);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (code != null) ctx.usesDrawings = true;
|
|
706
|
+
return code;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function emitUdt(ctx: EmitCtx, name: string, methodSrc: expr | null, args: CallArgs): string | null {
|
|
710
|
+
if (methodSrc == null || methodSrc.kind !== "Name") return null;
|
|
711
|
+
const id = (methodSrc as Name).id;
|
|
712
|
+
const spec = ctx.udtTypes.get(id);
|
|
713
|
+
if (spec == null) return null;
|
|
714
|
+
if (name === "copy") {
|
|
715
|
+
return `({...${pick(args, 0, ["id", "this", "source"], "null")}})`;
|
|
716
|
+
}
|
|
717
|
+
if (name !== "new") return null;
|
|
718
|
+
const parts: string[] = [];
|
|
719
|
+
const usedKw = new Set<string>();
|
|
720
|
+
let posIdx = 0;
|
|
721
|
+
for (const field of spec.fields) {
|
|
722
|
+
let expr: string | undefined;
|
|
723
|
+
if (Object.hasOwn(args.kw, field)) {
|
|
724
|
+
expr = args.kw[field];
|
|
725
|
+
usedKw.add(field);
|
|
726
|
+
} else if (posIdx < args.pos.length) {
|
|
727
|
+
expr = args.pos[posIdx++];
|
|
728
|
+
} else if (Object.hasOwn(spec.defaults, field)) {
|
|
729
|
+
expr = spec.defaults[field];
|
|
730
|
+
}
|
|
731
|
+
if (expr != null) parts.push(`${field}: ${expr}`);
|
|
732
|
+
}
|
|
733
|
+
for (const [k, v] of Object.entries(args.kw)) {
|
|
734
|
+
if (usedKw.has(k)) continue;
|
|
735
|
+
parts.push(`${k}: ${v}`);
|
|
736
|
+
}
|
|
737
|
+
return `__h.udtNew(${JSON.stringify(id)}, {${parts.join(", ")}})`;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const SECURITY_FUNCS = new Set([
|
|
741
|
+
"security",
|
|
742
|
+
"request_security",
|
|
743
|
+
"request_security_lower_tf",
|
|
744
|
+
"request_seed",
|
|
745
|
+
]);
|
|
746
|
+
|
|
747
|
+
const SIMPLE_CHART_EXPR = new Set([
|
|
748
|
+
"close",
|
|
749
|
+
"open",
|
|
750
|
+
"high",
|
|
751
|
+
"low",
|
|
752
|
+
"volume",
|
|
753
|
+
"time",
|
|
754
|
+
"hl2",
|
|
755
|
+
"hlc3",
|
|
756
|
+
"ohlc4",
|
|
757
|
+
]);
|
|
758
|
+
|
|
759
|
+
const SIMPLE_CHART_ARR = /^(?:open|high|low|close|vol|volume|time|hl2|hlc3|ohlc4)_arr\[__bar_idx\]$/;
|
|
760
|
+
|
|
761
|
+
/** `p.plus(1)` → method UDF with instance first. TA methods stay TA. */
|
|
762
|
+
function emitUdtMethod(
|
|
763
|
+
ctx: EmitCtx,
|
|
764
|
+
name: string,
|
|
765
|
+
methodSrc: expr | null,
|
|
766
|
+
args: CallArgs,
|
|
767
|
+
visit: VisitFn,
|
|
768
|
+
): string | null {
|
|
769
|
+
if (methodSrc == null || methodSrc.kind !== "Name") return null;
|
|
770
|
+
if (!ctx.udtMethodNames.has(name) && !ctx.userFuncs.has(name)) return null;
|
|
771
|
+
if (TA_METHODS.has(name)) return null;
|
|
772
|
+
return emitUserFuncCall(ctx, name, [visit(methodSrc), ...args.pos]);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** Same-symbol OHLCV passthrough only. HA marker remaps OHLC. Foreign / complex expr → `null`. */
|
|
776
|
+
function emitRequest(ctx: EmitCtx, name: string, args: CallArgs): string | null {
|
|
777
|
+
if (SECURITY_FUNCS.has(name)) {
|
|
778
|
+
const symbol = pick(args, 0, ["symbol", "ticker"], "null");
|
|
779
|
+
const expression = pick(args, 2, ["expression", "expr"], "close");
|
|
780
|
+
if (isHeikinashiSecurity(symbol) && isSimpleSecurityExpr(expression)) {
|
|
781
|
+
ctx.needsHeikinashi = true;
|
|
782
|
+
return mapOhlcvExprToHeikinashi(expression);
|
|
783
|
+
}
|
|
784
|
+
if (isSimpleSecurityExpr(expression) && isChartSecuritySymbol(symbol)) return expression;
|
|
785
|
+
return "null";
|
|
786
|
+
}
|
|
787
|
+
if (name === "request_currency_rate" || name === "currency_rate") {
|
|
788
|
+
return `__h.currencyRate(${pick(args, 0, ["from", "from_currency"], "null")}, ${pick(args, 1, ["to", "to_currency"], "null")})`;
|
|
789
|
+
}
|
|
790
|
+
if (name.startsWith("request_")) return "null";
|
|
791
|
+
return null;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function isSimpleSecurityExpr(expr: string): boolean {
|
|
795
|
+
const e = expr.trim();
|
|
796
|
+
if (!e) return false;
|
|
797
|
+
if (SIMPLE_CHART_EXPR.has(e)) return true;
|
|
798
|
+
return SIMPLE_CHART_ARR.test(e);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function unquoteJsString(s: string): string {
|
|
802
|
+
if (s.length >= 2 && ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'")))) {
|
|
803
|
+
if (s.startsWith('"')) {
|
|
804
|
+
try {
|
|
805
|
+
return JSON.parse(s) as string;
|
|
806
|
+
} catch {
|
|
807
|
+
return s.slice(1, -1);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return s.slice(1, -1);
|
|
811
|
+
}
|
|
812
|
+
return s;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function isHeikinashiSecurity(sym: string): boolean {
|
|
816
|
+
const s = sym.trim();
|
|
817
|
+
if (s === "__HEIKINASHI__" || s === "'__HEIKINASHI__'" || s === '"__HEIKINASHI__"') return true;
|
|
818
|
+
if (/^['"]__HEIKINASHI__['"]$/.test(s)) return true;
|
|
819
|
+
if (s.includes("__HEIKINASHI__") && !s.toUpperCase().includes("HA(")) return true;
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const HA_OHLC_MAP: Record<string, string> = {
|
|
824
|
+
open: "ha_open_arr[__bar_idx]",
|
|
825
|
+
high: "ha_high_arr[__bar_idx]",
|
|
826
|
+
low: "ha_low_arr[__bar_idx]",
|
|
827
|
+
close: "ha_close_arr[__bar_idx]",
|
|
828
|
+
Open: "ha_open_arr[__bar_idx]",
|
|
829
|
+
High: "ha_high_arr[__bar_idx]",
|
|
830
|
+
Low: "ha_low_arr[__bar_idx]",
|
|
831
|
+
Close: "ha_close_arr[__bar_idx]",
|
|
832
|
+
open_arr: "ha_open_arr",
|
|
833
|
+
high_arr: "ha_high_arr",
|
|
834
|
+
low_arr: "ha_low_arr",
|
|
835
|
+
close_arr: "ha_close_arr",
|
|
836
|
+
Open_arr: "ha_open_arr",
|
|
837
|
+
High_arr: "ha_high_arr",
|
|
838
|
+
Low_arr: "ha_low_arr",
|
|
839
|
+
Close_arr: "ha_close_arr",
|
|
840
|
+
"open_arr[__bar_idx]": "ha_open_arr[__bar_idx]",
|
|
841
|
+
"high_arr[__bar_idx]": "ha_high_arr[__bar_idx]",
|
|
842
|
+
"low_arr[__bar_idx]": "ha_low_arr[__bar_idx]",
|
|
843
|
+
"close_arr[__bar_idx]": "ha_close_arr[__bar_idx]",
|
|
844
|
+
"Open_arr[__bar_idx]": "ha_open_arr[__bar_idx]",
|
|
845
|
+
"High_arr[__bar_idx]": "ha_high_arr[__bar_idx]",
|
|
846
|
+
"Low_arr[__bar_idx]": "ha_low_arr[__bar_idx]",
|
|
847
|
+
"Close_arr[__bar_idx]": "ha_close_arr[__bar_idx]",
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
/** Chart OHLC → HA arrays. volume / time stay as chart. */
|
|
851
|
+
function mapOhlcvExprToHeikinashi(expr: string): string {
|
|
852
|
+
return HA_OHLC_MAP[expr.trim()] ?? expr;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function isChartSecuritySymbol(sym: string): boolean {
|
|
856
|
+
const s = sym.trim();
|
|
857
|
+
if (!s || s === "null" || s === "na" || s === "undefined" || s === "None" || s === "np.nan") {
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
if (isHeikinashiSecurity(s)) return true;
|
|
861
|
+
const inner = unquoteJsString(s).trim();
|
|
862
|
+
if (!inner || inner === "na" || inner === "null" || inner === "SYMBOL") return true;
|
|
863
|
+
if (s === "SYMBOL" || s === "tickerid" || s === "ticker") return true;
|
|
864
|
+
if (s === "syminfo_ticker" || s === "syminfo_tickerid") return true;
|
|
865
|
+
const low = s.toLowerCase();
|
|
866
|
+
if (low.includes("syminfo") && (s.includes("ticker") || s.includes("tickerid") || s.includes("root") || s.includes("prefix"))) {
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function constString(node: expr | undefined): string | null {
|
|
873
|
+
if (node == null || node.kind !== "Constant") return null;
|
|
874
|
+
const v = (node as Constant).value;
|
|
875
|
+
return typeof v === "string" ? v : null;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function isStrategyAction(name: string, method: string): boolean {
|
|
879
|
+
return (
|
|
880
|
+
STRATEGY_ACTIONS.has(name) ||
|
|
881
|
+
method === "entry" ||
|
|
882
|
+
method === "close" ||
|
|
883
|
+
method === "close_all" ||
|
|
884
|
+
method === "exit" ||
|
|
885
|
+
method === "order" ||
|
|
886
|
+
method === "cancel" ||
|
|
887
|
+
method === "cancel_all" ||
|
|
888
|
+
method.startsWith("risk_") ||
|
|
889
|
+
name.startsWith("strategy_entry") ||
|
|
890
|
+
name.startsWith("strategy_risk")
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function emitStrategyEntry(method: "entry" | "order", args: CallArgs): string {
|
|
895
|
+
const id = pick(args, 0, ["id"], "null");
|
|
896
|
+
const direction = pick(args, 1, ["direction"], "null");
|
|
897
|
+
const qty = pick(args, 2, ["qty"], "null");
|
|
898
|
+
const fields: string[] = [];
|
|
899
|
+
if (hasArg(args, 3, ["limit"])) fields.push(`limit: ${pick(args, 3, ["limit"], "null")}`);
|
|
900
|
+
if (hasArg(args, 4, ["stop"])) fields.push(`stop: ${pick(args, 4, ["stop"], "null")}`);
|
|
901
|
+
if (hasArg(args, 5, ["comment"])) fields.push(`comment: ${pick(args, 5, ["comment"], "null")}`);
|
|
902
|
+
return `__h.strategy.${method}(${id}, ${direction}, ${qty}, {${fields.join(", ")}})`;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function emitStrategy(ctx: EmitCtx, name: string, args: CallArgs): string {
|
|
906
|
+
const method = name.startsWith("strategy_") ? name.slice("strategy_".length) : name;
|
|
907
|
+
|
|
908
|
+
if (method === "long" || method === "short") return JSON.stringify(method);
|
|
909
|
+
|
|
910
|
+
if (isStrategyAction(name, method) || STRATEGY_QUERIES.has(name)) ctx.usesStrategy = true;
|
|
911
|
+
|
|
912
|
+
switch (method) {
|
|
913
|
+
case "entry":
|
|
914
|
+
case "order":
|
|
915
|
+
ctx.usesStrategy = true;
|
|
916
|
+
return emitStrategyEntry(method, args);
|
|
917
|
+
case "close": {
|
|
918
|
+
ctx.usesStrategy = true;
|
|
919
|
+
const id = pick(args, 0, ["id"], "null");
|
|
920
|
+
return hasArg(args, 1, ["qty"])
|
|
921
|
+
? `__h.strategy.close(${id}, ${pick(args, 1, ["qty"], "null")})`
|
|
922
|
+
: `__h.strategy.close(${id})`;
|
|
923
|
+
}
|
|
924
|
+
case "close_all":
|
|
925
|
+
ctx.usesStrategy = true;
|
|
926
|
+
return "__h.strategy.close_all()";
|
|
927
|
+
case "exit": {
|
|
928
|
+
ctx.usesStrategy = true;
|
|
929
|
+
const fromEntry = pick(args, 1, ["from_entry"], "null");
|
|
930
|
+
return hasArg(args, 2, ["qty"])
|
|
931
|
+
? `__h.strategy.exit(${fromEntry}, ${pick(args, 2, ["qty"], "null")})`
|
|
932
|
+
: `__h.strategy.exit(${fromEntry})`;
|
|
933
|
+
}
|
|
934
|
+
case "cancel":
|
|
935
|
+
ctx.usesStrategy = true;
|
|
936
|
+
return `__h.strategy.cancel(${pick(args, 0, ["id"], "null")})`;
|
|
937
|
+
case "cancel_all":
|
|
938
|
+
ctx.usesStrategy = true;
|
|
939
|
+
return "__h.strategy.cancel_all()";
|
|
940
|
+
case "position_size":
|
|
941
|
+
case "netprofit":
|
|
942
|
+
case "equity":
|
|
943
|
+
case "openprofit":
|
|
944
|
+
case "opentrades":
|
|
945
|
+
case "closedtrades":
|
|
946
|
+
ctx.usesStrategy = true;
|
|
947
|
+
return `__h.strategy.${method}()`;
|
|
948
|
+
case "risk_allow_entry_in":
|
|
949
|
+
ctx.usesStrategy = true;
|
|
950
|
+
return `__h.strategy.risk_allow_entry_in(${pick(args, 0, ["value"], "null")})`;
|
|
951
|
+
case "risk_max_position_size":
|
|
952
|
+
ctx.usesStrategy = true;
|
|
953
|
+
return `__h.strategy.risk_max_position_size(${pick(args, 0, ["percent", "value"], "null")})`;
|
|
954
|
+
case "risk_max_drawdown":
|
|
955
|
+
ctx.usesStrategy = true;
|
|
956
|
+
return `__h.strategy.risk_max_drawdown(${pick(args, 0, ["value"], "null")}, ${pick(args, 1, ["type"], "null")})`;
|
|
957
|
+
default:
|
|
958
|
+
if (isStrategyAction(name, method)) ctx.usesStrategy = true;
|
|
959
|
+
return "null";
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function seriesNameRef(ctx: EmitCtx, id: string): string {
|
|
964
|
+
const chart = CHART_SERIES_ARR[id];
|
|
965
|
+
if (chart != null) return chart;
|
|
966
|
+
const raw = `${id}_arr`;
|
|
967
|
+
const safe = `__u_${id}_arr`;
|
|
968
|
+
if (ctx.arrays.has(raw)) return raw;
|
|
969
|
+
if (ctx.arrays.has(safe)) return safe;
|
|
970
|
+
return raw;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function seriesArg(ctx: EmitCtx, node: expr | undefined, visited: string): string {
|
|
974
|
+
if (node != null && node.kind === "Name") return seriesNameRef(ctx, (node as Name).id);
|
|
975
|
+
return visited || "null";
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function packUserFuncArgs(ctx: EmitCtx, name: string, args: CallArgs): string[] {
|
|
979
|
+
const formals = ctx.funcParamNames.get(name);
|
|
980
|
+
const series = ctx.funcSeriesParams.get(name);
|
|
981
|
+
if (formals == null || formals.length === 0) return args.pos.slice();
|
|
982
|
+
|
|
983
|
+
const packed: string[] = [];
|
|
984
|
+
for (let i = 0; i < formals.length; i++) {
|
|
985
|
+
const formal = formals[i]!;
|
|
986
|
+
const fromPos = i < args.pos.length;
|
|
987
|
+
const node = fromPos ? args.posNodes[i] : args.kwNodes[formal];
|
|
988
|
+
const visited = fromPos ? args.pos[i]! : (args.kw[formal] ?? "null");
|
|
989
|
+
packed.push(series?.has(formal) ? seriesArg(ctx, node, visited) : visited);
|
|
990
|
+
}
|
|
991
|
+
for (let i = formals.length; i < args.pos.length; i++) packed.push(args.pos[i]!);
|
|
992
|
+
return packed;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function emitPlot(ctx: EmitCtx, _fname: string, args: CallArgs): string {
|
|
996
|
+
let title: string | null = constString(args.kwNodes.title);
|
|
997
|
+
if (title == null) {
|
|
998
|
+
for (const n of args.posNodes) {
|
|
999
|
+
const s = constString(n);
|
|
1000
|
+
if (s != null) {
|
|
1001
|
+
title = s;
|
|
1002
|
+
break;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
if (title == null || title === "") title = `plot_${ctx.plots.length}`;
|
|
1007
|
+
const series = pick(args, 0, ["series", "source", "price"], "null");
|
|
1008
|
+
const i = ctx.plots.length;
|
|
1009
|
+
ctx.plots.push({ title });
|
|
1010
|
+
return `plot_${i}[__bar_idx] = __h.naNum(${series})`;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function emitColor(name: string, args: CallArgs): string | null {
|
|
1014
|
+
if (name === "color_r") return `__h.colorR(${pick(args, 0, ["color"], "null")})`;
|
|
1015
|
+
if (name === "color_g") return `__h.colorG(${pick(args, 0, ["color"], "null")})`;
|
|
1016
|
+
if (name === "color_b") return `__h.colorB(${pick(args, 0, ["color"], "null")})`;
|
|
1017
|
+
if (name === "color_t") return `__h.colorT(${pick(args, 0, ["color"], "null")})`;
|
|
1018
|
+
if (name === "color_rgb") {
|
|
1019
|
+
const r = pick(args, 0, ["red", "r"], "null");
|
|
1020
|
+
const g = pick(args, 1, ["green", "g"], "null");
|
|
1021
|
+
const b = pick(args, 2, ["blue", "b"], "null");
|
|
1022
|
+
if (hasArg(args, 3, ["transp", "t", "transparency"])) {
|
|
1023
|
+
return `__h.colorRgb(${r}, ${g}, ${b}, ${pick(args, 3, ["transp", "t", "transparency"], "null")})`;
|
|
1024
|
+
}
|
|
1025
|
+
return `__h.colorRgb(${r}, ${g}, ${b})`;
|
|
1026
|
+
}
|
|
1027
|
+
if (name === "color_from_gradient") {
|
|
1028
|
+
return `__h.colorFromGradient(${pick(args, 0, ["value"], "null")}, ${pick(args, 1, ["bottom_value", "bottom"], "null")}, ${pick(args, 2, ["top_value", "top"], "null")}, ${pick(args, 3, ["color1"], "null")}, ${pick(args, 4, ["color2"], "null")})`;
|
|
1029
|
+
}
|
|
1030
|
+
if (name === "color_new" || name === "color") {
|
|
1031
|
+
const color = pick(args, 0, ["color"], "null");
|
|
1032
|
+
if (hasArg(args, 1, ["transp", "transparency"])) {
|
|
1033
|
+
return `__h.colorNew(${color}, ${pick(args, 1, ["transp", "transparency"], "null")})`;
|
|
1034
|
+
}
|
|
1035
|
+
return `__h.colorNew(${color})`;
|
|
1036
|
+
}
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function emitInput(ctx: EmitCtx, args: CallArgs): string {
|
|
1041
|
+
const def = pick(args, 0, ["defval"], "null");
|
|
1042
|
+
return `__h.input(${JSON.stringify(inputKey(ctx, args))}, ${def})`;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function inputKey(ctx: EmitCtx, args: CallArgs): string {
|
|
1046
|
+
const titleKw = constString(args.kwNodes.title);
|
|
1047
|
+
if (titleKw != null && titleKw !== "") return titleKw;
|
|
1048
|
+
const defvalKw = constString(args.kwNodes.defval);
|
|
1049
|
+
if (defvalKw != null && defvalKw !== "") return defvalKw;
|
|
1050
|
+
if (args.posNodes.length > 1) {
|
|
1051
|
+
const posTitle = constString(args.posNodes[1]);
|
|
1052
|
+
if (posTitle != null && posTitle !== "") return posTitle;
|
|
1053
|
+
}
|
|
1054
|
+
const bag = ctx as EmitCtx & { nextInput?: number };
|
|
1055
|
+
const i = bag.nextInput ?? 0;
|
|
1056
|
+
bag.nextInput = i + 1;
|
|
1057
|
+
return i === 0 ? "input" : `input${i}`;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function emitMath(name: string, args: CallArgs): string | null {
|
|
1061
|
+
const bare = name.startsWith("math_") ? name.slice(5) : name;
|
|
1062
|
+
const isNs = name.startsWith("math_");
|
|
1063
|
+
const isBareMath =
|
|
1064
|
+
name === "abs" ||
|
|
1065
|
+
name === "max" ||
|
|
1066
|
+
name === "min" ||
|
|
1067
|
+
name === "sqrt" ||
|
|
1068
|
+
name === "log" ||
|
|
1069
|
+
name === "log10" ||
|
|
1070
|
+
name === "pow" ||
|
|
1071
|
+
name === "sign" ||
|
|
1072
|
+
name === "floor" ||
|
|
1073
|
+
name === "ceil" ||
|
|
1074
|
+
name === "round" ||
|
|
1075
|
+
name === "exp" ||
|
|
1076
|
+
name === "sin" ||
|
|
1077
|
+
name === "cos" ||
|
|
1078
|
+
name === "tan" ||
|
|
1079
|
+
name === "asin" ||
|
|
1080
|
+
name === "acos" ||
|
|
1081
|
+
name === "atan" ||
|
|
1082
|
+
name === "avg" ||
|
|
1083
|
+
name === "random" ||
|
|
1084
|
+
name === "todegrees" ||
|
|
1085
|
+
name === "toradians" ||
|
|
1086
|
+
name === "iff" ||
|
|
1087
|
+
name === "fixnan";
|
|
1088
|
+
if (!isNs && !isBareMath) return null;
|
|
1089
|
+
|
|
1090
|
+
if (bare === "iff") {
|
|
1091
|
+
const cond = pick(args, 0, ["condition", "cond"], "null");
|
|
1092
|
+
const a = pick(args, 1, ["then", "if_true"], "null");
|
|
1093
|
+
const b = pick(args, 2, ["else", "if_false"], "null");
|
|
1094
|
+
return `(__h.nz(${cond}) ? ${a} : ${b})`;
|
|
1095
|
+
}
|
|
1096
|
+
if (bare === "fixnan") {
|
|
1097
|
+
return `__h.nz(${pick(args, 0, ["source", "x"], "null")}, 0)`;
|
|
1098
|
+
}
|
|
1099
|
+
if (bare === "max" || bare === "min") {
|
|
1100
|
+
const xs = variadic(args);
|
|
1101
|
+
if (xs.length === 0) return "null";
|
|
1102
|
+
if (xs.length === 1) return naUnary(xs[0]!, "__x");
|
|
1103
|
+
const init = bare === "max" ? "-Infinity" : "Infinity";
|
|
1104
|
+
const cmp = bare === "max" ? "__x > __m" : "__x < __m";
|
|
1105
|
+
return `((...__xs) => { if (__xs.length === 0) return null; let __m = ${init}; for (const __x of __xs) { if (__h.isNa(__x)) return null; if (${cmp}) __m = Number(__x); } return Number.isFinite(__m) ? __m : null; })(${xs.join(", ")})`;
|
|
1106
|
+
}
|
|
1107
|
+
if (bare === "avg") {
|
|
1108
|
+
const xs = variadic(args);
|
|
1109
|
+
if (xs.length === 0) return "null";
|
|
1110
|
+
if (xs.length === 1) return naUnary(xs[0]!, "__x");
|
|
1111
|
+
return `((...__xs) => { let __s = 0; for (const __x of __xs) { if (__h.isNa(__x)) return null; __s += Number(__x); } const __r = __s / __xs.length; return Number.isFinite(__r) ? __r : null; })(${xs.join(", ")})`;
|
|
1112
|
+
}
|
|
1113
|
+
if (bare === "sum" && isNs) {
|
|
1114
|
+
const xs = variadic(args);
|
|
1115
|
+
if (xs.length === 0) return "null";
|
|
1116
|
+
return `((...__xs) => { let __s = 0; for (const __x of __xs) { if (__h.isNa(__x)) return null; __s += Number(__x); } return Number.isFinite(__s) ? __s : null; })(${xs.join(", ")})`;
|
|
1117
|
+
}
|
|
1118
|
+
if (bare === "pow") {
|
|
1119
|
+
const b = pick(args, 0, ["base", "number", "x"], "null");
|
|
1120
|
+
const e = hasArg(args, 1, ["exponent", "exp", "y"]) ? pick(args, 1, ["exponent", "exp", "y"], "2") : "2";
|
|
1121
|
+
return `((__b, __e) => { if (__h.isNa(__b) || __h.isNa(__e)) return null; const __r = Math.pow(Number(__b), Number(__e)); return Number.isFinite(__r) ? __r : null; })(${b}, ${e})`;
|
|
1122
|
+
}
|
|
1123
|
+
if (bare === "round") {
|
|
1124
|
+
const x = pick(args, 0, ["number", "x"], "null");
|
|
1125
|
+
if (!hasArg(args, 1, ["precision"])) {
|
|
1126
|
+
return `((__x) => { if (__h.isNa(__x)) return null; const __n = Number(__x); const __r = __n >= 0 ? Math.round(__n) : -Math.round(-__n); return Number.isFinite(__r) ? __r : null; })(${x})`;
|
|
1127
|
+
}
|
|
1128
|
+
const p = pick(args, 1, ["precision"], "0");
|
|
1129
|
+
return `((__x, __p) => { if (__h.isNa(__x) || __h.isNa(__p)) return null; const __n = Number(__x); const __f = 10 ** Math.trunc(Number(__p)); if (!Number.isFinite(__f)) return null; const __y = __n * __f; const __r = (__y >= 0 ? Math.round(__y) : -Math.round(-__y)) / __f; return Number.isFinite(__r) ? __r : null; })(${x}, ${p})`;
|
|
1130
|
+
}
|
|
1131
|
+
if (bare === "random") {
|
|
1132
|
+
const a0 = hasArg(args, 0, ["min"]) ? pick(args, 0, ["min"], "null") : null;
|
|
1133
|
+
const a1 = hasArg(args, 1, ["max"]) ? pick(args, 1, ["max"], "null") : null;
|
|
1134
|
+
if (a0 == null) {
|
|
1135
|
+
return "((() => { const __r = Math.random(); return Number.isFinite(__r) ? __r : null; })())";
|
|
1136
|
+
}
|
|
1137
|
+
if (a1 == null) {
|
|
1138
|
+
return `((__hi) => { if (__h.isNa(__hi)) return null; const __r = Number(__hi) * Math.random(); return Number.isFinite(__r) ? __r : null; })(${a0})`;
|
|
1139
|
+
}
|
|
1140
|
+
return `((__lo, __hi) => { if (__h.isNa(__lo) || __h.isNa(__hi)) return null; const __r = Number(__lo) + (Number(__hi) - Number(__lo)) * Math.random(); return Number.isFinite(__r) ? __r : null; })(${a0}, ${a1})`;
|
|
1141
|
+
}
|
|
1142
|
+
if (bare === "log" && hasArg(args, 1, ["base"])) {
|
|
1143
|
+
const x = pick(args, 0, ["number", "x"], "null");
|
|
1144
|
+
const base = pick(args, 1, ["base"], "null");
|
|
1145
|
+
return `((__x, __b) => { if (__h.isNa(__x) || __h.isNa(__b) || Number(__x) <= 0 || Number(__b) <= 0 || Number(__b) === 1) return null; const __r = Math.log(Number(__x)) / Math.log(Number(__b)); return Number.isFinite(__r) ? __r : null; })(${x}, ${base})`;
|
|
1146
|
+
}
|
|
1147
|
+
const spec = MATH_UNARY[bare];
|
|
1148
|
+
if (spec != null) {
|
|
1149
|
+
const x = pick(args, 0, ["number", "x", "angle", "radians", "degrees"], "null");
|
|
1150
|
+
return naMath(x, spec.fn, spec.guard);
|
|
1151
|
+
}
|
|
1152
|
+
return isNs ? "null" : null;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
function variadic(args: CallArgs): string[] {
|
|
1156
|
+
if (args.pos.length > 0) return args.pos;
|
|
1157
|
+
return Object.values(args.kw);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function naUnary(x: string, _bind: string): string {
|
|
1161
|
+
return `((__x) => (__h.isNa(__x) ? null : Number.isFinite(Number(__x)) ? Number(__x) : null))(${x})`;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function naMath(x: string, expr: string, guard?: string): string {
|
|
1165
|
+
const extra = guard != null ? ` || (${guard})` : "";
|
|
1166
|
+
return `((__x) => { if (__h.isNa(__x)${extra}) return null; const __r = ${expr}; return Number.isFinite(__r) ? __r : null; })(${x})`;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function allocSite(ctx: EmitCtx): string {
|
|
1170
|
+
return JSON.stringify("c" + ctx.nextSite++);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function taCall(method: string, site: string, parts: string[]): string {
|
|
1174
|
+
return `__h.ta.${method}(${site}, ${parts.join(", ")})`;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
1178
|
+
if (!TA_METHODS.has(method)) return "null";
|
|
1179
|
+
const site = allocSite(ctx);
|
|
1180
|
+
const src = () => pick(args, 0, ["source", "series", "src"], CLOSE);
|
|
1181
|
+
const len = (index: number, fb: string, names: string[] = ["length", "len", "period"]) =>
|
|
1182
|
+
pick(args, index, names, fb);
|
|
1183
|
+
|
|
1184
|
+
if (SRC_LEN.has(method)) {
|
|
1185
|
+
return taCall(method, site, [src(), len(1, "14")]);
|
|
1186
|
+
}
|
|
1187
|
+
if (SRC_LEN1.has(method)) {
|
|
1188
|
+
return taCall(method, site, [src(), len(1, "1")]);
|
|
1189
|
+
}
|
|
1190
|
+
if (method === "highest" || method === "lowest" || method === "max" || method === "min") {
|
|
1191
|
+
const defSrc = method === "lowest" || method === "min" ? LOW : HIGH;
|
|
1192
|
+
if (args.pos.length >= 2 || Object.hasOwn(args.kw, "source") || Object.hasOwn(args.kw, "series")) {
|
|
1193
|
+
return taCall(method, site, [src(), len(1, "14")]);
|
|
1194
|
+
}
|
|
1195
|
+
if (args.pos.length === 1 || Object.hasOwn(args.kw, "length")) {
|
|
1196
|
+
return taCall(method, site, [defSrc, len(0, "14")]);
|
|
1197
|
+
}
|
|
1198
|
+
return taCall(method, site, [defSrc, "14"]);
|
|
1199
|
+
}
|
|
1200
|
+
if (method === "atr") {
|
|
1201
|
+
if (args.pos.length >= 4) {
|
|
1202
|
+
return taCall("atr", site, [args.pos[0]!, args.pos[1]!, args.pos[2]!, args.pos[3]!]);
|
|
1203
|
+
}
|
|
1204
|
+
return taCall("atr", site, [HIGH, LOW, CLOSE, len(0, "14")]);
|
|
1205
|
+
}
|
|
1206
|
+
if (method === "tr") {
|
|
1207
|
+
if (args.pos.length >= 3) {
|
|
1208
|
+
return taCall("tr", site, [args.pos[0]!, args.pos[1]!, args.pos[2]!]);
|
|
1209
|
+
}
|
|
1210
|
+
return taCall("tr", site, [HIGH, LOW, CLOSE]);
|
|
1211
|
+
}
|
|
1212
|
+
if (method === "cross" || method === "crossover" || method === "crossunder") {
|
|
1213
|
+
const a = pick(args, 0, ["source", "series", "a"], CLOSE);
|
|
1214
|
+
const b = pick(args, 1, ["source2", "b", "series2"], "0");
|
|
1215
|
+
return taCall(method, site, [a, b]);
|
|
1216
|
+
}
|
|
1217
|
+
if (method === "macd") {
|
|
1218
|
+
const call = taCall("macd", site, [
|
|
1219
|
+
src(),
|
|
1220
|
+
pick(args, 1, ["fastlen", "fastLength", "fast"], "12"),
|
|
1221
|
+
pick(args, 2, ["slowlen", "slowLength", "slow"], "26"),
|
|
1222
|
+
pick(args, 3, ["signal", "signalLength", "siglen"], "9"),
|
|
1223
|
+
]);
|
|
1224
|
+
return `(() => { const __r = ${call}; return [__r.macd, __r.signal, __r.hist]; })()`;
|
|
1225
|
+
}
|
|
1226
|
+
if (method === "bb") {
|
|
1227
|
+
const call = taCall("bb", site, [
|
|
1228
|
+
src(),
|
|
1229
|
+
len(1, "20"),
|
|
1230
|
+
pick(args, 2, ["mult", "multiplier"], "2"),
|
|
1231
|
+
]);
|
|
1232
|
+
return `(() => { const __r = ${call}; return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1233
|
+
}
|
|
1234
|
+
if (method === "bbw") {
|
|
1235
|
+
return taCall("bbw", site, [src(), len(1, "20"), pick(args, 2, ["mult", "multiplier"], "2")]);
|
|
1236
|
+
}
|
|
1237
|
+
if (method === "kc") {
|
|
1238
|
+
const call = taCall("kc", site, [
|
|
1239
|
+
HIGH,
|
|
1240
|
+
LOW,
|
|
1241
|
+
src(),
|
|
1242
|
+
len(1, "20"),
|
|
1243
|
+
pick(args, 2, ["mult", "multiplier"], "1"),
|
|
1244
|
+
]);
|
|
1245
|
+
return `(() => { const __r = ${call}; return [__r.mid, __r.up, __r.lo]; })()`;
|
|
1246
|
+
}
|
|
1247
|
+
if (method === "stoch") {
|
|
1248
|
+
if (args.pos.length <= 1 && args.kw.high == null) {
|
|
1249
|
+
return taCall("stoch", site, [CLOSE, HIGH, LOW, len(0, "14")]);
|
|
1250
|
+
}
|
|
1251
|
+
return taCall("stoch", site, [
|
|
1252
|
+
src(),
|
|
1253
|
+
pick(args, 1, ["high"], HIGH),
|
|
1254
|
+
pick(args, 2, ["low"], LOW),
|
|
1255
|
+
len(3, "14"),
|
|
1256
|
+
]);
|
|
1257
|
+
}
|
|
1258
|
+
if (method === "vwma") {
|
|
1259
|
+
return taCall("vwma", site, [src(), pick(args, 2, ["volume"], VOL), len(1, "14")]);
|
|
1260
|
+
}
|
|
1261
|
+
if (method === "cci") {
|
|
1262
|
+
return taCall("cci", site, [src(), len(1, "14")]);
|
|
1263
|
+
}
|
|
1264
|
+
if (method === "mfi") {
|
|
1265
|
+
if (args.pos.length <= 1) {
|
|
1266
|
+
return taCall("mfi", site, [HIGH, LOW, CLOSE, VOL, len(0, "14")]);
|
|
1267
|
+
}
|
|
1268
|
+
if (args.pos.length === 2) {
|
|
1269
|
+
return taCall("mfi", site, [src(), src(), src(), VOL, len(1, "14")]);
|
|
1270
|
+
}
|
|
1271
|
+
return taCall("mfi", site, [
|
|
1272
|
+
pick(args, 0, ["high"], HIGH),
|
|
1273
|
+
pick(args, 1, ["low"], LOW),
|
|
1274
|
+
pick(args, 2, ["close"], CLOSE),
|
|
1275
|
+
pick(args, 3, ["volume"], VOL),
|
|
1276
|
+
len(4, "14"),
|
|
1277
|
+
]);
|
|
1278
|
+
}
|
|
1279
|
+
if (method === "tsi") {
|
|
1280
|
+
if (args.pos.length === 2) {
|
|
1281
|
+
return taCall("tsi", site, [CLOSE, pick(args, 1, ["long", "long_length"], "25"), pick(args, 0, ["short", "short_length"], "13")]);
|
|
1282
|
+
}
|
|
1283
|
+
return taCall("tsi", site, [
|
|
1284
|
+
src(),
|
|
1285
|
+
pick(args, 2, ["long", "long_length"], "25"),
|
|
1286
|
+
pick(args, 1, ["short", "short_length"], "13"),
|
|
1287
|
+
]);
|
|
1288
|
+
}
|
|
1289
|
+
if (method === "dmi") {
|
|
1290
|
+
const call = taCall("dmi", site, [
|
|
1291
|
+
HIGH,
|
|
1292
|
+
LOW,
|
|
1293
|
+
CLOSE,
|
|
1294
|
+
pick(args, 0, ["diLength", "length"], "14"),
|
|
1295
|
+
pick(args, 1, ["adxSmoothing", "adxlen"], "14"),
|
|
1296
|
+
]);
|
|
1297
|
+
return `(() => { const __r = ${call}; return [__r.plus, __r.minus, __r.adx]; })()`;
|
|
1298
|
+
}
|
|
1299
|
+
if (method === "supertrend") {
|
|
1300
|
+
const call = taCall("supertrend", site, [
|
|
1301
|
+
HIGH,
|
|
1302
|
+
LOW,
|
|
1303
|
+
CLOSE,
|
|
1304
|
+
pick(args, 0, ["factor"], "3"),
|
|
1305
|
+
pick(args, 1, ["atrPeriod", "length"], "10"),
|
|
1306
|
+
]);
|
|
1307
|
+
return `(() => { const __r = ${call}; return [__r.st, __r.dir]; })()`;
|
|
1308
|
+
}
|
|
1309
|
+
if (method === "alma") {
|
|
1310
|
+
return taCall("alma", site, [
|
|
1311
|
+
src(),
|
|
1312
|
+
len(1, "9"),
|
|
1313
|
+
pick(args, 2, ["offset"], "0.85"),
|
|
1314
|
+
pick(args, 3, ["sigma"], "6"),
|
|
1315
|
+
]);
|
|
1316
|
+
}
|
|
1317
|
+
if (method === "kama") {
|
|
1318
|
+
return taCall("kama", site, [
|
|
1319
|
+
src(),
|
|
1320
|
+
len(1, "14"),
|
|
1321
|
+
pick(args, 2, ["fastLength", "fast", "fastlen"], "2"),
|
|
1322
|
+
pick(args, 3, ["slowLength", "slow", "slowlen"], "30"),
|
|
1323
|
+
]);
|
|
1324
|
+
}
|
|
1325
|
+
if (method === "linreg") {
|
|
1326
|
+
return taCall("linreg", site, [src(), len(1, "14"), pick(args, 2, ["offset"], "0")]);
|
|
1327
|
+
}
|
|
1328
|
+
if (method === "barssince") {
|
|
1329
|
+
return taCall("barssince", site, [pick(args, 0, ["condition", "cond"], "null")]);
|
|
1330
|
+
}
|
|
1331
|
+
if (method === "valuewhen") {
|
|
1332
|
+
return taCall("valuewhen", site, [
|
|
1333
|
+
pick(args, 0, ["condition", "cond"], "null"),
|
|
1334
|
+
pick(args, 1, ["source", "series"], CLOSE),
|
|
1335
|
+
pick(args, 2, ["occurrence", "occ"], "0"),
|
|
1336
|
+
]);
|
|
1337
|
+
}
|
|
1338
|
+
if (method === "pivothigh" || method === "pivotlow") {
|
|
1339
|
+
return taCall(method, site, [
|
|
1340
|
+
src(),
|
|
1341
|
+
pick(args, 1, ["leftbars", "left"], "5"),
|
|
1342
|
+
pick(args, 2, ["rightbars", "right"], "5"),
|
|
1343
|
+
]);
|
|
1344
|
+
}
|
|
1345
|
+
if (method === "swma") {
|
|
1346
|
+
if (hasArg(args, 1, ["length", "period"])) {
|
|
1347
|
+
return taCall("swma", site, [src(), len(1, "4")]);
|
|
1348
|
+
}
|
|
1349
|
+
return taCall("swma", site, [src()]);
|
|
1350
|
+
}
|
|
1351
|
+
if (method === "willr" || method === "wpr") {
|
|
1352
|
+
if (args.pos.length >= 4) {
|
|
1353
|
+
return taCall("willr", site, [args.pos[0]!, args.pos[1]!, args.pos[2]!, args.pos[3]!]);
|
|
1354
|
+
}
|
|
1355
|
+
return taCall("willr", site, [HIGH, LOW, CLOSE, len(0, "14")]);
|
|
1356
|
+
}
|
|
1357
|
+
if (method === "vwap") {
|
|
1358
|
+
return taCall("vwap", site, [src(), pick(args, 1, ["volume"], VOL)]);
|
|
1359
|
+
}
|
|
1360
|
+
if (method === "sar") {
|
|
1361
|
+
return taCall("sar", site, [
|
|
1362
|
+
HIGH,
|
|
1363
|
+
LOW,
|
|
1364
|
+
pick(args, 0, ["start"], "0.02"),
|
|
1365
|
+
pick(args, 1, ["increment"], "0.02"),
|
|
1366
|
+
pick(args, 2, ["maximum", "max"], "0.2"),
|
|
1367
|
+
]);
|
|
1368
|
+
}
|
|
1369
|
+
if (method === "adx") {
|
|
1370
|
+
return taCall("adx", site, [HIGH, LOW, CLOSE, len(0, "14")]);
|
|
1371
|
+
}
|
|
1372
|
+
if (method === "correlation") {
|
|
1373
|
+
return taCall("correlation", site, [
|
|
1374
|
+
pick(args, 0, ["source1", "source", "a"], CLOSE),
|
|
1375
|
+
pick(args, 1, ["source2", "b"], CLOSE),
|
|
1376
|
+
len(2, "14"),
|
|
1377
|
+
]);
|
|
1378
|
+
}
|
|
1379
|
+
if (method === "cum") {
|
|
1380
|
+
return taCall("cum", site, [src()]);
|
|
1381
|
+
}
|
|
1382
|
+
if (method === "accdist") {
|
|
1383
|
+
return taCall("accdist", site, [
|
|
1384
|
+
pick(args, 0, ["high"], HIGH),
|
|
1385
|
+
pick(args, 1, ["low"], LOW),
|
|
1386
|
+
pick(args, 2, ["close"], CLOSE),
|
|
1387
|
+
pick(args, 3, ["volume"], VOL),
|
|
1388
|
+
]);
|
|
1389
|
+
}
|
|
1390
|
+
if (method === "pvt" || method === "obv" || method === "nvi" || method === "pvi") {
|
|
1391
|
+
return taCall(method, site, [
|
|
1392
|
+
pick(args, 0, ["source", "close"], CLOSE),
|
|
1393
|
+
pick(args, 1, ["volume"], VOL),
|
|
1394
|
+
]);
|
|
1395
|
+
}
|
|
1396
|
+
if (method === "wad") {
|
|
1397
|
+
return taCall("wad", site, [HIGH, LOW, CLOSE]);
|
|
1398
|
+
}
|
|
1399
|
+
if (method === "iii") {
|
|
1400
|
+
return taCall("iii", site, [HIGH, LOW, CLOSE, VOL]);
|
|
1401
|
+
}
|
|
1402
|
+
if (method === "wvad") {
|
|
1403
|
+
return taCall("wvad", site, [OPEN, HIGH, LOW, CLOSE, VOL]);
|
|
1404
|
+
}
|
|
1405
|
+
return "null";
|
|
1406
|
+
}
|