@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,352 +1,352 @@
1
- import type { KLineData } from '../../types/price'
2
-
3
- /**
4
- * KLineData 的 SoA (Structure of Arrays) 布局
5
- * 用于零拷贝传输到 Web Worker
6
- *
7
- * 内存布局:
8
- * | timestamps | opens | highs | lows | closes | volumes | turnovers |
9
- * | 8*N | 8*N | 8*N | 8*N | 8*N | 8*N | 8*N |
10
- *
11
- * 其中 N 为数据长度,每列 8 字节(Float64)
12
- */
13
- export interface KLineSoALayout {
14
- /** 底层缓冲区(SharedArrayBuffer 或 ArrayBuffer) */
15
- buffer: SharedArrayBuffer | ArrayBuffer
16
- /** 数据长度 */
17
- length: number
18
- /** 是否使用 SharedArrayBuffer */
19
- isShared: boolean
20
- /** 是否有成交量数据 */
21
- hasVolume: boolean
22
- /** 是否有成交额数据 */
23
- hasTurnover: boolean
24
- /** 时间戳数组(毫秒) */
25
- timestamps: Float64Array
26
- /** 开盘价数组 */
27
- opens: Float64Array
28
- /** 最高价数组 */
29
- highs: Float64Array
30
- /** 最低价数组 */
31
- lows: Float64Array
32
- /** 收盘价数组 */
33
- closes: Float64Array
34
- /** 成交量数组(无值时为 0) */
35
- volumes: Float64Array
36
- /** 成交额数组(无值时为 0) */
37
- turnovers: Float64Array
38
- }
39
-
40
- /** 列索引定义(用于计算 offset) */
41
- const enum ColumnIndex {
42
- TIMESTAMP = 0,
43
- OPEN = 1,
44
- HIGH = 2,
45
- LOW = 3,
46
- CLOSE = 4,
47
- VOLUME = 5,
48
- TURNOVER = 6,
49
- COUNT = 7,
50
- }
51
-
52
- /** 每列的字节数 */
53
- const BYTES_PER_COLUMN = 8 // Float64
54
-
55
- /**
56
- * SharedKLineBuffer - K线数据的 SoA 管理类
57
- *
58
- * 提供 AoS (KLineData[]) 与 SoA 布局之间的转换,
59
- * 支持 SharedArrayBuffer(零拷贝)和 ArrayBuffer(降级)
60
- */
61
- export class SharedKLineBuffer {
62
- /**
63
- * 检测 SharedArrayBuffer 是否可用
64
- * 需要页面有 COOP/COEP 头支持
65
- */
66
- static detectSupport(): boolean {
67
- try {
68
- if (typeof SharedArrayBuffer === 'undefined') {
69
- return false
70
- }
71
- // 尝试创建并检测能否成功创建视图
72
- const testSab = new SharedArrayBuffer(8)
73
- const testView = new Float64Array(testSab)
74
- testView[0] = 1.0
75
- return testView[0] === 1.0
76
- } catch {
77
- return false
78
- }
79
- }
80
-
81
- /**
82
- * 计算 SoA 布局所需的总字节数
83
- */
84
- static calculateByteLength(length: number): number {
85
- return length * BYTES_PER_COLUMN * ColumnIndex.COUNT
86
- }
87
-
88
- /**
89
- * 将 KLineData[] 转换为 SoA 布局
90
- * @param data K线数据数组(AoS 格式)
91
- * @param preferShared 是否优先使用 SharedArrayBuffer(默认 true)
92
- * @returns SoA 布局对象
93
- */
94
- static fromKLineData(
95
- data: KLineData[],
96
- preferShared: boolean = true
97
- ): KLineSoALayout {
98
- const length = data.length
99
- const byteLength = SharedKLineBuffer.calculateByteLength(length)
100
- const useShared = preferShared && SharedKLineBuffer.detectSupport()
101
-
102
- // 检测是否有 volume 和 turnover 数据
103
- let hasVolume = false
104
- let hasTurnover = false
105
- for (const item of data) {
106
- if (item.volume !== undefined && item.volume !== 0) {
107
- hasVolume = true
108
- }
109
- if (item.turnover !== undefined && item.turnover !== 0) {
110
- hasTurnover = true
111
- }
112
- if (hasVolume && hasTurnover) break
113
- }
114
-
115
- // 创建缓冲区
116
- const buffer = useShared
117
- ? new SharedArrayBuffer(byteLength)
118
- : new ArrayBuffer(byteLength)
119
-
120
- // 创建列视图
121
- const timestamps = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.TIMESTAMP, length)
122
- const opens = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.OPEN, length)
123
- const highs = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.HIGH, length)
124
- const lows = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.LOW, length)
125
- const closes = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.CLOSE, length)
126
- const volumes = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.VOLUME, length)
127
- const turnovers = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.TURNOVER, length)
128
-
129
- // 填充数据
130
- for (let i = 0; i < length; i++) {
131
- const item = data[i]!
132
- timestamps[i] = item.timestamp
133
- opens[i] = item.open
134
- highs[i] = item.high
135
- lows[i] = item.low
136
- closes[i] = item.close
137
- volumes[i] = item.volume ?? 0
138
- turnovers[i] = item.turnover ?? 0
139
- }
140
-
141
- return {
142
- buffer,
143
- length,
144
- isShared: useShared,
145
- hasVolume,
146
- hasTurnover,
147
- timestamps,
148
- opens,
149
- highs,
150
- lows,
151
- closes,
152
- volumes,
153
- turnovers,
154
- }
155
- }
156
-
157
- /**
158
- * 更新现有 SoA 布局的数据(尽可能复用缓冲区)
159
- * 如果新数据长度超过原缓冲区,会创建新缓冲区
160
- *
161
- * @param layout 现有 SoA 布局
162
- * @param data 新的 K线数据
163
- * @returns 更新后的 SoA 布局(可能是新对象)
164
- */
165
- static updateExisting(layout: KLineSoALayout, data: KLineData[]): KLineSoALayout {
166
- const newLength = data.length
167
- const oldLength = layout.length
168
-
169
- // 如果长度变化或缓冲区类型不匹配,创建新布局
170
- if (newLength !== oldLength) {
171
- return SharedKLineBuffer.fromKLineData(data, layout.isShared)
172
- }
173
-
174
- // 检测是否有 volume 和 turnover 数据
175
- let hasVolume = false
176
- let hasTurnover = false
177
- for (const item of data) {
178
- if (item.volume !== undefined && item.volume !== 0) {
179
- hasVolume = true
180
- }
181
- if (item.turnover !== undefined && item.turnover !== 0) {
182
- hasTurnover = true
183
- }
184
- if (hasVolume && hasTurnover) break
185
- }
186
-
187
- // 复用现有视图,直接更新数据
188
- const { timestamps, opens, highs, lows, closes, volumes, turnovers } = layout
189
-
190
- for (let i = 0; i < newLength; i++) {
191
- const item = data[i]!
192
- timestamps[i] = item.timestamp
193
- opens[i] = item.open
194
- highs[i] = item.high
195
- lows[i] = item.low
196
- closes[i] = item.close
197
- volumes[i] = item.volume ?? 0
198
- turnovers[i] = item.turnover ?? 0
199
- }
200
-
201
- return {
202
- ...layout,
203
- hasVolume,
204
- hasTurnover,
205
- }
206
- }
207
-
208
- /**
209
- * 将 SoA 布局转换回 KLineData[](用于测试和兼容性)
210
- * @param layout SoA 布局
211
- * @returns K线数据数组
212
- */
213
- static toKLineData(layout: KLineSoALayout): KLineData[] {
214
- const result: KLineData[] = new Array(layout.length)
215
- const { timestamps, opens, highs, lows, closes, volumes, turnovers, hasVolume, hasTurnover } = layout
216
-
217
- for (let i = 0; i < layout.length; i++) {
218
- const item: KLineData = {
219
- timestamp: timestamps[i]!,
220
- open: opens[i]!,
221
- high: highs[i]!,
222
- low: lows[i]!,
223
- close: closes[i]!,
224
- }
225
-
226
- if (hasVolume) {
227
- const vol = volumes[i]!
228
- if (vol !== 0) item.volume = vol
229
- }
230
- if (hasTurnover) {
231
- const to = turnovers[i]!
232
- if (to !== 0) item.turnover = to
233
- }
234
-
235
- result[i] = item
236
- }
237
-
238
- return result
239
- }
240
-
241
- /**
242
- * 创建子视图(用于 Worker 中处理部分数据范围)
243
- * @param layout 原 SoA 布局
244
- * @param start 起始索引(包含)
245
- * @param end 结束索引(不包含)
246
- * @returns 子视图对象(共享同一缓冲区)
247
- */
248
- static createSubview(layout: KLineSoALayout, start: number, end: number): KLineSoALayout {
249
- if (start < 0) start = 0
250
- if (end > layout.length) end = layout.length
251
- if (start >= end) {
252
- throw new Error(`Invalid subview range: [${start}, ${end})`)
253
- }
254
-
255
- const length = end - start
256
- // 每列的总字节数 = 数据长度 * 每元素字节数
257
- const columnByteLength = layout.length * BYTES_PER_COLUMN
258
-
259
- return {
260
- buffer: layout.buffer,
261
- length,
262
- isShared: layout.isShared,
263
- hasVolume: layout.hasVolume,
264
- hasTurnover: layout.hasTurnover,
265
- timestamps: new Float64Array(
266
- layout.buffer,
267
- start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.TIMESTAMP,
268
- length
269
- ),
270
- opens: new Float64Array(
271
- layout.buffer,
272
- start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.OPEN,
273
- length
274
- ),
275
- highs: new Float64Array(
276
- layout.buffer,
277
- start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.HIGH,
278
- length
279
- ),
280
- lows: new Float64Array(
281
- layout.buffer,
282
- start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.LOW,
283
- length
284
- ),
285
- closes: new Float64Array(
286
- layout.buffer,
287
- start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.CLOSE,
288
- length
289
- ),
290
- volumes: new Float64Array(
291
- layout.buffer,
292
- start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.VOLUME,
293
- length
294
- ),
295
- turnovers: new Float64Array(
296
- layout.buffer,
297
- start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.TURNOVER,
298
- length
299
- ),
300
- }
301
- }
302
-
303
- /**
304
- * 获取缓冲区信息(用于调试和序列化)
305
- */
306
- static getBufferInfo(layout: KLineSoALayout): {
307
- byteLength: number
308
- isShared: boolean
309
- length: number
310
- columns: string[]
311
- } {
312
- return {
313
- byteLength: layout.buffer.byteLength,
314
- isShared: layout.isShared,
315
- length: layout.length,
316
- columns: ['timestamp', 'open', 'high', 'low', 'close', 'volume', 'turnover'],
317
- }
318
- }
319
- }
320
-
321
- /**
322
- * 获取 SoA 布局中 closes 列的视图(最常用列)
323
- * 用于指标计算中快速访问收盘价
324
- */
325
- export function getClosesView(layout: KLineSoALayout): Float64Array {
326
- return layout.closes
327
- }
328
-
329
- /**
330
- * 获取 SoA 布局中 highs/lows 列的视图
331
- * 用于需要高低价的指标(如 BOLL、STOCH、WMSR)
332
- */
333
- export function getHighsLowsViews(layout: KLineSoALayout): { highs: Float64Array; lows: Float64Array } {
334
- return { highs: layout.highs, lows: layout.lows }
335
- }
336
-
337
- /**
338
- * 获取 SoA 布局中 OHLC 四价视图
339
- */
340
- export function getOHLCViews(layout: KLineSoALayout): {
341
- opens: Float64Array
342
- highs: Float64Array
343
- lows: Float64Array
344
- closes: Float64Array
345
- } {
346
- return {
347
- opens: layout.opens,
348
- highs: layout.highs,
349
- lows: layout.lows,
350
- closes: layout.closes,
351
- }
352
- }
1
+ import type { KLineData } from '../../types/price'
2
+
3
+ /**
4
+ * KLineData 的 SoA (Structure of Arrays) 布局
5
+ * 用于零拷贝传输到 Web Worker
6
+ *
7
+ * 内存布局:
8
+ * | timestamps | opens | highs | lows | closes | volumes | turnovers |
9
+ * | 8*N | 8*N | 8*N | 8*N | 8*N | 8*N | 8*N |
10
+ *
11
+ * 其中 N 为数据长度,每列 8 字节(Float64)
12
+ */
13
+ export interface KLineSoALayout {
14
+ /** 底层缓冲区(SharedArrayBuffer 或 ArrayBuffer) */
15
+ buffer: SharedArrayBuffer | ArrayBuffer
16
+ /** 数据长度 */
17
+ length: number
18
+ /** 是否使用 SharedArrayBuffer */
19
+ isShared: boolean
20
+ /** 是否有成交量数据 */
21
+ hasVolume: boolean
22
+ /** 是否有成交额数据 */
23
+ hasTurnover: boolean
24
+ /** 时间戳数组(毫秒) */
25
+ timestamps: Float64Array
26
+ /** 开盘价数组 */
27
+ opens: Float64Array
28
+ /** 最高价数组 */
29
+ highs: Float64Array
30
+ /** 最低价数组 */
31
+ lows: Float64Array
32
+ /** 收盘价数组 */
33
+ closes: Float64Array
34
+ /** 成交量数组(无值时为 0) */
35
+ volumes: Float64Array
36
+ /** 成交额数组(无值时为 0) */
37
+ turnovers: Float64Array
38
+ }
39
+
40
+ /** 列索引定义(用于计算 offset) */
41
+ const enum ColumnIndex {
42
+ TIMESTAMP = 0,
43
+ OPEN = 1,
44
+ HIGH = 2,
45
+ LOW = 3,
46
+ CLOSE = 4,
47
+ VOLUME = 5,
48
+ TURNOVER = 6,
49
+ COUNT = 7,
50
+ }
51
+
52
+ /** 每列的字节数 */
53
+ const BYTES_PER_COLUMN = 8 // Float64
54
+
55
+ /**
56
+ * SharedKLineBuffer - K线数据的 SoA 管理类
57
+ *
58
+ * 提供 AoS (KLineData[]) 与 SoA 布局之间的转换,
59
+ * 支持 SharedArrayBuffer(零拷贝)和 ArrayBuffer(降级)
60
+ */
61
+ export class SharedKLineBuffer {
62
+ /**
63
+ * 检测 SharedArrayBuffer 是否可用
64
+ * 需要页面有 COOP/COEP 头支持
65
+ */
66
+ static detectSupport(): boolean {
67
+ try {
68
+ if (typeof SharedArrayBuffer === 'undefined') {
69
+ return false
70
+ }
71
+ // 尝试创建并检测能否成功创建视图
72
+ const testSab = new SharedArrayBuffer(8)
73
+ const testView = new Float64Array(testSab)
74
+ testView[0] = 1.0
75
+ return testView[0] === 1.0
76
+ } catch {
77
+ return false
78
+ }
79
+ }
80
+
81
+ /**
82
+ * 计算 SoA 布局所需的总字节数
83
+ */
84
+ static calculateByteLength(length: number): number {
85
+ return length * BYTES_PER_COLUMN * ColumnIndex.COUNT
86
+ }
87
+
88
+ /**
89
+ * 将 KLineData[] 转换为 SoA 布局
90
+ * @param data K线数据数组(AoS 格式)
91
+ * @param preferShared 是否优先使用 SharedArrayBuffer(默认 true)
92
+ * @returns SoA 布局对象
93
+ */
94
+ static fromKLineData(
95
+ data: KLineData[],
96
+ preferShared: boolean = true
97
+ ): KLineSoALayout {
98
+ const length = data.length
99
+ const byteLength = SharedKLineBuffer.calculateByteLength(length)
100
+ const useShared = preferShared && SharedKLineBuffer.detectSupport()
101
+
102
+ // 检测是否有 volume 和 turnover 数据
103
+ let hasVolume = false
104
+ let hasTurnover = false
105
+ for (const item of data) {
106
+ if (item.volume !== undefined && item.volume !== 0) {
107
+ hasVolume = true
108
+ }
109
+ if (item.turnover !== undefined && item.turnover !== 0) {
110
+ hasTurnover = true
111
+ }
112
+ if (hasVolume && hasTurnover) break
113
+ }
114
+
115
+ // 创建缓冲区
116
+ const buffer = useShared
117
+ ? new SharedArrayBuffer(byteLength)
118
+ : new ArrayBuffer(byteLength)
119
+
120
+ // 创建列视图
121
+ const timestamps = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.TIMESTAMP, length)
122
+ const opens = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.OPEN, length)
123
+ const highs = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.HIGH, length)
124
+ const lows = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.LOW, length)
125
+ const closes = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.CLOSE, length)
126
+ const volumes = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.VOLUME, length)
127
+ const turnovers = new Float64Array(buffer, length * BYTES_PER_COLUMN * ColumnIndex.TURNOVER, length)
128
+
129
+ // 填充数据
130
+ for (let i = 0; i < length; i++) {
131
+ const item = data[i]!
132
+ timestamps[i] = item.timestamp
133
+ opens[i] = item.open
134
+ highs[i] = item.high
135
+ lows[i] = item.low
136
+ closes[i] = item.close
137
+ volumes[i] = item.volume ?? 0
138
+ turnovers[i] = item.turnover ?? 0
139
+ }
140
+
141
+ return {
142
+ buffer,
143
+ length,
144
+ isShared: useShared,
145
+ hasVolume,
146
+ hasTurnover,
147
+ timestamps,
148
+ opens,
149
+ highs,
150
+ lows,
151
+ closes,
152
+ volumes,
153
+ turnovers,
154
+ }
155
+ }
156
+
157
+ /**
158
+ * 更新现有 SoA 布局的数据(尽可能复用缓冲区)
159
+ * 如果新数据长度超过原缓冲区,会创建新缓冲区
160
+ *
161
+ * @param layout 现有 SoA 布局
162
+ * @param data 新的 K线数据
163
+ * @returns 更新后的 SoA 布局(可能是新对象)
164
+ */
165
+ static updateExisting(layout: KLineSoALayout, data: KLineData[]): KLineSoALayout {
166
+ const newLength = data.length
167
+ const oldLength = layout.length
168
+
169
+ // 如果长度变化或缓冲区类型不匹配,创建新布局
170
+ if (newLength !== oldLength) {
171
+ return SharedKLineBuffer.fromKLineData(data, layout.isShared)
172
+ }
173
+
174
+ // 检测是否有 volume 和 turnover 数据
175
+ let hasVolume = false
176
+ let hasTurnover = false
177
+ for (const item of data) {
178
+ if (item.volume !== undefined && item.volume !== 0) {
179
+ hasVolume = true
180
+ }
181
+ if (item.turnover !== undefined && item.turnover !== 0) {
182
+ hasTurnover = true
183
+ }
184
+ if (hasVolume && hasTurnover) break
185
+ }
186
+
187
+ // 复用现有视图,直接更新数据
188
+ const { timestamps, opens, highs, lows, closes, volumes, turnovers } = layout
189
+
190
+ for (let i = 0; i < newLength; i++) {
191
+ const item = data[i]!
192
+ timestamps[i] = item.timestamp
193
+ opens[i] = item.open
194
+ highs[i] = item.high
195
+ lows[i] = item.low
196
+ closes[i] = item.close
197
+ volumes[i] = item.volume ?? 0
198
+ turnovers[i] = item.turnover ?? 0
199
+ }
200
+
201
+ return {
202
+ ...layout,
203
+ hasVolume,
204
+ hasTurnover,
205
+ }
206
+ }
207
+
208
+ /**
209
+ * 将 SoA 布局转换回 KLineData[](用于测试和兼容性)
210
+ * @param layout SoA 布局
211
+ * @returns K线数据数组
212
+ */
213
+ static toKLineData(layout: KLineSoALayout): KLineData[] {
214
+ const result: KLineData[] = new Array(layout.length)
215
+ const { timestamps, opens, highs, lows, closes, volumes, turnovers, hasVolume, hasTurnover } = layout
216
+
217
+ for (let i = 0; i < layout.length; i++) {
218
+ const item: KLineData = {
219
+ timestamp: timestamps[i]!,
220
+ open: opens[i]!,
221
+ high: highs[i]!,
222
+ low: lows[i]!,
223
+ close: closes[i]!,
224
+ }
225
+
226
+ if (hasVolume) {
227
+ const vol = volumes[i]!
228
+ if (vol !== 0) item.volume = vol
229
+ }
230
+ if (hasTurnover) {
231
+ const to = turnovers[i]!
232
+ if (to !== 0) item.turnover = to
233
+ }
234
+
235
+ result[i] = item
236
+ }
237
+
238
+ return result
239
+ }
240
+
241
+ /**
242
+ * 创建子视图(用于 Worker 中处理部分数据范围)
243
+ * @param layout 原 SoA 布局
244
+ * @param start 起始索引(包含)
245
+ * @param end 结束索引(不包含)
246
+ * @returns 子视图对象(共享同一缓冲区)
247
+ */
248
+ static createSubview(layout: KLineSoALayout, start: number, end: number): KLineSoALayout {
249
+ if (start < 0) start = 0
250
+ if (end > layout.length) end = layout.length
251
+ if (start >= end) {
252
+ throw new Error(`Invalid subview range: [${start}, ${end})`)
253
+ }
254
+
255
+ const length = end - start
256
+ // 每列的总字节数 = 数据长度 * 每元素字节数
257
+ const columnByteLength = layout.length * BYTES_PER_COLUMN
258
+
259
+ return {
260
+ buffer: layout.buffer,
261
+ length,
262
+ isShared: layout.isShared,
263
+ hasVolume: layout.hasVolume,
264
+ hasTurnover: layout.hasTurnover,
265
+ timestamps: new Float64Array(
266
+ layout.buffer,
267
+ start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.TIMESTAMP,
268
+ length
269
+ ),
270
+ opens: new Float64Array(
271
+ layout.buffer,
272
+ start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.OPEN,
273
+ length
274
+ ),
275
+ highs: new Float64Array(
276
+ layout.buffer,
277
+ start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.HIGH,
278
+ length
279
+ ),
280
+ lows: new Float64Array(
281
+ layout.buffer,
282
+ start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.LOW,
283
+ length
284
+ ),
285
+ closes: new Float64Array(
286
+ layout.buffer,
287
+ start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.CLOSE,
288
+ length
289
+ ),
290
+ volumes: new Float64Array(
291
+ layout.buffer,
292
+ start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.VOLUME,
293
+ length
294
+ ),
295
+ turnovers: new Float64Array(
296
+ layout.buffer,
297
+ start * BYTES_PER_COLUMN + columnByteLength * ColumnIndex.TURNOVER,
298
+ length
299
+ ),
300
+ }
301
+ }
302
+
303
+ /**
304
+ * 获取缓冲区信息(用于调试和序列化)
305
+ */
306
+ static getBufferInfo(layout: KLineSoALayout): {
307
+ byteLength: number
308
+ isShared: boolean
309
+ length: number
310
+ columns: string[]
311
+ } {
312
+ return {
313
+ byteLength: layout.buffer.byteLength,
314
+ isShared: layout.isShared,
315
+ length: layout.length,
316
+ columns: ['timestamp', 'open', 'high', 'low', 'close', 'volume', 'turnover'],
317
+ }
318
+ }
319
+ }
320
+
321
+ /**
322
+ * 获取 SoA 布局中 closes 列的视图(最常用列)
323
+ * 用于指标计算中快速访问收盘价
324
+ */
325
+ export function getClosesView(layout: KLineSoALayout): Float64Array {
326
+ return layout.closes
327
+ }
328
+
329
+ /**
330
+ * 获取 SoA 布局中 highs/lows 列的视图
331
+ * 用于需要高低价的指标(如 BOLL、STOCH、WMSR)
332
+ */
333
+ export function getHighsLowsViews(layout: KLineSoALayout): { highs: Float64Array; lows: Float64Array } {
334
+ return { highs: layout.highs, lows: layout.lows }
335
+ }
336
+
337
+ /**
338
+ * 获取 SoA 布局中 OHLC 四价视图
339
+ */
340
+ export function getOHLCViews(layout: KLineSoALayout): {
341
+ opens: Float64Array
342
+ highs: Float64Array
343
+ lows: Float64Array
344
+ closes: Float64Array
345
+ } {
346
+ return {
347
+ opens: layout.opens,
348
+ highs: layout.highs,
349
+ lows: layout.lows,
350
+ closes: layout.closes,
351
+ }
352
+ }