@invinite-org/chartlang-runtime 1.1.1 → 1.2.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 (226) hide show
  1. package/CHANGELOG.md +236 -0
  2. package/dist/barPoint.d.ts +20 -0
  3. package/dist/barPoint.d.ts.map +1 -0
  4. package/dist/barPoint.js +72 -0
  5. package/dist/barPoint.js.map +1 -0
  6. package/dist/bufferSnapshot.d.ts +102 -0
  7. package/dist/bufferSnapshot.d.ts.map +1 -0
  8. package/dist/bufferSnapshot.js +119 -0
  9. package/dist/bufferSnapshot.js.map +1 -0
  10. package/dist/createScriptRunner.d.ts +6 -3
  11. package/dist/createScriptRunner.d.ts.map +1 -1
  12. package/dist/createScriptRunner.js +29 -5
  13. package/dist/createScriptRunner.js.map +1 -1
  14. package/dist/dep/DepRunner.d.ts.map +1 -1
  15. package/dist/dep/DepRunner.js +1 -0
  16. package/dist/dep/DepRunner.js.map +1 -1
  17. package/dist/emit/draw/boxes/fillBetween.d.ts +45 -0
  18. package/dist/emit/draw/boxes/fillBetween.d.ts.map +1 -0
  19. package/dist/emit/draw/boxes/fillBetween.js +36 -0
  20. package/dist/emit/draw/boxes/fillBetween.js.map +1 -0
  21. package/dist/emit/draw/handle.d.ts +9 -0
  22. package/dist/emit/draw/handle.d.ts.map +1 -1
  23. package/dist/emit/draw/handle.js +65 -10
  24. package/dist/emit/draw/handle.js.map +1 -1
  25. package/dist/emit/draw/namespace.d.ts +4 -3
  26. package/dist/emit/draw/namespace.d.ts.map +1 -1
  27. package/dist/emit/draw/namespace.js +6 -3
  28. package/dist/emit/draw/namespace.js.map +1 -1
  29. package/dist/emit/plot.d.ts +7 -0
  30. package/dist/emit/plot.d.ts.map +1 -1
  31. package/dist/emit/plot.js +13 -0
  32. package/dist/emit/plot.js.map +1 -1
  33. package/dist/execution/dispose.d.ts.map +1 -1
  34. package/dist/execution/dispose.js +16 -0
  35. package/dist/execution/dispose.js.map +1 -1
  36. package/dist/execution/runComputeStep.d.ts.map +1 -1
  37. package/dist/execution/runComputeStep.js +10 -1
  38. package/dist/execution/runComputeStep.js.map +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/persistentStateStore.runtime.d.ts.map +1 -1
  44. package/dist/persistentStateStore.runtime.js +21 -7
  45. package/dist/persistentStateStore.runtime.js.map +1 -1
  46. package/dist/request/index.d.ts +2 -1
  47. package/dist/request/index.d.ts.map +1 -1
  48. package/dist/request/index.js +2 -1
  49. package/dist/request/index.js.map +1 -1
  50. package/dist/request/requestNamespace.d.ts.map +1 -1
  51. package/dist/request/requestNamespace.js +16 -3
  52. package/dist/request/requestNamespace.js.map +1 -1
  53. package/dist/request/security.d.ts +20 -1
  54. package/dist/request/security.d.ts.map +1 -1
  55. package/dist/request/security.js +62 -23
  56. package/dist/request/security.js.map +1 -1
  57. package/dist/request/securityExprRunner.d.ts +133 -0
  58. package/dist/request/securityExprRunner.d.ts.map +1 -0
  59. package/dist/request/securityExprRunner.js +235 -0
  60. package/dist/request/securityExprRunner.js.map +1 -0
  61. package/dist/request/streamBars.d.ts +14 -1
  62. package/dist/request/streamBars.d.ts.map +1 -1
  63. package/dist/request/streamBars.js +39 -1
  64. package/dist/request/streamBars.js.map +1 -1
  65. package/dist/runtimeContext.d.ts +48 -0
  66. package/dist/runtimeContext.d.ts.map +1 -1
  67. package/dist/runtimeContext.js.map +1 -1
  68. package/dist/seriesView.d.ts +42 -17
  69. package/dist/seriesView.d.ts.map +1 -1
  70. package/dist/seriesView.js +65 -42
  71. package/dist/seriesView.js.map +1 -1
  72. package/dist/state/index.d.ts +2 -1
  73. package/dist/state/index.d.ts.map +1 -1
  74. package/dist/state/index.js +2 -1
  75. package/dist/state/index.js.map +1 -1
  76. package/dist/state/lifecycle.d.ts +40 -0
  77. package/dist/state/lifecycle.d.ts.map +1 -1
  78. package/dist/state/lifecycle.js +53 -0
  79. package/dist/state/lifecycle.js.map +1 -1
  80. package/dist/state/seriesPersistence.d.ts +48 -0
  81. package/dist/state/seriesPersistence.d.ts.map +1 -0
  82. package/dist/state/seriesPersistence.js +87 -0
  83. package/dist/state/seriesPersistence.js.map +1 -0
  84. package/dist/state/seriesSlot.d.ts +105 -0
  85. package/dist/state/seriesSlot.d.ts.map +1 -0
  86. package/dist/state/seriesSlot.js +123 -0
  87. package/dist/state/seriesSlot.js.map +1 -0
  88. package/dist/state/stateNamespace.d.ts.map +1 -1
  89. package/dist/state/stateNamespace.js +28 -0
  90. package/dist/state/stateNamespace.js.map +1 -1
  91. package/dist/streamState.d.ts +25 -19
  92. package/dist/streamState.d.ts.map +1 -1
  93. package/dist/streamState.js +40 -66
  94. package/dist/streamState.js.map +1 -1
  95. package/dist/ta/adx.d.ts +3 -2
  96. package/dist/ta/adx.d.ts.map +1 -1
  97. package/dist/ta/adx.js +3 -2
  98. package/dist/ta/adx.js.map +1 -1
  99. package/dist/ta/alma.d.ts +6 -4
  100. package/dist/ta/alma.d.ts.map +1 -1
  101. package/dist/ta/alma.js +19 -6
  102. package/dist/ta/alma.js.map +1 -1
  103. package/dist/ta/atr.d.ts +3 -2
  104. package/dist/ta/atr.d.ts.map +1 -1
  105. package/dist/ta/atr.js +3 -2
  106. package/dist/ta/atr.js.map +1 -1
  107. package/dist/ta/bb.d.ts +3 -2
  108. package/dist/ta/bb.d.ts.map +1 -1
  109. package/dist/ta/bb.js +3 -2
  110. package/dist/ta/bb.js.map +1 -1
  111. package/dist/ta/chaikinOsc.d.ts +3 -2
  112. package/dist/ta/chaikinOsc.d.ts.map +1 -1
  113. package/dist/ta/chaikinOsc.js +3 -2
  114. package/dist/ta/chaikinOsc.js.map +1 -1
  115. package/dist/ta/crossover.d.ts +3 -2
  116. package/dist/ta/crossover.d.ts.map +1 -1
  117. package/dist/ta/crossover.js +3 -2
  118. package/dist/ta/crossover.js.map +1 -1
  119. package/dist/ta/crossunder.d.ts +3 -2
  120. package/dist/ta/crossunder.d.ts.map +1 -1
  121. package/dist/ta/crossunder.js +3 -2
  122. package/dist/ta/crossunder.js.map +1 -1
  123. package/dist/ta/dmi.d.ts +4 -3
  124. package/dist/ta/dmi.d.ts.map +1 -1
  125. package/dist/ta/dmi.js +4 -3
  126. package/dist/ta/dmi.js.map +1 -1
  127. package/dist/ta/ema.d.ts +3 -2
  128. package/dist/ta/ema.d.ts.map +1 -1
  129. package/dist/ta/ema.js +3 -2
  130. package/dist/ta/ema.js.map +1 -1
  131. package/dist/ta/eom.d.ts +3 -1
  132. package/dist/ta/eom.d.ts.map +1 -1
  133. package/dist/ta/eom.js +3 -1
  134. package/dist/ta/eom.js.map +1 -1
  135. package/dist/ta/highestbars.d.ts +25 -0
  136. package/dist/ta/highestbars.d.ts.map +1 -0
  137. package/dist/ta/highestbars.js +106 -0
  138. package/dist/ta/highestbars.js.map +1 -0
  139. package/dist/ta/historicalVolatility.d.ts +3 -2
  140. package/dist/ta/historicalVolatility.d.ts.map +1 -1
  141. package/dist/ta/historicalVolatility.js +3 -2
  142. package/dist/ta/historicalVolatility.js.map +1 -1
  143. package/dist/ta/ichimoku.d.ts +3 -1
  144. package/dist/ta/ichimoku.d.ts.map +1 -1
  145. package/dist/ta/ichimoku.js +3 -1
  146. package/dist/ta/ichimoku.js.map +1 -1
  147. package/dist/ta/lowestbars.d.ts +25 -0
  148. package/dist/ta/lowestbars.d.ts.map +1 -0
  149. package/dist/ta/lowestbars.js +102 -0
  150. package/dist/ta/lowestbars.js.map +1 -0
  151. package/dist/ta/macd.d.ts +3 -2
  152. package/dist/ta/macd.d.ts.map +1 -1
  153. package/dist/ta/macd.js +3 -2
  154. package/dist/ta/macd.js.map +1 -1
  155. package/dist/ta/massIndex.d.ts +3 -2
  156. package/dist/ta/massIndex.d.ts.map +1 -1
  157. package/dist/ta/massIndex.js +3 -2
  158. package/dist/ta/massIndex.js.map +1 -1
  159. package/dist/ta/mfi.d.ts +3 -1
  160. package/dist/ta/mfi.d.ts.map +1 -1
  161. package/dist/ta/mfi.js +3 -1
  162. package/dist/ta/mfi.js.map +1 -1
  163. package/dist/ta/netVolume.d.ts +3 -1
  164. package/dist/ta/netVolume.d.ts.map +1 -1
  165. package/dist/ta/netVolume.js +3 -1
  166. package/dist/ta/netVolume.js.map +1 -1
  167. package/dist/ta/nvi.d.ts +3 -1
  168. package/dist/ta/nvi.d.ts.map +1 -1
  169. package/dist/ta/nvi.js +3 -1
  170. package/dist/ta/nvi.js.map +1 -1
  171. package/dist/ta/persistence.d.ts.map +1 -1
  172. package/dist/ta/persistence.js +1 -40
  173. package/dist/ta/persistence.js.map +1 -1
  174. package/dist/ta/ppo.d.ts +3 -2
  175. package/dist/ta/ppo.d.ts.map +1 -1
  176. package/dist/ta/ppo.js +3 -2
  177. package/dist/ta/ppo.js.map +1 -1
  178. package/dist/ta/pvi.d.ts +3 -1
  179. package/dist/ta/pvi.d.ts.map +1 -1
  180. package/dist/ta/pvi.js +3 -1
  181. package/dist/ta/pvi.js.map +1 -1
  182. package/dist/ta/pvo.d.ts +3 -1
  183. package/dist/ta/pvo.d.ts.map +1 -1
  184. package/dist/ta/pvo.js +3 -1
  185. package/dist/ta/pvo.js.map +1 -1
  186. package/dist/ta/pvt.d.ts +3 -1
  187. package/dist/ta/pvt.d.ts.map +1 -1
  188. package/dist/ta/pvt.js +3 -1
  189. package/dist/ta/pvt.js.map +1 -1
  190. package/dist/ta/registry.d.ts +7 -1
  191. package/dist/ta/registry.d.ts.map +1 -1
  192. package/dist/ta/registry.js +4 -0
  193. package/dist/ta/registry.js.map +1 -1
  194. package/dist/ta/rsi.d.ts +3 -2
  195. package/dist/ta/rsi.d.ts.map +1 -1
  196. package/dist/ta/rsi.js +3 -2
  197. package/dist/ta/rsi.js.map +1 -1
  198. package/dist/ta/rvi.d.ts +3 -2
  199. package/dist/ta/rvi.d.ts.map +1 -1
  200. package/dist/ta/rvi.js +3 -2
  201. package/dist/ta/rvi.js.map +1 -1
  202. package/dist/ta/sma.d.ts +6 -3
  203. package/dist/ta/sma.d.ts.map +1 -1
  204. package/dist/ta/sma.js +6 -3
  205. package/dist/ta/sma.js.map +1 -1
  206. package/dist/ta/stdev.d.ts +3 -2
  207. package/dist/ta/stdev.d.ts.map +1 -1
  208. package/dist/ta/stdev.js +3 -2
  209. package/dist/ta/stdev.js.map +1 -1
  210. package/dist/ta/trendStrengthIndex.d.ts +3 -2
  211. package/dist/ta/trendStrengthIndex.d.ts.map +1 -1
  212. package/dist/ta/trendStrengthIndex.js +3 -2
  213. package/dist/ta/trendStrengthIndex.js.map +1 -1
  214. package/dist/ta/trix.d.ts +4 -3
  215. package/dist/ta/trix.d.ts.map +1 -1
  216. package/dist/ta/trix.js +4 -3
  217. package/dist/ta/trix.js.map +1 -1
  218. package/dist/ta/vortex.d.ts +3 -2
  219. package/dist/ta/vortex.d.ts.map +1 -1
  220. package/dist/ta/vortex.js +3 -2
  221. package/dist/ta/vortex.js.map +1 -1
  222. package/package.json +3 -3
  223. package/dist/ta/lib/applyOffset.d.ts +0 -19
  224. package/dist/ta/lib/applyOffset.d.ts.map +0 -1
  225. package/dist/ta/lib/applyOffset.js +0 -38
  226. package/dist/ta/lib/applyOffset.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"chaikinOsc.js","sourceRoot":"","sources":["../../src/ta/chaikinOsc.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Ported from invinite/src/components/trading-chart/indicators/chaikin-osc.ts\n// (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02, © Invinite).\n// Re-licensed MIT for chartlang. The math is the reference, the code\n// style is not.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape — NOT invinite's\n// IndicatorPlugin shape. Per §9.4 we fold invinite's private ADL +\n// EMA copies onto the canonical `ta.adl` + `ta.ema` primitives via\n// three sub-slots (`${slotId}/adl`, `${slotId}/fast`, `${slotId}/slow`).\n\nimport type { ChaikinOscOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport { adl } from \"./adl.js\";\nimport { ema } from \"./ema.js\";\n\nconst DEFAULT_FAST = 3;\nconst DEFAULT_SLOW = 10;\n\ntype ChaikinOscSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.chaikinOsc called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): ChaikinOscSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n };\n}\n\nfunction viewForOffset(slot: ChaikinOscSlot, offset: number): Series<number> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<number>(slot.outBuffer, offset);\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction diff(fast: number, slow: number): number {\n if (!Number.isFinite(fast) || !Number.isFinite(slow)) return Number.NaN;\n return fast - slow;\n}\n\n/**\n * Chaikin Oscillator — `EMA(ADL, fastLength) − EMA(ADL, slowLength)`.\n * Composes one `ta.adl` sub-slot (cumulative money-flow volume) plus\n * two `ta.ema` sub-slots over the ADL series; a fix to `ta.adl` or\n * `ta.ema` flows in for free. Renders in its own pane (volume\n * category, oscillator-shape around zero).\n *\n * Defaults `{ fastLength: 3, slowLength: 10 }` (TradingView /\n * invinite canonical). ADL has warmup 0; the slow EMA seeds at bar\n * `slowLength − 1`, so the oscillator first emits a finite value at\n * that bar.\n *\n * **Tick mode.** The sub-slots handle their own tick replay (ADL\n * snapshots `prevClosedCumAdl`; EMA snapshots `prevClosedEma`); this\n * primitive's parent slot just re-evaluates `fastEma − slowEma`\n * against the live sub-slot heads and `replaceHead`s its own output.\n *\n * @formula chaikinOsc[t] = ema(adl(t), fastLength) − ema(adl(t), slowLength)\n * @warmup slowLength − 1\n * @since 0.2\n * @stable\n *\n * `opts.offset` shifts the returned series so `series.current` reads\n * the value `offset` bars ago.\n *\n * @example\n * // import { ta, plot } from \"@invinite-org/chartlang-core\";\n * // const c = ta.chaikinOsc();\n * // plot(c);\n */\nexport function chaikinOsc(slotId: string, opts?: ChaikinOscOpts): Series<number> {\n const ctx = getCtx();\n const fastLength = opts?.fastLength ?? DEFAULT_FAST;\n const slowLength = opts?.slowLength ?? DEFAULT_SLOW;\n const offset = opts?.offset ?? 0;\n\n const adlSeries = adl(`${slotId}/adl`);\n const fastSeries = ema(`${slotId}/fast`, adlSeries.current, fastLength);\n const slowSeries = ema(`${slotId}/slow`, adlSeries.current, slowLength);\n\n let slot = ctx.stream.taSlots.get(slotId) as ChaikinOscSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const value = diff(fastSeries.current, slowSeries.current);\n if (ctx.isTick) slot.outBuffer.replaceHead(value);\n else slot.outBuffer.append(value);\n return viewForOffset(slot, offset);\n}\n"]}
1
+ {"version":3,"file":"chaikinOsc.js","sourceRoot":"","sources":["../../src/ta/chaikinOsc.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Ported from invinite/src/components/trading-chart/indicators/chaikin-osc.ts\n// (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02, © Invinite).\n// Re-licensed MIT for chartlang. The math is the reference, the code\n// style is not.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape — NOT invinite's\n// IndicatorPlugin shape. Per §9.4 we fold invinite's private ADL +\n// EMA copies onto the canonical `ta.adl` + `ta.ema` primitives via\n// three sub-slots (`${slotId}/adl`, `${slotId}/fast`, `${slotId}/slow`).\n\nimport type { ChaikinOscOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport { adl } from \"./adl.js\";\nimport { ema } from \"./ema.js\";\n\nconst DEFAULT_FAST = 3;\nconst DEFAULT_SLOW = 10;\n\ntype ChaikinOscSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.chaikinOsc called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): ChaikinOscSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n };\n}\n\nfunction viewForOffset(slot: ChaikinOscSlot, offset: number): Series<number> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<number>(slot.outBuffer, offset);\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction diff(fast: number, slow: number): number {\n if (!Number.isFinite(fast) || !Number.isFinite(slow)) return Number.NaN;\n return fast - slow;\n}\n\n/**\n * Chaikin Oscillator — `EMA(ADL, fastLength) − EMA(ADL, slowLength)`.\n * Composes one `ta.adl` sub-slot (cumulative money-flow volume) plus\n * two `ta.ema` sub-slots over the ADL series; a fix to `ta.adl` or\n * `ta.ema` flows in for free. Renders in its own pane (volume\n * category, oscillator-shape around zero).\n *\n * Defaults `{ fastLength: 3, slowLength: 10 }` (TradingView /\n * invinite canonical). ADL has warmup 0; the slow EMA seeds at bar\n * `slowLength − 1`, so the oscillator first emits a finite value at\n * that bar.\n *\n * **Tick mode.** The sub-slots handle their own tick replay (ADL\n * snapshots `prevClosedCumAdl`; EMA snapshots `prevClosedEma`); this\n * primitive's parent slot just re-evaluates `fastEma − slowEma`\n * against the live sub-slot heads and `replaceHead`s its own output.\n *\n * @formula chaikinOsc[t] = ema(adl(t), fastLength) − ema(adl(t), slowLength)\n * @warmup slowLength − 1\n * @since 0.2\n * @stable\n *\n * `opts.offset` is a presentation display shift carried to the plot\n * emission as `xShift` (`+n` right / future, `−n` left / past); the\n * series value is unshifted.\n *\n * @example\n * // import { ta, plot } from \"@invinite-org/chartlang-core\";\n * // const c = ta.chaikinOsc();\n * // plot(c);\n */\nexport function chaikinOsc(slotId: string, opts?: ChaikinOscOpts): Series<number> {\n const ctx = getCtx();\n const fastLength = opts?.fastLength ?? DEFAULT_FAST;\n const slowLength = opts?.slowLength ?? DEFAULT_SLOW;\n const offset = opts?.offset ?? 0;\n\n const adlSeries = adl(`${slotId}/adl`);\n const fastSeries = ema(`${slotId}/fast`, adlSeries.current, fastLength);\n const slowSeries = ema(`${slotId}/slow`, adlSeries.current, slowLength);\n\n let slot = ctx.stream.taSlots.get(slotId) as ChaikinOscSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const value = diff(fastSeries.current, slowSeries.current);\n if (ctx.isTick) slot.outBuffer.replaceHead(value);\n else slot.outBuffer.append(value);\n return viewForOffset(slot, offset);\n}\n"]}
@@ -16,8 +16,9 @@ import { type ScalarOrSeries } from "./lib/sourceValue.js";
16
16
  * @since 0.1
17
17
  * @stable
18
18
  *
19
- * `opts.offset` shifts the boolean series so `series.current` returns
20
- * the crossover detection `offset` bars ago.
19
+ * `opts.offset` is a presentation display shift carried to the plot
20
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
21
+ * boolean crossover series value is unshifted.
21
22
  *
22
23
  * @example
23
24
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"crossover.d.ts","sourceRoot":"","sources":["../../src/ta/crossover.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAK1E,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AA4D5E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,SAAS,CACrB,MAAM,EAAE,MAAM,EACd,CAAC,EAAE,cAAc,EACjB,CAAC,EAAE,cAAc,EACjB,IAAI,CAAC,EAAE,aAAa,GACrB,MAAM,CAAC,OAAO,CAAC,CA+BjB"}
1
+ {"version":3,"file":"crossover.d.ts","sourceRoot":"","sources":["../../src/ta/crossover.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAK1E,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AA4D5E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,SAAS,CACrB,MAAM,EAAE,MAAM,EACd,CAAC,EAAE,cAAc,EACjB,CAAC,EAAE,cAAc,EACjB,IAAI,CAAC,EAAE,aAAa,GACrB,MAAM,CAAC,OAAO,CAAC,CA+BjB"}
@@ -64,8 +64,9 @@ function detect(prevA, prevB, currA, currB) {
64
64
  * @since 0.1
65
65
  * @stable
66
66
  *
67
- * `opts.offset` shifts the boolean series so `series.current` returns
68
- * the crossover detection `offset` bars ago.
67
+ * `opts.offset` is a presentation display shift carried to the plot
68
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
69
+ * boolean crossover series value is unshifted.
69
70
  *
70
71
  * @example
71
72
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"crossover.js","sourceRoot":"","sources":["../../src/ta/crossover.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// No invinite source — Phase-1 new code, semantics per Pine\n// `ta.crossover` / `ta.crossunder`.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape.\n\nimport type { CrossoverOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport { type ScalarOrSeries, readSourceValue } from \"./lib/sourceValue.js\";\n\ntype CrossSlot = {\n readonly outBuffer: RingBuffer<boolean>;\n readonly series: Series<boolean>;\n /** Prior closed-bar values of `a` and `b` (rolling 2-slot history). */\n prevA: number;\n prevB: number;\n /** As-of-current-close values, frozen so ticks replay against prior. */\n currA: number;\n currB: number;\n initialised: boolean;\n /** Per-offset Series-view cache; see `sma.ts` for the convention. */\n readonly shiftedViews: Map<number, Series<boolean>>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.crossover called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): CrossSlot {\n const outBuffer = new RingBuffer<boolean>(capacity);\n return {\n outBuffer,\n series: makeSeriesView<boolean>(outBuffer) as Series<boolean>,\n prevA: Number.NaN,\n prevB: Number.NaN,\n currA: Number.NaN,\n currB: Number.NaN,\n initialised: false,\n shiftedViews: new Map(),\n };\n}\n\nfunction viewForOffset(slot: CrossSlot, offset: number): Series<boolean> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<boolean>(slot.outBuffer, offset) as Series<boolean>;\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction detect(prevA: number, prevB: number, currA: number, currB: number): boolean {\n if (\n !Number.isFinite(prevA) ||\n !Number.isFinite(prevB) ||\n !Number.isFinite(currA) ||\n !Number.isFinite(currB)\n ) {\n return false;\n }\n return currA > currB && prevA <= prevB;\n}\n\n/**\n * `true` exactly at the bar where `a` crosses above `b`: `a.current >\n * b.current && a.prev <= b.prev`. `b` may be a scalar (treated as a\n * constant series). NaN inputs yield `false` — Pine semantics for\n * Boolean series (NaN doesn't bubble through booleans).\n *\n * The slot keeps a 2-slot history of both `a` and `b` so scalar\n * sources work without the caller wrapping them. Tick-mode replays\n * the head with the prior closed-bar's `(a, b)` pair so a partial-\n * bar value doesn't seed the next close's comparison.\n *\n * @formula out[t] = a[t] > b[t] && a[t − 1] ≤ b[t − 1] (else false)\n * @warmup 1 (need a prior bar)\n * @since 0.1\n * @stable\n *\n * `opts.offset` shifts the boolean series so `series.current` returns\n * the crossover detection `offset` bars ago.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const c = ta.crossover(\"slot\", fastEma, slowEma);\n * // if (c.current) { ... }\n * // const lagged = ta.crossover(\"slot2\", fastEma, slowEma, { offset: 1 });\n */\nexport function crossover(\n slotId: string,\n a: ScalarOrSeries,\n b: ScalarOrSeries,\n opts?: CrossoverOpts,\n): Series<boolean> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as CrossSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const aValue = readSourceValue(a);\n const bValue = readSourceValue(b);\n const offset = opts?.offset ?? 0;\n if (ctx.isTick) {\n const out = detect(slot.prevA, slot.prevB, aValue, bValue);\n slot.outBuffer.replaceHead(out);\n return viewForOffset(slot, offset);\n }\n if (!slot.initialised) {\n slot.initialised = true;\n slot.prevA = aValue;\n slot.prevB = bValue;\n slot.currA = aValue;\n slot.currB = bValue;\n slot.outBuffer.append(false);\n return viewForOffset(slot, offset);\n }\n // Standard close-side advance: prev becomes currA/B, currA/B become the new sample.\n slot.prevA = slot.currA;\n slot.prevB = slot.currB;\n slot.currA = aValue;\n slot.currB = bValue;\n slot.outBuffer.append(detect(slot.prevA, slot.prevB, slot.currA, slot.currB));\n return viewForOffset(slot, offset);\n}\n"]}
1
+ {"version":3,"file":"crossover.js","sourceRoot":"","sources":["../../src/ta/crossover.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// No invinite source — Phase-1 new code, semantics per Pine\n// `ta.crossover` / `ta.crossunder`.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape.\n\nimport type { CrossoverOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport { type ScalarOrSeries, readSourceValue } from \"./lib/sourceValue.js\";\n\ntype CrossSlot = {\n readonly outBuffer: RingBuffer<boolean>;\n readonly series: Series<boolean>;\n /** Prior closed-bar values of `a` and `b` (rolling 2-slot history). */\n prevA: number;\n prevB: number;\n /** As-of-current-close values, frozen so ticks replay against prior. */\n currA: number;\n currB: number;\n initialised: boolean;\n /** Per-offset Series-view cache; see `sma.ts` for the convention. */\n readonly shiftedViews: Map<number, Series<boolean>>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.crossover called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): CrossSlot {\n const outBuffer = new RingBuffer<boolean>(capacity);\n return {\n outBuffer,\n series: makeSeriesView<boolean>(outBuffer) as Series<boolean>,\n prevA: Number.NaN,\n prevB: Number.NaN,\n currA: Number.NaN,\n currB: Number.NaN,\n initialised: false,\n shiftedViews: new Map(),\n };\n}\n\nfunction viewForOffset(slot: CrossSlot, offset: number): Series<boolean> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<boolean>(slot.outBuffer, offset) as Series<boolean>;\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction detect(prevA: number, prevB: number, currA: number, currB: number): boolean {\n if (\n !Number.isFinite(prevA) ||\n !Number.isFinite(prevB) ||\n !Number.isFinite(currA) ||\n !Number.isFinite(currB)\n ) {\n return false;\n }\n return currA > currB && prevA <= prevB;\n}\n\n/**\n * `true` exactly at the bar where `a` crosses above `b`: `a.current >\n * b.current && a.prev <= b.prev`. `b` may be a scalar (treated as a\n * constant series). NaN inputs yield `false` — Pine semantics for\n * Boolean series (NaN doesn't bubble through booleans).\n *\n * The slot keeps a 2-slot history of both `a` and `b` so scalar\n * sources work without the caller wrapping them. Tick-mode replays\n * the head with the prior closed-bar's `(a, b)` pair so a partial-\n * bar value doesn't seed the next close's comparison.\n *\n * @formula out[t] = a[t] > b[t] && a[t − 1] ≤ b[t − 1] (else false)\n * @warmup 1 (need a prior bar)\n * @since 0.1\n * @stable\n *\n * `opts.offset` is a presentation display shift carried to the plot\n * emission as `xShift` (`+n` right / future, `−n` left / past); the\n * boolean crossover series value is unshifted.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const c = ta.crossover(\"slot\", fastEma, slowEma);\n * // if (c.current) { ... }\n * // const lagged = ta.crossover(\"slot2\", fastEma, slowEma, { offset: 1 });\n */\nexport function crossover(\n slotId: string,\n a: ScalarOrSeries,\n b: ScalarOrSeries,\n opts?: CrossoverOpts,\n): Series<boolean> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as CrossSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const aValue = readSourceValue(a);\n const bValue = readSourceValue(b);\n const offset = opts?.offset ?? 0;\n if (ctx.isTick) {\n const out = detect(slot.prevA, slot.prevB, aValue, bValue);\n slot.outBuffer.replaceHead(out);\n return viewForOffset(slot, offset);\n }\n if (!slot.initialised) {\n slot.initialised = true;\n slot.prevA = aValue;\n slot.prevB = bValue;\n slot.currA = aValue;\n slot.currB = bValue;\n slot.outBuffer.append(false);\n return viewForOffset(slot, offset);\n }\n // Standard close-side advance: prev becomes currA/B, currA/B become the new sample.\n slot.prevA = slot.currA;\n slot.prevB = slot.currB;\n slot.currA = aValue;\n slot.currB = bValue;\n slot.outBuffer.append(detect(slot.prevA, slot.prevB, slot.currA, slot.currB));\n return viewForOffset(slot, offset);\n}\n"]}
@@ -10,8 +10,9 @@ import { type ScalarOrSeries } from "./lib/sourceValue.js";
10
10
  * @since 0.1
11
11
  * @stable
12
12
  *
13
- * `opts.offset` shifts the boolean series so `series.current` returns
14
- * the crossunder detection `offset` bars ago.
13
+ * `opts.offset` is a presentation display shift carried to the plot
14
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
15
+ * boolean crossunder series value is unshifted.
15
16
  *
16
17
  * @example
17
18
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"crossunder.d.ts","sourceRoot":"","sources":["../../src/ta/crossunder.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAK3E,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AA0D5E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CACtB,MAAM,EAAE,MAAM,EACd,CAAC,EAAE,cAAc,EACjB,CAAC,EAAE,cAAc,EACjB,IAAI,CAAC,EAAE,cAAc,GACtB,MAAM,CAAC,OAAO,CAAC,CA8BjB"}
1
+ {"version":3,"file":"crossunder.d.ts","sourceRoot":"","sources":["../../src/ta/crossunder.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAK3E,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AA0D5E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CACtB,MAAM,EAAE,MAAM,EACd,CAAC,EAAE,cAAc,EACjB,CAAC,EAAE,cAAc,EACjB,IAAI,CAAC,EAAE,cAAc,GACtB,MAAM,CAAC,OAAO,CAAC,CA8BjB"}
@@ -58,8 +58,9 @@ function detect(prevA, prevB, currA, currB) {
58
58
  * @since 0.1
59
59
  * @stable
60
60
  *
61
- * `opts.offset` shifts the boolean series so `series.current` returns
62
- * the crossunder detection `offset` bars ago.
61
+ * `opts.offset` is a presentation display shift carried to the plot
62
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
63
+ * boolean crossunder series value is unshifted.
63
64
  *
64
65
  * @example
65
66
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"crossunder.js","sourceRoot":"","sources":["../../src/ta/crossunder.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// No invinite source — Phase-1 new code, semantics per Pine\n// `ta.crossover` / `ta.crossunder`.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape.\n\nimport type { CrossunderOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport { type ScalarOrSeries, readSourceValue } from \"./lib/sourceValue.js\";\n\ntype CrossSlot = {\n readonly outBuffer: RingBuffer<boolean>;\n readonly series: Series<boolean>;\n prevA: number;\n prevB: number;\n currA: number;\n currB: number;\n initialised: boolean;\n /** Per-offset Series-view cache; see `sma.ts` for the convention. */\n readonly shiftedViews: Map<number, Series<boolean>>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.crossunder called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): CrossSlot {\n const outBuffer = new RingBuffer<boolean>(capacity);\n return {\n outBuffer,\n series: makeSeriesView<boolean>(outBuffer) as Series<boolean>,\n prevA: Number.NaN,\n prevB: Number.NaN,\n currA: Number.NaN,\n currB: Number.NaN,\n initialised: false,\n shiftedViews: new Map(),\n };\n}\n\nfunction viewForOffset(slot: CrossSlot, offset: number): Series<boolean> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<boolean>(slot.outBuffer, offset) as Series<boolean>;\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction detect(prevA: number, prevB: number, currA: number, currB: number): boolean {\n if (\n !Number.isFinite(prevA) ||\n !Number.isFinite(prevB) ||\n !Number.isFinite(currA) ||\n !Number.isFinite(currB)\n ) {\n return false;\n }\n return currA < currB && prevA >= prevB;\n}\n\n/**\n * `true` exactly at the bar where `a` crosses below `b`: `a.current <\n * b.current && a.prev >= b.prev`. Mirror of {@link crossover}; NaN\n * inputs yield `false`.\n *\n * @formula out[t] = a[t] < b[t] && a[t − 1] ≥ b[t − 1] (else false)\n * @warmup 1\n * @since 0.1\n * @stable\n *\n * `opts.offset` shifts the boolean series so `series.current` returns\n * the crossunder detection `offset` bars ago.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const c = ta.crossunder(\"slot\", fastEma, slowEma);\n * // if (c.current) { ... }\n * // const lagged = ta.crossunder(\"slot2\", fastEma, slowEma, { offset: 1 });\n */\nexport function crossunder(\n slotId: string,\n a: ScalarOrSeries,\n b: ScalarOrSeries,\n opts?: CrossunderOpts,\n): Series<boolean> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as CrossSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const aValue = readSourceValue(a);\n const bValue = readSourceValue(b);\n const offset = opts?.offset ?? 0;\n if (ctx.isTick) {\n const out = detect(slot.prevA, slot.prevB, aValue, bValue);\n slot.outBuffer.replaceHead(out);\n return viewForOffset(slot, offset);\n }\n if (!slot.initialised) {\n slot.initialised = true;\n slot.prevA = aValue;\n slot.prevB = bValue;\n slot.currA = aValue;\n slot.currB = bValue;\n slot.outBuffer.append(false);\n return viewForOffset(slot, offset);\n }\n slot.prevA = slot.currA;\n slot.prevB = slot.currB;\n slot.currA = aValue;\n slot.currB = bValue;\n slot.outBuffer.append(detect(slot.prevA, slot.prevB, slot.currA, slot.currB));\n return viewForOffset(slot, offset);\n}\n"]}
1
+ {"version":3,"file":"crossunder.js","sourceRoot":"","sources":["../../src/ta/crossunder.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// No invinite source — Phase-1 new code, semantics per Pine\n// `ta.crossover` / `ta.crossunder`.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape.\n\nimport type { CrossunderOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport { type ScalarOrSeries, readSourceValue } from \"./lib/sourceValue.js\";\n\ntype CrossSlot = {\n readonly outBuffer: RingBuffer<boolean>;\n readonly series: Series<boolean>;\n prevA: number;\n prevB: number;\n currA: number;\n currB: number;\n initialised: boolean;\n /** Per-offset Series-view cache; see `sma.ts` for the convention. */\n readonly shiftedViews: Map<number, Series<boolean>>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.crossunder called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): CrossSlot {\n const outBuffer = new RingBuffer<boolean>(capacity);\n return {\n outBuffer,\n series: makeSeriesView<boolean>(outBuffer) as Series<boolean>,\n prevA: Number.NaN,\n prevB: Number.NaN,\n currA: Number.NaN,\n currB: Number.NaN,\n initialised: false,\n shiftedViews: new Map(),\n };\n}\n\nfunction viewForOffset(slot: CrossSlot, offset: number): Series<boolean> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<boolean>(slot.outBuffer, offset) as Series<boolean>;\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction detect(prevA: number, prevB: number, currA: number, currB: number): boolean {\n if (\n !Number.isFinite(prevA) ||\n !Number.isFinite(prevB) ||\n !Number.isFinite(currA) ||\n !Number.isFinite(currB)\n ) {\n return false;\n }\n return currA < currB && prevA >= prevB;\n}\n\n/**\n * `true` exactly at the bar where `a` crosses below `b`: `a.current <\n * b.current && a.prev >= b.prev`. Mirror of {@link crossover}; NaN\n * inputs yield `false`.\n *\n * @formula out[t] = a[t] < b[t] && a[t − 1] ≥ b[t − 1] (else false)\n * @warmup 1\n * @since 0.1\n * @stable\n *\n * `opts.offset` is a presentation display shift carried to the plot\n * emission as `xShift` (`+n` right / future, `−n` left / past); the\n * boolean crossunder series value is unshifted.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const c = ta.crossunder(\"slot\", fastEma, slowEma);\n * // if (c.current) { ... }\n * // const lagged = ta.crossunder(\"slot2\", fastEma, slowEma, { offset: 1 });\n */\nexport function crossunder(\n slotId: string,\n a: ScalarOrSeries,\n b: ScalarOrSeries,\n opts?: CrossunderOpts,\n): Series<boolean> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as CrossSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const aValue = readSourceValue(a);\n const bValue = readSourceValue(b);\n const offset = opts?.offset ?? 0;\n if (ctx.isTick) {\n const out = detect(slot.prevA, slot.prevB, aValue, bValue);\n slot.outBuffer.replaceHead(out);\n return viewForOffset(slot, offset);\n }\n if (!slot.initialised) {\n slot.initialised = true;\n slot.prevA = aValue;\n slot.prevB = bValue;\n slot.currA = aValue;\n slot.currB = bValue;\n slot.outBuffer.append(false);\n return viewForOffset(slot, offset);\n }\n slot.prevA = slot.currA;\n slot.prevB = slot.currB;\n slot.currA = aValue;\n slot.currB = bValue;\n slot.outBuffer.append(detect(slot.prevA, slot.prevB, slot.currA, slot.currB));\n return viewForOffset(slot, offset);\n}\n"]}
package/dist/ta/dmi.d.ts CHANGED
@@ -22,9 +22,10 @@ import type { DmiOpts, DmiResult } from "@invinite-org/chartlang-core";
22
22
  * @since 0.2
23
23
  * @stable
24
24
  *
25
- * `opts.offset` shifts both series in lockstep
26
- * `series.current` on each output returns the value `offset` bars
27
- * ago.
25
+ * `opts.offset` is a presentation display shift carried to the plot
26
+ * emission as `xShift` for both series in lockstep (`+n` right / future,
27
+ * `−n` left / past); the series values are unshifted, so
28
+ * `series.current` on each output returns the value at the current bar.
28
29
  *
29
30
  * @example
30
31
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"dmi.d.ts","sourceRoot":"","sources":["../../src/ta/dmi.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AA8DvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAuB7E"}
1
+ {"version":3,"file":"dmi.d.ts","sourceRoot":"","sources":["../../src/ta/dmi.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AA8DvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAuB7E"}
package/dist/ta/dmi.js CHANGED
@@ -74,9 +74,10 @@ function resultForOffset(slot, offset) {
74
74
  * @since 0.2
75
75
  * @stable
76
76
  *
77
- * `opts.offset` shifts both series in lockstep
78
- * `series.current` on each output returns the value `offset` bars
79
- * ago.
77
+ * `opts.offset` is a presentation display shift carried to the plot
78
+ * emission as `xShift` for both series in lockstep (`+n` right / future,
79
+ * `−n` left / past); the series values are unshifted, so
80
+ * `series.current` on each output returns the value at the current bar.
80
81
  *
81
82
  * @example
82
83
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"dmi.js","sourceRoot":"","sources":["../../src/ta/dmi.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,mCAAmC;AACnC,mEAAmE;AACnE,qEAAqE;AACrE,gBAAgB;AAChB,qEAAqE;AACrE,4DAA4D;AAC5D,wEAAwE;AACxE,uEAAuE;AACvE,iEAAiE;AACjE,2DAA2D;AAC3D,+CAA+C;AAI/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAuB,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EACH,uBAAuB,EAEvB,oBAAoB,EACpB,eAAe,GAClB,MAAM,2BAA2B,CAAC;AAgBnC,SAAS,MAAM;IACX,MAAM,GAAG,GAAG,sBAAsB,CAAC,OAAO,CAAC;IAC3C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc,EAAE,QAAgB;IAC9C,MAAM,YAAY,GAAG,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACrD,MAAM,aAAa,GAAG,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACtD,OAAO;QACH,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;YAClB,MAAM,EAAE,cAAc,CAAS,YAAY,CAAC;YAC5C,OAAO,EAAE,cAAc,CAAS,aAAa,CAAC;SACjD,CAAC;QACF,YAAY;QACZ,aAAa;QACb,QAAQ,EAAE,oBAAoB,CAAC,MAAM,CAAC;QACtC,cAAc,EAAE,IAAI,GAAG,EAAE;KAC5B,CAAC;AACN,CAAC;AAED,SAAS,eAAe,CAAC,IAAa,EAAE,MAAc;IAClD,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IACrC,IAAI,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YACnB,MAAM,EAAE,qBAAqB,CAAS,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC;YAChE,OAAO,EAAE,qBAAqB,CAAS,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC;SACrE,CAAC,CAAC;QACH,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,MAAc,EAAE,IAAc;IAC9D,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,IAAI,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAwB,CAAC;IACjE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACrB,IAAI,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;IAC3B,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QACzF,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;SAAM,CAAC;QACJ,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,uBAAuB,CAC/C,IAAI,CAAC,QAAQ,EACb,GAAG,CAAC,IAAI,EACR,GAAG,CAAC,GAAG,EACP,GAAG,CAAC,KAAK,CACZ,CAAC;QACF,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC;AACpD,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Ported from invinite/src/components/trading-chart/indicators/dmi.ts\n// plus lib/wilder-directional.ts\n// (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02, © Invinite).\n// Re-licensed MIT for chartlang. The math is the reference, the code\n// style is not.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape — NOT invinite's\n// IndicatorPlugin shape. DMI reads `bar.high` / `bar.low` / `bar.close`\n// directly (mirrors Pine's `ta.dmi(length)` which has no source param)\n// and runs the Wilder +DM / -DM / TR smoothing incrementally via\n// `wilderStep` (the same per-step recurrence the reference\n// `lib/wilderDirectional.ts` uses internally).\n\nimport type { DmiOpts, DmiResult } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport {\n advanceDirectionalClose,\n type DirectionalState,\n initDirectionalState,\n tickDirectional,\n} from \"./lib/directionalState.js\";\n\ntype DmiSlot = {\n readonly result: DmiResult;\n readonly plusDiBuffer: Float64RingBuffer;\n readonly minusDiBuffer: Float64RingBuffer;\n readonly dirState: DirectionalState;\n /**\n * Per-offset frozen `DmiResult` cache. `offset === 0` returns\n * `result` by identity. Non-zero offsets get a frozen result\n * whose two Series are `makeShiftedSeriesView` proxies over the\n * same two underlying ring buffers.\n */\n readonly shiftedResults: Map<number, DmiResult>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.dmi called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(length: number, capacity: number): DmiSlot {\n const plusDiBuffer = new Float64RingBuffer(capacity);\n const minusDiBuffer = new Float64RingBuffer(capacity);\n return {\n result: Object.freeze({\n plusDi: makeSeriesView<number>(plusDiBuffer),\n minusDi: makeSeriesView<number>(minusDiBuffer),\n }),\n plusDiBuffer,\n minusDiBuffer,\n dirState: initDirectionalState(length),\n shiftedResults: new Map(),\n };\n}\n\nfunction resultForOffset(slot: DmiSlot, offset: number): DmiResult {\n if (offset === 0) return slot.result;\n let cached = slot.shiftedResults.get(offset);\n if (cached === undefined) {\n cached = Object.freeze({\n plusDi: makeShiftedSeriesView<number>(slot.plusDiBuffer, offset),\n minusDi: makeShiftedSeriesView<number>(slot.minusDiBuffer, offset),\n });\n slot.shiftedResults.set(offset, cached);\n }\n return cached;\n}\n\n/**\n * Wilder's Directional Movement Index — `+DI` / `−DI` pair derived\n * from the Wilder-smoothed `+DM` / `−DM` over the smoothed True\n * Range. Reads `bar.high` / `bar.low` / `bar.close` directly\n * (mirrors Pine's `ta.dmi(length)` — no source param). Both series\n * ∈ [0, 100] when defined; NaN until `length` closed bars have\n * folded into the seed window. The first defined value lands at\n * bar index `length` (counted zero-based — matches the\n * full-recompute reference in `lib/wilderDirectional.ts`).\n *\n * @formula TR[t] = max(high − low, |high − prevClose|, |low − prevClose|) ;\n * upMove = high[t] − high[t−1] ; downMove = low[t−1] − low[t] ;\n * +DM = upMove > downMove && upMove > 0 ? upMove : 0 ;\n * −DM = downMove > upMove && downMove > 0 ? downMove : 0 ;\n * seed at bar `length` = simple sum over the seed window ;\n * smoothed via wilderStep(α = 1/length) thereafter ;\n * +DI = 100 · smoothed+DM / smoothedTR ;\n * −DI = 100 · smoothed−DM / smoothedTR ;\n * DI falls back to 0 when smoothedTR is 0 (matches invinite).\n * @warmup length\n * @since 0.2\n * @stable\n *\n * `opts.offset` shifts both series in lockstep —\n * `series.current` on each output returns the value `offset` bars\n * ago.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const d = ta.dmi(\"slot\", 14);\n * // plot(d.plusDi);\n * // plot(d.minusDi);\n */\nexport function dmi(slotId: string, length: number, opts?: DmiOpts): DmiResult {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as DmiSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(length, ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const bar = ctx.stream.bar;\n if (ctx.isTick) {\n const { plusDi, minusDi } = tickDirectional(slot.dirState, bar.high, bar.low, bar.close);\n slot.plusDiBuffer.replaceHead(plusDi);\n slot.minusDiBuffer.replaceHead(minusDi);\n } else {\n const { plusDi, minusDi } = advanceDirectionalClose(\n slot.dirState,\n bar.high,\n bar.low,\n bar.close,\n );\n slot.plusDiBuffer.append(plusDi);\n slot.minusDiBuffer.append(minusDi);\n }\n return resultForOffset(slot, opts?.offset ?? 0);\n}\n"]}
1
+ {"version":3,"file":"dmi.js","sourceRoot":"","sources":["../../src/ta/dmi.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Ported from invinite/src/components/trading-chart/indicators/dmi.ts\n// plus lib/wilder-directional.ts\n// (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02, © Invinite).\n// Re-licensed MIT for chartlang. The math is the reference, the code\n// style is not.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape — NOT invinite's\n// IndicatorPlugin shape. DMI reads `bar.high` / `bar.low` / `bar.close`\n// directly (mirrors Pine's `ta.dmi(length)` which has no source param)\n// and runs the Wilder +DM / -DM / TR smoothing incrementally via\n// `wilderStep` (the same per-step recurrence the reference\n// `lib/wilderDirectional.ts` uses internally).\n\nimport type { DmiOpts, DmiResult } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport {\n advanceDirectionalClose,\n type DirectionalState,\n initDirectionalState,\n tickDirectional,\n} from \"./lib/directionalState.js\";\n\ntype DmiSlot = {\n readonly result: DmiResult;\n readonly plusDiBuffer: Float64RingBuffer;\n readonly minusDiBuffer: Float64RingBuffer;\n readonly dirState: DirectionalState;\n /**\n * Per-offset frozen `DmiResult` cache. `offset === 0` returns\n * `result` by identity. Non-zero offsets get a frozen result\n * whose two Series are `makeShiftedSeriesView` proxies over the\n * same two underlying ring buffers.\n */\n readonly shiftedResults: Map<number, DmiResult>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.dmi called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(length: number, capacity: number): DmiSlot {\n const plusDiBuffer = new Float64RingBuffer(capacity);\n const minusDiBuffer = new Float64RingBuffer(capacity);\n return {\n result: Object.freeze({\n plusDi: makeSeriesView<number>(plusDiBuffer),\n minusDi: makeSeriesView<number>(minusDiBuffer),\n }),\n plusDiBuffer,\n minusDiBuffer,\n dirState: initDirectionalState(length),\n shiftedResults: new Map(),\n };\n}\n\nfunction resultForOffset(slot: DmiSlot, offset: number): DmiResult {\n if (offset === 0) return slot.result;\n let cached = slot.shiftedResults.get(offset);\n if (cached === undefined) {\n cached = Object.freeze({\n plusDi: makeShiftedSeriesView<number>(slot.plusDiBuffer, offset),\n minusDi: makeShiftedSeriesView<number>(slot.minusDiBuffer, offset),\n });\n slot.shiftedResults.set(offset, cached);\n }\n return cached;\n}\n\n/**\n * Wilder's Directional Movement Index — `+DI` / `−DI` pair derived\n * from the Wilder-smoothed `+DM` / `−DM` over the smoothed True\n * Range. Reads `bar.high` / `bar.low` / `bar.close` directly\n * (mirrors Pine's `ta.dmi(length)` — no source param). Both series\n * ∈ [0, 100] when defined; NaN until `length` closed bars have\n * folded into the seed window. The first defined value lands at\n * bar index `length` (counted zero-based — matches the\n * full-recompute reference in `lib/wilderDirectional.ts`).\n *\n * @formula TR[t] = max(high − low, |high − prevClose|, |low − prevClose|) ;\n * upMove = high[t] − high[t−1] ; downMove = low[t−1] − low[t] ;\n * +DM = upMove > downMove && upMove > 0 ? upMove : 0 ;\n * −DM = downMove > upMove && downMove > 0 ? downMove : 0 ;\n * seed at bar `length` = simple sum over the seed window ;\n * smoothed via wilderStep(α = 1/length) thereafter ;\n * +DI = 100 · smoothed+DM / smoothedTR ;\n * −DI = 100 · smoothed−DM / smoothedTR ;\n * DI falls back to 0 when smoothedTR is 0 (matches invinite).\n * @warmup length\n * @since 0.2\n * @stable\n *\n * `opts.offset` is a presentation display shift carried to the plot\n * emission as `xShift` for both series in lockstep (`+n` right / future,\n * `−n` left / past); the series values are unshifted, so\n * `series.current` on each output returns the value at the current bar.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const d = ta.dmi(\"slot\", 14);\n * // plot(d.plusDi);\n * // plot(d.minusDi);\n */\nexport function dmi(slotId: string, length: number, opts?: DmiOpts): DmiResult {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as DmiSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(length, ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const bar = ctx.stream.bar;\n if (ctx.isTick) {\n const { plusDi, minusDi } = tickDirectional(slot.dirState, bar.high, bar.low, bar.close);\n slot.plusDiBuffer.replaceHead(plusDi);\n slot.minusDiBuffer.replaceHead(minusDi);\n } else {\n const { plusDi, minusDi } = advanceDirectionalClose(\n slot.dirState,\n bar.high,\n bar.low,\n bar.close,\n );\n slot.plusDiBuffer.append(plusDi);\n slot.minusDiBuffer.append(minusDi);\n }\n return resultForOffset(slot, opts?.offset ?? 0);\n}\n"]}
package/dist/ta/ema.d.ts CHANGED
@@ -14,8 +14,9 @@ import { type ScalarOrSeries } from "./lib/sourceValue.js";
14
14
  * @since 0.1
15
15
  * @stable
16
16
  *
17
- * `opts.offset` shifts the returned series so `series.current` reads
18
- * the value `offset` bars ago.
17
+ * `opts.offset` is a presentation display shift carried to the plot
18
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
19
+ * series value is unshifted.
19
20
  *
20
21
  * @example
21
22
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"ema.d.ts","sourceRoot":"","sources":["../../src/ta/ema.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAKpE,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AAiF5E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,GAAG,CACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,CAAC,CAWhB"}
1
+ {"version":3,"file":"ema.d.ts","sourceRoot":"","sources":["../../src/ta/ema.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAKpE,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AAiF5E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,GAAG,CACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,CAAC,CAWhB"}
package/dist/ta/ema.js CHANGED
@@ -90,8 +90,9 @@ function compute(slot, src, isTick) {
90
90
  * @since 0.1
91
91
  * @stable
92
92
  *
93
- * `opts.offset` shifts the returned series so `series.current` reads
94
- * the value `offset` bars ago.
93
+ * `opts.offset` is a presentation display shift carried to the plot
94
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
95
+ * series value is unshifted.
95
96
  *
96
97
  * @example
97
98
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"ema.js","sourceRoot":"","sources":["../../src/ta/ema.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,+BAA+B;AAC/B,mEAAmE;AACnE,qEAAqE;AACrE,gBAAgB;AAChB,qEAAqE;AACrE,4DAA4D;AAC5D,yBAAyB;AAIzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAuB,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAuB,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAgB5E,SAAS,MAAM;IACX,MAAM,GAAG,GAAG,sBAAsB,CAAC,OAAO,CAAC;IAC3C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc,EAAE,QAAgB;IAC9C,MAAM,SAAS,GAAG,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAClD,OAAO;QACH,IAAI,EAAE,QAAQ;QACd,SAAS;QACT,MAAM,EAAE,cAAc,CAAS,SAAS,CAAC;QACzC,KAAK,EAAE,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QACvB,MAAM;QACN,OAAO,EAAE,CAAC;QACV,SAAS,EAAE,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC,GAAG;QACnB,aAAa,EAAE,MAAM,CAAC,GAAG;QACzB,YAAY,EAAE,IAAI,GAAG,EAAE;KAC1B,CAAC;AACN,CAAC;AAED,SAAS,aAAa,CAAC,IAAa,EAAE,MAAc;IAChD,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IACrC,IAAI,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACrB,IAAI,GAAG,qBAAqB,CAAS,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC7D,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,OAAO,CAAC,IAAa,EAAE,GAAW,EAAE,MAAe;IACxD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC;IACtD,CAAC;IACD,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC/B,IAAI,MAAM,EAAE,CAAC;YACT,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;YACnC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;YACrC,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM;gBAAE,OAAO,MAAM,CAAC,GAAG,CAAC;YAC/C,OAAO,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QACjC,CAAC;QACD,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC;QACpB,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;QACpB,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC/B,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC;YAChC,OAAO,MAAM,CAAC,GAAG,CAAC;QACtB,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QAC7C,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;IAChC,MAAM,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,GAAG,CACf,MAAc,EACd,MAAsB,EACtB,MAAc,EACd,IAAc;IAEd,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,IAAI,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAwB,CAAC;IACjE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACrB,IAAI,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACjE,IAAI,GAAG,CAAC,MAAM;QAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;;QAC7C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC;AAClD,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Ported from invinite/src/components/trading-chart/indicators/ema.ts\n// plus lib/ema-of-float64.ts\n// (commit d2d1043c1b039f66d2f3674526d303d31cf2f1e0, © Invinite).\n// Re-licensed MIT for chartlang. The math is the reference, the code\n// style is not.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape — NOT invinite's\n// IndicatorPlugin shape.\n\nimport type { EmaOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport { type ScalarOrSeries, readSourceValue } from \"./lib/sourceValue.js\";\n\ntype EmaSlot = {\n readonly kind: \"ta.ema\";\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly alpha: number;\n readonly length: number;\n seedSum: number;\n seedCount: number;\n prevEma: number;\n prevClosedEma: number;\n /** Per-offset Series-view cache; see `sma.ts` for the convention. */\n readonly shiftedViews: Map<number, Series<number>>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.ema called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(length: number, capacity: number): EmaSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n kind: \"ta.ema\",\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n alpha: 2 / (length + 1),\n length,\n seedSum: 0,\n seedCount: 0,\n prevEma: Number.NaN,\n prevClosedEma: Number.NaN,\n shiftedViews: new Map(),\n };\n}\n\nfunction viewForOffset(slot: EmaSlot, offset: number): Series<number> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<number>(slot.outBuffer, offset);\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction compute(slot: EmaSlot, src: number, isTick: boolean): number {\n if (!Number.isFinite(src)) {\n return isTick ? slot.prevEma : slot.prevClosedEma;\n }\n if (slot.seedCount < slot.length) {\n if (isTick) {\n const nextSum = slot.seedSum + src;\n const nextCount = slot.seedCount + 1;\n if (nextCount < slot.length) return Number.NaN;\n return nextSum / slot.length;\n }\n slot.seedSum += src;\n slot.seedCount += 1;\n if (slot.seedCount < slot.length) {\n slot.prevClosedEma = Number.NaN;\n return Number.NaN;\n }\n const seedValue = slot.seedSum / slot.length;\n slot.prevClosedEma = seedValue;\n slot.prevEma = seedValue;\n return seedValue;\n }\n const prev = slot.prevClosedEma;\n const next = src * slot.alpha + prev * (1 - slot.alpha);\n if (!isTick) {\n slot.prevClosedEma = next;\n slot.prevEma = next;\n }\n return next;\n}\n\n/**\n * Exponential moving average. Recurrence `EMA[t] = α·x[t] + (1 − α)·EMA[t − 1]`\n * with `α = 2 / (length + 1)` after a seed of `simple mean of the first\n * `length` finite source values`. Tick-mode (`onBarTick`) recomputes the\n * head from the previous closed EMA so partial-bar values don't bleed\n * into the next close's recurrence.\n *\n * @formula α = 2 / (length + 1) ;\n * seed at bar length−1 = mean(source[0..length−1]) ;\n * EMA[t] = source[t]·α + EMA[t−1]·(1−α)\n * @warmup length − 1\n * @since 0.1\n * @stable\n *\n * `opts.offset` shifts the returned series so `series.current` reads\n * the value `offset` bars ago.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const e = ta.ema(\"slot-id\", bar.close, 20);\n * // const head = e.current; // NaN until bar length-1\n * // const projected = ta.ema(\"slot2\", bar.close, 20, { offset: 5 });\n */\nexport function ema(\n slotId: string,\n source: ScalarOrSeries,\n length: number,\n opts?: EmaOpts,\n): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as EmaSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(length, ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const value = compute(slot, readSourceValue(source), ctx.isTick);\n if (ctx.isTick) slot.outBuffer.replaceHead(value);\n else slot.outBuffer.append(value);\n return viewForOffset(slot, opts?.offset ?? 0);\n}\n"]}
1
+ {"version":3,"file":"ema.js","sourceRoot":"","sources":["../../src/ta/ema.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Ported from invinite/src/components/trading-chart/indicators/ema.ts\n// plus lib/ema-of-float64.ts\n// (commit d2d1043c1b039f66d2f3674526d303d31cf2f1e0, © Invinite).\n// Re-licensed MIT for chartlang. The math is the reference, the code\n// style is not.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape — NOT invinite's\n// IndicatorPlugin shape.\n\nimport type { EmaOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\nimport { type ScalarOrSeries, readSourceValue } from \"./lib/sourceValue.js\";\n\ntype EmaSlot = {\n readonly kind: \"ta.ema\";\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly alpha: number;\n readonly length: number;\n seedSum: number;\n seedCount: number;\n prevEma: number;\n prevClosedEma: number;\n /** Per-offset Series-view cache; see `sma.ts` for the convention. */\n readonly shiftedViews: Map<number, Series<number>>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.ema called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(length: number, capacity: number): EmaSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n kind: \"ta.ema\",\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n alpha: 2 / (length + 1),\n length,\n seedSum: 0,\n seedCount: 0,\n prevEma: Number.NaN,\n prevClosedEma: Number.NaN,\n shiftedViews: new Map(),\n };\n}\n\nfunction viewForOffset(slot: EmaSlot, offset: number): Series<number> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<number>(slot.outBuffer, offset);\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction compute(slot: EmaSlot, src: number, isTick: boolean): number {\n if (!Number.isFinite(src)) {\n return isTick ? slot.prevEma : slot.prevClosedEma;\n }\n if (slot.seedCount < slot.length) {\n if (isTick) {\n const nextSum = slot.seedSum + src;\n const nextCount = slot.seedCount + 1;\n if (nextCount < slot.length) return Number.NaN;\n return nextSum / slot.length;\n }\n slot.seedSum += src;\n slot.seedCount += 1;\n if (slot.seedCount < slot.length) {\n slot.prevClosedEma = Number.NaN;\n return Number.NaN;\n }\n const seedValue = slot.seedSum / slot.length;\n slot.prevClosedEma = seedValue;\n slot.prevEma = seedValue;\n return seedValue;\n }\n const prev = slot.prevClosedEma;\n const next = src * slot.alpha + prev * (1 - slot.alpha);\n if (!isTick) {\n slot.prevClosedEma = next;\n slot.prevEma = next;\n }\n return next;\n}\n\n/**\n * Exponential moving average. Recurrence `EMA[t] = α·x[t] + (1 − α)·EMA[t − 1]`\n * with `α = 2 / (length + 1)` after a seed of `simple mean of the first\n * `length` finite source values`. Tick-mode (`onBarTick`) recomputes the\n * head from the previous closed EMA so partial-bar values don't bleed\n * into the next close's recurrence.\n *\n * @formula α = 2 / (length + 1) ;\n * seed at bar length−1 = mean(source[0..length−1]) ;\n * EMA[t] = source[t]·α + EMA[t−1]·(1−α)\n * @warmup length − 1\n * @since 0.1\n * @stable\n *\n * `opts.offset` is a presentation display shift carried to the plot\n * emission as `xShift` (`+n` right / future, `−n` left / past); the\n * series value is unshifted.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const e = ta.ema(\"slot-id\", bar.close, 20);\n * // const head = e.current; // NaN until bar length-1\n * // const projected = ta.ema(\"slot2\", bar.close, 20, { offset: 5 });\n */\nexport function ema(\n slotId: string,\n source: ScalarOrSeries,\n length: number,\n opts?: EmaOpts,\n): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as EmaSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(length, ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const value = compute(slot, readSourceValue(source), ctx.isTick);\n if (ctx.isTick) slot.outBuffer.replaceHead(value);\n else slot.outBuffer.append(value);\n return viewForOffset(slot, opts?.offset ?? 0);\n}\n"]}
package/dist/ta/eom.d.ts CHANGED
@@ -24,7 +24,9 @@ import type { EomOpts, Series } from "@invinite-org/chartlang-core";
24
24
  * @since 0.2
25
25
  * @stable
26
26
  *
27
- * `opts.offset` shifts the returned series.
27
+ * `opts.offset` is a presentation display shift carried to the plot
28
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
29
+ * series value is unshifted.
28
30
  *
29
31
  * @example
30
32
  * // import { ta, plot } from "@invinite-org/chartlang-core";
@@ -1 +1 @@
1
- {"version":3,"file":"eom.d.ts","sourceRoot":"","sources":["../../src/ta/eom.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAyGpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAiElF"}
1
+ {"version":3,"file":"eom.d.ts","sourceRoot":"","sources":["../../src/ta/eom.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAyGpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAiElF"}
package/dist/ta/eom.js CHANGED
@@ -111,7 +111,9 @@ function emit(slot, ready) {
111
111
  * @since 0.2
112
112
  * @stable
113
113
  *
114
- * `opts.offset` shifts the returned series.
114
+ * `opts.offset` is a presentation display shift carried to the plot
115
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
116
+ * series value is unshifted.
115
117
  *
116
118
  * @example
117
119
  * // import { ta, plot } from "@invinite-org/chartlang-core";
@@ -1 +1 @@
1
- {"version":3,"file":"eom.js","sourceRoot":"","sources":["../../src/ta/eom.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,mEAAmE;AACnE,qEAAqE;AACrE,gBAAgB;AAChB,qEAAqE;AACrE,4DAA4D;AAC5D,+DAA+D;AAC/D,mEAAmE;AACnE,mEAAmE;AACnE,mEAAmE;AACnE,iEAAiE;AACjE,qBAAqB;AAIrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAuB,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG,KAAK,CAAC;AA4BtB,SAAS,MAAM;IACX,MAAM,GAAG,GAAG,sBAAsB,CAAC,OAAO,CAAC;IAC3C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc,EAAE,QAAgB;IAC9C,MAAM,SAAS,GAAG,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAClD,OAAO;QACH,SAAS;QACT,MAAM,EAAE,cAAc,CAAS,SAAS,CAAC;QACzC,YAAY,EAAE,IAAI,GAAG,EAAE;QACvB,MAAM;QACN,YAAY,EAAE,IAAI,iBAAiB,CAAC,MAAM,CAAC;QAC3C,SAAS,EAAE,CAAC;QACZ,QAAQ,EAAE,CAAC;QACX,OAAO,EAAE,MAAM,CAAC,GAAG;QACnB,WAAW,EAAE,MAAM,CAAC,GAAG;KAC1B,CAAC;AACN,CAAC;AAED,SAAS,aAAa,CAAC,IAAa,EAAE,MAAc;IAChD,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IACrC,IAAI,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACrB,IAAI,GAAG,qBAAqB,CAAS,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC7D,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,OAAO,CAAC,MAAc;IAC3B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW,EAAE,MAAc,EAAE,OAAe;IACxE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/E,OAAO,MAAM,CAAC,GAAG,CAAC;IACtB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,GAAG,GAAG,CAAC;IACzB,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IACnC,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,GAAG,KAAK,CAAC;IACnD,IAAI,QAAQ,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IACtC,MAAM,YAAY,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;IAChD,OAAO,YAAY,GAAG,QAAQ,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,IAAI,CAAC,IAAa,EAAE,KAAc;IACvC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IACnD,OAAO,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;AACxC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,MAAc,EAAE,IAAc;IAC9D,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,IAAI,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAwB,CAAC;IACjE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACrB,IAAI,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC;IACjC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;IAE7C,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACb,4DAA4D;QAC5D,8DAA8D;QAC9D,8DAA8D;QAC9D,4DAA4D;QAC5D,2DAA2D;QAC3D,iEAAiE;QACjE,qCAAqC;QACrC,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACzC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvC,OAAO,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,UAAU,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1E,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACb,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvC,OAAO,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,sDAAsD;QACtD,+DAA+D;QAC/D,4DAA4D;QAC5D,8DAA8D;QAC9D,sCAAsC;QACtC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;QACrE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QACjD,OAAO,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;IAED,oEAAoE;IACpE,6DAA6D;IAC7D,qEAAqE;IACrE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC;IAEhC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,QAAQ,GACV,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;IAEpF,6CAA6C;IAC7C,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACrD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC;;YACjD,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,iDAAiD;IACjD,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC;;QAC3C,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;IACxB,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC;IAExB,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC;IACvD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACzC,OAAO,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACvC,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Ported from invinite/src/components/trading-chart/indicators/eom.ts\n// (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02, © Invinite).\n// Re-licensed MIT for chartlang. The math is the reference, the code\n// style is not.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape — NOT invinite's\n// IndicatorPlugin shape. The `length`-bar SMA over the raw EOM\n// series is inlined (rather than composed via a `ta.sma` sub-slot)\n// because each per-bar rawEom is a scalar derived from H/L/V + the\n// per-slot `prevMid` state — feeding it through SMA's `Series`-or-\n// scalar surface would be more friction than the trailing-window\n// running sum below.\n\nimport type { EomOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\n\n/**\n * Hard-coded divisor matching invinite's default\n * (`indicators/eom.ts:37` ships `divisor: 10000` as the\n * conventional choice). TV's published example uses 100 000 000 and\n * explicitly warns the divisor \"should be adjusted based on trading\n * volume\" — there is no canonical default. We pin invinite's default;\n * scripts that want a different scale can multiply the output.\n */\nconst DIVISOR = 10000;\n\ntype EomSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n readonly length: number;\n /**\n * Closed-bar raw-EOM values across the trailing `length` bars\n * (capacity `length`). `at(0)` is the head (most recent); older\n * slots index upward.\n */\n readonly rawEomWindow: Float64RingBuffer;\n /** Running sum of `rawEomWindow` (over only the finite entries). */\n sumRawEom: number;\n /** Count of NaN entries in `rawEomWindow` — forces NaN output when > 0. */\n nanCount: number;\n /** Midpoint of the most recent closed bar (NaN before bar 0 lands). */\n prevMid: number;\n /**\n * Midpoint of the bar BEFORE the most recent close — needed so a\n * tick can recompute the head bar's rawEom against the same prevMid\n * the close-side update used. Snapshotted at the START of every\n * close-side update.\n */\n prevPrevMid: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.eom called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(length: number, capacity: number): EomSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n length,\n rawEomWindow: new Float64RingBuffer(length),\n sumRawEom: 0,\n nanCount: 0,\n prevMid: Number.NaN,\n prevPrevMid: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: EomSlot, offset: number): Series<number> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<number>(slot.outBuffer, offset);\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction safeVol(volume: number): number {\n return Number.isFinite(volume) ? volume : 0;\n}\n\n/**\n * Per-bar raw EOM `(midpointMove) / boxRatio`. Returns NaN when the\n * range is zero (no movement, boxRatio undefined), boxRatio is zero\n * (volume-free bar), prevMid is NaN (first bar), or any input is NaN.\n */\nfunction rawEomAt(high: number, low: number, volume: number, prevMid: number): number {\n if (!Number.isFinite(high) || !Number.isFinite(low) || !Number.isFinite(prevMid)) {\n return Number.NaN;\n }\n const range = high - low;\n if (range === 0) return Number.NaN;\n const boxRatio = safeVol(volume) / DIVISOR / range;\n if (boxRatio === 0) return Number.NaN;\n const midpointMove = (high + low) / 2 - prevMid;\n return midpointMove / boxRatio;\n}\n\n/**\n * Emit the SMA over `rawEomWindow` once it's full AND contains no\n * NaN entries. Pre-warmup OR any NaN in the window → NaN.\n */\nfunction emit(slot: EomSlot, ready: boolean): number {\n if (!ready || slot.nanCount > 0) return Number.NaN;\n return slot.sumRawEom / slot.length;\n}\n\n/**\n * Ease of Movement — `length`-bar SMA of per-bar `(midpointMove) /\n * (boxRatio)` where `midpointMove = mid[t] − mid[t − 1]` and\n * `boxRatio = (volume / divisor) / (high − low)`. Zero-range bars,\n * zero-volume bars, and any NaN input produce a NaN slot in the\n * window; the window SMA propagates NaN — any NaN in the trailing\n * `length` slots forces the output to NaN (forces a clean restart\n * after a flat / NaN bar). The hard-coded divisor matches invinite's\n * default of `10000`.\n *\n * **Tick mode.** Computes the tick's rawEom against the snapshot\n * prevMid, substitutes it for the head slot in the trailing window\n * (`hypSum = sum − headRawEom + tickRawEom`), and emits\n * `hypSum / length` if the window is full AND `hypNanCount === 0`,\n * else NaN. Does NOT mutate the closed window.\n *\n * @formula rawEom[t] = ((high[t] + low[t]) / 2 − (high[t − 1] + low[t − 1]) / 2)\n * / ((volume[t] / 10000) / (high[t] − low[t])) ;\n * eom[t] = SMA(rawEom, length)[t] ;\n * any NaN in the window → NaN\n * @warmup length (first defined output at bar `length`; bar 1 has the\n * first finite rawEom; the window needs `length` such values)\n * @since 0.2\n * @stable\n *\n * `opts.offset` shifts the returned series.\n *\n * @example\n * // import { ta, plot } from \"@invinite-org/chartlang-core\";\n * // const e = ta.eom(14);\n * // plot(e);\n */\nexport function eom(slotId: string, length: number, opts?: EomOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as EomSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(length, ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const offset = opts?.offset ?? 0;\n const { high, low, volume } = ctx.stream.bar;\n\n if (ctx.isTick) {\n // Tick replay against the closed window: recompute the head\n // bar's rawEom against the SAME prevMid the close used (which\n // is the prior bar's midpoint = `prevPrevMid`), substitute it\n // for the head slot in the trailing window without mutating\n // the window. `sumRawEom` + `nanCount` already reflect the\n // post-close state; subtract the current head and add the tick's\n // rawEom. Pre-warmup ticks emit NaN.\n if (slot.rawEomWindow.length < slot.length) {\n slot.outBuffer.replaceHead(Number.NaN);\n return viewForOffset(slot, offset);\n }\n const tickRaw = rawEomAt(high, low, volume, slot.prevPrevMid);\n const headRaw = slot.rawEomWindow.at(0);\n const headWasNaN = !Number.isFinite(headRaw);\n const tickIsNaN = !Number.isFinite(tickRaw);\n const hypNan = slot.nanCount - (headWasNaN ? 1 : 0) + (tickIsNaN ? 1 : 0);\n if (hypNan > 0) {\n slot.outBuffer.replaceHead(Number.NaN);\n return viewForOffset(slot, offset);\n }\n // When we reach here `hypNan === 0`, which guarantees\n // `tickIsNaN === false` (the only way nanCount could decrement\n // is via the head being NaN and the tick being finite). The\n // `headWasNaN ? 0 : headRaw` guards against subtracting a NaN\n // headRaw when the head slot was NaN.\n const hypSum = slot.sumRawEom - (headWasNaN ? 0 : headRaw) + tickRaw;\n slot.outBuffer.replaceHead(hypSum / slot.length);\n return viewForOffset(slot, offset);\n }\n\n // Close-side: snapshot the bar-before-last's midpoint (so a tick on\n // the new head bar can recompute its rawEom against the same\n // prevMid the close used), then fold the new rawEom into the window.\n slot.prevPrevMid = slot.prevMid;\n\n const raw = rawEomAt(high, low, volume, slot.prevMid);\n const midpoint =\n Number.isFinite(high) && Number.isFinite(low) ? (high + low) / 2 : slot.prevMid;\n\n // Evict the oldest slot if the ring is full.\n if (slot.rawEomWindow.length === slot.length) {\n const oldest = slot.rawEomWindow.at(slot.length - 1);\n if (Number.isFinite(oldest)) slot.sumRawEom -= oldest;\n else slot.nanCount -= 1;\n }\n // Append the new rawEom + update sum / nanCount.\n slot.rawEomWindow.append(raw);\n if (Number.isFinite(raw)) slot.sumRawEom += raw;\n else slot.nanCount += 1;\n slot.prevMid = midpoint;\n\n const ready = slot.rawEomWindow.length === slot.length;\n slot.outBuffer.append(emit(slot, ready));\n return viewForOffset(slot, offset);\n}\n"]}
1
+ {"version":3,"file":"eom.js","sourceRoot":"","sources":["../../src/ta/eom.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Ported from invinite/src/components/trading-chart/indicators/eom.ts\n// (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02, © Invinite).\n// Re-licensed MIT for chartlang. The math is the reference, the code\n// style is not.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape — NOT invinite's\n// IndicatorPlugin shape. The `length`-bar SMA over the raw EOM\n// series is inlined (rather than composed via a `ta.sma` sub-slot)\n// because each per-bar rawEom is a scalar derived from H/L/V + the\n// per-slot `prevMid` state — feeding it through SMA's `Series`-or-\n// scalar surface would be more friction than the trailing-window\n// running sum below.\n\nimport type { EomOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView, makeShiftedSeriesView } from \"../seriesView.js\";\n\n/**\n * Hard-coded divisor matching invinite's default\n * (`indicators/eom.ts:37` ships `divisor: 10000` as the\n * conventional choice). TV's published example uses 100 000 000 and\n * explicitly warns the divisor \"should be adjusted based on trading\n * volume\" — there is no canonical default. We pin invinite's default;\n * scripts that want a different scale can multiply the output.\n */\nconst DIVISOR = 10000;\n\ntype EomSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n readonly length: number;\n /**\n * Closed-bar raw-EOM values across the trailing `length` bars\n * (capacity `length`). `at(0)` is the head (most recent); older\n * slots index upward.\n */\n readonly rawEomWindow: Float64RingBuffer;\n /** Running sum of `rawEomWindow` (over only the finite entries). */\n sumRawEom: number;\n /** Count of NaN entries in `rawEomWindow` — forces NaN output when > 0. */\n nanCount: number;\n /** Midpoint of the most recent closed bar (NaN before bar 0 lands). */\n prevMid: number;\n /**\n * Midpoint of the bar BEFORE the most recent close — needed so a\n * tick can recompute the head bar's rawEom against the same prevMid\n * the close-side update used. Snapshotted at the START of every\n * close-side update.\n */\n prevPrevMid: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.eom called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(length: number, capacity: number): EomSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n length,\n rawEomWindow: new Float64RingBuffer(length),\n sumRawEom: 0,\n nanCount: 0,\n prevMid: Number.NaN,\n prevPrevMid: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: EomSlot, offset: number): Series<number> {\n if (offset === 0) return slot.series;\n let view = slot.shiftedViews.get(offset);\n if (view === undefined) {\n view = makeShiftedSeriesView<number>(slot.outBuffer, offset);\n slot.shiftedViews.set(offset, view);\n }\n return view;\n}\n\nfunction safeVol(volume: number): number {\n return Number.isFinite(volume) ? volume : 0;\n}\n\n/**\n * Per-bar raw EOM `(midpointMove) / boxRatio`. Returns NaN when the\n * range is zero (no movement, boxRatio undefined), boxRatio is zero\n * (volume-free bar), prevMid is NaN (first bar), or any input is NaN.\n */\nfunction rawEomAt(high: number, low: number, volume: number, prevMid: number): number {\n if (!Number.isFinite(high) || !Number.isFinite(low) || !Number.isFinite(prevMid)) {\n return Number.NaN;\n }\n const range = high - low;\n if (range === 0) return Number.NaN;\n const boxRatio = safeVol(volume) / DIVISOR / range;\n if (boxRatio === 0) return Number.NaN;\n const midpointMove = (high + low) / 2 - prevMid;\n return midpointMove / boxRatio;\n}\n\n/**\n * Emit the SMA over `rawEomWindow` once it's full AND contains no\n * NaN entries. Pre-warmup OR any NaN in the window → NaN.\n */\nfunction emit(slot: EomSlot, ready: boolean): number {\n if (!ready || slot.nanCount > 0) return Number.NaN;\n return slot.sumRawEom / slot.length;\n}\n\n/**\n * Ease of Movement — `length`-bar SMA of per-bar `(midpointMove) /\n * (boxRatio)` where `midpointMove = mid[t] − mid[t − 1]` and\n * `boxRatio = (volume / divisor) / (high − low)`. Zero-range bars,\n * zero-volume bars, and any NaN input produce a NaN slot in the\n * window; the window SMA propagates NaN — any NaN in the trailing\n * `length` slots forces the output to NaN (forces a clean restart\n * after a flat / NaN bar). The hard-coded divisor matches invinite's\n * default of `10000`.\n *\n * **Tick mode.** Computes the tick's rawEom against the snapshot\n * prevMid, substitutes it for the head slot in the trailing window\n * (`hypSum = sum − headRawEom + tickRawEom`), and emits\n * `hypSum / length` if the window is full AND `hypNanCount === 0`,\n * else NaN. Does NOT mutate the closed window.\n *\n * @formula rawEom[t] = ((high[t] + low[t]) / 2 − (high[t − 1] + low[t − 1]) / 2)\n * / ((volume[t] / 10000) / (high[t] − low[t])) ;\n * eom[t] = SMA(rawEom, length)[t] ;\n * any NaN in the window → NaN\n * @warmup length (first defined output at bar `length`; bar 1 has the\n * first finite rawEom; the window needs `length` such values)\n * @since 0.2\n * @stable\n *\n * `opts.offset` is a presentation display shift carried to the plot\n * emission as `xShift` (`+n` right / future, `−n` left / past); the\n * series value is unshifted.\n *\n * @example\n * // import { ta, plot } from \"@invinite-org/chartlang-core\";\n * // const e = ta.eom(14);\n * // plot(e);\n */\nexport function eom(slotId: string, length: number, opts?: EomOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as EomSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(length, ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const offset = opts?.offset ?? 0;\n const { high, low, volume } = ctx.stream.bar;\n\n if (ctx.isTick) {\n // Tick replay against the closed window: recompute the head\n // bar's rawEom against the SAME prevMid the close used (which\n // is the prior bar's midpoint = `prevPrevMid`), substitute it\n // for the head slot in the trailing window without mutating\n // the window. `sumRawEom` + `nanCount` already reflect the\n // post-close state; subtract the current head and add the tick's\n // rawEom. Pre-warmup ticks emit NaN.\n if (slot.rawEomWindow.length < slot.length) {\n slot.outBuffer.replaceHead(Number.NaN);\n return viewForOffset(slot, offset);\n }\n const tickRaw = rawEomAt(high, low, volume, slot.prevPrevMid);\n const headRaw = slot.rawEomWindow.at(0);\n const headWasNaN = !Number.isFinite(headRaw);\n const tickIsNaN = !Number.isFinite(tickRaw);\n const hypNan = slot.nanCount - (headWasNaN ? 1 : 0) + (tickIsNaN ? 1 : 0);\n if (hypNan > 0) {\n slot.outBuffer.replaceHead(Number.NaN);\n return viewForOffset(slot, offset);\n }\n // When we reach here `hypNan === 0`, which guarantees\n // `tickIsNaN === false` (the only way nanCount could decrement\n // is via the head being NaN and the tick being finite). The\n // `headWasNaN ? 0 : headRaw` guards against subtracting a NaN\n // headRaw when the head slot was NaN.\n const hypSum = slot.sumRawEom - (headWasNaN ? 0 : headRaw) + tickRaw;\n slot.outBuffer.replaceHead(hypSum / slot.length);\n return viewForOffset(slot, offset);\n }\n\n // Close-side: snapshot the bar-before-last's midpoint (so a tick on\n // the new head bar can recompute its rawEom against the same\n // prevMid the close used), then fold the new rawEom into the window.\n slot.prevPrevMid = slot.prevMid;\n\n const raw = rawEomAt(high, low, volume, slot.prevMid);\n const midpoint =\n Number.isFinite(high) && Number.isFinite(low) ? (high + low) / 2 : slot.prevMid;\n\n // Evict the oldest slot if the ring is full.\n if (slot.rawEomWindow.length === slot.length) {\n const oldest = slot.rawEomWindow.at(slot.length - 1);\n if (Number.isFinite(oldest)) slot.sumRawEom -= oldest;\n else slot.nanCount -= 1;\n }\n // Append the new rawEom + update sum / nanCount.\n slot.rawEomWindow.append(raw);\n if (Number.isFinite(raw)) slot.sumRawEom += raw;\n else slot.nanCount += 1;\n slot.prevMid = midpoint;\n\n const ready = slot.rawEomWindow.length === slot.length;\n slot.outBuffer.append(emit(slot, ready));\n return viewForOffset(slot, offset);\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import type { HighestbarsOpts, Series } from "@invinite-org/chartlang-core";
2
+ import { type ScalarOrSeries } from "./lib/sourceValue.js";
3
+ /**
4
+ * Bar offset (≤ 0) to the highest `source` value over the trailing
5
+ * `length` bars (the window INCLUDES the current bar). `0` means the
6
+ * current bar is the highest; `-k` means the highest occurred `k` bars
7
+ * ago. Ties resolve to the MOST RECENT bar (smallest |offset|). NaN
8
+ * inputs are skipped as candidates; an all-NaN window emits NaN. The
9
+ * output is NaN until `length` closed bars have folded in. Tick-mode
10
+ * replays the in-progress head as the offset-0 candidate without
11
+ * advancing the buffer.
12
+ *
13
+ * @formula out[t] = argmax_{k ∈ [0, length)} source[t − k] expressed as −k
14
+ * (NaN slots skipped; ties → smallest k)
15
+ * @warmup length − 1
16
+ * @since 0.2
17
+ * @stable
18
+ *
19
+ * @example
20
+ * // import { ta } from "@invinite-org/chartlang-core";
21
+ * // const hbar = ta.highestbars(bar.high, 20);
22
+ * // const left = bar.point(hbar.current, bar.high.current);
23
+ */
24
+ export declare function highestbars(slotId: string, source: ScalarOrSeries, length: number, _opts?: HighestbarsOpts): Series<number>;
25
+ //# sourceMappingURL=highestbars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highestbars.d.ts","sourceRoot":"","sources":["../../src/ta/highestbars.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAK5E,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AA+E5E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CACvB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe,GACxB,MAAM,CAAC,MAAM,CAAC,CAchB"}
@@ -0,0 +1,106 @@
1
+ // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
+ // See the LICENSE file in the repo root for full license text.
3
+ //
4
+ // No invinite source — semantics per Pine `ta.highestbars`.
5
+ // Structural choices (callsite-id slot, Series<T> proxy, replaceHead
6
+ // mode) follow chartlang's primitive shape.
7
+ import { Float64RingBuffer } from "../ringBuffer.js";
8
+ import { ACTIVE_RUNTIME_CONTEXT } from "../runtimeContext.js";
9
+ import { makeSeriesView } from "../seriesView.js";
10
+ import { readSourceValue } from "./lib/sourceValue.js";
11
+ function getCtx() {
12
+ const ctx = ACTIVE_RUNTIME_CONTEXT.current;
13
+ if (ctx === null) {
14
+ throw new Error("ta.highestbars called outside an active script step");
15
+ }
16
+ return ctx;
17
+ }
18
+ function initSlot(length, capacity) {
19
+ const outBuffer = new Float64RingBuffer(capacity);
20
+ return {
21
+ outBuffer,
22
+ series: makeSeriesView(outBuffer),
23
+ length,
24
+ sourceWindow: new Float64RingBuffer(length),
25
+ barCount: 0,
26
+ };
27
+ }
28
+ /**
29
+ * Bar offset (≤ 0) to the highest value across the retained window.
30
+ * Walks the window most-recent-first (`at(0)` is the head bar) and
31
+ * updates the running extreme only on a STRICT improvement, so ties
32
+ * keep the most-recent bar (smallest |offset|). `headValue` overrides
33
+ * the head candidate (`at(0)`) for the tick-replay path; pass
34
+ * `undefined` to read the closed head from the window.
35
+ *
36
+ * Returns `NaN` when every candidate in the window is NaN.
37
+ */
38
+ function offsetToMax(slot, headValue) {
39
+ const filled = slot.sourceWindow.length;
40
+ let bestValue = Number.NEGATIVE_INFINITY;
41
+ let bestOffset = Number.NaN;
42
+ for (let i = 0; i < filled; i += 1) {
43
+ const v = i === 0 && headValue !== undefined ? headValue : slot.sourceWindow.at(i);
44
+ if (Number.isFinite(v) && v > bestValue) {
45
+ bestValue = v;
46
+ // `i === 0` ⇒ offset 0 (NOT −0) so `Object.is`-based equality
47
+ // and JSON serialisation never see negative zero.
48
+ bestOffset = i === 0 ? 0 : -i;
49
+ }
50
+ }
51
+ return bestOffset;
52
+ }
53
+ function closeValue(slot, src) {
54
+ slot.barCount += 1;
55
+ slot.sourceWindow.append(src);
56
+ // Warmup: require at least `length` closed bars before emitting.
57
+ if (slot.barCount < slot.length)
58
+ return Number.NaN;
59
+ return offsetToMax(slot, undefined);
60
+ }
61
+ function tickValue(slot, src) {
62
+ if (slot.barCount < slot.length)
63
+ return Number.NaN;
64
+ // Replay the in-progress head: its source overrides the closed
65
+ // head candidate without advancing the window or mutating closed
66
+ // state.
67
+ return offsetToMax(slot, src);
68
+ }
69
+ /**
70
+ * Bar offset (≤ 0) to the highest `source` value over the trailing
71
+ * `length` bars (the window INCLUDES the current bar). `0` means the
72
+ * current bar is the highest; `-k` means the highest occurred `k` bars
73
+ * ago. Ties resolve to the MOST RECENT bar (smallest |offset|). NaN
74
+ * inputs are skipped as candidates; an all-NaN window emits NaN. The
75
+ * output is NaN until `length` closed bars have folded in. Tick-mode
76
+ * replays the in-progress head as the offset-0 candidate without
77
+ * advancing the buffer.
78
+ *
79
+ * @formula out[t] = argmax_{k ∈ [0, length)} source[t − k] expressed as −k
80
+ * (NaN slots skipped; ties → smallest k)
81
+ * @warmup length − 1
82
+ * @since 0.2
83
+ * @stable
84
+ *
85
+ * @example
86
+ * // import { ta } from "@invinite-org/chartlang-core";
87
+ * // const hbar = ta.highestbars(bar.high, 20);
88
+ * // const left = bar.point(hbar.current, bar.high.current);
89
+ */
90
+ export function highestbars(slotId, source, length, _opts) {
91
+ const ctx = getCtx();
92
+ let slot = ctx.stream.taSlots.get(slotId);
93
+ if (slot === undefined) {
94
+ slot = initSlot(length, ctx.stream.ohlcv.close.capacity);
95
+ ctx.stream.taSlots.set(slotId, slot);
96
+ }
97
+ const src = readSourceValue(source);
98
+ if (ctx.isTick) {
99
+ slot.outBuffer.replaceHead(tickValue(slot, src));
100
+ }
101
+ else {
102
+ slot.outBuffer.append(closeValue(slot, src));
103
+ }
104
+ return slot.series;
105
+ }
106
+ //# sourceMappingURL=highestbars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highestbars.js","sourceRoot":"","sources":["../../src/ta/highestbars.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// No invinite source — semantics per Pine `ta.highestbars`.\n// Structural choices (callsite-id slot, Series<T> proxy, replaceHead\n// mode) follow chartlang's primitive shape.\n\nimport type { HighestbarsOpts, Series } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { ACTIVE_RUNTIME_CONTEXT, type RuntimeContext } from \"../runtimeContext.js\";\nimport { makeSeriesView } from \"../seriesView.js\";\nimport { type ScalarOrSeries, readSourceValue } from \"./lib/sourceValue.js\";\n\ntype HighestbarsSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly length: number;\n /**\n * Closed source values across the trailing `length` bars (capacity\n * `length`). `at(0)` is the most recent close (the head bar),\n * `at(k)` the value `k` closed bars ago.\n */\n readonly sourceWindow: Float64RingBuffer;\n /** Number of closed bars folded into the slot. */\n barCount: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.highestbars called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(length: number, capacity: number): HighestbarsSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n length,\n sourceWindow: new Float64RingBuffer(length),\n barCount: 0,\n };\n}\n\n/**\n * Bar offset (≤ 0) to the highest value across the retained window.\n * Walks the window most-recent-first (`at(0)` is the head bar) and\n * updates the running extreme only on a STRICT improvement, so ties\n * keep the most-recent bar (smallest |offset|). `headValue` overrides\n * the head candidate (`at(0)`) for the tick-replay path; pass\n * `undefined` to read the closed head from the window.\n *\n * Returns `NaN` when every candidate in the window is NaN.\n */\nfunction offsetToMax(slot: HighestbarsSlot, headValue: number | undefined): number {\n const filled = slot.sourceWindow.length;\n let bestValue = Number.NEGATIVE_INFINITY;\n let bestOffset = Number.NaN;\n for (let i = 0; i < filled; i += 1) {\n const v = i === 0 && headValue !== undefined ? headValue : slot.sourceWindow.at(i);\n if (Number.isFinite(v) && v > bestValue) {\n bestValue = v;\n // `i === 0` ⇒ offset 0 (NOT −0) so `Object.is`-based equality\n // and JSON serialisation never see negative zero.\n bestOffset = i === 0 ? 0 : -i;\n }\n }\n return bestOffset;\n}\n\nfunction closeValue(slot: HighestbarsSlot, src: number): number {\n slot.barCount += 1;\n slot.sourceWindow.append(src);\n\n // Warmup: require at least `length` closed bars before emitting.\n if (slot.barCount < slot.length) return Number.NaN;\n\n return offsetToMax(slot, undefined);\n}\n\nfunction tickValue(slot: HighestbarsSlot, src: number): number {\n if (slot.barCount < slot.length) return Number.NaN;\n // Replay the in-progress head: its source overrides the closed\n // head candidate without advancing the window or mutating closed\n // state.\n return offsetToMax(slot, src);\n}\n\n/**\n * Bar offset (≤ 0) to the highest `source` value over the trailing\n * `length` bars (the window INCLUDES the current bar). `0` means the\n * current bar is the highest; `-k` means the highest occurred `k` bars\n * ago. Ties resolve to the MOST RECENT bar (smallest |offset|). NaN\n * inputs are skipped as candidates; an all-NaN window emits NaN. The\n * output is NaN until `length` closed bars have folded in. Tick-mode\n * replays the in-progress head as the offset-0 candidate without\n * advancing the buffer.\n *\n * @formula out[t] = argmax_{k ∈ [0, length)} source[t − k] expressed as −k\n * (NaN slots skipped; ties → smallest k)\n * @warmup length − 1\n * @since 0.2\n * @stable\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-core\";\n * // const hbar = ta.highestbars(bar.high, 20);\n * // const left = bar.point(hbar.current, bar.high.current);\n */\nexport function highestbars(\n slotId: string,\n source: ScalarOrSeries,\n length: number,\n _opts?: HighestbarsOpts,\n): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as HighestbarsSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(length, ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const src = readSourceValue(source);\n if (ctx.isTick) {\n slot.outBuffer.replaceHead(tickValue(slot, src));\n } else {\n slot.outBuffer.append(closeValue(slot, src));\n }\n return slot.series;\n}\n"]}