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.
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/runtime/inputs.d.ts.map +1 -1
- package/dist/cjs/runtime/inputs.js +6 -2
- package/dist/cjs/runtime/inputs.js.map +2 -2
- package/dist/cjs/script/index.d.ts +15 -1
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +27 -1
- package/dist/cjs/script/index.js.map +2 -2
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +13 -2
- package/dist/cjs/utils/index.js.map +2 -2
- package/dist/cjs/utils/source-resolver.d.ts +17 -0
- package/dist/cjs/utils/source-resolver.d.ts.map +1 -0
- package/dist/cjs/utils/source-resolver.js +45 -0
- package/dist/cjs/utils/source-resolver.js.map +7 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/runtime/inputs.d.ts.map +1 -1
- package/dist/esm/runtime/inputs.js +8 -2
- package/dist/esm/runtime/inputs.js.map +1 -1
- package/dist/esm/script/index.d.ts +15 -1
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +34 -1
- package/dist/esm/script/index.js.map +1 -1
- package/dist/esm/utils/index.d.ts.map +1 -1
- package/dist/esm/utils/index.js +18 -1
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/source-resolver.d.ts +17 -0
- package/dist/esm/utils/source-resolver.d.ts.map +1 -0
- package/dist/esm/utils/source-resolver.js +24 -0
- package/dist/esm/utils/source-resolver.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/runtime/inputs.ts +7 -2
- package/src/script/index.ts +49 -3
- package/src/utils/index.ts +15 -1
- package/src/utils/source-resolver.ts +29 -0
package/src/runtime/inputs.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/src/script/index.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/src/utils/index.ts
CHANGED
|
@@ -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:
|
|
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
|
+
}
|