@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":"mfi.js","sourceRoot":"","sources":["../../src/ta/mfi.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/mfi.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 rolling-window pos / neg money-flow sums\n// follow the `cmf.ts` / `ulcerIndex.ts` \"subtract head + add tick\"\n// tick-mode shape (no window mutation on tick).\n\nimport type { MfiOpts, 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\ntype MfiSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n readonly length: number;\n /** Closed-bar positive money-flow contributions (capacity `length`). */\n readonly posMfWindow: Float64RingBuffer;\n /** Closed-bar negative money-flow contributions. */\n readonly negMfWindow: Float64RingBuffer;\n sumPosMf: number;\n sumNegMf: number;\n /** Most recent finite typical price (lookback target for the next bar). */\n prevTp: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.mfi called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(length: number, capacity: number): MfiSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n length,\n posMfWindow: new Float64RingBuffer(length),\n negMfWindow: new Float64RingBuffer(length),\n sumPosMf: 0,\n sumNegMf: 0,\n prevTp: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: MfiSlot, 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\n/**\n * Per-bar typical-price contributions to the (posMF, negMF) split,\n * given the prior typical price. NaN OHLC / volume → (0, 0). Equal\n * `tp === prevTp` or first bar (`prevTp` NaN) → (0, 0) per Pine\n * convention.\n */\nfunction bucketMf(tp: number, prevTp: number, volume: number): { posMf: number; negMf: number } {\n if (!Number.isFinite(tp) || !Number.isFinite(volume)) {\n return { posMf: 0, negMf: 0 };\n }\n if (!Number.isFinite(prevTp) || tp === prevTp) {\n return { posMf: 0, negMf: 0 };\n }\n const mf = tp * volume;\n if (tp > prevTp) return { posMf: mf, negMf: 0 };\n return { posMf: 0, negMf: mf };\n}\n\nfunction emitMfi(sumPos: number, sumNeg: number, ready: boolean): number {\n if (!ready) return Number.NaN;\n const total = sumPos + sumNeg;\n if (total === 0) return Number.NaN;\n return (100 * sumPos) / total;\n}\n\n/**\n * Money Flow Index — volume-weighted RSI over a trailing window of\n * `length` typical-price comparisons. Per-bar typical price `tp = (H +\n * L + C) / 3`; per-bar money flow `mf = tp · volume` lands in the\n * positive bucket when `tp > prevTp`, in the negative bucket when\n * `tp < prevTp`, and is dropped on equality / first bar. The emit is\n * `100 · sumPos / (sumPos + sumNeg)` once `length` such comparisons\n * have accumulated; NaN before warmup and when `sumPos + sumNeg ===\n * 0` (no flow either way — invinite's zero-denominator guard).\n *\n * Range `[0, 100]` when defined: `sumPos === 0` → 0 (perfect\n * downflow); `sumNeg === 0` with `sumPos > 0` → 100 (perfect\n * upflow). NaN OHLC / volume contributes 0 to both buckets (matches\n * `cmf.ts:62-75`'s defensive shape).\n *\n * **Tick mode.** Substitutes the tick's (posMf, negMf) contribution\n * for the head slot's stored values without mutating the trailing-\n * window rings or advancing `prevTp` — mirrors `cmf.ts:125-138`.\n *\n * @formula tp = (high + low + close) / 3 ;\n * mf = tp · volume ;\n * pos = mf when tp > prevTp else 0 ;\n * neg = mf when tp < prevTp else 0 ;\n * mfi = 100 · Σ pos / (Σ pos + Σ neg) over the trailing `length` window\n * @warmup length + 1 (one bar to seed prevTp + `length` comparisons)\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 m = ta.mfi(14);\n * // plot(m);\n */\nexport function mfi(slotId: string, length: number, opts?: MfiOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as MfiSlot | 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, close, volume } = ctx.stream.bar;\n const tp = (high + low + close) / 3;\n const { posMf, negMf } = bucketMf(tp, slot.prevTp, volume);\n\n // The seed bar (prevTp NaN) contributes no real comparison — we\n // skip the window append so the trailing window only ever holds\n // `length` REAL (prevTp-defined) comparisons. First finite emit\n // lands at bar `length` (warmup `length + 1` per the JSDoc).\n const hasComparison = Number.isFinite(slot.prevTp);\n\n if (ctx.isTick) {\n const ready = slot.posMfWindow.length === slot.length;\n if (!ready || !hasComparison) {\n slot.outBuffer.replaceHead(Number.NaN);\n return viewForOffset(slot, offset);\n }\n const headPos = slot.posMfWindow.at(0);\n const headNeg = slot.negMfWindow.at(0);\n const hypPos = slot.sumPosMf - headPos + posMf;\n const hypNeg = slot.sumNegMf - headNeg + negMf;\n slot.outBuffer.replaceHead(emitMfi(hypPos, hypNeg, true));\n return viewForOffset(slot, offset);\n }\n\n if (hasComparison) {\n if (slot.posMfWindow.length === slot.length) {\n slot.sumPosMf -= slot.posMfWindow.at(slot.length - 1);\n slot.sumNegMf -= slot.negMfWindow.at(slot.length - 1);\n }\n slot.posMfWindow.append(posMf);\n slot.negMfWindow.append(negMf);\n slot.sumPosMf += posMf;\n slot.sumNegMf += negMf;\n }\n if (Number.isFinite(tp)) slot.prevTp = tp;\n slot.outBuffer.append(\n emitMfi(slot.sumPosMf, slot.sumNegMf, slot.posMfWindow.length === slot.length),\n );\n return viewForOffset(slot, offset);\n}\n"]}
1
+ {"version":3,"file":"mfi.js","sourceRoot":"","sources":["../../src/ta/mfi.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/mfi.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 rolling-window pos / neg money-flow sums\n// follow the `cmf.ts` / `ulcerIndex.ts` \"subtract head + add tick\"\n// tick-mode shape (no window mutation on tick).\n\nimport type { MfiOpts, 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\ntype MfiSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n readonly length: number;\n /** Closed-bar positive money-flow contributions (capacity `length`). */\n readonly posMfWindow: Float64RingBuffer;\n /** Closed-bar negative money-flow contributions. */\n readonly negMfWindow: Float64RingBuffer;\n sumPosMf: number;\n sumNegMf: number;\n /** Most recent finite typical price (lookback target for the next bar). */\n prevTp: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.mfi called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(length: number, capacity: number): MfiSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n length,\n posMfWindow: new Float64RingBuffer(length),\n negMfWindow: new Float64RingBuffer(length),\n sumPosMf: 0,\n sumNegMf: 0,\n prevTp: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: MfiSlot, 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\n/**\n * Per-bar typical-price contributions to the (posMF, negMF) split,\n * given the prior typical price. NaN OHLC / volume → (0, 0). Equal\n * `tp === prevTp` or first bar (`prevTp` NaN) → (0, 0) per Pine\n * convention.\n */\nfunction bucketMf(tp: number, prevTp: number, volume: number): { posMf: number; negMf: number } {\n if (!Number.isFinite(tp) || !Number.isFinite(volume)) {\n return { posMf: 0, negMf: 0 };\n }\n if (!Number.isFinite(prevTp) || tp === prevTp) {\n return { posMf: 0, negMf: 0 };\n }\n const mf = tp * volume;\n if (tp > prevTp) return { posMf: mf, negMf: 0 };\n return { posMf: 0, negMf: mf };\n}\n\nfunction emitMfi(sumPos: number, sumNeg: number, ready: boolean): number {\n if (!ready) return Number.NaN;\n const total = sumPos + sumNeg;\n if (total === 0) return Number.NaN;\n return (100 * sumPos) / total;\n}\n\n/**\n * Money Flow Index — volume-weighted RSI over a trailing window of\n * `length` typical-price comparisons. Per-bar typical price `tp = (H +\n * L + C) / 3`; per-bar money flow `mf = tp · volume` lands in the\n * positive bucket when `tp > prevTp`, in the negative bucket when\n * `tp < prevTp`, and is dropped on equality / first bar. The emit is\n * `100 · sumPos / (sumPos + sumNeg)` once `length` such comparisons\n * have accumulated; NaN before warmup and when `sumPos + sumNeg ===\n * 0` (no flow either way — invinite's zero-denominator guard).\n *\n * Range `[0, 100]` when defined: `sumPos === 0` → 0 (perfect\n * downflow); `sumNeg === 0` with `sumPos > 0` → 100 (perfect\n * upflow). NaN OHLC / volume contributes 0 to both buckets (matches\n * `cmf.ts:62-75`'s defensive shape).\n *\n * **Tick mode.** Substitutes the tick's (posMf, negMf) contribution\n * for the head slot's stored values without mutating the trailing-\n * window rings or advancing `prevTp` — mirrors `cmf.ts:125-138`.\n *\n * @formula tp = (high + low + close) / 3 ;\n * mf = tp · volume ;\n * pos = mf when tp > prevTp else 0 ;\n * neg = mf when tp < prevTp else 0 ;\n * mfi = 100 · Σ pos / (Σ pos + Σ neg) over the trailing `length` window\n * @warmup length + 1 (one bar to seed prevTp + `length` comparisons)\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 m = ta.mfi(14);\n * // plot(m);\n */\nexport function mfi(slotId: string, length: number, opts?: MfiOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as MfiSlot | 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, close, volume } = ctx.stream.bar;\n const tp = (high + low + close) / 3;\n const { posMf, negMf } = bucketMf(tp, slot.prevTp, volume);\n\n // The seed bar (prevTp NaN) contributes no real comparison — we\n // skip the window append so the trailing window only ever holds\n // `length` REAL (prevTp-defined) comparisons. First finite emit\n // lands at bar `length` (warmup `length + 1` per the JSDoc).\n const hasComparison = Number.isFinite(slot.prevTp);\n\n if (ctx.isTick) {\n const ready = slot.posMfWindow.length === slot.length;\n if (!ready || !hasComparison) {\n slot.outBuffer.replaceHead(Number.NaN);\n return viewForOffset(slot, offset);\n }\n const headPos = slot.posMfWindow.at(0);\n const headNeg = slot.negMfWindow.at(0);\n const hypPos = slot.sumPosMf - headPos + posMf;\n const hypNeg = slot.sumNegMf - headNeg + negMf;\n slot.outBuffer.replaceHead(emitMfi(hypPos, hypNeg, true));\n return viewForOffset(slot, offset);\n }\n\n if (hasComparison) {\n if (slot.posMfWindow.length === slot.length) {\n slot.sumPosMf -= slot.posMfWindow.at(slot.length - 1);\n slot.sumNegMf -= slot.negMfWindow.at(slot.length - 1);\n }\n slot.posMfWindow.append(posMf);\n slot.negMfWindow.append(negMf);\n slot.sumPosMf += posMf;\n slot.sumNegMf += negMf;\n }\n if (Number.isFinite(tp)) slot.prevTp = tp;\n slot.outBuffer.append(\n emitMfi(slot.sumPosMf, slot.sumNegMf, slot.posMfWindow.length === slot.length),\n );\n return viewForOffset(slot, offset);\n}\n"]}
@@ -20,7 +20,9 @@ import type { NetVolumeOpts, Series } from "@invinite-org/chartlang-core";
20
20
  * @since 0.2
21
21
  * @stable
22
22
  *
23
- * `opts.offset` shifts the returned series.
23
+ * `opts.offset` is a presentation display shift carried to the plot
24
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
25
+ * series value is unshifted.
24
26
  *
25
27
  * @example
26
28
  * // import { ta, plot } from "@invinite-org/chartlang-core";
@@ -1 +1 @@
1
- {"version":3,"file":"netVolume.d.ts","sourceRoot":"","sources":["../../src/ta/netVolume.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAwE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,CAuB9E"}
1
+ {"version":3,"file":"netVolume.d.ts","sourceRoot":"","sources":["../../src/ta/netVolume.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAwE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,CAuB9E"}
@@ -86,7 +86,9 @@ function fold(inCum, inPrevClose, close, volume) {
86
86
  * @since 0.2
87
87
  * @stable
88
88
  *
89
- * `opts.offset` shifts the returned series.
89
+ * `opts.offset` is a presentation display shift carried to the plot
90
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
91
+ * series value is unshifted.
90
92
  *
91
93
  * @example
92
94
  * // import { ta, plot } from "@invinite-org/chartlang-core";
@@ -1 +1 @@
1
- {"version":3,"file":"netVolume.js","sourceRoot":"","sources":["../../src/ta/netVolume.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/net-volume.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 math is identical to `ta.obv`; both\n// primitives exist in invinite under their own names, so the\n// chartlang surface mirrors the public API for naming parity. See\n// `obv.ts` for the canonical commentary on `fold`, snapshot fields,\n// and NaN handling — this file repeats the shape verbatim under its\n// own slot type so each primitive has its own per-callsite state.\n\nimport type { NetVolumeOpts, 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\ntype NetVolumeSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n cumNetVol: number;\n prevClose: number;\n prevClosedCumNetVol: number;\n prevClosedPrevClose: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.netVolume called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): NetVolumeSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n cumNetVol: 0,\n prevClose: Number.NaN,\n prevClosedCumNetVol: 0,\n prevClosedPrevClose: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: NetVolumeSlot, 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 signOfDelta(delta: number): number {\n if (delta > 0) return 1;\n if (delta < 0) return -1;\n return 0;\n}\n\nfunction fold(\n inCum: number,\n inPrevClose: number,\n close: number,\n volume: number,\n): { cum: number; prevClose: number } {\n if (!Number.isFinite(close)) {\n return { cum: inCum, prevClose: inPrevClose };\n }\n if (!Number.isFinite(inPrevClose)) {\n return { cum: inCum, prevClose: close };\n }\n if (!Number.isFinite(volume)) {\n return { cum: inCum, prevClose: close };\n }\n const direction = signOfDelta(close - inPrevClose);\n return { cum: inCum + direction * volume, prevClose: close };\n}\n\n/**\n * Net Volume — cumulative `sign(close − prevClose) · volume`. **The\n * math is identical to `ta.obv`** (both primitives ship in invinite\n * under their own names; chartlang mirrors the public surface for\n * naming parity). Prefer `ta.obv` when writing new scripts — this\n * primitive exists to satisfy translation of Pine / invinite\n * indicators that call `netVolume()` directly. The cross-equivalence\n * is property-tested (`netVolume.property.test.ts`).\n *\n * First bar emits `0` (no prior close to difference against — Pine\n * convention). NaN volume carries the accumulator forward without an\n * update; NaN close holds `prevClose` at its prior value.\n *\n * **Tick mode.** Replays the head bar's contribution against a\n * snapshot of the prior-close `(cumNetVol, prevClose)` tuple.\n *\n * @formula netVolume[t] = netVolume[t − 1] + sign(close[t] − close[t − 1]) · volume[t]\n * @warmup 1 (needs a prior close to compute the delta; bar 0 emits 0)\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 nv = ta.netVolume();\n * // plot(nv);\n */\nexport function netVolume(slotId: string, opts?: NetVolumeOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as NetVolumeSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const offset = opts?.offset ?? 0;\n const { close, volume } = ctx.stream.bar;\n\n if (ctx.isTick) {\n const next = fold(slot.prevClosedCumNetVol, slot.prevClosedPrevClose, close, volume);\n slot.outBuffer.replaceHead(next.cum);\n return viewForOffset(slot, offset);\n }\n\n slot.prevClosedCumNetVol = slot.cumNetVol;\n slot.prevClosedPrevClose = slot.prevClose;\n const next = fold(slot.cumNetVol, slot.prevClose, close, volume);\n slot.cumNetVol = next.cum;\n slot.prevClose = next.prevClose;\n slot.outBuffer.append(slot.cumNetVol);\n return viewForOffset(slot, offset);\n}\n"]}
1
+ {"version":3,"file":"netVolume.js","sourceRoot":"","sources":["../../src/ta/netVolume.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/net-volume.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 math is identical to `ta.obv`; both\n// primitives exist in invinite under their own names, so the\n// chartlang surface mirrors the public API for naming parity. See\n// `obv.ts` for the canonical commentary on `fold`, snapshot fields,\n// and NaN handling — this file repeats the shape verbatim under its\n// own slot type so each primitive has its own per-callsite state.\n\nimport type { NetVolumeOpts, 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\ntype NetVolumeSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n cumNetVol: number;\n prevClose: number;\n prevClosedCumNetVol: number;\n prevClosedPrevClose: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.netVolume called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): NetVolumeSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n cumNetVol: 0,\n prevClose: Number.NaN,\n prevClosedCumNetVol: 0,\n prevClosedPrevClose: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: NetVolumeSlot, 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 signOfDelta(delta: number): number {\n if (delta > 0) return 1;\n if (delta < 0) return -1;\n return 0;\n}\n\nfunction fold(\n inCum: number,\n inPrevClose: number,\n close: number,\n volume: number,\n): { cum: number; prevClose: number } {\n if (!Number.isFinite(close)) {\n return { cum: inCum, prevClose: inPrevClose };\n }\n if (!Number.isFinite(inPrevClose)) {\n return { cum: inCum, prevClose: close };\n }\n if (!Number.isFinite(volume)) {\n return { cum: inCum, prevClose: close };\n }\n const direction = signOfDelta(close - inPrevClose);\n return { cum: inCum + direction * volume, prevClose: close };\n}\n\n/**\n * Net Volume — cumulative `sign(close − prevClose) · volume`. **The\n * math is identical to `ta.obv`** (both primitives ship in invinite\n * under their own names; chartlang mirrors the public surface for\n * naming parity). Prefer `ta.obv` when writing new scripts — this\n * primitive exists to satisfy translation of Pine / invinite\n * indicators that call `netVolume()` directly. The cross-equivalence\n * is property-tested (`netVolume.property.test.ts`).\n *\n * First bar emits `0` (no prior close to difference against — Pine\n * convention). NaN volume carries the accumulator forward without an\n * update; NaN close holds `prevClose` at its prior value.\n *\n * **Tick mode.** Replays the head bar's contribution against a\n * snapshot of the prior-close `(cumNetVol, prevClose)` tuple.\n *\n * @formula netVolume[t] = netVolume[t − 1] + sign(close[t] − close[t − 1]) · volume[t]\n * @warmup 1 (needs a prior close to compute the delta; bar 0 emits 0)\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 nv = ta.netVolume();\n * // plot(nv);\n */\nexport function netVolume(slotId: string, opts?: NetVolumeOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as NetVolumeSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const offset = opts?.offset ?? 0;\n const { close, volume } = ctx.stream.bar;\n\n if (ctx.isTick) {\n const next = fold(slot.prevClosedCumNetVol, slot.prevClosedPrevClose, close, volume);\n slot.outBuffer.replaceHead(next.cum);\n return viewForOffset(slot, offset);\n }\n\n slot.prevClosedCumNetVol = slot.cumNetVol;\n slot.prevClosedPrevClose = slot.prevClose;\n const next = fold(slot.cumNetVol, slot.prevClose, close, volume);\n slot.cumNetVol = next.cum;\n slot.prevClose = next.prevClose;\n slot.outBuffer.append(slot.cumNetVol);\n return viewForOffset(slot, offset);\n}\n"]}
package/dist/ta/nvi.d.ts CHANGED
@@ -24,7 +24,9 @@ import type { NviOpts, 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":"nvi.d.ts","sourceRoot":"","sources":["../../src/ta/nvi.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAoGpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CA+BlE"}
1
+ {"version":3,"file":"nvi.d.ts","sourceRoot":"","sources":["../../src/ta/nvi.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAoGpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CA+BlE"}
package/dist/ta/nvi.js CHANGED
@@ -106,7 +106,9 @@ function fold(inValue, inPrevClose, inPrevVolume, close, volume) {
106
106
  * @since 0.2
107
107
  * @stable
108
108
  *
109
- * `opts.offset` shifts the returned series.
109
+ * `opts.offset` is a presentation display shift carried to the plot
110
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
111
+ * series value is unshifted.
110
112
  *
111
113
  * @example
112
114
  * // import { ta, plot } from "@invinite-org/chartlang-core";
@@ -1 +1 @@
1
- {"version":3,"file":"nvi.js","sourceRoot":"","sources":["../../src/ta/nvi.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/nvi.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 runtime emits the RAW NVI line only;\n// invinite's optional smoothing block is left to the script author\n// (`ta.ema(ta.nvi(), 255)` etc.). The mirror primitive `ta.pvi`\n// re-uses this file's `nviLikeFold` shape with a flipped predicate.\n\nimport type { NviOpts, 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 * Anchored seed value at bar 0 (and after any bar where the\n * accumulator has been carried-forward through NaN inputs).\n *\n * @anchors seedValue\n */\nconst SEED_VALUE = 1000;\n\ntype NviSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n /** Active NVI value across the closed bars so far. */\n value: number;\n /** Most recent finite close. */\n prevClose: number;\n /** Most recent volume (treated as 0 on NaN). */\n prevVolume: number;\n /** Snapshot of `value` BEFORE the most recent close-side update. */\n prevClosedValue: number;\n prevClosedPrevClose: number;\n prevClosedPrevVolume: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.nvi called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): NviSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n value: SEED_VALUE,\n prevClose: Number.NaN,\n prevVolume: Number.NaN,\n prevClosedValue: SEED_VALUE,\n prevClosedPrevClose: Number.NaN,\n prevClosedPrevVolume: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: NviSlot, 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 * Fold the bar's `(close, volume)` into the prior NVI state. NaN close\n * carries every field forward (don't advance `prevClose` — the next\n * finite close differences against the last finite one). NaN volume\n * is treated as 0 (matches invinite's `safeVolume` shape). The\n * comparison runs `currV < prevV` (NVI) and only updates when the\n * comparison holds AND `prevClose !== 0`.\n */\nfunction fold(\n inValue: number,\n inPrevClose: number,\n inPrevVolume: number,\n close: number,\n volume: number,\n): { value: number; prevClose: number; prevVolume: number } {\n if (!Number.isFinite(close)) {\n return { value: inValue, prevClose: inPrevClose, prevVolume: inPrevVolume };\n }\n const v = safeVol(volume);\n if (!Number.isFinite(inPrevClose)) {\n // First defined close — seed `prevClose` + `prevVolume`,\n // leave `value` at its 1000 seed.\n return { value: inValue, prevClose: close, prevVolume: v };\n }\n const pv = safeVol(inPrevVolume);\n const shouldUpdate = v < pv;\n if (!shouldUpdate || inPrevClose === 0) {\n return { value: inValue, prevClose: close, prevVolume: v };\n }\n const next = inValue * (1 + (close - inPrevClose) / inPrevClose);\n return { value: next, prevClose: close, prevVolume: v };\n}\n\n/**\n * Negative Volume Index — cumulative percentage-change in close on\n * bars whose volume is strictly LOWER than the prior bar's; bars with\n * equal-or-higher volume carry the prior NVI value unchanged. Seeded\n * at 1000 (anchor — see `SEED_VALUE`); the property tests pin the\n * seed at bar 0.\n *\n * NaN volume is treated as 0 (matches invinite's `safeVolume` shape) —\n * a NaN-volume bar is \"lower\" than any positive-volume bar, so the\n * comparison is exercised. NaN close carries every accumulator field\n * forward without advancing — the next finite close differences\n * against the last finite one.\n *\n * **Tick mode.** Replays the head bar's contribution against a\n * snapshot of the prior-close `(value, prevClose, prevVolume)` tuple.\n *\n * @formula nvi[0] = 1000 ;\n * nvi[t] = (volume[t] < volume[t − 1] && prevClose != 0)\n * ? nvi[t − 1] · (1 + (close[t] − close[t − 1]) / close[t − 1])\n * : nvi[t − 1]\n * @warmup 1 (bar 0 emits the 1000 seed)\n * @anchors seedValue\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 n = ta.nvi();\n * // plot(n);\n */\nexport function nvi(slotId: string, opts?: NviOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as NviSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const offset = opts?.offset ?? 0;\n const { close, volume } = ctx.stream.bar;\n\n if (ctx.isTick) {\n const next = fold(\n slot.prevClosedValue,\n slot.prevClosedPrevClose,\n slot.prevClosedPrevVolume,\n close,\n volume,\n );\n slot.outBuffer.replaceHead(next.value);\n return viewForOffset(slot, offset);\n }\n\n slot.prevClosedValue = slot.value;\n slot.prevClosedPrevClose = slot.prevClose;\n slot.prevClosedPrevVolume = slot.prevVolume;\n const next = fold(slot.value, slot.prevClose, slot.prevVolume, close, volume);\n slot.value = next.value;\n slot.prevClose = next.prevClose;\n slot.prevVolume = next.prevVolume;\n slot.outBuffer.append(slot.value);\n return viewForOffset(slot, offset);\n}\n"]}
1
+ {"version":3,"file":"nvi.js","sourceRoot":"","sources":["../../src/ta/nvi.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/nvi.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 runtime emits the RAW NVI line only;\n// invinite's optional smoothing block is left to the script author\n// (`ta.ema(ta.nvi(), 255)` etc.). The mirror primitive `ta.pvi`\n// re-uses this file's `nviLikeFold` shape with a flipped predicate.\n\nimport type { NviOpts, 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 * Anchored seed value at bar 0 (and after any bar where the\n * accumulator has been carried-forward through NaN inputs).\n *\n * @anchors seedValue\n */\nconst SEED_VALUE = 1000;\n\ntype NviSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n /** Active NVI value across the closed bars so far. */\n value: number;\n /** Most recent finite close. */\n prevClose: number;\n /** Most recent volume (treated as 0 on NaN). */\n prevVolume: number;\n /** Snapshot of `value` BEFORE the most recent close-side update. */\n prevClosedValue: number;\n prevClosedPrevClose: number;\n prevClosedPrevVolume: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.nvi called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): NviSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n value: SEED_VALUE,\n prevClose: Number.NaN,\n prevVolume: Number.NaN,\n prevClosedValue: SEED_VALUE,\n prevClosedPrevClose: Number.NaN,\n prevClosedPrevVolume: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: NviSlot, 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 * Fold the bar's `(close, volume)` into the prior NVI state. NaN close\n * carries every field forward (don't advance `prevClose` — the next\n * finite close differences against the last finite one). NaN volume\n * is treated as 0 (matches invinite's `safeVolume` shape). The\n * comparison runs `currV < prevV` (NVI) and only updates when the\n * comparison holds AND `prevClose !== 0`.\n */\nfunction fold(\n inValue: number,\n inPrevClose: number,\n inPrevVolume: number,\n close: number,\n volume: number,\n): { value: number; prevClose: number; prevVolume: number } {\n if (!Number.isFinite(close)) {\n return { value: inValue, prevClose: inPrevClose, prevVolume: inPrevVolume };\n }\n const v = safeVol(volume);\n if (!Number.isFinite(inPrevClose)) {\n // First defined close — seed `prevClose` + `prevVolume`,\n // leave `value` at its 1000 seed.\n return { value: inValue, prevClose: close, prevVolume: v };\n }\n const pv = safeVol(inPrevVolume);\n const shouldUpdate = v < pv;\n if (!shouldUpdate || inPrevClose === 0) {\n return { value: inValue, prevClose: close, prevVolume: v };\n }\n const next = inValue * (1 + (close - inPrevClose) / inPrevClose);\n return { value: next, prevClose: close, prevVolume: v };\n}\n\n/**\n * Negative Volume Index — cumulative percentage-change in close on\n * bars whose volume is strictly LOWER than the prior bar's; bars with\n * equal-or-higher volume carry the prior NVI value unchanged. Seeded\n * at 1000 (anchor — see `SEED_VALUE`); the property tests pin the\n * seed at bar 0.\n *\n * NaN volume is treated as 0 (matches invinite's `safeVolume` shape) —\n * a NaN-volume bar is \"lower\" than any positive-volume bar, so the\n * comparison is exercised. NaN close carries every accumulator field\n * forward without advancing — the next finite close differences\n * against the last finite one.\n *\n * **Tick mode.** Replays the head bar's contribution against a\n * snapshot of the prior-close `(value, prevClose, prevVolume)` tuple.\n *\n * @formula nvi[0] = 1000 ;\n * nvi[t] = (volume[t] < volume[t − 1] && prevClose != 0)\n * ? nvi[t − 1] · (1 + (close[t] − close[t − 1]) / close[t − 1])\n * : nvi[t − 1]\n * @warmup 1 (bar 0 emits the 1000 seed)\n * @anchors seedValue\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 n = ta.nvi();\n * // plot(n);\n */\nexport function nvi(slotId: string, opts?: NviOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as NviSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const offset = opts?.offset ?? 0;\n const { close, volume } = ctx.stream.bar;\n\n if (ctx.isTick) {\n const next = fold(\n slot.prevClosedValue,\n slot.prevClosedPrevClose,\n slot.prevClosedPrevVolume,\n close,\n volume,\n );\n slot.outBuffer.replaceHead(next.value);\n return viewForOffset(slot, offset);\n }\n\n slot.prevClosedValue = slot.value;\n slot.prevClosedPrevClose = slot.prevClose;\n slot.prevClosedPrevVolume = slot.prevVolume;\n const next = fold(slot.value, slot.prevClose, slot.prevVolume, close, volume);\n slot.value = next.value;\n slot.prevClose = next.prevClose;\n slot.prevVolume = next.prevVolume;\n slot.outBuffer.append(slot.value);\n return viewForOffset(slot, offset);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/ta/persistence.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAU,MAAM,8BAA8B,CAAC;AAItE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkTrD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CASzF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC1B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACzC,IAAI,CASN"}
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/ta/persistence.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAU,MAAM,8BAA8B,CAAC;AAatE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA6PrD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CASzF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC1B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACzC,IAAI,CASN"}
@@ -1,19 +1,9 @@
1
1
  // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
2
  // See the LICENSE file in the repo root for full license text.
3
+ import { finiteOrNull, isBufferSnapshot, isInteger, isRecord, restoreBuffer, restoreNumber, serialiseBuffer, } from "../bufferSnapshot.js";
3
4
  import { Float64RingBuffer } from "../ringBuffer.js";
4
5
  import { makeSeriesView } from "../seriesView.js";
5
6
  const TA_SLOT_PREFIX = "ta:";
6
- function isRecord(value) {
7
- return typeof value === "object" && value !== null && !Array.isArray(value);
8
- }
9
- function finiteOrNull(value) {
10
- return Number.isFinite(value) ? value : null;
11
- }
12
- function restoreNumber(value) {
13
- if (value === null)
14
- return Number.NaN;
15
- return typeof value === "number" && Number.isFinite(value) ? value : null;
16
- }
17
7
  /**
18
8
  * Restore a record of named numeric fields, returning `null` if any one
19
9
  * field fails {@link restoreNumber}. Lets restore functions read several
@@ -32,35 +22,6 @@ function restoreNumbers(fields) {
32
22
  }
33
23
  return out;
34
24
  }
35
- function isInteger(value) {
36
- return typeof value === "number" && Number.isInteger(value);
37
- }
38
- function isBufferSnapshot(value) {
39
- if (!isRecord(value))
40
- return false;
41
- if (!isInteger(value.headIndex) || !isInteger(value.filled))
42
- return false;
43
- return (Array.isArray(value.values) &&
44
- value.values.every((entry) => entry === null || (typeof entry === "number" && Number.isFinite(entry))));
45
- }
46
- function serialiseBuffer(buffer) {
47
- const snapshot = buffer.serialiseSnapshotBuffer();
48
- return {
49
- headIndex: snapshot.headIndex,
50
- filled: snapshot.filled,
51
- values: snapshot.values,
52
- };
53
- }
54
- function restoreBuffer(snapshot, capacity) {
55
- const buffer = new Float64RingBuffer(capacity);
56
- try {
57
- buffer.restoreFromSnapshotBuffer(snapshot);
58
- return buffer;
59
- }
60
- catch {
61
- return null;
62
- }
63
- }
64
25
  function baseSlot(outBuffer) {
65
26
  return {
66
27
  outBuffer,
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/ta/persistence.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\nimport type { JsonValue, Series } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { makeSeriesView } from \"../seriesView.js\";\nimport type { StreamState } from \"../streamState.js\";\n\nconst TA_SLOT_PREFIX = \"ta:\";\n\ntype BufferSnapshot = Readonly<{\n headIndex: number;\n filled: number;\n values: ReadonlyArray<number | null>;\n}>;\n\ntype RestoredBaseSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n};\n\ntype RestoredSmaSlot = RestoredBaseSlot & {\n readonly kind: \"ta.sma\";\n readonly length: number;\n readonly window: Float64RingBuffer;\n sum: number;\n};\n\ntype RestoredEmaSlot = RestoredBaseSlot & {\n readonly kind: \"ta.ema\";\n readonly alpha: number;\n readonly length: number;\n seedSum: number;\n seedCount: number;\n prevEma: number;\n prevClosedEma: number;\n};\n\ntype RestoredRsiSlot = RestoredBaseSlot & {\n readonly kind: \"ta.rsi\";\n readonly length: number;\n seedGainSum: number;\n seedLossSum: number;\n diffCount: number;\n avgGain: number;\n avgLoss: number;\n prevSrc: number;\n prevClosedSrc: number;\n};\n\ntype RestoredTaSlot = RestoredSmaSlot | RestoredEmaSlot | RestoredRsiSlot;\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction finiteOrNull(value: number): number | null {\n return Number.isFinite(value) ? value : null;\n}\n\nfunction restoreNumber(value: unknown): number | null {\n if (value === null) return Number.NaN;\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\n/**\n * Restore a record of named numeric fields, returning `null` if any one\n * field fails {@link restoreNumber}. Lets restore functions read several\n * persisted numbers in one all-or-nothing step instead of an OR-chain of\n * per-field null checks.\n *\n * @internal\n */\nfunction restoreNumbers<K extends string>(\n fields: Readonly<Record<K, unknown>>,\n): Record<K, number> | null {\n const out = {} as Record<K, number>;\n for (const key of Object.keys(fields) as K[]) {\n const restored = restoreNumber(fields[key]);\n if (restored === null) return null;\n out[key] = restored;\n }\n return out;\n}\n\nfunction isInteger(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value);\n}\n\nfunction isBufferSnapshot(value: unknown): value is BufferSnapshot {\n if (!isRecord(value)) return false;\n if (!isInteger(value.headIndex) || !isInteger(value.filled)) return false;\n return (\n Array.isArray(value.values) &&\n value.values.every(\n (entry) => entry === null || (typeof entry === \"number\" && Number.isFinite(entry)),\n )\n );\n}\n\nfunction serialiseBuffer(buffer: Float64RingBuffer): JsonValue {\n const snapshot = buffer.serialiseSnapshotBuffer();\n return {\n headIndex: snapshot.headIndex,\n filled: snapshot.filled,\n values: snapshot.values,\n };\n}\n\nfunction restoreBuffer(snapshot: BufferSnapshot, capacity: number): Float64RingBuffer | null {\n const buffer = new Float64RingBuffer(capacity);\n try {\n buffer.restoreFromSnapshotBuffer(snapshot);\n return buffer;\n } catch {\n return null;\n }\n}\n\nfunction baseSlot(outBuffer: Float64RingBuffer): RestoredBaseSlot {\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map<number, Series<number>>(),\n };\n}\n\nfunction isFloat64RingBuffer(value: unknown): value is Float64RingBuffer {\n return value instanceof Float64RingBuffer;\n}\n\nfunction serialiseSma(slot: Readonly<Record<string, unknown>>): JsonValue | null {\n if (\n slot.kind !== \"ta.sma\" ||\n typeof slot.length !== \"number\" ||\n typeof slot.sum !== \"number\" ||\n !isFloat64RingBuffer(slot.outBuffer) ||\n !isFloat64RingBuffer(slot.window)\n ) {\n return null;\n }\n return {\n kind: \"ta.sma\",\n length: slot.length,\n outBuffer: serialiseBuffer(slot.outBuffer),\n window: serialiseBuffer(slot.window),\n sum: finiteOrNull(slot.sum),\n };\n}\n\nfunction serialiseEma(slot: Readonly<Record<string, unknown>>): JsonValue | null {\n if (\n slot.kind !== \"ta.ema\" ||\n typeof slot.alpha !== \"number\" ||\n typeof slot.length !== \"number\" ||\n typeof slot.seedSum !== \"number\" ||\n typeof slot.seedCount !== \"number\" ||\n typeof slot.prevEma !== \"number\" ||\n typeof slot.prevClosedEma !== \"number\" ||\n !isFloat64RingBuffer(slot.outBuffer)\n ) {\n return null;\n }\n return {\n kind: \"ta.ema\",\n alpha: finiteOrNull(slot.alpha),\n length: slot.length,\n outBuffer: serialiseBuffer(slot.outBuffer),\n seedSum: finiteOrNull(slot.seedSum),\n seedCount: slot.seedCount,\n prevEma: finiteOrNull(slot.prevEma),\n prevClosedEma: finiteOrNull(slot.prevClosedEma),\n };\n}\n\nfunction serialiseRsi(slot: Readonly<Record<string, unknown>>): JsonValue | null {\n if (\n slot.kind !== \"ta.rsi\" ||\n typeof slot.length !== \"number\" ||\n typeof slot.seedGainSum !== \"number\" ||\n typeof slot.seedLossSum !== \"number\" ||\n typeof slot.diffCount !== \"number\" ||\n typeof slot.avgGain !== \"number\" ||\n typeof slot.avgLoss !== \"number\" ||\n typeof slot.prevSrc !== \"number\" ||\n typeof slot.prevClosedSrc !== \"number\" ||\n !isFloat64RingBuffer(slot.outBuffer)\n ) {\n return null;\n }\n return {\n kind: \"ta.rsi\",\n length: slot.length,\n outBuffer: serialiseBuffer(slot.outBuffer),\n seedGainSum: finiteOrNull(slot.seedGainSum),\n seedLossSum: finiteOrNull(slot.seedLossSum),\n diffCount: slot.diffCount,\n avgGain: finiteOrNull(slot.avgGain),\n avgLoss: finiteOrNull(slot.avgLoss),\n prevSrc: finiteOrNull(slot.prevSrc),\n prevClosedSrc: finiteOrNull(slot.prevClosedSrc),\n };\n}\n\nfunction restoreSma(snapshot: Readonly<Record<string, unknown>>): RestoredSmaSlot | null {\n const outSnapshot = snapshot.outBuffer;\n const windowSnapshot = snapshot.window;\n if (\n snapshot.kind !== \"ta.sma\" ||\n !isInteger(snapshot.length) ||\n !isBufferSnapshot(outSnapshot) ||\n !isBufferSnapshot(windowSnapshot)\n ) {\n return null;\n }\n const sum = restoreNumber(snapshot.sum);\n if (sum === null) return null;\n const outBuffer = restoreBuffer(outSnapshot, outSnapshot.values.length);\n const window = restoreBuffer(windowSnapshot, snapshot.length);\n if (outBuffer === null || window === null) return null;\n return {\n kind: \"ta.sma\",\n ...baseSlot(outBuffer),\n length: snapshot.length,\n window,\n sum,\n };\n}\n\nfunction restoreEma(snapshot: Readonly<Record<string, unknown>>): RestoredEmaSlot | null {\n const outSnapshot = snapshot.outBuffer;\n if (\n snapshot.kind !== \"ta.ema\" ||\n !isInteger(snapshot.length) ||\n !isInteger(snapshot.seedCount) ||\n !isBufferSnapshot(outSnapshot)\n ) {\n return null;\n }\n const numbers = restoreNumbers({\n alpha: snapshot.alpha,\n seedSum: snapshot.seedSum,\n prevEma: snapshot.prevEma,\n prevClosedEma: snapshot.prevClosedEma,\n });\n if (numbers === null) return null;\n const outBuffer = restoreBuffer(outSnapshot, outSnapshot.values.length);\n if (outBuffer === null) return null;\n return {\n kind: \"ta.ema\",\n ...baseSlot(outBuffer),\n alpha: numbers.alpha,\n length: snapshot.length,\n seedSum: numbers.seedSum,\n seedCount: snapshot.seedCount,\n prevEma: numbers.prevEma,\n prevClosedEma: numbers.prevClosedEma,\n };\n}\n\nfunction restoreRsi(snapshot: Readonly<Record<string, unknown>>): RestoredRsiSlot | null {\n const outSnapshot = snapshot.outBuffer;\n if (\n snapshot.kind !== \"ta.rsi\" ||\n !isInteger(snapshot.length) ||\n !isInteger(snapshot.diffCount) ||\n !isBufferSnapshot(outSnapshot)\n ) {\n return null;\n }\n const numbers = restoreNumbers({\n seedGainSum: snapshot.seedGainSum,\n seedLossSum: snapshot.seedLossSum,\n avgGain: snapshot.avgGain,\n avgLoss: snapshot.avgLoss,\n prevSrc: snapshot.prevSrc,\n prevClosedSrc: snapshot.prevClosedSrc,\n });\n if (numbers === null) return null;\n const outBuffer = restoreBuffer(outSnapshot, outSnapshot.values.length);\n if (outBuffer === null) return null;\n return {\n kind: \"ta.rsi\",\n ...baseSlot(outBuffer),\n length: snapshot.length,\n seedGainSum: numbers.seedGainSum,\n seedLossSum: numbers.seedLossSum,\n diffCount: snapshot.diffCount,\n avgGain: numbers.avgGain,\n avgLoss: numbers.avgLoss,\n prevSrc: numbers.prevSrc,\n prevClosedSrc: numbers.prevClosedSrc,\n };\n}\n\nfunction serialiseTaSlot(slot: unknown): JsonValue | null {\n if (!isRecord(slot)) return null;\n if (slot.kind === \"ta.sma\") return serialiseSma(slot);\n if (slot.kind === \"ta.ema\") return serialiseEma(slot);\n if (slot.kind === \"ta.rsi\") return serialiseRsi(slot);\n return null;\n}\n\nfunction restoreTaSlot(snapshot: unknown): RestoredTaSlot | null {\n if (!isRecord(snapshot)) return null;\n if (snapshot.kind === \"ta.sma\") return restoreSma(snapshot);\n if (snapshot.kind === \"ta.ema\") return restoreEma(snapshot);\n if (snapshot.kind === \"ta.rsi\") return restoreRsi(snapshot);\n return null;\n}\n\n/**\n * Return whether a snapshot slot key belongs to the TA persistence namespace.\n *\n * @since 0.5\n * @internal\n * @stable\n * @formula key.startsWith(\"ta:\")\n * @example\n * isTaSlotSnapshotKey(\"ta:slot#0\"); // true\n */\nexport function isTaSlotSnapshotKey(key: string): boolean {\n return key.startsWith(TA_SLOT_PREFIX);\n}\n\n/**\n * Serialise supported `ta.*` runtime slots into JSON-clean snapshot entries.\n *\n * @since 0.5\n * @internal\n * @stable\n * @formula snapshot[`ta:${slotId}`] = serialise(slot) for supported TA slots\n * @example\n * // const entries = serialiseTaSlots(stream);\n * const entries = {};\n * void entries;\n */\nexport function serialiseTaSlots(stream: StreamState): Readonly<Record<string, JsonValue>> {\n const out: Record<string, JsonValue> = {};\n for (const [slotId, slot] of stream.taSlots.entries()) {\n const snapshot = serialiseTaSlot(slot);\n if (snapshot !== null) {\n out[`${TA_SLOT_PREFIX}${slotId}`] = snapshot;\n }\n }\n return Object.freeze(out);\n}\n\n/**\n * Restore supported `ta.*` runtime slots from namespaced snapshot entries.\n *\n * @since 0.5\n * @internal\n * @stable\n * @formula stream.taSlots[slotId] = restore(snapshot[`ta:${slotId}`])\n * @example\n * // restoreTaSlots(stream, snapshot.slots);\n * const restored = true;\n * void restored;\n */\nexport function restoreTaSlots(\n stream: StreamState,\n slots: Readonly<Record<string, unknown>>,\n): void {\n stream.taSlots.clear();\n for (const [key, value] of Object.entries(slots)) {\n if (!isTaSlotSnapshotKey(key)) continue;\n const slot = restoreTaSlot(value);\n if (slot !== null) {\n stream.taSlots.set(key.slice(TA_SLOT_PREFIX.length), slot);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/ta/persistence.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\nimport type { JsonValue, Series } from \"@invinite-org/chartlang-core\";\n\nimport {\n finiteOrNull,\n isBufferSnapshot,\n isInteger,\n isRecord,\n restoreBuffer,\n restoreNumber,\n serialiseBuffer,\n} from \"../bufferSnapshot.js\";\nimport { Float64RingBuffer } from \"../ringBuffer.js\";\nimport { makeSeriesView } from \"../seriesView.js\";\nimport type { StreamState } from \"../streamState.js\";\n\nconst TA_SLOT_PREFIX = \"ta:\";\n\ntype RestoredBaseSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n};\n\ntype RestoredSmaSlot = RestoredBaseSlot & {\n readonly kind: \"ta.sma\";\n readonly length: number;\n readonly window: Float64RingBuffer;\n sum: number;\n};\n\ntype RestoredEmaSlot = RestoredBaseSlot & {\n readonly kind: \"ta.ema\";\n readonly alpha: number;\n readonly length: number;\n seedSum: number;\n seedCount: number;\n prevEma: number;\n prevClosedEma: number;\n};\n\ntype RestoredRsiSlot = RestoredBaseSlot & {\n readonly kind: \"ta.rsi\";\n readonly length: number;\n seedGainSum: number;\n seedLossSum: number;\n diffCount: number;\n avgGain: number;\n avgLoss: number;\n prevSrc: number;\n prevClosedSrc: number;\n};\n\ntype RestoredTaSlot = RestoredSmaSlot | RestoredEmaSlot | RestoredRsiSlot;\n\n/**\n * Restore a record of named numeric fields, returning `null` if any one\n * field fails {@link restoreNumber}. Lets restore functions read several\n * persisted numbers in one all-or-nothing step instead of an OR-chain of\n * per-field null checks.\n *\n * @internal\n */\nfunction restoreNumbers<K extends string>(\n fields: Readonly<Record<K, unknown>>,\n): Record<K, number> | null {\n const out = {} as Record<K, number>;\n for (const key of Object.keys(fields) as K[]) {\n const restored = restoreNumber(fields[key]);\n if (restored === null) return null;\n out[key] = restored;\n }\n return out;\n}\n\nfunction baseSlot(outBuffer: Float64RingBuffer): RestoredBaseSlot {\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map<number, Series<number>>(),\n };\n}\n\nfunction isFloat64RingBuffer(value: unknown): value is Float64RingBuffer {\n return value instanceof Float64RingBuffer;\n}\n\nfunction serialiseSma(slot: Readonly<Record<string, unknown>>): JsonValue | null {\n if (\n slot.kind !== \"ta.sma\" ||\n typeof slot.length !== \"number\" ||\n typeof slot.sum !== \"number\" ||\n !isFloat64RingBuffer(slot.outBuffer) ||\n !isFloat64RingBuffer(slot.window)\n ) {\n return null;\n }\n return {\n kind: \"ta.sma\",\n length: slot.length,\n outBuffer: serialiseBuffer(slot.outBuffer),\n window: serialiseBuffer(slot.window),\n sum: finiteOrNull(slot.sum),\n };\n}\n\nfunction serialiseEma(slot: Readonly<Record<string, unknown>>): JsonValue | null {\n if (\n slot.kind !== \"ta.ema\" ||\n typeof slot.alpha !== \"number\" ||\n typeof slot.length !== \"number\" ||\n typeof slot.seedSum !== \"number\" ||\n typeof slot.seedCount !== \"number\" ||\n typeof slot.prevEma !== \"number\" ||\n typeof slot.prevClosedEma !== \"number\" ||\n !isFloat64RingBuffer(slot.outBuffer)\n ) {\n return null;\n }\n return {\n kind: \"ta.ema\",\n alpha: finiteOrNull(slot.alpha),\n length: slot.length,\n outBuffer: serialiseBuffer(slot.outBuffer),\n seedSum: finiteOrNull(slot.seedSum),\n seedCount: slot.seedCount,\n prevEma: finiteOrNull(slot.prevEma),\n prevClosedEma: finiteOrNull(slot.prevClosedEma),\n };\n}\n\nfunction serialiseRsi(slot: Readonly<Record<string, unknown>>): JsonValue | null {\n if (\n slot.kind !== \"ta.rsi\" ||\n typeof slot.length !== \"number\" ||\n typeof slot.seedGainSum !== \"number\" ||\n typeof slot.seedLossSum !== \"number\" ||\n typeof slot.diffCount !== \"number\" ||\n typeof slot.avgGain !== \"number\" ||\n typeof slot.avgLoss !== \"number\" ||\n typeof slot.prevSrc !== \"number\" ||\n typeof slot.prevClosedSrc !== \"number\" ||\n !isFloat64RingBuffer(slot.outBuffer)\n ) {\n return null;\n }\n return {\n kind: \"ta.rsi\",\n length: slot.length,\n outBuffer: serialiseBuffer(slot.outBuffer),\n seedGainSum: finiteOrNull(slot.seedGainSum),\n seedLossSum: finiteOrNull(slot.seedLossSum),\n diffCount: slot.diffCount,\n avgGain: finiteOrNull(slot.avgGain),\n avgLoss: finiteOrNull(slot.avgLoss),\n prevSrc: finiteOrNull(slot.prevSrc),\n prevClosedSrc: finiteOrNull(slot.prevClosedSrc),\n };\n}\n\nfunction restoreSma(snapshot: Readonly<Record<string, unknown>>): RestoredSmaSlot | null {\n const outSnapshot = snapshot.outBuffer;\n const windowSnapshot = snapshot.window;\n if (\n snapshot.kind !== \"ta.sma\" ||\n !isInteger(snapshot.length) ||\n !isBufferSnapshot(outSnapshot) ||\n !isBufferSnapshot(windowSnapshot)\n ) {\n return null;\n }\n const sum = restoreNumber(snapshot.sum);\n if (sum === null) return null;\n const outBuffer = restoreBuffer(outSnapshot, outSnapshot.values.length);\n const window = restoreBuffer(windowSnapshot, snapshot.length);\n if (outBuffer === null || window === null) return null;\n return {\n kind: \"ta.sma\",\n ...baseSlot(outBuffer),\n length: snapshot.length,\n window,\n sum,\n };\n}\n\nfunction restoreEma(snapshot: Readonly<Record<string, unknown>>): RestoredEmaSlot | null {\n const outSnapshot = snapshot.outBuffer;\n if (\n snapshot.kind !== \"ta.ema\" ||\n !isInteger(snapshot.length) ||\n !isInteger(snapshot.seedCount) ||\n !isBufferSnapshot(outSnapshot)\n ) {\n return null;\n }\n const numbers = restoreNumbers({\n alpha: snapshot.alpha,\n seedSum: snapshot.seedSum,\n prevEma: snapshot.prevEma,\n prevClosedEma: snapshot.prevClosedEma,\n });\n if (numbers === null) return null;\n const outBuffer = restoreBuffer(outSnapshot, outSnapshot.values.length);\n if (outBuffer === null) return null;\n return {\n kind: \"ta.ema\",\n ...baseSlot(outBuffer),\n alpha: numbers.alpha,\n length: snapshot.length,\n seedSum: numbers.seedSum,\n seedCount: snapshot.seedCount,\n prevEma: numbers.prevEma,\n prevClosedEma: numbers.prevClosedEma,\n };\n}\n\nfunction restoreRsi(snapshot: Readonly<Record<string, unknown>>): RestoredRsiSlot | null {\n const outSnapshot = snapshot.outBuffer;\n if (\n snapshot.kind !== \"ta.rsi\" ||\n !isInteger(snapshot.length) ||\n !isInteger(snapshot.diffCount) ||\n !isBufferSnapshot(outSnapshot)\n ) {\n return null;\n }\n const numbers = restoreNumbers({\n seedGainSum: snapshot.seedGainSum,\n seedLossSum: snapshot.seedLossSum,\n avgGain: snapshot.avgGain,\n avgLoss: snapshot.avgLoss,\n prevSrc: snapshot.prevSrc,\n prevClosedSrc: snapshot.prevClosedSrc,\n });\n if (numbers === null) return null;\n const outBuffer = restoreBuffer(outSnapshot, outSnapshot.values.length);\n if (outBuffer === null) return null;\n return {\n kind: \"ta.rsi\",\n ...baseSlot(outBuffer),\n length: snapshot.length,\n seedGainSum: numbers.seedGainSum,\n seedLossSum: numbers.seedLossSum,\n diffCount: snapshot.diffCount,\n avgGain: numbers.avgGain,\n avgLoss: numbers.avgLoss,\n prevSrc: numbers.prevSrc,\n prevClosedSrc: numbers.prevClosedSrc,\n };\n}\n\nfunction serialiseTaSlot(slot: unknown): JsonValue | null {\n if (!isRecord(slot)) return null;\n if (slot.kind === \"ta.sma\") return serialiseSma(slot);\n if (slot.kind === \"ta.ema\") return serialiseEma(slot);\n if (slot.kind === \"ta.rsi\") return serialiseRsi(slot);\n return null;\n}\n\nfunction restoreTaSlot(snapshot: unknown): RestoredTaSlot | null {\n if (!isRecord(snapshot)) return null;\n if (snapshot.kind === \"ta.sma\") return restoreSma(snapshot);\n if (snapshot.kind === \"ta.ema\") return restoreEma(snapshot);\n if (snapshot.kind === \"ta.rsi\") return restoreRsi(snapshot);\n return null;\n}\n\n/**\n * Return whether a snapshot slot key belongs to the TA persistence namespace.\n *\n * @since 0.5\n * @internal\n * @stable\n * @formula key.startsWith(\"ta:\")\n * @example\n * isTaSlotSnapshotKey(\"ta:slot#0\"); // true\n */\nexport function isTaSlotSnapshotKey(key: string): boolean {\n return key.startsWith(TA_SLOT_PREFIX);\n}\n\n/**\n * Serialise supported `ta.*` runtime slots into JSON-clean snapshot entries.\n *\n * @since 0.5\n * @internal\n * @stable\n * @formula snapshot[`ta:${slotId}`] = serialise(slot) for supported TA slots\n * @example\n * // const entries = serialiseTaSlots(stream);\n * const entries = {};\n * void entries;\n */\nexport function serialiseTaSlots(stream: StreamState): Readonly<Record<string, JsonValue>> {\n const out: Record<string, JsonValue> = {};\n for (const [slotId, slot] of stream.taSlots.entries()) {\n const snapshot = serialiseTaSlot(slot);\n if (snapshot !== null) {\n out[`${TA_SLOT_PREFIX}${slotId}`] = snapshot;\n }\n }\n return Object.freeze(out);\n}\n\n/**\n * Restore supported `ta.*` runtime slots from namespaced snapshot entries.\n *\n * @since 0.5\n * @internal\n * @stable\n * @formula stream.taSlots[slotId] = restore(snapshot[`ta:${slotId}`])\n * @example\n * // restoreTaSlots(stream, snapshot.slots);\n * const restored = true;\n * void restored;\n */\nexport function restoreTaSlots(\n stream: StreamState,\n slots: Readonly<Record<string, unknown>>,\n): void {\n stream.taSlots.clear();\n for (const [key, value] of Object.entries(slots)) {\n if (!isTaSlotSnapshotKey(key)) continue;\n const slot = restoreTaSlot(value);\n if (slot !== null) {\n stream.taSlots.set(key.slice(TA_SLOT_PREFIX.length), slot);\n }\n }\n}\n"]}
package/dist/ta/ppo.d.ts CHANGED
@@ -24,8 +24,9 @@ import { type ScalarOrSeries } from "./lib/sourceValue.js";
24
24
  * @since 0.2
25
25
  * @stable
26
26
  *
27
- * `opts.offset` shifts all three outputs in lockstep
28
- * `series.current` on each output returns the value `offset` bars ago.
27
+ * `opts.offset` is a presentation display shift carried to the plot
28
+ * emission as `xShift` for all three outputs in lockstep (`+n` right / future,
29
+ * `−n` left / past); the series values are unshifted.
29
30
  *
30
31
  * @example
31
32
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"ppo.d.ts","sourceRoot":"","sources":["../../src/ta/ppo.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAU,MAAM,8BAA8B,CAAC;AAM/E,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AAqE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAiCrF"}
1
+ {"version":3,"file":"ppo.d.ts","sourceRoot":"","sources":["../../src/ta/ppo.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAU,MAAM,8BAA8B,CAAC;AAM/E,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AAqE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAiCrF"}
package/dist/ta/ppo.js CHANGED
@@ -84,8 +84,9 @@ function ppoValue(fast, slow) {
84
84
  * @since 0.2
85
85
  * @stable
86
86
  *
87
- * `opts.offset` shifts all three outputs in lockstep
88
- * `series.current` on each output returns the value `offset` bars ago.
87
+ * `opts.offset` is a presentation display shift carried to the plot
88
+ * emission as `xShift` for all three outputs in lockstep (`+n` right / future,
89
+ * `−n` left / past); the series values are unshifted.
89
90
  *
90
91
  * @example
91
92
  * // import { ta } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"ppo.js","sourceRoot":"","sources":["../../src/ta/ppo.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/ppo.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 EMA copy\n// onto the canonical `ta.ema` primitive via three sub-slots\n// (`${slotId}/fast`, `${slotId}/slow`, `${slotId}/signal`) — same\n// composition pattern as `ta.macd`.\n\nimport type { PpoOpts, PpoResult, 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 { ema } from \"./ema.js\";\nimport { type ScalarOrSeries, readSourceValue } from \"./lib/sourceValue.js\";\n\nconst DEFAULT_FAST = 12;\nconst DEFAULT_SLOW = 26;\nconst DEFAULT_SIGNAL = 9;\n\ntype PpoSlot = {\n readonly result: PpoResult;\n readonly ppoBuf: Float64RingBuffer;\n readonly histBuf: Float64RingBuffer;\n /**\n * Reference to the signal-EMA sub-slot's output ring buffer.\n * Captured at first call so per-offset shifted signal views can be\n * constructed without re-entering `ema()` (which would double-\n * advance the sub-slot's compute on every bar). Same pattern as\n * `macd.ts`.\n */\n readonly signalBuf: Float64RingBuffer;\n /** Per-offset frozen `PpoResult` cache (identity-preserving for `offset === 0`). */\n readonly shiftedResults: Map<number, PpoResult>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.ppo called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(\n capacity: number,\n signalSeries: Series<number>,\n signalBuf: Float64RingBuffer,\n): PpoSlot {\n const ppoBuf = new Float64RingBuffer(capacity);\n const histBuf = new Float64RingBuffer(capacity);\n return {\n result: Object.freeze({\n ppo: makeSeriesView<number>(ppoBuf),\n signal: signalSeries,\n hist: makeSeriesView<number>(histBuf),\n }),\n ppoBuf,\n histBuf,\n signalBuf,\n shiftedResults: new Map(),\n };\n}\n\nfunction resultForOffset(slot: PpoSlot, offset: number): PpoResult {\n if (offset === 0) return slot.result;\n let cached = slot.shiftedResults.get(offset);\n if (cached === undefined) {\n cached = Object.freeze({\n ppo: makeShiftedSeriesView<number>(slot.ppoBuf, offset),\n signal: makeShiftedSeriesView<number>(slot.signalBuf, offset),\n hist: makeShiftedSeriesView<number>(slot.histBuf, offset),\n });\n slot.shiftedResults.set(offset, cached);\n }\n return cached;\n}\n\nfunction ppoValue(fast: number, slow: number): number {\n if (!Number.isFinite(fast) || !Number.isFinite(slow) || slow === 0) return Number.NaN;\n return (100 * (fast - slow)) / slow;\n}\n\n/**\n * Percentage Price Oscillator — same shape as MACD but normalised by\n * the slow EMA so the histogram + lines are scale-invariant across\n * symbols. Composes three `ta.ema` sub-slots (`${slotId}/fast`,\n * `${slotId}/slow`, `${slotId}/signal`); a fix to `ema` flows into\n * PPO for free. Multi-output: `{ ppo, signal, hist }`. The registry\n * records `primarySeriesKey: \"ppo\"`, `visibleSeriesKeys: [\"ppo\",\n * \"signal\", \"hist\"]`, and `yDomain: { kind: \"auto\" }` via\n * `TA_REGISTRY_METADATA`.\n *\n * Defaults `{ fastLength: 12, slowLength: 26, signalLength: 9 }`\n * (Appel-era — matches MACD). `slow === 0` (degenerate price stream)\n * emits `NaN` at the PPO line, which propagates to hist; signal can\n * still be defined off prior PPO values.\n *\n * @formula fast = ema(source, fastLength) ;\n * slow = ema(source, slowLength) ;\n * ppo = 100 · (fast − slow) / slow ; NaN if slow === 0 ;\n * signal = ema(ppo, signalLength) ;\n * hist = ppo − signal\n * @warmup slowLength + signalLength − 2\n * @since 0.2\n * @stable\n *\n * `opts.offset` shifts all three outputs in lockstep —\n * `series.current` on each output returns the value `offset` bars ago.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const p = ta.ppo(\"slot\", bar.close);\n * // const head = p.hist.current;\n * // const lagged = ta.ppo(\"slot2\", bar.close, { offset: 5 });\n */\nexport function ppo(slotId: string, source: ScalarOrSeries, opts?: PpoOpts): PpoResult {\n const ctx = getCtx();\n const fastLength = opts?.fastLength ?? DEFAULT_FAST;\n const slowLength = opts?.slowLength ?? DEFAULT_SLOW;\n const signalLength = opts?.signalLength ?? DEFAULT_SIGNAL;\n const offset = opts?.offset ?? 0;\n const signalSlotId = `${slotId}/signal`;\n const src = readSourceValue(source);\n const fastSeries = ema(`${slotId}/fast`, src, fastLength);\n const slowSeries = ema(`${slotId}/slow`, src, slowLength);\n const pv = ppoValue(fastSeries.current, slowSeries.current);\n // Feed PPO scalar into the signal EMA. Always call with the\n // un-shifted (default) view — offset shifting for the composite\n // PpoResult happens via `resultForOffset`, which reads directly off\n // the signal-EMA's outBuffer (captured below).\n const signalSeries = ema(signalSlotId, pv, signalLength);\n\n let slot = ctx.stream.taSlots.get(slotId) as PpoSlot | undefined;\n if (slot === undefined) {\n const emaSlot = ctx.stream.taSlots.get(signalSlotId) as { outBuffer: Float64RingBuffer };\n slot = initSlot(ctx.stream.ohlcv.close.capacity, signalSeries, emaSlot.outBuffer);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const sig = signalSeries.current;\n const histValue = Number.isFinite(pv) && Number.isFinite(sig) ? pv - sig : Number.NaN;\n if (ctx.isTick) {\n slot.ppoBuf.replaceHead(pv);\n slot.histBuf.replaceHead(histValue);\n } else {\n slot.ppoBuf.append(pv);\n slot.histBuf.append(histValue);\n }\n return resultForOffset(slot, offset);\n}\n"]}
1
+ {"version":3,"file":"ppo.js","sourceRoot":"","sources":["../../src/ta/ppo.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/ppo.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 EMA copy\n// onto the canonical `ta.ema` primitive via three sub-slots\n// (`${slotId}/fast`, `${slotId}/slow`, `${slotId}/signal`) — same\n// composition pattern as `ta.macd`.\n\nimport type { PpoOpts, PpoResult, 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 { ema } from \"./ema.js\";\nimport { type ScalarOrSeries, readSourceValue } from \"./lib/sourceValue.js\";\n\nconst DEFAULT_FAST = 12;\nconst DEFAULT_SLOW = 26;\nconst DEFAULT_SIGNAL = 9;\n\ntype PpoSlot = {\n readonly result: PpoResult;\n readonly ppoBuf: Float64RingBuffer;\n readonly histBuf: Float64RingBuffer;\n /**\n * Reference to the signal-EMA sub-slot's output ring buffer.\n * Captured at first call so per-offset shifted signal views can be\n * constructed without re-entering `ema()` (which would double-\n * advance the sub-slot's compute on every bar). Same pattern as\n * `macd.ts`.\n */\n readonly signalBuf: Float64RingBuffer;\n /** Per-offset frozen `PpoResult` cache (identity-preserving for `offset === 0`). */\n readonly shiftedResults: Map<number, PpoResult>;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.ppo called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(\n capacity: number,\n signalSeries: Series<number>,\n signalBuf: Float64RingBuffer,\n): PpoSlot {\n const ppoBuf = new Float64RingBuffer(capacity);\n const histBuf = new Float64RingBuffer(capacity);\n return {\n result: Object.freeze({\n ppo: makeSeriesView<number>(ppoBuf),\n signal: signalSeries,\n hist: makeSeriesView<number>(histBuf),\n }),\n ppoBuf,\n histBuf,\n signalBuf,\n shiftedResults: new Map(),\n };\n}\n\nfunction resultForOffset(slot: PpoSlot, offset: number): PpoResult {\n if (offset === 0) return slot.result;\n let cached = slot.shiftedResults.get(offset);\n if (cached === undefined) {\n cached = Object.freeze({\n ppo: makeShiftedSeriesView<number>(slot.ppoBuf, offset),\n signal: makeShiftedSeriesView<number>(slot.signalBuf, offset),\n hist: makeShiftedSeriesView<number>(slot.histBuf, offset),\n });\n slot.shiftedResults.set(offset, cached);\n }\n return cached;\n}\n\nfunction ppoValue(fast: number, slow: number): number {\n if (!Number.isFinite(fast) || !Number.isFinite(slow) || slow === 0) return Number.NaN;\n return (100 * (fast - slow)) / slow;\n}\n\n/**\n * Percentage Price Oscillator — same shape as MACD but normalised by\n * the slow EMA so the histogram + lines are scale-invariant across\n * symbols. Composes three `ta.ema` sub-slots (`${slotId}/fast`,\n * `${slotId}/slow`, `${slotId}/signal`); a fix to `ema` flows into\n * PPO for free. Multi-output: `{ ppo, signal, hist }`. The registry\n * records `primarySeriesKey: \"ppo\"`, `visibleSeriesKeys: [\"ppo\",\n * \"signal\", \"hist\"]`, and `yDomain: { kind: \"auto\" }` via\n * `TA_REGISTRY_METADATA`.\n *\n * Defaults `{ fastLength: 12, slowLength: 26, signalLength: 9 }`\n * (Appel-era — matches MACD). `slow === 0` (degenerate price stream)\n * emits `NaN` at the PPO line, which propagates to hist; signal can\n * still be defined off prior PPO values.\n *\n * @formula fast = ema(source, fastLength) ;\n * slow = ema(source, slowLength) ;\n * ppo = 100 · (fast − slow) / slow ; NaN if slow === 0 ;\n * signal = ema(ppo, signalLength) ;\n * hist = ppo − signal\n * @warmup slowLength + signalLength − 2\n * @since 0.2\n * @stable\n *\n * `opts.offset` is a presentation display shift carried to the plot\n * emission as `xShift` for all three outputs in lockstep (`+n` right / future,\n * `−n` left / past); the series values are unshifted.\n *\n * @example\n * // import { ta } from \"@invinite-org/chartlang-runtime\";\n * // const p = ta.ppo(\"slot\", bar.close);\n * // const head = p.hist.current;\n * // const lagged = ta.ppo(\"slot2\", bar.close, { offset: 5 });\n */\nexport function ppo(slotId: string, source: ScalarOrSeries, opts?: PpoOpts): PpoResult {\n const ctx = getCtx();\n const fastLength = opts?.fastLength ?? DEFAULT_FAST;\n const slowLength = opts?.slowLength ?? DEFAULT_SLOW;\n const signalLength = opts?.signalLength ?? DEFAULT_SIGNAL;\n const offset = opts?.offset ?? 0;\n const signalSlotId = `${slotId}/signal`;\n const src = readSourceValue(source);\n const fastSeries = ema(`${slotId}/fast`, src, fastLength);\n const slowSeries = ema(`${slotId}/slow`, src, slowLength);\n const pv = ppoValue(fastSeries.current, slowSeries.current);\n // Feed PPO scalar into the signal EMA. Always call with the\n // un-shifted (default) view — offset shifting for the composite\n // PpoResult happens via `resultForOffset`, which reads directly off\n // the signal-EMA's outBuffer (captured below).\n const signalSeries = ema(signalSlotId, pv, signalLength);\n\n let slot = ctx.stream.taSlots.get(slotId) as PpoSlot | undefined;\n if (slot === undefined) {\n const emaSlot = ctx.stream.taSlots.get(signalSlotId) as { outBuffer: Float64RingBuffer };\n slot = initSlot(ctx.stream.ohlcv.close.capacity, signalSeries, emaSlot.outBuffer);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const sig = signalSeries.current;\n const histValue = Number.isFinite(pv) && Number.isFinite(sig) ? pv - sig : Number.NaN;\n if (ctx.isTick) {\n slot.ppoBuf.replaceHead(pv);\n slot.histBuf.replaceHead(histValue);\n } else {\n slot.ppoBuf.append(pv);\n slot.histBuf.append(histValue);\n }\n return resultForOffset(slot, offset);\n}\n"]}
package/dist/ta/pvi.d.ts CHANGED
@@ -21,7 +21,9 @@ import type { PviOpts, Series } from "@invinite-org/chartlang-core";
21
21
  * @since 0.2
22
22
  * @stable
23
23
  *
24
- * `opts.offset` shifts the returned series.
24
+ * `opts.offset` is a presentation display shift carried to the plot
25
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
26
+ * series value is unshifted.
25
27
  *
26
28
  * @example
27
29
  * // import { ta, plot } from "@invinite-org/chartlang-core";
@@ -1 +1 @@
1
- {"version":3,"file":"pvi.d.ts","sourceRoot":"","sources":["../../src/ta/pvi.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAsFpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CA+BlE"}
1
+ {"version":3,"file":"pvi.d.ts","sourceRoot":"","sources":["../../src/ta/pvi.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAsFpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CA+BlE"}
package/dist/ta/pvi.js CHANGED
@@ -93,7 +93,9 @@ function fold(inValue, inPrevClose, inPrevVolume, close, volume) {
93
93
  * @since 0.2
94
94
  * @stable
95
95
  *
96
- * `opts.offset` shifts the returned series.
96
+ * `opts.offset` is a presentation display shift carried to the plot
97
+ * emission as `xShift` (`+n` right / future, `−n` left / past); the
98
+ * series value is unshifted.
97
99
  *
98
100
  * @example
99
101
  * // import { ta, plot } from "@invinite-org/chartlang-core";
@@ -1 +1 @@
1
- {"version":3,"file":"pvi.js","sourceRoot":"","sources":["../../src/ta/pvi.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/pvi.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. Mirror of `nvi.ts` — same recurrence, the\n// only difference is the volume-comparison predicate (`>` vs `<`).\n// Kept as parallel files (rather than a shared helper) so each\n// primitive has its own slot type + JSDoc + per-callsite tests.\n\nimport type { PviOpts, 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 * Anchored seed value at bar 0 (and after any bar where the\n * accumulator has been carried-forward through NaN inputs).\n *\n * @anchors seedValue\n */\nconst SEED_VALUE = 1000;\n\ntype PviSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n value: number;\n prevClose: number;\n prevVolume: number;\n prevClosedValue: number;\n prevClosedPrevClose: number;\n prevClosedPrevVolume: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.pvi called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): PviSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n value: SEED_VALUE,\n prevClose: Number.NaN,\n prevVolume: Number.NaN,\n prevClosedValue: SEED_VALUE,\n prevClosedPrevClose: Number.NaN,\n prevClosedPrevVolume: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: PviSlot, 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\nfunction fold(\n inValue: number,\n inPrevClose: number,\n inPrevVolume: number,\n close: number,\n volume: number,\n): { value: number; prevClose: number; prevVolume: number } {\n if (!Number.isFinite(close)) {\n return { value: inValue, prevClose: inPrevClose, prevVolume: inPrevVolume };\n }\n const v = safeVol(volume);\n if (!Number.isFinite(inPrevClose)) {\n return { value: inValue, prevClose: close, prevVolume: v };\n }\n const pv = safeVol(inPrevVolume);\n const shouldUpdate = v > pv;\n if (!shouldUpdate || inPrevClose === 0) {\n return { value: inValue, prevClose: close, prevVolume: v };\n }\n const next = inValue * (1 + (close - inPrevClose) / inPrevClose);\n return { value: next, prevClose: close, prevVolume: v };\n}\n\n/**\n * Positive Volume Index — mirror of {@link nvi}. Cumulative percentage-\n * change in close on bars whose volume is strictly HIGHER than the\n * prior bar's; bars with equal-or-lower volume carry the prior PVI\n * value unchanged. Seeded at 1000.\n *\n * NaN volume is treated as 0 (matches invinite's `safeVolume` shape);\n * NaN close carries the accumulator forward without advancing\n * `prevClose`.\n *\n * **Tick mode.** Replays the head bar's contribution against a\n * snapshot of the prior-close `(value, prevClose, prevVolume)` tuple.\n *\n * @formula pvi[0] = 1000 ;\n * pvi[t] = (volume[t] > volume[t − 1] && prevClose != 0)\n * ? pvi[t − 1] · (1 + (close[t] − close[t − 1]) / close[t − 1])\n * : pvi[t − 1]\n * @warmup 1 (bar 0 emits the 1000 seed)\n * @anchors seedValue\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 p = ta.pvi();\n * // plot(p);\n */\nexport function pvi(slotId: string, opts?: PviOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as PviSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const offset = opts?.offset ?? 0;\n const { close, volume } = ctx.stream.bar;\n\n if (ctx.isTick) {\n const next = fold(\n slot.prevClosedValue,\n slot.prevClosedPrevClose,\n slot.prevClosedPrevVolume,\n close,\n volume,\n );\n slot.outBuffer.replaceHead(next.value);\n return viewForOffset(slot, offset);\n }\n\n slot.prevClosedValue = slot.value;\n slot.prevClosedPrevClose = slot.prevClose;\n slot.prevClosedPrevVolume = slot.prevVolume;\n const next = fold(slot.value, slot.prevClose, slot.prevVolume, close, volume);\n slot.value = next.value;\n slot.prevClose = next.prevClose;\n slot.prevVolume = next.prevVolume;\n slot.outBuffer.append(slot.value);\n return viewForOffset(slot, offset);\n}\n"]}
1
+ {"version":3,"file":"pvi.js","sourceRoot":"","sources":["../../src/ta/pvi.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/pvi.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. Mirror of `nvi.ts` — same recurrence, the\n// only difference is the volume-comparison predicate (`>` vs `<`).\n// Kept as parallel files (rather than a shared helper) so each\n// primitive has its own slot type + JSDoc + per-callsite tests.\n\nimport type { PviOpts, 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 * Anchored seed value at bar 0 (and after any bar where the\n * accumulator has been carried-forward through NaN inputs).\n *\n * @anchors seedValue\n */\nconst SEED_VALUE = 1000;\n\ntype PviSlot = {\n readonly outBuffer: Float64RingBuffer;\n readonly series: Series<number>;\n readonly shiftedViews: Map<number, Series<number>>;\n value: number;\n prevClose: number;\n prevVolume: number;\n prevClosedValue: number;\n prevClosedPrevClose: number;\n prevClosedPrevVolume: number;\n};\n\nfunction getCtx(): RuntimeContext {\n const ctx = ACTIVE_RUNTIME_CONTEXT.current;\n if (ctx === null) {\n throw new Error(\"ta.pvi called outside an active script step\");\n }\n return ctx;\n}\n\nfunction initSlot(capacity: number): PviSlot {\n const outBuffer = new Float64RingBuffer(capacity);\n return {\n outBuffer,\n series: makeSeriesView<number>(outBuffer),\n shiftedViews: new Map(),\n value: SEED_VALUE,\n prevClose: Number.NaN,\n prevVolume: Number.NaN,\n prevClosedValue: SEED_VALUE,\n prevClosedPrevClose: Number.NaN,\n prevClosedPrevVolume: Number.NaN,\n };\n}\n\nfunction viewForOffset(slot: PviSlot, 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\nfunction fold(\n inValue: number,\n inPrevClose: number,\n inPrevVolume: number,\n close: number,\n volume: number,\n): { value: number; prevClose: number; prevVolume: number } {\n if (!Number.isFinite(close)) {\n return { value: inValue, prevClose: inPrevClose, prevVolume: inPrevVolume };\n }\n const v = safeVol(volume);\n if (!Number.isFinite(inPrevClose)) {\n return { value: inValue, prevClose: close, prevVolume: v };\n }\n const pv = safeVol(inPrevVolume);\n const shouldUpdate = v > pv;\n if (!shouldUpdate || inPrevClose === 0) {\n return { value: inValue, prevClose: close, prevVolume: v };\n }\n const next = inValue * (1 + (close - inPrevClose) / inPrevClose);\n return { value: next, prevClose: close, prevVolume: v };\n}\n\n/**\n * Positive Volume Index — mirror of {@link nvi}. Cumulative percentage-\n * change in close on bars whose volume is strictly HIGHER than the\n * prior bar's; bars with equal-or-lower volume carry the prior PVI\n * value unchanged. Seeded at 1000.\n *\n * NaN volume is treated as 0 (matches invinite's `safeVolume` shape);\n * NaN close carries the accumulator forward without advancing\n * `prevClose`.\n *\n * **Tick mode.** Replays the head bar's contribution against a\n * snapshot of the prior-close `(value, prevClose, prevVolume)` tuple.\n *\n * @formula pvi[0] = 1000 ;\n * pvi[t] = (volume[t] > volume[t − 1] && prevClose != 0)\n * ? pvi[t − 1] · (1 + (close[t] − close[t − 1]) / close[t − 1])\n * : pvi[t − 1]\n * @warmup 1 (bar 0 emits the 1000 seed)\n * @anchors seedValue\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 p = ta.pvi();\n * // plot(p);\n */\nexport function pvi(slotId: string, opts?: PviOpts): Series<number> {\n const ctx = getCtx();\n let slot = ctx.stream.taSlots.get(slotId) as PviSlot | undefined;\n if (slot === undefined) {\n slot = initSlot(ctx.stream.ohlcv.close.capacity);\n ctx.stream.taSlots.set(slotId, slot);\n }\n const offset = opts?.offset ?? 0;\n const { close, volume } = ctx.stream.bar;\n\n if (ctx.isTick) {\n const next = fold(\n slot.prevClosedValue,\n slot.prevClosedPrevClose,\n slot.prevClosedPrevVolume,\n close,\n volume,\n );\n slot.outBuffer.replaceHead(next.value);\n return viewForOffset(slot, offset);\n }\n\n slot.prevClosedValue = slot.value;\n slot.prevClosedPrevClose = slot.prevClose;\n slot.prevClosedPrevVolume = slot.prevVolume;\n const next = fold(slot.value, slot.prevClose, slot.prevVolume, close, volume);\n slot.value = next.value;\n slot.prevClose = next.prevClose;\n slot.prevVolume = next.prevVolume;\n slot.outBuffer.append(slot.value);\n return viewForOffset(slot, offset);\n}\n"]}
package/dist/ta/pvo.d.ts CHANGED
@@ -23,7 +23,9 @@ import type { PvoOpts, PvoResult } from "@invinite-org/chartlang-core";
23
23
  * @since 0.2
24
24
  * @stable
25
25
  *
26
- * `opts.offset` shifts all three outputs in lockstep.
26
+ * `opts.offset` is a presentation display shift carried to the plot
27
+ * emission as `xShift` for all three outputs in lockstep; it does NOT
28
+ * transform the values (`series.current` is unshifted).
27
29
  *
28
30
  * @example
29
31
  * // import { ta, plot } from "@invinite-org/chartlang-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"pvo.d.ts","sourceRoot":"","sources":["../../src/ta/pvo.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAU,MAAM,8BAA8B,CAAC;AAwE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CA6B7D"}
1
+ {"version":3,"file":"pvo.d.ts","sourceRoot":"","sources":["../../src/ta/pvo.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAU,MAAM,8BAA8B,CAAC;AAwE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CA6B7D"}
package/dist/ta/pvo.js CHANGED
@@ -83,7 +83,9 @@ function pvoValue(fast, slow) {
83
83
  * @since 0.2
84
84
  * @stable
85
85
  *
86
- * `opts.offset` shifts all three outputs in lockstep.
86
+ * `opts.offset` is a presentation display shift carried to the plot
87
+ * emission as `xShift` for all three outputs in lockstep; it does NOT
88
+ * transform the values (`series.current` is unshifted).
87
89
  *
88
90
  * @example
89
91
  * // import { ta, plot } from "@invinite-org/chartlang-runtime";