oakscriptjs 0.9.5 → 0.10.0

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 (256) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/array/index.d.ts +93 -50
  3. package/dist/cjs/array/index.d.ts.map +1 -1
  4. package/dist/cjs/array/index.js +239 -78
  5. package/dist/cjs/array/index.js.map +3 -3
  6. package/dist/cjs/callsite/index.d.ts +81 -2
  7. package/dist/cjs/callsite/index.d.ts.map +1 -1
  8. package/dist/cjs/callsite/index.js +62 -1
  9. package/dist/cjs/callsite/index.js.map +2 -2
  10. package/dist/cjs/color/index.d.ts +7 -3
  11. package/dist/cjs/color/index.d.ts.map +1 -1
  12. package/dist/cjs/color/index.js +6 -1
  13. package/dist/cjs/color/index.js.map +2 -2
  14. package/dist/cjs/drawing/bar-time.d.ts +24 -0
  15. package/dist/cjs/drawing/bar-time.d.ts.map +1 -0
  16. package/dist/cjs/drawing/bar-time.js +47 -0
  17. package/dist/cjs/drawing/bar-time.js.map +7 -0
  18. package/dist/cjs/drawing/output.d.ts +32 -0
  19. package/dist/cjs/drawing/output.d.ts.map +1 -0
  20. package/dist/cjs/drawing/output.js +184 -0
  21. package/dist/cjs/drawing/output.js.map +7 -0
  22. package/dist/cjs/drawing/registry.d.ts +5 -4
  23. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  24. package/dist/cjs/drawing/registry.js +11 -2
  25. package/dist/cjs/drawing/registry.js.map +2 -2
  26. package/dist/cjs/index.d.ts +6 -3
  27. package/dist/cjs/index.d.ts.map +1 -1
  28. package/dist/cjs/index.js +9 -2
  29. package/dist/cjs/index.js.map +2 -2
  30. package/dist/cjs/label/index.d.ts.map +1 -1
  31. package/dist/cjs/label/index.js +2 -1
  32. package/dist/cjs/label/index.js.map +2 -2
  33. package/dist/cjs/linefill/index.d.ts.map +1 -1
  34. package/dist/cjs/linefill/index.js +1 -0
  35. package/dist/cjs/linefill/index.js.map +2 -2
  36. package/dist/cjs/math/index.d.ts +76 -21
  37. package/dist/cjs/math/index.d.ts.map +1 -1
  38. package/dist/cjs/math/index.js +155 -41
  39. package/dist/cjs/math/index.js.map +3 -3
  40. package/dist/cjs/matrix/index.d.ts.map +1 -1
  41. package/dist/cjs/matrix/index.js +42 -36
  42. package/dist/cjs/matrix/index.js.map +2 -2
  43. package/dist/cjs/polyline/index.d.ts +3 -5
  44. package/dist/cjs/polyline/index.d.ts.map +1 -1
  45. package/dist/cjs/polyline/index.js +0 -5
  46. package/dist/cjs/polyline/index.js.map +2 -2
  47. package/dist/cjs/runtime/series.d.ts +9 -0
  48. package/dist/cjs/runtime/series.d.ts.map +1 -1
  49. package/dist/cjs/runtime/series.js +16 -2
  50. package/dist/cjs/runtime/series.js.map +2 -2
  51. package/dist/cjs/runtime/types.d.ts +22 -0
  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 +97 -36
  55. package/dist/cjs/script/index.d.ts.map +1 -1
  56. package/dist/cjs/script/index.js +173 -26
  57. package/dist/cjs/script/index.js.map +3 -3
  58. package/dist/cjs/session/calendar.d.ts.map +1 -1
  59. package/dist/cjs/session/calendar.js +15 -2
  60. package/dist/cjs/session/calendar.js.map +2 -2
  61. package/dist/cjs/str/index.d.ts +50 -38
  62. package/dist/cjs/str/index.d.ts.map +1 -1
  63. package/dist/cjs/str/index.js +43 -15
  64. package/dist/cjs/str/index.js.map +2 -2
  65. package/dist/cjs/str/messageformat.d.ts +2 -1
  66. package/dist/cjs/str/messageformat.d.ts.map +1 -1
  67. package/dist/cjs/str/messageformat.js +4 -1
  68. package/dist/cjs/str/messageformat.js.map +2 -2
  69. package/dist/cjs/str/numberformat.d.ts +4 -0
  70. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  71. package/dist/cjs/str/numberformat.js +26 -4
  72. package/dist/cjs/str/numberformat.js.map +3 -3
  73. package/dist/cjs/ta/index.d.ts +86 -43
  74. package/dist/cjs/ta/index.d.ts.map +1 -1
  75. package/dist/cjs/ta/index.js +173 -267
  76. package/dist/cjs/ta/index.js.map +3 -3
  77. package/dist/cjs/ta/running-sum.d.ts +54 -2
  78. package/dist/cjs/ta/running-sum.d.ts.map +1 -1
  79. package/dist/cjs/ta/running-sum.js +132 -52
  80. package/dist/cjs/ta/running-sum.js.map +2 -2
  81. package/dist/cjs/ta-series.d.ts +4 -4
  82. package/dist/cjs/ta-series.d.ts.map +1 -1
  83. package/dist/cjs/ta-series.js +4 -3
  84. package/dist/cjs/ta-series.js.map +2 -2
  85. package/dist/cjs/table/index.d.ts +87 -0
  86. package/dist/cjs/table/index.d.ts.map +1 -0
  87. package/dist/cjs/table/index.js +150 -0
  88. package/dist/cjs/table/index.js.map +7 -0
  89. package/dist/cjs/text/index.d.ts +17 -2
  90. package/dist/cjs/text/index.d.ts.map +1 -1
  91. package/dist/cjs/text/index.js +15 -1
  92. package/dist/cjs/text/index.js.map +2 -2
  93. package/dist/cjs/time/datestring.d.ts +1 -0
  94. package/dist/cjs/time/datestring.d.ts.map +1 -1
  95. package/dist/cjs/time/datestring.js +7 -0
  96. package/dist/cjs/time/datestring.js.map +2 -2
  97. package/dist/cjs/time/index.d.ts.map +1 -1
  98. package/dist/cjs/time/index.js +2 -1
  99. package/dist/cjs/time/index.js.map +2 -2
  100. package/dist/cjs/time/session.d.ts +28 -9
  101. package/dist/cjs/time/session.d.ts.map +1 -1
  102. package/dist/cjs/time/session.js +74 -28
  103. package/dist/cjs/time/session.js.map +2 -2
  104. package/dist/cjs/time/timezone.d.ts +5 -0
  105. package/dist/cjs/time/timezone.d.ts.map +1 -1
  106. package/dist/cjs/time/timezone.js +15 -1
  107. package/dist/cjs/time/timezone.js.map +2 -2
  108. package/dist/cjs/types/index.d.ts +30 -1
  109. package/dist/cjs/types/index.d.ts.map +1 -1
  110. package/dist/cjs/types/index.js +12 -0
  111. package/dist/cjs/types/index.js.map +2 -2
  112. package/dist/cjs/types/metadata.d.ts +258 -26
  113. package/dist/cjs/types/metadata.d.ts.map +1 -1
  114. package/dist/cjs/types/metadata.js.map +1 -1
  115. package/dist/esm/array/index.d.ts +93 -50
  116. package/dist/esm/array/index.d.ts.map +1 -1
  117. package/dist/esm/array/index.js +383 -133
  118. package/dist/esm/array/index.js.map +1 -1
  119. package/dist/esm/callsite/index.d.ts +81 -2
  120. package/dist/esm/callsite/index.d.ts.map +1 -1
  121. package/dist/esm/callsite/index.js +145 -3
  122. package/dist/esm/callsite/index.js.map +1 -1
  123. package/dist/esm/color/index.d.ts +7 -3
  124. package/dist/esm/color/index.d.ts.map +1 -1
  125. package/dist/esm/color/index.js +15 -3
  126. package/dist/esm/color/index.js.map +1 -1
  127. package/dist/esm/drawing/bar-time.d.ts +24 -0
  128. package/dist/esm/drawing/bar-time.d.ts.map +1 -0
  129. package/dist/esm/drawing/bar-time.js +44 -0
  130. package/dist/esm/drawing/bar-time.js.map +1 -0
  131. package/dist/esm/drawing/output.d.ts +32 -0
  132. package/dist/esm/drawing/output.d.ts.map +1 -0
  133. package/dist/esm/drawing/output.js +190 -0
  134. package/dist/esm/drawing/output.js.map +1 -0
  135. package/dist/esm/drawing/registry.d.ts +5 -4
  136. package/dist/esm/drawing/registry.d.ts.map +1 -1
  137. package/dist/esm/drawing/registry.js +12 -2
  138. package/dist/esm/drawing/registry.js.map +1 -1
  139. package/dist/esm/index.d.ts +6 -3
  140. package/dist/esm/index.d.ts.map +1 -1
  141. package/dist/esm/index.js +7 -3
  142. package/dist/esm/index.js.map +1 -1
  143. package/dist/esm/label/index.d.ts.map +1 -1
  144. package/dist/esm/label/index.js +2 -1
  145. package/dist/esm/label/index.js.map +1 -1
  146. package/dist/esm/linefill/index.d.ts.map +1 -1
  147. package/dist/esm/linefill/index.js +3 -0
  148. package/dist/esm/linefill/index.js.map +1 -1
  149. package/dist/esm/math/index.d.ts +76 -21
  150. package/dist/esm/math/index.d.ts.map +1 -1
  151. package/dist/esm/math/index.js +227 -61
  152. package/dist/esm/math/index.js.map +1 -1
  153. package/dist/esm/matrix/index.d.ts.map +1 -1
  154. package/dist/esm/matrix/index.js +57 -43
  155. package/dist/esm/matrix/index.js.map +1 -1
  156. package/dist/esm/polyline/index.d.ts +3 -5
  157. package/dist/esm/polyline/index.d.ts.map +1 -1
  158. package/dist/esm/polyline/index.js +3 -9
  159. package/dist/esm/polyline/index.js.map +1 -1
  160. package/dist/esm/runtime/series.d.ts +9 -0
  161. package/dist/esm/runtime/series.d.ts.map +1 -1
  162. package/dist/esm/runtime/series.js +18 -1
  163. package/dist/esm/runtime/series.js.map +1 -1
  164. package/dist/esm/runtime/types.d.ts +22 -0
  165. package/dist/esm/runtime/types.d.ts.map +1 -1
  166. package/dist/esm/script/index.d.ts +97 -36
  167. package/dist/esm/script/index.d.ts.map +1 -1
  168. package/dist/esm/script/index.js +211 -22
  169. package/dist/esm/script/index.js.map +1 -1
  170. package/dist/esm/session/calendar.d.ts.map +1 -1
  171. package/dist/esm/session/calendar.js +22 -2
  172. package/dist/esm/session/calendar.js.map +1 -1
  173. package/dist/esm/str/index.d.ts +50 -38
  174. package/dist/esm/str/index.d.ts.map +1 -1
  175. package/dist/esm/str/index.js +103 -52
  176. package/dist/esm/str/index.js.map +1 -1
  177. package/dist/esm/str/messageformat.d.ts +2 -1
  178. package/dist/esm/str/messageformat.d.ts.map +1 -1
  179. package/dist/esm/str/messageformat.js +7 -2
  180. package/dist/esm/str/messageformat.js.map +1 -1
  181. package/dist/esm/str/numberformat.d.ts +4 -0
  182. package/dist/esm/str/numberformat.d.ts.map +1 -1
  183. package/dist/esm/str/numberformat.js +38 -4
  184. package/dist/esm/str/numberformat.js.map +1 -1
  185. package/dist/esm/ta/index.d.ts +86 -43
  186. package/dist/esm/ta/index.d.ts.map +1 -1
  187. package/dist/esm/ta/index.js +296 -348
  188. package/dist/esm/ta/index.js.map +1 -1
  189. package/dist/esm/ta/running-sum.d.ts +54 -2
  190. package/dist/esm/ta/running-sum.d.ts.map +1 -1
  191. package/dist/esm/ta/running-sum.js +166 -71
  192. package/dist/esm/ta/running-sum.js.map +1 -1
  193. package/dist/esm/ta-series.d.ts +4 -4
  194. package/dist/esm/ta-series.d.ts.map +1 -1
  195. package/dist/esm/ta-series.js +5 -4
  196. package/dist/esm/ta-series.js.map +1 -1
  197. package/dist/esm/table/index.d.ts +87 -0
  198. package/dist/esm/table/index.d.ts.map +1 -0
  199. package/dist/esm/table/index.js +169 -0
  200. package/dist/esm/table/index.js.map +1 -0
  201. package/dist/esm/text/index.d.ts +17 -2
  202. package/dist/esm/text/index.d.ts.map +1 -1
  203. package/dist/esm/text/index.js +17 -2
  204. package/dist/esm/text/index.js.map +1 -1
  205. package/dist/esm/time/datestring.d.ts +1 -0
  206. package/dist/esm/time/datestring.d.ts.map +1 -1
  207. package/dist/esm/time/datestring.js +12 -0
  208. package/dist/esm/time/datestring.js.map +1 -1
  209. package/dist/esm/time/index.d.ts.map +1 -1
  210. package/dist/esm/time/index.js +3 -1
  211. package/dist/esm/time/index.js.map +1 -1
  212. package/dist/esm/time/session.d.ts +28 -9
  213. package/dist/esm/time/session.d.ts.map +1 -1
  214. package/dist/esm/time/session.js +113 -42
  215. package/dist/esm/time/session.js.map +1 -1
  216. package/dist/esm/time/timezone.d.ts +5 -0
  217. package/dist/esm/time/timezone.d.ts.map +1 -1
  218. package/dist/esm/time/timezone.js +21 -1
  219. package/dist/esm/time/timezone.js.map +1 -1
  220. package/dist/esm/types/index.d.ts +30 -1
  221. package/dist/esm/types/index.d.ts.map +1 -1
  222. package/dist/esm/types/index.js +14 -0
  223. package/dist/esm/types/index.js.map +1 -1
  224. package/dist/esm/types/metadata.d.ts +258 -26
  225. package/dist/esm/types/metadata.d.ts.map +1 -1
  226. package/package.json +1 -1
  227. package/src/array/index.ts +364 -150
  228. package/src/callsite/index.ts +147 -3
  229. package/src/color/index.ts +15 -5
  230. package/src/drawing/bar-time.ts +42 -0
  231. package/src/drawing/output.ts +214 -0
  232. package/src/drawing/registry.ts +16 -6
  233. package/src/index.ts +9 -3
  234. package/src/label/index.ts +2 -1
  235. package/src/linefill/index.ts +2 -0
  236. package/src/math/index.ts +256 -74
  237. package/src/matrix/index.ts +52 -52
  238. package/src/polyline/index.ts +3 -12
  239. package/src/runtime/series.ts +22 -1
  240. package/src/runtime/types.ts +23 -0
  241. package/src/script/index.ts +307 -25
  242. package/src/session/calendar.ts +22 -2
  243. package/src/str/index.ts +95 -51
  244. package/src/str/messageformat.ts +7 -2
  245. package/src/str/numberformat.ts +39 -4
  246. package/src/ta/index.ts +306 -386
  247. package/src/ta/running-sum.ts +187 -74
  248. package/src/ta-series.ts +8 -7
  249. package/src/table/index.ts +221 -0
  250. package/src/text/index.ts +18 -2
  251. package/src/time/datestring.ts +10 -0
  252. package/src/time/index.ts +3 -1
  253. package/src/time/session.ts +97 -47
  254. package/src/time/timezone.ts +21 -1
  255. package/src/types/index.ts +32 -1
  256. package/src/types/metadata.ts +325 -26
@@ -174,6 +174,17 @@ export class BarData {
174
174
  * const materialized = complex.materialize(); // Breaks closure chain
175
175
  * ```
176
176
  */
177
+ /** Largest history reference of a script series (PineScript max_bars_back limit). */
178
+ export const MAX_BARS_BACK = 5000;
179
+
180
+ /** PineScript's runtime error RE10008 text. */
181
+ export function historyError(bar: number, barsBack: number): string {
182
+ return (
183
+ `Error on bar ${bar}: The script attempts to reference historical data that is too far from the current bar ` +
184
+ `(${barsBack} bars back). The history-referencing length for the expression must be a value between 0 and ${MAX_BARS_BACK}.`
185
+ );
186
+ }
187
+
177
188
  export class Series {
178
189
  private extractor: SeriesExtractor;
179
190
  private dataSource: BarData;
@@ -186,6 +197,12 @@ export class Series {
186
197
  * @param data - Bar data (Bar[] or BarData)
187
198
  * @param extractor - Function to extract/compute value for each bar
188
199
  */
200
+ /**
201
+ * True for PineScript's built-in price variables (open, high, low, close, volume, hl2...): their history covers
202
+ * every bar, so references further than 5000 bars back are allowed (measured: close[bar_index] runs).
203
+ */
204
+ fullHistory = false;
205
+
189
206
  constructor(data: Bar[] | BarData, extractor: SeriesExtractor) {
190
207
  // Support both Bar[] and BarData for backward compatibility
191
208
  this.dataSource = data instanceof BarData ? data : new BarData(data);
@@ -219,7 +236,9 @@ export class Series {
219
236
  * @returns Series with extracted field values
220
237
  */
221
238
  static fromBars(bars: Bar[] | BarData, field: 'open' | 'high' | 'low' | 'close' | 'volume'): Series {
222
- return new Series(bars, (bar) => bar[field] ?? NaN);
239
+ const series = new Series(bars, (bar) => bar[field] ?? NaN);
240
+ series.fullHistory = true;
241
+ return series;
223
242
  }
224
243
 
225
244
  /**
@@ -516,6 +535,8 @@ export class Series {
516
535
  */
517
536
  offset(offset: number): Series {
518
537
  if (offset < 0) throw new RangeError(`Series.offset(${offset}): the offset cannot be negative (it would read a future bar).`);
538
+ // PineScript runtime error RE10008: a script series keeps at most 5000 bars of history
539
+ if (offset > MAX_BARS_BACK && !this.fullHistory) throw new Error(historyError(0, Math.floor(offset)));
519
540
  return new Series(this.dataSource, (_bar, i, data) => {
520
541
  const targetIndex = i - offset;
521
542
  if (targetIndex < 0 || targetIndex >= data.length) {
@@ -138,6 +138,29 @@ export interface ArrowConfig {
138
138
  forceOverlay?: boolean;
139
139
  }
140
140
 
141
+ /**
142
+ * Configuration of a PineScript `plotcandle()` (kind 'candle') or `plotbar()` (kind 'bar') declaration. The candles
143
+ * are in `result.plotCandles[id]`, the bars in `result.plotBars[id]`.
144
+ */
145
+ export interface CandleConfig {
146
+ /** Unique identifier (key of the data in the result) */
147
+ id: string;
148
+ /** 'candle' for plotcandle, 'bar' for plotbar */
149
+ kind: 'candle' | 'bar';
150
+ /** Display title */
151
+ title?: string;
152
+ /** Static body colour (plotcandle) or bar colour (plotbar) */
153
+ color?: string;
154
+ /** Static wick colour (plotcandle only) */
155
+ wickColor?: string;
156
+ /** Static border colour (plotcandle only) */
157
+ borderColor?: string;
158
+ /** Display mode */
159
+ display?: 'all' | 'none' | 'data_window' | 'status_line' | 'pane';
160
+ /** Drawn on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false */
161
+ forceOverlay?: boolean;
162
+ }
163
+
141
164
  /**
142
165
  * Configuration for horizontal line outputs in generated indicators
143
166
  * Matches PineScript hline() function parameters
@@ -37,25 +37,42 @@ import type { Bar } from '../types/index.js';
37
37
  import type {
38
38
  ArrowData,
39
39
  BarColorData,
40
+ BgColorData,
41
+ BoxData,
40
42
  FillData,
41
43
  FillGradient,
42
44
  IndicatorResult,
45
+ LabelData,
46
+ LabelStyle,
47
+ LineDrawingData,
48
+ LinefillData,
43
49
  MarkerData,
44
50
  MarkerLocation,
51
+ MarkerPosition,
52
+ MarkerShape,
45
53
  MarkerSize,
54
+ PineSize,
55
+ PlotBarData,
56
+ PlotCandleData,
57
+ PolylineData,
46
58
  ShapeStyle,
59
+ TableCellData,
60
+ TableData,
61
+ TableMergeData,
62
+ TablePosition,
47
63
  TimeValue,
48
64
  } from '../types/metadata.js';
49
65
  import type {
50
66
  ArrowConfig,
51
67
  BarColorConfig,
68
+ CandleConfig,
52
69
  FillConfig,
53
70
  HLineConfig,
54
71
  InputConfig,
55
72
  PlotConfig,
56
73
  ShapeConfig,
57
74
  } from '../runtime/types.js';
58
- import { BarData, Series } from '../runtime/series.js';
75
+ import { BarData, Series, MAX_BARS_BACK, historyError } from '../runtime/series.js';
59
76
  import * as taSeries from '../ta-series.js';
60
77
  import * as colorCore from '../color/index.js';
61
78
  import { inputColorDefault } from '../color/parse.js';
@@ -65,10 +82,12 @@ import * as labelCore from '../label/index.js';
65
82
  import * as boxCore from '../box/index.js';
66
83
  import * as linefillCore from '../linefill/index.js';
67
84
  import * as polylineCore from '../polyline/index.js';
85
+ import * as tableCore from '../table/index.js';
68
86
  import * as chartPointCore from '../chartpoint/index.js';
69
87
  import * as textCore from '../text/index.js';
70
88
  import * as mathCore from '../math/index.js';
71
89
  import { resetDrawings, setDrawingLimits } from '../drawing/registry.js';
90
+ import { drawingOutputs } from '../drawing/output.js';
72
91
  import * as timeframeCore from '../timeframe/index.js';
73
92
  import * as timeCore from '../time/index.js';
74
93
  import * as strCore from '../str/index.js';
@@ -76,6 +95,7 @@ import { formatMessage } from '../str/messageformat.js';
76
95
  import { TradingCalendar, type SessionSpec } from '../session/calendar.js';
77
96
  import { SessionBars, sessionFlags, type SessionFlags } from '../session/bars.js';
78
97
  import { afterCompletion, heikinAshi, mapToChart, periodsOf, resample } from '../security/resample.js';
98
+ import { position } from '../types/index.js';
79
99
  import type { ChartPoint } from '../types/index.js';
80
100
  import {
81
101
  STRATEGY_CONSTANTS,
@@ -98,6 +118,7 @@ import {
98
118
  type StrategyTrade,
99
119
  type StrategyVariable,
100
120
  } from '../strategy/index.js';
121
+ import { parsePineSession } from '../time/session.js';
101
122
 
102
123
  /** Everything one run of a script produced. */
103
124
  export interface ScriptRunResult {
@@ -109,11 +130,17 @@ export interface ScriptRunResult {
109
130
  shapeConfig: ShapeConfig[];
110
131
  arrowConfig: ArrowConfig[];
111
132
  barColorConfig: BarColorConfig[];
133
+ /** plotcandle() / plotbar() declarations */
134
+ candleConfig: CandleConfig[];
112
135
  defaultInputs: Record<string, unknown>;
113
136
  alertConfig: AlertConditionConfig[];
114
137
  /** Strategy properties (declared values, else the PineScript defaults) when the script declares strategy(). */
115
138
  strategyConfig?: StrategyProperties;
116
- /** The renderable output (plots keyed by plotConfig ids, plot-pair fills). */
139
+ /**
140
+ * The renderable output: plots keyed by plotConfig ids, plot-pair fills, markers, arrows, background and bar
141
+ * colours, candles and bars, and the drawings (labels, lines, boxes, linefills, polylines, tables) alive after the
142
+ * last bar.
143
+ */
117
144
  result: IndicatorResult & { alerts?: AlertState[] };
118
145
  }
119
146
 
@@ -158,8 +185,11 @@ interface Collector {
158
185
  arrowConfig: ArrowConfig[];
159
186
  arrows: ArrowData[];
160
187
  barColorConfig: BarColorConfig[];
161
- bgcolors: BarColorData[];
162
- barcolors: BarColorData[];
188
+ bgColors: BgColorData[];
189
+ barColors: BarColorData[];
190
+ candleConfig: CandleConfig[];
191
+ plotCandles: Record<string, PlotCandleData[]>;
192
+ plotBars: Record<string, PlotBarData[]>;
163
193
  alertConfig: AlertConditionConfig[];
164
194
  alerts: AlertState[];
165
195
  strategy?: StrategyRun;
@@ -209,8 +239,11 @@ function freshCollector(inputValues: Record<string, unknown>): Collector {
209
239
  arrowConfig: [],
210
240
  arrows: [],
211
241
  barColorConfig: [],
212
- bgcolors: [],
213
- barcolors: [],
242
+ bgColors: [],
243
+ barColors: [],
244
+ candleConfig: [],
245
+ plotCandles: {},
246
+ plotBars: {},
214
247
  alertConfig: [],
215
248
  alerts: [],
216
249
  };
@@ -264,6 +297,7 @@ export function executeScript(
264
297
  scriptOptions = options;
265
298
  ctx = freshCollector(inputs);
266
299
  resetDrawings();
300
+ mathCore.resetRandom();
267
301
  try {
268
302
  body();
269
303
  const c = ctx;
@@ -282,6 +316,7 @@ export function executeScript(
282
316
  shapeConfig: c.shapeConfig,
283
317
  arrowConfig: c.arrowConfig,
284
318
  barColorConfig: c.barColorConfig,
319
+ candleConfig: c.candleConfig,
285
320
  defaultInputs: c.defaultInputs,
286
321
  alertConfig: c.alertConfig,
287
322
  strategyConfig: c.strategy?.properties,
@@ -291,8 +326,11 @@ export function executeScript(
291
326
  fills: c.fills.length ? c.fills : undefined,
292
327
  markers: c.markers.length ? c.markers : undefined,
293
328
  arrows: c.arrows.length ? c.arrows : undefined,
294
- bgcolors: c.bgcolors.length ? c.bgcolors : undefined,
295
- barcolors: c.barcolors.length ? c.barcolors : undefined,
329
+ bgColors: c.bgColors.length ? c.bgColors : undefined,
330
+ barColors: c.barColors.length ? c.barColors : undefined,
331
+ plotCandles: Object.keys(c.plotCandles).length ? c.plotCandles : undefined,
332
+ plotBars: Object.keys(c.plotBars).length ? c.plotBars : undefined,
333
+ ...drawingOutputs(ctxBars.bars, chartCtx.timeUnit),
296
334
  alerts: c.alerts.length ? c.alerts : undefined,
297
335
  },
298
336
  };
@@ -449,6 +487,8 @@ export const hl2 = new Series(ctxBars, (b) => (b.high + b.low) / 2);
449
487
  export const hlc3 = new Series(ctxBars, (b) => (b.high + b.low + b.close) / 3);
450
488
  export const ohlc4 = new Series(ctxBars, (b) => (b.open + b.high + b.low + b.close) / 4);
451
489
  export const hlcc4 = new Series(ctxBars, (b) => (b.high + b.low + b.close + b.close) / 4);
490
+ // built-in price variables keep the whole history (no 5000-bar limit)
491
+ for (const s of [hl2, hlc3, ohlc4, hlcc4]) s.fullHistory = true;
452
492
 
453
493
  const SOURCES: Record<string, Series> = { open, high, low, close, volume, hl2, hlc3, ohlc4, hlcc4 };
454
494
 
@@ -775,8 +815,46 @@ export interface PlotCharOptions extends Omit<PlotShapeOptions, 'style'> {
775
815
  char?: string;
776
816
  }
777
817
 
818
+ /** PineScript shape.* style -> marker shape. */
819
+ const MARKER_SHAPES: Record<ShapeStyle, MarkerShape> = {
820
+ xcross: 'xcross',
821
+ cross: 'cross',
822
+ triangleup: 'triangleUp',
823
+ triangledown: 'triangleDown',
824
+ flag: 'flag',
825
+ circle: 'circle',
826
+ arrowup: 'arrowUp',
827
+ arrowdown: 'arrowDown',
828
+ labelup: 'labelUp',
829
+ labeldown: 'labelDown',
830
+ square: 'square',
831
+ diamond: 'diamond',
832
+ };
833
+
834
+ /**
835
+ * Marker position of a PineScript location. With location.absolute the shape sits on the price: a label pointing
836
+ * down above it, a label pointing up below it, the other shapes (and the characters) centred on it.
837
+ */
838
+ function markerPosition(location: MarkerLocation, shape: ShapeStyle | undefined): MarkerPosition {
839
+ switch (location) {
840
+ case 'abovebar':
841
+ return 'aboveBar';
842
+ case 'belowbar':
843
+ return 'belowBar';
844
+ case 'top':
845
+ return 'top';
846
+ case 'bottom':
847
+ return 'bottom';
848
+ case 'absolute':
849
+ return shape === 'labeldown' ? 'atPriceTop' : shape === 'labelup' ? 'atPriceBottom' : 'atPriceMiddle';
850
+ }
851
+ }
852
+
853
+ /** Default colour of plotshape / plotchar markers. */
854
+ const MARKER_COLOR = '#2962FF';
855
+
778
856
  /** Shared emitter for plotshape()/plotchar(). Emits one marker per bar where
779
- * the condition is truthy (not na, not 0). */
857
+ * the condition is truthy (not na, not 0). A plotchar marker has the shape 'circle' and its character in `char`. */
780
858
  function emitMarkers(
781
859
  kind: 'shape' | 'char',
782
860
  condition: Series,
@@ -802,6 +880,19 @@ function emitMarkers(
802
880
  offset: options.offset,
803
881
  forceOverlay: options.force_overlay,
804
882
  });
883
+ const shapeStyle = kind === 'shape' ? (style as ShapeStyle) : undefined;
884
+ const template: Omit<MarkerData, 'time'> = {
885
+ position: markerPosition(location, shapeStyle),
886
+ shape: shapeStyle ? MARKER_SHAPES[shapeStyle] : 'circle',
887
+ color: options.color ?? MARKER_COLOR,
888
+ id,
889
+ };
890
+ if (char !== undefined) template.char = char;
891
+ if (options.text !== undefined) template.text = options.text;
892
+ if (options.textcolor !== undefined) template.textColor = options.textcolor;
893
+ if (options.size !== undefined) template.size = options.size;
894
+ if (options.tooltip !== undefined) template.tooltip = options.tooltip;
895
+ if (options.force_overlay) template.forceOverlay = true;
805
896
  const values = condition.toArray();
806
897
  const b = bars();
807
898
  const off = options.offset ?? 0;
@@ -810,19 +901,8 @@ function emitMarkers(
810
901
  if (v === undefined || Number.isNaN(v) || v === 0) continue;
811
902
  const j = i + off;
812
903
  if (j < 0 || j >= b.length) continue;
813
- c.markers.push({
814
- time: b[j]!.time,
815
- id,
816
- location,
817
- style,
818
- char,
819
- color: options.color,
820
- text: options.text,
821
- textcolor: options.textcolor,
822
- size: options.size,
823
- tooltip: options.tooltip,
824
- price: location === 'absolute' ? v : undefined,
825
- });
904
+ const time = b[j]!.time;
905
+ c.markers.push(location === 'absolute' ? { time, ...template, price: v } : { time, ...template });
826
906
  }
827
907
  }
828
908
 
@@ -902,13 +982,14 @@ function emitBarColors(
902
982
  c.barColorConfig.push({ id, kind, title: options.title, offset: options.offset, forceOverlay: options.force_overlay });
903
983
  const b = bars();
904
984
  const off = options.offset ?? 0;
905
- const target = kind === 'bgcolor' ? c.bgcolors : c.barcolors;
985
+ const target: BgColorData[] = kind === 'bgcolor' ? c.bgColors : c.barColors;
986
+ const forceOverlay = kind === 'bgcolor' && options.force_overlay === true;
906
987
  for (let i = 0; i < b.length; i++) {
907
988
  const col = typeof colors === 'string' ? colors : colors[i];
908
989
  if (!col) continue;
909
990
  const j = i + off;
910
991
  if (j < 0 || j >= b.length) continue;
911
- target.push({ time: b[j]!.time, color: col });
992
+ target.push(forceOverlay ? { time: b[j]!.time, color: col, forceOverlay } : { time: b[j]!.time, color: col });
912
993
  }
913
994
  }
914
995
 
@@ -931,6 +1012,129 @@ export function barcolor(
931
1012
  emitBarColors('barcolor', colors, options);
932
1013
  }
933
1014
 
1015
+ /** A colour argument of plotcandle() / plotbar(): a colour, or per-bar colours (null / undefined entries are na). */
1016
+ export type ScriptCandleColor = string | Array<string | null | undefined>;
1017
+
1018
+ export interface PlotCandleOptions {
1019
+ /** Body colour: a colour or a per-bar array (color.when); an na entry gives a transparent body */
1020
+ color?: ScriptCandleColor;
1021
+ /** Wick colour: a colour or a per-bar array */
1022
+ wickcolor?: ScriptCandleColor;
1023
+ /** Border colour: a colour or a per-bar array */
1024
+ bordercolor?: ScriptCandleColor;
1025
+ display?: CandleConfig['display'];
1026
+ /** Draw on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false. */
1027
+ force_overlay?: boolean;
1028
+ }
1029
+
1030
+ export interface PlotBarOptions {
1031
+ /** Bar colour: a colour or a per-bar array (color.when); an na entry gives a transparent bar */
1032
+ color?: ScriptCandleColor;
1033
+ display?: CandleConfig['display'];
1034
+ /** Draw on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false. */
1035
+ force_overlay?: boolean;
1036
+ }
1037
+
1038
+ /** Colour of bar i: undefined when the argument is not given, 'transparent' for an na entry. */
1039
+ function candleColor(col: ScriptCandleColor | undefined, i: number): string | undefined {
1040
+ if (col === undefined) return undefined;
1041
+ if (typeof col === 'string') return col;
1042
+ return col[i] ?? 'transparent';
1043
+ }
1044
+
1045
+ interface OhlcRow {
1046
+ i: number;
1047
+ open: number;
1048
+ high: number;
1049
+ low: number;
1050
+ close: number;
1051
+ }
1052
+
1053
+ /** The bars where open, high, low and close are all not na (PineScript draws no candle / bar otherwise). */
1054
+ function ohlcRows(o: Series, h: Series, l: Series, cl: Series): OhlcRow[] {
1055
+ const [ov, hv, lv, cv] = [o.toArray(), h.toArray(), l.toArray(), cl.toArray()];
1056
+ const rows: OhlcRow[] = [];
1057
+ for (let i = 0; i < bars().length; i++) {
1058
+ const [open, high, low, close] = [ov[i]!, hv[i]!, lv[i]!, cv[i]!];
1059
+ if (Number.isFinite(open) && Number.isFinite(high) && Number.isFinite(low) && Number.isFinite(close)) {
1060
+ rows.push({ i, open, high, low, close });
1061
+ }
1062
+ }
1063
+ return rows;
1064
+ }
1065
+
1066
+ const staticColor = (col: ScriptCandleColor | undefined): string | undefined => (typeof col === 'string' ? col : undefined);
1067
+
1068
+ /**
1069
+ * PineScript `plotcandle(open, high, low, close, title?, { color, wickcolor, bordercolor, display, force_overlay })`:
1070
+ * candles in `result.plotCandles[id]`, the declaration in `candleConfig`. A bar with an na value has no candle.
1071
+ */
1072
+ export function plotcandle(
1073
+ open: Series,
1074
+ high: Series,
1075
+ low: Series,
1076
+ close: Series,
1077
+ title?: string,
1078
+ options: PlotCandleOptions = {}
1079
+ ): void {
1080
+ const c = collector();
1081
+ const id = `plotcandle${c.candleConfig.filter((x) => x.kind === 'candle').length}`;
1082
+ c.candleConfig.push({
1083
+ id,
1084
+ kind: 'candle',
1085
+ title,
1086
+ color: staticColor(options.color),
1087
+ wickColor: staticColor(options.wickcolor),
1088
+ borderColor: staticColor(options.bordercolor),
1089
+ display: options.display,
1090
+ forceOverlay: options.force_overlay,
1091
+ });
1092
+ const b = bars();
1093
+ c.plotCandles[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
1094
+ const candle: PlotCandleData = { time: b[i]!.time, ...ohlc };
1095
+ const color = candleColor(options.color, i);
1096
+ const wickColor = candleColor(options.wickcolor, i);
1097
+ const borderColor = candleColor(options.bordercolor, i);
1098
+ if (color !== undefined) candle.color = color;
1099
+ if (wickColor !== undefined) candle.wickColor = wickColor;
1100
+ if (borderColor !== undefined) candle.borderColor = borderColor;
1101
+ if (options.force_overlay) candle.forceOverlay = true;
1102
+ return candle;
1103
+ });
1104
+ }
1105
+
1106
+ /**
1107
+ * PineScript `plotbar(open, high, low, close, title?, { color, display, force_overlay })`: OHLC bars in
1108
+ * `result.plotBars[id]`, the declaration in `candleConfig`. A bar with an na value is not drawn.
1109
+ */
1110
+ export function plotbar(
1111
+ open: Series,
1112
+ high: Series,
1113
+ low: Series,
1114
+ close: Series,
1115
+ title?: string,
1116
+ options: PlotBarOptions = {}
1117
+ ): void {
1118
+ const c = collector();
1119
+ const id = `plotbar${c.candleConfig.filter((x) => x.kind === 'bar').length}`;
1120
+ c.candleConfig.push({
1121
+ id,
1122
+ kind: 'bar',
1123
+ title,
1124
+ color: staticColor(options.color),
1125
+ display: options.display,
1126
+ forceOverlay: options.force_overlay,
1127
+ });
1128
+ const b = bars();
1129
+ c.plotBars[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
1130
+ const bar: PlotBarData = { time: b[i]!.time, ...ohlc };
1131
+ const color = candleColor(options.color, i);
1132
+ if (color !== undefined) bar.color = color;
1133
+ if (options.force_overlay) bar.forceOverlay = true;
1134
+ return bar;
1135
+ });
1136
+ }
1137
+
934
1138
  // ── Per-bar execution (eachBar) ──────────────────────────────────────────────
935
1139
 
936
1140
  /**
@@ -975,11 +1179,30 @@ export interface BarContext {
975
1179
  * });
976
1180
  * ```
977
1181
  */
1182
+ /** Initial history buffer of a script series and its growth factor (measured, issue #149). */
1183
+ const HISTORY_START = 242;
1184
+ const HISTORY_GROWTH = 1.618034;
1185
+
1186
+ /**
1187
+ * History buffer of one script series in eachBar(), as PineScript grows it (measured on PineScript, issue #149):
1188
+ * the buffer starts with 242 bars; when the largest reference of a bar goes past it, a jump (the reference grew by
1189
+ * more than 1 since the previous bar) sets the buffer to the reference, and a step of 1 grows it to
1190
+ * round(size * 1.618034); a buffer above 5000 stops the script on the next bar (RE10008), with that size as
1191
+ * "bars back". A reference above 5000 stops it at once.
1192
+ */
1193
+ interface HistoryBuffer {
1194
+ size: number;
1195
+ previous: number;
1196
+ barMax: number;
1197
+ }
1198
+
978
1199
  export function eachBar(fn: (c: BarContext) => number | boolean | void): Series {
979
1200
  collector(); // same lifecycle guard (and error message) as the declaration calls
980
1201
  const b = bars();
981
1202
  const out: number[] = new Array(b.length).fill(NaN);
982
1203
  const cache = new Map<Series, number[]>();
1204
+ const buffers = new Map<Series, HistoryBuffer>();
1205
+ let pendingError: { bar: number; barsBack: number } | null = null;
983
1206
  let idx = 0;
984
1207
  const ctx: BarContext = {
985
1208
  get i() {
@@ -1006,6 +1229,13 @@ export function eachBar(fn: (c: BarContext) => number | boolean | void): Series
1006
1229
  },
1007
1230
  get(src: Series, offset: number = 0): number {
1008
1231
  if (offset < 0) throw new RangeError(`c.get(): the offset cannot be negative (${offset} would read a future bar).`);
1232
+ if (!src.fullHistory && offset > 0) {
1233
+ const k = Math.floor(offset);
1234
+ if (k > MAX_BARS_BACK) throw new Error(historyError(idx, k));
1235
+ let buffer = buffers.get(src);
1236
+ if (!buffer) buffers.set(src, (buffer = { size: HISTORY_START, previous: 0, barMax: 0 }));
1237
+ if (k > buffer.barMax) buffer.barMax = k;
1238
+ }
1009
1239
  let vals = cache.get(src);
1010
1240
  if (!vals) {
1011
1241
  vals = src.toArray();
@@ -1021,9 +1251,21 @@ export function eachBar(fn: (c: BarContext) => number | boolean | void): Series
1021
1251
  };
1022
1252
  try {
1023
1253
  for (idx = 0; idx < b.length; idx++) {
1254
+ if (pendingError && pendingError.bar === idx) throw new Error(historyError(idx, pendingError.barsBack));
1024
1255
  currentBar = idx;
1025
1256
  const r = fn(ctx);
1026
1257
  out[idx] = typeof r === 'number' ? r : typeof r === 'boolean' ? (r ? 1 : 0) : NaN;
1258
+ // grow the history buffers from the largest reference of this bar
1259
+ for (const buffer of buffers.values()) {
1260
+ const k = buffer.barMax;
1261
+ if (k > buffer.size) {
1262
+ const next = k - buffer.previous > 1 ? k : Math.round(buffer.size * HISTORY_GROWTH);
1263
+ if (next > MAX_BARS_BACK && !pendingError) pendingError = { bar: idx + 1, barsBack: next };
1264
+ buffer.size = next;
1265
+ }
1266
+ buffer.previous = k;
1267
+ buffer.barMax = 0;
1268
+ }
1027
1269
  }
1028
1270
  } finally {
1029
1271
  currentBar = -1;
@@ -1467,6 +1709,9 @@ export function inSession(time: number, session: string, timezone?: string): boo
1467
1709
  /** PineScript `str.*`; `format_time` and the dates of `format` use the exchange time zone by default. */
1468
1710
  export const str = {
1469
1711
  ...strCore,
1712
+ // format.mintick rounds to the chart symbol's tick (syminfo.mintick)
1713
+ tostring: ((value: unknown, format?: string, mintick?: number) =>
1714
+ strCore.tostring(value as never, format, format === 'mintick' ? (mintick ?? syminfo.mintick) : mintick)) as typeof strCore.tostring,
1470
1715
  format_time(time: number, format: string = "yyyy-MM-dd'T'HH:mm:ssZ", timezone?: string): string {
1471
1716
  return strCore.format_time(time, format, timezone ?? exchangeTimezone());
1472
1717
  },
@@ -1514,6 +1759,13 @@ function calendarFor(session?: string, timezone?: string): { bars: SessionBars;
1514
1759
  const { bars } = sessionBars();
1515
1760
  if (!session && !timezone) return { bars, test: false };
1516
1761
  const tz = timezone ?? exchangeTimezone();
1762
+ // a session string that is empty or does not start with a digit is the symbol session: "regular" the regular
1763
+ // hours, "extended" and any other name the loaded hours (PineScript)
1764
+ if (session !== undefined && parsePineSession(session) === null) {
1765
+ const name = session.trim();
1766
+ const symbol = name === 'regular' && chartCtx.regularSession ? chartCtx.regularSession : chartCtx.session!;
1767
+ return { bars: new SessionBars(new TradingCalendar(tz, { session: toSpec(symbol).session }, 'symbol'), bars.chart.period), test: true };
1768
+ }
1517
1769
  const spec = session ? { session } : { session: toSpec(chartCtx.session!).session };
1518
1770
  return { bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol'), bars.chart.period), test: true };
1519
1771
  }
@@ -1779,7 +2031,20 @@ export const polyline = {
1779
2031
  },
1780
2032
  };
1781
2033
 
1782
- /** PineScript `text.format_*` constants. */
2034
+ /**
2035
+ * PineScript `table.*`; `table.all` lists the live tables in creation order. The live tables go to `result.tables`.
2036
+ */
2037
+ export const table = {
2038
+ ...tableCore,
2039
+ get all() {
2040
+ return tableCore.all();
2041
+ },
2042
+ };
2043
+
2044
+ /** PineScript `position.*` constants (table positions). */
2045
+ export { position };
2046
+
2047
+ /** PineScript `text.format_*`, `text.align_*` and `text.wrap_*` constants. */
1783
2048
  export const text = { ...textCore };
1784
2049
 
1785
2050
  /**
@@ -1861,10 +2126,27 @@ export type {
1861
2126
  ShapeConfig,
1862
2127
  ArrowConfig,
1863
2128
  BarColorConfig,
2129
+ CandleConfig,
1864
2130
  MarkerData,
2131
+ MarkerPosition,
2132
+ MarkerShape,
1865
2133
  ArrowData,
1866
2134
  BarColorData,
2135
+ BgColorData,
2136
+ PlotCandleData,
2137
+ PlotBarData,
2138
+ LabelData,
2139
+ LabelStyle,
2140
+ LineDrawingData,
2141
+ BoxData,
2142
+ LinefillData,
2143
+ PolylineData,
2144
+ TableData,
2145
+ TableCellData,
2146
+ TableMergeData,
2147
+ TablePosition,
1867
2148
  MarkerLocation,
1868
2149
  MarkerSize,
2150
+ PineSize,
1869
2151
  ShapeStyle,
1870
2152
  };
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import { zonedFields, zonedToUnix } from '../time/timezone.js';
19
+ import { parsePineSession } from '../time/session.js';
19
20
 
20
21
  /** Session information of a symbol (hours, corrections, holidays). */
21
22
  export interface SessionSpec {
@@ -44,6 +45,8 @@ interface Version {
44
45
  periods: Period[];
45
46
  /** Index 1 (Sunday) to 7 (Saturday) */
46
47
  days: boolean[];
48
+ /** PineScript session strings: the periods of each weekday (index 1 to 7), from the "|" sections */
49
+ byDay?: Period[][];
47
50
  }
48
51
 
49
52
  export const toDateNum = (y: number, m: number, d: number): DateNum => y * 10000 + m * 100 + d;
@@ -82,10 +85,26 @@ function parsePeriods(text: string, spec: string): Period[] {
82
85
  });
83
86
  }
84
87
 
88
+ /** A PineScript session string as periods per weekday (rules in time/session.ts). */
89
+ function pineVersion(text: string, from: DateNum): Version {
90
+ const ranges = parsePineSession(text);
91
+ if (ranges === null) throw new SyntaxError(`Session "${text}" is the symbol session`);
92
+ const byDay: Period[][] = Array.from({ length: 8 }, () => []);
93
+ for (const { start, end, days } of ranges) {
94
+ const period: Period =
95
+ start === 0 && end === 0
96
+ ? { start: 0, end: 1440, startOffset: 0, endOffset: 0 }
97
+ : { start, end, startOffset: end <= start ? 1 : 0, endOffset: 0 };
98
+ for (const d of days) byDay[d]!.push(period);
99
+ }
100
+ return { from, periods: [], days: byDay.map((p) => p.length > 0), byDay };
101
+ }
102
+
85
103
  function parseVersion(text: string, from: DateNum, spec: string, rule: 'symbol' | 'pine'): Version {
104
+ if (rule === 'pine') return pineVersion(text, from);
86
105
  const [periodText = '', dayText] = text.trim().split(':');
87
106
  const periods = parsePeriods(periodText, spec);
88
- const allDays = periodText.trim() === '24x7' || (rule === 'pine' && periods.length === 1);
107
+ const allDays = periodText.trim() === '24x7';
89
108
  const digits = dayText ?? (allDays ? '1234567' : '23456');
90
109
  if (!/^[1-7]+$/.test(digits)) throw new SyntaxError(`Invalid session days in "${spec}"`);
91
110
  const days = new Array<boolean>(8).fill(false);
@@ -137,7 +156,8 @@ export class TradingCalendar {
137
156
  periods = correction ?? [];
138
157
  } else if (!this.holidays.has(date)) {
139
158
  const version = [...this.versions].reverse().find((v) => v.from <= date)!;
140
- if (version.days[weekdayOf(date)]) periods = version.periods;
159
+ if (version.byDay) periods = version.byDay[weekdayOf(date)]!;
160
+ else if (version.days[weekdayOf(date)]) periods = version.periods;
141
161
  }
142
162
  const y = Math.floor(date / 10000);
143
163
  const m = Math.floor(date / 100) % 100;