@hoox-sh/pynets 0.5.0 → 0.7.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.
@@ -155,6 +155,62 @@ const TA_METHODS = new Set([
155
155
  "volume_profile_high",
156
156
  "volume_profile_low",
157
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",
196
+ "ema_cross_signal",
197
+ "momentum_filter",
198
+ "volatility_regime",
199
+ "economic_impact_score",
200
+ "contrarian_signal",
201
+ "drawdown_recovery_level",
202
+ "pivot_point_levels",
203
+ "volatility_regime_score",
204
+ "correlation_filter",
205
+ "advanced_breakout_detector",
206
+ "pullback_bounce_level",
207
+ "divergence_detector",
208
+ "momentum_divergence",
209
+ "spread_analysis",
210
+ "employment_cycle_indicator",
211
+ "gdp_growth_proxy",
212
+ "inflation_proxy_indicator",
213
+ "expected_value",
158
214
  ]);
159
215
 
160
216
  /**
@@ -367,6 +423,13 @@ export function emitCall(ctx: EmitCtx, node: Call, visit: VisitFn): string {
367
423
  ctx.needsHeikinashi = true;
368
424
  return `"__HEIKINASHI__"`;
369
425
  }
426
+ if (name === "ticker_inherit") {
427
+ const fallback = pick(args, 0, ["ticker", "tickerid", "from", "source"], `"SYMBOL"`);
428
+ return pick(args, 1, ["symbol", "ticker"], fallback);
429
+ }
430
+ if (name === "renko" || name === "kagi" || name === "linebreak" || name === "pointfigure") {
431
+ return pick(args, 0, ["ticker", "tickerid", "symbol"], `"SYMBOL"`);
432
+ }
370
433
  // ticker.new / standard / modify / renko / kagi / linebreak / pointfigure → first arg or "SYMBOL"
371
434
  if (name.startsWith("ticker_")) {
372
435
  return pick(args, 0, ["symbol", "ticker", "tickerid"], `"SYMBOL"`);
@@ -530,6 +593,26 @@ function hasArg(args: CallArgs, index: number, names: string[]): boolean {
530
593
  return index >= 0 && index < args.pos.length;
531
594
  }
532
595
 
596
+ /** Bare series names: Python sees a `PineSeries`, so list-only handlers return `na`. */
597
+ const BARE_SERIES_NAMES = new Set([
598
+ "open",
599
+ "high",
600
+ "low",
601
+ "close",
602
+ "volume",
603
+ "hl2",
604
+ "hlc3",
605
+ "ohlc4",
606
+ "hlcc4",
607
+ "time",
608
+ "time_close",
609
+ ]);
610
+
611
+ function hasSeriesArg(args: CallArgs): boolean {
612
+ const isSeries = (n: expr): boolean => n.kind === "Name" && BARE_SERIES_NAMES.has(n.id);
613
+ return args.posNodes.some(isSeries) || Object.values(args.kwNodes).some(isSeries);
614
+ }
615
+
533
616
  /** True when a keyword argument named one of *names* is present (interpret hasKwarg). */
534
617
  function hasAnyKw(args: CallArgs, names: string[]): boolean {
535
618
  return names.some((n) => Object.hasOwn(args.kw, n));
@@ -559,6 +642,11 @@ function cellOf(expr: string): string {
559
642
  return `((__c) => (__c == null ? null : typeof __c === "boolean" ? (__c ? 1 : 0) : (typeof __c === "number" && Number.isFinite(__c) ? __c : null)))(${expr})`;
560
643
  }
561
644
 
645
+ /** Cell or array/object (history-based TA args keep arrays intact). */
646
+ function arrOrCell(expr: string): string {
647
+ return `((__v) => (__v != null && typeof __v === "object" ? __v : (__v == null ? null : typeof __v === "boolean" ? (__v ? 1 : 0) : (typeof __v === "number" && Number.isFinite(__v) ? __v : null))))(${expr})`;
648
+ }
649
+
562
650
  /**
563
651
  * Interpret `wave10bList`: a Pine array becomes its cells; a scalar is a
564
652
  * one-element list. Bar-mode series stay one element.
@@ -567,6 +655,11 @@ function cellList(expr: string): string {
567
655
  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)]; })()`;
568
656
  }
569
657
 
658
+ /** Interpret `seriesLastCell`: array → last cell; scalar → the value itself. */
659
+ function cellLast(expr: string): string {
660
+ 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); })()`;
661
+ }
662
+
570
663
  const DRAW_KINDS = new Set(["label", "line", "box", "table", "polyline", "linefill"]);
571
664
 
572
665
  const ARRAY_SIGS: Record<string, string[][]> = {
@@ -953,6 +1046,10 @@ function emitDraw(ctx: EmitCtx, name: string, methodSrc: expr | null, args: Call
953
1046
  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")})`;
954
1047
  } else if (kind === "polyline") {
955
1048
  code = `__h.draw.polyline_new(__bar_idx, ${pick(args, 0, ["points"], "null")})`;
1049
+ } else if (kind === "table") {
1050
+ // Pine positional order: (position, columns, rows, bgcolor, frame_color,
1051
+ // frame_width, border_color, border_width, force_overlay).
1052
+ 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")})`;
956
1053
  } else if (kind === "linefill") {
957
1054
  const color = hasArg(args, 2, ["color"]) ? pick(args, 2, ["color"], "null") : "null";
958
1055
  code = `__h.draw.linefill_new(__bar_idx, ${pick(args, 0, ["id1", "line1"], "null")}, ${pick(args, 1, ["id2", "line2"], "null")}, ${color})`;
@@ -1619,6 +1716,11 @@ function taCall(method: string, site: string, parts: string[]): string {
1619
1716
  return `__h.ta.${method}(${site}, ${parts.join(", ")})`;
1620
1717
  }
1621
1718
 
1719
+ /** Call a stateless TaEngine method (no call-site argument). */
1720
+ function taPure(method: string, parts: string[]): string {
1721
+ return `__h.ta.${method}(${parts.join(", ")})`;
1722
+ }
1723
+
1622
1724
  function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
1623
1725
  if (!TA_METHODS.has(method)) return "null";
1624
1726
  if (method === "ad") method = "accdist";
@@ -2089,17 +2191,31 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
2089
2191
  ]);
2090
2192
  }
2091
2193
  if (method === "stoch_smooth") {
2092
- 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"))})`;
2194
+ return taCall("stochSmooth", site, [
2195
+ cellOf(pick(args, 0, ["high"], "null")),
2196
+ cellOf(pick(args, 1, ["low"], "null")),
2197
+ cellOf(pick(args, 2, ["close"], "null")),
2198
+ cellOf(pick(args, 3, ["period", "length"], "null")),
2199
+ cellOf(pick(args, 4, ["smooth_k", "k"], "null")),
2200
+ cellOf(pick(args, 5, ["smooth_d", "d"], "null")),
2201
+ ]);
2093
2202
  }
2094
2203
  if (method === "engulfing") {
2095
- // Bar-mode sentinel. Args are still evaluated (interpret reads all four).
2096
- 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(); })()`;
2204
+ // Args are still evaluated (interpret reads all four); high/low are unused.
2205
+ 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); })()`;
2097
2206
  }
2098
2207
  if (method === "inside_bar_pattern") {
2099
- return `(() => { ${cellOf(pick(args, 0, ["high"], "null"))}; ${cellOf(pick(args, 1, ["low"], "null"))}; return __h.ta.insideBarPattern(); })()`;
2208
+ return taCall("insideBarPattern", site, [
2209
+ cellOf(pick(args, 0, ["high"], "null")),
2210
+ cellOf(pick(args, 1, ["low"], "null")),
2211
+ ]);
2100
2212
  }
2101
2213
  if (method === "double_top_bottom") {
2102
- return `(() => { ${cellOf(pick(args, 0, ["high"], "null"))}; ${cellOf(pick(args, 1, ["low"], "null"))}; return __h.ta.doubleTopBottom(${cellOf(pick(args, 2, ["period", "length"], "null"))}); })()`;
2214
+ return taCall("doubleTopBottom", site, [
2215
+ cellOf(pick(args, 0, ["high"], "null")),
2216
+ cellOf(pick(args, 1, ["low"], "null")),
2217
+ cellOf(pick(args, 2, ["period", "length"], "null")),
2218
+ ]);
2103
2219
  }
2104
2220
  if (method === "voi") {
2105
2221
  return `__h.ta.voi(${cellOf(pick(args, 0, ["buy_volume", "buy"], "null"))}, ${cellOf(pick(args, 1, ["sell_volume", "sell"], "null"))})`;
@@ -2113,7 +2229,10 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
2113
2229
  if (method === "rsi_divergence") {
2114
2230
  // Missing period is 0 (interpret `period ?? 0`), not the engine's null → na.
2115
2231
  const period = `((__n) => (__n == null ? 0 : __n))(${cellOf(pick(args, 1, ["period", "length"], "null"))})`;
2116
- return `__h.ta.rsiDivergence(${cellList(pick(args, 0, ["rsi_series", "series", "source"], "null"))}, ${period})`;
2232
+ return taCall("rsiDivergence", site, [
2233
+ cellLast(pick(args, 0, ["rsi_series", "series", "source"], "null")),
2234
+ period,
2235
+ ]);
2117
2236
  }
2118
2237
  if (method === "cumulative_delta") {
2119
2238
  return taCall("cumulativeDelta", site, [
@@ -2148,5 +2267,365 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
2148
2267
  `((__n) => (__n == null ? 0.3 : __n))(${sens})`,
2149
2268
  ]);
2150
2269
  }
2270
+ if (method === "sma_weighted") {
2271
+ return taCall("smaWeighted", site, [
2272
+ cellOf(pick(args, 0, ["series", "source"], "null")),
2273
+ cellOf(pick(args, 1, ["period", "length"], "null")),
2274
+ pick(args, 2, ["weight_type", "weight"], '"linear"'),
2275
+ ]);
2276
+ }
2277
+ if (method === "apo") {
2278
+ return taCall("apo", site, [
2279
+ cellOf(pick(args, 0, ["series", "source"], "null")),
2280
+ cellOf(pick(args, 1, ["fast_period", "fast"], "null")),
2281
+ cellOf(pick(args, 2, ["slow_period", "slow"], "null")),
2282
+ ]);
2283
+ }
2284
+ if (method === "atr_normalized") {
2285
+ return taCall("atrNormalized", site, [
2286
+ cellOf(pick(args, 0, ["high"], "null")),
2287
+ cellOf(pick(args, 1, ["low"], "null")),
2288
+ cellOf(pick(args, 2, ["close"], "null")),
2289
+ cellOf(pick(args, 3, ["period", "length"], "null")),
2290
+ ]);
2291
+ }
2292
+ if (method === "beta" || method === "r_squared" || method === "comovement") {
2293
+ const fn = method === "beta" ? "beta" : method === "r_squared" ? "rSquared" : "comovement";
2294
+ return taCall(fn, site, [
2295
+ cellOf(pick(args, 0, ["series1", "series", "source"], "null")),
2296
+ cellOf(pick(args, 1, ["series2", "series_b", "other"], "null")),
2297
+ cellOf(pick(args, 2, ["length", "period"], "null")),
2298
+ ]);
2299
+ }
2300
+ if (method === "skewness" || method === "kurtosis") {
2301
+ return taCall(method, site, [
2302
+ cellOf(pick(args, 0, ["series", "source"], "null")),
2303
+ cellOf(pick(args, 1, ["period", "length"], "null")),
2304
+ ]);
2305
+ }
2306
+ if (method === "volume_weighted_momentum") {
2307
+ return taCall("volumeWeightedMomentum", site, [
2308
+ cellOf(pick(args, 0, ["series", "source"], "null")),
2309
+ cellOf(pick(args, 1, ["volume"], "null")),
2310
+ cellOf(pick(args, 2, ["length", "period"], "null")),
2311
+ ]);
2312
+ }
2313
+ if (method === "parkinson") {
2314
+ if (hasSeriesArg(args)) return "null";
2315
+ return `__h.ta.parkinson(${cellOf(pick(args, 0, ["high"], "null"))}, ${cellOf(pick(args, 1, ["low"], "null"))})`;
2316
+ }
2317
+ if (method === "garman_klass") {
2318
+ if (hasSeriesArg(args)) return "null";
2319
+ 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"))})`;
2320
+ }
2321
+ if (method === "mean_reversion_score") {
2322
+ return taCall("meanReversionScore", site, [
2323
+ cellOf(pick(args, 0, ["close", "source"], "null")),
2324
+ cellOf(pick(args, 1, ["sma"], "null")),
2325
+ cellOf(pick(args, 2, ["stdev", "std_dev"], "null")),
2326
+ cellOf(pick(args, 3, ["period", "length"], "null")),
2327
+ ]);
2328
+ }
2329
+ if (method === "market_condition") {
2330
+ return taCall("marketCondition", site, [
2331
+ cellOf(pick(args, 0, ["close", "source"], "null")),
2332
+ cellOf(pick(args, 1, ["atr"], "null")),
2333
+ cellOf(pick(args, 2, ["sma_period", "sma_length"], "null")),
2334
+ cellOf(pick(args, 3, ["stdev_period", "stdev_length"], "null")),
2335
+ ]);
2336
+ }
2337
+ if (method === "order_flow_imbalance") {
2338
+ return taCall("orderFlowImbalance", site, [
2339
+ cellOf(pick(args, 0, ["high"], "null")),
2340
+ cellOf(pick(args, 1, ["low"], "null")),
2341
+ cellOf(pick(args, 2, ["close"], "null")),
2342
+ cellOf(pick(args, 3, ["volume"], "null")),
2343
+ cellOf(pick(args, 4, ["period", "length"], "null")),
2344
+ ]);
2345
+ }
2346
+ if (method === "liquidity_score") {
2347
+ return taCall("liquidityScore", site, [
2348
+ cellOf(pick(args, 0, ["volume"], "null")),
2349
+ cellOf(pick(args, 1, ["volatility"], "null")),
2350
+ cellOf(pick(args, 2, ["spread"], "null")),
2351
+ cellOf(pick(args, 3, ["period", "length"], "null")),
2352
+ ]);
2353
+ }
2354
+ if (method === "fear_greed_index") {
2355
+ 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"))})`;
2356
+ }
2357
+ if (method === "crowd_sentiment") {
2358
+ 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"))})`;
2359
+ }
2360
+ if (method === "smart_money_flow") {
2361
+ 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"))})`;
2362
+ }
2363
+ if (method === "trend_confirmation_score") {
2364
+ 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"))})`;
2365
+ }
2366
+ if (method === "max_loss_level") {
2367
+ 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"))})`;
2368
+ }
2369
+ if (method === "profit_lock_level") {
2370
+ 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"))})`;
2371
+ }
2372
+ if (method === "position_sizing") {
2373
+ return taPure("positionSizing", [
2374
+ cellOf(pick(args, 0, ["account", "account_size"], "null")),
2375
+ cellOf(pick(args, 1, ["risk_pct", "risk_percent"], "null")),
2376
+ cellOf(pick(args, 2, ["entry", "entry_price"], "null")),
2377
+ cellOf(pick(args, 3, ["stop", "stop_price"], "null")),
2378
+ ]);
2379
+ }
2380
+ if (method === "kelly_criterion") {
2381
+ return taPure("kellyCriterion", [
2382
+ cellOf(pick(args, 0, ["win_rate"], "null")),
2383
+ cellOf(pick(args, 1, ["avg_win"], "null")),
2384
+ cellOf(pick(args, 2, ["avg_loss"], "null")),
2385
+ ]);
2386
+ }
2387
+ if (method === "risk_reward_ratio") {
2388
+ return taPure("riskRewardRatio", [
2389
+ cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
2390
+ cellOf(pick(args, 1, ["stop", "stop_price"], "null")),
2391
+ cellOf(pick(args, 2, ["target", "target_price"], "null")),
2392
+ ]);
2393
+ }
2394
+ if (method === "risk_reward_asymmetry") {
2395
+ return taPure("riskRewardAsymmetry", [
2396
+ cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
2397
+ cellOf(pick(args, 1, ["stop", "stop_price"], "null")),
2398
+ cellOf(pick(args, 2, ["target", "target_price"], "null")),
2399
+ cellOf(pick(args, 3, ["win_rate"], "null")),
2400
+ ]);
2401
+ }
2402
+ if (method === "probability_of_movement") {
2403
+ return taPure("probabilityOfMovement", [
2404
+ cellOf(pick(args, 0, ["current", "current_price"], "null")),
2405
+ cellOf(pick(args, 1, ["target", "target_price"], "null")),
2406
+ cellOf(pick(args, 2, ["atr"], "null")),
2407
+ cellOf(pick(args, 3, ["period", "length"], "null")),
2408
+ ]);
2409
+ }
2410
+ if (method === "trend_strength") {
2411
+ return taPure("trendStrength", [
2412
+ cellOf(pick(args, 0, ["price", "source"], "null")),
2413
+ cellOf(pick(args, 1, ["adx"], "null")),
2414
+ cellOf(pick(args, 2, ["rsi"], "null")),
2415
+ ]);
2416
+ }
2417
+ if (method === "breakeven_level") {
2418
+ return taPure("breakevenLevel", [
2419
+ cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
2420
+ cellOf(pick(args, 1, ["quantity", "qty"], "null")),
2421
+ cellOf(pick(args, 2, ["commission"], "null")),
2422
+ cellOf(pick(args, 3, ["slippage"], "null")),
2423
+ cellOf(pick(args, 4, ["direction"], "null")),
2424
+ ]);
2425
+ }
2426
+ if (method === "market_structure_pivot") {
2427
+ return taCall("marketStructurePivot", site, [
2428
+ arrOrCell(pick(args, 0, ["high_list", "high"], "null")),
2429
+ arrOrCell(pick(args, 1, ["low_list", "low"], "null")),
2430
+ cellOf(pick(args, 3, ["period", "left", "left_bars"], "null")),
2431
+ cellOf(pick(args, 4, ["mode", "right", "right_bars"], "null")),
2432
+ ]);
2433
+ }
2434
+ if (method === "position_sizing_score") {
2435
+ return taPure("positionSizingScore", [
2436
+ cellOf(pick(args, 0, ["account_risk", "risk"], "null")),
2437
+ cellOf(pick(args, 1, ["volatility"], "null")),
2438
+ cellOf(pick(args, 2, ["conviction"], "null")),
2439
+ cellOf(pick(args, 3, ["correlation"], "null")),
2440
+ ]);
2441
+ }
2442
+ if (method === "trailing_exit_level") {
2443
+ return taPure("trailingExitLevel", [
2444
+ cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
2445
+ cellOf(pick(args, 1, ["current", "current_price"], "null")),
2446
+ cellOf(pick(args, 2, ["atr"], "null")),
2447
+ cellOf(pick(args, 3, ["multiplier", "mult"], "null")),
2448
+ cellOf(pick(args, 4, ["step"], "null")),
2449
+ ]);
2450
+ }
2451
+ if (method === "optimal_entry_zone") {
2452
+ return taCall("optimalEntryZone", site, [arrOrCell(pick(args, 2, ["close"], "null"))]);
2453
+ }
2454
+ if (method === "mean_reversion_entry") {
2455
+ return taCall("meanReversionEntry", site, [
2456
+ arrOrCell(pick(args, 0, ["price", "source"], "null")),
2457
+ arrOrCell(pick(args, 1, ["sma"], "null")),
2458
+ arrOrCell(pick(args, 2, ["stdev", "std_dev"], "null")),
2459
+ cellOf(pick(args, 4, ["threshold"], "null")),
2460
+ ]);
2461
+ }
2462
+ if (method === "multi_timeframe_signal") {
2463
+ return taPure("multiTimeframeSignal", [
2464
+ cellOf(pick(args, 0, ["s1", "signal1"], "null")),
2465
+ cellOf(pick(args, 1, ["s2", "signal2"], "null")),
2466
+ cellOf(pick(args, 2, ["s3", "signal3"], "null")),
2467
+ cellOf(pick(args, 3, ["w1", "weight1"], "null")),
2468
+ cellOf(pick(args, 4, ["w2", "weight2"], "null")),
2469
+ cellOf(pick(args, 5, ["w3", "weight3"], "null")),
2470
+ ]);
2471
+ }
2472
+ if (method === "market_timing_index") {
2473
+ return taPure("marketTimingIndex", [
2474
+ cellOf(pick(args, 0, ["trend"], "null")),
2475
+ cellOf(pick(args, 1, ["momentum"], "null")),
2476
+ cellOf(pick(args, 2, ["volatility"], "null")),
2477
+ cellOf(pick(args, 3, ["sentiment"], "null")),
2478
+ ]);
2479
+ }
2480
+ if (method === "regime_adaptive_signal") {
2481
+ return taPure("regimeAdaptiveSignal", [
2482
+ cellOf(pick(args, 0, ["signal_strength", "strength"], "null")),
2483
+ pick(args, 1, ["volatility_regime", "vol_regime"], '""'),
2484
+ pick(args, 2, ["trend_regime"], '""'),
2485
+ ]);
2486
+ }
2487
+ if (method === "strategy_score") {
2488
+ return taPure("strategyScore", [
2489
+ cellOf(pick(args, 0, ["rsi"], "null")),
2490
+ cellOf(pick(args, 1, ["macd"], "null")),
2491
+ cellOf(pick(args, 2, ["ema_cross"], "null")),
2492
+ cellOf(pick(args, 3, ["trend"], "null")),
2493
+ ]);
2494
+ }
2495
+ if (method === "breakout_detection") {
2496
+ return taPure("breakoutDetection", [
2497
+ cellOf(pick(args, 0, ["close", "source"], "null")),
2498
+ cellOf(pick(args, 1, ["resistance"], "null")),
2499
+ cellOf(pick(args, 2, ["support"], "null")),
2500
+ ]);
2501
+ }
2502
+ if (method === "ema_cross_signal") {
2503
+ return taCall("emaCrossSignal", site, [
2504
+ cellOf(pick(args, 0, ["fast_ema", "fast"], "null")),
2505
+ cellOf(pick(args, 1, ["slow_ema", "slow"], "null")),
2506
+ cellOf(pick(args, 2, ["threshold"], "null")),
2507
+ ]);
2508
+ }
2509
+ if (method === "momentum_filter") {
2510
+ return taCall("momentumFilter", site, [
2511
+ cellOf(pick(args, 0, ["momentum_raw", "momentum"], "null")),
2512
+ cellOf(pick(args, 1, ["volume"], "null")),
2513
+ cellOf(pick(args, 2, ["period", "length"], "null")),
2514
+ ]);
2515
+ }
2516
+ if (method === "volatility_regime") {
2517
+ return taCall("volatilityRegime", site, [
2518
+ cellOf(pick(args, 0, ["atr_series", "atr", "source"], "null")),
2519
+ cellOf(pick(args, 1, ["period", "length"], "null")),
2520
+ ]);
2521
+ }
2522
+ if (method === "economic_impact_score") {
2523
+ return taPure("economicImpactScore", [
2524
+ cellOf(pick(args, 0, ["price_change"], "null")),
2525
+ cellOf(pick(args, 1, ["volatility"], "null")),
2526
+ cellOf(pick(args, 2, ["volume_change"], "null")),
2527
+ ]);
2528
+ }
2529
+ if (method === "contrarian_signal") {
2530
+ return taPure("contrarianSignal", [
2531
+ cellOf(pick(args, 0, ["sentiment"], "null")),
2532
+ cellOf(pick(args, 1, ["volatility"], "null")),
2533
+ cellOf(pick(args, 2, ["time_since_extreme", "time_extreme"], "null")),
2534
+ ]);
2535
+ }
2536
+ if (method === "drawdown_recovery_level") {
2537
+ return taPure("drawdownRecoveryLevel", [
2538
+ cellOf(pick(args, 0, ["peak"], "null")),
2539
+ cellOf(pick(args, 1, ["current", "current_price"], "null")),
2540
+ cellOf(pick(args, 2, ["recovery_factor", "factor"], "null")),
2541
+ cellOf(pick(args, 3, ["period", "length"], "null")),
2542
+ ]);
2543
+ }
2544
+ if (method === "pivot_point_levels") {
2545
+ return taPure("pivotPointLevels", [
2546
+ cellOf(pick(args, 0, ["high"], "null")),
2547
+ cellOf(pick(args, 1, ["low"], "null")),
2548
+ cellOf(pick(args, 2, ["close"], "null")),
2549
+ pick(args, 3, ["traditional"], "true"),
2550
+ ]);
2551
+ }
2552
+ if (method === "volatility_regime_score") {
2553
+ return taCall("volatilityRegimeScore", site, [
2554
+ arrOrCell(pick(args, 0, ["atr_list", "atr"], "null")),
2555
+ arrOrCell(pick(args, 1, ["volatility_list", "volatility"], "null")),
2556
+ arrOrCell(pick(args, 2, ["vix_list", "vix"], "null")),
2557
+ cellOf(pick(args, 3, ["threshold"], "null")),
2558
+ ]);
2559
+ }
2560
+ if (method === "correlation_filter") {
2561
+ return taCall("correlationFilter", site, [
2562
+ arrOrCell(pick(args, 0, ["signal1_list", "signal1", "s1"], "null")),
2563
+ arrOrCell(pick(args, 1, ["signal2_list", "signal2", "s2"], "null")),
2564
+ arrOrCell(pick(args, 2, ["signal3_list", "signal3", "s3"], "null")),
2565
+ cellOf(pick(args, 4, ["threshold"], "null")),
2566
+ ]);
2567
+ }
2568
+ if (method === "advanced_breakout_detector") {
2569
+ return taCall("advancedBreakoutDetector", site, [
2570
+ arrOrCell(pick(args, 0, ["price_list", "price"], "null")),
2571
+ arrOrCell(pick(args, 1, ["volume_list", "volume"], "null")),
2572
+ cellOf(pick(args, 2, ["level"], "null")),
2573
+ cellOf(pick(args, 3, ["lookback"], "null")),
2574
+ cellOf(pick(args, 4, ["volume_multiplier", "vol_mult"], "null")),
2575
+ ]);
2576
+ }
2577
+ if (method === "pullback_bounce_level") {
2578
+ return taCall("pullbackBounceLevel", site, [
2579
+ arrOrCell(pick(args, 0, ["high_list", "high"], "null")),
2580
+ arrOrCell(pick(args, 1, ["low_list", "low"], "null")),
2581
+ cellOf(pick(args, 3, ["trend_direction", "trend_dir"], "null")),
2582
+ cellOf(pick(args, 4, ["lookback"], "null")),
2583
+ ]);
2584
+ }
2585
+ if (method === "divergence_detector") {
2586
+ return taCall("divergenceDetector", site, [
2587
+ arrOrCell(pick(args, 0, ["price_list", "price"], "null")),
2588
+ arrOrCell(pick(args, 1, ["indicator_list", "indicator"], "null")),
2589
+ cellOf(pick(args, 2, ["lookback"], "null")),
2590
+ ]);
2591
+ }
2592
+ if (method === "momentum_divergence") {
2593
+ return taCall("momentumDivergence", site, [
2594
+ arrOrCell(pick(args, 0, ["price"], "null")),
2595
+ arrOrCell(pick(args, 1, ["momentum_fast", "mom_fast"], "null")),
2596
+ arrOrCell(pick(args, 2, ["momentum_slow", "mom_slow"], "null")),
2597
+ ]);
2598
+ }
2599
+ if (method === "spread_analysis") {
2600
+ return taCall("spreadAnalysis", site, [
2601
+ arrOrCell(pick(args, 0, ["bid"], "null")),
2602
+ arrOrCell(pick(args, 1, ["ask"], "null")),
2603
+ cellOf(pick(args, 2, ["period", "length"], "null")),
2604
+ ]);
2605
+ }
2606
+ if (method === "employment_cycle_indicator") {
2607
+ return taCall("employmentCycleIndicator", site, [
2608
+ arrOrCell(pick(args, 0, ["cyclical_stocks", "cyclical"], "null")),
2609
+ arrOrCell(pick(args, 1, ["defensive_stocks", "defensive"], "null")),
2610
+ arrOrCell(pick(args, 2, ["unemployment_proxy", "unemployment"], "null")),
2611
+ ]);
2612
+ }
2613
+ if (method === "gdp_growth_proxy") {
2614
+ return taCall("gdpGrowthProxy", site, [
2615
+ arrOrCell(pick(args, 0, ["market_breadth", "breadth"], "null")),
2616
+ arrOrCell(pick(args, 1, ["market_volume", "volume"], "null")),
2617
+ arrOrCell(pick(args, 2, ["price_momentum", "momentum"], "null")),
2618
+ ]);
2619
+ }
2620
+ if (method === "inflation_proxy_indicator") {
2621
+ return taCall("inflationProxyIndicator", site, [
2622
+ arrOrCell(pick(args, 0, ["usd_index", "usd"], "null")),
2623
+ arrOrCell(pick(args, 1, ["commodity_prices", "commodities"], "null")),
2624
+ arrOrCell(pick(args, 2, ["bond_yields", "yields"], "null")),
2625
+ ]);
2626
+ }
2627
+ if (method === "expected_value") {
2628
+ return `__h.ta.expectedValue(${cellList(pick(args, 0, ["returns"], "null"))}, ${cellList(pick(args, 1, ["probabilities", "probs"], "null"))})`;
2629
+ }
2151
2630
  return "null";
2152
2631
  }
@@ -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
 
@@ -79,7 +79,7 @@ import {
79
79
  } from "../footprint.ts";
80
80
  import { resolveCurrencyRate, resolveSeed } from "../request.ts";
81
81
  import { TaEngine } from "../ta.ts";
82
- import { utcPartsFromMs, timestamp, weekOfYear, timeTradingDay, type UtcParts } from "../time.ts";
82
+ import { utcPartsFromMs, timestamp, timestampToMs, weekOfYear, timeTradingDay, type UtcParts } from "../time.ts";
83
83
  import { TickerId } from "../ticker.ts";
84
84
  import { timeframeInSeconds, timeframePeriodChanged } from "../timeframe.ts";
85
85
  import { compileArray, compileMap, compileMatrix } from "./collections.ts";
@@ -203,8 +203,8 @@ export function createCompileHelpers(opts?: CompileHelperOpts) {
203
203
  second: calendarSecond,
204
204
  dayofweek: calendarDayofweek,
205
205
  timestamp,
206
- weekOfYear,
207
- timeTradingDay,
206
+ weekOfYear: calendarWeekOfYear,
207
+ timeTradingDay: calendarTimeTradingDay,
208
208
  timeframeInSeconds: timeframeInSecondsOrDaily,
209
209
  timeframeChange,
210
210
  timeClose,
@@ -339,10 +339,25 @@ function NA_FN(v?: unknown): number | null {
339
339
  return isNaCell(v) ? 1 : 0;
340
340
  }
341
341
 
342
- /** 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`). */
343
343
  function utcPart(t: unknown, key: keyof UtcParts): number | null {
344
344
  if (typeof t !== "number" || !Number.isFinite(t)) return null;
345
- 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);
346
361
  }
347
362
 
348
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