oakscriptjs 0.8.2 → 0.9.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 (555) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +31 -9
  3. package/dist/cjs/array/index.d.ts +701 -0
  4. package/dist/cjs/array/index.d.ts.map +1 -0
  5. package/dist/cjs/array/index.js +393 -0
  6. package/dist/cjs/array/index.js.map +7 -0
  7. package/dist/cjs/box/index.d.ts +444 -0
  8. package/dist/cjs/box/index.d.ts.map +1 -0
  9. package/dist/cjs/box/index.js +246 -0
  10. package/dist/cjs/box/index.js.map +7 -0
  11. package/dist/cjs/callsite/index.d.ts +88 -0
  12. package/dist/cjs/callsite/index.d.ts.map +1 -0
  13. package/dist/cjs/callsite/index.js +112 -0
  14. package/dist/cjs/callsite/index.js.map +7 -0
  15. package/dist/cjs/chartpoint/index.d.ts +90 -0
  16. package/dist/cjs/chartpoint/index.d.ts.map +1 -0
  17. package/dist/cjs/chartpoint/index.js +56 -0
  18. package/dist/cjs/chartpoint/index.js.map +7 -0
  19. package/dist/cjs/color/index.d.ts +286 -0
  20. package/dist/cjs/color/index.d.ts.map +1 -0
  21. package/dist/cjs/color/index.js +122 -0
  22. package/dist/cjs/color/index.js.map +7 -0
  23. package/dist/cjs/color/parse.d.ts +29 -0
  24. package/dist/cjs/color/parse.d.ts.map +1 -0
  25. package/dist/cjs/color/parse.js +57 -0
  26. package/dist/cjs/color/parse.js.map +7 -0
  27. package/dist/cjs/compare/index.d.ts +26 -0
  28. package/dist/cjs/compare/index.d.ts.map +1 -0
  29. package/dist/cjs/compare/index.js +50 -0
  30. package/dist/cjs/compare/index.js.map +7 -0
  31. package/dist/cjs/drawing/registry.d.ts +42 -0
  32. package/dist/cjs/drawing/registry.d.ts.map +1 -0
  33. package/dist/cjs/drawing/registry.js +87 -0
  34. package/dist/cjs/drawing/registry.js.map +7 -0
  35. package/dist/cjs/index.d.ts +85 -0
  36. package/dist/cjs/index.d.ts.map +1 -0
  37. package/dist/cjs/index.js +184 -0
  38. package/dist/cjs/index.js.map +7 -0
  39. package/dist/cjs/indicator.d.ts +117 -0
  40. package/dist/cjs/indicator.d.ts.map +1 -0
  41. package/dist/cjs/indicator.js +74 -0
  42. package/dist/cjs/indicator.js.map +7 -0
  43. package/dist/cjs/input.d.ts +196 -0
  44. package/dist/cjs/input.d.ts.map +1 -0
  45. package/dist/cjs/input.js +197 -0
  46. package/dist/cjs/input.js.map +7 -0
  47. package/dist/cjs/label/index.d.ts +303 -0
  48. package/dist/cjs/label/index.d.ts.map +1 -0
  49. package/dist/cjs/label/index.js +190 -0
  50. package/dist/cjs/label/index.js.map +7 -0
  51. package/dist/cjs/lib/index.d.ts +8 -0
  52. package/dist/cjs/lib/index.d.ts.map +1 -0
  53. package/dist/cjs/lib/index.js +19 -0
  54. package/dist/cjs/lib/index.js.map +7 -0
  55. package/dist/{lib → cjs/lib}/zigzag/index.d.ts +4 -1
  56. package/dist/cjs/lib/zigzag/index.d.ts.map +1 -0
  57. package/dist/cjs/lib/zigzag/index.js +27 -0
  58. package/dist/cjs/lib/zigzag/index.js.map +7 -0
  59. package/dist/cjs/lib/zigzag/zigzag.d.ts +100 -0
  60. package/dist/cjs/lib/zigzag/zigzag.d.ts.map +1 -0
  61. package/dist/cjs/lib/zigzag/zigzag.js +207 -0
  62. package/dist/cjs/lib/zigzag/zigzag.js.map +7 -0
  63. package/dist/cjs/line/index.d.ts +342 -0
  64. package/dist/cjs/line/index.d.ts.map +1 -0
  65. package/dist/cjs/line/index.js +193 -0
  66. package/dist/cjs/line/index.js.map +7 -0
  67. package/dist/cjs/linefill/index.d.ts +124 -0
  68. package/dist/cjs/linefill/index.d.ts.map +1 -0
  69. package/dist/cjs/linefill/index.js +56 -0
  70. package/dist/cjs/linefill/index.js.map +7 -0
  71. package/dist/cjs/map/index.d.ts +69 -0
  72. package/dist/cjs/map/index.d.ts.map +1 -0
  73. package/dist/cjs/map/index.js +80 -0
  74. package/dist/cjs/map/index.js.map +7 -0
  75. package/dist/cjs/math/index.d.ts +560 -0
  76. package/dist/cjs/math/index.d.ts.map +1 -0
  77. package/dist/cjs/math/index.js +441 -0
  78. package/dist/cjs/math/index.js.map +7 -0
  79. package/dist/cjs/matrix/index.d.ts +1231 -0
  80. package/dist/cjs/matrix/index.d.ts.map +1 -0
  81. package/dist/cjs/matrix/index.js +1200 -0
  82. package/dist/cjs/matrix/index.js.map +7 -0
  83. package/dist/cjs/package.json +1 -0
  84. package/dist/cjs/plot.d.ts +76 -0
  85. package/dist/cjs/plot.d.ts.map +1 -0
  86. package/dist/cjs/plot.js +51 -0
  87. package/dist/cjs/plot.js.map +7 -0
  88. package/dist/cjs/polyline/index.d.ts +115 -0
  89. package/dist/cjs/polyline/index.d.ts.map +1 -0
  90. package/dist/cjs/polyline/index.js +76 -0
  91. package/dist/cjs/polyline/index.js.map +7 -0
  92. package/dist/cjs/runtime/adapters/LightweightChartsAdapter.d.ts +86 -0
  93. package/dist/cjs/runtime/adapters/LightweightChartsAdapter.d.ts.map +1 -0
  94. package/dist/cjs/runtime/adapters/LightweightChartsAdapter.js +141 -0
  95. package/dist/cjs/runtime/adapters/LightweightChartsAdapter.js.map +7 -0
  96. package/dist/cjs/runtime/adapters/SimpleInputAdapter.d.ts +59 -0
  97. package/dist/cjs/runtime/adapters/SimpleInputAdapter.d.ts.map +1 -0
  98. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +117 -0
  99. package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +7 -0
  100. package/dist/{runtime → cjs/runtime}/index.d.ts +12 -5
  101. package/dist/cjs/runtime/index.d.ts.map +1 -0
  102. package/dist/cjs/runtime/index.js +49 -0
  103. package/dist/cjs/runtime/index.js.map +7 -0
  104. package/dist/cjs/runtime/inputs.d.ts +91 -0
  105. package/dist/cjs/runtime/inputs.d.ts.map +1 -0
  106. package/dist/cjs/runtime/inputs.js +262 -0
  107. package/dist/cjs/runtime/inputs.js.map +7 -0
  108. package/dist/cjs/runtime/runtime.d.ts +70 -0
  109. package/dist/cjs/runtime/runtime.d.ts.map +1 -0
  110. package/dist/cjs/runtime/runtime.js +148 -0
  111. package/dist/cjs/runtime/runtime.js.map +7 -0
  112. package/dist/cjs/runtime/series.d.ts +362 -0
  113. package/dist/cjs/runtime/series.d.ts.map +1 -0
  114. package/dist/cjs/runtime/series.js +560 -0
  115. package/dist/cjs/runtime/series.js.map +7 -0
  116. package/dist/cjs/runtime/types.d.ts +282 -0
  117. package/dist/cjs/runtime/types.d.ts.map +1 -0
  118. package/dist/cjs/runtime/types.js +17 -0
  119. package/dist/cjs/runtime/types.js.map +7 -0
  120. package/dist/cjs/script/index.d.ts +991 -0
  121. package/dist/cjs/script/index.d.ts.map +1 -0
  122. package/dist/cjs/script/index.js +1174 -0
  123. package/dist/cjs/script/index.js.map +7 -0
  124. package/dist/cjs/security/resample.d.ts +63 -0
  125. package/dist/cjs/security/resample.d.ts.map +1 -0
  126. package/dist/cjs/security/resample.js +84 -0
  127. package/dist/cjs/security/resample.js.map +7 -0
  128. package/dist/cjs/session/bars.d.ts +80 -0
  129. package/dist/cjs/session/bars.d.ts.map +1 -0
  130. package/dist/cjs/session/bars.js +219 -0
  131. package/dist/cjs/session/bars.js.map +7 -0
  132. package/dist/cjs/session/calendar.d.ts +70 -0
  133. package/dist/cjs/session/calendar.d.ts.map +1 -0
  134. package/dist/cjs/session/calendar.js +164 -0
  135. package/dist/cjs/session/calendar.js.map +7 -0
  136. package/dist/cjs/str/dateformat.d.ts +15 -0
  137. package/dist/cjs/str/dateformat.d.ts.map +1 -0
  138. package/dist/cjs/str/dateformat.js +149 -0
  139. package/dist/cjs/str/dateformat.js.map +7 -0
  140. package/dist/cjs/str/index.d.ts +459 -0
  141. package/dist/cjs/str/index.d.ts.map +1 -0
  142. package/dist/cjs/str/index.js +143 -0
  143. package/dist/cjs/str/index.js.map +7 -0
  144. package/dist/cjs/str/messageformat.d.ts +14 -0
  145. package/dist/cjs/str/messageformat.d.ts.map +1 -0
  146. package/dist/cjs/str/messageformat.js +114 -0
  147. package/dist/cjs/str/messageformat.js.map +7 -0
  148. package/dist/cjs/str/numberformat.d.ts +19 -0
  149. package/dist/cjs/str/numberformat.d.ts.map +1 -0
  150. package/dist/cjs/str/numberformat.js +170 -0
  151. package/dist/cjs/str/numberformat.js.map +7 -0
  152. package/dist/{strategy → cjs/strategy}/index.d.ts +50 -0
  153. package/dist/cjs/strategy/index.d.ts.map +1 -0
  154. package/dist/cjs/strategy/index.js +92 -0
  155. package/dist/cjs/strategy/index.js.map +7 -0
  156. package/dist/cjs/ta/index.d.ts +1400 -0
  157. package/dist/cjs/ta/index.d.ts.map +1 -0
  158. package/dist/cjs/ta/index.js +1719 -0
  159. package/dist/cjs/ta/index.js.map +7 -0
  160. package/dist/cjs/ta/running-sum.d.ts +30 -0
  161. package/dist/cjs/ta/running-sum.d.ts.map +1 -0
  162. package/dist/cjs/ta/running-sum.js +99 -0
  163. package/dist/cjs/ta/running-sum.js.map +7 -0
  164. package/dist/cjs/ta-series.d.ts +558 -0
  165. package/dist/cjs/ta-series.d.ts.map +1 -0
  166. package/dist/cjs/ta-series.js +606 -0
  167. package/dist/cjs/ta-series.js.map +7 -0
  168. package/dist/cjs/text/index.d.ts +14 -0
  169. package/dist/cjs/text/index.d.ts.map +1 -0
  170. package/dist/cjs/text/index.js +29 -0
  171. package/dist/cjs/text/index.js.map +7 -0
  172. package/dist/cjs/time/datestring.d.ts +23 -0
  173. package/dist/cjs/time/datestring.d.ts.map +1 -0
  174. package/dist/cjs/time/datestring.js +106 -0
  175. package/dist/cjs/time/datestring.js.map +7 -0
  176. package/dist/cjs/time/index.d.ts +89 -0
  177. package/dist/cjs/time/index.d.ts.map +1 -0
  178. package/dist/cjs/time/index.js +85 -0
  179. package/dist/cjs/time/index.js.map +7 -0
  180. package/dist/cjs/time/session.d.ts +17 -0
  181. package/dist/cjs/time/session.d.ts.map +1 -0
  182. package/dist/cjs/time/session.js +65 -0
  183. package/dist/cjs/time/session.js.map +7 -0
  184. package/dist/cjs/time/timezone.d.ts +40 -0
  185. package/dist/cjs/time/timezone.d.ts.map +1 -0
  186. package/dist/cjs/time/timezone.js +125 -0
  187. package/dist/cjs/time/timezone.js.map +7 -0
  188. package/dist/cjs/timeframe/index.d.ts +73 -0
  189. package/dist/cjs/timeframe/index.d.ts.map +1 -0
  190. package/dist/cjs/timeframe/index.js +85 -0
  191. package/dist/cjs/timeframe/index.js.map +7 -0
  192. package/dist/cjs/types/index.d.ts +343 -0
  193. package/dist/cjs/types/index.d.ts.map +1 -0
  194. package/dist/cjs/types/index.js +32 -0
  195. package/dist/cjs/types/index.js.map +7 -0
  196. package/dist/cjs/types/metadata.d.ts +300 -0
  197. package/dist/cjs/types/metadata.d.ts.map +1 -0
  198. package/dist/cjs/types/metadata.js +17 -0
  199. package/dist/cjs/types/metadata.js.map +7 -0
  200. package/dist/cjs/utils/index.d.ts +167 -0
  201. package/dist/cjs/utils/index.d.ts.map +1 -0
  202. package/dist/cjs/utils/index.js +209 -0
  203. package/dist/cjs/utils/index.js.map +7 -0
  204. package/dist/esm/array/index.d.ts +701 -0
  205. package/dist/esm/array/index.d.ts.map +1 -0
  206. package/dist/esm/array/index.js +984 -0
  207. package/dist/esm/array/index.js.map +1 -0
  208. package/dist/esm/box/index.d.ts +444 -0
  209. package/dist/esm/box/index.d.ts.map +1 -0
  210. package/dist/esm/box/index.js +602 -0
  211. package/dist/esm/box/index.js.map +1 -0
  212. package/dist/esm/callsite/index.d.ts +88 -0
  213. package/dist/esm/callsite/index.d.ts.map +1 -0
  214. package/dist/esm/callsite/index.js +176 -0
  215. package/dist/esm/callsite/index.js.map +1 -0
  216. package/dist/esm/chartpoint/index.d.ts +90 -0
  217. package/dist/esm/chartpoint/index.d.ts.map +1 -0
  218. package/dist/esm/chartpoint/index.js +113 -0
  219. package/dist/esm/chartpoint/index.js.map +1 -0
  220. package/dist/esm/color/index.d.ts +286 -0
  221. package/dist/esm/color/index.d.ts.map +1 -0
  222. package/dist/esm/color/index.js +350 -0
  223. package/dist/esm/color/index.js.map +1 -0
  224. package/dist/esm/color/parse.d.ts +29 -0
  225. package/dist/esm/color/parse.d.ts.map +1 -0
  226. package/dist/esm/color/parse.js +55 -0
  227. package/dist/esm/color/parse.js.map +1 -0
  228. package/dist/esm/compare/index.d.ts +26 -0
  229. package/dist/esm/compare/index.d.ts.map +1 -0
  230. package/dist/esm/compare/index.js +39 -0
  231. package/dist/esm/compare/index.js.map +1 -0
  232. package/dist/esm/drawing/registry.d.ts +42 -0
  233. package/dist/esm/drawing/registry.d.ts.map +1 -0
  234. package/dist/esm/drawing/registry.js +89 -0
  235. package/dist/esm/drawing/registry.js.map +1 -0
  236. package/dist/esm/index.d.ts +85 -0
  237. package/dist/esm/index.d.ts.map +1 -0
  238. package/dist/esm/index.js +96 -0
  239. package/dist/esm/index.js.map +1 -0
  240. package/dist/esm/indicator.d.ts +117 -0
  241. package/dist/esm/indicator.d.ts.map +1 -0
  242. package/dist/esm/indicator.js +84 -0
  243. package/dist/esm/indicator.js.map +1 -0
  244. package/dist/esm/input.d.ts +196 -0
  245. package/dist/esm/input.d.ts.map +1 -0
  246. package/dist/esm/input.js +183 -0
  247. package/dist/esm/input.js.map +1 -0
  248. package/dist/esm/label/index.d.ts +303 -0
  249. package/dist/esm/label/index.d.ts.map +1 -0
  250. package/dist/esm/label/index.js +417 -0
  251. package/dist/esm/label/index.js.map +1 -0
  252. package/dist/esm/lib/index.d.ts +8 -0
  253. package/dist/esm/lib/index.d.ts.map +1 -0
  254. package/dist/esm/lib/index.js +8 -0
  255. package/dist/esm/lib/index.js.map +1 -0
  256. package/dist/esm/lib/zigzag/index.d.ts +5 -0
  257. package/dist/esm/lib/zigzag/index.d.ts.map +1 -0
  258. package/dist/esm/lib/zigzag/index.js +5 -0
  259. package/dist/esm/lib/zigzag/index.js.map +1 -0
  260. package/dist/esm/lib/zigzag/zigzag.d.ts +100 -0
  261. package/dist/esm/lib/zigzag/zigzag.d.ts.map +1 -0
  262. package/dist/esm/lib/zigzag/zigzag.js +229 -0
  263. package/dist/esm/lib/zigzag/zigzag.js.map +1 -0
  264. package/dist/esm/line/index.d.ts +342 -0
  265. package/dist/esm/line/index.d.ts.map +1 -0
  266. package/dist/esm/line/index.js +470 -0
  267. package/dist/esm/line/index.js.map +1 -0
  268. package/dist/esm/linefill/index.d.ts +124 -0
  269. package/dist/esm/linefill/index.d.ts.map +1 -0
  270. package/dist/esm/linefill/index.js +144 -0
  271. package/dist/esm/linefill/index.js.map +1 -0
  272. package/dist/esm/map/index.d.ts +69 -0
  273. package/dist/esm/map/index.d.ts.map +1 -0
  274. package/dist/esm/map/index.js +106 -0
  275. package/dist/esm/map/index.js.map +1 -0
  276. package/dist/esm/math/index.d.ts +560 -0
  277. package/dist/esm/math/index.d.ts.map +1 -0
  278. package/dist/esm/math/index.js +495 -0
  279. package/dist/esm/math/index.js.map +1 -0
  280. package/dist/esm/matrix/index.d.ts +1231 -0
  281. package/dist/esm/matrix/index.d.ts.map +1 -0
  282. package/dist/esm/matrix/index.js +2450 -0
  283. package/dist/esm/matrix/index.js.map +1 -0
  284. package/dist/esm/plot.d.ts +76 -0
  285. package/dist/esm/plot.d.ts.map +1 -0
  286. package/dist/esm/plot.js +63 -0
  287. package/dist/esm/plot.js.map +1 -0
  288. package/dist/esm/polyline/index.d.ts +115 -0
  289. package/dist/esm/polyline/index.d.ts.map +1 -0
  290. package/dist/esm/polyline/index.js +157 -0
  291. package/dist/esm/polyline/index.js.map +1 -0
  292. package/dist/esm/runtime/adapters/LightweightChartsAdapter.d.ts +86 -0
  293. package/dist/esm/runtime/adapters/LightweightChartsAdapter.d.ts.map +1 -0
  294. package/dist/esm/runtime/adapters/LightweightChartsAdapter.js +140 -0
  295. package/dist/esm/runtime/adapters/LightweightChartsAdapter.js.map +1 -0
  296. package/dist/esm/runtime/adapters/SimpleInputAdapter.d.ts +59 -0
  297. package/dist/esm/runtime/adapters/SimpleInputAdapter.d.ts.map +1 -0
  298. package/dist/esm/runtime/adapters/SimpleInputAdapter.js +115 -0
  299. package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -0
  300. package/dist/esm/runtime/index.d.ts +13 -0
  301. package/dist/esm/runtime/index.d.ts.map +1 -0
  302. package/dist/esm/runtime/index.js +14 -0
  303. package/dist/esm/runtime/index.js.map +1 -0
  304. package/dist/esm/runtime/inputs.d.ts +91 -0
  305. package/dist/esm/runtime/inputs.d.ts.map +1 -0
  306. package/dist/esm/runtime/inputs.js +334 -0
  307. package/dist/esm/runtime/inputs.js.map +1 -0
  308. package/dist/esm/runtime/runtime.d.ts +70 -0
  309. package/dist/esm/runtime/runtime.d.ts.map +1 -0
  310. package/dist/esm/runtime/runtime.js +193 -0
  311. package/dist/esm/runtime/runtime.js.map +1 -0
  312. package/dist/esm/runtime/series.d.ts +362 -0
  313. package/dist/esm/runtime/series.d.ts.map +1 -0
  314. package/dist/esm/runtime/series.js +583 -0
  315. package/dist/esm/runtime/series.js.map +1 -0
  316. package/dist/esm/runtime/types.d.ts +282 -0
  317. package/dist/esm/runtime/types.d.ts.map +1 -0
  318. package/dist/esm/runtime/types.js +7 -0
  319. package/dist/esm/runtime/types.js.map +1 -0
  320. package/dist/esm/script/index.d.ts +991 -0
  321. package/dist/esm/script/index.d.ts.map +1 -0
  322. package/dist/esm/script/index.js +1294 -0
  323. package/dist/esm/script/index.js.map +1 -0
  324. package/dist/esm/security/resample.d.ts +63 -0
  325. package/dist/esm/security/resample.d.ts.map +1 -0
  326. package/dist/esm/security/resample.js +110 -0
  327. package/dist/esm/security/resample.js.map +1 -0
  328. package/dist/esm/session/bars.d.ts +80 -0
  329. package/dist/esm/session/bars.d.ts.map +1 -0
  330. package/dist/esm/session/bars.js +232 -0
  331. package/dist/esm/session/bars.js.map +1 -0
  332. package/dist/esm/session/calendar.d.ts +70 -0
  333. package/dist/esm/session/calendar.d.ts.map +1 -0
  334. package/dist/esm/session/calendar.js +172 -0
  335. package/dist/esm/session/calendar.js.map +1 -0
  336. package/dist/esm/str/dateformat.d.ts +15 -0
  337. package/dist/esm/str/dateformat.d.ts.map +1 -0
  338. package/dist/esm/str/dateformat.js +136 -0
  339. package/dist/esm/str/dateformat.js.map +1 -0
  340. package/dist/esm/str/index.d.ts +459 -0
  341. package/dist/esm/str/index.d.ts.map +1 -0
  342. package/dist/esm/str/index.js +540 -0
  343. package/dist/esm/str/index.js.map +1 -0
  344. package/dist/esm/str/messageformat.d.ts +14 -0
  345. package/dist/esm/str/messageformat.d.ts.map +1 -0
  346. package/dist/esm/str/messageformat.js +111 -0
  347. package/dist/esm/str/messageformat.js.map +1 -0
  348. package/dist/esm/str/numberformat.d.ts +19 -0
  349. package/dist/esm/str/numberformat.d.ts.map +1 -0
  350. package/dist/esm/str/numberformat.js +189 -0
  351. package/dist/esm/str/numberformat.js.map +1 -0
  352. package/dist/esm/strategy/index.d.ts +245 -0
  353. package/dist/esm/strategy/index.d.ts.map +1 -0
  354. package/dist/esm/strategy/index.js +84 -0
  355. package/dist/esm/strategy/index.js.map +1 -0
  356. package/dist/esm/ta/index.d.ts +1400 -0
  357. package/dist/esm/ta/index.d.ts.map +1 -0
  358. package/dist/esm/ta/index.js +3236 -0
  359. package/dist/esm/ta/index.js.map +1 -0
  360. package/dist/esm/ta/running-sum.d.ts +30 -0
  361. package/dist/esm/ta/running-sum.d.ts.map +1 -0
  362. package/dist/esm/ta/running-sum.js +113 -0
  363. package/dist/esm/ta/running-sum.js.map +1 -0
  364. package/dist/esm/ta-series.d.ts +558 -0
  365. package/dist/esm/ta-series.d.ts.map +1 -0
  366. package/dist/esm/ta-series.js +910 -0
  367. package/dist/esm/ta-series.js.map +1 -0
  368. package/dist/esm/text/index.d.ts +14 -0
  369. package/dist/esm/text/index.d.ts.map +1 -0
  370. package/dist/esm/text/index.js +14 -0
  371. package/dist/esm/text/index.js.map +1 -0
  372. package/dist/esm/time/datestring.d.ts +23 -0
  373. package/dist/esm/time/datestring.d.ts.map +1 -0
  374. package/dist/esm/time/datestring.js +94 -0
  375. package/dist/esm/time/datestring.js.map +1 -0
  376. package/dist/esm/time/index.d.ts +89 -0
  377. package/dist/esm/time/index.d.ts.map +1 -0
  378. package/dist/esm/time/index.js +108 -0
  379. package/dist/esm/time/index.js.map +1 -0
  380. package/dist/esm/time/session.d.ts +17 -0
  381. package/dist/esm/time/session.d.ts.map +1 -0
  382. package/dist/esm/time/session.js +69 -0
  383. package/dist/esm/time/session.js.map +1 -0
  384. package/dist/esm/time/timezone.d.ts +40 -0
  385. package/dist/esm/time/timezone.d.ts.map +1 -0
  386. package/dist/esm/time/timezone.js +144 -0
  387. package/dist/esm/time/timezone.js.map +1 -0
  388. package/dist/esm/timeframe/index.d.ts +73 -0
  389. package/dist/esm/timeframe/index.d.ts.map +1 -0
  390. package/dist/esm/timeframe/index.js +131 -0
  391. package/dist/esm/timeframe/index.js.map +1 -0
  392. package/dist/esm/types/index.d.ts +343 -0
  393. package/dist/esm/types/index.d.ts.map +1 -0
  394. package/dist/esm/types/index.js +17 -0
  395. package/dist/esm/types/index.js.map +1 -0
  396. package/dist/esm/types/metadata.d.ts +300 -0
  397. package/dist/esm/types/metadata.d.ts.map +1 -0
  398. package/dist/esm/types/metadata.js +7 -0
  399. package/dist/esm/types/metadata.js.map +1 -0
  400. package/dist/esm/utils/index.d.ts +167 -0
  401. package/dist/esm/utils/index.d.ts.map +1 -0
  402. package/dist/esm/utils/index.js +295 -0
  403. package/dist/esm/utils/index.js.map +1 -0
  404. package/package.json +53 -23
  405. package/src/array/index.ts +1057 -0
  406. package/src/box/index.ts +680 -0
  407. package/src/callsite/index.ts +178 -0
  408. package/src/chartpoint/index.ts +126 -0
  409. package/src/color/index.ts +389 -0
  410. package/src/color/parse.ts +56 -0
  411. package/src/compare/index.ts +46 -0
  412. package/src/drawing/registry.ts +106 -0
  413. package/src/index.ts +201 -0
  414. package/src/indicator.ts +185 -0
  415. package/src/input.ts +280 -0
  416. package/src/label/index.ts +490 -0
  417. package/src/lib/index.ts +8 -0
  418. package/src/lib/zigzag/index.ts +13 -0
  419. package/src/lib/zigzag/zigzag.ts +318 -0
  420. package/src/line/index.ts +530 -0
  421. package/src/linefill/index.ts +152 -0
  422. package/src/map/index.ts +115 -0
  423. package/src/math/index.ts +1071 -0
  424. package/src/matrix/index.ts +2697 -0
  425. package/src/plot.ts +121 -0
  426. package/src/polyline/index.ts +184 -0
  427. package/src/runtime/adapters/LightweightChartsAdapter.ts +201 -0
  428. package/src/runtime/adapters/SimpleInputAdapter.ts +132 -0
  429. package/src/runtime/index.ts +52 -0
  430. package/src/runtime/inputs.ts +377 -0
  431. package/src/runtime/runtime.ts +241 -0
  432. package/src/runtime/series.ts +644 -0
  433. package/src/runtime/types.ts +292 -0
  434. package/src/script/index.ts +1870 -0
  435. package/src/security/resample.ts +119 -0
  436. package/src/session/bars.ts +262 -0
  437. package/src/session/calendar.ts +199 -0
  438. package/src/str/dateformat.ts +104 -0
  439. package/src/str/index.ts +557 -0
  440. package/src/str/messageformat.ts +109 -0
  441. package/src/str/numberformat.ts +197 -0
  442. package/src/strategy/index.ts +281 -0
  443. package/src/ta/index.ts +3587 -0
  444. package/src/ta/running-sum.ts +117 -0
  445. package/src/ta-series.ts +1036 -0
  446. package/src/text/index.ts +14 -0
  447. package/src/time/datestring.ts +96 -0
  448. package/src/time/index.ts +161 -0
  449. package/src/time/session.ts +79 -0
  450. package/src/time/timezone.ts +180 -0
  451. package/src/timeframe/index.ts +144 -0
  452. package/src/types/index.ts +398 -0
  453. package/src/types/metadata.ts +334 -0
  454. package/src/utils/index.ts +334 -0
  455. package/dist/array/index.d.ts +0 -57
  456. package/dist/array/index.d.ts.map +0 -1
  457. package/dist/box/index.d.ts +0 -37
  458. package/dist/box/index.d.ts.map +0 -1
  459. package/dist/callsite/index.d.ts +0 -7
  460. package/dist/callsite/index.d.ts.map +0 -1
  461. package/dist/chartpoint/index.d.ts +0 -8
  462. package/dist/chartpoint/index.d.ts.map +0 -1
  463. package/dist/color/index.d.ts +0 -28
  464. package/dist/color/index.d.ts.map +0 -1
  465. package/dist/compare/index.d.ts +0 -8
  466. package/dist/compare/index.d.ts.map +0 -1
  467. package/dist/drawing/registry.d.ts +0 -19
  468. package/dist/drawing/registry.d.ts.map +0 -1
  469. package/dist/index.cjs +0 -7981
  470. package/dist/index.d.ts +0 -64
  471. package/dist/index.d.ts.map +0 -1
  472. package/dist/index.mjs +0 -7964
  473. package/dist/indicator.d.ts +0 -42
  474. package/dist/indicator.d.ts.map +0 -1
  475. package/dist/input.d.ts +0 -47
  476. package/dist/input.d.ts.map +0 -1
  477. package/dist/label/index.d.ts +0 -30
  478. package/dist/label/index.d.ts.map +0 -1
  479. package/dist/lib/index.d.ts +0 -2
  480. package/dist/lib/index.d.ts.map +0 -1
  481. package/dist/lib/zigzag/index.d.ts.map +0 -1
  482. package/dist/lib/zigzag/zigzag.d.ts +0 -47
  483. package/dist/lib/zigzag/zigzag.d.ts.map +0 -1
  484. package/dist/line/index.d.ts +0 -30
  485. package/dist/line/index.d.ts.map +0 -1
  486. package/dist/linefill/index.d.ts +0 -10
  487. package/dist/linefill/index.d.ts.map +0 -1
  488. package/dist/map/index.d.ts +0 -14
  489. package/dist/map/index.d.ts.map +0 -1
  490. package/dist/math/index.d.ts +0 -53
  491. package/dist/math/index.d.ts.map +0 -1
  492. package/dist/matrix/index.d.ts +0 -52
  493. package/dist/matrix/index.d.ts.map +0 -1
  494. package/dist/plot.d.ts +0 -20
  495. package/dist/plot.d.ts.map +0 -1
  496. package/dist/polyline/index.d.ts +0 -11
  497. package/dist/polyline/index.d.ts.map +0 -1
  498. package/dist/runtime/adapters/LightweightChartsAdapter.d.ts +0 -15
  499. package/dist/runtime/adapters/LightweightChartsAdapter.d.ts.map +0 -1
  500. package/dist/runtime/adapters/SimpleInputAdapter.d.ts +0 -16
  501. package/dist/runtime/adapters/SimpleInputAdapter.d.ts.map +0 -1
  502. package/dist/runtime/index.cjs +0 -600
  503. package/dist/runtime/index.d.ts.map +0 -1
  504. package/dist/runtime/index.mjs +0 -577
  505. package/dist/runtime/inputs.d.ts +0 -13
  506. package/dist/runtime/inputs.d.ts.map +0 -1
  507. package/dist/runtime/runtime.d.ts +0 -16
  508. package/dist/runtime/runtime.d.ts.map +0 -1
  509. package/dist/runtime/series.d.ts +0 -60
  510. package/dist/runtime/series.d.ts.map +0 -1
  511. package/dist/runtime/types.d.ts +0 -126
  512. package/dist/runtime/types.d.ts.map +0 -1
  513. package/dist/script/index.cjs +0 -6377
  514. package/dist/script/index.d.ts +0 -713
  515. package/dist/script/index.d.ts.map +0 -1
  516. package/dist/script/index.mjs +0 -6360
  517. package/dist/security/resample.d.ts +0 -13
  518. package/dist/security/resample.d.ts.map +0 -1
  519. package/dist/session/bars.d.ts +0 -34
  520. package/dist/session/bars.d.ts.map +0 -1
  521. package/dist/session/calendar.d.ts +0 -31
  522. package/dist/session/calendar.d.ts.map +0 -1
  523. package/dist/str/dateformat.d.ts +0 -2
  524. package/dist/str/dateformat.d.ts.map +0 -1
  525. package/dist/str/index.d.ts +0 -24
  526. package/dist/str/index.d.ts.map +0 -1
  527. package/dist/str/messageformat.d.ts +0 -2
  528. package/dist/str/messageformat.d.ts.map +0 -1
  529. package/dist/str/numberformat.d.ts +0 -3
  530. package/dist/str/numberformat.d.ts.map +0 -1
  531. package/dist/strategy/index.d.ts.map +0 -1
  532. package/dist/ta/index.d.ts +0 -72
  533. package/dist/ta/index.d.ts.map +0 -1
  534. package/dist/ta/running-sum.d.ts +0 -3
  535. package/dist/ta/running-sum.d.ts.map +0 -1
  536. package/dist/ta-series.d.ts +0 -143
  537. package/dist/ta-series.d.ts.map +0 -1
  538. package/dist/text/index.d.ts +0 -4
  539. package/dist/text/index.d.ts.map +0 -1
  540. package/dist/time/datestring.d.ts +0 -2
  541. package/dist/time/datestring.d.ts.map +0 -1
  542. package/dist/time/index.d.ts +0 -15
  543. package/dist/time/index.d.ts.map +0 -1
  544. package/dist/time/session.d.ts +0 -2
  545. package/dist/time/session.d.ts.map +0 -1
  546. package/dist/time/timezone.d.ts +0 -13
  547. package/dist/time/timezone.d.ts.map +0 -1
  548. package/dist/timeframe/index.d.ts +0 -17
  549. package/dist/timeframe/index.d.ts.map +0 -1
  550. package/dist/types/index.d.ts +0 -181
  551. package/dist/types/index.d.ts.map +0 -1
  552. package/dist/types/metadata.d.ts +0 -132
  553. package/dist/types/metadata.d.ts.map +0 -1
  554. package/dist/utils/index.d.ts +0 -26
  555. package/dist/utils/index.d.ts.map +0 -1
@@ -0,0 +1,3236 @@
1
+ /**
2
+ * Technical Analysis (ta) namespace
3
+ * Mirrors PineScript's ta.* functions for technical analysis indicators and calculations.
4
+ *
5
+ * @remarks
6
+ * All technical analysis functions in this namespace follow PineScript v6 API specifications.
7
+ *
8
+ * @version 6
9
+ */
10
+ import { eq, ge, gt, le, lt } from '../compare/index.js';
11
+ import { runningSum, runningVariance } from './running-sum.js';
12
+ /**
13
+ * Simple Moving Average - returns the moving average (sum of last y values divided by y).
14
+ *
15
+ * @param source - Series of values to process
16
+ * @param length - Number of bars (length)
17
+ * @returns Simple moving average of source for length bars back
18
+ *
19
+ * @remarks
20
+ * - `na` values (also +/-Infinity) in the source series are ignored: the mean of the last `length` non-`na`
21
+ * values, and a bar with an `na` source keeps the previous result (as in PineScript)
22
+ * - Returns NaN until `length` non-`na` values are available
23
+ * - The sum is a compensated running sum, as in PineScript, so the result can differ from the exact window mean in
24
+ * the last bits (bit for bit with PineScript)
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const closePrices = [10, 11, 12, 13, 14];
29
+ * const sma5 = ta.sma(closePrices, 5); // Returns: [NaN, NaN, NaN, NaN, 12]
30
+ * ```
31
+ */
32
+ export function sma(source, length) {
33
+ // Floor the length to match PineScript's auto-truncation of float to int
34
+ const len = Math.floor(length);
35
+ return runningSum(source, len).map((sum) => sum / len);
36
+ }
37
+ /** Mean of the last `length` bars; na when the window holds an na value (used by ta.dev). */
38
+ function strictWindowMean(source, length) {
39
+ const len = Math.floor(length);
40
+ return Array.from({ length: source.length }, (_, i) => {
41
+ if (i < len - 1)
42
+ return NaN;
43
+ let sum = 0;
44
+ for (let j = 0; j < len; j++)
45
+ sum += source[i - j];
46
+ return sum / len;
47
+ });
48
+ }
49
+ /**
50
+ * Exponential Moving Average - returns the exponentially weighted moving average.
51
+ *
52
+ * @param source - Series of values to process
53
+ * @param length - Number of bars (length)
54
+ * @returns Exponential moving average of source with alpha = 2 / (length + 1)
55
+ *
56
+ * @remarks
57
+ * - In EMA, weighting factors decrease exponentially
58
+ * - Formula: `EMA = alpha * source + (1 - alpha) * EMA[1]`, where `alpha = 2 / (length + 1)`
59
+ * - `na` values (also +/-Infinity) in the source series are ignored
60
+ * - The function calculates on the `length` quantity of non-`na` values
61
+ * - May cause indicator repainting
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const closePrices = [10, 11, 12, 13, 14];
66
+ * const ema5 = ta.ema(closePrices, 5);
67
+ * ```
68
+ */
69
+ export function ema(source, length) {
70
+ const result = [];
71
+ const len = Math.floor(length);
72
+ const multiplier = 2 / (len + 1);
73
+ // SMA seed: slide past leading NaN, then collect `len` valid values.
74
+ // An infinite value is na, as in PineScript.
75
+ let firstValidIndex = -1;
76
+ let validCount = 0;
77
+ let initSum = 0;
78
+ for (let i = 0; i < source.length; i++) {
79
+ const val = source[i];
80
+ if (val !== undefined && Number.isFinite(val)) {
81
+ initSum += val;
82
+ validCount++;
83
+ if (validCount === len) {
84
+ firstValidIndex = i;
85
+ break;
86
+ }
87
+ }
88
+ }
89
+ let emaValue = validCount > 0 ? initSum / validCount : NaN;
90
+ const emaInitialized = firstValidIndex >= 0;
91
+ for (let i = 0; i < source.length; i++) {
92
+ if (!emaInitialized || i < firstValidIndex) {
93
+ result.push(NaN);
94
+ }
95
+ else if (i === firstValidIndex) {
96
+ result.push(emaValue);
97
+ }
98
+ else {
99
+ const val = source[i];
100
+ if (val !== undefined && Number.isFinite(val)) {
101
+ emaValue = (val - emaValue) * multiplier + emaValue;
102
+ result.push(emaValue);
103
+ }
104
+ else {
105
+ // na source: na on this bar; the next bar continues from the last value (as in PineScript)
106
+ result.push(NaN);
107
+ }
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ /**
113
+ * Relative Strength Index - momentum oscillator measuring speed and magnitude of price changes.
114
+ *
115
+ * @param source - Series of values to process
116
+ * @param length - Number of bars (length)
117
+ * @returns RSI series (values range from 0 to 100)
118
+ *
119
+ * @remarks
120
+ * - RSI values above 70 typically indicate overbought conditions
121
+ * - RSI values below 30 typically indicate oversold conditions
122
+ * - Uses `ta.rma()` (Relative Moving Average) for smoothing, matching PineScript v6
123
+ * - Formula: RSI = 100 - (100 / (1 + RS)), where RS = Average Gain / Average Loss
124
+ * - As in PineScript (`down == 0 ? 100 : up == 0 ? 0 : ...`): 100 when the average loss is 0, else 0 when the
125
+ * average gain is 0, both with the 1e-10 tolerance of the comparison operators
126
+ * - Average Gain and Average Loss are calculated using RMA (alpha = 1 / length)
127
+ * - `na` values in the source series are ignored
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * const rsi14 = ta.rsi(closePrices, 14);
132
+ * // Identify overbought/oversold conditions
133
+ * const overbought = rsi14.map(v => v > 70);
134
+ * const oversold = rsi14.map(v => v < 30);
135
+ * ```
136
+ */
137
+ export function rsi(source, length) {
138
+ const result = [];
139
+ // Calculate price changes
140
+ const changes = [];
141
+ for (let i = 1; i < source.length; i++) {
142
+ changes.push(source[i] - source[i - 1]);
143
+ }
144
+ // Separate gains and losses; a change from or to na is na (skipped by rma), not a 0 gain / loss
145
+ const gains = changes.map(c => (Number.isNaN(c) ? NaN : c > 0 ? c : 0));
146
+ const losses = changes.map(c => (Number.isNaN(c) ? NaN : c < 0 ? -c : 0));
147
+ // Calculate average gains and losses using RMA (not SMA)
148
+ const avgGains = rma(gains, length);
149
+ const avgLosses = rma(losses, length);
150
+ result.push(NaN); // First value is NaN
151
+ for (let i = 0; i < avgGains.length; i++) {
152
+ if (eq(avgLosses[i], 0)) {
153
+ result.push(100);
154
+ }
155
+ else if (eq(avgGains[i], 0)) {
156
+ result.push(0);
157
+ }
158
+ else {
159
+ const rs = avgGains[i] / avgLosses[i];
160
+ result.push(100 - (100 / (1 + rs)));
161
+ }
162
+ }
163
+ return result;
164
+ }
165
+ /**
166
+ * Moving Average Convergence Divergence - trend-following momentum indicator showing relationship
167
+ * between two moving averages.
168
+ *
169
+ * @param source - Series of values to process
170
+ * @param fastLength - Fast EMA length (typically 12)
171
+ * @param slowLength - Slow EMA length (typically 26)
172
+ * @param signalLength - Signal line EMA length (typically 9)
173
+ * @returns Tuple of [macdLine, signalLine, histogram]
174
+ *
175
+ * @remarks
176
+ * - MACD Line = Fast EMA - Slow EMA
177
+ * - Signal Line = EMA of MACD Line
178
+ * - Histogram = MACD Line - Signal Line
179
+ * - Crossovers between MACD and signal line indicate potential buy/sell signals
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * const [macdLine, signal, histogram] = ta.macd(closePrices, 12, 26, 9);
184
+ * ```
185
+ */
186
+ export function macd(source, fastLength, slowLength, signalLength) {
187
+ const fastEma = ema(source, fastLength);
188
+ const slowEma = ema(source, slowLength);
189
+ const macdLine = [];
190
+ for (let i = 0; i < source.length; i++) {
191
+ macdLine.push(fastEma[i] - slowEma[i]);
192
+ }
193
+ const signalLine = ema(macdLine, signalLength);
194
+ const histogram = [];
195
+ for (let i = 0; i < source.length; i++) {
196
+ histogram.push(macdLine[i] - signalLine[i]);
197
+ }
198
+ return [macdLine, signalLine, histogram];
199
+ }
200
+ /**
201
+ * Bollinger Bands - a technical analysis tool defined by lines plotted two standard deviations
202
+ * away from a simple moving average.
203
+ *
204
+ * @param series - Series of values to process
205
+ * @param length - Number of bars (length)
206
+ * @param mult - Standard deviation factor
207
+ * @returns Tuple of [middle, upper, lower] bands
208
+ *
209
+ * @remarks
210
+ * - Middle band is the SMA of the source
211
+ * - Upper band = middle + (mult * standard deviation)
212
+ * - Lower band = middle - (mult * standard deviation)
213
+ * - `na` values in the source series are ignored
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * const [middle, upper, lower] = ta.bb(closePrices, 20, 2);
218
+ * ```
219
+ */
220
+ export function bb(series, length, mult) {
221
+ const basis = sma(series, length);
222
+ const dev = stdev(series, length);
223
+ const upper = [];
224
+ const lower = [];
225
+ for (let i = 0; i < series.length; i++) {
226
+ upper.push(basis[i] + mult * dev[i]);
227
+ lower.push(basis[i] - mult * dev[i]);
228
+ }
229
+ return [basis, upper, lower];
230
+ }
231
+ /**
232
+ * Standard deviation over the last `length` non-na values.
233
+ *
234
+ * @param source - Series of values to process
235
+ * @param length - Number of values
236
+ * @param biased - true (default): biased estimate (divides by `length`); false: unbiased (by `length - 1`)
237
+ * @returns Standard deviation series (na until `length` non-na values exist)
238
+ *
239
+ * @remarks
240
+ * As in PineScript: `na` values (also +/-Infinity) are skipped, the window holds the last
241
+ * `length` non-na values, and a bar whose own value is `na` keeps the previous result.
242
+ * The result is the square root of {@link variance} (0 when the variance is not positive), computed from
243
+ * compensated running sums of the values and of their squares, bit for bit as PineScript.
244
+ */
245
+ export function stdev(source, length, biased = true) {
246
+ return variance(source, length, biased).map((v) => (Number.isNaN(v) ? NaN : v > 0 ? Math.sqrt(v) : 0));
247
+ }
248
+ /**
249
+ * The last `length` non-na values up to bar `i` (the bar itself may be na), or null when fewer exist.
250
+ * `isNa` tells which values are skipped (NaN by default).
251
+ */
252
+ function lastValues(source, i, length) {
253
+ const values = [];
254
+ for (let j = i; j >= 0 && values.length < length; j--) {
255
+ const x = source[j];
256
+ if (!Number.isNaN(x))
257
+ values.push(x);
258
+ }
259
+ return values.length === length ? values : null;
260
+ }
261
+ /**
262
+ * The extreme of the window of `length` bars ending at each bar, in one pass (monotonic deque): the bar index of
263
+ * the oldest highest (or lowest) value, or -1 when the window is empty. As PineScript does for ta.highest /
264
+ * ta.lowest / ta.highestbars / ta.lowestbars, the window stops at the first `na` value going back, so a bar whose
265
+ * own value is `na` has an empty window. `zeros[i]` tells whether the window holds a +0 (highest) or a -0
266
+ * (lowest), the zero that `Math.max` / `Math.min` return when the extreme is 0.
267
+ */
268
+ function windowExtremes(source, length, lowest) {
269
+ const n = source.length;
270
+ const span = length > 0 ? Math.ceil(length) : 0; // the bars j = 0, 1... with j < length
271
+ const at = new Int32Array(n).fill(-1);
272
+ const zeros = new Uint8Array(n);
273
+ const queue = new Int32Array(n); // bar indices; values from best to worst, the oldest first among equal values
274
+ const preferredZero = lowest ? -0 : 0;
275
+ let head = 0;
276
+ let tail = 0;
277
+ let zeroCount = 0; // preferred zeros in the queue
278
+ let lastNa = -1;
279
+ const isPreferredZero = (k) => Object.is(source[k], preferredZero);
280
+ for (let i = 0; i < n; i++) {
281
+ const x = source[i];
282
+ if (Number.isNaN(x)) {
283
+ lastNa = i;
284
+ head = tail = 0;
285
+ zeroCount = 0;
286
+ continue;
287
+ }
288
+ // drop the values that the new one beats; equal values stay (the oldest wins ties)
289
+ while (tail > head && (lowest ? source[queue[tail - 1]] > x : source[queue[tail - 1]] < x)) {
290
+ if (isPreferredZero(queue[--tail]))
291
+ zeroCount--;
292
+ }
293
+ queue[tail++] = i;
294
+ if (isPreferredZero(i))
295
+ zeroCount++;
296
+ const start = Math.max(i - span + 1, lastNa + 1);
297
+ while (head < tail && queue[head] < start) {
298
+ if (isPreferredZero(queue[head++]))
299
+ zeroCount--;
300
+ }
301
+ if (head < tail) {
302
+ at[i] = queue[head];
303
+ zeros[i] = zeroCount > 0 ? 1 : 0;
304
+ }
305
+ }
306
+ return { at, zeros };
307
+ }
308
+ /** Offset of the best value of the window; on ties the oldest bar wins. 0 when the window is empty. */
309
+ function extremeOffset(source, length, lowest) {
310
+ const { at } = windowExtremes(source, length, lowest);
311
+ // -(i - at): -0 when the extreme is the current bar, as the offset -j of the window scan
312
+ return source.map((_, i) => (i < length - 1 ? NaN : at[i] < 0 ? 0 : -(i - at[i])));
313
+ }
314
+ /** Best value of the window (`na` when the window is empty), with the zero sign of `Math.max` / `Math.min`. */
315
+ function extremeValue(source, length, lowest) {
316
+ const { at, zeros } = windowExtremes(source, length, lowest);
317
+ return source.map((_, i) => {
318
+ if (i < length - 1 || at[i] < 0)
319
+ return NaN;
320
+ const v = source[at[i]];
321
+ return v === 0 && zeros[i] ? (lowest ? -0 : 0) : v;
322
+ });
323
+ }
324
+ /**
325
+ * Crossover - returns true when series1 crosses over series2 (moves from below to above).
326
+ *
327
+ * @param series1 - First series
328
+ * @param series2 - Second series
329
+ * @returns Boolean series (true at crossover points)
330
+ *
331
+ * @remarks
332
+ * - True when: series1[i]! > series2[i]! AND series1[i-1] <= series2[i-1]
333
+ * - First value is always false (no previous value to compare)
334
+ * - Useful for detecting bullish signals (e.g., fast MA crossing over slow MA)
335
+ *
336
+ * @example
337
+ * ```typescript
338
+ * const crossUp = ta.crossover(fastMA, slowMA);
339
+ * ```
340
+ */
341
+ export function crossover(series1, series2) {
342
+ const result = [];
343
+ for (let i = 0; i < series1.length; i++) {
344
+ if (i === 0) {
345
+ result.push(false);
346
+ }
347
+ else {
348
+ result.push(series1[i] > series2[i] && series1[i - 1] <= series2[i - 1]);
349
+ }
350
+ }
351
+ return result;
352
+ }
353
+ /**
354
+ * Crossunder - returns true when series1 crosses under series2 (moves from above to below).
355
+ *
356
+ * @param series1 - First series
357
+ * @param series2 - Second series
358
+ * @returns Boolean series (true at crossunder points)
359
+ *
360
+ * @remarks
361
+ * - True when: series1[i]! < series2[i]! AND series1[i-1] >= series2[i-1]
362
+ * - First value is always false (no previous value to compare)
363
+ * - Useful for detecting bearish signals (e.g., fast MA crossing under slow MA)
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * const crossDown = ta.crossunder(fastMA, slowMA);
368
+ * ```
369
+ */
370
+ export function crossunder(series1, series2) {
371
+ const result = [];
372
+ for (let i = 0; i < series1.length; i++) {
373
+ if (i === 0) {
374
+ result.push(false);
375
+ }
376
+ else {
377
+ result.push(series1[i] < series2[i] && series1[i - 1] >= series2[i - 1]);
378
+ }
379
+ }
380
+ return result;
381
+ }
382
+ /**
383
+ * Change - calculates the difference between the current value and its value length bars ago.
384
+ *
385
+ * @param source - Series of values to process
386
+ * @param length - Number of bars back (default: 1)
387
+ * @returns Change series (source[i]! - source[i - length])
388
+ *
389
+ * @remarks
390
+ * - Returns NaN for the first `length` values
391
+ * - Default length is 1 (difference from previous bar)
392
+ * - Positive values indicate increase, negative values indicate decrease
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * const change1 = ta.change(closePrices); // Daily change
397
+ * const change5 = ta.change(closePrices, 5); // 5-day change
398
+ * ```
399
+ */
400
+ export function change(source, length = 1) {
401
+ const result = [];
402
+ for (let i = 0; i < source.length; i++) {
403
+ if (i < length) {
404
+ result.push(NaN);
405
+ }
406
+ else {
407
+ result.push(source[i] - source[i - length]);
408
+ }
409
+ }
410
+ return result;
411
+ }
412
+ /**
413
+ * True Range - measures market volatility by calculating the greatest of three price ranges.
414
+ *
415
+ * @param handle_na - Defines how the function calculates when previous close is na (default: false)
416
+ * @param high - High price series (required when not using context API)
417
+ * @param low - Low price series (required when not using context API)
418
+ * @param close - Close price series (required when not using context API)
419
+ * @returns True range series
420
+ *
421
+ * @remarks
422
+ * - **PineScript v6 signature**: `ta.tr(handle_na?)` - uses implicit chart data
423
+ * - **JavaScript signature**: Requires explicit `high`, `low`, `close` OR use `createContext()`
424
+ * - True Range = max(high - low, abs(high - close[1]), abs(low - close[1]))
425
+ * - When `handle_na` is true: returns `high - low` if previous close is na
426
+ * - When `handle_na` is false: returns na if previous close is na
427
+ * - Used as a component in ATR calculations
428
+ *
429
+ * @example
430
+ * ```typescript
431
+ * // Direct call with explicit data
432
+ * const trueRange = ta.tr(false, high, low, close);
433
+ *
434
+ * // Or use context API for cleaner syntax
435
+ * const { ta } = createContext({ chart: { high, low, close } });
436
+ * const trueRange = ta.tr(); // Matches PineScript!
437
+ * ```
438
+ */
439
+ export function tr(handle_na = false, high, low, close) {
440
+ if (!high || !low || !close) {
441
+ throw new Error('ta.tr() requires high, low, and close series. ' +
442
+ 'Either pass them explicitly or use createContext({ chart: { high, low, close } }) for implicit data.');
443
+ }
444
+ const result = [];
445
+ for (let i = 0; i < high.length; i++) {
446
+ if (i === 0) {
447
+ // First bar: the previous close is na
448
+ result.push(handle_na ? high[i] - low[i] : NaN);
449
+ }
450
+ else {
451
+ const prevClose = close[i - 1];
452
+ // Handle na previous close based on handle_na parameter
453
+ if (isNaN(prevClose)) {
454
+ if (handle_na) {
455
+ result.push(high[i] - low[i]);
456
+ }
457
+ else {
458
+ result.push(NaN);
459
+ }
460
+ }
461
+ else {
462
+ const tr = Math.max(high[i] - low[i], Math.abs(high[i] - prevClose), Math.abs(low[i] - prevClose));
463
+ result.push(tr);
464
+ }
465
+ }
466
+ }
467
+ return result;
468
+ }
469
+ /**
470
+ * Average True Range - returns the RMA (Relative Moving Average) of true range.
471
+ *
472
+ * @param length - Number of bars (length)
473
+ * @param high - High price series (required when not using context API)
474
+ * @param low - Low price series (required when not using context API)
475
+ * @param close - Close price series (required when not using context API)
476
+ * @returns Average true range series
477
+ *
478
+ * @remarks
479
+ * - **PineScript v6 signature**: `ta.atr(length)` - uses implicit chart data
480
+ * - **JavaScript signature**: Requires explicit `high`, `low`, `close` OR use `createContext()`
481
+ * - Uses `ta.rma()` (Relative Moving Average) for smoothing, matching PineScript v6
482
+ * - True range is max(high - low, abs(high - close[1]), abs(low - close[1]))
483
+ * - ATR is a measure of volatility, higher values indicate greater volatility
484
+ * - `na` values in the source series are ignored
485
+ * - The function calculates on the `length` quantity of non-`na` values
486
+ *
487
+ * @example
488
+ * ```typescript
489
+ * // Direct call with explicit data
490
+ * const atr14 = ta.atr(14, high, low, close);
491
+ *
492
+ * // Or use context API for cleaner syntax
493
+ * const { ta } = createContext({ chart: { high, low, close } });
494
+ * const atr14 = ta.atr(14); // Matches PineScript!
495
+ * ```
496
+ */
497
+ export function atr(length, high, low, close) {
498
+ if (!high || !low || !close) {
499
+ throw new Error('ta.atr() requires high, low, and close series. ' +
500
+ 'Either pass them explicitly or use createContext({ chart: { high, low, close } }) for implicit data.');
501
+ }
502
+ // PineScript: ta.atr uses ta.tr(true), so bar 0 is high - low
503
+ const trueRange = tr(true, high, low, close);
504
+ return rma(trueRange, length);
505
+ }
506
+ /**
507
+ * SuperTrend Indicator - a trend-following indicator that helps identify trend direction.
508
+ *
509
+ * @param factor - The multiplier by which the ATR will get multiplied (series int/float)
510
+ * @param atrPeriod - Length of ATR (simple int)
511
+ * @param high - High price series (required when not using context API)
512
+ * @param low - Low price series (required when not using context API)
513
+ * @param close - Close price series (required when not using context API)
514
+ * @param wicks - Whether to use wicks for trend reversal (NOT in PineScript v6 API, default: false)
515
+ * @returns Tuple of [supertrend, direction] where direction is 1 (downtrend) or -1 (uptrend)
516
+ *
517
+ * @remarks
518
+ * - **PineScript v6 signature**: `ta.supertrend(factor, atrPeriod)` - uses implicit chart data
519
+ * - **JavaScript signature**: Requires explicit `high`, `low`, `close` OR use `createContext()`
520
+ * - The `wicks` parameter is NOT part of the official PineScript v6 API
521
+ * - Direction: 1 = downtrend (red), -1 = uptrend (green)
522
+ * - Uses hl2 (average of high and low) as the source
523
+ * - SuperTrend helps identify the current market trend and potential reversal points
524
+ *
525
+ * @example
526
+ * ```typescript
527
+ * // Direct call with explicit data
528
+ * const [supertrend, direction] = ta.supertrend(3, 10, high, low, close);
529
+ * // Plot uptrend when direction < 0
530
+ * // Plot downtrend when direction > 0
531
+ *
532
+ * // Or use context API for cleaner syntax
533
+ * const { ta } = createContext({ chart: { high, low, close } });
534
+ * const [supertrend, direction] = ta.supertrend(3, 10); // Matches PineScript!
535
+ * ```
536
+ */
537
+ export function supertrend(factor, atrPeriod, high, low, close, wicks = false) {
538
+ if (!high || !low || !close) {
539
+ throw new Error('ta.supertrend() requires high, low, and close series. ' +
540
+ 'Either pass them explicitly or use createContext({ chart: { high, low, close } }) for implicit data.');
541
+ }
542
+ const supertrendValues = [];
543
+ const directions = [];
544
+ // Calculate hl2 (average of high and low)
545
+ const source = [];
546
+ for (let i = 0; i < high.length; i++) {
547
+ source.push((high[i] + low[i]) / 2);
548
+ }
549
+ // Calculate ATR
550
+ const atrValues = atr(atrPeriod, high, low, close);
551
+ // Track previous values across iterations
552
+ let prevLowerBand = NaN;
553
+ let prevUpperBand = NaN;
554
+ let prevSuperTrend = NaN;
555
+ for (let i = 0; i < source.length; i++) {
556
+ const atrValue = atrValues[i] * factor;
557
+ // Skip calculation if ATR is not available yet
558
+ if (isNaN(atrValue)) {
559
+ supertrendValues.push(NaN);
560
+ directions.push(1);
561
+ continue;
562
+ }
563
+ // Calculate initial bands
564
+ let upperBand = source[i] + atrValue;
565
+ let lowerBand = source[i] - atrValue;
566
+ // Determine which price to use for comparison
567
+ const highPrice = wicks ? high[i] : close[i];
568
+ const lowPrice = wicks ? low[i] : close[i];
569
+ const prevLowPrice = i > 0 ? (wicks ? low[i - 1] : close[i - 1]) : 0;
570
+ const prevHighPrice = i > 0 ? (wicks ? high[i - 1] : close[i - 1]) : 0;
571
+ // Update bands conditionally (trailing behavior) - only if previous bands are valid
572
+ if (i > 0 && !isNaN(prevLowerBand) && !isNaN(prevUpperBand)) {
573
+ lowerBand = (lowerBand > prevLowerBand || prevLowPrice < prevLowerBand) ? lowerBand : prevLowerBand;
574
+ upperBand = (upperBand < prevUpperBand || prevHighPrice > prevUpperBand) ? upperBand : prevUpperBand;
575
+ }
576
+ // Determine trend direction
577
+ let currentDirection;
578
+ if (isNaN(prevSuperTrend)) {
579
+ // Initial direction when we don't have previous supertrend
580
+ currentDirection = 1;
581
+ }
582
+ else if (prevSuperTrend === prevUpperBand) {
583
+ // Was in downtrend (following upper band)
584
+ currentDirection = highPrice > upperBand ? -1 : 1;
585
+ }
586
+ else {
587
+ // Was in uptrend (following lower band)
588
+ currentDirection = lowPrice < lowerBand ? 1 : -1;
589
+ }
590
+ // Calculate supertrend value based on direction
591
+ const superTrendValue = currentDirection === -1 ? lowerBand : upperBand;
592
+ supertrendValues.push(superTrendValue);
593
+ directions.push(currentDirection);
594
+ // Update previous values for next iteration
595
+ prevLowerBand = lowerBand;
596
+ prevUpperBand = upperBand;
597
+ prevSuperTrend = superTrendValue;
598
+ }
599
+ return [supertrendValues, directions];
600
+ }
601
+ /**
602
+ * Relative Moving Average (RMA) - exponentially weighted moving average with alpha = 1 / length.
603
+ *
604
+ * @param source - Series of values to process
605
+ * @param length - Number of bars (length)
606
+ * @returns RMA of source for length bars back
607
+ *
608
+ * @remarks
609
+ * - Moving average used in RSI calculation
610
+ * - Alpha = 1 / length (different from EMA which uses alpha = 2 / (length + 1))
611
+ * - First value is initialized with SMA, then uses exponential smoothing
612
+ * - Formula: `RMA = (source + (length - 1) * RMA[1]) / length`, evaluated in this order as in PineScript
613
+ * (equal to `alpha * source + (1 - alpha) * RMA[1]`, but bit for bit)
614
+ * - `na` values (also +/-Infinity) in the source series are ignored
615
+ * - The function calculates on the `length` quantity of non-`na` values
616
+ *
617
+ * @example
618
+ * ```typescript
619
+ * const rma14 = ta.rma(closePrices, 14);
620
+ * // Used internally by RSI:
621
+ * // avgGain = ta.rma(gains, 14);
622
+ * // avgLoss = ta.rma(losses, 14);
623
+ * ```
624
+ */
625
+ export function rma(source, length) {
626
+ const result = [];
627
+ // Floor the length to match PineScript's auto-truncation of float to int
628
+ const len = Math.floor(length);
629
+ // Find the first index where we have enough non-na values for SMA initialization.
630
+ // An infinite value is na, as in PineScript.
631
+ let firstValidIndex = -1;
632
+ let validCount = 0;
633
+ let initSum = 0;
634
+ for (let i = 0; i < source.length; i++) {
635
+ const val = source[i];
636
+ if (val !== undefined && Number.isFinite(val)) {
637
+ initSum += val;
638
+ validCount++;
639
+ if (validCount === len) {
640
+ firstValidIndex = i;
641
+ break;
642
+ }
643
+ }
644
+ }
645
+ // Initialize RMA value with SMA of first `length` non-NaN values
646
+ let rmaValue = validCount > 0 ? initSum / validCount : NaN;
647
+ const rmaInitialized = firstValidIndex >= 0;
648
+ for (let i = 0; i < source.length; i++) {
649
+ if (!rmaInitialized || i < firstValidIndex) {
650
+ // Not enough data yet for RMA
651
+ result.push(NaN);
652
+ }
653
+ else if (i === firstValidIndex) {
654
+ // First valid RMA value
655
+ result.push(rmaValue);
656
+ }
657
+ else {
658
+ const val = source[i];
659
+ if (val !== undefined && Number.isFinite(val)) {
660
+ // PineScript evaluates (source + (length - 1) * RMA[1]) / length; the form
661
+ // alpha * source + (1 - alpha) * RMA[1] differs in the last bits
662
+ rmaValue = (val + (len - 1) * rmaValue) / len;
663
+ result.push(rmaValue);
664
+ }
665
+ else {
666
+ // na source: na on this bar; the next bar continues from the last value (as in PineScript)
667
+ result.push(NaN);
668
+ }
669
+ }
670
+ }
671
+ return result;
672
+ }
673
+ /**
674
+ * Weighted Moving Average (WMA) - moving average with linearly decreasing weights.
675
+ *
676
+ * @param source - Series of values to process
677
+ * @param length - Number of bars (length)
678
+ * @returns WMA of source for length bars back
679
+ *
680
+ * @remarks
681
+ * - Weighting factors decrease in arithmetical progression
682
+ * - Most recent value has weight `length`, previous has `length-1`, etc.
683
+ * - Formula: `sum(source[i]! * (length - i)) / sum(length - i)` for i = 0 to length-1
684
+ * - As in PineScript: na on a bar whose source is `na` (also +/-Infinity) and until `length` non-`na` values were
685
+ * received; an `na` value inside the window is replaced by the previous non-`na` value
686
+ * - More responsive to recent price changes than SMA
687
+ *
688
+ * @example
689
+ * ```typescript
690
+ * const wma20 = ta.wma(closePrices, 20);
691
+ * // WMA gives more weight to recent prices
692
+ * ```
693
+ */
694
+ export function wma(source, length) {
695
+ const result = [];
696
+ // Floor the length to match PineScript's auto-truncation of float to int
697
+ const len = Math.floor(length);
698
+ // PineScript: na on a bar with an na source (also +/-Infinity) and until `len` non-na values were received;
699
+ // otherwise the last `len` bars, each na replaced by the previous non-na value
700
+ const filled = [];
701
+ let last = NaN;
702
+ let count = 0;
703
+ for (let i = 0; i < source.length; i++) {
704
+ const v = source[i];
705
+ const isNa = v === undefined || v === null || !Number.isFinite(v);
706
+ if (!isNa) {
707
+ last = v;
708
+ count++;
709
+ }
710
+ filled.push(last);
711
+ if (isNa || count < len) {
712
+ result.push(NaN);
713
+ continue;
714
+ }
715
+ // Summed from the oldest bar (weight 1) to the newest (weight len), as PineScript (bit for bit)
716
+ let sum = 0;
717
+ let weightSum = 0;
718
+ for (let j = len - 1; j >= 0; j--) {
719
+ const weight = len - j;
720
+ sum += filled[i - j] * weight;
721
+ weightSum += weight;
722
+ }
723
+ result.push(sum / weightSum);
724
+ }
725
+ return result;
726
+ }
727
+ /**
728
+ * Highest value over the last `length` bars.
729
+ *
730
+ * @param source - Series of values to process
731
+ * @param length - Number of bars
732
+ * @returns Highest value series (na on the first `length - 1` bars)
733
+ *
734
+ * @remarks
735
+ * As in PineScript: an `na` value ends the window, so only the values after the last `na`
736
+ * count, and a bar whose own value is `na` gives `na`.
737
+ * PineScript `ta.highest(length)` (source = high) is `ta.highest(length)` in the script API.
738
+ */
739
+ export function highest(source, length) {
740
+ return extremeValue(source, length, false);
741
+ }
742
+ /**
743
+ * Lowest value over the last `length` bars.
744
+ *
745
+ * @param source - Series of values to process
746
+ * @param length - Number of bars
747
+ * @returns Lowest value series (na on the first `length - 1` bars)
748
+ *
749
+ * @remarks
750
+ * As in PineScript: an `na` value ends the window, so only the values after the last `na`
751
+ * count, and a bar whose own value is `na` gives `na`.
752
+ */
753
+ export function lowest(source, length) {
754
+ return extremeValue(source, length, true);
755
+ }
756
+ /**
757
+ * Cumulative Sum - returns the total sum of all elements from the beginning.
758
+ *
759
+ * @param source - Series of values to process
760
+ * @returns Series containing the cumulative sum at each bar
761
+ *
762
+ * @remarks
763
+ * - Returns the running total of all values from index 0 to current index
764
+ * - As in PineScript: an `na` value adds nothing to the sum,
765
+ * and the result is `na` on that bar only; the sum continues on the next bars
766
+ * - Useful for calculating total volume, total trades, etc.
767
+ *
768
+ * @example
769
+ * ```typescript
770
+ * const cumulativeVolume = ta.cum(volume);
771
+ * const totalGains = ta.cum(gains);
772
+ * ```
773
+ */
774
+ export function cum(source) {
775
+ let sum = 0;
776
+ return source.map((v) => {
777
+ // PineScript: an na value (also +/-Infinity) gives na on its bar and is not added
778
+ if (!Number.isFinite(v))
779
+ return NaN;
780
+ sum += v;
781
+ return sum;
782
+ });
783
+ }
784
+ /**
785
+ * Cross - returns true when two series cross each other (either direction).
786
+ *
787
+ * @param source1 - First series
788
+ * @param source2 - Second series
789
+ * @returns Boolean series (true at cross points)
790
+ *
791
+ * @remarks
792
+ * - True when: (source1[i]! > source2[i]! AND source1[i-1] <= source2[i-1]) OR
793
+ * (source1[i]! < source2[i]! AND source1[i-1] >= source2[i-1])
794
+ * - First value is always false (no previous value to compare)
795
+ * - Detects any crossing (either over or under)
796
+ * - Use `ta.crossover()` or `ta.crossunder()` for directional crosses
797
+ *
798
+ * @example
799
+ * ```typescript
800
+ * const crossed = ta.cross(fastMA, slowMA);
801
+ * // Detect any MA crossover
802
+ * ```
803
+ */
804
+ export function cross(source1, source2) {
805
+ const result = [];
806
+ for (let i = 0; i < source1.length; i++) {
807
+ if (i === 0) {
808
+ result.push(false);
809
+ }
810
+ else {
811
+ const crossedUp = source1[i] > source2[i] && source1[i - 1] <= source2[i - 1];
812
+ const crossedDown = source1[i] < source2[i] && source1[i - 1] >= source2[i - 1];
813
+ result.push(crossedUp || crossedDown);
814
+ }
815
+ }
816
+ return result;
817
+ }
818
+ /**
819
+ * ta.rising / ta.falling: true when each of the `length` steps between the last `length + 1` non-na values (up to
820
+ * the current bar) passes `step(newer, older)`; false when there are fewer non-na values.
821
+ *
822
+ * @internal
823
+ */
824
+ function monotonic(source, length, step) {
825
+ const result = [];
826
+ const values = []; // non-na values so far
827
+ for (let i = 0; i < source.length; i++) {
828
+ const v = source[i];
829
+ if (!Number.isNaN(v))
830
+ values.push(v);
831
+ const n = values.length;
832
+ let ok = n > length;
833
+ for (let j = 1; ok && j <= length; j++)
834
+ ok = step(values[n - j], values[n - j - 1]);
835
+ result.push(ok);
836
+ }
837
+ return result;
838
+ }
839
+ /**
840
+ * Rising - returns true if source is rising for length bars.
841
+ *
842
+ * @param source - Series of values to process
843
+ * @param length - Number of bars (length)
844
+ * @returns Boolean series (true when rising)
845
+ *
846
+ * @remarks
847
+ * PineScript rules:
848
+ * - each of the `length` steps is a rise larger than the 1e-10 tolerance
849
+ * - `na` values are skipped: the steps are between the last `length + 1` non-`na` values up to the current bar
850
+ * (on an `na` bar, the result of the last non-`na` bar)
851
+ * - false while there are fewer than `length + 1` non-`na` values
852
+ *
853
+ * @example
854
+ * ```typescript
855
+ * const isRising = ta.rising(close, 3);
856
+ * // Detect upward momentum
857
+ * ```
858
+ */
859
+ export function rising(source, length) {
860
+ return monotonic(source, length, gt);
861
+ }
862
+ /**
863
+ * Falling - returns true if source is falling for length bars.
864
+ *
865
+ * @param source - Series of values to process
866
+ * @param length - Number of bars (length)
867
+ * @returns Boolean series (true when falling)
868
+ *
869
+ * @remarks
870
+ * PineScript rules:
871
+ * - each of the `length` steps is a fall larger than the 1e-10 tolerance
872
+ * - `na` values are skipped: the steps are between the last `length + 1` non-`na` values up to the current bar
873
+ * (on an `na` bar, the result of the last non-`na` bar)
874
+ * - false while there are fewer than `length + 1` non-`na` values
875
+ *
876
+ * @example
877
+ * ```typescript
878
+ * const isFalling = ta.falling(close, 3);
879
+ * // Detect downward momentum
880
+ * ```
881
+ */
882
+ export function falling(source, length) {
883
+ return monotonic(source, length, lt);
884
+ }
885
+ /**
886
+ * Rate of Change (ROC) - percentage change between current value and value length bars ago.
887
+ *
888
+ * @param source - Series of values to process
889
+ * @param length - Number of bars (length)
890
+ * @returns ROC series as percentage
891
+ *
892
+ * @remarks
893
+ * - Formula: `100 * change(source, length) / source[length]`
894
+ * - Equivalent to: `100 * (source - source[length]) / source[length]`
895
+ * - Returns percentage change, e.g., 5.0 means 5% increase
896
+ * - `na` values in the source series are included in calculations and will produce an `na` result
897
+ * - Useful for momentum analysis and trend strength measurement
898
+ *
899
+ * @example
900
+ * ```typescript
901
+ * const roc10 = ta.roc(closePrices, 10);
902
+ * // Positive ROC indicates upward momentum
903
+ * // Negative ROC indicates downward momentum
904
+ * ```
905
+ */
906
+ export function roc(source, length) {
907
+ const result = [];
908
+ for (let i = 0; i < source.length; i++) {
909
+ if (i < length) {
910
+ result.push(NaN);
911
+ }
912
+ else {
913
+ const oldValue = source[i - length];
914
+ if (oldValue === 0 || isNaN(oldValue) || isNaN(source[i])) {
915
+ result.push(NaN);
916
+ }
917
+ else {
918
+ const changeValue = source[i] - oldValue;
919
+ result.push((100 * changeValue) / oldValue);
920
+ }
921
+ }
922
+ }
923
+ return result;
924
+ }
925
+ /**
926
+ * Momentum (MOM) - difference between current value and value length bars ago.
927
+ *
928
+ * @param source - Series of values to process
929
+ * @param length - Offset from current bar to previous bar
930
+ * @returns Momentum series
931
+ *
932
+ * @remarks
933
+ * - Formula: `source - source[length]`
934
+ * - Equivalent to `ta.change(source, length)`
935
+ * - Positive momentum indicates upward movement
936
+ * - Negative momentum indicates downward movement
937
+ * - `na` values in the source series are included in calculations and will produce an `na` result
938
+ *
939
+ * @example
940
+ * ```typescript
941
+ * const mom10 = ta.mom(closePrices, 10);
942
+ * // Measures raw price momentum over 10 bars
943
+ * ```
944
+ */
945
+ export function mom(source, length) {
946
+ const result = [];
947
+ for (let i = 0; i < source.length; i++) {
948
+ if (i < length) {
949
+ result.push(NaN);
950
+ }
951
+ else {
952
+ if (isNaN(source[i]) || isNaN(source[i - length])) {
953
+ result.push(NaN);
954
+ }
955
+ else {
956
+ result.push(source[i] - source[i - length]);
957
+ }
958
+ }
959
+ }
960
+ return result;
961
+ }
962
+ /**
963
+ * Mean Absolute Deviation - measure of difference between series and its SMA.
964
+ *
965
+ * @param source - Series of values to process
966
+ * @param length - Number of bars (length)
967
+ * @returns Mean absolute deviation series
968
+ *
969
+ * @remarks
970
+ * - Measures average absolute distance from the mean
971
+ * - Formula: `sum(abs(source[i]! - sma)) / length` for i in 0 to length-1
972
+ * - Less sensitive to outliers than standard deviation
973
+ * - `na` values in the source series are ignored
974
+ * - The function calculates on the `length` quantity of non-`na` values
975
+ *
976
+ * @example
977
+ * ```typescript
978
+ * const dev10 = ta.dev(closePrices, 10);
979
+ * // Measures volatility using mean absolute deviation
980
+ * ```
981
+ */
982
+ export function dev(source, length) {
983
+ const result = [];
984
+ // PineScript: na when the window of `length` bars holds an na value
985
+ const meanValues = strictWindowMean(source, length);
986
+ for (let i = 0; i < source.length; i++) {
987
+ if (i < length - 1 || isNaN(meanValues[i])) {
988
+ result.push(NaN);
989
+ }
990
+ else {
991
+ let sum = 0;
992
+ for (let j = 0; j < length; j++) {
993
+ if (!isNaN(source[i - j])) {
994
+ sum += Math.abs(source[i - j] - meanValues[i]);
995
+ }
996
+ }
997
+ result.push(sum / length);
998
+ }
999
+ }
1000
+ return result;
1001
+ }
1002
+ /**
1003
+ * Variance - expectation of squared deviation from mean.
1004
+ *
1005
+ * @param source - Series of values to process
1006
+ * @param length - Number of bars (length)
1007
+ * @param biased - Use biased (true) or unbiased (false) estimate (default: true)
1008
+ * @returns Variance series
1009
+ *
1010
+ * @remarks
1011
+ * - Measures how far values are spread out from their mean
1012
+ * - If `biased` is true: divides by `length` (population variance)
1013
+ * - If `biased` is false: divides by `length - 1` (sample variance)
1014
+ * - Formula (biased): `sum((source[i]! - mean)^2) / length`
1015
+ * - Formula (unbiased): `sum((source[i]! - mean)^2) / (length - 1)`
1016
+ * - `na` values in the source series are ignored
1017
+ * - The function calculates on the `length` quantity of non-`na` values
1018
+ * - Relationship: `stdev = sqrt(variance)`
1019
+ * - Computed as PineScript, bit for bit, from compensated running sums of the values and of their squares:
1020
+ * biased `sumSq / length - mean * mean`, unbiased `sumSq / (length - 1) - mean * sum / (length - 1)`
1021
+ *
1022
+ * @example
1023
+ * ```typescript
1024
+ * const variance20 = ta.variance(closePrices, 20);
1025
+ * const sampleVariance = ta.variance(closePrices, 20, false);
1026
+ * ```
1027
+ */
1028
+ export function variance(source, length, biased = true) {
1029
+ const len = Math.floor(length);
1030
+ // PineScript: the last `len` non-na values (also +/-Infinity skipped), as ta.sma
1031
+ if ((biased ? len : len - 1) <= 0)
1032
+ return source.map(() => NaN);
1033
+ return runningVariance(source, len, biased);
1034
+ }
1035
+ /**
1036
+ * Median - returns the median (middle value) of the series.
1037
+ *
1038
+ * @param source - Series of values to process
1039
+ * @param length - Number of bars (length)
1040
+ * @returns Median series
1041
+ *
1042
+ * @remarks
1043
+ * - Returns the middle value when values are sorted
1044
+ * - For an even length, returns the average of the two middle values
1045
+ * - As in PineScript: `na` values are skipped, the window holds the last `length` non-na values
1046
+ * (going back as many bars as needed), and a bar whose own value is `na` still gets a result
1047
+ * - Returns NaN until `length` non-na values exist
1048
+ * - More robust to outliers than mean (SMA)
1049
+ *
1050
+ * @example
1051
+ * ```typescript
1052
+ * const median20 = ta.median(closePrices, 20);
1053
+ * // Median is less affected by extreme values than SMA
1054
+ * ```
1055
+ */
1056
+ export function median(source, length) {
1057
+ const len = Math.floor(length);
1058
+ const n = source.length;
1059
+ const out = new Array(n).fill(NaN);
1060
+ if (!(len >= 1))
1061
+ return out;
1062
+ // The last `len` non-na values, sorted; equal values (also +0 / -0) from the newest to the oldest, which is the
1063
+ // order a stable sort of the window gives, so the middle values are the same numbers.
1064
+ const sorted = [];
1065
+ const kept = new Float64Array(n); // the non-na values in bar order
1066
+ let count = 0;
1067
+ /** First position whose value is >= v (`after`: > v). */
1068
+ const bound = (v, after) => {
1069
+ let lo = 0;
1070
+ let hi = sorted.length;
1071
+ while (lo < hi) {
1072
+ const m = (lo + hi) >> 1;
1073
+ if (after ? sorted[m] <= v : sorted[m] < v)
1074
+ lo = m + 1;
1075
+ else
1076
+ hi = m;
1077
+ }
1078
+ return lo;
1079
+ };
1080
+ const mid = Math.floor(len / 2);
1081
+ for (let i = 0; i < n; i++) {
1082
+ const x = source[i];
1083
+ if (!Number.isNaN(x)) {
1084
+ kept[count++] = x;
1085
+ sorted.splice(bound(x, false), 0, x); // before the equal values: the newest first
1086
+ // the value leaving the window is the oldest, so the last of its equal values
1087
+ if (count > len)
1088
+ sorted.splice(bound(kept[count - 1 - len], true) - 1, 1);
1089
+ }
1090
+ if (count >= len)
1091
+ out[i] = len % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
1092
+ }
1093
+ return out;
1094
+ }
1095
+ /**
1096
+ * Symmetrically Weighted Moving Average (SWMA) - fixed length 4 with symmetric weights.
1097
+ *
1098
+ * @param source - Series of values to process
1099
+ * @returns SWMA series
1100
+ *
1101
+ * @remarks
1102
+ * - Fixed length of 4 bars
1103
+ * - Weights: [1/6, 2/6, 2/6, 1/6] (symmetric)
1104
+ * - Formula: `source[3] * 1/6 + source[2] * 2/6 + source[1] * 2/6 + source[0] * 1/6`
1105
+ * - More weight given to middle values
1106
+ * - `na` values in the source series are included in calculations and will produce an `na` result
1107
+ * - Returns NaN for the first 3 bars
1108
+ *
1109
+ * @example
1110
+ * ```typescript
1111
+ * const swma = ta.swma(closePrices);
1112
+ * // Smoothed price with symmetric weighting
1113
+ * ```
1114
+ */
1115
+ export function swma(source) {
1116
+ const result = [];
1117
+ for (let i = 0; i < source.length; i++) {
1118
+ if (i < 3) {
1119
+ result.push(NaN);
1120
+ }
1121
+ else {
1122
+ // Check for any NaN values in the window
1123
+ if (isNaN(source[i]) || isNaN(source[i - 1]) || isNaN(source[i - 2]) || isNaN(source[i - 3])) {
1124
+ result.push(NaN);
1125
+ }
1126
+ else {
1127
+ const value = source[i - 3] * (1 / 6) +
1128
+ source[i - 2] * (2 / 6) +
1129
+ source[i - 1] * (2 / 6) +
1130
+ source[i] * (1 / 6);
1131
+ result.push(value);
1132
+ }
1133
+ }
1134
+ }
1135
+ return result;
1136
+ }
1137
+ /**
1138
+ * Volume Weighted Moving Average (VWMA) - moving average weighted by volume.
1139
+ *
1140
+ * @param source - Series of values to process
1141
+ * @param length - Number of bars (length)
1142
+ * @param volume - Volume series (required when not using context API)
1143
+ * @returns VWMA series
1144
+ *
1145
+ * @remarks
1146
+ * - **PineScript v6 signature**: `ta.vwma(source, length)` - uses implicit volume data
1147
+ * - **JavaScript signature**: Requires explicit `volume` OR use `createContext()`
1148
+ * - Formula: `sma(source * volume, length) / sma(volume, length)`
1149
+ * - Gives more weight to bars with higher volume
1150
+ * - `na` values in the source series are ignored
1151
+ * - Useful for price analysis considering volume significance
1152
+ *
1153
+ * @example
1154
+ * ```typescript
1155
+ * // Direct call with explicit volume
1156
+ * const vwma20 = ta.vwma(closePrices, 20, volumeData);
1157
+ *
1158
+ * // Or use context API for cleaner syntax
1159
+ * const { ta } = createContext({ chart: { high, low, close, volume } });
1160
+ * const vwma20 = ta.vwma(close, 20); // Matches PineScript!
1161
+ * ```
1162
+ */
1163
+ export function vwma(source, length, volume) {
1164
+ if (!volume) {
1165
+ throw new Error('ta.vwma() requires volume series. ' +
1166
+ 'Either pass it explicitly or use createContext({ chart: { ..., volume } }) for implicit data.');
1167
+ }
1168
+ // Calculate source * volume
1169
+ const sourceTimesVolume = [];
1170
+ for (let i = 0; i < source.length; i++) {
1171
+ sourceTimesVolume.push(source[i] * volume[i]);
1172
+ }
1173
+ const numerator = sma(sourceTimesVolume, length);
1174
+ const denominator = sma(volume, length);
1175
+ const result = [];
1176
+ for (let i = 0; i < source.length; i++) {
1177
+ if (denominator[i] === 0 || isNaN(denominator[i])) {
1178
+ result.push(NaN);
1179
+ }
1180
+ else {
1181
+ result.push(numerator[i] / denominator[i]);
1182
+ }
1183
+ }
1184
+ return result;
1185
+ }
1186
+ /**
1187
+ * Linear Regression - line that best fits prices using least squares method.
1188
+ *
1189
+ * @param source - Series of values to process
1190
+ * @param length - Number of bars (length)
1191
+ * @param offset - Offset (default: 0)
1192
+ * @returns Linear regression value
1193
+ *
1194
+ * @remarks
1195
+ * - Calculates line of best fit using least squares method
1196
+ * - Formula: `linreg = intercept + slope * (length - 1 - offset)`
1197
+ * - offset=0 gives current fitted value, offset<0 gives future projection
1198
+ * - `na` values in the source series are included in calculations and will produce an `na` result
1199
+ * - Useful for trend detection and prediction
1200
+ *
1201
+ * @example
1202
+ * ```typescript
1203
+ * const linreg20 = ta.linreg(closePrices, 20, 0);
1204
+ * const linregFuture = ta.linreg(closePrices, 20, -5); // Project 5 bars ahead
1205
+ * ```
1206
+ */
1207
+ export function linreg(source, length, offset = 0) {
1208
+ const result = [];
1209
+ // Floor the length to match PineScript's auto-truncation of float to int
1210
+ const len = Math.floor(length);
1211
+ for (let i = 0; i < source.length; i++) {
1212
+ if (i < len - 1) {
1213
+ result.push(NaN);
1214
+ }
1215
+ else {
1216
+ // Check for NaN values in window
1217
+ let hasNaN = false;
1218
+ for (let j = 0; j < len; j++) {
1219
+ if (isNaN(source[i - j])) {
1220
+ hasNaN = true;
1221
+ break;
1222
+ }
1223
+ }
1224
+ if (hasNaN) {
1225
+ result.push(NaN);
1226
+ }
1227
+ else {
1228
+ // Calculate least squares regression
1229
+ let sumX = 0;
1230
+ let sumY = 0;
1231
+ let sumXY = 0;
1232
+ let sumX2 = 0;
1233
+ for (let j = 0; j < len; j++) {
1234
+ const x = j;
1235
+ const y = source[i - (len - 1 - j)];
1236
+ sumX += x;
1237
+ sumY += y;
1238
+ sumXY += x * y;
1239
+ sumX2 += x * x;
1240
+ }
1241
+ const slope = (len * sumXY - sumX * sumY) / (len * sumX2 - sumX * sumX);
1242
+ const intercept = (sumY - slope * sumX) / len;
1243
+ // Calculate linreg value at offset
1244
+ const x = len - 1 - offset;
1245
+ result.push(intercept + slope * x);
1246
+ }
1247
+ }
1248
+ }
1249
+ return result;
1250
+ }
1251
+ /**
1252
+ * Correlation Coefficient - measures degree to which two series deviate from their means together.
1253
+ *
1254
+ * @param source1 - First series
1255
+ * @param source2 - Second series
1256
+ * @param length - Number of bars (length)
1257
+ * @returns Correlation coefficient (-1 to +1)
1258
+ *
1259
+ * @remarks
1260
+ * - Returns value between -1 and +1
1261
+ * - +1 = perfect positive correlation
1262
+ * - -1 = perfect negative correlation
1263
+ * - 0 = no correlation
1264
+ * - Measures linear relationship between two series
1265
+ * - `na` values in the source series are ignored
1266
+ * - The function calculates on the `length` quantity of non-`na` values
1267
+ *
1268
+ * @example
1269
+ * ```typescript
1270
+ * const corr = ta.correlation(series1, series2, 20);
1271
+ * // Values close to +1 or -1 indicate strong relationship
1272
+ * ```
1273
+ */
1274
+ export function correlation(source1, source2, length) {
1275
+ const result = [];
1276
+ // the non-NaN pairs of the window, newest first (reused on every bar)
1277
+ const span = length > 0 ? Math.ceil(length) : 0;
1278
+ const a = new Float64Array(span);
1279
+ const b = new Float64Array(span);
1280
+ for (let i = 0; i < source1.length; i++) {
1281
+ if (i < length - 1) {
1282
+ result.push(NaN);
1283
+ continue;
1284
+ }
1285
+ let m = 0;
1286
+ for (let j = 0; j < length; j++) {
1287
+ const v1 = source1[i - j];
1288
+ const v2 = source2[i - j];
1289
+ if (!isNaN(v1) && !isNaN(v2)) {
1290
+ a[m] = v1;
1291
+ b[m] = v2;
1292
+ m++;
1293
+ }
1294
+ }
1295
+ if (m === 0) {
1296
+ result.push(NaN);
1297
+ continue;
1298
+ }
1299
+ // Calculate means
1300
+ let sum1 = 0;
1301
+ let sum2 = 0;
1302
+ for (let k = 0; k < m; k++) {
1303
+ sum1 += a[k];
1304
+ sum2 += b[k];
1305
+ }
1306
+ const mean1 = sum1 / m;
1307
+ const mean2 = sum2 / m;
1308
+ // Calculate correlation components
1309
+ let numerator = 0;
1310
+ let sum1Sq = 0;
1311
+ let sum2Sq = 0;
1312
+ for (let k = 0; k < m; k++) {
1313
+ const dev1 = a[k] - mean1;
1314
+ const dev2 = b[k] - mean2;
1315
+ numerator += dev1 * dev2;
1316
+ sum1Sq += dev1 * dev1;
1317
+ sum2Sq += dev2 * dev2;
1318
+ }
1319
+ const denominator = Math.sqrt(sum1Sq * sum2Sq);
1320
+ result.push(denominator === 0 ? NaN : numerator / denominator);
1321
+ }
1322
+ return result;
1323
+ }
1324
+ /**
1325
+ * Percent Rank - percentage of how many previous values were less than or equal to current value.
1326
+ *
1327
+ * @param source - Series of values to process
1328
+ * @param length - Number of bars (length)
1329
+ * @returns Percent rank (0 to 100)
1330
+ *
1331
+ * @remarks
1332
+ * - Returns value between 0 and 100
1333
+ * - 0 = current value is lowest in the period
1334
+ * - 100 = current value is highest in the period
1335
+ * - 50 = current value is at median
1336
+ * - Useful for identifying relative strength within a period
1337
+ *
1338
+ * PineScript rules:
1339
+ * - percentrank = count(source[k] <= source, k = 1..length) / length * 100
1340
+ * - a value from bar `length` on, when the current source is not `na`; an `na` value in the window counts as not
1341
+ * `<=` the current value
1342
+ * - the comparison uses the 1e-10 tolerance
1343
+ *
1344
+ * @example
1345
+ * ```typescript
1346
+ * const pctrank = ta.percentrank(closePrices, 100);
1347
+ * // Values near 100 indicate recent strength
1348
+ * // Values near 0 indicate recent weakness
1349
+ * ```
1350
+ */
1351
+ export function percentrank(source, length) {
1352
+ const result = [];
1353
+ for (let i = 0; i < source.length; i++) {
1354
+ const currentValue = source[i];
1355
+ if (i < length || Number.isNaN(currentValue)) {
1356
+ result.push(NaN);
1357
+ continue;
1358
+ }
1359
+ // le() is false for an na value in the window
1360
+ let countLessOrEqual = 0;
1361
+ for (let j = 1; j <= length; j++) {
1362
+ if (le(source[i - j], currentValue))
1363
+ countLessOrEqual++;
1364
+ }
1365
+ result.push((countLessOrEqual / length) * 100);
1366
+ }
1367
+ return result;
1368
+ }
1369
+ /**
1370
+ * Commodity Channel Index (CCI) - measures deviation from average price.
1371
+ *
1372
+ * @param source - Series of values to process
1373
+ * @param length - Number of bars (length)
1374
+ * @returns CCI series
1375
+ *
1376
+ * @remarks
1377
+ * - CCI = (Typical Price - SMA of TP) / (0.015 * Mean Deviation)
1378
+ * - Typical Price = (High + Low + Close) / 3
1379
+ * - Mean Deviation = Average of absolute differences from mean
1380
+ * - Scaled by 0.015 to provide more readable numbers
1381
+ * - As in PineScript: when the mean deviation is 0 (1e-10 tolerance, a flat window), the CCI of the previous bar
1382
+ * - Values above +100 indicate overbought conditions
1383
+ * - Values below -100 indicate oversold conditions
1384
+ * - \`na\` values in the source series are ignored
1385
+ *
1386
+ * @example
1387
+ * \`\`\`typescript
1388
+ * const cci20 = ta.cci(typicalPrice, 20);
1389
+ * // Overbought when cci > 100
1390
+ * // Oversold when cci < -100
1391
+ * \`\`\`
1392
+ */
1393
+ export function cci(source, length) {
1394
+ const result = [];
1395
+ const smaValues = sma(source, length);
1396
+ const devValues = dev(source, length);
1397
+ for (let i = 0; i < source.length; i++) {
1398
+ if (isNaN(smaValues[i]) || isNaN(devValues[i])) {
1399
+ result.push(NaN);
1400
+ }
1401
+ else if (eq(devValues[i], 0)) {
1402
+ result.push(i > 0 ? result[i - 1] : NaN);
1403
+ }
1404
+ else {
1405
+ const cci = (source[i] - smaValues[i]) / (0.015 * devValues[i]);
1406
+ result.push(cci);
1407
+ }
1408
+ }
1409
+ return result;
1410
+ }
1411
+ /**
1412
+ * Stochastic Oscillator - momentum indicator comparing closing price to price range.
1413
+ *
1414
+ * @param source - Source series (typically close)
1415
+ * @param high - High price series
1416
+ * @param low - Low price series
1417
+ * @param length - Number of bars (length)
1418
+ * @returns Stochastic %K series (values range from 0 to 100)
1419
+ *
1420
+ * @remarks
1421
+ * - Formula: 100 * (close - lowest(low, length)) / (highest(high, length) - lowest(low, length))
1422
+ * - Measures where close is relative to the high-low range
1423
+ * - Values above 80 typically indicate overbought
1424
+ * - Values below 20 typically indicate oversold
1425
+ * - Returns NaN when range is zero (high equals low)
1426
+ * - \`na\` values in the source series are ignored
1427
+ *
1428
+ * @example
1429
+ * \`\`\`typescript
1430
+ * const stochK = ta.stoch(close, high, low, 14);
1431
+ * // Smooth with SMA for %D line:
1432
+ * const stochD = ta.sma(stochK, 3);
1433
+ * \`\`\`
1434
+ */
1435
+ export function stoch(source, high, low, length) {
1436
+ const result = [];
1437
+ const lowestValues = lowest(low, length);
1438
+ const highestValues = highest(high, length);
1439
+ for (let i = 0; i < source.length; i++) {
1440
+ if (isNaN(lowestValues[i]) || isNaN(highestValues[i])) {
1441
+ result.push(NaN);
1442
+ }
1443
+ else {
1444
+ const range = highestValues[i] - lowestValues[i];
1445
+ if (range === 0) {
1446
+ result.push(NaN);
1447
+ }
1448
+ else {
1449
+ const stochValue = 100 * (source[i] - lowestValues[i]) / range;
1450
+ result.push(stochValue);
1451
+ }
1452
+ }
1453
+ }
1454
+ return result;
1455
+ }
1456
+ /**
1457
+ * Money Flow Index (MFI) - volume-weighted RSI measuring buying and selling pressure.
1458
+ *
1459
+ * @param source - Source series (typically hlc3 or close)
1460
+ * @param length - Number of bars (length)
1461
+ * @param volume - Volume series (required when not using context API)
1462
+ * @returns MFI series (values range from 0 to 100)
1463
+ *
1464
+ * @remarks
1465
+ * - **PineScript v6 signature**: `ta.mfi(source, length)` - uses implicit volume data
1466
+ * - **JavaScript signature**: Requires explicit `volume` OR use `createContext()`
1467
+ * - Combines price and volume to identify overbought/oversold conditions
1468
+ * - Formula: 100 - (100 / (1 + Positive Money Flow / Negative Money Flow))
1469
+ * - Values above 80 indicate overbought
1470
+ * - Values below 20 indicate oversold
1471
+ * - `na` values in the source series are ignored
1472
+ *
1473
+ * @example
1474
+ * ```typescript
1475
+ * // Direct call with explicit volume
1476
+ * const mfi14 = ta.mfi(hlc3, 14, volume);
1477
+ *
1478
+ * // Or use context API for cleaner syntax
1479
+ * const { ta } = createContext({ chart: { high, low, close, volume } });
1480
+ * const mfi14 = ta.mfi(hlc3, 14); // Matches PineScript!
1481
+ * ```
1482
+ */
1483
+ export function mfi(source, length, volume) {
1484
+ if (!volume) {
1485
+ throw new Error('ta.mfi() requires volume series. ' +
1486
+ 'Either pass it explicitly or use createContext({ chart: { ..., volume } }) for implicit data.');
1487
+ }
1488
+ // PineScript reference:
1489
+ // upper = math.sum(volume * (ta.change(src) <= 0 ? 0 : src), length)
1490
+ // lower = math.sum(volume * (ta.change(src) >= 0 ? 0 : src), length)
1491
+ // A comparison with na is false, so a bar with an na change (bar 0) adds its money flow to both sums.
1492
+ // The comparisons use the 1e-10 tolerance of Pine operators.
1493
+ const upperFlow = [];
1494
+ const lowerFlow = [];
1495
+ for (let i = 0; i < source.length; i++) {
1496
+ const change = i === 0 ? NaN : source[i] - source[i - 1];
1497
+ const flow = volume[i] * source[i];
1498
+ upperFlow.push(le(change, 0) ? 0 : flow);
1499
+ lowerFlow.push(ge(change, 0) ? 0 : flow);
1500
+ }
1501
+ // math.sum: the compensated running sum of the last `length` non-na values (as PineScript, bit for bit)
1502
+ const upper = runningSum(upperFlow, length);
1503
+ const lower = runningSum(lowerFlow, length);
1504
+ return upper.map((u, i) => {
1505
+ const l = lower[i];
1506
+ if (Number.isNaN(u) || Number.isNaN(l))
1507
+ return NaN;
1508
+ if (l === 0)
1509
+ return 100;
1510
+ return 100 - 100 / (1 + u / l);
1511
+ });
1512
+ }
1513
+ /**
1514
+ * Hull Moving Average (HMA) - improved moving average with reduced lag.
1515
+ *
1516
+ * @param source - Series of values to process
1517
+ * @param length - Number of bars (length)
1518
+ * @returns Hull moving average series
1519
+ *
1520
+ * @remarks
1521
+ * - Formula: WMA(2 * WMA(src, len/2) - WMA(src, len), sqrt(len))
1522
+ * - Significantly reduces lag compared to traditional moving averages
1523
+ * - Smoother than WMA while being more responsive
1524
+ * - Created by Alan Hull
1525
+ * - `na` values in the source series are ignored
1526
+ *
1527
+ * @example
1528
+ * ```typescript
1529
+ * const hma20 = ta.hma(closePrices, 20);
1530
+ * // Faster response to price changes than SMA or EMA
1531
+ * ```
1532
+ */
1533
+ export function hma(source, length) {
1534
+ const halfLength = Math.floor(length / 2);
1535
+ const sqrtLength = Math.floor(Math.sqrt(length));
1536
+ const wmaHalf = wma(source, halfLength);
1537
+ const wmaFull = wma(source, length);
1538
+ const diff = [];
1539
+ for (let i = 0; i < source.length; i++) {
1540
+ diff.push(2 * wmaHalf[i] - wmaFull[i]);
1541
+ }
1542
+ return wma(diff, sqrtLength);
1543
+ }
1544
+ /**
1545
+ * Parabolic SAR (Stop and Reverse) - trend-following indicator.
1546
+ *
1547
+ * @param start - Acceleration factor start value (typically 0.02)
1548
+ * @param inc - Acceleration factor increment (typically 0.02)
1549
+ * @param max - Maximum acceleration factor (typically 0.2)
1550
+ * @param high - High price series (required when not using context API)
1551
+ * @param low - Low price series (required when not using context API)
1552
+ * @param close - Close price series (required when not using context API)
1553
+ * @returns SAR series
1554
+ *
1555
+ * @remarks
1556
+ * - **PineScript v6 signature**: `ta.sar(start, inc, max)` - uses implicit chart data
1557
+ * - **JavaScript signature**: Requires explicit `high`, `low`, `close` OR use `createContext()`
1558
+ * - SAR below price indicates uptrend, above price indicates downtrend
1559
+ * - Created by J. Welles Wilder Jr.
1560
+ *
1561
+ * @example
1562
+ * ```typescript
1563
+ * const sar = ta.sar(0.02, 0.02, 0.2, high, low, close);
1564
+ * ```
1565
+ */
1566
+ export function sar(start, inc, max, high, low, close) {
1567
+ if (!high || !low || !close) {
1568
+ throw new Error('ta.sar() requires high, low, and close series. ' +
1569
+ 'Either pass them explicitly or use createContext({ chart: { high, low, close } }) for implicit data.');
1570
+ }
1571
+ const result = [];
1572
+ let sarValue = NaN;
1573
+ let extremePoint = NaN;
1574
+ let acceleration = start;
1575
+ let isUpTrend = false;
1576
+ let isFirstTrendBar = false;
1577
+ for (let i = 0; i < close.length; i++) {
1578
+ if (i === 0) {
1579
+ result.push(NaN);
1580
+ continue;
1581
+ }
1582
+ if (i === 1) {
1583
+ if (close[i] > close[i - 1]) {
1584
+ isUpTrend = true;
1585
+ extremePoint = high[i];
1586
+ sarValue = low[i - 1];
1587
+ }
1588
+ else {
1589
+ isUpTrend = false;
1590
+ extremePoint = low[i];
1591
+ sarValue = high[i - 1];
1592
+ }
1593
+ isFirstTrendBar = true;
1594
+ acceleration = start;
1595
+ }
1596
+ sarValue = sarValue + acceleration * (extremePoint - sarValue);
1597
+ if (isUpTrend) {
1598
+ if (sarValue > low[i]) {
1599
+ isFirstTrendBar = true;
1600
+ isUpTrend = false;
1601
+ sarValue = Math.max(high[i], extremePoint);
1602
+ extremePoint = low[i];
1603
+ acceleration = start;
1604
+ }
1605
+ }
1606
+ else {
1607
+ if (sarValue < high[i]) {
1608
+ isFirstTrendBar = true;
1609
+ isUpTrend = true;
1610
+ sarValue = Math.min(low[i], extremePoint);
1611
+ extremePoint = high[i];
1612
+ acceleration = start;
1613
+ }
1614
+ }
1615
+ if (!isFirstTrendBar) {
1616
+ if (isUpTrend) {
1617
+ if (high[i] > extremePoint) {
1618
+ extremePoint = high[i];
1619
+ acceleration = Math.min(acceleration + inc, max);
1620
+ }
1621
+ }
1622
+ else {
1623
+ if (low[i] < extremePoint) {
1624
+ extremePoint = low[i];
1625
+ acceleration = Math.min(acceleration + inc, max);
1626
+ }
1627
+ }
1628
+ }
1629
+ if (isUpTrend) {
1630
+ sarValue = Math.min(sarValue, low[i - 1]);
1631
+ if (i > 1) {
1632
+ sarValue = Math.min(sarValue, low[i - 2]);
1633
+ }
1634
+ }
1635
+ else {
1636
+ sarValue = Math.max(sarValue, high[i - 1]);
1637
+ if (i > 1) {
1638
+ sarValue = Math.max(sarValue, high[i - 2]);
1639
+ }
1640
+ }
1641
+ result.push(sarValue);
1642
+ isFirstTrendBar = false;
1643
+ }
1644
+ return result;
1645
+ }
1646
+ /**
1647
+ * Pivot detection as PineScript computes it.
1648
+ *
1649
+ * The value of the pivot bar is returned `rightbars` bars later, on the bar where the pivot is
1650
+ * confirmed, so no future bar is used. A pivot high may equal values on its left but must be higher
1651
+ * than every value on its right (a pivot low: equal on the left, lower on the right). An `na`
1652
+ * neighbour ends the check on its side; an `na` pivot value gives no pivot.
1653
+ */
1654
+ function pivot(source, leftbars, rightbars, isHigh) {
1655
+ // "beats" = the neighbour prevents the pivot
1656
+ const beatsLeft = isHigh ? (x, v) => x > v : (x, v) => x < v;
1657
+ const beatsRight = isHigh ? (x, v) => x >= v : (x, v) => x <= v;
1658
+ return source.map((_, i) => {
1659
+ const center = i - rightbars;
1660
+ if (center - leftbars < 0)
1661
+ return NaN;
1662
+ const v = source[center];
1663
+ if (Number.isNaN(v))
1664
+ return NaN;
1665
+ for (let j = 1; j <= leftbars; j++) {
1666
+ const x = source[center - j];
1667
+ if (Number.isNaN(x))
1668
+ break;
1669
+ if (beatsLeft(x, v))
1670
+ return NaN;
1671
+ }
1672
+ for (let j = 1; j <= rightbars; j++) {
1673
+ const x = source[center + j];
1674
+ if (Number.isNaN(x))
1675
+ break;
1676
+ if (beatsRight(x, v))
1677
+ return NaN;
1678
+ }
1679
+ return v;
1680
+ });
1681
+ }
1682
+ /**
1683
+ * Pivot High - detects pivot high points in the price series.
1684
+ *
1685
+ * @param sourceOrLeftbars - Source series or leftbars (overloaded)
1686
+ * @param leftbarsOrRightbars - Leftbars or rightbars (overloaded)
1687
+ * @param rightbars - Number of bars to the right (optional)
1688
+ * @param high - High price series (used in 2-param version)
1689
+ * @returns Series with the pivot high value on the bar where the pivot is confirmed
1690
+ * (`rightbars` bars after the pivot bar), NaN elsewhere
1691
+ *
1692
+ * @remarks
1693
+ * - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
1694
+ * index is `i - rightbars`
1695
+ * - A pivot high may equal values on its left but must be higher than all values on its right
1696
+ * - An `na` neighbour ends the check on its side
1697
+ *
1698
+ * @example
1699
+ * ```typescript
1700
+ * const pivotHighs = ta.pivothigh(high, 2, 2);
1701
+ * // pivotHighs[i] is high[i - 2] when bar i - 2 is a pivot high
1702
+ * ```
1703
+ */
1704
+ export function pivothigh(sourceOrLeftbars, leftbarsOrRightbars, rightbars, high) {
1705
+ if (rightbars === undefined) {
1706
+ if (!high) {
1707
+ throw new Error('ta.pivothigh() requires high series when using two-parameter version.');
1708
+ }
1709
+ return pivot(high, sourceOrLeftbars, leftbarsOrRightbars, true);
1710
+ }
1711
+ return pivot(sourceOrLeftbars, leftbarsOrRightbars, rightbars, true);
1712
+ }
1713
+ /**
1714
+ * Pivot Low - detects pivot low points in the price series.
1715
+ *
1716
+ * @param sourceOrLeftbars - Source series or leftbars (overloaded)
1717
+ * @param leftbarsOrRightbars - Leftbars or rightbars (overloaded)
1718
+ * @param rightbars - Number of bars to the right (optional)
1719
+ * @param low - Low price series (used in 2-param version)
1720
+ * @returns Series with the pivot low value on the bar where the pivot is confirmed
1721
+ * (`rightbars` bars after the pivot bar), NaN elsewhere
1722
+ *
1723
+ * @remarks
1724
+ * - As in PineScript, the value appears `rightbars` bars after the pivot bar: the pivot bar
1725
+ * index is `i - rightbars`
1726
+ * - A pivot low may equal values on its left but must be lower than all values on its right
1727
+ * - An `na` neighbour ends the check on its side
1728
+ *
1729
+ * @example
1730
+ * ```typescript
1731
+ * const pivotLows = ta.pivotlow(low, 2, 2);
1732
+ * // pivotLows[i] is low[i - 2] when bar i - 2 is a pivot low
1733
+ * ```
1734
+ */
1735
+ export function pivotlow(sourceOrLeftbars, leftbarsOrRightbars, rightbars, low) {
1736
+ if (rightbars === undefined) {
1737
+ if (!low) {
1738
+ throw new Error('ta.pivotlow() requires low series when using two-parameter version.');
1739
+ }
1740
+ return pivot(low, sourceOrLeftbars, leftbarsOrRightbars, false);
1741
+ }
1742
+ return pivot(sourceOrLeftbars, leftbarsOrRightbars, rightbars, false);
1743
+ }
1744
+ /**
1745
+ * Bars Since - returns number of bars since condition was true.
1746
+ *
1747
+ * @param condition - Boolean series condition
1748
+ * @returns Series with number of bars since condition was last true
1749
+ *
1750
+ * @remarks
1751
+ * - Returns 0 when condition is currently true
1752
+ * - Increments by 1 for each bar condition remains false
1753
+ * - Returns NaN if condition has never been true
1754
+ *
1755
+ * @example
1756
+ * ```typescript
1757
+ * const crossovers = ta.crossover(fastMA, slowMA);
1758
+ * const barsSinceCross = ta.barssince(crossovers);
1759
+ * ```
1760
+ */
1761
+ export function barssince(condition) {
1762
+ const result = [];
1763
+ let barsSinceTrue = NaN;
1764
+ for (let i = 0; i < condition.length; i++) {
1765
+ if (condition[i]) {
1766
+ barsSinceTrue = 0;
1767
+ }
1768
+ else if (!isNaN(barsSinceTrue)) {
1769
+ barsSinceTrue++;
1770
+ }
1771
+ result.push(barsSinceTrue);
1772
+ }
1773
+ return result;
1774
+ }
1775
+ /**
1776
+ * Value When - returns the value when condition was true.
1777
+ *
1778
+ * @param condition - Boolean series condition
1779
+ * @param source - Source series to get value from
1780
+ * @param occurrence - Which occurrence to get (0 = most recent)
1781
+ * @returns Series with values from when condition was true
1782
+ *
1783
+ * @remarks
1784
+ * - occurrence=0 returns value from most recent true condition
1785
+ * - occurrence=1 returns value from second most recent, etc.
1786
+ * - Returns NaN if condition hasn't been true occurrence+1 times yet
1787
+ *
1788
+ * @example
1789
+ * ```typescript
1790
+ * const crossovers = ta.crossover(fastMA, slowMA);
1791
+ * const lastCrossPrice = ta.valuewhen(crossovers, close, 0);
1792
+ * ```
1793
+ */
1794
+ export function valuewhen(condition, source, occurrence) {
1795
+ const result = [];
1796
+ for (let i = 0; i < condition.length; i++) {
1797
+ let occurrenceCount = 0;
1798
+ let foundValue = NaN;
1799
+ for (let j = i; j >= 0; j--) {
1800
+ if (condition[j]) {
1801
+ if (occurrenceCount === occurrence) {
1802
+ foundValue = source[j];
1803
+ break;
1804
+ }
1805
+ occurrenceCount++;
1806
+ }
1807
+ }
1808
+ result.push(foundValue);
1809
+ }
1810
+ return result;
1811
+ }
1812
+ /**
1813
+ * Directional Movement Index - returns Directional Movement indicators.
1814
+ *
1815
+ * @param diLength - DI averaging length
1816
+ * @param adxSmoothing - ADX smoothing length
1817
+ * @returns Tuple of [plusDI, minusDI, ADX]
1818
+ *
1819
+ * @remarks
1820
+ * - +DI and -DI measure directional movement
1821
+ * - ADX measures trend strength (0-100)
1822
+ * - ADX above 25 typically indicates strong trend
1823
+ * - Requires high, low, and close data from context
1824
+ *
1825
+ * @example
1826
+ * ```typescript
1827
+ * const [plusDI, minusDI, adx] = ta.dmi(14, 14);
1828
+ * // When +DI > -DI and ADX > 25, strong uptrend
1829
+ * // When -DI > +DI and ADX > 25, strong downtrend
1830
+ * ```
1831
+ */
1832
+ export function dmi(diLength, adxSmoothing, high, low, close) {
1833
+ // PineScript reference (built-in DMI):
1834
+ // up = ta.change(high), down = -ta.change(low)
1835
+ // plusDM = na(up) ? na : (up > down and up > 0 ? up : 0), minusDM likewise
1836
+ // trur = ta.rma(ta.tr, len)
1837
+ // plus = fixnan(100 * ta.rma(plusDM, len) / trur), minus likewise
1838
+ // adx = 100 * ta.rma(math.abs(plus - minus) / (sum == 0 ? 1 : sum), lensig)
1839
+ const len = Math.max(high.length, low.length, close.length);
1840
+ const plusDM = [];
1841
+ const minusDM = [];
1842
+ for (let i = 0; i < len; i++) {
1843
+ const up = i === 0 ? NaN : high[i] - high[i - 1];
1844
+ const down = i === 0 ? NaN : low[i - 1] - low[i];
1845
+ plusDM.push(Number.isNaN(up) ? NaN : gt(up, down) && gt(up, 0) ? up : 0);
1846
+ minusDM.push(Number.isNaN(down) ? NaN : gt(down, up) && gt(down, 0) ? down : 0);
1847
+ }
1848
+ const trur = rma(tr(false, high, low, close), diLength);
1849
+ const smoothedPlus = rma(plusDM, diLength);
1850
+ const smoothedMinus = rma(minusDM, diLength);
1851
+ // x / 0 is na; fixnan keeps the previous value
1852
+ const directional = (smoothed) => {
1853
+ let last = NaN;
1854
+ return smoothed.map((v, i) => {
1855
+ const t = trur[i];
1856
+ const value = t === 0 ? NaN : (100 * v) / t;
1857
+ if (!Number.isNaN(value))
1858
+ last = value;
1859
+ return last;
1860
+ });
1861
+ };
1862
+ const plusDI = directional(smoothedPlus);
1863
+ const minusDI = directional(smoothedMinus);
1864
+ const dx = plusDI.map((plus, i) => {
1865
+ const minus = minusDI[i];
1866
+ const sum = plus + minus;
1867
+ return Math.abs(plus - minus) / (eq(sum, 0) ? 1 : sum);
1868
+ });
1869
+ const adx = rma(dx, adxSmoothing).map((v) => 100 * v);
1870
+ return [plusDI, minusDI, adx];
1871
+ }
1872
+ /**
1873
+ * True Strength Index - momentum oscillator based on double smoothed momentum.
1874
+ *
1875
+ * @param source - Series of values to process
1876
+ * @param shortLength - Short smoothing length
1877
+ * @param longLength - Long smoothing length
1878
+ * @returns TSI series
1879
+ *
1880
+ * @remarks
1881
+ * - TSI oscillates between +100 and -100
1882
+ * - Positive values indicate bullish momentum
1883
+ * - Negative values indicate bearish momentum
1884
+ * - Crossovers of zero line can signal trend changes
1885
+ * - Less sensitive to short-term price fluctuations than RSI
1886
+ *
1887
+ * @example
1888
+ * ```typescript
1889
+ * const tsi = ta.tsi(close, 13, 25);
1890
+ * // TSI > 0: bullish momentum
1891
+ * // TSI < 0: bearish momentum
1892
+ * ```
1893
+ */
1894
+ export function tsi(source, shortLength, longLength) {
1895
+ const momentum = [];
1896
+ // Calculate momentum (price change)
1897
+ for (let i = 0; i < source.length; i++) {
1898
+ if (i === 0) {
1899
+ momentum.push(NaN);
1900
+ }
1901
+ else {
1902
+ momentum.push(source[i] - source[i - 1]);
1903
+ }
1904
+ }
1905
+ // Double smooth momentum
1906
+ const smoothedMomentum = ema(ema(momentum, longLength), shortLength);
1907
+ // Double smooth absolute momentum
1908
+ const absMomentum = momentum.map(Math.abs);
1909
+ const smoothedAbsMomentum = ema(ema(absMomentum, longLength), shortLength);
1910
+ // Calculate TSI
1911
+ const result = [];
1912
+ for (let i = 0; i < source.length; i++) {
1913
+ if (smoothedAbsMomentum[i] === 0) {
1914
+ result.push(0);
1915
+ }
1916
+ else {
1917
+ result.push((smoothedMomentum[i] / smoothedAbsMomentum[i]) * 100);
1918
+ }
1919
+ }
1920
+ return result;
1921
+ }
1922
+ /**
1923
+ * Chande Momentum Oscillator - momentum indicator similar to RSI.
1924
+ *
1925
+ * @param source - Series of values to process
1926
+ * @param length - Number of bars
1927
+ * @returns CMO series
1928
+ *
1929
+ * @remarks
1930
+ * - CMO oscillates between +100 and -100
1931
+ * - CMO > +50: overbought conditions
1932
+ * - CMO < -50: oversold conditions
1933
+ * - Unlike RSI, CMO uses sum of gains/losses instead of averages
1934
+ * - More volatile than RSI
1935
+ *
1936
+ * @example
1937
+ * ```typescript
1938
+ * const cmo = ta.cmo(close, 14);
1939
+ * // CMO > 50: overbought
1940
+ * // CMO < -50: oversold
1941
+ * ```
1942
+ */
1943
+ export function cmo(source, length) {
1944
+ const result = [];
1945
+ for (let i = 0; i < source.length; i++) {
1946
+ if (i < length) {
1947
+ result.push(NaN);
1948
+ continue;
1949
+ }
1950
+ let sumGains = 0;
1951
+ let sumLosses = 0;
1952
+ for (let j = 0; j < length; j++) {
1953
+ const change = source[i - j] - source[i - j - 1];
1954
+ if (change > 0) {
1955
+ sumGains += change;
1956
+ }
1957
+ else {
1958
+ sumLosses += Math.abs(change);
1959
+ }
1960
+ }
1961
+ const totalMovement = sumGains + sumLosses;
1962
+ if (totalMovement === 0) {
1963
+ result.push(0);
1964
+ }
1965
+ else {
1966
+ const cmoValue = ((sumGains - sumLosses) / totalMovement) * 100;
1967
+ result.push(cmoValue);
1968
+ }
1969
+ }
1970
+ return result;
1971
+ }
1972
+ /**
1973
+ * Keltner Channels - volatility-based envelope indicator.
1974
+ *
1975
+ * @param source - Series of values to process
1976
+ * @param length - Number of bars for EMA
1977
+ * @param mult - Multiplier for the range
1978
+ * @param useTrueRange - Use True Range (default: true) or high-low
1979
+ * @returns Tuple of [middle, upper, lower]
1980
+ *
1981
+ * @remarks
1982
+ * - Middle band is EMA of source
1983
+ * - Upper/lower bands are middle ± (range EMA × multiplier)
1984
+ * - When useTrueRange=true, uses ATR for volatility
1985
+ * - When useTrueRange=false, uses high-low range
1986
+ * - Price breaking out of bands may signal trend continuation
1987
+ * - Requires high, low, close data from context when useTrueRange=true
1988
+ *
1989
+ * @example
1990
+ * ```typescript
1991
+ * const [middle, upper, lower] = ta.kc(close, 20, 2, true);
1992
+ * // Price above upper: potential uptrend
1993
+ * // Price below lower: potential downtrend
1994
+ * ```
1995
+ */
1996
+ export function kc(source, length, mult, useTrueRange = true, high, low, close) {
1997
+ // Calculate middle band (EMA of source)
1998
+ const middle = ema(source, length);
1999
+ // Calculate range
2000
+ let range;
2001
+ if (useTrueRange) {
2002
+ if (!high || !low || !close) {
2003
+ throw new Error('ta.kc() with useTrueRange=true requires high, low, and close data');
2004
+ }
2005
+ // PineScript reference: ta.tr, which is na on bar 0
2006
+ range = tr(false, high, low, close);
2007
+ }
2008
+ else {
2009
+ if (!high || !low) {
2010
+ throw new Error('ta.kc() requires high and low data');
2011
+ }
2012
+ range = [];
2013
+ for (let i = 0; i < high.length; i++) {
2014
+ range.push(high[i] - low[i]);
2015
+ }
2016
+ }
2017
+ // Smooth the range with EMA
2018
+ const rangeEma = ema(range, length);
2019
+ // Calculate upper and lower bands
2020
+ const upper = [];
2021
+ const lower = [];
2022
+ for (let i = 0; i < middle.length; i++) {
2023
+ upper.push(middle[i] + rangeEma[i] * mult);
2024
+ lower.push(middle[i] - rangeEma[i] * mult);
2025
+ }
2026
+ return [middle, upper, lower];
2027
+ }
2028
+ /**
2029
+ * Bollinger Bands Width - measures the width of Bollinger Bands.
2030
+ *
2031
+ * @param source - Series of values to process
2032
+ * @param length - Number of bars
2033
+ * @param mult - Standard deviation multiplier
2034
+ * @returns BBW series (percentage)
2035
+ *
2036
+ * @remarks
2037
+ * - BBW = ((upper band - lower band) / middle band) × 100
2038
+ * - Low BBW values indicate low volatility (potential breakout setup)
2039
+ * - High BBW values indicate high volatility
2040
+ * - BBW squeeze (narrowing bands) often precedes strong moves
2041
+ * - Works with existing bb() function
2042
+ *
2043
+ * @example
2044
+ * ```typescript
2045
+ * const bbw = ta.bbw(close, 20, 2);
2046
+ * // Low BBW: potential breakout coming
2047
+ * // High BBW: high volatility period
2048
+ * ```
2049
+ */
2050
+ export function bbw(source, length, mult) {
2051
+ const [basis, upper, lower] = bb(source, length, mult);
2052
+ const result = [];
2053
+ for (let i = 0; i < source.length; i++) {
2054
+ if (basis[i] === 0) {
2055
+ result.push(NaN);
2056
+ }
2057
+ else {
2058
+ const width = ((upper[i] - lower[i]) / basis[i]) * 100;
2059
+ result.push(width);
2060
+ }
2061
+ }
2062
+ return result;
2063
+ }
2064
+ /**
2065
+ * Williams %R (Williams Percent Range)
2066
+ *
2067
+ * Williams %R is a momentum indicator that measures overbought/oversold levels.
2068
+ * It compares the closing price to the high-low range over a specified period.
2069
+ *
2070
+ * Values range from -100 (oversold) to 0 (overbought):
2071
+ * - Above -20: Overbought
2072
+ * - Below -80: Oversold
2073
+ *
2074
+ * @param high - High price series
2075
+ * @param low - Low price series
2076
+ * @param close - Close price series
2077
+ * @param length - Lookback period (default: 14)
2078
+ * @returns Williams %R series
2079
+ *
2080
+ * @example
2081
+ * ```typescript
2082
+ * const wpr = ta.wpr(high, low, close, 14);
2083
+ * // wpr < -80: oversold
2084
+ * // wpr > -20: overbought
2085
+ * ```
2086
+ */
2087
+ export function wpr(high, low, close, length = 14) {
2088
+ const result = [];
2089
+ for (let i = 0; i < close.length; i++) {
2090
+ if (i < length - 1) {
2091
+ result.push(NaN);
2092
+ continue;
2093
+ }
2094
+ // Find highest high and lowest low in the period
2095
+ let highestHigh = high[i - length + 1];
2096
+ let lowestLow = low[i - length + 1];
2097
+ for (let j = i - length + 2; j <= i; j++) {
2098
+ if (high[j] > highestHigh)
2099
+ highestHigh = high[j];
2100
+ if (low[j] < lowestLow)
2101
+ lowestLow = low[j];
2102
+ }
2103
+ const range = highestHigh - lowestLow;
2104
+ if (range === 0) {
2105
+ result.push(NaN);
2106
+ }
2107
+ else {
2108
+ // Formula: (Highest High - Close) / (Highest High - Lowest Low) * -100
2109
+ const wprValue = ((highestHigh - close[i]) / range) * -100;
2110
+ result.push(wprValue);
2111
+ }
2112
+ }
2113
+ return result;
2114
+ }
2115
+ export function vwap(source, volume, anchor, stdev_mult) {
2116
+ if (source.length !== volume.length) {
2117
+ throw new Error('ta.vwap: source and volume must have the same length');
2118
+ }
2119
+ const mid = [];
2120
+ const upper = [];
2121
+ const lower = [];
2122
+ let sumPV = 0;
2123
+ let sumV = 0;
2124
+ let sumPPV = 0;
2125
+ for (let i = 0; i < source.length; i++) {
2126
+ if (anchor && anchor[i]) {
2127
+ sumPV = 0;
2128
+ sumV = 0;
2129
+ sumPPV = 0;
2130
+ }
2131
+ const s = source[i];
2132
+ const v = volume[i];
2133
+ if (!Number.isNaN(s) && !Number.isNaN(v)) {
2134
+ sumPV += s * v;
2135
+ sumV += v;
2136
+ sumPPV += s * s * v;
2137
+ }
2138
+ const m = sumV === 0 ? NaN : sumPV / sumV;
2139
+ mid.push(m);
2140
+ if (stdev_mult !== undefined) {
2141
+ const sd = Math.sqrt(Math.max(0, sumPPV / sumV - m * m));
2142
+ upper.push(m + stdev_mult * sd);
2143
+ lower.push(m - stdev_mult * sd);
2144
+ }
2145
+ }
2146
+ return stdev_mult === undefined ? mid : [mid, upper, lower];
2147
+ }
2148
+ /**
2149
+ * Arnaud Legoux Moving Average (ALMA)
2150
+ *
2151
+ * ALMA uses a Gaussian distribution for weighting, reducing lag while maintaining smoothness.
2152
+ * It's particularly good at tracking price action with minimal lag.
2153
+ *
2154
+ * @param source - Source series
2155
+ * @param length - Window size (default: 9)
2156
+ * @param offset - Controls the center of the Gaussian curve. 0.85 = focus on recent prices (default: 0.85)
2157
+ * @param sigma - Standard deviation of the Gaussian. Controls smoothness (default: 6)
2158
+ * @returns ALMA series
2159
+ *
2160
+ * @example
2161
+ * ```typescript
2162
+ * const alma = ta.alma(close, 9, 0.85, 6);
2163
+ * // offset closer to 1: more responsive
2164
+ * // offset closer to 0: smoother
2165
+ * ```
2166
+ */
2167
+ export function alma(source, length = 9, offset = 0.85, sigma = 6, floor = false) {
2168
+ const result = [];
2169
+ const m = floor ? Math.floor(offset * (length - 1)) : offset * (length - 1);
2170
+ const s = length / sigma;
2171
+ // Pre-calculate weights
2172
+ const weights = [];
2173
+ let weightSum = 0;
2174
+ for (let i = 0; i < length; i++) {
2175
+ const weight = Math.exp(-1 * Math.pow(i - m, 2) / (2 * Math.pow(s, 2)));
2176
+ weights.push(weight);
2177
+ weightSum += weight;
2178
+ }
2179
+ // Normalize weights
2180
+ for (let i = 0; i < length; i++) {
2181
+ weights[i] /= weightSum;
2182
+ }
2183
+ // Calculate ALMA
2184
+ for (let i = 0; i < source.length; i++) {
2185
+ if (i < length - 1) {
2186
+ result.push(NaN);
2187
+ continue;
2188
+ }
2189
+ let almaValue = 0;
2190
+ for (let j = 0; j < length; j++) {
2191
+ almaValue += source[i - length + 1 + j] * weights[j];
2192
+ }
2193
+ result.push(almaValue);
2194
+ }
2195
+ return result;
2196
+ }
2197
+ /**
2198
+ * Keltner Channels Width (KCW)
2199
+ *
2200
+ * Measures the width of Keltner Channels as a percentage of the middle line.
2201
+ * Similar to BBW but uses ATR instead of standard deviation.
2202
+ *
2203
+ * Low KCW suggests consolidation/low volatility.
2204
+ * High KCW suggests expansion/high volatility.
2205
+ *
2206
+ * @param source - Source series
2207
+ * @param length - Number of bars for EMA and ATR (default: 20)
2208
+ * @param mult - ATR multiplier (default: 2)
2209
+ * @param useTrueRange - Use True Range instead of high-low (default: true)
2210
+ * @param high - High price series
2211
+ * @param low - Low price series
2212
+ * @param close - Close price series
2213
+ * @returns KCW series
2214
+ *
2215
+ * @example
2216
+ * ```typescript
2217
+ * const kcw = ta.kcw(close, 20, 2, true, high, low, close);
2218
+ * // Low KCW: potential breakout coming
2219
+ * // High KCW: high volatility period
2220
+ * ```
2221
+ */
2222
+ export function kcw(source, length = 20, mult = 2, useTrueRange = true, high, low, close) {
2223
+ const [basis, upper, lower] = kc(source, length, mult, useTrueRange, high, low, close);
2224
+ const result = [];
2225
+ for (let i = 0; i < source.length; i++) {
2226
+ if (isNaN(basis[i]) || basis[i] === 0) {
2227
+ result.push(NaN);
2228
+ }
2229
+ else {
2230
+ const width = ((upper[i] - lower[i]) / basis[i]) * 100;
2231
+ result.push(width);
2232
+ }
2233
+ }
2234
+ return result;
2235
+ }
2236
+ /**
2237
+ * Difference between the highest and the lowest value over the last `length` non-na values
2238
+ * (PineScript `ta.range(source, length)`).
2239
+ *
2240
+ * @remarks
2241
+ * As in PineScript: `na` values are skipped (the window holds the last `length` non-na
2242
+ * values), and a bar whose own value is `na` still gets a result.
2243
+ */
2244
+ export function range(source, length) {
2245
+ return source.map((_, i) => {
2246
+ const values = lastValues(source, i, length);
2247
+ return values ? Math.max(...values) - Math.min(...values) : NaN;
2248
+ });
2249
+ }
2250
+ /**
2251
+ * Offset to the bar with the highest value over the last `length` bars: 0 for the current bar,
2252
+ * -1 for the previous bar, and so on (as in PineScript, the offset is negative).
2253
+ *
2254
+ * @remarks
2255
+ * As in PineScript: on equal values the oldest bar wins; an `na` value ends the window;
2256
+ * 0 when the window has no value.
2257
+ */
2258
+ export function highestbars(source, length) {
2259
+ return extremeOffset(source, length, false);
2260
+ }
2261
+ /**
2262
+ * Offset to the bar with the lowest value over the last `length` bars: 0 for the current bar,
2263
+ * -1 for the previous bar, and so on (as in PineScript, the offset is negative).
2264
+ *
2265
+ * @remarks
2266
+ * As in PineScript: on equal values the oldest bar wins; an `na` value ends the window;
2267
+ * 0 when the window has no value.
2268
+ */
2269
+ export function lowestbars(source, length) {
2270
+ return extremeOffset(source, length, true);
2271
+ }
2272
+ /**
2273
+ * All-time highest value of `source` up to each bar (PineScript `ta.max(source)`).
2274
+ *
2275
+ * @remarks
2276
+ * As in PineScript: `na` values are skipped, the value carries over `na` bars, and the
2277
+ * result is `na` until the first non-na value.
2278
+ */
2279
+ export function max(source) {
2280
+ let best = NaN;
2281
+ return source.map((x) => {
2282
+ if (!Number.isNaN(x) && !(x <= best))
2283
+ best = x;
2284
+ return best;
2285
+ });
2286
+ }
2287
+ /**
2288
+ * All-time lowest value of `source` up to each bar (PineScript `ta.min(source)`).
2289
+ *
2290
+ * @remarks
2291
+ * As in PineScript: `na` values are skipped, the value carries over `na` bars, and the
2292
+ * result is `na` until the first non-na value.
2293
+ */
2294
+ export function min(source) {
2295
+ let best = NaN;
2296
+ return source.map((x) => {
2297
+ if (!Number.isNaN(x) && !(x >= best))
2298
+ best = x;
2299
+ return best;
2300
+ });
2301
+ }
2302
+ /**
2303
+ * Center of Gravity (COG)
2304
+ *
2305
+ * The Center of Gravity indicator is an oscillator developed by John Ehlers.
2306
+ * It identifies turning points with minimal lag and provides clear signals.
2307
+ *
2308
+ * The COG calculates a weighted average where more recent prices have higher weights,
2309
+ * similar to a moving average but with a focus on momentum shifts.
2310
+ *
2311
+ * @param source - Source series (typically close)
2312
+ * @param length - Lookback period (default: 10)
2313
+ * @returns COG series
2314
+ *
2315
+ * @example
2316
+ * ```typescript
2317
+ * const cogValue = ta.cog(close, 10);
2318
+ * // Use COG crossovers as signals:
2319
+ * // - COG crossing above 0: potential buy signal
2320
+ * // - COG crossing below 0: potential sell signal
2321
+ * ```
2322
+ */
2323
+ export function cog(source, length = 10) {
2324
+ const result = [];
2325
+ for (let i = 0; i < source.length; i++) {
2326
+ if (i < length - 1) {
2327
+ result.push(NaN);
2328
+ continue;
2329
+ }
2330
+ let numerator = 0;
2331
+ let denominator = 0;
2332
+ for (let j = 0; j < length; j++) {
2333
+ const weight = j + 1;
2334
+ const price = source[i - length + 1 + j];
2335
+ numerator += weight * price;
2336
+ denominator += price;
2337
+ }
2338
+ if (denominator === 0) {
2339
+ result.push(NaN);
2340
+ }
2341
+ else {
2342
+ // COG formula: -1 * (sum of (weight * price) / sum of prices) + (length + 1) / 2
2343
+ const cog = -1 * (numerator / denominator) + (length + 1) / 2;
2344
+ result.push(cog);
2345
+ }
2346
+ }
2347
+ return result;
2348
+ }
2349
+ /**
2350
+ * Mode (Most Frequent Value)
2351
+ *
2352
+ * Returns the mode of the series - the most frequently occurring value.
2353
+ * If there are several values with the same frequency, it returns the smallest value.
2354
+ *
2355
+ * @param source - Series of values to process
2356
+ * @param length - Number of bars to look back
2357
+ * @returns The most frequently occurring value
2358
+ *
2359
+ * @example
2360
+ * ```typescript
2361
+ * const values = [1, 2, 2, 3, 3, 3, 4, 4];
2362
+ * const modeValue = ta.mode(values, 8); // Returns 3 (most frequent)
2363
+ * ```
2364
+ *
2365
+ * @remarks
2366
+ * - `na` values in the source series are ignored
2367
+ * - If no mode exists, returns the smallest value
2368
+ * - Returns NaN for the first (length - 1) values where there's insufficient data
2369
+ */
2370
+ export function mode(source, length) {
2371
+ const result = [];
2372
+ for (let i = 0; i < source.length; i++) {
2373
+ if (i < length - 1) {
2374
+ result.push(NaN);
2375
+ continue;
2376
+ }
2377
+ // Collect non-NaN values in the window
2378
+ const values = [];
2379
+ for (let j = 0; j < length; j++) {
2380
+ const value = source[i - j];
2381
+ if (!isNaN(value)) {
2382
+ values.push(value);
2383
+ }
2384
+ }
2385
+ if (values.length === 0) {
2386
+ result.push(NaN);
2387
+ continue;
2388
+ }
2389
+ // Count frequency of each value
2390
+ const frequencyMap = new Map();
2391
+ for (const value of values) {
2392
+ frequencyMap.set(value, (frequencyMap.get(value) || 0) + 1);
2393
+ }
2394
+ // Find the maximum frequency
2395
+ let maxFrequency = 0;
2396
+ frequencyMap.forEach((freq) => {
2397
+ if (freq > maxFrequency) {
2398
+ maxFrequency = freq;
2399
+ }
2400
+ });
2401
+ // Find all values with max frequency, then return the smallest
2402
+ const modesWithMaxFreq = [];
2403
+ frequencyMap.forEach((freq, value) => {
2404
+ if (freq === maxFrequency) {
2405
+ modesWithMaxFreq.push(value);
2406
+ }
2407
+ });
2408
+ result.push(Math.min(...modesWithMaxFreq));
2409
+ }
2410
+ return result;
2411
+ }
2412
+ /**
2413
+ * Percentile (Linear Interpolation Method)
2414
+ *
2415
+ * Calculates the percentile using the method of linear interpolation between
2416
+ * the two nearest ranks. This method may return values that are not members
2417
+ * of the input data set.
2418
+ *
2419
+ * @param source - Series of values to process
2420
+ * @param length - Number of bars to look back
2421
+ * @param percentage - Percentile to calculate (0-100)
2422
+ * @returns The calculated percentile value
2423
+ *
2424
+ * @example
2425
+ * ```typescript
2426
+ * const p50 = ta.percentile_linear_interpolation(close, 20, 50); // Median
2427
+ * const p75 = ta.percentile_linear_interpolation(close, 20, 75); // 75th percentile
2428
+ * ```
2429
+ *
2430
+ * @remarks
2431
+ * - The result will NOT always be a member of the input data set
2432
+ * - Uses linear interpolation between adjacent values when needed
2433
+ * - Returns NaN for the first (length - 1) values where there's insufficient data
2434
+ *
2435
+ * PineScript rules:
2436
+ * - position in the sorted window: `percentage / 100 * length - 0.5`, clamped to the first / last value, linear
2437
+ * interpolation between the two neighbours (na when a neighbour is na, also at an exact position)
2438
+ * - `na` values stay in the window (window size = `length`). The window is kept sorted from bar to bar: the new
2439
+ * value is inserted before the first value `>=` it, passing the `na` values (an `na` goes last), then the value
2440
+ * leaving the window is removed. So the place of an `na` depends on the history: at the start of a series the
2441
+ * `na` values are before all numbers
2442
+ */
2443
+ export function percentile_linear_interpolation(source, length, percentage) {
2444
+ return sortedWindow(source, length, (s) => {
2445
+ const pos = (percentage / 100) * length - 0.5;
2446
+ if (pos <= 0)
2447
+ return s[0];
2448
+ if (pos >= length - 1)
2449
+ return s[length - 1];
2450
+ const lo = Math.floor(pos);
2451
+ return s[lo] + (pos - lo) * (s[lo + 1] - s[lo]);
2452
+ });
2453
+ }
2454
+ /**
2455
+ * Sorted window of ta.percentile_*, as PineScript keeps it: each bar first inserts the new value before the first
2456
+ * value `>=` it (scanning from the start; an `na` is never `>=`, so a value passes the `na` values and an `na` goes
2457
+ * last), then removes the value of the bar leaving the window. `fn` reads the sorted window (`length` values).
2458
+ * na for the first `length - 1` bars.
2459
+ *
2460
+ * @internal
2461
+ */
2462
+ function sortedWindow(source, length, fn) {
2463
+ const result = [];
2464
+ const values = []; // sorted window
2465
+ const bars = []; // bar index of each value
2466
+ for (let i = 0; i < source.length; i++) {
2467
+ const v = source[i];
2468
+ let k = 0;
2469
+ while (k < values.length && !(values[k] >= v))
2470
+ k++;
2471
+ values.splice(k, 0, v);
2472
+ bars.splice(k, 0, i);
2473
+ if (i >= length) {
2474
+ const old = bars.indexOf(i - length);
2475
+ values.splice(old, 1);
2476
+ bars.splice(old, 1);
2477
+ }
2478
+ result.push(i < length - 1 ? NaN : fn(values));
2479
+ }
2480
+ return result;
2481
+ }
2482
+ /**
2483
+ * Percentile (Nearest Rank Method)
2484
+ *
2485
+ * Calculates the percentile using the Nearest Rank method. This method
2486
+ * always returns a value that is a member of the input data set.
2487
+ *
2488
+ * @param source - Series of values to process
2489
+ * @param length - Number of bars to look back
2490
+ * @param percentage - Percentile to calculate (0-100)
2491
+ * @returns The calculated percentile value
2492
+ *
2493
+ * @example
2494
+ * ```typescript
2495
+ * const p50 = ta.percentile_nearest_rank(close, 20, 50); // Median
2496
+ * const p90 = ta.percentile_nearest_rank(close, 20, 90); // 90th percentile
2497
+ * ```
2498
+ *
2499
+ * @remarks
2500
+ * - The result will ALWAYS be a member of the input data set
2501
+ * - The 100th percentile is defined as the largest value
2502
+ * - Using this method on lengths < 100 may result in the same value for multiple percentiles
2503
+ * - Returns NaN for the first (length - 1) values where there's insufficient data
2504
+ *
2505
+ * PineScript rules:
2506
+ * - the value at rank `ceil(percentage / 100 * length)` of the sorted window (na when that rank holds an `na`)
2507
+ * - `na` values stay in the window, sorted from bar to bar as in `ta.percentile_linear_interpolation`
2508
+ */
2509
+ export function percentile_nearest_rank(source, length, percentage) {
2510
+ const rank = Math.min(length, Math.max(1, Math.ceil((percentage / 100) * length)));
2511
+ return sortedWindow(source, length, (s) => s[rank - 1]);
2512
+ }
2513
+ /**
2514
+ * Rank Correlation Index (RCI)
2515
+ *
2516
+ * Calculates the Rank Correlation Index using Spearman's rank correlation coefficient.
2517
+ * RCI measures the directional consistency of price movements, indicating whether
2518
+ * the source consistently increased (positive values) or decreased (negative values).
2519
+ *
2520
+ * @param source - Series of values to process
2521
+ * @param length - Number of bars to look back
2522
+ * @returns RCI value scaled to range -100 to 100
2523
+ *
2524
+ * @example
2525
+ * ```typescript
2526
+ * const rci9 = ta.rci(close, 9);
2527
+ * // RCI near +100: strong upward consistency
2528
+ * // RCI near -100: strong downward consistency
2529
+ * // RCI near 0: no clear trend
2530
+ * ```
2531
+ *
2532
+ * @remarks
2533
+ * - Result is scaled to -100 to 100 range
2534
+ * - +100 indicates source consistently increased over the period
2535
+ * - -100 indicates source consistently decreased over the period
2536
+ * - 0 indicates no directional consistency
2537
+ * - Returns NaN for the first (length - 1) values where there's insufficient data
2538
+ */
2539
+ export function rci(source, length) {
2540
+ const result = [];
2541
+ for (let i = 0; i < source.length; i++) {
2542
+ if (i < length - 1) {
2543
+ result.push(NaN);
2544
+ continue;
2545
+ }
2546
+ // Collect values in the window
2547
+ const values = [];
2548
+ for (let j = 0; j < length; j++) {
2549
+ values.push(source[i - length + 1 + j]);
2550
+ }
2551
+ // Check for NaN values
2552
+ if (values.some(v => isNaN(v))) {
2553
+ result.push(NaN);
2554
+ continue;
2555
+ }
2556
+ // Create array of indices with their values for ranking
2557
+ const indexed = values.map((value, index) => ({ value, index }));
2558
+ // Sort by value to get ranks
2559
+ const sorted = [...indexed].sort((a, b) => a.value - b.value);
2560
+ // Assign ranks (handling ties by averaging ranks)
2561
+ const ranks = new Array(length).fill(0);
2562
+ let currentRank = 1;
2563
+ for (let j = 0; j < sorted.length; j++) {
2564
+ // Count ties
2565
+ let tieCount = 1;
2566
+ while (j + tieCount < sorted.length && sorted[j].value === sorted[j + tieCount].value) {
2567
+ tieCount++;
2568
+ }
2569
+ // Average rank for ties
2570
+ const avgRank = (currentRank + (currentRank + tieCount - 1)) / 2;
2571
+ // Assign average rank to all tied values
2572
+ for (let k = 0; k < tieCount; k++) {
2573
+ ranks[sorted[j + k].index] = avgRank;
2574
+ }
2575
+ j += tieCount - 1;
2576
+ currentRank += tieCount;
2577
+ }
2578
+ // Calculate D² = sum of (price_rank - time_rank)²
2579
+ let sumSquaredDiff = 0;
2580
+ for (let j = 0; j < length; j++) {
2581
+ const timeRank = j + 1;
2582
+ const diff = ranks[j] - timeRank;
2583
+ sumSquaredDiff += diff * diff;
2584
+ }
2585
+ const n = length;
2586
+ const base = (n * n * n - n) / 12;
2587
+ // Compute tie correction for price ranks
2588
+ // Count tied groups from the sorted array
2589
+ let tieCorrection = 0;
2590
+ for (let j = 0; j < sorted.length;) {
2591
+ let tieCount = 1;
2592
+ while (j + tieCount < sorted.length && sorted[j].value === sorted[j + tieCount].value) {
2593
+ tieCount++;
2594
+ }
2595
+ if (tieCount > 1) {
2596
+ tieCorrection += (tieCount * tieCount * tieCount - tieCount) / 12;
2597
+ }
2598
+ j += tieCount;
2599
+ }
2600
+ const A = base - tieCorrection; // Corrected for tied price ranks
2601
+ const B = base; // Time ranks have no ties
2602
+ let rho;
2603
+ if (A === 0 || B === 0) {
2604
+ rho = 0;
2605
+ }
2606
+ else {
2607
+ rho = (A + B - sumSquaredDiff) / (2 * Math.sqrt(A * B));
2608
+ }
2609
+ result.push(rho * 100);
2610
+ }
2611
+ return result;
2612
+ }
2613
+ /**
2614
+ * Pivot Point Levels
2615
+ *
2616
+ * Calculates pivot point levels using various calculation methods.
2617
+ * Returns an array containing: [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5]
2618
+ *
2619
+ * @param type - Calculation type: "Traditional", "Fibonacci", "Woodie", "Classic", "DM", "Camarilla"
2620
+ * @param anchor - Condition that triggers reset of calculations
2621
+ * @param developing - If true, pivots recalculate continuously; if false, use last anchor values
2622
+ * @param high - High price series (optional, uses context if not provided)
2623
+ * @param low - Low price series (optional, uses context if not provided)
2624
+ * @param close - Close price series (optional, uses context if not provided)
2625
+ * @param open - Open price series (optional, uses context if not provided)
2626
+ * @returns Array of 11 pivot levels
2627
+ *
2628
+ * @example
2629
+ * ```typescript
2630
+ * const weekChange = [false, false, false, false, true, false, ...]; // Weekly anchor
2631
+ * const pivots = ta.pivot_point_levels("Traditional", weekChange, false, high, low, close);
2632
+ * // pivots[i]! = [P, R1, S1, R2, S2, R3, S3, R4, S4, R5, S5]
2633
+ * ```
2634
+ *
2635
+ * @remarks
2636
+ * - Woodie type cannot use developing=true (will error in PineScript)
2637
+ * - DM type only calculates P, R1, S1 (other levels are NaN)
2638
+ * - All calculations follow PineScript v6 specifications
2639
+ */
2640
+ export function pivot_point_levels(type, anchor, developing = false, high, low, close, open) {
2641
+ // For simplicity, we need to handle the case where type is a string array
2642
+ // But according to the docs, type is "series string", meaning it can change per bar
2643
+ // For this implementation, we'll support constant type strings
2644
+ const typeStr = typeof type === 'string' ? type : String(type[0]);
2645
+ const isDeveloping = typeof developing === 'boolean' ? developing : developing[0];
2646
+ // Woodie cannot be developing
2647
+ if (typeStr === 'Woodie' && isDeveloping) {
2648
+ throw new Error('ta.pivot_point_levels: Woodie type cannot use developing=true');
2649
+ }
2650
+ // Ensure all series have the same length
2651
+ const length = anchor.length;
2652
+ if (high && high.length !== length)
2653
+ throw new Error('High series length mismatch');
2654
+ if (low && low.length !== length)
2655
+ throw new Error('Low series length mismatch');
2656
+ if (close && close.length !== length)
2657
+ throw new Error('Close series length mismatch');
2658
+ if (open && open.length !== length)
2659
+ throw new Error('Open series length mismatch');
2660
+ // Initialize result arrays for all 11 levels
2661
+ const results = Array.from({ length: 11 }, () => []);
2662
+ // Track the last anchor point data
2663
+ let lastH = NaN, lastL = NaN, lastC = NaN, lastO = NaN;
2664
+ let lastAnchorIndex = -1;
2665
+ for (let i = 0; i < length; i++) {
2666
+ // Check if anchor triggered
2667
+ if (anchor[i]) {
2668
+ lastAnchorIndex = i;
2669
+ // Store OHLC at anchor point (these will be used for calculations)
2670
+ lastH = high ? high[i] : NaN;
2671
+ lastL = low ? low[i] : NaN;
2672
+ lastC = close ? close[i] : NaN;
2673
+ lastO = open ? open[i] : NaN;
2674
+ }
2675
+ // Calculate data to use
2676
+ let h, l, c, o;
2677
+ if (isDeveloping && lastAnchorIndex >= 0) {
2678
+ // Developing: use max/min/last since anchor
2679
+ h = high ? Math.max(...high.slice(lastAnchorIndex, i + 1).filter(v => !isNaN(v))) : NaN;
2680
+ l = low ? Math.min(...low.slice(lastAnchorIndex, i + 1).filter(v => !isNaN(v))) : NaN;
2681
+ c = close ? close[i] : NaN;
2682
+ o = open && lastAnchorIndex >= 0 ? open[lastAnchorIndex] : NaN;
2683
+ }
2684
+ else {
2685
+ // Not developing: use last anchor values
2686
+ h = lastH;
2687
+ l = lastL;
2688
+ c = lastC;
2689
+ o = lastO;
2690
+ }
2691
+ // Calculate pivot levels based on type
2692
+ const levels = calculatePivotLevels(typeStr, h, l, c, o);
2693
+ // Push to results
2694
+ for (let j = 0; j < 11; j++) {
2695
+ results[j].push(levels[j]);
2696
+ }
2697
+ }
2698
+ return results;
2699
+ }
2700
+ /**
2701
+ * Helper function to calculate pivot levels
2702
+ */
2703
+ function calculatePivotLevels(type, h, l, c, o) {
2704
+ // Return NaN array if data is insufficient
2705
+ if (isNaN(h) || isNaN(l) || isNaN(c)) {
2706
+ return Array(11).fill(NaN);
2707
+ }
2708
+ const levels = Array(11).fill(NaN);
2709
+ // Calculate pivot point (P)
2710
+ let P;
2711
+ switch (type) {
2712
+ case 'Traditional':
2713
+ case 'Fibonacci':
2714
+ case 'Classic':
2715
+ P = (h + l + c) / 3;
2716
+ break;
2717
+ case 'Woodie':
2718
+ P = (h + l + 2 * c) / 4;
2719
+ break;
2720
+ case 'DM':
2721
+ P = (h + l + c) / 3;
2722
+ break;
2723
+ case 'Camarilla':
2724
+ P = (h + l + c) / 3;
2725
+ break;
2726
+ default:
2727
+ P = (h + l + c) / 3;
2728
+ }
2729
+ levels[0] = P; // P is at index 0
2730
+ // Calculate resistance and support levels based on type
2731
+ switch (type) {
2732
+ case 'Traditional':
2733
+ case 'Classic':
2734
+ levels[1] = 2 * P - l; // R1
2735
+ levels[2] = 2 * P - h; // S1
2736
+ levels[3] = P + (h - l); // R2
2737
+ levels[4] = P - (h - l); // S2
2738
+ levels[5] = h + 2 * (P - l); // R3
2739
+ levels[6] = l - 2 * (h - P); // S3
2740
+ levels[7] = levels[5] + (h - l); // R4
2741
+ levels[8] = levels[6] - (h - l); // S4
2742
+ levels[9] = levels[7] + (h - l); // R5
2743
+ levels[10] = levels[8] - (h - l); // S5
2744
+ break;
2745
+ case 'Fibonacci':
2746
+ levels[1] = P + 0.382 * (h - l); // R1
2747
+ levels[2] = P - 0.382 * (h - l); // S1
2748
+ levels[3] = P + 0.618 * (h - l); // R2
2749
+ levels[4] = P - 0.618 * (h - l); // S2
2750
+ levels[5] = P + (h - l); // R3
2751
+ levels[6] = P - (h - l); // S3
2752
+ levels[7] = levels[5] + 0.618 * (h - l); // R4
2753
+ levels[8] = levels[6] - 0.618 * (h - l); // S4
2754
+ levels[9] = levels[7] + 0.382 * (h - l); // R5
2755
+ levels[10] = levels[8] - 0.382 * (h - l); // S5
2756
+ break;
2757
+ case 'Woodie':
2758
+ levels[1] = 2 * P - l; // R1
2759
+ levels[2] = 2 * P - h; // S1
2760
+ levels[3] = P + (h - l); // R2
2761
+ levels[4] = P - (h - l); // S2
2762
+ levels[5] = h + 2 * (P - l); // R3
2763
+ levels[6] = l - 2 * (h - P); // S3
2764
+ levels[7] = levels[5] + (h - l); // R4
2765
+ levels[8] = levels[6] - (h - l); // S4
2766
+ levels[9] = levels[7] + (h - l); // R5
2767
+ levels[10] = levels[8] - (h - l); // S5
2768
+ break;
2769
+ case 'DM': {
2770
+ // DM (Demark) only calculates P, R1, S1
2771
+ const x = h + l + (c * 2) + (isNaN(o) ? c : o);
2772
+ const newP = x / (isNaN(o) ? 4 : 5);
2773
+ levels[0] = newP;
2774
+ levels[1] = x / 2 - l; // R1
2775
+ levels[2] = x / 2 - h; // S1
2776
+ // R2-S5 remain NaN
2777
+ break;
2778
+ }
2779
+ case 'Camarilla': {
2780
+ const range = h - l;
2781
+ levels[1] = c + range * 1.1 / 12; // R1
2782
+ levels[2] = c - range * 1.1 / 12; // S1
2783
+ levels[3] = c + range * 1.1 / 6; // R2
2784
+ levels[4] = c - range * 1.1 / 6; // S2
2785
+ levels[5] = c + range * 1.1 / 4; // R3
2786
+ levels[6] = c - range * 1.1 / 4; // S3
2787
+ levels[7] = c + range * 1.1 / 2; // R4
2788
+ levels[8] = c - range * 1.1 / 2; // S4
2789
+ levels[9] = h; // R5 (high)
2790
+ levels[10] = l; // S5 (low)
2791
+ break;
2792
+ }
2793
+ }
2794
+ return levels;
2795
+ }
2796
+ /**
2797
+ * Ichimoku Kinko Hyo (Ichimoku Cloud) - Japanese charting technique for trend identification.
2798
+ *
2799
+ * @param conversionPeriods - Period for Tenkan-sen (Conversion Line), default: 9
2800
+ * @param basePeriods - Period for Kijun-sen (Base Line), default: 26
2801
+ * @param laggingSpan2Periods - Period for Senkou Span B (Leading Span B), default: 52
2802
+ * @param displacement - Displacement for Senkou Spans and Chikou Span, default: 26
2803
+ * @param high - High price series
2804
+ * @param low - Low price series
2805
+ * @param close - Close price series
2806
+ * @returns Tuple of [tenkanSen, kijunSen, senkouSpanA, senkouSpanB, chikouSpan]
2807
+ *
2808
+ * @remarks
2809
+ * - **Tenkan-sen (Conversion Line)**: `(highest(high, conversionPeriods) + lowest(low, conversionPeriods)) / 2`
2810
+ * - **Kijun-sen (Base Line)**: `(highest(high, basePeriods) + lowest(low, basePeriods)) / 2`
2811
+ * - **Senkou Span A (Leading Span A)**: `(tenkan + kijun) / 2` offset forward by `displacement` periods
2812
+ * - **Senkou Span B (Leading Span B)**: `(highest(high, laggingSpan2Periods) + lowest(low, laggingSpan2Periods)) / 2` offset forward by `displacement` periods
2813
+ * - **Chikou Span (Lagging Span)**: `close` offset backward by `displacement` periods
2814
+ *
2815
+ * The forward offset for Senkou Spans means they are projected into the future (NaN values at the end).
2816
+ * The backward offset for Chikou Span means it shows past prices (NaN values at the beginning).
2817
+ *
2818
+ * @example
2819
+ * ```typescript
2820
+ * const [tenkan, kijun, senkouA, senkouB, chikou] = ta.ichimoku(9, 26, 52, 26, high, low, close);
2821
+ *
2822
+ * // Tenkan-sen crosses above Kijun-sen = bullish signal
2823
+ * const bullishSignal = ta.crossover(tenkan, kijun);
2824
+ *
2825
+ * // Price above cloud = bullish trend
2826
+ * // Cloud = area between senkouSpanA and senkouSpanB
2827
+ * ```
2828
+ */
2829
+ export function ichimoku(conversionPeriods, basePeriods, laggingSpan2Periods, displacement, high, low, close) {
2830
+ const length = high.length;
2831
+ // Calculate Tenkan-sen (Conversion Line): (highest(high, 9) + lowest(low, 9)) / 2
2832
+ const highestConversion = highest(high, conversionPeriods);
2833
+ const lowestConversion = lowest(low, conversionPeriods);
2834
+ const tenkanSen = [];
2835
+ for (let i = 0; i < length; i++) {
2836
+ if (isNaN(highestConversion[i]) || isNaN(lowestConversion[i])) {
2837
+ tenkanSen.push(NaN);
2838
+ }
2839
+ else {
2840
+ tenkanSen.push((highestConversion[i] + lowestConversion[i]) / 2);
2841
+ }
2842
+ }
2843
+ // Calculate Kijun-sen (Base Line): (highest(high, 26) + lowest(low, 26)) / 2
2844
+ const highestBase = highest(high, basePeriods);
2845
+ const lowestBase = lowest(low, basePeriods);
2846
+ const kijunSen = [];
2847
+ for (let i = 0; i < length; i++) {
2848
+ if (isNaN(highestBase[i]) || isNaN(lowestBase[i])) {
2849
+ kijunSen.push(NaN);
2850
+ }
2851
+ else {
2852
+ kijunSen.push((highestBase[i] + lowestBase[i]) / 2);
2853
+ }
2854
+ }
2855
+ // Calculate Senkou Span A (Leading Span A): (tenkan + kijun) / 2, offset forward by displacement
2856
+ // This means at index i, we store the value that would normally be at index (i - displacement)
2857
+ // Result: first 'displacement' values are NaN, and last 'displacement' calculated values are lost
2858
+ const senkouSpanA = [];
2859
+ for (let i = 0; i < length; i++) {
2860
+ const sourceIndex = i - displacement;
2861
+ if (sourceIndex < 0 || isNaN(tenkanSen[sourceIndex]) || isNaN(kijunSen[sourceIndex])) {
2862
+ senkouSpanA.push(NaN);
2863
+ }
2864
+ else {
2865
+ senkouSpanA.push((tenkanSen[sourceIndex] + kijunSen[sourceIndex]) / 2);
2866
+ }
2867
+ }
2868
+ // Calculate Senkou Span B (Leading Span B): (highest(high, 52) + lowest(low, 52)) / 2, offset forward by displacement
2869
+ const highestLagging = highest(high, laggingSpan2Periods);
2870
+ const lowestLagging = lowest(low, laggingSpan2Periods);
2871
+ const senkouSpanB = [];
2872
+ for (let i = 0; i < length; i++) {
2873
+ const sourceIndex = i - displacement;
2874
+ if (sourceIndex < 0 || isNaN(highestLagging[sourceIndex]) || isNaN(lowestLagging[sourceIndex])) {
2875
+ senkouSpanB.push(NaN);
2876
+ }
2877
+ else {
2878
+ senkouSpanB.push((highestLagging[sourceIndex] + lowestLagging[sourceIndex]) / 2);
2879
+ }
2880
+ }
2881
+ // Calculate Chikou Span (Lagging Span): close, offset backward by displacement
2882
+ // This means at index i, we store the close value from index (i + displacement)
2883
+ // Result: last 'displacement' values are NaN
2884
+ const chikouSpan = [];
2885
+ for (let i = 0; i < length; i++) {
2886
+ const sourceIndex = i + displacement;
2887
+ if (sourceIndex >= length || isNaN(close[sourceIndex])) {
2888
+ chikouSpan.push(NaN);
2889
+ }
2890
+ else {
2891
+ chikouSpan.push(close[sourceIndex]);
2892
+ }
2893
+ }
2894
+ return [tenkanSen, kijunSen, senkouSpanA, senkouSpanB, chikouSpan];
2895
+ }
2896
+ /**
2897
+ * ZigZag indicator - identifies significant trend reversals by filtering out minor price movements.
2898
+ *
2899
+ * @param deviation - Minimum percentage price change to form a new pivot (default: 5.0)
2900
+ * @param depth - Minimum bars between pivots for pivot detection (default: 10)
2901
+ * @param backstep - Bars to look back for confirmation (default: 3)
2902
+ * @param source - Price source (optional, typically close)
2903
+ * @param high - High price series (optional, for high/low mode)
2904
+ * @param low - Low price series (optional, for high/low mode)
2905
+ * @returns Tuple of [zigzag values, direction, pivot flags (boolean)]
2906
+ *
2907
+ * @remarks
2908
+ * - **Parameter order differs from PineScript**: JavaScript signature puts deviation first,
2909
+ * while PineScript uses `ta.zigzag(source, deviation, depth, backstep)`. This allows
2910
+ * deviation to have a default value and makes the API more ergonomic for JS users.
2911
+ * - **JavaScript signature**: Requires explicit `source`, `high`, `low` OR use `createContext()`
2912
+ * - Returns the pivot price at pivot points, NaN for non-pivot bars
2913
+ * - Direction: 1 = uptrend (from low to high), -1 = downtrend (from high to low)
2914
+ * - The ZigZag indicator **repaints** by design - the last segment can change as new data arrives
2915
+ * - Supports two modes:
2916
+ * - Single source mode: uses same series for highs and lows
2917
+ * - High/Low mode: uses high for pivot highs, low for pivot lows
2918
+ *
2919
+ * @example
2920
+ * ```typescript
2921
+ * // Using high/low for more accurate pivots
2922
+ * const [zigzag, direction, isPivot] = ta.zigzag(5, 10, 3, undefined, high, low);
2923
+ *
2924
+ * // Find pivot prices
2925
+ * for (let i = 0; i < zigzag.length; i++) {
2926
+ * if (!isNaN(zigzag[i]!)) {
2927
+ * console.log(`Pivot at bar ${i}: ${zigzag[i]!}, direction: ${direction[i]! === 1 ? 'UP' : 'DOWN'}`);
2928
+ * }
2929
+ * }
2930
+ * ```
2931
+ */
2932
+ export function zigzag(deviation = 5.0, depth = 10, backstep = 3, source, high, low) {
2933
+ // Determine which mode we're in
2934
+ let highSource;
2935
+ let lowSource;
2936
+ if (high && low) {
2937
+ // High/Low mode
2938
+ highSource = high;
2939
+ lowSource = low;
2940
+ }
2941
+ else if (source) {
2942
+ // Single source mode
2943
+ highSource = source;
2944
+ lowSource = source;
2945
+ }
2946
+ else {
2947
+ throw new Error('ta.zigzag() requires either source series or high/low series. ' +
2948
+ 'Either pass them explicitly or use createContext({ chart: { high, low, close } }) for implicit data.');
2949
+ }
2950
+ const length = highSource.length;
2951
+ if (lowSource.length !== length) {
2952
+ throw new Error('ta.zigzag: high and low must have the same length');
2953
+ }
2954
+ // Result arrays
2955
+ const zigzagValues = new Array(length).fill(NaN);
2956
+ const directions = new Array(length).fill(0);
2957
+ const isPivot = new Array(length).fill(false);
2958
+ // Helper function to calculate percentage deviation
2959
+ const getDeviation = (price1, price2) => {
2960
+ // Use epsilon threshold to avoid numerical instability with very small values
2961
+ if (Math.abs(price1) < 1e-10 || isNaN(price1) || isNaN(price2))
2962
+ return 0;
2963
+ return Math.abs((price2 - price1) / price1) * 100;
2964
+ };
2965
+ let lastConfirmedPivot = null;
2966
+ let potentialPivot = null;
2967
+ let currentDirection = 0; // 0 = undefined, 1 = up, -1 = down
2968
+ // Store confirmed pivots for final zigzag construction
2969
+ const confirmedPivots = [];
2970
+ // First pass: Find initial pivot to start
2971
+ let startIndex;
2972
+ let initialHighest = -Infinity;
2973
+ let initialLowest = Infinity;
2974
+ let initialHighIndex = -1;
2975
+ let initialLowIndex = -1;
2976
+ // Find the first significant pivot using depth bars
2977
+ for (let i = 0; i < Math.min(depth, length); i++) {
2978
+ if (!isNaN(highSource[i]) && highSource[i] > initialHighest) {
2979
+ initialHighest = highSource[i];
2980
+ initialHighIndex = i;
2981
+ }
2982
+ if (!isNaN(lowSource[i]) && lowSource[i] < initialLowest) {
2983
+ initialLowest = lowSource[i];
2984
+ initialLowIndex = i;
2985
+ }
2986
+ }
2987
+ // Determine starting direction based on which came first
2988
+ if (initialHighIndex >= 0 && initialLowIndex >= 0) {
2989
+ if (initialLowIndex <= initialHighIndex) {
2990
+ // Low came first or same - start with low, direction will be up
2991
+ lastConfirmedPivot = { index: initialLowIndex, price: initialLowest, type: 'low' };
2992
+ currentDirection = 1;
2993
+ }
2994
+ else {
2995
+ // High came first - start with high, direction will be down
2996
+ lastConfirmedPivot = { index: initialHighIndex, price: initialHighest, type: 'high' };
2997
+ currentDirection = -1;
2998
+ }
2999
+ confirmedPivots.push(lastConfirmedPivot);
3000
+ startIndex = lastConfirmedPivot.index + 1;
3001
+ }
3002
+ else {
3003
+ startIndex = depth;
3004
+ }
3005
+ // Main loop: process bars
3006
+ for (let i = Math.max(startIndex, depth); i < length; i++) {
3007
+ const currentHigh = highSource[i];
3008
+ const currentLow = lowSource[i];
3009
+ if (isNaN(currentHigh) || isNaN(currentLow)) {
3010
+ directions[i] = currentDirection;
3011
+ continue;
3012
+ }
3013
+ // Check for potential pivot high
3014
+ let isPotentialHigh = true;
3015
+ for (let j = 1; j <= backstep && i - j >= 0; j++) {
3016
+ if (!isNaN(highSource[i - j]) && highSource[i - j] >= currentHigh) {
3017
+ isPotentialHigh = false;
3018
+ break;
3019
+ }
3020
+ }
3021
+ // Check for potential pivot low
3022
+ let isPotentialLow = true;
3023
+ for (let j = 1; j <= backstep && i - j >= 0; j++) {
3024
+ if (!isNaN(lowSource[i - j]) && lowSource[i - j] <= currentLow) {
3025
+ isPotentialLow = false;
3026
+ break;
3027
+ }
3028
+ }
3029
+ if (currentDirection === 1) {
3030
+ // Looking for pivot high (uptrend ending)
3031
+ if (isPotentialHigh) {
3032
+ if (potentialPivot === null || potentialPivot.type !== 'high') {
3033
+ // New potential high
3034
+ if (lastConfirmedPivot && getDeviation(lastConfirmedPivot.price, currentHigh) >= deviation) {
3035
+ potentialPivot = { index: i, price: currentHigh, type: 'high' };
3036
+ }
3037
+ }
3038
+ else {
3039
+ // Already have a potential high - update if this is higher
3040
+ if (currentHigh > potentialPivot.price) {
3041
+ potentialPivot = { index: i, price: currentHigh, type: 'high' };
3042
+ }
3043
+ }
3044
+ }
3045
+ // Check if we should confirm the potential high and start looking for a low
3046
+ if (potentialPivot && potentialPivot.type === 'high') {
3047
+ const deviationFromPotential = getDeviation(potentialPivot.price, currentLow);
3048
+ if (deviationFromPotential >= deviation && i - potentialPivot.index >= backstep) {
3049
+ // Confirm the high pivot
3050
+ confirmedPivots.push(potentialPivot);
3051
+ lastConfirmedPivot = potentialPivot;
3052
+ currentDirection = -1;
3053
+ potentialPivot = null;
3054
+ }
3055
+ }
3056
+ }
3057
+ else if (currentDirection === -1) {
3058
+ // Looking for pivot low (downtrend ending)
3059
+ if (isPotentialLow) {
3060
+ if (potentialPivot === null || potentialPivot.type !== 'low') {
3061
+ // New potential low
3062
+ if (lastConfirmedPivot && getDeviation(lastConfirmedPivot.price, currentLow) >= deviation) {
3063
+ potentialPivot = { index: i, price: currentLow, type: 'low' };
3064
+ }
3065
+ }
3066
+ else {
3067
+ // Already have a potential low - update if this is lower
3068
+ if (currentLow < potentialPivot.price) {
3069
+ potentialPivot = { index: i, price: currentLow, type: 'low' };
3070
+ }
3071
+ }
3072
+ }
3073
+ // Check if we should confirm the potential low and start looking for a high
3074
+ if (potentialPivot && potentialPivot.type === 'low') {
3075
+ const deviationFromPotential = getDeviation(potentialPivot.price, currentHigh);
3076
+ if (deviationFromPotential >= deviation && i - potentialPivot.index >= backstep) {
3077
+ // Confirm the low pivot
3078
+ confirmedPivots.push(potentialPivot);
3079
+ lastConfirmedPivot = potentialPivot;
3080
+ currentDirection = 1;
3081
+ potentialPivot = null;
3082
+ }
3083
+ }
3084
+ }
3085
+ else {
3086
+ // Initial state - determine direction based on first significant move
3087
+ if (lastConfirmedPivot) {
3088
+ if (lastConfirmedPivot.type === 'low' && getDeviation(lastConfirmedPivot.price, currentHigh) >= deviation) {
3089
+ currentDirection = 1;
3090
+ }
3091
+ else if (lastConfirmedPivot.type === 'high' && getDeviation(lastConfirmedPivot.price, currentLow) >= deviation) {
3092
+ currentDirection = -1;
3093
+ }
3094
+ }
3095
+ }
3096
+ directions[i] = currentDirection;
3097
+ }
3098
+ // Include the last potential pivot if it exists (repaint behavior)
3099
+ // This shows where the zigzag line extends to, but doesn't change direction
3100
+ const lastPotentialWasAdded = potentialPivot !== null;
3101
+ if (potentialPivot) {
3102
+ confirmedPivots.push(potentialPivot);
3103
+ }
3104
+ // Build the final zigzag output
3105
+ for (const pivot of confirmedPivots) {
3106
+ zigzagValues[pivot.index] = pivot.price;
3107
+ isPivot[pivot.index] = true;
3108
+ }
3109
+ // Set directions for all bars based on confirmed pivots
3110
+ // Direction represents the current trend at each bar:
3111
+ // - After a LOW pivot, trend is UP (1) until the next HIGH
3112
+ // - After a HIGH pivot, trend is DOWN (-1) until the next LOW
3113
+ let currentDir = 0;
3114
+ let pivotIdx = 0;
3115
+ const numConfirmed = lastPotentialWasAdded ? confirmedPivots.length - 1 : confirmedPivots.length;
3116
+ for (let i = 0; i < length; i++) {
3117
+ // Move to next confirmed pivot if we passed the current one
3118
+ // Don't count the last potential pivot for direction changes
3119
+ while (pivotIdx < numConfirmed && confirmedPivots[pivotIdx].index <= i) {
3120
+ const pivot = confirmedPivots[pivotIdx];
3121
+ currentDir = pivot.type === 'low' ? 1 : -1;
3122
+ pivotIdx++;
3123
+ }
3124
+ directions[i] = currentDir;
3125
+ }
3126
+ return [zigzagValues, directions, isPivot];
3127
+ }
3128
+ // ── Volume built-in variables (ta.obv, ta.pvt, ta.accdist, ta.nvi, ta.pvi, ta.iii, ta.wad, ta.wvad) ──
3129
+ //
3130
+ // In PineScript these are series variables computed from the chart bars. Here they take the
3131
+ // bar series explicitly.
3132
+ /**
3133
+ * On Balance Volume: `ta.cum(math.sign(ta.change(close)) * volume)`.
3134
+ *
3135
+ * @param close - Close price series
3136
+ * @param volume - Volume series
3137
+ * @returns OBV series (`na` on the first bar, and on bars without volume)
3138
+ */
3139
+ export function obv(close, volume) {
3140
+ return cum(close.map((c, i) => (i === 0 ? NaN : Math.sign(c - close[i - 1]) * volume[i])));
3141
+ }
3142
+ /**
3143
+ * Price-Volume Trend: `ta.cum((ta.change(close) / close[1]) * volume)`.
3144
+ *
3145
+ * @param close - Close price series
3146
+ * @param volume - Volume series
3147
+ * @returns PVT series (`na` on the first bar, and on bars without volume)
3148
+ */
3149
+ export function pvt(close, volume) {
3150
+ return cum(close.map((c, i) => (i === 0 ? NaN : ((c - close[i - 1]) / close[i - 1]) * volume[i])));
3151
+ }
3152
+ /**
3153
+ * Accumulation/Distribution index: the running sum of
3154
+ * `(2 * close - low - high) / (high - low) * volume`, with 0 on bars where `high == low`.
3155
+ */
3156
+ export function accdist(high, low, close, volume) {
3157
+ return cum(close.map((c, i) => {
3158
+ const h = high[i];
3159
+ const l = low[i];
3160
+ return h === l ? 0 : ((2 * c - l - h) / (h - l)) * volume[i];
3161
+ }));
3162
+ }
3163
+ /**
3164
+ * Intraday Intensity Index: `(2 * close - high - low) / (high - low) * volume`.
3165
+ *
3166
+ * The PineScript built-in value is this formula. The formula shown in the PineScript reference,
3167
+ * `(2 * close - high - low) / ((high - low) * volume)`, gives other values.
3168
+ *
3169
+ * @returns III series (`na` where `high == low`)
3170
+ */
3171
+ export function iii(high, low, close, volume) {
3172
+ return close.map((c, i) => {
3173
+ const range = high[i] - low[i];
3174
+ return range === 0 ? NaN : ((2 * c - high[i] - low[i]) / range) * volume[i];
3175
+ });
3176
+ }
3177
+ /**
3178
+ * Williams Variable Accumulation/Distribution: `(close - open) / (high - low) * volume`.
3179
+ *
3180
+ * @returns WVAD series (`na` where `high == low`)
3181
+ */
3182
+ export function wvad(open, high, low, close, volume) {
3183
+ return close.map((c, i) => {
3184
+ const range = high[i] - low[i];
3185
+ return range === 0 ? NaN : ((c - open[i]) / range) * volume[i];
3186
+ });
3187
+ }
3188
+ /**
3189
+ * Williams Accumulation/Distribution: the running sum of `close - min(low, close[1])` on up
3190
+ * closes, `close - max(high, close[1])` on down closes, and 0 otherwise (first bar included).
3191
+ */
3192
+ export function wad(high, low, close) {
3193
+ return cum(close.map((c, i) => {
3194
+ const prev = i === 0 ? NaN : close[i - 1];
3195
+ const mom = c - prev;
3196
+ if (mom > 0)
3197
+ return c - Math.min(low[i], prev);
3198
+ if (mom < 0)
3199
+ return c - Math.max(high[i], prev);
3200
+ return 0;
3201
+ }));
3202
+ }
3203
+ /** Volume index shared by NVI and PVI: moves with the close change on bars selected by `use`. */
3204
+ function volumeIndex(close, volume, use) {
3205
+ const result = [];
3206
+ let prevIndex = NaN;
3207
+ for (let i = 0; i < close.length; i++) {
3208
+ const prev = !prevIndex ? 1 : prevIndex; // nz(index[1], 0) == 0 ? 1 : index[1]
3209
+ const c = close[i];
3210
+ const prevC = i === 0 ? NaN : close[i - 1];
3211
+ let value = prev;
3212
+ if (c && prevC) {
3213
+ const prevV = i === 0 || Number.isNaN(volume[i - 1]) ? 0 : volume[i - 1];
3214
+ if (use(volume[i], prevV))
3215
+ value = prev + ((c - prevC) / prevC) * prev;
3216
+ }
3217
+ result.push(value);
3218
+ prevIndex = value;
3219
+ }
3220
+ return result;
3221
+ }
3222
+ /**
3223
+ * Negative Volume Index: moves with the close change only on bars where volume falls.
3224
+ * Starts at 1.
3225
+ */
3226
+ export function nvi(close, volume) {
3227
+ return volumeIndex(close, volume, (v, prevV) => v < prevV);
3228
+ }
3229
+ /**
3230
+ * Positive Volume Index: moves with the close change only on bars where volume rises.
3231
+ * Starts at 1.
3232
+ */
3233
+ export function pvi(close, volume) {
3234
+ return volumeIndex(close, volume, (v, prevV) => v > prevV);
3235
+ }
3236
+ //# sourceMappingURL=index.js.map