@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.
@@ -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 `__h.ta.stochSmooth(${cellOf(pick(args, 0, ["high"], "null"))}, ${cellOf(pick(args, 1, ["low"], "null"))}, ${cellOf(pick(args, 2, ["close"], "null"))}, ${cellOf(pick(args, 3, ["period", "length"], "null"))}, ${cellOf(pick(args, 4, ["smooth_k", "k"], "null"))}, ${cellOf(pick(args, 5, ["smooth_d", "d"], "null"))})`;
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
- // Bar-mode sentinel. Args are still evaluated (interpret reads all four).
2043
- return `(() => { ${cellOf(pick(args, 0, ["open"], "null"))}; ${cellOf(pick(args, 1, ["high"], "null"))}; ${cellOf(pick(args, 2, ["low"], "null"))}; ${cellOf(pick(args, 3, ["close"], "null"))}; return __h.ta.engulfing(); })()`;
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 `(() => { ${cellOf(pick(args, 0, ["high"], "null"))}; ${cellOf(pick(args, 1, ["low"], "null"))}; return __h.ta.insideBarPattern(); })()`;
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 `(() => { ${cellOf(pick(args, 0, ["high"], "null"))}; ${cellOf(pick(args, 1, ["low"], "null"))}; return __h.ta.doubleTopBottom(${cellOf(pick(args, 2, ["period", "length"], "null"))}); })()`;
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 `__h.ta.rsiDivergence(${cellList(pick(args, 0, ["rsi_series", "series", "source"], "null"))}, ${period})`;
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 { resolveCurrencyRate } from "../request.ts";
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
- return utcPartsFromMs(t)[key];
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.fillClose(this.bar, str(id, ""), this.mark, {
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.closeAll(this.bar, this.mark);
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