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/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 { parsePineSession } from '../time/session.js';
|
|
125
|
+
import { parsePineSession, sessionRun } from '../time/session.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;
|
|
1575
1624
|
}
|
|
1576
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
|
+
}
|
|
1638
|
+
}
|
|
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. */
|
|
@@ -1829,7 +1901,8 @@ function sessionBars(): { bars: SessionBars; regular: SessionBars } {
|
|
|
1829
1901
|
*/
|
|
1830
1902
|
function calendarFor(
|
|
1831
1903
|
session?: string,
|
|
1832
|
-
timezone?: string
|
|
1904
|
+
timezone?: string,
|
|
1905
|
+
dayPlacement = false
|
|
1833
1906
|
): { bars: SessionBars; test: boolean; inside?: (time: number) => boolean } {
|
|
1834
1907
|
const { bars } = sessionBars();
|
|
1835
1908
|
if (!session && !timezone) return { bars, test: false };
|
|
@@ -1843,7 +1916,7 @@ function calendarFor(
|
|
|
1843
1916
|
}
|
|
1844
1917
|
const spec = session ? { session } : { session: toSpec(chartCtx.session!).session };
|
|
1845
1918
|
return {
|
|
1846
|
-
bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol'), bars.chart.period),
|
|
1919
|
+
bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol', dayPlacement), bars.chart.period),
|
|
1847
1920
|
test: true,
|
|
1848
1921
|
// a PineScript session string: the rules of time.inSession (runs ending at 0000, days around a clock change)
|
|
1849
1922
|
inside: session
|
|
@@ -1864,18 +1937,49 @@ function callableSeries<F extends (...args: never[]) => Series>(series: Series,
|
|
|
1864
1937
|
return fn as Series & F;
|
|
1865
1938
|
}
|
|
1866
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
|
+
|
|
1867
1973
|
function timeFunction(tf?: string, session?: string, timezone?: string): Series {
|
|
1868
1974
|
collector();
|
|
1869
|
-
const {
|
|
1870
|
-
|
|
1871
|
-
return perBar((t) => (test && !(inside ? inside(t) : bars.inside(t)) ? NaN : bars.timeOf(t, timeframeString)));
|
|
1975
|
+
const { open } = sessionPeriods(tf || chartTimeframe().period, session, timezone);
|
|
1976
|
+
return perBar((t) => open(t));
|
|
1872
1977
|
}
|
|
1873
1978
|
|
|
1874
1979
|
function timeCloseFunction(tf?: string, session?: string, timezone?: string): Series {
|
|
1875
1980
|
collector();
|
|
1876
|
-
const {
|
|
1877
|
-
|
|
1878
|
-
return perBar((t) => (test && !(inside ? inside(t) : bars.inside(t)) ? NaN : bars.closeOf(t, timeframeString)));
|
|
1981
|
+
const { close } = sessionPeriods(tf || chartTimeframe().period, session, timezone);
|
|
1982
|
+
return perBar((t) => close(t));
|
|
1879
1983
|
}
|
|
1880
1984
|
|
|
1881
1985
|
/**
|
|
@@ -1909,6 +2013,26 @@ function changeOf(tf: string, firstBar = false): Series {
|
|
|
1909
2013
|
return perBar((t, i, times) => ((i > 0 ? changed(bars.timeOf(t, tf), bars.timeOf(times[i - 1]!, tf)) : firstBar) ? 1 : 0));
|
|
1910
2014
|
}
|
|
1911
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
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
|
|
1912
2036
|
function flag(key: keyof SessionFlags): Series {
|
|
1913
2037
|
const { bars, regular } = sessionBars();
|
|
1914
2038
|
const values = sessionFlags(ctxBars.bars.map(barTime), bars, regular)[key];
|
|
@@ -1998,10 +2122,14 @@ function valuesOf(value: Series | number | boolean, length: number): number[] {
|
|
|
1998
2122
|
}
|
|
1999
2123
|
|
|
2000
2124
|
/**
|
|
2001
|
-
* PineScript `request.security`
|
|
2002
|
-
* `ticker.standard(...)`)
|
|
2003
|
-
*
|
|
2004
|
-
*
|
|
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`.
|
|
2005
2133
|
*
|
|
2006
2134
|
* PineScript rules: lookahead off shows a period's value from the
|
|
2007
2135
|
* bar that completes it; lookahead on from its first bar; gaps on keeps only the bar where the value appears.
|
|
@@ -2012,18 +2140,20 @@ function security<T extends SecurityValue>(
|
|
|
2012
2140
|
timeframe: string,
|
|
2013
2141
|
expression: () => T,
|
|
2014
2142
|
gaps: string = barmerge.gaps_off,
|
|
2015
|
-
lookahead: string = barmerge.lookahead_off
|
|
2143
|
+
lookahead: string = barmerge.lookahead_off,
|
|
2144
|
+
ignore_invalid_symbol: boolean = false
|
|
2016
2145
|
): T extends unknown[] ? Series[] : Series {
|
|
2017
2146
|
collector();
|
|
2018
2147
|
const heikin = symbol.startsWith(HEIKIN_ASHI);
|
|
2019
2148
|
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
2149
|
const chart = chartTimeframe();
|
|
2024
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
|
+
}
|
|
2025
2154
|
if (timeframeCore.in_seconds(tf) < timeframeCore.in_seconds(chart.period)) {
|
|
2026
|
-
|
|
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);
|
|
2027
2157
|
}
|
|
2028
2158
|
|
|
2029
2159
|
const chartBars = ctxBars.bars;
|
|
@@ -2045,6 +2175,8 @@ function security<T extends SecurityValue>(
|
|
|
2045
2175
|
const periods = at ? calendar.calendar.periods(at.date) : [];
|
|
2046
2176
|
return periods.length ? calendar.timeOf(periods[0]![0], tf) : starts[i]!;
|
|
2047
2177
|
});
|
|
2178
|
+
const tfInfo = timeframeCore.info(tf);
|
|
2179
|
+
strayPeriods(starts, dayPeriods, times, loaded, tfInfo.isdwm ? null : timeframeCore.in_seconds(tf) * 1000);
|
|
2048
2180
|
const reaches = times.map((t) => loaded.closeOf(t, chart.period) >= calendar.periodEnd(t, tf));
|
|
2049
2181
|
({ group, latest } = periodsOf(starts, dayPeriods, reaches));
|
|
2050
2182
|
const unit = chartCtx.timeUnit === 'ms' ? 1 : 1000;
|
|
@@ -2074,8 +2206,138 @@ function security<T extends SecurityValue>(
|
|
|
2074
2206
|
return (Array.isArray(result) ? mapped : mapped[0]) as T extends unknown[] ? Series[] : Series;
|
|
2075
2207
|
}
|
|
2076
2208
|
|
|
2077
|
-
/**
|
|
2078
|
-
|
|
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 };
|
|
2079
2341
|
|
|
2080
2342
|
// ── Drawing objects ──────────────────────────────────────────────────────────
|
|
2081
2343
|
// The drawing namespaces come from this bundle so that they share the registry that
|