oakscriptjs 0.10.1 → 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.d.ts.map +1 -1
- package/dist/cjs/box/index.js +7 -1
- package/dist/cjs/box/index.js.map +2 -2
- package/dist/cjs/callsite/index.d.ts +73 -32
- package/dist/cjs/callsite/index.d.ts.map +1 -1
- package/dist/cjs/callsite/index.js +69 -65
- package/dist/cjs/callsite/index.js.map +2 -2
- package/dist/cjs/drawing/registry.d.ts +84 -0
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +153 -6
- package/dist/cjs/drawing/registry.js.map +2 -2
- package/dist/cjs/index.d.ts +8 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +33 -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.d.ts.map +1 -1
- package/dist/cjs/label/index.js +3 -0
- 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 +11 -22
- 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/math/index.d.ts.map +1 -1
- package/dist/cjs/math/index.js +2 -0
- package/dist/cjs/math/index.js.map +2 -2
- package/dist/cjs/polyline/index.d.ts.map +1 -1
- package/dist/cjs/polyline/index.js +3 -0
- package/dist/cjs/polyline/index.js.map +2 -2
- 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 +91 -21
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +207 -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 +24 -1
- package/dist/cjs/session/bars.d.ts.map +1 -1
- package/dist/cjs/session/bars.js +56 -7
- package/dist/cjs/session/bars.js.map +3 -3
- package/dist/cjs/session/calendar.d.ts +13 -2
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +39 -9
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/session/context.d.ts +76 -0
- package/dist/cjs/session/context.d.ts.map +1 -0
- package/dist/cjs/session/context.js +114 -0
- package/dist/cjs/session/context.js.map +7 -0
- 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 -1
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +2 -1
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +7 -3
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +4 -3
- package/dist/cjs/str/numberformat.js.map +2 -2
- package/dist/cjs/ta/index.d.ts +67 -34
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +202 -145
- package/dist/cjs/ta/index.js.map +2 -2
- package/dist/cjs/ta/limits.d.ts +35 -0
- package/dist/cjs/ta/limits.d.ts.map +1 -0
- package/dist/cjs/ta/limits.js +64 -0
- package/dist/cjs/ta/limits.js.map +7 -0
- package/dist/cjs/ta/running-sum.d.ts +0 -16
- package/dist/cjs/ta/running-sum.d.ts.map +1 -1
- package/dist/cjs/ta/running-sum.js +2 -0
- package/dist/cjs/ta/running-sum.js.map +2 -2
- package/dist/cjs/ta-series.d.ts +8 -8
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +10 -8
- 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/index.d.ts +5 -2
- package/dist/cjs/time/index.d.ts.map +1 -1
- package/dist/cjs/time/index.js +2 -2
- package/dist/cjs/time/index.js.map +2 -2
- package/dist/cjs/time/session.d.ts +26 -1
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +94 -12
- 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 +28 -4
- 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.d.ts.map +1 -1
- package/dist/esm/box/index.js +8 -2
- package/dist/esm/box/index.js.map +1 -1
- package/dist/esm/callsite/index.d.ts +73 -32
- package/dist/esm/callsite/index.d.ts.map +1 -1
- package/dist/esm/callsite/index.js +143 -106
- package/dist/esm/callsite/index.js.map +1 -1
- package/dist/esm/drawing/registry.d.ts +84 -0
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +260 -6
- package/dist/esm/drawing/registry.js.map +1 -1
- package/dist/esm/index.d.ts +8 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +13 -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.d.ts.map +1 -1
- package/dist/esm/label/index.js +4 -1
- 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 +22 -60
- 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/math/index.d.ts.map +1 -1
- package/dist/esm/math/index.js +3 -0
- package/dist/esm/math/index.js.map +1 -1
- package/dist/esm/polyline/index.d.ts.map +1 -1
- package/dist/esm/polyline/index.js +5 -1
- package/dist/esm/polyline/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 +91 -21
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +259 -28
- 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 +24 -1
- package/dist/esm/session/bars.d.ts.map +1 -1
- package/dist/esm/session/bars.js +75 -6
- package/dist/esm/session/bars.js.map +1 -1
- package/dist/esm/session/calendar.d.ts +13 -2
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +54 -10
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/session/context.d.ts +76 -0
- package/dist/esm/session/context.d.ts.map +1 -0
- package/dist/esm/session/context.js +124 -0
- package/dist/esm/session/context.js.map +1 -0
- 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 -1
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +9 -2
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +7 -3
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +11 -5
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +67 -34
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +284 -240
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/limits.d.ts +35 -0
- package/dist/esm/ta/limits.d.ts.map +1 -0
- package/dist/esm/ta/limits.js +67 -0
- package/dist/esm/ta/limits.js.map +1 -0
- package/dist/esm/ta/running-sum.d.ts +0 -16
- package/dist/esm/ta/running-sum.d.ts.map +1 -1
- package/dist/esm/ta/running-sum.js +2 -0
- package/dist/esm/ta/running-sum.js.map +1 -1
- package/dist/esm/ta-series.d.ts +8 -8
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +11 -8
- 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/index.d.ts +5 -2
- package/dist/esm/time/index.d.ts.map +1 -1
- package/dist/esm/time/index.js +6 -3
- package/dist/esm/time/index.js.map +1 -1
- package/dist/esm/time/session.d.ts +26 -1
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +141 -16
- 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 +28 -4
- 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 +8 -2
- package/src/callsite/index.ts +150 -100
- package/src/drawing/registry.ts +278 -5
- package/src/index.ts +44 -1
- package/src/input.ts +18 -1
- package/src/label/index.ts +4 -1
- package/src/line/index.ts +22 -64
- package/src/linefill/index.ts +20 -1
- package/src/math/index.ts +2 -0
- package/src/polyline/index.ts +3 -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 +355 -34
- package/src/security/other.ts +269 -0
- package/src/session/bars.ts +70 -6
- package/src/session/calendar.ts +60 -10
- package/src/session/context.ts +162 -0
- package/src/str/floatingdecimal.ts +268 -0
- package/src/str/index.ts +8 -2
- package/src/str/numberformat.ts +13 -6
- package/src/ta/index.ts +304 -248
- package/src/ta/limits.ts +74 -0
- package/src/ta/running-sum.ts +2 -0
- package/src/ta-series.ts +19 -16
- package/src/table/index.ts +4 -1
- package/src/time/index.ts +6 -3
- package/src/time/session.ts +137 -15
- package/src/time/timezone.ts +9 -2
- package/src/types/metadata.ts +28 -4
package/src/script/index.ts
CHANGED
|
@@ -87,7 +87,8 @@ import * as tableCore from '../table/index.js';
|
|
|
87
87
|
import * as chartPointCore from '../chartpoint/index.js';
|
|
88
88
|
import * as textCore from '../text/index.js';
|
|
89
89
|
import * as mathCore from '../math/index.js';
|
|
90
|
-
import { resetDrawings, setDrawingLimits } from '../drawing/registry.js';
|
|
90
|
+
import { resetDrawings, setDrawingBar, setDrawingLimits } from '../drawing/registry.js';
|
|
91
|
+
export { holdDrawing, releaseDrawing, varDrawing, type DrawingVariable } from '../drawing/registry.js';
|
|
91
92
|
import { drawingOutputs } from '../drawing/output.js';
|
|
92
93
|
import * as timeframeCore from '../timeframe/index.js';
|
|
93
94
|
import * as timeCore from '../time/index.js';
|
|
@@ -95,8 +96,9 @@ import * as strCore from '../str/index.js';
|
|
|
95
96
|
import { formatMessage } from '../str/messageformat.js';
|
|
96
97
|
import { tickScale } from '../str/numberformat.js';
|
|
97
98
|
import { TradingCalendar, type SessionSpec } from '../session/calendar.js';
|
|
98
|
-
import { SessionBars, sessionFlags, type SessionFlags } from '../session/bars.js';
|
|
99
|
+
import { SessionBars, sessionFlags, strayPeriods, type SessionFlags } from '../session/bars.js';
|
|
99
100
|
import { afterCompletion, heikinAshi, mapToChart, periodsOf, resample } from '../security/resample.js';
|
|
101
|
+
import { securityLowerTf, securityOther, type OtherSymbolChart } from '../security/other.js';
|
|
100
102
|
import { position } from '../types/index.js';
|
|
101
103
|
import type { ChartPoint } from '../types/index.js';
|
|
102
104
|
import {
|
|
@@ -120,7 +122,7 @@ import {
|
|
|
120
122
|
type StrategyTrade,
|
|
121
123
|
type StrategyVariable,
|
|
122
124
|
} from '../strategy/index.js';
|
|
123
|
-
import { parsePineSession } from '../time/session.js';
|
|
125
|
+
import { parsePineSession, sessionRun } from '../time/session.js';
|
|
124
126
|
|
|
125
127
|
/** Everything one run of a script produced. */
|
|
126
128
|
export interface ScriptRunResult {
|
|
@@ -296,12 +298,15 @@ export function executeScript(
|
|
|
296
298
|
): ScriptRunResult {
|
|
297
299
|
ctxBars.setAll(barsInput);
|
|
298
300
|
chartCtx = { ...chart };
|
|
301
|
+
runNow = chart.now ?? Date.now();
|
|
299
302
|
scriptOptions = options;
|
|
300
303
|
ctx = freshCollector(inputs);
|
|
301
304
|
resetDrawings();
|
|
302
305
|
mathCore.resetRandom();
|
|
306
|
+
missingSymbols = [];
|
|
303
307
|
try {
|
|
304
308
|
body();
|
|
309
|
+
if (missingSymbols.length) throw new MissingSymbolDataError(missingSymbols);
|
|
305
310
|
const c = ctx;
|
|
306
311
|
return {
|
|
307
312
|
metadata: {
|
|
@@ -478,6 +483,10 @@ export const input = {
|
|
|
478
483
|
'input_session'
|
|
479
484
|
);
|
|
480
485
|
},
|
|
486
|
+
/** Ticker id ("NASDAQ:MSFT"), for `request.security` of another symbol (#180). */
|
|
487
|
+
symbol(defval: string, title?: string, opts: StringInputOptions = {}): string {
|
|
488
|
+
return registerInput<string>({ type: 'symbol', defval, title, options: opts.options, ...common(opts) }, 'input_symbol');
|
|
489
|
+
},
|
|
481
490
|
/** Date and time as a UNIX time in milliseconds, e.g. `input.time(timestamp('2024-01-01'), 'Start')`. */
|
|
482
491
|
time(defval: number, title?: string, opts: InputOptions = {}): number {
|
|
483
492
|
const v = registerInput<unknown>({ type: 'time', defval, title, ...common(opts) }, 'input_time');
|
|
@@ -547,25 +556,27 @@ export const ta = {
|
|
|
547
556
|
): [Series, Series, Series, Series, Series] =>
|
|
548
557
|
taSeries.ichimoku(bars(), conversionPeriods, basePeriods, laggingSpan2Periods, displacement),
|
|
549
558
|
/** `ta.highest(source, length)` or `ta.highest(length)` (source = high). */
|
|
550
|
-
highest: (sourceOrLength: Series | number, length?: number): Series =>
|
|
559
|
+
highest: (sourceOrLength: Series | number, length?: number | Series): Series =>
|
|
551
560
|
typeof sourceOrLength === 'number' ? taSeries.highest(high, sourceOrLength) : taSeries.highest(sourceOrLength, length!),
|
|
552
561
|
/** `ta.lowest(source, length)` or `ta.lowest(length)` (source = low). */
|
|
553
|
-
lowest: (sourceOrLength: Series | number, length?: number): Series =>
|
|
562
|
+
lowest: (sourceOrLength: Series | number, length?: number | Series): Series =>
|
|
554
563
|
typeof sourceOrLength === 'number' ? taSeries.lowest(low, sourceOrLength) : taSeries.lowest(sourceOrLength, length!),
|
|
555
564
|
/** `ta.highestbars(source, length)` or `ta.highestbars(length)` (source = high); offsets are 0 or negative. */
|
|
556
|
-
highestbars: (sourceOrLength: Series | number, length?: number): Series =>
|
|
565
|
+
highestbars: (sourceOrLength: Series | number, length?: number | Series): Series =>
|
|
557
566
|
typeof sourceOrLength === 'number'
|
|
558
567
|
? taSeries.highestbars(high, sourceOrLength)
|
|
559
568
|
: taSeries.highestbars(sourceOrLength, length!),
|
|
560
569
|
/** `ta.lowestbars(source, length)` or `ta.lowestbars(length)` (source = low); offsets are 0 or negative. */
|
|
561
|
-
lowestbars: (sourceOrLength: Series | number, length?: number): Series =>
|
|
570
|
+
lowestbars: (sourceOrLength: Series | number, length?: number | Series): Series =>
|
|
562
571
|
typeof sourceOrLength === 'number'
|
|
563
572
|
? taSeries.lowestbars(low, sourceOrLength)
|
|
564
573
|
: taSeries.lowestbars(sourceOrLength, length!),
|
|
565
|
-
/** `ta.vwap(source, anchor?, stdev_mult?)` with the chart volume; the default anchor is a new trading
|
|
566
|
-
*
|
|
574
|
+
/** `ta.vwap(source, anchor?, stdev_mult?)` with the chart volume; the default anchor is a new trading day
|
|
575
|
+
* and the first bar: as in PineScript, the first loaded bars (a day in progress) have a value. On D / W / M
|
|
576
|
+
* charts every bar is a new day (no time zone or session needed); on intraday charts it needs the chart context
|
|
577
|
+
* (timeframe, time zone, session). */
|
|
567
578
|
vwap: ((source: Series, anchor?: Series, stdev_mult?: number): Series | [Series, Series, Series] => {
|
|
568
|
-
const a = anchor ??
|
|
579
|
+
const a = anchor ?? vwapAnchor();
|
|
569
580
|
return stdev_mult === undefined ? taSeries.vwap(source, volume, a) : taSeries.vwapBands(source, volume, a, stdev_mult);
|
|
570
581
|
}) as {
|
|
571
582
|
(source: Series, anchor?: Series): Series;
|
|
@@ -575,12 +586,12 @@ export const ta = {
|
|
|
575
586
|
mfi: (source: Series, length: number): Series => taSeries.mfi(source, length, volume),
|
|
576
587
|
/** `ta.pivothigh(source, leftbars, rightbars)` or `ta.pivothigh(leftbars, rightbars)` (source = high).
|
|
577
588
|
* The value appears `rightbars` bars after the pivot bar, as in PineScript. */
|
|
578
|
-
pivothigh: (sourceOrLeft: Series | number, leftOrRight: number, rightbars?: number): Series =>
|
|
589
|
+
pivothigh: (sourceOrLeft: Series | number, leftOrRight: number | Series, rightbars?: number | Series): Series =>
|
|
579
590
|
typeof sourceOrLeft === 'number'
|
|
580
591
|
? taSeries.pivothigh(high, sourceOrLeft, leftOrRight)
|
|
581
592
|
: taSeries.pivothigh(sourceOrLeft, leftOrRight, rightbars!),
|
|
582
593
|
/** `ta.pivotlow(source, leftbars, rightbars)` or `ta.pivotlow(leftbars, rightbars)` (source = low). */
|
|
583
|
-
pivotlow: (sourceOrLeft: Series | number, leftOrRight: number, rightbars?: number): Series =>
|
|
594
|
+
pivotlow: (sourceOrLeft: Series | number, leftOrRight: number | Series, rightbars?: number | Series): Series =>
|
|
584
595
|
typeof sourceOrLeft === 'number'
|
|
585
596
|
? taSeries.pivotlow(low, sourceOrLeft, leftOrRight)
|
|
586
597
|
: taSeries.pivotlow(sourceOrLeft, leftOrRight, rightbars!),
|
|
@@ -1262,6 +1273,7 @@ export function eachBar(fn: (c: BarContext) => number | boolean | void): Series
|
|
|
1262
1273
|
for (idx = 0; idx < b.length; idx++) {
|
|
1263
1274
|
if (pendingError && pendingError.bar === idx) throw new Error(historyError(idx, pendingError.barsBack));
|
|
1264
1275
|
currentBar = idx;
|
|
1276
|
+
setDrawingBar(idx, idx === b.length - 1, b.length - 1);
|
|
1265
1277
|
const r = fn(ctx);
|
|
1266
1278
|
out[idx] = typeof r === 'number' ? r : typeof r === 'boolean' ? (r ? 1 : 0) : NaN;
|
|
1267
1279
|
// grow the history buffers from the largest reference of this bar
|
|
@@ -1278,6 +1290,7 @@ export function eachBar(fn: (c: BarContext) => number | boolean | void): Series
|
|
|
1278
1290
|
}
|
|
1279
1291
|
} finally {
|
|
1280
1292
|
currentBar = -1;
|
|
1293
|
+
setDrawingBar(null);
|
|
1281
1294
|
}
|
|
1282
1295
|
return Series.fromArray(ctxBars, out);
|
|
1283
1296
|
}
|
|
@@ -1547,6 +1560,23 @@ export interface ChartContext {
|
|
|
1547
1560
|
pointvalue?: number;
|
|
1548
1561
|
/** Smallest tradable quantity (PineScript `syminfo.mincontract`); 1 for whole shares */
|
|
1549
1562
|
mincontract?: number;
|
|
1563
|
+
/** Symbol type (PineScript `syminfo.type`): "stock", "crypto", "forex", "futures", "index", "fund"... */
|
|
1564
|
+
symbolType?: string;
|
|
1565
|
+
/** Currency of the symbol (PineScript `syminfo.currency`), e.g. "USD" */
|
|
1566
|
+
currency?: string;
|
|
1567
|
+
/**
|
|
1568
|
+
* Base currency of the symbol (PineScript `syminfo.basecurrency`): "BTC" for BITSTAMP:BTCUSD, BINANCE:BTCUSDT.P and
|
|
1569
|
+
* CME:BTC1!, "EUR" for FX:EURUSD, "" for a stock, an index or CME_MINI:ES1! (#180)
|
|
1570
|
+
*/
|
|
1571
|
+
basecurrency?: string;
|
|
1572
|
+
/** Root of the symbol (PineScript `syminfo.root`): "BTC" for CME:BTC1!, "ES" for CME_MINI:ES1!, "AAPL" for NASDAQ:AAPL (#180) */
|
|
1573
|
+
root?: string;
|
|
1574
|
+
/** Chart foreground colour (PineScript `chart.fg_color`), e.g. "#DBDBDB" on a dark chart */
|
|
1575
|
+
foregroundColor?: string;
|
|
1576
|
+
/** Chart background colour (PineScript `chart.bg_color`), e.g. "#0F0F0F" on a dark chart */
|
|
1577
|
+
backgroundColor?: string;
|
|
1578
|
+
/** Current time in ms (PineScript `timenow`); default: the clock of the computer at the start of the run */
|
|
1579
|
+
now?: number;
|
|
1550
1580
|
/**
|
|
1551
1581
|
* The last bar is closed (`barstate.isconfirmed` on the last bar). Default false: PineScript treats the last bar
|
|
1552
1582
|
* of the data as not confirmed until the data feed closes it.
|
|
@@ -1556,8 +1586,60 @@ export interface ChartContext {
|
|
|
1556
1586
|
realtime?: boolean;
|
|
1557
1587
|
/** On a realtime last bar: this run is the first update of the bar (`barstate.isnew`); default true. */
|
|
1558
1588
|
lastBarNew?: boolean;
|
|
1589
|
+
/**
|
|
1590
|
+
* Bars of another symbol for `request.security(symbol, timeframe, ...)` (#179): the bars of `symbol` (the ticker id
|
|
1591
|
+
* as written in the script, e.g. "NASDAQ:MSFT") on `timeframe` (the chart timeframe for ""), oldest first, times in
|
|
1592
|
+
* `timeUnit`. Return undefined when they are not loaded: the run then ends with a {@link MissingSymbolDataError}
|
|
1593
|
+
* that lists every request, so the host can load them and run again. Return [] for a symbol that does not exist.
|
|
1594
|
+
*/
|
|
1595
|
+
bars?: (symbol: string, timeframe: string) => Bar[] | undefined;
|
|
1596
|
+
/**
|
|
1597
|
+
* Calendar and properties of another symbol: its time zone and session give the close times and trading days that
|
|
1598
|
+
* `request.security` merges on (#179), and the other fields its `syminfo.*` inside the expression.
|
|
1599
|
+
*/
|
|
1600
|
+
symbolInfo?: (symbol: string) => SymbolInfo | undefined;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/** Calendar and properties of a symbol, as in {@link ChartContext}. */
|
|
1604
|
+
export type SymbolInfo = Pick<
|
|
1605
|
+
ChartContext,
|
|
1606
|
+
| 'timezone'
|
|
1607
|
+
| 'session'
|
|
1608
|
+
| 'regularSession'
|
|
1609
|
+
| 'mintick'
|
|
1610
|
+
| 'minmove'
|
|
1611
|
+
| 'pricescale'
|
|
1612
|
+
| 'pointvalue'
|
|
1613
|
+
| 'mincontract'
|
|
1614
|
+
| 'symbolType'
|
|
1615
|
+
| 'currency'
|
|
1616
|
+
| 'basecurrency'
|
|
1617
|
+
| 'root'
|
|
1618
|
+
>;
|
|
1619
|
+
|
|
1620
|
+
/** A request of `request.security` for another symbol whose bars the host has not given. */
|
|
1621
|
+
export interface SymbolDataRequest {
|
|
1622
|
+
symbol: string;
|
|
1623
|
+
timeframe: string;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
/**
|
|
1627
|
+
* Thrown by `executeScript` when `request.security` asked for bars of another symbol that `ChartContext.bars` did not
|
|
1628
|
+
* give (#179). `requests` lists them all, once each: load them, then run the script again.
|
|
1629
|
+
*/
|
|
1630
|
+
export class MissingSymbolDataError extends Error {
|
|
1631
|
+
constructor(readonly requests: SymbolDataRequest[]) {
|
|
1632
|
+
super(
|
|
1633
|
+
`request.security needs the bars of ${requests.map((r) => `${r.symbol} (${r.timeframe})`).join(', ')}: ` +
|
|
1634
|
+
'pass them with ChartContext.bars.'
|
|
1635
|
+
);
|
|
1636
|
+
this.name = 'MissingSymbolDataError';
|
|
1637
|
+
}
|
|
1559
1638
|
}
|
|
1560
1639
|
|
|
1640
|
+
/** Requests of other symbols without bars in the current run. */
|
|
1641
|
+
let missingSymbols: SymbolDataRequest[] = [];
|
|
1642
|
+
|
|
1561
1643
|
let chartCtx: ChartContext = {};
|
|
1562
1644
|
|
|
1563
1645
|
function chartTimeframe(): timeframeCore.TimeframeInfo {
|
|
@@ -1638,6 +1720,16 @@ function symbolProperty(key: 'mintick' | 'pointvalue' | 'mincontract'): number {
|
|
|
1638
1720
|
return value;
|
|
1639
1721
|
}
|
|
1640
1722
|
|
|
1723
|
+
/** A text value of the chart or the symbol that the host passes in the chart context. */
|
|
1724
|
+
function chartText(key: 'symbolType' | 'currency' | 'basecurrency' | 'root' | 'foregroundColor' | 'backgroundColor', name: string): string {
|
|
1725
|
+
collector();
|
|
1726
|
+
const value = chartCtx[key];
|
|
1727
|
+
if (value === undefined) {
|
|
1728
|
+
throw new Error(`${name} is not known: pass it as executeScript(body, bars, inputs, { ${key} }).`);
|
|
1729
|
+
}
|
|
1730
|
+
return value;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1641
1733
|
/** `[minmove, pricescale]` of the chart symbol: from the chart context, else from its mintick. */
|
|
1642
1734
|
function symbolScale(): [number, number] {
|
|
1643
1735
|
collector();
|
|
@@ -1649,7 +1741,8 @@ function symbolScale(): [number, number] {
|
|
|
1649
1741
|
|
|
1650
1742
|
/**
|
|
1651
1743
|
* PineScript `syminfo.timezone`, `syminfo.tickerid`, `syminfo.session` (session type), `syminfo.mintick`,
|
|
1652
|
-
* `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue
|
|
1744
|
+
* `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue`, `syminfo.mincontract`, `syminfo.type`,
|
|
1745
|
+
* `syminfo.currency`, `syminfo.basecurrency` and `syminfo.root`, from the chart context.
|
|
1653
1746
|
*/
|
|
1654
1747
|
export const syminfo = {
|
|
1655
1748
|
get mintick(): number {
|
|
@@ -1681,6 +1774,18 @@ export const syminfo = {
|
|
|
1681
1774
|
collector();
|
|
1682
1775
|
return chartCtx.sessionType ?? 'regular';
|
|
1683
1776
|
},
|
|
1777
|
+
get type(): string {
|
|
1778
|
+
return chartText('symbolType', 'syminfo.type');
|
|
1779
|
+
},
|
|
1780
|
+
get currency(): string {
|
|
1781
|
+
return chartText('currency', 'syminfo.currency');
|
|
1782
|
+
},
|
|
1783
|
+
get basecurrency(): string {
|
|
1784
|
+
return chartText('basecurrency', 'syminfo.basecurrency');
|
|
1785
|
+
},
|
|
1786
|
+
get root(): string {
|
|
1787
|
+
return chartText('root', 'syminfo.root');
|
|
1788
|
+
},
|
|
1684
1789
|
};
|
|
1685
1790
|
|
|
1686
1791
|
/** A calendar function: `(time, timezone?)` on a UNIX time or on a Series of times. */
|
|
@@ -1794,7 +1899,11 @@ function sessionBars(): { bars: SessionBars; regular: SessionBars } {
|
|
|
1794
1899
|
* session. As in PineScript, an explicit time zone reads the symbol session hours in that zone without
|
|
1795
1900
|
* corrections and holidays, and an explicit session follows PineScript's session string rules.
|
|
1796
1901
|
*/
|
|
1797
|
-
function calendarFor(
|
|
1902
|
+
function calendarFor(
|
|
1903
|
+
session?: string,
|
|
1904
|
+
timezone?: string,
|
|
1905
|
+
dayPlacement = false
|
|
1906
|
+
): { bars: SessionBars; test: boolean; inside?: (time: number) => boolean } {
|
|
1798
1907
|
const { bars } = sessionBars();
|
|
1799
1908
|
if (!session && !timezone) return { bars, test: false };
|
|
1800
1909
|
const tz = timezone ?? exchangeTimezone();
|
|
@@ -1806,7 +1915,14 @@ function calendarFor(session?: string, timezone?: string): { bars: SessionBars;
|
|
|
1806
1915
|
return { bars: new SessionBars(new TradingCalendar(tz, { session: toSpec(symbol).session }, 'symbol'), bars.chart.period), test: true };
|
|
1807
1916
|
}
|
|
1808
1917
|
const spec = session ? { session } : { session: toSpec(chartCtx.session!).session };
|
|
1809
|
-
return {
|
|
1918
|
+
return {
|
|
1919
|
+
bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol', dayPlacement), bars.chart.period),
|
|
1920
|
+
test: true,
|
|
1921
|
+
// a PineScript session string: the rules of time.inSession (runs ending at 0000, days around a clock change)
|
|
1922
|
+
inside: session
|
|
1923
|
+
? (time) => timeCore.inSession(time, session, tz, timeframeCore.in_seconds(bars.chart.period) * 1000)
|
|
1924
|
+
: undefined,
|
|
1925
|
+
};
|
|
1810
1926
|
}
|
|
1811
1927
|
|
|
1812
1928
|
function perBar(fn: (t: number, i: number, times: number[]) => number): Series {
|
|
@@ -1821,24 +1937,61 @@ function callableSeries<F extends (...args: never[]) => Series>(series: Series,
|
|
|
1821
1937
|
return fn as Series & F;
|
|
1822
1938
|
}
|
|
1823
1939
|
|
|
1940
|
+
/**
|
|
1941
|
+
* `time(tf, session)` / `time_close(tf, session)` of a bar, as PineScript computes them (#173): an intraday timeframe
|
|
1942
|
+
* tests the bar with the session rules of time.inSession and aligns its periods on the start of the run that
|
|
1943
|
+
* contains the bar; "D" tests the bar against the day's session period (the calendar); a timeframe of more than one
|
|
1944
|
+
* day ("2D", "W", "M") has no test and keeps the period's time.
|
|
1945
|
+
*/
|
|
1946
|
+
function sessionPeriods(timeframeString: string, session?: string, timezone?: string): { open: (t: number) => number; close: (t: number) => number } {
|
|
1947
|
+
const tf = timeframeCore.info(timeframeString);
|
|
1948
|
+
const { bars, test, inside } = calendarFor(session, timezone, !tf.isintraday);
|
|
1949
|
+
const open = (t: number): number => bars.timeOf(t, timeframeString);
|
|
1950
|
+
const close = (t: number): number => bars.closeOf(t, timeframeString);
|
|
1951
|
+
if (!test) return { open, close };
|
|
1952
|
+
if (tf.isintraday) {
|
|
1953
|
+
if (session !== undefined && parsePineSession(session) !== null && !bars.chart.isdwm) {
|
|
1954
|
+
const tz = timezone ?? exchangeTimezone();
|
|
1955
|
+
const barMs = timeframeCore.in_seconds(bars.chart.period) * 1000;
|
|
1956
|
+
const ms = timeframeCore.in_seconds(tf.period) * 1000;
|
|
1957
|
+
const run = (t: number) => sessionRun(t, session, tz, barMs);
|
|
1958
|
+
const start = (t: number, r: [number, number]) => r[0] + Math.floor((t - r[0]) / ms) * ms;
|
|
1959
|
+
return {
|
|
1960
|
+
open: (t) => { const r = run(t); return r ? start(t, r) : NaN; },
|
|
1961
|
+
close: (t) => { const r = run(t); return r ? Math.min(start(t, r) + ms, r[1]) : NaN; },
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
const isIn = (t: number) => (inside ? inside(t) : bars.inside(t));
|
|
1965
|
+
return { open: (t) => (isIn(t) ? open(t) : NaN), close: (t) => (isIn(t) ? close(t) : NaN) };
|
|
1966
|
+
}
|
|
1967
|
+
if (tf.isdaily && tf.multiplier === 1) {
|
|
1968
|
+
return { open: (t) => (bars.inside(t) ? open(t) : NaN), close: (t) => (bars.inside(t) ? close(t) : NaN) };
|
|
1969
|
+
}
|
|
1970
|
+
return { open, close };
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1824
1973
|
function timeFunction(tf?: string, session?: string, timezone?: string): Series {
|
|
1825
1974
|
collector();
|
|
1826
|
-
const {
|
|
1827
|
-
|
|
1828
|
-
return perBar((t) => (test && !bars.inside(t) ? NaN : bars.timeOf(t, timeframeString)));
|
|
1975
|
+
const { open } = sessionPeriods(tf || chartTimeframe().period, session, timezone);
|
|
1976
|
+
return perBar((t) => open(t));
|
|
1829
1977
|
}
|
|
1830
1978
|
|
|
1831
1979
|
function timeCloseFunction(tf?: string, session?: string, timezone?: string): Series {
|
|
1832
1980
|
collector();
|
|
1833
|
-
const {
|
|
1834
|
-
|
|
1835
|
-
return perBar((t) => (test && !bars.inside(t) ? NaN : bars.closeOf(t, timeframeString)));
|
|
1981
|
+
const { close } = sessionPeriods(tf || chartTimeframe().period, session, timezone);
|
|
1982
|
+
return perBar((t) => close(t));
|
|
1836
1983
|
}
|
|
1837
1984
|
|
|
1838
1985
|
/**
|
|
1839
1986
|
* PineScript `time`: the bar open time in ms (a Series), and `time(timeframe?, session?, timezone?)`:
|
|
1840
1987
|
* the open time of the `timeframe` period that contains the bar, `na` outside `session`.
|
|
1841
1988
|
*/
|
|
1989
|
+
/** Current time of the run in ms, see `ChartContext.now`. */
|
|
1990
|
+
let runNow = NaN;
|
|
1991
|
+
|
|
1992
|
+
/** PineScript `timenow`: the current time in ms, the same on every bar of a run (`ChartContext.now`). */
|
|
1993
|
+
export const timenow = new Series(ctxBars, () => runNow);
|
|
1994
|
+
|
|
1842
1995
|
export const time = callableSeries(new Series(ctxBars, (b) => barTime(b)), timeFunction);
|
|
1843
1996
|
|
|
1844
1997
|
/**
|
|
@@ -1853,9 +2006,31 @@ export const time_close = callableSeries(
|
|
|
1853
2006
|
/** PineScript `time_tradingday`: 00:00 UTC of the trading day of each bar, in ms. */
|
|
1854
2007
|
export const time_tradingday = new Series(ctxBars, (b) => sessionBars().bars.tradingDayTime(barTime(b)));
|
|
1855
2008
|
|
|
1856
|
-
function changeOf(tf: string): Series {
|
|
2009
|
+
function changeOf(tf: string, firstBar = false): Series {
|
|
1857
2010
|
const { bars } = sessionBars();
|
|
1858
|
-
|
|
2011
|
+
// a bar without a period (after an early close) is no change, and neither is the bar after it
|
|
2012
|
+
const changed = (a: number, b: number): boolean => !Number.isNaN(a) && !Number.isNaN(b) && a !== b;
|
|
2013
|
+
return perBar((t, i, times) => ((i > 0 ? changed(bars.timeOf(t, tf), bars.timeOf(times[i - 1]!, tf)) : firstBar) ? 1 : 0));
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
/**
|
|
2017
|
+
* Default anchor of `ta.vwap` (#173, #178): the first bar and each bar whose trading day differs from the last bar
|
|
2018
|
+
* that has one. Bars after an early close have no day: the first of them continues the day before it, each further
|
|
2019
|
+
* one starts a new sum, and the next day starts on its first bar (where `timeframe.change("D")` is false). On
|
|
2020
|
+
* D / W / M charts every bar.
|
|
2021
|
+
*/
|
|
2022
|
+
function vwapAnchor(): Series {
|
|
2023
|
+
if (chartTimeframe().isdwm) return barFlag(() => true);
|
|
2024
|
+
const { bars } = sessionBars();
|
|
2025
|
+
let last = NaN;
|
|
2026
|
+
let previous = NaN;
|
|
2027
|
+
return perBar((t, i) => {
|
|
2028
|
+
const day = bars.timeOf(t, '1D');
|
|
2029
|
+
const isNew = i === 0 || (Number.isNaN(day) ? Number.isNaN(previous) : day !== last);
|
|
2030
|
+
if (!Number.isNaN(day)) last = day;
|
|
2031
|
+
previous = day;
|
|
2032
|
+
return isNew ? 1 : 0;
|
|
2033
|
+
});
|
|
1859
2034
|
}
|
|
1860
2035
|
|
|
1861
2036
|
function flag(key: keyof SessionFlags): Series {
|
|
@@ -1947,10 +2122,14 @@ function valuesOf(value: Series | number | boolean, length: number): number[] {
|
|
|
1947
2122
|
}
|
|
1948
2123
|
|
|
1949
2124
|
/**
|
|
1950
|
-
* PineScript `request.security`
|
|
1951
|
-
* `ticker.standard(...)`)
|
|
1952
|
-
*
|
|
1953
|
-
*
|
|
2125
|
+
* PineScript `request.security` on a timeframe equal to or higher than the chart's. For the chart symbol
|
|
2126
|
+
* (`syminfo.tickerid`, `""`, `ticker.heikinashi(...)`, `ticker.standard(...)`) the higher-timeframe bars are built by
|
|
2127
|
+
* grouping the chart bars; another symbol's bars come from `ChartContext.bars` (#179) and are merged on the chart bars
|
|
2128
|
+
* by close time (lookahead off) or by open time / trading day (lookahead on), see `securityOther`. `expression` runs
|
|
2129
|
+
* on those bars (its `ta.*` calls, tuples, `timeframe.*` and `syminfo.*` see them). A lower timeframe throws.
|
|
2130
|
+
*
|
|
2131
|
+
* A symbol that does not exist (bars []) stops the script with "Invalid symbol: X", or gives na with
|
|
2132
|
+
* `ignore_invalid_symbol`.
|
|
1954
2133
|
*
|
|
1955
2134
|
* PineScript rules: lookahead off shows a period's value from the
|
|
1956
2135
|
* bar that completes it; lookahead on from its first bar; gaps on keeps only the bar where the value appears.
|
|
@@ -1961,18 +2140,20 @@ function security<T extends SecurityValue>(
|
|
|
1961
2140
|
timeframe: string,
|
|
1962
2141
|
expression: () => T,
|
|
1963
2142
|
gaps: string = barmerge.gaps_off,
|
|
1964
|
-
lookahead: string = barmerge.lookahead_off
|
|
2143
|
+
lookahead: string = barmerge.lookahead_off,
|
|
2144
|
+
ignore_invalid_symbol: boolean = false
|
|
1965
2145
|
): T extends unknown[] ? Series[] : Series {
|
|
1966
2146
|
collector();
|
|
1967
2147
|
const heikin = symbol.startsWith(HEIKIN_ASHI);
|
|
1968
2148
|
const base = heikin ? symbol.slice(HEIKIN_ASHI.length) : symbol;
|
|
1969
|
-
if (base !== '' && base !== chartCtx.tickerid) {
|
|
1970
|
-
throw new Error(`request.security: "${symbol}" is not the chart symbol; other symbols need external data.`);
|
|
1971
|
-
}
|
|
1972
2149
|
const chart = chartTimeframe();
|
|
1973
2150
|
const tf = timeframeCore.info(timeframe || chart.period).period;
|
|
2151
|
+
if (base !== '' && base !== chartCtx.tickerid) {
|
|
2152
|
+
return externalSecurity(base, heikin, tf, expression, gaps, lookahead, ignore_invalid_symbol, false);
|
|
2153
|
+
}
|
|
1974
2154
|
if (timeframeCore.in_seconds(tf) < timeframeCore.in_seconds(chart.period)) {
|
|
1975
|
-
|
|
2155
|
+
// a lower timeframe of the chart symbol: its bars come from ChartContext.bars (#181)
|
|
2156
|
+
return externalSecurity(chartCtx.tickerid ?? '', heikin, tf, expression, gaps, lookahead, ignore_invalid_symbol, true);
|
|
1976
2157
|
}
|
|
1977
2158
|
|
|
1978
2159
|
const chartBars = ctxBars.bars;
|
|
@@ -1994,6 +2175,8 @@ function security<T extends SecurityValue>(
|
|
|
1994
2175
|
const periods = at ? calendar.calendar.periods(at.date) : [];
|
|
1995
2176
|
return periods.length ? calendar.timeOf(periods[0]![0], tf) : starts[i]!;
|
|
1996
2177
|
});
|
|
2178
|
+
const tfInfo = timeframeCore.info(tf);
|
|
2179
|
+
strayPeriods(starts, dayPeriods, times, loaded, tfInfo.isdwm ? null : timeframeCore.in_seconds(tf) * 1000);
|
|
1997
2180
|
const reaches = times.map((t) => loaded.closeOf(t, chart.period) >= calendar.periodEnd(t, tf));
|
|
1998
2181
|
({ group, latest } = periodsOf(starts, dayPeriods, reaches));
|
|
1999
2182
|
const unit = chartCtx.timeUnit === 'ms' ? 1 : 1000;
|
|
@@ -2023,8 +2206,138 @@ function security<T extends SecurityValue>(
|
|
|
2023
2206
|
return (Array.isArray(result) ? mapped : mapped[0]) as T extends unknown[] ? Series[] : Series;
|
|
2024
2207
|
}
|
|
2025
2208
|
|
|
2026
|
-
/**
|
|
2027
|
-
|
|
2209
|
+
/** Bars of a symbol on a timeframe from `ChartContext.bars`, with its calendar and the values of `expression` on them. */
|
|
2210
|
+
interface ExternalRun<T> {
|
|
2211
|
+
bars: Bar[];
|
|
2212
|
+
info: SymbolInfo;
|
|
2213
|
+
result: T;
|
|
2214
|
+
values: number[][];
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
/**
|
|
2218
|
+
* Runs `expression` on the bars of `symbol` / `tf` given by `ChartContext.bars` (#179, #181). Bars not given: the
|
|
2219
|
+
* request is recorded for the {@link MissingSymbolDataError} and the expression runs on no bar. `own`: the chart
|
|
2220
|
+
* symbol, whose calendar defaults to the chart's.
|
|
2221
|
+
*/
|
|
2222
|
+
function runExternal<T extends SecurityValue>(
|
|
2223
|
+
symbol: string,
|
|
2224
|
+
heikin: boolean,
|
|
2225
|
+
tf: string,
|
|
2226
|
+
expression: () => T,
|
|
2227
|
+
ignoreInvalid: boolean,
|
|
2228
|
+
own: boolean
|
|
2229
|
+
): ExternalRun<T> {
|
|
2230
|
+
let bars = chartCtx.bars?.(symbol, tf);
|
|
2231
|
+
if (bars === undefined && !missingSymbols.some((r) => r.symbol === symbol && r.timeframe === tf)) {
|
|
2232
|
+
missingSymbols.push({ symbol, timeframe: tf });
|
|
2233
|
+
}
|
|
2234
|
+
if (bars !== undefined && bars.length === 0 && !ignoreInvalid) throw new Error(`Invalid symbol: ${symbol}`);
|
|
2235
|
+
bars ??= [];
|
|
2236
|
+
if (heikin) bars = heikinAshi(bars);
|
|
2237
|
+
const given = chartCtx.symbolInfo?.(symbol);
|
|
2238
|
+
const info: SymbolInfo = own ? { ...chartCtx, ...given } : (given ?? {});
|
|
2239
|
+
const chartBars = ctxBars.bars;
|
|
2240
|
+
const savedCtx = chartCtx;
|
|
2241
|
+
let result: T;
|
|
2242
|
+
let values: number[][];
|
|
2243
|
+
ctxBars.setAll(bars);
|
|
2244
|
+
// the expression sees that symbol: its ticker, timeframe, calendar and properties
|
|
2245
|
+
chartCtx = {
|
|
2246
|
+
...chartCtx,
|
|
2247
|
+
tickerid: symbol,
|
|
2248
|
+
timeframe: tf,
|
|
2249
|
+
timezone: info.timezone,
|
|
2250
|
+
session: info.session,
|
|
2251
|
+
regularSession: info.regularSession,
|
|
2252
|
+
mintick: info.mintick,
|
|
2253
|
+
minmove: info.minmove,
|
|
2254
|
+
pricescale: info.pricescale,
|
|
2255
|
+
pointvalue: info.pointvalue,
|
|
2256
|
+
mincontract: info.mincontract,
|
|
2257
|
+
symbolType: info.symbolType,
|
|
2258
|
+
currency: info.currency,
|
|
2259
|
+
basecurrency: info.basecurrency,
|
|
2260
|
+
root: info.root,
|
|
2261
|
+
};
|
|
2262
|
+
try {
|
|
2263
|
+
result = expression();
|
|
2264
|
+
const parts = Array.isArray(result) ? result : [result];
|
|
2265
|
+
values = parts.map((p) => valuesOf(p, bars!.length));
|
|
2266
|
+
} finally {
|
|
2267
|
+
ctxBars.setAll(chartBars);
|
|
2268
|
+
chartCtx = savedCtx;
|
|
2269
|
+
}
|
|
2270
|
+
return { bars, info, result, values };
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
/** The chart for the merge functions of `security/other`. */
|
|
2274
|
+
function mergeChart(): OtherSymbolChart {
|
|
2275
|
+
return {
|
|
2276
|
+
timeframe: chartTimeframe().period,
|
|
2277
|
+
timezone: chartCtx.timezone,
|
|
2278
|
+
session: chartCtx.session,
|
|
2279
|
+
timeUnit: chartCtx.timeUnit,
|
|
2280
|
+
// the realtime rule applies to a last bar that receives live updates
|
|
2281
|
+
lastBarConfirmed: chartCtx.realtime !== true,
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
/**
|
|
2286
|
+
* `request.security` of another symbol (#179), or of the chart symbol on a lower timeframe (#181): its bars from
|
|
2287
|
+
* `ChartContext.bars`, merged by `securityOther`.
|
|
2288
|
+
*/
|
|
2289
|
+
function externalSecurity<T extends SecurityValue>(
|
|
2290
|
+
symbol: string,
|
|
2291
|
+
heikin: boolean,
|
|
2292
|
+
tf: string,
|
|
2293
|
+
expression: () => T,
|
|
2294
|
+
gaps: string,
|
|
2295
|
+
lookahead: string,
|
|
2296
|
+
ignoreInvalid: boolean,
|
|
2297
|
+
own: boolean
|
|
2298
|
+
): T extends unknown[] ? Series[] : Series {
|
|
2299
|
+
const { bars, info, result, values } = runExternal(symbol, heikin, tf, expression, ignoreInvalid, own);
|
|
2300
|
+
const index = securityOther(ctxBars.bars, bars, tf, mergeChart(), {
|
|
2301
|
+
gaps: gaps === barmerge.gaps_on,
|
|
2302
|
+
lookahead: lookahead === barmerge.lookahead_on,
|
|
2303
|
+
other: info,
|
|
2304
|
+
});
|
|
2305
|
+
const mapped = values.map((v) => Series.fromArray(ctxBars, index.map((j) => (j < 0 ? NaN : (v[j] ?? NaN)))));
|
|
2306
|
+
return (Array.isArray(result) ? mapped : mapped[0]) as T extends unknown[] ? Series[] : Series;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/**
|
|
2310
|
+
* PineScript `request.security_lower_tf(symbol, timeframe, expression, ignore_invalid_symbol)` (#181): for each
|
|
2311
|
+
* chart bar, the array of the values of `expression` on the bars of the lower timeframe that the chart bar contains
|
|
2312
|
+
* (those whose close is after the open of the chart bar and at or before its close; empty when there are none).
|
|
2313
|
+
* The bars come from `ChartContext.bars`, for the chart symbol (`""`, `syminfo.tickerid`) as for another symbol.
|
|
2314
|
+
* Returns one array per chart bar (oldest first), or one such list per tuple part.
|
|
2315
|
+
* @throws Error when `timeframe` is higher than the chart timeframe
|
|
2316
|
+
*/
|
|
2317
|
+
function security_lower_tf<T extends SecurityValue>(
|
|
2318
|
+
symbol: string,
|
|
2319
|
+
timeframe: string,
|
|
2320
|
+
expression: () => T,
|
|
2321
|
+
ignore_invalid_symbol: boolean = false
|
|
2322
|
+
): T extends unknown[] ? number[][][] : number[][] {
|
|
2323
|
+
collector();
|
|
2324
|
+
const heikin = symbol.startsWith(HEIKIN_ASHI);
|
|
2325
|
+
const base = heikin ? symbol.slice(HEIKIN_ASHI.length) : symbol;
|
|
2326
|
+
const chart = chartTimeframe();
|
|
2327
|
+
const tf = timeframeCore.info(timeframe || chart.period).period;
|
|
2328
|
+
if (timeframeCore.in_seconds(tf) > timeframeCore.in_seconds(chart.period)) {
|
|
2329
|
+
throw new Error(`request.security_lower_tf: timeframe "${tf}" is higher than the chart timeframe "${chart.period}".`);
|
|
2330
|
+
}
|
|
2331
|
+
const own = base === '' || base === chartCtx.tickerid;
|
|
2332
|
+
const ticker = own ? (chartCtx.tickerid ?? '') : base;
|
|
2333
|
+
const { bars, info, result, values } = runExternal(ticker, heikin, tf, expression, ignore_invalid_symbol, own);
|
|
2334
|
+
const ranges = securityLowerTf(ctxBars.bars, bars, tf, mergeChart(), { other: info });
|
|
2335
|
+
const mapped = values.map((v) => ranges.map(([from, to]) => v.slice(from, to)));
|
|
2336
|
+
return (Array.isArray(result) ? mapped : mapped[0]) as T extends unknown[] ? number[][][] : number[][];
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
/** PineScript `request.*`: `security` (issue #101, other symbols #179, lower timeframes #181) and `security_lower_tf`. */
|
|
2340
|
+
export const request = { security, security_lower_tf };
|
|
2028
2341
|
|
|
2029
2342
|
// ── Drawing objects ──────────────────────────────────────────────────────────
|
|
2030
2343
|
// The drawing namespaces come from this bundle so that they share the registry that
|
|
@@ -2091,6 +2404,14 @@ export const text = { ...textCore };
|
|
|
2091
2404
|
* (index, time, and `price`, default the close), so it runs inside eachBar().
|
|
2092
2405
|
*/
|
|
2093
2406
|
export const chart = {
|
|
2407
|
+
/** Chart foreground colour (PineScript `chart.fg_color`), from the chart context. */
|
|
2408
|
+
get fg_color(): string {
|
|
2409
|
+
return chartText('foregroundColor', 'chart.fg_color');
|
|
2410
|
+
},
|
|
2411
|
+
/** Chart background colour (PineScript `chart.bg_color`), from the chart context. */
|
|
2412
|
+
get bg_color(): string {
|
|
2413
|
+
return chartText('backgroundColor', 'chart.bg_color');
|
|
2414
|
+
},
|
|
2094
2415
|
point: {
|
|
2095
2416
|
...chartPointCore,
|
|
2096
2417
|
now(price?: number): ChartPoint {
|