@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.
@@ -114,7 +114,7 @@ import {
114
114
  mathToDegrees,
115
115
  mathToRadians,
116
116
  } from "./math.ts";
117
- import { utcPartsFromMs, timestamp, timeTradingDay, weekOfYear } from "./time.ts";
117
+ import { utcPartsFromMs, timestamp, timestampToMs, timeTradingDay, weekOfYear } from "./time.ts";
118
118
  import {
119
119
  LibraryModule,
120
120
  LibraryRegistry,
@@ -588,6 +588,7 @@ export function interpretTree(
588
588
  high: num(env.ctx.high) ?? undefined,
589
589
  low: num(env.ctx.low) ?? undefined,
590
590
  close: num(env.ctx.close) ?? undefined,
591
+ time: num(env.ctx.time) ?? undefined,
591
592
  });
592
593
  env.book.markOpenTrades(
593
594
  num(env.ctx.high) ?? num(env.ctx.close) ?? 0,
@@ -937,6 +938,35 @@ function seriesGet(env: Env, id: string): PineSeries | undefined {
937
938
  return env.series.get(id);
938
939
  }
939
940
 
941
+ /** Bare series names Python's evaluator exposes as `PineSeries` objects. */
942
+ const BARE_SERIES_NAMES = new Set([
943
+ "open",
944
+ "high",
945
+ "low",
946
+ "close",
947
+ "volume",
948
+ "hl2",
949
+ "hlc3",
950
+ "ohlc4",
951
+ "hlcc4",
952
+ "time",
953
+ "time_close",
954
+ ]);
955
+
956
+ /**
957
+ * True when an argument expression is a series reference rather than a scalar.
958
+ * Python handlers that do `args[0] if isinstance(args[0], list) else [args[0]]`
959
+ * (e.g. `ta.parkinson`, `ta.garman_klass`) see the `PineSeries` object in bar
960
+ * mode and therefore return `na`.
961
+ */
962
+ function isSeriesExpr(value: expr, env: Env): boolean {
963
+ if (value.kind === "Name") {
964
+ if (BARE_SERIES_NAMES.has(value.id)) return true;
965
+ return seriesGet(env, value.id) != null;
966
+ }
967
+ return false;
968
+ }
969
+
940
970
  function bindName(env: Env, id: string, value: Value): void {
941
971
  const stored: Value = typeof value === "number" && !Number.isFinite(value) ? NA : value;
942
972
  const frame = activeFrame(env);
@@ -2075,6 +2105,39 @@ function evalCall(node: Call, env: Env): Value {
2075
2105
  evalAsString(callArg(node.args, 3, ["adjust", "adjustment"]), env) ?? undefined,
2076
2106
  });
2077
2107
  }
2108
+ if (fname === "ticker.inherit") {
2109
+ const first = evalExpr(callArg(node.args, 0, ["ticker", "tickerid", "from", "source"]), env);
2110
+ const secondArg = callArg(node.args, 1, ["symbol", "ticker"]);
2111
+ if (secondArg != null) {
2112
+ const target = evalExpr(secondArg, env);
2113
+ const sym =
2114
+ target instanceof TickerId
2115
+ ? target.symbol
2116
+ : typeof target === "string"
2117
+ ? target
2118
+ : env.symbol;
2119
+ if (first instanceof TickerId) return tickerNew(sym, first.session, first.adjust);
2120
+ return tickerNew(sym);
2121
+ }
2122
+ if (first instanceof TickerId) {
2123
+ return tickerNew(first.symbol, first.session, first.adjust);
2124
+ }
2125
+ const sym = typeof first === "string" ? first : env.symbol;
2126
+ return tickerNew(sym);
2127
+ }
2128
+ if (
2129
+ fname === "renko" ||
2130
+ fname === "kagi" ||
2131
+ fname === "linebreak" ||
2132
+ fname === "pointfigure"
2133
+ ) {
2134
+ const raw = evalExpr(callArg(node.args, 0, ["ticker", "tickerid", "symbol"]), env);
2135
+ const sym = raw instanceof TickerId ? raw.symbol : typeof raw === "string" ? raw : env.symbol;
2136
+ if (fname === "renko") return tickerRenko(sym);
2137
+ if (fname === "kagi") return tickerKagi(sym);
2138
+ if (fname === "linebreak") return tickerLinebreak(sym);
2139
+ return tickerPointfigure(sym);
2140
+ }
2078
2141
  const mapVal = evalMapCall(fname, node, env);
2079
2142
  if (mapVal !== undefined) return mapVal;
2080
2143
  const matrixVal = evalMatrixCall(fname, node, env);
@@ -2107,6 +2170,7 @@ function evalCall(node: Call, env: Env): Value {
2107
2170
  time: num(env.ctx.time) ?? undefined,
2108
2171
  oca_name: evalAsString(callArg(node.args, -1, ["oca_name"]), env),
2109
2172
  oca_type: evalAsString(callArg(node.args, -1, ["oca_type"]), env),
2173
+ deferMarket: true,
2110
2174
  });
2111
2175
  return NA;
2112
2176
  }
@@ -2115,12 +2179,10 @@ function evalCall(node: Call, env: Env): Value {
2115
2179
  const mark = num(env.ctx.close);
2116
2180
  const qtyExpr = callArg(node.args, 1, ["qty"]);
2117
2181
  const qtyRaw = qtyExpr == null ? undefined : unwrap(evalExpr(qtyExpr, env));
2118
- if (env.book.position.qty !== 0 && mark != null) {
2119
- env.book.fillClose(env.barIndex, id, mark, {
2120
- time: num(env.ctx.time) ?? undefined,
2121
- qty: qtyRaw ?? undefined,
2122
- });
2123
- } else env.book.close(env.barIndex, id);
2182
+ env.book.placeClose(env.barIndex, id, mark ?? undefined, {
2183
+ time: num(env.ctx.time) ?? undefined,
2184
+ qty: qtyRaw ?? undefined,
2185
+ });
2124
2186
  return NA;
2125
2187
  }
2126
2188
  if (fname === "strategy.exit") {
@@ -2140,17 +2202,12 @@ function evalCall(node: Call, env: Env): Value {
2140
2202
  trail_offset: unwrap(evalExpr(callArg(node.args, 10, ["trail_offset"]), env)),
2141
2203
  price: num(env.ctx.close) ?? undefined,
2142
2204
  time: num(env.ctx.time) ?? undefined,
2143
- ohlc: {
2144
- open: num(env.ctx.open) ?? undefined,
2145
- high: num(env.ctx.high) ?? undefined,
2146
- low: num(env.ctx.low) ?? undefined,
2147
- close: num(env.ctx.close) ?? undefined,
2148
- },
2205
+ deferMarket: true,
2149
2206
  });
2150
2207
  return NA;
2151
2208
  }
2152
2209
  if (fname === "strategy.close_all") {
2153
- env.book.closeAll(env.barIndex, num(env.ctx.close) ?? undefined);
2210
+ env.book.placeCloseAll(env.barIndex, num(env.ctx.close) ?? undefined);
2154
2211
  return NA;
2155
2212
  }
2156
2213
  if (fname === "strategy.cancel") {
@@ -2173,6 +2230,7 @@ function evalCall(node: Call, env: Env): Value {
2173
2230
  time: num(env.ctx.time) ?? undefined,
2174
2231
  oca_name: evalAsString(callArg(node.args, -1, ["oca_name"]), env),
2175
2232
  oca_type: evalAsString(callArg(node.args, -1, ["oca_type"]), env),
2233
+ deferMarket: true,
2176
2234
  });
2177
2235
  return NA;
2178
2236
  }
@@ -3816,7 +3874,7 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
3816
3874
  lenArg(node, env, 1),
3817
3875
  );
3818
3876
  }
3819
- if (fname === "ta.percentile_nearest_rank") {
3877
+ if (fname === "ta.percentile_nearest_rank" || fname === "percentile_nearest_rank") {
3820
3878
  if (typeof ta.percentileNearest !== "function") return NA;
3821
3879
  return (ta.percentileNearest as TaEngine["percentileNearest"]).call(
3822
3880
  env.ta,
@@ -3826,7 +3884,7 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
3826
3884
  lenOrDefault(node, env, 2, ["percentage", "percent"], 50),
3827
3885
  );
3828
3886
  }
3829
- if (fname === "ta.percentile_linear_interpolation") {
3887
+ if (fname === "ta.percentile_linear_interpolation" || fname === "percentile_linear_interpolation") {
3830
3888
  if (typeof ta.percentileLinear !== "function") return NA;
3831
3889
  return (ta.percentileLinear as TaEngine["percentileLinear"]).call(
3832
3890
  env.ta,
@@ -4024,17 +4082,6 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
4024
4082
  const r = env.ta.aroon(site, num(env.ctx.high), num(env.ctx.low), length ?? 14);
4025
4083
  return tupleOf([r.down, r.up]);
4026
4084
  }
4027
- if (fname === "ta.pivot_point_levels") {
4028
- if (typeof ta.pivotPoints !== "function") return NA;
4029
- const r = (ta.pivotPoints as TaEngine["pivotPoints"]).call(
4030
- env.ta,
4031
- site,
4032
- num(env.ctx.high),
4033
- num(env.ctx.low),
4034
- num(env.ctx.close),
4035
- );
4036
- return tupleOf([r.pp, r.r1, r.s1, r.r2, r.s2]);
4037
- }
4038
4085
  if (fname === "ta.uo" || fname === "uo") {
4039
4086
  return env.ta.uo(
4040
4087
  site,
@@ -4126,11 +4173,11 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
4126
4173
  }
4127
4174
  if (fname === "ta.engulfing" || fname === "engulfing") {
4128
4175
  const msg = "ta.engulfing() requires 4 arguments: open, high, low, close";
4129
- unwrap(evalExpr(wave10aArg(0, ["open"], msg), env));
4176
+ const open = unwrap(evalExpr(wave10aArg(0, ["open"], msg), env));
4130
4177
  unwrap(evalExpr(wave10aArg(1, ["high"], msg), env));
4131
4178
  unwrap(evalExpr(wave10aArg(2, ["low"], msg), env));
4132
- unwrap(evalExpr(wave10aArg(3, ["close"], msg), env));
4133
- return env.ta.engulfing();
4179
+ const close = unwrap(evalExpr(wave10aArg(3, ["close"], msg), env));
4180
+ return env.ta.engulfing(site, open, close);
4134
4181
  }
4135
4182
  if (fname === "ta.gap_detector" || fname === "gap_detector") {
4136
4183
  const msg = "ta.gap_detector() requires 3 arguments: high, low, prev_close";
@@ -4142,15 +4189,20 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
4142
4189
  }
4143
4190
  if (fname === "ta.double_top_bottom" || fname === "double_top_bottom") {
4144
4191
  const msg = "ta.double_top_bottom() requires 3 arguments";
4145
- unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
4146
- unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
4147
- return env.ta.doubleTopBottom(unwrap(evalExpr(wave10aArg(2, ["period", "length"], msg), env)));
4192
+ const high = unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
4193
+ const low = unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
4194
+ return env.ta.doubleTopBottom(
4195
+ site,
4196
+ high,
4197
+ low,
4198
+ unwrap(evalExpr(wave10aArg(2, ["period", "length"], msg), env)),
4199
+ );
4148
4200
  }
4149
4201
  if (fname === "ta.inside_bar_pattern" || fname === "inside_bar_pattern") {
4150
4202
  const msg = "ta.inside_bar_pattern() requires 2 arguments";
4151
- unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
4152
- unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
4153
- return env.ta.insideBarPattern();
4203
+ const high = unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
4204
+ const low = unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
4205
+ return env.ta.insideBarPattern(site, high, low);
4154
4206
  }
4155
4207
  if (fname === "ta.macd_signal" || fname === "macd_signal") {
4156
4208
  const msg = "ta.macd_signal() requires 2 arguments: macd_line, signal_line";
@@ -4170,6 +4222,7 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
4170
4222
  if (fname === "ta.stoch_smooth" || fname === "stoch_smooth") {
4171
4223
  const msg = "ta.stoch_smooth() requires 6 arguments: high, low, close, period, smooth_k, smooth_d";
4172
4224
  return env.ta.stochSmooth(
4225
+ site,
4173
4226
  unwrap(evalExpr(wave10aArg(0, ["high"], msg), env)),
4174
4227
  unwrap(evalExpr(wave10aArg(1, ["low"], msg), env)),
4175
4228
  unwrap(evalExpr(wave10aArg(2, ["close"], msg), env)),
@@ -4213,7 +4266,11 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
4213
4266
  }
4214
4267
  if (fname === "ta.rsi_divergence" || fname === "rsi_divergence") {
4215
4268
  const period = unwrap(evalExpr(callArg(node.args, 1, ["period", "length"]), env));
4216
- return env.ta.rsiDivergence(wave10bList(0, ["rsi_series", "series", "source"]), period ?? 0);
4269
+ return env.ta.rsiDivergence(
4270
+ site,
4271
+ seriesLastCell(evalExpr(callArg(node.args, 0, ["rsi_series", "series", "source"]), env)),
4272
+ period ?? 0,
4273
+ );
4217
4274
  }
4218
4275
  if (fname === "ta.cumulative_delta" || fname === "cumulative_delta") {
4219
4276
  const period = unwrap(evalExpr(callArg(node.args, 2, ["period", "length"]), env));
@@ -4260,6 +4317,549 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
4260
4317
  );
4261
4318
  }
4262
4319
  // wave10b-end
4320
+ // wave11-start — advanced / statistical ta.* surface
4321
+ const wave11 = (index: number, names: string[], message: string): Value =>
4322
+ unwrap(evalExpr(wave10aArg(index, names, message), env));
4323
+ const wave11n = (index: number, names: string[], message: string): Cell =>
4324
+ num(wave11(index, names, message));
4325
+ const wave13v = (index: number, names: string[], message: string): Value =>
4326
+ evalExpr(wave10aArg(index, names, message), env);
4327
+ if (fname === "ta.sma_weighted" || fname === "sma_weighted") {
4328
+ const msg = "ta.sma_weighted() requires at least 2 arguments: series, period";
4329
+ const weightArg = callArg(node.args, 2, ["weight_type", "weight"]);
4330
+ return env.ta.smaWeighted(
4331
+ site,
4332
+ wave11n(0, ["series", "source"], msg),
4333
+ wave11n(1, ["period", "length"], msg),
4334
+ weightArg == null ? "linear" : evalExpr(weightArg, env),
4335
+ );
4336
+ }
4337
+ if (fname === "ta.apo" || fname === "apo") {
4338
+ const msg = "ta.apo() requires 3 arguments: series, fast_period, slow_period";
4339
+ return env.ta.apo(
4340
+ site,
4341
+ wave11n(0, ["series", "source"], msg),
4342
+ wave11n(1, ["fast_period", "fast"], msg),
4343
+ wave11n(2, ["slow_period", "slow"], msg),
4344
+ );
4345
+ }
4346
+ if (fname === "ta.atr_normalized" || fname === "atr_normalized") {
4347
+ const msg = "ta.atr_normalized() requires 4 arguments: high, low, close, period";
4348
+ return env.ta.atrNormalized(
4349
+ site,
4350
+ wave11n(0, ["high"], msg),
4351
+ wave11n(1, ["low"], msg),
4352
+ wave11n(2, ["close"], msg),
4353
+ wave11n(3, ["period", "length"], msg),
4354
+ );
4355
+ }
4356
+ if (fname === "ta.beta" || fname === "beta") {
4357
+ const msg = "ta.beta() requires 3 arguments: series1, series2, length";
4358
+ return env.ta.beta(
4359
+ site,
4360
+ wave11n(0, ["series1", "series", "source"], msg),
4361
+ wave11n(1, ["series2", "series_b", "other"], msg),
4362
+ wave11n(2, ["length", "period"], msg),
4363
+ );
4364
+ }
4365
+ if (fname === "ta.r_squared" || fname === "r_squared") {
4366
+ const msg = "ta.r_squared() requires 3 arguments: series1, series2, length";
4367
+ return env.ta.rSquared(
4368
+ site,
4369
+ wave11n(0, ["series1", "series", "source"], msg),
4370
+ wave11n(1, ["series2", "series_b", "other"], msg),
4371
+ wave11n(2, ["length", "period"], msg),
4372
+ );
4373
+ }
4374
+ if (fname === "ta.comovement" || fname === "comovement") {
4375
+ const msg = "ta.comovement() requires 3 arguments: series1, series2, length";
4376
+ return env.ta.comovement(
4377
+ site,
4378
+ wave11n(0, ["series1", "series", "source"], msg),
4379
+ wave11n(1, ["series2", "series_b", "other"], msg),
4380
+ wave11n(2, ["length", "period"], msg),
4381
+ );
4382
+ }
4383
+ if (fname === "ta.skewness" || fname === "skewness") {
4384
+ const msg = "ta.skewness() requires 2 arguments: series, period";
4385
+ return env.ta.skewness(
4386
+ site,
4387
+ wave11n(0, ["series", "source"], msg),
4388
+ wave11n(1, ["period", "length"], msg),
4389
+ );
4390
+ }
4391
+ if (fname === "ta.kurtosis" || fname === "kurtosis") {
4392
+ const msg = "ta.kurtosis() requires 2 arguments: series, period";
4393
+ return env.ta.kurtosis(
4394
+ site,
4395
+ wave11n(0, ["series", "source"], msg),
4396
+ wave11n(1, ["period", "length"], msg),
4397
+ );
4398
+ }
4399
+ if (fname === "ta.volume_weighted_momentum" || fname === "volume_weighted_momentum") {
4400
+ const msg = "ta.volume_weighted_momentum() requires 3 arguments: series, volume, length";
4401
+ return env.ta.volumeWeightedMomentum(
4402
+ site,
4403
+ wave11n(0, ["series", "source"], msg),
4404
+ wave11n(1, ["volume"], msg),
4405
+ wave11n(2, ["length", "period"], msg),
4406
+ );
4407
+ }
4408
+ if (fname === "ta.parkinson" || fname === "parkinson") {
4409
+ const msg = "ta.parkinson() requires 2 arguments: high, low";
4410
+ const highArg = wave10aArg(0, ["high"], msg);
4411
+ const lowArg = wave10aArg(1, ["low"], msg);
4412
+ if (isSeriesExpr(highArg, env) || isSeriesExpr(lowArg, env)) return NA;
4413
+ return env.ta.parkinson(num(evalExpr(highArg, env)), num(evalExpr(lowArg, env)));
4414
+ }
4415
+ if (fname === "ta.garman_klass" || fname === "garman_klass") {
4416
+ const msg = "ta.garman_klass() requires 4 arguments: high, low, close, open";
4417
+ const argsExpr = [
4418
+ wave10aArg(0, ["high"], msg),
4419
+ wave10aArg(1, ["low"], msg),
4420
+ wave10aArg(2, ["close"], msg),
4421
+ wave10aArg(3, ["open"], msg),
4422
+ ];
4423
+ if (argsExpr.some((a) => isSeriesExpr(a, env))) return NA;
4424
+ return env.ta.garmanKlass(
4425
+ num(evalExpr(argsExpr[0]!, env)),
4426
+ num(evalExpr(argsExpr[1]!, env)),
4427
+ num(evalExpr(argsExpr[2]!, env)),
4428
+ num(evalExpr(argsExpr[3]!, env)),
4429
+ );
4430
+ }
4431
+ if (fname === "ta.mean_reversion_score" || fname === "mean_reversion_score") {
4432
+ const msg = "ta.mean_reversion_score() requires 4 arguments";
4433
+ return env.ta.meanReversionScore(
4434
+ site,
4435
+ wave11n(0, ["close", "source"], msg),
4436
+ wave11n(1, ["sma"], msg),
4437
+ wave11n(2, ["stdev", "std_dev"], msg),
4438
+ wave11n(3, ["period", "length"], msg),
4439
+ );
4440
+ }
4441
+ if (fname === "ta.market_condition" || fname === "market_condition") {
4442
+ const msg = "ta.market_condition() requires 4 arguments";
4443
+ return env.ta.marketCondition(
4444
+ site,
4445
+ wave11n(0, ["close", "source"], msg),
4446
+ wave11n(1, ["atr"], msg),
4447
+ wave11n(2, ["sma_period", "sma_length"], msg),
4448
+ wave11n(3, ["stdev_period", "stdev_length"], msg),
4449
+ );
4450
+ }
4451
+ if (fname === "ta.order_flow_imbalance" || fname === "order_flow_imbalance") {
4452
+ const msg = "ta.order_flow_imbalance() requires 5 arguments";
4453
+ return env.ta.orderFlowImbalance(
4454
+ site,
4455
+ wave11n(0, ["high"], msg),
4456
+ wave11n(1, ["low"], msg),
4457
+ wave11n(2, ["close"], msg),
4458
+ wave11n(3, ["volume"], msg),
4459
+ wave11n(4, ["period", "length"], msg),
4460
+ );
4461
+ }
4462
+ if (fname === "ta.liquidity_score" || fname === "liquidity_score") {
4463
+ const msg = "ta.liquidity_score() requires 4 arguments";
4464
+ return env.ta.liquidityScore(
4465
+ site,
4466
+ wave11n(0, ["volume"], msg),
4467
+ wave11n(1, ["volatility"], msg),
4468
+ wave11n(2, ["spread"], msg),
4469
+ wave11n(3, ["period", "length"], msg),
4470
+ );
4471
+ }
4472
+ if (fname === "ta.fear_greed_index" || fname === "fear_greed_index") {
4473
+ const msg = "ta.fear_greed_index() requires 4 arguments";
4474
+ return env.ta.fearGreedIndex(
4475
+ wave11n(0, ["rsi"], msg),
4476
+ wave11n(1, ["vix"], msg),
4477
+ wave11n(2, ["put_call", "put_call_ratio"], msg),
4478
+ wave11n(3, ["breadth"], msg),
4479
+ );
4480
+ }
4481
+ if (fname === "ta.crowd_sentiment" || fname === "crowd_sentiment") {
4482
+ const msg = "ta.crowd_sentiment() requires 3 arguments";
4483
+ return env.ta.crowdSentiment(
4484
+ wave11n(0, ["price_agreement", "price"], msg),
4485
+ wave11n(1, ["volume_agreement", "volume"], msg),
4486
+ wave11n(2, ["time_agreement", "time"], msg),
4487
+ );
4488
+ }
4489
+ if (fname === "ta.smart_money_flow" || fname === "smart_money_flow") {
4490
+ const msg = "ta.smart_money_flow() requires 4 arguments";
4491
+ return env.ta.smartMoneyFlow(
4492
+ wave11n(0, ["price_change"], msg),
4493
+ wave11n(1, ["volume"], msg),
4494
+ wave11n(2, ["time_since_high", "time_high"], msg),
4495
+ wave11n(3, ["time_since_low", "time_low"], msg),
4496
+ );
4497
+ }
4498
+ if (fname === "ta.trend_confirmation_score" || fname === "trend_confirmation_score") {
4499
+ const msg = "ta.trend_confirmation_score() requires 6 arguments";
4500
+ wave11n(5, ["support_distance"], msg); // required, unused (Python parity)
4501
+ return env.ta.trendConfirmationScore(
4502
+ wave11n(0, ["momentum"], msg),
4503
+ wave11n(1, ["trend_alignment"], msg),
4504
+ wave11n(2, ["strength"], msg),
4505
+ wave11n(3, ["rsi"], msg),
4506
+ wave11n(4, ["rsi_alignment"], msg),
4507
+ );
4508
+ }
4509
+ if (fname === "ta.max_loss_level" || fname === "max_loss_level") {
4510
+ const msg = "ta.max_loss_level() requires 3 arguments";
4511
+ return env.ta.maxLossLevel(
4512
+ wave11n(0, ["entry", "entry_price"], msg),
4513
+ wave11n(1, ["account", "account_size"], msg),
4514
+ wave11n(2, ["max_loss_pct", "max_loss_percent"], msg),
4515
+ );
4516
+ }
4517
+ if (fname === "ta.profit_lock_level" || fname === "profit_lock_level") {
4518
+ const msg = "ta.profit_lock_level() requires 4 arguments";
4519
+ return env.ta.profitLockLevel(
4520
+ wave11n(0, ["entry", "entry_price"], msg),
4521
+ wave11n(1, ["current", "current_price"], msg),
4522
+ wave11n(2, ["trail_pct", "trail_percent"], msg),
4523
+ wave11n(3, ["direction"], msg),
4524
+ );
4525
+ }
4526
+ // wave11-end
4527
+ // wave12-start — risk / sizing / timing analytics
4528
+ const wave12s = (index: number, names: string[], message: string): Value =>
4529
+ evalExpr(wave10aArg(index, names, message), env);
4530
+ if (fname === "ta.position_sizing" || fname === "position_sizing") {
4531
+ const msg = "ta.position_sizing() requires 4 arguments";
4532
+ return env.ta.positionSizing(
4533
+ wave11n(0, ["account", "account_size"], msg),
4534
+ wave11n(1, ["risk_pct", "risk_percent"], msg),
4535
+ wave11n(2, ["entry", "entry_price"], msg),
4536
+ wave11n(3, ["stop", "stop_price"], msg),
4537
+ );
4538
+ }
4539
+ if (fname === "ta.kelly_criterion" || fname === "kelly_criterion") {
4540
+ const msg = "ta.kelly_criterion() requires 3 arguments";
4541
+ return env.ta.kellyCriterion(
4542
+ wave11n(0, ["win_rate"], msg),
4543
+ wave11n(1, ["avg_win"], msg),
4544
+ wave11n(2, ["avg_loss"], msg),
4545
+ );
4546
+ }
4547
+ if (fname === "ta.risk_reward_ratio" || fname === "risk_reward_ratio") {
4548
+ const msg = "ta.risk_reward_ratio() requires 3 arguments";
4549
+ return env.ta.riskRewardRatio(
4550
+ wave11n(0, ["entry", "entry_price"], msg),
4551
+ wave11n(1, ["stop", "stop_price"], msg),
4552
+ wave11n(2, ["target", "target_price"], msg),
4553
+ );
4554
+ }
4555
+ if (fname === "ta.risk_reward_asymmetry" || fname === "risk_reward_asymmetry") {
4556
+ const msg = "ta.risk_reward_asymmetry() requires 4 arguments";
4557
+ return env.ta.riskRewardAsymmetry(
4558
+ wave11n(0, ["entry", "entry_price"], msg),
4559
+ wave11n(1, ["stop", "stop_price"], msg),
4560
+ wave11n(2, ["target", "target_price"], msg),
4561
+ wave11n(3, ["win_rate"], msg),
4562
+ );
4563
+ }
4564
+ if (fname === "ta.probability_of_movement" || fname === "probability_of_movement") {
4565
+ const msg = "ta.probability_of_movement() requires 4 arguments";
4566
+ return env.ta.probabilityOfMovement(
4567
+ wave11n(0, ["current", "current_price"], msg),
4568
+ wave11n(1, ["target", "target_price"], msg),
4569
+ wave11n(2, ["atr"], msg),
4570
+ wave11n(3, ["period", "length"], msg),
4571
+ );
4572
+ }
4573
+ if (fname === "ta.trend_strength" || fname === "trend_strength") {
4574
+ const msg = "ta.trend_strength() requires 3 arguments";
4575
+ return env.ta.trendStrength(
4576
+ wave11n(0, ["price", "source"], msg),
4577
+ wave11n(1, ["adx"], msg),
4578
+ wave11n(2, ["rsi"], msg),
4579
+ );
4580
+ }
4581
+ if (fname === "ta.breakeven_level" || fname === "breakeven_level") {
4582
+ const msg = "ta.breakeven_level() requires 5 arguments";
4583
+ return env.ta.breakevenLevel(
4584
+ wave11n(0, ["entry", "entry_price"], msg),
4585
+ wave11n(1, ["quantity", "qty"], msg),
4586
+ wave11n(2, ["commission"], msg),
4587
+ wave11n(3, ["slippage"], msg),
4588
+ wave11n(4, ["direction"], msg),
4589
+ );
4590
+ }
4591
+ if (fname === "ta.market_structure_pivot" || fname === "market_structure_pivot") {
4592
+ const msg = "ta.market_structure_pivot() requires 5 arguments";
4593
+ const high = wave13v(0, ["high_list", "high"], msg);
4594
+ const low = wave13v(1, ["low_list", "low"], msg);
4595
+ wave13v(2, ["close_list", "close"], msg);
4596
+ const period = wave11n(3, ["period", "left", "left_bars"], msg);
4597
+ const mode = wave11n(4, ["mode", "right", "right_bars"], msg);
4598
+ return env.ta.marketStructurePivot(site, high, low, period, mode);
4599
+ }
4600
+ if (fname === "ta.position_sizing_score" || fname === "position_sizing_score") {
4601
+ const msg = "ta.position_sizing_score() requires 4 arguments";
4602
+ return env.ta.positionSizingScore(
4603
+ wave11n(0, ["account_risk", "risk"], msg),
4604
+ wave11n(1, ["volatility"], msg),
4605
+ wave11n(2, ["conviction"], msg),
4606
+ wave11n(3, ["correlation"], msg),
4607
+ );
4608
+ }
4609
+ if (fname === "ta.trailing_exit_level" || fname === "trailing_exit_level") {
4610
+ const msg = "ta.trailing_exit_level() requires 5 arguments";
4611
+ return env.ta.trailingExitLevel(
4612
+ wave11n(0, ["entry", "entry_price"], msg),
4613
+ wave11n(1, ["current", "current_price"], msg),
4614
+ wave11n(2, ["atr"], msg),
4615
+ wave11n(3, ["multiplier", "mult"], msg),
4616
+ wave11n(4, ["step"], msg),
4617
+ );
4618
+ }
4619
+ if (fname === "ta.optimal_entry_zone" || fname === "optimal_entry_zone") {
4620
+ const msg = "ta.optimal_entry_zone() requires 4 arguments";
4621
+ wave11n(0, ["high"], msg);
4622
+ wave11n(1, ["low"], msg);
4623
+ const close = wave13v(2, ["close"], msg);
4624
+ wave11n(3, ["period", "length"], msg);
4625
+ return env.ta.optimalEntryZone(site, close);
4626
+ }
4627
+ if (fname === "ta.mean_reversion_entry" || fname === "mean_reversion_entry") {
4628
+ const msg = "ta.mean_reversion_entry() requires 5 arguments";
4629
+ return env.ta.meanReversionEntry(
4630
+ site,
4631
+ wave13v(0, ["price", "source"], msg),
4632
+ wave13v(1, ["sma"], msg),
4633
+ wave13v(2, ["stdev", "std_dev"], msg),
4634
+ wave11n(4, ["threshold"], msg),
4635
+ );
4636
+ }
4637
+ if (fname === "ta.multi_timeframe_signal" || fname === "multi_timeframe_signal") {
4638
+ const msg = "ta.multi_timeframe_signal() requires 6 arguments";
4639
+ return env.ta.multiTimeframeSignal(
4640
+ wave11n(0, ["s1", "signal1"], msg),
4641
+ wave11n(1, ["s2", "signal2"], msg),
4642
+ wave11n(2, ["s3", "signal3"], msg),
4643
+ wave11n(3, ["w1", "weight1"], msg),
4644
+ wave11n(4, ["w2", "weight2"], msg),
4645
+ wave11n(5, ["w3", "weight3"], msg),
4646
+ );
4647
+ }
4648
+ if (fname === "ta.market_timing_index" || fname === "market_timing_index") {
4649
+ const msg = "ta.market_timing_index() requires 4 arguments";
4650
+ return env.ta.marketTimingIndex(
4651
+ wave11n(0, ["trend"], msg),
4652
+ wave11n(1, ["momentum"], msg),
4653
+ wave11n(2, ["volatility"], msg),
4654
+ wave11n(3, ["sentiment"], msg),
4655
+ );
4656
+ }
4657
+ if (fname === "ta.regime_adaptive_signal" || fname === "regime_adaptive_signal") {
4658
+ const msg = "ta.regime_adaptive_signal() requires 4 arguments";
4659
+ const strength = wave11n(0, ["signal_strength", "strength"], msg);
4660
+ const volRegime = wave12s(1, ["volatility_regime", "vol_regime"], msg);
4661
+ const trendRegime = wave12s(2, ["trend_regime"], msg);
4662
+ wave11n(3, ["period", "length"], msg);
4663
+ return env.ta.regimeAdaptiveSignal(strength, volRegime, trendRegime);
4664
+ }
4665
+ if (fname === "ta.strategy_score" || fname === "strategy_score") {
4666
+ const msg = "ta.strategy_score() requires 4 arguments";
4667
+ return env.ta.strategyScore(
4668
+ wave11n(0, ["rsi"], msg),
4669
+ wave11n(1, ["macd"], msg),
4670
+ wave11n(2, ["ema_cross"], msg),
4671
+ wave11n(3, ["trend"], msg),
4672
+ );
4673
+ }
4674
+ if (fname === "ta.breakout_detection" || fname === "breakout_detection") {
4675
+ const msg = "ta.breakout_detection() requires 3 arguments";
4676
+ return env.ta.breakoutDetection(
4677
+ wave11n(0, ["close", "source"], msg),
4678
+ wave11n(1, ["resistance"], msg),
4679
+ wave11n(2, ["support"], msg),
4680
+ );
4681
+ }
4682
+ // wave12-end
4683
+ // wave13-start — history / sentiment / macro analytics
4684
+ if (fname === "ta.ema_cross_signal" || fname === "ema_cross_signal") {
4685
+ const msg = "ta.ema_cross_signal() requires 3 arguments";
4686
+ return env.ta.emaCrossSignal(
4687
+ site,
4688
+ wave11n(0, ["fast_ema", "fast"], msg),
4689
+ wave11n(1, ["slow_ema", "slow"], msg),
4690
+ wave11n(2, ["threshold"], msg),
4691
+ );
4692
+ }
4693
+ if (fname === "ta.momentum_filter" || fname === "momentum_filter") {
4694
+ const msg = "ta.momentum_filter() requires 3 arguments";
4695
+ return env.ta.momentumFilter(
4696
+ site,
4697
+ wave11n(0, ["momentum_raw", "momentum"], msg),
4698
+ wave11n(1, ["volume"], msg),
4699
+ wave11n(2, ["period", "length"], msg),
4700
+ );
4701
+ }
4702
+ if (fname === "ta.volatility_regime" || fname === "volatility_regime") {
4703
+ const msg = "ta.volatility_regime() requires 2 arguments";
4704
+ return env.ta.volatilityRegime(
4705
+ site,
4706
+ wave11n(0, ["atr_series", "atr", "source"], msg),
4707
+ wave11n(1, ["period", "length"], msg),
4708
+ );
4709
+ }
4710
+ if (fname === "ta.economic_impact_score" || fname === "economic_impact_score") {
4711
+ const msg = "ta.economic_impact_score() requires 3 arguments";
4712
+ return env.ta.economicImpactScore(
4713
+ wave11n(0, ["price_change"], msg),
4714
+ wave11n(1, ["volatility"], msg),
4715
+ wave11n(2, ["volume_change"], msg),
4716
+ );
4717
+ }
4718
+ if (fname === "ta.contrarian_signal" || fname === "contrarian_signal") {
4719
+ const msg = "ta.contrarian_signal() requires 3 arguments";
4720
+ return env.ta.contrarianSignal(
4721
+ wave11n(0, ["sentiment"], msg),
4722
+ wave11n(1, ["volatility"], msg),
4723
+ wave11n(2, ["time_since_extreme", "time_extreme"], msg),
4724
+ );
4725
+ }
4726
+ if (fname === "ta.drawdown_recovery_level" || fname === "drawdown_recovery_level") {
4727
+ const msg = "ta.drawdown_recovery_level() requires 4 arguments";
4728
+ return env.ta.drawdownRecoveryLevel(
4729
+ wave11n(0, ["peak"], msg),
4730
+ wave11n(1, ["current", "current_price"], msg),
4731
+ wave11n(2, ["recovery_factor", "factor"], msg),
4732
+ wave11n(3, ["period", "length"], msg),
4733
+ );
4734
+ }
4735
+ if (fname === "ta.pivot_point_levels" || fname === "pivot_point_levels") {
4736
+ const msg = "ta.pivot_point_levels() requires type+anchor or high, low, close";
4737
+ const first = wave11(0, ["type", "high"], msg);
4738
+ if (typeof first === "string") {
4739
+ // Reference Pine type+anchor form → context pivot-point tuple.
4740
+ if (typeof env.ta.pivotPoints !== "function") return NA;
4741
+ const r = (env.ta.pivotPoints as TaEngine["pivotPoints"]).call(
4742
+ env.ta,
4743
+ site,
4744
+ num(env.ctx.high),
4745
+ num(env.ctx.low),
4746
+ num(env.ctx.close),
4747
+ );
4748
+ return tupleOf([r.pp, r.r1, r.s1, r.r2, r.s2]);
4749
+ }
4750
+ const tradArg = callArg(node.args, 3, ["traditional"]);
4751
+ const traditional = tradArg == null ? true : unwrap(evalExpr(tradArg, env)) !== 0;
4752
+ return env.ta.pivotPointLevels(
4753
+ num(first),
4754
+ wave11n(1, ["low"], msg),
4755
+ wave11n(2, ["close"], msg),
4756
+ traditional,
4757
+ );
4758
+ }
4759
+ if (fname === "ta.volatility_regime_score" || fname === "volatility_regime_score") {
4760
+ const msg = "ta.volatility_regime_score() requires 4 arguments";
4761
+ return env.ta.volatilityRegimeScore(
4762
+ site,
4763
+ wave13v(0, ["atr_list", "atr"], msg),
4764
+ wave13v(1, ["volatility_list", "volatility"], msg),
4765
+ wave13v(2, ["vix_list", "vix"], msg),
4766
+ wave11n(3, ["threshold"], msg),
4767
+ );
4768
+ }
4769
+ if (fname === "ta.correlation_filter" || fname === "correlation_filter") {
4770
+ const msg = "ta.correlation_filter() requires 5 arguments";
4771
+ const s1 = wave13v(0, ["signal1_list", "signal1", "s1"], msg);
4772
+ const s2 = wave13v(1, ["signal2_list", "signal2", "s2"], msg);
4773
+ const s3 = wave13v(2, ["signal3_list", "signal3", "s3"], msg);
4774
+ wave11n(3, ["num_signals"], msg);
4775
+ return env.ta.correlationFilter(site, s1, s2, s3, wave11n(4, ["threshold"], msg));
4776
+ }
4777
+ if (fname === "ta.advanced_breakout_detector" || fname === "advanced_breakout_detector") {
4778
+ const msg = "ta.advanced_breakout_detector() requires 5 arguments";
4779
+ return env.ta.advancedBreakoutDetector(
4780
+ site,
4781
+ wave13v(0, ["price_list", "price"], msg),
4782
+ wave13v(1, ["volume_list", "volume"], msg),
4783
+ wave11n(2, ["level"], msg),
4784
+ wave11n(3, ["lookback"], msg),
4785
+ wave11n(4, ["volume_multiplier", "vol_mult"], msg),
4786
+ );
4787
+ }
4788
+ if (fname === "ta.pullback_bounce_level" || fname === "pullback_bounce_level") {
4789
+ const msg = "ta.pullback_bounce_level() requires 5 arguments";
4790
+ const high = wave13v(0, ["high_list", "high"], msg);
4791
+ const low = wave13v(1, ["low_list", "low"], msg);
4792
+ wave13v(2, ["close_list", "close"], msg);
4793
+ return env.ta.pullbackBounceLevel(
4794
+ site,
4795
+ high,
4796
+ low,
4797
+ wave11n(3, ["trend_direction", "trend_dir"], msg),
4798
+ wave11n(4, ["lookback"], msg),
4799
+ );
4800
+ }
4801
+ if (fname === "ta.divergence_detector" || fname === "divergence_detector") {
4802
+ const msg = "ta.divergence_detector() requires 3 arguments";
4803
+ return env.ta.divergenceDetector(
4804
+ site,
4805
+ wave13v(0, ["price_list", "price"], msg),
4806
+ wave13v(1, ["indicator_list", "indicator"], msg),
4807
+ wave11n(2, ["lookback"], msg),
4808
+ );
4809
+ }
4810
+ if (fname === "ta.momentum_divergence" || fname === "momentum_divergence") {
4811
+ const msg = "ta.momentum_divergence() requires 3 arguments";
4812
+ return env.ta.momentumDivergence(
4813
+ site,
4814
+ wave13v(0, ["price"], msg),
4815
+ wave13v(1, ["momentum_fast", "mom_fast"], msg),
4816
+ wave13v(2, ["momentum_slow", "mom_slow"], msg),
4817
+ );
4818
+ }
4819
+ if (fname === "ta.spread_analysis" || fname === "spread_analysis") {
4820
+ const msg = "ta.spread_analysis() requires 3 arguments";
4821
+ return env.ta.spreadAnalysis(
4822
+ site,
4823
+ wave13v(0, ["bid"], msg),
4824
+ wave13v(1, ["ask"], msg),
4825
+ wave11n(2, ["period", "length"], msg),
4826
+ );
4827
+ }
4828
+ if (fname === "ta.employment_cycle_indicator" || fname === "employment_cycle_indicator") {
4829
+ const msg = "ta.employment_cycle_indicator() requires 3 arguments";
4830
+ return env.ta.employmentCycleIndicator(
4831
+ site,
4832
+ wave13v(0, ["cyclical_stocks", "cyclical"], msg),
4833
+ wave13v(1, ["defensive_stocks", "defensive"], msg),
4834
+ wave13v(2, ["unemployment_proxy", "unemployment"], msg),
4835
+ );
4836
+ }
4837
+ if (fname === "ta.gdp_growth_proxy" || fname === "gdp_growth_proxy") {
4838
+ const msg = "ta.gdp_growth_proxy() requires 3 arguments";
4839
+ return env.ta.gdpGrowthProxy(
4840
+ site,
4841
+ wave13v(0, ["market_breadth", "breadth"], msg),
4842
+ wave13v(1, ["market_volume", "volume"], msg),
4843
+ wave13v(2, ["price_momentum", "momentum"], msg),
4844
+ );
4845
+ }
4846
+ if (fname === "ta.inflation_proxy_indicator" || fname === "inflation_proxy_indicator") {
4847
+ const msg = "ta.inflation_proxy_indicator() requires 3 arguments";
4848
+ return env.ta.inflationProxyIndicator(
4849
+ site,
4850
+ wave13v(0, ["usd_index", "usd"], msg),
4851
+ wave13v(1, ["commodity_prices", "commodities"], msg),
4852
+ wave13v(2, ["bond_yields", "yields"], msg),
4853
+ );
4854
+ }
4855
+ if (fname === "ta.expected_value" || fname === "expected_value") {
4856
+ const msg = "ta.expected_value() requires 2 arguments: returns, probabilities";
4857
+ const returns = numericList(evalExpr(wave10aArg(0, ["returns"], msg), env));
4858
+ const probs = numericList(evalExpr(wave10aArg(1, ["probabilities", "probs"], msg), env));
4859
+ if (returns.length !== probs.length) runtimeError("Returns and probabilities must have same length");
4860
+ return env.ta.expectedValue(returns, probs);
4861
+ }
4862
+ // wave13-end
4263
4863
  return undefined;
4264
4864
  }
4265
4865
 
@@ -4271,6 +4871,31 @@ function hlc3(env: Env): Cell {
4271
4871
  return (h + l + c) / 3;
4272
4872
  }
4273
4873
 
4874
+ /** Python `_series_last`: array → last cell; scalar → the value itself. */
4875
+ function seriesLastCell(value: Value): number | null {
4876
+ if (value instanceof PineArray) {
4877
+ const n = value.size();
4878
+ return n > 0 ? value.get(n - 1) : null;
4879
+ }
4880
+ const v = unwrap(value);
4881
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
4882
+ }
4883
+
4884
+ /** Python `_expect_list` for array-valued args: numeric cells only. */
4885
+ function numericList(value: Value): number[] {
4886
+ const arr = asArray(value);
4887
+ if (arr == null) {
4888
+ const v = unwrap(value);
4889
+ return typeof v === "number" && Number.isFinite(v) ? [v] : [];
4890
+ }
4891
+ const out: number[] = [];
4892
+ for (let i = 0; i < arr.size(); i++) {
4893
+ const v = arr.get(i);
4894
+ if (typeof v === "number" && Number.isFinite(v)) out.push(v);
4895
+ }
4896
+ return out;
4897
+ }
4898
+
4274
4899
  function asArray(value: Value): PineArray | null {
4275
4900
  if (value instanceof PineArray) return value;
4276
4901
  if (value && typeof value === "object" && "__array" in value) {
@@ -5149,11 +5774,24 @@ function evalDrawingCall(fname: string | null, node: Call, env: Env): Value | un
5149
5774
  return NA;
5150
5775
  }
5151
5776
  if (fname === "table.new") {
5777
+ // Pine positional order is (position, columns, rows, bgcolor,
5778
+ // frame_color, frame_width, border_color, border_width, force_overlay).
5779
+ // Python `_handle_table_new` SoT (columns then rows, not rows then columns).
5152
5780
  const extra: Record<string, unknown> = {};
5153
5781
  const position = drawRaw(node, env, 0, ["position"]);
5154
5782
  if (position !== undefined) extra.position = position;
5155
- putFinite(extra, "rows", drawNum(node, env, 1, ["rows"]));
5156
- putFinite(extra, "columns", drawNum(node, env, 2, ["columns"]));
5783
+ putFinite(extra, "columns", drawNum(node, env, 1, ["columns"]));
5784
+ putFinite(extra, "rows", drawNum(node, env, 2, ["rows"]));
5785
+ const bgcolor = drawRaw(node, env, 3, ["bgcolor"]);
5786
+ if (bgcolor !== undefined) extra.bgcolor = bgcolor;
5787
+ const frameColor = drawRaw(node, env, 4, ["frame_color"]);
5788
+ if (frameColor !== undefined) extra.frame_color = frameColor;
5789
+ putFinite(extra, "frame_width", drawNum(node, env, 5, ["frame_width"]));
5790
+ const borderColor = drawRaw(node, env, 6, ["border_color"]);
5791
+ if (borderColor !== undefined) extra.border_color = borderColor;
5792
+ putFinite(extra, "border_width", drawNum(node, env, 7, ["border_width"]));
5793
+ const forceOverlay = drawRaw(node, env, 8, ["force_overlay"]);
5794
+ if (forceOverlay !== undefined) extra.force_overlay = forceOverlay;
5157
5795
  return book.tableNew(env.barIndex, extra);
5158
5796
  }
5159
5797
  if (fname === "polyline.new") {
@@ -6194,9 +6832,10 @@ function syminfoPrefix(node: Call, env: Env): string {
6194
6832
  }
6195
6833
 
6196
6834
  function calendarMs(node: Call, env: Env): number | null {
6835
+ // Python `_timestamp_to_ms`: chart hosts store opens in seconds.
6197
6836
  const arg = callArg(node.args, 0, ["time", "timestamp"]);
6198
- if (arg == null) return num(env.ctx.time);
6199
- return unwrap(evalExpr(arg, env));
6837
+ if (arg == null) return timestampToMs(num(env.ctx.time));
6838
+ return timestampToMs(unwrap(evalExpr(arg, env)));
6200
6839
  }
6201
6840
 
6202
6841
  function evalLibraryMemberCall(mod: LibraryModule, attr: string, node: Call, env: Env): Value {