oakscriptjs 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/dist/cjs/array/index.d.ts +8 -2
  2. package/dist/cjs/array/index.d.ts.map +1 -1
  3. package/dist/cjs/array/index.js +2 -1
  4. package/dist/cjs/array/index.js.map +2 -2
  5. package/dist/cjs/box/index.js +6 -6
  6. package/dist/cjs/box/index.js.map +2 -2
  7. package/dist/cjs/callsite/index.js +1 -1
  8. package/dist/cjs/callsite/index.js.map +2 -2
  9. package/dist/cjs/drawing/registry.d.ts +15 -6
  10. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  11. package/dist/cjs/drawing/registry.js +16 -2
  12. package/dist/cjs/drawing/registry.js.map +2 -2
  13. package/dist/cjs/index.d.ts +3 -2
  14. package/dist/cjs/index.d.ts.map +1 -1
  15. package/dist/cjs/index.js +6 -1
  16. package/dist/cjs/index.js.map +2 -2
  17. package/dist/cjs/input.d.ts +9 -1
  18. package/dist/cjs/input.d.ts.map +1 -1
  19. package/dist/cjs/input.js +16 -0
  20. package/dist/cjs/input.js.map +2 -2
  21. package/dist/cjs/label/index.js +3 -3
  22. package/dist/cjs/label/index.js.map +2 -2
  23. package/dist/cjs/line/index.d.ts +10 -27
  24. package/dist/cjs/line/index.d.ts.map +1 -1
  25. package/dist/cjs/line/index.js +10 -27
  26. package/dist/cjs/line/index.js.map +2 -2
  27. package/dist/cjs/linefill/index.d.ts +3 -0
  28. package/dist/cjs/linefill/index.d.ts.map +1 -1
  29. package/dist/cjs/linefill/index.js +15 -1
  30. package/dist/cjs/linefill/index.js.map +3 -3
  31. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
  32. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
  33. package/dist/cjs/runtime/index.d.ts +1 -1
  34. package/dist/cjs/runtime/index.d.ts.map +1 -1
  35. package/dist/cjs/runtime/index.js +1 -0
  36. package/dist/cjs/runtime/index.js.map +2 -2
  37. package/dist/cjs/runtime/inputs.d.ts +9 -0
  38. package/dist/cjs/runtime/inputs.d.ts.map +1 -1
  39. package/dist/cjs/runtime/inputs.js +8 -0
  40. package/dist/cjs/runtime/inputs.js.map +2 -2
  41. package/dist/cjs/runtime/types.d.ts +1 -1
  42. package/dist/cjs/runtime/types.d.ts.map +1 -1
  43. package/dist/cjs/runtime/types.js.map +1 -1
  44. package/dist/cjs/script/index.d.ts +65 -12
  45. package/dist/cjs/script/index.d.ts.map +1 -1
  46. package/dist/cjs/script/index.js +168 -19
  47. package/dist/cjs/script/index.js.map +3 -3
  48. package/dist/cjs/security/other.d.ts +92 -0
  49. package/dist/cjs/security/other.d.ts.map +1 -0
  50. package/dist/cjs/security/other.js +164 -0
  51. package/dist/cjs/security/other.js.map +7 -0
  52. package/dist/cjs/session/bars.d.ts +10 -1
  53. package/dist/cjs/session/bars.d.ts.map +1 -1
  54. package/dist/cjs/session/bars.js +16 -2
  55. package/dist/cjs/session/bars.js.map +2 -2
  56. package/dist/cjs/session/calendar.d.ts +4 -1
  57. package/dist/cjs/session/calendar.d.ts.map +1 -1
  58. package/dist/cjs/session/calendar.js +11 -7
  59. package/dist/cjs/session/calendar.js.map +2 -2
  60. package/dist/cjs/session/context.d.ts +2 -1
  61. package/dist/cjs/session/context.d.ts.map +1 -1
  62. package/dist/cjs/session/context.js +2 -1
  63. package/dist/cjs/session/context.js.map +2 -2
  64. package/dist/cjs/str/floatingdecimal.d.ts +31 -0
  65. package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
  66. package/dist/cjs/str/floatingdecimal.js +298 -0
  67. package/dist/cjs/str/floatingdecimal.js.map +7 -0
  68. package/dist/cjs/str/index.d.ts +4 -3
  69. package/dist/cjs/str/index.d.ts.map +1 -1
  70. package/dist/cjs/str/index.js +1 -1
  71. package/dist/cjs/str/index.js.map +2 -2
  72. package/dist/cjs/str/numberformat.d.ts +6 -4
  73. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  74. package/dist/cjs/str/numberformat.js +3 -2
  75. package/dist/cjs/str/numberformat.js.map +2 -2
  76. package/dist/cjs/ta/index.d.ts +34 -17
  77. package/dist/cjs/ta/index.d.ts.map +1 -1
  78. package/dist/cjs/ta/index.js +132 -116
  79. package/dist/cjs/ta/index.js.map +2 -2
  80. package/dist/cjs/ta/limits.d.ts +11 -0
  81. package/dist/cjs/ta/limits.d.ts.map +1 -1
  82. package/dist/cjs/ta/limits.js +15 -1
  83. package/dist/cjs/ta/limits.js.map +2 -2
  84. package/dist/cjs/ta/running-sum.js +1 -1
  85. package/dist/cjs/ta/running-sum.js.map +2 -2
  86. package/dist/cjs/ta-series.d.ts +1 -1
  87. package/dist/cjs/ta-series.d.ts.map +1 -1
  88. package/dist/cjs/ta-series.js +1 -1
  89. package/dist/cjs/ta-series.js.map +2 -2
  90. package/dist/cjs/table/index.d.ts +3 -1
  91. package/dist/cjs/table/index.d.ts.map +1 -1
  92. package/dist/cjs/table/index.js +1 -0
  93. package/dist/cjs/table/index.js.map +3 -3
  94. package/dist/cjs/time/session.d.ts +9 -0
  95. package/dist/cjs/time/session.d.ts.map +1 -1
  96. package/dist/cjs/time/session.js +42 -14
  97. package/dist/cjs/time/session.js.map +3 -3
  98. package/dist/cjs/time/timezone.d.ts +6 -1
  99. package/dist/cjs/time/timezone.d.ts.map +1 -1
  100. package/dist/cjs/time/timezone.js +2 -2
  101. package/dist/cjs/time/timezone.js.map +2 -2
  102. package/dist/cjs/types/metadata.d.ts +1 -1
  103. package/dist/cjs/types/metadata.d.ts.map +1 -1
  104. package/dist/cjs/types/metadata.js.map +1 -1
  105. package/dist/esm/array/index.d.ts +8 -2
  106. package/dist/esm/array/index.d.ts.map +1 -1
  107. package/dist/esm/array/index.js +12 -3
  108. package/dist/esm/array/index.js.map +1 -1
  109. package/dist/esm/box/index.js +6 -6
  110. package/dist/esm/box/index.js.map +1 -1
  111. package/dist/esm/callsite/index.js +1 -1
  112. package/dist/esm/callsite/index.js.map +1 -1
  113. package/dist/esm/drawing/registry.d.ts +15 -6
  114. package/dist/esm/drawing/registry.d.ts.map +1 -1
  115. package/dist/esm/drawing/registry.js +30 -7
  116. package/dist/esm/drawing/registry.js.map +1 -1
  117. package/dist/esm/index.d.ts +3 -2
  118. package/dist/esm/index.d.ts.map +1 -1
  119. package/dist/esm/index.js +4 -2
  120. package/dist/esm/index.js.map +1 -1
  121. package/dist/esm/input.d.ts +9 -1
  122. package/dist/esm/input.d.ts.map +1 -1
  123. package/dist/esm/input.js +16 -0
  124. package/dist/esm/input.js.map +1 -1
  125. package/dist/esm/label/index.js +3 -3
  126. package/dist/esm/label/index.js.map +1 -1
  127. package/dist/esm/line/index.d.ts +10 -27
  128. package/dist/esm/line/index.d.ts.map +1 -1
  129. package/dist/esm/line/index.js +20 -64
  130. package/dist/esm/line/index.js.map +1 -1
  131. package/dist/esm/linefill/index.d.ts +3 -0
  132. package/dist/esm/linefill/index.d.ts.map +1 -1
  133. package/dist/esm/linefill/index.js +20 -1
  134. package/dist/esm/linefill/index.js.map +1 -1
  135. package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
  136. package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
  137. package/dist/esm/runtime/index.d.ts +1 -1
  138. package/dist/esm/runtime/index.d.ts.map +1 -1
  139. package/dist/esm/runtime/index.js +1 -1
  140. package/dist/esm/runtime/index.js.map +1 -1
  141. package/dist/esm/runtime/inputs.d.ts +9 -0
  142. package/dist/esm/runtime/inputs.d.ts.map +1 -1
  143. package/dist/esm/runtime/inputs.js +15 -0
  144. package/dist/esm/runtime/inputs.js.map +1 -1
  145. package/dist/esm/runtime/types.d.ts +1 -1
  146. package/dist/esm/runtime/types.d.ts.map +1 -1
  147. package/dist/esm/script/index.d.ts +65 -12
  148. package/dist/esm/script/index.d.ts.map +1 -1
  149. package/dist/esm/script/index.js +217 -29
  150. package/dist/esm/script/index.js.map +1 -1
  151. package/dist/esm/security/other.d.ts +92 -0
  152. package/dist/esm/security/other.d.ts.map +1 -0
  153. package/dist/esm/security/other.js +211 -0
  154. package/dist/esm/security/other.js.map +1 -0
  155. package/dist/esm/session/bars.d.ts +10 -1
  156. package/dist/esm/session/bars.d.ts.map +1 -1
  157. package/dist/esm/session/bars.js +26 -1
  158. package/dist/esm/session/bars.js.map +1 -1
  159. package/dist/esm/session/calendar.d.ts +4 -1
  160. package/dist/esm/session/calendar.d.ts.map +1 -1
  161. package/dist/esm/session/calendar.js +19 -7
  162. package/dist/esm/session/calendar.js.map +1 -1
  163. package/dist/esm/session/context.d.ts +2 -1
  164. package/dist/esm/session/context.d.ts.map +1 -1
  165. package/dist/esm/session/context.js +5 -3
  166. package/dist/esm/session/context.js.map +1 -1
  167. package/dist/esm/str/floatingdecimal.d.ts +31 -0
  168. package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
  169. package/dist/esm/str/floatingdecimal.js +272 -0
  170. package/dist/esm/str/floatingdecimal.js.map +1 -0
  171. package/dist/esm/str/index.d.ts +4 -3
  172. package/dist/esm/str/index.d.ts.map +1 -1
  173. package/dist/esm/str/index.js +6 -5
  174. package/dist/esm/str/index.js.map +1 -1
  175. package/dist/esm/str/numberformat.d.ts +6 -4
  176. package/dist/esm/str/numberformat.d.ts.map +1 -1
  177. package/dist/esm/str/numberformat.js +9 -5
  178. package/dist/esm/str/numberformat.js.map +1 -1
  179. package/dist/esm/ta/index.d.ts +34 -17
  180. package/dist/esm/ta/index.d.ts.map +1 -1
  181. package/dist/esm/ta/index.js +155 -185
  182. package/dist/esm/ta/index.js.map +1 -1
  183. package/dist/esm/ta/limits.d.ts +11 -0
  184. package/dist/esm/ta/limits.d.ts.map +1 -1
  185. package/dist/esm/ta/limits.js +27 -2
  186. package/dist/esm/ta/limits.js.map +1 -1
  187. package/dist/esm/ta/running-sum.js +1 -1
  188. package/dist/esm/ta/running-sum.js.map +1 -1
  189. package/dist/esm/ta-series.d.ts +1 -1
  190. package/dist/esm/ta-series.d.ts.map +1 -1
  191. package/dist/esm/ta-series.js +2 -1
  192. package/dist/esm/ta-series.js.map +1 -1
  193. package/dist/esm/table/index.d.ts +3 -1
  194. package/dist/esm/table/index.d.ts.map +1 -1
  195. package/dist/esm/table/index.js +6 -1
  196. package/dist/esm/table/index.js.map +1 -1
  197. package/dist/esm/time/session.d.ts +9 -0
  198. package/dist/esm/time/session.d.ts.map +1 -1
  199. package/dist/esm/time/session.js +53 -15
  200. package/dist/esm/time/session.js.map +1 -1
  201. package/dist/esm/time/timezone.d.ts +6 -1
  202. package/dist/esm/time/timezone.d.ts.map +1 -1
  203. package/dist/esm/time/timezone.js +8 -2
  204. package/dist/esm/time/timezone.js.map +1 -1
  205. package/dist/esm/types/metadata.d.ts +1 -1
  206. package/dist/esm/types/metadata.d.ts.map +1 -1
  207. package/package.json +1 -1
  208. package/src/array/index.ts +11 -3
  209. package/src/box/index.ts +6 -6
  210. package/src/callsite/index.ts +1 -1
  211. package/src/drawing/registry.ts +30 -7
  212. package/src/index.ts +11 -1
  213. package/src/input.ts +18 -1
  214. package/src/label/index.ts +3 -3
  215. package/src/line/index.ts +20 -68
  216. package/src/linefill/index.ts +20 -1
  217. package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
  218. package/src/runtime/index.ts +1 -0
  219. package/src/runtime/inputs.ts +16 -0
  220. package/src/runtime/types.ts +1 -1
  221. package/src/script/index.ts +292 -30
  222. package/src/security/other.ts +269 -0
  223. package/src/session/bars.ts +24 -1
  224. package/src/session/calendar.ts +23 -7
  225. package/src/session/context.ts +5 -3
  226. package/src/str/floatingdecimal.ts +268 -0
  227. package/src/str/index.ts +6 -5
  228. package/src/str/numberformat.ts +11 -6
  229. package/src/ta/index.ts +157 -183
  230. package/src/ta/limits.ts +28 -1
  231. package/src/ta/running-sum.ts +1 -1
  232. package/src/ta-series.ts +3 -2
  233. package/src/table/index.ts +4 -1
  234. package/src/time/session.ts +58 -16
  235. package/src/time/timezone.ts +9 -2
  236. package/src/types/metadata.ts +1 -1
@@ -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,20 @@ 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
+ const r = 100 - 100 / (1 + x / y);
115
+ return Number.isFinite(r) ? r : NaN;
116
+ });
117
+ }
118
+ checkPositiveLength('rsi', length, 0, 'y');
130
119
  const result = [];
131
120
  // Calculate price changes
132
121
  const changes = [];
@@ -463,6 +452,7 @@ export function crossunder(series1, series2) {
463
452
  * ```
464
453
  */
465
454
  export function change(source, length = 1) {
455
+ checkPositiveLength('change', length, 0);
466
456
  const result = [];
467
457
  for (let i = 0; i < source.length; i++) {
468
458
  if (i < length) {
@@ -560,6 +550,7 @@ export function tr(handle_na = false, high, low, close) {
560
550
  * ```
561
551
  */
562
552
  export function atr(length, high, low, close) {
553
+ checkPositiveLength('atr->rma', length, 0);
563
554
  if (!high || !low || !close) {
564
555
  throw new Error('ta.atr() requires high, low, and close series. ' +
565
556
  'Either pass them explicitly or use createContext({ chart: { high, low, close } }) for implicit data.');
@@ -684,6 +675,7 @@ export function supertrend(factor, atrPeriod, high, low, close, wicks = false) {
684
675
  * ```
685
676
  */
686
677
  export function rma(source, length) {
678
+ checkPositiveLength('rma', length, 0);
687
679
  const result = [];
688
680
  // Floor the length to match PineScript's auto-truncation of float to int
689
681
  const len = Math.floor(length);
@@ -763,6 +755,8 @@ export function wma(source, length) {
763
755
  const result = [];
764
756
  // Floor the length to match PineScript's auto-truncation of float to int
765
757
  const fixed = typeof length === 'number' ? Math.floor(length) : NaN;
758
+ if (typeof length === 'number')
759
+ checkPositiveLength('wma', fixed, 0);
766
760
  // PineScript: na on a bar with an na source (also +/-Infinity) and until `len` non-na values were received;
767
761
  // otherwise the last `len` bars, each na replaced by the previous non-na value
768
762
  const filled = [];
@@ -778,6 +772,8 @@ export function wma(source, length) {
778
772
  filled.push(last);
779
773
  // a series length: the window of this bar's length (the result of ta.wma with that fixed length)
780
774
  const len = typeof length === 'number' ? fixed : Math.floor(length[i]);
775
+ if (typeof length !== 'number')
776
+ checkPositiveLength('wma', len, i);
781
777
  if (isNa || !(len >= 1) || count < len) {
782
778
  result.push(NaN);
783
779
  continue;
@@ -937,6 +933,7 @@ function monotonic(source, length, step) {
937
933
  * ```
938
934
  */
939
935
  export function rising(source, length) {
936
+ checkPositiveLength('rising', length, 0);
940
937
  return monotonic(source, length, gt);
941
938
  }
942
939
  /**
@@ -961,6 +958,7 @@ export function rising(source, length) {
961
958
  * ```
962
959
  */
963
960
  export function falling(source, length) {
961
+ checkPositiveLength('falling', length, 0);
964
962
  return monotonic(source, length, lt);
965
963
  }
966
964
  /**
@@ -985,6 +983,7 @@ export function falling(source, length) {
985
983
  * ```
986
984
  */
987
985
  export function roc(source, length) {
986
+ checkPositiveLength('roc', length, 0);
988
987
  const result = [];
989
988
  for (let i = 0; i < source.length; i++) {
990
989
  if (i < length) {
@@ -1024,6 +1023,7 @@ export function roc(source, length) {
1024
1023
  * ```
1025
1024
  */
1026
1025
  export function mom(source, length) {
1026
+ checkPositiveLength('mom', length, 0);
1027
1027
  const result = [];
1028
1028
  for (let i = 0; i < source.length; i++) {
1029
1029
  if (i < length) {
@@ -1062,6 +1062,7 @@ export function mom(source, length) {
1062
1062
  * ```
1063
1063
  */
1064
1064
  export function dev(source, length) {
1065
+ checkPositiveLength('dev', length, 0);
1065
1066
  const len = Math.floor(length);
1066
1067
  return sma(source, len).map((mean, i) => {
1067
1068
  if (i < len - 1 || Number.isNaN(mean))
@@ -1136,6 +1137,7 @@ function varianceOf(source, length, biased, name) {
1136
1137
  * ```
1137
1138
  */
1138
1139
  export function median(source, length) {
1140
+ checkPositiveLength('median', length, 0);
1139
1141
  const len = Math.floor(length);
1140
1142
  const n = source.length;
1141
1143
  const out = new Array(n).fill(NaN);
@@ -1224,6 +1226,7 @@ export function swma(source) {
1224
1226
  * ```
1225
1227
  */
1226
1228
  export function vwma(source, length, volume) {
1229
+ checkPositiveLength('vwma->sma', length, 0);
1227
1230
  if (!volume) {
1228
1231
  throw new Error('ta.vwma() requires volume series. ' +
1229
1232
  'Either pass it explicitly or use createContext({ chart: { ..., volume } }) for implicit data.');
@@ -1271,6 +1274,7 @@ export function vwma(source, length, volume) {
1271
1274
  * ```
1272
1275
  */
1273
1276
  export function linreg(source, length, offset = 0) {
1277
+ checkPositiveLength('linreg', length, 0);
1274
1278
  const result = [];
1275
1279
  // Floor the length to match PineScript's auto-truncation of float to int
1276
1280
  const len = Math.floor(length);
@@ -1338,6 +1342,7 @@ export function linreg(source, length, offset = 0) {
1338
1342
  * ```
1339
1343
  */
1340
1344
  export function correlation(source1, source2, length) {
1345
+ checkPositiveLength('correlation', length, 0);
1341
1346
  // PineScript: from compensated running sums, cov = sma(x * y) - sma(x) * sma(y), r = cov / sqrt(var(x) * var(y))
1342
1347
  // (biased variances); each running sum skips its own na values
1343
1348
  const product = Array.from({ length: source1.length }, (_, i) => {
@@ -1377,8 +1382,8 @@ export function correlation(source1, source2, length) {
1377
1382
  *
1378
1383
  * PineScript rules:
1379
1384
  * - 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
1385
+ * - a value from bar `length` on; an `na` value in the window counts as not `<=` the current value, and an `na`
1386
+ * current value gives 0 (#177)
1382
1387
  * - the comparison uses the 1e-10 tolerance
1383
1388
  *
1384
1389
  * @example
@@ -1389,14 +1394,15 @@ export function correlation(source1, source2, length) {
1389
1394
  * ```
1390
1395
  */
1391
1396
  export function percentrank(source, length) {
1397
+ checkPositiveLength('percentrank', length, 0);
1392
1398
  const result = [];
1393
1399
  for (let i = 0; i < source.length; i++) {
1394
1400
  const currentValue = source[i];
1395
- if (i < length || Number.isNaN(currentValue)) {
1401
+ if (i < length) {
1396
1402
  result.push(NaN);
1397
1403
  continue;
1398
1404
  }
1399
- // le() is false for an na value in the window
1405
+ // le() is false for an na value in the window, and for all of them when the current value is na (0)
1400
1406
  let countLessOrEqual = 0;
1401
1407
  for (let j = 1; j <= length; j++) {
1402
1408
  if (le(source[i - j], currentValue))
@@ -1435,6 +1441,7 @@ export function percentrank(source, length) {
1435
1441
  * \`\`\`
1436
1442
  */
1437
1443
  export function cci(source, length) {
1444
+ checkPositiveLength('cci', length, 0);
1438
1445
  const smaValues = sma(source, length);
1439
1446
  const devValues = dev(source, length);
1440
1447
  let last = 0;
@@ -1472,6 +1479,7 @@ 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);
@@ -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);
@@ -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;
@@ -2255,6 +2271,7 @@ export function kcw(source, length = 20, mult = 2, useTrueRange = true, high, lo
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;
@@ -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)
@@ -2371,6 +2389,7 @@ export function cog(source, length = 10) {
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
@@ -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.