@hoox-sh/pynets 0.7.0 → 0.9.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/dist/index.js CHANGED
@@ -44189,6 +44189,150 @@ class TaEngine {
44189
44189
  ev += returns[i] * probabilities[i];
44190
44190
  return finiteOut2(ev / totalProb) ?? 0;
44191
44191
  }
44192
+ gammaLevels(volatility, currentPrice, period) {
44193
+ const v = finiteCell2(volatility) ?? 0.02;
44194
+ const p = finiteCell2(currentPrice) ?? 100;
44195
+ const n = this.coerceTaInt(period, "period must be integer");
44196
+ const volAdjusted = v * Math.sqrt(n);
44197
+ const distance = p * volAdjusted;
44198
+ const out = new PineArray;
44199
+ out.push(finiteOut2(p + distance));
44200
+ out.push(finiteOut2(p - distance));
44201
+ return out;
44202
+ }
44203
+ intelligentStrategySynthesizer(site, trend, momentum, volatility, volume, marketCondition, riskProfile) {
44204
+ const th = this.windowFor(`${site}:trend`, trend);
44205
+ const mh = this.windowFor(`${site}:momentum`, momentum);
44206
+ const vh = this.windowFor(`${site}:volatility`, volatility);
44207
+ const volh = this.windowFor(`${site}:volume`, volume);
44208
+ const regime = typeof marketCondition === "string" ? marketCondition : "ranging";
44209
+ const profile = typeof riskProfile === "string" ? riskProfile : "balanced";
44210
+ const nums = (h) => h.filter((v) => v !== null);
44211
+ const trendVals = nums(th);
44212
+ const momentumVals = nums(mh);
44213
+ const volatilityVals = nums(vh);
44214
+ const volumeVals = nums(volh);
44215
+ const avg = (vals, fallback) => vals.length === 0 ? fallback : vals.reduce((a, b) => a + b, 0) / vals.length;
44216
+ const trendAvg = avg(trendVals, 0);
44217
+ const momentumAvg = avg(momentumVals, 0);
44218
+ const volatilityAvg = avg(volatilityVals, 0.5);
44219
+ const volumeAvg = avg(volumeVals, 0);
44220
+ const volatilityNormalized = Math.max(0, Math.min(1, volatilityAvg));
44221
+ const composite = trendAvg * 0.4 + momentumAvg * 0.35 + volumeAvg * 0.25;
44222
+ const compositeSignal = Math.max(-1, Math.min(1, composite));
44223
+ let agreement = 0;
44224
+ if (Math.abs(trendAvg) > 0.3)
44225
+ agreement += 1;
44226
+ if (Math.abs(momentumAvg) > 0.3)
44227
+ agreement += 1;
44228
+ if (Math.abs(volumeAvg) > 0.3)
44229
+ agreement += 1;
44230
+ const baseConfidence = agreement / 3;
44231
+ const confidence = Math.max(0.1, Math.min(0.99, baseConfidence - volatilityNormalized * 0.3));
44232
+ let recommendation;
44233
+ if (compositeSignal > 0.6) {
44234
+ recommendation = profile === "aggressive" ? "aggressive_long" : "conservative_long";
44235
+ } else if (compositeSignal > 0.2) {
44236
+ recommendation = "conservative_long";
44237
+ } else if (compositeSignal < -0.6) {
44238
+ recommendation = profile === "aggressive" ? "aggressive_short" : "conservative_short";
44239
+ } else if (compositeSignal < -0.2) {
44240
+ recommendation = "conservative_short";
44241
+ } else {
44242
+ recommendation = "hold";
44243
+ }
44244
+ const riskMult = profile === "conservative" ? 0.5 : profile === "aggressive" ? 1.5 : 1;
44245
+ const riskLevel = volatilityNormalized * 50 * riskMult;
44246
+ const absSignal = Math.abs(compositeSignal);
44247
+ const expectedReturn = absSignal * 3;
44248
+ let holdingPeriod;
44249
+ if (volatilityNormalized > 0.7)
44250
+ holdingPeriod = "scalp";
44251
+ else if (volatilityNormalized > 0.5)
44252
+ holdingPeriod = "day_trade";
44253
+ else if (absSignal > 0.5)
44254
+ holdingPeriod = "swing";
44255
+ else
44256
+ holdingPeriod = "position";
44257
+ const shortRec = recommendation === "aggressive_short" || recommendation === "conservative_short";
44258
+ const longRec = recommendation === "aggressive_long" || recommendation === "conservative_long";
44259
+ const stopLoss = shortRec ? -0.7 : longRec ? -0.2 : -0.15;
44260
+ const takeProfit = recommendation === "aggressive_long" || recommendation === "aggressive_short" ? 1.5 : longRec || shortRec ? 1 : 0.5;
44261
+ let alignment = 50;
44262
+ if (regime === "trending_up") {
44263
+ if (compositeSignal > 0)
44264
+ alignment = 90;
44265
+ else if (compositeSignal > -0.3)
44266
+ alignment = 60;
44267
+ else
44268
+ alignment = 30;
44269
+ } else if (regime === "trending_down") {
44270
+ if (compositeSignal < 0)
44271
+ alignment = 90;
44272
+ else if (compositeSignal < 0.3)
44273
+ alignment = 60;
44274
+ else
44275
+ alignment = 30;
44276
+ } else if (regime === "ranging") {
44277
+ alignment = 50 + (1 - absSignal) * 30;
44278
+ } else if (regime === "volatile") {
44279
+ alignment = 30 + absSignal * 40;
44280
+ } else {
44281
+ alignment = 40;
44282
+ }
44283
+ return taRec({
44284
+ composite_signal: finiteOut2(compositeSignal) ?? 0,
44285
+ confidence_level: finiteOut2(confidence) ?? 0,
44286
+ strategy_recommendation: recommendation,
44287
+ risk_level: finiteOut2(Math.max(0, Math.min(100, riskLevel))) ?? 0,
44288
+ expected_return: finiteOut2(expectedReturn) ?? 0,
44289
+ holding_period: holdingPeriod,
44290
+ stop_loss_priority: stopLoss,
44291
+ take_profit_priority: takeProfit,
44292
+ regime_alignment: finiteOut2(Math.max(0, Math.min(100, alignment))) ?? 0
44293
+ });
44294
+ }
44295
+ signalConfluence(signals) {
44296
+ let vals = [];
44297
+ if (signals != null && typeof signals === "object") {
44298
+ const obj = signals;
44299
+ if (typeof obj.values === "function") {
44300
+ const got = obj.values();
44301
+ vals = Array.isArray(got) ? got : [];
44302
+ } else if (!Array.isArray(signals)) {
44303
+ vals = Object.entries(signals).filter(([k]) => k !== "__taRecord").map(([, v]) => v);
44304
+ }
44305
+ }
44306
+ let signalCount = 0;
44307
+ let bullish = 0;
44308
+ let bearish = 0;
44309
+ for (const v of vals) {
44310
+ if (typeof v === "boolean") {
44311
+ if (v) {
44312
+ bullish += 1;
44313
+ signalCount += 1;
44314
+ }
44315
+ continue;
44316
+ }
44317
+ if (typeof v === "number" && Number.isFinite(v)) {
44318
+ if (v > 0) {
44319
+ bullish += 1;
44320
+ signalCount += 1;
44321
+ } else if (v < 0) {
44322
+ bearish += 1;
44323
+ signalCount += 1;
44324
+ }
44325
+ }
44326
+ }
44327
+ const total = vals.length > 0 ? vals.length : 1;
44328
+ const confluence = signalCount / total;
44329
+ const primary = bullish > bearish ? "buy" : bearish > bullish ? "sell" : "neutral";
44330
+ return taRec({
44331
+ signal_count: signalCount,
44332
+ confluence_level: finiteOut2(confluence) ?? 0,
44333
+ primary_signal: primary
44334
+ });
44335
+ }
44192
44336
  volumeProfileHigh(prices, volumes) {
44193
44337
  if (prices.length === 0 || volumes.length === 0 || prices.length !== volumes.length)
44194
44338
  return 0;
@@ -44830,7 +44974,10 @@ var TA_METHODS = new Set([
44830
44974
  "employment_cycle_indicator",
44831
44975
  "gdp_growth_proxy",
44832
44976
  "inflation_proxy_indicator",
44833
- "expected_value"
44977
+ "expected_value",
44978
+ "gamma_levels",
44979
+ "intelligent_strategy_synthesizer",
44980
+ "signal_confluence"
44834
44981
  ]);
44835
44982
  var TA_BARE_ATTRS = new Set([
44836
44983
  "accdist",
@@ -45199,6 +45346,9 @@ function hasSeriesArg(args) {
45199
45346
  const isSeries = (n) => n.kind === "Name" && BARE_SERIES_NAMES.has(n.id);
45200
45347
  return args.posNodes.some(isSeries) || Object.values(args.kwNodes).some(isSeries);
45201
45348
  }
45349
+ function isSeriesNode(n) {
45350
+ return n != null && n.kind === "Name" && BARE_SERIES_NAMES.has(n.id);
45351
+ }
45202
45352
  function hasAnyKw(args, names) {
45203
45353
  return names.some((n) => Object.hasOwn(args.kw, n));
45204
45354
  }
@@ -46951,10 +47101,11 @@ function emitTa(ctx, method, args) {
46951
47101
  ]);
46952
47102
  }
46953
47103
  if (method === "breakout_detection") {
47104
+ const arg = (i, names) => isSeriesNode(args.posNodes[i]) ? "null" : cellOf(pick(args, i, names, "null"));
46954
47105
  return taPure("breakoutDetection", [
46955
- cellOf(pick(args, 0, ["close", "source"], "null")),
46956
- cellOf(pick(args, 1, ["resistance"], "null")),
46957
- cellOf(pick(args, 2, ["support"], "null"))
47106
+ arg(0, ["close", "source"]),
47107
+ arg(1, ["resistance"]),
47108
+ arg(2, ["support"])
46958
47109
  ]);
46959
47110
  }
46960
47111
  if (method === "ema_cross_signal") {
@@ -47085,6 +47236,24 @@ function emitTa(ctx, method, args) {
47085
47236
  if (method === "expected_value") {
47086
47237
  return `__h.ta.expectedValue(${cellList(pick(args, 0, ["returns"], "null"))}, ${cellList(pick(args, 1, ["probabilities", "probs"], "null"))})`;
47087
47238
  }
47239
+ if (method === "gamma_levels") {
47240
+ const vol = isSeriesNode(args.posNodes[0]) ? "null" : cellOf(pick(args, 0, ["volatility"], "null"));
47241
+ const price = isSeriesNode(args.posNodes[1]) ? "null" : cellOf(pick(args, 1, ["current_price", "price"], "null"));
47242
+ return taPure("gammaLevels", [vol, price, cellOf(pick(args, 2, ["period", "length"], "null"))]);
47243
+ }
47244
+ if (method === "intelligent_strategy_synthesizer") {
47245
+ return taCall("intelligentStrategySynthesizer", site, [
47246
+ arrOrCell(pick(args, 0, ["trend_indicators", "trend"], "null")),
47247
+ arrOrCell(pick(args, 1, ["momentum_indicators", "momentum"], "null")),
47248
+ arrOrCell(pick(args, 2, ["volatility_indicators", "volatility"], "null")),
47249
+ arrOrCell(pick(args, 3, ["volume_indicators", "volume"], "null")),
47250
+ pick(args, 4, ["market_condition", "market_regime"], '"ranging"'),
47251
+ pick(args, 5, ["risk_profile"], '"balanced"')
47252
+ ]);
47253
+ }
47254
+ if (method === "signal_confluence") {
47255
+ return taPure("signalConfluence", [pick(args, 0, ["signals"], "null")]);
47256
+ }
47088
47257
  return "null";
47089
47258
  }
47090
47259
 
@@ -50740,7 +50909,8 @@ class Runtime {
50740
50909
  broker: extraOpts?.broker ?? this.broker,
50741
50910
  timeframe: extraOpts?.timeframe ?? this.timeframe,
50742
50911
  libraries: extraOpts?.libraries != null ? resolveLibraries(extraOpts.libraries) : this.libraries,
50743
- timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds
50912
+ timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds,
50913
+ realtime_last_bar: extraOpts?.realtimeLastBar ?? false
50744
50914
  };
50745
50915
  if (mode === "compile") {
50746
50916
  return runCompiled(source, ohlcv, host);
@@ -50803,7 +50973,7 @@ class RuntimeStream {
50803
50973
  return;
50804
50974
  this.bars.push(bar);
50805
50975
  try {
50806
- const out = this.runtime.run(this.source, this.bars);
50976
+ const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: true });
50807
50977
  for (const h of this.barHandlers)
50808
50978
  h(out);
50809
50979
  return out;
@@ -50814,9 +50984,29 @@ class RuntimeStream {
50814
50984
  return;
50815
50985
  }
50816
50986
  }
50987
+ get barCount() {
50988
+ return this.bars.length;
50989
+ }
50990
+ updateLastBar(bar) {
50991
+ if (this.closed)
50992
+ throw new Error("stream is closed");
50993
+ if (this.bars.length === 0)
50994
+ throw new Error("updateLastBar requires at least one bar");
50995
+ this.bars[this.bars.length - 1] = bar;
50996
+ const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: true });
50997
+ for (const h of this.barHandlers)
50998
+ h(out);
50999
+ return out;
51000
+ }
51001
+ appendBars(bars) {
51002
+ let out;
51003
+ for (const bar of bars)
51004
+ out = this.push(bar);
51005
+ return out;
51006
+ }
50817
51007
  close() {
50818
51008
  this.closed = true;
50819
- const out = this.runtime.run(this.source, this.bars);
51009
+ const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: false });
50820
51010
  for (const h of this.endHandlers)
50821
51011
  h(out);
50822
51012
  return out;
@@ -50863,7 +51053,8 @@ function interpretTree(tree, ohlcv, host) {
50863
51053
  pendingExports: new Map,
50864
51054
  onceFired: new WeakSet,
50865
51055
  maxBarsBackDecls: [],
50866
- alertFireBars: new Map
51056
+ alertFireBars: new Map,
51057
+ realtimeLastBar: host?.realtime_last_bar ?? false
50867
51058
  };
50868
51059
  const n = ohlcv.length;
50869
51060
  let runError;
@@ -51612,7 +51803,8 @@ function evalExpr(node, env) {
51612
51803
  return NA;
51613
51804
  for (const s of onceNode.body)
51614
51805
  execStmt(s, env);
51615
- env.onceFired.add(onceNode);
51806
+ if (barIsConfirmed(env))
51807
+ env.onceFired.add(onceNode);
51616
51808
  return NA;
51617
51809
  }
51618
51810
  case "BoolOp":
@@ -51959,20 +52151,21 @@ function evalAttribute(node, env) {
51959
52151
  }
51960
52152
  if (node.value.id === "barstate") {
51961
52153
  const last = env.barIndex === env.barCount - 1;
52154
+ const realtime = env.realtimeLastBar && last;
51962
52155
  if (node.attr === "isfirst")
51963
52156
  return env.barIndex === 0 ? 1 : 0;
51964
52157
  if (node.attr === "islast")
51965
52158
  return last ? 1 : 0;
51966
52159
  if (node.attr === "ishistory")
51967
- return last ? 0 : 1;
52160
+ return realtime ? 0 : 1;
51968
52161
  if (node.attr === "isconfirmed")
51969
52162
  return 1;
51970
52163
  if (node.attr === "isnew")
51971
52164
  return 1;
51972
52165
  if (node.attr === "isrealtime")
51973
- return 0;
52166
+ return realtime ? 1 : 0;
51974
52167
  if (node.attr === "islastconfirmedhistory")
51975
- return last ? 1 : 0;
52168
+ return realtime ? 0 : last ? 1 : 0;
51976
52169
  }
51977
52170
  if (node.value.id === "session") {
51978
52171
  if (node.attr === "regular")
@@ -52741,7 +52934,7 @@ function formatRunError(err) {
52741
52934
  return "runtime error";
52742
52935
  }
52743
52936
  function isPlainInputs(extra) {
52744
- return !("inputs" in extra) && !("broker" in extra) && !("timeframe" in extra) && !("libraries" in extra) && !("mode" in extra) && !("timeout_seconds" in extra);
52937
+ return !("inputs" in extra) && !("broker" in extra) && !("timeframe" in extra) && !("libraries" in extra) && !("mode" in extra) && !("timeout_seconds" in extra) && !("realtimeLastBar" in extra);
52745
52938
  }
52746
52939
  function resolveLibraries(libs) {
52747
52940
  if (libs instanceof LibraryRegistry)
@@ -54200,7 +54393,11 @@ function evalExtraTa(fname, node, env, site) {
54200
54393
  }
54201
54394
  if (fname === "ta.breakout_detection" || fname === "breakout_detection") {
54202
54395
  const msg = "ta.breakout_detection() requires 3 arguments";
54203
- return env.ta.breakoutDetection(wave11n(0, ["close", "source"], msg), wave11n(1, ["resistance"], msg), wave11n(2, ["support"], msg));
54396
+ const arg = (i, names) => {
54397
+ const e = wave10aArg(i, names, msg);
54398
+ return isSeriesExpr(e, env) ? NA : num2(evalExpr(e, env));
54399
+ };
54400
+ return env.ta.breakoutDetection(arg(0, ["close", "source"]), arg(1, ["resistance"]), arg(2, ["support"]));
54204
54401
  }
54205
54402
  if (fname === "ta.ema_cross_signal" || fname === "ema_cross_signal") {
54206
54403
  const msg = "ta.ema_cross_signal() requires 3 arguments";
@@ -54294,6 +54491,22 @@ function evalExtraTa(fname, node, env, site) {
54294
54491
  runtimeError("Returns and probabilities must have same length");
54295
54492
  return env.ta.expectedValue(returns, probs);
54296
54493
  }
54494
+ if (fname === "ta.gamma_levels" || fname === "gamma_levels") {
54495
+ const msg = "ta.gamma_levels() requires 3 arguments";
54496
+ const volArg = wave10aArg(0, ["volatility"], msg);
54497
+ const priceArg = wave10aArg(1, ["current_price", "price"], msg);
54498
+ const vol = isSeriesExpr(volArg, env) ? NA : num2(evalExpr(volArg, env));
54499
+ const price = isSeriesExpr(priceArg, env) ? NA : num2(evalExpr(priceArg, env));
54500
+ return env.ta.gammaLevels(vol, price, wave11n(2, ["period", "length"], msg));
54501
+ }
54502
+ if (fname === "ta.intelligent_strategy_synthesizer" || fname === "intelligent_strategy_synthesizer") {
54503
+ const msg = "ta.intelligent_strategy_synthesizer() requires 6 arguments";
54504
+ return env.ta.intelligentStrategySynthesizer(site, wave13v(0, ["trend_indicators", "trend"], msg), wave13v(1, ["momentum_indicators", "momentum"], msg), wave13v(2, ["volatility_indicators", "volatility"], msg), wave13v(3, ["volume_indicators", "volume"], msg), wave13v(4, ["market_condition", "market_regime"], msg), wave13v(5, ["risk_profile"], msg));
54505
+ }
54506
+ if (fname === "ta.signal_confluence" || fname === "signal_confluence") {
54507
+ const msg = "ta.signal_confluence() requires 1 argument";
54508
+ return env.ta.signalConfluence(wave13v(0, ["signals"], msg));
54509
+ }
54297
54510
  return;
54298
54511
  }
54299
54512
  function hlc3(env) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoox-sh/pynets",
3
- "version": "0.7.0",
3
+ "version": "0.9.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.7.0";
15
+ export const VERSION = "0.9.0";
16
16
 
17
17
  type Rgb = readonly [number, number, number];
18
18
 
@@ -211,6 +211,9 @@ const TA_METHODS = new Set([
211
211
  "gdp_growth_proxy",
212
212
  "inflation_proxy_indicator",
213
213
  "expected_value",
214
+ "gamma_levels",
215
+ "intelligent_strategy_synthesizer",
216
+ "signal_confluence",
214
217
  ]);
215
218
 
216
219
  /**
@@ -613,6 +616,10 @@ function hasSeriesArg(args: CallArgs): boolean {
613
616
  return args.posNodes.some(isSeries) || Object.values(args.kwNodes).some(isSeries);
614
617
  }
615
618
 
619
+ function isSeriesNode(n: expr | undefined): boolean {
620
+ return n != null && n.kind === "Name" && BARE_SERIES_NAMES.has(n.id);
621
+ }
622
+
616
623
  /** True when a keyword argument named one of *names* is present (interpret hasKwarg). */
617
624
  function hasAnyKw(args: CallArgs, names: string[]): boolean {
618
625
  return names.some((n) => Object.hasOwn(args.kw, n));
@@ -2493,10 +2500,12 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
2493
2500
  ]);
2494
2501
  }
2495
2502
  if (method === "breakout_detection") {
2503
+ const arg = (i: number, names: string[]): string =>
2504
+ isSeriesNode(args.posNodes[i]) ? "null" : cellOf(pick(args, i, names, "null"));
2496
2505
  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")),
2506
+ arg(0, ["close", "source"]),
2507
+ arg(1, ["resistance"]),
2508
+ arg(2, ["support"]),
2500
2509
  ]);
2501
2510
  }
2502
2511
  if (method === "ema_cross_signal") {
@@ -2627,5 +2636,27 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
2627
2636
  if (method === "expected_value") {
2628
2637
  return `__h.ta.expectedValue(${cellList(pick(args, 0, ["returns"], "null"))}, ${cellList(pick(args, 1, ["probabilities", "probs"], "null"))})`;
2629
2638
  }
2639
+ if (method === "gamma_levels") {
2640
+ const vol = isSeriesNode(args.posNodes[0])
2641
+ ? "null"
2642
+ : cellOf(pick(args, 0, ["volatility"], "null"));
2643
+ const price = isSeriesNode(args.posNodes[1])
2644
+ ? "null"
2645
+ : cellOf(pick(args, 1, ["current_price", "price"], "null"));
2646
+ return taPure("gammaLevels", [vol, price, cellOf(pick(args, 2, ["period", "length"], "null"))]);
2647
+ }
2648
+ if (method === "intelligent_strategy_synthesizer") {
2649
+ return taCall("intelligentStrategySynthesizer", site, [
2650
+ arrOrCell(pick(args, 0, ["trend_indicators", "trend"], "null")),
2651
+ arrOrCell(pick(args, 1, ["momentum_indicators", "momentum"], "null")),
2652
+ arrOrCell(pick(args, 2, ["volatility_indicators", "volatility"], "null")),
2653
+ arrOrCell(pick(args, 3, ["volume_indicators", "volume"], "null")),
2654
+ pick(args, 4, ["market_condition", "market_regime"], '"ranging"'),
2655
+ pick(args, 5, ["risk_profile"], '"balanced"'),
2656
+ ]);
2657
+ }
2658
+ if (method === "signal_confluence") {
2659
+ return taPure("signalConfluence", [pick(args, 0, ["signals"], "null")]);
2660
+ }
2630
2661
  return "null";
2631
2662
  }
@@ -218,6 +218,8 @@ export interface RuntimeOptions {
218
218
  mode?: RuntimeMode;
219
219
  /** Wall-clock budget in seconds. Interpret only; checked every 32 bars. */
220
220
  timeout_seconds?: number | null;
221
+ /** Visit the last bar as a forming realtime bar (`barstate.isrealtime`). */
222
+ realtimeLastBar?: boolean;
221
223
  }
222
224
 
223
225
  export interface RuntimeFill {
@@ -362,6 +364,8 @@ interface Env {
362
364
  maxBarsBackDecls: Array<{ var: Value; num: Value }>;
363
365
  /** Python `_alert_fire_bars` keys for `alert` / `alertcondition` freq gating. */
364
366
  alertFireBars: Map<string, number>;
367
+ /** Visit the last bar as a forming realtime bar (`barstate.isrealtime`). */
368
+ realtimeLastBar: boolean;
365
369
  }
366
370
 
367
371
  export class Runtime {
@@ -420,6 +424,7 @@ export class Runtime {
420
424
  timeframe: extraOpts?.timeframe ?? this.timeframe,
421
425
  libraries: extraOpts?.libraries != null ? resolveLibraries(extraOpts.libraries) : this.libraries,
422
426
  timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds,
427
+ realtime_last_bar: extraOpts?.realtimeLastBar ?? false,
423
428
  };
424
429
  if (mode === "compile") {
425
430
  return runCompiled(source, ohlcv, host);
@@ -494,7 +499,7 @@ export class RuntimeStream {
494
499
  if (this.closed) return undefined;
495
500
  this.bars.push(bar);
496
501
  try {
497
- const out = this.runtime.run(this.source, this.bars);
502
+ const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: true });
498
503
  for (const h of this.barHandlers) h(out);
499
504
  return out;
500
505
  } catch (err) {
@@ -504,9 +509,35 @@ export class RuntimeStream {
504
509
  }
505
510
  }
506
511
 
512
+ /** Number of committed bars in the stream. */
513
+ get barCount(): number {
514
+ return this.bars.length;
515
+ }
516
+
517
+ /**
518
+ * Re-tick the forming last bar with revised OHLCV. The bar count does not
519
+ * advance; the script is re-evaluated from bar 0, so `once`, TA state, and
520
+ * broker fills naturally roll back to the revised history.
521
+ */
522
+ updateLastBar(bar: OHLCVBar): RuntimeResult {
523
+ if (this.closed) throw new Error("stream is closed");
524
+ if (this.bars.length === 0) throw new Error("updateLastBar requires at least one bar");
525
+ this.bars[this.bars.length - 1] = bar;
526
+ const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: true });
527
+ for (const h of this.barHandlers) h(out);
528
+ return out;
529
+ }
530
+
531
+ /** Append several bars, returning the final result. */
532
+ appendBars(bars: OHLCVBar[]): RuntimeResult | undefined {
533
+ let out: RuntimeResult | undefined;
534
+ for (const bar of bars) out = this.push(bar);
535
+ return out;
536
+ }
537
+
507
538
  close(): RuntimeResult {
508
539
  this.closed = true;
509
- const out = this.runtime.run(this.source, this.bars);
540
+ const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: false });
510
541
  for (const h of this.endHandlers) h(out);
511
542
  return out;
512
543
  }
@@ -528,6 +559,7 @@ export function interpretTree(
528
559
  timeframe?: string | null;
529
560
  libraries?: LibraryRegistry;
530
561
  timeout_seconds?: number | null;
562
+ realtime_last_bar?: boolean;
531
563
  },
532
564
  ): RuntimeResult {
533
565
  const scriptNode = tree.kind === "Script" ? (tree as Script) : null;
@@ -566,6 +598,7 @@ export function interpretTree(
566
598
  onceFired: new WeakSet(),
567
599
  maxBarsBackDecls: [],
568
600
  alertFireBars: new Map(),
601
+ realtimeLastBar: host?.realtime_last_bar ?? false,
569
602
  };
570
603
 
571
604
  const n = ohlcv.length;
@@ -1319,7 +1352,9 @@ function evalExpr(node: expr | undefined, env: Env): Value {
1319
1352
  const t = onceNode.test == null ? 1 : unwrap(evalExpr(onceNode.test, env));
1320
1353
  if (!isTruthy(t)) return NA;
1321
1354
  for (const s of onceNode.body) execStmt(s, env);
1322
- env.onceFired.add(onceNode);
1355
+ // Python commits the fired flag only on a confirmed bar; unconfirmed
1356
+ // realtime ticks may re-run the body (rollback semantics).
1357
+ if (barIsConfirmed(env)) env.onceFired.add(onceNode);
1323
1358
  return NA;
1324
1359
  }
1325
1360
  case "BoolOp":
@@ -1652,13 +1687,14 @@ function evalAttribute(node: Attribute, env: Env): Value {
1652
1687
  }
1653
1688
  if (node.value.id === "barstate") {
1654
1689
  const last = env.barIndex === env.barCount - 1;
1690
+ const realtime = env.realtimeLastBar && last;
1655
1691
  if (node.attr === "isfirst") return env.barIndex === 0 ? 1 : 0;
1656
1692
  if (node.attr === "islast") return last ? 1 : 0;
1657
- if (node.attr === "ishistory") return last ? 0 : 1;
1693
+ if (node.attr === "ishistory") return realtime ? 0 : 1;
1658
1694
  if (node.attr === "isconfirmed") return 1;
1659
1695
  if (node.attr === "isnew") return 1;
1660
- if (node.attr === "isrealtime") return 0;
1661
- if (node.attr === "islastconfirmedhistory") return last ? 1 : 0;
1696
+ if (node.attr === "isrealtime") return realtime ? 1 : 0;
1697
+ if (node.attr === "islastconfirmedhistory") return realtime ? 0 : last ? 1 : 0;
1662
1698
  }
1663
1699
  if (node.value.id === "session") {
1664
1700
  if (node.attr === "regular") return "regular";
@@ -2470,7 +2506,8 @@ function isPlainInputs(extra: RuntimeOptions | InputOverrides): extra is InputOv
2470
2506
  !("timeframe" in extra) &&
2471
2507
  !("libraries" in extra) &&
2472
2508
  !("mode" in extra) &&
2473
- !("timeout_seconds" in extra)
2509
+ !("timeout_seconds" in extra) &&
2510
+ !("realtimeLastBar" in extra)
2474
2511
  );
2475
2512
  }
2476
2513
 
@@ -2623,6 +2660,7 @@ function runAuto(
2623
2660
  timeframe?: string | null;
2624
2661
  libraries?: LibraryRegistry;
2625
2662
  timeout_seconds?: number | null;
2663
+ realtime_last_bar?: boolean;
2626
2664
  },
2627
2665
  ): RuntimeResult {
2628
2666
  const elig = compileEligible(source);
@@ -2700,7 +2738,7 @@ function normalizeAlertFreq(freq: unknown): string {
2700
2738
  return ALERT_FREQ_ALIASES[s] ?? (s ? s : FREQ_ONCE_PER_BAR);
2701
2739
  }
2702
2740
 
2703
- /** Python `_bar_is_confirmed`. This host sets `barstate.isconfirmed` to 1 on every bar. */
2741
+ /** Python `_bar_is_confirmed`: single-tick runs are confirmed (ticks>1 not modeled). */
2704
2742
  function barIsConfirmed(_env: Env): boolean {
2705
2743
  return true;
2706
2744
  }
@@ -4673,10 +4711,14 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
4673
4711
  }
4674
4712
  if (fname === "ta.breakout_detection" || fname === "breakout_detection") {
4675
4713
  const msg = "ta.breakout_detection() requires 3 arguments";
4714
+ const arg = (i: number, names: string[]): Cell => {
4715
+ const e = wave10aArg(i, names, msg);
4716
+ return isSeriesExpr(e, env) ? NA : num(evalExpr(e, env));
4717
+ };
4676
4718
  return env.ta.breakoutDetection(
4677
- wave11n(0, ["close", "source"], msg),
4678
- wave11n(1, ["resistance"], msg),
4679
- wave11n(2, ["support"], msg),
4719
+ arg(0, ["close", "source"]),
4720
+ arg(1, ["resistance"]),
4721
+ arg(2, ["support"]),
4680
4722
  );
4681
4723
  }
4682
4724
  // wave12-end
@@ -4860,6 +4902,35 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
4860
4902
  return env.ta.expectedValue(returns, probs);
4861
4903
  }
4862
4904
  // wave13-end
4905
+ // wave14-start — list-return + capstone synthesis
4906
+ if (fname === "ta.gamma_levels" || fname === "gamma_levels") {
4907
+ const msg = "ta.gamma_levels() requires 3 arguments";
4908
+ const volArg = wave10aArg(0, ["volatility"], msg);
4909
+ const priceArg = wave10aArg(1, ["current_price", "price"], msg);
4910
+ const vol = isSeriesExpr(volArg, env) ? NA : num(evalExpr(volArg, env));
4911
+ const price = isSeriesExpr(priceArg, env) ? NA : num(evalExpr(priceArg, env));
4912
+ return env.ta.gammaLevels(vol, price, wave11n(2, ["period", "length"], msg));
4913
+ }
4914
+ if (
4915
+ fname === "ta.intelligent_strategy_synthesizer" ||
4916
+ fname === "intelligent_strategy_synthesizer"
4917
+ ) {
4918
+ const msg = "ta.intelligent_strategy_synthesizer() requires 6 arguments";
4919
+ return env.ta.intelligentStrategySynthesizer(
4920
+ site,
4921
+ wave13v(0, ["trend_indicators", "trend"], msg),
4922
+ wave13v(1, ["momentum_indicators", "momentum"], msg),
4923
+ wave13v(2, ["volatility_indicators", "volatility"], msg),
4924
+ wave13v(3, ["volume_indicators", "volume"], msg),
4925
+ wave13v(4, ["market_condition", "market_regime"], msg),
4926
+ wave13v(5, ["risk_profile"], msg),
4927
+ );
4928
+ }
4929
+ if (fname === "ta.signal_confluence" || fname === "signal_confluence") {
4930
+ const msg = "ta.signal_confluence() requires 1 argument";
4931
+ return env.ta.signalConfluence(wave13v(0, ["signals"], msg));
4932
+ }
4933
+ // wave14-end
4863
4934
  return undefined;
4864
4935
  }
4865
4936