@hoox-sh/pynets 0.8.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
@@ -50909,7 +50909,8 @@ class Runtime {
50909
50909
  broker: extraOpts?.broker ?? this.broker,
50910
50910
  timeframe: extraOpts?.timeframe ?? this.timeframe,
50911
50911
  libraries: extraOpts?.libraries != null ? resolveLibraries(extraOpts.libraries) : this.libraries,
50912
- timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds
50912
+ timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds,
50913
+ realtime_last_bar: extraOpts?.realtimeLastBar ?? false
50913
50914
  };
50914
50915
  if (mode === "compile") {
50915
50916
  return runCompiled(source, ohlcv, host);
@@ -50972,7 +50973,7 @@ class RuntimeStream {
50972
50973
  return;
50973
50974
  this.bars.push(bar);
50974
50975
  try {
50975
- const out = this.runtime.run(this.source, this.bars);
50976
+ const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: true });
50976
50977
  for (const h of this.barHandlers)
50977
50978
  h(out);
50978
50979
  return out;
@@ -50983,9 +50984,29 @@ class RuntimeStream {
50983
50984
  return;
50984
50985
  }
50985
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
+ }
50986
51007
  close() {
50987
51008
  this.closed = true;
50988
- const out = this.runtime.run(this.source, this.bars);
51009
+ const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: false });
50989
51010
  for (const h of this.endHandlers)
50990
51011
  h(out);
50991
51012
  return out;
@@ -51032,7 +51053,8 @@ function interpretTree(tree, ohlcv, host) {
51032
51053
  pendingExports: new Map,
51033
51054
  onceFired: new WeakSet,
51034
51055
  maxBarsBackDecls: [],
51035
- alertFireBars: new Map
51056
+ alertFireBars: new Map,
51057
+ realtimeLastBar: host?.realtime_last_bar ?? false
51036
51058
  };
51037
51059
  const n = ohlcv.length;
51038
51060
  let runError;
@@ -51781,7 +51803,8 @@ function evalExpr(node, env) {
51781
51803
  return NA;
51782
51804
  for (const s of onceNode.body)
51783
51805
  execStmt(s, env);
51784
- env.onceFired.add(onceNode);
51806
+ if (barIsConfirmed(env))
51807
+ env.onceFired.add(onceNode);
51785
51808
  return NA;
51786
51809
  }
51787
51810
  case "BoolOp":
@@ -52128,20 +52151,21 @@ function evalAttribute(node, env) {
52128
52151
  }
52129
52152
  if (node.value.id === "barstate") {
52130
52153
  const last = env.barIndex === env.barCount - 1;
52154
+ const realtime = env.realtimeLastBar && last;
52131
52155
  if (node.attr === "isfirst")
52132
52156
  return env.barIndex === 0 ? 1 : 0;
52133
52157
  if (node.attr === "islast")
52134
52158
  return last ? 1 : 0;
52135
52159
  if (node.attr === "ishistory")
52136
- return last ? 0 : 1;
52160
+ return realtime ? 0 : 1;
52137
52161
  if (node.attr === "isconfirmed")
52138
52162
  return 1;
52139
52163
  if (node.attr === "isnew")
52140
52164
  return 1;
52141
52165
  if (node.attr === "isrealtime")
52142
- return 0;
52166
+ return realtime ? 1 : 0;
52143
52167
  if (node.attr === "islastconfirmedhistory")
52144
- return last ? 1 : 0;
52168
+ return realtime ? 0 : last ? 1 : 0;
52145
52169
  }
52146
52170
  if (node.value.id === "session") {
52147
52171
  if (node.attr === "regular")
@@ -52910,7 +52934,7 @@ function formatRunError(err) {
52910
52934
  return "runtime error";
52911
52935
  }
52912
52936
  function isPlainInputs(extra) {
52913
- 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);
52914
52938
  }
52915
52939
  function resolveLibraries(libs) {
52916
52940
  if (libs instanceof LibraryRegistry)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoox-sh/pynets",
3
- "version": "0.8.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.8.0";
15
+ export const VERSION = "0.9.0";
16
16
 
17
17
  type Rgb = readonly [number, number, number];
18
18
 
@@ -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
  }