oakscriptjs 0.11.0 → 0.11.2
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.d.ts.map +1 -1
- package/dist/cjs/callsite/index.js +6 -3
- 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 +4 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -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 +66 -13
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +139 -35
- 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 +12 -7
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/session/context.d.ts +22 -5
- package/dist/cjs/session/context.d.ts.map +1 -1
- package/dist/cjs/session/context.js +67 -6
- 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 +64 -45
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +140 -126
- package/dist/cjs/ta/index.js.map +3 -3
- 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 +10 -3
- package/dist/cjs/table/index.d.ts.map +1 -1
- package/dist/cjs/table/index.js +11 -1
- 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.d.ts.map +1 -1
- package/dist/esm/callsite/index.js +6 -3
- 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 +4 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +8 -5
- 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 +66 -13
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +187 -54
- 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 +23 -7
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/session/context.d.ts +22 -5
- package/dist/esm/session/context.d.ts.map +1 -1
- package/dist/esm/session/context.js +86 -10
- 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 +64 -45
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +198 -228
- 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 +10 -3
- package/dist/esm/table/index.d.ts.map +1 -1
- package/dist/esm/table/index.js +23 -3
- 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 +6 -3
- package/src/drawing/registry.ts +30 -7
- package/src/index.ts +16 -3
- 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 +262 -58
- package/src/security/other.ts +269 -0
- package/src/session/bars.ts +24 -1
- package/src/session/calendar.ts +26 -7
- package/src/session/context.ts +104 -10
- 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 +200 -225
- 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 +22 -3
- package/src/time/session.ts +58 -16
- package/src/time/timezone.ts +9 -2
- package/src/types/metadata.ts +1 -1
package/src/script/index.ts
CHANGED
|
@@ -96,8 +96,9 @@ import * as strCore from '../str/index.js';
|
|
|
96
96
|
import { formatMessage } from '../str/messageformat.js';
|
|
97
97
|
import { tickScale } from '../str/numberformat.js';
|
|
98
98
|
import { TradingCalendar, type SessionSpec } from '../session/calendar.js';
|
|
99
|
-
import { SessionBars, sessionFlags, type SessionFlags } from '../session/bars.js';
|
|
99
|
+
import { SessionBars, sessionFlags, strayPeriods, type SessionFlags } from '../session/bars.js';
|
|
100
100
|
import { afterCompletion, heikinAshi, mapToChart, periodsOf, resample } from '../security/resample.js';
|
|
101
|
+
import { securityLowerTf, securityOther, type OtherSymbolChart } from '../security/other.js';
|
|
101
102
|
import { position } from '../types/index.js';
|
|
102
103
|
import type { ChartPoint } from '../types/index.js';
|
|
103
104
|
import {
|
|
@@ -121,7 +122,7 @@ import {
|
|
|
121
122
|
type StrategyTrade,
|
|
122
123
|
type StrategyVariable,
|
|
123
124
|
} from '../strategy/index.js';
|
|
124
|
-
import {
|
|
125
|
+
import { sessionPeriods, type SessionPeriods } from '../session/context.js';
|
|
125
126
|
|
|
126
127
|
/** Everything one run of a script produced. */
|
|
127
128
|
export interface ScriptRunResult {
|
|
@@ -302,8 +303,10 @@ export function executeScript(
|
|
|
302
303
|
ctx = freshCollector(inputs);
|
|
303
304
|
resetDrawings();
|
|
304
305
|
mathCore.resetRandom();
|
|
306
|
+
missingSymbols = [];
|
|
305
307
|
try {
|
|
306
308
|
body();
|
|
309
|
+
if (missingSymbols.length) throw new MissingSymbolDataError(missingSymbols);
|
|
307
310
|
const c = ctx;
|
|
308
311
|
return {
|
|
309
312
|
metadata: {
|
|
@@ -480,6 +483,10 @@ export const input = {
|
|
|
480
483
|
'input_session'
|
|
481
484
|
);
|
|
482
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
|
+
},
|
|
483
490
|
/** Date and time as a UNIX time in milliseconds, e.g. `input.time(timestamp('2024-01-01'), 'Start')`. */
|
|
484
491
|
time(defval: number, title?: string, opts: InputOptions = {}): number {
|
|
485
492
|
const v = registerInput<unknown>({ type: 'time', defval, title, ...common(opts) }, 'input_time');
|
|
@@ -564,12 +571,12 @@ export const ta = {
|
|
|
564
571
|
typeof sourceOrLength === 'number'
|
|
565
572
|
? taSeries.lowestbars(low, sourceOrLength)
|
|
566
573
|
: taSeries.lowestbars(sourceOrLength, length!),
|
|
567
|
-
/** `ta.vwap(source, anchor?, stdev_mult?)` with the chart volume; the default anchor is a new trading
|
|
568
|
-
*
|
|
569
|
-
*
|
|
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). */
|
|
570
578
|
vwap: ((source: Series, anchor?: Series, stdev_mult?: number): Series | [Series, Series, Series] => {
|
|
571
|
-
|
|
572
|
-
const a = anchor ?? changeOf('1D', true);
|
|
579
|
+
const a = anchor ?? vwapAnchor();
|
|
573
580
|
return stdev_mult === undefined ? taSeries.vwap(source, volume, a) : taSeries.vwapBands(source, volume, a, stdev_mult);
|
|
574
581
|
}) as {
|
|
575
582
|
(source: Series, anchor?: Series): Series;
|
|
@@ -1266,7 +1273,7 @@ export function eachBar(fn: (c: BarContext) => number | boolean | void): Series
|
|
|
1266
1273
|
for (idx = 0; idx < b.length; idx++) {
|
|
1267
1274
|
if (pendingError && pendingError.bar === idx) throw new Error(historyError(idx, pendingError.barsBack));
|
|
1268
1275
|
currentBar = idx;
|
|
1269
|
-
setDrawingBar(idx, idx === b.length - 1);
|
|
1276
|
+
setDrawingBar(idx, idx === b.length - 1, b.length - 1);
|
|
1270
1277
|
const r = fn(ctx);
|
|
1271
1278
|
out[idx] = typeof r === 'number' ? r : typeof r === 'boolean' ? (r ? 1 : 0) : NaN;
|
|
1272
1279
|
// grow the history buffers from the largest reference of this bar
|
|
@@ -1557,6 +1564,13 @@ export interface ChartContext {
|
|
|
1557
1564
|
symbolType?: string;
|
|
1558
1565
|
/** Currency of the symbol (PineScript `syminfo.currency`), e.g. "USD" */
|
|
1559
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;
|
|
1560
1574
|
/** Chart foreground colour (PineScript `chart.fg_color`), e.g. "#DBDBDB" on a dark chart */
|
|
1561
1575
|
foregroundColor?: string;
|
|
1562
1576
|
/** Chart background colour (PineScript `chart.bg_color`), e.g. "#0F0F0F" on a dark chart */
|
|
@@ -1572,8 +1586,60 @@ export interface ChartContext {
|
|
|
1572
1586
|
realtime?: boolean;
|
|
1573
1587
|
/** On a realtime last bar: this run is the first update of the bar (`barstate.isnew`); default true. */
|
|
1574
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
|
+
}
|
|
1575
1638
|
}
|
|
1576
1639
|
|
|
1640
|
+
/** Requests of other symbols without bars in the current run. */
|
|
1641
|
+
let missingSymbols: SymbolDataRequest[] = [];
|
|
1642
|
+
|
|
1577
1643
|
let chartCtx: ChartContext = {};
|
|
1578
1644
|
|
|
1579
1645
|
function chartTimeframe(): timeframeCore.TimeframeInfo {
|
|
@@ -1655,7 +1721,7 @@ function symbolProperty(key: 'mintick' | 'pointvalue' | 'mincontract'): number {
|
|
|
1655
1721
|
}
|
|
1656
1722
|
|
|
1657
1723
|
/** A text value of the chart or the symbol that the host passes in the chart context. */
|
|
1658
|
-
function chartText(key: 'symbolType' | 'currency' | 'foregroundColor' | 'backgroundColor', name: string): string {
|
|
1724
|
+
function chartText(key: 'symbolType' | 'currency' | 'basecurrency' | 'root' | 'foregroundColor' | 'backgroundColor', name: string): string {
|
|
1659
1725
|
collector();
|
|
1660
1726
|
const value = chartCtx[key];
|
|
1661
1727
|
if (value === undefined) {
|
|
@@ -1675,8 +1741,8 @@ function symbolScale(): [number, number] {
|
|
|
1675
1741
|
|
|
1676
1742
|
/**
|
|
1677
1743
|
* PineScript `syminfo.timezone`, `syminfo.tickerid`, `syminfo.session` (session type), `syminfo.mintick`,
|
|
1678
|
-
* `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue`, `syminfo.mincontract`, `syminfo.type
|
|
1679
|
-
* `syminfo.currency`, from the chart context.
|
|
1744
|
+
* `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue`, `syminfo.mincontract`, `syminfo.type`,
|
|
1745
|
+
* `syminfo.currency`, `syminfo.basecurrency` and `syminfo.root`, from the chart context.
|
|
1680
1746
|
*/
|
|
1681
1747
|
export const syminfo = {
|
|
1682
1748
|
get mintick(): number {
|
|
@@ -1714,6 +1780,12 @@ export const syminfo = {
|
|
|
1714
1780
|
get currency(): string {
|
|
1715
1781
|
return chartText('currency', 'syminfo.currency');
|
|
1716
1782
|
},
|
|
1783
|
+
get basecurrency(): string {
|
|
1784
|
+
return chartText('basecurrency', 'syminfo.basecurrency');
|
|
1785
|
+
},
|
|
1786
|
+
get root(): string {
|
|
1787
|
+
return chartText('root', 'syminfo.root');
|
|
1788
|
+
},
|
|
1717
1789
|
};
|
|
1718
1790
|
|
|
1719
1791
|
/** A calendar function: `(time, timezone?)` on a UNIX time or on a Series of times. */
|
|
@@ -1822,36 +1894,6 @@ function sessionBars(): { bars: SessionBars; regular: SessionBars } {
|
|
|
1822
1894
|
return sessionCache;
|
|
1823
1895
|
}
|
|
1824
1896
|
|
|
1825
|
-
/**
|
|
1826
|
-
* Calendar for `time(tf, session, timezone)` arguments. Without session and time zone: the loaded
|
|
1827
|
-
* session. As in PineScript, an explicit time zone reads the symbol session hours in that zone without
|
|
1828
|
-
* corrections and holidays, and an explicit session follows PineScript's session string rules.
|
|
1829
|
-
*/
|
|
1830
|
-
function calendarFor(
|
|
1831
|
-
session?: string,
|
|
1832
|
-
timezone?: string
|
|
1833
|
-
): { bars: SessionBars; test: boolean; inside?: (time: number) => boolean } {
|
|
1834
|
-
const { bars } = sessionBars();
|
|
1835
|
-
if (!session && !timezone) return { bars, test: false };
|
|
1836
|
-
const tz = timezone ?? exchangeTimezone();
|
|
1837
|
-
// a session string that is empty or does not start with a digit is the symbol session: "regular" the regular
|
|
1838
|
-
// hours, "extended" and any other name the loaded hours (PineScript)
|
|
1839
|
-
if (session !== undefined && parsePineSession(session) === null) {
|
|
1840
|
-
const name = session.trim();
|
|
1841
|
-
const symbol = name === 'regular' && chartCtx.regularSession ? chartCtx.regularSession : chartCtx.session!;
|
|
1842
|
-
return { bars: new SessionBars(new TradingCalendar(tz, { session: toSpec(symbol).session }, 'symbol'), bars.chart.period), test: true };
|
|
1843
|
-
}
|
|
1844
|
-
const spec = session ? { session } : { session: toSpec(chartCtx.session!).session };
|
|
1845
|
-
return {
|
|
1846
|
-
bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol'), bars.chart.period),
|
|
1847
|
-
test: true,
|
|
1848
|
-
// a PineScript session string: the rules of time.inSession (runs ending at 0000, days around a clock change)
|
|
1849
|
-
inside: session
|
|
1850
|
-
? (time) => timeCore.inSession(time, session, tz, timeframeCore.in_seconds(bars.chart.period) * 1000)
|
|
1851
|
-
: undefined,
|
|
1852
|
-
};
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
1897
|
function perBar(fn: (t: number, i: number, times: number[]) => number): Series {
|
|
1856
1898
|
const times = ctxBars.bars.map(barTime);
|
|
1857
1899
|
return Series.fromArray(ctxBars, times.map((t, i) => fn(t, i, times)));
|
|
@@ -1864,18 +1906,22 @@ function callableSeries<F extends (...args: never[]) => Series>(series: Series,
|
|
|
1864
1906
|
return fn as Series & F;
|
|
1865
1907
|
}
|
|
1866
1908
|
|
|
1909
|
+
/** `time(tf, session, timezone)` / `time_close(...)` periods of the run's chart (see session/context `sessionPeriods`). */
|
|
1910
|
+
function chartSessionPeriods(timeframeString: string, session?: string, timezone?: string): SessionPeriods {
|
|
1911
|
+
const { bars } = sessionBars();
|
|
1912
|
+
return sessionPeriods(bars, { ...chartCtx, timezone: exchangeTimezone() }, timeframeString, session, timezone);
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1867
1915
|
function timeFunction(tf?: string, session?: string, timezone?: string): Series {
|
|
1868
1916
|
collector();
|
|
1869
|
-
const {
|
|
1870
|
-
|
|
1871
|
-
return perBar((t) => (test && !(inside ? inside(t) : bars.inside(t)) ? NaN : bars.timeOf(t, timeframeString)));
|
|
1917
|
+
const { open } = chartSessionPeriods(tf || chartTimeframe().period, session, timezone);
|
|
1918
|
+
return perBar((t) => open(t));
|
|
1872
1919
|
}
|
|
1873
1920
|
|
|
1874
1921
|
function timeCloseFunction(tf?: string, session?: string, timezone?: string): Series {
|
|
1875
1922
|
collector();
|
|
1876
|
-
const {
|
|
1877
|
-
|
|
1878
|
-
return perBar((t) => (test && !(inside ? inside(t) : bars.inside(t)) ? NaN : bars.closeOf(t, timeframeString)));
|
|
1923
|
+
const { close } = chartSessionPeriods(tf || chartTimeframe().period, session, timezone);
|
|
1924
|
+
return perBar((t) => close(t));
|
|
1879
1925
|
}
|
|
1880
1926
|
|
|
1881
1927
|
/**
|
|
@@ -1909,6 +1955,26 @@ function changeOf(tf: string, firstBar = false): Series {
|
|
|
1909
1955
|
return perBar((t, i, times) => ((i > 0 ? changed(bars.timeOf(t, tf), bars.timeOf(times[i - 1]!, tf)) : firstBar) ? 1 : 0));
|
|
1910
1956
|
}
|
|
1911
1957
|
|
|
1958
|
+
/**
|
|
1959
|
+
* Default anchor of `ta.vwap` (#173, #178): the first bar and each bar whose trading day differs from the last bar
|
|
1960
|
+
* that has one. Bars after an early close have no day: the first of them continues the day before it, each further
|
|
1961
|
+
* one starts a new sum, and the next day starts on its first bar (where `timeframe.change("D")` is false). On
|
|
1962
|
+
* D / W / M charts every bar.
|
|
1963
|
+
*/
|
|
1964
|
+
function vwapAnchor(): Series {
|
|
1965
|
+
if (chartTimeframe().isdwm) return barFlag(() => true);
|
|
1966
|
+
const { bars } = sessionBars();
|
|
1967
|
+
let last = NaN;
|
|
1968
|
+
let previous = NaN;
|
|
1969
|
+
return perBar((t, i) => {
|
|
1970
|
+
const day = bars.timeOf(t, '1D');
|
|
1971
|
+
const isNew = i === 0 || (Number.isNaN(day) ? Number.isNaN(previous) : day !== last);
|
|
1972
|
+
if (!Number.isNaN(day)) last = day;
|
|
1973
|
+
previous = day;
|
|
1974
|
+
return isNew ? 1 : 0;
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1912
1978
|
function flag(key: keyof SessionFlags): Series {
|
|
1913
1979
|
const { bars, regular } = sessionBars();
|
|
1914
1980
|
const values = sessionFlags(ctxBars.bars.map(barTime), bars, regular)[key];
|
|
@@ -1998,10 +2064,14 @@ function valuesOf(value: Series | number | boolean, length: number): number[] {
|
|
|
1998
2064
|
}
|
|
1999
2065
|
|
|
2000
2066
|
/**
|
|
2001
|
-
* PineScript `request.security`
|
|
2002
|
-
* `ticker.standard(...)`)
|
|
2003
|
-
*
|
|
2004
|
-
*
|
|
2067
|
+
* PineScript `request.security` on a timeframe equal to or higher than the chart's. For the chart symbol
|
|
2068
|
+
* (`syminfo.tickerid`, `""`, `ticker.heikinashi(...)`, `ticker.standard(...)`) the higher-timeframe bars are built by
|
|
2069
|
+
* grouping the chart bars; another symbol's bars come from `ChartContext.bars` (#179) and are merged on the chart bars
|
|
2070
|
+
* by close time (lookahead off) or by open time / trading day (lookahead on), see `securityOther`. `expression` runs
|
|
2071
|
+
* on those bars (its `ta.*` calls, tuples, `timeframe.*` and `syminfo.*` see them). A lower timeframe throws.
|
|
2072
|
+
*
|
|
2073
|
+
* A symbol that does not exist (bars []) stops the script with "Invalid symbol: X", or gives na with
|
|
2074
|
+
* `ignore_invalid_symbol`.
|
|
2005
2075
|
*
|
|
2006
2076
|
* PineScript rules: lookahead off shows a period's value from the
|
|
2007
2077
|
* bar that completes it; lookahead on from its first bar; gaps on keeps only the bar where the value appears.
|
|
@@ -2012,18 +2082,20 @@ function security<T extends SecurityValue>(
|
|
|
2012
2082
|
timeframe: string,
|
|
2013
2083
|
expression: () => T,
|
|
2014
2084
|
gaps: string = barmerge.gaps_off,
|
|
2015
|
-
lookahead: string = barmerge.lookahead_off
|
|
2085
|
+
lookahead: string = barmerge.lookahead_off,
|
|
2086
|
+
ignore_invalid_symbol: boolean = false
|
|
2016
2087
|
): T extends unknown[] ? Series[] : Series {
|
|
2017
2088
|
collector();
|
|
2018
2089
|
const heikin = symbol.startsWith(HEIKIN_ASHI);
|
|
2019
2090
|
const base = heikin ? symbol.slice(HEIKIN_ASHI.length) : symbol;
|
|
2020
|
-
if (base !== '' && base !== chartCtx.tickerid) {
|
|
2021
|
-
throw new Error(`request.security: "${symbol}" is not the chart symbol; other symbols need external data.`);
|
|
2022
|
-
}
|
|
2023
2091
|
const chart = chartTimeframe();
|
|
2024
2092
|
const tf = timeframeCore.info(timeframe || chart.period).period;
|
|
2093
|
+
if (base !== '' && base !== chartCtx.tickerid) {
|
|
2094
|
+
return externalSecurity(base, heikin, tf, expression, gaps, lookahead, ignore_invalid_symbol, false);
|
|
2095
|
+
}
|
|
2025
2096
|
if (timeframeCore.in_seconds(tf) < timeframeCore.in_seconds(chart.period)) {
|
|
2026
|
-
|
|
2097
|
+
// a lower timeframe of the chart symbol: its bars come from ChartContext.bars (#181)
|
|
2098
|
+
return externalSecurity(chartCtx.tickerid ?? '', heikin, tf, expression, gaps, lookahead, ignore_invalid_symbol, true);
|
|
2027
2099
|
}
|
|
2028
2100
|
|
|
2029
2101
|
const chartBars = ctxBars.bars;
|
|
@@ -2045,6 +2117,8 @@ function security<T extends SecurityValue>(
|
|
|
2045
2117
|
const periods = at ? calendar.calendar.periods(at.date) : [];
|
|
2046
2118
|
return periods.length ? calendar.timeOf(periods[0]![0], tf) : starts[i]!;
|
|
2047
2119
|
});
|
|
2120
|
+
const tfInfo = timeframeCore.info(tf);
|
|
2121
|
+
strayPeriods(starts, dayPeriods, times, loaded, tfInfo.isdwm ? null : timeframeCore.in_seconds(tf) * 1000);
|
|
2048
2122
|
const reaches = times.map((t) => loaded.closeOf(t, chart.period) >= calendar.periodEnd(t, tf));
|
|
2049
2123
|
({ group, latest } = periodsOf(starts, dayPeriods, reaches));
|
|
2050
2124
|
const unit = chartCtx.timeUnit === 'ms' ? 1 : 1000;
|
|
@@ -2074,8 +2148,138 @@ function security<T extends SecurityValue>(
|
|
|
2074
2148
|
return (Array.isArray(result) ? mapped : mapped[0]) as T extends unknown[] ? Series[] : Series;
|
|
2075
2149
|
}
|
|
2076
2150
|
|
|
2077
|
-
/**
|
|
2078
|
-
|
|
2151
|
+
/** Bars of a symbol on a timeframe from `ChartContext.bars`, with its calendar and the values of `expression` on them. */
|
|
2152
|
+
interface ExternalRun<T> {
|
|
2153
|
+
bars: Bar[];
|
|
2154
|
+
info: SymbolInfo;
|
|
2155
|
+
result: T;
|
|
2156
|
+
values: number[][];
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
/**
|
|
2160
|
+
* Runs `expression` on the bars of `symbol` / `tf` given by `ChartContext.bars` (#179, #181). Bars not given: the
|
|
2161
|
+
* request is recorded for the {@link MissingSymbolDataError} and the expression runs on no bar. `own`: the chart
|
|
2162
|
+
* symbol, whose calendar defaults to the chart's.
|
|
2163
|
+
*/
|
|
2164
|
+
function runExternal<T extends SecurityValue>(
|
|
2165
|
+
symbol: string,
|
|
2166
|
+
heikin: boolean,
|
|
2167
|
+
tf: string,
|
|
2168
|
+
expression: () => T,
|
|
2169
|
+
ignoreInvalid: boolean,
|
|
2170
|
+
own: boolean
|
|
2171
|
+
): ExternalRun<T> {
|
|
2172
|
+
let bars = chartCtx.bars?.(symbol, tf);
|
|
2173
|
+
if (bars === undefined && !missingSymbols.some((r) => r.symbol === symbol && r.timeframe === tf)) {
|
|
2174
|
+
missingSymbols.push({ symbol, timeframe: tf });
|
|
2175
|
+
}
|
|
2176
|
+
if (bars !== undefined && bars.length === 0 && !ignoreInvalid) throw new Error(`Invalid symbol: ${symbol}`);
|
|
2177
|
+
bars ??= [];
|
|
2178
|
+
if (heikin) bars = heikinAshi(bars);
|
|
2179
|
+
const given = chartCtx.symbolInfo?.(symbol);
|
|
2180
|
+
const info: SymbolInfo = own ? { ...chartCtx, ...given } : (given ?? {});
|
|
2181
|
+
const chartBars = ctxBars.bars;
|
|
2182
|
+
const savedCtx = chartCtx;
|
|
2183
|
+
let result: T;
|
|
2184
|
+
let values: number[][];
|
|
2185
|
+
ctxBars.setAll(bars);
|
|
2186
|
+
// the expression sees that symbol: its ticker, timeframe, calendar and properties
|
|
2187
|
+
chartCtx = {
|
|
2188
|
+
...chartCtx,
|
|
2189
|
+
tickerid: symbol,
|
|
2190
|
+
timeframe: tf,
|
|
2191
|
+
timezone: info.timezone,
|
|
2192
|
+
session: info.session,
|
|
2193
|
+
regularSession: info.regularSession,
|
|
2194
|
+
mintick: info.mintick,
|
|
2195
|
+
minmove: info.minmove,
|
|
2196
|
+
pricescale: info.pricescale,
|
|
2197
|
+
pointvalue: info.pointvalue,
|
|
2198
|
+
mincontract: info.mincontract,
|
|
2199
|
+
symbolType: info.symbolType,
|
|
2200
|
+
currency: info.currency,
|
|
2201
|
+
basecurrency: info.basecurrency,
|
|
2202
|
+
root: info.root,
|
|
2203
|
+
};
|
|
2204
|
+
try {
|
|
2205
|
+
result = expression();
|
|
2206
|
+
const parts = Array.isArray(result) ? result : [result];
|
|
2207
|
+
values = parts.map((p) => valuesOf(p, bars!.length));
|
|
2208
|
+
} finally {
|
|
2209
|
+
ctxBars.setAll(chartBars);
|
|
2210
|
+
chartCtx = savedCtx;
|
|
2211
|
+
}
|
|
2212
|
+
return { bars, info, result, values };
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
/** The chart for the merge functions of `security/other`. */
|
|
2216
|
+
function mergeChart(): OtherSymbolChart {
|
|
2217
|
+
return {
|
|
2218
|
+
timeframe: chartTimeframe().period,
|
|
2219
|
+
timezone: chartCtx.timezone,
|
|
2220
|
+
session: chartCtx.session,
|
|
2221
|
+
timeUnit: chartCtx.timeUnit,
|
|
2222
|
+
// the realtime rule applies to a last bar that receives live updates
|
|
2223
|
+
lastBarConfirmed: chartCtx.realtime !== true,
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
/**
|
|
2228
|
+
* `request.security` of another symbol (#179), or of the chart symbol on a lower timeframe (#181): its bars from
|
|
2229
|
+
* `ChartContext.bars`, merged by `securityOther`.
|
|
2230
|
+
*/
|
|
2231
|
+
function externalSecurity<T extends SecurityValue>(
|
|
2232
|
+
symbol: string,
|
|
2233
|
+
heikin: boolean,
|
|
2234
|
+
tf: string,
|
|
2235
|
+
expression: () => T,
|
|
2236
|
+
gaps: string,
|
|
2237
|
+
lookahead: string,
|
|
2238
|
+
ignoreInvalid: boolean,
|
|
2239
|
+
own: boolean
|
|
2240
|
+
): T extends unknown[] ? Series[] : Series {
|
|
2241
|
+
const { bars, info, result, values } = runExternal(symbol, heikin, tf, expression, ignoreInvalid, own);
|
|
2242
|
+
const index = securityOther(ctxBars.bars, bars, tf, mergeChart(), {
|
|
2243
|
+
gaps: gaps === barmerge.gaps_on,
|
|
2244
|
+
lookahead: lookahead === barmerge.lookahead_on,
|
|
2245
|
+
other: info,
|
|
2246
|
+
});
|
|
2247
|
+
const mapped = values.map((v) => Series.fromArray(ctxBars, index.map((j) => (j < 0 ? NaN : (v[j] ?? NaN)))));
|
|
2248
|
+
return (Array.isArray(result) ? mapped : mapped[0]) as T extends unknown[] ? Series[] : Series;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
/**
|
|
2252
|
+
* PineScript `request.security_lower_tf(symbol, timeframe, expression, ignore_invalid_symbol)` (#181): for each
|
|
2253
|
+
* chart bar, the array of the values of `expression` on the bars of the lower timeframe that the chart bar contains
|
|
2254
|
+
* (those whose close is after the open of the chart bar and at or before its close; empty when there are none).
|
|
2255
|
+
* The bars come from `ChartContext.bars`, for the chart symbol (`""`, `syminfo.tickerid`) as for another symbol.
|
|
2256
|
+
* Returns one array per chart bar (oldest first), or one such list per tuple part.
|
|
2257
|
+
* @throws Error when `timeframe` is higher than the chart timeframe
|
|
2258
|
+
*/
|
|
2259
|
+
function security_lower_tf<T extends SecurityValue>(
|
|
2260
|
+
symbol: string,
|
|
2261
|
+
timeframe: string,
|
|
2262
|
+
expression: () => T,
|
|
2263
|
+
ignore_invalid_symbol: boolean = false
|
|
2264
|
+
): T extends unknown[] ? number[][][] : number[][] {
|
|
2265
|
+
collector();
|
|
2266
|
+
const heikin = symbol.startsWith(HEIKIN_ASHI);
|
|
2267
|
+
const base = heikin ? symbol.slice(HEIKIN_ASHI.length) : symbol;
|
|
2268
|
+
const chart = chartTimeframe();
|
|
2269
|
+
const tf = timeframeCore.info(timeframe || chart.period).period;
|
|
2270
|
+
if (timeframeCore.in_seconds(tf) > timeframeCore.in_seconds(chart.period)) {
|
|
2271
|
+
throw new Error(`request.security_lower_tf: timeframe "${tf}" is higher than the chart timeframe "${chart.period}".`);
|
|
2272
|
+
}
|
|
2273
|
+
const own = base === '' || base === chartCtx.tickerid;
|
|
2274
|
+
const ticker = own ? (chartCtx.tickerid ?? '') : base;
|
|
2275
|
+
const { bars, info, result, values } = runExternal(ticker, heikin, tf, expression, ignore_invalid_symbol, own);
|
|
2276
|
+
const ranges = securityLowerTf(ctxBars.bars, bars, tf, mergeChart(), { other: info });
|
|
2277
|
+
const mapped = values.map((v) => ranges.map(([from, to]) => v.slice(from, to)));
|
|
2278
|
+
return (Array.isArray(result) ? mapped : mapped[0]) as T extends unknown[] ? number[][][] : number[][];
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
/** PineScript `request.*`: `security` (issue #101, other symbols #179, lower timeframes #181) and `security_lower_tf`. */
|
|
2282
|
+
export const request = { security, security_lower_tf };
|
|
2079
2283
|
|
|
2080
2284
|
// ── Drawing objects ──────────────────────────────────────────────────────────
|
|
2081
2285
|
// The drawing namespaces come from this bundle so that they share the registry that
|