oakscriptjs 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) 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.d.ts.map +1 -1
  8. package/dist/cjs/callsite/index.js +6 -3
  9. package/dist/cjs/callsite/index.js.map +2 -2
  10. package/dist/cjs/drawing/registry.d.ts +15 -6
  11. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  12. package/dist/cjs/drawing/registry.js +16 -2
  13. package/dist/cjs/drawing/registry.js.map +2 -2
  14. package/dist/cjs/index.d.ts +4 -3
  15. package/dist/cjs/index.d.ts.map +1 -1
  16. package/dist/cjs/index.js +7 -1
  17. package/dist/cjs/index.js.map +2 -2
  18. package/dist/cjs/input.d.ts +9 -1
  19. package/dist/cjs/input.d.ts.map +1 -1
  20. package/dist/cjs/input.js +16 -0
  21. package/dist/cjs/input.js.map +2 -2
  22. package/dist/cjs/label/index.js +3 -3
  23. package/dist/cjs/label/index.js.map +2 -2
  24. package/dist/cjs/line/index.d.ts +10 -27
  25. package/dist/cjs/line/index.d.ts.map +1 -1
  26. package/dist/cjs/line/index.js +10 -27
  27. package/dist/cjs/line/index.js.map +2 -2
  28. package/dist/cjs/linefill/index.d.ts +3 -0
  29. package/dist/cjs/linefill/index.d.ts.map +1 -1
  30. package/dist/cjs/linefill/index.js +15 -1
  31. package/dist/cjs/linefill/index.js.map +3 -3
  32. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
  33. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
  34. package/dist/cjs/runtime/index.d.ts +1 -1
  35. package/dist/cjs/runtime/index.d.ts.map +1 -1
  36. package/dist/cjs/runtime/index.js +1 -0
  37. package/dist/cjs/runtime/index.js.map +2 -2
  38. package/dist/cjs/runtime/inputs.d.ts +9 -0
  39. package/dist/cjs/runtime/inputs.d.ts.map +1 -1
  40. package/dist/cjs/runtime/inputs.js +8 -0
  41. package/dist/cjs/runtime/inputs.js.map +2 -2
  42. package/dist/cjs/runtime/types.d.ts +1 -1
  43. package/dist/cjs/runtime/types.d.ts.map +1 -1
  44. package/dist/cjs/runtime/types.js.map +1 -1
  45. package/dist/cjs/script/index.d.ts +66 -13
  46. package/dist/cjs/script/index.d.ts.map +1 -1
  47. package/dist/cjs/script/index.js +139 -35
  48. package/dist/cjs/script/index.js.map +3 -3
  49. package/dist/cjs/security/other.d.ts +92 -0
  50. package/dist/cjs/security/other.d.ts.map +1 -0
  51. package/dist/cjs/security/other.js +164 -0
  52. package/dist/cjs/security/other.js.map +7 -0
  53. package/dist/cjs/session/bars.d.ts +10 -1
  54. package/dist/cjs/session/bars.d.ts.map +1 -1
  55. package/dist/cjs/session/bars.js +16 -2
  56. package/dist/cjs/session/bars.js.map +2 -2
  57. package/dist/cjs/session/calendar.d.ts +4 -1
  58. package/dist/cjs/session/calendar.d.ts.map +1 -1
  59. package/dist/cjs/session/calendar.js +12 -7
  60. package/dist/cjs/session/calendar.js.map +2 -2
  61. package/dist/cjs/session/context.d.ts +22 -5
  62. package/dist/cjs/session/context.d.ts.map +1 -1
  63. package/dist/cjs/session/context.js +67 -6
  64. package/dist/cjs/session/context.js.map +2 -2
  65. package/dist/cjs/str/floatingdecimal.d.ts +31 -0
  66. package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
  67. package/dist/cjs/str/floatingdecimal.js +298 -0
  68. package/dist/cjs/str/floatingdecimal.js.map +7 -0
  69. package/dist/cjs/str/index.d.ts +4 -3
  70. package/dist/cjs/str/index.d.ts.map +1 -1
  71. package/dist/cjs/str/index.js +1 -1
  72. package/dist/cjs/str/index.js.map +2 -2
  73. package/dist/cjs/str/numberformat.d.ts +6 -4
  74. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  75. package/dist/cjs/str/numberformat.js +3 -2
  76. package/dist/cjs/str/numberformat.js.map +2 -2
  77. package/dist/cjs/ta/index.d.ts +64 -45
  78. package/dist/cjs/ta/index.d.ts.map +1 -1
  79. package/dist/cjs/ta/index.js +140 -126
  80. package/dist/cjs/ta/index.js.map +3 -3
  81. package/dist/cjs/ta/limits.d.ts +11 -0
  82. package/dist/cjs/ta/limits.d.ts.map +1 -1
  83. package/dist/cjs/ta/limits.js +15 -1
  84. package/dist/cjs/ta/limits.js.map +2 -2
  85. package/dist/cjs/ta/running-sum.js +1 -1
  86. package/dist/cjs/ta/running-sum.js.map +2 -2
  87. package/dist/cjs/ta-series.d.ts +1 -1
  88. package/dist/cjs/ta-series.d.ts.map +1 -1
  89. package/dist/cjs/ta-series.js +1 -1
  90. package/dist/cjs/ta-series.js.map +2 -2
  91. package/dist/cjs/table/index.d.ts +10 -3
  92. package/dist/cjs/table/index.d.ts.map +1 -1
  93. package/dist/cjs/table/index.js +11 -1
  94. package/dist/cjs/table/index.js.map +3 -3
  95. package/dist/cjs/time/session.d.ts +9 -0
  96. package/dist/cjs/time/session.d.ts.map +1 -1
  97. package/dist/cjs/time/session.js +42 -14
  98. package/dist/cjs/time/session.js.map +3 -3
  99. package/dist/cjs/time/timezone.d.ts +6 -1
  100. package/dist/cjs/time/timezone.d.ts.map +1 -1
  101. package/dist/cjs/time/timezone.js +2 -2
  102. package/dist/cjs/time/timezone.js.map +2 -2
  103. package/dist/cjs/types/metadata.d.ts +1 -1
  104. package/dist/cjs/types/metadata.d.ts.map +1 -1
  105. package/dist/cjs/types/metadata.js.map +1 -1
  106. package/dist/esm/array/index.d.ts +8 -2
  107. package/dist/esm/array/index.d.ts.map +1 -1
  108. package/dist/esm/array/index.js +12 -3
  109. package/dist/esm/array/index.js.map +1 -1
  110. package/dist/esm/box/index.js +6 -6
  111. package/dist/esm/box/index.js.map +1 -1
  112. package/dist/esm/callsite/index.d.ts.map +1 -1
  113. package/dist/esm/callsite/index.js +6 -3
  114. package/dist/esm/callsite/index.js.map +1 -1
  115. package/dist/esm/drawing/registry.d.ts +15 -6
  116. package/dist/esm/drawing/registry.d.ts.map +1 -1
  117. package/dist/esm/drawing/registry.js +30 -7
  118. package/dist/esm/drawing/registry.js.map +1 -1
  119. package/dist/esm/index.d.ts +4 -3
  120. package/dist/esm/index.d.ts.map +1 -1
  121. package/dist/esm/index.js +8 -5
  122. package/dist/esm/index.js.map +1 -1
  123. package/dist/esm/input.d.ts +9 -1
  124. package/dist/esm/input.d.ts.map +1 -1
  125. package/dist/esm/input.js +16 -0
  126. package/dist/esm/input.js.map +1 -1
  127. package/dist/esm/label/index.js +3 -3
  128. package/dist/esm/label/index.js.map +1 -1
  129. package/dist/esm/line/index.d.ts +10 -27
  130. package/dist/esm/line/index.d.ts.map +1 -1
  131. package/dist/esm/line/index.js +20 -64
  132. package/dist/esm/line/index.js.map +1 -1
  133. package/dist/esm/linefill/index.d.ts +3 -0
  134. package/dist/esm/linefill/index.d.ts.map +1 -1
  135. package/dist/esm/linefill/index.js +20 -1
  136. package/dist/esm/linefill/index.js.map +1 -1
  137. package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
  138. package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
  139. package/dist/esm/runtime/index.d.ts +1 -1
  140. package/dist/esm/runtime/index.d.ts.map +1 -1
  141. package/dist/esm/runtime/index.js +1 -1
  142. package/dist/esm/runtime/index.js.map +1 -1
  143. package/dist/esm/runtime/inputs.d.ts +9 -0
  144. package/dist/esm/runtime/inputs.d.ts.map +1 -1
  145. package/dist/esm/runtime/inputs.js +15 -0
  146. package/dist/esm/runtime/inputs.js.map +1 -1
  147. package/dist/esm/runtime/types.d.ts +1 -1
  148. package/dist/esm/runtime/types.d.ts.map +1 -1
  149. package/dist/esm/script/index.d.ts +66 -13
  150. package/dist/esm/script/index.d.ts.map +1 -1
  151. package/dist/esm/script/index.js +187 -54
  152. package/dist/esm/script/index.js.map +1 -1
  153. package/dist/esm/security/other.d.ts +92 -0
  154. package/dist/esm/security/other.d.ts.map +1 -0
  155. package/dist/esm/security/other.js +211 -0
  156. package/dist/esm/security/other.js.map +1 -0
  157. package/dist/esm/session/bars.d.ts +10 -1
  158. package/dist/esm/session/bars.d.ts.map +1 -1
  159. package/dist/esm/session/bars.js +26 -1
  160. package/dist/esm/session/bars.js.map +1 -1
  161. package/dist/esm/session/calendar.d.ts +4 -1
  162. package/dist/esm/session/calendar.d.ts.map +1 -1
  163. package/dist/esm/session/calendar.js +23 -7
  164. package/dist/esm/session/calendar.js.map +1 -1
  165. package/dist/esm/session/context.d.ts +22 -5
  166. package/dist/esm/session/context.d.ts.map +1 -1
  167. package/dist/esm/session/context.js +86 -10
  168. package/dist/esm/session/context.js.map +1 -1
  169. package/dist/esm/str/floatingdecimal.d.ts +31 -0
  170. package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
  171. package/dist/esm/str/floatingdecimal.js +272 -0
  172. package/dist/esm/str/floatingdecimal.js.map +1 -0
  173. package/dist/esm/str/index.d.ts +4 -3
  174. package/dist/esm/str/index.d.ts.map +1 -1
  175. package/dist/esm/str/index.js +6 -5
  176. package/dist/esm/str/index.js.map +1 -1
  177. package/dist/esm/str/numberformat.d.ts +6 -4
  178. package/dist/esm/str/numberformat.d.ts.map +1 -1
  179. package/dist/esm/str/numberformat.js +9 -5
  180. package/dist/esm/str/numberformat.js.map +1 -1
  181. package/dist/esm/ta/index.d.ts +64 -45
  182. package/dist/esm/ta/index.d.ts.map +1 -1
  183. package/dist/esm/ta/index.js +198 -228
  184. package/dist/esm/ta/index.js.map +1 -1
  185. package/dist/esm/ta/limits.d.ts +11 -0
  186. package/dist/esm/ta/limits.d.ts.map +1 -1
  187. package/dist/esm/ta/limits.js +27 -2
  188. package/dist/esm/ta/limits.js.map +1 -1
  189. package/dist/esm/ta/running-sum.js +1 -1
  190. package/dist/esm/ta/running-sum.js.map +1 -1
  191. package/dist/esm/ta-series.d.ts +1 -1
  192. package/dist/esm/ta-series.d.ts.map +1 -1
  193. package/dist/esm/ta-series.js +2 -1
  194. package/dist/esm/ta-series.js.map +1 -1
  195. package/dist/esm/table/index.d.ts +10 -3
  196. package/dist/esm/table/index.d.ts.map +1 -1
  197. package/dist/esm/table/index.js +23 -3
  198. package/dist/esm/table/index.js.map +1 -1
  199. package/dist/esm/time/session.d.ts +9 -0
  200. package/dist/esm/time/session.d.ts.map +1 -1
  201. package/dist/esm/time/session.js +53 -15
  202. package/dist/esm/time/session.js.map +1 -1
  203. package/dist/esm/time/timezone.d.ts +6 -1
  204. package/dist/esm/time/timezone.d.ts.map +1 -1
  205. package/dist/esm/time/timezone.js +8 -2
  206. package/dist/esm/time/timezone.js.map +1 -1
  207. package/dist/esm/types/metadata.d.ts +1 -1
  208. package/dist/esm/types/metadata.d.ts.map +1 -1
  209. package/package.json +1 -1
  210. package/src/array/index.ts +11 -3
  211. package/src/box/index.ts +6 -6
  212. package/src/callsite/index.ts +6 -3
  213. package/src/drawing/registry.ts +30 -7
  214. package/src/index.ts +16 -3
  215. package/src/input.ts +18 -1
  216. package/src/label/index.ts +3 -3
  217. package/src/line/index.ts +20 -68
  218. package/src/linefill/index.ts +20 -1
  219. package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
  220. package/src/runtime/index.ts +1 -0
  221. package/src/runtime/inputs.ts +16 -0
  222. package/src/runtime/types.ts +1 -1
  223. package/src/script/index.ts +262 -58
  224. package/src/security/other.ts +269 -0
  225. package/src/session/bars.ts +24 -1
  226. package/src/session/calendar.ts +26 -7
  227. package/src/session/context.ts +104 -10
  228. package/src/str/floatingdecimal.ts +268 -0
  229. package/src/str/index.ts +6 -5
  230. package/src/str/numberformat.ts +11 -6
  231. package/src/ta/index.ts +200 -225
  232. package/src/ta/limits.ts +28 -1
  233. package/src/ta/running-sum.ts +1 -1
  234. package/src/ta-series.ts +3 -2
  235. package/src/table/index.ts +22 -3
  236. package/src/time/session.ts +58 -16
  237. package/src/time/timezone.ts +9 -2
  238. package/src/types/metadata.ts +1 -1
@@ -55,11 +55,12 @@ import * as strCore from '../str/index.js';
55
55
  import { formatMessage } from '../str/messageformat.js';
56
56
  import { tickScale } from '../str/numberformat.js';
57
57
  import { TradingCalendar } from '../session/calendar.js';
58
- import { SessionBars, sessionFlags } from '../session/bars.js';
58
+ import { SessionBars, sessionFlags, strayPeriods } from '../session/bars.js';
59
59
  import { afterCompletion, heikinAshi, mapToChart, periodsOf, resample } from '../security/resample.js';
60
+ import { securityLowerTf, securityOther } from '../security/other.js';
60
61
  import { position } from '../types/index.js';
61
62
  import { STRATEGY_CONSTANTS, STRATEGY_DEFAULTS, STRATEGY_NUMBER_VARIABLES, STRATEGY_TEXT_VARIABLES, } from '../strategy/index.js';
62
- import { parsePineSession } from '../time/session.js';
63
+ import { sessionPeriods } from '../session/context.js';
63
64
  /** Bars for the current run. Module-level and REUSED across runs so builtins
64
65
  * and every Series derived from them keep their version-keyed memoization. */
65
66
  const ctxBars = new BarData([]);
@@ -126,8 +127,11 @@ export function executeScript(body, barsInput, inputs = {}, chart = {}, options
126
127
  ctx = freshCollector(inputs);
127
128
  resetDrawings();
128
129
  mathCore.resetRandom();
130
+ missingSymbols = [];
129
131
  try {
130
132
  body();
133
+ if (missingSymbols.length)
134
+ throw new MissingSymbolDataError(missingSymbols);
131
135
  const c = ctx;
132
136
  return {
133
137
  metadata: {
@@ -254,6 +258,10 @@ export const input = {
254
258
  session(defval, title, opts = {}) {
255
259
  return registerInput({ type: 'session', defval, title, options: opts.options, ...common(opts) }, 'input_session');
256
260
  },
261
+ /** Ticker id ("NASDAQ:MSFT"), for `request.security` of another symbol (#180). */
262
+ symbol(defval, title, opts = {}) {
263
+ return registerInput({ type: 'symbol', defval, title, options: opts.options, ...common(opts) }, 'input_symbol');
264
+ },
257
265
  /** Date and time as a UNIX time in milliseconds, e.g. `input.time(timestamp('2024-01-01'), 'Start')`. */
258
266
  time(defval, title, opts = {}) {
259
267
  const v = registerInput({ type: 'time', defval, title, ...common(opts) }, 'input_time');
@@ -317,12 +325,12 @@ export const ta = {
317
325
  lowestbars: (sourceOrLength, length) => typeof sourceOrLength === 'number'
318
326
  ? taSeries.lowestbars(low, sourceOrLength)
319
327
  : taSeries.lowestbars(sourceOrLength, length),
320
- /** `ta.vwap(source, anchor?, stdev_mult?)` with the chart volume; the default anchor is a new trading
321
- * day (`timeframe.change("1D")`), which needs the chart context (timeframe, time zone, session), and the
322
- * first bar: as in PineScript, the first loaded bars (a day in progress) have a value. */
328
+ /** `ta.vwap(source, anchor?, stdev_mult?)` with the chart volume; the default anchor is a new trading day
329
+ * and the first bar: as in PineScript, the first loaded bars (a day in progress) have a value. On D / W / M
330
+ * charts every bar is a new day (no time zone or session needed); on intraday charts it needs the chart context
331
+ * (timeframe, time zone, session). */
323
332
  vwap: ((source, anchor, stdev_mult) => {
324
- // PineScript default: timeframe.change("1D"); the sums also start on the first bar (a day in progress)
325
- const a = anchor ?? changeOf('1D', true);
333
+ const a = anchor ?? vwapAnchor();
326
334
  return stdev_mult === undefined ? taSeries.vwap(source, volume, a) : taSeries.vwapBands(source, volume, a, stdev_mult);
327
335
  }),
328
336
  /** `ta.mfi(source, length)` with the chart volume (PineScript takes 2 arguments). */
@@ -818,7 +826,7 @@ export function eachBar(fn) {
818
826
  if (pendingError && pendingError.bar === idx)
819
827
  throw new Error(historyError(idx, pendingError.barsBack));
820
828
  currentBar = idx;
821
- setDrawingBar(idx, idx === b.length - 1);
829
+ setDrawingBar(idx, idx === b.length - 1, b.length - 1);
822
830
  const r = fn(ctx);
823
831
  out[idx] = typeof r === 'number' ? r : typeof r === 'boolean' ? (r ? 1 : 0) : NaN;
824
832
  // grow the history buffers from the largest reference of this bar
@@ -1018,6 +1026,21 @@ for (const name of [...STRATEGY_NUMBER_VARIABLES, ...STRATEGY_TEXT_VARIABLES]) {
1018
1026
  * ```
1019
1027
  */
1020
1028
  export const strategy = Object.defineProperties(Object.assign((title, options) => declareStrategy(title, options), strategyMembers), variableGetters);
1029
+ /**
1030
+ * Thrown by `executeScript` when `request.security` asked for bars of another symbol that `ChartContext.bars` did not
1031
+ * give (#179). `requests` lists them all, once each: load them, then run the script again.
1032
+ */
1033
+ export class MissingSymbolDataError extends Error {
1034
+ requests;
1035
+ constructor(requests) {
1036
+ super(`request.security needs the bars of ${requests.map((r) => `${r.symbol} (${r.timeframe})`).join(', ')}: ` +
1037
+ 'pass them with ChartContext.bars.');
1038
+ this.requests = requests;
1039
+ this.name = 'MissingSymbolDataError';
1040
+ }
1041
+ }
1042
+ /** Requests of other symbols without bars in the current run. */
1043
+ let missingSymbols = [];
1021
1044
  let chartCtx = {};
1022
1045
  function chartTimeframe() {
1023
1046
  collector();
@@ -1113,8 +1136,8 @@ function symbolScale() {
1113
1136
  }
1114
1137
  /**
1115
1138
  * PineScript `syminfo.timezone`, `syminfo.tickerid`, `syminfo.session` (session type), `syminfo.mintick`,
1116
- * `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue`, `syminfo.mincontract`, `syminfo.type` and
1117
- * `syminfo.currency`, from the chart context.
1139
+ * `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue`, `syminfo.mincontract`, `syminfo.type`,
1140
+ * `syminfo.currency`, `syminfo.basecurrency` and `syminfo.root`, from the chart context.
1118
1141
  */
1119
1142
  export const syminfo = {
1120
1143
  get mintick() {
@@ -1152,6 +1175,12 @@ export const syminfo = {
1152
1175
  get currency() {
1153
1176
  return chartText('currency', 'syminfo.currency');
1154
1177
  },
1178
+ get basecurrency() {
1179
+ return chartText('basecurrency', 'syminfo.basecurrency');
1180
+ },
1181
+ get root() {
1182
+ return chartText('root', 'syminfo.root');
1183
+ },
1155
1184
  };
1156
1185
  function calendar(fn) {
1157
1186
  return ((time, timezone) => {
@@ -1228,33 +1257,6 @@ function sessionBars() {
1228
1257
  }
1229
1258
  return sessionCache;
1230
1259
  }
1231
- /**
1232
- * Calendar for `time(tf, session, timezone)` arguments. Without session and time zone: the loaded
1233
- * session. As in PineScript, an explicit time zone reads the symbol session hours in that zone without
1234
- * corrections and holidays, and an explicit session follows PineScript's session string rules.
1235
- */
1236
- function calendarFor(session, timezone) {
1237
- const { bars } = sessionBars();
1238
- if (!session && !timezone)
1239
- return { bars, test: false };
1240
- const tz = timezone ?? exchangeTimezone();
1241
- // a session string that is empty or does not start with a digit is the symbol session: "regular" the regular
1242
- // hours, "extended" and any other name the loaded hours (PineScript)
1243
- if (session !== undefined && parsePineSession(session) === null) {
1244
- const name = session.trim();
1245
- const symbol = name === 'regular' && chartCtx.regularSession ? chartCtx.regularSession : chartCtx.session;
1246
- return { bars: new SessionBars(new TradingCalendar(tz, { session: toSpec(symbol).session }, 'symbol'), bars.chart.period), test: true };
1247
- }
1248
- const spec = session ? { session } : { session: toSpec(chartCtx.session).session };
1249
- return {
1250
- bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol'), bars.chart.period),
1251
- test: true,
1252
- // a PineScript session string: the rules of time.inSession (runs ending at 0000, days around a clock change)
1253
- inside: session
1254
- ? (time) => timeCore.inSession(time, session, tz, timeframeCore.in_seconds(bars.chart.period) * 1000)
1255
- : undefined,
1256
- };
1257
- }
1258
1260
  function perBar(fn) {
1259
1261
  const times = ctxBars.bars.map(barTime);
1260
1262
  return Series.fromArray(ctxBars, times.map((t, i) => fn(t, i, times)));
@@ -1265,17 +1267,20 @@ function callableSeries(series, fn) {
1265
1267
  Object.assign(fn, series);
1266
1268
  return fn;
1267
1269
  }
1270
+ /** `time(tf, session, timezone)` / `time_close(...)` periods of the run's chart (see session/context `sessionPeriods`). */
1271
+ function chartSessionPeriods(timeframeString, session, timezone) {
1272
+ const { bars } = sessionBars();
1273
+ return sessionPeriods(bars, { ...chartCtx, timezone: exchangeTimezone() }, timeframeString, session, timezone);
1274
+ }
1268
1275
  function timeFunction(tf, session, timezone) {
1269
1276
  collector();
1270
- const { bars, test, inside } = calendarFor(session, timezone);
1271
- const timeframeString = tf || chartTimeframe().period;
1272
- return perBar((t) => (test && !(inside ? inside(t) : bars.inside(t)) ? NaN : bars.timeOf(t, timeframeString)));
1277
+ const { open } = chartSessionPeriods(tf || chartTimeframe().period, session, timezone);
1278
+ return perBar((t) => open(t));
1273
1279
  }
1274
1280
  function timeCloseFunction(tf, session, timezone) {
1275
1281
  collector();
1276
- const { bars, test, inside } = calendarFor(session, timezone);
1277
- const timeframeString = tf || chartTimeframe().period;
1278
- return perBar((t) => (test && !(inside ? inside(t) : bars.inside(t)) ? NaN : bars.closeOf(t, timeframeString)));
1282
+ const { close } = chartSessionPeriods(tf || chartTimeframe().period, session, timezone);
1283
+ return perBar((t) => close(t));
1279
1284
  }
1280
1285
  /**
1281
1286
  * PineScript `time`: the bar open time in ms (a Series), and `time(timeframe?, session?, timezone?)`:
@@ -1299,6 +1304,27 @@ function changeOf(tf, firstBar = false) {
1299
1304
  const changed = (a, b) => !Number.isNaN(a) && !Number.isNaN(b) && a !== b;
1300
1305
  return perBar((t, i, times) => ((i > 0 ? changed(bars.timeOf(t, tf), bars.timeOf(times[i - 1], tf)) : firstBar) ? 1 : 0));
1301
1306
  }
1307
+ /**
1308
+ * Default anchor of `ta.vwap` (#173, #178): the first bar and each bar whose trading day differs from the last bar
1309
+ * that has one. Bars after an early close have no day: the first of them continues the day before it, each further
1310
+ * one starts a new sum, and the next day starts on its first bar (where `timeframe.change("D")` is false). On
1311
+ * D / W / M charts every bar.
1312
+ */
1313
+ function vwapAnchor() {
1314
+ if (chartTimeframe().isdwm)
1315
+ return barFlag(() => true);
1316
+ const { bars } = sessionBars();
1317
+ let last = NaN;
1318
+ let previous = NaN;
1319
+ return perBar((t, i) => {
1320
+ const day = bars.timeOf(t, '1D');
1321
+ const isNew = i === 0 || (Number.isNaN(day) ? Number.isNaN(previous) : day !== last);
1322
+ if (!Number.isNaN(day))
1323
+ last = day;
1324
+ previous = day;
1325
+ return isNew ? 1 : 0;
1326
+ });
1327
+ }
1302
1328
  function flag(key) {
1303
1329
  const { bars, regular } = sessionBars();
1304
1330
  const values = sessionFlags(ctxBars.bars.map(barTime), bars, regular)[key];
@@ -1374,26 +1400,31 @@ function valuesOf(value, length) {
1374
1400
  return value.toArray().slice();
1375
1401
  }
1376
1402
  /**
1377
- * PineScript `request.security` for the chart symbol (`syminfo.tickerid`, `""`, `ticker.heikinashi(...)`,
1378
- * `ticker.standard(...)`) and a timeframe equal to or higher than the chart's. The higher-timeframe bars are
1379
- * built by grouping the chart bars, and `expression` runs on them (its `ta.*` calls, tuples and `timeframe.*`
1380
- * see the higher timeframe). Other symbols need external data and throw; a lower timeframe throws.
1403
+ * PineScript `request.security` on a timeframe equal to or higher than the chart's. For the chart symbol
1404
+ * (`syminfo.tickerid`, `""`, `ticker.heikinashi(...)`, `ticker.standard(...)`) the higher-timeframe bars are built by
1405
+ * grouping the chart bars; another symbol's bars come from `ChartContext.bars` (#179) and are merged on the chart bars
1406
+ * by close time (lookahead off) or by open time / trading day (lookahead on), see `securityOther`. `expression` runs
1407
+ * on those bars (its `ta.*` calls, tuples, `timeframe.*` and `syminfo.*` see them). A lower timeframe throws.
1408
+ *
1409
+ * A symbol that does not exist (bars []) stops the script with "Invalid symbol: X", or gives na with
1410
+ * `ignore_invalid_symbol`.
1381
1411
  *
1382
1412
  * PineScript rules: lookahead off shows a period's value from the
1383
1413
  * bar that completes it; lookahead on from its first bar; gaps on keeps only the bar where the value appears.
1384
1414
  * Built bars can differ from the exchange's bars (official open / close, volume).
1385
1415
  */
1386
- function security(symbol, timeframe, expression, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off) {
1416
+ function security(symbol, timeframe, expression, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off, ignore_invalid_symbol = false) {
1387
1417
  collector();
1388
1418
  const heikin = symbol.startsWith(HEIKIN_ASHI);
1389
1419
  const base = heikin ? symbol.slice(HEIKIN_ASHI.length) : symbol;
1390
- if (base !== '' && base !== chartCtx.tickerid) {
1391
- throw new Error(`request.security: "${symbol}" is not the chart symbol; other symbols need external data.`);
1392
- }
1393
1420
  const chart = chartTimeframe();
1394
1421
  const tf = timeframeCore.info(timeframe || chart.period).period;
1422
+ if (base !== '' && base !== chartCtx.tickerid) {
1423
+ return externalSecurity(base, heikin, tf, expression, gaps, lookahead, ignore_invalid_symbol, false);
1424
+ }
1395
1425
  if (timeframeCore.in_seconds(tf) < timeframeCore.in_seconds(chart.period)) {
1396
- throw new Error(`request.security: timeframe "${tf}" is lower than the chart timeframe "${chart.period}".`);
1426
+ // a lower timeframe of the chart symbol: its bars come from ChartContext.bars (#181)
1427
+ return externalSecurity(chartCtx.tickerid ?? '', heikin, tf, expression, gaps, lookahead, ignore_invalid_symbol, true);
1397
1428
  }
1398
1429
  const chartBars = ctxBars.bars;
1399
1430
  const times = chartBars.map(barTime);
@@ -1415,6 +1446,8 @@ function security(symbol, timeframe, expression, gaps = barmerge.gaps_off, looka
1415
1446
  const periods = at ? calendar.calendar.periods(at.date) : [];
1416
1447
  return periods.length ? calendar.timeOf(periods[0][0], tf) : starts[i];
1417
1448
  });
1449
+ const tfInfo = timeframeCore.info(tf);
1450
+ strayPeriods(starts, dayPeriods, times, loaded, tfInfo.isdwm ? null : timeframeCore.in_seconds(tf) * 1000);
1418
1451
  const reaches = times.map((t) => loaded.closeOf(t, chart.period) >= calendar.periodEnd(t, tf));
1419
1452
  ({ group, latest } = periodsOf(starts, dayPeriods, reaches));
1420
1453
  const unit = chartCtx.timeUnit === 'ms' ? 1 : 1000;
@@ -1442,8 +1475,108 @@ function security(symbol, timeframe, expression, gaps = barmerge.gaps_off, looka
1442
1475
  const mapped = values.map((v) => Series.fromArray(ctxBars, mapToChart(v, group, latest, lookahead === barmerge.lookahead_on, gaps === barmerge.gaps_on)));
1443
1476
  return (Array.isArray(result) ? mapped : mapped[0]);
1444
1477
  }
1445
- /** PineScript `request.*`: `security` for the chart symbol (issue #101). */
1446
- export const request = { security };
1478
+ /**
1479
+ * Runs `expression` on the bars of `symbol` / `tf` given by `ChartContext.bars` (#179, #181). Bars not given: the
1480
+ * request is recorded for the {@link MissingSymbolDataError} and the expression runs on no bar. `own`: the chart
1481
+ * symbol, whose calendar defaults to the chart's.
1482
+ */
1483
+ function runExternal(symbol, heikin, tf, expression, ignoreInvalid, own) {
1484
+ let bars = chartCtx.bars?.(symbol, tf);
1485
+ if (bars === undefined && !missingSymbols.some((r) => r.symbol === symbol && r.timeframe === tf)) {
1486
+ missingSymbols.push({ symbol, timeframe: tf });
1487
+ }
1488
+ if (bars !== undefined && bars.length === 0 && !ignoreInvalid)
1489
+ throw new Error(`Invalid symbol: ${symbol}`);
1490
+ bars ??= [];
1491
+ if (heikin)
1492
+ bars = heikinAshi(bars);
1493
+ const given = chartCtx.symbolInfo?.(symbol);
1494
+ const info = own ? { ...chartCtx, ...given } : (given ?? {});
1495
+ const chartBars = ctxBars.bars;
1496
+ const savedCtx = chartCtx;
1497
+ let result;
1498
+ let values;
1499
+ ctxBars.setAll(bars);
1500
+ // the expression sees that symbol: its ticker, timeframe, calendar and properties
1501
+ chartCtx = {
1502
+ ...chartCtx,
1503
+ tickerid: symbol,
1504
+ timeframe: tf,
1505
+ timezone: info.timezone,
1506
+ session: info.session,
1507
+ regularSession: info.regularSession,
1508
+ mintick: info.mintick,
1509
+ minmove: info.minmove,
1510
+ pricescale: info.pricescale,
1511
+ pointvalue: info.pointvalue,
1512
+ mincontract: info.mincontract,
1513
+ symbolType: info.symbolType,
1514
+ currency: info.currency,
1515
+ basecurrency: info.basecurrency,
1516
+ root: info.root,
1517
+ };
1518
+ try {
1519
+ result = expression();
1520
+ const parts = Array.isArray(result) ? result : [result];
1521
+ values = parts.map((p) => valuesOf(p, bars.length));
1522
+ }
1523
+ finally {
1524
+ ctxBars.setAll(chartBars);
1525
+ chartCtx = savedCtx;
1526
+ }
1527
+ return { bars, info, result, values };
1528
+ }
1529
+ /** The chart for the merge functions of `security/other`. */
1530
+ function mergeChart() {
1531
+ return {
1532
+ timeframe: chartTimeframe().period,
1533
+ timezone: chartCtx.timezone,
1534
+ session: chartCtx.session,
1535
+ timeUnit: chartCtx.timeUnit,
1536
+ // the realtime rule applies to a last bar that receives live updates
1537
+ lastBarConfirmed: chartCtx.realtime !== true,
1538
+ };
1539
+ }
1540
+ /**
1541
+ * `request.security` of another symbol (#179), or of the chart symbol on a lower timeframe (#181): its bars from
1542
+ * `ChartContext.bars`, merged by `securityOther`.
1543
+ */
1544
+ function externalSecurity(symbol, heikin, tf, expression, gaps, lookahead, ignoreInvalid, own) {
1545
+ const { bars, info, result, values } = runExternal(symbol, heikin, tf, expression, ignoreInvalid, own);
1546
+ const index = securityOther(ctxBars.bars, bars, tf, mergeChart(), {
1547
+ gaps: gaps === barmerge.gaps_on,
1548
+ lookahead: lookahead === barmerge.lookahead_on,
1549
+ other: info,
1550
+ });
1551
+ const mapped = values.map((v) => Series.fromArray(ctxBars, index.map((j) => (j < 0 ? NaN : (v[j] ?? NaN)))));
1552
+ return (Array.isArray(result) ? mapped : mapped[0]);
1553
+ }
1554
+ /**
1555
+ * PineScript `request.security_lower_tf(symbol, timeframe, expression, ignore_invalid_symbol)` (#181): for each
1556
+ * chart bar, the array of the values of `expression` on the bars of the lower timeframe that the chart bar contains
1557
+ * (those whose close is after the open of the chart bar and at or before its close; empty when there are none).
1558
+ * The bars come from `ChartContext.bars`, for the chart symbol (`""`, `syminfo.tickerid`) as for another symbol.
1559
+ * Returns one array per chart bar (oldest first), or one such list per tuple part.
1560
+ * @throws Error when `timeframe` is higher than the chart timeframe
1561
+ */
1562
+ function security_lower_tf(symbol, timeframe, expression, ignore_invalid_symbol = false) {
1563
+ collector();
1564
+ const heikin = symbol.startsWith(HEIKIN_ASHI);
1565
+ const base = heikin ? symbol.slice(HEIKIN_ASHI.length) : symbol;
1566
+ const chart = chartTimeframe();
1567
+ const tf = timeframeCore.info(timeframe || chart.period).period;
1568
+ if (timeframeCore.in_seconds(tf) > timeframeCore.in_seconds(chart.period)) {
1569
+ throw new Error(`request.security_lower_tf: timeframe "${tf}" is higher than the chart timeframe "${chart.period}".`);
1570
+ }
1571
+ const own = base === '' || base === chartCtx.tickerid;
1572
+ const ticker = own ? (chartCtx.tickerid ?? '') : base;
1573
+ const { bars, info, result, values } = runExternal(ticker, heikin, tf, expression, ignore_invalid_symbol, own);
1574
+ const ranges = securityLowerTf(ctxBars.bars, bars, tf, mergeChart(), { other: info });
1575
+ const mapped = values.map((v) => ranges.map(([from, to]) => v.slice(from, to)));
1576
+ return (Array.isArray(result) ? mapped : mapped[0]);
1577
+ }
1578
+ /** PineScript `request.*`: `security` (issue #101, other symbols #179, lower timeframes #181) and `security_lower_tf`. */
1579
+ export const request = { security, security_lower_tf };
1447
1580
  // ── Drawing objects ──────────────────────────────────────────────────────────
1448
1581
  // The drawing namespaces come from this bundle so that they share the registry that
1449
1582
  // executeScript() resets. `line.all` etc. are read like PineScript variables.