oakscriptjs 0.11.0 → 0.11.1
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/array/index.d.ts +8 -2
- package/dist/cjs/array/index.d.ts.map +1 -1
- package/dist/cjs/array/index.js +2 -1
- package/dist/cjs/array/index.js.map +2 -2
- package/dist/cjs/box/index.js +6 -6
- package/dist/cjs/box/index.js.map +2 -2
- package/dist/cjs/callsite/index.js +1 -1
- package/dist/cjs/callsite/index.js.map +2 -2
- package/dist/cjs/drawing/registry.d.ts +15 -6
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +16 -2
- package/dist/cjs/drawing/registry.js.map +2 -2
- package/dist/cjs/index.d.ts +3 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/input.d.ts +9 -1
- package/dist/cjs/input.d.ts.map +1 -1
- package/dist/cjs/input.js +16 -0
- package/dist/cjs/input.js.map +2 -2
- package/dist/cjs/label/index.js +3 -3
- package/dist/cjs/label/index.js.map +2 -2
- package/dist/cjs/line/index.d.ts +10 -27
- package/dist/cjs/line/index.d.ts.map +1 -1
- package/dist/cjs/line/index.js +10 -27
- package/dist/cjs/line/index.js.map +2 -2
- package/dist/cjs/linefill/index.d.ts +3 -0
- package/dist/cjs/linefill/index.d.ts.map +1 -1
- package/dist/cjs/linefill/index.js +15 -1
- package/dist/cjs/linefill/index.js.map +3 -3
- package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
- package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
- package/dist/cjs/runtime/index.d.ts +1 -1
- package/dist/cjs/runtime/index.d.ts.map +1 -1
- package/dist/cjs/runtime/index.js +1 -0
- package/dist/cjs/runtime/index.js.map +2 -2
- package/dist/cjs/runtime/inputs.d.ts +9 -0
- package/dist/cjs/runtime/inputs.d.ts.map +1 -1
- package/dist/cjs/runtime/inputs.js +8 -0
- package/dist/cjs/runtime/inputs.js.map +2 -2
- package/dist/cjs/runtime/types.d.ts +1 -1
- package/dist/cjs/runtime/types.d.ts.map +1 -1
- package/dist/cjs/runtime/types.js.map +1 -1
- package/dist/cjs/script/index.d.ts +65 -12
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +168 -19
- package/dist/cjs/script/index.js.map +3 -3
- package/dist/cjs/security/other.d.ts +92 -0
- package/dist/cjs/security/other.d.ts.map +1 -0
- package/dist/cjs/security/other.js +164 -0
- package/dist/cjs/security/other.js.map +7 -0
- package/dist/cjs/session/bars.d.ts +10 -1
- package/dist/cjs/session/bars.d.ts.map +1 -1
- package/dist/cjs/session/bars.js +16 -2
- package/dist/cjs/session/bars.js.map +2 -2
- package/dist/cjs/session/calendar.d.ts +4 -1
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +11 -7
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/session/context.d.ts +2 -1
- package/dist/cjs/session/context.d.ts.map +1 -1
- package/dist/cjs/session/context.js +2 -1
- package/dist/cjs/session/context.js.map +2 -2
- package/dist/cjs/str/floatingdecimal.d.ts +31 -0
- package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
- package/dist/cjs/str/floatingdecimal.js +298 -0
- package/dist/cjs/str/floatingdecimal.js.map +7 -0
- package/dist/cjs/str/index.d.ts +4 -3
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +1 -1
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +6 -4
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +3 -2
- package/dist/cjs/str/numberformat.js.map +2 -2
- package/dist/cjs/ta/index.d.ts +34 -17
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +132 -116
- package/dist/cjs/ta/index.js.map +2 -2
- package/dist/cjs/ta/limits.d.ts +11 -0
- package/dist/cjs/ta/limits.d.ts.map +1 -1
- package/dist/cjs/ta/limits.js +15 -1
- package/dist/cjs/ta/limits.js.map +2 -2
- package/dist/cjs/ta/running-sum.js +1 -1
- package/dist/cjs/ta/running-sum.js.map +2 -2
- package/dist/cjs/ta-series.d.ts +1 -1
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +1 -1
- package/dist/cjs/ta-series.js.map +2 -2
- package/dist/cjs/table/index.d.ts +3 -1
- package/dist/cjs/table/index.d.ts.map +1 -1
- package/dist/cjs/table/index.js +1 -0
- package/dist/cjs/table/index.js.map +3 -3
- package/dist/cjs/time/session.d.ts +9 -0
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +42 -14
- package/dist/cjs/time/session.js.map +3 -3
- package/dist/cjs/time/timezone.d.ts +6 -1
- package/dist/cjs/time/timezone.d.ts.map +1 -1
- package/dist/cjs/time/timezone.js +2 -2
- package/dist/cjs/time/timezone.js.map +2 -2
- package/dist/cjs/types/metadata.d.ts +1 -1
- package/dist/cjs/types/metadata.d.ts.map +1 -1
- package/dist/cjs/types/metadata.js.map +1 -1
- package/dist/esm/array/index.d.ts +8 -2
- package/dist/esm/array/index.d.ts.map +1 -1
- package/dist/esm/array/index.js +12 -3
- package/dist/esm/array/index.js.map +1 -1
- package/dist/esm/box/index.js +6 -6
- package/dist/esm/box/index.js.map +1 -1
- package/dist/esm/callsite/index.js +1 -1
- package/dist/esm/callsite/index.js.map +1 -1
- package/dist/esm/drawing/registry.d.ts +15 -6
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +30 -7
- package/dist/esm/drawing/registry.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/input.d.ts +9 -1
- package/dist/esm/input.d.ts.map +1 -1
- package/dist/esm/input.js +16 -0
- package/dist/esm/input.js.map +1 -1
- package/dist/esm/label/index.js +3 -3
- package/dist/esm/label/index.js.map +1 -1
- package/dist/esm/line/index.d.ts +10 -27
- package/dist/esm/line/index.d.ts.map +1 -1
- package/dist/esm/line/index.js +20 -64
- package/dist/esm/line/index.js.map +1 -1
- package/dist/esm/linefill/index.d.ts +3 -0
- package/dist/esm/linefill/index.d.ts.map +1 -1
- package/dist/esm/linefill/index.js +20 -1
- package/dist/esm/linefill/index.js.map +1 -1
- package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
- package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
- package/dist/esm/runtime/index.d.ts +1 -1
- package/dist/esm/runtime/index.d.ts.map +1 -1
- package/dist/esm/runtime/index.js +1 -1
- package/dist/esm/runtime/index.js.map +1 -1
- package/dist/esm/runtime/inputs.d.ts +9 -0
- package/dist/esm/runtime/inputs.d.ts.map +1 -1
- package/dist/esm/runtime/inputs.js +15 -0
- package/dist/esm/runtime/inputs.js.map +1 -1
- package/dist/esm/runtime/types.d.ts +1 -1
- package/dist/esm/runtime/types.d.ts.map +1 -1
- package/dist/esm/script/index.d.ts +65 -12
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +217 -29
- package/dist/esm/script/index.js.map +1 -1
- package/dist/esm/security/other.d.ts +92 -0
- package/dist/esm/security/other.d.ts.map +1 -0
- package/dist/esm/security/other.js +211 -0
- package/dist/esm/security/other.js.map +1 -0
- package/dist/esm/session/bars.d.ts +10 -1
- package/dist/esm/session/bars.d.ts.map +1 -1
- package/dist/esm/session/bars.js +26 -1
- package/dist/esm/session/bars.js.map +1 -1
- package/dist/esm/session/calendar.d.ts +4 -1
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +19 -7
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/session/context.d.ts +2 -1
- package/dist/esm/session/context.d.ts.map +1 -1
- package/dist/esm/session/context.js +5 -3
- package/dist/esm/session/context.js.map +1 -1
- package/dist/esm/str/floatingdecimal.d.ts +31 -0
- package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
- package/dist/esm/str/floatingdecimal.js +272 -0
- package/dist/esm/str/floatingdecimal.js.map +1 -0
- package/dist/esm/str/index.d.ts +4 -3
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +6 -5
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +6 -4
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +9 -5
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +34 -17
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +155 -185
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/limits.d.ts +11 -0
- package/dist/esm/ta/limits.d.ts.map +1 -1
- package/dist/esm/ta/limits.js +27 -2
- package/dist/esm/ta/limits.js.map +1 -1
- package/dist/esm/ta/running-sum.js +1 -1
- package/dist/esm/ta/running-sum.js.map +1 -1
- package/dist/esm/ta-series.d.ts +1 -1
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +2 -1
- package/dist/esm/ta-series.js.map +1 -1
- package/dist/esm/table/index.d.ts +3 -1
- package/dist/esm/table/index.d.ts.map +1 -1
- package/dist/esm/table/index.js +6 -1
- package/dist/esm/table/index.js.map +1 -1
- package/dist/esm/time/session.d.ts +9 -0
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +53 -15
- package/dist/esm/time/session.js.map +1 -1
- package/dist/esm/time/timezone.d.ts +6 -1
- package/dist/esm/time/timezone.d.ts.map +1 -1
- package/dist/esm/time/timezone.js +8 -2
- package/dist/esm/time/timezone.js.map +1 -1
- package/dist/esm/types/metadata.d.ts +1 -1
- package/dist/esm/types/metadata.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/array/index.ts +11 -3
- package/src/box/index.ts +6 -6
- package/src/callsite/index.ts +1 -1
- package/src/drawing/registry.ts +30 -7
- package/src/index.ts +11 -1
- package/src/input.ts +18 -1
- package/src/label/index.ts +3 -3
- package/src/line/index.ts +20 -68
- package/src/linefill/index.ts +20 -1
- package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
- package/src/runtime/index.ts +1 -0
- package/src/runtime/inputs.ts +16 -0
- package/src/runtime/types.ts +1 -1
- package/src/script/index.ts +292 -30
- package/src/security/other.ts +269 -0
- package/src/session/bars.ts +24 -1
- package/src/session/calendar.ts +23 -7
- package/src/session/context.ts +5 -3
- package/src/str/floatingdecimal.ts +268 -0
- package/src/str/index.ts +6 -5
- package/src/str/numberformat.ts +11 -6
- package/src/ta/index.ts +157 -183
- package/src/ta/limits.ts +28 -1
- package/src/ta/running-sum.ts +1 -1
- package/src/ta-series.ts +3 -2
- package/src/table/index.ts +4 -1
- package/src/time/session.ts +58 -16
- package/src/time/timezone.ts +9 -2
- package/src/types/metadata.ts +1 -1
package/dist/esm/script/index.js
CHANGED
|
@@ -55,11 +55,12 @@ import * as strCore from '../str/index.js';
|
|
|
55
55
|
import { formatMessage } from '../str/messageformat.js';
|
|
56
56
|
import { tickScale } from '../str/numberformat.js';
|
|
57
57
|
import { TradingCalendar } from '../session/calendar.js';
|
|
58
|
-
import { SessionBars, sessionFlags } from '../session/bars.js';
|
|
58
|
+
import { SessionBars, sessionFlags, strayPeriods } from '../session/bars.js';
|
|
59
59
|
import { afterCompletion, heikinAshi, mapToChart, periodsOf, resample } from '../security/resample.js';
|
|
60
|
+
import { securityLowerTf, securityOther } from '../security/other.js';
|
|
60
61
|
import { position } from '../types/index.js';
|
|
61
62
|
import { STRATEGY_CONSTANTS, STRATEGY_DEFAULTS, STRATEGY_NUMBER_VARIABLES, STRATEGY_TEXT_VARIABLES, } from '../strategy/index.js';
|
|
62
|
-
import { parsePineSession } from '../time/session.js';
|
|
63
|
+
import { parsePineSession, sessionRun } from '../time/session.js';
|
|
63
64
|
/** Bars for the current run. Module-level and REUSED across runs so builtins
|
|
64
65
|
* and every Series derived from them keep their version-keyed memoization. */
|
|
65
66
|
const ctxBars = new BarData([]);
|
|
@@ -126,8 +127,11 @@ export function executeScript(body, barsInput, inputs = {}, chart = {}, options
|
|
|
126
127
|
ctx = freshCollector(inputs);
|
|
127
128
|
resetDrawings();
|
|
128
129
|
mathCore.resetRandom();
|
|
130
|
+
missingSymbols = [];
|
|
129
131
|
try {
|
|
130
132
|
body();
|
|
133
|
+
if (missingSymbols.length)
|
|
134
|
+
throw new MissingSymbolDataError(missingSymbols);
|
|
131
135
|
const c = ctx;
|
|
132
136
|
return {
|
|
133
137
|
metadata: {
|
|
@@ -254,6 +258,10 @@ export const input = {
|
|
|
254
258
|
session(defval, title, opts = {}) {
|
|
255
259
|
return registerInput({ type: 'session', defval, title, options: opts.options, ...common(opts) }, 'input_session');
|
|
256
260
|
},
|
|
261
|
+
/** Ticker id ("NASDAQ:MSFT"), for `request.security` of another symbol (#180). */
|
|
262
|
+
symbol(defval, title, opts = {}) {
|
|
263
|
+
return registerInput({ type: 'symbol', defval, title, options: opts.options, ...common(opts) }, 'input_symbol');
|
|
264
|
+
},
|
|
257
265
|
/** Date and time as a UNIX time in milliseconds, e.g. `input.time(timestamp('2024-01-01'), 'Start')`. */
|
|
258
266
|
time(defval, title, opts = {}) {
|
|
259
267
|
const v = registerInput({ type: 'time', defval, title, ...common(opts) }, 'input_time');
|
|
@@ -317,12 +325,12 @@ export const ta = {
|
|
|
317
325
|
lowestbars: (sourceOrLength, length) => typeof sourceOrLength === 'number'
|
|
318
326
|
? taSeries.lowestbars(low, sourceOrLength)
|
|
319
327
|
: taSeries.lowestbars(sourceOrLength, length),
|
|
320
|
-
/** `ta.vwap(source, anchor?, stdev_mult?)` with the chart volume; the default anchor is a new trading
|
|
321
|
-
*
|
|
322
|
-
*
|
|
328
|
+
/** `ta.vwap(source, anchor?, stdev_mult?)` with the chart volume; the default anchor is a new trading day
|
|
329
|
+
* and the first bar: as in PineScript, the first loaded bars (a day in progress) have a value. On D / W / M
|
|
330
|
+
* charts every bar is a new day (no time zone or session needed); on intraday charts it needs the chart context
|
|
331
|
+
* (timeframe, time zone, session). */
|
|
323
332
|
vwap: ((source, anchor, stdev_mult) => {
|
|
324
|
-
|
|
325
|
-
const a = anchor ?? changeOf('1D', true);
|
|
333
|
+
const a = anchor ?? vwapAnchor();
|
|
326
334
|
return stdev_mult === undefined ? taSeries.vwap(source, volume, a) : taSeries.vwapBands(source, volume, a, stdev_mult);
|
|
327
335
|
}),
|
|
328
336
|
/** `ta.mfi(source, length)` with the chart volume (PineScript takes 2 arguments). */
|
|
@@ -818,7 +826,7 @@ export function eachBar(fn) {
|
|
|
818
826
|
if (pendingError && pendingError.bar === idx)
|
|
819
827
|
throw new Error(historyError(idx, pendingError.barsBack));
|
|
820
828
|
currentBar = idx;
|
|
821
|
-
setDrawingBar(idx, idx === b.length - 1);
|
|
829
|
+
setDrawingBar(idx, idx === b.length - 1, b.length - 1);
|
|
822
830
|
const r = fn(ctx);
|
|
823
831
|
out[idx] = typeof r === 'number' ? r : typeof r === 'boolean' ? (r ? 1 : 0) : NaN;
|
|
824
832
|
// grow the history buffers from the largest reference of this bar
|
|
@@ -1018,6 +1026,21 @@ for (const name of [...STRATEGY_NUMBER_VARIABLES, ...STRATEGY_TEXT_VARIABLES]) {
|
|
|
1018
1026
|
* ```
|
|
1019
1027
|
*/
|
|
1020
1028
|
export const strategy = Object.defineProperties(Object.assign((title, options) => declareStrategy(title, options), strategyMembers), variableGetters);
|
|
1029
|
+
/**
|
|
1030
|
+
* Thrown by `executeScript` when `request.security` asked for bars of another symbol that `ChartContext.bars` did not
|
|
1031
|
+
* give (#179). `requests` lists them all, once each: load them, then run the script again.
|
|
1032
|
+
*/
|
|
1033
|
+
export class MissingSymbolDataError extends Error {
|
|
1034
|
+
requests;
|
|
1035
|
+
constructor(requests) {
|
|
1036
|
+
super(`request.security needs the bars of ${requests.map((r) => `${r.symbol} (${r.timeframe})`).join(', ')}: ` +
|
|
1037
|
+
'pass them with ChartContext.bars.');
|
|
1038
|
+
this.requests = requests;
|
|
1039
|
+
this.name = 'MissingSymbolDataError';
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
/** Requests of other symbols without bars in the current run. */
|
|
1043
|
+
let missingSymbols = [];
|
|
1021
1044
|
let chartCtx = {};
|
|
1022
1045
|
function chartTimeframe() {
|
|
1023
1046
|
collector();
|
|
@@ -1113,8 +1136,8 @@ function symbolScale() {
|
|
|
1113
1136
|
}
|
|
1114
1137
|
/**
|
|
1115
1138
|
* PineScript `syminfo.timezone`, `syminfo.tickerid`, `syminfo.session` (session type), `syminfo.mintick`,
|
|
1116
|
-
* `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue`, `syminfo.mincontract`, `syminfo.type
|
|
1117
|
-
* `syminfo.currency`, from the chart context.
|
|
1139
|
+
* `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue`, `syminfo.mincontract`, `syminfo.type`,
|
|
1140
|
+
* `syminfo.currency`, `syminfo.basecurrency` and `syminfo.root`, from the chart context.
|
|
1118
1141
|
*/
|
|
1119
1142
|
export const syminfo = {
|
|
1120
1143
|
get mintick() {
|
|
@@ -1152,6 +1175,12 @@ export const syminfo = {
|
|
|
1152
1175
|
get currency() {
|
|
1153
1176
|
return chartText('currency', 'syminfo.currency');
|
|
1154
1177
|
},
|
|
1178
|
+
get basecurrency() {
|
|
1179
|
+
return chartText('basecurrency', 'syminfo.basecurrency');
|
|
1180
|
+
},
|
|
1181
|
+
get root() {
|
|
1182
|
+
return chartText('root', 'syminfo.root');
|
|
1183
|
+
},
|
|
1155
1184
|
};
|
|
1156
1185
|
function calendar(fn) {
|
|
1157
1186
|
return ((time, timezone) => {
|
|
@@ -1233,7 +1262,7 @@ function sessionBars() {
|
|
|
1233
1262
|
* session. As in PineScript, an explicit time zone reads the symbol session hours in that zone without
|
|
1234
1263
|
* corrections and holidays, and an explicit session follows PineScript's session string rules.
|
|
1235
1264
|
*/
|
|
1236
|
-
function calendarFor(session, timezone) {
|
|
1265
|
+
function calendarFor(session, timezone, dayPlacement = false) {
|
|
1237
1266
|
const { bars } = sessionBars();
|
|
1238
1267
|
if (!session && !timezone)
|
|
1239
1268
|
return { bars, test: false };
|
|
@@ -1247,7 +1276,7 @@ function calendarFor(session, timezone) {
|
|
|
1247
1276
|
}
|
|
1248
1277
|
const spec = session ? { session } : { session: toSpec(chartCtx.session).session };
|
|
1249
1278
|
return {
|
|
1250
|
-
bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol'), bars.chart.period),
|
|
1279
|
+
bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol', dayPlacement), bars.chart.period),
|
|
1251
1280
|
test: true,
|
|
1252
1281
|
// a PineScript session string: the rules of time.inSession (runs ending at 0000, days around a clock change)
|
|
1253
1282
|
inside: session
|
|
@@ -1265,17 +1294,48 @@ function callableSeries(series, fn) {
|
|
|
1265
1294
|
Object.assign(fn, series);
|
|
1266
1295
|
return fn;
|
|
1267
1296
|
}
|
|
1297
|
+
/**
|
|
1298
|
+
* `time(tf, session)` / `time_close(tf, session)` of a bar, as PineScript computes them (#173): an intraday timeframe
|
|
1299
|
+
* tests the bar with the session rules of time.inSession and aligns its periods on the start of the run that
|
|
1300
|
+
* contains the bar; "D" tests the bar against the day's session period (the calendar); a timeframe of more than one
|
|
1301
|
+
* day ("2D", "W", "M") has no test and keeps the period's time.
|
|
1302
|
+
*/
|
|
1303
|
+
function sessionPeriods(timeframeString, session, timezone) {
|
|
1304
|
+
const tf = timeframeCore.info(timeframeString);
|
|
1305
|
+
const { bars, test, inside } = calendarFor(session, timezone, !tf.isintraday);
|
|
1306
|
+
const open = (t) => bars.timeOf(t, timeframeString);
|
|
1307
|
+
const close = (t) => bars.closeOf(t, timeframeString);
|
|
1308
|
+
if (!test)
|
|
1309
|
+
return { open, close };
|
|
1310
|
+
if (tf.isintraday) {
|
|
1311
|
+
if (session !== undefined && parsePineSession(session) !== null && !bars.chart.isdwm) {
|
|
1312
|
+
const tz = timezone ?? exchangeTimezone();
|
|
1313
|
+
const barMs = timeframeCore.in_seconds(bars.chart.period) * 1000;
|
|
1314
|
+
const ms = timeframeCore.in_seconds(tf.period) * 1000;
|
|
1315
|
+
const run = (t) => sessionRun(t, session, tz, barMs);
|
|
1316
|
+
const start = (t, r) => r[0] + Math.floor((t - r[0]) / ms) * ms;
|
|
1317
|
+
return {
|
|
1318
|
+
open: (t) => { const r = run(t); return r ? start(t, r) : NaN; },
|
|
1319
|
+
close: (t) => { const r = run(t); return r ? Math.min(start(t, r) + ms, r[1]) : NaN; },
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
const isIn = (t) => (inside ? inside(t) : bars.inside(t));
|
|
1323
|
+
return { open: (t) => (isIn(t) ? open(t) : NaN), close: (t) => (isIn(t) ? close(t) : NaN) };
|
|
1324
|
+
}
|
|
1325
|
+
if (tf.isdaily && tf.multiplier === 1) {
|
|
1326
|
+
return { open: (t) => (bars.inside(t) ? open(t) : NaN), close: (t) => (bars.inside(t) ? close(t) : NaN) };
|
|
1327
|
+
}
|
|
1328
|
+
return { open, close };
|
|
1329
|
+
}
|
|
1268
1330
|
function timeFunction(tf, session, timezone) {
|
|
1269
1331
|
collector();
|
|
1270
|
-
const {
|
|
1271
|
-
|
|
1272
|
-
return perBar((t) => (test && !(inside ? inside(t) : bars.inside(t)) ? NaN : bars.timeOf(t, timeframeString)));
|
|
1332
|
+
const { open } = sessionPeriods(tf || chartTimeframe().period, session, timezone);
|
|
1333
|
+
return perBar((t) => open(t));
|
|
1273
1334
|
}
|
|
1274
1335
|
function timeCloseFunction(tf, session, timezone) {
|
|
1275
1336
|
collector();
|
|
1276
|
-
const {
|
|
1277
|
-
|
|
1278
|
-
return perBar((t) => (test && !(inside ? inside(t) : bars.inside(t)) ? NaN : bars.closeOf(t, timeframeString)));
|
|
1337
|
+
const { close } = sessionPeriods(tf || chartTimeframe().period, session, timezone);
|
|
1338
|
+
return perBar((t) => close(t));
|
|
1279
1339
|
}
|
|
1280
1340
|
/**
|
|
1281
1341
|
* PineScript `time`: the bar open time in ms (a Series), and `time(timeframe?, session?, timezone?)`:
|
|
@@ -1299,6 +1359,27 @@ function changeOf(tf, firstBar = false) {
|
|
|
1299
1359
|
const changed = (a, b) => !Number.isNaN(a) && !Number.isNaN(b) && a !== b;
|
|
1300
1360
|
return perBar((t, i, times) => ((i > 0 ? changed(bars.timeOf(t, tf), bars.timeOf(times[i - 1], tf)) : firstBar) ? 1 : 0));
|
|
1301
1361
|
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Default anchor of `ta.vwap` (#173, #178): the first bar and each bar whose trading day differs from the last bar
|
|
1364
|
+
* that has one. Bars after an early close have no day: the first of them continues the day before it, each further
|
|
1365
|
+
* one starts a new sum, and the next day starts on its first bar (where `timeframe.change("D")` is false). On
|
|
1366
|
+
* D / W / M charts every bar.
|
|
1367
|
+
*/
|
|
1368
|
+
function vwapAnchor() {
|
|
1369
|
+
if (chartTimeframe().isdwm)
|
|
1370
|
+
return barFlag(() => true);
|
|
1371
|
+
const { bars } = sessionBars();
|
|
1372
|
+
let last = NaN;
|
|
1373
|
+
let previous = NaN;
|
|
1374
|
+
return perBar((t, i) => {
|
|
1375
|
+
const day = bars.timeOf(t, '1D');
|
|
1376
|
+
const isNew = i === 0 || (Number.isNaN(day) ? Number.isNaN(previous) : day !== last);
|
|
1377
|
+
if (!Number.isNaN(day))
|
|
1378
|
+
last = day;
|
|
1379
|
+
previous = day;
|
|
1380
|
+
return isNew ? 1 : 0;
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1302
1383
|
function flag(key) {
|
|
1303
1384
|
const { bars, regular } = sessionBars();
|
|
1304
1385
|
const values = sessionFlags(ctxBars.bars.map(barTime), bars, regular)[key];
|
|
@@ -1374,26 +1455,31 @@ function valuesOf(value, length) {
|
|
|
1374
1455
|
return value.toArray().slice();
|
|
1375
1456
|
}
|
|
1376
1457
|
/**
|
|
1377
|
-
* PineScript `request.security`
|
|
1378
|
-
* `ticker.standard(...)`)
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1458
|
+
* PineScript `request.security` on a timeframe equal to or higher than the chart's. For the chart symbol
|
|
1459
|
+
* (`syminfo.tickerid`, `""`, `ticker.heikinashi(...)`, `ticker.standard(...)`) the higher-timeframe bars are built by
|
|
1460
|
+
* grouping the chart bars; another symbol's bars come from `ChartContext.bars` (#179) and are merged on the chart bars
|
|
1461
|
+
* by close time (lookahead off) or by open time / trading day (lookahead on), see `securityOther`. `expression` runs
|
|
1462
|
+
* on those bars (its `ta.*` calls, tuples, `timeframe.*` and `syminfo.*` see them). A lower timeframe throws.
|
|
1463
|
+
*
|
|
1464
|
+
* A symbol that does not exist (bars []) stops the script with "Invalid symbol: X", or gives na with
|
|
1465
|
+
* `ignore_invalid_symbol`.
|
|
1381
1466
|
*
|
|
1382
1467
|
* PineScript rules: lookahead off shows a period's value from the
|
|
1383
1468
|
* bar that completes it; lookahead on from its first bar; gaps on keeps only the bar where the value appears.
|
|
1384
1469
|
* Built bars can differ from the exchange's bars (official open / close, volume).
|
|
1385
1470
|
*/
|
|
1386
|
-
function security(symbol, timeframe, expression, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off) {
|
|
1471
|
+
function security(symbol, timeframe, expression, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off, ignore_invalid_symbol = false) {
|
|
1387
1472
|
collector();
|
|
1388
1473
|
const heikin = symbol.startsWith(HEIKIN_ASHI);
|
|
1389
1474
|
const base = heikin ? symbol.slice(HEIKIN_ASHI.length) : symbol;
|
|
1390
|
-
if (base !== '' && base !== chartCtx.tickerid) {
|
|
1391
|
-
throw new Error(`request.security: "${symbol}" is not the chart symbol; other symbols need external data.`);
|
|
1392
|
-
}
|
|
1393
1475
|
const chart = chartTimeframe();
|
|
1394
1476
|
const tf = timeframeCore.info(timeframe || chart.period).period;
|
|
1477
|
+
if (base !== '' && base !== chartCtx.tickerid) {
|
|
1478
|
+
return externalSecurity(base, heikin, tf, expression, gaps, lookahead, ignore_invalid_symbol, false);
|
|
1479
|
+
}
|
|
1395
1480
|
if (timeframeCore.in_seconds(tf) < timeframeCore.in_seconds(chart.period)) {
|
|
1396
|
-
|
|
1481
|
+
// a lower timeframe of the chart symbol: its bars come from ChartContext.bars (#181)
|
|
1482
|
+
return externalSecurity(chartCtx.tickerid ?? '', heikin, tf, expression, gaps, lookahead, ignore_invalid_symbol, true);
|
|
1397
1483
|
}
|
|
1398
1484
|
const chartBars = ctxBars.bars;
|
|
1399
1485
|
const times = chartBars.map(barTime);
|
|
@@ -1415,6 +1501,8 @@ function security(symbol, timeframe, expression, gaps = barmerge.gaps_off, looka
|
|
|
1415
1501
|
const periods = at ? calendar.calendar.periods(at.date) : [];
|
|
1416
1502
|
return periods.length ? calendar.timeOf(periods[0][0], tf) : starts[i];
|
|
1417
1503
|
});
|
|
1504
|
+
const tfInfo = timeframeCore.info(tf);
|
|
1505
|
+
strayPeriods(starts, dayPeriods, times, loaded, tfInfo.isdwm ? null : timeframeCore.in_seconds(tf) * 1000);
|
|
1418
1506
|
const reaches = times.map((t) => loaded.closeOf(t, chart.period) >= calendar.periodEnd(t, tf));
|
|
1419
1507
|
({ group, latest } = periodsOf(starts, dayPeriods, reaches));
|
|
1420
1508
|
const unit = chartCtx.timeUnit === 'ms' ? 1 : 1000;
|
|
@@ -1442,8 +1530,108 @@ function security(symbol, timeframe, expression, gaps = barmerge.gaps_off, looka
|
|
|
1442
1530
|
const mapped = values.map((v) => Series.fromArray(ctxBars, mapToChart(v, group, latest, lookahead === barmerge.lookahead_on, gaps === barmerge.gaps_on)));
|
|
1443
1531
|
return (Array.isArray(result) ? mapped : mapped[0]);
|
|
1444
1532
|
}
|
|
1445
|
-
/**
|
|
1446
|
-
|
|
1533
|
+
/**
|
|
1534
|
+
* Runs `expression` on the bars of `symbol` / `tf` given by `ChartContext.bars` (#179, #181). Bars not given: the
|
|
1535
|
+
* request is recorded for the {@link MissingSymbolDataError} and the expression runs on no bar. `own`: the chart
|
|
1536
|
+
* symbol, whose calendar defaults to the chart's.
|
|
1537
|
+
*/
|
|
1538
|
+
function runExternal(symbol, heikin, tf, expression, ignoreInvalid, own) {
|
|
1539
|
+
let bars = chartCtx.bars?.(symbol, tf);
|
|
1540
|
+
if (bars === undefined && !missingSymbols.some((r) => r.symbol === symbol && r.timeframe === tf)) {
|
|
1541
|
+
missingSymbols.push({ symbol, timeframe: tf });
|
|
1542
|
+
}
|
|
1543
|
+
if (bars !== undefined && bars.length === 0 && !ignoreInvalid)
|
|
1544
|
+
throw new Error(`Invalid symbol: ${symbol}`);
|
|
1545
|
+
bars ??= [];
|
|
1546
|
+
if (heikin)
|
|
1547
|
+
bars = heikinAshi(bars);
|
|
1548
|
+
const given = chartCtx.symbolInfo?.(symbol);
|
|
1549
|
+
const info = own ? { ...chartCtx, ...given } : (given ?? {});
|
|
1550
|
+
const chartBars = ctxBars.bars;
|
|
1551
|
+
const savedCtx = chartCtx;
|
|
1552
|
+
let result;
|
|
1553
|
+
let values;
|
|
1554
|
+
ctxBars.setAll(bars);
|
|
1555
|
+
// the expression sees that symbol: its ticker, timeframe, calendar and properties
|
|
1556
|
+
chartCtx = {
|
|
1557
|
+
...chartCtx,
|
|
1558
|
+
tickerid: symbol,
|
|
1559
|
+
timeframe: tf,
|
|
1560
|
+
timezone: info.timezone,
|
|
1561
|
+
session: info.session,
|
|
1562
|
+
regularSession: info.regularSession,
|
|
1563
|
+
mintick: info.mintick,
|
|
1564
|
+
minmove: info.minmove,
|
|
1565
|
+
pricescale: info.pricescale,
|
|
1566
|
+
pointvalue: info.pointvalue,
|
|
1567
|
+
mincontract: info.mincontract,
|
|
1568
|
+
symbolType: info.symbolType,
|
|
1569
|
+
currency: info.currency,
|
|
1570
|
+
basecurrency: info.basecurrency,
|
|
1571
|
+
root: info.root,
|
|
1572
|
+
};
|
|
1573
|
+
try {
|
|
1574
|
+
result = expression();
|
|
1575
|
+
const parts = Array.isArray(result) ? result : [result];
|
|
1576
|
+
values = parts.map((p) => valuesOf(p, bars.length));
|
|
1577
|
+
}
|
|
1578
|
+
finally {
|
|
1579
|
+
ctxBars.setAll(chartBars);
|
|
1580
|
+
chartCtx = savedCtx;
|
|
1581
|
+
}
|
|
1582
|
+
return { bars, info, result, values };
|
|
1583
|
+
}
|
|
1584
|
+
/** The chart for the merge functions of `security/other`. */
|
|
1585
|
+
function mergeChart() {
|
|
1586
|
+
return {
|
|
1587
|
+
timeframe: chartTimeframe().period,
|
|
1588
|
+
timezone: chartCtx.timezone,
|
|
1589
|
+
session: chartCtx.session,
|
|
1590
|
+
timeUnit: chartCtx.timeUnit,
|
|
1591
|
+
// the realtime rule applies to a last bar that receives live updates
|
|
1592
|
+
lastBarConfirmed: chartCtx.realtime !== true,
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* `request.security` of another symbol (#179), or of the chart symbol on a lower timeframe (#181): its bars from
|
|
1597
|
+
* `ChartContext.bars`, merged by `securityOther`.
|
|
1598
|
+
*/
|
|
1599
|
+
function externalSecurity(symbol, heikin, tf, expression, gaps, lookahead, ignoreInvalid, own) {
|
|
1600
|
+
const { bars, info, result, values } = runExternal(symbol, heikin, tf, expression, ignoreInvalid, own);
|
|
1601
|
+
const index = securityOther(ctxBars.bars, bars, tf, mergeChart(), {
|
|
1602
|
+
gaps: gaps === barmerge.gaps_on,
|
|
1603
|
+
lookahead: lookahead === barmerge.lookahead_on,
|
|
1604
|
+
other: info,
|
|
1605
|
+
});
|
|
1606
|
+
const mapped = values.map((v) => Series.fromArray(ctxBars, index.map((j) => (j < 0 ? NaN : (v[j] ?? NaN)))));
|
|
1607
|
+
return (Array.isArray(result) ? mapped : mapped[0]);
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* PineScript `request.security_lower_tf(symbol, timeframe, expression, ignore_invalid_symbol)` (#181): for each
|
|
1611
|
+
* chart bar, the array of the values of `expression` on the bars of the lower timeframe that the chart bar contains
|
|
1612
|
+
* (those whose close is after the open of the chart bar and at or before its close; empty when there are none).
|
|
1613
|
+
* The bars come from `ChartContext.bars`, for the chart symbol (`""`, `syminfo.tickerid`) as for another symbol.
|
|
1614
|
+
* Returns one array per chart bar (oldest first), or one such list per tuple part.
|
|
1615
|
+
* @throws Error when `timeframe` is higher than the chart timeframe
|
|
1616
|
+
*/
|
|
1617
|
+
function security_lower_tf(symbol, timeframe, expression, ignore_invalid_symbol = false) {
|
|
1618
|
+
collector();
|
|
1619
|
+
const heikin = symbol.startsWith(HEIKIN_ASHI);
|
|
1620
|
+
const base = heikin ? symbol.slice(HEIKIN_ASHI.length) : symbol;
|
|
1621
|
+
const chart = chartTimeframe();
|
|
1622
|
+
const tf = timeframeCore.info(timeframe || chart.period).period;
|
|
1623
|
+
if (timeframeCore.in_seconds(tf) > timeframeCore.in_seconds(chart.period)) {
|
|
1624
|
+
throw new Error(`request.security_lower_tf: timeframe "${tf}" is higher than the chart timeframe "${chart.period}".`);
|
|
1625
|
+
}
|
|
1626
|
+
const own = base === '' || base === chartCtx.tickerid;
|
|
1627
|
+
const ticker = own ? (chartCtx.tickerid ?? '') : base;
|
|
1628
|
+
const { bars, info, result, values } = runExternal(ticker, heikin, tf, expression, ignore_invalid_symbol, own);
|
|
1629
|
+
const ranges = securityLowerTf(ctxBars.bars, bars, tf, mergeChart(), { other: info });
|
|
1630
|
+
const mapped = values.map((v) => ranges.map(([from, to]) => v.slice(from, to)));
|
|
1631
|
+
return (Array.isArray(result) ? mapped : mapped[0]);
|
|
1632
|
+
}
|
|
1633
|
+
/** PineScript `request.*`: `security` (issue #101, other symbols #179, lower timeframes #181) and `security_lower_tf`. */
|
|
1634
|
+
export const request = { security, security_lower_tf };
|
|
1447
1635
|
// ── Drawing objects ──────────────────────────────────────────────────────────
|
|
1448
1636
|
// The drawing namespaces come from this bundle so that they share the registry that
|
|
1449
1637
|
// executeScript() resets. `line.all` etc. are read like PineScript variables.
|