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
@@ -6,8 +6,8 @@
6
6
  * - each bar first removes, with a Kahan step, the compensated value that was added for the value that leaves the
7
7
  * window, then adds the new value with a Kahan step and keeps its compensated value;
8
8
  * - when the compensation of the previous step would be lost when applied to the new value, the sum is computed
9
- * again from the window values (newest first) and the compensation is reset. The test rounds on the grid of the
10
- * new value toward +infinity: for a negative power of two, the finer grid of the binade below (#136).
9
+ * again from the window values (newest first) and the compensation is reset. The test moves the new value toward
10
+ * +infinity by the size of the compensation, in floating point (#136, #142).
11
11
  *
12
12
  * So the result can differ from the exact window sum in the last bits (e.g. a small residue on a window of zeros),
13
13
  * as in PineScript.
@@ -15,115 +15,228 @@
15
15
  * @internal
16
16
  */
17
17
 
18
- const view = new DataView(new ArrayBuffer(8));
19
-
20
- /** Binary exponent of a finite non-zero number (from its bits). */
21
- function exponentOf(x: number): number {
22
- view.setFloat64(0, x);
23
- const e = (view.getUint16(0) >> 4) & 0x7ff;
24
- return e === 0 ? -1074 : e - 1023;
25
- }
26
-
27
- /**
28
- * Distance from a finite non-zero `x` to the next number toward +infinity: the spacing of the binade of `x`, or half
29
- * of it when `x` is a negative power of two (the next number up is in the binade below).
30
- */
31
- function spacingUp(x: number): number {
32
- const e = exponentOf(x);
33
- const powerOfTwo = (view.getUint16(0) & 0xf) === 0 && view.getUint16(2) === 0 && view.getUint32(4) === 0;
34
- return 2 ** (e - (x < 0 && powerOfTwo && e > -1022 ? 53 : 52));
35
- }
36
-
37
18
  /**
38
- * Whether the compensation `c` is (partly) lost when it is applied to `x`: `x - c` rounded on the grid of `x`
39
- * (spacing {@link spacingUp}, ties to an even mantissa) applies less than `c`.
19
+ * Whether the compensation `c` is (partly) lost when it is applied to `x`: `x + |c|` in floating point moves `x` by
20
+ * less than `|c|`. Moving toward +infinity uses the grid above `x` (the coarser binade just below a power of two, the
21
+ * finer binade for a negative power of two), as PineScript (measured bit for bit, #142).
40
22
  */
41
23
  function compensationLost(x: number, c: number): boolean {
42
24
  if (c === 0 || x === 0) return false;
43
- const ulp = spacingUp(x);
44
- const q = c / ulp;
45
- const mantissa = x / ulp;
46
- const f = Math.floor(q);
47
- const d = q - f;
48
- // parity of mantissa - f from each part: the mantissa can be 2^53 (half spacing), where mantissa - f is not exact
49
- const applied = d > 0.5 ? f + 1 : d < 0.5 ? f : Math.abs(mantissa % 2) === Math.abs(f % 2) ? f : f + 1;
50
- return Math.abs(applied) < Math.abs(q);
25
+ const size = Math.abs(c);
26
+ return Math.abs(x + size - x) < size;
51
27
  }
52
28
 
53
29
  /** A value PineScript treats as na in window functions (also +/-Infinity). */
54
- function isNa(v: number | null | undefined): boolean {
30
+ export function isNa(v: number | null | undefined): boolean {
55
31
  return v === undefined || v === null || !Number.isFinite(v);
56
32
  }
57
33
 
58
34
  /**
59
- * The sum of the last `length` non-na values on each bar, as PineScript computes it.
35
+ * Running sum state, one value at a time. {@link step} computes the sum with a new value from the kept state without
36
+ * changing it; {@link commit} keeps the last step. So several candidate values can be tried on one bar and only the
37
+ * last one kept (a ta.* call in a loop).
60
38
  *
61
- * @param source - Values (na: NaN, null, undefined or +/-Infinity, skipped)
62
- * @param length - Number of non-na values in the window
63
- * @returns The running sum per bar: NaN until `length` non-na values exist; a bar with an na value keeps the
64
- * previous result
39
+ * The length can change from one step to the next (a series length). PineScript then first moves the kept sum to the
40
+ * new length with Kahan steps of the raw values (not the stored compensated values): a smaller length removes the
41
+ * values that leave the window, oldest first; a larger length adds back the values from the one before the old window
42
+ * down to the one `length` values back, newest first (one more than the window needs). Then the usual step runs with
43
+ * the new length: it removes the stored value of the one `length` values back and adds the new value.
65
44
  */
66
- export function runningSum(source: ArrayLike<number | null | undefined>, length: number): number[] {
67
- const len = Math.floor(length);
68
- const out: number[] = new Array<number>(source.length).fill(NaN);
69
- if (!(len >= 1)) return out;
70
- const values: number[] = [];
71
- const stored: number[] = [];
72
- let s = 0;
73
- let c = 0;
74
- let previous = NaN;
75
- const kahanAdd = (v: number): number => {
76
- const y = v - c;
77
- const t = s + y;
78
- c = (t - s) - y;
79
- s = t;
80
- return y;
81
- };
82
- for (let i = 0; i < source.length; i++) {
83
- const v = source[i];
84
- if (isNa(v)) {
85
- out[i] = previous;
86
- continue;
45
+ export class RunningSum {
46
+ private readonly values: number[] = [];
47
+ private readonly stored: number[] = [];
48
+ private keptLength = NaN;
49
+ private s = 0;
50
+ private c = 0;
51
+ private stepLength = NaN;
52
+ private stepX = NaN;
53
+ private stepY = NaN;
54
+ private stepS = 0;
55
+ private stepC = 0;
56
+
57
+ /** Kahan steps that move the kept sum from the kept length to `len`; the result goes to stepS / stepC. */
58
+ private resize(len: number): void {
59
+ const values = this.values;
60
+ const n = values.length;
61
+ const kept = this.keptLength;
62
+ let s = this.s;
63
+ let c = this.c;
64
+ const add = (v: number) => {
65
+ const y = v - c;
66
+ const t = s + y;
67
+ c = (t - s) - y;
68
+ s = t;
69
+ };
70
+ if (len < kept) {
71
+ for (let k = Math.max(0, n - kept); k <= n - len - 1; k++) add(-values[k]!);
72
+ } else if (len > kept) {
73
+ for (let k = n - kept - 1; k >= Math.max(0, n - len); k--) add(values[k]!);
87
74
  }
88
- const x = v as number;
75
+ this.stepS = s;
76
+ this.stepC = c;
77
+ }
78
+
79
+ /**
80
+ * The running sum of the last `length` values with the (not na) value `x` added, from the kept state; the state is
81
+ * unchanged until {@link commit}.
82
+ *
83
+ * @returns The sum, or NaN while fewer than `length` values exist (with `x`)
84
+ */
85
+ step(x: number, length: number): number {
86
+ const len = length;
87
+ const values = this.values;
88
+ this.resize(len);
89
+ let s = this.stepS;
90
+ let c = this.stepC;
89
91
  const c0 = c;
90
92
  const n = values.length;
91
- if (n >= len) kahanAdd(-stored[n - len]!);
92
- values.push(x);
93
+ if (n >= len) {
94
+ const y = -this.stored[n - len]! - c;
95
+ const t = s + y;
96
+ c = (t - s) - y;
97
+ s = t;
98
+ }
93
99
  if (compensationLost(x, c0)) {
94
- let fresh = 0;
95
- for (let k = values.length - 1; k >= Math.max(0, values.length - len); k--) fresh += values[k]!;
100
+ let fresh = x;
101
+ for (let k = n - 1; k >= Math.max(0, n + 1 - len); k--) fresh += values[k]!;
96
102
  s = fresh;
97
103
  c = 0;
98
- stored.push(x);
104
+ this.stepY = x;
99
105
  } else {
100
- stored.push(kahanAdd(x));
106
+ const y = x - c;
107
+ const t = s + y;
108
+ c = (t - s) - y;
109
+ s = t;
110
+ this.stepY = y;
101
111
  }
102
- previous = values.length >= len ? s : NaN;
103
- out[i] = previous;
112
+ this.stepLength = len;
113
+ this.stepX = x;
114
+ this.stepS = s;
115
+ this.stepC = c;
116
+ return n + 1 >= len ? s : NaN;
117
+ }
118
+
119
+ /**
120
+ * The running sum of the last `length` values on a bar with an na value (no value added), from the kept state; the
121
+ * state is unchanged until {@link commit}. With an unchanged length, it is the previous result.
122
+ *
123
+ * @returns The sum, or NaN while fewer than `length` values exist
124
+ */
125
+ stepNa(length: number): number {
126
+ this.resize(length);
127
+ this.stepLength = length;
128
+ this.stepX = NaN;
129
+ const n = this.values.length;
130
+ return n > 0 && n >= length ? this.stepS : NaN;
131
+ }
132
+
133
+ /** Keeps the state of the last {@link step} or {@link stepNa}. */
134
+ commit(): void {
135
+ if (!Number.isNaN(this.stepX)) {
136
+ this.values.push(this.stepX);
137
+ this.stored.push(this.stepY);
138
+ }
139
+ this.keptLength = this.stepLength;
140
+ this.s = this.stepS;
141
+ this.c = this.stepC;
142
+ }
143
+ }
144
+
145
+ /** The running sums of `source` with the length `lengthAt(i)` on bar `i`. */
146
+ function sumWindows(source: ArrayLike<number | null | undefined>, lengthAt: (i: number) => number): number[] {
147
+ const out: number[] = new Array<number>(source.length);
148
+ const sum = new RunningSum();
149
+ for (let i = 0; i < source.length; i++) {
150
+ const v = source[i];
151
+ const len = lengthAt(i);
152
+ out[i] = isNa(v) ? sum.stepNa(len) : sum.step(v as number, len);
153
+ sum.commit();
104
154
  }
105
155
  return out;
106
156
  }
107
157
 
108
158
  /**
109
- * Window variance from the running sums of the values and of their squares, as PineScript computes it:
110
- * biased `sumSq / n - mean * mean`, unbiased `sumSq / (n - 1) - mean * sum / (n - 1)`, with `mean = sum / n`.
159
+ * The sum of the last `length` non-na values on each bar, as PineScript computes it.
160
+ *
161
+ * @param source - Values (na: NaN, null, undefined or +/-Infinity, skipped)
162
+ * @param length - Number of non-na values in the window
163
+ * @returns The running sum per bar: NaN until `length` non-na values exist; a bar with an na value keeps the
164
+ * previous result
111
165
  */
112
- export function runningVariance(
166
+ export function runningSum(source: ArrayLike<number | null | undefined>, length: number): number[] {
167
+ const len = Math.floor(length);
168
+ if (!(len >= 1)) return new Array<number>(source.length).fill(NaN);
169
+ return sumWindows(source, () => len);
170
+ }
171
+
172
+ /** The values of a series length, floored, checked to be at least 1 on every bar. */
173
+ function seriesLengths(lengths: ArrayLike<number>, count: number, name: string): number[] {
174
+ if (lengths.length < count) {
175
+ throw new Error(`${name}: the length series has ${lengths.length} values for ${count} bars`);
176
+ }
177
+ return Array.from({ length: count }, (_, i) => {
178
+ const len = Math.floor(lengths[i]!);
179
+ if (!(len >= 1)) throw new Error(`${name}: length must be at least 1 (bar ${i}: ${lengths[i]})`);
180
+ return len;
181
+ });
182
+ }
183
+
184
+ /**
185
+ * The sum of the last `lengths[i]` non-na values on each bar `i` (a series length), as PineScript computes it (see
186
+ * {@link RunningSum}). A bar with an na value gives the sum of the last `lengths[i]` earlier values.
187
+ *
188
+ * @param name - Function name for the error messages
189
+ */
190
+ export function runningSumSeries(
113
191
  source: ArrayLike<number | null | undefined>,
114
- length: number,
192
+ lengths: ArrayLike<number>,
193
+ name = 'math.sum'
194
+ ): number[] {
195
+ const lens = seriesLengths(lengths, source.length, name);
196
+ return sumWindows(source, (i) => lens[i]!);
197
+ }
198
+
199
+ /** Variance from the running sums of the values and of their squares; rules in {@link runningVariance}. */
200
+ function varianceFromSums(
201
+ source: ArrayLike<number | null | undefined>,
202
+ sumOf: (src: ArrayLike<number | null | undefined>) => number[],
203
+ lengthAt: (i: number) => number,
115
204
  biased: boolean
116
205
  ): number[] {
117
- const len = Math.floor(length);
118
206
  const squares = Array.from({ length: source.length }, (_, i) => {
119
207
  const v = source[i];
120
208
  return isNa(v) ? NaN : (v as number) * (v as number);
121
209
  });
122
- const sums = runningSum(source, len);
123
- const sumSquares = runningSum(squares, len);
210
+ const sums = sumOf(source);
211
+ const sumSquares = sumOf(squares);
124
212
  return sums.map((sum, i) => {
213
+ const len = lengthAt(i);
125
214
  const mean = sum / len;
126
215
  const sq = sumSquares[i]!;
127
216
  return biased ? sq / len - mean * mean : sq / (len - 1) - (mean * sum) / (len - 1);
128
217
  });
129
218
  }
219
+
220
+ /**
221
+ * Window variance from the running sums of the values and of their squares, as PineScript computes it:
222
+ * biased `sumSq / n - mean * mean`, unbiased `sumSq / (n - 1) - mean * sum / (n - 1)`, with `mean = sum / n`.
223
+ */
224
+ export function runningVariance(
225
+ source: ArrayLike<number | null | undefined>,
226
+ length: number,
227
+ biased: boolean
228
+ ): number[] {
229
+ const len = Math.floor(length);
230
+ return varianceFromSums(source, (src) => runningSum(src, len), () => len, biased);
231
+ }
232
+
233
+ /** {@link runningVariance} with a series length (the length of bar `i` is `lengths[i]`). */
234
+ export function runningVarianceSeries(
235
+ source: ArrayLike<number | null | undefined>,
236
+ lengths: ArrayLike<number>,
237
+ biased: boolean,
238
+ name = 'ta.variance'
239
+ ): number[] {
240
+ const lens = seriesLengths(lengths, source.length, name);
241
+ return varianceFromSums(source, (src) => sumWindows(src, (i) => lens[i]!), (i) => lens[i]!, biased);
242
+ }
package/src/ta-series.ts CHANGED
@@ -14,10 +14,10 @@ import type { Bar } from './types/index.js';
14
14
  * @param length - Period length
15
15
  * @returns Series with SMA values
16
16
  */
17
- export function sma(source: Series, length: number): Series {
17
+ export function sma(source: Series, length: number | Series): Series {
18
18
  const bars = source.bars as Bar[];
19
19
  const sourceValues = source.toArray();
20
- const result = taCore.sma(sourceValues, length);
20
+ const result = taCore.sma(sourceValues, length instanceof Series ? length.toArray() : length);
21
21
  return Series.fromArray(bars, result);
22
22
  }
23
23
 
@@ -124,9 +124,10 @@ export function bb(source: Series, length: number, mult: number): [Series, Serie
124
124
  * @param biased - true (default): divide by length; false: by length - 1
125
125
  * @returns Series with standard deviation values
126
126
  */
127
- export function stdev(source: Series, length: number, biased: boolean = true): Series {
127
+ export function stdev(source: Series, length: number | Series, biased: boolean = true): Series {
128
128
  const bars = source.bars as Bar[];
129
- return Series.fromArray(bars, taCore.stdev(source.toArray(), length, biased));
129
+ const len = length instanceof Series ? length.toArray() : length;
130
+ return Series.fromArray(bars, taCore.stdev(source.toArray(), len, biased));
130
131
  }
131
132
 
132
133
 
@@ -600,9 +601,9 @@ export function dev(source: Series, length: number): Series {
600
601
  * @param biased - Use biased estimation (default: true)
601
602
  * @returns Series with variance values
602
603
  */
603
- export function variance(source: Series, length: number, biased: boolean = true): Series {
604
+ export function variance(source: Series, length: number | Series, biased: boolean = true): Series {
604
605
  const bars = source.bars as Bar[];
605
- const result = taCore.variance(source.toArray(), length, biased);
606
+ const result = taCore.variance(source.toArray(), length instanceof Series ? length.toArray() : length, biased);
606
607
  return Series.fromArray(bars, result);
607
608
  }
608
609
 
@@ -757,7 +758,7 @@ export function dmi(bars: Bar[], diLength: number, adxSmoothing: number): [Serie
757
758
  * @param source - Source series
758
759
  * @param shortLength - Short smoothing period
759
760
  * @param longLength - Long smoothing period
760
- * @returns Series with TSI values
761
+ * @returns Series with TSI values, in [-1, 1] as PineScript
761
762
  */
762
763
  export function tsi(source: Series, shortLength: number, longLength: number): Series {
763
764
  const bars = source.bars as Bar[];
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Table namespace
3
+ * Functions for creating and filling table drawing objects.
4
+ *
5
+ * @remarks
6
+ * A table is drawn at a fixed position of the pane (`position.*`), not at a bar. Tables have no getters in
7
+ * PineScript; the script API returns the live tables in `result.tables`.
8
+ *
9
+ * PineScript rules:
10
+ * - `table.cell` defines a cell: the arguments it does not give get their default, a previous definition of the
11
+ * cell is replaced
12
+ * - `table.cell_set_*` changes one property of a cell (a cell that was not defined is created)
13
+ * - a column or row outside the table is a runtime error
14
+ * - a deleted table is `na`: its functions do nothing
15
+ *
16
+ * @version 6
17
+ */
18
+
19
+ import { all as allDrawings, isDeleted, register, remove } from '../drawing/registry.js';
20
+ import type { Table, TableCell, color } from '../types/index.js';
21
+
22
+ type TablePosition = Table['position'];
23
+ type HAlign = 'left' | 'center' | 'right';
24
+ type VAlign = 'top' | 'center' | 'bottom';
25
+ type FontFamily = 'default' | 'monospace';
26
+
27
+ /**
28
+ * Creates a new table (PineScript `table.new`).
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const t = table.new(position.top_right, 2, 3, '#131722', '#787B86', 1);
33
+ * table.cell(t, 0, 0, 'RSI', 0, 0, '#FFFFFF');
34
+ * ```
35
+ */
36
+ export function new_table(
37
+ position: TablePosition,
38
+ columns: number,
39
+ rows: number,
40
+ bgcolor?: color,
41
+ frame_color?: color,
42
+ frame_width?: number,
43
+ border_color?: color,
44
+ border_width?: number,
45
+ force_overlay?: boolean
46
+ ): Table {
47
+ return register('table', {
48
+ position,
49
+ columns,
50
+ rows,
51
+ bgcolor,
52
+ frame_color,
53
+ frame_width,
54
+ border_color,
55
+ border_width,
56
+ force_overlay: force_overlay ?? false,
57
+ cells: new Map(),
58
+ merges: [],
59
+ });
60
+ }
61
+
62
+ function checkCell(caller: string, id: Table, column: number, row: number): void {
63
+ if (!(column >= 0 && column < id.columns && row >= 0 && row < id.rows)) {
64
+ throw new RangeError(
65
+ `${caller}: column ${column}, row ${row} is outside the table (${id.columns} columns, ${id.rows} rows).`
66
+ );
67
+ }
68
+ }
69
+
70
+ const key = (column: number, row: number): string => `${column},${row}`;
71
+
72
+ /** The cell at column / row, created when it was not defined. */
73
+ function cellOf(caller: string, id: Table, column: number, row: number): TableCell {
74
+ checkCell(caller, id, column, row);
75
+ const cells = (id.cells ??= new Map());
76
+ let cell = cells.get(key(column, row));
77
+ if (!cell) cells.set(key(column, row), (cell = { text: '' }));
78
+ return cell;
79
+ }
80
+
81
+ /**
82
+ * Defines a cell (PineScript `table.cell`): its text and properties. Omitted properties get their default.
83
+ */
84
+ export function cell(
85
+ table_id: Table,
86
+ column: number,
87
+ row: number,
88
+ text?: string,
89
+ width?: number,
90
+ height?: number,
91
+ text_color?: color,
92
+ text_halign?: HAlign,
93
+ text_valign?: VAlign,
94
+ text_size?: string | number,
95
+ bgcolor?: color,
96
+ tooltip?: string,
97
+ text_font_family?: FontFamily,
98
+ text_formatting?: number
99
+ ): void {
100
+ if (isDeleted(table_id)) return;
101
+ checkCell('table.cell()', table_id, column, row);
102
+ (table_id.cells ??= new Map()).set(key(column, row), {
103
+ text: text ?? '',
104
+ width,
105
+ height,
106
+ text_color,
107
+ text_halign,
108
+ text_valign,
109
+ text_size,
110
+ bgcolor,
111
+ tooltip,
112
+ text_font_family,
113
+ text_formatting,
114
+ });
115
+ }
116
+
117
+ function setCell<K extends keyof TableCell>(name: string, field: K) {
118
+ return (table_id: Table, column: number, row: number, value: TableCell[K]): void => {
119
+ if (isDeleted(table_id)) return;
120
+ cellOf(`table.${name}()`, table_id, column, row)[field] = value;
121
+ };
122
+ }
123
+
124
+ /** Sets the text of a cell. */
125
+ export const cell_set_text = setCell('cell_set_text', 'text');
126
+ /** Sets the width of a cell (% of the chart width; 0 fits the text). */
127
+ export const cell_set_width = setCell('cell_set_width', 'width');
128
+ /** Sets the height of a cell (% of the chart height; 0 fits the text). */
129
+ export const cell_set_height = setCell('cell_set_height', 'height');
130
+ /** Sets the text colour of a cell. */
131
+ export const cell_set_text_color = setCell('cell_set_text_color', 'text_color');
132
+ /** Sets the horizontal text alignment of a cell. */
133
+ export const cell_set_text_halign = setCell('cell_set_text_halign', 'text_halign');
134
+ /** Sets the vertical text alignment of a cell. */
135
+ export const cell_set_text_valign = setCell('cell_set_text_valign', 'text_valign');
136
+ /** Sets the text size of a cell. */
137
+ export const cell_set_text_size = setCell('cell_set_text_size', 'text_size');
138
+ /** Sets the background colour of a cell. */
139
+ export const cell_set_bgcolor = setCell('cell_set_bgcolor', 'bgcolor');
140
+ /** Sets the tooltip of a cell. */
141
+ export const cell_set_tooltip = setCell('cell_set_tooltip', 'tooltip');
142
+ /** Sets the font family of a cell. */
143
+ export const cell_set_text_font_family = setCell('cell_set_text_font_family', 'text_font_family');
144
+ /** Sets the text formatting of a cell (`text.format_*` flags). */
145
+ export const cell_set_text_formatting = setCell('cell_set_text_formatting', 'text_formatting');
146
+
147
+ /**
148
+ * Merges the cells from start_column / start_row to end_column / end_row (PineScript `table.merge_cells`). The
149
+ * merged cell shows the content of the start cell.
150
+ */
151
+ export function merge_cells(
152
+ table_id: Table,
153
+ start_column: number,
154
+ start_row: number,
155
+ end_column: number,
156
+ end_row: number
157
+ ): void {
158
+ if (isDeleted(table_id)) return;
159
+ checkCell('table.merge_cells()', table_id, start_column, start_row);
160
+ checkCell('table.merge_cells()', table_id, end_column, end_row);
161
+ if (end_column < start_column || end_row < start_row) {
162
+ throw new RangeError('table.merge_cells(): the end cell is before the start cell.');
163
+ }
164
+ (table_id.merges ??= []).push({ start_column, start_row, end_column, end_row });
165
+ }
166
+
167
+ /**
168
+ * Removes the cells from start_column / start_row to end_column / end_row (PineScript `table.clear`; default end:
169
+ * the start cell), and the merges that start in that range.
170
+ */
171
+ export function clear(
172
+ table_id: Table,
173
+ start_column: number,
174
+ start_row: number,
175
+ end_column: number = start_column,
176
+ end_row: number = start_row
177
+ ): void {
178
+ if (isDeleted(table_id)) return;
179
+ checkCell('table.clear()', table_id, start_column, start_row);
180
+ checkCell('table.clear()', table_id, end_column, end_row);
181
+ const inside = (column: number, row: number): boolean =>
182
+ column >= start_column && column <= end_column && row >= start_row && row <= end_row;
183
+ for (let column = start_column; column <= end_column; column++) {
184
+ for (let row = start_row; row <= end_row; row++) table_id.cells?.delete(key(column, row));
185
+ }
186
+ if (table_id.merges) table_id.merges = table_id.merges.filter((m) => !inside(m.start_column, m.start_row));
187
+ }
188
+
189
+ function setTable<K extends keyof Table>(field: K) {
190
+ return (table_id: Table, value: Table[K]): void => {
191
+ if (isDeleted(table_id)) return;
192
+ table_id[field] = value;
193
+ };
194
+ }
195
+
196
+ /** Moves the table to another position (`position.*`). */
197
+ export const set_position = setTable('position');
198
+ /** Sets the background colour of the table. */
199
+ export const set_bgcolor = setTable('bgcolor');
200
+ /** Sets the colour of the outer frame. */
201
+ export const set_frame_color = setTable('frame_color');
202
+ /** Sets the width of the outer frame. */
203
+ export const set_frame_width = setTable('frame_width');
204
+ /** Sets the colour of the cell borders. */
205
+ export const set_border_color = setTable('border_color');
206
+ /** Sets the width of the cell borders. */
207
+ export const set_border_width = setTable('border_width');
208
+
209
+ /** Deletes the table (PineScript `table.delete`). */
210
+ export function delete_table(table_id: Table): void {
211
+ remove('table', table_id);
212
+ }
213
+
214
+ /** All live tables, in creation order (PineScript `table.all`). A new array. */
215
+ export function all(): Table[] {
216
+ return allDrawings('table');
217
+ }
218
+
219
+ // Alias to match PineScript API
220
+ export { new_table as new };
221
+ export { delete_table as delete };
package/src/text/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Text namespace
3
- * PineScript `text.format_*` constants for `label.set_text_formatting` and `box.set_text_formatting`.
3
+ * PineScript `text.format_*` constants for `label.set_text_formatting` and `box.set_text_formatting`, and the
4
+ * `text.align_*` / `text.wrap_*` constants.
4
5
  *
5
- * The values are flags that add up, as in PineScript (`text.format_bold + text.format_italic`).
6
+ * The format values are flags that add up, as in PineScript (`text.format_bold + text.format_italic`).
6
7
  * They are the values PineScript uses for drawn labels and boxes.
7
8
  */
8
9
 
@@ -12,3 +13,18 @@ export const format_none = 0;
12
13
  export const format_bold = 1;
13
14
  /** Italic text. */
14
15
  export const format_italic = 2;
16
+
17
+ /** Text aligned to the left (`table.cell`, `box.new`, `label.new` textalign). */
18
+ export const align_left = 'left' as const;
19
+ /** Text centred horizontally or vertically. */
20
+ export const align_center = 'center' as const;
21
+ /** Text aligned to the right. */
22
+ export const align_right = 'right' as const;
23
+ /** Text aligned to the top (`table.cell`, `box.new` text_valign). */
24
+ export const align_top = 'top' as const;
25
+ /** Text aligned to the bottom. */
26
+ export const align_bottom = 'bottom' as const;
27
+ /** Text wrapped to the box width (`box.new` text_wrap). */
28
+ export const wrap_auto = 'auto' as const;
29
+ /** Text not wrapped. */
30
+ export const wrap_none = 'none' as const;
@@ -10,6 +10,7 @@
10
10
  * Time zones: "UT", "UTC", "GMT", "+0530", "-05:00", "GMT+5", "UTC-0300", and the RFC 2822
11
11
  * names EST, EDT, CST, CDT, MST, MDT, PST, PDT. After a "T" time, only numeric offsets.
12
12
  * Without a time the time is 00:00. Without a time zone the time zone is GMT+0.
13
+ * "2025" and "2025-06" are the first day of the year / month. "01 Jan 2022 GMT+3" (a zone without a time) gives na.
13
14
  * "24:00" is midnight at the end of the day.
14
15
  *
15
16
  * "Z", "2024-01-01T09:30:00 GMT", "24:30" and "09:30:60" are compile errors in PineScript, and
@@ -34,6 +35,7 @@ const NUMERIC_ZONE = String.raw`([+-]\d{2}(?::?\d{2})?)`;
34
35
  const DATE = String.raw`(\d{4})([-/])(\d{1,2})\2(\d{1,2})`;
35
36
  const WEEKDAY = String.raw`(?:[a-z]{3,9},?\s+)?`;
36
37
 
38
+ const YEAR_MONTH = /^(\d{4})(?:-(\d{1,2}))?$/;
37
39
  const ISO_T = new RegExp(String.raw`^${DATE}t${TIME}\s*${NUMERIC_ZONE}?$`, 'i');
38
40
  const ISO = new RegExp(String.raw`^${DATE}(?:\s+${TIME})?\s*${ZONE}?$`, 'i');
39
41
  const DAY_FIRST = new RegExp(String.raw`^${WEEKDAY}(\d{1,2})(?:\s+|-)([a-z]+)\.?,?(?:\s+|-)(\d{4})(?:,?\s+${TIME})?\s*${ZONE}?$`, 'i');
@@ -68,12 +70,20 @@ export function parseDateString(dateString: string): number {
68
70
  let rest: Array<string | undefined>;
69
71
 
70
72
  let m: RegExpExecArray | null;
73
+ // "2025" and "2025-06": the first day of the year / month, 00:00 UTC (PineScript)
74
+ if ((m = YEAR_MONTH.exec(text))) {
75
+ const mo = m[2] ? Number(m[2]) : 1;
76
+ if (mo < 1 || mo > 12) throw new SyntaxError(`Invalid date string "${dateString}": a field is out of range`);
77
+ return Date.UTC(Number(m[1]), mo - 1, 1);
78
+ }
71
79
  if ((m = ISO_T.exec(text) ?? ISO.exec(text))) {
72
80
  [year, month, day] = [m[1]!, Number(m[3]), m[4]!];
73
81
  rest = m.slice(5);
74
82
  } else if ((m = DAY_FIRST.exec(text))) {
75
83
  [day, month, year] = [m[1]!, monthNumber(m[2]!), m[3]!];
76
84
  rest = m.slice(4);
85
+ // a time zone without a time ("01 Jan 2022 GMT+3") gives na in PineScript
86
+ if (rest[0] === undefined && rest[4] !== undefined) return NaN;
77
87
  } else if ((m = MONTH_FIRST.exec(text))) {
78
88
  [month, day, year] = [monthNumber(m[1]!), m[2]!, m[3]!];
79
89
  rest = m.slice(4);
package/src/time/index.ts CHANGED
@@ -61,8 +61,10 @@ export function timestamp(
61
61
  export function timestamp(first: string | simple_int, ...rest: Array<simple_int | undefined>): int {
62
62
  if (typeof first === 'string') {
63
63
  if (rest.length === 0) return parseDateString(first);
64
+ // PineScript: an na field counts as 0 (day na of January 2020 is 2019-12-31)
65
+ const f = (v: number | undefined) => (v === undefined || v === null || Number.isNaN(v) ? 0 : v);
64
66
  const [year, month, day, hour, minute, second] = rest as [number, number, number, number?, number?, number?];
65
- return zonedToUnix(first, year, month, day, hour ?? 0, minute ?? 0, second ?? 0);
67
+ return zonedToUnix(first, f(year), f(month), f(day), f(hour), f(minute), f(second));
66
68
  }
67
69
  const [month, day, hour, minute, second] = rest as [number, number, number?, number?, number?];
68
70
  return new Date(first, month - 1, day, hour || 0, minute || 0, second || 0).getTime();