@363045841yyt/klinechart-core 0.7.8 → 0.7.10

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 (251) hide show
  1. package/dist/controllers/index.d.ts +1 -1
  2. package/dist/controllers/index.d.ts.map +1 -1
  3. package/dist/controllers/index.js.map +1 -1
  4. package/dist/engine/chart.d.ts.map +1 -1
  5. package/dist/engine/chart.js +41 -229
  6. package/dist/engine/chart.js.map +1 -1
  7. package/dist/engine/indicators/indicatorDefinitionRegistry.d.ts +10 -3
  8. package/dist/engine/indicators/indicatorDefinitionRegistry.d.ts.map +1 -1
  9. package/dist/engine/indicators/indicatorDefinitionRegistry.js +29 -2
  10. package/dist/engine/indicators/indicatorDefinitionRegistry.js.map +1 -1
  11. package/dist/engine/indicators/indicatorMetadata.d.ts +79 -2
  12. package/dist/engine/indicators/indicatorMetadata.d.ts.map +1 -1
  13. package/dist/engine/indicators/indicatorMetadata.js.map +1 -1
  14. package/dist/engine/indicators/indicatorRegistry.d.ts +5 -0
  15. package/dist/engine/indicators/indicatorRegistry.d.ts.map +1 -1
  16. package/dist/engine/indicators/indicatorRegistry.js +49 -5
  17. package/dist/engine/indicators/indicatorRegistry.js.map +1 -1
  18. package/dist/engine/indicators/registerBuiltins.d.ts +42 -0
  19. package/dist/engine/indicators/registerBuiltins.d.ts.map +1 -0
  20. package/dist/engine/indicators/registerBuiltins.js +46 -0
  21. package/dist/engine/indicators/registerBuiltins.js.map +1 -0
  22. package/dist/engine/indicators/scheduler.d.ts +2 -2
  23. package/dist/engine/indicators/scheduler.d.ts.map +1 -1
  24. package/dist/engine/indicators/scheduler.js +41 -11
  25. package/dist/engine/indicators/scheduler.js.map +1 -1
  26. package/dist/engine/indicators/stateComposer.d.ts +6 -4
  27. package/dist/engine/indicators/stateComposer.d.ts.map +1 -1
  28. package/dist/engine/indicators/stateComposer.js +80 -984
  29. package/dist/engine/indicators/stateComposer.js.map +1 -1
  30. package/dist/engine/indicators/visibleStateComposers.d.ts +145 -0
  31. package/dist/engine/indicators/visibleStateComposers.d.ts.map +1 -0
  32. package/dist/engine/indicators/visibleStateComposers.js +469 -0
  33. package/dist/engine/indicators/visibleStateComposers.js.map +1 -0
  34. package/dist/engine/renderers/Indicator/atr.d.ts.map +1 -1
  35. package/dist/engine/renderers/Indicator/atr.js +8 -0
  36. package/dist/engine/renderers/Indicator/atr.js.map +1 -1
  37. package/dist/engine/renderers/Indicator/boll.d.ts.map +1 -1
  38. package/dist/engine/renderers/Indicator/boll.js +47 -0
  39. package/dist/engine/renderers/Indicator/boll.js.map +1 -1
  40. package/dist/engine/renderers/Indicator/cci.d.ts.map +1 -1
  41. package/dist/engine/renderers/Indicator/cci.js +8 -1
  42. package/dist/engine/renderers/Indicator/cci.js.map +1 -1
  43. package/dist/engine/renderers/Indicator/chaikinVol.d.ts.map +1 -1
  44. package/dist/engine/renderers/Indicator/chaikinVol.js +7 -1
  45. package/dist/engine/renderers/Indicator/chaikinVol.js.map +1 -1
  46. package/dist/engine/renderers/Indicator/cmf.d.ts.map +1 -1
  47. package/dist/engine/renderers/Indicator/cmf.js +7 -1
  48. package/dist/engine/renderers/Indicator/cmf.js.map +1 -1
  49. package/dist/engine/renderers/Indicator/dema.d.ts.map +1 -1
  50. package/dist/engine/renderers/Indicator/dema.js +8 -1
  51. package/dist/engine/renderers/Indicator/dema.js.map +1 -1
  52. package/dist/engine/renderers/Indicator/donchian.d.ts.map +1 -1
  53. package/dist/engine/renderers/Indicator/donchian.js +8 -1
  54. package/dist/engine/renderers/Indicator/donchian.js.map +1 -1
  55. package/dist/engine/renderers/Indicator/ene.d.ts.map +1 -1
  56. package/dist/engine/renderers/Indicator/ene.js +45 -0
  57. package/dist/engine/renderers/Indicator/ene.js.map +1 -1
  58. package/dist/engine/renderers/Indicator/expma.d.ts.map +1 -1
  59. package/dist/engine/renderers/Indicator/expma.js +45 -0
  60. package/dist/engine/renderers/Indicator/expma.js.map +1 -1
  61. package/dist/engine/renderers/Indicator/fastk.d.ts.map +1 -1
  62. package/dist/engine/renderers/Indicator/fastk.js +8 -1
  63. package/dist/engine/renderers/Indicator/fastk.js.map +1 -1
  64. package/dist/engine/renderers/Indicator/fib.d.ts.map +1 -1
  65. package/dist/engine/renderers/Indicator/fib.js +8 -1
  66. package/dist/engine/renderers/Indicator/fib.js.map +1 -1
  67. package/dist/engine/renderers/Indicator/hma.d.ts.map +1 -1
  68. package/dist/engine/renderers/Indicator/hma.js +8 -1
  69. package/dist/engine/renderers/Indicator/hma.js.map +1 -1
  70. package/dist/engine/renderers/Indicator/hv.d.ts.map +1 -1
  71. package/dist/engine/renderers/Indicator/hv.js +7 -0
  72. package/dist/engine/renderers/Indicator/hv.js.map +1 -1
  73. package/dist/engine/renderers/Indicator/ichimoku.d.ts.map +1 -1
  74. package/dist/engine/renderers/Indicator/ichimoku.js +8 -1
  75. package/dist/engine/renderers/Indicator/ichimoku.js.map +1 -1
  76. package/dist/engine/renderers/Indicator/index.d.ts +7 -2
  77. package/dist/engine/renderers/Indicator/index.d.ts.map +1 -1
  78. package/dist/engine/renderers/Indicator/index.js +2 -114
  79. package/dist/engine/renderers/Indicator/index.js.map +1 -1
  80. package/dist/engine/renderers/Indicator/kama.d.ts.map +1 -1
  81. package/dist/engine/renderers/Indicator/kama.js +8 -1
  82. package/dist/engine/renderers/Indicator/kama.js.map +1 -1
  83. package/dist/engine/renderers/Indicator/keltner.d.ts.map +1 -1
  84. package/dist/engine/renderers/Indicator/keltner.js +8 -1
  85. package/dist/engine/renderers/Indicator/keltner.js.map +1 -1
  86. package/dist/engine/renderers/Indicator/kst.d.ts.map +1 -1
  87. package/dist/engine/renderers/Indicator/kst.js +8 -0
  88. package/dist/engine/renderers/Indicator/kst.js.map +1 -1
  89. package/dist/engine/renderers/Indicator/ma.d.ts.map +1 -1
  90. package/dist/engine/renderers/Indicator/ma.js +63 -0
  91. package/dist/engine/renderers/Indicator/ma.js.map +1 -1
  92. package/dist/engine/renderers/Indicator/macd.d.ts.map +1 -1
  93. package/dist/engine/renderers/Indicator/macd.js +8 -1
  94. package/dist/engine/renderers/Indicator/macd.js.map +1 -1
  95. package/dist/engine/renderers/Indicator/mfi.d.ts.map +1 -1
  96. package/dist/engine/renderers/Indicator/mfi.js +7 -1
  97. package/dist/engine/renderers/Indicator/mfi.js.map +1 -1
  98. package/dist/engine/renderers/Indicator/mom.d.ts.map +1 -1
  99. package/dist/engine/renderers/Indicator/mom.js +8 -0
  100. package/dist/engine/renderers/Indicator/mom.js.map +1 -1
  101. package/dist/engine/renderers/Indicator/obv.d.ts.map +1 -1
  102. package/dist/engine/renderers/Indicator/obv.js +7 -1
  103. package/dist/engine/renderers/Indicator/obv.js.map +1 -1
  104. package/dist/engine/renderers/Indicator/parkinson.d.ts.map +1 -1
  105. package/dist/engine/renderers/Indicator/parkinson.js +7 -0
  106. package/dist/engine/renderers/Indicator/parkinson.js.map +1 -1
  107. package/dist/engine/renderers/Indicator/pivot.d.ts.map +1 -1
  108. package/dist/engine/renderers/Indicator/pivot.js +8 -1
  109. package/dist/engine/renderers/Indicator/pivot.js.map +1 -1
  110. package/dist/engine/renderers/Indicator/pvt.d.ts.map +1 -1
  111. package/dist/engine/renderers/Indicator/pvt.js +7 -1
  112. package/dist/engine/renderers/Indicator/pvt.js.map +1 -1
  113. package/dist/engine/renderers/Indicator/roc.d.ts.map +1 -1
  114. package/dist/engine/renderers/Indicator/roc.js +7 -1
  115. package/dist/engine/renderers/Indicator/roc.js.map +1 -1
  116. package/dist/engine/renderers/Indicator/rsi.d.ts.map +1 -1
  117. package/dist/engine/renderers/Indicator/rsi.js +8 -1
  118. package/dist/engine/renderers/Indicator/rsi.js.map +1 -1
  119. package/dist/engine/renderers/Indicator/sar.d.ts.map +1 -1
  120. package/dist/engine/renderers/Indicator/sar.js +8 -1
  121. package/dist/engine/renderers/Indicator/sar.js.map +1 -1
  122. package/dist/engine/renderers/Indicator/stoch.d.ts.map +1 -1
  123. package/dist/engine/renderers/Indicator/stoch.js +8 -1
  124. package/dist/engine/renderers/Indicator/stoch.js.map +1 -1
  125. package/dist/engine/renderers/Indicator/structure.d.ts.map +1 -1
  126. package/dist/engine/renderers/Indicator/structure.js +8 -1
  127. package/dist/engine/renderers/Indicator/structure.js.map +1 -1
  128. package/dist/engine/renderers/Indicator/subPaneConfig.d.ts +1 -1
  129. package/dist/engine/renderers/Indicator/subPaneConfig.d.ts.map +1 -1
  130. package/dist/engine/renderers/Indicator/subPaneConfig.js.map +1 -1
  131. package/dist/engine/renderers/Indicator/supertrend.d.ts.map +1 -1
  132. package/dist/engine/renderers/Indicator/supertrend.js +8 -1
  133. package/dist/engine/renderers/Indicator/supertrend.js.map +1 -1
  134. package/dist/engine/renderers/Indicator/tema.d.ts.map +1 -1
  135. package/dist/engine/renderers/Indicator/tema.js +8 -1
  136. package/dist/engine/renderers/Indicator/tema.js.map +1 -1
  137. package/dist/engine/renderers/Indicator/trix.d.ts.map +1 -1
  138. package/dist/engine/renderers/Indicator/trix.js +7 -0
  139. package/dist/engine/renderers/Indicator/trix.js.map +1 -1
  140. package/dist/engine/renderers/Indicator/vma.d.ts.map +1 -1
  141. package/dist/engine/renderers/Indicator/vma.js +7 -0
  142. package/dist/engine/renderers/Indicator/vma.js.map +1 -1
  143. package/dist/engine/renderers/Indicator/volumeProfile.d.ts.map +1 -1
  144. package/dist/engine/renderers/Indicator/volumeProfile.js +7 -1
  145. package/dist/engine/renderers/Indicator/volumeProfile.js.map +1 -1
  146. package/dist/engine/renderers/Indicator/vwap.d.ts.map +1 -1
  147. package/dist/engine/renderers/Indicator/vwap.js +7 -1
  148. package/dist/engine/renderers/Indicator/vwap.js.map +1 -1
  149. package/dist/engine/renderers/Indicator/wma.d.ts.map +1 -1
  150. package/dist/engine/renderers/Indicator/wma.js +8 -1
  151. package/dist/engine/renderers/Indicator/wma.js.map +1 -1
  152. package/dist/engine/renderers/Indicator/wmsr.d.ts.map +1 -1
  153. package/dist/engine/renderers/Indicator/wmsr.js +8 -1
  154. package/dist/engine/renderers/Indicator/wmsr.js.map +1 -1
  155. package/dist/engine/renderers/Indicator/zones.d.ts.map +1 -1
  156. package/dist/engine/renderers/Indicator/zones.js +8 -1
  157. package/dist/engine/renderers/Indicator/zones.js.map +1 -1
  158. package/dist/engine/renderers/extremaMarkers.d.ts.map +1 -1
  159. package/dist/engine/renderers/extremaMarkers.js +0 -22
  160. package/dist/engine/renderers/extremaMarkers.js.map +1 -1
  161. package/dist/engine/renderers/subVolume.d.ts.map +1 -1
  162. package/dist/engine/renderers/subVolume.js +3 -0
  163. package/dist/engine/renderers/subVolume.js.map +1 -1
  164. package/dist/engine/subPaneManager.d.ts +1 -0
  165. package/dist/engine/subPaneManager.d.ts.map +1 -1
  166. package/dist/engine/subPaneManager.js +30 -237
  167. package/dist/engine/subPaneManager.js.map +1 -1
  168. package/dist/semantic/controller.d.ts +9 -4
  169. package/dist/semantic/controller.d.ts.map +1 -1
  170. package/dist/semantic/controller.js +57 -53
  171. package/dist/semantic/controller.js.map +1 -1
  172. package/dist/tokens/theme-dark.js +1 -1
  173. package/dist/version.d.ts +1 -1
  174. package/dist/version.d.ts.map +1 -1
  175. package/dist/version.js +1 -1
  176. package/dist/version.js.map +1 -1
  177. package/package.json +1 -1
  178. package/src/controllers/index.ts +7 -0
  179. package/src/engine/__tests__/chart.dpr.test.ts +2 -1
  180. package/src/engine/__tests__/paneRenderer.resize.test.ts +1 -0
  181. package/src/engine/chart.ts +51 -229
  182. package/src/engine/controller/__tests__/interaction.dpr.test.ts +1 -0
  183. package/src/engine/indicators/__tests__/indicatorDefinitionRegistry.test.ts +59 -0
  184. package/src/engine/indicators/__tests__/indicatorRegistry.test.ts +62 -0
  185. package/src/engine/indicators/__tests__/registerBuiltins.test.ts +380 -0
  186. package/src/engine/indicators/__tests__/scheduler.test.ts +92 -8
  187. package/src/engine/indicators/__tests__/stateComposer.test.ts +444 -0
  188. package/src/engine/indicators/indicatorDefinitionRegistry.ts +49 -4
  189. package/src/engine/indicators/indicatorMetadata.ts +107 -2
  190. package/src/engine/indicators/indicatorRegistry.ts +54 -5
  191. package/src/engine/indicators/registerBuiltins.ts +47 -0
  192. package/src/engine/indicators/scheduler.ts +61 -13
  193. package/src/engine/indicators/stateComposer.ts +110 -1040
  194. package/src/engine/indicators/visibleStateComposers.ts +734 -0
  195. package/src/engine/renderers/Indicator/__tests__/createSubIndicatorRenderer.test.ts +61 -0
  196. package/src/engine/renderers/Indicator/atr.ts +9 -1
  197. package/src/engine/renderers/Indicator/boll.ts +53 -1
  198. package/src/engine/renderers/Indicator/cci.ts +9 -2
  199. package/src/engine/renderers/Indicator/chaikinVol.ts +8 -2
  200. package/src/engine/renderers/Indicator/cmf.ts +8 -2
  201. package/src/engine/renderers/Indicator/dema.ts +9 -2
  202. package/src/engine/renderers/Indicator/donchian.ts +9 -2
  203. package/src/engine/renderers/Indicator/ene.ts +51 -1
  204. package/src/engine/renderers/Indicator/expma.ts +51 -1
  205. package/src/engine/renderers/Indicator/fastk.ts +9 -2
  206. package/src/engine/renderers/Indicator/fib.ts +9 -2
  207. package/src/engine/renderers/Indicator/hma.ts +9 -2
  208. package/src/engine/renderers/Indicator/hv.ts +8 -1
  209. package/src/engine/renderers/Indicator/ichimoku.ts +9 -2
  210. package/src/engine/renderers/Indicator/index.ts +9 -121
  211. package/src/engine/renderers/Indicator/kama.ts +9 -2
  212. package/src/engine/renderers/Indicator/keltner.ts +9 -2
  213. package/src/engine/renderers/Indicator/kst.ts +9 -1
  214. package/src/engine/renderers/Indicator/ma.ts +69 -0
  215. package/src/engine/renderers/Indicator/macd.ts +9 -2
  216. package/src/engine/renderers/Indicator/mfi.ts +8 -2
  217. package/src/engine/renderers/Indicator/mom.ts +9 -1
  218. package/src/engine/renderers/Indicator/obv.ts +8 -2
  219. package/src/engine/renderers/Indicator/parkinson.ts +8 -1
  220. package/src/engine/renderers/Indicator/pivot.ts +9 -2
  221. package/src/engine/renderers/Indicator/pvt.ts +8 -2
  222. package/src/engine/renderers/Indicator/roc.ts +8 -2
  223. package/src/engine/renderers/Indicator/rsi.ts +9 -2
  224. package/src/engine/renderers/Indicator/sar.ts +9 -2
  225. package/src/engine/renderers/Indicator/stoch.ts +9 -2
  226. package/src/engine/renderers/Indicator/structure.ts +9 -2
  227. package/src/engine/renderers/Indicator/subPaneConfig.ts +1 -1
  228. package/src/engine/renderers/Indicator/supertrend.ts +9 -2
  229. package/src/engine/renderers/Indicator/tema.ts +9 -2
  230. package/src/engine/renderers/Indicator/trix.ts +8 -1
  231. package/src/engine/renderers/Indicator/vma.ts +8 -1
  232. package/src/engine/renderers/Indicator/volumeProfile.ts +8 -2
  233. package/src/engine/renderers/Indicator/vwap.ts +8 -2
  234. package/src/engine/renderers/Indicator/wma.ts +9 -2
  235. package/src/engine/renderers/Indicator/wmsr.ts +9 -2
  236. package/src/engine/renderers/Indicator/zones.ts +9 -2
  237. package/src/engine/renderers/__tests__/boll.renderer.test.ts +9 -1
  238. package/src/engine/renderers/__tests__/ene.renderer.test.ts +2 -1
  239. package/src/engine/renderers/__tests__/expma.renderer.test.ts +0 -1
  240. package/src/engine/renderers/__tests__/ma.renderer.test.ts +0 -1
  241. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +0 -1
  242. package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +3 -0
  243. package/src/engine/renderers/extremaMarkers.ts +0 -24
  244. package/src/engine/renderers/subVolume.ts +3 -0
  245. package/src/engine/subPaneManager.ts +34 -274
  246. package/src/semantic/__tests__/controller.test.ts +84 -0
  247. package/src/semantic/controller.ts +69 -65
  248. package/src/tokens/__tests__/__snapshots__/baseline.test.ts.snap +4 -2
  249. package/src/tokens/__tests__/tokens.test.ts +1 -0
  250. package/src/tokens/theme-dark.ts +1 -1
  251. package/src/version.ts +1 -1
@@ -0,0 +1,380 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { getRegisteredIndicatorDefinition } from '../indicatorDefinitionRegistry'
3
+ import { getBuiltinIndicatorDefinitions } from '../registerBuiltins'
4
+
5
+ describe('builtin indicator registration', () => {
6
+ it('loads all builtin indicator definitions through decorators', () => {
7
+ const definitions = getBuiltinIndicatorDefinitions()
8
+
9
+ expect(definitions).toHaveLength(40)
10
+ expect(definitions.map((definition) => definition.name)).toEqual(
11
+ expect.arrayContaining([
12
+ 'ma',
13
+ 'boll',
14
+ 'rsi',
15
+ 'macd',
16
+ 'volume',
17
+ 'volumeProfile',
18
+ 'zones',
19
+ ]),
20
+ )
21
+ })
22
+
23
+ it('allows builtin definitions to be queried by display name', () => {
24
+ expect(getRegisteredIndicatorDefinition('RSI')?.name).toBe('rsi')
25
+ expect(getRegisteredIndicatorDefinition('MACD')?.name).toBe('macd')
26
+ expect(getRegisteredIndicatorDefinition('VOL')?.name).toBe('volume')
27
+ })
28
+
29
+ it('registers metadata config updaters for stage 4A indicators', () => {
30
+ expect(getRegisteredIndicatorDefinition('RSI')?.updateConfig).toBeTypeOf('function')
31
+ expect(getRegisteredIndicatorDefinition('MACD')?.updateConfig).toBeTypeOf('function')
32
+ expect(getRegisteredIndicatorDefinition('VOL')?.updateConfig).toBeTypeOf('function')
33
+ expect(getRegisteredIndicatorDefinition('VOLUME_PROFILE')?.updateConfig).toBeTypeOf('function')
34
+ })
35
+
36
+ it('registers metadata config updaters for stage 4B indicators', () => {
37
+ const expectedIndicators = [
38
+ 'CCI', 'STOCH', 'MOM', 'WMSR', 'KST', 'FASTK', 'ATR',
39
+ 'WMA', 'DEMA', 'TEMA', 'HMA', 'KAMA', 'SAR',
40
+ 'SUPERTREND', 'KELTNER', 'DONCHIAN', 'ICHIMOKU',
41
+ 'ROC', 'TRIX', 'HV', 'PARKINSON', 'CHAIKIN_VOL',
42
+ 'VMA', 'OBV', 'PVT', 'VWAP', 'CMF', 'MFI',
43
+ 'PIVOT', 'FIB', 'STRUCTURE', 'ZONES',
44
+ ]
45
+ for (const id of expectedIndicators) {
46
+ expect(getRegisteredIndicatorDefinition(id)?.updateConfig).toBeTypeOf('function')
47
+ }
48
+ })
49
+
50
+ it('routes stage 4A metadata config updates to scheduler methods', () => {
51
+ const scheduler = {
52
+ updateRSIConfig: vi.fn(),
53
+ updateMACDConfig: vi.fn(),
54
+ updateVolumeProfileConfig: vi.fn(),
55
+ }
56
+
57
+ getRegisteredIndicatorDefinition('RSI')?.updateConfig?.(scheduler, { period1: 7 }, 'RSI_0')
58
+ getRegisteredIndicatorDefinition('MACD')?.updateConfig?.(scheduler, { fastPeriod: 8 }, 'MACD_0')
59
+ getRegisteredIndicatorDefinition('VOLUME_PROFILE')?.updateConfig?.(scheduler, { bins: 32 }, 'VP_0')
60
+ getRegisteredIndicatorDefinition('VOL')?.updateConfig?.(scheduler, {}, 'VOL_0')
61
+
62
+ expect(scheduler.updateRSIConfig).toHaveBeenCalledWith({ period1: 7 }, 'RSI_0')
63
+ expect(scheduler.updateMACDConfig).toHaveBeenCalledWith({ fastPeriod: 8 }, 'MACD_0')
64
+ expect(scheduler.updateVolumeProfileConfig).toHaveBeenCalledWith({ bins: 32 }, 'VP_0')
65
+ })
66
+
67
+ it('routes stage 4B metadata config updates to scheduler methods', () => {
68
+ const scheduler = {
69
+ updateCCIConfig: vi.fn(),
70
+ updateATRConfig: vi.fn(),
71
+ updateChaikinVolConfig: vi.fn(),
72
+ updateZonesConfig: vi.fn(),
73
+ }
74
+
75
+ getRegisteredIndicatorDefinition('CCI')?.updateConfig?.(scheduler, { period: 14 }, 'CCI_0')
76
+ getRegisteredIndicatorDefinition('ATR')?.updateConfig?.(scheduler, { period: 10 }, 'ATR_0')
77
+ getRegisteredIndicatorDefinition('CHAIKIN_VOL')?.updateConfig?.(scheduler, { emaPeriod: 10 }, 'CV_0')
78
+ getRegisteredIndicatorDefinition('ZONES')?.updateConfig?.(scheduler, { showFVG: true }, 'Z_0')
79
+
80
+ expect(scheduler.updateCCIConfig).toHaveBeenCalledWith({ period: 14 }, 'CCI_0')
81
+ expect(scheduler.updateATRConfig).toHaveBeenCalledWith({ period: 10 }, 'ATR_0')
82
+ expect(scheduler.updateChaikinVolConfig).toHaveBeenCalledWith({ emaPeriod: 10 }, 'CV_0')
83
+ expect(scheduler.updateZonesConfig).toHaveBeenCalledWith({ showFVG: true }, 'Z_0')
84
+ })
85
+
86
+ it('registers dedicated scale renderer factories for stage 5A indicators', () => {
87
+ const expectedIndicators = [
88
+ 'VOL', 'MACD', 'RSI', 'CCI', 'STOCH', 'MOM', 'WMSR', 'KST', 'FASTK', 'ATR',
89
+ ]
90
+
91
+ for (const id of expectedIndicators) {
92
+ expect(getRegisteredIndicatorDefinition(id)?.scaleRendererFactory).toBeTypeOf('function')
93
+ }
94
+ })
95
+
96
+ it('creates scale renderers through stage 5A metadata factories', () => {
97
+ const rsiScaleRenderer = getRegisteredIndicatorDefinition('RSI')?.scaleRendererFactory?.({
98
+ indicatorId: 'RSI',
99
+ paneId: 'RSI_0',
100
+ axisWidth: 80,
101
+ yPaddingPx: 4,
102
+ getCrosshair: () => null,
103
+ })
104
+ const volumeScaleRenderer = getRegisteredIndicatorDefinition('VOL')?.scaleRendererFactory?.({
105
+ indicatorId: 'VOLUME',
106
+ paneId: 'VOLUME_0',
107
+ axisWidth: 80,
108
+ yPaddingPx: 4,
109
+ getCrosshair: () => null,
110
+ })
111
+
112
+ expect(rsiScaleRenderer?.name).toBe('rsiScale_RSI_0')
113
+ expect(volumeScaleRenderer?.name).toBe('volumeScale_VOLUME_0')
114
+ })
115
+
116
+ it('registers generic scale metadata for stage 5B indicators', () => {
117
+ const expected: Record<string, { indicatorKey: string; label: string; decimals: number }> = {
118
+ WMA: { indicatorKey: 'wma', label: 'WMA', decimals: 2 },
119
+ DEMA: { indicatorKey: 'dema', label: 'DEMA', decimals: 2 },
120
+ TEMA: { indicatorKey: 'tema', label: 'TEMA', decimals: 2 },
121
+ HMA: { indicatorKey: 'hma', label: 'HMA', decimals: 2 },
122
+ KAMA: { indicatorKey: 'kama', label: 'KAMA', decimals: 2 },
123
+ SAR: { indicatorKey: 'sar', label: 'SAR', decimals: 4 },
124
+ SUPERTREND: { indicatorKey: 'supertrend', label: 'SuperTrend', decimals: 2 },
125
+ KELTNER: { indicatorKey: 'keltner', label: 'Keltner', decimals: 2 },
126
+ DONCHIAN: { indicatorKey: 'donchian', label: 'Donchian', decimals: 2 },
127
+ ICHIMOKU: { indicatorKey: 'ichimoku', label: 'Ichimoku', decimals: 2 },
128
+ ROC: { indicatorKey: 'roc', label: 'ROC', decimals: 2 },
129
+ TRIX: { indicatorKey: 'trix', label: 'TRIX', decimals: 6 },
130
+ HV: { indicatorKey: 'hv', label: 'HV', decimals: 2 },
131
+ PARKINSON: { indicatorKey: 'parkinson', label: 'Parkinson', decimals: 2 },
132
+ CHAIKIN_VOL: { indicatorKey: 'chaikinVol', label: 'ChaikinVol', decimals: 2 },
133
+ VMA: { indicatorKey: 'vma', label: 'VMA', decimals: 0 },
134
+ OBV: { indicatorKey: 'obv', label: 'OBV', decimals: 0 },
135
+ PVT: { indicatorKey: 'pvt', label: 'PVT', decimals: 0 },
136
+ VWAP: { indicatorKey: 'vwap', label: 'VWAP', decimals: 2 },
137
+ CMF: { indicatorKey: 'cmf', label: 'CMF', decimals: 4 },
138
+ MFI: { indicatorKey: 'mfi', label: 'MFI', decimals: 2 },
139
+ PIVOT: { indicatorKey: 'pivot', label: 'Pivot', decimals: 2 },
140
+ FIB: { indicatorKey: 'fib', label: 'Fib', decimals: 4 },
141
+ STRUCTURE: { indicatorKey: 'structure', label: 'Structure', decimals: 2 },
142
+ ZONES: { indicatorKey: 'zones', label: 'Zones', decimals: 2 },
143
+ VOLUME_PROFILE: { indicatorKey: 'volumeProfile', label: 'VP', decimals: 0 },
144
+ }
145
+
146
+ for (const [id, scale] of Object.entries(expected)) {
147
+ expect(getRegisteredIndicatorDefinition(id)?.scale).toEqual(scale)
148
+ }
149
+ })
150
+
151
+ it('creates generic scale renderers through stage 5B metadata', () => {
152
+ const definition = getRegisteredIndicatorDefinition('WMA')
153
+ expect(definition?.scale).toBeDefined()
154
+ })
155
+
156
+ it('registers main pane renderer metadata for stage 6A-1 indicators', () => {
157
+ const expected: Record<string, string> = {
158
+ MA: 'ma',
159
+ BOLL: 'boll',
160
+ EXPMA: 'expma',
161
+ ENE: 'ene',
162
+ }
163
+
164
+ for (const [id, rendererName] of Object.entries(expected)) {
165
+ expect(getRegisteredIndicatorDefinition(id)?.mainPane?.rendererName).toBe(rendererName)
166
+ }
167
+ })
168
+
169
+ it('creates main pane renderers through stage 6A-1 metadata factories', () => {
170
+ const maRenderer = getRegisteredIndicatorDefinition('MA')?.rendererFactory({ paneId: 'main', indicatorId: 'MA' })
171
+ const bollRenderer = getRegisteredIndicatorDefinition('BOLL')?.rendererFactory({ paneId: 'main', indicatorId: 'BOLL' })
172
+
173
+ expect(maRenderer?.name).toBe('ma')
174
+ expect(bollRenderer?.name).toBe('boll')
175
+ })
176
+
177
+ it('registers main pane renderer metadata for stage 6A-2 indicators', () => {
178
+ const expected: Record<string, string> = {
179
+ WMA: 'wma_main',
180
+ DEMA: 'dema_main',
181
+ TEMA: 'tema_main',
182
+ HMA: 'hma_main',
183
+ KAMA: 'kama_main',
184
+ SAR: 'sar_main',
185
+ SUPERTREND: 'supertrend_main',
186
+ KELTNER: 'keltner_main',
187
+ DONCHIAN: 'donchian_main',
188
+ ICHIMOKU: 'ichimoku_main',
189
+ PIVOT: 'pivot_main',
190
+ FIB: 'fib_main',
191
+ STRUCTURE: 'structure_main',
192
+ ZONES: 'zones_main',
193
+ }
194
+
195
+ for (const [id, rendererName] of Object.entries(expected)) {
196
+ expect(getRegisteredIndicatorDefinition(id)?.mainPane?.rendererName).toBe(rendererName)
197
+ }
198
+ })
199
+
200
+ it('creates overlay main pane renderers through stage 6A-2 metadata factories', () => {
201
+ const wma = getRegisteredIndicatorDefinition('WMA')?.rendererFactory({ paneId: 'main', indicatorId: 'WMA' })
202
+ const zones = getRegisteredIndicatorDefinition('ZONES')?.rendererFactory({ paneId: 'main', indicatorId: 'ZONES' })
203
+
204
+ expect(wma?.name).toBe('wma_main')
205
+ expect(zones?.name).toBe('zones_main')
206
+ })
207
+
208
+ it('registers overlay main config metadata for stage 6B-2 indicators', () => {
209
+ const ids = [
210
+ 'WMA', 'DEMA', 'TEMA', 'HMA', 'KAMA',
211
+ 'SAR', 'SUPERTREND', 'KELTNER', 'DONCHIAN',
212
+ 'ICHIMOKU', 'PIVOT', 'FIB', 'STRUCTURE', 'ZONES',
213
+ ]
214
+
215
+ for (const id of ids) {
216
+ expect(getRegisteredIndicatorDefinition(id)?.mainPane?.toActiveConfig).toBeTypeOf('function')
217
+ }
218
+ })
219
+
220
+ it('builds overlay main active configs through stage 6B-2 metadata', () => {
221
+ expect(getRegisteredIndicatorDefinition('WMA')?.mainPane?.toActiveConfig?.({ period: 10 }, false)).toEqual({
222
+ period: 10, showWMA: false,
223
+ })
224
+
225
+ expect(getRegisteredIndicatorDefinition('KELTNER')?.mainPane?.toActiveConfig?.({ emaPeriod: 20 }, false)).toEqual({
226
+ emaPeriod: 20, showUpper: false, showMiddle: false, showLower: false,
227
+ })
228
+
229
+ expect(getRegisteredIndicatorDefinition('ICHIMOKU')?.mainPane?.toActiveConfig?.({}, false)).toEqual({
230
+ showTenkan: false, showKijun: false, showSpanA: false,
231
+ showSpanB: false, showChikou: false, showCloud: false,
232
+ })
233
+
234
+ expect(getRegisteredIndicatorDefinition('ZONES')?.mainPane?.toActiveConfig?.({}, false)).toEqual({
235
+ showFVG: false, showOB: false, showFilledZones: false,
236
+ })
237
+ })
238
+
239
+ it('registers base main config metadata for stage 6B-1 indicators', () => {
240
+ for (const id of ['MA', 'BOLL', 'EXPMA', 'ENE']) {
241
+ const definition = getRegisteredIndicatorDefinition(id)
242
+ expect(definition?.updateConfig).toBeTypeOf('function')
243
+ expect(definition?.mainPane?.toActiveConfig).toBeTypeOf('function')
244
+ }
245
+ })
246
+
247
+ it('builds base main active configs through stage 6B-1 metadata', () => {
248
+ expect(getRegisteredIndicatorDefinition('MA')?.mainPane?.toActiveConfig?.({}, false)).toEqual({
249
+ ma5: false,
250
+ ma10: false,
251
+ ma20: false,
252
+ ma30: false,
253
+ ma60: false,
254
+ })
255
+ expect(getRegisteredIndicatorDefinition('BOLL')?.mainPane?.toActiveConfig?.({ period: 20 }, false)).toEqual({
256
+ period: 20,
257
+ showUpper: false,
258
+ showMiddle: false,
259
+ showLower: false,
260
+ showBand: false,
261
+ })
262
+ expect(getRegisteredIndicatorDefinition('EXPMA')?.mainPane?.toActiveConfig?.({}, false)).toBeNull()
263
+ expect(getRegisteredIndicatorDefinition('ENE')?.mainPane?.toActiveConfig?.({}, false)).toBeNull()
264
+ })
265
+
266
+ it('routes base main metadata config updates to scheduler methods', () => {
267
+ const scheduler = {
268
+ updateMAConfig: vi.fn(),
269
+ updateBOLLConfig: vi.fn(),
270
+ updateEXPMAConfig: vi.fn(),
271
+ updateENEConfig: vi.fn(),
272
+ }
273
+
274
+ getRegisteredIndicatorDefinition('MA')?.updateConfig?.(scheduler, { ma5: true }, 'main')
275
+ getRegisteredIndicatorDefinition('BOLL')?.updateConfig?.(scheduler, { period: 20 }, 'main')
276
+ getRegisteredIndicatorDefinition('EXPMA')?.updateConfig?.(scheduler, { fastPeriod: 12 }, 'main')
277
+ getRegisteredIndicatorDefinition('ENE')?.updateConfig?.(scheduler, { period: 10 }, 'main')
278
+
279
+ expect(scheduler.updateMAConfig).toHaveBeenCalledWith({ ma5: true })
280
+ expect(scheduler.updateBOLLConfig).toHaveBeenCalledWith({ period: 20 })
281
+ expect(scheduler.updateEXPMAConfig).toHaveBeenCalledWith({ fastPeriod: 12 })
282
+ expect(scheduler.updateENEConfig).toHaveBeenCalledWith({ period: 10 })
283
+ })
284
+
285
+ it('registers semantic apply metadata for stage 7A main indicators', () => {
286
+ for (const id of ['MA', 'BOLL', 'EXPMA', 'ENE']) {
287
+ expect(getRegisteredIndicatorDefinition(id)?.semantic?.apply).toBeTypeOf('function')
288
+ }
289
+ })
290
+
291
+ it('routes semantic main indicator configs through stage 7A metadata', () => {
292
+ const chart = { updateRendererConfig: vi.fn() }
293
+
294
+ getRegisteredIndicatorDefinition('MA')?.semantic?.apply?.(chart, {
295
+ type: 'MA',
296
+ enabled: true,
297
+ params: { periods: [5, 20, 99] },
298
+ })
299
+ getRegisteredIndicatorDefinition('BOLL')?.semantic?.apply?.(chart, {
300
+ type: 'BOLL',
301
+ enabled: true,
302
+ params: { period: 21, multiplier: 2.5 },
303
+ })
304
+ getRegisteredIndicatorDefinition('EXPMA')?.semantic?.apply?.(chart, {
305
+ type: 'EXPMA',
306
+ enabled: true,
307
+ params: {},
308
+ })
309
+ getRegisteredIndicatorDefinition('ENE')?.semantic?.apply?.(chart, {
310
+ type: 'ENE',
311
+ enabled: true,
312
+ })
313
+
314
+ expect(chart.updateRendererConfig).toHaveBeenCalledWith('ma', { ma5: true, ma20: true })
315
+ expect(chart.updateRendererConfig).toHaveBeenCalledWith('boll', { period: 21, multiplier: 2.5 })
316
+ expect(chart.updateRendererConfig).toHaveBeenCalledWith('expma', { fastPeriod: 12, slowPeriod: 50 })
317
+ expect(chart.updateRendererConfig).toHaveBeenCalledWith('ene', { period: 10, deviation: 11 })
318
+ })
319
+
320
+ it('registers main pane price range metadata for stage 8A indicators', () => {
321
+ for (const id of ['MA', 'BOLL', 'EXPMA', 'ENE']) {
322
+ expect(getRegisteredIndicatorDefinition(id)?.mainPane?.computePriceRange).toBeTypeOf('function')
323
+ }
324
+ })
325
+
326
+ it('registers main pane render state composer metadata for stage 8B indicators', () => {
327
+ for (const id of ['MA', 'BOLL', 'EXPMA', 'ENE']) {
328
+ expect(getRegisteredIndicatorDefinition(id)?.mainPane?.composeRenderState).toBeTypeOf('function')
329
+ }
330
+ })
331
+
332
+ it('registers visible state composer metadata for stage 8C-A indicators', () => {
333
+ for (const id of ['WMA', 'DEMA', 'TEMA', 'HMA', 'KAMA', 'ROC', 'ChaikinVol', 'OBV', 'PVT', 'VWAP']) {
334
+ expect(getRegisteredIndicatorDefinition(id)?.visibleState?.compose).toBeTypeOf('function')
335
+ }
336
+ })
337
+
338
+ it('registers visible state composer metadata for stage 8C-B indicators', () => {
339
+ for (const id of ['RSI', 'STOCH', 'FASTK', 'MFI', 'WMSR', 'CMF']) {
340
+ expect(getRegisteredIndicatorDefinition(id)?.visibleState?.compose).toBeTypeOf('function')
341
+ }
342
+ })
343
+
344
+ it('registers visible state composer metadata for stage 8C-C indicators', () => {
345
+ for (const id of ['MOM', 'KST', 'ATR', 'HV', 'PARKINSON', 'VMA', 'TRIX']) {
346
+ expect(getRegisteredIndicatorDefinition(id)?.visibleState?.compose).toBeTypeOf('function')
347
+ }
348
+ })
349
+
350
+ it('registers visible state composer metadata for stage 8C-D1 MACD indicator', () => {
351
+ expect(getRegisteredIndicatorDefinition('MACD')?.visibleState?.compose).toBeTypeOf('function')
352
+ })
353
+
354
+ it('registers visible state composer metadata for stage 8C-D2 overlay indicators', () => {
355
+ for (const id of ['SAR', 'SUPERTREND', 'KELTNER', 'DONCHIAN', 'ICHIMOKU', 'PIVOT', 'FIB']) {
356
+ expect(getRegisteredIndicatorDefinition(id)?.visibleState?.compose).toBeTypeOf('function')
357
+ }
358
+ })
359
+
360
+ it('registers visible state composer metadata for stage 8C-D2-B structure indicators', () => {
361
+ for (const id of ['STRUCTURE', 'ZONES', 'VOLUME_PROFILE']) {
362
+ expect(getRegisteredIndicatorDefinition(id)?.visibleState?.compose).toBeTypeOf('function')
363
+ }
364
+ })
365
+
366
+ it('registers visible state composer metadata for all composed visible sub indicators', () => {
367
+ const ids = [
368
+ 'RSI', 'CCI', 'STOCH', 'MOM', 'WMSR', 'FASTK', 'MACD',
369
+ 'ATR', 'WMA', 'DEMA', 'TEMA', 'HMA', 'KAMA',
370
+ 'SAR', 'SUPERTREND', 'KELTNER', 'DONCHIAN', 'ICHIMOKU',
371
+ 'ROC', 'TRIX', 'HV', 'PARKINSON', 'CHAIKIN_VOL',
372
+ 'VMA', 'OBV', 'PVT', 'VWAP', 'CMF', 'MFI',
373
+ 'KST', 'PIVOT', 'FIB', 'STRUCTURE', 'ZONES', 'VOLUME_PROFILE',
374
+ ]
375
+
376
+ for (const id of ids) {
377
+ expect(getRegisteredIndicatorDefinition(id)?.visibleState?.compose).toBeTypeOf('function')
378
+ }
379
+ })
380
+ })
@@ -1,13 +1,66 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest'
2
2
  import { IndicatorScheduler } from '../scheduler'
3
+ import { getBuiltinIndicatorDefinitions } from '../registerBuiltins'
3
4
  import { MA_STATE_KEY, EMPTY_MA_STATE, type MARenderState } from '../maState'
4
5
  import { BOLL_STATE_KEY, EMPTY_BOLL_STATE, type BOLLRenderState } from '../bollState'
5
6
  import { EXPMA_STATE_KEY, EMPTY_EXPMA_STATE, type EXPMARenderState } from '../expmaState'
6
7
  import { ENE_STATE_KEY, EMPTY_ENE_STATE, type ENERenderState } from '../eneState'
7
8
  import { createRSIStateKey, EMPTY_RSI_STATE, type RSIRenderState } from '../rsiState'
9
+ import type { IndicatorMetadata } from '../indicatorMetadata'
8
10
  import type { KLineData } from '@/types/price'
9
11
  import type { PluginHost } from '@/plugin'
10
12
 
13
+ function applyMainResult(key: string): NonNullable<IndicatorMetadata['applyResult']> {
14
+ return (host, state, _paneId) => {
15
+ ;(host as PluginHost).setSharedState(key, state as any, 'indicator_scheduler')
16
+ }
17
+ }
18
+
19
+ const builtinDefinitionsByName = new Map(getBuiltinIndicatorDefinitions().map((definition) => [definition.name, definition]))
20
+
21
+ function getBuiltinTestIndicator(name: string): IndicatorMetadata {
22
+ const definition = builtinDefinitionsByName.get(name)
23
+ if (!definition) {
24
+ throw new Error(`Missing builtin test indicator: ${name}`)
25
+ }
26
+ return definition
27
+ }
28
+
29
+ function registerTestIndicators(scheduler: IndicatorScheduler): void {
30
+ const indicators: Array<IndicatorMetadata> = [
31
+ getBuiltinTestIndicator('ma'),
32
+ getBuiltinTestIndicator('boll'),
33
+ getBuiltinTestIndicator('expma'),
34
+ getBuiltinTestIndicator('ene'),
35
+ getBuiltinTestIndicator('rsi'),
36
+ getBuiltinTestIndicator('macd'),
37
+ { name: 'volume', displayName: 'Volume', category: 'sub' as const, stateKey: 'indicator:volume:sub_Volume', defaultPaneId: 'sub_Volume', rendererFactory: vi.fn() as any, paneIdField: 'volumePaneId' as any, applyResult: applyMainResult('indicator:volume:sub_Volume') },
38
+ getBuiltinTestIndicator('stoch'),
39
+ getBuiltinTestIndicator('wmsr'),
40
+ getBuiltinTestIndicator('mom'),
41
+ getBuiltinTestIndicator('fastk'),
42
+ getBuiltinTestIndicator('cci'),
43
+ getBuiltinTestIndicator('atr'),
44
+ getBuiltinTestIndicator('kst'),
45
+ getBuiltinTestIndicator('roc'),
46
+ getBuiltinTestIndicator('trix'),
47
+ getBuiltinTestIndicator('hv'),
48
+ getBuiltinTestIndicator('parkinson'),
49
+ getBuiltinTestIndicator('chaikinVol'),
50
+ getBuiltinTestIndicator('vma'),
51
+ getBuiltinTestIndicator('obv'),
52
+ getBuiltinTestIndicator('pvt'),
53
+ getBuiltinTestIndicator('vwap'),
54
+ getBuiltinTestIndicator('cmf'),
55
+ getBuiltinTestIndicator('mfi'),
56
+ getBuiltinTestIndicator('sar'),
57
+ getBuiltinTestIndicator('keltner'),
58
+ ]
59
+ for (const meta of indicators) {
60
+ scheduler.registerIndicator(meta)
61
+ }
62
+ }
63
+
11
64
  /**
12
65
  * 创建测试用的 K 线数据
13
66
  */
@@ -76,6 +129,8 @@ describe('IndicatorScheduler', () => {
76
129
  scheduler = new IndicatorScheduler()
77
130
  mockHost = createMockPluginHost()
78
131
  scheduler.setPluginHost(mockHost)
132
+ registerTestIndicators(scheduler)
133
+ vi.mocked(mockHost.setSharedState).mockClear()
79
134
  })
80
135
 
81
136
  describe('initialization', () => {
@@ -107,7 +162,7 @@ describe('IndicatorScheduler', () => {
107
162
  visibleMin: expect.any(Number),
108
163
  visibleMax: expect.any(Number),
109
164
  }),
110
- 'ma_scheduler'
165
+ expect.any(String)
111
166
  )
112
167
  })
113
168
 
@@ -154,6 +209,28 @@ describe('IndicatorScheduler', () => {
154
209
  expect(state!.visibleMin).toBe(Infinity)
155
210
  expect(state!.visibleMax).toBe(-Infinity)
156
211
  })
212
+
213
+ it('should refresh main indicator visible extremes when visible range changes', () => {
214
+ const data = createTestData(100, 100)
215
+ const invalidate = vi.fn()
216
+ scheduler.setInvalidateCallback(invalidate)
217
+
218
+ scheduler.update(data, { start: 0, end: 0 })
219
+ invalidate.mockClear()
220
+ const initialState = getStateFromMockCalls<BOLLRenderState>(mockHost, BOLL_STATE_KEY)
221
+ expect(initialState).toBeDefined()
222
+ expect(initialState!.visibleMin).toBe(Infinity)
223
+ expect(initialState!.visibleMax).toBe(-Infinity)
224
+
225
+ scheduler.updateVisibleRange({ start: 30, end: 60 })
226
+
227
+ const updatedState = getStateFromMockCalls<BOLLRenderState>(mockHost, BOLL_STATE_KEY)
228
+ expect(updatedState).toBeDefined()
229
+ expect(updatedState!.visibleMin).toBeLessThan(updatedState!.visibleMax)
230
+ expect(Number.isFinite(updatedState!.visibleMin)).toBe(true)
231
+ expect(Number.isFinite(updatedState!.visibleMax)).toBe(true)
232
+ expect(invalidate).toHaveBeenCalledTimes(1)
233
+ })
157
234
  })
158
235
 
159
236
  describe('MA config update', () => {
@@ -230,9 +307,11 @@ describe('IndicatorScheduler', () => {
230
307
  // Update only viewport
231
308
  scheduler.updateVisibleRange({ start: 50, end: 60 })
232
309
 
233
- // updateVisibleStatesOnly writes the 32 sub-indicators.
234
- // Main indicators (MA, BOLL, EXPMA, ENE) are not rewritten on viewport-only changes.
235
- expect(mockHost.setSharedState).toHaveBeenCalledTimes(35)
310
+ const calls = vi.mocked(mockHost.setSharedState).mock.calls
311
+ expect(calls.some((call) => call[0] === MA_STATE_KEY)).toBe(false)
312
+ expect(calls.some((call) => call[0] === BOLL_STATE_KEY)).toBe(false)
313
+ expect(calls.some((call) => call[0] === EXPMA_STATE_KEY)).toBe(false)
314
+ expect(calls.some((call) => call[0] === ENE_STATE_KEY)).toBe(false)
236
315
 
237
316
  // Inspect a sub-indicator (RSI) since main indicators are not rewritten on viewport-only updates
238
317
  const rsiKey = createRSIStateKey('sub_RSI')
@@ -252,8 +331,8 @@ describe('IndicatorScheduler', () => {
252
331
  const data2 = createTestData(100, 200)
253
332
  scheduler.update(data2, { start: 0, end: 100 })
254
333
 
255
- // Should be called 64 times (32 indicators × 2 data updates)
256
- expect(mockHost.setSharedState).toHaveBeenCalledTimes(78)
334
+ // 26 registered indicators produce states × 2 data updates (1st triggers compute, 2nd retriggers with different data)
335
+ expect(mockHost.setSharedState).toHaveBeenCalledTimes(52)
257
336
  })
258
337
  })
259
338
 
@@ -266,8 +345,8 @@ describe('IndicatorScheduler', () => {
266
345
 
267
346
  scheduler.recompute()
268
347
 
269
- // Should write all 32 indicator states (31 from PR 7 + VWAP)
270
- expect(mockHost.setSharedState).toHaveBeenCalledTimes(39)
348
+ // Should write all 26 indicator states that have registered + known to composeRenderStates
349
+ expect(mockHost.setSharedState).toHaveBeenCalledTimes(26)
271
350
  })
272
351
 
273
352
  it('should recalculate with same data and range', () => {
@@ -345,6 +424,7 @@ describe('BOLL State in scheduler', () => {
345
424
  scheduler = new IndicatorScheduler()
346
425
  mockHost = createMockPluginHost()
347
426
  scheduler.setPluginHost(mockHost)
427
+ registerTestIndicators(scheduler)
348
428
  })
349
429
 
350
430
  it('should write BOLLRenderState to StateStore after update', () => {
@@ -427,6 +507,7 @@ describe('EXPMA State in scheduler', () => {
427
507
  scheduler = new IndicatorScheduler()
428
508
  mockHost = createMockPluginHost()
429
509
  scheduler.setPluginHost(mockHost)
510
+ registerTestIndicators(scheduler)
430
511
  })
431
512
 
432
513
  it('should write EXPMARenderState to StateStore after update', () => {
@@ -480,6 +561,7 @@ describe('ENE State in scheduler', () => {
480
561
  scheduler = new IndicatorScheduler()
481
562
  mockHost = createMockPluginHost()
482
563
  scheduler.setPluginHost(mockHost)
564
+ registerTestIndicators(scheduler)
483
565
  })
484
566
 
485
567
  it('should write ENERenderState to StateStore after update', () => {
@@ -536,6 +618,7 @@ describe('Per-indicator dirty flags', () => {
536
618
  scheduler = new IndicatorScheduler()
537
619
  mockHost = createMockPluginHost()
538
620
  scheduler.setPluginHost(mockHost)
621
+ registerTestIndicators(scheduler)
539
622
  })
540
623
 
541
624
  it('updateBOLLConfig should not recalculate MA series', () => {
@@ -684,6 +767,7 @@ describe('RSI State in scheduler', () => {
684
767
  scheduler = new IndicatorScheduler()
685
768
  mockHost = createMockPluginHost()
686
769
  scheduler.setPluginHost(mockHost)
770
+ registerTestIndicators(scheduler)
687
771
  })
688
772
 
689
773
  it('should write RSIRenderState to StateStore after update', () => {