@363045841yyt/klinechart-core 0.7.3 → 0.7.5

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 (247) hide show
  1. package/README.md +201 -201
  2. package/README.zh-CN.md +201 -201
  3. package/dist/controllers/index.d.ts +1 -0
  4. package/dist/controllers/index.d.ts.map +1 -1
  5. package/dist/controllers/index.js +1 -0
  6. package/dist/controllers/index.js.map +1 -1
  7. package/dist/engine/chart.d.ts +11 -19
  8. package/dist/engine/chart.d.ts.map +1 -1
  9. package/dist/engine/chart.js +92 -109
  10. package/dist/engine/chart.js.map +1 -1
  11. package/dist/engine/renderers/Indicator/indicatorData.d.ts +1 -0
  12. package/dist/engine/renderers/Indicator/indicatorData.d.ts.map +1 -1
  13. package/dist/engine/renderers/Indicator/indicatorData.js +1 -1
  14. package/dist/engine/renderers/Indicator/indicatorData.js.map +1 -1
  15. package/dist/engine/renderers/webgl/candleSurface.js +47 -47
  16. package/dist/engine/subPaneManager.d.ts +4 -0
  17. package/dist/engine/subPaneManager.d.ts.map +1 -1
  18. package/dist/engine/subPaneManager.js +13 -0
  19. package/dist/engine/subPaneManager.js.map +1 -1
  20. package/dist/version.d.ts +1 -1
  21. package/dist/version.d.ts.map +1 -1
  22. package/dist/version.js +1 -2
  23. package/dist/version.js.map +1 -1
  24. package/package.json +129 -122
  25. package/src/__tests__/signal.test.ts +124 -124
  26. package/src/config/chartSettings.ts +66 -66
  27. package/src/controllers/__tests__/drawing.test.ts +214 -214
  28. package/src/controllers/__tests__/indicatorSelector.test.ts +481 -481
  29. package/src/controllers/__tests__/toolbar.test.ts +225 -225
  30. package/src/controllers/createChartController.ts +665 -665
  31. package/src/controllers/createDrawingController.ts +96 -96
  32. package/src/controllers/createIndicatorSelectorController.ts +307 -307
  33. package/src/controllers/createToolbarController.ts +146 -146
  34. package/src/controllers/index.ts +20 -19
  35. package/src/controllers/types.ts +284 -284
  36. package/src/engine/__tests__/chart.dpr.test.ts +401 -401
  37. package/src/engine/__tests__/paneRenderer.resize.test.ts +92 -92
  38. package/src/engine/chart-store.ts +121 -121
  39. package/src/engine/chart.d.ts +617 -617
  40. package/src/engine/chart.ts +2803 -2815
  41. package/src/engine/controller/__tests__/interaction.dpr.test.ts +259 -259
  42. package/src/engine/controller/interaction.ts +722 -722
  43. package/src/engine/controller/markerInteraction.ts +130 -130
  44. package/src/engine/controller/pinchTracker.ts +82 -82
  45. package/src/engine/controller/tooltipPosition.ts +48 -48
  46. package/src/engine/draw/__tests__/pixelAlign.spec.ts +176 -176
  47. package/src/engine/draw/pixelAlign.ts +259 -259
  48. package/src/engine/drawing/index.ts +655 -655
  49. package/src/engine/drawing/interaction.ts +842 -842
  50. package/src/engine/drawing/plugin.ts +343 -343
  51. package/src/engine/indicators/__tests__/__fixtures__/golden/atr.json +38 -38
  52. package/src/engine/indicators/__tests__/__fixtures__/golden/dema.json +14 -14
  53. package/src/engine/indicators/__tests__/__fixtures__/golden/hma.json +14 -14
  54. package/src/engine/indicators/__tests__/__fixtures__/golden/index.ts +55 -55
  55. package/src/engine/indicators/__tests__/__fixtures__/golden/kama.json +14 -14
  56. package/src/engine/indicators/__tests__/__fixtures__/golden/tema.json +14 -14
  57. package/src/engine/indicators/__tests__/__fixtures__/golden/wma.json +40 -40
  58. package/src/engine/indicators/__tests__/__fixtures__/synthetic.ts +65 -65
  59. package/src/engine/indicators/__tests__/_propertyAssertions.ts +76 -76
  60. package/src/engine/indicators/__tests__/atr.test.ts +153 -153
  61. package/src/engine/indicators/__tests__/calculators.test.ts +614 -614
  62. package/src/engine/indicators/__tests__/cmf-mfi.test.ts +100 -100
  63. package/src/engine/indicators/__tests__/dema.test.ts +73 -73
  64. package/src/engine/indicators/__tests__/donchian.test.ts +70 -70
  65. package/src/engine/indicators/__tests__/hma.test.ts +73 -73
  66. package/src/engine/indicators/__tests__/ichimoku.test.ts +105 -105
  67. package/src/engine/indicators/__tests__/kama.test.ts +80 -80
  68. package/src/engine/indicators/__tests__/keltner.test.ts +65 -65
  69. package/src/engine/indicators/__tests__/pivot-fib.test.ts +110 -110
  70. package/src/engine/indicators/__tests__/roc.test.ts +68 -68
  71. package/src/engine/indicators/__tests__/sar.test.ts +86 -86
  72. package/src/engine/indicators/__tests__/scheduler.test.ts +831 -831
  73. package/src/engine/indicators/__tests__/soa.test.ts +533 -533
  74. package/src/engine/indicators/__tests__/structure.test.ts +110 -110
  75. package/src/engine/indicators/__tests__/supertrend.test.ts +65 -65
  76. package/src/engine/indicators/__tests__/tema.test.ts +68 -68
  77. package/src/engine/indicators/__tests__/trix.test.ts +70 -70
  78. package/src/engine/indicators/__tests__/volatility.test.ts +117 -117
  79. package/src/engine/indicators/__tests__/volume.test.ts +115 -115
  80. package/src/engine/indicators/__tests__/volumeProfile.test.ts +74 -74
  81. package/src/engine/indicators/__tests__/vwap.test.ts +69 -69
  82. package/src/engine/indicators/__tests__/wma.test.ts +112 -112
  83. package/src/engine/indicators/__tests__/zones.test.ts +95 -95
  84. package/src/engine/indicators/atrState.ts +27 -27
  85. package/src/engine/indicators/bollState.ts +51 -51
  86. package/src/engine/indicators/calculators.ts +2593 -2593
  87. package/src/engine/indicators/cciState.ts +25 -25
  88. package/src/engine/indicators/chaikinVolState.ts +32 -32
  89. package/src/engine/indicators/cmfState.ts +27 -27
  90. package/src/engine/indicators/demaState.ts +27 -27
  91. package/src/engine/indicators/donchianState.ts +43 -43
  92. package/src/engine/indicators/eneState.ts +43 -43
  93. package/src/engine/indicators/expmaState.ts +43 -43
  94. package/src/engine/indicators/fastkState.ts +25 -25
  95. package/src/engine/indicators/fibState.ts +41 -41
  96. package/src/engine/indicators/hmaState.ts +27 -27
  97. package/src/engine/indicators/hvState.ts +28 -28
  98. package/src/engine/indicators/ichimokuState.ts +70 -70
  99. package/src/engine/indicators/indicator.worker.ts +169 -169
  100. package/src/engine/indicators/indicatorDefinitionRegistry.ts +62 -62
  101. package/src/engine/indicators/indicatorMetadata.ts +110 -110
  102. package/src/engine/indicators/indicatorRegistry.ts +106 -106
  103. package/src/engine/indicators/indicatorRuntime.ts +1548 -1548
  104. package/src/engine/indicators/kamaState.ts +34 -34
  105. package/src/engine/indicators/keltnerState.ts +49 -49
  106. package/src/engine/indicators/kstState.ts +42 -42
  107. package/src/engine/indicators/maState.ts +36 -36
  108. package/src/engine/indicators/macdState.ts +76 -76
  109. package/src/engine/indicators/mfiState.ts +27 -27
  110. package/src/engine/indicators/momState.ts +25 -25
  111. package/src/engine/indicators/obvState.ts +25 -25
  112. package/src/engine/indicators/parkinsonState.ts +28 -28
  113. package/src/engine/indicators/pivotState.ts +51 -51
  114. package/src/engine/indicators/pvtState.ts +25 -25
  115. package/src/engine/indicators/rocState.ts +27 -27
  116. package/src/engine/indicators/rsiState.ts +65 -65
  117. package/src/engine/indicators/sarState.ts +41 -41
  118. package/src/engine/indicators/scheduler.ts +1205 -1205
  119. package/src/engine/indicators/soa.ts +352 -352
  120. package/src/engine/indicators/stateComposer.ts +1262 -1262
  121. package/src/engine/indicators/stochState.ts +26 -26
  122. package/src/engine/indicators/structureState.ts +69 -69
  123. package/src/engine/indicators/supertrendState.ts +37 -37
  124. package/src/engine/indicators/temaState.ts +27 -27
  125. package/src/engine/indicators/trixState.ts +35 -35
  126. package/src/engine/indicators/vmaState.ts +27 -27
  127. package/src/engine/indicators/volumeProfileState.ts +63 -63
  128. package/src/engine/indicators/vwapState.ts +29 -29
  129. package/src/engine/indicators/wmaState.ts +27 -27
  130. package/src/engine/indicators/wmsrState.ts +25 -25
  131. package/src/engine/indicators/workerProtocol.ts +613 -613
  132. package/src/engine/indicators/zonesState.ts +47 -47
  133. package/src/engine/layout/pane.ts +161 -161
  134. package/src/engine/marker/registry.ts +265 -265
  135. package/src/engine/paneRenderer.ts +169 -169
  136. package/src/engine/renderers/Indicator/atr.ts +237 -237
  137. package/src/engine/renderers/Indicator/boll.ts +317 -317
  138. package/src/engine/renderers/Indicator/cci.ts +275 -275
  139. package/src/engine/renderers/Indicator/chaikinVol.ts +138 -138
  140. package/src/engine/renderers/Indicator/cmf.ts +137 -137
  141. package/src/engine/renderers/Indicator/dema.ts +136 -136
  142. package/src/engine/renderers/Indicator/donchian.ts +137 -137
  143. package/src/engine/renderers/Indicator/ene.ts +271 -271
  144. package/src/engine/renderers/Indicator/expma.ts +197 -197
  145. package/src/engine/renderers/Indicator/fastk.ts +316 -316
  146. package/src/engine/renderers/Indicator/fib.ts +141 -141
  147. package/src/engine/renderers/Indicator/hma.ts +136 -136
  148. package/src/engine/renderers/Indicator/hv.ts +124 -124
  149. package/src/engine/renderers/Indicator/ichimoku.ts +181 -181
  150. package/src/engine/renderers/Indicator/index.ts +241 -241
  151. package/src/engine/renderers/Indicator/indicatorData.ts +650 -650
  152. package/src/engine/renderers/Indicator/kama.ts +136 -136
  153. package/src/engine/renderers/Indicator/keltner.ts +137 -137
  154. package/src/engine/renderers/Indicator/kst.ts +302 -302
  155. package/src/engine/renderers/Indicator/ma.ts +200 -200
  156. package/src/engine/renderers/Indicator/macd.ts +477 -477
  157. package/src/engine/renderers/Indicator/macdLegend.ts +141 -141
  158. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +272 -272
  159. package/src/engine/renderers/Indicator/mfi.ts +142 -142
  160. package/src/engine/renderers/Indicator/mom.ts +311 -311
  161. package/src/engine/renderers/Indicator/obv.ts +123 -123
  162. package/src/engine/renderers/Indicator/parkinson.ts +124 -124
  163. package/src/engine/renderers/Indicator/pivot.ts +131 -131
  164. package/src/engine/renderers/Indicator/pvt.ts +123 -123
  165. package/src/engine/renderers/Indicator/roc.ts +143 -143
  166. package/src/engine/renderers/Indicator/rsi.ts +390 -390
  167. package/src/engine/renderers/Indicator/sar.ts +113 -113
  168. package/src/engine/renderers/Indicator/scale/atr_scale.ts +19 -19
  169. package/src/engine/renderers/Indicator/scale/cci_scale.ts +19 -19
  170. package/src/engine/renderers/Indicator/scale/fastk_scale.ts +19 -19
  171. package/src/engine/renderers/Indicator/scale/indicator_scale.ts +204 -204
  172. package/src/engine/renderers/Indicator/scale/kst_scale.ts +19 -19
  173. package/src/engine/renderers/Indicator/scale/macd_scale.ts +22 -22
  174. package/src/engine/renderers/Indicator/scale/mom_scale.ts +19 -19
  175. package/src/engine/renderers/Indicator/scale/rsi_scale.ts +19 -19
  176. package/src/engine/renderers/Indicator/scale/stoch_scale.ts +19 -19
  177. package/src/engine/renderers/Indicator/scale/volume_scale.ts +26 -26
  178. package/src/engine/renderers/Indicator/scale/wmsr_scale.ts +19 -19
  179. package/src/engine/renderers/Indicator/stoch.ts +359 -359
  180. package/src/engine/renderers/Indicator/structure.ts +126 -126
  181. package/src/engine/renderers/Indicator/subPaneConfig.ts +265 -265
  182. package/src/engine/renderers/Indicator/supertrend.ts +115 -115
  183. package/src/engine/renderers/Indicator/tema.ts +136 -136
  184. package/src/engine/renderers/Indicator/trix.ts +158 -158
  185. package/src/engine/renderers/Indicator/vma.ts +124 -124
  186. package/src/engine/renderers/Indicator/volumeProfile.ts +125 -125
  187. package/src/engine/renderers/Indicator/vwap.ts +123 -123
  188. package/src/engine/renderers/Indicator/wma.ts +136 -136
  189. package/src/engine/renderers/Indicator/wmsr.ts +328 -328
  190. package/src/engine/renderers/Indicator/zones.ts +104 -104
  191. package/src/engine/renderers/__tests__/boll.renderer.test.ts +314 -314
  192. package/src/engine/renderers/__tests__/ene.renderer.test.ts +305 -305
  193. package/src/engine/renderers/__tests__/expma.renderer.test.ts +279 -279
  194. package/src/engine/renderers/__tests__/ma.renderer.test.ts +426 -426
  195. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +502 -502
  196. package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +173 -173
  197. package/src/engine/renderers/candle.ts +459 -459
  198. package/src/engine/renderers/crosshair.ts +69 -69
  199. package/src/engine/renderers/customMarkers.ts +162 -162
  200. package/src/engine/renderers/extremaMarkers.ts +246 -246
  201. package/src/engine/renderers/gridLines.ts +90 -90
  202. package/src/engine/renderers/lastPrice.ts +97 -97
  203. package/src/engine/renderers/paneTitle.ts +136 -136
  204. package/src/engine/renderers/subVolume.ts +236 -236
  205. package/src/engine/renderers/timeAxis.ts +121 -121
  206. package/src/engine/renderers/webgl/candleSurface.ts +955 -955
  207. package/src/engine/renderers/webgl/sharedWebGLSurface.ts +146 -146
  208. package/src/engine/renderers/yAxis.ts +105 -105
  209. package/src/engine/scale/__tests__/logFormula.spec.ts +148 -148
  210. package/src/engine/scale/logFormula.ts +130 -130
  211. package/src/engine/scale/price.ts +39 -39
  212. package/src/engine/scale/priceScale.ts +264 -264
  213. package/src/engine/subPaneManager.ts +442 -427
  214. package/src/engine/theme/colors.ts +642 -642
  215. package/src/engine/theme/fonts.ts +20 -20
  216. package/src/engine/utils/klineConfig.ts +49 -49
  217. package/src/engine/utils/tickCount.ts +11 -11
  218. package/src/engine/utils/tickPosition.ts +214 -214
  219. package/src/engine/utils/zoom.ts +83 -83
  220. package/src/engine/viewport/viewport.ts +67 -67
  221. package/src/index.ts +3 -3
  222. package/src/plugin/ConfigManager.ts +93 -93
  223. package/src/plugin/EventBus.ts +77 -77
  224. package/src/plugin/HookSystem.ts +106 -106
  225. package/src/plugin/PluginHost.ts +243 -243
  226. package/src/plugin/PluginRegistry.ts +92 -92
  227. package/src/plugin/StateStore.ts +73 -73
  228. package/src/plugin/index.ts +19 -19
  229. package/src/plugin/rendererPluginManager.ts +368 -368
  230. package/src/plugin/stateKeys.ts +8 -8
  231. package/src/plugin/types.ts +526 -526
  232. package/src/reactivity/index.ts +2 -2
  233. package/src/reactivity/signal.ts +119 -119
  234. package/src/semantic/controller.ts +251 -251
  235. package/src/semantic/drawShape.ts +260 -260
  236. package/src/semantic/index.ts +28 -28
  237. package/src/semantic/schema.json +256 -256
  238. package/src/semantic/types.ts +251 -251
  239. package/src/semantic/validator.ts +349 -349
  240. package/src/types/kLine.ts +13 -13
  241. package/src/types/price.ts +56 -56
  242. package/src/types/volumePrice.ts +33 -33
  243. package/src/utils/dateFormat.ts +208 -208
  244. package/src/utils/kLineDraw/axis.ts +562 -562
  245. package/src/utils/priceToY.ts +34 -34
  246. package/src/utils/volumePrice.ts +202 -202
  247. package/src/version.ts +1 -1
@@ -1,1205 +1,1205 @@
1
- /**
2
- * IndicatorScheduler - 指标调度器(Worker 化重构版)
3
- *
4
- * 职责:
5
- * 1. 维护当前图表激活的指标配置
6
- * 2. 在数据/配置变更时触发 Worker 计算
7
- * 3. 接收 Worker 结果,组装 RenderState 并写入 StateStore
8
- * 4. 同步处理 visibleRange 变更(不走 Worker,避免异步延迟)
9
- *
10
- * 架构:
11
- * - 主线程 facade(本文件)
12
- * - Worker backend(indicator.worker.ts)
13
- * - Inline fallback backend(indicatorRuntime.ts)
14
- */
15
-
16
- import type { PluginHost } from '../../plugin'
17
- import type { KLineData } from '../../types/price'
18
- import { IndicatorRuntime } from './indicatorRuntime'
19
- import type { IndicatorWorkerResponse } from './workerProtocol'
20
- import { isWorkerResponse, PROTOCOL_VERSION } from './workerProtocol'
21
- import { composeRenderStates, composeVisibleSubIndicatorStates, computeMainIndicatorPriceRange } from './stateComposer'
22
- import type {
23
- BOLLSchedulerConfig,
24
- EXPMASchedulerConfig,
25
- ENESchedulerConfig,
26
- RSISchedulerConfig,
27
- CCISchedulerConfig,
28
- STOCHSchedulerConfig,
29
- MOMSchedulerConfig,
30
- WMSRSchedulerConfig,
31
- KSTSchedulerConfig,
32
- FASTKSchedulerConfig,
33
- MACDSchedulerConfig,
34
- ATRSchedulerConfig,
35
- WMASchedulerConfig,
36
- DEMASchedulerConfig,
37
- TEMASchedulerConfig,
38
- HMASchedulerConfig,
39
- KAMASchedulerConfig,
40
- SARSchedulerConfig,
41
- SuperTrendSchedulerConfig,
42
- KeltnerSchedulerConfig,
43
- DonchianSchedulerConfig,
44
- IchimokuSchedulerConfig,
45
- ROCSchedulerConfig,
46
- TRIXSchedulerConfig,
47
- HVSchedulerConfig,
48
- ParkinsonSchedulerConfig,
49
- ChaikinVolSchedulerConfig,
50
- VMASchedulerConfig,
51
- OBVSchedulerConfig,
52
- PVTSchedulerConfig,
53
- VWAPSchedulerConfig,
54
- CMFSchedulerConfig,
55
- MFISchedulerConfig,
56
- PivotSchedulerConfig,
57
- FibSchedulerConfig,
58
- StructureSchedulerConfig,
59
- ZonesSchedulerConfig,
60
- VolumeProfileSchedulerConfig,
61
- IndicatorConfigSnapshot,
62
- IndicatorSeriesBundle,
63
- } from './workerProtocol'
64
- import type { MAFlags } from './calculators'
65
- import { IndicatorRegistry } from './indicatorRegistry'
66
- import type { IndicatorMetadata } from './indicatorMetadata'
67
- import type { BaseIndicatorState } from '../../plugin'
68
-
69
- // Default constants for default config
70
- import { DEFAULT_ATR_PERIOD } from './atrState'
71
- import { DEFAULT_WMA_PERIOD } from './wmaState'
72
- import { DEFAULT_DEMA_PERIOD } from './demaState'
73
- import { DEFAULT_TEMA_PERIOD } from './temaState'
74
- import { DEFAULT_HMA_PERIOD } from './hmaState'
75
- import { DEFAULT_KAMA_PERIOD, DEFAULT_KAMA_FAST_PERIOD, DEFAULT_KAMA_SLOW_PERIOD } from './kamaState'
76
- import { DEFAULT_SAR_STEP, DEFAULT_SAR_MAX_STEP } from './sarState'
77
- import { DEFAULT_SUPERTREND_ATR_PERIOD, DEFAULT_SUPERTREND_MULTIPLIER } from './supertrendState'
78
- import { DEFAULT_KELTNER_EMA_PERIOD, DEFAULT_KELTNER_ATR_PERIOD, DEFAULT_KELTNER_MULTIPLIER } from './keltnerState'
79
- import { DEFAULT_DONCHIAN_PERIOD } from './donchianState'
80
- import { DEFAULT_ICHIMOKU_TENKAN, DEFAULT_ICHIMOKU_KIJUN, DEFAULT_ICHIMOKU_SPAN_B, DEFAULT_ICHIMOKU_DISPLACEMENT } from './ichimokuState'
81
- import { DEFAULT_ROC_PERIOD } from './rocState'
82
- import { DEFAULT_TRIX_PERIOD, DEFAULT_TRIX_SIGNAL_PERIOD } from './trixState'
83
- import { DEFAULT_HV_PERIOD, DEFAULT_HV_ANNUALIZATION } from './hvState'
84
- import { DEFAULT_PARKINSON_PERIOD, DEFAULT_PARKINSON_ANNUALIZATION } from './parkinsonState'
85
- import { DEFAULT_CHAIKIN_VOL_EMA_PERIOD, DEFAULT_CHAIKIN_VOL_ROC_PERIOD } from './chaikinVolState'
86
- import { DEFAULT_VMA_PERIOD } from './vmaState'
87
- import { DEFAULT_VWAP_SESSION_GAP_MS } from './vwapState'
88
- import { DEFAULT_CMF_PERIOD } from './cmfState'
89
- import { DEFAULT_MFI_PERIOD } from './mfiState'
90
- import { DEFAULT_FIB_PERIOD } from './fibState'
91
- import { DEFAULT_STRUCTURE_LEFT, DEFAULT_STRUCTURE_RIGHT } from './structureState'
92
- import { DEFAULT_ZONES_OB_LOOKBACK } from './zonesState'
93
- import { DEFAULT_VP_BINS, DEFAULT_VP_LOOKBACK, DEFAULT_VP_VALUE_AREA } from './volumeProfileState'
94
-
95
- /**
96
- * 可见范围
97
- */
98
- interface VisibleRange {
99
- start: number
100
- end: number
101
- }
102
-
103
- // 重新导出配置类型(保持向后兼容)
104
- export type {
105
- BOLLSchedulerConfig,
106
- EXPMASchedulerConfig,
107
- ENESchedulerConfig,
108
- RSISchedulerConfig,
109
- CCISchedulerConfig,
110
- STOCHSchedulerConfig,
111
- MOMSchedulerConfig,
112
- WMSRSchedulerConfig,
113
- KSTSchedulerConfig,
114
- FASTKSchedulerConfig,
115
- MACDSchedulerConfig,
116
- ATRSchedulerConfig,
117
- WMASchedulerConfig,
118
- DEMASchedulerConfig,
119
- TEMASchedulerConfig,
120
- HMASchedulerConfig,
121
- KAMASchedulerConfig,
122
- SARSchedulerConfig,
123
- SuperTrendSchedulerConfig,
124
- KeltnerSchedulerConfig,
125
- DonchianSchedulerConfig,
126
- IchimokuSchedulerConfig,
127
- ROCSchedulerConfig,
128
- TRIXSchedulerConfig,
129
- HVSchedulerConfig,
130
- ParkinsonSchedulerConfig,
131
- ChaikinVolSchedulerConfig,
132
- VMASchedulerConfig,
133
- OBVSchedulerConfig,
134
- PVTSchedulerConfig,
135
- VWAPSchedulerConfig,
136
- CMFSchedulerConfig,
137
- MFISchedulerConfig,
138
- PivotSchedulerConfig,
139
- FibSchedulerConfig,
140
- StructureSchedulerConfig,
141
- ZonesSchedulerConfig,
142
- VolumeProfileSchedulerConfig,
143
- } from './workerProtocol'
144
-
145
- /**
146
- * IndicatorScheduler - 主线程 facade
147
- */
148
- export class IndicatorScheduler {
149
- private pluginHost: PluginHost | null = null
150
- private visibleRange: VisibleRange = { start: 0, end: 0 }
151
- private activeMainIndicators: Set<string> = new Set()
152
-
153
- // 版本控制
154
- private dataVersion = 0
155
- private configVersion = 0
156
- private requestId = 0
157
- private lastAppliedRequestId = 0
158
-
159
- // 当前数据和配置快照
160
- private currentData: KLineData[] = []
161
- private configSnapshot: IndicatorConfigSnapshot = this.getDefaultConfig()
162
-
163
- // Worker 相关
164
- private worker: Worker | null = null
165
- private workerReady = false
166
- private useWorker = false
167
- private pendingRequest: {
168
- requestId: number
169
- dataVersion: number
170
- configVersion: number
171
- } | null = null
172
-
173
- // Inline fallback runtime
174
- private inlineRuntime: IndicatorRuntime | null = null
175
-
176
- // 缓存的最新结果(用于 visibleRange 变更时同步更新)
177
- private latestResult: IndicatorSeriesBundle | null = null
178
-
179
- // 重绘回调
180
- private invalidateCallback: (() => void) | null = null
181
-
182
- /** 从 Chart 获取活跃副图 paneId 列表的回调 */
183
- private getActiveSubPaneIds: (() => string[]) | null = null
184
-
185
- // 注册表
186
- private registry: IndicatorRegistry
187
-
188
- constructor() {
189
- this.registry = new IndicatorRegistry()
190
- this.initBackend()
191
- }
192
-
193
- // ============================================================================
194
- // 公共 API:指标注册/注销
195
- // ============================================================================
196
-
197
- /**
198
- * 注册新指标(支持动态扩展)
199
- */
200
- registerIndicator<T>(meta: IndicatorMetadata<T>): void {
201
- if (this.registry.has(meta.name)) {
202
- console.warn(`[IndicatorScheduler] '${meta.name}' already registered, overwriting`)
203
- }
204
- this.registry.register(meta)
205
- this.configVersion++
206
- this.triggerRecompute()
207
- console.log(`[IndicatorScheduler] Registered indicator '${meta.name}' (${meta.displayName})`)
208
- }
209
-
210
- /**
211
- * 注销指标
212
- */
213
- unregisterIndicator(name: string): boolean {
214
- const success = this.registry.unregister(name)
215
- if (success) {
216
- this.configVersion++
217
- this.triggerRecompute()
218
- console.log(`[IndicatorScheduler] Unregistered indicator '${name}'`)
219
- }
220
- return success
221
- }
222
-
223
- /**
224
- * 获取指标元数据(供渲染器查询)
225
- */
226
- getIndicatorMetadata(name: string): IndicatorMetadata | undefined {
227
- return this.registry.get(name)
228
- }
229
-
230
- /**
231
- * 获取所有已注册指标
232
- */
233
- getAllIndicators(): readonly IndicatorMetadata[] {
234
- return this.registry.getAll()
235
- }
236
-
237
- /**
238
- * 设置 PluginHost
239
- */
240
- setPluginHost(host: PluginHost): void {
241
- this.pluginHost = host
242
- host.registerService('indicatorScheduler', this)
243
- }
244
-
245
- /**
246
- * 设置重绘回调
247
- */
248
- setInvalidateCallback(callback: () => void): void {
249
- this.invalidateCallback = callback
250
- }
251
-
252
- /**
253
- * 副图增删后通知 scheduler 刷新 active mask
254
- */
255
- onSubPaneChanged(): void {
256
- if (this.latestResult) this.updateVisibleStatesOnly()
257
- }
258
-
259
- /**
260
- * 设置活跃副图 paneId 提供者(来自 Chart.getSubPaneIndicators)
261
- */
262
- setActiveSubPaneProvider(provider: () => string[]): void {
263
- this.getActiveSubPaneIds = provider
264
- }
265
-
266
- /**
267
- * 销毁调度器
268
- */
269
- destroy(): void {
270
- this.terminateWorker()
271
- this.inlineRuntime = null
272
- this.latestResult = null
273
- this.invalidateCallback = null
274
- }
275
-
276
- // ============================================================================
277
- // 初始化
278
- // ============================================================================
279
-
280
- private getDefaultConfig(): IndicatorConfigSnapshot {
281
- return {
282
- ma: { ma5: true, ma10: true, ma20: true, ma30: true, ma60: true },
283
- boll: {
284
- period: 20,
285
- multiplier: 2,
286
- showUpper: true,
287
- showMiddle: true,
288
- showLower: true,
289
- showBand: true,
290
- },
291
- expma: { fastPeriod: 12, slowPeriod: 50 },
292
- ene: { period: 10, deviation: 11 },
293
- rsi: {
294
- period1: 6,
295
- period2: 12,
296
- period3: 24,
297
- showRSI1: true,
298
- showRSI2: true,
299
- showRSI3: true,
300
- },
301
- cci: { period: 14, showCCI: true },
302
- stoch: { n: 9, m: 3, showK: true, showD: true },
303
- mom: { period: 10, showMOM: true },
304
- wmsr: { period: 14, showWMSR: true },
305
- kst: {
306
- roc1: 10,
307
- roc2: 15,
308
- roc3: 20,
309
- roc4: 30,
310
- signalPeriod: 9,
311
- showKST: true,
312
- showSignal: true,
313
- },
314
- fastk: { period: 9, showFASTK: true },
315
- macd: {
316
- fastPeriod: 12,
317
- slowPeriod: 26,
318
- signalPeriod: 9,
319
- showDIF: true,
320
- showDEA: true,
321
- showBAR: true,
322
- },
323
- atr: { period: DEFAULT_ATR_PERIOD, showATR: true },
324
- wma: { period: DEFAULT_WMA_PERIOD, showWMA: true },
325
- dema: { period: DEFAULT_DEMA_PERIOD, showDEMA: true },
326
- tema: { period: DEFAULT_TEMA_PERIOD, showTEMA: true },
327
- hma: { period: DEFAULT_HMA_PERIOD, showHMA: true },
328
- kama: {
329
- period: DEFAULT_KAMA_PERIOD,
330
- fastPeriod: DEFAULT_KAMA_FAST_PERIOD,
331
- slowPeriod: DEFAULT_KAMA_SLOW_PERIOD,
332
- showKAMA: true,
333
- },
334
- sar: { step: DEFAULT_SAR_STEP, maxStep: DEFAULT_SAR_MAX_STEP, showSAR: true },
335
- supertrend: {
336
- atrPeriod: DEFAULT_SUPERTREND_ATR_PERIOD,
337
- multiplier: DEFAULT_SUPERTREND_MULTIPLIER,
338
- showSuperTrend: true,
339
- },
340
- keltner: {
341
- emaPeriod: DEFAULT_KELTNER_EMA_PERIOD,
342
- atrPeriod: DEFAULT_KELTNER_ATR_PERIOD,
343
- multiplier: DEFAULT_KELTNER_MULTIPLIER,
344
- showUpper: true,
345
- showMiddle: true,
346
- showLower: true,
347
- },
348
- donchian: {
349
- period: DEFAULT_DONCHIAN_PERIOD,
350
- showUpper: true,
351
- showMiddle: true,
352
- showLower: true,
353
- },
354
- ichimoku: {
355
- tenkanPeriod: DEFAULT_ICHIMOKU_TENKAN,
356
- kijunPeriod: DEFAULT_ICHIMOKU_KIJUN,
357
- spanBPeriod: DEFAULT_ICHIMOKU_SPAN_B,
358
- displacement: DEFAULT_ICHIMOKU_DISPLACEMENT,
359
- showTenkan: true,
360
- showKijun: true,
361
- showSpanA: true,
362
- showSpanB: true,
363
- showCloud: true,
364
- showChikou: true,
365
- },
366
- roc: { period: DEFAULT_ROC_PERIOD, showROC: true },
367
- trix: {
368
- period: DEFAULT_TRIX_PERIOD,
369
- signalPeriod: DEFAULT_TRIX_SIGNAL_PERIOD,
370
- showTRIX: true,
371
- showSignal: true,
372
- },
373
- hv: { period: DEFAULT_HV_PERIOD, annualizationFactor: DEFAULT_HV_ANNUALIZATION, showHV: true },
374
- parkinson: { period: DEFAULT_PARKINSON_PERIOD, annualizationFactor: DEFAULT_PARKINSON_ANNUALIZATION, showParkinson: true },
375
- chaikinVol: { emaPeriod: DEFAULT_CHAIKIN_VOL_EMA_PERIOD, rocPeriod: DEFAULT_CHAIKIN_VOL_ROC_PERIOD, showChaikinVol: true },
376
- vma: { period: DEFAULT_VMA_PERIOD, showVMA: true },
377
- obv: { showOBV: true },
378
- pvt: { showPVT: true },
379
- vwap: { sessionResetGapMs: DEFAULT_VWAP_SESSION_GAP_MS, showVWAP: true },
380
- cmf: { period: DEFAULT_CMF_PERIOD, showCMF: true },
381
- mfi: { period: DEFAULT_MFI_PERIOD, showMFI: true },
382
- pivot: { showPP: true, showR1: true, showR2: true, showR3: false, showS1: true, showS2: true, showS3: false },
383
- fib: { period: DEFAULT_FIB_PERIOD, showLevels: true },
384
- structure: {
385
- leftWindow: DEFAULT_STRUCTURE_LEFT,
386
- rightWindow: DEFAULT_STRUCTURE_RIGHT,
387
- breakoutSource: 'close',
388
- showSwingLabels: true,
389
- showBOS: true,
390
- showCHOCH: true,
391
- showProvisional: false,
392
- },
393
- zones: {
394
- showFVG: true,
395
- showOB: true,
396
- showFilledZones: false,
397
- obLookback: DEFAULT_ZONES_OB_LOOKBACK,
398
- },
399
- volumeProfile: {
400
- bins: DEFAULT_VP_BINS,
401
- lookback: DEFAULT_VP_LOOKBACK,
402
- valueAreaPercent: DEFAULT_VP_VALUE_AREA,
403
- showPOC: true,
404
- showValueArea: true,
405
- },
406
- rsiPaneId: 'sub_RSI',
407
- cciPaneId: 'sub_CCI',
408
- stochPaneId: 'sub_STOCH',
409
- momPaneId: 'sub_MOM',
410
- wmsrPaneId: 'sub_WMSR',
411
- kstPaneId: 'sub_KST',
412
- fastkPaneId: 'sub_FASTK',
413
- macdPaneId: 'sub_MACD',
414
- atrPaneId: 'sub_ATR',
415
- wmaPaneId: 'sub_WMA',
416
- demaPaneId: 'sub_DEMA',
417
- temaPaneId: 'sub_TEMA',
418
- hmaPaneId: 'sub_HMA',
419
- kamaPaneId: 'sub_KAMA',
420
- sarPaneId: 'sub_SAR',
421
- supertrendPaneId: 'sub_SuperTrend',
422
- keltnerPaneId: 'sub_Keltner',
423
- donchianPaneId: 'sub_Donchian',
424
- ichimokuPaneId: 'sub_Ichimoku',
425
- rocPaneId: 'sub_ROC',
426
- trixPaneId: 'sub_TRIX',
427
- hvPaneId: 'sub_HV',
428
- parkinsonPaneId: 'sub_Parkinson',
429
- chaikinVolPaneId: 'sub_ChaikinVol',
430
- vmaPaneId: 'sub_VMA',
431
- obvPaneId: 'sub_OBV',
432
- pvtPaneId: 'sub_PVT',
433
- vwapPaneId: 'sub_VWAP',
434
- cmfPaneId: 'sub_CMF',
435
- mfiPaneId: 'sub_MFI',
436
- pivotPaneId: 'sub_Pivot',
437
- fibPaneId: 'sub_Fib',
438
- structurePaneId: 'sub_Structure',
439
- zonesPaneId: 'sub_Zones',
440
- volumeProfilePaneId: 'sub_VolumeProfile',
441
- }
442
- }
443
-
444
- private initBackend(): void {
445
- // 尝试初始化 Worker
446
- if (this.tryInitWorker()) {
447
- return
448
- }
449
- // 失败则使用 inline fallback
450
- this.initInlineRuntime()
451
- }
452
-
453
- private tryInitWorker(): boolean {
454
- console.log('[IndicatorScheduler] tryInitWorker: Worker available?', typeof Worker !== 'undefined')
455
- if (typeof Worker === 'undefined') {
456
- return false
457
- }
458
- try {
459
- // Vite 模块 Worker
460
- const workerUrl = new URL('./indicator.worker.ts', import.meta.url)
461
- console.log('[IndicatorScheduler] Creating worker from:', workerUrl.href)
462
- this.worker = new Worker(workerUrl, { type: 'module' })
463
- console.log('[IndicatorScheduler] Worker created, waiting for ready...')
464
- this.worker.onmessage = (e) => this.handleWorkerMessage(e.data)
465
- this.worker.onerror = (err) => {
466
- console.error('[IndicatorScheduler] Worker error:', err)
467
- this.fallbackToInline()
468
- }
469
- // 发送 init
470
- this.worker.postMessage({
471
- type: 'init',
472
- protocolVersion: PROTOCOL_VERSION,
473
- })
474
- return true
475
- } catch (err) {
476
- console.warn('[IndicatorScheduler] Failed to init worker:', err)
477
- return false
478
- }
479
- }
480
-
481
- private initInlineRuntime(): void {
482
- console.log('[IndicatorScheduler] Using INLINE runtime (fallback)')
483
- this.inlineRuntime = new IndicatorRuntime()
484
- this.useWorker = false
485
- this.workerReady = true
486
- }
487
-
488
- private fallbackToInline(): void {
489
- console.warn('[IndicatorScheduler] Falling back to inline runtime')
490
- this.terminateWorker()
491
- this.initInlineRuntime()
492
- // 如果有待处理的请求,用 inline 重新执行
493
- if (this.pendingRequest) {
494
- this.computeWithInline()
495
- }
496
- }
497
-
498
- private terminateWorker(): void {
499
- if (this.worker) {
500
- this.worker.terminate()
501
- this.worker = null
502
- }
503
- this.workerReady = false
504
- this.useWorker = false
505
- }
506
-
507
- // ============================================================================
508
- // Worker 消息处理
509
- // ============================================================================
510
-
511
- private handleWorkerMessage(msg: unknown): void {
512
- if (!isWorkerResponse(msg)) {
513
- console.warn('[IndicatorScheduler] Invalid worker response:', msg)
514
- return
515
- }
516
-
517
- switch (msg.type) {
518
- case 'ready':
519
- this.workerReady = true
520
- this.useWorker = true
521
- console.log('[IndicatorScheduler] Worker READY - using Worker backend')
522
- // Worker 就绪后立即补算一次,确保后续走 Worker
523
- this.triggerRecompute()
524
- break
525
-
526
- case 'seriesResult':
527
- this.handleSeriesResult(msg)
528
- break
529
-
530
- case 'error':
531
- console.error('[IndicatorScheduler] Worker error:', msg.stage, msg.message)
532
- if (this.pendingRequest && msg.requestId === this.pendingRequest.requestId) {
533
- this.fallbackToInline()
534
- }
535
- break
536
-
537
- default: {
538
- const _exhaustive: never = msg
539
- console.warn('[IndicatorScheduler] Unknown response type:', (_exhaustive as unknown as { type: string }).type)
540
- }
541
- }
542
- }
543
-
544
- private handleSeriesResult(msg: Extract<IndicatorWorkerResponse, { type: 'seriesResult' }>): void {
545
- // 检查版本是否过期
546
- if (msg.requestId < this.lastAppliedRequestId) {
547
- return // 丢弃旧结果
548
- }
549
- if (msg.dataVersion !== this.dataVersion || msg.configVersion !== this.configVersion) {
550
- return // 数据或配置已变更,丢弃旧结果
551
- }
552
-
553
- console.log(`[IndicatorScheduler] << Worker result: requestId=${msg.requestId} metrics=`, msg.metrics)
554
- this.lastAppliedRequestId = msg.requestId
555
- this.pendingRequest = null
556
- this.latestResult = msg.results
557
-
558
- // 组装并写入 states
559
- this.applyResults(msg.results)
560
-
561
- // 触发重绘
562
- this.invalidateCallback?.()
563
- }
564
-
565
- // ============================================================================
566
- // 结果应用
567
- // ============================================================================
568
-
569
- /**
570
- * 检查 state 中的 visibleMin/visibleMax 是否为 Infinity,如果是则输出 warning
571
- */
572
- private checkVisibleExtremes(state: { visibleMin: number; visibleMax: number }, indicatorName: string): void {
573
- if (!Number.isFinite(state.visibleMin) || !Number.isFinite(state.visibleMax)) {
574
- console.warn(`[IndicatorScheduler] ${indicatorName} state has non-finite visibleMin/visibleMax:`, {
575
- visibleMin: state.visibleMin,
576
- visibleMax: state.visibleMax,
577
- })
578
- }
579
- }
580
-
581
- /** 遍历注册表中的所有指标,通过 applyResult 回调写入 StateStore */
582
- private applyResults(bundle: IndicatorSeriesBundle): void {
583
- if (!this.pluginHost) return
584
-
585
- const changed = new Set(bundle._changed)
586
- const timestamp = Date.now()
587
- const states = composeRenderStates(bundle, this.visibleRange, timestamp)
588
-
589
- for (const meta of this.registry.getAll()) {
590
- if (!changed.has(meta.name)) continue
591
- if (!meta.applyResult) continue
592
-
593
- const state = (states as Record<string, unknown>)[meta.name]
594
- if (!state) continue
595
-
596
- this.checkVisibleExtremes(
597
- state as { visibleMin: number; visibleMax: number },
598
- meta.displayName,
599
- )
600
-
601
- const paneId = meta.paneIdField
602
- ? (this.configSnapshot as unknown as Record<string, string>)[meta.paneIdField as string]
603
- : meta.defaultPaneId
604
-
605
- meta.applyResult(this.pluginHost, state as BaseIndicatorState, paneId)
606
- }
607
- }
608
-
609
- /** 仅副图:重算可见范围极值并回调 applyResult(视口变更时同步更新,不走 Worker) */
610
- private updateVisibleStatesOnly(): void {
611
- if (!this.pluginHost || !this.latestResult) return
612
-
613
- const timestamp = Date.now()
614
- const activeMask = this.buildActiveSubIndicatorMask()
615
- const states = composeVisibleSubIndicatorStates(this.latestResult, this.visibleRange, timestamp, activeMask)
616
-
617
- for (const meta of this.registry.getAll()) {
618
- if (!meta.applyResult) continue
619
- if (!meta.paneIdField && meta.category === 'main') continue
620
-
621
- const state = (states as Record<string, unknown>)[meta.name]
622
- if (state === undefined) continue
623
-
624
- this.checkVisibleExtremes(
625
- state as { visibleMin: number; visibleMax: number },
626
- meta.displayName,
627
- )
628
-
629
- const paneId = meta.paneIdField
630
- ? (this.configSnapshot as unknown as Record<string, string>)[meta.paneIdField as string]
631
- : meta.defaultPaneId
632
-
633
- meta.applyResult(this.pluginHost, state as BaseIndicatorState, paneId)
634
- }
635
- }
636
-
637
- /** 遍历注册表,标记当前可见副图,仅这些指标参与计算 */
638
- private buildActiveSubIndicatorMask(): Record<string, boolean> {
639
- const activeIds = this.getActiveSubPaneIds?.() ?? []
640
- const mask: Record<string, boolean> = {}
641
- for (const meta of this.registry.getAll()) {
642
- if (!meta.paneIdField) continue
643
- const paneId = (this.configSnapshot as unknown as Record<string, string>)[meta.paneIdField as string] ?? meta.defaultPaneId
644
- mask[meta.name] = activeIds.includes(paneId) || !!(meta.allowMainPane && paneId === 'main')
645
- }
646
- return mask
647
- }
648
-
649
- /** 遍历注册表,禁用非活跃副图指标的 show* 字段,后端只算活跃指标 */
650
- private buildActiveConfig(): IndicatorConfigSnapshot {
651
- const activeIds = this.getActiveSubPaneIds?.() ?? []
652
- if (activeIds.length === 0) return { ...this.configSnapshot }
653
-
654
- const cfg: Record<string, unknown> = { ...this.configSnapshot }
655
- for (const meta of this.registry.getAll()) {
656
- if (!meta.paneIdField) continue
657
- const paneId = cfg[meta.paneIdField] as string
658
- if (!activeIds.includes(paneId) && paneId !== 'main') {
659
- const subCfg = { ...(cfg[meta.name] as Record<string, unknown>) }
660
- for (const k of Object.keys(subCfg)) {
661
- if (k.startsWith('show')) {
662
- subCfg[k] = false
663
- }
664
- }
665
- cfg[meta.name] = subCfg
666
- }
667
- }
668
- return cfg as unknown as IndicatorConfigSnapshot
669
- }
670
-
671
-
672
- // ============================================================================
673
- // Public API
674
- // ============================================================================
675
-
676
- /**
677
- * 数据变更时调用
678
- */
679
- update(data: KLineData[], visibleRange: VisibleRange): void {
680
- this.currentData = data
681
- this.visibleRange = visibleRange
682
- this.dataVersion++
683
-
684
- if (this.useWorker && this.worker && this.workerReady) {
685
- this.computeWithWorker()
686
- } else {
687
- this.computeWithInline()
688
- }
689
- }
690
-
691
- /**
692
- * 视口变更时调用 - 同步处理,不走 Worker
693
- */
694
- updateVisibleRange(visibleRange: VisibleRange): void {
695
- this.visibleRange = visibleRange
696
-
697
- // 基于缓存的 series 同步更新极值
698
- if (this.latestResult) {
699
- this.updateVisibleStatesOnly()
700
- }
701
- }
702
-
703
- /**
704
- * MA 配置变更
705
- */
706
- updateMAConfig(config: MAFlags): void {
707
- this.configSnapshot.ma = { ...config }
708
- this.configVersion++
709
- this.triggerRecompute()
710
- }
711
-
712
- /**
713
- * BOLL 配置变更
714
- */
715
- updateBOLLConfig(config: Partial<BOLLSchedulerConfig>): void {
716
- this.configSnapshot.boll = { ...this.configSnapshot.boll, ...config }
717
- this.configVersion++
718
- this.triggerRecompute()
719
- }
720
-
721
- /**
722
- * EXPMA 配置变更
723
- */
724
- updateEXPMAConfig(config: Partial<EXPMASchedulerConfig>): void {
725
- this.configSnapshot.expma = { ...this.configSnapshot.expma, ...config }
726
- this.configVersion++
727
- this.triggerRecompute()
728
- }
729
-
730
- /**
731
- * ENE 配置变更
732
- */
733
- updateENEConfig(config: Partial<ENESchedulerConfig>): void {
734
- this.configSnapshot.ene = { ...this.configSnapshot.ene, ...config }
735
- this.configVersion++
736
- this.triggerRecompute()
737
- }
738
-
739
- /**
740
- * RSI 配置变更
741
- */
742
- updateRSIConfig(config: Partial<RSISchedulerConfig>, paneId?: string): void {
743
- if (paneId !== undefined) {
744
- this.configSnapshot.rsiPaneId = paneId
745
- }
746
- this.configSnapshot.rsi = { ...this.configSnapshot.rsi, ...config }
747
- this.configVersion++
748
- this.triggerRecompute()
749
- }
750
-
751
- /**
752
- * CCI 配置变更
753
- */
754
- updateCCIConfig(config: Partial<CCISchedulerConfig>, paneId?: string): void {
755
- if (paneId !== undefined) {
756
- this.configSnapshot.cciPaneId = paneId
757
- }
758
- this.configSnapshot.cci = { ...this.configSnapshot.cci, ...config }
759
- this.configVersion++
760
- this.triggerRecompute()
761
- }
762
-
763
- /**
764
- * STOCH 配置变更
765
- */
766
- updateSTOCHConfig(config: Partial<STOCHSchedulerConfig>, paneId?: string): void {
767
- if (paneId !== undefined) {
768
- this.configSnapshot.stochPaneId = paneId
769
- }
770
- this.configSnapshot.stoch = { ...this.configSnapshot.stoch, ...config }
771
- this.configVersion++
772
- this.triggerRecompute()
773
- }
774
-
775
- /**
776
- * MOM 配置变更
777
- */
778
- updateMOMConfig(config: Partial<MOMSchedulerConfig>, paneId?: string): void {
779
- if (paneId !== undefined) {
780
- this.configSnapshot.momPaneId = paneId
781
- }
782
- this.configSnapshot.mom = { ...this.configSnapshot.mom, ...config }
783
- this.configVersion++
784
- this.triggerRecompute()
785
- }
786
-
787
- /**
788
- * WMSR 配置变更
789
- */
790
- updateWMSRConfig(config: Partial<WMSRSchedulerConfig>, paneId?: string): void {
791
- if (paneId !== undefined) {
792
- this.configSnapshot.wmsrPaneId = paneId
793
- }
794
- this.configSnapshot.wmsr = { ...this.configSnapshot.wmsr, ...config }
795
- this.configVersion++
796
- this.triggerRecompute()
797
- }
798
-
799
- /**
800
- * KST 配置变更
801
- */
802
- updateKSTConfig(config: Partial<KSTSchedulerConfig>, paneId?: string): void {
803
- if (paneId !== undefined) {
804
- this.configSnapshot.kstPaneId = paneId
805
- }
806
- this.configSnapshot.kst = { ...this.configSnapshot.kst, ...config }
807
- this.configVersion++
808
- this.triggerRecompute()
809
- }
810
-
811
- /**
812
- * FASTK 配置变更
813
- */
814
- updateFASTKConfig(config: Partial<FASTKSchedulerConfig>, paneId?: string): void {
815
- if (paneId !== undefined) {
816
- this.configSnapshot.fastkPaneId = paneId
817
- }
818
- this.configSnapshot.fastk = { ...this.configSnapshot.fastk, ...config }
819
- this.configVersion++
820
- this.triggerRecompute()
821
- }
822
-
823
- /**
824
- * MACD 配置变更
825
- */
826
- updateMACDConfig(config: Partial<MACDSchedulerConfig>, paneId?: string): void {
827
- if (paneId !== undefined) {
828
- this.configSnapshot.macdPaneId = paneId
829
- }
830
- this.configSnapshot.macd = { ...this.configSnapshot.macd, ...config }
831
- this.configVersion++
832
- this.triggerRecompute()
833
- }
834
-
835
- /**
836
- * ATR 配置变更
837
- */
838
- updateATRConfig(config: Partial<ATRSchedulerConfig>, paneId?: string): void {
839
- if (paneId !== undefined) {
840
- this.configSnapshot.atrPaneId = paneId
841
- }
842
- this.configSnapshot.atr = { ...this.configSnapshot.atr, ...config }
843
- this.configVersion++
844
- this.triggerRecompute()
845
- }
846
-
847
- /**
848
- * WMA 配置变更
849
- */
850
- updateWMAConfig(config: Partial<WMASchedulerConfig>, paneId?: string): void {
851
- if (paneId !== undefined) {
852
- this.configSnapshot.wmaPaneId = paneId
853
- }
854
- this.configSnapshot.wma = { ...this.configSnapshot.wma, ...config }
855
- this.configVersion++
856
- this.triggerRecompute()
857
- }
858
-
859
- /**
860
- * DEMA 配置变更
861
- */
862
- updateDEMAConfig(config: Partial<DEMASchedulerConfig>, paneId?: string): void {
863
- if (paneId !== undefined) {
864
- this.configSnapshot.demaPaneId = paneId
865
- }
866
- this.configSnapshot.dema = { ...this.configSnapshot.dema, ...config }
867
- this.configVersion++
868
- this.triggerRecompute()
869
- }
870
-
871
- /**
872
- * TEMA 配置变更
873
- */
874
- updateTEMAConfig(config: Partial<TEMASchedulerConfig>, paneId?: string): void {
875
- if (paneId !== undefined) {
876
- this.configSnapshot.temaPaneId = paneId
877
- }
878
- this.configSnapshot.tema = { ...this.configSnapshot.tema, ...config }
879
- this.configVersion++
880
- this.triggerRecompute()
881
- }
882
-
883
- /**
884
- * HMA 配置变更
885
- */
886
- updateHMAConfig(config: Partial<HMASchedulerConfig>, paneId?: string): void {
887
- if (paneId !== undefined) {
888
- this.configSnapshot.hmaPaneId = paneId
889
- }
890
- this.configSnapshot.hma = { ...this.configSnapshot.hma, ...config }
891
- this.configVersion++
892
- this.triggerRecompute()
893
- }
894
-
895
- /**
896
- * KAMA 配置变更
897
- */
898
- updateKAMAConfig(config: Partial<KAMASchedulerConfig>, paneId?: string): void {
899
- if (paneId !== undefined) {
900
- this.configSnapshot.kamaPaneId = paneId
901
- }
902
- this.configSnapshot.kama = { ...this.configSnapshot.kama, ...config }
903
- this.configVersion++
904
- this.triggerRecompute()
905
- }
906
-
907
- /**
908
- * SAR 配置变更
909
- */
910
- updateSARConfig(config: Partial<SARSchedulerConfig>, paneId?: string): void {
911
- if (paneId !== undefined) {
912
- this.configSnapshot.sarPaneId = paneId
913
- }
914
- this.configSnapshot.sar = { ...this.configSnapshot.sar, ...config }
915
- this.configVersion++
916
- this.triggerRecompute()
917
- }
918
-
919
- /**
920
- * SuperTrend 配置变更
921
- */
922
- updateSuperTrendConfig(config: Partial<SuperTrendSchedulerConfig>, paneId?: string): void {
923
- if (paneId !== undefined) this.configSnapshot.supertrendPaneId = paneId
924
- this.configSnapshot.supertrend = { ...this.configSnapshot.supertrend, ...config }
925
- this.configVersion++
926
- this.triggerRecompute()
927
- }
928
-
929
- /**
930
- * Keltner 配置变更
931
- */
932
- updateKeltnerConfig(config: Partial<KeltnerSchedulerConfig>, paneId?: string): void {
933
- if (paneId !== undefined) this.configSnapshot.keltnerPaneId = paneId
934
- this.configSnapshot.keltner = { ...this.configSnapshot.keltner, ...config }
935
- this.configVersion++
936
- this.triggerRecompute()
937
- }
938
-
939
- /**
940
- * Donchian 配置变更
941
- */
942
- updateDonchianConfig(config: Partial<DonchianSchedulerConfig>, paneId?: string): void {
943
- if (paneId !== undefined) this.configSnapshot.donchianPaneId = paneId
944
- this.configSnapshot.donchian = { ...this.configSnapshot.donchian, ...config }
945
- this.configVersion++
946
- this.triggerRecompute()
947
- }
948
-
949
- /**
950
- * Ichimoku 配置变更
951
- */
952
- updateIchimokuConfig(config: Partial<IchimokuSchedulerConfig>, paneId?: string): void {
953
- if (paneId !== undefined) this.configSnapshot.ichimokuPaneId = paneId
954
- this.configSnapshot.ichimoku = { ...this.configSnapshot.ichimoku, ...config }
955
- this.configVersion++
956
- this.triggerRecompute()
957
- }
958
-
959
- /**
960
- * ROC 配置变更
961
- */
962
- updateROCConfig(config: Partial<ROCSchedulerConfig>, paneId?: string): void {
963
- if (paneId !== undefined) this.configSnapshot.rocPaneId = paneId
964
- this.configSnapshot.roc = { ...this.configSnapshot.roc, ...config }
965
- this.configVersion++
966
- this.triggerRecompute()
967
- }
968
-
969
- /**
970
- * TRIX 配置变更
971
- */
972
- updateTRIXConfig(config: Partial<TRIXSchedulerConfig>, paneId?: string): void {
973
- if (paneId !== undefined) this.configSnapshot.trixPaneId = paneId
974
- this.configSnapshot.trix = { ...this.configSnapshot.trix, ...config }
975
- this.configVersion++
976
- this.triggerRecompute()
977
- }
978
-
979
- /**
980
- * HV 配置变更
981
- */
982
- updateHVConfig(config: Partial<HVSchedulerConfig>, paneId?: string): void {
983
- if (paneId !== undefined) this.configSnapshot.hvPaneId = paneId
984
- this.configSnapshot.hv = { ...this.configSnapshot.hv, ...config }
985
- this.configVersion++
986
- this.triggerRecompute()
987
- }
988
-
989
- /**
990
- * Parkinson 配置变更
991
- */
992
- updateParkinsonConfig(config: Partial<ParkinsonSchedulerConfig>, paneId?: string): void {
993
- if (paneId !== undefined) this.configSnapshot.parkinsonPaneId = paneId
994
- this.configSnapshot.parkinson = { ...this.configSnapshot.parkinson, ...config }
995
- this.configVersion++
996
- this.triggerRecompute()
997
- }
998
-
999
- /**
1000
- * ChaikinVol 配置变更
1001
- */
1002
- updateChaikinVolConfig(config: Partial<ChaikinVolSchedulerConfig>, paneId?: string): void {
1003
- if (paneId !== undefined) this.configSnapshot.chaikinVolPaneId = paneId
1004
- this.configSnapshot.chaikinVol = { ...this.configSnapshot.chaikinVol, ...config }
1005
- this.configVersion++
1006
- this.triggerRecompute()
1007
- }
1008
-
1009
- /**
1010
- * VMA 配置变更
1011
- */
1012
- updateVMAConfig(config: Partial<VMASchedulerConfig>, paneId?: string): void {
1013
- if (paneId !== undefined) this.configSnapshot.vmaPaneId = paneId
1014
- this.configSnapshot.vma = { ...this.configSnapshot.vma, ...config }
1015
- this.configVersion++
1016
- this.triggerRecompute()
1017
- }
1018
-
1019
- /**
1020
- * OBV 配置变更
1021
- */
1022
- updateOBVConfig(config: Partial<OBVSchedulerConfig>, paneId?: string): void {
1023
- if (paneId !== undefined) this.configSnapshot.obvPaneId = paneId
1024
- this.configSnapshot.obv = { ...this.configSnapshot.obv, ...config }
1025
- this.configVersion++
1026
- this.triggerRecompute()
1027
- }
1028
-
1029
- /**
1030
- * PVT 配置变更
1031
- */
1032
- updatePVTConfig(config: Partial<PVTSchedulerConfig>, paneId?: string): void {
1033
- if (paneId !== undefined) this.configSnapshot.pvtPaneId = paneId
1034
- this.configSnapshot.pvt = { ...this.configSnapshot.pvt, ...config }
1035
- this.configVersion++
1036
- this.triggerRecompute()
1037
- }
1038
-
1039
- /**
1040
- * VWAP 配置变更
1041
- */
1042
- updateVWAPConfig(config: Partial<VWAPSchedulerConfig>, paneId?: string): void {
1043
- if (paneId !== undefined) this.configSnapshot.vwapPaneId = paneId
1044
- this.configSnapshot.vwap = { ...this.configSnapshot.vwap, ...config }
1045
- this.configVersion++
1046
- this.triggerRecompute()
1047
- }
1048
-
1049
- /** CMF 配置变更 */
1050
- updateCMFConfig(config: Partial<CMFSchedulerConfig>, paneId?: string): void {
1051
- if (paneId !== undefined) this.configSnapshot.cmfPaneId = paneId
1052
- this.configSnapshot.cmf = { ...this.configSnapshot.cmf, ...config }
1053
- this.configVersion++
1054
- this.triggerRecompute()
1055
- }
1056
-
1057
- /** MFI 配置变更 */
1058
- updateMFIConfig(config: Partial<MFISchedulerConfig>, paneId?: string): void {
1059
- if (paneId !== undefined) this.configSnapshot.mfiPaneId = paneId
1060
- this.configSnapshot.mfi = { ...this.configSnapshot.mfi, ...config }
1061
- this.configVersion++
1062
- this.triggerRecompute()
1063
- }
1064
-
1065
- /** Pivot 配置变更 */
1066
- updatePivotConfig(config: Partial<PivotSchedulerConfig>, paneId?: string): void {
1067
- if (paneId !== undefined) this.configSnapshot.pivotPaneId = paneId
1068
- this.configSnapshot.pivot = { ...this.configSnapshot.pivot, ...config }
1069
- this.configVersion++
1070
- this.triggerRecompute()
1071
- }
1072
-
1073
- /** Fib 配置变更 */
1074
- updateFibConfig(config: Partial<FibSchedulerConfig>, paneId?: string): void {
1075
- if (paneId !== undefined) this.configSnapshot.fibPaneId = paneId
1076
- this.configSnapshot.fib = { ...this.configSnapshot.fib, ...config }
1077
- this.configVersion++
1078
- this.triggerRecompute()
1079
- }
1080
-
1081
- /** Structure 配置变更 */
1082
- updateStructureConfig(config: Partial<StructureSchedulerConfig>, paneId?: string): void {
1083
- if (paneId !== undefined) this.configSnapshot.structurePaneId = paneId
1084
- this.configSnapshot.structure = { ...this.configSnapshot.structure, ...config }
1085
- this.configVersion++
1086
- this.triggerRecompute()
1087
- }
1088
-
1089
- /** Zones 配置变更 */
1090
- updateZonesConfig(config: Partial<ZonesSchedulerConfig>, paneId?: string): void {
1091
- if (paneId !== undefined) this.configSnapshot.zonesPaneId = paneId
1092
- this.configSnapshot.zones = { ...this.configSnapshot.zones, ...config }
1093
- this.configVersion++
1094
- this.triggerRecompute()
1095
- }
1096
-
1097
- /** Volume Profile 配置变更 */
1098
- updateVolumeProfileConfig(config: Partial<VolumeProfileSchedulerConfig>, paneId?: string): void {
1099
- if (paneId !== undefined) this.configSnapshot.volumeProfilePaneId = paneId
1100
- this.configSnapshot.volumeProfile = { ...this.configSnapshot.volumeProfile, ...config }
1101
- this.configVersion++
1102
- this.triggerRecompute()
1103
- }
1104
-
1105
- /**
1106
- * 设置当前激活的主图指标
1107
- */
1108
- setActiveMainIndicators(indicators: string[]): void {
1109
- this.activeMainIndicators = new Set(indicators.map(i => i.toLowerCase()))
1110
- }
1111
-
1112
- /**
1113
- * 获取主图指标价格范围
1114
- */
1115
- getMainIndicatorPriceRange(): { min: number; max: number } | null {
1116
- if (!this.latestResult) return null
1117
- return computeMainIndicatorPriceRange(
1118
- this.latestResult,
1119
- this.visibleRange,
1120
- this.activeMainIndicators
1121
- )
1122
- }
1123
-
1124
- /**
1125
- * 强制全部重算
1126
- */
1127
- recompute(): void {
1128
- // 强制 inline runtime 重新计算(无视脏标记)
1129
- if (this.inlineRuntime) {
1130
- this.inlineRuntime.forceDirty()
1131
- }
1132
- // 递增版本号,确保 worker 端也会重新计算
1133
- this.dataVersion++
1134
- this.triggerRecompute()
1135
- }
1136
-
1137
- // ============================================================================
1138
- // 计算触发
1139
- // ============================================================================
1140
-
1141
- private triggerRecompute(): void {
1142
- if (this.useWorker && this.worker && this.workerReady) {
1143
- this.computeWithWorker()
1144
- } else {
1145
- this.computeWithInline()
1146
- }
1147
- }
1148
-
1149
- private computeWithWorker(): void {
1150
- if (!this.worker || !this.workerReady) return
1151
-
1152
- console.log(`[IndicatorScheduler] >> Worker compute: requestId=${this.requestId + 1} dataV=${this.dataVersion} configV=${this.configVersion}`)
1153
- this.requestId++
1154
- this.pendingRequest = {
1155
- requestId: this.requestId,
1156
- dataVersion: this.dataVersion,
1157
- configVersion: this.configVersion,
1158
- }
1159
-
1160
- // 发送数据(首次或变更时)
1161
- this.worker.postMessage({
1162
- type: 'setData',
1163
- dataVersion: this.dataVersion,
1164
- format: 'aos',
1165
- data: this.currentData,
1166
- })
1167
-
1168
- // 发送配置(仅活跃副图)
1169
- this.worker.postMessage({
1170
- type: 'setConfig',
1171
- configVersion: this.configVersion,
1172
- configs: this.buildActiveConfig(),
1173
- })
1174
-
1175
- // 请求计算
1176
- this.worker.postMessage({
1177
- type: 'computeSeries',
1178
- requestId: this.requestId,
1179
- dataVersion: this.dataVersion,
1180
- configVersion: this.configVersion,
1181
- })
1182
- }
1183
-
1184
- private computeWithInline(): void {
1185
- if (!this.inlineRuntime) {
1186
- this.inlineRuntime = new IndicatorRuntime()
1187
- }
1188
-
1189
- console.log(`[IndicatorScheduler] >> INLINE compute: dataV=${this.dataVersion} configV=${this.configVersion}`)
1190
-
1191
- // 设置数据和配置(仅活跃副图)
1192
- this.inlineRuntime.setData(this.currentData, this.dataVersion)
1193
- this.inlineRuntime.setConfig(this.buildActiveConfig(), this.configVersion)
1194
-
1195
- // 同步计算
1196
- const results = this.inlineRuntime.computeSeries()
1197
- this.latestResult = results
1198
-
1199
- // 组装并写入 states
1200
- this.applyResults(results)
1201
-
1202
- // 触发重绘
1203
- this.invalidateCallback?.()
1204
- }
1205
- }
1
+ /**
2
+ * IndicatorScheduler - 指标调度器(Worker 化重构版)
3
+ *
4
+ * 职责:
5
+ * 1. 维护当前图表激活的指标配置
6
+ * 2. 在数据/配置变更时触发 Worker 计算
7
+ * 3. 接收 Worker 结果,组装 RenderState 并写入 StateStore
8
+ * 4. 同步处理 visibleRange 变更(不走 Worker,避免异步延迟)
9
+ *
10
+ * 架构:
11
+ * - 主线程 facade(本文件)
12
+ * - Worker backend(indicator.worker.ts)
13
+ * - Inline fallback backend(indicatorRuntime.ts)
14
+ */
15
+
16
+ import type { PluginHost } from '../../plugin'
17
+ import type { KLineData } from '../../types/price'
18
+ import { IndicatorRuntime } from './indicatorRuntime'
19
+ import type { IndicatorWorkerResponse } from './workerProtocol'
20
+ import { isWorkerResponse, PROTOCOL_VERSION } from './workerProtocol'
21
+ import { composeRenderStates, composeVisibleSubIndicatorStates, computeMainIndicatorPriceRange } from './stateComposer'
22
+ import type {
23
+ BOLLSchedulerConfig,
24
+ EXPMASchedulerConfig,
25
+ ENESchedulerConfig,
26
+ RSISchedulerConfig,
27
+ CCISchedulerConfig,
28
+ STOCHSchedulerConfig,
29
+ MOMSchedulerConfig,
30
+ WMSRSchedulerConfig,
31
+ KSTSchedulerConfig,
32
+ FASTKSchedulerConfig,
33
+ MACDSchedulerConfig,
34
+ ATRSchedulerConfig,
35
+ WMASchedulerConfig,
36
+ DEMASchedulerConfig,
37
+ TEMASchedulerConfig,
38
+ HMASchedulerConfig,
39
+ KAMASchedulerConfig,
40
+ SARSchedulerConfig,
41
+ SuperTrendSchedulerConfig,
42
+ KeltnerSchedulerConfig,
43
+ DonchianSchedulerConfig,
44
+ IchimokuSchedulerConfig,
45
+ ROCSchedulerConfig,
46
+ TRIXSchedulerConfig,
47
+ HVSchedulerConfig,
48
+ ParkinsonSchedulerConfig,
49
+ ChaikinVolSchedulerConfig,
50
+ VMASchedulerConfig,
51
+ OBVSchedulerConfig,
52
+ PVTSchedulerConfig,
53
+ VWAPSchedulerConfig,
54
+ CMFSchedulerConfig,
55
+ MFISchedulerConfig,
56
+ PivotSchedulerConfig,
57
+ FibSchedulerConfig,
58
+ StructureSchedulerConfig,
59
+ ZonesSchedulerConfig,
60
+ VolumeProfileSchedulerConfig,
61
+ IndicatorConfigSnapshot,
62
+ IndicatorSeriesBundle,
63
+ } from './workerProtocol'
64
+ import type { MAFlags } from './calculators'
65
+ import { IndicatorRegistry } from './indicatorRegistry'
66
+ import type { IndicatorMetadata } from './indicatorMetadata'
67
+ import type { BaseIndicatorState } from '../../plugin'
68
+
69
+ // Default constants for default config
70
+ import { DEFAULT_ATR_PERIOD } from './atrState'
71
+ import { DEFAULT_WMA_PERIOD } from './wmaState'
72
+ import { DEFAULT_DEMA_PERIOD } from './demaState'
73
+ import { DEFAULT_TEMA_PERIOD } from './temaState'
74
+ import { DEFAULT_HMA_PERIOD } from './hmaState'
75
+ import { DEFAULT_KAMA_PERIOD, DEFAULT_KAMA_FAST_PERIOD, DEFAULT_KAMA_SLOW_PERIOD } from './kamaState'
76
+ import { DEFAULT_SAR_STEP, DEFAULT_SAR_MAX_STEP } from './sarState'
77
+ import { DEFAULT_SUPERTREND_ATR_PERIOD, DEFAULT_SUPERTREND_MULTIPLIER } from './supertrendState'
78
+ import { DEFAULT_KELTNER_EMA_PERIOD, DEFAULT_KELTNER_ATR_PERIOD, DEFAULT_KELTNER_MULTIPLIER } from './keltnerState'
79
+ import { DEFAULT_DONCHIAN_PERIOD } from './donchianState'
80
+ import { DEFAULT_ICHIMOKU_TENKAN, DEFAULT_ICHIMOKU_KIJUN, DEFAULT_ICHIMOKU_SPAN_B, DEFAULT_ICHIMOKU_DISPLACEMENT } from './ichimokuState'
81
+ import { DEFAULT_ROC_PERIOD } from './rocState'
82
+ import { DEFAULT_TRIX_PERIOD, DEFAULT_TRIX_SIGNAL_PERIOD } from './trixState'
83
+ import { DEFAULT_HV_PERIOD, DEFAULT_HV_ANNUALIZATION } from './hvState'
84
+ import { DEFAULT_PARKINSON_PERIOD, DEFAULT_PARKINSON_ANNUALIZATION } from './parkinsonState'
85
+ import { DEFAULT_CHAIKIN_VOL_EMA_PERIOD, DEFAULT_CHAIKIN_VOL_ROC_PERIOD } from './chaikinVolState'
86
+ import { DEFAULT_VMA_PERIOD } from './vmaState'
87
+ import { DEFAULT_VWAP_SESSION_GAP_MS } from './vwapState'
88
+ import { DEFAULT_CMF_PERIOD } from './cmfState'
89
+ import { DEFAULT_MFI_PERIOD } from './mfiState'
90
+ import { DEFAULT_FIB_PERIOD } from './fibState'
91
+ import { DEFAULT_STRUCTURE_LEFT, DEFAULT_STRUCTURE_RIGHT } from './structureState'
92
+ import { DEFAULT_ZONES_OB_LOOKBACK } from './zonesState'
93
+ import { DEFAULT_VP_BINS, DEFAULT_VP_LOOKBACK, DEFAULT_VP_VALUE_AREA } from './volumeProfileState'
94
+
95
+ /**
96
+ * 可见范围
97
+ */
98
+ interface VisibleRange {
99
+ start: number
100
+ end: number
101
+ }
102
+
103
+ // 重新导出配置类型(保持向后兼容)
104
+ export type {
105
+ BOLLSchedulerConfig,
106
+ EXPMASchedulerConfig,
107
+ ENESchedulerConfig,
108
+ RSISchedulerConfig,
109
+ CCISchedulerConfig,
110
+ STOCHSchedulerConfig,
111
+ MOMSchedulerConfig,
112
+ WMSRSchedulerConfig,
113
+ KSTSchedulerConfig,
114
+ FASTKSchedulerConfig,
115
+ MACDSchedulerConfig,
116
+ ATRSchedulerConfig,
117
+ WMASchedulerConfig,
118
+ DEMASchedulerConfig,
119
+ TEMASchedulerConfig,
120
+ HMASchedulerConfig,
121
+ KAMASchedulerConfig,
122
+ SARSchedulerConfig,
123
+ SuperTrendSchedulerConfig,
124
+ KeltnerSchedulerConfig,
125
+ DonchianSchedulerConfig,
126
+ IchimokuSchedulerConfig,
127
+ ROCSchedulerConfig,
128
+ TRIXSchedulerConfig,
129
+ HVSchedulerConfig,
130
+ ParkinsonSchedulerConfig,
131
+ ChaikinVolSchedulerConfig,
132
+ VMASchedulerConfig,
133
+ OBVSchedulerConfig,
134
+ PVTSchedulerConfig,
135
+ VWAPSchedulerConfig,
136
+ CMFSchedulerConfig,
137
+ MFISchedulerConfig,
138
+ PivotSchedulerConfig,
139
+ FibSchedulerConfig,
140
+ StructureSchedulerConfig,
141
+ ZonesSchedulerConfig,
142
+ VolumeProfileSchedulerConfig,
143
+ } from './workerProtocol'
144
+
145
+ /**
146
+ * IndicatorScheduler - 主线程 facade
147
+ */
148
+ export class IndicatorScheduler {
149
+ private pluginHost: PluginHost | null = null
150
+ private visibleRange: VisibleRange = { start: 0, end: 0 }
151
+ private activeMainIndicators: Set<string> = new Set()
152
+
153
+ // 版本控制
154
+ private dataVersion = 0
155
+ private configVersion = 0
156
+ private requestId = 0
157
+ private lastAppliedRequestId = 0
158
+
159
+ // 当前数据和配置快照
160
+ private currentData: KLineData[] = []
161
+ private configSnapshot: IndicatorConfigSnapshot = this.getDefaultConfig()
162
+
163
+ // Worker 相关
164
+ private worker: Worker | null = null
165
+ private workerReady = false
166
+ private useWorker = false
167
+ private pendingRequest: {
168
+ requestId: number
169
+ dataVersion: number
170
+ configVersion: number
171
+ } | null = null
172
+
173
+ // Inline fallback runtime
174
+ private inlineRuntime: IndicatorRuntime | null = null
175
+
176
+ // 缓存的最新结果(用于 visibleRange 变更时同步更新)
177
+ private latestResult: IndicatorSeriesBundle | null = null
178
+
179
+ // 重绘回调
180
+ private invalidateCallback: (() => void) | null = null
181
+
182
+ /** 从 Chart 获取活跃副图 paneId 列表的回调 */
183
+ private getActiveSubPaneIds: (() => string[]) | null = null
184
+
185
+ // 注册表
186
+ private registry: IndicatorRegistry
187
+
188
+ constructor() {
189
+ this.registry = new IndicatorRegistry()
190
+ this.initBackend()
191
+ }
192
+
193
+ // ============================================================================
194
+ // 公共 API:指标注册/注销
195
+ // ============================================================================
196
+
197
+ /**
198
+ * 注册新指标(支持动态扩展)
199
+ */
200
+ registerIndicator<T>(meta: IndicatorMetadata<T>): void {
201
+ if (this.registry.has(meta.name)) {
202
+ console.warn(`[IndicatorScheduler] '${meta.name}' already registered, overwriting`)
203
+ }
204
+ this.registry.register(meta)
205
+ this.configVersion++
206
+ this.triggerRecompute()
207
+ console.log(`[IndicatorScheduler] Registered indicator '${meta.name}' (${meta.displayName})`)
208
+ }
209
+
210
+ /**
211
+ * 注销指标
212
+ */
213
+ unregisterIndicator(name: string): boolean {
214
+ const success = this.registry.unregister(name)
215
+ if (success) {
216
+ this.configVersion++
217
+ this.triggerRecompute()
218
+ console.log(`[IndicatorScheduler] Unregistered indicator '${name}'`)
219
+ }
220
+ return success
221
+ }
222
+
223
+ /**
224
+ * 获取指标元数据(供渲染器查询)
225
+ */
226
+ getIndicatorMetadata(name: string): IndicatorMetadata | undefined {
227
+ return this.registry.get(name)
228
+ }
229
+
230
+ /**
231
+ * 获取所有已注册指标
232
+ */
233
+ getAllIndicators(): readonly IndicatorMetadata[] {
234
+ return this.registry.getAll()
235
+ }
236
+
237
+ /**
238
+ * 设置 PluginHost
239
+ */
240
+ setPluginHost(host: PluginHost): void {
241
+ this.pluginHost = host
242
+ host.registerService('indicatorScheduler', this)
243
+ }
244
+
245
+ /**
246
+ * 设置重绘回调
247
+ */
248
+ setInvalidateCallback(callback: () => void): void {
249
+ this.invalidateCallback = callback
250
+ }
251
+
252
+ /**
253
+ * 副图增删后通知 scheduler 刷新 active mask
254
+ */
255
+ onSubPaneChanged(): void {
256
+ if (this.latestResult) this.updateVisibleStatesOnly()
257
+ }
258
+
259
+ /**
260
+ * 设置活跃副图 paneId 提供者(来自 Chart.getSubPaneIndicators)
261
+ */
262
+ setActiveSubPaneProvider(provider: () => string[]): void {
263
+ this.getActiveSubPaneIds = provider
264
+ }
265
+
266
+ /**
267
+ * 销毁调度器
268
+ */
269
+ destroy(): void {
270
+ this.terminateWorker()
271
+ this.inlineRuntime = null
272
+ this.latestResult = null
273
+ this.invalidateCallback = null
274
+ }
275
+
276
+ // ============================================================================
277
+ // 初始化
278
+ // ============================================================================
279
+
280
+ private getDefaultConfig(): IndicatorConfigSnapshot {
281
+ return {
282
+ ma: { ma5: true, ma10: true, ma20: true, ma30: true, ma60: true },
283
+ boll: {
284
+ period: 20,
285
+ multiplier: 2,
286
+ showUpper: true,
287
+ showMiddle: true,
288
+ showLower: true,
289
+ showBand: true,
290
+ },
291
+ expma: { fastPeriod: 12, slowPeriod: 50 },
292
+ ene: { period: 10, deviation: 11 },
293
+ rsi: {
294
+ period1: 6,
295
+ period2: 12,
296
+ period3: 24,
297
+ showRSI1: true,
298
+ showRSI2: true,
299
+ showRSI3: true,
300
+ },
301
+ cci: { period: 14, showCCI: true },
302
+ stoch: { n: 9, m: 3, showK: true, showD: true },
303
+ mom: { period: 10, showMOM: true },
304
+ wmsr: { period: 14, showWMSR: true },
305
+ kst: {
306
+ roc1: 10,
307
+ roc2: 15,
308
+ roc3: 20,
309
+ roc4: 30,
310
+ signalPeriod: 9,
311
+ showKST: true,
312
+ showSignal: true,
313
+ },
314
+ fastk: { period: 9, showFASTK: true },
315
+ macd: {
316
+ fastPeriod: 12,
317
+ slowPeriod: 26,
318
+ signalPeriod: 9,
319
+ showDIF: true,
320
+ showDEA: true,
321
+ showBAR: true,
322
+ },
323
+ atr: { period: DEFAULT_ATR_PERIOD, showATR: true },
324
+ wma: { period: DEFAULT_WMA_PERIOD, showWMA: true },
325
+ dema: { period: DEFAULT_DEMA_PERIOD, showDEMA: true },
326
+ tema: { period: DEFAULT_TEMA_PERIOD, showTEMA: true },
327
+ hma: { period: DEFAULT_HMA_PERIOD, showHMA: true },
328
+ kama: {
329
+ period: DEFAULT_KAMA_PERIOD,
330
+ fastPeriod: DEFAULT_KAMA_FAST_PERIOD,
331
+ slowPeriod: DEFAULT_KAMA_SLOW_PERIOD,
332
+ showKAMA: true,
333
+ },
334
+ sar: { step: DEFAULT_SAR_STEP, maxStep: DEFAULT_SAR_MAX_STEP, showSAR: true },
335
+ supertrend: {
336
+ atrPeriod: DEFAULT_SUPERTREND_ATR_PERIOD,
337
+ multiplier: DEFAULT_SUPERTREND_MULTIPLIER,
338
+ showSuperTrend: true,
339
+ },
340
+ keltner: {
341
+ emaPeriod: DEFAULT_KELTNER_EMA_PERIOD,
342
+ atrPeriod: DEFAULT_KELTNER_ATR_PERIOD,
343
+ multiplier: DEFAULT_KELTNER_MULTIPLIER,
344
+ showUpper: true,
345
+ showMiddle: true,
346
+ showLower: true,
347
+ },
348
+ donchian: {
349
+ period: DEFAULT_DONCHIAN_PERIOD,
350
+ showUpper: true,
351
+ showMiddle: true,
352
+ showLower: true,
353
+ },
354
+ ichimoku: {
355
+ tenkanPeriod: DEFAULT_ICHIMOKU_TENKAN,
356
+ kijunPeriod: DEFAULT_ICHIMOKU_KIJUN,
357
+ spanBPeriod: DEFAULT_ICHIMOKU_SPAN_B,
358
+ displacement: DEFAULT_ICHIMOKU_DISPLACEMENT,
359
+ showTenkan: true,
360
+ showKijun: true,
361
+ showSpanA: true,
362
+ showSpanB: true,
363
+ showCloud: true,
364
+ showChikou: true,
365
+ },
366
+ roc: { period: DEFAULT_ROC_PERIOD, showROC: true },
367
+ trix: {
368
+ period: DEFAULT_TRIX_PERIOD,
369
+ signalPeriod: DEFAULT_TRIX_SIGNAL_PERIOD,
370
+ showTRIX: true,
371
+ showSignal: true,
372
+ },
373
+ hv: { period: DEFAULT_HV_PERIOD, annualizationFactor: DEFAULT_HV_ANNUALIZATION, showHV: true },
374
+ parkinson: { period: DEFAULT_PARKINSON_PERIOD, annualizationFactor: DEFAULT_PARKINSON_ANNUALIZATION, showParkinson: true },
375
+ chaikinVol: { emaPeriod: DEFAULT_CHAIKIN_VOL_EMA_PERIOD, rocPeriod: DEFAULT_CHAIKIN_VOL_ROC_PERIOD, showChaikinVol: true },
376
+ vma: { period: DEFAULT_VMA_PERIOD, showVMA: true },
377
+ obv: { showOBV: true },
378
+ pvt: { showPVT: true },
379
+ vwap: { sessionResetGapMs: DEFAULT_VWAP_SESSION_GAP_MS, showVWAP: true },
380
+ cmf: { period: DEFAULT_CMF_PERIOD, showCMF: true },
381
+ mfi: { period: DEFAULT_MFI_PERIOD, showMFI: true },
382
+ pivot: { showPP: true, showR1: true, showR2: true, showR3: false, showS1: true, showS2: true, showS3: false },
383
+ fib: { period: DEFAULT_FIB_PERIOD, showLevels: true },
384
+ structure: {
385
+ leftWindow: DEFAULT_STRUCTURE_LEFT,
386
+ rightWindow: DEFAULT_STRUCTURE_RIGHT,
387
+ breakoutSource: 'close',
388
+ showSwingLabels: true,
389
+ showBOS: true,
390
+ showCHOCH: true,
391
+ showProvisional: false,
392
+ },
393
+ zones: {
394
+ showFVG: true,
395
+ showOB: true,
396
+ showFilledZones: false,
397
+ obLookback: DEFAULT_ZONES_OB_LOOKBACK,
398
+ },
399
+ volumeProfile: {
400
+ bins: DEFAULT_VP_BINS,
401
+ lookback: DEFAULT_VP_LOOKBACK,
402
+ valueAreaPercent: DEFAULT_VP_VALUE_AREA,
403
+ showPOC: true,
404
+ showValueArea: true,
405
+ },
406
+ rsiPaneId: 'sub_RSI',
407
+ cciPaneId: 'sub_CCI',
408
+ stochPaneId: 'sub_STOCH',
409
+ momPaneId: 'sub_MOM',
410
+ wmsrPaneId: 'sub_WMSR',
411
+ kstPaneId: 'sub_KST',
412
+ fastkPaneId: 'sub_FASTK',
413
+ macdPaneId: 'sub_MACD',
414
+ atrPaneId: 'sub_ATR',
415
+ wmaPaneId: 'sub_WMA',
416
+ demaPaneId: 'sub_DEMA',
417
+ temaPaneId: 'sub_TEMA',
418
+ hmaPaneId: 'sub_HMA',
419
+ kamaPaneId: 'sub_KAMA',
420
+ sarPaneId: 'sub_SAR',
421
+ supertrendPaneId: 'sub_SuperTrend',
422
+ keltnerPaneId: 'sub_Keltner',
423
+ donchianPaneId: 'sub_Donchian',
424
+ ichimokuPaneId: 'sub_Ichimoku',
425
+ rocPaneId: 'sub_ROC',
426
+ trixPaneId: 'sub_TRIX',
427
+ hvPaneId: 'sub_HV',
428
+ parkinsonPaneId: 'sub_Parkinson',
429
+ chaikinVolPaneId: 'sub_ChaikinVol',
430
+ vmaPaneId: 'sub_VMA',
431
+ obvPaneId: 'sub_OBV',
432
+ pvtPaneId: 'sub_PVT',
433
+ vwapPaneId: 'sub_VWAP',
434
+ cmfPaneId: 'sub_CMF',
435
+ mfiPaneId: 'sub_MFI',
436
+ pivotPaneId: 'sub_Pivot',
437
+ fibPaneId: 'sub_Fib',
438
+ structurePaneId: 'sub_Structure',
439
+ zonesPaneId: 'sub_Zones',
440
+ volumeProfilePaneId: 'sub_VolumeProfile',
441
+ }
442
+ }
443
+
444
+ private initBackend(): void {
445
+ // 尝试初始化 Worker
446
+ if (this.tryInitWorker()) {
447
+ return
448
+ }
449
+ // 失败则使用 inline fallback
450
+ this.initInlineRuntime()
451
+ }
452
+
453
+ private tryInitWorker(): boolean {
454
+ console.log('[IndicatorScheduler] tryInitWorker: Worker available?', typeof Worker !== 'undefined')
455
+ if (typeof Worker === 'undefined') {
456
+ return false
457
+ }
458
+ try {
459
+ // Vite 模块 Worker
460
+ const workerUrl = new URL('./indicator.worker.ts', import.meta.url)
461
+ console.log('[IndicatorScheduler] Creating worker from:', workerUrl.href)
462
+ this.worker = new Worker(workerUrl, { type: 'module' })
463
+ console.log('[IndicatorScheduler] Worker created, waiting for ready...')
464
+ this.worker.onmessage = (e) => this.handleWorkerMessage(e.data)
465
+ this.worker.onerror = (err) => {
466
+ console.error('[IndicatorScheduler] Worker error:', err)
467
+ this.fallbackToInline()
468
+ }
469
+ // 发送 init
470
+ this.worker.postMessage({
471
+ type: 'init',
472
+ protocolVersion: PROTOCOL_VERSION,
473
+ })
474
+ return true
475
+ } catch (err) {
476
+ console.warn('[IndicatorScheduler] Failed to init worker:', err)
477
+ return false
478
+ }
479
+ }
480
+
481
+ private initInlineRuntime(): void {
482
+ console.log('[IndicatorScheduler] Using INLINE runtime (fallback)')
483
+ this.inlineRuntime = new IndicatorRuntime()
484
+ this.useWorker = false
485
+ this.workerReady = true
486
+ }
487
+
488
+ private fallbackToInline(): void {
489
+ console.warn('[IndicatorScheduler] Falling back to inline runtime')
490
+ this.terminateWorker()
491
+ this.initInlineRuntime()
492
+ // 如果有待处理的请求,用 inline 重新执行
493
+ if (this.pendingRequest) {
494
+ this.computeWithInline()
495
+ }
496
+ }
497
+
498
+ private terminateWorker(): void {
499
+ if (this.worker) {
500
+ this.worker.terminate()
501
+ this.worker = null
502
+ }
503
+ this.workerReady = false
504
+ this.useWorker = false
505
+ }
506
+
507
+ // ============================================================================
508
+ // Worker 消息处理
509
+ // ============================================================================
510
+
511
+ private handleWorkerMessage(msg: unknown): void {
512
+ if (!isWorkerResponse(msg)) {
513
+ console.warn('[IndicatorScheduler] Invalid worker response:', msg)
514
+ return
515
+ }
516
+
517
+ switch (msg.type) {
518
+ case 'ready':
519
+ this.workerReady = true
520
+ this.useWorker = true
521
+ console.log('[IndicatorScheduler] Worker READY - using Worker backend')
522
+ // Worker 就绪后立即补算一次,确保后续走 Worker
523
+ this.triggerRecompute()
524
+ break
525
+
526
+ case 'seriesResult':
527
+ this.handleSeriesResult(msg)
528
+ break
529
+
530
+ case 'error':
531
+ console.error('[IndicatorScheduler] Worker error:', msg.stage, msg.message)
532
+ if (this.pendingRequest && msg.requestId === this.pendingRequest.requestId) {
533
+ this.fallbackToInline()
534
+ }
535
+ break
536
+
537
+ default: {
538
+ const _exhaustive: never = msg
539
+ console.warn('[IndicatorScheduler] Unknown response type:', (_exhaustive as unknown as { type: string }).type)
540
+ }
541
+ }
542
+ }
543
+
544
+ private handleSeriesResult(msg: Extract<IndicatorWorkerResponse, { type: 'seriesResult' }>): void {
545
+ // 检查版本是否过期
546
+ if (msg.requestId < this.lastAppliedRequestId) {
547
+ return // 丢弃旧结果
548
+ }
549
+ if (msg.dataVersion !== this.dataVersion || msg.configVersion !== this.configVersion) {
550
+ return // 数据或配置已变更,丢弃旧结果
551
+ }
552
+
553
+ console.log(`[IndicatorScheduler] << Worker result: requestId=${msg.requestId} metrics=`, msg.metrics)
554
+ this.lastAppliedRequestId = msg.requestId
555
+ this.pendingRequest = null
556
+ this.latestResult = msg.results
557
+
558
+ // 组装并写入 states
559
+ this.applyResults(msg.results)
560
+
561
+ // 触发重绘
562
+ this.invalidateCallback?.()
563
+ }
564
+
565
+ // ============================================================================
566
+ // 结果应用
567
+ // ============================================================================
568
+
569
+ /**
570
+ * 检查 state 中的 visibleMin/visibleMax 是否为 Infinity,如果是则输出 warning
571
+ */
572
+ private checkVisibleExtremes(state: { visibleMin: number; visibleMax: number }, indicatorName: string): void {
573
+ if (!Number.isFinite(state.visibleMin) || !Number.isFinite(state.visibleMax)) {
574
+ console.warn(`[IndicatorScheduler] ${indicatorName} state has non-finite visibleMin/visibleMax:`, {
575
+ visibleMin: state.visibleMin,
576
+ visibleMax: state.visibleMax,
577
+ })
578
+ }
579
+ }
580
+
581
+ /** 遍历注册表中的所有指标,通过 applyResult 回调写入 StateStore */
582
+ private applyResults(bundle: IndicatorSeriesBundle): void {
583
+ if (!this.pluginHost) return
584
+
585
+ const changed = new Set(bundle._changed)
586
+ const timestamp = Date.now()
587
+ const states = composeRenderStates(bundle, this.visibleRange, timestamp)
588
+
589
+ for (const meta of this.registry.getAll()) {
590
+ if (!changed.has(meta.name)) continue
591
+ if (!meta.applyResult) continue
592
+
593
+ const state = (states as Record<string, unknown>)[meta.name]
594
+ if (!state) continue
595
+
596
+ this.checkVisibleExtremes(
597
+ state as { visibleMin: number; visibleMax: number },
598
+ meta.displayName,
599
+ )
600
+
601
+ const paneId = meta.paneIdField
602
+ ? (this.configSnapshot as unknown as Record<string, string>)[meta.paneIdField as string]
603
+ : meta.defaultPaneId
604
+
605
+ meta.applyResult(this.pluginHost, state as BaseIndicatorState, paneId)
606
+ }
607
+ }
608
+
609
+ /** 仅副图:重算可见范围极值并回调 applyResult(视口变更时同步更新,不走 Worker) */
610
+ private updateVisibleStatesOnly(): void {
611
+ if (!this.pluginHost || !this.latestResult) return
612
+
613
+ const timestamp = Date.now()
614
+ const activeMask = this.buildActiveSubIndicatorMask()
615
+ const states = composeVisibleSubIndicatorStates(this.latestResult, this.visibleRange, timestamp, activeMask)
616
+
617
+ for (const meta of this.registry.getAll()) {
618
+ if (!meta.applyResult) continue
619
+ if (!meta.paneIdField && meta.category === 'main') continue
620
+
621
+ const state = (states as Record<string, unknown>)[meta.name]
622
+ if (state === undefined) continue
623
+
624
+ this.checkVisibleExtremes(
625
+ state as { visibleMin: number; visibleMax: number },
626
+ meta.displayName,
627
+ )
628
+
629
+ const paneId = meta.paneIdField
630
+ ? (this.configSnapshot as unknown as Record<string, string>)[meta.paneIdField as string]
631
+ : meta.defaultPaneId
632
+
633
+ meta.applyResult(this.pluginHost, state as BaseIndicatorState, paneId)
634
+ }
635
+ }
636
+
637
+ /** 遍历注册表,标记当前可见副图,仅这些指标参与计算 */
638
+ private buildActiveSubIndicatorMask(): Record<string, boolean> {
639
+ const activeIds = this.getActiveSubPaneIds?.() ?? []
640
+ const mask: Record<string, boolean> = {}
641
+ for (const meta of this.registry.getAll()) {
642
+ if (!meta.paneIdField) continue
643
+ const paneId = (this.configSnapshot as unknown as Record<string, string>)[meta.paneIdField as string] ?? meta.defaultPaneId
644
+ mask[meta.name] = activeIds.includes(paneId) || !!(meta.allowMainPane && paneId === 'main')
645
+ }
646
+ return mask
647
+ }
648
+
649
+ /** 遍历注册表,禁用非活跃副图指标的 show* 字段,后端只算活跃指标 */
650
+ private buildActiveConfig(): IndicatorConfigSnapshot {
651
+ const activeIds = this.getActiveSubPaneIds?.() ?? []
652
+ if (activeIds.length === 0) return { ...this.configSnapshot }
653
+
654
+ const cfg: Record<string, unknown> = { ...this.configSnapshot }
655
+ for (const meta of this.registry.getAll()) {
656
+ if (!meta.paneIdField) continue
657
+ const paneId = cfg[meta.paneIdField] as string
658
+ if (!activeIds.includes(paneId) && paneId !== 'main') {
659
+ const subCfg = { ...(cfg[meta.name] as Record<string, unknown>) }
660
+ for (const k of Object.keys(subCfg)) {
661
+ if (k.startsWith('show')) {
662
+ subCfg[k] = false
663
+ }
664
+ }
665
+ cfg[meta.name] = subCfg
666
+ }
667
+ }
668
+ return cfg as unknown as IndicatorConfigSnapshot
669
+ }
670
+
671
+
672
+ // ============================================================================
673
+ // Public API
674
+ // ============================================================================
675
+
676
+ /**
677
+ * 数据变更时调用
678
+ */
679
+ update(data: KLineData[], visibleRange: VisibleRange): void {
680
+ this.currentData = data
681
+ this.visibleRange = visibleRange
682
+ this.dataVersion++
683
+
684
+ if (this.useWorker && this.worker && this.workerReady) {
685
+ this.computeWithWorker()
686
+ } else {
687
+ this.computeWithInline()
688
+ }
689
+ }
690
+
691
+ /**
692
+ * 视口变更时调用 - 同步处理,不走 Worker
693
+ */
694
+ updateVisibleRange(visibleRange: VisibleRange): void {
695
+ this.visibleRange = visibleRange
696
+
697
+ // 基于缓存的 series 同步更新极值
698
+ if (this.latestResult) {
699
+ this.updateVisibleStatesOnly()
700
+ }
701
+ }
702
+
703
+ /**
704
+ * MA 配置变更
705
+ */
706
+ updateMAConfig(config: MAFlags): void {
707
+ this.configSnapshot.ma = { ...config }
708
+ this.configVersion++
709
+ this.triggerRecompute()
710
+ }
711
+
712
+ /**
713
+ * BOLL 配置变更
714
+ */
715
+ updateBOLLConfig(config: Partial<BOLLSchedulerConfig>): void {
716
+ this.configSnapshot.boll = { ...this.configSnapshot.boll, ...config }
717
+ this.configVersion++
718
+ this.triggerRecompute()
719
+ }
720
+
721
+ /**
722
+ * EXPMA 配置变更
723
+ */
724
+ updateEXPMAConfig(config: Partial<EXPMASchedulerConfig>): void {
725
+ this.configSnapshot.expma = { ...this.configSnapshot.expma, ...config }
726
+ this.configVersion++
727
+ this.triggerRecompute()
728
+ }
729
+
730
+ /**
731
+ * ENE 配置变更
732
+ */
733
+ updateENEConfig(config: Partial<ENESchedulerConfig>): void {
734
+ this.configSnapshot.ene = { ...this.configSnapshot.ene, ...config }
735
+ this.configVersion++
736
+ this.triggerRecompute()
737
+ }
738
+
739
+ /**
740
+ * RSI 配置变更
741
+ */
742
+ updateRSIConfig(config: Partial<RSISchedulerConfig>, paneId?: string): void {
743
+ if (paneId !== undefined) {
744
+ this.configSnapshot.rsiPaneId = paneId
745
+ }
746
+ this.configSnapshot.rsi = { ...this.configSnapshot.rsi, ...config }
747
+ this.configVersion++
748
+ this.triggerRecompute()
749
+ }
750
+
751
+ /**
752
+ * CCI 配置变更
753
+ */
754
+ updateCCIConfig(config: Partial<CCISchedulerConfig>, paneId?: string): void {
755
+ if (paneId !== undefined) {
756
+ this.configSnapshot.cciPaneId = paneId
757
+ }
758
+ this.configSnapshot.cci = { ...this.configSnapshot.cci, ...config }
759
+ this.configVersion++
760
+ this.triggerRecompute()
761
+ }
762
+
763
+ /**
764
+ * STOCH 配置变更
765
+ */
766
+ updateSTOCHConfig(config: Partial<STOCHSchedulerConfig>, paneId?: string): void {
767
+ if (paneId !== undefined) {
768
+ this.configSnapshot.stochPaneId = paneId
769
+ }
770
+ this.configSnapshot.stoch = { ...this.configSnapshot.stoch, ...config }
771
+ this.configVersion++
772
+ this.triggerRecompute()
773
+ }
774
+
775
+ /**
776
+ * MOM 配置变更
777
+ */
778
+ updateMOMConfig(config: Partial<MOMSchedulerConfig>, paneId?: string): void {
779
+ if (paneId !== undefined) {
780
+ this.configSnapshot.momPaneId = paneId
781
+ }
782
+ this.configSnapshot.mom = { ...this.configSnapshot.mom, ...config }
783
+ this.configVersion++
784
+ this.triggerRecompute()
785
+ }
786
+
787
+ /**
788
+ * WMSR 配置变更
789
+ */
790
+ updateWMSRConfig(config: Partial<WMSRSchedulerConfig>, paneId?: string): void {
791
+ if (paneId !== undefined) {
792
+ this.configSnapshot.wmsrPaneId = paneId
793
+ }
794
+ this.configSnapshot.wmsr = { ...this.configSnapshot.wmsr, ...config }
795
+ this.configVersion++
796
+ this.triggerRecompute()
797
+ }
798
+
799
+ /**
800
+ * KST 配置变更
801
+ */
802
+ updateKSTConfig(config: Partial<KSTSchedulerConfig>, paneId?: string): void {
803
+ if (paneId !== undefined) {
804
+ this.configSnapshot.kstPaneId = paneId
805
+ }
806
+ this.configSnapshot.kst = { ...this.configSnapshot.kst, ...config }
807
+ this.configVersion++
808
+ this.triggerRecompute()
809
+ }
810
+
811
+ /**
812
+ * FASTK 配置变更
813
+ */
814
+ updateFASTKConfig(config: Partial<FASTKSchedulerConfig>, paneId?: string): void {
815
+ if (paneId !== undefined) {
816
+ this.configSnapshot.fastkPaneId = paneId
817
+ }
818
+ this.configSnapshot.fastk = { ...this.configSnapshot.fastk, ...config }
819
+ this.configVersion++
820
+ this.triggerRecompute()
821
+ }
822
+
823
+ /**
824
+ * MACD 配置变更
825
+ */
826
+ updateMACDConfig(config: Partial<MACDSchedulerConfig>, paneId?: string): void {
827
+ if (paneId !== undefined) {
828
+ this.configSnapshot.macdPaneId = paneId
829
+ }
830
+ this.configSnapshot.macd = { ...this.configSnapshot.macd, ...config }
831
+ this.configVersion++
832
+ this.triggerRecompute()
833
+ }
834
+
835
+ /**
836
+ * ATR 配置变更
837
+ */
838
+ updateATRConfig(config: Partial<ATRSchedulerConfig>, paneId?: string): void {
839
+ if (paneId !== undefined) {
840
+ this.configSnapshot.atrPaneId = paneId
841
+ }
842
+ this.configSnapshot.atr = { ...this.configSnapshot.atr, ...config }
843
+ this.configVersion++
844
+ this.triggerRecompute()
845
+ }
846
+
847
+ /**
848
+ * WMA 配置变更
849
+ */
850
+ updateWMAConfig(config: Partial<WMASchedulerConfig>, paneId?: string): void {
851
+ if (paneId !== undefined) {
852
+ this.configSnapshot.wmaPaneId = paneId
853
+ }
854
+ this.configSnapshot.wma = { ...this.configSnapshot.wma, ...config }
855
+ this.configVersion++
856
+ this.triggerRecompute()
857
+ }
858
+
859
+ /**
860
+ * DEMA 配置变更
861
+ */
862
+ updateDEMAConfig(config: Partial<DEMASchedulerConfig>, paneId?: string): void {
863
+ if (paneId !== undefined) {
864
+ this.configSnapshot.demaPaneId = paneId
865
+ }
866
+ this.configSnapshot.dema = { ...this.configSnapshot.dema, ...config }
867
+ this.configVersion++
868
+ this.triggerRecompute()
869
+ }
870
+
871
+ /**
872
+ * TEMA 配置变更
873
+ */
874
+ updateTEMAConfig(config: Partial<TEMASchedulerConfig>, paneId?: string): void {
875
+ if (paneId !== undefined) {
876
+ this.configSnapshot.temaPaneId = paneId
877
+ }
878
+ this.configSnapshot.tema = { ...this.configSnapshot.tema, ...config }
879
+ this.configVersion++
880
+ this.triggerRecompute()
881
+ }
882
+
883
+ /**
884
+ * HMA 配置变更
885
+ */
886
+ updateHMAConfig(config: Partial<HMASchedulerConfig>, paneId?: string): void {
887
+ if (paneId !== undefined) {
888
+ this.configSnapshot.hmaPaneId = paneId
889
+ }
890
+ this.configSnapshot.hma = { ...this.configSnapshot.hma, ...config }
891
+ this.configVersion++
892
+ this.triggerRecompute()
893
+ }
894
+
895
+ /**
896
+ * KAMA 配置变更
897
+ */
898
+ updateKAMAConfig(config: Partial<KAMASchedulerConfig>, paneId?: string): void {
899
+ if (paneId !== undefined) {
900
+ this.configSnapshot.kamaPaneId = paneId
901
+ }
902
+ this.configSnapshot.kama = { ...this.configSnapshot.kama, ...config }
903
+ this.configVersion++
904
+ this.triggerRecompute()
905
+ }
906
+
907
+ /**
908
+ * SAR 配置变更
909
+ */
910
+ updateSARConfig(config: Partial<SARSchedulerConfig>, paneId?: string): void {
911
+ if (paneId !== undefined) {
912
+ this.configSnapshot.sarPaneId = paneId
913
+ }
914
+ this.configSnapshot.sar = { ...this.configSnapshot.sar, ...config }
915
+ this.configVersion++
916
+ this.triggerRecompute()
917
+ }
918
+
919
+ /**
920
+ * SuperTrend 配置变更
921
+ */
922
+ updateSuperTrendConfig(config: Partial<SuperTrendSchedulerConfig>, paneId?: string): void {
923
+ if (paneId !== undefined) this.configSnapshot.supertrendPaneId = paneId
924
+ this.configSnapshot.supertrend = { ...this.configSnapshot.supertrend, ...config }
925
+ this.configVersion++
926
+ this.triggerRecompute()
927
+ }
928
+
929
+ /**
930
+ * Keltner 配置变更
931
+ */
932
+ updateKeltnerConfig(config: Partial<KeltnerSchedulerConfig>, paneId?: string): void {
933
+ if (paneId !== undefined) this.configSnapshot.keltnerPaneId = paneId
934
+ this.configSnapshot.keltner = { ...this.configSnapshot.keltner, ...config }
935
+ this.configVersion++
936
+ this.triggerRecompute()
937
+ }
938
+
939
+ /**
940
+ * Donchian 配置变更
941
+ */
942
+ updateDonchianConfig(config: Partial<DonchianSchedulerConfig>, paneId?: string): void {
943
+ if (paneId !== undefined) this.configSnapshot.donchianPaneId = paneId
944
+ this.configSnapshot.donchian = { ...this.configSnapshot.donchian, ...config }
945
+ this.configVersion++
946
+ this.triggerRecompute()
947
+ }
948
+
949
+ /**
950
+ * Ichimoku 配置变更
951
+ */
952
+ updateIchimokuConfig(config: Partial<IchimokuSchedulerConfig>, paneId?: string): void {
953
+ if (paneId !== undefined) this.configSnapshot.ichimokuPaneId = paneId
954
+ this.configSnapshot.ichimoku = { ...this.configSnapshot.ichimoku, ...config }
955
+ this.configVersion++
956
+ this.triggerRecompute()
957
+ }
958
+
959
+ /**
960
+ * ROC 配置变更
961
+ */
962
+ updateROCConfig(config: Partial<ROCSchedulerConfig>, paneId?: string): void {
963
+ if (paneId !== undefined) this.configSnapshot.rocPaneId = paneId
964
+ this.configSnapshot.roc = { ...this.configSnapshot.roc, ...config }
965
+ this.configVersion++
966
+ this.triggerRecompute()
967
+ }
968
+
969
+ /**
970
+ * TRIX 配置变更
971
+ */
972
+ updateTRIXConfig(config: Partial<TRIXSchedulerConfig>, paneId?: string): void {
973
+ if (paneId !== undefined) this.configSnapshot.trixPaneId = paneId
974
+ this.configSnapshot.trix = { ...this.configSnapshot.trix, ...config }
975
+ this.configVersion++
976
+ this.triggerRecompute()
977
+ }
978
+
979
+ /**
980
+ * HV 配置变更
981
+ */
982
+ updateHVConfig(config: Partial<HVSchedulerConfig>, paneId?: string): void {
983
+ if (paneId !== undefined) this.configSnapshot.hvPaneId = paneId
984
+ this.configSnapshot.hv = { ...this.configSnapshot.hv, ...config }
985
+ this.configVersion++
986
+ this.triggerRecompute()
987
+ }
988
+
989
+ /**
990
+ * Parkinson 配置变更
991
+ */
992
+ updateParkinsonConfig(config: Partial<ParkinsonSchedulerConfig>, paneId?: string): void {
993
+ if (paneId !== undefined) this.configSnapshot.parkinsonPaneId = paneId
994
+ this.configSnapshot.parkinson = { ...this.configSnapshot.parkinson, ...config }
995
+ this.configVersion++
996
+ this.triggerRecompute()
997
+ }
998
+
999
+ /**
1000
+ * ChaikinVol 配置变更
1001
+ */
1002
+ updateChaikinVolConfig(config: Partial<ChaikinVolSchedulerConfig>, paneId?: string): void {
1003
+ if (paneId !== undefined) this.configSnapshot.chaikinVolPaneId = paneId
1004
+ this.configSnapshot.chaikinVol = { ...this.configSnapshot.chaikinVol, ...config }
1005
+ this.configVersion++
1006
+ this.triggerRecompute()
1007
+ }
1008
+
1009
+ /**
1010
+ * VMA 配置变更
1011
+ */
1012
+ updateVMAConfig(config: Partial<VMASchedulerConfig>, paneId?: string): void {
1013
+ if (paneId !== undefined) this.configSnapshot.vmaPaneId = paneId
1014
+ this.configSnapshot.vma = { ...this.configSnapshot.vma, ...config }
1015
+ this.configVersion++
1016
+ this.triggerRecompute()
1017
+ }
1018
+
1019
+ /**
1020
+ * OBV 配置变更
1021
+ */
1022
+ updateOBVConfig(config: Partial<OBVSchedulerConfig>, paneId?: string): void {
1023
+ if (paneId !== undefined) this.configSnapshot.obvPaneId = paneId
1024
+ this.configSnapshot.obv = { ...this.configSnapshot.obv, ...config }
1025
+ this.configVersion++
1026
+ this.triggerRecompute()
1027
+ }
1028
+
1029
+ /**
1030
+ * PVT 配置变更
1031
+ */
1032
+ updatePVTConfig(config: Partial<PVTSchedulerConfig>, paneId?: string): void {
1033
+ if (paneId !== undefined) this.configSnapshot.pvtPaneId = paneId
1034
+ this.configSnapshot.pvt = { ...this.configSnapshot.pvt, ...config }
1035
+ this.configVersion++
1036
+ this.triggerRecompute()
1037
+ }
1038
+
1039
+ /**
1040
+ * VWAP 配置变更
1041
+ */
1042
+ updateVWAPConfig(config: Partial<VWAPSchedulerConfig>, paneId?: string): void {
1043
+ if (paneId !== undefined) this.configSnapshot.vwapPaneId = paneId
1044
+ this.configSnapshot.vwap = { ...this.configSnapshot.vwap, ...config }
1045
+ this.configVersion++
1046
+ this.triggerRecompute()
1047
+ }
1048
+
1049
+ /** CMF 配置变更 */
1050
+ updateCMFConfig(config: Partial<CMFSchedulerConfig>, paneId?: string): void {
1051
+ if (paneId !== undefined) this.configSnapshot.cmfPaneId = paneId
1052
+ this.configSnapshot.cmf = { ...this.configSnapshot.cmf, ...config }
1053
+ this.configVersion++
1054
+ this.triggerRecompute()
1055
+ }
1056
+
1057
+ /** MFI 配置变更 */
1058
+ updateMFIConfig(config: Partial<MFISchedulerConfig>, paneId?: string): void {
1059
+ if (paneId !== undefined) this.configSnapshot.mfiPaneId = paneId
1060
+ this.configSnapshot.mfi = { ...this.configSnapshot.mfi, ...config }
1061
+ this.configVersion++
1062
+ this.triggerRecompute()
1063
+ }
1064
+
1065
+ /** Pivot 配置变更 */
1066
+ updatePivotConfig(config: Partial<PivotSchedulerConfig>, paneId?: string): void {
1067
+ if (paneId !== undefined) this.configSnapshot.pivotPaneId = paneId
1068
+ this.configSnapshot.pivot = { ...this.configSnapshot.pivot, ...config }
1069
+ this.configVersion++
1070
+ this.triggerRecompute()
1071
+ }
1072
+
1073
+ /** Fib 配置变更 */
1074
+ updateFibConfig(config: Partial<FibSchedulerConfig>, paneId?: string): void {
1075
+ if (paneId !== undefined) this.configSnapshot.fibPaneId = paneId
1076
+ this.configSnapshot.fib = { ...this.configSnapshot.fib, ...config }
1077
+ this.configVersion++
1078
+ this.triggerRecompute()
1079
+ }
1080
+
1081
+ /** Structure 配置变更 */
1082
+ updateStructureConfig(config: Partial<StructureSchedulerConfig>, paneId?: string): void {
1083
+ if (paneId !== undefined) this.configSnapshot.structurePaneId = paneId
1084
+ this.configSnapshot.structure = { ...this.configSnapshot.structure, ...config }
1085
+ this.configVersion++
1086
+ this.triggerRecompute()
1087
+ }
1088
+
1089
+ /** Zones 配置变更 */
1090
+ updateZonesConfig(config: Partial<ZonesSchedulerConfig>, paneId?: string): void {
1091
+ if (paneId !== undefined) this.configSnapshot.zonesPaneId = paneId
1092
+ this.configSnapshot.zones = { ...this.configSnapshot.zones, ...config }
1093
+ this.configVersion++
1094
+ this.triggerRecompute()
1095
+ }
1096
+
1097
+ /** Volume Profile 配置变更 */
1098
+ updateVolumeProfileConfig(config: Partial<VolumeProfileSchedulerConfig>, paneId?: string): void {
1099
+ if (paneId !== undefined) this.configSnapshot.volumeProfilePaneId = paneId
1100
+ this.configSnapshot.volumeProfile = { ...this.configSnapshot.volumeProfile, ...config }
1101
+ this.configVersion++
1102
+ this.triggerRecompute()
1103
+ }
1104
+
1105
+ /**
1106
+ * 设置当前激活的主图指标
1107
+ */
1108
+ setActiveMainIndicators(indicators: string[]): void {
1109
+ this.activeMainIndicators = new Set(indicators.map(i => i.toLowerCase()))
1110
+ }
1111
+
1112
+ /**
1113
+ * 获取主图指标价格范围
1114
+ */
1115
+ getMainIndicatorPriceRange(): { min: number; max: number } | null {
1116
+ if (!this.latestResult) return null
1117
+ return computeMainIndicatorPriceRange(
1118
+ this.latestResult,
1119
+ this.visibleRange,
1120
+ this.activeMainIndicators
1121
+ )
1122
+ }
1123
+
1124
+ /**
1125
+ * 强制全部重算
1126
+ */
1127
+ recompute(): void {
1128
+ // 强制 inline runtime 重新计算(无视脏标记)
1129
+ if (this.inlineRuntime) {
1130
+ this.inlineRuntime.forceDirty()
1131
+ }
1132
+ // 递增版本号,确保 worker 端也会重新计算
1133
+ this.dataVersion++
1134
+ this.triggerRecompute()
1135
+ }
1136
+
1137
+ // ============================================================================
1138
+ // 计算触发
1139
+ // ============================================================================
1140
+
1141
+ private triggerRecompute(): void {
1142
+ if (this.useWorker && this.worker && this.workerReady) {
1143
+ this.computeWithWorker()
1144
+ } else {
1145
+ this.computeWithInline()
1146
+ }
1147
+ }
1148
+
1149
+ private computeWithWorker(): void {
1150
+ if (!this.worker || !this.workerReady) return
1151
+
1152
+ console.log(`[IndicatorScheduler] >> Worker compute: requestId=${this.requestId + 1} dataV=${this.dataVersion} configV=${this.configVersion}`)
1153
+ this.requestId++
1154
+ this.pendingRequest = {
1155
+ requestId: this.requestId,
1156
+ dataVersion: this.dataVersion,
1157
+ configVersion: this.configVersion,
1158
+ }
1159
+
1160
+ // 发送数据(首次或变更时)
1161
+ this.worker.postMessage({
1162
+ type: 'setData',
1163
+ dataVersion: this.dataVersion,
1164
+ format: 'aos',
1165
+ data: this.currentData,
1166
+ })
1167
+
1168
+ // 发送配置(仅活跃副图)
1169
+ this.worker.postMessage({
1170
+ type: 'setConfig',
1171
+ configVersion: this.configVersion,
1172
+ configs: this.buildActiveConfig(),
1173
+ })
1174
+
1175
+ // 请求计算
1176
+ this.worker.postMessage({
1177
+ type: 'computeSeries',
1178
+ requestId: this.requestId,
1179
+ dataVersion: this.dataVersion,
1180
+ configVersion: this.configVersion,
1181
+ })
1182
+ }
1183
+
1184
+ private computeWithInline(): void {
1185
+ if (!this.inlineRuntime) {
1186
+ this.inlineRuntime = new IndicatorRuntime()
1187
+ }
1188
+
1189
+ console.log(`[IndicatorScheduler] >> INLINE compute: dataV=${this.dataVersion} configV=${this.configVersion}`)
1190
+
1191
+ // 设置数据和配置(仅活跃副图)
1192
+ this.inlineRuntime.setData(this.currentData, this.dataVersion)
1193
+ this.inlineRuntime.setConfig(this.buildActiveConfig(), this.configVersion)
1194
+
1195
+ // 同步计算
1196
+ const results = this.inlineRuntime.computeSeries()
1197
+ this.latestResult = results
1198
+
1199
+ // 组装并写入 states
1200
+ this.applyResults(results)
1201
+
1202
+ // 触发重绘
1203
+ this.invalidateCallback?.()
1204
+ }
1205
+ }