oakscriptjs 0.8.1 → 0.9.0

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