@hoox-sh/pynets 0.4.0 → 0.6.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 +34 -0
- package/README.md +9 -5
- package/dist/browser.js +68 -68
- package/dist/index.js +2196 -206
- package/package.json +1 -1
- package/src/cli/rich.ts +1 -1
- package/src/generated/PinescriptLexerBase.ts +22 -4
- package/src/parser/LexerBase.ts +22 -4
- package/src/runtime/compile/draw.ts +26 -2
- package/src/runtime/compile/emit.ts +17 -0
- package/src/runtime/compile/emit_call.ts +378 -7
- package/src/runtime/compile/helpers.ts +2 -1
- package/src/runtime/compile/runtime.ts +71 -6
- package/src/runtime/compile/strategy.ts +5 -2
- package/src/runtime/footprint.ts +272 -0
- package/src/runtime/interpret.ts +621 -36
- package/src/runtime/request.ts +19 -4
- package/src/runtime/str.ts +3 -2
- package/src/runtime/strategy.ts +164 -12
- package/src/runtime/ta.ts +1090 -149
- package/src/runtime/time.ts +12 -0
|
@@ -46,6 +46,8 @@ const NS = new Set([
|
|
|
46
46
|
"syminfo",
|
|
47
47
|
"timenow",
|
|
48
48
|
"chart",
|
|
49
|
+
"footprint",
|
|
50
|
+
"volume_row",
|
|
49
51
|
]);
|
|
50
52
|
|
|
51
53
|
/** TaEngine methods we emit. Unknown `ta.*` → `null`. */
|
|
@@ -153,6 +155,44 @@ const TA_METHODS = new Set([
|
|
|
153
155
|
"volume_profile_high",
|
|
154
156
|
"volume_profile_low",
|
|
155
157
|
"volume_thrust",
|
|
158
|
+
"sma_weighted",
|
|
159
|
+
"apo",
|
|
160
|
+
"atr_normalized",
|
|
161
|
+
"beta",
|
|
162
|
+
"r_squared",
|
|
163
|
+
"comovement",
|
|
164
|
+
"skewness",
|
|
165
|
+
"kurtosis",
|
|
166
|
+
"volume_weighted_momentum",
|
|
167
|
+
"parkinson",
|
|
168
|
+
"garman_klass",
|
|
169
|
+
"mean_reversion_score",
|
|
170
|
+
"market_condition",
|
|
171
|
+
"order_flow_imbalance",
|
|
172
|
+
"liquidity_score",
|
|
173
|
+
"fear_greed_index",
|
|
174
|
+
"crowd_sentiment",
|
|
175
|
+
"smart_money_flow",
|
|
176
|
+
"trend_confirmation_score",
|
|
177
|
+
"max_loss_level",
|
|
178
|
+
"profit_lock_level",
|
|
179
|
+
"position_sizing",
|
|
180
|
+
"kelly_criterion",
|
|
181
|
+
"risk_reward_ratio",
|
|
182
|
+
"risk_reward_asymmetry",
|
|
183
|
+
"probability_of_movement",
|
|
184
|
+
"trend_strength",
|
|
185
|
+
"breakeven_level",
|
|
186
|
+
"market_structure_pivot",
|
|
187
|
+
"position_sizing_score",
|
|
188
|
+
"trailing_exit_level",
|
|
189
|
+
"optimal_entry_zone",
|
|
190
|
+
"mean_reversion_entry",
|
|
191
|
+
"multi_timeframe_signal",
|
|
192
|
+
"market_timing_index",
|
|
193
|
+
"regime_adaptive_signal",
|
|
194
|
+
"strategy_score",
|
|
195
|
+
"breakout_detection",
|
|
156
196
|
]);
|
|
157
197
|
|
|
158
198
|
/**
|
|
@@ -453,6 +493,8 @@ export function emitCall(ctx: EmitCtx, node: Call, visit: VisitFn): string {
|
|
|
453
493
|
|
|
454
494
|
const req = emitRequest(ctx, name, args);
|
|
455
495
|
if (req != null) return req;
|
|
496
|
+
const fp = emitFootprint(name, args);
|
|
497
|
+
if (fp != null) return fp;
|
|
456
498
|
|
|
457
499
|
return "null";
|
|
458
500
|
}
|
|
@@ -526,6 +568,26 @@ function hasArg(args: CallArgs, index: number, names: string[]): boolean {
|
|
|
526
568
|
return index >= 0 && index < args.pos.length;
|
|
527
569
|
}
|
|
528
570
|
|
|
571
|
+
/** Bare series names: Python sees a `PineSeries`, so list-only handlers return `na`. */
|
|
572
|
+
const BARE_SERIES_NAMES = new Set([
|
|
573
|
+
"open",
|
|
574
|
+
"high",
|
|
575
|
+
"low",
|
|
576
|
+
"close",
|
|
577
|
+
"volume",
|
|
578
|
+
"hl2",
|
|
579
|
+
"hlc3",
|
|
580
|
+
"ohlc4",
|
|
581
|
+
"hlcc4",
|
|
582
|
+
"time",
|
|
583
|
+
"time_close",
|
|
584
|
+
]);
|
|
585
|
+
|
|
586
|
+
function hasSeriesArg(args: CallArgs): boolean {
|
|
587
|
+
const isSeries = (n: expr): boolean => n.kind === "Name" && BARE_SERIES_NAMES.has(n.id);
|
|
588
|
+
return args.posNodes.some(isSeries) || Object.values(args.kwNodes).some(isSeries);
|
|
589
|
+
}
|
|
590
|
+
|
|
529
591
|
/** True when a keyword argument named one of *names* is present (interpret hasKwarg). */
|
|
530
592
|
function hasAnyKw(args: CallArgs, names: string[]): boolean {
|
|
531
593
|
return names.some((n) => Object.hasOwn(args.kw, n));
|
|
@@ -563,6 +625,11 @@ function cellList(expr: string): string {
|
|
|
563
625
|
return `(() => { const __v = ${expr}; const __cell = (e) => (e == null ? null : typeof e === "boolean" ? (e ? 1 : 0) : (typeof e === "number" && Number.isFinite(e) ? e : null)); if (__v != null && typeof __v.size === "function" && typeof __v.get === "function") { const __n = __v.size(); const __o = []; for (let __i = 0; __i < __n; __i++) __o.push(__cell(__v.get(__i))); return __o; } return [__cell(__v)]; })()`;
|
|
564
626
|
}
|
|
565
627
|
|
|
628
|
+
/** Interpret `seriesLastCell`: array → last cell; scalar → the value itself. */
|
|
629
|
+
function cellLast(expr: string): string {
|
|
630
|
+
return `(() => { const __v = ${expr}; const __cell = (e) => (e == null ? null : typeof e === "boolean" ? (e ? 1 : 0) : (typeof e === "number" && Number.isFinite(e) ? e : null)); if (__v != null && typeof __v.size === "function" && typeof __v.get === "function") { const __n = __v.size(); return __n === 0 ? null : __cell(__v.get(__n - 1)); } return __cell(__v); })()`;
|
|
631
|
+
}
|
|
632
|
+
|
|
566
633
|
const DRAW_KINDS = new Set(["label", "line", "box", "table", "polyline", "linefill"]);
|
|
567
634
|
|
|
568
635
|
const ARRAY_SIGS: Record<string, string[][]> = {
|
|
@@ -949,6 +1016,10 @@ function emitDraw(ctx: EmitCtx, name: string, methodSrc: expr | null, args: Call
|
|
|
949
1016
|
code = `__h.draw.box_new(__bar_idx, ${pick(args, 0, ["left"], "0")}, ${pick(args, 1, ["top"], "0")}, ${pick(args, 2, ["right"], "0")}, ${pick(args, 3, ["bottom"], "0")})`;
|
|
950
1017
|
} else if (kind === "polyline") {
|
|
951
1018
|
code = `__h.draw.polyline_new(__bar_idx, ${pick(args, 0, ["points"], "null")})`;
|
|
1019
|
+
} else if (kind === "table") {
|
|
1020
|
+
// Pine positional order: (position, columns, rows, bgcolor, frame_color,
|
|
1021
|
+
// frame_width, border_color, border_width, force_overlay).
|
|
1022
|
+
code = `__h.draw.table_new(__bar_idx, ${pick(args, 0, ["position"], "null")}, ${pick(args, 1, ["columns"], "null")}, ${pick(args, 2, ["rows"], "null")}, ${pick(args, 3, ["bgcolor"], "null")}, ${pick(args, 4, ["frame_color"], "null")}, ${pick(args, 5, ["frame_width"], "null")}, ${pick(args, 6, ["border_color"], "null")}, ${pick(args, 7, ["border_width"], "null")}, ${pick(args, 8, ["force_overlay"], "null")})`;
|
|
952
1023
|
} else if (kind === "linefill") {
|
|
953
1024
|
const color = hasArg(args, 2, ["color"]) ? pick(args, 2, ["color"], "null") : "null";
|
|
954
1025
|
code = `__h.draw.linefill_new(__bar_idx, ${pick(args, 0, ["id1", "line1"], "null")}, ${pick(args, 1, ["id2", "line2"], "null")}, ${color})`;
|
|
@@ -1018,7 +1089,6 @@ const SECURITY_FUNCS = new Set([
|
|
|
1018
1089
|
"security_lower_tf",
|
|
1019
1090
|
"request_security",
|
|
1020
1091
|
"request_security_lower_tf",
|
|
1021
|
-
"request_seed",
|
|
1022
1092
|
]);
|
|
1023
1093
|
|
|
1024
1094
|
const SIMPLE_CHART_EXPR = new Set([
|
|
@@ -1064,10 +1134,60 @@ function emitRequest(ctx: EmitCtx, name: string, args: CallArgs): string | null
|
|
|
1064
1134
|
if (name === "request_currency_rate" || name === "currency_rate") {
|
|
1065
1135
|
return `__h.currencyRate(${pick(args, 0, ["from", "from_currency"], "null")}, ${pick(args, 1, ["to", "to_currency"], "null")})`;
|
|
1066
1136
|
}
|
|
1137
|
+
if (name === "request_seed") {
|
|
1138
|
+
return `__h.requestSeed(${pick(args, 0, ["seed"], "0")})`;
|
|
1139
|
+
}
|
|
1140
|
+
if (name === "request_footprint") {
|
|
1141
|
+
return `__h.requestFootprint(${pick(args, 0, ["num_ticks"], "100")}, ${pick(args, 1, ["va_percentage"], "70")})`;
|
|
1142
|
+
}
|
|
1067
1143
|
if (name.startsWith("request_")) return "null";
|
|
1068
1144
|
return null;
|
|
1069
1145
|
}
|
|
1070
1146
|
|
|
1147
|
+
/** Python `request.footprint` mock + `footprint.*` / `volume_row.*` accessors. */
|
|
1148
|
+
function emitFootprint(name: string, args: CallArgs): string | null {
|
|
1149
|
+
const fp = pick(args, 0, ["id", "footprint"], "null");
|
|
1150
|
+
const vr = pick(args, 0, ["id", "volume_row"], "null");
|
|
1151
|
+
switch (name) {
|
|
1152
|
+
case "footprint_buy_volume":
|
|
1153
|
+
return `__h.footprint.buy_volume(${fp})`;
|
|
1154
|
+
case "footprint_sell_volume":
|
|
1155
|
+
return `__h.footprint.sell_volume(${fp})`;
|
|
1156
|
+
case "footprint_delta":
|
|
1157
|
+
return `__h.footprint.delta(${fp})`;
|
|
1158
|
+
case "footprint_total_volume":
|
|
1159
|
+
return `__h.footprint.total_volume(${fp})`;
|
|
1160
|
+
case "footprint_vah":
|
|
1161
|
+
return `__h.footprint.vah(${fp})`;
|
|
1162
|
+
case "footprint_val":
|
|
1163
|
+
return `__h.footprint.val(${fp})`;
|
|
1164
|
+
case "footprint_poc":
|
|
1165
|
+
return `__h.footprint.poc(${fp})`;
|
|
1166
|
+
case "footprint_rows":
|
|
1167
|
+
return `__h.footprint.rows(${fp})`;
|
|
1168
|
+
case "footprint_get_row_by_price":
|
|
1169
|
+
return `__h.footprint.get_row_by_price(${fp}, ${pick(args, 1, ["price"], "null")})`;
|
|
1170
|
+
case "volume_row_up_price":
|
|
1171
|
+
return `__h.footprint.volume_row.up_price(${vr})`;
|
|
1172
|
+
case "volume_row_down_price":
|
|
1173
|
+
return `__h.footprint.volume_row.down_price(${vr})`;
|
|
1174
|
+
case "volume_row_buy_volume":
|
|
1175
|
+
return `__h.footprint.volume_row.buy_volume(${vr})`;
|
|
1176
|
+
case "volume_row_sell_volume":
|
|
1177
|
+
return `__h.footprint.volume_row.sell_volume(${vr})`;
|
|
1178
|
+
case "volume_row_delta":
|
|
1179
|
+
return `__h.footprint.volume_row.delta(${vr})`;
|
|
1180
|
+
case "volume_row_total_volume":
|
|
1181
|
+
return `__h.footprint.volume_row.total_volume(${vr})`;
|
|
1182
|
+
case "volume_row_has_buy_imbalance":
|
|
1183
|
+
return `__h.footprint.volume_row.has_buy_imbalance(${vr})`;
|
|
1184
|
+
case "volume_row_has_sell_imbalance":
|
|
1185
|
+
return `__h.footprint.volume_row.has_sell_imbalance(${vr})`;
|
|
1186
|
+
default:
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1071
1191
|
function isSimpleSecurityExpr(expr: string): boolean {
|
|
1072
1192
|
const e = expr.trim();
|
|
1073
1193
|
if (!e) return false;
|
|
@@ -1566,6 +1686,11 @@ function taCall(method: string, site: string, parts: string[]): string {
|
|
|
1566
1686
|
return `__h.ta.${method}(${site}, ${parts.join(", ")})`;
|
|
1567
1687
|
}
|
|
1568
1688
|
|
|
1689
|
+
/** Call a stateless TaEngine method (no call-site argument). */
|
|
1690
|
+
function taPure(method: string, parts: string[]): string {
|
|
1691
|
+
return `__h.ta.${method}(${parts.join(", ")})`;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1569
1694
|
function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
1570
1695
|
if (!TA_METHODS.has(method)) return "null";
|
|
1571
1696
|
if (method === "ad") method = "accdist";
|
|
@@ -2036,17 +2161,31 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
2036
2161
|
]);
|
|
2037
2162
|
}
|
|
2038
2163
|
if (method === "stoch_smooth") {
|
|
2039
|
-
return
|
|
2164
|
+
return taCall("stochSmooth", site, [
|
|
2165
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2166
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2167
|
+
cellOf(pick(args, 2, ["close"], "null")),
|
|
2168
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2169
|
+
cellOf(pick(args, 4, ["smooth_k", "k"], "null")),
|
|
2170
|
+
cellOf(pick(args, 5, ["smooth_d", "d"], "null")),
|
|
2171
|
+
]);
|
|
2040
2172
|
}
|
|
2041
2173
|
if (method === "engulfing") {
|
|
2042
|
-
//
|
|
2043
|
-
return `(() => { ${cellOf(pick(args, 0, ["open"], "null"))}; ${cellOf(pick(args, 1, ["high"], "null"))}; ${cellOf(pick(args, 2, ["low"], "null"))}; ${cellOf(pick(args, 3, ["close"], "null"))}; return __h.ta.engulfing(); })()`;
|
|
2174
|
+
// Args are still evaluated (interpret reads all four); high/low are unused.
|
|
2175
|
+
return `(() => { const __o = ${cellOf(pick(args, 0, ["open"], "null"))}; ${cellOf(pick(args, 1, ["high"], "null"))}; ${cellOf(pick(args, 2, ["low"], "null"))}; const __c = ${cellOf(pick(args, 3, ["close"], "null"))}; return __h.ta.engulfing(${site}, __o, __c); })()`;
|
|
2044
2176
|
}
|
|
2045
2177
|
if (method === "inside_bar_pattern") {
|
|
2046
|
-
return
|
|
2178
|
+
return taCall("insideBarPattern", site, [
|
|
2179
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2180
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2181
|
+
]);
|
|
2047
2182
|
}
|
|
2048
2183
|
if (method === "double_top_bottom") {
|
|
2049
|
-
return
|
|
2184
|
+
return taCall("doubleTopBottom", site, [
|
|
2185
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2186
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2187
|
+
cellOf(pick(args, 2, ["period", "length"], "null")),
|
|
2188
|
+
]);
|
|
2050
2189
|
}
|
|
2051
2190
|
if (method === "voi") {
|
|
2052
2191
|
return `__h.ta.voi(${cellOf(pick(args, 0, ["buy_volume", "buy"], "null"))}, ${cellOf(pick(args, 1, ["sell_volume", "sell"], "null"))})`;
|
|
@@ -2060,7 +2199,10 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
2060
2199
|
if (method === "rsi_divergence") {
|
|
2061
2200
|
// Missing period is 0 (interpret `period ?? 0`), not the engine's null → na.
|
|
2062
2201
|
const period = `((__n) => (__n == null ? 0 : __n))(${cellOf(pick(args, 1, ["period", "length"], "null"))})`;
|
|
2063
|
-
return
|
|
2202
|
+
return taCall("rsiDivergence", site, [
|
|
2203
|
+
cellLast(pick(args, 0, ["rsi_series", "series", "source"], "null")),
|
|
2204
|
+
period,
|
|
2205
|
+
]);
|
|
2064
2206
|
}
|
|
2065
2207
|
if (method === "cumulative_delta") {
|
|
2066
2208
|
return taCall("cumulativeDelta", site, [
|
|
@@ -2095,5 +2237,234 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
2095
2237
|
`((__n) => (__n == null ? 0.3 : __n))(${sens})`,
|
|
2096
2238
|
]);
|
|
2097
2239
|
}
|
|
2240
|
+
if (method === "sma_weighted") {
|
|
2241
|
+
return taCall("smaWeighted", site, [
|
|
2242
|
+
cellOf(pick(args, 0, ["series", "source"], "null")),
|
|
2243
|
+
cellOf(pick(args, 1, ["period", "length"], "null")),
|
|
2244
|
+
pick(args, 2, ["weight_type", "weight"], '"linear"'),
|
|
2245
|
+
]);
|
|
2246
|
+
}
|
|
2247
|
+
if (method === "apo") {
|
|
2248
|
+
return taCall("apo", site, [
|
|
2249
|
+
cellOf(pick(args, 0, ["series", "source"], "null")),
|
|
2250
|
+
cellOf(pick(args, 1, ["fast_period", "fast"], "null")),
|
|
2251
|
+
cellOf(pick(args, 2, ["slow_period", "slow"], "null")),
|
|
2252
|
+
]);
|
|
2253
|
+
}
|
|
2254
|
+
if (method === "atr_normalized") {
|
|
2255
|
+
return taCall("atrNormalized", site, [
|
|
2256
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2257
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2258
|
+
cellOf(pick(args, 2, ["close"], "null")),
|
|
2259
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2260
|
+
]);
|
|
2261
|
+
}
|
|
2262
|
+
if (method === "beta" || method === "r_squared" || method === "comovement") {
|
|
2263
|
+
const fn = method === "beta" ? "beta" : method === "r_squared" ? "rSquared" : "comovement";
|
|
2264
|
+
return taCall(fn, site, [
|
|
2265
|
+
cellOf(pick(args, 0, ["series1", "series", "source"], "null")),
|
|
2266
|
+
cellOf(pick(args, 1, ["series2", "series_b", "other"], "null")),
|
|
2267
|
+
cellOf(pick(args, 2, ["length", "period"], "null")),
|
|
2268
|
+
]);
|
|
2269
|
+
}
|
|
2270
|
+
if (method === "skewness" || method === "kurtosis") {
|
|
2271
|
+
return taCall(method, site, [
|
|
2272
|
+
cellOf(pick(args, 0, ["series", "source"], "null")),
|
|
2273
|
+
cellOf(pick(args, 1, ["period", "length"], "null")),
|
|
2274
|
+
]);
|
|
2275
|
+
}
|
|
2276
|
+
if (method === "volume_weighted_momentum") {
|
|
2277
|
+
return taCall("volumeWeightedMomentum", site, [
|
|
2278
|
+
cellOf(pick(args, 0, ["series", "source"], "null")),
|
|
2279
|
+
cellOf(pick(args, 1, ["volume"], "null")),
|
|
2280
|
+
cellOf(pick(args, 2, ["length", "period"], "null")),
|
|
2281
|
+
]);
|
|
2282
|
+
}
|
|
2283
|
+
if (method === "parkinson") {
|
|
2284
|
+
if (hasSeriesArg(args)) return "null";
|
|
2285
|
+
return `__h.ta.parkinson(${cellOf(pick(args, 0, ["high"], "null"))}, ${cellOf(pick(args, 1, ["low"], "null"))})`;
|
|
2286
|
+
}
|
|
2287
|
+
if (method === "garman_klass") {
|
|
2288
|
+
if (hasSeriesArg(args)) return "null";
|
|
2289
|
+
return `__h.ta.garmanKlass(${cellOf(pick(args, 0, ["high"], "null"))}, ${cellOf(pick(args, 1, ["low"], "null"))}, ${cellOf(pick(args, 2, ["close"], "null"))}, ${cellOf(pick(args, 3, ["open"], "null"))})`;
|
|
2290
|
+
}
|
|
2291
|
+
if (method === "mean_reversion_score") {
|
|
2292
|
+
return taCall("meanReversionScore", site, [
|
|
2293
|
+
cellOf(pick(args, 0, ["close", "source"], "null")),
|
|
2294
|
+
cellOf(pick(args, 1, ["sma"], "null")),
|
|
2295
|
+
cellOf(pick(args, 2, ["stdev", "std_dev"], "null")),
|
|
2296
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2297
|
+
]);
|
|
2298
|
+
}
|
|
2299
|
+
if (method === "market_condition") {
|
|
2300
|
+
return taCall("marketCondition", site, [
|
|
2301
|
+
cellOf(pick(args, 0, ["close", "source"], "null")),
|
|
2302
|
+
cellOf(pick(args, 1, ["atr"], "null")),
|
|
2303
|
+
cellOf(pick(args, 2, ["sma_period", "sma_length"], "null")),
|
|
2304
|
+
cellOf(pick(args, 3, ["stdev_period", "stdev_length"], "null")),
|
|
2305
|
+
]);
|
|
2306
|
+
}
|
|
2307
|
+
if (method === "order_flow_imbalance") {
|
|
2308
|
+
return taCall("orderFlowImbalance", site, [
|
|
2309
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2310
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2311
|
+
cellOf(pick(args, 2, ["close"], "null")),
|
|
2312
|
+
cellOf(pick(args, 3, ["volume"], "null")),
|
|
2313
|
+
cellOf(pick(args, 4, ["period", "length"], "null")),
|
|
2314
|
+
]);
|
|
2315
|
+
}
|
|
2316
|
+
if (method === "liquidity_score") {
|
|
2317
|
+
return taCall("liquidityScore", site, [
|
|
2318
|
+
cellOf(pick(args, 0, ["volume"], "null")),
|
|
2319
|
+
cellOf(pick(args, 1, ["volatility"], "null")),
|
|
2320
|
+
cellOf(pick(args, 2, ["spread"], "null")),
|
|
2321
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2322
|
+
]);
|
|
2323
|
+
}
|
|
2324
|
+
if (method === "fear_greed_index") {
|
|
2325
|
+
return `__h.ta.fearGreedIndex(${cellOf(pick(args, 0, ["rsi"], "null"))}, ${cellOf(pick(args, 1, ["vix"], "null"))}, ${cellOf(pick(args, 2, ["put_call", "put_call_ratio"], "null"))}, ${cellOf(pick(args, 3, ["breadth"], "null"))})`;
|
|
2326
|
+
}
|
|
2327
|
+
if (method === "crowd_sentiment") {
|
|
2328
|
+
return `__h.ta.crowdSentiment(${cellOf(pick(args, 0, ["price_agreement", "price"], "null"))}, ${cellOf(pick(args, 1, ["volume_agreement", "volume"], "null"))}, ${cellOf(pick(args, 2, ["time_agreement", "time"], "null"))})`;
|
|
2329
|
+
}
|
|
2330
|
+
if (method === "smart_money_flow") {
|
|
2331
|
+
return `__h.ta.smartMoneyFlow(${cellOf(pick(args, 0, ["price_change"], "null"))}, ${cellOf(pick(args, 1, ["volume"], "null"))}, ${cellOf(pick(args, 2, ["time_since_high", "time_high"], "null"))}, ${cellOf(pick(args, 3, ["time_since_low", "time_low"], "null"))})`;
|
|
2332
|
+
}
|
|
2333
|
+
if (method === "trend_confirmation_score") {
|
|
2334
|
+
return `__h.ta.trendConfirmationScore(${cellOf(pick(args, 0, ["momentum"], "null"))}, ${cellOf(pick(args, 1, ["trend_alignment"], "null"))}, ${cellOf(pick(args, 2, ["strength"], "null"))}, ${cellOf(pick(args, 3, ["rsi"], "null"))}, ${cellOf(pick(args, 4, ["rsi_alignment"], "null"))})`;
|
|
2335
|
+
}
|
|
2336
|
+
if (method === "max_loss_level") {
|
|
2337
|
+
return `__h.ta.maxLossLevel(${cellOf(pick(args, 0, ["entry", "entry_price"], "null"))}, ${cellOf(pick(args, 1, ["account", "account_size"], "null"))}, ${cellOf(pick(args, 2, ["max_loss_pct", "max_loss_percent"], "null"))})`;
|
|
2338
|
+
}
|
|
2339
|
+
if (method === "profit_lock_level") {
|
|
2340
|
+
return `__h.ta.profitLockLevel(${cellOf(pick(args, 0, ["entry", "entry_price"], "null"))}, ${cellOf(pick(args, 1, ["current", "current_price"], "null"))}, ${cellOf(pick(args, 2, ["trail_pct", "trail_percent"], "null"))}, ${cellOf(pick(args, 3, ["direction"], "null"))})`;
|
|
2341
|
+
}
|
|
2342
|
+
if (method === "position_sizing") {
|
|
2343
|
+
return taPure("positionSizing", [
|
|
2344
|
+
cellOf(pick(args, 0, ["account", "account_size"], "null")),
|
|
2345
|
+
cellOf(pick(args, 1, ["risk_pct", "risk_percent"], "null")),
|
|
2346
|
+
cellOf(pick(args, 2, ["entry", "entry_price"], "null")),
|
|
2347
|
+
cellOf(pick(args, 3, ["stop", "stop_price"], "null")),
|
|
2348
|
+
]);
|
|
2349
|
+
}
|
|
2350
|
+
if (method === "kelly_criterion") {
|
|
2351
|
+
return taPure("kellyCriterion", [
|
|
2352
|
+
cellOf(pick(args, 0, ["win_rate"], "null")),
|
|
2353
|
+
cellOf(pick(args, 1, ["avg_win"], "null")),
|
|
2354
|
+
cellOf(pick(args, 2, ["avg_loss"], "null")),
|
|
2355
|
+
]);
|
|
2356
|
+
}
|
|
2357
|
+
if (method === "risk_reward_ratio") {
|
|
2358
|
+
return taPure("riskRewardRatio", [
|
|
2359
|
+
cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
|
|
2360
|
+
cellOf(pick(args, 1, ["stop", "stop_price"], "null")),
|
|
2361
|
+
cellOf(pick(args, 2, ["target", "target_price"], "null")),
|
|
2362
|
+
]);
|
|
2363
|
+
}
|
|
2364
|
+
if (method === "risk_reward_asymmetry") {
|
|
2365
|
+
return taPure("riskRewardAsymmetry", [
|
|
2366
|
+
cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
|
|
2367
|
+
cellOf(pick(args, 1, ["stop", "stop_price"], "null")),
|
|
2368
|
+
cellOf(pick(args, 2, ["target", "target_price"], "null")),
|
|
2369
|
+
cellOf(pick(args, 3, ["win_rate"], "null")),
|
|
2370
|
+
]);
|
|
2371
|
+
}
|
|
2372
|
+
if (method === "probability_of_movement") {
|
|
2373
|
+
return taPure("probabilityOfMovement", [
|
|
2374
|
+
cellOf(pick(args, 0, ["current", "current_price"], "null")),
|
|
2375
|
+
cellOf(pick(args, 1, ["target", "target_price"], "null")),
|
|
2376
|
+
cellOf(pick(args, 2, ["atr"], "null")),
|
|
2377
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2378
|
+
]);
|
|
2379
|
+
}
|
|
2380
|
+
if (method === "trend_strength") {
|
|
2381
|
+
return taPure("trendStrength", [
|
|
2382
|
+
cellOf(pick(args, 0, ["price", "source"], "null")),
|
|
2383
|
+
cellOf(pick(args, 1, ["adx"], "null")),
|
|
2384
|
+
cellOf(pick(args, 2, ["rsi"], "null")),
|
|
2385
|
+
]);
|
|
2386
|
+
}
|
|
2387
|
+
if (method === "breakeven_level") {
|
|
2388
|
+
return taPure("breakevenLevel", [
|
|
2389
|
+
cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
|
|
2390
|
+
cellOf(pick(args, 1, ["quantity", "qty"], "null")),
|
|
2391
|
+
cellOf(pick(args, 2, ["commission"], "null")),
|
|
2392
|
+
cellOf(pick(args, 3, ["slippage"], "null")),
|
|
2393
|
+
cellOf(pick(args, 4, ["direction"], "null")),
|
|
2394
|
+
]);
|
|
2395
|
+
}
|
|
2396
|
+
if (method === "market_structure_pivot") {
|
|
2397
|
+
return taPure("marketStructurePivot", [
|
|
2398
|
+
cellOf(pick(args, 2, ["close"], "null")),
|
|
2399
|
+
]);
|
|
2400
|
+
}
|
|
2401
|
+
if (method === "position_sizing_score") {
|
|
2402
|
+
return taPure("positionSizingScore", [
|
|
2403
|
+
cellOf(pick(args, 0, ["account_risk", "risk"], "null")),
|
|
2404
|
+
cellOf(pick(args, 1, ["volatility"], "null")),
|
|
2405
|
+
cellOf(pick(args, 2, ["conviction"], "null")),
|
|
2406
|
+
cellOf(pick(args, 3, ["correlation"], "null")),
|
|
2407
|
+
]);
|
|
2408
|
+
}
|
|
2409
|
+
if (method === "trailing_exit_level") {
|
|
2410
|
+
return taPure("trailingExitLevel", [
|
|
2411
|
+
cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
|
|
2412
|
+
cellOf(pick(args, 1, ["current", "current_price"], "null")),
|
|
2413
|
+
cellOf(pick(args, 2, ["atr"], "null")),
|
|
2414
|
+
cellOf(pick(args, 3, ["multiplier", "mult"], "null")),
|
|
2415
|
+
cellOf(pick(args, 4, ["step"], "null")),
|
|
2416
|
+
]);
|
|
2417
|
+
}
|
|
2418
|
+
if (method === "optimal_entry_zone") {
|
|
2419
|
+
return taPure("optimalEntryZone", [cellOf(pick(args, 2, ["close"], "null"))]);
|
|
2420
|
+
}
|
|
2421
|
+
if (method === "mean_reversion_entry") {
|
|
2422
|
+
return taPure("meanReversionEntry", [
|
|
2423
|
+
cellOf(pick(args, 0, ["price", "source"], "null")),
|
|
2424
|
+
cellOf(pick(args, 1, ["sma"], "null")),
|
|
2425
|
+
cellOf(pick(args, 2, ["stdev", "std_dev"], "null")),
|
|
2426
|
+
cellOf(pick(args, 4, ["threshold"], "null")),
|
|
2427
|
+
]);
|
|
2428
|
+
}
|
|
2429
|
+
if (method === "multi_timeframe_signal") {
|
|
2430
|
+
return taPure("multiTimeframeSignal", [
|
|
2431
|
+
cellOf(pick(args, 0, ["s1", "signal1"], "null")),
|
|
2432
|
+
cellOf(pick(args, 1, ["s2", "signal2"], "null")),
|
|
2433
|
+
cellOf(pick(args, 2, ["s3", "signal3"], "null")),
|
|
2434
|
+
cellOf(pick(args, 3, ["w1", "weight1"], "null")),
|
|
2435
|
+
cellOf(pick(args, 4, ["w2", "weight2"], "null")),
|
|
2436
|
+
cellOf(pick(args, 5, ["w3", "weight3"], "null")),
|
|
2437
|
+
]);
|
|
2438
|
+
}
|
|
2439
|
+
if (method === "market_timing_index") {
|
|
2440
|
+
return taPure("marketTimingIndex", [
|
|
2441
|
+
cellOf(pick(args, 0, ["trend"], "null")),
|
|
2442
|
+
cellOf(pick(args, 1, ["momentum"], "null")),
|
|
2443
|
+
cellOf(pick(args, 2, ["volatility"], "null")),
|
|
2444
|
+
cellOf(pick(args, 3, ["sentiment"], "null")),
|
|
2445
|
+
]);
|
|
2446
|
+
}
|
|
2447
|
+
if (method === "regime_adaptive_signal") {
|
|
2448
|
+
return taPure("regimeAdaptiveSignal", [
|
|
2449
|
+
cellOf(pick(args, 0, ["signal_strength", "strength"], "null")),
|
|
2450
|
+
pick(args, 1, ["volatility_regime", "vol_regime"], '""'),
|
|
2451
|
+
pick(args, 2, ["trend_regime"], '""'),
|
|
2452
|
+
]);
|
|
2453
|
+
}
|
|
2454
|
+
if (method === "strategy_score") {
|
|
2455
|
+
return taPure("strategyScore", [
|
|
2456
|
+
cellOf(pick(args, 0, ["rsi"], "null")),
|
|
2457
|
+
cellOf(pick(args, 1, ["macd"], "null")),
|
|
2458
|
+
cellOf(pick(args, 2, ["ema_cross"], "null")),
|
|
2459
|
+
cellOf(pick(args, 3, ["trend"], "null")),
|
|
2460
|
+
]);
|
|
2461
|
+
}
|
|
2462
|
+
if (method === "breakout_detection") {
|
|
2463
|
+
return taPure("breakoutDetection", [
|
|
2464
|
+
cellOf(pick(args, 0, ["close", "source"], "null")),
|
|
2465
|
+
cellOf(pick(args, 1, ["resistance"], "null")),
|
|
2466
|
+
cellOf(pick(args, 2, ["support"], "null")),
|
|
2467
|
+
]);
|
|
2468
|
+
}
|
|
2098
2469
|
return "null";
|
|
2099
2470
|
}
|
|
@@ -20,9 +20,10 @@ export function naNum(v: unknown): number | null {
|
|
|
20
20
|
return NA;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/** Store cell or object handle. Objects (array/map/matrix/UDT) stay intact. */
|
|
23
|
+
/** Store cell or object handle. Objects (array/map/matrix/UDT) and strings stay intact. */
|
|
24
24
|
export function hold(v: unknown): unknown {
|
|
25
25
|
if (v != null && typeof v === "object") return v;
|
|
26
|
+
if (typeof v === "string") return v;
|
|
26
27
|
return naNum(v);
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -56,9 +56,30 @@ import {
|
|
|
56
56
|
strUpper,
|
|
57
57
|
} from "../str.ts";
|
|
58
58
|
import { LogBook, formatLogParts } from "../log.ts";
|
|
59
|
-
import {
|
|
59
|
+
import { PineArray } from "../array.ts";
|
|
60
|
+
import {
|
|
61
|
+
createFootprint,
|
|
62
|
+
footprintBuyVolume,
|
|
63
|
+
footprintDelta,
|
|
64
|
+
footprintGetRowByPrice,
|
|
65
|
+
footprintPoc,
|
|
66
|
+
footprintRows,
|
|
67
|
+
footprintSellVolume,
|
|
68
|
+
footprintTotalVolume,
|
|
69
|
+
footprintVah,
|
|
70
|
+
footprintVal,
|
|
71
|
+
volumeRowBuyVolume,
|
|
72
|
+
volumeRowDelta,
|
|
73
|
+
volumeRowDownPrice,
|
|
74
|
+
volumeRowHasBuyImbalance,
|
|
75
|
+
volumeRowHasSellImbalance,
|
|
76
|
+
volumeRowSellVolume,
|
|
77
|
+
volumeRowTotalVolume,
|
|
78
|
+
volumeRowUpPrice,
|
|
79
|
+
} from "../footprint.ts";
|
|
80
|
+
import { resolveCurrencyRate, resolveSeed } from "../request.ts";
|
|
60
81
|
import { TaEngine } from "../ta.ts";
|
|
61
|
-
import { utcPartsFromMs, timestamp, weekOfYear, timeTradingDay, type UtcParts } from "../time.ts";
|
|
82
|
+
import { utcPartsFromMs, timestamp, timestampToMs, weekOfYear, timeTradingDay, type UtcParts } from "../time.ts";
|
|
62
83
|
import { TickerId } from "../ticker.ts";
|
|
63
84
|
import { timeframeInSeconds, timeframePeriodChanged } from "../timeframe.ts";
|
|
64
85
|
import { compileArray, compileMap, compileMatrix } from "./collections.ts";
|
|
@@ -182,8 +203,8 @@ export function createCompileHelpers(opts?: CompileHelperOpts) {
|
|
|
182
203
|
second: calendarSecond,
|
|
183
204
|
dayofweek: calendarDayofweek,
|
|
184
205
|
timestamp,
|
|
185
|
-
weekOfYear,
|
|
186
|
-
timeTradingDay,
|
|
206
|
+
weekOfYear: calendarWeekOfYear,
|
|
207
|
+
timeTradingDay: calendarTimeTradingDay,
|
|
187
208
|
timeframeInSeconds: timeframeInSecondsOrDaily,
|
|
188
209
|
timeframeChange,
|
|
189
210
|
timeClose,
|
|
@@ -227,12 +248,41 @@ export function createCompileHelpers(opts?: CompileHelperOpts) {
|
|
|
227
248
|
udtSet,
|
|
228
249
|
udtRegister,
|
|
229
250
|
currencyRate: resolveCurrencyRate,
|
|
251
|
+
requestSeed: resolveSeed,
|
|
252
|
+
requestFootprint: createFootprint,
|
|
253
|
+
footprint: {
|
|
254
|
+
buy_volume: footprintBuyVolume,
|
|
255
|
+
sell_volume: footprintSellVolume,
|
|
256
|
+
delta: footprintDelta,
|
|
257
|
+
total_volume: footprintTotalVolume,
|
|
258
|
+
vah: footprintVah,
|
|
259
|
+
val: footprintVal,
|
|
260
|
+
poc: footprintPoc,
|
|
261
|
+
rows: compileFootprintRows,
|
|
262
|
+
get_row_by_price: footprintGetRowByPrice,
|
|
263
|
+
volume_row: {
|
|
264
|
+
up_price: volumeRowUpPrice,
|
|
265
|
+
down_price: volumeRowDownPrice,
|
|
266
|
+
buy_volume: volumeRowBuyVolume,
|
|
267
|
+
sell_volume: volumeRowSellVolume,
|
|
268
|
+
delta: volumeRowDelta,
|
|
269
|
+
total_volume: volumeRowTotalVolume,
|
|
270
|
+
has_buy_imbalance: volumeRowHasBuyImbalance,
|
|
271
|
+
has_sell_imbalance: volumeRowHasSellImbalance,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
230
274
|
runtimeError(msg: unknown): never {
|
|
231
275
|
throw new Error(String(msg));
|
|
232
276
|
},
|
|
233
277
|
};
|
|
234
278
|
}
|
|
235
279
|
|
|
280
|
+
function compileFootprintRows(fp: unknown): PineArray {
|
|
281
|
+
const arr = new PineArray();
|
|
282
|
+
for (const row of footprintRows(fp)) arr.push(row);
|
|
283
|
+
return arr;
|
|
284
|
+
}
|
|
285
|
+
|
|
236
286
|
/** Pine `color.new(color, transp)` — first arg is a color, not r,g,b. */
|
|
237
287
|
function compileColorNew(color: unknown, transp?: unknown) {
|
|
238
288
|
const parsed = asColor(color);
|
|
@@ -289,10 +339,25 @@ function NA_FN(v?: unknown): number | null {
|
|
|
289
339
|
return isNaCell(v) ? 1 : 0;
|
|
290
340
|
}
|
|
291
341
|
|
|
292
|
-
/** Unix-ms → UTC calendar part. na / non-finite time → null. */
|
|
342
|
+
/** Unix-ms → UTC calendar part. na / non-finite time → null. Unix-second stamps become ms (Python `utility._timestamp_to_ms`). */
|
|
293
343
|
function utcPart(t: unknown, key: keyof UtcParts): number | null {
|
|
294
344
|
if (typeof t !== "number" || !Number.isFinite(t)) return null;
|
|
295
|
-
|
|
345
|
+
const ms = timestampToMs(t);
|
|
346
|
+
return ms == null ? null : utcPartsFromMs(ms)[key];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** `weekofyear(t)` with unix-seconds coercion (Python `_resolve_timestamp_arg`). */
|
|
350
|
+
function calendarWeekOfYear(t: unknown): number | null {
|
|
351
|
+
if (typeof t !== "number" || !Number.isFinite(t)) return null;
|
|
352
|
+
const ms = timestampToMs(t);
|
|
353
|
+
return ms == null ? null : weekOfYear(ms);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** `time_tradingday(t)` with unix-seconds coercion (Python `_resolve_timestamp_arg`). */
|
|
357
|
+
function calendarTimeTradingDay(t: unknown): number | null {
|
|
358
|
+
if (typeof t !== "number" || !Number.isFinite(t)) return null;
|
|
359
|
+
const ms = timestampToMs(t);
|
|
360
|
+
return ms == null ? null : timeTradingDay(ms);
|
|
296
361
|
}
|
|
297
362
|
|
|
298
363
|
function calendarYear(t: unknown): number | null {
|
|
@@ -124,6 +124,7 @@ export class CompileStrategy {
|
|
|
124
124
|
high: num(high, this.mark),
|
|
125
125
|
low: num(low, this.mark),
|
|
126
126
|
close: this.mark,
|
|
127
|
+
time: this.time,
|
|
127
128
|
};
|
|
128
129
|
this.ohlc = ohlc;
|
|
129
130
|
this.book.processPending(this.bar, ohlc);
|
|
@@ -141,18 +142,19 @@ export class CompileStrategy {
|
|
|
141
142
|
comment: opts?.comment,
|
|
142
143
|
oca_name: opts?.oca_name,
|
|
143
144
|
oca_type: opts?.oca_type,
|
|
145
|
+
deferMarket: true,
|
|
144
146
|
});
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
close(id?: unknown, qty?: unknown): void {
|
|
148
|
-
this.book.
|
|
150
|
+
this.book.placeClose(this.bar, str(id, ""), this.mark, {
|
|
149
151
|
time: this.time,
|
|
150
152
|
qty: optNum(qty),
|
|
151
153
|
});
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
close_all(_comment?: unknown): void {
|
|
155
|
-
this.book.
|
|
157
|
+
this.book.placeCloseAll(this.bar, this.mark);
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
order(id?: unknown, direction?: unknown, qty?: unknown, opts?: PlaceEntryOpts): void {
|
|
@@ -174,6 +176,7 @@ export class CompileStrategy {
|
|
|
174
176
|
price: this.mark,
|
|
175
177
|
time: this.time,
|
|
176
178
|
ohlc: this.ohlc,
|
|
179
|
+
deferMarket: true,
|
|
177
180
|
});
|
|
178
181
|
}
|
|
179
182
|
|