oakscriptjs 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/dist/cjs/array/index.d.ts +8 -2
  2. package/dist/cjs/array/index.d.ts.map +1 -1
  3. package/dist/cjs/array/index.js +2 -1
  4. package/dist/cjs/array/index.js.map +2 -2
  5. package/dist/cjs/box/index.js +6 -6
  6. package/dist/cjs/box/index.js.map +2 -2
  7. package/dist/cjs/callsite/index.d.ts.map +1 -1
  8. package/dist/cjs/callsite/index.js +6 -3
  9. package/dist/cjs/callsite/index.js.map +2 -2
  10. package/dist/cjs/drawing/registry.d.ts +15 -6
  11. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  12. package/dist/cjs/drawing/registry.js +16 -2
  13. package/dist/cjs/drawing/registry.js.map +2 -2
  14. package/dist/cjs/index.d.ts +4 -3
  15. package/dist/cjs/index.d.ts.map +1 -1
  16. package/dist/cjs/index.js +7 -1
  17. package/dist/cjs/index.js.map +2 -2
  18. package/dist/cjs/input.d.ts +9 -1
  19. package/dist/cjs/input.d.ts.map +1 -1
  20. package/dist/cjs/input.js +16 -0
  21. package/dist/cjs/input.js.map +2 -2
  22. package/dist/cjs/label/index.js +3 -3
  23. package/dist/cjs/label/index.js.map +2 -2
  24. package/dist/cjs/line/index.d.ts +10 -27
  25. package/dist/cjs/line/index.d.ts.map +1 -1
  26. package/dist/cjs/line/index.js +10 -27
  27. package/dist/cjs/line/index.js.map +2 -2
  28. package/dist/cjs/linefill/index.d.ts +3 -0
  29. package/dist/cjs/linefill/index.d.ts.map +1 -1
  30. package/dist/cjs/linefill/index.js +15 -1
  31. package/dist/cjs/linefill/index.js.map +3 -3
  32. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
  33. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
  34. package/dist/cjs/runtime/index.d.ts +1 -1
  35. package/dist/cjs/runtime/index.d.ts.map +1 -1
  36. package/dist/cjs/runtime/index.js +1 -0
  37. package/dist/cjs/runtime/index.js.map +2 -2
  38. package/dist/cjs/runtime/inputs.d.ts +9 -0
  39. package/dist/cjs/runtime/inputs.d.ts.map +1 -1
  40. package/dist/cjs/runtime/inputs.js +8 -0
  41. package/dist/cjs/runtime/inputs.js.map +2 -2
  42. package/dist/cjs/runtime/types.d.ts +1 -1
  43. package/dist/cjs/runtime/types.d.ts.map +1 -1
  44. package/dist/cjs/runtime/types.js.map +1 -1
  45. package/dist/cjs/script/index.d.ts +66 -13
  46. package/dist/cjs/script/index.d.ts.map +1 -1
  47. package/dist/cjs/script/index.js +139 -35
  48. package/dist/cjs/script/index.js.map +3 -3
  49. package/dist/cjs/security/other.d.ts +92 -0
  50. package/dist/cjs/security/other.d.ts.map +1 -0
  51. package/dist/cjs/security/other.js +164 -0
  52. package/dist/cjs/security/other.js.map +7 -0
  53. package/dist/cjs/session/bars.d.ts +10 -1
  54. package/dist/cjs/session/bars.d.ts.map +1 -1
  55. package/dist/cjs/session/bars.js +16 -2
  56. package/dist/cjs/session/bars.js.map +2 -2
  57. package/dist/cjs/session/calendar.d.ts +4 -1
  58. package/dist/cjs/session/calendar.d.ts.map +1 -1
  59. package/dist/cjs/session/calendar.js +12 -7
  60. package/dist/cjs/session/calendar.js.map +2 -2
  61. package/dist/cjs/session/context.d.ts +22 -5
  62. package/dist/cjs/session/context.d.ts.map +1 -1
  63. package/dist/cjs/session/context.js +67 -6
  64. package/dist/cjs/session/context.js.map +2 -2
  65. package/dist/cjs/str/floatingdecimal.d.ts +31 -0
  66. package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
  67. package/dist/cjs/str/floatingdecimal.js +298 -0
  68. package/dist/cjs/str/floatingdecimal.js.map +7 -0
  69. package/dist/cjs/str/index.d.ts +4 -3
  70. package/dist/cjs/str/index.d.ts.map +1 -1
  71. package/dist/cjs/str/index.js +1 -1
  72. package/dist/cjs/str/index.js.map +2 -2
  73. package/dist/cjs/str/numberformat.d.ts +6 -4
  74. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  75. package/dist/cjs/str/numberformat.js +3 -2
  76. package/dist/cjs/str/numberformat.js.map +2 -2
  77. package/dist/cjs/ta/index.d.ts +64 -45
  78. package/dist/cjs/ta/index.d.ts.map +1 -1
  79. package/dist/cjs/ta/index.js +140 -126
  80. package/dist/cjs/ta/index.js.map +3 -3
  81. package/dist/cjs/ta/limits.d.ts +11 -0
  82. package/dist/cjs/ta/limits.d.ts.map +1 -1
  83. package/dist/cjs/ta/limits.js +15 -1
  84. package/dist/cjs/ta/limits.js.map +2 -2
  85. package/dist/cjs/ta/running-sum.js +1 -1
  86. package/dist/cjs/ta/running-sum.js.map +2 -2
  87. package/dist/cjs/ta-series.d.ts +1 -1
  88. package/dist/cjs/ta-series.d.ts.map +1 -1
  89. package/dist/cjs/ta-series.js +1 -1
  90. package/dist/cjs/ta-series.js.map +2 -2
  91. package/dist/cjs/table/index.d.ts +10 -3
  92. package/dist/cjs/table/index.d.ts.map +1 -1
  93. package/dist/cjs/table/index.js +11 -1
  94. package/dist/cjs/table/index.js.map +3 -3
  95. package/dist/cjs/time/session.d.ts +9 -0
  96. package/dist/cjs/time/session.d.ts.map +1 -1
  97. package/dist/cjs/time/session.js +42 -14
  98. package/dist/cjs/time/session.js.map +3 -3
  99. package/dist/cjs/time/timezone.d.ts +6 -1
  100. package/dist/cjs/time/timezone.d.ts.map +1 -1
  101. package/dist/cjs/time/timezone.js +2 -2
  102. package/dist/cjs/time/timezone.js.map +2 -2
  103. package/dist/cjs/types/metadata.d.ts +1 -1
  104. package/dist/cjs/types/metadata.d.ts.map +1 -1
  105. package/dist/cjs/types/metadata.js.map +1 -1
  106. package/dist/esm/array/index.d.ts +8 -2
  107. package/dist/esm/array/index.d.ts.map +1 -1
  108. package/dist/esm/array/index.js +12 -3
  109. package/dist/esm/array/index.js.map +1 -1
  110. package/dist/esm/box/index.js +6 -6
  111. package/dist/esm/box/index.js.map +1 -1
  112. package/dist/esm/callsite/index.d.ts.map +1 -1
  113. package/dist/esm/callsite/index.js +6 -3
  114. package/dist/esm/callsite/index.js.map +1 -1
  115. package/dist/esm/drawing/registry.d.ts +15 -6
  116. package/dist/esm/drawing/registry.d.ts.map +1 -1
  117. package/dist/esm/drawing/registry.js +30 -7
  118. package/dist/esm/drawing/registry.js.map +1 -1
  119. package/dist/esm/index.d.ts +4 -3
  120. package/dist/esm/index.d.ts.map +1 -1
  121. package/dist/esm/index.js +8 -5
  122. package/dist/esm/index.js.map +1 -1
  123. package/dist/esm/input.d.ts +9 -1
  124. package/dist/esm/input.d.ts.map +1 -1
  125. package/dist/esm/input.js +16 -0
  126. package/dist/esm/input.js.map +1 -1
  127. package/dist/esm/label/index.js +3 -3
  128. package/dist/esm/label/index.js.map +1 -1
  129. package/dist/esm/line/index.d.ts +10 -27
  130. package/dist/esm/line/index.d.ts.map +1 -1
  131. package/dist/esm/line/index.js +20 -64
  132. package/dist/esm/line/index.js.map +1 -1
  133. package/dist/esm/linefill/index.d.ts +3 -0
  134. package/dist/esm/linefill/index.d.ts.map +1 -1
  135. package/dist/esm/linefill/index.js +20 -1
  136. package/dist/esm/linefill/index.js.map +1 -1
  137. package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
  138. package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
  139. package/dist/esm/runtime/index.d.ts +1 -1
  140. package/dist/esm/runtime/index.d.ts.map +1 -1
  141. package/dist/esm/runtime/index.js +1 -1
  142. package/dist/esm/runtime/index.js.map +1 -1
  143. package/dist/esm/runtime/inputs.d.ts +9 -0
  144. package/dist/esm/runtime/inputs.d.ts.map +1 -1
  145. package/dist/esm/runtime/inputs.js +15 -0
  146. package/dist/esm/runtime/inputs.js.map +1 -1
  147. package/dist/esm/runtime/types.d.ts +1 -1
  148. package/dist/esm/runtime/types.d.ts.map +1 -1
  149. package/dist/esm/script/index.d.ts +66 -13
  150. package/dist/esm/script/index.d.ts.map +1 -1
  151. package/dist/esm/script/index.js +187 -54
  152. package/dist/esm/script/index.js.map +1 -1
  153. package/dist/esm/security/other.d.ts +92 -0
  154. package/dist/esm/security/other.d.ts.map +1 -0
  155. package/dist/esm/security/other.js +211 -0
  156. package/dist/esm/security/other.js.map +1 -0
  157. package/dist/esm/session/bars.d.ts +10 -1
  158. package/dist/esm/session/bars.d.ts.map +1 -1
  159. package/dist/esm/session/bars.js +26 -1
  160. package/dist/esm/session/bars.js.map +1 -1
  161. package/dist/esm/session/calendar.d.ts +4 -1
  162. package/dist/esm/session/calendar.d.ts.map +1 -1
  163. package/dist/esm/session/calendar.js +23 -7
  164. package/dist/esm/session/calendar.js.map +1 -1
  165. package/dist/esm/session/context.d.ts +22 -5
  166. package/dist/esm/session/context.d.ts.map +1 -1
  167. package/dist/esm/session/context.js +86 -10
  168. package/dist/esm/session/context.js.map +1 -1
  169. package/dist/esm/str/floatingdecimal.d.ts +31 -0
  170. package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
  171. package/dist/esm/str/floatingdecimal.js +272 -0
  172. package/dist/esm/str/floatingdecimal.js.map +1 -0
  173. package/dist/esm/str/index.d.ts +4 -3
  174. package/dist/esm/str/index.d.ts.map +1 -1
  175. package/dist/esm/str/index.js +6 -5
  176. package/dist/esm/str/index.js.map +1 -1
  177. package/dist/esm/str/numberformat.d.ts +6 -4
  178. package/dist/esm/str/numberformat.d.ts.map +1 -1
  179. package/dist/esm/str/numberformat.js +9 -5
  180. package/dist/esm/str/numberformat.js.map +1 -1
  181. package/dist/esm/ta/index.d.ts +64 -45
  182. package/dist/esm/ta/index.d.ts.map +1 -1
  183. package/dist/esm/ta/index.js +198 -228
  184. package/dist/esm/ta/index.js.map +1 -1
  185. package/dist/esm/ta/limits.d.ts +11 -0
  186. package/dist/esm/ta/limits.d.ts.map +1 -1
  187. package/dist/esm/ta/limits.js +27 -2
  188. package/dist/esm/ta/limits.js.map +1 -1
  189. package/dist/esm/ta/running-sum.js +1 -1
  190. package/dist/esm/ta/running-sum.js.map +1 -1
  191. package/dist/esm/ta-series.d.ts +1 -1
  192. package/dist/esm/ta-series.d.ts.map +1 -1
  193. package/dist/esm/ta-series.js +2 -1
  194. package/dist/esm/ta-series.js.map +1 -1
  195. package/dist/esm/table/index.d.ts +10 -3
  196. package/dist/esm/table/index.d.ts.map +1 -1
  197. package/dist/esm/table/index.js +23 -3
  198. package/dist/esm/table/index.js.map +1 -1
  199. package/dist/esm/time/session.d.ts +9 -0
  200. package/dist/esm/time/session.d.ts.map +1 -1
  201. package/dist/esm/time/session.js +53 -15
  202. package/dist/esm/time/session.js.map +1 -1
  203. package/dist/esm/time/timezone.d.ts +6 -1
  204. package/dist/esm/time/timezone.d.ts.map +1 -1
  205. package/dist/esm/time/timezone.js +8 -2
  206. package/dist/esm/time/timezone.js.map +1 -1
  207. package/dist/esm/types/metadata.d.ts +1 -1
  208. package/dist/esm/types/metadata.d.ts.map +1 -1
  209. package/package.json +1 -1
  210. package/src/array/index.ts +11 -3
  211. package/src/box/index.ts +6 -6
  212. package/src/callsite/index.ts +6 -3
  213. package/src/drawing/registry.ts +30 -7
  214. package/src/index.ts +16 -3
  215. package/src/input.ts +18 -1
  216. package/src/label/index.ts +3 -3
  217. package/src/line/index.ts +20 -68
  218. package/src/linefill/index.ts +20 -1
  219. package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
  220. package/src/runtime/index.ts +1 -0
  221. package/src/runtime/inputs.ts +16 -0
  222. package/src/runtime/types.ts +1 -1
  223. package/src/script/index.ts +262 -58
  224. package/src/security/other.ts +269 -0
  225. package/src/session/bars.ts +24 -1
  226. package/src/session/calendar.ts +26 -7
  227. package/src/session/context.ts +104 -10
  228. package/src/str/floatingdecimal.ts +268 -0
  229. package/src/str/index.ts +6 -5
  230. package/src/str/numberformat.ts +11 -6
  231. package/src/ta/index.ts +200 -225
  232. package/src/ta/limits.ts +28 -1
  233. package/src/ta/running-sum.ts +1 -1
  234. package/src/ta-series.ts +3 -2
  235. package/src/table/index.ts +22 -3
  236. package/src/time/session.ts +58 -16
  237. package/src/time/timezone.ts +9 -2
  238. package/src/types/metadata.ts +1 -1
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { eq, ge, gt, le, lt } from '../compare/index.js';
11
11
  import { runningSum, runningSumSeries, runningVariance, runningVarianceSeries } from './running-sum.js';
12
- import { checkSumLength, checkWindowLength } from './limits.js';
12
+ import { checkPositiveLength, checkSumLength, checkWindowLength } from './limits.js';
13
13
  /**
14
14
  * Simple Moving Average - returns the moving average (sum of last y values divided by y).
15
15
  *
@@ -64,6 +64,7 @@ export function sma(source, length) {
64
64
  * ```
65
65
  */
66
66
  export function ema(source, length) {
67
+ checkPositiveLength('ema', length, 0);
67
68
  const result = [];
68
69
  const len = Math.floor(length);
69
70
  const multiplier = 2 / (len + 1);
@@ -101,32 +102,19 @@ export function ema(source, length) {
101
102
  }
102
103
  return result;
103
104
  }
104
- /**
105
- * Relative Strength Index - momentum oscillator measuring speed and magnitude of price changes.
106
- *
107
- * @param source - Series of values to process
108
- * @param length - Number of bars (length)
109
- * @returns RSI series (values range from 0 to 100)
110
- *
111
- * @remarks
112
- * - RSI values above 70 typically indicate overbought conditions
113
- * - RSI values below 30 typically indicate oversold conditions
114
- * - Uses `ta.rma()` (Relative Moving Average) for smoothing, matching PineScript v6
115
- * - Formula: RSI = 100 - (100 / (1 + RS)), where RS = Average Gain / Average Loss
116
- * - As in PineScript (`down == 0 ? 100 : up == 0 ? 0 : ...`): 100 when the average loss is 0, else 0 when the
117
- * average gain is 0, both with the 1e-10 tolerance of the comparison operators
118
- * - Average Gain and Average Loss are calculated using RMA (alpha = 1 / length)
119
- * - `na` values in the source series are ignored
120
- *
121
- * @example
122
- * ```typescript
123
- * const rsi14 = ta.rsi(closePrices, 14);
124
- * // Identify overbought/oversold conditions
125
- * const overbought = rsi14.map(v => v > 70);
126
- * const oversold = rsi14.map(v => v < 30);
127
- * ```
128
- */
129
105
  export function rsi(source, length) {
106
+ if (typeof length !== 'number') {
107
+ return Array.from(source, (xv, i) => {
108
+ const x = xv ?? NaN;
109
+ const y = length[i] ?? NaN;
110
+ if (eq(y, 0))
111
+ return 100;
112
+ if (eq(x, 0))
113
+ return 0;
114
+ return 100 - 100 / (1 + x / y);
115
+ });
116
+ }
117
+ checkPositiveLength('rsi', length, 0, 'y');
130
118
  const result = [];
131
119
  // Calculate price changes
132
120
  const changes = [];
@@ -239,14 +227,14 @@ export function stdev(source, length, biased = true) {
239
227
  return varianceOf(source, length, biased, 'ta.stdev').map((v) => (Number.isNaN(v) ? NaN : v > 0 ? Math.sqrt(v) : 0));
240
228
  }
241
229
  /**
242
- * The last `length` non-na values up to bar `i` (the bar itself may be na), or null when fewer exist.
243
- * `isNa` tells which values are skipped (NaN by default).
230
+ * The last `length` non-na values up to bar `i` (the bar itself may be na), or null when fewer exist. +/-Infinity
231
+ * counts as na (#183).
244
232
  */
245
233
  function lastValues(source, i, length) {
246
234
  const values = [];
247
235
  for (let j = i; j >= 0 && values.length < length; j--) {
248
236
  const x = source[j];
249
- if (!Number.isNaN(x))
237
+ if (Number.isFinite(x))
250
238
  values.push(x);
251
239
  }
252
240
  return values.length === length ? values : null;
@@ -305,7 +293,7 @@ function windowExtremes(source, length, lowest, newest = false) {
305
293
  /**
306
294
  * The non-na values of `source` with their bars, and for each bar the position of its last non-na value (-1 when
307
295
  * there is none yet). PineScript's ta.highest / ta.lowest / ta.highestbars / ta.lowestbars / ta.pivothigh /
308
- * ta.pivotlow keep the non-na values they receive and nothing else.
296
+ * ta.pivotlow keep the non-na values they receive and nothing else; +/-Infinity is skipped like na (#183).
309
297
  */
310
298
  function keptValues(source) {
311
299
  const values = [];
@@ -313,7 +301,7 @@ function keptValues(source) {
313
301
  const last = new Int32Array(source.length);
314
302
  for (let i = 0; i < source.length; i++) {
315
303
  const x = source[i];
316
- if (!Number.isNaN(x)) {
304
+ if (Number.isFinite(x)) {
317
305
  values.push(x);
318
306
  bars.push(i);
319
307
  }
@@ -463,6 +451,7 @@ export function crossunder(series1, series2) {
463
451
  * ```
464
452
  */
465
453
  export function change(source, length = 1) {
454
+ checkPositiveLength('change', length, 0);
466
455
  const result = [];
467
456
  for (let i = 0; i < source.length; i++) {
468
457
  if (i < length) {
@@ -560,6 +549,7 @@ export function tr(handle_na = false, high, low, close) {
560
549
  * ```
561
550
  */
562
551
  export function atr(length, high, low, close) {
552
+ checkPositiveLength('atr->rma', length, 0);
563
553
  if (!high || !low || !close) {
564
554
  throw new Error('ta.atr() requires high, low, and close series. ' +
565
555
  'Either pass them explicitly or use createContext({ chart: { high, low, close } }) for implicit data.');
@@ -684,6 +674,7 @@ export function supertrend(factor, atrPeriod, high, low, close, wicks = false) {
684
674
  * ```
685
675
  */
686
676
  export function rma(source, length) {
677
+ checkPositiveLength('rma', length, 0);
687
678
  const result = [];
688
679
  // Floor the length to match PineScript's auto-truncation of float to int
689
680
  const len = Math.floor(length);
@@ -763,6 +754,8 @@ export function wma(source, length) {
763
754
  const result = [];
764
755
  // Floor the length to match PineScript's auto-truncation of float to int
765
756
  const fixed = typeof length === 'number' ? Math.floor(length) : NaN;
757
+ if (typeof length === 'number')
758
+ checkPositiveLength('wma', fixed, 0);
766
759
  // PineScript: na on a bar with an na source (also +/-Infinity) and until `len` non-na values were received;
767
760
  // otherwise the last `len` bars, each na replaced by the previous non-na value
768
761
  const filled = [];
@@ -778,6 +771,8 @@ export function wma(source, length) {
778
771
  filled.push(last);
779
772
  // a series length: the window of this bar's length (the result of ta.wma with that fixed length)
780
773
  const len = typeof length === 'number' ? fixed : Math.floor(length[i]);
774
+ if (typeof length !== 'number')
775
+ checkPositiveLength('wma', len, i);
781
776
  if (isNa || !(len >= 1) || count < len) {
782
777
  result.push(NaN);
783
778
  continue;
@@ -802,9 +797,9 @@ export function wma(source, length) {
802
797
  * @returns Highest value series (na on the first `length - 1` bars)
803
798
  *
804
799
  * @remarks
805
- * As in PineScript: `na` values are skipped. The result is the highest of the last `length` non-na values (of all
806
- * of them when there are fewer), also on a bar whose own value is `na`; `na` before bar `length - 1` and while no
807
- * value was received. A fractional length is truncated (7.5 is 7 bars).
800
+ * As in PineScript: `na` values (also +/-Infinity, #183) are skipped. The result is the highest of the last `length`
801
+ * non-na values (of all of them when there are fewer), also on a bar whose own value is `na`; `na` before bar
802
+ * `length - 1` and while no value was received. A fractional length is truncated (7.5 is 7 bars).
808
803
  * `length` can be an array (a length per bar, PineScript series length), also for `ta.lowest`, `ta.highestbars`
809
804
  * and `ta.lowestbars`: each bar uses its own length.
810
805
  * A length above 15,000 throws the PineScript runtime error (also `ta.lowest`, `ta.highestbars`, `ta.lowestbars`).
@@ -821,9 +816,9 @@ export function highest(source, length) {
821
816
  * @returns Lowest value series (na on the first `length - 1` bars)
822
817
  *
823
818
  * @remarks
824
- * As in PineScript: `na` values are skipped. The result is the lowest of the last `length` non-na values (of all
825
- * of them when there are fewer), also on a bar whose own value is `na`; `na` before bar `length - 1` and while no
826
- * value was received.
819
+ * As in PineScript: `na` values (also +/-Infinity, #183) are skipped. The result is the lowest of the last `length`
820
+ * non-na values (of all of them when there are fewer), also on a bar whose own value is `na`; `na` before bar
821
+ * `length - 1` and while no value was received.
827
822
  */
828
823
  export function lowest(source, length) {
829
824
  return extremeValue(source, length, true);
@@ -909,7 +904,7 @@ function monotonic(source, length, step) {
909
904
  for (let i = 0; i < source.length; i++) {
910
905
  const v = source[i];
911
906
  const prev = i > 0 ? source[i - 1] : NaN;
912
- if (!Number.isNaN(v) && !Number.isNaN(prev))
907
+ if (Number.isFinite(v) && Number.isFinite(prev))
913
908
  count = step(v, prev) ? count + 1 : 0;
914
909
  result.push(count >= length);
915
910
  }
@@ -926,8 +921,8 @@ function monotonic(source, length, step) {
926
921
  * PineScript rules:
927
922
  * - a counter of rises from the previous bar (a rise is larger than the 1e-10 tolerance); a step that is not a rise
928
923
  * resets it to 0
929
- * - a step from or to an `na` bar leaves the counter unchanged (so an `na` bar, and the bar after it, keep the
930
- * result of the bar before the `na`)
924
+ * - a step from or to an `na` (or +/-Infinity) bar leaves the counter unchanged (so an `na` bar, and the bar after
925
+ * it, keep the result of the bar before the `na`)
931
926
  * - true when the counter is at least `length` (false at the start)
932
927
  *
933
928
  * @example
@@ -937,6 +932,7 @@ function monotonic(source, length, step) {
937
932
  * ```
938
933
  */
939
934
  export function rising(source, length) {
935
+ checkPositiveLength('rising', length, 0);
940
936
  return monotonic(source, length, gt);
941
937
  }
942
938
  /**
@@ -950,8 +946,8 @@ export function rising(source, length) {
950
946
  * PineScript rules:
951
947
  * - a counter of falls from the previous bar (a fall is larger than the 1e-10 tolerance); a step that is not a fall
952
948
  * resets it to 0
953
- * - a step from or to an `na` bar leaves the counter unchanged (so an `na` bar, and the bar after it, keep the
954
- * result of the bar before the `na`)
949
+ * - a step from or to an `na` (or +/-Infinity) bar leaves the counter unchanged (so an `na` bar, and the bar after
950
+ * it, keep the result of the bar before the `na`)
955
951
  * - true when the counter is at least `length` (false at the start)
956
952
  *
957
953
  * @example
@@ -961,6 +957,7 @@ export function rising(source, length) {
961
957
  * ```
962
958
  */
963
959
  export function falling(source, length) {
960
+ checkPositiveLength('falling', length, 0);
964
961
  return monotonic(source, length, lt);
965
962
  }
966
963
  /**
@@ -985,6 +982,7 @@ export function falling(source, length) {
985
982
  * ```
986
983
  */
987
984
  export function roc(source, length) {
985
+ checkPositiveLength('roc', length, 0);
988
986
  const result = [];
989
987
  for (let i = 0; i < source.length; i++) {
990
988
  if (i < length) {
@@ -1024,6 +1022,7 @@ export function roc(source, length) {
1024
1022
  * ```
1025
1023
  */
1026
1024
  export function mom(source, length) {
1025
+ checkPositiveLength('mom', length, 0);
1027
1026
  const result = [];
1028
1027
  for (let i = 0; i < source.length; i++) {
1029
1028
  if (i < length) {
@@ -1062,6 +1061,7 @@ export function mom(source, length) {
1062
1061
  * ```
1063
1062
  */
1064
1063
  export function dev(source, length) {
1064
+ checkPositiveLength('dev', length, 0);
1065
1065
  const len = Math.floor(length);
1066
1066
  return sma(source, len).map((mean, i) => {
1067
1067
  if (i < len - 1 || Number.isNaN(mean))
@@ -1124,7 +1124,7 @@ function varianceOf(source, length, biased, name) {
1124
1124
  * @remarks
1125
1125
  * - Returns the middle value when values are sorted
1126
1126
  * - For an even length, returns the average of the two middle values
1127
- * - As in PineScript: `na` values are skipped, the window holds the last `length` non-na values
1127
+ * - As in PineScript: `na` values (also +/-Infinity) are skipped, the window holds the last `length` non-na values
1128
1128
  * (going back as many bars as needed), and a bar whose own value is `na` still gets a result
1129
1129
  * - Returns NaN until `length` non-na values exist
1130
1130
  * - More robust to outliers than mean (SMA)
@@ -1136,6 +1136,7 @@ function varianceOf(source, length, biased, name) {
1136
1136
  * ```
1137
1137
  */
1138
1138
  export function median(source, length) {
1139
+ checkPositiveLength('median', length, 0);
1139
1140
  const len = Math.floor(length);
1140
1141
  const n = source.length;
1141
1142
  const out = new Array(n).fill(NaN);
@@ -1162,7 +1163,7 @@ export function median(source, length) {
1162
1163
  const mid = Math.floor(len / 2);
1163
1164
  for (let i = 0; i < n; i++) {
1164
1165
  const x = source[i];
1165
- if (!Number.isNaN(x)) {
1166
+ if (Number.isFinite(x)) {
1166
1167
  kept[count++] = x;
1167
1168
  sorted.splice(bound(x, false), 0, x); // before the equal values: the newest first
1168
1169
  // the value leaving the window is the oldest, so the last of its equal values
@@ -1224,6 +1225,7 @@ export function swma(source) {
1224
1225
  * ```
1225
1226
  */
1226
1227
  export function vwma(source, length, volume) {
1228
+ checkPositiveLength('vwma->sma', length, 0);
1227
1229
  if (!volume) {
1228
1230
  throw new Error('ta.vwma() requires volume series. ' +
1229
1231
  'Either pass it explicitly or use createContext({ chart: { ..., volume } }) for implicit data.');
@@ -1271,6 +1273,7 @@ export function vwma(source, length, volume) {
1271
1273
  * ```
1272
1274
  */
1273
1275
  export function linreg(source, length, offset = 0) {
1276
+ checkPositiveLength('linreg', length, 0);
1274
1277
  const result = [];
1275
1278
  // Floor the length to match PineScript's auto-truncation of float to int
1276
1279
  const len = Math.floor(length);
@@ -1338,6 +1341,7 @@ export function linreg(source, length, offset = 0) {
1338
1341
  * ```
1339
1342
  */
1340
1343
  export function correlation(source1, source2, length) {
1344
+ checkPositiveLength('correlation', length, 0);
1341
1345
  // PineScript: from compensated running sums, cov = sma(x * y) - sma(x) * sma(y), r = cov / sqrt(var(x) * var(y))
1342
1346
  // (biased variances); each running sum skips its own na values
1343
1347
  const product = Array.from({ length: source1.length }, (_, i) => {
@@ -1377,8 +1381,8 @@ export function correlation(source1, source2, length) {
1377
1381
  *
1378
1382
  * PineScript rules:
1379
1383
  * - percentrank = count(source[k] <= source, k = 1..length) * 100 / length (in this order: the PineScript bits)
1380
- * - a value from bar `length` on, when the current source is not `na`; an `na` value in the window counts as not
1381
- * `<=` the current value
1384
+ * - a value from bar `length` on; an `na` value in the window counts as not `<=` the current value, and an `na`
1385
+ * current value gives 0 (#177)
1382
1386
  * - the comparison uses the 1e-10 tolerance
1383
1387
  *
1384
1388
  * @example
@@ -1389,14 +1393,15 @@ export function correlation(source1, source2, length) {
1389
1393
  * ```
1390
1394
  */
1391
1395
  export function percentrank(source, length) {
1396
+ checkPositiveLength('percentrank', length, 0);
1392
1397
  const result = [];
1393
1398
  for (let i = 0; i < source.length; i++) {
1394
1399
  const currentValue = source[i];
1395
- if (i < length || Number.isNaN(currentValue)) {
1400
+ if (i < length) {
1396
1401
  result.push(NaN);
1397
1402
  continue;
1398
1403
  }
1399
- // le() is false for an na value in the window
1404
+ // le() is false for an na value in the window, and for all of them when the current value is na (0)
1400
1405
  let countLessOrEqual = 0;
1401
1406
  for (let j = 1; j <= length; j++) {
1402
1407
  if (le(source[i - j], currentValue))
@@ -1435,6 +1440,7 @@ export function percentrank(source, length) {
1435
1440
  * \`\`\`
1436
1441
  */
1437
1442
  export function cci(source, length) {
1443
+ checkPositiveLength('cci', length, 0);
1438
1444
  const smaValues = sma(source, length);
1439
1445
  const devValues = dev(source, length);
1440
1446
  let last = 0;
@@ -1461,8 +1467,9 @@ export function cci(source, length) {
1461
1467
  * - Measures where close is relative to the high-low range
1462
1468
  * - Values above 80 typically indicate overbought
1463
1469
  * - Values below 20 typically indicate oversold
1464
- * - As PineScript: when the source, \`highest(high, length)\` or \`lowest(low, length)\` is \`na\`, or the range is 0, the
1465
- * result is the previous result of the call (\`na\` until a first value)
1470
+ * - As PineScript: when the source, \`highest(high, length)\` or \`lowest(low, length)\` is \`na\`, or the range and
1471
+ * \`source - lowest\` are both 0, the result is the previous result of the call (\`na\` until a first value); a zero
1472
+ * range with another source value gives +/-Infinity (plotted as \`na\`)
1466
1473
  *
1467
1474
  * @example
1468
1475
  * \`\`\`typescript
@@ -1472,17 +1479,18 @@ export function cci(source, length) {
1472
1479
  * \`\`\`
1473
1480
  */
1474
1481
  export function stoch(source, high, low, length) {
1482
+ checkPositiveLength('stoch->highest', length, 0);
1475
1483
  const result = [];
1476
1484
  const lowestValues = lowest(low, length);
1477
1485
  const highestValues = highest(high, length);
1478
- // PineScript: an na input or a zero range keeps the previous result
1486
+ // PineScript: an na (NaN) result keeps the previous one: an na input, or 0 / 0 on a zero range; a non-zero value
1487
+ // over a zero range gives +/-Infinity (#183)
1479
1488
  let previous = NaN;
1480
1489
  for (let i = 0; i < source.length; i++) {
1481
- const value = source[i];
1482
1490
  const lo = lowestValues[i];
1483
- const range = highestValues[i] - lo;
1484
- if (!Number.isNaN(value) && !Number.isNaN(range) && range !== 0)
1485
- previous = (100 * (value - lo)) / range;
1491
+ const k = (100 * (source[i] - lo)) / (highestValues[i] - lo);
1492
+ if (!Number.isNaN(k))
1493
+ previous = k;
1486
1494
  result.push(previous);
1487
1495
  }
1488
1496
  return result;
@@ -1521,6 +1529,7 @@ export function stoch(source, high, low, length) {
1521
1529
  * ```
1522
1530
  */
1523
1531
  export function mfi(source, length, volume) {
1532
+ checkPositiveLength('mfi', length, 0);
1524
1533
  if (!volume) {
1525
1534
  throw new Error('ta.mfi() requires volume series. ' +
1526
1535
  'Either pass it explicitly or use createContext({ chart: { ..., volume } }) for implicit data.');
@@ -1576,6 +1585,7 @@ export function mfi(source, length, volume) {
1576
1585
  * ```
1577
1586
  */
1578
1587
  export function hma(source, length) {
1588
+ checkPositiveLength('hma', length, 0);
1579
1589
  const halfLength = Math.floor(length / 2);
1580
1590
  const sqrtLength = Math.floor(Math.sqrt(length));
1581
1591
  const wmaHalf = wma(source, halfLength);
@@ -1728,8 +1738,8 @@ function pivot(source, leftbars, rightbars, isHigh) {
1728
1738
  * - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
1729
1739
  * index is `i - rightbars`
1730
1740
  * - A pivot high may equal values on its left but must be higher than all values on its right
1731
- * - `na` values are skipped: the window is the last `leftbars + rightbars + 1` non-na values, and the highest of
1732
- * them must be on the bar `rightbars` bars back
1741
+ * - `na` values (also +/-Infinity) are skipped: the window is the last `leftbars + rightbars + 1` non-na values,
1742
+ * and the highest of them must be on the bar `rightbars` bars back
1733
1743
  * - `leftbars` / `rightbars` can be arrays (a length per bar, PineScript series length), also for `ta.pivotlow`:
1734
1744
  * each bar uses its own lengths
1735
1745
  *
@@ -1762,8 +1772,8 @@ export function pivothigh(sourceOrLeftbars, leftbarsOrRightbars, rightbars, high
1762
1772
  * - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
1763
1773
  * index is `i - rightbars`
1764
1774
  * - A pivot low may equal values on its left but must be lower than all values on its right
1765
- * - `na` values are skipped: the window is the last `leftbars + rightbars + 1` non-na values, and the lowest of
1766
- * them must be on the bar `rightbars` bars back
1775
+ * - `na` values (also +/-Infinity) are skipped: the window is the last `leftbars + rightbars + 1` non-na values,
1776
+ * and the lowest of them must be on the bar `rightbars` bars back
1767
1777
  *
1768
1778
  * @example
1769
1779
  * ```typescript
@@ -1933,6 +1943,7 @@ export function dmi(diLength, adxSmoothing, high, low, close) {
1933
1943
  * ```
1934
1944
  */
1935
1945
  export function tsi(source, shortLength, longLength) {
1946
+ checkPositiveLength('tsi->ema', shortLength, 0);
1936
1947
  const momentum = [];
1937
1948
  // Calculate momentum (price change)
1938
1949
  for (let i = 0; i < source.length; i++) {
@@ -1978,6 +1989,7 @@ export function tsi(source, shortLength, longLength) {
1978
1989
  * ```
1979
1990
  */
1980
1991
  export function cmo(source, length) {
1992
+ checkPositiveLength('cmo', length, 0);
1981
1993
  // PineScript definition: mom = ta.change(src), sm1 = math.sum(mom >= 0 ? mom : 0, length),
1982
1994
  // sm2 = math.sum(mom >= 0 ? 0 : -mom, length), cmo = 100 * (sm1 - sm2) / (sm1 + sm2), with the running sums of
1983
1995
  // math.sum (bit for bit). A comparison with na is false: an na change adds 0 to sm1 and na (skipped) to sm2.
@@ -2018,6 +2030,7 @@ export function cmo(source, length) {
2018
2030
  * ```
2019
2031
  */
2020
2032
  export function kc(source, length, mult, useTrueRange = true, high, low, close) {
2033
+ checkPositiveLength('kc', length, 0);
2021
2034
  // Calculate middle band (EMA of source)
2022
2035
  const middle = ema(source, length);
2023
2036
  // Calculate range
@@ -2072,6 +2085,7 @@ export function kc(source, length, mult, useTrueRange = true, high, low, close)
2072
2085
  * ```
2073
2086
  */
2074
2087
  export function bbw(source, length, mult) {
2088
+ checkPositiveLength('bbw', length, 0);
2075
2089
  const [basis, upper, lower] = bb(source, length, mult);
2076
2090
  const result = [];
2077
2091
  for (let i = 0; i < source.length; i++) {
@@ -2113,6 +2127,7 @@ export function bbw(source, length, mult) {
2113
2127
  * ```
2114
2128
  */
2115
2129
  export function wpr(high, low, close, length = 14) {
2130
+ checkPositiveLength('wpr', length, 0);
2116
2131
  const highs = highest(high, length);
2117
2132
  const lows = lowest(low, length);
2118
2133
  return Array.from({ length: close.length }, (_, i) => {
@@ -2177,6 +2192,7 @@ export function vwap(source, volume, anchor, stdev_mult) {
2177
2192
  * ```
2178
2193
  */
2179
2194
  export function alma(source, length = 9, offset = 0.85, sigma = 6, floor = false) {
2195
+ checkPositiveLength('alma', length, 0);
2180
2196
  const result = [];
2181
2197
  const m = floor ? Math.floor(offset * (length - 1)) : offset * (length - 1);
2182
2198
  const s = length / sigma;
@@ -2251,10 +2267,11 @@ export function kcw(source, length = 20, mult = 2, useTrueRange = true, high, lo
2251
2267
  * (PineScript `ta.range(source, length)`).
2252
2268
  *
2253
2269
  * @remarks
2254
- * As in PineScript: `na` values are skipped (the window holds the last `length` non-na
2270
+ * As in PineScript: `na` values (also +/-Infinity) are skipped (the window holds the last `length` non-na
2255
2271
  * values), and a bar whose own value is `na` still gets a result.
2256
2272
  */
2257
2273
  export function range(source, length) {
2274
+ checkPositiveLength('range', length, 0);
2258
2275
  return source.map((_, i) => {
2259
2276
  const values = lastValues(source, i, length);
2260
2277
  return values ? Math.max(...values) - Math.min(...values) : NaN;
@@ -2265,9 +2282,9 @@ export function range(source, length) {
2265
2282
  * -1 for the previous bar, and so on (as in PineScript, the offset is negative).
2266
2283
  *
2267
2284
  * @remarks
2268
- * As in PineScript: the best of the last `length` non-na values, the newest one on equal values; the offset is the
2269
- * distance in bars to its bar, so it is below `-(length - 1)` when `na` values were skipped. `na` before bar
2270
- * `length - 1` and while no value was received; a fractional length is truncated.
2285
+ * As in PineScript: the best of the last `length` non-na values (+/-Infinity skipped), the newest one on equal
2286
+ * values; the offset is the distance in bars to its bar, so it is below `-(length - 1)` when `na` values were
2287
+ * skipped. `na` before bar `length - 1` and while no value was received; a fractional length is truncated.
2271
2288
  */
2272
2289
  export function highestbars(source, length) {
2273
2290
  return extremeOffset(source, length, false);
@@ -2277,9 +2294,9 @@ export function highestbars(source, length) {
2277
2294
  * -1 for the previous bar, and so on (as in PineScript, the offset is negative).
2278
2295
  *
2279
2296
  * @remarks
2280
- * As in PineScript: the best of the last `length` non-na values, the newest one on equal values; the offset is the
2281
- * distance in bars to its bar, so it is below `-(length - 1)` when `na` values were skipped. `na` before bar
2282
- * `length - 1` and while no value was received; a fractional length is truncated.
2297
+ * As in PineScript: the best of the last `length` non-na values (+/-Infinity skipped), the newest one on equal
2298
+ * values; the offset is the distance in bars to its bar, so it is below `-(length - 1)` when `na` values were
2299
+ * skipped. `na` before bar `length - 1` and while no value was received; a fractional length is truncated.
2283
2300
  */
2284
2301
  export function lowestbars(source, length) {
2285
2302
  return extremeOffset(source, length, true);
@@ -2288,13 +2305,13 @@ export function lowestbars(source, length) {
2288
2305
  * All-time highest value of `source` up to each bar (PineScript `ta.max(source)`).
2289
2306
  *
2290
2307
  * @remarks
2291
- * As in PineScript: `na` values are skipped, the value carries over `na` bars, and the
2308
+ * As in PineScript: `na` values (also +/-Infinity) are skipped, the value carries over `na` bars, and the
2292
2309
  * result is `na` until the first non-na value.
2293
2310
  */
2294
2311
  export function max(source) {
2295
2312
  let best = NaN;
2296
2313
  return source.map((x) => {
2297
- if (!Number.isNaN(x) && !(x <= best))
2314
+ if (Number.isFinite(x) && !(x <= best))
2298
2315
  best = x;
2299
2316
  return best;
2300
2317
  });
@@ -2303,13 +2320,13 @@ export function max(source) {
2303
2320
  * All-time lowest value of `source` up to each bar (PineScript `ta.min(source)`).
2304
2321
  *
2305
2322
  * @remarks
2306
- * As in PineScript: `na` values are skipped, the value carries over `na` bars, and the
2323
+ * As in PineScript: `na` values (also +/-Infinity) are skipped, the value carries over `na` bars, and the
2307
2324
  * result is `na` until the first non-na value.
2308
2325
  */
2309
2326
  export function min(source) {
2310
2327
  let best = NaN;
2311
2328
  return source.map((x) => {
2312
- if (!Number.isNaN(x) && !(x >= best))
2329
+ if (Number.isFinite(x) && !(x >= best))
2313
2330
  best = x;
2314
2331
  return best;
2315
2332
  });
@@ -2337,6 +2354,7 @@ export function min(source) {
2337
2354
  * ```
2338
2355
  */
2339
2356
  export function cog(source, length = 10) {
2357
+ checkPositiveLength('cog', length, 0);
2340
2358
  const len = Math.floor(length);
2341
2359
  return runningSum(source, len).map((sum, i) => {
2342
2360
  if (i < len - 1 || Number.isNaN(sum) || sum === 0)
@@ -2365,18 +2383,19 @@ export function cog(source, length = 10) {
2365
2383
  *
2366
2384
  * @remarks
2367
2385
  * PineScript rules:
2368
- * - the window is the last `length` non-`na` values (an `na` bar does not enter it, and gets the mode of the
2369
- * window it keeps)
2386
+ * - the window is the last `length` non-`na` values (an `na` or +/-Infinity bar does not enter it, and gets the mode
2387
+ * of the window it keeps)
2370
2388
  * - NaN until `length` non-`na` values exist
2371
2389
  * - If no mode exists, returns the smallest value
2372
2390
  */
2373
2391
  export function mode(source, length) {
2392
+ checkPositiveLength('mode', length, 0);
2374
2393
  const result = [];
2375
2394
  const len = Math.floor(length);
2376
2395
  const seen = []; // non-na values so far
2377
2396
  for (let i = 0; i < source.length; i++) {
2378
2397
  const v = source[i];
2379
- if (!Number.isNaN(v))
2398
+ if (Number.isFinite(v))
2380
2399
  seen.push(v);
2381
2400
  if (seen.length < len) {
2382
2401
  result.push(NaN);
@@ -2438,6 +2457,7 @@ export function mode(source, length) {
2438
2457
  * `na` values are before all numbers
2439
2458
  */
2440
2459
  export function percentile_linear_interpolation(source, length, percentage) {
2460
+ checkPositiveLength('percentile_linear_interpolation', length, 0);
2441
2461
  return sortedWindow(source, length, (s) => {
2442
2462
  const pos = (percentage / 100) * length - 0.5;
2443
2463
  if (pos <= 0)
@@ -2504,6 +2524,7 @@ function sortedWindow(source, length, fn) {
2504
2524
  * - `na` values stay in the window, sorted from bar to bar as in `ta.percentile_linear_interpolation`
2505
2525
  */
2506
2526
  export function percentile_nearest_rank(source, length, percentage) {
2527
+ checkPositiveLength('percentile_nearest_rank', length, 0);
2507
2528
  const rank = Math.min(length, Math.max(1, Math.ceil((percentage / 100) * length)));
2508
2529
  return sortedWindow(source, length, (s) => s[rank - 1]);
2509
2530
  }
@@ -2539,6 +2560,7 @@ export function percentile_nearest_rank(source, length, percentage) {
2539
2560
  * where the `na` was
2540
2561
  */
2541
2562
  export function rci(source, length) {
2563
+ checkPositiveLength('rci', length, 0);
2542
2564
  const len = Math.floor(length);
2543
2565
  const result = [];
2544
2566
  // PineScript keeps the values in a ring of `length + 1` slots indexed by the bar index, filled with 0 at the start
@@ -2604,187 +2626,135 @@ export function rci(source, length) {
2604
2626
  return result;
2605
2627
  }
2606
2628
  /**
2607
- * Pivot Point Levels
2629
+ * Pivot Point Levels (PineScript `ta.pivot_point_levels`).
2608
2630
  *
2609
- * Calculates pivot point levels using various calculation methods.
2610
- * Returns an array containing: [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5]
2631
+ * Returns 11 series: [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5] (one value per bar in each).
2611
2632
  *
2612
2633
  * @param type - Calculation type: "Traditional", "Fibonacci", "Woodie", "Classic", "DM", "Camarilla"
2613
- * @param anchor - Condition that triggers reset of calculations
2614
- * @param developing - If true, pivots recalculate continuously; if false, use last anchor values
2615
- * @param high - High price series (optional, uses context if not provided)
2616
- * @param low - Low price series (optional, uses context if not provided)
2617
- * @param close - Close price series (optional, uses context if not provided)
2618
- * @param open - Open price series (optional, uses context if not provided)
2619
- * @returns Array of 11 pivot levels
2634
+ * @param anchor - Condition that starts a new period (`timeframe.change("W")`...)
2635
+ * @param developing - false: the levels of the previous period, constant until the next anchor; true: the levels of
2636
+ * the current period so far, recalculated on each bar
2637
+ * @param high - High price series
2638
+ * @param low - Low price series
2639
+ * @param close - Close price series
2640
+ * @param open - Open price series (Woodie and DM)
2641
+ * @returns 11 series of levels
2620
2642
  *
2621
2643
  * @example
2622
2644
  * ```typescript
2623
2645
  * const weekChange = [false, false, false, false, true, false, ...]; // Weekly anchor
2624
- * const pivots = ta.pivot_point_levels("Traditional", weekChange, false, high, low, close);
2625
- * // pivots[i]! = [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5]
2646
+ * const levels = ta.pivot_point_levels("Traditional", weekChange, false, high, low, close, open);
2647
+ * // levels[0]![i] is P on bar i, levels[5]![i] is R3
2626
2648
  * ```
2627
2649
  *
2628
2650
  * @remarks
2629
- * - Woodie type cannot use developing=true (will error in PineScript)
2630
- * - DM type only calculates P, R1, S1 (other levels are NaN)
2631
- * - All calculations follow PineScript v6 specifications
2651
+ * As in PineScript (#177):
2652
+ * - a period runs from an anchor bar to the bar before the next anchor; the bars before the first anchor are a
2653
+ * period too (from bar 0). The fixed levels use the high and low of the previous period and the close of the bar
2654
+ * before the anchor (na with an anchor on bar 0), and are na until the first anchor; the developing levels use
2655
+ * the current period's high and low and the bar's close, from bar 0
2656
+ * - Traditional: R3 = P * 2 + (H - 2 * L), S3 = P * 2 - (2 * H - L), R4 = P * 3 + (H - 3 * L), S4 = P * 3 - (3 * H - L),
2657
+ * R5 = P * 4 + (H - 4 * L), S5 = P * 4 - (4 * H - L)
2658
+ * - Fibonacci: P +- 0.382 / 0.618 / 1 * (H - L); R4 to S5 na
2659
+ * - Classic: R3 / S3 = P +- 2 * (H - L), R4 / S4 = P +- 3 * (H - L); R5 / S5 na
2660
+ * - Woodie: P = (H + L + 2 * open of the current period) / 4, R3 = H + 2 * (P - L), S3 = L - 2 * (H - P), R4 / S4 =
2661
+ * R3 + (H - L) / S3 - (H - L); R5 / S5 na. `developing` true throws (RE10128)
2662
+ * - DM: x = H + 2 * L + C (C < open of the period), 2 * H + L + C (C > O) or H + L + 2 * C; P = x / 4,
2663
+ * R1 = x / 2 - L, S1 = x / 2 - H; the other levels na
2664
+ * - Camarilla: C +- (H - L) * 1.1 / 12, / 6, / 4, / 2 for R1 to S4, R5 = (H / L) * C, S5 = 2 * C - R5
2632
2665
  */
2633
2666
  export function pivot_point_levels(type, anchor, developing = false, high, low, close, open) {
2634
- // For simplicity, we need to handle the case where type is a string array
2635
- // But according to the docs, type is "series string", meaning it can change per bar
2636
- // For this implementation, we'll support constant type strings
2637
2667
  const typeStr = typeof type === 'string' ? type : String(type[0]);
2638
- const isDeveloping = typeof developing === 'boolean' ? developing : developing[0];
2639
- // Woodie cannot be developing
2668
+ const isDeveloping = typeof developing === 'boolean' ? developing : Boolean(developing[0]);
2640
2669
  if (typeStr === 'Woodie' && isDeveloping) {
2641
- throw new Error('ta.pivot_point_levels: Woodie type cannot use developing=true');
2642
- }
2643
- // Ensure all series have the same length
2644
- const length = anchor.length;
2645
- if (high && high.length !== length)
2646
- throw new Error('High series length mismatch');
2647
- if (low && low.length !== length)
2648
- throw new Error('Low series length mismatch');
2649
- if (close && close.length !== length)
2650
- throw new Error('Close series length mismatch');
2651
- if (open && open.length !== length)
2652
- throw new Error('Open series length mismatch');
2653
- // Initialize result arrays for all 11 levels
2654
- const results = Array.from({ length: 11 }, () => []);
2655
- // Track the last anchor point data
2656
- let lastH = NaN, lastL = NaN, lastC = NaN, lastO = NaN;
2657
- let lastAnchorIndex = -1;
2658
- for (let i = 0; i < length; i++) {
2659
- // Check if anchor triggered
2660
- if (anchor[i]) {
2661
- lastAnchorIndex = i;
2662
- // Store OHLC at anchor point (these will be used for calculations)
2663
- lastH = high ? high[i] : NaN;
2664
- lastL = low ? low[i] : NaN;
2665
- lastC = close ? close[i] : NaN;
2666
- lastO = open ? open[i] : NaN;
2667
- }
2668
- // Calculate data to use
2669
- let h, l, c, o;
2670
- if (isDeveloping && lastAnchorIndex >= 0) {
2671
- // Developing: use max/min/last since anchor
2672
- h = high ? Math.max(...high.slice(lastAnchorIndex, i + 1).filter(v => !isNaN(v))) : NaN;
2673
- l = low ? Math.min(...low.slice(lastAnchorIndex, i + 1).filter(v => !isNaN(v))) : NaN;
2674
- c = close ? close[i] : NaN;
2675
- o = open && lastAnchorIndex >= 0 ? open[lastAnchorIndex] : NaN;
2676
- }
2677
- else {
2678
- // Not developing: use last anchor values
2679
- h = lastH;
2680
- l = lastL;
2681
- c = lastC;
2682
- o = lastO;
2683
- }
2684
- // Calculate pivot levels based on type
2685
- const levels = calculatePivotLevels(typeStr, h, l, c, o);
2686
- // Push to results
2687
- for (let j = 0; j < 11; j++) {
2688
- results[j].push(levels[j]);
2670
+ throw new Error('Error on bar 0: The `developing` parameter of the `ta.pivot_point_levels()` cannot be `true` when `type` is "Woodie".');
2671
+ }
2672
+ const n = anchor.length;
2673
+ for (const [s, name] of [[high, 'High'], [low, 'Low'], [close, 'Close'], [open, 'Open']]) {
2674
+ if (s && s.length !== n)
2675
+ throw new Error(`${name} series length mismatch`);
2676
+ }
2677
+ const at = (s, i) => (s ? (s[i] ?? NaN) : NaN);
2678
+ const results = Array.from({ length: 11 }, () => new Array(n));
2679
+ // the current period (from bar 0 until the first anchor) and the previous one
2680
+ let curH = NaN, curL = NaN, curO = NaN;
2681
+ let prevH = NaN, prevL = NaN, prevC = NaN, prevO = NaN;
2682
+ let anchored = false;
2683
+ for (let i = 0; i < n; i++) {
2684
+ const h = at(high, i), l = at(low, i);
2685
+ if (i === 0) {
2686
+ // the first period starts on bar 0: an anchor there makes bar 0 its own previous period
2687
+ curH = h;
2688
+ curL = l;
2689
+ curO = at(open, i);
2689
2690
  }
2691
+ if (anchor[i]) {
2692
+ prevH = curH;
2693
+ prevL = curL;
2694
+ prevC = i > 0 ? at(close, i - 1) : NaN;
2695
+ prevO = curO;
2696
+ curH = h;
2697
+ curL = l;
2698
+ curO = at(open, i);
2699
+ anchored = true;
2700
+ }
2701
+ else if (i > 0) {
2702
+ if (h > curH || Number.isNaN(curH))
2703
+ curH = h;
2704
+ if (l < curL || Number.isNaN(curL))
2705
+ curL = l;
2706
+ }
2707
+ const levels = isDeveloping
2708
+ ? pivotLevels(typeStr, curH, curL, at(close, i), curO, at(close, i))
2709
+ : anchored
2710
+ ? pivotLevels(typeStr, prevH, prevL, prevC, prevO, curO)
2711
+ : NA_LEVELS;
2712
+ for (let k = 0; k < 11; k++)
2713
+ results[k][i] = levels[k];
2690
2714
  }
2691
2715
  return results;
2692
2716
  }
2717
+ const NA_LEVELS = new Array(11).fill(NaN);
2693
2718
  /**
2694
- * Helper function to calculate pivot levels
2719
+ * The 11 levels of one bar, in PineScript's operation order (bit for bit). `o` is the open of the period (DM),
2720
+ * `wo` the close term of Woodie (the open of the current period, or the bar's close while developing).
2695
2721
  */
2696
- function calculatePivotLevels(type, h, l, c, o) {
2697
- // Return NaN array if data is insufficient
2698
- if (isNaN(h) || isNaN(l) || isNaN(c)) {
2699
- return Array(11).fill(NaN);
2700
- }
2701
- const levels = Array(11).fill(NaN);
2702
- // Calculate pivot point (P)
2703
- let P;
2722
+ function pivotLevels(type, h, l, c, o, wo) {
2723
+ const N = NaN;
2724
+ const r = h - l;
2704
2725
  switch (type) {
2705
- case 'Traditional':
2706
- case 'Fibonacci':
2707
- case 'Classic':
2708
- P = (h + l + c) / 3;
2709
- break;
2710
- case 'Woodie':
2711
- P = (h + l + 2 * c) / 4;
2712
- break;
2713
- case 'DM':
2714
- P = (h + l + c) / 3;
2715
- break;
2716
- case 'Camarilla':
2717
- P = (h + l + c) / 3;
2718
- break;
2719
- default:
2720
- P = (h + l + c) / 3;
2721
- }
2722
- levels[0] = P; // P is at index 0
2723
- // Calculate resistance and support levels based on type
2724
- switch (type) {
2725
- case 'Traditional':
2726
- case 'Classic':
2727
- levels[1] = 2 * P - l; // R1
2728
- levels[2] = 2 * P - h; // S1
2729
- levels[3] = P + (h - l); // R2
2730
- levels[4] = P - (h - l); // S2
2731
- levels[5] = h + 2 * (P - l); // R3
2732
- levels[6] = l - 2 * (h - P); // S3
2733
- levels[7] = levels[5] + (h - l); // R4
2734
- levels[8] = levels[6] - (h - l); // S4
2735
- levels[9] = levels[7] + (h - l); // R5
2736
- levels[10] = levels[8] - (h - l); // S5
2737
- break;
2738
- case 'Fibonacci':
2739
- levels[1] = P + 0.382 * (h - l); // R1
2740
- levels[2] = P - 0.382 * (h - l); // S1
2741
- levels[3] = P + 0.618 * (h - l); // R2
2742
- levels[4] = P - 0.618 * (h - l); // S2
2743
- levels[5] = P + (h - l); // R3
2744
- levels[6] = P - (h - l); // S3
2745
- levels[7] = levels[5] + 0.618 * (h - l); // R4
2746
- levels[8] = levels[6] - 0.618 * (h - l); // S4
2747
- levels[9] = levels[7] + 0.382 * (h - l); // R5
2748
- levels[10] = levels[8] - 0.382 * (h - l); // S5
2749
- break;
2750
- case 'Woodie':
2751
- levels[1] = 2 * P - l; // R1
2752
- levels[2] = 2 * P - h; // S1
2753
- levels[3] = P + (h - l); // R2
2754
- levels[4] = P - (h - l); // S2
2755
- levels[5] = h + 2 * (P - l); // R3
2756
- levels[6] = l - 2 * (h - P); // S3
2757
- levels[7] = levels[5] + (h - l); // R4
2758
- levels[8] = levels[6] - (h - l); // S4
2759
- levels[9] = levels[7] + (h - l); // R5
2760
- levels[10] = levels[8] - (h - l); // S5
2761
- break;
2726
+ case 'Traditional': {
2727
+ const p = (h + l + c) / 3;
2728
+ return [p, 2 * p - l, 2 * p - h, p + r, p - r, p * 2 + (h - 2 * l), p * 2 - (2 * h - l), p * 3 + (h - 3 * l),
2729
+ p * 3 - (3 * h - l), p * 4 + (h - 4 * l), p * 4 - (4 * h - l)];
2730
+ }
2731
+ case 'Fibonacci': {
2732
+ const p = (h + l + c) / 3;
2733
+ return [p, p + 0.382 * r, p - 0.382 * r, p + 0.618 * r, p - 0.618 * r, p + 1 * r, p - 1 * r, N, N, N, N];
2734
+ }
2735
+ case 'Classic': {
2736
+ const p = (h + l + c) / 3;
2737
+ return [p, 2 * p - l, 2 * p - h, p + r, p - r, p + 2 * r, p - 2 * r, p + 3 * r, p - 3 * r, N, N];
2738
+ }
2739
+ case 'Woodie': {
2740
+ const p = (h + l + 2 * wo) / 4;
2741
+ const r3 = h + 2 * (p - l);
2742
+ const s3 = l - 2 * (h - p);
2743
+ return [p, 2 * p - l, 2 * p - h, p + r, p - r, r3, s3, r3 + r, s3 - r, N, N];
2744
+ }
2762
2745
  case 'DM': {
2763
- // DM (Demark) only calculates P, R1, S1
2764
- const x = h + l + (c * 2) + (isNaN(o) ? c : o);
2765
- const newP = x / (isNaN(o) ? 4 : 5);
2766
- levels[0] = newP;
2767
- levels[1] = x / 2 - l; // R1
2768
- levels[2] = x / 2 - h; // S1
2769
- // R2-S5 remain NaN
2770
- break;
2746
+ const x = c < o ? h + 2 * l + c : c > o ? 2 * h + l + c : h + l + 2 * c;
2747
+ return [x / 4, x / 2 - l, x / 2 - h, N, N, N, N, N, N, N, N];
2771
2748
  }
2772
2749
  case 'Camarilla': {
2773
- const range = h - l;
2774
- levels[1] = c + range * 1.1 / 12; // R1
2775
- levels[2] = c - range * 1.1 / 12; // S1
2776
- levels[3] = c + range * 1.1 / 6; // R2
2777
- levels[4] = c - range * 1.1 / 6; // S2
2778
- levels[5] = c + range * 1.1 / 4; // R3
2779
- levels[6] = c - range * 1.1 / 4; // S3
2780
- levels[7] = c + range * 1.1 / 2; // R4
2781
- levels[8] = c - range * 1.1 / 2; // S4
2782
- levels[9] = h; // R5 (high)
2783
- levels[10] = l; // S5 (low)
2784
- break;
2750
+ const p = (h + l + c) / 3;
2751
+ const r5 = (h / l) * c;
2752
+ return [p, c + (r * 1.1) / 12, c - (r * 1.1) / 12, c + (r * 1.1) / 6, c - (r * 1.1) / 6, c + (r * 1.1) / 4,
2753
+ c - (r * 1.1) / 4, c + (r * 1.1) / 2, c - (r * 1.1) / 2, r5, 2 * c - r5];
2785
2754
  }
2755
+ default:
2756
+ return NA_LEVELS;
2786
2757
  }
2787
- return levels;
2788
2758
  }
2789
2759
  /**
2790
2760
  * Ichimoku Kinko Hyo (Ichimoku Cloud) - Japanese charting technique for trend identification.