oakscriptjs 0.10.1 → 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 (271) 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.d.ts.map +1 -1
  6. package/dist/cjs/box/index.js +7 -1
  7. package/dist/cjs/box/index.js.map +2 -2
  8. package/dist/cjs/callsite/index.d.ts +73 -32
  9. package/dist/cjs/callsite/index.d.ts.map +1 -1
  10. package/dist/cjs/callsite/index.js +69 -65
  11. package/dist/cjs/callsite/index.js.map +2 -2
  12. package/dist/cjs/drawing/registry.d.ts +84 -0
  13. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  14. package/dist/cjs/drawing/registry.js +153 -6
  15. package/dist/cjs/drawing/registry.js.map +2 -2
  16. package/dist/cjs/index.d.ts +8 -2
  17. package/dist/cjs/index.d.ts.map +1 -1
  18. package/dist/cjs/index.js +33 -1
  19. package/dist/cjs/index.js.map +2 -2
  20. package/dist/cjs/input.d.ts +9 -1
  21. package/dist/cjs/input.d.ts.map +1 -1
  22. package/dist/cjs/input.js +16 -0
  23. package/dist/cjs/input.js.map +2 -2
  24. package/dist/cjs/label/index.d.ts.map +1 -1
  25. package/dist/cjs/label/index.js +3 -0
  26. package/dist/cjs/label/index.js.map +2 -2
  27. package/dist/cjs/line/index.d.ts +10 -27
  28. package/dist/cjs/line/index.d.ts.map +1 -1
  29. package/dist/cjs/line/index.js +11 -22
  30. package/dist/cjs/line/index.js.map +2 -2
  31. package/dist/cjs/linefill/index.d.ts +3 -0
  32. package/dist/cjs/linefill/index.d.ts.map +1 -1
  33. package/dist/cjs/linefill/index.js +15 -1
  34. package/dist/cjs/linefill/index.js.map +3 -3
  35. package/dist/cjs/math/index.d.ts.map +1 -1
  36. package/dist/cjs/math/index.js +2 -0
  37. package/dist/cjs/math/index.js.map +2 -2
  38. package/dist/cjs/polyline/index.d.ts.map +1 -1
  39. package/dist/cjs/polyline/index.js +3 -0
  40. package/dist/cjs/polyline/index.js.map +2 -2
  41. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
  42. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
  43. package/dist/cjs/runtime/index.d.ts +1 -1
  44. package/dist/cjs/runtime/index.d.ts.map +1 -1
  45. package/dist/cjs/runtime/index.js +1 -0
  46. package/dist/cjs/runtime/index.js.map +2 -2
  47. package/dist/cjs/runtime/inputs.d.ts +9 -0
  48. package/dist/cjs/runtime/inputs.d.ts.map +1 -1
  49. package/dist/cjs/runtime/inputs.js +8 -0
  50. package/dist/cjs/runtime/inputs.js.map +2 -2
  51. package/dist/cjs/runtime/types.d.ts +1 -1
  52. package/dist/cjs/runtime/types.d.ts.map +1 -1
  53. package/dist/cjs/runtime/types.js.map +1 -1
  54. package/dist/cjs/script/index.d.ts +91 -21
  55. package/dist/cjs/script/index.d.ts.map +1 -1
  56. package/dist/cjs/script/index.js +207 -19
  57. package/dist/cjs/script/index.js.map +3 -3
  58. package/dist/cjs/security/other.d.ts +92 -0
  59. package/dist/cjs/security/other.d.ts.map +1 -0
  60. package/dist/cjs/security/other.js +164 -0
  61. package/dist/cjs/security/other.js.map +7 -0
  62. package/dist/cjs/session/bars.d.ts +24 -1
  63. package/dist/cjs/session/bars.d.ts.map +1 -1
  64. package/dist/cjs/session/bars.js +56 -7
  65. package/dist/cjs/session/bars.js.map +3 -3
  66. package/dist/cjs/session/calendar.d.ts +13 -2
  67. package/dist/cjs/session/calendar.d.ts.map +1 -1
  68. package/dist/cjs/session/calendar.js +39 -9
  69. package/dist/cjs/session/calendar.js.map +2 -2
  70. package/dist/cjs/session/context.d.ts +76 -0
  71. package/dist/cjs/session/context.d.ts.map +1 -0
  72. package/dist/cjs/session/context.js +114 -0
  73. package/dist/cjs/session/context.js.map +7 -0
  74. package/dist/cjs/str/floatingdecimal.d.ts +31 -0
  75. package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
  76. package/dist/cjs/str/floatingdecimal.js +298 -0
  77. package/dist/cjs/str/floatingdecimal.js.map +7 -0
  78. package/dist/cjs/str/index.d.ts +4 -1
  79. package/dist/cjs/str/index.d.ts.map +1 -1
  80. package/dist/cjs/str/index.js +2 -1
  81. package/dist/cjs/str/index.js.map +2 -2
  82. package/dist/cjs/str/numberformat.d.ts +7 -3
  83. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  84. package/dist/cjs/str/numberformat.js +4 -3
  85. package/dist/cjs/str/numberformat.js.map +2 -2
  86. package/dist/cjs/ta/index.d.ts +67 -34
  87. package/dist/cjs/ta/index.d.ts.map +1 -1
  88. package/dist/cjs/ta/index.js +202 -145
  89. package/dist/cjs/ta/index.js.map +2 -2
  90. package/dist/cjs/ta/limits.d.ts +35 -0
  91. package/dist/cjs/ta/limits.d.ts.map +1 -0
  92. package/dist/cjs/ta/limits.js +64 -0
  93. package/dist/cjs/ta/limits.js.map +7 -0
  94. package/dist/cjs/ta/running-sum.d.ts +0 -16
  95. package/dist/cjs/ta/running-sum.d.ts.map +1 -1
  96. package/dist/cjs/ta/running-sum.js +2 -0
  97. package/dist/cjs/ta/running-sum.js.map +2 -2
  98. package/dist/cjs/ta-series.d.ts +8 -8
  99. package/dist/cjs/ta-series.d.ts.map +1 -1
  100. package/dist/cjs/ta-series.js +10 -8
  101. package/dist/cjs/ta-series.js.map +2 -2
  102. package/dist/cjs/table/index.d.ts +3 -1
  103. package/dist/cjs/table/index.d.ts.map +1 -1
  104. package/dist/cjs/table/index.js +1 -0
  105. package/dist/cjs/table/index.js.map +3 -3
  106. package/dist/cjs/time/index.d.ts +5 -2
  107. package/dist/cjs/time/index.d.ts.map +1 -1
  108. package/dist/cjs/time/index.js +2 -2
  109. package/dist/cjs/time/index.js.map +2 -2
  110. package/dist/cjs/time/session.d.ts +26 -1
  111. package/dist/cjs/time/session.d.ts.map +1 -1
  112. package/dist/cjs/time/session.js +94 -12
  113. package/dist/cjs/time/session.js.map +3 -3
  114. package/dist/cjs/time/timezone.d.ts +6 -1
  115. package/dist/cjs/time/timezone.d.ts.map +1 -1
  116. package/dist/cjs/time/timezone.js +2 -2
  117. package/dist/cjs/time/timezone.js.map +2 -2
  118. package/dist/cjs/types/metadata.d.ts +28 -4
  119. package/dist/cjs/types/metadata.d.ts.map +1 -1
  120. package/dist/cjs/types/metadata.js.map +1 -1
  121. package/dist/esm/array/index.d.ts +8 -2
  122. package/dist/esm/array/index.d.ts.map +1 -1
  123. package/dist/esm/array/index.js +12 -3
  124. package/dist/esm/array/index.js.map +1 -1
  125. package/dist/esm/box/index.d.ts.map +1 -1
  126. package/dist/esm/box/index.js +8 -2
  127. package/dist/esm/box/index.js.map +1 -1
  128. package/dist/esm/callsite/index.d.ts +73 -32
  129. package/dist/esm/callsite/index.d.ts.map +1 -1
  130. package/dist/esm/callsite/index.js +143 -106
  131. package/dist/esm/callsite/index.js.map +1 -1
  132. package/dist/esm/drawing/registry.d.ts +84 -0
  133. package/dist/esm/drawing/registry.d.ts.map +1 -1
  134. package/dist/esm/drawing/registry.js +260 -6
  135. package/dist/esm/drawing/registry.js.map +1 -1
  136. package/dist/esm/index.d.ts +8 -2
  137. package/dist/esm/index.d.ts.map +1 -1
  138. package/dist/esm/index.js +13 -2
  139. package/dist/esm/index.js.map +1 -1
  140. package/dist/esm/input.d.ts +9 -1
  141. package/dist/esm/input.d.ts.map +1 -1
  142. package/dist/esm/input.js +16 -0
  143. package/dist/esm/input.js.map +1 -1
  144. package/dist/esm/label/index.d.ts.map +1 -1
  145. package/dist/esm/label/index.js +4 -1
  146. package/dist/esm/label/index.js.map +1 -1
  147. package/dist/esm/line/index.d.ts +10 -27
  148. package/dist/esm/line/index.d.ts.map +1 -1
  149. package/dist/esm/line/index.js +22 -60
  150. package/dist/esm/line/index.js.map +1 -1
  151. package/dist/esm/linefill/index.d.ts +3 -0
  152. package/dist/esm/linefill/index.d.ts.map +1 -1
  153. package/dist/esm/linefill/index.js +20 -1
  154. package/dist/esm/linefill/index.js.map +1 -1
  155. package/dist/esm/math/index.d.ts.map +1 -1
  156. package/dist/esm/math/index.js +3 -0
  157. package/dist/esm/math/index.js.map +1 -1
  158. package/dist/esm/polyline/index.d.ts.map +1 -1
  159. package/dist/esm/polyline/index.js +5 -1
  160. package/dist/esm/polyline/index.js.map +1 -1
  161. package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
  162. package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
  163. package/dist/esm/runtime/index.d.ts +1 -1
  164. package/dist/esm/runtime/index.d.ts.map +1 -1
  165. package/dist/esm/runtime/index.js +1 -1
  166. package/dist/esm/runtime/index.js.map +1 -1
  167. package/dist/esm/runtime/inputs.d.ts +9 -0
  168. package/dist/esm/runtime/inputs.d.ts.map +1 -1
  169. package/dist/esm/runtime/inputs.js +15 -0
  170. package/dist/esm/runtime/inputs.js.map +1 -1
  171. package/dist/esm/runtime/types.d.ts +1 -1
  172. package/dist/esm/runtime/types.d.ts.map +1 -1
  173. package/dist/esm/script/index.d.ts +91 -21
  174. package/dist/esm/script/index.d.ts.map +1 -1
  175. package/dist/esm/script/index.js +259 -28
  176. package/dist/esm/script/index.js.map +1 -1
  177. package/dist/esm/security/other.d.ts +92 -0
  178. package/dist/esm/security/other.d.ts.map +1 -0
  179. package/dist/esm/security/other.js +211 -0
  180. package/dist/esm/security/other.js.map +1 -0
  181. package/dist/esm/session/bars.d.ts +24 -1
  182. package/dist/esm/session/bars.d.ts.map +1 -1
  183. package/dist/esm/session/bars.js +75 -6
  184. package/dist/esm/session/bars.js.map +1 -1
  185. package/dist/esm/session/calendar.d.ts +13 -2
  186. package/dist/esm/session/calendar.d.ts.map +1 -1
  187. package/dist/esm/session/calendar.js +54 -10
  188. package/dist/esm/session/calendar.js.map +1 -1
  189. package/dist/esm/session/context.d.ts +76 -0
  190. package/dist/esm/session/context.d.ts.map +1 -0
  191. package/dist/esm/session/context.js +124 -0
  192. package/dist/esm/session/context.js.map +1 -0
  193. package/dist/esm/str/floatingdecimal.d.ts +31 -0
  194. package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
  195. package/dist/esm/str/floatingdecimal.js +272 -0
  196. package/dist/esm/str/floatingdecimal.js.map +1 -0
  197. package/dist/esm/str/index.d.ts +4 -1
  198. package/dist/esm/str/index.d.ts.map +1 -1
  199. package/dist/esm/str/index.js +9 -2
  200. package/dist/esm/str/index.js.map +1 -1
  201. package/dist/esm/str/numberformat.d.ts +7 -3
  202. package/dist/esm/str/numberformat.d.ts.map +1 -1
  203. package/dist/esm/str/numberformat.js +11 -5
  204. package/dist/esm/str/numberformat.js.map +1 -1
  205. package/dist/esm/ta/index.d.ts +67 -34
  206. package/dist/esm/ta/index.d.ts.map +1 -1
  207. package/dist/esm/ta/index.js +284 -240
  208. package/dist/esm/ta/index.js.map +1 -1
  209. package/dist/esm/ta/limits.d.ts +35 -0
  210. package/dist/esm/ta/limits.d.ts.map +1 -0
  211. package/dist/esm/ta/limits.js +67 -0
  212. package/dist/esm/ta/limits.js.map +1 -0
  213. package/dist/esm/ta/running-sum.d.ts +0 -16
  214. package/dist/esm/ta/running-sum.d.ts.map +1 -1
  215. package/dist/esm/ta/running-sum.js +2 -0
  216. package/dist/esm/ta/running-sum.js.map +1 -1
  217. package/dist/esm/ta-series.d.ts +8 -8
  218. package/dist/esm/ta-series.d.ts.map +1 -1
  219. package/dist/esm/ta-series.js +11 -8
  220. package/dist/esm/ta-series.js.map +1 -1
  221. package/dist/esm/table/index.d.ts +3 -1
  222. package/dist/esm/table/index.d.ts.map +1 -1
  223. package/dist/esm/table/index.js +6 -1
  224. package/dist/esm/table/index.js.map +1 -1
  225. package/dist/esm/time/index.d.ts +5 -2
  226. package/dist/esm/time/index.d.ts.map +1 -1
  227. package/dist/esm/time/index.js +6 -3
  228. package/dist/esm/time/index.js.map +1 -1
  229. package/dist/esm/time/session.d.ts +26 -1
  230. package/dist/esm/time/session.d.ts.map +1 -1
  231. package/dist/esm/time/session.js +141 -16
  232. package/dist/esm/time/session.js.map +1 -1
  233. package/dist/esm/time/timezone.d.ts +6 -1
  234. package/dist/esm/time/timezone.d.ts.map +1 -1
  235. package/dist/esm/time/timezone.js +8 -2
  236. package/dist/esm/time/timezone.js.map +1 -1
  237. package/dist/esm/types/metadata.d.ts +28 -4
  238. package/dist/esm/types/metadata.d.ts.map +1 -1
  239. package/package.json +1 -1
  240. package/src/array/index.ts +11 -3
  241. package/src/box/index.ts +8 -2
  242. package/src/callsite/index.ts +150 -100
  243. package/src/drawing/registry.ts +278 -5
  244. package/src/index.ts +44 -1
  245. package/src/input.ts +18 -1
  246. package/src/label/index.ts +4 -1
  247. package/src/line/index.ts +22 -64
  248. package/src/linefill/index.ts +20 -1
  249. package/src/math/index.ts +2 -0
  250. package/src/polyline/index.ts +3 -1
  251. package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
  252. package/src/runtime/index.ts +1 -0
  253. package/src/runtime/inputs.ts +16 -0
  254. package/src/runtime/types.ts +1 -1
  255. package/src/script/index.ts +355 -34
  256. package/src/security/other.ts +269 -0
  257. package/src/session/bars.ts +70 -6
  258. package/src/session/calendar.ts +60 -10
  259. package/src/session/context.ts +162 -0
  260. package/src/str/floatingdecimal.ts +268 -0
  261. package/src/str/index.ts +8 -2
  262. package/src/str/numberformat.ts +13 -6
  263. package/src/ta/index.ts +304 -248
  264. package/src/ta/limits.ts +74 -0
  265. package/src/ta/running-sum.ts +2 -0
  266. package/src/ta-series.ts +19 -16
  267. package/src/table/index.ts +4 -1
  268. package/src/time/index.ts +6 -3
  269. package/src/time/session.ts +137 -15
  270. package/src/time/timezone.ts +9 -2
  271. package/src/types/metadata.ts +28 -4
@@ -87,7 +87,8 @@ import * as tableCore from '../table/index.js';
87
87
  import * as chartPointCore from '../chartpoint/index.js';
88
88
  import * as textCore from '../text/index.js';
89
89
  import * as mathCore from '../math/index.js';
90
- import { resetDrawings, setDrawingLimits } from '../drawing/registry.js';
90
+ import { resetDrawings, setDrawingBar, setDrawingLimits } from '../drawing/registry.js';
91
+ export { holdDrawing, releaseDrawing, varDrawing, type DrawingVariable } from '../drawing/registry.js';
91
92
  import { drawingOutputs } from '../drawing/output.js';
92
93
  import * as timeframeCore from '../timeframe/index.js';
93
94
  import * as timeCore from '../time/index.js';
@@ -95,8 +96,9 @@ import * as strCore from '../str/index.js';
95
96
  import { formatMessage } from '../str/messageformat.js';
96
97
  import { tickScale } from '../str/numberformat.js';
97
98
  import { TradingCalendar, type SessionSpec } from '../session/calendar.js';
98
- import { SessionBars, sessionFlags, type SessionFlags } from '../session/bars.js';
99
+ import { SessionBars, sessionFlags, strayPeriods, type SessionFlags } from '../session/bars.js';
99
100
  import { afterCompletion, heikinAshi, mapToChart, periodsOf, resample } from '../security/resample.js';
101
+ import { securityLowerTf, securityOther, type OtherSymbolChart } from '../security/other.js';
100
102
  import { position } from '../types/index.js';
101
103
  import type { ChartPoint } from '../types/index.js';
102
104
  import {
@@ -120,7 +122,7 @@ import {
120
122
  type StrategyTrade,
121
123
  type StrategyVariable,
122
124
  } from '../strategy/index.js';
123
- import { parsePineSession } from '../time/session.js';
125
+ import { parsePineSession, sessionRun } from '../time/session.js';
124
126
 
125
127
  /** Everything one run of a script produced. */
126
128
  export interface ScriptRunResult {
@@ -296,12 +298,15 @@ export function executeScript(
296
298
  ): ScriptRunResult {
297
299
  ctxBars.setAll(barsInput);
298
300
  chartCtx = { ...chart };
301
+ runNow = chart.now ?? Date.now();
299
302
  scriptOptions = options;
300
303
  ctx = freshCollector(inputs);
301
304
  resetDrawings();
302
305
  mathCore.resetRandom();
306
+ missingSymbols = [];
303
307
  try {
304
308
  body();
309
+ if (missingSymbols.length) throw new MissingSymbolDataError(missingSymbols);
305
310
  const c = ctx;
306
311
  return {
307
312
  metadata: {
@@ -478,6 +483,10 @@ export const input = {
478
483
  'input_session'
479
484
  );
480
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
+ },
481
490
  /** Date and time as a UNIX time in milliseconds, e.g. `input.time(timestamp('2024-01-01'), 'Start')`. */
482
491
  time(defval: number, title?: string, opts: InputOptions = {}): number {
483
492
  const v = registerInput<unknown>({ type: 'time', defval, title, ...common(opts) }, 'input_time');
@@ -547,25 +556,27 @@ export const ta = {
547
556
  ): [Series, Series, Series, Series, Series] =>
548
557
  taSeries.ichimoku(bars(), conversionPeriods, basePeriods, laggingSpan2Periods, displacement),
549
558
  /** `ta.highest(source, length)` or `ta.highest(length)` (source = high). */
550
- highest: (sourceOrLength: Series | number, length?: number): Series =>
559
+ highest: (sourceOrLength: Series | number, length?: number | Series): Series =>
551
560
  typeof sourceOrLength === 'number' ? taSeries.highest(high, sourceOrLength) : taSeries.highest(sourceOrLength, length!),
552
561
  /** `ta.lowest(source, length)` or `ta.lowest(length)` (source = low). */
553
- lowest: (sourceOrLength: Series | number, length?: number): Series =>
562
+ lowest: (sourceOrLength: Series | number, length?: number | Series): Series =>
554
563
  typeof sourceOrLength === 'number' ? taSeries.lowest(low, sourceOrLength) : taSeries.lowest(sourceOrLength, length!),
555
564
  /** `ta.highestbars(source, length)` or `ta.highestbars(length)` (source = high); offsets are 0 or negative. */
556
- highestbars: (sourceOrLength: Series | number, length?: number): Series =>
565
+ highestbars: (sourceOrLength: Series | number, length?: number | Series): Series =>
557
566
  typeof sourceOrLength === 'number'
558
567
  ? taSeries.highestbars(high, sourceOrLength)
559
568
  : taSeries.highestbars(sourceOrLength, length!),
560
569
  /** `ta.lowestbars(source, length)` or `ta.lowestbars(length)` (source = low); offsets are 0 or negative. */
561
- lowestbars: (sourceOrLength: Series | number, length?: number): Series =>
570
+ lowestbars: (sourceOrLength: Series | number, length?: number | Series): Series =>
562
571
  typeof sourceOrLength === 'number'
563
572
  ? taSeries.lowestbars(low, sourceOrLength)
564
573
  : taSeries.lowestbars(sourceOrLength, length!),
565
- /** `ta.vwap(source, anchor?, stdev_mult?)` with the chart volume; the default anchor is a new trading
566
- * day (`timeframe.change("1D")`), which needs the chart context (timeframe, time zone, session). */
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). */
567
578
  vwap: ((source: Series, anchor?: Series, stdev_mult?: number): Series | [Series, Series, Series] => {
568
- const a = anchor ?? changeOf('1D'); // PineScript default: timeframe.change("1D")
579
+ const a = anchor ?? vwapAnchor();
569
580
  return stdev_mult === undefined ? taSeries.vwap(source, volume, a) : taSeries.vwapBands(source, volume, a, stdev_mult);
570
581
  }) as {
571
582
  (source: Series, anchor?: Series): Series;
@@ -575,12 +586,12 @@ export const ta = {
575
586
  mfi: (source: Series, length: number): Series => taSeries.mfi(source, length, volume),
576
587
  /** `ta.pivothigh(source, leftbars, rightbars)` or `ta.pivothigh(leftbars, rightbars)` (source = high).
577
588
  * The value appears `rightbars` bars after the pivot bar, as in PineScript. */
578
- pivothigh: (sourceOrLeft: Series | number, leftOrRight: number, rightbars?: number): Series =>
589
+ pivothigh: (sourceOrLeft: Series | number, leftOrRight: number | Series, rightbars?: number | Series): Series =>
579
590
  typeof sourceOrLeft === 'number'
580
591
  ? taSeries.pivothigh(high, sourceOrLeft, leftOrRight)
581
592
  : taSeries.pivothigh(sourceOrLeft, leftOrRight, rightbars!),
582
593
  /** `ta.pivotlow(source, leftbars, rightbars)` or `ta.pivotlow(leftbars, rightbars)` (source = low). */
583
- pivotlow: (sourceOrLeft: Series | number, leftOrRight: number, rightbars?: number): Series =>
594
+ pivotlow: (sourceOrLeft: Series | number, leftOrRight: number | Series, rightbars?: number | Series): Series =>
584
595
  typeof sourceOrLeft === 'number'
585
596
  ? taSeries.pivotlow(low, sourceOrLeft, leftOrRight)
586
597
  : taSeries.pivotlow(sourceOrLeft, leftOrRight, rightbars!),
@@ -1262,6 +1273,7 @@ export function eachBar(fn: (c: BarContext) => number | boolean | void): Series
1262
1273
  for (idx = 0; idx < b.length; idx++) {
1263
1274
  if (pendingError && pendingError.bar === idx) throw new Error(historyError(idx, pendingError.barsBack));
1264
1275
  currentBar = idx;
1276
+ setDrawingBar(idx, idx === b.length - 1, b.length - 1);
1265
1277
  const r = fn(ctx);
1266
1278
  out[idx] = typeof r === 'number' ? r : typeof r === 'boolean' ? (r ? 1 : 0) : NaN;
1267
1279
  // grow the history buffers from the largest reference of this bar
@@ -1278,6 +1290,7 @@ export function eachBar(fn: (c: BarContext) => number | boolean | void): Series
1278
1290
  }
1279
1291
  } finally {
1280
1292
  currentBar = -1;
1293
+ setDrawingBar(null);
1281
1294
  }
1282
1295
  return Series.fromArray(ctxBars, out);
1283
1296
  }
@@ -1547,6 +1560,23 @@ export interface ChartContext {
1547
1560
  pointvalue?: number;
1548
1561
  /** Smallest tradable quantity (PineScript `syminfo.mincontract`); 1 for whole shares */
1549
1562
  mincontract?: number;
1563
+ /** Symbol type (PineScript `syminfo.type`): "stock", "crypto", "forex", "futures", "index", "fund"... */
1564
+ symbolType?: string;
1565
+ /** Currency of the symbol (PineScript `syminfo.currency`), e.g. "USD" */
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;
1574
+ /** Chart foreground colour (PineScript `chart.fg_color`), e.g. "#DBDBDB" on a dark chart */
1575
+ foregroundColor?: string;
1576
+ /** Chart background colour (PineScript `chart.bg_color`), e.g. "#0F0F0F" on a dark chart */
1577
+ backgroundColor?: string;
1578
+ /** Current time in ms (PineScript `timenow`); default: the clock of the computer at the start of the run */
1579
+ now?: number;
1550
1580
  /**
1551
1581
  * The last bar is closed (`barstate.isconfirmed` on the last bar). Default false: PineScript treats the last bar
1552
1582
  * of the data as not confirmed until the data feed closes it.
@@ -1556,8 +1586,60 @@ export interface ChartContext {
1556
1586
  realtime?: boolean;
1557
1587
  /** On a realtime last bar: this run is the first update of the bar (`barstate.isnew`); default true. */
1558
1588
  lastBarNew?: boolean;
1589
+ /**
1590
+ * Bars of another symbol for `request.security(symbol, timeframe, ...)` (#179): the bars of `symbol` (the ticker id
1591
+ * as written in the script, e.g. "NASDAQ:MSFT") on `timeframe` (the chart timeframe for ""), oldest first, times in
1592
+ * `timeUnit`. Return undefined when they are not loaded: the run then ends with a {@link MissingSymbolDataError}
1593
+ * that lists every request, so the host can load them and run again. Return [] for a symbol that does not exist.
1594
+ */
1595
+ bars?: (symbol: string, timeframe: string) => Bar[] | undefined;
1596
+ /**
1597
+ * Calendar and properties of another symbol: its time zone and session give the close times and trading days that
1598
+ * `request.security` merges on (#179), and the other fields its `syminfo.*` inside the expression.
1599
+ */
1600
+ symbolInfo?: (symbol: string) => SymbolInfo | undefined;
1601
+ }
1602
+
1603
+ /** Calendar and properties of a symbol, as in {@link ChartContext}. */
1604
+ export type SymbolInfo = Pick<
1605
+ ChartContext,
1606
+ | 'timezone'
1607
+ | 'session'
1608
+ | 'regularSession'
1609
+ | 'mintick'
1610
+ | 'minmove'
1611
+ | 'pricescale'
1612
+ | 'pointvalue'
1613
+ | 'mincontract'
1614
+ | 'symbolType'
1615
+ | 'currency'
1616
+ | 'basecurrency'
1617
+ | 'root'
1618
+ >;
1619
+
1620
+ /** A request of `request.security` for another symbol whose bars the host has not given. */
1621
+ export interface SymbolDataRequest {
1622
+ symbol: string;
1623
+ timeframe: string;
1624
+ }
1625
+
1626
+ /**
1627
+ * Thrown by `executeScript` when `request.security` asked for bars of another symbol that `ChartContext.bars` did not
1628
+ * give (#179). `requests` lists them all, once each: load them, then run the script again.
1629
+ */
1630
+ export class MissingSymbolDataError extends Error {
1631
+ constructor(readonly requests: SymbolDataRequest[]) {
1632
+ super(
1633
+ `request.security needs the bars of ${requests.map((r) => `${r.symbol} (${r.timeframe})`).join(', ')}: ` +
1634
+ 'pass them with ChartContext.bars.'
1635
+ );
1636
+ this.name = 'MissingSymbolDataError';
1637
+ }
1559
1638
  }
1560
1639
 
1640
+ /** Requests of other symbols without bars in the current run. */
1641
+ let missingSymbols: SymbolDataRequest[] = [];
1642
+
1561
1643
  let chartCtx: ChartContext = {};
1562
1644
 
1563
1645
  function chartTimeframe(): timeframeCore.TimeframeInfo {
@@ -1638,6 +1720,16 @@ function symbolProperty(key: 'mintick' | 'pointvalue' | 'mincontract'): number {
1638
1720
  return value;
1639
1721
  }
1640
1722
 
1723
+ /** A text value of the chart or the symbol that the host passes in the chart context. */
1724
+ function chartText(key: 'symbolType' | 'currency' | 'basecurrency' | 'root' | 'foregroundColor' | 'backgroundColor', name: string): string {
1725
+ collector();
1726
+ const value = chartCtx[key];
1727
+ if (value === undefined) {
1728
+ throw new Error(`${name} is not known: pass it as executeScript(body, bars, inputs, { ${key} }).`);
1729
+ }
1730
+ return value;
1731
+ }
1732
+
1641
1733
  /** `[minmove, pricescale]` of the chart symbol: from the chart context, else from its mintick. */
1642
1734
  function symbolScale(): [number, number] {
1643
1735
  collector();
@@ -1649,7 +1741,8 @@ function symbolScale(): [number, number] {
1649
1741
 
1650
1742
  /**
1651
1743
  * PineScript `syminfo.timezone`, `syminfo.tickerid`, `syminfo.session` (session type), `syminfo.mintick`,
1652
- * `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue` and `syminfo.mincontract`, 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.
1653
1746
  */
1654
1747
  export const syminfo = {
1655
1748
  get mintick(): number {
@@ -1681,6 +1774,18 @@ export const syminfo = {
1681
1774
  collector();
1682
1775
  return chartCtx.sessionType ?? 'regular';
1683
1776
  },
1777
+ get type(): string {
1778
+ return chartText('symbolType', 'syminfo.type');
1779
+ },
1780
+ get currency(): string {
1781
+ return chartText('currency', 'syminfo.currency');
1782
+ },
1783
+ get basecurrency(): string {
1784
+ return chartText('basecurrency', 'syminfo.basecurrency');
1785
+ },
1786
+ get root(): string {
1787
+ return chartText('root', 'syminfo.root');
1788
+ },
1684
1789
  };
1685
1790
 
1686
1791
  /** A calendar function: `(time, timezone?)` on a UNIX time or on a Series of times. */
@@ -1794,7 +1899,11 @@ function sessionBars(): { bars: SessionBars; regular: SessionBars } {
1794
1899
  * session. As in PineScript, an explicit time zone reads the symbol session hours in that zone without
1795
1900
  * corrections and holidays, and an explicit session follows PineScript's session string rules.
1796
1901
  */
1797
- function calendarFor(session?: string, timezone?: string): { bars: SessionBars; test: boolean } {
1902
+ function calendarFor(
1903
+ session?: string,
1904
+ timezone?: string,
1905
+ dayPlacement = false
1906
+ ): { bars: SessionBars; test: boolean; inside?: (time: number) => boolean } {
1798
1907
  const { bars } = sessionBars();
1799
1908
  if (!session && !timezone) return { bars, test: false };
1800
1909
  const tz = timezone ?? exchangeTimezone();
@@ -1806,7 +1915,14 @@ function calendarFor(session?: string, timezone?: string): { bars: SessionBars;
1806
1915
  return { bars: new SessionBars(new TradingCalendar(tz, { session: toSpec(symbol).session }, 'symbol'), bars.chart.period), test: true };
1807
1916
  }
1808
1917
  const spec = session ? { session } : { session: toSpec(chartCtx.session!).session };
1809
- return { bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol'), bars.chart.period), test: true };
1918
+ return {
1919
+ bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol', dayPlacement), bars.chart.period),
1920
+ test: true,
1921
+ // a PineScript session string: the rules of time.inSession (runs ending at 0000, days around a clock change)
1922
+ inside: session
1923
+ ? (time) => timeCore.inSession(time, session, tz, timeframeCore.in_seconds(bars.chart.period) * 1000)
1924
+ : undefined,
1925
+ };
1810
1926
  }
1811
1927
 
1812
1928
  function perBar(fn: (t: number, i: number, times: number[]) => number): Series {
@@ -1821,24 +1937,61 @@ function callableSeries<F extends (...args: never[]) => Series>(series: Series,
1821
1937
  return fn as Series & F;
1822
1938
  }
1823
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
+
1824
1973
  function timeFunction(tf?: string, session?: string, timezone?: string): Series {
1825
1974
  collector();
1826
- const { bars, test } = calendarFor(session, timezone);
1827
- const timeframeString = tf || chartTimeframe().period;
1828
- return perBar((t) => (test && !bars.inside(t) ? NaN : bars.timeOf(t, timeframeString)));
1975
+ const { open } = sessionPeriods(tf || chartTimeframe().period, session, timezone);
1976
+ return perBar((t) => open(t));
1829
1977
  }
1830
1978
 
1831
1979
  function timeCloseFunction(tf?: string, session?: string, timezone?: string): Series {
1832
1980
  collector();
1833
- const { bars, test } = calendarFor(session, timezone);
1834
- const timeframeString = tf || chartTimeframe().period;
1835
- return perBar((t) => (test && !bars.inside(t) ? NaN : bars.closeOf(t, timeframeString)));
1981
+ const { close } = sessionPeriods(tf || chartTimeframe().period, session, timezone);
1982
+ return perBar((t) => close(t));
1836
1983
  }
1837
1984
 
1838
1985
  /**
1839
1986
  * PineScript `time`: the bar open time in ms (a Series), and `time(timeframe?, session?, timezone?)`:
1840
1987
  * the open time of the `timeframe` period that contains the bar, `na` outside `session`.
1841
1988
  */
1989
+ /** Current time of the run in ms, see `ChartContext.now`. */
1990
+ let runNow = NaN;
1991
+
1992
+ /** PineScript `timenow`: the current time in ms, the same on every bar of a run (`ChartContext.now`). */
1993
+ export const timenow = new Series(ctxBars, () => runNow);
1994
+
1842
1995
  export const time = callableSeries(new Series(ctxBars, (b) => barTime(b)), timeFunction);
1843
1996
 
1844
1997
  /**
@@ -1853,9 +2006,31 @@ export const time_close = callableSeries(
1853
2006
  /** PineScript `time_tradingday`: 00:00 UTC of the trading day of each bar, in ms. */
1854
2007
  export const time_tradingday = new Series(ctxBars, (b) => sessionBars().bars.tradingDayTime(barTime(b)));
1855
2008
 
1856
- function changeOf(tf: string): Series {
2009
+ function changeOf(tf: string, firstBar = false): Series {
1857
2010
  const { bars } = sessionBars();
1858
- return perBar((t, i, times) => (i > 0 && bars.timeOf(t, tf) !== bars.timeOf(times[i - 1]!, tf) ? 1 : 0));
2011
+ // a bar without a period (after an early close) is no change, and neither is the bar after it
2012
+ const changed = (a: number, b: number): boolean => !Number.isNaN(a) && !Number.isNaN(b) && a !== b;
2013
+ return perBar((t, i, times) => ((i > 0 ? changed(bars.timeOf(t, tf), bars.timeOf(times[i - 1]!, tf)) : firstBar) ? 1 : 0));
2014
+ }
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
+ });
1859
2034
  }
1860
2035
 
1861
2036
  function flag(key: keyof SessionFlags): Series {
@@ -1947,10 +2122,14 @@ function valuesOf(value: Series | number | boolean, length: number): number[] {
1947
2122
  }
1948
2123
 
1949
2124
  /**
1950
- * PineScript `request.security` for the chart symbol (`syminfo.tickerid`, `""`, `ticker.heikinashi(...)`,
1951
- * `ticker.standard(...)`) and a timeframe equal to or higher than the chart's. The higher-timeframe bars are
1952
- * built by grouping the chart bars, and `expression` runs on them (its `ta.*` calls, tuples and `timeframe.*`
1953
- * 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`.
1954
2133
  *
1955
2134
  * PineScript rules: lookahead off shows a period's value from the
1956
2135
  * bar that completes it; lookahead on from its first bar; gaps on keeps only the bar where the value appears.
@@ -1961,18 +2140,20 @@ function security<T extends SecurityValue>(
1961
2140
  timeframe: string,
1962
2141
  expression: () => T,
1963
2142
  gaps: string = barmerge.gaps_off,
1964
- lookahead: string = barmerge.lookahead_off
2143
+ lookahead: string = barmerge.lookahead_off,
2144
+ ignore_invalid_symbol: boolean = false
1965
2145
  ): T extends unknown[] ? Series[] : Series {
1966
2146
  collector();
1967
2147
  const heikin = symbol.startsWith(HEIKIN_ASHI);
1968
2148
  const base = heikin ? symbol.slice(HEIKIN_ASHI.length) : symbol;
1969
- if (base !== '' && base !== chartCtx.tickerid) {
1970
- throw new Error(`request.security: "${symbol}" is not the chart symbol; other symbols need external data.`);
1971
- }
1972
2149
  const chart = chartTimeframe();
1973
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
+ }
1974
2154
  if (timeframeCore.in_seconds(tf) < timeframeCore.in_seconds(chart.period)) {
1975
- 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);
1976
2157
  }
1977
2158
 
1978
2159
  const chartBars = ctxBars.bars;
@@ -1994,6 +2175,8 @@ function security<T extends SecurityValue>(
1994
2175
  const periods = at ? calendar.calendar.periods(at.date) : [];
1995
2176
  return periods.length ? calendar.timeOf(periods[0]![0], tf) : starts[i]!;
1996
2177
  });
2178
+ const tfInfo = timeframeCore.info(tf);
2179
+ strayPeriods(starts, dayPeriods, times, loaded, tfInfo.isdwm ? null : timeframeCore.in_seconds(tf) * 1000);
1997
2180
  const reaches = times.map((t) => loaded.closeOf(t, chart.period) >= calendar.periodEnd(t, tf));
1998
2181
  ({ group, latest } = periodsOf(starts, dayPeriods, reaches));
1999
2182
  const unit = chartCtx.timeUnit === 'ms' ? 1 : 1000;
@@ -2023,8 +2206,138 @@ function security<T extends SecurityValue>(
2023
2206
  return (Array.isArray(result) ? mapped : mapped[0]) as T extends unknown[] ? Series[] : Series;
2024
2207
  }
2025
2208
 
2026
- /** PineScript `request.*`: `security` for the chart symbol (issue #101). */
2027
- 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 };
2028
2341
 
2029
2342
  // ── Drawing objects ──────────────────────────────────────────────────────────
2030
2343
  // The drawing namespaces come from this bundle so that they share the registry that
@@ -2091,6 +2404,14 @@ export const text = { ...textCore };
2091
2404
  * (index, time, and `price`, default the close), so it runs inside eachBar().
2092
2405
  */
2093
2406
  export const chart = {
2407
+ /** Chart foreground colour (PineScript `chart.fg_color`), from the chart context. */
2408
+ get fg_color(): string {
2409
+ return chartText('foregroundColor', 'chart.fg_color');
2410
+ },
2411
+ /** Chart background colour (PineScript `chart.bg_color`), from the chart context. */
2412
+ get bg_color(): string {
2413
+ return chartText('backgroundColor', 'chart.bg_color');
2414
+ },
2094
2415
  point: {
2095
2416
  ...chartPointCore,
2096
2417
  now(price?: number): ChartPoint {