oakscriptjs 0.12.2 → 0.13.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.
Files changed (49) hide show
  1. package/dist/cjs/drawing/registry.d.ts +37 -0
  2. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  3. package/dist/cjs/drawing/registry.js +40 -0
  4. package/dist/cjs/drawing/registry.js.map +2 -2
  5. package/dist/cjs/index.d.ts +1 -1
  6. package/dist/cjs/index.js +1 -1
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/math/index.d.ts +4 -0
  9. package/dist/cjs/math/index.d.ts.map +1 -1
  10. package/dist/cjs/math/index.js +9 -0
  11. package/dist/cjs/math/index.js.map +2 -2
  12. package/dist/cjs/runtime/types.d.ts +6 -0
  13. package/dist/cjs/runtime/types.d.ts.map +1 -1
  14. package/dist/cjs/runtime/types.js.map +1 -1
  15. package/dist/cjs/script/index.d.ts +52 -1
  16. package/dist/cjs/script/index.d.ts.map +1 -1
  17. package/dist/cjs/script/index.js +217 -11
  18. package/dist/cjs/script/index.js.map +3 -3
  19. package/dist/cjs/strategy/index.d.ts +3 -0
  20. package/dist/cjs/strategy/index.d.ts.map +1 -1
  21. package/dist/cjs/strategy/index.js +1 -0
  22. package/dist/cjs/strategy/index.js.map +2 -2
  23. package/dist/esm/drawing/registry.d.ts +37 -0
  24. package/dist/esm/drawing/registry.d.ts.map +1 -1
  25. package/dist/esm/drawing/registry.js +41 -0
  26. package/dist/esm/drawing/registry.js.map +1 -1
  27. package/dist/esm/index.d.ts +1 -1
  28. package/dist/esm/index.js +1 -1
  29. package/dist/esm/math/index.d.ts +4 -0
  30. package/dist/esm/math/index.d.ts.map +1 -1
  31. package/dist/esm/math/index.js +10 -0
  32. package/dist/esm/math/index.js.map +1 -1
  33. package/dist/esm/runtime/types.d.ts +6 -0
  34. package/dist/esm/runtime/types.d.ts.map +1 -1
  35. package/dist/esm/script/index.d.ts +52 -1
  36. package/dist/esm/script/index.d.ts.map +1 -1
  37. package/dist/esm/script/index.js +254 -10
  38. package/dist/esm/script/index.js.map +1 -1
  39. package/dist/esm/strategy/index.d.ts +3 -0
  40. package/dist/esm/strategy/index.d.ts.map +1 -1
  41. package/dist/esm/strategy/index.js +1 -0
  42. package/dist/esm/strategy/index.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/drawing/registry.ts +56 -0
  45. package/src/index.ts +1 -1
  46. package/src/math/index.ts +11 -0
  47. package/src/runtime/types.ts +6 -0
  48. package/src/script/index.ts +338 -10
  49. package/src/strategy/index.ts +4 -0
@@ -34,6 +34,7 @@ __export(script_exports, {
34
34
  MissingSymbolDataError: () => MissingSymbolDataError,
35
35
  STRATEGY_DEFAULTS: () => import_strategy2.STRATEGY_DEFAULTS,
36
36
  Series: () => import_series.Series,
37
+ StrategyRecorder: () => StrategyRecorder,
37
38
  alert: () => alert,
38
39
  alertcondition: () => alertcondition,
39
40
  barcolor: () => barcolor,
@@ -48,6 +49,7 @@ __export(script_exports, {
48
49
  dayofmonth: () => dayofmonth,
49
50
  dayofweek: () => dayofweek,
50
51
  eachBar: () => eachBar,
52
+ executeIntrabar: () => executeIntrabar,
51
53
  executeScript: () => executeScript,
52
54
  fill: () => fill,
53
55
  fixnan: () => fixnan,
@@ -100,6 +102,7 @@ __export(script_exports, {
100
102
  timenow: () => timenow,
101
103
  timestamp: () => timestamp,
102
104
  varDrawing: () => import_registry2.varDrawing,
105
+ varip: () => varip,
103
106
  volume: () => volume,
104
107
  weekofyear: () => weekofyear,
105
108
  year: () => year
@@ -187,12 +190,55 @@ function slugId(title, fallback, taken) {
187
190
  return id;
188
191
  }
189
192
  let scriptOptions = {};
193
+ let intrabarRun = null;
194
+ class StrategyRecorder {
195
+ /** Per bar: the values the engine returned to the strategy.eachBar() callback (strategy.*, trades...), in order. */
196
+ answers = [];
197
+ /** Per bar: the `varip` values when the bar's strategy.eachBar() callback started in the main run. */
198
+ varipStarts = [];
199
+ /** Current `varip` values: kept through the intrabar runs (no rollback). */
200
+ varip = [];
201
+ }
202
+ function saveRunState() {
203
+ return {
204
+ bars: ctxBars.bars,
205
+ ctx,
206
+ chartCtx,
207
+ runNow,
208
+ scriptOptions,
209
+ currentBar,
210
+ missingSymbols,
211
+ sessionCache,
212
+ intrabarRun,
213
+ varip: varipRun,
214
+ drawings: (0, import_registry.saveDrawingState)(),
215
+ random: mathCore.saveRandom()
216
+ };
217
+ }
218
+ function restoreRunState(st) {
219
+ ctxBars.setAll(st.bars);
220
+ ctx = st.ctx;
221
+ chartCtx = st.chartCtx;
222
+ runNow = st.runNow;
223
+ scriptOptions = st.scriptOptions;
224
+ currentBar = st.currentBar;
225
+ missingSymbols = st.missingSymbols;
226
+ sessionCache = st.sessionCache;
227
+ intrabarRun = st.intrabarRun;
228
+ varipRun = st.varip;
229
+ (0, import_registry.restoreDrawingState)(st.drawings);
230
+ mathCore.restoreRandom(st.random);
231
+ }
190
232
  function executeScript(body, barsInput, inputs = {}, chart2 = {}, options = {}) {
233
+ const outer = ctx ? saveRunState() : null;
191
234
  ctxBars.setAll(barsInput);
192
235
  chartCtx = { ...chart2 };
193
236
  runNow = chart2.now ?? Date.now();
194
237
  scriptOptions = options;
195
238
  ctx = freshCollector(inputs);
239
+ intrabarRun = null;
240
+ varipRun = { slot: 0, recorder: options.recorder ?? null, mode: "live", scratch: null };
241
+ currentBar = -1;
196
242
  (0, import_registry.resetDrawings)();
197
243
  mathCore.resetRandom();
198
244
  missingSymbols = [];
@@ -240,7 +286,56 @@ function executeScript(body, barsInput, inputs = {}, chart2 = {}, options = {})
240
286
  };
241
287
  } finally {
242
288
  ctx = null;
289
+ if (outer) restoreRunState(outer);
290
+ }
291
+ }
292
+ function executeIntrabar(body, bars2, inputs, chart2, options) {
293
+ const run = {
294
+ bar: options.bar,
295
+ lastBarIndex: options.lastBarIndex,
296
+ engine: options.engine,
297
+ recorder: options.recorder
298
+ };
299
+ executeScript(
300
+ () => {
301
+ intrabarRun = run;
302
+ varipRun = { slot: 0, recorder: options.recorder, mode: "live", scratch: null };
303
+ body();
304
+ },
305
+ bars2,
306
+ inputs,
307
+ // History bars are confirmed, also on their ticks (the reference app: a barstate.isconfirmed entry fills on them).
308
+ { ...chart2, lastBarConfirmed: true, realtime: false },
309
+ { strategyEngine: () => options.engine }
310
+ );
311
+ }
312
+ let varipRun = { slot: 0, recorder: null, mode: "live", scratch: null };
313
+ function varip(initial) {
314
+ collector();
315
+ const slot = varipRun.slot++;
316
+ const state = varipRun;
317
+ const rec = state.recorder;
318
+ if (!rec) {
319
+ let current = initial;
320
+ return {
321
+ get value() {
322
+ return current;
323
+ },
324
+ set value(v) {
325
+ current = v;
326
+ }
327
+ };
243
328
  }
329
+ if (rec.varip.length <= slot) rec.varip[slot] = initial;
330
+ const store = () => state.mode === "replay" && state.scratch ? state.scratch : rec.varip;
331
+ return {
332
+ get value() {
333
+ return store()[slot];
334
+ },
335
+ set value(v) {
336
+ store()[slot] = v;
337
+ }
338
+ };
244
339
  }
245
340
  function indicator(title, options = {}) {
246
341
  const c = collector();
@@ -418,10 +513,21 @@ const ta = {
418
513
  wvad: barsSeries(taSeries.wvad)
419
514
  };
420
515
  function fixnan(source) {
421
- return source instanceof import_series.Series ? import_series.Series.fromArray(ctxBars, (0, import_utils.fixnan)(source.toArray())) : (0, import_utils.fixnan)(source);
516
+ if (source instanceof import_series.Series) return import_series.Series.fromArray(ctxBars, (0, import_utils.fixnan)(source.toArray()));
517
+ return withPalette((0, import_utils.fixnan)(source), colorPalettes.get(source));
518
+ }
519
+ const colorPalettes = /* @__PURE__ */ new WeakMap();
520
+ function withPalette(colors, palette) {
521
+ if (palette?.length) colorPalettes.set(colors, palette);
522
+ return colors;
523
+ }
524
+ function paletteOfColors(col) {
525
+ return typeof col === "object" && col !== null ? colorPalettes.get(col) : void 0;
422
526
  }
423
527
  function colorWhen(cond, colorTrue, colorFalse) {
424
- return cond.toArray().map((v) => !(0, import_utils.isNA)(v) && v !== 0 ? colorTrue : colorFalse);
528
+ const colors = cond.toArray().map((v) => !(0, import_utils.isNA)(v) && v !== 0 ? colorTrue : colorFalse);
529
+ const palette = [colorTrue, ...colorFalse !== void 0 && colorFalse !== colorTrue ? [colorFalse] : []];
530
+ return withPalette(colors, palette);
425
531
  }
426
532
  const color = {
427
533
  ...colorCore,
@@ -456,7 +562,8 @@ function plotImpl(series, title = DEFAULT_OUTPUT_TITLES.plot, options = {}) {
456
562
  display: options.display,
457
563
  histbase: options.histbase,
458
564
  forceOverlay: options.force_overlay,
459
- ...options.editable === false ? { editable: false } : {}
565
+ ...options.editable === false ? { editable: false } : {},
566
+ ...paletteOfColors(perBar2) ? { palette: paletteOfColors(perBar2) } : {}
460
567
  });
461
568
  c.outputs.push({ kind: "plot", id, title });
462
569
  const values = series.toArray();
@@ -524,6 +631,7 @@ function fill(a, b, ...args) {
524
631
  let title;
525
632
  let gradient;
526
633
  let editable;
634
+ let perBarArg;
527
635
  if (typeof third === "number" || third instanceof import_series.Series) {
528
636
  const n = ctxBars.bars.length;
529
637
  gradient = {
@@ -537,12 +645,14 @@ function fill(a, b, ...args) {
537
645
  } else if (third !== null && typeof third === "object" && !Array.isArray(third)) {
538
646
  const options = third;
539
647
  perBar2 = Array.isArray(options.color) ? options.color : void 0;
648
+ perBarArg = perBar2;
540
649
  staticColor2 = typeof options.color === "string" ? options.color : void 0;
541
650
  title = options.title;
542
651
  editable = options.editable;
543
652
  } else {
544
653
  const color2 = third;
545
654
  perBar2 = Array.isArray(color2) ? color2.map((x) => x ?? "") : void 0;
655
+ perBarArg = Array.isArray(color2) ? color2 : void 0;
546
656
  staticColor2 = typeof color2 === "string" ? color2 : void 0;
547
657
  title = args[1];
548
658
  editable = args[2];
@@ -562,7 +672,8 @@ function fill(a, b, ...args) {
562
672
  title,
563
673
  ...topColor ? { topColor } : {},
564
674
  ...bottomColor ? { bottomColor } : {},
565
- ...editable === false ? { editable: false } : {}
675
+ ...editable === false ? { editable: false } : {},
676
+ ...paletteOfColors(perBarArg) ? { palette: paletteOfColors(perBarArg) } : {}
566
677
  });
567
678
  if (hlines) return;
568
679
  c.fills.push({
@@ -710,8 +821,8 @@ function emitBarColors(kind, colors, options) {
710
821
  title: options.title ?? DEFAULT_OUTPUT_TITLES[kind],
711
822
  offset: options.offset,
712
823
  forceOverlay: options.force_overlay,
713
- // a constant colour is a palette of one colour (PineScript)
714
- ...typeof colors === "string" ? { palette: [colors] } : {},
824
+ // a constant colour is a palette of one colour (PineScript); per-bar colours from constants keep theirs
825
+ ...typeof colors === "string" ? { palette: [colors] } : paletteOfColors(colors) ? { palette: paletteOfColors(colors) } : {},
715
826
  ...options.editable === false ? { editable: false } : {},
716
827
  outputIndex: c.outputs.length
717
828
  });
@@ -751,6 +862,14 @@ function ohlcRows(o, h, l, cl) {
751
862
  }
752
863
  const staticColor = (col) => typeof col === "string" ? col : void 0;
753
864
  const DEFAULT_CANDLE_COLORS = { color: "#2962FF", wickColor: "#737375", borderColor: "#000000" };
865
+ function candlePalettes(cols) {
866
+ const palettes = {};
867
+ for (const k of ["color", "wickColor", "borderColor"]) {
868
+ const pal = paletteOfColors(cols[k]);
869
+ if (pal) palettes[k] = pal;
870
+ }
871
+ return Object.keys(palettes).length ? { palettes } : {};
872
+ }
754
873
  const orDefault = (col, fallback) => col === void 0 ? fallback : col;
755
874
  function plotcandle(open2, high2, low2, close2, title, options = {}) {
756
875
  const c = collector();
@@ -766,6 +885,7 @@ function plotcandle(open2, high2, low2, close2, title, options = {}) {
766
885
  color: staticColor(color2),
767
886
  wickColor: staticColor(wickcolor),
768
887
  borderColor: staticColor(bordercolor),
888
+ ...candlePalettes({ color: color2, wickColor: wickcolor, borderColor: bordercolor }),
769
889
  display: options.display,
770
890
  forceOverlay: options.force_overlay,
771
891
  ...options.editable === false ? { editable: false } : {}
@@ -794,6 +914,7 @@ function plotbar(open2, high2, low2, close2, title, options = {}) {
794
914
  kind: "bar",
795
915
  title,
796
916
  color: staticColor(barColor),
917
+ ...candlePalettes({ color: barColor }),
797
918
  display: options.display,
798
919
  forceOverlay: options.force_overlay,
799
920
  ...options.editable === false ? { editable: false } : {}
@@ -813,6 +934,7 @@ const HISTORY_GROWTH = 1.618034;
813
934
  function eachBar(fn) {
814
935
  collector();
815
936
  const b = bars();
937
+ const lastIndex = intrabarRun ? intrabarRun.lastBarIndex : b.length - 1;
816
938
  const out = new Array(b.length).fill(NaN);
817
939
  const cache = /* @__PURE__ */ new Map();
818
940
  const buffers = /* @__PURE__ */ new Map();
@@ -822,7 +944,7 @@ function eachBar(fn) {
822
944
  get i() {
823
945
  return idx;
824
946
  },
825
- n: b.length,
947
+ n: lastIndex + 1,
826
948
  get time() {
827
949
  return b[idx].time;
828
950
  },
@@ -867,7 +989,7 @@ function eachBar(fn) {
867
989
  for (idx = 0; idx < b.length; idx++) {
868
990
  if (pendingError && pendingError.bar === idx) throw new Error((0, import_series.historyError)(idx, pendingError.barsBack));
869
991
  currentBar = idx;
870
- (0, import_registry.setDrawingBar)(idx, idx === b.length - 1, b.length - 1);
992
+ (0, import_registry.setDrawingBar)(idx, idx === lastIndex, lastIndex);
871
993
  const r = fn(ctx2);
872
994
  out[idx] = typeof r === "number" ? r : typeof r === "boolean" ? r ? 1 : 0 : NaN;
873
995
  for (const buffer of buffers.values()) {
@@ -929,6 +1051,7 @@ function strategyVariable(name) {
929
1051
  return value;
930
1052
  }
931
1053
  function riskRule(rule) {
1054
+ if (intrabarRun) return;
932
1055
  const engine = strategyEngine(`strategy.risk.${rule.rule}()`);
933
1056
  if (!engine.risk) throw new Error(`strategy.risk.${rule.rule}() is not provided by the strategy engine.`);
934
1057
  engine.risk(rule);
@@ -966,17 +1089,95 @@ function strategyEachBar(fn) {
966
1089
  const engine = strategyEngine("strategy.eachBar()");
967
1090
  if (run.looped) throw new Error("strategy.eachBar() runs once per script run.");
968
1091
  run.looped = true;
1092
+ const intrabar = intrabarRun;
1093
+ if (intrabar) {
1094
+ const varipState = varipRun;
1095
+ return eachBar((c) => {
1096
+ const replay = c.i < intrabar.bar;
1097
+ run.engine = replay ? replayEngine(intrabar.recorder.answers[c.i] ?? []) : intrabar.engine;
1098
+ varipState.mode = replay ? "replay" : "live";
1099
+ varipState.scratch = replay ? [...intrabar.recorder.varipStarts[c.i] ?? intrabar.recorder.varip] : null;
1100
+ run.inLoop = true;
1101
+ try {
1102
+ return fn(c);
1103
+ } finally {
1104
+ run.inLoop = false;
1105
+ run.engine = intrabar.engine;
1106
+ varipState.mode = "live";
1107
+ varipState.scratch = null;
1108
+ }
1109
+ });
1110
+ }
1111
+ const recorder = scriptOptions.recorder;
969
1112
  return eachBar((c) => {
970
1113
  engine.processBar(c.i);
1114
+ if (recorder) {
1115
+ recorder.varipStarts[c.i] = [...recorder.varip];
1116
+ run.engine = recordingEngine(engine, recorder.answers[c.i] = []);
1117
+ }
971
1118
  run.inLoop = true;
972
1119
  try {
973
1120
  return fn(c);
974
1121
  } finally {
975
1122
  run.inLoop = false;
1123
+ run.engine = engine;
976
1124
  engine.processClose(c.i);
977
1125
  }
978
1126
  });
979
1127
  }
1128
+ function recordingEngine(engine, log) {
1129
+ const keep = (v) => {
1130
+ log.push(v);
1131
+ return v;
1132
+ };
1133
+ return {
1134
+ ...forward(engine),
1135
+ get: (name) => keep(engine.get(name)),
1136
+ openTrade: (index) => keep(engine.openTrade(index)),
1137
+ closedTrade: (index) => keep(engine.closedTrade(index)),
1138
+ defaultEntryQty: engine.defaultEntryQty ? (price) => keep(engine.defaultEntryQty(price)) : void 0
1139
+ };
1140
+ }
1141
+ function replayEngine(log) {
1142
+ let k = 0;
1143
+ const next = () => log[k++];
1144
+ const none = () => {
1145
+ };
1146
+ return {
1147
+ processBar: none,
1148
+ processClose: none,
1149
+ entry: none,
1150
+ order: none,
1151
+ exit: none,
1152
+ close: none,
1153
+ close_all: none,
1154
+ cancel: none,
1155
+ cancel_all: none,
1156
+ risk: none,
1157
+ get: () => next(),
1158
+ openTrade: () => next(),
1159
+ closedTrade: () => next(),
1160
+ defaultEntryQty: () => next()
1161
+ };
1162
+ }
1163
+ function forward(engine) {
1164
+ return {
1165
+ processBar: (bar) => engine.processBar(bar),
1166
+ processClose: (bar) => engine.processClose(bar),
1167
+ entry: (id, direction, options) => engine.entry(id, direction, options),
1168
+ order: (id, direction, options) => engine.order(id, direction, options),
1169
+ exit: (id, options) => engine.exit(id, options),
1170
+ close: (id, options) => engine.close(id, options),
1171
+ close_all: (options) => engine.close_all(options),
1172
+ cancel: (id) => engine.cancel(id),
1173
+ cancel_all: () => engine.cancel_all(),
1174
+ risk: engine.risk ? (rule) => engine.risk(rule) : void 0,
1175
+ get: (name) => engine.get(name),
1176
+ openTrade: (index) => engine.openTrade(index),
1177
+ closedTrade: (index) => engine.closedTrade(index),
1178
+ defaultEntryQty: engine.defaultEntryQty ? (price) => engine.defaultEntryQty(price) : void 0
1179
+ };
1180
+ }
980
1181
  const strategyMembers = {
981
1182
  ...import_strategy.STRATEGY_CONSTANTS,
982
1183
  eachBar: strategyEachBar,
@@ -1288,13 +1489,14 @@ function barFlag(test) {
1288
1489
  const n = ctxBars.bars.length;
1289
1490
  return import_series.Series.fromArray(ctxBars, Array.from({ length: n }, (_, i) => test(i, n) ? 1 : 0));
1290
1491
  }
1492
+ const chartBarCount = (n) => intrabarRun ? intrabarRun.lastBarIndex + 1 : n;
1291
1493
  const lastRealtime = (i, n) => i === n - 1 && chartCtx.realtime === true;
1292
1494
  const barstate = {
1293
1495
  get isfirst() {
1294
1496
  return barFlag((i) => i === 0);
1295
1497
  },
1296
1498
  get islast() {
1297
- return barFlag((i, n) => i === n - 1);
1499
+ return barFlag((i, n) => i === chartBarCount(n) - 1);
1298
1500
  },
1299
1501
  get isconfirmed() {
1300
1502
  return barFlag((i, n) => i < n - 1 || chartCtx.lastBarConfirmed === true);
@@ -1311,7 +1513,8 @@ const barstate = {
1311
1513
  get islastconfirmedhistory() {
1312
1514
  return barFlag((i, n) => {
1313
1515
  const lastConfirmed = chartCtx.lastBarConfirmed === true && chartCtx.realtime !== true;
1314
- return i === (lastConfirmed ? n - 1 : n - 2);
1516
+ const count = chartBarCount(n);
1517
+ return i === (lastConfirmed ? count - 1 : count - 2);
1315
1518
  });
1316
1519
  }
1317
1520
  };
@@ -1600,8 +1803,11 @@ const alert = Object.assign(alertImpl, {
1600
1803
  freq_once_per_bar: "once_per_bar",
1601
1804
  freq_once_per_bar_close: "once_per_bar_close"
1602
1805
  });
1806
+ const { saveRandom: _saveRandom, restoreRandom: _restoreRandom, ...mathScript } = mathCore;
1807
+ void _saveRandom;
1808
+ void _restoreRandom;
1603
1809
  const math = {
1604
- ...mathCore,
1810
+ ...mathScript,
1605
1811
  round_to_mintick: ((number, mintick) => {
1606
1812
  const tick = mintick ?? syminfo.mintick;
1607
1813
  return number instanceof import_series.Series ? mathCore.round_to_mintick(number, tick) : mathCore.round_to_mintick(number, tick);