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
package/src/ta/index.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  import type { series_float, series_bool, series_int, int, Source, simple_int, simple_float, simple_bool } from '../types/index.js';
12
12
  import { eq, ge, gt, le, lt } from '../compare/index.js';
13
13
  import { runningSum, runningSumSeries, runningVariance, runningVarianceSeries } from './running-sum.js';
14
- import { checkSumLength, checkWindowLength } from './limits.js';
14
+ import { checkPositiveLength, checkSumLength, checkWindowLength } from './limits.js';
15
15
 
16
16
  /**
17
17
  * Simple Moving Average - returns the moving average (sum of last y values divided by y).
@@ -68,6 +68,7 @@ export function sma(source: Source, length: simple_int | ArrayLike<number>): ser
68
68
  * ```
69
69
  */
70
70
  export function ema(source: Source, length: simple_int): series_float {
71
+ checkPositiveLength('ema', length, 0);
71
72
  const result: series_float = [];
72
73
  const len = Math.floor(length);
73
74
  const multiplier = 2 / (len + 1);
@@ -133,7 +134,25 @@ export function ema(source: Source, length: simple_int): series_float {
133
134
  * const oversold = rsi14.map(v => v < 30);
134
135
  * ```
135
136
  */
136
- export function rsi(source: Source, length: simple_int): series_float {
137
+ export function rsi(source: Source, length: simple_int): series_float;
138
+ /**
139
+ * PineScript v4 `rsi(x, y)` with two series (#180): `x` and `y` are the averages of the upward and downward changes,
140
+ * already computed. As in PineScript: 100 when `y` is within 1e-10 of zero (also with an na `x`), else 0 when `x` is,
141
+ * else `100 - 100 / (1 + x / y)`, na when that divides by zero (`x == -y`). Versions 5 and 6 have the length form only.
142
+ */
143
+ export function rsi(x: Source, y: Source): series_float;
144
+ export function rsi(source: Source, length: simple_int | Source): series_float {
145
+ if (typeof length !== 'number') {
146
+ return Array.from(source, (xv, i) => {
147
+ const x = xv ?? NaN;
148
+ const y = length[i] ?? NaN;
149
+ if (eq(y, 0)) return 100;
150
+ if (eq(x, 0)) return 0;
151
+ const r = 100 - 100 / (1 + x / y);
152
+ return Number.isFinite(r) ? r : NaN;
153
+ });
154
+ }
155
+ checkPositiveLength('rsi', length, 0, 'y');
137
156
  const result: series_float = [];
138
157
 
139
158
  // Calculate price changes
@@ -506,6 +525,7 @@ export function crossunder(series1: Source, series2: Source): series_bool {
506
525
  * ```
507
526
  */
508
527
  export function change(source: Source, length: simple_int = 1): series_float {
528
+ checkPositiveLength('change', length, 0);
509
529
  const result: series_float = [];
510
530
 
511
531
  for (let i = 0; i < source.length; i++) {
@@ -618,6 +638,7 @@ export function tr(
618
638
  * ```
619
639
  */
620
640
  export function atr(length: simple_int, high?: Source, low?: Source, close?: Source): series_float {
641
+ checkPositiveLength('atr->rma', length, 0);
621
642
  if (!high || !low || !close) {
622
643
  throw new Error(
623
644
  'ta.atr() requires high, low, and close series. ' +
@@ -763,6 +784,7 @@ export function supertrend(
763
784
  * ```
764
785
  */
765
786
  export function rma(source: Source, length: simple_int): series_float {
787
+ checkPositiveLength('rma', length, 0);
766
788
  const result: series_float = [];
767
789
  // Floor the length to match PineScript's auto-truncation of float to int
768
790
  const len = Math.floor(length);
@@ -846,6 +868,7 @@ export function wma(source: Source, length: simple_int | ArrayLike<number>): ser
846
868
  const result: series_float = [];
847
869
  // Floor the length to match PineScript's auto-truncation of float to int
848
870
  const fixed = typeof length === 'number' ? Math.floor(length) : NaN;
871
+ if (typeof length === 'number') checkPositiveLength('wma', fixed, 0);
849
872
  // PineScript: na on a bar with an na source (also +/-Infinity) and until `len` non-na values were received;
850
873
  // otherwise the last `len` bars, each na replaced by the previous non-na value
851
874
  const filled: number[] = [];
@@ -861,6 +884,7 @@ export function wma(source: Source, length: simple_int | ArrayLike<number>): ser
861
884
  filled.push(last);
862
885
  // a series length: the window of this bar's length (the result of ta.wma with that fixed length)
863
886
  const len = typeof length === 'number' ? fixed : Math.floor(length[i]!);
887
+ if (typeof length !== 'number') checkPositiveLength('wma', len, i);
864
888
  if (isNa || !(len >= 1) || count < len) {
865
889
  result.push(NaN);
866
890
  continue;
@@ -1022,6 +1046,7 @@ function monotonic(source: Source, length: simple_int, step: (newer: number, old
1022
1046
  * ```
1023
1047
  */
1024
1048
  export function rising(source: Source, length: simple_int): series_bool {
1049
+ checkPositiveLength('rising', length, 0);
1025
1050
  return monotonic(source, length, gt);
1026
1051
  }
1027
1052
 
@@ -1047,6 +1072,7 @@ export function rising(source: Source, length: simple_int): series_bool {
1047
1072
  * ```
1048
1073
  */
1049
1074
  export function falling(source: Source, length: simple_int): series_bool {
1075
+ checkPositiveLength('falling', length, 0);
1050
1076
  return monotonic(source, length, lt);
1051
1077
  }
1052
1078
 
@@ -1072,6 +1098,7 @@ export function falling(source: Source, length: simple_int): series_bool {
1072
1098
  * ```
1073
1099
  */
1074
1100
  export function roc(source: Source, length: simple_int): series_float {
1101
+ checkPositiveLength('roc', length, 0);
1075
1102
  const result: series_float = [];
1076
1103
 
1077
1104
  for (let i = 0; i < source.length; i++) {
@@ -1112,6 +1139,7 @@ export function roc(source: Source, length: simple_int): series_float {
1112
1139
  * ```
1113
1140
  */
1114
1141
  export function mom(source: Source, length: simple_int): series_float {
1142
+ checkPositiveLength('mom', length, 0);
1115
1143
  const result: series_float = [];
1116
1144
 
1117
1145
  for (let i = 0; i < source.length; i++) {
@@ -1151,6 +1179,7 @@ export function mom(source: Source, length: simple_int): series_float {
1151
1179
  * ```
1152
1180
  */
1153
1181
  export function dev(source: Source, length: simple_int): series_float {
1182
+ checkPositiveLength('dev', length, 0);
1154
1183
  const len = Math.floor(length);
1155
1184
  return sma(source, len).map((mean, i) => {
1156
1185
  if (i < len - 1 || Number.isNaN(mean)) return NaN;
@@ -1234,6 +1263,7 @@ function varianceOf(
1234
1263
  * ```
1235
1264
  */
1236
1265
  export function median(source: Source, length: simple_int): series_float {
1266
+ checkPositiveLength('median', length, 0);
1237
1267
  const len = Math.floor(length);
1238
1268
  const n = source.length;
1239
1269
  const out: series_float = new Array<number>(n).fill(NaN);
@@ -1321,6 +1351,7 @@ export function swma(source: Source): series_float {
1321
1351
  * ```
1322
1352
  */
1323
1353
  export function vwma(source: Source, length: simple_int, volume?: Source): series_float {
1354
+ checkPositiveLength('vwma->sma', length, 0);
1324
1355
  if (!volume) {
1325
1356
  throw new Error(
1326
1357
  'ta.vwma() requires volume series. ' +
@@ -1374,6 +1405,7 @@ export function vwma(source: Source, length: simple_int, volume?: Source): serie
1374
1405
  * ```
1375
1406
  */
1376
1407
  export function linreg(source: Source, length: simple_int, offset: simple_int = 0): series_float {
1408
+ checkPositiveLength('linreg', length, 0);
1377
1409
  const result: series_float = [];
1378
1410
  // Floor the length to match PineScript's auto-truncation of float to int
1379
1411
  const len = Math.floor(length);
@@ -1443,6 +1475,7 @@ export function linreg(source: Source, length: simple_int, offset: simple_int =
1443
1475
  * ```
1444
1476
  */
1445
1477
  export function correlation(source1: Source, source2: Source, length: simple_int): series_float {
1478
+ checkPositiveLength('correlation', length, 0);
1446
1479
  // PineScript: from compensated running sums, cov = sma(x * y) - sma(x) * sma(y), r = cov / sqrt(var(x) * var(y))
1447
1480
  // (biased variances); each running sum skips its own na values
1448
1481
  const product = Array.from({ length: source1.length }, (_, i) => {
@@ -1481,8 +1514,8 @@ export function correlation(source1: Source, source2: Source, length: simple_int
1481
1514
  *
1482
1515
  * PineScript rules:
1483
1516
  * - percentrank = count(source[k] <= source, k = 1..length) * 100 / length (in this order: the PineScript bits)
1484
- * - a value from bar `length` on, when the current source is not `na`; an `na` value in the window counts as not
1485
- * `<=` the current value
1517
+ * - a value from bar `length` on; an `na` value in the window counts as not `<=` the current value, and an `na`
1518
+ * current value gives 0 (#177)
1486
1519
  * - the comparison uses the 1e-10 tolerance
1487
1520
  *
1488
1521
  * @example
@@ -1493,15 +1526,16 @@ export function correlation(source1: Source, source2: Source, length: simple_int
1493
1526
  * ```
1494
1527
  */
1495
1528
  export function percentrank(source: Source, length: simple_int): series_float {
1529
+ checkPositiveLength('percentrank', length, 0);
1496
1530
  const result: series_float = [];
1497
1531
 
1498
1532
  for (let i = 0; i < source.length; i++) {
1499
1533
  const currentValue = source[i]!;
1500
- if (i < length || Number.isNaN(currentValue)) {
1534
+ if (i < length) {
1501
1535
  result.push(NaN);
1502
1536
  continue;
1503
1537
  }
1504
- // le() is false for an na value in the window
1538
+ // le() is false for an na value in the window, and for all of them when the current value is na (0)
1505
1539
  let countLessOrEqual = 0;
1506
1540
  for (let j = 1; j <= length; j++) {
1507
1541
  if (le(source[i - j]!, currentValue)) countLessOrEqual++;
@@ -1541,6 +1575,7 @@ export function percentrank(source: Source, length: simple_int): series_float {
1541
1575
  * \`\`\`
1542
1576
  */
1543
1577
  export function cci(source: Source, length: simple_int): series_float {
1578
+ checkPositiveLength('cci', length, 0);
1544
1579
  const smaValues = sma(source, length);
1545
1580
  const devValues = dev(source, length);
1546
1581
  let last = 0;
@@ -1577,6 +1612,7 @@ export function cci(source: Source, length: simple_int): series_float {
1577
1612
  * \`\`\`
1578
1613
  */
1579
1614
  export function stoch(source: Source, high: Source, low: Source, length: simple_int): series_float {
1615
+ checkPositiveLength('stoch->highest', length, 0);
1580
1616
  const result: series_float = [];
1581
1617
  const lowestValues = lowest(low, length);
1582
1618
  const highestValues = highest(high, length);
@@ -1629,6 +1665,7 @@ export function stoch(source: Source, high: Source, low: Source, length: simple_
1629
1665
  * ```
1630
1666
  */
1631
1667
  export function mfi(source: Source, length: simple_int, volume?: Source): series_float {
1668
+ checkPositiveLength('mfi', length, 0);
1632
1669
  if (!volume) {
1633
1670
  throw new Error(
1634
1671
  'ta.mfi() requires volume series. ' +
@@ -1687,6 +1724,7 @@ export function mfi(source: Source, length: simple_int, volume?: Source): series
1687
1724
  * ```
1688
1725
  */
1689
1726
  export function hma(source: Source, length: simple_int): series_float {
1727
+ checkPositiveLength('hma', length, 0);
1690
1728
  const halfLength = Math.floor(length / 2);
1691
1729
  const sqrtLength = Math.floor(Math.sqrt(length));
1692
1730
 
@@ -2093,6 +2131,7 @@ export function dmi(
2093
2131
  * ```
2094
2132
  */
2095
2133
  export function tsi(source: Source, shortLength: simple_int, longLength: simple_int): series_float {
2134
+ checkPositiveLength('tsi->ema', shortLength, 0);
2096
2135
  const momentum: series_float = [];
2097
2136
 
2098
2137
  // Calculate momentum (price change)
@@ -2142,6 +2181,7 @@ export function tsi(source: Source, shortLength: simple_int, longLength: simple_
2142
2181
  * ```
2143
2182
  */
2144
2183
  export function cmo(source: Source, length: simple_int): series_float {
2184
+ checkPositiveLength('cmo', length, 0);
2145
2185
  // PineScript definition: mom = ta.change(src), sm1 = math.sum(mom >= 0 ? mom : 0, length),
2146
2186
  // sm2 = math.sum(mom >= 0 ? 0 : -mom, length), cmo = 100 * (sm1 - sm2) / (sm1 + sm2), with the running sums of
2147
2187
  // math.sum (bit for bit). A comparison with na is false: an na change adds 0 to sm1 and na (skipped) to sm2.
@@ -2191,6 +2231,7 @@ export function kc(
2191
2231
  low?: Source,
2192
2232
  close?: Source
2193
2233
  ): [series_float, series_float, series_float] {
2234
+ checkPositiveLength('kc', length, 0);
2194
2235
  // Calculate middle band (EMA of source)
2195
2236
  const middle = ema(source, length);
2196
2237
 
@@ -2250,6 +2291,7 @@ export function kc(
2250
2291
  * ```
2251
2292
  */
2252
2293
  export function bbw(source: Source, length: simple_int, mult: simple_float): series_float {
2294
+ checkPositiveLength('bbw', length, 0);
2253
2295
  const [basis, upper, lower] = bb(source, length, mult);
2254
2296
  const result: series_float = [];
2255
2297
 
@@ -2293,6 +2335,7 @@ export function bbw(source: Source, length: simple_int, mult: simple_float): ser
2293
2335
  * ```
2294
2336
  */
2295
2337
  export function wpr(high: Source, low: Source, close: Source, length: simple_int = 14): series_float {
2338
+ checkPositiveLength('wpr', length, 0);
2296
2339
  const highs = highest(high, length);
2297
2340
  const lows = lowest(low, length);
2298
2341
  return Array.from({ length: close.length }, (_, i) => {
@@ -2393,6 +2436,7 @@ export function alma(
2393
2436
  sigma: simple_float = 6,
2394
2437
  floor: boolean = false
2395
2438
  ): series_float {
2439
+ checkPositiveLength('alma', length, 0);
2396
2440
  const result: series_float = [];
2397
2441
  const m = floor ? Math.floor(offset * (length - 1)) : offset * (length - 1);
2398
2442
  const s = length / sigma;
@@ -2489,6 +2533,7 @@ export function kcw(
2489
2533
  * values), and a bar whose own value is `na` still gets a result.
2490
2534
  */
2491
2535
  export function range(source: Source, length: simple_int): series_float {
2536
+ checkPositiveLength('range', length, 0);
2492
2537
  return source.map((_, i) => {
2493
2538
  const values = lastValues(source, i, length);
2494
2539
  return values ? Math.max(...values) - Math.min(...values) : NaN;
@@ -2574,6 +2619,7 @@ export function min(source: Source): series_float {
2574
2619
  * ```
2575
2620
  */
2576
2621
  export function cog(source: Source, length: simple_int = 10): series_float {
2622
+ checkPositiveLength('cog', length, 0);
2577
2623
  const len = Math.floor(length);
2578
2624
  return runningSum(source, len).map((sum, i) => {
2579
2625
  if (i < len - 1 || Number.isNaN(sum) || sum === 0) return NaN;
@@ -2607,6 +2653,7 @@ export function cog(source: Source, length: simple_int = 10): series_float {
2607
2653
  * - If no mode exists, returns the smallest value
2608
2654
  */
2609
2655
  export function mode(source: Source, length: simple_int): series_float {
2656
+ checkPositiveLength('mode', length, 0);
2610
2657
  const result: series_float = [];
2611
2658
  const len = Math.floor(length);
2612
2659
  const seen: number[] = []; // non-na values so far
@@ -2684,6 +2731,7 @@ export function percentile_linear_interpolation(
2684
2731
  length: simple_int,
2685
2732
  percentage: number
2686
2733
  ): series_float {
2734
+ checkPositiveLength('percentile_linear_interpolation', length, 0);
2687
2735
  return sortedWindow(source, length, (s) => {
2688
2736
  const pos = (percentage / 100) * length - 0.5;
2689
2737
  if (pos <= 0) return s[0]!;
@@ -2753,6 +2801,7 @@ export function percentile_nearest_rank(
2753
2801
  length: simple_int,
2754
2802
  percentage: number
2755
2803
  ): series_float {
2804
+ checkPositiveLength('percentile_nearest_rank', length, 0);
2756
2805
  const rank = Math.min(length, Math.max(1, Math.ceil((percentage / 100) * length)));
2757
2806
  return sortedWindow(source, length, (s) => s[rank - 1]!);
2758
2807
  }
@@ -2789,6 +2838,7 @@ export function percentile_nearest_rank(
2789
2838
  * where the `na` was
2790
2839
  */
2791
2840
  export function rci(source: Source, length: simple_int): series_float {
2841
+ checkPositiveLength('rci', length, 0);
2792
2842
  const len = Math.floor(length);
2793
2843
  const result: series_float = [];
2794
2844
  // PineScript keeps the values in a ring of `length + 1` slots indexed by the bar index, filled with 0 at the start
@@ -2852,31 +2902,42 @@ export function rci(source: Source, length: simple_int): series_float {
2852
2902
  }
2853
2903
 
2854
2904
  /**
2855
- * Pivot Point Levels
2905
+ * Pivot Point Levels (PineScript `ta.pivot_point_levels`).
2856
2906
  *
2857
- * Calculates pivot point levels using various calculation methods.
2858
- * Returns an array containing: [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5]
2907
+ * Returns 11 series: [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5] (one value per bar in each).
2859
2908
  *
2860
2909
  * @param type - Calculation type: "Traditional", "Fibonacci", "Woodie", "Classic", "DM", "Camarilla"
2861
- * @param anchor - Condition that triggers reset of calculations
2862
- * @param developing - If true, pivots recalculate continuously; if false, use last anchor values
2863
- * @param high - High price series (optional, uses context if not provided)
2864
- * @param low - Low price series (optional, uses context if not provided)
2865
- * @param close - Close price series (optional, uses context if not provided)
2866
- * @param open - Open price series (optional, uses context if not provided)
2867
- * @returns Array of 11 pivot levels
2910
+ * @param anchor - Condition that starts a new period (`timeframe.change("W")`...)
2911
+ * @param developing - false: the levels of the previous period, constant until the next anchor; true: the levels of
2912
+ * the current period so far, recalculated on each bar
2913
+ * @param high - High price series
2914
+ * @param low - Low price series
2915
+ * @param close - Close price series
2916
+ * @param open - Open price series (Woodie and DM)
2917
+ * @returns 11 series of levels
2868
2918
  *
2869
2919
  * @example
2870
2920
  * ```typescript
2871
2921
  * const weekChange = [false, false, false, false, true, false, ...]; // Weekly anchor
2872
- * const pivots = ta.pivot_point_levels("Traditional", weekChange, false, high, low, close);
2873
- * // pivots[i]! = [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5]
2922
+ * const levels = ta.pivot_point_levels("Traditional", weekChange, false, high, low, close, open);
2923
+ * // levels[0]![i] is P on bar i, levels[5]![i] is R3
2874
2924
  * ```
2875
2925
  *
2876
2926
  * @remarks
2877
- * - Woodie type cannot use developing=true (will error in PineScript)
2878
- * - DM type only calculates P, R1, S1 (other levels are NaN)
2879
- * - All calculations follow PineScript v6 specifications
2927
+ * As in PineScript (#177):
2928
+ * - a period runs from an anchor bar to the bar before the next anchor; the bars before the first anchor are a
2929
+ * period too (from bar 0). The fixed levels use the high and low of the previous period and the close of the bar
2930
+ * before the anchor (na with an anchor on bar 0), and are na until the first anchor; the developing levels use
2931
+ * the current period's high and low and the bar's close, from bar 0
2932
+ * - 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),
2933
+ * R5 = P * 4 + (H - 4 * L), S5 = P * 4 - (4 * H - L)
2934
+ * - Fibonacci: P +- 0.382 / 0.618 / 1 * (H - L); R4 to S5 na
2935
+ * - Classic: R3 / S3 = P +- 2 * (H - L), R4 / S4 = P +- 3 * (H - L); R5 / S5 na
2936
+ * - Woodie: P = (H + L + 2 * open of the current period) / 4, R3 = H + 2 * (P - L), S3 = L - 2 * (H - P), R4 / S4 =
2937
+ * R3 + (H - L) / S3 - (H - L); R5 / S5 na. `developing` true throws (RE10128)
2938
+ * - 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,
2939
+ * R1 = x / 2 - L, S1 = x / 2 - H; the other levels na
2940
+ * - Camarilla: C +- (H - L) * 1.1 / 12, / 6, / 4, / 2 for R1 to S4, R5 = (H / L) * C, S5 = 2 * C - R5
2880
2941
  */
2881
2942
  export function pivot_point_levels(
2882
2943
  type: series_float | string,
@@ -2887,183 +2948,96 @@ export function pivot_point_levels(
2887
2948
  close?: Source,
2888
2949
  open?: Source
2889
2950
  ): series_float[] {
2890
- // For simplicity, we need to handle the case where type is a string array
2891
- // But according to the docs, type is "series string", meaning it can change per bar
2892
- // For this implementation, we'll support constant type strings
2893
-
2894
2951
  const typeStr = typeof type === 'string' ? type : String(type[0]);
2895
- const isDeveloping = typeof developing === 'boolean' ? developing : developing[0];
2896
-
2897
- // Woodie cannot be developing
2952
+ const isDeveloping = typeof developing === 'boolean' ? developing : Boolean(developing[0]);
2898
2953
  if (typeStr === 'Woodie' && isDeveloping) {
2899
- throw new Error('ta.pivot_point_levels: Woodie type cannot use developing=true');
2954
+ throw new Error(
2955
+ 'Error on bar 0: The `developing` parameter of the `ta.pivot_point_levels()` cannot be `true` when `type` is "Woodie".'
2956
+ );
2900
2957
  }
2901
-
2902
- // Ensure all series have the same length
2903
- const length = anchor.length;
2904
- if (high && high.length !== length) throw new Error('High series length mismatch');
2905
- if (low && low.length !== length) throw new Error('Low series length mismatch');
2906
- if (close && close.length !== length) throw new Error('Close series length mismatch');
2907
- if (open && open.length !== length) throw new Error('Open series length mismatch');
2908
-
2909
- // Initialize result arrays for all 11 levels
2910
- const results: series_float[] = Array.from({ length: 11 }, () => []);
2911
-
2912
- // Track the last anchor point data
2913
- let lastH = NaN, lastL = NaN, lastC = NaN, lastO = NaN;
2914
- let lastAnchorIndex = -1;
2915
-
2916
- for (let i = 0; i < length; i++) {
2917
- // Check if anchor triggered
2918
- if (anchor[i]!) {
2919
- lastAnchorIndex = i;
2920
- // Store OHLC at anchor point (these will be used for calculations)
2921
- lastH = high ? high[i]! : NaN;
2922
- lastL = low ? low[i]! : NaN;
2923
- lastC = close ? close[i]! : NaN;
2924
- lastO = open ? open[i]! : NaN;
2925
- }
2926
-
2927
- // Calculate data to use
2928
- let h: number, l: number, c: number, o: number;
2929
-
2930
- if (isDeveloping && lastAnchorIndex >= 0) {
2931
- // Developing: use max/min/last since anchor
2932
- h = high ? Math.max(...high.slice(lastAnchorIndex, i + 1).filter(v => !isNaN(v))) : NaN;
2933
- l = low ? Math.min(...low.slice(lastAnchorIndex, i + 1).filter(v => !isNaN(v))) : NaN;
2934
- c = close ? close[i]! : NaN;
2935
- o = open && lastAnchorIndex >= 0 ? open[lastAnchorIndex]! : NaN;
2936
- } else {
2937
- // Not developing: use last anchor values
2938
- h = lastH;
2939
- l = lastL;
2940
- c = lastC;
2941
- o = lastO;
2942
- }
2943
-
2944
- // Calculate pivot levels based on type
2945
- const levels = calculatePivotLevels(typeStr, h, l, c, o);
2946
-
2947
- // Push to results
2948
- for (let j = 0; j < 11; j++) {
2949
- results[j]!.push(levels[j]!);
2950
- }
2958
+ const n = anchor.length;
2959
+ for (const [s, name] of [[high, 'High'], [low, 'Low'], [close, 'Close'], [open, 'Open']] as const) {
2960
+ if (s && s.length !== n) throw new Error(`${name} series length mismatch`);
2961
+ }
2962
+ const at = (s: Source | undefined, i: number): number => (s ? (s[i] ?? NaN) : NaN);
2963
+ const results: series_float[] = Array.from({ length: 11 }, () => new Array<number>(n));
2964
+ // the current period (from bar 0 until the first anchor) and the previous one
2965
+ let curH = NaN, curL = NaN, curO = NaN;
2966
+ let prevH = NaN, prevL = NaN, prevC = NaN, prevO = NaN;
2967
+ let anchored = false;
2968
+ for (let i = 0; i < n; i++) {
2969
+ const h = at(high, i), l = at(low, i);
2970
+ if (i === 0) {
2971
+ // the first period starts on bar 0: an anchor there makes bar 0 its own previous period
2972
+ curH = h;
2973
+ curL = l;
2974
+ curO = at(open, i);
2975
+ }
2976
+ if (anchor[i]) {
2977
+ prevH = curH;
2978
+ prevL = curL;
2979
+ prevC = i > 0 ? at(close, i - 1) : NaN;
2980
+ prevO = curO;
2981
+ curH = h;
2982
+ curL = l;
2983
+ curO = at(open, i);
2984
+ anchored = true;
2985
+ } else if (i > 0) {
2986
+ if (h > curH || Number.isNaN(curH)) curH = h;
2987
+ if (l < curL || Number.isNaN(curL)) curL = l;
2988
+ }
2989
+ const levels = isDeveloping
2990
+ ? pivotLevels(typeStr, curH, curL, at(close, i), curO, at(close, i))
2991
+ : anchored
2992
+ ? pivotLevels(typeStr, prevH, prevL, prevC, prevO, curO)
2993
+ : NA_LEVELS;
2994
+ for (let k = 0; k < 11; k++) results[k]![i] = levels[k]!;
2951
2995
  }
2952
-
2953
2996
  return results;
2954
2997
  }
2955
2998
 
2999
+ const NA_LEVELS: readonly number[] = new Array<number>(11).fill(NaN);
3000
+
2956
3001
  /**
2957
- * Helper function to calculate pivot levels
3002
+ * The 11 levels of one bar, in PineScript's operation order (bit for bit). `o` is the open of the period (DM),
3003
+ * `wo` the close term of Woodie (the open of the current period, or the bar's close while developing).
2958
3004
  */
2959
- function calculatePivotLevels(
2960
- type: string,
2961
- h: number,
2962
- l: number,
2963
- c: number,
2964
- o: number
2965
- ): number[] {
2966
- // Return NaN array if data is insufficient
2967
- if (isNaN(h) || isNaN(l) || isNaN(c)) {
2968
- return Array(11).fill(NaN);
2969
- }
2970
-
2971
- const levels: number[] = Array(11).fill(NaN);
2972
-
2973
- // Calculate pivot point (P)
2974
- let P: number;
2975
-
2976
- switch (type) {
2977
- case 'Traditional':
2978
- case 'Fibonacci':
2979
- case 'Classic':
2980
- P = (h + l + c) / 3;
2981
- break;
2982
- case 'Woodie':
2983
- P = (h + l + 2 * c) / 4;
2984
- break;
2985
- case 'DM':
2986
- P = (h + l + c) / 3;
2987
- break;
2988
- case 'Camarilla':
2989
- P = (h + l + c) / 3;
2990
- break;
2991
- default:
2992
- P = (h + l + c) / 3;
2993
- }
2994
-
2995
- levels[0] = P; // P is at index 0
2996
-
2997
- // Calculate resistance and support levels based on type
3005
+ function pivotLevels(type: string, h: number, l: number, c: number, o: number, wo: number): readonly number[] {
3006
+ const N = NaN;
3007
+ const r = h - l;
2998
3008
  switch (type) {
2999
- case 'Traditional':
3000
- case 'Classic':
3001
- levels[1] = 2 * P - l; // R1
3002
- levels[2] = 2 * P - h; // S1
3003
- levels[3] = P + (h - l); // R2
3004
- levels[4] = P - (h - l); // S2
3005
- levels[5] = h + 2 * (P - l); // R3
3006
- levels[6] = l - 2 * (h - P); // S3
3007
- levels[7] = levels[5] + (h - l); // R4
3008
- levels[8] = levels[6] - (h - l); // S4
3009
- levels[9] = levels[7] + (h - l); // R5
3010
- levels[10] = levels[8] - (h - l); // S5
3011
- break;
3012
-
3013
- case 'Fibonacci':
3014
- levels[1] = P + 0.382 * (h - l); // R1
3015
- levels[2] = P - 0.382 * (h - l); // S1
3016
- levels[3] = P + 0.618 * (h - l); // R2
3017
- levels[4] = P - 0.618 * (h - l); // S2
3018
- levels[5] = P + (h - l); // R3
3019
- levels[6] = P - (h - l); // S3
3020
- levels[7] = levels[5] + 0.618 * (h - l); // R4
3021
- levels[8] = levels[6] - 0.618 * (h - l); // S4
3022
- levels[9] = levels[7] + 0.382 * (h - l); // R5
3023
- levels[10] = levels[8] - 0.382 * (h - l); // S5
3024
- break;
3025
-
3026
- case 'Woodie':
3027
- levels[1] = 2 * P - l; // R1
3028
- levels[2] = 2 * P - h; // S1
3029
- levels[3] = P + (h - l); // R2
3030
- levels[4] = P - (h - l); // S2
3031
- levels[5] = h + 2 * (P - l); // R3
3032
- levels[6] = l - 2 * (h - P); // S3
3033
- levels[7] = levels[5] + (h - l); // R4
3034
- levels[8] = levels[6] - (h - l); // S4
3035
- levels[9] = levels[7] + (h - l); // R5
3036
- levels[10] = levels[8] - (h - l); // S5
3037
- break;
3038
-
3009
+ case 'Traditional': {
3010
+ const p = (h + l + c) / 3;
3011
+ 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),
3012
+ p * 3 - (3 * h - l), p * 4 + (h - 4 * l), p * 4 - (4 * h - l)];
3013
+ }
3014
+ case 'Fibonacci': {
3015
+ const p = (h + l + c) / 3;
3016
+ 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];
3017
+ }
3018
+ case 'Classic': {
3019
+ const p = (h + l + c) / 3;
3020
+ 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];
3021
+ }
3022
+ case 'Woodie': {
3023
+ const p = (h + l + 2 * wo) / 4;
3024
+ const r3 = h + 2 * (p - l);
3025
+ const s3 = l - 2 * (h - p);
3026
+ return [p, 2 * p - l, 2 * p - h, p + r, p - r, r3, s3, r3 + r, s3 - r, N, N];
3027
+ }
3039
3028
  case 'DM': {
3040
- // DM (Demark) only calculates P, R1, S1
3041
- const x = h + l + (c * 2) + (isNaN(o) ? c : o);
3042
- const newP = x / (isNaN(o) ? 4 : 5);
3043
- levels[0] = newP;
3044
- levels[1] = x / 2 - l; // R1
3045
- levels[2] = x / 2 - h; // S1
3046
- // R2-S5 remain NaN
3047
- break;
3029
+ const x = c < o ? h + 2 * l + c : c > o ? 2 * h + l + c : h + l + 2 * c;
3030
+ return [x / 4, x / 2 - l, x / 2 - h, N, N, N, N, N, N, N, N];
3048
3031
  }
3049
-
3050
3032
  case 'Camarilla': {
3051
- const range = h - l;
3052
- levels[1] = c + range * 1.1 / 12; // R1
3053
- levels[2] = c - range * 1.1 / 12; // S1
3054
- levels[3] = c + range * 1.1 / 6; // R2
3055
- levels[4] = c - range * 1.1 / 6; // S2
3056
- levels[5] = c + range * 1.1 / 4; // R3
3057
- levels[6] = c - range * 1.1 / 4; // S3
3058
- levels[7] = c + range * 1.1 / 2; // R4
3059
- levels[8] = c - range * 1.1 / 2; // S4
3060
- levels[9] = h; // R5 (high)
3061
- levels[10] = l; // S5 (low)
3062
- break;
3033
+ const p = (h + l + c) / 3;
3034
+ const r5 = (h / l) * c;
3035
+ 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,
3036
+ c - (r * 1.1) / 4, c + (r * 1.1) / 2, c - (r * 1.1) / 2, r5, 2 * c - r5];
3063
3037
  }
3038
+ default:
3039
+ return NA_LEVELS;
3064
3040
  }
3065
-
3066
- return levels;
3067
3041
  }
3068
3042
 
3069
3043
  /**
package/src/ta/limits.ts CHANGED
@@ -37,11 +37,38 @@ export function windowError(name: string, length: number, bar?: number, max = MA
37
37
  /** Throws RE10004 when the (truncated) length of a window function is above the limit. */
38
38
  export function checkWindowLength(name: string, length: number, bar?: number, max = MAX_WINDOW_LENGTH): void {
39
39
  const len = Math.trunc(length);
40
+ if (len <= 0) throw new Error(lengthError(name, len, bar));
40
41
  if (len > max) throw new Error(windowError(name, len, bar, max));
41
42
  }
42
43
 
43
44
  /** Throws RE10004 for a function of {@link SUM_FUNCTIONS} ("ta.sma", ...) with a length above 5,000. */
44
45
  export function checkSumLength(name: string, length: number, bar?: number): void {
45
46
  const fn = SUM_FUNCTIONS[name];
46
- if (fn !== undefined) checkWindowLength(fn, length, bar, MAX_SUM_LENGTH);
47
+ if (fn === undefined) return;
48
+ // RE10001 names 'bb' itself, RE10004 its inner 'sma'
49
+ checkPositiveLength(name === 'ta.bb' ? 'bb' : fn, length, bar);
50
+ checkWindowLength(fn, length, bar, MAX_SUM_LENGTH);
51
+ }
52
+
53
+ /**
54
+ * PineScript's runtime error RE10001 text for a length that is 0 or negative. `name` is the function, or a call path
55
+ * "atr->rma" (printed 'atr'->'rma'); the value prints as a float ("0.0", "-1.0").
56
+ */
57
+ export function lengthError(name: string, value: number, bar?: number, arg = 'length'): string {
58
+ const fn = name.split('->').map((part) => `'${part}'`).join('->');
59
+ const text = Number.isInteger(value) ? value.toFixed(1) : String(value);
60
+ return (
61
+ (bar === undefined ? '' : `Error on bar ${bar}: `) +
62
+ `Invalid value of the '${arg}' argument (${text}) in the ${fn} function. It must be > 0.`
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Throws RE10001 when the (truncated) length is 0 or negative, as PineScript does for every window function on the
68
+ * bar of the call (#175): `ta.sma(close, 0)` stops on bar 0, a series length that becomes 0 on the bar where it
69
+ * does. `ta.hma(src, 1)` stops in its inner `wma(src, 0)`. An na length is not checked here.
70
+ */
71
+ export function checkPositiveLength(name: string, length: number, bar?: number, arg = 'length'): void {
72
+ const len = Math.trunc(length);
73
+ if (len <= 0) throw new Error(lengthError(name, len, bar, arg));
47
74
  }