oakscriptjs 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/dist/cjs/array/index.d.ts +8 -2
  2. package/dist/cjs/array/index.d.ts.map +1 -1
  3. package/dist/cjs/array/index.js +2 -1
  4. package/dist/cjs/array/index.js.map +2 -2
  5. package/dist/cjs/box/index.js +6 -6
  6. package/dist/cjs/box/index.js.map +2 -2
  7. package/dist/cjs/callsite/index.js +1 -1
  8. package/dist/cjs/callsite/index.js.map +2 -2
  9. package/dist/cjs/drawing/registry.d.ts +15 -6
  10. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  11. package/dist/cjs/drawing/registry.js +16 -2
  12. package/dist/cjs/drawing/registry.js.map +2 -2
  13. package/dist/cjs/index.d.ts +3 -2
  14. package/dist/cjs/index.d.ts.map +1 -1
  15. package/dist/cjs/index.js +6 -1
  16. package/dist/cjs/index.js.map +2 -2
  17. package/dist/cjs/input.d.ts +9 -1
  18. package/dist/cjs/input.d.ts.map +1 -1
  19. package/dist/cjs/input.js +16 -0
  20. package/dist/cjs/input.js.map +2 -2
  21. package/dist/cjs/label/index.js +3 -3
  22. package/dist/cjs/label/index.js.map +2 -2
  23. package/dist/cjs/line/index.d.ts +10 -27
  24. package/dist/cjs/line/index.d.ts.map +1 -1
  25. package/dist/cjs/line/index.js +10 -27
  26. package/dist/cjs/line/index.js.map +2 -2
  27. package/dist/cjs/linefill/index.d.ts +3 -0
  28. package/dist/cjs/linefill/index.d.ts.map +1 -1
  29. package/dist/cjs/linefill/index.js +15 -1
  30. package/dist/cjs/linefill/index.js.map +3 -3
  31. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
  32. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
  33. package/dist/cjs/runtime/index.d.ts +1 -1
  34. package/dist/cjs/runtime/index.d.ts.map +1 -1
  35. package/dist/cjs/runtime/index.js +1 -0
  36. package/dist/cjs/runtime/index.js.map +2 -2
  37. package/dist/cjs/runtime/inputs.d.ts +9 -0
  38. package/dist/cjs/runtime/inputs.d.ts.map +1 -1
  39. package/dist/cjs/runtime/inputs.js +8 -0
  40. package/dist/cjs/runtime/inputs.js.map +2 -2
  41. package/dist/cjs/runtime/types.d.ts +1 -1
  42. package/dist/cjs/runtime/types.d.ts.map +1 -1
  43. package/dist/cjs/runtime/types.js.map +1 -1
  44. package/dist/cjs/script/index.d.ts +65 -12
  45. package/dist/cjs/script/index.d.ts.map +1 -1
  46. package/dist/cjs/script/index.js +168 -19
  47. package/dist/cjs/script/index.js.map +3 -3
  48. package/dist/cjs/security/other.d.ts +92 -0
  49. package/dist/cjs/security/other.d.ts.map +1 -0
  50. package/dist/cjs/security/other.js +164 -0
  51. package/dist/cjs/security/other.js.map +7 -0
  52. package/dist/cjs/session/bars.d.ts +10 -1
  53. package/dist/cjs/session/bars.d.ts.map +1 -1
  54. package/dist/cjs/session/bars.js +16 -2
  55. package/dist/cjs/session/bars.js.map +2 -2
  56. package/dist/cjs/session/calendar.d.ts +4 -1
  57. package/dist/cjs/session/calendar.d.ts.map +1 -1
  58. package/dist/cjs/session/calendar.js +11 -7
  59. package/dist/cjs/session/calendar.js.map +2 -2
  60. package/dist/cjs/session/context.d.ts +2 -1
  61. package/dist/cjs/session/context.d.ts.map +1 -1
  62. package/dist/cjs/session/context.js +2 -1
  63. package/dist/cjs/session/context.js.map +2 -2
  64. package/dist/cjs/str/floatingdecimal.d.ts +31 -0
  65. package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
  66. package/dist/cjs/str/floatingdecimal.js +298 -0
  67. package/dist/cjs/str/floatingdecimal.js.map +7 -0
  68. package/dist/cjs/str/index.d.ts +4 -3
  69. package/dist/cjs/str/index.d.ts.map +1 -1
  70. package/dist/cjs/str/index.js +1 -1
  71. package/dist/cjs/str/index.js.map +2 -2
  72. package/dist/cjs/str/numberformat.d.ts +6 -4
  73. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  74. package/dist/cjs/str/numberformat.js +3 -2
  75. package/dist/cjs/str/numberformat.js.map +2 -2
  76. package/dist/cjs/ta/index.d.ts +34 -17
  77. package/dist/cjs/ta/index.d.ts.map +1 -1
  78. package/dist/cjs/ta/index.js +132 -116
  79. package/dist/cjs/ta/index.js.map +2 -2
  80. package/dist/cjs/ta/limits.d.ts +11 -0
  81. package/dist/cjs/ta/limits.d.ts.map +1 -1
  82. package/dist/cjs/ta/limits.js +15 -1
  83. package/dist/cjs/ta/limits.js.map +2 -2
  84. package/dist/cjs/ta/running-sum.js +1 -1
  85. package/dist/cjs/ta/running-sum.js.map +2 -2
  86. package/dist/cjs/ta-series.d.ts +1 -1
  87. package/dist/cjs/ta-series.d.ts.map +1 -1
  88. package/dist/cjs/ta-series.js +1 -1
  89. package/dist/cjs/ta-series.js.map +2 -2
  90. package/dist/cjs/table/index.d.ts +3 -1
  91. package/dist/cjs/table/index.d.ts.map +1 -1
  92. package/dist/cjs/table/index.js +1 -0
  93. package/dist/cjs/table/index.js.map +3 -3
  94. package/dist/cjs/time/session.d.ts +9 -0
  95. package/dist/cjs/time/session.d.ts.map +1 -1
  96. package/dist/cjs/time/session.js +42 -14
  97. package/dist/cjs/time/session.js.map +3 -3
  98. package/dist/cjs/time/timezone.d.ts +6 -1
  99. package/dist/cjs/time/timezone.d.ts.map +1 -1
  100. package/dist/cjs/time/timezone.js +2 -2
  101. package/dist/cjs/time/timezone.js.map +2 -2
  102. package/dist/cjs/types/metadata.d.ts +1 -1
  103. package/dist/cjs/types/metadata.d.ts.map +1 -1
  104. package/dist/cjs/types/metadata.js.map +1 -1
  105. package/dist/esm/array/index.d.ts +8 -2
  106. package/dist/esm/array/index.d.ts.map +1 -1
  107. package/dist/esm/array/index.js +12 -3
  108. package/dist/esm/array/index.js.map +1 -1
  109. package/dist/esm/box/index.js +6 -6
  110. package/dist/esm/box/index.js.map +1 -1
  111. package/dist/esm/callsite/index.js +1 -1
  112. package/dist/esm/callsite/index.js.map +1 -1
  113. package/dist/esm/drawing/registry.d.ts +15 -6
  114. package/dist/esm/drawing/registry.d.ts.map +1 -1
  115. package/dist/esm/drawing/registry.js +30 -7
  116. package/dist/esm/drawing/registry.js.map +1 -1
  117. package/dist/esm/index.d.ts +3 -2
  118. package/dist/esm/index.d.ts.map +1 -1
  119. package/dist/esm/index.js +4 -2
  120. package/dist/esm/index.js.map +1 -1
  121. package/dist/esm/input.d.ts +9 -1
  122. package/dist/esm/input.d.ts.map +1 -1
  123. package/dist/esm/input.js +16 -0
  124. package/dist/esm/input.js.map +1 -1
  125. package/dist/esm/label/index.js +3 -3
  126. package/dist/esm/label/index.js.map +1 -1
  127. package/dist/esm/line/index.d.ts +10 -27
  128. package/dist/esm/line/index.d.ts.map +1 -1
  129. package/dist/esm/line/index.js +20 -64
  130. package/dist/esm/line/index.js.map +1 -1
  131. package/dist/esm/linefill/index.d.ts +3 -0
  132. package/dist/esm/linefill/index.d.ts.map +1 -1
  133. package/dist/esm/linefill/index.js +20 -1
  134. package/dist/esm/linefill/index.js.map +1 -1
  135. package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
  136. package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
  137. package/dist/esm/runtime/index.d.ts +1 -1
  138. package/dist/esm/runtime/index.d.ts.map +1 -1
  139. package/dist/esm/runtime/index.js +1 -1
  140. package/dist/esm/runtime/index.js.map +1 -1
  141. package/dist/esm/runtime/inputs.d.ts +9 -0
  142. package/dist/esm/runtime/inputs.d.ts.map +1 -1
  143. package/dist/esm/runtime/inputs.js +15 -0
  144. package/dist/esm/runtime/inputs.js.map +1 -1
  145. package/dist/esm/runtime/types.d.ts +1 -1
  146. package/dist/esm/runtime/types.d.ts.map +1 -1
  147. package/dist/esm/script/index.d.ts +65 -12
  148. package/dist/esm/script/index.d.ts.map +1 -1
  149. package/dist/esm/script/index.js +217 -29
  150. package/dist/esm/script/index.js.map +1 -1
  151. package/dist/esm/security/other.d.ts +92 -0
  152. package/dist/esm/security/other.d.ts.map +1 -0
  153. package/dist/esm/security/other.js +211 -0
  154. package/dist/esm/security/other.js.map +1 -0
  155. package/dist/esm/session/bars.d.ts +10 -1
  156. package/dist/esm/session/bars.d.ts.map +1 -1
  157. package/dist/esm/session/bars.js +26 -1
  158. package/dist/esm/session/bars.js.map +1 -1
  159. package/dist/esm/session/calendar.d.ts +4 -1
  160. package/dist/esm/session/calendar.d.ts.map +1 -1
  161. package/dist/esm/session/calendar.js +19 -7
  162. package/dist/esm/session/calendar.js.map +1 -1
  163. package/dist/esm/session/context.d.ts +2 -1
  164. package/dist/esm/session/context.d.ts.map +1 -1
  165. package/dist/esm/session/context.js +5 -3
  166. package/dist/esm/session/context.js.map +1 -1
  167. package/dist/esm/str/floatingdecimal.d.ts +31 -0
  168. package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
  169. package/dist/esm/str/floatingdecimal.js +272 -0
  170. package/dist/esm/str/floatingdecimal.js.map +1 -0
  171. package/dist/esm/str/index.d.ts +4 -3
  172. package/dist/esm/str/index.d.ts.map +1 -1
  173. package/dist/esm/str/index.js +6 -5
  174. package/dist/esm/str/index.js.map +1 -1
  175. package/dist/esm/str/numberformat.d.ts +6 -4
  176. package/dist/esm/str/numberformat.d.ts.map +1 -1
  177. package/dist/esm/str/numberformat.js +9 -5
  178. package/dist/esm/str/numberformat.js.map +1 -1
  179. package/dist/esm/ta/index.d.ts +34 -17
  180. package/dist/esm/ta/index.d.ts.map +1 -1
  181. package/dist/esm/ta/index.js +155 -185
  182. package/dist/esm/ta/index.js.map +1 -1
  183. package/dist/esm/ta/limits.d.ts +11 -0
  184. package/dist/esm/ta/limits.d.ts.map +1 -1
  185. package/dist/esm/ta/limits.js +27 -2
  186. package/dist/esm/ta/limits.js.map +1 -1
  187. package/dist/esm/ta/running-sum.js +1 -1
  188. package/dist/esm/ta/running-sum.js.map +1 -1
  189. package/dist/esm/ta-series.d.ts +1 -1
  190. package/dist/esm/ta-series.d.ts.map +1 -1
  191. package/dist/esm/ta-series.js +2 -1
  192. package/dist/esm/ta-series.js.map +1 -1
  193. package/dist/esm/table/index.d.ts +3 -1
  194. package/dist/esm/table/index.d.ts.map +1 -1
  195. package/dist/esm/table/index.js +6 -1
  196. package/dist/esm/table/index.js.map +1 -1
  197. package/dist/esm/time/session.d.ts +9 -0
  198. package/dist/esm/time/session.d.ts.map +1 -1
  199. package/dist/esm/time/session.js +53 -15
  200. package/dist/esm/time/session.js.map +1 -1
  201. package/dist/esm/time/timezone.d.ts +6 -1
  202. package/dist/esm/time/timezone.d.ts.map +1 -1
  203. package/dist/esm/time/timezone.js +8 -2
  204. package/dist/esm/time/timezone.js.map +1 -1
  205. package/dist/esm/types/metadata.d.ts +1 -1
  206. package/dist/esm/types/metadata.d.ts.map +1 -1
  207. package/package.json +1 -1
  208. package/src/array/index.ts +11 -3
  209. package/src/box/index.ts +6 -6
  210. package/src/callsite/index.ts +1 -1
  211. package/src/drawing/registry.ts +30 -7
  212. package/src/index.ts +11 -1
  213. package/src/input.ts +18 -1
  214. package/src/label/index.ts +3 -3
  215. package/src/line/index.ts +20 -68
  216. package/src/linefill/index.ts +20 -1
  217. package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
  218. package/src/runtime/index.ts +1 -0
  219. package/src/runtime/inputs.ts +16 -0
  220. package/src/runtime/types.ts +1 -1
  221. package/src/script/index.ts +292 -30
  222. package/src/security/other.ts +269 -0
  223. package/src/session/bars.ts +24 -1
  224. package/src/session/calendar.ts +23 -7
  225. package/src/session/context.ts +5 -3
  226. package/src/str/floatingdecimal.ts +268 -0
  227. package/src/str/index.ts +6 -5
  228. package/src/str/numberformat.ts +11 -6
  229. package/src/ta/index.ts +157 -183
  230. package/src/ta/limits.ts +28 -1
  231. package/src/ta/running-sum.ts +1 -1
  232. package/src/ta-series.ts +3 -2
  233. package/src/table/index.ts +4 -1
  234. package/src/time/session.ts +58 -16
  235. package/src/time/timezone.ts +9 -2
  236. package/src/types/metadata.ts +1 -1
@@ -41,7 +41,7 @@ function position(name: string, id: PineArray<unknown>, index: number, extra = 0
41
41
  const i = Math.trunc(index);
42
42
  const n = id.length;
43
43
  if (i < -n || i >= n + extra) {
44
- throw new Error(`array.${name}: index ${i} is out of bounds, array size is ${n}`);
44
+ throw new Error(`In 'array.${name}()' function. Index ${i} is out of bounds, array size is ${n}.`);
45
45
  }
46
46
  return i < 0 ? i + n : i;
47
47
  }
@@ -50,7 +50,12 @@ function position(name: string, id: PineArray<unknown>, index: number, extra = 0
50
50
  * Returns the element at the specified index.
51
51
  *
52
52
  * PineScript rules: a negative index counts from the end (`get(a, -1)` is the last element); an index outside
53
- * `-size .. size - 1` is a runtime error (throws); an `na` index gives `na`.
53
+ * `-size .. size - 1` is a runtime error (throws "In 'array.get()' function. Index 5 is out of bounds, array size
54
+ * is 3."); an `na` index gives `na`.
55
+ *
56
+ * Negative indexes are a PineScript v6 rule, which oakscriptjs follows: a version 5 script stops on them with the
57
+ * same out of bounds error (`array.get(a, -1)`, #176). A port of a v5 script that the original stops must check the
58
+ * index itself.
54
59
  */
55
60
  export function get<T>(id: PineArray<T>, index: simple_int): T {
56
61
  if (Number.isNaN(index)) return naOf(id);
@@ -973,7 +978,8 @@ export function binary_search_rightmost(id: PineArray<float>, val: float): int {
973
978
  * - Formula: Cov(X,Y) = E[(X - μX)(Y - μY)]
974
979
  *
975
980
  * PineScript rules: a pair where either value is `na` is skipped and the count is the number of pairs kept; both
976
- * means first, then the sum of the products from the first to the last pair. One pair gives 0 biased, NaN unbiased.
981
+ * means first, then the sum of the products from the first to the last pair, set to 0 whenever it is within 1e-10 of
982
+ * zero (#177: five equal closes give 0, not -1.7e-18). One pair gives 0 biased, NaN unbiased.
977
983
  */
978
984
  export function covariance(id1: PineArray<float>, id2: PineArray<float>, biased: bool = true): float {
979
985
  if (id1.length !== id2.length) {
@@ -996,9 +1002,11 @@ export function covariance(id1: PineArray<float>, id2: PineArray<float>, biased:
996
1002
  }
997
1003
  mean1 /= n;
998
1004
  mean2 /= n;
1005
+ // the co-moment is a running sum set to 0 when it is within 1e-10 of zero, as array.sum (#177)
999
1006
  let sum = 0;
1000
1007
  for (let i = 0; i < n; i++) {
1001
1008
  sum += (xs[i]! - mean1) * (ys[i]! - mean2);
1009
+ if (Math.abs(sum) <= 1e-10) sum = 0;
1002
1010
  }
1003
1011
  return sum / (biased ? n : n - 1);
1004
1012
  }
package/src/box/index.ts CHANGED
@@ -91,8 +91,8 @@ export function new_box(...args: unknown[]): Box {
91
91
  (string | number)?, color?, ('left' | 'center' | 'right')?, ('top' | 'center' | 'bottom')?,
92
92
  ('none' | 'auto')?, ('default' | 'monospace')?, boolean?, number?,
93
93
  ];
94
- checkBarIndex('box.new', 'left', left, xloc);
95
- checkBarIndex('box.new', 'right', right, xloc);
94
+ left = checkBarIndex('box.new', 'left', left, xloc);
95
+ right = checkBarIndex('box.new', 'right', right, xloc);
96
96
  return register('box', {
97
97
  left,
98
98
  top,
@@ -248,7 +248,7 @@ export function copy(id: Box): Box {
248
248
  */
249
249
  export function set_left(id: Box, left: number): Box {
250
250
  if (isDeleted(id)) return id;
251
- checkBarIndex('box.set_(left*/xloc)', 'left', left, id.xloc);
251
+ left = checkBarIndex('box.set_(left*/xloc)', 'left', left, id.xloc);
252
252
  id.left = left;
253
253
  return id;
254
254
  }
@@ -268,7 +268,7 @@ export function set_left(id: Box, left: number): Box {
268
268
  */
269
269
  export function set_right(id: Box, right: number): Box {
270
270
  if (isDeleted(id)) return id;
271
- checkBarIndex('box.set_(right*/xlock)', 'right', right, id.xloc);
271
+ right = checkBarIndex('box.set_(right*/xlock)', 'right', right, id.xloc);
272
272
  id.right = right;
273
273
  return id;
274
274
  }
@@ -327,7 +327,7 @@ export function set_bottom(id: Box, bottom: number): Box {
327
327
  */
328
328
  export function set_lefttop(id: Box, left: number, top: number): Box {
329
329
  if (isDeleted(id)) return id;
330
- checkBarIndex('box.set_(left*/xloc)', 'left', left, id.xloc);
330
+ left = checkBarIndex('box.set_(left*/xloc)', 'left', left, id.xloc);
331
331
  id.left = left;
332
332
  id.top = top;
333
333
  return id;
@@ -349,7 +349,7 @@ export function set_lefttop(id: Box, left: number, top: number): Box {
349
349
  */
350
350
  export function set_rightbottom(id: Box, right: number, bottom: number): Box {
351
351
  if (isDeleted(id)) return id;
352
- checkBarIndex('box.set_(right*/xlock)', 'right', right, id.xloc);
352
+ right = checkBarIndex('box.set_(right*/xlock)', 'right', right, id.xloc);
353
353
  id.right = right;
354
354
  id.bottom = bottom;
355
355
  return id;
@@ -364,8 +364,8 @@ function loopSumSite(
364
364
  let pending = false;
365
365
  return (barIndex, value, length) => {
366
366
  const len = Math.floor(length);
367
- if (!(len >= 1)) throw new Error(`callsite.${name}: length must be at least 1 (got ${length})`);
368
367
  checkSumLength(name === 'sum' ? 'math.sum' : `ta.${name}`, len, barIndex);
368
+ if (!(len >= 1)) throw new Error(`callsite.${name}: length must be at least 1 (got ${length})`);
369
369
  if (barIndex !== lastBar) {
370
370
  if (pending) {
371
371
  running.commit();
@@ -36,8 +36,9 @@
36
36
  * - polylines on the last bar: the `P` carried from the bar before is counted twice (the first creation adds it once
37
37
  * more, unless it sets `P` to 0), later creations do not set `P` to 0, and the polylines of the bars before that a
38
38
  * cut deletes are added to `P`. Once none is
39
- * left, a cut deletes polylines of the last bar and `P` becomes the number of polylines of the bars before deleted
40
- * on the last bar (by the count or by the script). Equal to PineScript for the polylines alive at the end on 271 of
39
+ * left, `P` is at least the number of polylines of the bars before deleted on the last bar (by the count or by
40
+ * the script) and a cut deletes polylines of the last bar: a script that deletes its 100 polylines and creates 200
41
+ * on the last bar keeps them all (#176). Equal to PineScript for the polylines alive at the end on 271 of
41
42
  * 281 probe runs; the count inside the bar can differ when the script deletes polylines of the last bar
42
43
  *
43
44
  * - a bar index (`xloc.bar_index`) more than 10,000 bars before the current bar stops the script (runtime error
@@ -171,7 +172,10 @@ export function varDrawing<T extends object>(initial?: T): DrawingVariable<T> {
171
172
 
172
173
  /** Largest distance, in bars, of a bar index before the current bar (PineScript runtime error RE10026 above it). */
173
174
  export const MAX_DRAWING_BARS_BACK = 10000;
175
+ /** Largest distance, in bars, of a bar index after the last bar of the data (PineScript runtime error RE10020 above it). */
176
+ export const MAX_DRAWING_BARS_FORWARD = 500;
174
177
  let currentBar: number | null = null;
178
+ let lastBarIndex: number | null = null;
175
179
 
176
180
  /** Kinds whose allowance on the last bar is the `P` they had when the bar started. */
177
181
  type AllowanceKind = 'line' | 'label' | 'box';
@@ -198,11 +202,14 @@ let lastBar: LastBar | null = null;
198
202
  * bars back throws as in PineScript; `null` turns the check off. `eachBar` of the script API calls it.
199
203
  *
200
204
  * The bar also dates the objects held by a `var` variable (protected from the next bar on). `last` says that it is
201
- * the last bar of the data, which has rules of its own for the maximum count.
205
+ * the last bar of the data, which has rules of its own for the maximum count. `lastIndex` is the bar index of the
206
+ * last bar of the data (PineScript `last_bar_index`): with it, a bar index more than 500 bars after it throws, also
207
+ * on the bars before the last one (RE10020).
202
208
  */
203
- export function setDrawingBar(barIndex: number | null, last: boolean = false): void {
209
+ export function setDrawingBar(barIndex: number | null, last: boolean = false, lastIndex?: number): void {
204
210
  if (lastBar && !(last && barIndex === lastBar.bar)) lastBar = null;
205
211
  currentBar = barIndex;
212
+ lastBarIndex = barIndex === null ? null : (lastIndex ?? (last ? barIndex : null));
206
213
  if (barIndex !== null && last && !lastBar) {
207
214
  const start = (kind: AllowanceKind): number => (pending[kind] >= PENDING_RESET[kind] ? 0 : pending[kind]);
208
215
  lastBar = {
@@ -217,17 +224,30 @@ export function setDrawingBar(barIndex: number | null, last: boolean = false): v
217
224
  }
218
225
 
219
226
  /**
220
- * Throws the PineScript runtime error RE10026 when `x` is a bar index more than 10,000 bars before the current bar.
227
+ * Throws the PineScript runtime error RE10026 when `x` is a bar index more than 10,000 bars before the current bar,
228
+ * and RE10020 when it is more than 500 bars after the last bar of the data (when {@link setDrawingBar} knows it).
221
229
  * `method` and `variable` are the names of the PineScript message ("line.new", "x1").
230
+ *
231
+ * Returns `x`, truncated when it is a bar index: PineScript's x is an int (a box from 5537.94 starts on bar 5537,
232
+ * #176).
222
233
  */
223
- export function checkBarIndex(method: string, variable: string, x: number, xloc: string | undefined): void {
224
- if (currentBar === null || (xloc ?? 'bar_index') !== 'bar_index') return;
234
+ export function checkBarIndex(method: string, variable: string, x: number, xloc: string | undefined): number {
235
+ if ((xloc ?? 'bar_index') !== 'bar_index') return x;
236
+ x = Math.trunc(x);
237
+ if (currentBar === null) return x;
238
+ if (lastBarIndex !== null && x - lastBarIndex > MAX_DRAWING_BARS_FORWARD) {
239
+ throw new Error(
240
+ `Error on bar ${currentBar}: Objects positioned using xloc.bar_index cannot be drawn further than ` +
241
+ `${MAX_DRAWING_BARS_FORWARD} bars into the future.`
242
+ );
243
+ }
225
244
  if (currentBar - x > MAX_DRAWING_BARS_BACK) {
226
245
  throw new Error(
227
246
  `Error on bar ${currentBar}: Bar index value of the \`${variable}\` argument (${x}) in \`${method}()\` is too far ` +
228
247
  'from the current bar index. Try using `time` instead.'
229
248
  );
230
249
  }
250
+ return x;
231
251
  }
232
252
 
233
253
  /** Numeric fields that become NaN when an object is deleted, so that its getters return `na`. */
@@ -314,6 +334,8 @@ function registerLast<K extends DrawingKind>(bar: LastBar, kind: K, obj: Drawing
314
334
  }
315
335
  for (const old of cut) markDeleted(kind, old, true);
316
336
  } else {
337
+ // the polylines of the bars before deleted on this bar count before the cut (#176)
338
+ if (kind === 'polyline' && pending.polyline < bar.oldPolylinesGone) pending.polyline = bar.oldPolylinesGone;
317
339
  const cut = cutOldest(kind, list, obj);
318
340
  if (kind === 'polyline' && cut.length > 0) pending.polyline = bar.oldPolylinesGone;
319
341
  for (const old of cut) markDeleted(kind, old, false);
@@ -351,6 +373,7 @@ export function resetDrawings(newLimits: DrawingLimits = {}): void {
351
373
  heldAt = new WeakMap();
352
374
  pending = { line: 0, label: 0, box: 0, polyline: 0 };
353
375
  currentBar = null;
376
+ lastBarIndex = null;
354
377
  lastBar = null;
355
378
  limits = {
356
379
  line: newLimits.line ?? DEFAULT_MAX_COUNT,
package/src/index.ts CHANGED
@@ -75,6 +75,15 @@ export { SessionBars, sessionFlags, type SessionFlags } from './session/bars.js'
75
75
 
76
76
  // Same-symbol request.security on a higher timeframe (completion and lookahead rules) and ticker.heikinashi
77
77
  export { resample, periodsOf, afterCompletion, mapToChart, heikinAshi, type Resampled } from './security/resample.js';
78
+ // request.security of another symbol: which bar of the other symbol each chart bar shows (#179)
79
+ export {
80
+ securityOther,
81
+ securityOtherValues,
82
+ securityLowerTf,
83
+ type OtherSymbolChart,
84
+ type OtherSymbolOptions,
85
+ type SymbolCalendar,
86
+ } from './security/other.js';
78
87
 
79
88
  // Export Series class (self-contained, no context)
80
89
  export { Series, BarData } from './runtime/series.js';
@@ -170,6 +179,7 @@ export {
170
179
  input_source,
171
180
  input_timeframe,
172
181
  input_session,
182
+ input_symbol,
173
183
  input_time,
174
184
  } from './runtime/inputs.js';
175
185
  export { isInputActive } from './runtime/input-active.js';
@@ -205,7 +215,7 @@ export type { LightweightSeriesDefinitions } from './runtime/adapters/Lightweigh
205
215
  export { SimpleInputAdapter } from './runtime/adapters/SimpleInputAdapter.js';
206
216
 
207
217
  // Version
208
- export const VERSION = '0.11.0';
218
+ export const VERSION = '0.11.1';
209
219
 
210
220
  /**
211
221
  * Library information
package/src/input.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  export interface InputValue<T> {
11
11
  /** Input type identifier */
12
- type: 'int' | 'float' | 'source' | 'bool' | 'string' | 'timeframe' | 'session' | 'time';
12
+ type: 'int' | 'float' | 'source' | 'bool' | 'string' | 'timeframe' | 'session' | 'time' | 'symbol';
13
13
  /** Default value */
14
14
  defaultValue: T;
15
15
  /** Current value (initially equals defaultValue) */
@@ -254,6 +254,23 @@ export const input = {
254
254
  };
255
255
  },
256
256
 
257
+ /**
258
+ * Create a symbol input parameter (PineScript `input.symbol`): a ticker id ("NASDAQ:MSFT") that an application can
259
+ * edit with a symbol picker (#180)
260
+ * @param defaultValue - Default ticker id
261
+ * @param options - Additional options (title, options for dropdown)
262
+ * @returns Input value object
263
+ */
264
+ symbol(defaultValue: string, options: StringInputOptions = {}): InputValue<string> {
265
+ return {
266
+ type: 'symbol' as const,
267
+ defaultValue,
268
+ value: defaultValue,
269
+ title: options.title,
270
+ options: options.options,
271
+ };
272
+ },
273
+
257
274
  /**
258
275
  * Create a date/time input parameter (PineScript `input.time`)
259
276
  * @param defaultValue - Default UNIX time in milliseconds, e.g. `time.timestamp('2024-01-01')`
@@ -80,7 +80,7 @@ export function new_label(...args: unknown[]): Label {
80
80
  string?, LabelXloc?, LabelYloc?, color?, LabelStyle?, color?, (string | number)?,
81
81
  ('left' | 'center' | 'right')?, string?, ('default' | 'monospace')?, boolean?, number?,
82
82
  ];
83
- checkBarIndex('label.new', 'x', x, xloc);
83
+ x = checkBarIndex('label.new', 'x', x, xloc);
84
84
  return register('label', {
85
85
  x,
86
86
  y,
@@ -189,7 +189,7 @@ export function copy(id: Label): Label {
189
189
  */
190
190
  export function set_x(id: Label, x: number): Label {
191
191
  if (isDeleted(id)) return id;
192
- checkBarIndex('label.set_x', 'x', x, id.xloc);
192
+ x = checkBarIndex('label.set_x', 'x', x, id.xloc);
193
193
  id.x = x;
194
194
  return id;
195
195
  }
@@ -229,7 +229,7 @@ export function set_y(id: Label, y: number): Label {
229
229
  */
230
230
  export function set_xy(id: Label, x: number, y: number): Label {
231
231
  if (isDeleted(id)) return id;
232
- checkBarIndex('label.set_x', 'x', x, id.xloc);
232
+ x = checkBarIndex('label.set_x', 'x', x, id.xloc);
233
233
  id.x = x;
234
234
  id.y = y;
235
235
  return id;
package/src/line/index.ts CHANGED
@@ -70,8 +70,8 @@ export function new_line(...args: unknown[]): Line {
70
70
  const [xloc, extend, lineColor, style, width, force_overlay] = rest as [
71
71
  LineXloc?, LineExtend?, color?, LineStyle?, number?, boolean?,
72
72
  ];
73
- checkBarIndex('line.new', 'x1', x1, xloc);
74
- checkBarIndex('line.new', 'x2', x2, xloc);
73
+ x1 = checkBarIndex('line.new', 'x1', x1, xloc);
74
+ x2 = checkBarIndex('line.new', 'x2', x2, xloc);
75
75
  return register('line', {
76
76
  x1,
77
77
  y1,
@@ -87,83 +87,35 @@ export function new_line(...args: unknown[]): Line {
87
87
  }
88
88
 
89
89
  /**
90
- * Returns the price level of a line at a given bar index.
90
+ * Returns the price level of a line at a given bar index (PineScript `line.get_price`).
91
91
  *
92
92
  * @param id - Line object
93
93
  * @param x - Bar index to calculate price at
94
- * @returns Price level at the given bar index, or NaN if outside bounds and not extended
94
+ * @returns Price level at the given bar index; NaN for a vertical line (x1 == x2) or a deleted line
95
95
  *
96
96
  * @remarks
97
- * - **CRITICAL COMPUTATIONAL FUNCTION** - Enables trend line breakout detection
98
- * - Uses linear interpolation: y = y1 + (y2 - y1) * (x - x1) / (x2 - x1)
99
- * - Respects extend mode:
100
- * - 'none': Returns NaN if x is outside [x1, x2]
101
- * - 'left': Extends infinitely to the left (x < x1)
102
- * - 'right': Extends infinitely to the right (x > x2)
103
- * - 'both': Extends infinitely in both directions
104
- * - Only works with xloc='bar_index' lines (not xloc='bar_time')
105
- * - In PineScript, this function treats all lines as if extend='both' regardless of actual setting
106
- * For compatibility, we follow the actual extend setting
97
+ * As in PineScript (#174):
98
+ * - the line is read as extended on both sides whatever its `extend`: a point before x1 or after x2 has a price
99
+ * - the price is `y1 + (y2 - y1) * (x - x1) / (x2 - x1)`, in that order (bit for bit)
100
+ * - a line placed with `xloc.bar_time` throws "'line.get_price' must be used with lines created using
101
+ * 'xloc=xloc.bar_index'." (runtime error RE10119)
107
102
  *
108
103
  * @example
109
104
  * ```typescript
110
105
  * const trendLine = line.new(0, 100, 50, 150, 'bar_index', 'right');
111
106
  *
112
- * // Within segment
113
- * line.get_price(trendLine, 25); // Returns 125
114
- *
115
- * // Extended right
116
- * line.get_price(trendLine, 100); // Returns 200
117
- *
118
- * // Not extended left (with extend='right')
119
- * line.get_price(trendLine, -10); // Returns NaN
120
- *
121
- * // Use for breakout detection
122
- * const currentPrice = close[close.length - 1];
123
- * const linePrice = line.get_price(trendLine, close.length - 1);
124
- * if (currentPrice > linePrice) {
125
- * console.log('Breakout above trend line!');
126
- * }
107
+ * line.get_price(trendLine, 25); // 125
108
+ * line.get_price(trendLine, 100); // 200
109
+ * line.get_price(trendLine, -10); // 90: extended to the left too
127
110
  * ```
128
111
  */
129
112
  export function get_price(id: Line, x: number): number {
130
- const { x1, y1, x2, y2, extend, xloc } = id;
131
-
132
- // Only works with bar_index xloc
113
+ const { x1, y1, x2, y2, xloc } = id;
133
114
  if (xloc === 'bar_time') {
134
- throw new Error('line.get_price() only works with xloc.bar_index lines, not xloc.bar_time');
135
- }
136
-
137
- // Avoid division by zero
138
- if (x2 === x1) {
139
- // Vertical line - return NaN or could return y1/y2
140
- return NaN;
115
+ throw new Error("'line.get_price' must be used with lines created using 'xloc=xloc.bar_index'.");
141
116
  }
142
-
143
- // Linear interpolation formula: y = y1 + (y2 - y1) * (x - x1) / (x2 - x1)
144
- const slope = (y2 - y1) / (x2 - x1);
145
- const price = y1 + slope * (x - x1);
146
-
147
- // Check bounds based on extend mode
148
- if (extend === 'none') {
149
- // Only valid within the segment
150
- if (x < Math.min(x1, x2) || x > Math.max(x1, x2)) {
151
- return NaN;
152
- }
153
- } else if (extend === 'left') {
154
- // Valid to the left and within, but not to the right
155
- if (x > Math.max(x1, x2)) {
156
- return NaN;
157
- }
158
- } else if (extend === 'right') {
159
- // Valid to the right and within, but not to the left
160
- if (x < Math.min(x1, x2)) {
161
- return NaN;
162
- }
163
- }
164
- // extend === 'both': always valid
165
-
166
- return price;
117
+ if (x2 === x1) return NaN;
118
+ return y1 + ((y2 - y1) * (x - x1)) / (x2 - x1);
167
119
  }
168
120
 
169
121
  /**
@@ -267,7 +219,7 @@ export function copy(id: Line): Line {
267
219
  */
268
220
  export function set_x1(id: Line, x: number): Line {
269
221
  if (isDeleted(id)) return id;
270
- checkBarIndex('line.set_x***', 'x1', x, id.xloc);
222
+ x = checkBarIndex('line.set_x***', 'x1', x, id.xloc);
271
223
  id.x1 = x;
272
224
  return id;
273
225
  }
@@ -287,7 +239,7 @@ export function set_x1(id: Line, x: number): Line {
287
239
  */
288
240
  export function set_x2(id: Line, x: number): Line {
289
241
  if (isDeleted(id)) return id;
290
- checkBarIndex('line.set_x***', 'x2', x, id.xloc);
242
+ x = checkBarIndex('line.set_x***', 'x2', x, id.xloc);
291
243
  id.x2 = x;
292
244
  return id;
293
245
  }
@@ -346,7 +298,7 @@ export function set_y2(id: Line, y: number): Line {
346
298
  */
347
299
  export function set_xy1(id: Line, x: number, y: number): Line {
348
300
  if (isDeleted(id)) return id;
349
- checkBarIndex('line.set_x***', 'x1', x, id.xloc);
301
+ x = checkBarIndex('line.set_x***', 'x1', x, id.xloc);
350
302
  id.x1 = x;
351
303
  id.y1 = y;
352
304
  return id;
@@ -368,7 +320,7 @@ export function set_xy1(id: Line, x: number, y: number): Line {
368
320
  */
369
321
  export function set_xy2(id: Line, x: number, y: number): Line {
370
322
  if (isDeleted(id)) return id;
371
- checkBarIndex('line.set_x***', 'x2', x, id.xloc);
323
+ x = checkBarIndex('line.set_x***', 'x2', x, id.xloc);
372
324
  id.x2 = x;
373
325
  id.y2 = y;
374
326
  return id;
@@ -24,6 +24,9 @@ import type { Linefill, Line, color } from '../types/index.js';
24
24
  * @remarks
25
25
  * - Automatically follows line movements (if lines are updated, fill updates too)
26
26
  * - Automatically deleted if either line is deleted
27
+ * - One linefill per pair of lines, as in PineScript (#174): when both lines already belong to a live linefill
28
+ * (`linefill.new(b, a)` after `linefill.new(a, b)`, or `linefill.new(a, a)` after either), that linefill is
29
+ * returned with the new colour instead of a new one; with several, the one created or returned last
27
30
  * - If both lines extend in same direction, fill extends too
28
31
  * - Purely visual in PineScript, but getters allow retrieving line references
29
32
  *
@@ -42,13 +45,29 @@ import type { Linefill, Line, color } from '../types/index.js';
42
45
  export function new_linefill(line1: Line, line2: Line, fillColor?: color): Linefill {
43
46
  // PineScript: an na line gives an na linefill
44
47
  if (line1 === null || line1 === undefined || line2 === null || line2 === undefined) return null as unknown as Linefill;
45
- return register('linefill', {
48
+ let found: Linefill | undefined;
49
+ for (const fill of allDrawings('linefill')) {
50
+ const has = (l: Line): boolean => fill.line1 === l || fill.line2 === l;
51
+ if (has(line1) && has(line2) && (!found || (touched.get(fill) ?? 0) > (touched.get(found) ?? 0))) found = fill;
52
+ }
53
+ if (found) {
54
+ found.color = fillColor;
55
+ touched.set(found, ++touchCount);
56
+ return found;
57
+ }
58
+ const fill = register('linefill', {
46
59
  line1,
47
60
  line2,
48
61
  color: fillColor
49
62
  });
63
+ touched.set(fill, ++touchCount);
64
+ return fill;
50
65
  }
51
66
 
67
+ /** Order in which the linefills were created or returned by `linefill.new` (the last one wins a lookup). */
68
+ const touched = new WeakMap<Linefill, number>();
69
+ let touchCount = 0;
70
+
52
71
  /**
53
72
  * Returns the first line reference.
54
73
  *
@@ -70,7 +70,7 @@ export class SimpleInputAdapter implements InputAdapter {
70
70
 
71
71
  // Validate string, timeframe and session inputs with options
72
72
  if (
73
- (config.type === 'string' || config.type === 'timeframe' || config.type === 'session') &&
73
+ (config.type === 'string' || config.type === 'timeframe' || config.type === 'session' || config.type === 'symbol') &&
74
74
  config.options &&
75
75
  typeof value === 'string'
76
76
  ) {
@@ -27,6 +27,7 @@ export {
27
27
  input_source,
28
28
  input_timeframe,
29
29
  input_session,
30
+ input_symbol,
30
31
  input_time,
31
32
  enableAutoRecalculate,
32
33
  disableAutoRecalculate,
@@ -277,6 +277,22 @@ export function input_session(defval: string, title?: string, options?: string[]
277
277
  return typeof value === 'string' ? value : defval;
278
278
  }
279
279
 
280
+ /**
281
+ * Register a symbol input (PineScript `input.symbol`): a ticker id such as "NASDAQ:MSFT" (#180)
282
+ * First call registers with defval, subsequent calls return current value
283
+ * @param defval - Default ticker id
284
+ * @param title - Display title (optional)
285
+ * @param options - Array of symbols for a dropdown (optional)
286
+ * @returns Current ticker id
287
+ */
288
+ export function input_symbol(defval: string, title?: string, options?: string[]): string {
289
+ if (!getContext()) {
290
+ return defval;
291
+ }
292
+ const value = registerOnce({ id: generateInputId(title, defval, 'symbol'), type: 'symbol', defval, title, options });
293
+ return typeof value === 'string' ? value : defval;
294
+ }
295
+
280
296
  /**
281
297
  * Register a date/time input (PineScript `input.time`)
282
298
  * First call registers with defval, subsequent calls return current value
@@ -72,7 +72,7 @@ export interface InputConfig {
72
72
  * Type of input. `timeframe` and `session` values are strings ("60", "1D", "0930-1600:23456";
73
73
  * an empty timeframe means the chart timeframe), `time` values are UNIX times in milliseconds.
74
74
  */
75
- type: 'int' | 'float' | 'bool' | 'string' | 'source' | 'color' | 'timeframe' | 'session' | 'time';
75
+ type: 'int' | 'float' | 'bool' | 'string' | 'source' | 'color' | 'timeframe' | 'session' | 'time' | 'symbol';
76
76
  /** Default value */
77
77
  defval: unknown;
78
78
  /** Display title */