oakscriptjs 0.11.5 → 0.11.7

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 (42) hide show
  1. package/dist/cjs/index.d.ts +2 -1
  2. package/dist/cjs/index.d.ts.map +1 -1
  3. package/dist/cjs/index.js +5 -1
  4. package/dist/cjs/index.js.map +2 -2
  5. package/dist/cjs/runtime/inputs.d.ts.map +1 -1
  6. package/dist/cjs/runtime/inputs.js +6 -2
  7. package/dist/cjs/runtime/inputs.js.map +2 -2
  8. package/dist/cjs/script/index.d.ts +15 -1
  9. package/dist/cjs/script/index.d.ts.map +1 -1
  10. package/dist/cjs/script/index.js +27 -1
  11. package/dist/cjs/script/index.js.map +2 -2
  12. package/dist/cjs/utils/index.d.ts.map +1 -1
  13. package/dist/cjs/utils/index.js +13 -2
  14. package/dist/cjs/utils/index.js.map +2 -2
  15. package/dist/cjs/utils/source-resolver.d.ts +17 -0
  16. package/dist/cjs/utils/source-resolver.d.ts.map +1 -0
  17. package/dist/cjs/utils/source-resolver.js +45 -0
  18. package/dist/cjs/utils/source-resolver.js.map +7 -0
  19. package/dist/esm/index.d.ts +2 -1
  20. package/dist/esm/index.d.ts.map +1 -1
  21. package/dist/esm/index.js +2 -1
  22. package/dist/esm/index.js.map +1 -1
  23. package/dist/esm/runtime/inputs.d.ts.map +1 -1
  24. package/dist/esm/runtime/inputs.js +8 -2
  25. package/dist/esm/runtime/inputs.js.map +1 -1
  26. package/dist/esm/script/index.d.ts +15 -1
  27. package/dist/esm/script/index.d.ts.map +1 -1
  28. package/dist/esm/script/index.js +34 -1
  29. package/dist/esm/script/index.js.map +1 -1
  30. package/dist/esm/utils/index.d.ts.map +1 -1
  31. package/dist/esm/utils/index.js +18 -1
  32. package/dist/esm/utils/index.js.map +1 -1
  33. package/dist/esm/utils/source-resolver.d.ts +17 -0
  34. package/dist/esm/utils/source-resolver.d.ts.map +1 -0
  35. package/dist/esm/utils/source-resolver.js +24 -0
  36. package/dist/esm/utils/source-resolver.js.map +1 -0
  37. package/package.json +1 -1
  38. package/src/index.ts +2 -1
  39. package/src/runtime/inputs.ts +7 -2
  40. package/src/script/index.ts +49 -3
  41. package/src/utils/index.ts +15 -1
  42. package/src/utils/source-resolver.ts +29 -0
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type { InputConfig, InputOptions } from './types.js';
10
10
  import { getContext, recalculate } from './runtime.js';
11
+ import { externalSource } from '../utils/source-resolver.js';
11
12
 
12
13
  // Track registered inputs for idempotent registration
13
14
  const registeredInputs = new Map<string, boolean>();
@@ -403,7 +404,11 @@ function getSourceData(
403
404
  const c = ohlcv.close[i];
404
405
  return h !== undefined && l !== undefined && c !== undefined ? (o + h + l + c) / 4 : NaN;
405
406
  });
406
- default:
407
- return ohlcv.close;
407
+ default: {
408
+ // Another source (study on study), else close.
409
+ const ext = externalSource(sourceName);
410
+ if (!ext) return ohlcv.close;
411
+ return ohlcv.close.map((_, i) => (ext as ArrayLike<number>)[i] ?? NaN);
412
+ }
408
413
  }
409
414
  }
@@ -78,6 +78,7 @@ import * as taSeries from '../ta-series.js';
78
78
  import * as colorCore from '../color/index.js';
79
79
  import { inputColorDefault } from '../color/parse.js';
80
80
  import { fixnan as fixnanValues, isNA, nz } from '../utils/index.js';
81
+ import { externalSource } from '../utils/source-resolver.js';
81
82
  import * as lineCore from '../line/index.js';
82
83
  import * as labelCore from '../label/index.js';
83
84
  import * as boxCore from '../box/index.js';
@@ -136,6 +137,11 @@ export interface ScriptRunResult {
136
137
  barColorConfig: BarColorConfig[];
137
138
  /** plotcandle() / plotbar() declarations */
138
139
  candleConfig: CandleConfig[];
140
+ /**
141
+ * The plot(), plotshape(), plotchar(), plotarrow(), plotcandle() and plotbar() declarations in the order the
142
+ * script made them (the id of each in its own config). PineScript numbers a script's outputs in this order.
143
+ */
144
+ outputs: ScriptOutput[];
139
145
  defaultInputs: Record<string, unknown>;
140
146
  alertConfig: AlertConditionConfig[];
141
147
  /** Strategy properties (declared values, else the PineScript defaults) when the script declares strategy(). */
@@ -143,9 +149,10 @@ export interface ScriptRunResult {
143
149
  /**
144
150
  * The renderable output: plots keyed by plotConfig ids, plot-pair fills, markers, arrows, background and bar
145
151
  * colours, candles and bars, and the drawings (labels, lines, boxes, linefills, polylines, tables) alive after the
146
- * last bar.
152
+ * last bar. `outputValues`: per plotshape() / plotchar() / plotarrow() id, the value of its series on each bar where
153
+ * it is not na (a condition gives 1 or 0), at the bar it was computed on (the offset not applied).
147
154
  */
148
- result: IndicatorResult & { alerts?: AlertState[]; alertCalls?: AlertCall[] };
155
+ result: IndicatorResult & { alerts?: AlertState[]; alertCalls?: AlertCall[]; outputValues?: Record<string, TimeValue[]> };
149
156
  /** The script called alert() during the run (PineScript `hasAlertFunction` is known at compile time: a host that
150
157
  * needs it before any bar runs reads the source). */
151
158
  hasAlertFunction: boolean;
@@ -177,6 +184,14 @@ export interface AlertCall {
177
184
  freq: AlertFrequency;
178
185
  }
179
186
 
187
+ /** One output declaration of a script: plot(), plotshape() ('shape'), plotchar() ('char'), plotarrow() ('arrow'),
188
+ * plotcandle() ('candle') or plotbar() ('bar'), with its id in plotConfig / shapeConfig / arrowConfig / candleConfig. */
189
+ export interface ScriptOutput {
190
+ kind: 'plot' | 'shape' | 'char' | 'arrow' | 'candle' | 'bar';
191
+ id: string;
192
+ title?: string;
193
+ }
194
+
180
195
  /** Handle returned by plot()/hline() so fill() can reference them. */
181
196
  export interface PlotHandle {
182
197
  id: string;
@@ -201,6 +216,7 @@ interface Collector {
201
216
  fillConfig: FillConfig[];
202
217
  shapeConfig: ShapeConfig[];
203
218
  markers: MarkerData[];
219
+ outputValues: Record<string, TimeValue[]>;
204
220
  arrowConfig: ArrowConfig[];
205
221
  arrows: ArrowData[];
206
222
  barColorConfig: BarColorConfig[];
@@ -209,6 +225,7 @@ interface Collector {
209
225
  candleConfig: CandleConfig[];
210
226
  plotCandles: Record<string, PlotCandleData[]>;
211
227
  plotBars: Record<string, PlotBarData[]>;
228
+ outputs: ScriptOutput[];
212
229
  alertConfig: AlertConditionConfig[];
213
230
  alerts: AlertState[];
214
231
  alertCalls: AlertCall[];
@@ -257,6 +274,7 @@ function freshCollector(inputValues: Record<string, unknown>): Collector {
257
274
  fillConfig: [],
258
275
  shapeConfig: [],
259
276
  markers: [],
277
+ outputValues: {},
260
278
  arrowConfig: [],
261
279
  arrows: [],
262
280
  barColorConfig: [],
@@ -265,6 +283,7 @@ function freshCollector(inputValues: Record<string, unknown>): Collector {
265
283
  candleConfig: [],
266
284
  plotCandles: {},
267
285
  plotBars: {},
286
+ outputs: [],
268
287
  alertConfig: [],
269
288
  alerts: [],
270
289
  alertCalls: [],
@@ -343,6 +362,7 @@ export function executeScript(
343
362
  arrowConfig: c.arrowConfig,
344
363
  barColorConfig: c.barColorConfig,
345
364
  candleConfig: c.candleConfig,
365
+ outputs: c.outputs,
346
366
  defaultInputs: c.defaultInputs,
347
367
  alertConfig: c.alertConfig,
348
368
  strategyConfig: c.strategy?.properties,
@@ -351,6 +371,7 @@ export function executeScript(
351
371
  plots: c.plots,
352
372
  fills: c.fills.length ? c.fills : undefined,
353
373
  markers: c.markers.length ? c.markers : undefined,
374
+ outputValues: Object.keys(c.outputValues).length ? c.outputValues : undefined,
354
375
  arrows: c.arrows.length ? c.arrows : undefined,
355
376
  bgColors: c.bgColors.length ? c.bgColors : undefined,
356
377
  barColors: c.barColors.length ? c.barColors : undefined,
@@ -537,7 +558,13 @@ for (const s of [hl2, hlc3, ohlc4, hlcc4]) s.fullHistory = true;
537
558
  const SOURCES: Record<string, Series> = { open, high, low, close, volume, hl2, hlc3, ohlc4, hlcc4 };
538
559
 
539
560
  function sourceSeries(name: string): Series {
540
- return SOURCES[name] ?? close;
561
+ const builtin = SOURCES[name];
562
+ if (builtin) return builtin;
563
+ // Another source (study on study): its values as the host gives them now, aligned with the bars.
564
+ const ext = externalSource(name);
565
+ if (!ext) return close;
566
+ const values = Array.from(ext);
567
+ return new Series(ctxBars, (_bar, i) => values[i] ?? NaN);
541
568
  }
542
569
 
543
570
  // ── Bound ta namespace ───────────────────────────────────────────────────────
@@ -690,6 +717,7 @@ function plotImpl(series: Series, title?: string, options: ScriptPlotOptions = {
690
717
  histbase: options.histbase,
691
718
  forceOverlay: options.force_overlay,
692
719
  });
720
+ c.outputs.push({ kind: 'plot', id, title: title ?? id });
693
721
  const values = series.toArray();
694
722
  const b = bars();
695
723
  const data: TimeValue[] = [];
@@ -899,6 +927,18 @@ function markerPosition(location: MarkerLocation, shape: ShapeStyle | undefined)
899
927
  /** Default colour of plotshape / plotchar markers. */
900
928
  const MARKER_COLOR = '#2962FF';
901
929
 
930
+ /** The value of an output's series on each bar where it is not na (a condition: 1 or 0), at the bar it was computed
931
+ * on: what PineScript gives for the output in an alert message. */
932
+ function outputSeries(values: ArrayLike<unknown>, b: readonly Bar[]): TimeValue[] {
933
+ const series: TimeValue[] = [];
934
+ for (let i = 0; i < b.length; i++) {
935
+ const v = values[i];
936
+ if (typeof v === 'number' && Number.isFinite(v)) series.push({ time: b[i]!.time, value: v });
937
+ else if (typeof v === 'boolean') series.push({ time: b[i]!.time, value: v ? 1 : 0 });
938
+ }
939
+ return series;
940
+ }
941
+
902
942
  /** Shared emitter for plotshape()/plotchar(). Emits one marker per bar where
903
943
  * the condition is truthy (not na, not 0). A plotchar marker has the shape 'circle' and its character in `char`. */
904
944
  function emitMarkers(
@@ -926,6 +966,7 @@ function emitMarkers(
926
966
  offset: options.offset,
927
967
  forceOverlay: options.force_overlay,
928
968
  });
969
+ c.outputs.push({ kind, id, title });
929
970
  const shapeStyle = kind === 'shape' ? (style as ShapeStyle) : undefined;
930
971
  const template: Omit<MarkerData, 'time'> = {
931
972
  position: markerPosition(location, shapeStyle),
@@ -942,6 +983,7 @@ function emitMarkers(
942
983
  const values = condition.toArray();
943
984
  const b = bars();
944
985
  const off = options.offset ?? 0;
986
+ c.outputValues[id] = outputSeries(values, b);
945
987
  for (let i = 0; i < b.length; i++) {
946
988
  const v = values[i];
947
989
  if (v === undefined || Number.isNaN(v) || v === 0) continue;
@@ -1001,9 +1043,11 @@ export function plotarrow(series: Series, title?: string, options: PlotArrowOpti
1001
1043
  display: options.display,
1002
1044
  forceOverlay: options.force_overlay,
1003
1045
  });
1046
+ c.outputs.push({ kind: 'arrow', id, title });
1004
1047
  const values = series.toArray();
1005
1048
  const b = bars();
1006
1049
  const off = options.offset ?? 0;
1050
+ c.outputValues[id] = outputSeries(values, b);
1007
1051
  for (let i = 0; i < b.length; i++) {
1008
1052
  const v = values[i];
1009
1053
  if (v === undefined || Number.isNaN(v) || v === 0) continue;
@@ -1135,6 +1179,7 @@ export function plotcandle(
1135
1179
  display: options.display,
1136
1180
  forceOverlay: options.force_overlay,
1137
1181
  });
1182
+ c.outputs.push({ kind: 'candle', id, title });
1138
1183
  const b = bars();
1139
1184
  c.plotCandles[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
1140
1185
  const candle: PlotCandleData = { time: b[i]!.time, ...ohlc };
@@ -1171,6 +1216,7 @@ export function plotbar(
1171
1216
  display: options.display,
1172
1217
  forceOverlay: options.force_overlay,
1173
1218
  });
1219
+ c.outputs.push({ kind: 'bar', id, title });
1174
1220
  const b = bars();
1175
1221
  c.plotBars[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
1176
1222
  const bar: PlotBarData = { time: b[i]!.time, ...ohlc };
@@ -8,6 +8,7 @@ import { sma, ema, wma, rma } from '../ta/index.js';
8
8
  import { Series } from '../runtime/series.js';
9
9
  import type { SourceType } from '../input.js';
10
10
  import { isDeleted } from '../drawing/registry.js';
11
+ import { externalSource } from './source-resolver.js';
11
12
 
12
13
  /**
13
14
  * Validates that a series has sufficient data for a given length
@@ -197,6 +198,9 @@ export function getSource(
197
198
  case 'hlcc4': return ohlc.high.map((h, i) => (h + ohlc.low[i]! + ohlc.close[i]! + ohlc.close[i]!) / 4);
198
199
  case 'volume': return ohlc.volume ?? ohlc.close.map(() => NaN);
199
200
  }
201
+ const ext = externalSource(source);
202
+ if (!ext) return ohlc.close;
203
+ return ohlc.close.map((_, i) => (ext as ArrayLike<number>)[i] ?? NaN);
200
204
  } else {
201
205
  const bars = data as Bar[];
202
206
  switch (source) {
@@ -210,6 +214,10 @@ export function getSource(
210
214
  case 'hlcc4': return bars.map(b => (b.high + b.low + b.close + b.close) / 4);
211
215
  case 'volume': return bars.map(b => b.volume ?? NaN);
212
216
  }
217
+ // Another source (study on study), else close.
218
+ const ext = externalSource(source);
219
+ if (!ext) return bars.map(b => b.close);
220
+ return bars.map((_, i) => (ext as ArrayLike<number>)[i] ?? NaN);
213
221
  }
214
222
  }
215
223
 
@@ -248,7 +256,13 @@ export function getSourceSeries(
248
256
  case 'ohlc4': return open.add(high).add(low).add(close).div(4);
249
257
  case 'hlcc4': return high.add(low).add(close).add(close).div(4);
250
258
  case 'volume': return Series.fromBars(bars, 'volume');
251
- default: return close;
259
+ default: {
260
+ // Another source (study on study), else close.
261
+ const ext = externalSource(source);
262
+ if (!ext) return close;
263
+ const values = Array.from({ length: bars.length }, (_, i) => ext[i] ?? NaN);
264
+ return new Series(bars, (_bar, i) => values[i] ?? NaN);
265
+ }
252
266
  }
253
267
  }
254
268
 
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Sources other than the bar fields: a study calculated on another study's output (PineScript hosts call it "study
3
+ * on study"). A source input then holds a host name such as `"<sourceId>$<plotIndex>"`; the host resolves it to
4
+ * values aligned with the bars it passes to the calculation (`NaN` where there is no value).
5
+ *
6
+ * The host sets the resolver around a calculation and clears it after; a name it does not know falls back to
7
+ * `close`, as before.
8
+ */
9
+
10
+ /** Values of a source by name, aligned with the bars of the calculation; null/undefined = unknown name. */
11
+ export type SourceResolver = (name: string) => ArrayLike<number> | null | undefined;
12
+
13
+ let resolver: SourceResolver | null = null;
14
+
15
+ /** Built-in sources (bar fields and their averages). */
16
+ export const BUILTIN_SOURCES: ReadonlySet<string> = new Set([
17
+ 'open', 'high', 'low', 'close', 'hl2', 'hlc3', 'ohlc4', 'hlcc4', 'volume',
18
+ ]);
19
+
20
+ /** Set (or clear with null) how source names other than the built-ins are read. */
21
+ export function setSourceResolver(fn: SourceResolver | null): void {
22
+ resolver = fn;
23
+ }
24
+
25
+ /** Values of a non built-in source name, or null when no resolver knows it. */
26
+ export function externalSource(name: string): ArrayLike<number> | null {
27
+ if (!resolver || BUILTIN_SOURCES.has(name)) return null;
28
+ return resolver(name) ?? null;
29
+ }