oakscriptjs 0.13.0 → 0.13.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.
@@ -0,0 +1,140 @@
1
+ # Strategies
2
+
3
+ A strategy is a script declared with `strategy()` whose orders go to an order engine. OakScriptJS does not fill
4
+ orders itself: the host supplies the engine (an app's backtester, or your own), which fills the orders, keeps the
5
+ trades and computes the results the host shows (net profit, drawdown, list of trades...).
6
+
7
+ ## Declaration and sizing
8
+
9
+ ```js
10
+ strategy("Breakout", {
11
+ overlay: true, // default false
12
+ initial_capital: 10000, // default 100000
13
+ default_qty_type: strategy.percent_of_equity, // or strategy.fixed (units), strategy.cash (amount)
14
+ default_qty_value: 25, // default 100 (% of equity)
15
+ pyramiding: 0, // entries allowed in the same direction beyond the first
16
+ commission_type: strategy.commission.percent, // or .cash_per_contract, .cash_per_order
17
+ commission_value: 0.1,
18
+ slippage: 2, // ticks added to market and stop fills
19
+ });
20
+ const length = input.int(20, "Channel length", { minval: 2 });
21
+ const upper = ta.highest(high, length).offset(1);
22
+ const lower = ta.lowest(low, length).offset(1);
23
+ plot(upper, "Upper", { color: color.green });
24
+ plot(lower, "Lower", { color: color.red });
25
+ strategy.eachBar((c) => {
26
+ if (strategy.position_size <= 0) strategy.entry("Long", strategy.long, { stop: c.get(upper) });
27
+ if (strategy.position_size >= 0) strategy.entry("Short", strategy.short, { stop: c.get(lower) });
28
+ });
29
+ ```
30
+
31
+ Other properties, with the Pine Script v6 names and defaults: `currency`, `margin_long` / `margin_short` (100),
32
+ `process_orders_on_close` (false), `close_entries_rule` (`"FIFO"`), `calc_on_order_fills`, `calc_on_every_tick`,
33
+ `use_bar_magnifier`, `fill_orders_on_standard_ohlc`, `backtest_fill_limits_assumption`, `risk_free_rate` (2).
34
+ A host can override them from its settings.
35
+
36
+ Order size: the `qty` option of an order (units) wins over `default_qty_type` / `default_qty_value`.
37
+
38
+ ## The bar loop
39
+
40
+ `strategy.eachBar((c) => ...)` is the strategy's `eachBar` (same `c`, returns a Series) and runs once per script.
41
+ On each bar:
42
+
43
+ 1. the engine fills the orders placed before (market orders at the bar's open; stop / limit orders when the
44
+ bar reaches their price);
45
+ 2. the callback runs at the bar's close: read the state, place, change or cancel orders;
46
+ 3. with `process_orders_on_close: true`, new market orders fill at that close; else at the next bar's open.
47
+
48
+ Order calls and per-bar `strategy.*` reads throw outside `strategy.eachBar()`. Compute every Series before it.
49
+
50
+ ## Orders
51
+
52
+ | Call | Use |
53
+ |---|---|
54
+ | `strategy.entry(id, strategy.long / strategy.short, { qty, limit, stop, oca_name, oca_type, comment, alert_message })` | open a position; an entry in the other direction reverses the position; market order without `limit` / `stop` |
55
+ | `strategy.order(id, direction, { qty, limit, stop, ... })` | a raw order: adds to or reduces the position, no reversal logic |
56
+ | `strategy.exit(id, { from_entry, qty, qty_percent, stop, limit, loss, profit, trail_price, trail_points, trail_offset, comment, ... })` | stop loss / take profit / trailing stop for an entry; `stop` / `limit` / `trail_price` are prices, `loss` / `profit` / `trail_points` / `trail_offset` are in ticks |
57
+ | `strategy.close(id, { qty, qty_percent, comment, immediately })` | close the trades of entry `id` at market |
58
+ | `strategy.close_all({ comment, immediately })` | close the whole position at market |
59
+ | `strategy.cancel(id)`, `strategy.cancel_all()` | cancel pending orders |
60
+
61
+ - A call with the id of a pending order changes that order (calling `strategy.exit` on every bar with new prices
62
+ moves the stop).
63
+ - `NaN` in an option means "not given" (it is removed), as `na` in Pine.
64
+ - `strategy.exit` placed while the entry is still pending waits for it (`from_entry`).
65
+
66
+ ```js
67
+ strategy("Trailing exit", { overlay: true, initial_capital: 10000, default_qty_type: strategy.fixed, default_qty_value: 10 });
68
+ const fast = ta.ema(close, 12);
69
+ const slow = ta.ema(close, 26);
70
+ const atr = ta.atr(14);
71
+ const goLong = ta.crossover(fast, slow);
72
+ const goFlat = ta.crossunder(fast, slow);
73
+ strategy.eachBar((c) => {
74
+ if (c.get(goLong) === 1) strategy.entry("Long", strategy.long, { comment: "EMA cross" });
75
+ if (c.get(goFlat) === 1) strategy.close("Long", { comment: "Cross down" });
76
+ if (strategy.position_size > 0) {
77
+ const trade = strategy.opentrade(0); // Pine strategy.opentrades.*(0)
78
+ const stop = trade.entry_price - 2 * c.get(atr);
79
+ strategy.exit("Stop", { from_entry: "Long", stop, comment: "ATR stop" });
80
+ }
81
+ });
82
+ plot(fast, "Fast");
83
+ plot(slow, "Slow");
84
+ ```
85
+
86
+ ## Reading the results
87
+
88
+ Inside `strategy.eachBar()`, from the engine, for the current bar:
89
+
90
+ | Variable | Meaning |
91
+ |---|---|
92
+ | `strategy.position_size` | signed quantity: > 0 long, < 0 short, 0 flat |
93
+ | `strategy.position_avg_price`, `strategy.position_entry_name` | average entry price (NaN when flat), entry id |
94
+ | `strategy.equity`, `strategy.initial_capital` | initial capital + net profit + open profit |
95
+ | `strategy.netprofit`, `strategy.openprofit`, `strategy.grossprofit`, `strategy.grossloss` (and `_percent`) | profits |
96
+ | `strategy.opentrades`, `strategy.closedtrades`, `strategy.wintrades`, `strategy.losstrades`, `strategy.eventrades` | trade counts |
97
+ | `strategy.max_drawdown`, `strategy.max_runup`, `strategy.avg_trade`... | statistics |
98
+ | `strategy.opentrade(i)`, `strategy.closedtrade(i)` | trade `i` (0 = oldest): `{ entry_id, entry_price, entry_bar_index, entry_time, size, profit, commission, exit_price, exit_bar_index, ... }`, undefined when there is none |
99
+ | `strategy.default_entry_qty(price)` | the quantity a default-size entry would get |
100
+
101
+ Pine's `strategy.closedtrades.profit(i)` is `strategy.closedtrade(i)?.profit`. `strategy.initial_capital` and
102
+ `strategy.account_currency` can be read anywhere.
103
+
104
+ ```js
105
+ strategy("Equity curve", { overlay: false, initial_capital: 5000, default_qty_type: strategy.cash, default_qty_value: 1000 });
106
+ strategy.risk.allow_entry_in(strategy.direction.long); // risk rules: top level, after strategy()
107
+ const rsi = ta.rsi(close, 14);
108
+ const equity = strategy.eachBar((c) => {
109
+ if (c.get(rsi) < 30) strategy.entry("Long", strategy.long);
110
+ if (c.get(rsi) > 70) strategy.close_all({ comment: "RSI > 70" });
111
+ const last = strategy.closedtrade(strategy.closedtrades - 1);
112
+ if (last && last.exit_bar_index === c.i && last.profit < 0) alert("Losing trade closed");
113
+ return strategy.equity; // the loop's Series: equity per bar
114
+ });
115
+ plot(equity, "Equity", { color: color.teal });
116
+ hline(5000, "Initial capital", { color: color.gray });
117
+ ```
118
+
119
+ Risk rules: `strategy.risk.allow_entry_in(dir)`, `max_drawdown(value, type)`, `max_intraday_loss(value, type)`,
120
+ `max_cons_loss_days(count)`, `max_intraday_filled_orders(count)`, `max_position_size(contracts)` (type:
121
+ `"percent_of_equity"` or `"cash"`). An engine that does not support a rule or a variable throws
122
+ "... is not provided by the strategy engine".
123
+
124
+ ## The host side
125
+
126
+ A host passes an engine factory to `executeScript`:
127
+
128
+ ```ts
129
+ import { executeScript, type StrategyEngine } from 'oakscriptjs/script';
130
+
131
+ const run = executeScript(body, bars, inputs, chart, {
132
+ strategyEngine: ({ properties, bars, chart }) => new MyEngine(properties, bars), // implements StrategyEngine
133
+ });
134
+ // run.strategyConfig: the resolved properties
135
+ ```
136
+
137
+ `StrategyEngine` methods: `processBar(i)`, `processClose(i)`, `entry`, `order`, `exit`, `close`, `close_all`,
138
+ `cancel`, `cancel_all`, `get(variable)`, `openTrade(i)`, `closedTrade(i)`, and optionally `risk(rule)`,
139
+ `defaultEntryQty(price)`. The option objects keep the Pine names. Without an engine, `strategy()` declares the
140
+ strategy and `strategy.eachBar()` throws "no strategy engine".
@@ -0,0 +1,93 @@
1
+ # Other timeframes and symbols
2
+
3
+ ## request.security
4
+
5
+ `request.security(symbol, timeframe, expression, gaps?, lookahead?, ignore_invalid_symbol?)` runs `expression` on
6
+ the bars of `symbol` on `timeframe` and returns its values aligned on the chart bars (a Series).
7
+
8
+ - `symbol`: `""` or `syminfo.tickerid` for the chart symbol, a ticker id (`"NASDAQ:AAPL"`, from
9
+ `input.symbol`) for another one, `ticker.heikinashi(syminfo.tickerid)` for Heikin Ashi bars.
10
+ - `timeframe`: `"1"`, `"5"`, `"60"`, `"240"`, `"D"`, `"W"`, `"M"`, `"2D"`...; `""` is the chart timeframe.
11
+ - `expression`: a **function** that computes the series. Everything it needs on that timeframe is computed
12
+ inside it: `() => ta.ema(close, 20)`. A Series computed outside runs on the chart bars, and the call throws
13
+ "the expression returned a series computed on the chart bars".
14
+ - Return an array for several values (a Pine tuple): `const [h, l] = request.security("", "D", () => [high, low]);`
15
+ - Inputs and plain numbers from outside can be used inside (`() => ta.ema(close, length)`).
16
+
17
+ ```js
18
+ indicator("Daily levels", { overlay: true });
19
+ const length = input.int(20, "Daily EMA length", { minval: 1 });
20
+ const dailyEma = request.security("", "D", () => ta.ema(close, length));
21
+ const [prevHigh, prevLow] = request.security("", "D", () => [high.offset(1), low.offset(1)]);
22
+ plot(dailyEma, "Daily EMA", { color: color.orange, style: plot.style_stepline });
23
+ plot(prevHigh, "Previous day high", { color: color.green, style: plot.style_linebr });
24
+ plot(prevLow, "Previous day low", { color: color.red, style: plot.style_linebr });
25
+ ```
26
+
27
+ ### Lookahead and gaps
28
+
29
+ - `barmerge.lookahead_off` (default): a higher-timeframe value appears on the chart bar that completes its
30
+ period; before that, the bars show the previous period's value. This is the setting without future leak.
31
+ - `barmerge.lookahead_on`: the value of the period appears from its first chart bar. With a series that is not
32
+ offset (`close`, `ta.ema(close, 20)`) this reads the future on history bars: use it only with
33
+ `high.offset(1)`-style expressions (the previous period) or for display.
34
+ - `barmerge.gaps_on`: the value only on the bar where it appears, NaN elsewhere (use `plot.style_linebr`).
35
+
36
+ ```js
37
+ indicator("Weekly open", { overlay: true });
38
+ const weekOpen = request.security("", "W", () => open, barmerge.gaps_off, barmerge.lookahead_on);
39
+ plot(weekOpen, "Week open", { color: color.purple, style: plot.style_stepline });
40
+ ```
41
+
42
+ ### Other symbols
43
+
44
+ ```js
45
+ indicator("Relative strength", { overlay: false });
46
+ const other = input.symbol("NASDAQ:AAPL", "Compare with");
47
+ const otherClose = request.security(other, "", () => close);
48
+ const ratio = close.div(otherClose);
49
+ plot(ratio, "Ratio", { color: color.blue });
50
+ plot(ta.sma(ratio, 50), "Ratio SMA", { color: color.orange });
51
+ ```
52
+
53
+ The library fetches nothing: the host gives the bars of the other symbol. An app loads them when the script asks
54
+ (the first run reports what it needs, the app loads it and runs again); in your own `executeScript` call, pass
55
+ `bars(symbol, timeframe)` and `symbolInfo(symbol)` in the chart context and catch `MissingSymbolDataError`
56
+ (`error.requests` lists the `{ symbol, timeframe }` to load). A symbol with no bars stops the script with
57
+ "Invalid symbol: X" (or gives NaN with `ignore_invalid_symbol = true`).
58
+
59
+ The values are merged by time: with lookahead off a chart bar shows the last bar of the other symbol that closed
60
+ at or before its own close.
61
+
62
+ ### Lower timeframes
63
+
64
+ `request.security` on a timeframe below the chart's reads the lower-timeframe bars (given by the host as for
65
+ another symbol) and shows, per chart bar, the value on the last lower bar inside it.
66
+ `request.security_lower_tf(symbol, timeframe, expression)` returns, for each chart bar, an **array of numbers**:
67
+ the values on all the lower bars inside that bar (a plain `number[][]`, not a Series). Read it in a bar loop:
68
+
69
+ ```js
70
+ indicator("Intrabar up volume", { overlay: false });
71
+ const vols = request.security_lower_tf("", "15", () => volume.mul(close.gt(open).iff(1, 0)));
72
+ const upVolume = eachBar((c) => vols[c.i].reduce((sum, v) => sum + v, 0));
73
+ plot(upVolume, "Up volume", { style: plot.style_columns, color: color.teal });
74
+ ```
75
+
76
+ ## Timeframe values
77
+
78
+ - `timeframe.period` (`"60"`, `"1D"`...), `timeframe.multiplier`, `timeframe.isintraday`, `timeframe.isdaily`,
79
+ `timeframe.isweekly`, `timeframe.ismonthly`, `timeframe.isdwm`, `timeframe.isminutes`, `timeframe.isseconds`.
80
+ - `timeframe.change("D")`: 1 on the first chart bar of each new period.
81
+ - `timeframe.in_seconds(tf?)`, `timeframe.from_seconds(s)`.
82
+ - `input.timeframe("D", "Timeframe")` lets the user choose; `""` is the chart timeframe.
83
+ - `time("D")`, `time("240", "0930-1600")`: open time of the period of each bar, NaN outside the session.
84
+
85
+ A timeframe below the chart's with `request.security_lower_tf` and a higher one with `request.security` are the
86
+ normal cases; `request.security_lower_tf` on a higher timeframe throws.
87
+
88
+ ## What the host must give
89
+
90
+ Session-aware values (`request.security` on another timeframe, `time()`, `timeframe.change`, `session.*`,
91
+ `ta.vwap` on an intraday chart) need the chart timeframe, the exchange time zone and the session. An app gives
92
+ them; a direct `executeScript` call passes `{ timeframe, timezone, session }` in the chart context
93
+ (`session: "24x7"` for a symbol that always trades, `"0930-1600"` for regular stock hours).
package/src/index.ts CHANGED
@@ -224,7 +224,7 @@ export type { LightweightSeriesDefinitions } from './runtime/adapters/Lightweigh
224
224
  export { SimpleInputAdapter } from './runtime/adapters/SimpleInputAdapter.js';
225
225
 
226
226
  // Version
227
- export const VERSION = '0.12.3';
227
+ export const VERSION = '0.13.2';
228
228
 
229
229
  /**
230
230
  * Library information
@@ -338,6 +338,9 @@ export interface ScriptOptions {
338
338
 
339
339
  let scriptOptions: ScriptOptions = {};
340
340
 
341
+ /** The `varip` values of a replayed bar in a script without `varip`. */
342
+ const NO_VARIP: unknown[] = [];
343
+
341
344
  /** Intrabar run of the current run (see {@link executeIntrabar}); null in a main run. */
342
345
  interface IntrabarRun {
343
346
  /** The bar calculated inside the bar: the last bar of the run. */
@@ -474,7 +477,7 @@ export function executeScript(
474
477
  barColors: c.barColors.length ? c.barColors : undefined,
475
478
  plotCandles: Object.keys(c.plotCandles).length ? c.plotCandles : undefined,
476
479
  plotBars: Object.keys(c.plotBars).length ? c.plotBars : undefined,
477
- ...drawingOutputs(ctxBars.bars, chartCtx.timeUnit),
480
+ ...(intrabarRun ? {} : drawingOutputs(ctxBars.bars, chartCtx.timeUnit)),
478
481
  alerts: c.alerts.length ? c.alerts : undefined,
479
482
  alertCalls: c.alertCalls.length ? c.alertCalls : undefined,
480
483
  },
@@ -943,6 +946,7 @@ export const DEFAULT_OUTPUT_TITLES = {
943
946
 
944
947
  /** PineScript `plot()` — declares the plot and supplies its data in one call. */
945
948
  function plotImpl(series: Series, title: string = DEFAULT_OUTPUT_TITLES.plot, options: ScriptPlotOptions = {}): PlotHandle {
949
+ if (intrabarRun) return { id: '', kind: 'plot' }; // dropped: an intrabar run's outputs aren't read
946
950
  const c = collector();
947
951
  const id = `plot${c.plotConfig.length}`;
948
952
  const perBar = Array.isArray(options.color) ? options.color : undefined;
@@ -1077,6 +1081,7 @@ export function fill(
1077
1081
  ...rest: unknown[]
1078
1082
  ): void;
1079
1083
  export function fill(a: PlotHandle, b: PlotHandle, ...args: unknown[]): void {
1084
+ if (intrabarRun) return; // dropped: an intrabar run's outputs aren't read
1080
1085
  const c = collector();
1081
1086
  const third = args[0];
1082
1087
  let perBar: string[] | undefined;
@@ -1219,6 +1224,7 @@ function emitMarkers(
1219
1224
  title: string | undefined,
1220
1225
  options: PlotShapeOptions & PlotCharOptions
1221
1226
  ): void {
1227
+ if (intrabarRun) return; // dropped: an intrabar run's outputs aren't read
1222
1228
  const c = collector();
1223
1229
  const id = `${kind}${c.shapeConfig.filter((s) => s.kind === kind).length}`;
1224
1230
  title ??= DEFAULT_OUTPUT_TITLES[kind];
@@ -1302,6 +1308,7 @@ export interface PlotArrowOptions {
1302
1308
  * `arrowConfig`; the renderer scales the height with the absolute value between minheight and maxheight.
1303
1309
  */
1304
1310
  export function plotarrow(series: Series, title?: string, options: PlotArrowOptions = {}): void {
1311
+ if (intrabarRun) return; // dropped: an intrabar run's outputs aren't read
1305
1312
  const c = collector();
1306
1313
  const id = `arrow${c.arrowConfig.length}`;
1307
1314
  title ??= DEFAULT_OUTPUT_TITLES.arrow;
@@ -1345,6 +1352,7 @@ function emitBarColors(
1345
1352
  colors: string | Array<string | undefined>,
1346
1353
  options: { offset?: number; title?: string; force_overlay?: boolean; editable?: boolean }
1347
1354
  ): void {
1355
+ if (intrabarRun) return; // dropped: an intrabar run's outputs aren't read
1348
1356
  const c = collector();
1349
1357
  const id = `${kind}${c.barColorConfig.filter((x) => x.kind === kind).length}`;
1350
1358
  c.barColorConfig.push({
@@ -1478,6 +1486,7 @@ export function plotcandle(
1478
1486
  title?: string,
1479
1487
  options: PlotCandleOptions = {}
1480
1488
  ): void {
1489
+ if (intrabarRun) return; // dropped: an intrabar run's outputs aren't read
1481
1490
  const c = collector();
1482
1491
  const color = orDefault(options.color, DEFAULT_CANDLE_COLORS.color);
1483
1492
  const wickcolor = orDefault(options.wickcolor, DEFAULT_CANDLE_COLORS.wickColor);
@@ -1523,6 +1532,7 @@ export function plotbar(
1523
1532
  title?: string,
1524
1533
  options: PlotBarOptions = {}
1525
1534
  ): void {
1535
+ if (intrabarRun) return; // dropped: an intrabar run's outputs aren't read
1526
1536
  const c = collector();
1527
1537
  const barColor = orDefault(options.color, DEFAULT_CANDLE_COLORS.color);
1528
1538
  const id = `plotbar${c.candleConfig.filter((x) => x.kind === 'bar').length}`;
@@ -1800,11 +1810,18 @@ function strategyEachBar(fn: (c: BarContext) => number | boolean | void): Series
1800
1810
  // Intrabar run: the bars before replay the main run's answers, the bar itself calls the engine (whose
1801
1811
  // orders the host is filling: no processBar / processClose).
1802
1812
  const varipState = varipRun;
1813
+ const replayLog: { answers: readonly unknown[]; next: number } = { answers: [], next: 0 };
1814
+ const replayed = replayEngine(replayLog);
1803
1815
  return eachBar((c) => {
1804
1816
  const replay = c.i < intrabar.bar;
1805
- run.engine = replay ? replayEngine(intrabar.recorder.answers[c.i] ?? []) : intrabar.engine;
1817
+ if (replay) {
1818
+ replayLog.answers = intrabar.recorder.answers[c.i] ?? [];
1819
+ replayLog.next = 0;
1820
+ }
1821
+ run.engine = replay ? replayed : intrabar.engine;
1806
1822
  varipState.mode = replay ? 'replay' : 'live';
1807
- varipState.scratch = replay ? [...(intrabar.recorder.varipStarts[c.i] ?? intrabar.recorder.varip)] : null;
1823
+ const starts = replay ? intrabar.recorder.varipStarts[c.i] ?? intrabar.recorder.varip : null;
1824
+ varipState.scratch = starts ? (starts.length ? [...starts] : NO_VARIP) : null;
1808
1825
  run.inLoop = true;
1809
1826
  try {
1810
1827
  return fn(c);
@@ -1850,9 +1867,8 @@ function recordingEngine(engine: StrategyEngine, log: unknown[]): StrategyEngine
1850
1867
  }
1851
1868
 
1852
1869
  /** An engine that gives back recorded answers in order and drops the order calls (a replayed bar). */
1853
- function replayEngine(log: readonly unknown[]): StrategyEngine {
1854
- let k = 0;
1855
- const next = <T>(): T => log[k++] as T;
1870
+ function replayEngine(log: { answers: readonly unknown[]; next: number }): StrategyEngine {
1871
+ const next = <T>(): T => log.answers[log.next++] as T;
1856
1872
  const none = (): void => {};
1857
1873
  return {
1858
1874
  processBar: none,
@@ -2477,6 +2493,27 @@ function barFlag(test: (i: number, n: number) => boolean): Series {
2477
2493
  return Series.fromArray(ctxBars, Array.from({ length: n }, (_, i) => (test(i, n) ? 1 : 0)));
2478
2494
  }
2479
2495
 
2496
+ /**
2497
+ * An intrabar run reads the flag on the same bars as the run before, with at most one bar more, and the flags only
2498
+ * differ on their last bars: the values are kept between runs (the series only reads them).
2499
+ */
2500
+ const FLAG_TAIL = 3;
2501
+ const flagValues = new Map<string, number[]>();
2502
+ function namedFlag(name: string, test: (i: number, n: number) => boolean): Series {
2503
+ if (!intrabarRun) return barFlag(test);
2504
+ const n = ctxBars.bars.length;
2505
+ let values = flagValues.get(name);
2506
+ if (!values || values.length > n) {
2507
+ values = Array.from({ length: n }, (_, i) => (test(i, n) ? 1 : 0));
2508
+ flagValues.set(name, values);
2509
+ } else {
2510
+ const from = Math.max(0, Math.min(values.length, n) - FLAG_TAIL);
2511
+ values.length = n;
2512
+ for (let i = from; i < n; i++) values[i] = test(i, n) ? 1 : 0;
2513
+ }
2514
+ return Series.fromArray(ctxBars, values);
2515
+ }
2516
+
2480
2517
  /** Number of bars of the chart: an intrabar run ends before the chart's last bar. */
2481
2518
  const chartBarCount = (n: number): number => (intrabarRun ? intrabarRun.lastBarIndex + 1 : n);
2482
2519
 
@@ -2490,14 +2527,14 @@ const lastRealtime = (i: number, n: number): boolean => i === n - 1 && chartCtx.
2490
2527
  * `islastconfirmedhistory` is the last bar when it is confirmed and not realtime, else the bar before it.
2491
2528
  */
2492
2529
  export const barstate = {
2493
- get isfirst(): Series { return barFlag((i) => i === 0); },
2494
- get islast(): Series { return barFlag((i, n) => i === chartBarCount(n) - 1); },
2495
- get isconfirmed(): Series { return barFlag((i, n) => i < n - 1 || chartCtx.lastBarConfirmed === true); },
2496
- get isrealtime(): Series { return barFlag(lastRealtime); },
2497
- get ishistory(): Series { return barFlag((i, n) => !lastRealtime(i, n)); },
2498
- get isnew(): Series { return barFlag((i, n) => !lastRealtime(i, n) || chartCtx.lastBarNew !== false); },
2530
+ get isfirst(): Series { return namedFlag('isfirst', (i) => i === 0); },
2531
+ get islast(): Series { return namedFlag('islast', (i, n) => i === chartBarCount(n) - 1); },
2532
+ get isconfirmed(): Series { return namedFlag('isconfirmed', (i, n) => i < n - 1 || chartCtx.lastBarConfirmed === true); },
2533
+ get isrealtime(): Series { return namedFlag('isrealtime', lastRealtime); },
2534
+ get ishistory(): Series { return namedFlag('ishistory', (i, n) => !lastRealtime(i, n)); },
2535
+ get isnew(): Series { return namedFlag('isnew', (i, n) => !lastRealtime(i, n) || chartCtx.lastBarNew !== false); },
2499
2536
  get islastconfirmedhistory(): Series {
2500
- return barFlag((i, n) => {
2537
+ return namedFlag('islastconfirmedhistory', (i, n) => {
2501
2538
  const lastConfirmed = chartCtx.lastBarConfirmed === true && chartCtx.realtime !== true;
2502
2539
  const count = chartBarCount(n);
2503
2540
  return i === (lastConfirmed ? count - 1 : count - 2);