@363045841yyt/klinechart-core 0.7.3 → 0.7.5-alpha.2

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 (231) hide show
  1. package/README.md +201 -201
  2. package/README.zh-CN.md +201 -201
  3. package/dist/engine/renderers/webgl/candleSurface.js +47 -47
  4. package/dist/version.d.ts +1 -1
  5. package/dist/version.d.ts.map +1 -1
  6. package/dist/version.js +1 -2
  7. package/dist/version.js.map +1 -1
  8. package/package.json +129 -122
  9. package/src/__tests__/signal.test.ts +124 -124
  10. package/src/config/chartSettings.ts +66 -66
  11. package/src/controllers/__tests__/drawing.test.ts +214 -214
  12. package/src/controllers/__tests__/indicatorSelector.test.ts +481 -481
  13. package/src/controllers/__tests__/toolbar.test.ts +225 -225
  14. package/src/controllers/createChartController.ts +665 -665
  15. package/src/controllers/createDrawingController.ts +96 -96
  16. package/src/controllers/createIndicatorSelectorController.ts +307 -307
  17. package/src/controllers/createToolbarController.ts +146 -146
  18. package/src/controllers/index.ts +19 -19
  19. package/src/controllers/types.ts +284 -284
  20. package/src/engine/__tests__/chart.dpr.test.ts +401 -401
  21. package/src/engine/__tests__/paneRenderer.resize.test.ts +92 -92
  22. package/src/engine/chart-store.ts +121 -121
  23. package/src/engine/chart.d.ts +617 -617
  24. package/src/engine/chart.ts +2815 -2815
  25. package/src/engine/controller/__tests__/interaction.dpr.test.ts +259 -259
  26. package/src/engine/controller/interaction.ts +722 -722
  27. package/src/engine/controller/markerInteraction.ts +130 -130
  28. package/src/engine/controller/pinchTracker.ts +82 -82
  29. package/src/engine/controller/tooltipPosition.ts +48 -48
  30. package/src/engine/draw/__tests__/pixelAlign.spec.ts +176 -176
  31. package/src/engine/draw/pixelAlign.ts +259 -259
  32. package/src/engine/drawing/index.ts +655 -655
  33. package/src/engine/drawing/interaction.ts +842 -842
  34. package/src/engine/drawing/plugin.ts +343 -343
  35. package/src/engine/indicators/__tests__/__fixtures__/golden/atr.json +38 -38
  36. package/src/engine/indicators/__tests__/__fixtures__/golden/dema.json +14 -14
  37. package/src/engine/indicators/__tests__/__fixtures__/golden/hma.json +14 -14
  38. package/src/engine/indicators/__tests__/__fixtures__/golden/index.ts +55 -55
  39. package/src/engine/indicators/__tests__/__fixtures__/golden/kama.json +14 -14
  40. package/src/engine/indicators/__tests__/__fixtures__/golden/tema.json +14 -14
  41. package/src/engine/indicators/__tests__/__fixtures__/golden/wma.json +40 -40
  42. package/src/engine/indicators/__tests__/__fixtures__/synthetic.ts +65 -65
  43. package/src/engine/indicators/__tests__/_propertyAssertions.ts +76 -76
  44. package/src/engine/indicators/__tests__/atr.test.ts +153 -153
  45. package/src/engine/indicators/__tests__/calculators.test.ts +614 -614
  46. package/src/engine/indicators/__tests__/cmf-mfi.test.ts +100 -100
  47. package/src/engine/indicators/__tests__/dema.test.ts +73 -73
  48. package/src/engine/indicators/__tests__/donchian.test.ts +70 -70
  49. package/src/engine/indicators/__tests__/hma.test.ts +73 -73
  50. package/src/engine/indicators/__tests__/ichimoku.test.ts +105 -105
  51. package/src/engine/indicators/__tests__/kama.test.ts +80 -80
  52. package/src/engine/indicators/__tests__/keltner.test.ts +65 -65
  53. package/src/engine/indicators/__tests__/pivot-fib.test.ts +110 -110
  54. package/src/engine/indicators/__tests__/roc.test.ts +68 -68
  55. package/src/engine/indicators/__tests__/sar.test.ts +86 -86
  56. package/src/engine/indicators/__tests__/scheduler.test.ts +831 -831
  57. package/src/engine/indicators/__tests__/soa.test.ts +533 -533
  58. package/src/engine/indicators/__tests__/structure.test.ts +110 -110
  59. package/src/engine/indicators/__tests__/supertrend.test.ts +65 -65
  60. package/src/engine/indicators/__tests__/tema.test.ts +68 -68
  61. package/src/engine/indicators/__tests__/trix.test.ts +70 -70
  62. package/src/engine/indicators/__tests__/volatility.test.ts +117 -117
  63. package/src/engine/indicators/__tests__/volume.test.ts +115 -115
  64. package/src/engine/indicators/__tests__/volumeProfile.test.ts +74 -74
  65. package/src/engine/indicators/__tests__/vwap.test.ts +69 -69
  66. package/src/engine/indicators/__tests__/wma.test.ts +112 -112
  67. package/src/engine/indicators/__tests__/zones.test.ts +95 -95
  68. package/src/engine/indicators/atrState.ts +27 -27
  69. package/src/engine/indicators/bollState.ts +51 -51
  70. package/src/engine/indicators/calculators.ts +2593 -2593
  71. package/src/engine/indicators/cciState.ts +25 -25
  72. package/src/engine/indicators/chaikinVolState.ts +32 -32
  73. package/src/engine/indicators/cmfState.ts +27 -27
  74. package/src/engine/indicators/demaState.ts +27 -27
  75. package/src/engine/indicators/donchianState.ts +43 -43
  76. package/src/engine/indicators/eneState.ts +43 -43
  77. package/src/engine/indicators/expmaState.ts +43 -43
  78. package/src/engine/indicators/fastkState.ts +25 -25
  79. package/src/engine/indicators/fibState.ts +41 -41
  80. package/src/engine/indicators/hmaState.ts +27 -27
  81. package/src/engine/indicators/hvState.ts +28 -28
  82. package/src/engine/indicators/ichimokuState.ts +70 -70
  83. package/src/engine/indicators/indicator.worker.ts +169 -169
  84. package/src/engine/indicators/indicatorDefinitionRegistry.ts +62 -62
  85. package/src/engine/indicators/indicatorMetadata.ts +110 -110
  86. package/src/engine/indicators/indicatorRegistry.ts +106 -106
  87. package/src/engine/indicators/indicatorRuntime.ts +1548 -1548
  88. package/src/engine/indicators/kamaState.ts +34 -34
  89. package/src/engine/indicators/keltnerState.ts +49 -49
  90. package/src/engine/indicators/kstState.ts +42 -42
  91. package/src/engine/indicators/maState.ts +36 -36
  92. package/src/engine/indicators/macdState.ts +76 -76
  93. package/src/engine/indicators/mfiState.ts +27 -27
  94. package/src/engine/indicators/momState.ts +25 -25
  95. package/src/engine/indicators/obvState.ts +25 -25
  96. package/src/engine/indicators/parkinsonState.ts +28 -28
  97. package/src/engine/indicators/pivotState.ts +51 -51
  98. package/src/engine/indicators/pvtState.ts +25 -25
  99. package/src/engine/indicators/rocState.ts +27 -27
  100. package/src/engine/indicators/rsiState.ts +65 -65
  101. package/src/engine/indicators/sarState.ts +41 -41
  102. package/src/engine/indicators/scheduler.ts +1205 -1205
  103. package/src/engine/indicators/soa.ts +352 -352
  104. package/src/engine/indicators/stateComposer.ts +1262 -1262
  105. package/src/engine/indicators/stochState.ts +26 -26
  106. package/src/engine/indicators/structureState.ts +69 -69
  107. package/src/engine/indicators/supertrendState.ts +37 -37
  108. package/src/engine/indicators/temaState.ts +27 -27
  109. package/src/engine/indicators/trixState.ts +35 -35
  110. package/src/engine/indicators/vmaState.ts +27 -27
  111. package/src/engine/indicators/volumeProfileState.ts +63 -63
  112. package/src/engine/indicators/vwapState.ts +29 -29
  113. package/src/engine/indicators/wmaState.ts +27 -27
  114. package/src/engine/indicators/wmsrState.ts +25 -25
  115. package/src/engine/indicators/workerProtocol.ts +613 -613
  116. package/src/engine/indicators/zonesState.ts +47 -47
  117. package/src/engine/layout/pane.ts +161 -161
  118. package/src/engine/marker/registry.ts +265 -265
  119. package/src/engine/paneRenderer.ts +169 -169
  120. package/src/engine/renderers/Indicator/atr.ts +237 -237
  121. package/src/engine/renderers/Indicator/boll.ts +317 -317
  122. package/src/engine/renderers/Indicator/cci.ts +275 -275
  123. package/src/engine/renderers/Indicator/chaikinVol.ts +138 -138
  124. package/src/engine/renderers/Indicator/cmf.ts +137 -137
  125. package/src/engine/renderers/Indicator/dema.ts +136 -136
  126. package/src/engine/renderers/Indicator/donchian.ts +137 -137
  127. package/src/engine/renderers/Indicator/ene.ts +271 -271
  128. package/src/engine/renderers/Indicator/expma.ts +197 -197
  129. package/src/engine/renderers/Indicator/fastk.ts +316 -316
  130. package/src/engine/renderers/Indicator/fib.ts +141 -141
  131. package/src/engine/renderers/Indicator/hma.ts +136 -136
  132. package/src/engine/renderers/Indicator/hv.ts +124 -124
  133. package/src/engine/renderers/Indicator/ichimoku.ts +181 -181
  134. package/src/engine/renderers/Indicator/index.ts +241 -241
  135. package/src/engine/renderers/Indicator/indicatorData.ts +650 -650
  136. package/src/engine/renderers/Indicator/kama.ts +136 -136
  137. package/src/engine/renderers/Indicator/keltner.ts +137 -137
  138. package/src/engine/renderers/Indicator/kst.ts +302 -302
  139. package/src/engine/renderers/Indicator/ma.ts +200 -200
  140. package/src/engine/renderers/Indicator/macd.ts +477 -477
  141. package/src/engine/renderers/Indicator/macdLegend.ts +141 -141
  142. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +272 -272
  143. package/src/engine/renderers/Indicator/mfi.ts +142 -142
  144. package/src/engine/renderers/Indicator/mom.ts +311 -311
  145. package/src/engine/renderers/Indicator/obv.ts +123 -123
  146. package/src/engine/renderers/Indicator/parkinson.ts +124 -124
  147. package/src/engine/renderers/Indicator/pivot.ts +131 -131
  148. package/src/engine/renderers/Indicator/pvt.ts +123 -123
  149. package/src/engine/renderers/Indicator/roc.ts +143 -143
  150. package/src/engine/renderers/Indicator/rsi.ts +390 -390
  151. package/src/engine/renderers/Indicator/sar.ts +113 -113
  152. package/src/engine/renderers/Indicator/scale/atr_scale.ts +19 -19
  153. package/src/engine/renderers/Indicator/scale/cci_scale.ts +19 -19
  154. package/src/engine/renderers/Indicator/scale/fastk_scale.ts +19 -19
  155. package/src/engine/renderers/Indicator/scale/indicator_scale.ts +204 -204
  156. package/src/engine/renderers/Indicator/scale/kst_scale.ts +19 -19
  157. package/src/engine/renderers/Indicator/scale/macd_scale.ts +22 -22
  158. package/src/engine/renderers/Indicator/scale/mom_scale.ts +19 -19
  159. package/src/engine/renderers/Indicator/scale/rsi_scale.ts +19 -19
  160. package/src/engine/renderers/Indicator/scale/stoch_scale.ts +19 -19
  161. package/src/engine/renderers/Indicator/scale/volume_scale.ts +26 -26
  162. package/src/engine/renderers/Indicator/scale/wmsr_scale.ts +19 -19
  163. package/src/engine/renderers/Indicator/stoch.ts +359 -359
  164. package/src/engine/renderers/Indicator/structure.ts +126 -126
  165. package/src/engine/renderers/Indicator/subPaneConfig.ts +265 -265
  166. package/src/engine/renderers/Indicator/supertrend.ts +115 -115
  167. package/src/engine/renderers/Indicator/tema.ts +136 -136
  168. package/src/engine/renderers/Indicator/trix.ts +158 -158
  169. package/src/engine/renderers/Indicator/vma.ts +124 -124
  170. package/src/engine/renderers/Indicator/volumeProfile.ts +125 -125
  171. package/src/engine/renderers/Indicator/vwap.ts +123 -123
  172. package/src/engine/renderers/Indicator/wma.ts +136 -136
  173. package/src/engine/renderers/Indicator/wmsr.ts +328 -328
  174. package/src/engine/renderers/Indicator/zones.ts +104 -104
  175. package/src/engine/renderers/__tests__/boll.renderer.test.ts +314 -314
  176. package/src/engine/renderers/__tests__/ene.renderer.test.ts +305 -305
  177. package/src/engine/renderers/__tests__/expma.renderer.test.ts +279 -279
  178. package/src/engine/renderers/__tests__/ma.renderer.test.ts +426 -426
  179. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +502 -502
  180. package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +173 -173
  181. package/src/engine/renderers/candle.ts +459 -459
  182. package/src/engine/renderers/crosshair.ts +69 -69
  183. package/src/engine/renderers/customMarkers.ts +162 -162
  184. package/src/engine/renderers/extremaMarkers.ts +246 -246
  185. package/src/engine/renderers/gridLines.ts +90 -90
  186. package/src/engine/renderers/lastPrice.ts +97 -97
  187. package/src/engine/renderers/paneTitle.ts +136 -136
  188. package/src/engine/renderers/subVolume.ts +236 -236
  189. package/src/engine/renderers/timeAxis.ts +121 -121
  190. package/src/engine/renderers/webgl/candleSurface.ts +955 -955
  191. package/src/engine/renderers/webgl/sharedWebGLSurface.ts +146 -146
  192. package/src/engine/renderers/yAxis.ts +105 -105
  193. package/src/engine/scale/__tests__/logFormula.spec.ts +148 -148
  194. package/src/engine/scale/logFormula.ts +130 -130
  195. package/src/engine/scale/price.ts +39 -39
  196. package/src/engine/scale/priceScale.ts +264 -264
  197. package/src/engine/subPaneManager.ts +427 -427
  198. package/src/engine/theme/colors.ts +642 -642
  199. package/src/engine/theme/fonts.ts +20 -20
  200. package/src/engine/utils/klineConfig.ts +49 -49
  201. package/src/engine/utils/tickCount.ts +11 -11
  202. package/src/engine/utils/tickPosition.ts +214 -214
  203. package/src/engine/utils/zoom.ts +83 -83
  204. package/src/engine/viewport/viewport.ts +67 -67
  205. package/src/index.ts +3 -3
  206. package/src/plugin/ConfigManager.ts +93 -93
  207. package/src/plugin/EventBus.ts +77 -77
  208. package/src/plugin/HookSystem.ts +106 -106
  209. package/src/plugin/PluginHost.ts +243 -243
  210. package/src/plugin/PluginRegistry.ts +92 -92
  211. package/src/plugin/StateStore.ts +73 -73
  212. package/src/plugin/index.ts +19 -19
  213. package/src/plugin/rendererPluginManager.ts +368 -368
  214. package/src/plugin/stateKeys.ts +8 -8
  215. package/src/plugin/types.ts +526 -526
  216. package/src/reactivity/index.ts +2 -2
  217. package/src/reactivity/signal.ts +119 -119
  218. package/src/semantic/controller.ts +251 -251
  219. package/src/semantic/drawShape.ts +260 -260
  220. package/src/semantic/index.ts +28 -28
  221. package/src/semantic/schema.json +256 -256
  222. package/src/semantic/types.ts +251 -251
  223. package/src/semantic/validator.ts +349 -349
  224. package/src/types/kLine.ts +13 -13
  225. package/src/types/price.ts +56 -56
  226. package/src/types/volumePrice.ts +33 -33
  227. package/src/utils/dateFormat.ts +208 -208
  228. package/src/utils/kLineDraw/axis.ts +562 -562
  229. package/src/utils/priceToY.ts +34 -34
  230. package/src/utils/volumePrice.ts +202 -202
  231. 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
+ }