@hoox-sh/pynets 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoox-sh/pynets",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "author": "jango_blockchained",
package/src/cli/rich.ts CHANGED
@@ -12,7 +12,7 @@ const MUTED = [203, 204, 189] as const;
12
12
  const FAIL = [231, 0, 11] as const;
13
13
  const WARN = [235, 169, 65] as const;
14
14
 
15
- export const VERSION = "0.5.0";
15
+ export const VERSION = "0.6.0";
16
16
 
17
17
  type Rgb = readonly [number, number, number];
18
18
 
@@ -73,6 +73,10 @@ export default class PinescriptLexerBase extends Lexer {
73
73
  private _lastPendingTokenType = 0;
74
74
  private _lastPendingTokenTypeFromDefaultChannel = 0;
75
75
  private _numOpens = 0;
76
+ // Open square brackets (history-refs / tuples). Tracked separately from
77
+ // parens so wrapped `[...]` allows any indentation, like `(...)`.
78
+ // Python `PinescriptLexerBase._numSqBrackets` SoT (Sept 2026 release notes).
79
+ private _numSqBrackets = 0;
76
80
  private _indentLengthStack: number[] = [];
77
81
  private _inputStarted = false;
78
82
 
@@ -129,6 +133,7 @@ export default class PinescriptLexerBase extends Lexer {
129
133
  this._lastPendingTokenType = 0;
130
134
  this._lastPendingTokenTypeFromDefaultChannel = 0;
131
135
  this._numOpens = 0;
136
+ this._numSqBrackets = 0;
132
137
  this._indentLengthStack = [];
133
138
  this._inputStarted = false;
134
139
  }
@@ -147,11 +152,17 @@ export default class PinescriptLexerBase extends Lexer {
147
152
  const tok = this._currentToken!;
148
153
  const tokType = tok.type;
149
154
  const C = this.C;
150
- if (tokType === C.LPAR || tokType === C.LSQB) {
155
+ if (tokType === C.LPAR) {
151
156
  this._numOpens += 1;
152
157
  this._addPendingToken(tok);
153
- } else if (tokType === C.RPAR || tokType === C.RSQB) {
154
- this._numOpens -= 1;
158
+ } else if (tokType === C.LSQB) {
159
+ this._numSqBrackets += 1;
160
+ this._addPendingToken(tok);
161
+ } else if (tokType === C.RPAR) {
162
+ this._numOpens = Math.max(0, this._numOpens - 1);
163
+ this._addPendingToken(tok);
164
+ } else if (tokType === C.RSQB) {
165
+ this._numSqBrackets = Math.max(0, this._numSqBrackets - 1);
155
166
  this._addPendingToken(tok);
156
167
  } else if (tokType === C.NEWLINE) {
157
168
  this._handleNEWLINEToken();
@@ -264,7 +275,14 @@ export default class PinescriptLexerBase extends Lexer {
264
275
  }
265
276
 
266
277
  private _handleNEWLINEToken(): void {
267
- if (this._numOpens > 0 || this._operators.has(this._lastPendingTokenTypeFromDefaultChannel)) {
278
+ // Newlines inside (...) or [...] are hidden (Python parity): wrapped
279
+ // history-refs / tuples may use any indentation without emitting
280
+ // INDENT/DEDENT mid-expression.
281
+ if (
282
+ this._numOpens > 0 ||
283
+ this._numSqBrackets > 0 ||
284
+ this._operators.has(this._lastPendingTokenTypeFromDefaultChannel)
285
+ ) {
268
286
  this._hideAndAddPendingToken(this._currentToken!);
269
287
  return;
270
288
  }
@@ -73,6 +73,10 @@ export default class PinescriptLexerBase extends Lexer {
73
73
  private _lastPendingTokenType = 0;
74
74
  private _lastPendingTokenTypeFromDefaultChannel = 0;
75
75
  private _numOpens = 0;
76
+ // Open square brackets (history-refs / tuples). Tracked separately from
77
+ // parens so wrapped `[...]` allows any indentation, like `(...)`.
78
+ // Python `PinescriptLexerBase._numSqBrackets` SoT (Sept 2026 release notes).
79
+ private _numSqBrackets = 0;
76
80
  private _indentLengthStack: number[] = [];
77
81
  private _inputStarted = false;
78
82
 
@@ -129,6 +133,7 @@ export default class PinescriptLexerBase extends Lexer {
129
133
  this._lastPendingTokenType = 0;
130
134
  this._lastPendingTokenTypeFromDefaultChannel = 0;
131
135
  this._numOpens = 0;
136
+ this._numSqBrackets = 0;
132
137
  this._indentLengthStack = [];
133
138
  this._inputStarted = false;
134
139
  }
@@ -147,11 +152,17 @@ export default class PinescriptLexerBase extends Lexer {
147
152
  const tok = this._currentToken!;
148
153
  const tokType = tok.type;
149
154
  const C = this.C;
150
- if (tokType === C.LPAR || tokType === C.LSQB) {
155
+ if (tokType === C.LPAR) {
151
156
  this._numOpens += 1;
152
157
  this._addPendingToken(tok);
153
- } else if (tokType === C.RPAR || tokType === C.RSQB) {
154
- this._numOpens -= 1;
158
+ } else if (tokType === C.LSQB) {
159
+ this._numSqBrackets += 1;
160
+ this._addPendingToken(tok);
161
+ } else if (tokType === C.RPAR) {
162
+ this._numOpens = Math.max(0, this._numOpens - 1);
163
+ this._addPendingToken(tok);
164
+ } else if (tokType === C.RSQB) {
165
+ this._numSqBrackets = Math.max(0, this._numSqBrackets - 1);
155
166
  this._addPendingToken(tok);
156
167
  } else if (tokType === C.NEWLINE) {
157
168
  this._handleNEWLINEToken();
@@ -264,7 +275,14 @@ export default class PinescriptLexerBase extends Lexer {
264
275
  }
265
276
 
266
277
  private _handleNEWLINEToken(): void {
267
- if (this._numOpens > 0 || this._operators.has(this._lastPendingTokenTypeFromDefaultChannel)) {
278
+ // Newlines inside (...) or [...] are hidden (Python parity): wrapped
279
+ // history-refs / tuples may use any indentation without emitting
280
+ // INDENT/DEDENT mid-expression.
281
+ if (
282
+ this._numOpens > 0 ||
283
+ this._numSqBrackets > 0 ||
284
+ this._operators.has(this._lastPendingTokenTypeFromDefaultChannel)
285
+ ) {
268
286
  this._hideAndAddPendingToken(this._currentToken!);
269
287
  return;
270
288
  }
@@ -103,8 +103,32 @@ export class CompileDraw {
103
103
  return this.book.boxNew(n(bar, 0), Object.keys(extra).length ? extra : undefined);
104
104
  }
105
105
 
106
- table_new(bar: unknown): number {
107
- return this.book.tableNew(n(bar, 0));
106
+ table_new(
107
+ bar: unknown,
108
+ position?: unknown,
109
+ columns?: unknown,
110
+ rows?: unknown,
111
+ bgcolor?: unknown,
112
+ frameColor?: unknown,
113
+ frameWidth?: unknown,
114
+ borderColor?: unknown,
115
+ borderWidth?: unknown,
116
+ forceOverlay?: unknown,
117
+ ): number {
118
+ // Pine positional order: (position, columns, rows, bgcolor, frame_color,
119
+ // frame_width, border_color, border_width, force_overlay) — Python
120
+ // `compiler.py` table emit SoT (columns then rows).
121
+ const extra: Record<string, unknown> = {};
122
+ if (position != null) extra.position = position;
123
+ putNum(extra, "columns", columns);
124
+ putNum(extra, "rows", rows);
125
+ if (bgcolor != null) extra.bgcolor = bgcolor;
126
+ if (frameColor != null) extra.frame_color = frameColor;
127
+ putNum(extra, "frame_width", frameWidth);
128
+ if (borderColor != null) extra.border_color = borderColor;
129
+ putNum(extra, "border_width", borderWidth);
130
+ if (forceOverlay != null) extra.force_overlay = forceOverlay;
131
+ return this.book.tableNew(n(bar, 0), Object.keys(extra).length ? extra : undefined);
108
132
  }
109
133
 
110
134
  polyline_new(bar: unknown, points?: unknown): number {
@@ -155,6 +155,44 @@ 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",
158
196
  ]);
159
197
 
160
198
  /**
@@ -530,6 +568,26 @@ function hasArg(args: CallArgs, index: number, names: string[]): boolean {
530
568
  return index >= 0 && index < args.pos.length;
531
569
  }
532
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
+
533
591
  /** True when a keyword argument named one of *names* is present (interpret hasKwarg). */
534
592
  function hasAnyKw(args: CallArgs, names: string[]): boolean {
535
593
  return names.some((n) => Object.hasOwn(args.kw, n));
@@ -567,6 +625,11 @@ function cellList(expr: string): string {
567
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)]; })()`;
568
626
  }
569
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
+
570
633
  const DRAW_KINDS = new Set(["label", "line", "box", "table", "polyline", "linefill"]);
571
634
 
572
635
  const ARRAY_SIGS: Record<string, string[][]> = {
@@ -953,6 +1016,10 @@ function emitDraw(ctx: EmitCtx, name: string, methodSrc: expr | null, args: Call
953
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")})`;
954
1017
  } else if (kind === "polyline") {
955
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")})`;
956
1023
  } else if (kind === "linefill") {
957
1024
  const color = hasArg(args, 2, ["color"]) ? pick(args, 2, ["color"], "null") : "null";
958
1025
  code = `__h.draw.linefill_new(__bar_idx, ${pick(args, 0, ["id1", "line1"], "null")}, ${pick(args, 1, ["id2", "line2"], "null")}, ${color})`;
@@ -1619,6 +1686,11 @@ function taCall(method: string, site: string, parts: string[]): string {
1619
1686
  return `__h.ta.${method}(${site}, ${parts.join(", ")})`;
1620
1687
  }
1621
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
+
1622
1694
  function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
1623
1695
  if (!TA_METHODS.has(method)) return "null";
1624
1696
  if (method === "ad") method = "accdist";
@@ -2089,17 +2161,31 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
2089
2161
  ]);
2090
2162
  }
2091
2163
  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"))})`;
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
+ ]);
2093
2172
  }
2094
2173
  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(); })()`;
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); })()`;
2097
2176
  }
2098
2177
  if (method === "inside_bar_pattern") {
2099
- 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
+ ]);
2100
2182
  }
2101
2183
  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"))}); })()`;
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
+ ]);
2103
2189
  }
2104
2190
  if (method === "voi") {
2105
2191
  return `__h.ta.voi(${cellOf(pick(args, 0, ["buy_volume", "buy"], "null"))}, ${cellOf(pick(args, 1, ["sell_volume", "sell"], "null"))})`;
@@ -2113,7 +2199,10 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
2113
2199
  if (method === "rsi_divergence") {
2114
2200
  // Missing period is 0 (interpret `period ?? 0`), not the engine's null → na.
2115
2201
  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})`;
2202
+ return taCall("rsiDivergence", site, [
2203
+ cellLast(pick(args, 0, ["rsi_series", "series", "source"], "null")),
2204
+ period,
2205
+ ]);
2117
2206
  }
2118
2207
  if (method === "cumulative_delta") {
2119
2208
  return taCall("cumulativeDelta", site, [
@@ -2148,5 +2237,234 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
2148
2237
  `((__n) => (__n == null ? 0.3 : __n))(${sens})`,
2149
2238
  ]);
2150
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
+ }
2151
2469
  return "null";
2152
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
 
@@ -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