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.
Files changed (236) hide show
  1. package/dist/cjs/array/index.d.ts +8 -2
  2. package/dist/cjs/array/index.d.ts.map +1 -1
  3. package/dist/cjs/array/index.js +2 -1
  4. package/dist/cjs/array/index.js.map +2 -2
  5. package/dist/cjs/box/index.js +6 -6
  6. package/dist/cjs/box/index.js.map +2 -2
  7. package/dist/cjs/callsite/index.js +1 -1
  8. package/dist/cjs/callsite/index.js.map +2 -2
  9. package/dist/cjs/drawing/registry.d.ts +15 -6
  10. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  11. package/dist/cjs/drawing/registry.js +16 -2
  12. package/dist/cjs/drawing/registry.js.map +2 -2
  13. package/dist/cjs/index.d.ts +3 -2
  14. package/dist/cjs/index.d.ts.map +1 -1
  15. package/dist/cjs/index.js +6 -1
  16. package/dist/cjs/index.js.map +2 -2
  17. package/dist/cjs/input.d.ts +9 -1
  18. package/dist/cjs/input.d.ts.map +1 -1
  19. package/dist/cjs/input.js +16 -0
  20. package/dist/cjs/input.js.map +2 -2
  21. package/dist/cjs/label/index.js +3 -3
  22. package/dist/cjs/label/index.js.map +2 -2
  23. package/dist/cjs/line/index.d.ts +10 -27
  24. package/dist/cjs/line/index.d.ts.map +1 -1
  25. package/dist/cjs/line/index.js +10 -27
  26. package/dist/cjs/line/index.js.map +2 -2
  27. package/dist/cjs/linefill/index.d.ts +3 -0
  28. package/dist/cjs/linefill/index.d.ts.map +1 -1
  29. package/dist/cjs/linefill/index.js +15 -1
  30. package/dist/cjs/linefill/index.js.map +3 -3
  31. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
  32. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
  33. package/dist/cjs/runtime/index.d.ts +1 -1
  34. package/dist/cjs/runtime/index.d.ts.map +1 -1
  35. package/dist/cjs/runtime/index.js +1 -0
  36. package/dist/cjs/runtime/index.js.map +2 -2
  37. package/dist/cjs/runtime/inputs.d.ts +9 -0
  38. package/dist/cjs/runtime/inputs.d.ts.map +1 -1
  39. package/dist/cjs/runtime/inputs.js +8 -0
  40. package/dist/cjs/runtime/inputs.js.map +2 -2
  41. package/dist/cjs/runtime/types.d.ts +1 -1
  42. package/dist/cjs/runtime/types.d.ts.map +1 -1
  43. package/dist/cjs/runtime/types.js.map +1 -1
  44. package/dist/cjs/script/index.d.ts +65 -12
  45. package/dist/cjs/script/index.d.ts.map +1 -1
  46. package/dist/cjs/script/index.js +168 -19
  47. package/dist/cjs/script/index.js.map +3 -3
  48. package/dist/cjs/security/other.d.ts +92 -0
  49. package/dist/cjs/security/other.d.ts.map +1 -0
  50. package/dist/cjs/security/other.js +164 -0
  51. package/dist/cjs/security/other.js.map +7 -0
  52. package/dist/cjs/session/bars.d.ts +10 -1
  53. package/dist/cjs/session/bars.d.ts.map +1 -1
  54. package/dist/cjs/session/bars.js +16 -2
  55. package/dist/cjs/session/bars.js.map +2 -2
  56. package/dist/cjs/session/calendar.d.ts +4 -1
  57. package/dist/cjs/session/calendar.d.ts.map +1 -1
  58. package/dist/cjs/session/calendar.js +11 -7
  59. package/dist/cjs/session/calendar.js.map +2 -2
  60. package/dist/cjs/session/context.d.ts +2 -1
  61. package/dist/cjs/session/context.d.ts.map +1 -1
  62. package/dist/cjs/session/context.js +2 -1
  63. package/dist/cjs/session/context.js.map +2 -2
  64. package/dist/cjs/str/floatingdecimal.d.ts +31 -0
  65. package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
  66. package/dist/cjs/str/floatingdecimal.js +298 -0
  67. package/dist/cjs/str/floatingdecimal.js.map +7 -0
  68. package/dist/cjs/str/index.d.ts +4 -3
  69. package/dist/cjs/str/index.d.ts.map +1 -1
  70. package/dist/cjs/str/index.js +1 -1
  71. package/dist/cjs/str/index.js.map +2 -2
  72. package/dist/cjs/str/numberformat.d.ts +6 -4
  73. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  74. package/dist/cjs/str/numberformat.js +3 -2
  75. package/dist/cjs/str/numberformat.js.map +2 -2
  76. package/dist/cjs/ta/index.d.ts +34 -17
  77. package/dist/cjs/ta/index.d.ts.map +1 -1
  78. package/dist/cjs/ta/index.js +132 -116
  79. package/dist/cjs/ta/index.js.map +2 -2
  80. package/dist/cjs/ta/limits.d.ts +11 -0
  81. package/dist/cjs/ta/limits.d.ts.map +1 -1
  82. package/dist/cjs/ta/limits.js +15 -1
  83. package/dist/cjs/ta/limits.js.map +2 -2
  84. package/dist/cjs/ta/running-sum.js +1 -1
  85. package/dist/cjs/ta/running-sum.js.map +2 -2
  86. package/dist/cjs/ta-series.d.ts +1 -1
  87. package/dist/cjs/ta-series.d.ts.map +1 -1
  88. package/dist/cjs/ta-series.js +1 -1
  89. package/dist/cjs/ta-series.js.map +2 -2
  90. package/dist/cjs/table/index.d.ts +3 -1
  91. package/dist/cjs/table/index.d.ts.map +1 -1
  92. package/dist/cjs/table/index.js +1 -0
  93. package/dist/cjs/table/index.js.map +3 -3
  94. package/dist/cjs/time/session.d.ts +9 -0
  95. package/dist/cjs/time/session.d.ts.map +1 -1
  96. package/dist/cjs/time/session.js +42 -14
  97. package/dist/cjs/time/session.js.map +3 -3
  98. package/dist/cjs/time/timezone.d.ts +6 -1
  99. package/dist/cjs/time/timezone.d.ts.map +1 -1
  100. package/dist/cjs/time/timezone.js +2 -2
  101. package/dist/cjs/time/timezone.js.map +2 -2
  102. package/dist/cjs/types/metadata.d.ts +1 -1
  103. package/dist/cjs/types/metadata.d.ts.map +1 -1
  104. package/dist/cjs/types/metadata.js.map +1 -1
  105. package/dist/esm/array/index.d.ts +8 -2
  106. package/dist/esm/array/index.d.ts.map +1 -1
  107. package/dist/esm/array/index.js +12 -3
  108. package/dist/esm/array/index.js.map +1 -1
  109. package/dist/esm/box/index.js +6 -6
  110. package/dist/esm/box/index.js.map +1 -1
  111. package/dist/esm/callsite/index.js +1 -1
  112. package/dist/esm/callsite/index.js.map +1 -1
  113. package/dist/esm/drawing/registry.d.ts +15 -6
  114. package/dist/esm/drawing/registry.d.ts.map +1 -1
  115. package/dist/esm/drawing/registry.js +30 -7
  116. package/dist/esm/drawing/registry.js.map +1 -1
  117. package/dist/esm/index.d.ts +3 -2
  118. package/dist/esm/index.d.ts.map +1 -1
  119. package/dist/esm/index.js +4 -2
  120. package/dist/esm/index.js.map +1 -1
  121. package/dist/esm/input.d.ts +9 -1
  122. package/dist/esm/input.d.ts.map +1 -1
  123. package/dist/esm/input.js +16 -0
  124. package/dist/esm/input.js.map +1 -1
  125. package/dist/esm/label/index.js +3 -3
  126. package/dist/esm/label/index.js.map +1 -1
  127. package/dist/esm/line/index.d.ts +10 -27
  128. package/dist/esm/line/index.d.ts.map +1 -1
  129. package/dist/esm/line/index.js +20 -64
  130. package/dist/esm/line/index.js.map +1 -1
  131. package/dist/esm/linefill/index.d.ts +3 -0
  132. package/dist/esm/linefill/index.d.ts.map +1 -1
  133. package/dist/esm/linefill/index.js +20 -1
  134. package/dist/esm/linefill/index.js.map +1 -1
  135. package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
  136. package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
  137. package/dist/esm/runtime/index.d.ts +1 -1
  138. package/dist/esm/runtime/index.d.ts.map +1 -1
  139. package/dist/esm/runtime/index.js +1 -1
  140. package/dist/esm/runtime/index.js.map +1 -1
  141. package/dist/esm/runtime/inputs.d.ts +9 -0
  142. package/dist/esm/runtime/inputs.d.ts.map +1 -1
  143. package/dist/esm/runtime/inputs.js +15 -0
  144. package/dist/esm/runtime/inputs.js.map +1 -1
  145. package/dist/esm/runtime/types.d.ts +1 -1
  146. package/dist/esm/runtime/types.d.ts.map +1 -1
  147. package/dist/esm/script/index.d.ts +65 -12
  148. package/dist/esm/script/index.d.ts.map +1 -1
  149. package/dist/esm/script/index.js +217 -29
  150. package/dist/esm/script/index.js.map +1 -1
  151. package/dist/esm/security/other.d.ts +92 -0
  152. package/dist/esm/security/other.d.ts.map +1 -0
  153. package/dist/esm/security/other.js +211 -0
  154. package/dist/esm/security/other.js.map +1 -0
  155. package/dist/esm/session/bars.d.ts +10 -1
  156. package/dist/esm/session/bars.d.ts.map +1 -1
  157. package/dist/esm/session/bars.js +26 -1
  158. package/dist/esm/session/bars.js.map +1 -1
  159. package/dist/esm/session/calendar.d.ts +4 -1
  160. package/dist/esm/session/calendar.d.ts.map +1 -1
  161. package/dist/esm/session/calendar.js +19 -7
  162. package/dist/esm/session/calendar.js.map +1 -1
  163. package/dist/esm/session/context.d.ts +2 -1
  164. package/dist/esm/session/context.d.ts.map +1 -1
  165. package/dist/esm/session/context.js +5 -3
  166. package/dist/esm/session/context.js.map +1 -1
  167. package/dist/esm/str/floatingdecimal.d.ts +31 -0
  168. package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
  169. package/dist/esm/str/floatingdecimal.js +272 -0
  170. package/dist/esm/str/floatingdecimal.js.map +1 -0
  171. package/dist/esm/str/index.d.ts +4 -3
  172. package/dist/esm/str/index.d.ts.map +1 -1
  173. package/dist/esm/str/index.js +6 -5
  174. package/dist/esm/str/index.js.map +1 -1
  175. package/dist/esm/str/numberformat.d.ts +6 -4
  176. package/dist/esm/str/numberformat.d.ts.map +1 -1
  177. package/dist/esm/str/numberformat.js +9 -5
  178. package/dist/esm/str/numberformat.js.map +1 -1
  179. package/dist/esm/ta/index.d.ts +34 -17
  180. package/dist/esm/ta/index.d.ts.map +1 -1
  181. package/dist/esm/ta/index.js +155 -185
  182. package/dist/esm/ta/index.js.map +1 -1
  183. package/dist/esm/ta/limits.d.ts +11 -0
  184. package/dist/esm/ta/limits.d.ts.map +1 -1
  185. package/dist/esm/ta/limits.js +27 -2
  186. package/dist/esm/ta/limits.js.map +1 -1
  187. package/dist/esm/ta/running-sum.js +1 -1
  188. package/dist/esm/ta/running-sum.js.map +1 -1
  189. package/dist/esm/ta-series.d.ts +1 -1
  190. package/dist/esm/ta-series.d.ts.map +1 -1
  191. package/dist/esm/ta-series.js +2 -1
  192. package/dist/esm/ta-series.js.map +1 -1
  193. package/dist/esm/table/index.d.ts +3 -1
  194. package/dist/esm/table/index.d.ts.map +1 -1
  195. package/dist/esm/table/index.js +6 -1
  196. package/dist/esm/table/index.js.map +1 -1
  197. package/dist/esm/time/session.d.ts +9 -0
  198. package/dist/esm/time/session.d.ts.map +1 -1
  199. package/dist/esm/time/session.js +53 -15
  200. package/dist/esm/time/session.js.map +1 -1
  201. package/dist/esm/time/timezone.d.ts +6 -1
  202. package/dist/esm/time/timezone.d.ts.map +1 -1
  203. package/dist/esm/time/timezone.js +8 -2
  204. package/dist/esm/time/timezone.js.map +1 -1
  205. package/dist/esm/types/metadata.d.ts +1 -1
  206. package/dist/esm/types/metadata.d.ts.map +1 -1
  207. package/package.json +1 -1
  208. package/src/array/index.ts +11 -3
  209. package/src/box/index.ts +6 -6
  210. package/src/callsite/index.ts +1 -1
  211. package/src/drawing/registry.ts +30 -7
  212. package/src/index.ts +11 -1
  213. package/src/input.ts +18 -1
  214. package/src/label/index.ts +3 -3
  215. package/src/line/index.ts +20 -68
  216. package/src/linefill/index.ts +20 -1
  217. package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
  218. package/src/runtime/index.ts +1 -0
  219. package/src/runtime/inputs.ts +16 -0
  220. package/src/runtime/types.ts +1 -1
  221. package/src/script/index.ts +292 -30
  222. package/src/security/other.ts +269 -0
  223. package/src/session/bars.ts +24 -1
  224. package/src/session/calendar.ts +23 -7
  225. package/src/session/context.ts +5 -3
  226. package/src/str/floatingdecimal.ts +268 -0
  227. package/src/str/index.ts +6 -5
  228. package/src/str/numberformat.ts +11 -6
  229. package/src/ta/index.ts +157 -183
  230. package/src/ta/limits.ts +28 -1
  231. package/src/ta/running-sum.ts +1 -1
  232. package/src/ta-series.ts +3 -2
  233. package/src/table/index.ts +4 -1
  234. package/src/time/session.ts +58 -16
  235. package/src/time/timezone.ts +9 -2
  236. package/src/types/metadata.ts +1 -1
@@ -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
- * day (`timeframe.change("1D")`), which needs the chart context (timeframe, time zone, session), and the
569
- * first bar: as in PineScript, the first loaded bars (a day in progress) have a value. */
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
- // PineScript default: timeframe.change("1D"); the sums also start on the first bar (a day in progress)
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` and
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 { bars, test, inside } = calendarFor(session, timezone);
1870
- const timeframeString = tf || chartTimeframe().period;
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 { bars, test, inside } = calendarFor(session, timezone);
1877
- const timeframeString = tf || chartTimeframe().period;
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` for the chart symbol (`syminfo.tickerid`, `""`, `ticker.heikinashi(...)`,
2002
- * `ticker.standard(...)`) and a timeframe equal to or higher than the chart's. The higher-timeframe bars are
2003
- * built by grouping the chart bars, and `expression` runs on them (its `ta.*` calls, tuples and `timeframe.*`
2004
- * see the higher timeframe). Other symbols need external data and throw; a lower timeframe throws.
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
- throw new Error(`request.security: timeframe "${tf}" is lower than the chart timeframe "${chart.period}".`);
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
- /** PineScript `request.*`: `security` for the chart symbol (issue #101). */
2078
- export const request = { security };
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