@363045841yyt/klinechart-core 0.7.13 → 0.8.1-alpha.1

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 (87) hide show
  1. package/dist/config/chartSettings.d.ts +0 -6
  2. package/dist/config/chartSettings.d.ts.map +1 -1
  3. package/dist/config/chartSettings.js +0 -1
  4. package/dist/config/chartSettings.js.map +1 -1
  5. package/dist/controllers/createChartController.d.ts.map +1 -1
  6. package/dist/controllers/createChartController.js +22 -0
  7. package/dist/controllers/createChartController.js.map +1 -1
  8. package/dist/controllers/index.d.ts +4 -3
  9. package/dist/controllers/index.d.ts.map +1 -1
  10. package/dist/controllers/index.js +3 -1
  11. package/dist/controllers/index.js.map +1 -1
  12. package/dist/controllers/types.d.ts +21 -0
  13. package/dist/controllers/types.d.ts.map +1 -1
  14. package/dist/data-fetchers/baostock.d.ts +3 -0
  15. package/dist/data-fetchers/baostock.d.ts.map +1 -0
  16. package/dist/data-fetchers/baostock.js +34 -0
  17. package/dist/data-fetchers/baostock.js.map +1 -0
  18. package/dist/data-fetchers/hundred-mock.d.ts +3 -0
  19. package/dist/data-fetchers/hundred-mock.d.ts.map +1 -0
  20. package/dist/data-fetchers/hundred-mock.js +30 -0
  21. package/dist/data-fetchers/hundred-mock.js.map +1 -0
  22. package/dist/data-fetchers/index.d.ts +5 -0
  23. package/dist/data-fetchers/index.d.ts.map +1 -0
  24. package/dist/data-fetchers/index.js +5 -0
  25. package/dist/data-fetchers/index.js.map +1 -0
  26. package/dist/data-fetchers/router.d.ts +3 -0
  27. package/dist/data-fetchers/router.d.ts.map +1 -0
  28. package/dist/data-fetchers/router.js +16 -0
  29. package/dist/data-fetchers/router.js.map +1 -0
  30. package/dist/data-fetchers/thousand-mock.d.ts +3 -0
  31. package/dist/data-fetchers/thousand-mock.d.ts.map +1 -0
  32. package/dist/data-fetchers/thousand-mock.js +29 -0
  33. package/dist/data-fetchers/thousand-mock.js.map +1 -0
  34. package/dist/engine/chart.d.ts +14 -0
  35. package/dist/engine/chart.d.ts.map +1 -1
  36. package/dist/engine/chart.js +55 -1
  37. package/dist/engine/chart.js.map +1 -1
  38. package/dist/engine/renderers/Indicator/{indicatorData.d.ts → indicatorCatalog.d.ts} +1 -1
  39. package/dist/engine/renderers/Indicator/indicatorCatalog.d.ts.map +1 -0
  40. package/dist/engine/renderers/Indicator/{indicatorData.js → indicatorCatalog.js} +94 -406
  41. package/dist/engine/renderers/Indicator/indicatorCatalog.js.map +1 -0
  42. package/dist/engine/renderers/Indicator/structure.js +1 -1
  43. package/dist/engine/renderers/Indicator/structure.js.map +1 -1
  44. package/dist/engine/renderers/Indicator/supertrend.js +1 -1
  45. package/dist/engine/renderers/Indicator/supertrend.js.map +1 -1
  46. package/dist/engine/renderers/paneTitle.d.ts.map +1 -1
  47. package/dist/engine/renderers/paneTitle.js +3 -2
  48. package/dist/engine/renderers/paneTitle.js.map +1 -1
  49. package/dist/engine/subPaneManager.d.ts.map +1 -1
  50. package/dist/engine/subPaneManager.js +2 -1
  51. package/dist/engine/subPaneManager.js.map +1 -1
  52. package/dist/semantic/controller.d.ts +3 -14
  53. package/dist/semantic/controller.d.ts.map +1 -1
  54. package/dist/semantic/controller.js +9 -43
  55. package/dist/semantic/controller.js.map +1 -1
  56. package/dist/semantic/index.d.ts +3 -2
  57. package/dist/semantic/index.d.ts.map +1 -1
  58. package/dist/semantic/index.js +1 -1
  59. package/dist/semantic/index.js.map +1 -1
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.d.ts.map +1 -1
  62. package/dist/version.js +1 -1
  63. package/dist/version.js.map +1 -1
  64. package/package.json +4 -4
  65. package/src/config/chartSettings.ts +0 -1
  66. package/src/controllers/__tests__/indicatorSelector.test.ts +1 -1
  67. package/src/controllers/createChartController.ts +28 -1
  68. package/src/controllers/index.ts +7 -2
  69. package/src/controllers/types.ts +30 -0
  70. package/src/data-fetchers/baostock.ts +34 -0
  71. package/src/data-fetchers/hundred-mock.ts +31 -0
  72. package/src/data-fetchers/index.ts +4 -0
  73. package/src/data-fetchers/router.ts +17 -0
  74. package/src/data-fetchers/thousand-mock.ts +30 -0
  75. package/src/engine/chart.ts +61 -1
  76. package/src/engine/renderers/Indicator/indicatorCatalog.ts +346 -0
  77. package/src/engine/renderers/Indicator/structure.ts +1 -1
  78. package/src/engine/renderers/Indicator/supertrend.ts +1 -1
  79. package/src/engine/renderers/paneTitle.ts +3 -2
  80. package/src/engine/subPaneManager.ts +2 -1
  81. package/src/semantic/__tests__/controller.test.ts +19 -7
  82. package/src/semantic/controller.ts +9 -57
  83. package/src/semantic/index.ts +3 -2
  84. package/src/version.ts +1 -1
  85. package/dist/engine/renderers/Indicator/indicatorData.d.ts.map +0 -1
  86. package/dist/engine/renderers/Indicator/indicatorData.js.map +0 -1
  87. package/src/engine/renderers/Indicator/indicatorData.ts +0 -650
@@ -76,6 +76,31 @@ export interface KLineData {
76
76
 
77
77
  export type { PaneSpec }
78
78
 
79
+ // ---------------------------------------------------------------------------
80
+ // Symbol specification & DataFetcher adapter
81
+ // ---------------------------------------------------------------------------
82
+
83
+ export interface SymbolSpec {
84
+ symbol: string
85
+ exchange?: string
86
+ period?: string
87
+ adjust?: string
88
+ source?: string
89
+ startDate?: string
90
+ endDate?: string
91
+ }
92
+
93
+ export type DataFetcher = (
94
+ source: string,
95
+ config: {
96
+ symbol: string
97
+ startDate: string
98
+ endDate: string
99
+ period: string
100
+ adjust: string
101
+ },
102
+ ) => Promise<ReadonlyArray<KLineData>>
103
+
79
104
  // ---------------------------------------------------------------------------
80
105
  // Indicator metadata
81
106
  // ---------------------------------------------------------------------------
@@ -183,6 +208,8 @@ export interface DrawingControllerCallbacks {
183
208
  export interface ChartMountOptions {
184
209
  container: HTMLElement
185
210
  data: ReadonlyArray<KLineData>
211
+ symbols?: ReadonlyArray<SymbolSpec>
212
+ dataFetcher?: DataFetcher
186
213
  initialZoomLevel?: number
187
214
  zoomLevels?: number
188
215
  theme?: 'light' | 'dark'
@@ -205,6 +232,7 @@ export interface ChartController extends DrawingChartAdapter {
205
232
  // ---- Signals ----
206
233
  readonly viewport: Signal<ChartViewport>
207
234
  readonly data: Signal<ReadonlyArray<KLineData>>
235
+ readonly symbols: Signal<ReadonlyArray<SymbolSpec>>
208
236
  readonly theme: Signal<'light' | 'dark'>
209
237
  readonly indicators: Signal<ReadonlyArray<IndicatorInstance>>
210
238
  readonly subPanes: Signal<ReadonlyArray<SubPaneInfo>>
@@ -218,6 +246,8 @@ export interface ChartController extends DrawingChartAdapter {
218
246
  readonly catalog: ReadonlyArray<IndicatorDefinition>
219
247
 
220
248
  // ---- Data ----
249
+ setSymbols(next: ReadonlyArray<SymbolSpec>): void
250
+ setDataFetcher(fetcher: DataFetcher | null): void
221
251
  setData(next: ReadonlyArray<KLineData>): void
222
252
  appendData(next: ReadonlyArray<KLineData>): void
223
253
  updateData(next: ReadonlyArray<KLineData>): void
@@ -0,0 +1,34 @@
1
+ import type { DataFetcher, KLineData } from '../controllers/types'
2
+
3
+ export const baostockDataFetcher: DataFetcher = async (source, config) => {
4
+ console.log(`[baostock] fetching ${config.symbol} ${config.period} ${config.startDate}~${config.endDate}`)
5
+ const baseUrl = source === 'baostock' ? 'http://localhost:8000' : ''
6
+ const adjustMap: Record<string, string> = { qfq: '2', hfq: '1', none: '3' }
7
+ const periodMap: Record<string, string> = { daily: 'd', weekly: 'w', monthly: 'm', '5min': '5', '15min': '15', '30min': '30', '60min': '60' }
8
+ const adjustflag = adjustMap[config.adjust] ?? '3'
9
+ const url = `${baseUrl}/api/stock/kdata?stock_code=${config.symbol}&start_date=${config.startDate}&end_date=${config.endDate}&frequency=${periodMap[config.period] ?? 'd'}&adjustflag=${adjustflag}`
10
+ try {
11
+ const res = await fetch(url)
12
+ console.log(res)
13
+ if (!res.ok) {
14
+ console.warn(`[baostock] fetch failed: ${res.status} ${res.statusText}`)
15
+ return []
16
+ }
17
+ const json = await res.json()
18
+ console.log(json)
19
+ return (json.data ?? json).map((item: Record<string, unknown>) => ({
20
+ timestamp: new Date(item.date as string).getTime(),
21
+ open: Number(item.open),
22
+ high: Number(item.high),
23
+ low: Number(item.low),
24
+ close: Number(item.close),
25
+ volume: Number(item.volume),
26
+ turnover: Number(item.amount ?? 0),
27
+ turnoverRate: item.turn === '' ? 0 : Number(item.turn),
28
+ stockCode: String(item.code ?? config.symbol),
29
+ })) as KLineData[]
30
+ } catch (err) {
31
+ console.warn('[baostock] network error:', err)
32
+ return []
33
+ }
34
+ }
@@ -0,0 +1,31 @@
1
+ import type { DataFetcher, KLineData } from '../controllers/types'
2
+
3
+ export const hundredMockDataFetcher: DataFetcher = async (_source, config) => {
4
+ console.log(`[hundred-mock] generating ${config.symbol} ${config.period}`)
5
+ const start = new Date(config.startDate).getTime()
6
+ const end = new Date(config.endDate).getTime()
7
+ const dayMs = 86400000
8
+ const totalDays = Math.floor((end - start) / dayMs) + 1
9
+ const data: KLineData[] = []
10
+ let price = 10 + Math.random() * 5
11
+ for (let i = 0; i < totalDays; i++) {
12
+ const ts = start + i * dayMs
13
+ const change = (Math.random() - 0.48) * price * 0.06
14
+ const open = price
15
+ const close = Math.round((open + change) * 100) / 100
16
+ const high = Math.round(Math.max(open, close) * (1 + Math.random() * 0.03) * 100) / 100
17
+ const low = Math.round(Math.min(open, close) * (1 - Math.random() * 0.03) * 100) / 100
18
+ const volume = Math.round(Math.random() * 10000000 + 1000000)
19
+ data.push({
20
+ timestamp: ts,
21
+ open,
22
+ high,
23
+ low,
24
+ close,
25
+ volume,
26
+ turnover: Math.round((volume * (open + close)) / 2),
27
+ })
28
+ price = close
29
+ }
30
+ return data
31
+ }
@@ -0,0 +1,4 @@
1
+ export { thousandMockDataFetcher } from './thousand-mock'
2
+ export { hundredMockDataFetcher } from './hundred-mock'
3
+ export { baostockDataFetcher } from './baostock'
4
+ export { routerDataFetcher } from './router'
@@ -0,0 +1,17 @@
1
+ import type { DataFetcher } from '../controllers/types'
2
+ import { baostockDataFetcher } from './baostock'
3
+ import { hundredMockDataFetcher } from './hundred-mock'
4
+ import { thousandMockDataFetcher } from './thousand-mock'
5
+
6
+ export const routerDataFetcher: DataFetcher = (source, config) => {
7
+ switch (source) {
8
+ case 'baostock':
9
+ return baostockDataFetcher(source, config)
10
+ case 'mock-100':
11
+ return hundredMockDataFetcher(source, config)
12
+ case 'mock-10000':
13
+ return thousandMockDataFetcher(source, config)
14
+ default:
15
+ return hundredMockDataFetcher(source, config)
16
+ }
17
+ }
@@ -0,0 +1,30 @@
1
+ import type { DataFetcher, KLineData } from '../controllers/types'
2
+
3
+ export const thousandMockDataFetcher: DataFetcher = async (_source, _config) => {
4
+ console.log('[thousand-mock] generating 10k K-lines')
5
+ const data: KLineData[] = []
6
+ const startTime = new Date('2020-01-01').getTime()
7
+ const dayMs = 24 * 60 * 60 * 1000
8
+ let lastClose = 3000
9
+ for (let i = 0; i < 10000; i++) {
10
+ const timestamp = startTime + i * dayMs
11
+ const volatility = 0.02
12
+ const trend = 0.0001
13
+ const change = (Math.random() - 0.5) * 2 * volatility + trend
14
+ const open = lastClose
15
+ const close = open * (1 + change)
16
+ const high = Math.max(open, close) * (1 + Math.random() * 0.01)
17
+ const low = Math.min(open, close) * (1 - Math.random() * 0.01)
18
+ const volume = Math.floor(1000000 + Math.random() * 5000000)
19
+ data.push({
20
+ timestamp,
21
+ open: parseFloat(open.toFixed(2)),
22
+ high: parseFloat(high.toFixed(2)),
23
+ low: parseFloat(low.toFixed(2)),
24
+ close: parseFloat(close.toFixed(2)),
25
+ volume,
26
+ })
27
+ lastClose = close
28
+ }
29
+ return data
30
+ }
@@ -1,6 +1,7 @@
1
1
  import type { KLineData } from '../types/price'
2
2
  import type { ChartSettings } from '../config/chartSettings'
3
3
  import { createSignal, computed, type Signal, type Computed } from '../reactivity/signal'
4
+ import type { SymbolSpec, DataFetcher } from '../controllers/types'
4
5
  import { getVisibleRange } from './viewport/viewport'
5
6
  import { Pane, type VisibleRange, UpdateLevel } from './layout/pane'
6
7
  import { InteractionController, type InteractionSnapshot } from './controller/interaction'
@@ -157,6 +158,7 @@ export class Chart {
157
158
  private dom: ChartDom
158
159
  private opt: ResolvedChartOptions
159
160
  private _internalData: KLineData[] = []
161
+ private _dataFetcher: DataFetcher | null = null
160
162
 
161
163
  private raf: number | null = null
162
164
  private pendingUpdateLevel: UpdateLevel = UpdateLevel.All
@@ -742,7 +744,10 @@ export class Chart {
742
744
 
743
745
  // 2. 准备帧数据(视口 / 可见范围 / K 线坐标,优先走缓存)
744
746
  const frame = this.prepareFrameData(level)
745
- if (!frame) return
747
+ if (!frame) {
748
+ if (this._internalData.length === 0) this.clearAllCanvases()
749
+ return
750
+ }
746
751
 
747
752
  const { vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx, useCachedFrame } = frame
748
753
 
@@ -840,6 +845,24 @@ export class Chart {
840
845
  return { vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx, useCachedFrame }
841
846
  }
842
847
 
848
+ private clearAllCanvases() {
849
+ const vp = this.computeViewport()
850
+ if (!vp) return
851
+ for (const r of this.paneRenderers) {
852
+ const { mainCtx, overlayCtx, yAxisCtx } = r.getContexts()
853
+ const pane = r.getPane()
854
+ mainCtx?.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
855
+ overlayCtx?.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
856
+ yAxisCtx?.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
857
+ }
858
+ const xCtx = this.xAxisCtx
859
+ if (xCtx) {
860
+ const xW = xCtx.canvas.width
861
+ const xH = xCtx.canvas.height
862
+ xCtx.clearRect(0, 0, xW, xH)
863
+ }
864
+ }
865
+
843
866
  private renderPanes(
844
867
  vp: Viewport,
845
868
  range: VisibleRange,
@@ -2096,6 +2119,7 @@ export class Chart {
2096
2119
  })
2097
2120
 
2098
2121
  private _dataSignal = createSignal<ReadonlyArray<KLineData>>([])
2122
+ private _symbolsSignal = createSignal<ReadonlyArray<SymbolSpec>>([])
2099
2123
  private _themeSignal = createSignal<'light' | 'dark'>('light')
2100
2124
  private _drawingToolSignal = createSignal<DrawingToolType | null>(null)
2101
2125
  private _drawingsSignal = createSignal<ReadonlyArray<import('../plugin').DrawingObject>>([])
@@ -2160,6 +2184,11 @@ export class Chart {
2160
2184
  return this._dataSignal
2161
2185
  }
2162
2186
 
2187
+ /** 符号信号 */
2188
+ get symbols(): Signal<ReadonlyArray<SymbolSpec>> {
2189
+ return this._symbolsSignal
2190
+ }
2191
+
2163
2192
  /** 主题信号 */
2164
2193
  get theme(): Signal<'light' | 'dark'> {
2165
2194
  return this._themeSignal
@@ -2218,6 +2247,37 @@ export class Chart {
2218
2247
  this.setData(merged)
2219
2248
  }
2220
2249
 
2250
+ /**
2251
+ * 设置数据获取器适配器
2252
+ */
2253
+ setDataFetcher(fetcher: DataFetcher | null): void {
2254
+ this._dataFetcher = fetcher
2255
+ }
2256
+
2257
+ /**
2258
+ * 设置当前符号并触发数据加载
2259
+ */
2260
+ setSymbols(specs: ReadonlyArray<SymbolSpec>): void {
2261
+ this._symbolsSignal.set(specs)
2262
+ if (!this._dataFetcher || specs.length === 0) return
2263
+ const spec = specs[0]!
2264
+ console.log(this._dataFetcher)
2265
+ this._dataFetcher(
2266
+ spec.source ?? 'baostock',
2267
+ {
2268
+ symbol: spec.symbol,
2269
+ startDate: spec.startDate ?? '',
2270
+ endDate: spec.endDate ?? '',
2271
+ period: spec.period ?? 'daily',
2272
+ adjust: spec.adjust ?? 'none',
2273
+ },
2274
+ ).then((data) => {
2275
+ this.updateData([...data])
2276
+ }).catch((err) => {
2277
+ console.error('[Chart] setSymbols fetch failed:', err)
2278
+ })
2279
+ }
2280
+
2221
2281
  // ---------- Theme ----------
2222
2282
 
2223
2283
  /**
@@ -0,0 +1,346 @@
1
+ import { getRegisteredIndicatorDefinitions } from '../../indicators/indicatorDefinitionRegistry'
2
+
3
+ export interface ParamConfig {
4
+ key: string
5
+ label: string
6
+ type: 'number'
7
+ min?: number
8
+ max?: number
9
+ step?: number
10
+ default?: number
11
+ description?: string
12
+ }
13
+
14
+ export interface Indicator {
15
+ id: string
16
+ label: string
17
+ name: string
18
+ pane: 'main' | 'sub'
19
+ description?: string
20
+ params?: ParamConfig[]
21
+ }
22
+
23
+ function normalizeId(id: string): string {
24
+ return id.trim().toLowerCase().replace(/[^a-z0-9]/g, '')
25
+ }
26
+
27
+ // UI-only metadata that don't belong in the @Indicator decorator (rendering layer)
28
+ const uiMeta: Record<string, {
29
+ name: string
30
+ description: string
31
+ params?: ParamConfig[]
32
+ }> = {
33
+ ma: {
34
+ name: '均线',
35
+ description: '',
36
+ params: [],
37
+ },
38
+ volume: {
39
+ name: '成交量',
40
+ description:
41
+ '成交量反映市场活跃度,柱状图显示每根K线的交易量。上涨时柱子为红色,下跌时为绿色。',
42
+ },
43
+ boll: {
44
+ name: '布林带',
45
+ description:
46
+ '布林带由三条轨道线组成,用于判断价格的波动范围和趋势强度。价格触及上轨可能超买,触及下轨可能超卖。',
47
+ params: [
48
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 20, description: '计算移动平均线的周期数,周期越长轨道越平滑' },
49
+ { key: 'multiplier', label: '倍数', type: 'number', min: 0.1, max: 5, step: 0.1, default: 2, description: '标准差倍数,决定轨道宽度,通常为 2' },
50
+ ],
51
+ },
52
+ expma: {
53
+ name: '指数平滑移动平均线',
54
+ description:
55
+ 'EXPMA 对近期价格给予更高权重,比普通 MA 更敏感。快线上穿慢线为金叉看涨,下穿为死叉看跌。',
56
+ params: [
57
+ { key: 'fastPeriod', label: '快线', type: 'number', min: 2, max: 100, step: 1, default: 12, description: '快线周期,对价格变化更敏感' },
58
+ { key: 'slowPeriod', label: '慢线', type: 'number', min: 2, max: 200, step: 1, default: 50, description: '慢线周期,用于判断趋势方向' },
59
+ ],
60
+ },
61
+ ene: {
62
+ name: '轨道线',
63
+ description:
64
+ 'ENE 轨道线由三条轨道组成,价格突破上轨可能超买,突破下轨可能超卖,适合判断震荡行情的买卖点。',
65
+ params: [
66
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 10, description: '计算中轨的周期数' },
67
+ { key: 'deviation', label: '偏离率', type: 'number', min: 1, max: 30, step: 0.5, default: 11, description: '轨道偏离率百分比,决定轨道宽度' },
68
+ ],
69
+ },
70
+ macd: {
71
+ name: '指数平滑异同移动平均线',
72
+ description:
73
+ 'MACD 通过快慢均线的交叉判断趋势方向和动量。DIF 上穿 DEA 为金叉看涨,下穿为死叉看跌。',
74
+ params: [
75
+ { key: 'fastPeriod', label: '快线', type: 'number', min: 2, max: 50, step: 1, default: 12, description: '快线 EMA 周期,对价格变化更敏感' },
76
+ { key: 'slowPeriod', label: '慢线', type: 'number', min: 2, max: 100, step: 1, default: 26, description: '慢线 EMA 周期,用于计算 DIF' },
77
+ { key: 'signalPeriod', label: '信号', type: 'number', min: 2, max: 50, step: 1, default: 9, description: 'DEA 的 EMA 周期,用于生成买卖信号' },
78
+ ],
79
+ },
80
+ rsi: {
81
+ name: '相对强弱指标',
82
+ description: 'RSI 衡量价格变动的速度和幅度,判断超买超卖状态。RSI > 70 超买,RSI < 30 超卖。',
83
+ params: [
84
+ { key: 'period1', label: '周期 1', type: 'number', min: 2, max: 100, step: 1, default: 6, description: '第一条 RSI 周期,通常为 6(快线)' },
85
+ { key: 'period2', label: '周期 2', type: 'number', min: 2, max: 100, step: 1, default: 12, description: '第二条 RSI 周期,通常为 12(中线)' },
86
+ { key: 'period3', label: '周期 3', type: 'number', min: 2, max: 100, step: 1, default: 24, description: '第三条 RSI 周期,通常为 24(慢线)' },
87
+ ],
88
+ },
89
+ cci: {
90
+ name: '顺势指标',
91
+ description:
92
+ 'CCI 衡量价格与统计平均值的偏离程度。CCI > 100 超买,CCI < -100 超卖,适合捕捉趋势反转。',
93
+ params: [
94
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 14, description: '计算周期,周期越短信号越灵敏' },
95
+ ],
96
+ },
97
+ stoch: {
98
+ name: '随机指标',
99
+ description:
100
+ 'KDJ 随机指标通过比较收盘价与价格区间判断超买超卖。K > 80 超买,K < 20 超卖,K 上穿 D 金叉。',
101
+ params: [
102
+ { key: 'n', label: 'K 周期', type: 'number', min: 2, max: 100, step: 1, default: 9, description: '计算 K 值的周期,统计 N 日内价格区间' },
103
+ { key: 'm', label: 'D 周期', type: 'number', min: 1, max: 50, step: 1, default: 3, description: 'D 值是 K 的 M 日移动平均,使信号更平滑' },
104
+ ],
105
+ },
106
+ mom: {
107
+ name: '动量指标',
108
+ description:
109
+ '动量指标衡量价格变化的速度,MOM > 0 表示上涨动能,MOM < 0 表示下跌动能。适合判断趋势强度。',
110
+ params: [
111
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 10, description: '与多少日前价格比较,周期越短越灵敏' },
112
+ ],
113
+ },
114
+ wmsr: {
115
+ name: '威廉指标',
116
+ description: '威廉指标衡量超买超卖程度,范围为 -100 到 0。WMSR > -20 超买,WMSR < -80 超卖。',
117
+ params: [
118
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 14, description: '回溯周期,统计周期内最高最低价' },
119
+ ],
120
+ },
121
+ kst: {
122
+ name: '确然指标',
123
+ description:
124
+ 'KST 综合多个 ROC 判断长期趋势,KST 上穿信号线看涨,下穿看跌。适合捕捉主要趋势转换。',
125
+ params: [
126
+ { key: 'roc1', label: 'ROC1', type: 'number', min: 2, max: 100, step: 1, default: 10, description: '短期变化率周期' },
127
+ { key: 'roc2', label: 'ROC2', type: 'number', min: 2, max: 100, step: 1, default: 15, description: '中短期变化率周期' },
128
+ { key: 'roc3', label: 'ROC3', type: 'number', min: 2, max: 100, step: 1, default: 20, description: '中长期变化率周期' },
129
+ { key: 'roc4', label: 'ROC4', type: 'number', min: 2, max: 100, step: 1, default: 30, description: '长期变化率周期' },
130
+ { key: 'signalPeriod', label: '信号', type: 'number', min: 2, max: 50, step: 1, default: 9, description: '信号线的 SMA 周期' },
131
+ ],
132
+ },
133
+ fastk: {
134
+ name: '快速随机指标',
135
+ description:
136
+ 'FASTK 是未经过平滑处理的随机指标,比普通 KDJ 更敏感,能更快捕捉价格转折点,但假信号也更多。',
137
+ params: [
138
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 9, description: '计算周期,周期越短越敏感' },
139
+ ],
140
+ },
141
+ atr: {
142
+ name: '平均真实波幅',
143
+ description:
144
+ 'ATR(Average True Range)衡量市场波动性,值越大表示波动越剧烈。Wilder 平滑算法,常用于设置止损位和判断趋势强度。',
145
+ params: [
146
+ { key: 'period', label: '周期', type: 'number', min: 1, max: 100, step: 1, default: 14, description: 'ATR 计算周期,周期越长曲线越平滑' },
147
+ ],
148
+ },
149
+ wma: {
150
+ name: '加权移动平均',
151
+ description: 'WMA 对近期价格赋予更高权重,比普通 MA 反应更快,适用于短期趋势跟踪。',
152
+ params: [{ key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 10, description: '计算周期' }],
153
+ },
154
+ dema: {
155
+ name: '双指数移动平均',
156
+ description: 'DEMA 通过双重平滑减少滞后,比传统 EMA 更贴近价格,适合快速趋势判断。',
157
+ params: [{ key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 14, description: '计算周期' }],
158
+ },
159
+ tema: {
160
+ name: '三指数移动平均',
161
+ description: 'TEMA 三重平滑,滞后最小,响应最快,适合敏锐捕捉价格变化。',
162
+ params: [{ key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 14, description: '计算周期' }],
163
+ },
164
+ hma: {
165
+ name: '赫尔移动平均',
166
+ description: 'HMA 通过加权移动平均和平方根计算,在平滑度和响应速度之间取得极佳平衡。',
167
+ params: [{ key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 14, description: '计算周期' }],
168
+ },
169
+ kama: {
170
+ name: '考夫曼自适应移动平均',
171
+ description: 'KAMA 根据市场噪音自适应调整平滑度,趋势强时快速跟随,震荡时平滑过滤。',
172
+ params: [
173
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 10, description: 'ER 计算周期' },
174
+ { key: 'fastPeriod', label: '快线', type: 'number', min: 2, max: 30, step: 1, default: 2, description: '最快平滑系数' },
175
+ { key: 'slowPeriod', label: '慢线', type: 'number', min: 2, max: 60, step: 1, default: 30, description: '最慢平滑系数' },
176
+ ],
177
+ },
178
+ sar: {
179
+ name: '抛物线转向',
180
+ description: 'SAR(Parabolic Stop and Reverse)通过抛物线轨迹跟踪趋势,提供动态止损和反转信号。',
181
+ params: [
182
+ { key: 'step', label: '步长', type: 'number', min: 0.001, max: 0.1, step: 0.001, default: 0.02, description: '加速度步长' },
183
+ { key: 'maxStep', label: '最大步长', type: 'number', min: 0.01, max: 0.5, step: 0.01, default: 0.2, description: '最大加速度上限' },
184
+ ],
185
+ },
186
+ supertrend: {
187
+ name: '超级趋势',
188
+ description: 'SuperTrend 基于 ATR 的通道突破系统,价格在通道上方为多头趋势,下方为空头趋势。',
189
+ params: [
190
+ { key: 'atrPeriod', label: 'ATR 周期', type: 'number', min: 2, max: 100, step: 1, default: 10, description: 'ATR 计算周期' },
191
+ { key: 'multiplier', label: '倍数', type: 'number', min: 0.5, max: 10, step: 0.5, default: 3, description: 'ATR 倍数' },
192
+ ],
193
+ },
194
+ keltner: {
195
+ name: '肯特纳通道',
196
+ description: 'Keltner Channel 以 EMA 为中轨,ATR 倍数确定通道宽度,适合判断突破与回归。',
197
+ params: [
198
+ { key: 'emaPeriod', label: 'EMA 周期', type: 'number', min: 2, max: 100, step: 1, default: 20, description: '中轨 EMA 周期' },
199
+ { key: 'atrPeriod', label: 'ATR 周期', type: 'number', min: 2, max: 100, step: 1, default: 10, description: 'ATR 计算周期' },
200
+ { key: 'multiplier', label: '倍数', type: 'number', min: 0.5, max: 10, step: 0.5, default: 2, description: 'ATR 倍数' },
201
+ ],
202
+ },
203
+ donchian: {
204
+ name: '唐奇安通道',
205
+ description: 'Donchian Channel 显示 N 日内最高价和最低价的通道,价格突破上下轨视为趋势信号。',
206
+ params: [{ key: 'period', label: '周期', type: 'number', min: 2, max: 200, step: 1, default: 20, description: '通道周期' }],
207
+ },
208
+ ichimoku: {
209
+ name: '一目均衡表',
210
+ description: 'Ichimoku Kinko Hyo(一目均衡表)综合显示支撑阻力、趋势方向和动量,云区变色提示趋势转换。',
211
+ params: [
212
+ { key: 'tenkanPeriod', label: '转折线', type: 'number', min: 2, max: 100, step: 1, default: 9, description: 'Tenkan-sen 周期' },
213
+ { key: 'kijunPeriod', label: '基准线', type: 'number', min: 2, max: 100, step: 1, default: 26, description: 'Kijun-sen 周期' },
214
+ { key: 'spanBPeriod', label: '领先线 B', type: 'number', min: 2, max: 200, step: 1, default: 52, description: 'Senkou Span B 周期' },
215
+ { key: 'displacement', label: '偏移', type: 'number', min: 1, max: 52, step: 1, default: 26, description: '偏移量' },
216
+ ],
217
+ },
218
+ roc: {
219
+ name: '变化率',
220
+ description: 'ROC 衡量当前价格与 N 日前价格的百分比变化,正值表示上涨动能,负值表示下跌动能。',
221
+ params: [{ key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 12, description: 'ROC 周期' }],
222
+ },
223
+ trix: {
224
+ name: '三重指数平滑平均',
225
+ description: 'TRIX 三平滑后取 ROC,过滤短期波动,上穿信号线为买入信号,下穿为卖出信号。',
226
+ params: [
227
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 15, description: 'TRIX 周期' },
228
+ { key: 'signalPeriod', label: '信号', type: 'number', min: 2, max: 50, step: 1, default: 9, description: '信号线周期' },
229
+ ],
230
+ },
231
+ hv: {
232
+ name: '历史波动率',
233
+ description: 'HV 衡量过去 N 日对数收益率的年化标准差,值越高表示市场波动越大。',
234
+ params: [
235
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 20, description: '计算周期' },
236
+ { key: 'annualizationFactor', label: '年化因子', type: 'number', min: 1, max: 365, step: 1, default: 252, description: '年化天数' },
237
+ ],
238
+ },
239
+ parkinson: {
240
+ name: '帕金森波动率',
241
+ description: 'Parkinson 利用最高最低价估算波动率,比 HV 更高效,捕捉日内波动范围。',
242
+ params: [
243
+ { key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 20, description: '计算周期' },
244
+ { key: 'annualizationFactor', label: '年化因子', type: 'number', min: 1, max: 365, step: 1, default: 252, description: '年化天数' },
245
+ ],
246
+ },
247
+ chaikin_vol: {
248
+ name: '蔡金波动率',
249
+ description: 'Chaikin Volatility 衡量价格区间的宽度变化,波动率扩张预示突破,收缩预示盘整。',
250
+ params: [
251
+ { key: 'emaPeriod', label: 'EMA 周期', type: 'number', min: 2, max: 100, step: 1, default: 10, description: '平滑周期' },
252
+ { key: 'rocPeriod', label: 'ROC 周期', type: 'number', min: 2, max: 100, step: 1, default: 10, description: '变化率周期' },
253
+ ],
254
+ },
255
+ vma: {
256
+ name: '成交量移动平均',
257
+ description: 'VMA 对成交量做移动平均,量能上升确认趋势,量能萎缩预示反转。',
258
+ params: [{ key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 5, description: 'VMA 周期' }],
259
+ },
260
+ obv: {
261
+ name: '能量潮',
262
+ description: 'OBV 累积量能,价格上涨时加量,下跌时减量。OBV 趋势与价格背离常预示反转。',
263
+ },
264
+ pvt: {
265
+ name: '价量趋势',
266
+ description: 'PVT 将成交量按价格变化率加权累计,比 OBV 更精细,反映资金流入流出力度。',
267
+ },
268
+ vwap: {
269
+ name: '成交量加权均价',
270
+ description: 'VWAP(Volume-Weighted Average Price)以成交量加权的均价线,机构常用作日内交易基准。',
271
+ params: [{ key: 'sessionResetGapMs', label: '重置间隔', type: 'number', min: 0, max: 86400000, step: 3600000, default: 0, description: '0=不重置,>0=超过间隔毫秒重置' }],
272
+ },
273
+ cmf: {
274
+ name: '蔡金资金流',
275
+ description: 'CMF(Chaikin Money Flow)衡量资金流入流出强度,正值看涨,负值看跌。',
276
+ params: [{ key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 20, description: 'CMF 周期' }],
277
+ },
278
+ mfi: {
279
+ name: '资金流量指数',
280
+ description: 'MFI(Money Flow Index)类似 RSI 但用量能加权,判断超买超卖。MFI > 80 超买,MFI < 20 超卖。',
281
+ params: [{ key: 'period', label: '周期', type: 'number', min: 2, max: 100, step: 1, default: 14, description: 'MFI 周期' }],
282
+ },
283
+ pivot: {
284
+ name: '枢轴点',
285
+ description: 'Pivot Points 根据前日最高/最低/收盘计算支撑阻力位,R1-R3 为阻力,S1-S3 为支撑。',
286
+ },
287
+ fib: {
288
+ name: '斐波那契',
289
+ description: 'Fibonacci 回调线标记关键回撤位(0.236/0.382/0.5/0.618/0.786),用于判断回调目标。',
290
+ params: [{ key: 'period', label: '周期', type: 'number', min: 5, max: 500, step: 1, default: 50, description: '计算周期' }],
291
+ },
292
+ structure: {
293
+ name: 'SMC 结构',
294
+ description: 'Swing / BOS / CHOCH 识别市场结构转换,突破结构(BOS)确认趋势,趋势反转(CHOCH)提示反转。',
295
+ params: [
296
+ { key: 'leftWindow', label: '左窗口', type: 'number', min: 1, max: 20, step: 1, default: 2, description: '摆点左侧 K 线数' },
297
+ { key: 'rightWindow', label: '右窗口', type: 'number', min: 1, max: 20, step: 1, default: 2, description: '摆点右侧 K 线数' },
298
+ ],
299
+ },
300
+ zones: {
301
+ name: 'SMC 区域',
302
+ description: 'FVG(公允价值缺口)和 Order Block(订单块)识别机构交易行为中的关键流动性区域。',
303
+ params: [
304
+ { key: 'obLookback', label: 'OB 回溯', type: 'number', min: 1, max: 50, step: 1, default: 5, description: 'Order Block 回溯窗口' },
305
+ ],
306
+ },
307
+ volume_profile: {
308
+ name: '成交量分布',
309
+ description: 'Volume Profile 显示各价位成交量分布,识别高量区域(价值区)和低量区域(缺口),POC 为成交量最大价位。',
310
+ params: [
311
+ { key: 'bins', label: '分箱数', type: 'number', min: 5, max: 100, step: 1, default: 24, description: '价格分箱数' },
312
+ { key: 'lookback', label: '回溯', type: 'number', min: 0, max: 500, step: 1, default: 0, description: '0=全部数据' },
313
+ { key: 'valueAreaPercent', label: '价值区%', type: 'number', min: 0.1, max: 1, step: 0.05, default: 0.7, description: '价值区占比' },
314
+ ],
315
+ },
316
+ }
317
+
318
+ function buildAllIndicators(): Indicator[] {
319
+ return getRegisteredIndicatorDefinitions().map((def) => {
320
+ const key = normalizeId(def.name)
321
+ const ui = uiMeta[key]
322
+ return {
323
+ id: def.displayName.toUpperCase(),
324
+ label: def.displayName,
325
+ name: ui?.name ?? def.displayName,
326
+ pane: def.category === 'main' || def.allowMainPane ? 'main' : 'sub',
327
+ description: ui?.description,
328
+ params: ui?.params,
329
+ }
330
+ })
331
+ }
332
+
333
+ export const allIndicators: Indicator[] = buildAllIndicators()
334
+
335
+ export const mainIndicators = allIndicators.filter((i) => i.pane === 'main')
336
+ export const subIndicators = allIndicators.filter((i) => i.pane === 'sub')
337
+
338
+ export function findIndicator(id: string): Indicator | undefined {
339
+ const norm = normalizeId(id)
340
+ return allIndicators.find((i) => normalizeId(i.id) === norm || normalizeId(i.label) === norm)
341
+ }
342
+
343
+ export function isSubIndicatorId(id: string): boolean {
344
+ const indicator = findIndicator(id)
345
+ return indicator?.pane === 'sub'
346
+ }
@@ -142,7 +142,7 @@ export function getStructureTitleInfo(
142
142
  @Indicator({
143
143
  name: 'structure',
144
144
  displayName: 'Structure',
145
- category: 'sub',
145
+ category: 'main',
146
146
  defaultPaneId: 'sub_Structure',
147
147
  allowMainPane: true,
148
148
  mainPane: { rendererName: 'structure_main', toActiveConfig: (params, active) => ({ ...params, showSwingLabels: active, showBOS: active, showCHOCH: active }) },
@@ -126,7 +126,7 @@ export function getSuperTrendTitleInfo(
126
126
  name: 'supertrend',
127
127
  displayName: 'SuperTrend',
128
128
  getTitleInfo: getSuperTrendTitleInfo,
129
- category: 'oscillator',
129
+ category: 'main',
130
130
  defaultPaneId: 'sub_SuperTrend',
131
131
  allowMainPane: true,
132
132
  mainPane: { rendererName: 'supertrend_main', toActiveConfig: (params, active) => ({ ...params, showSuperTrend: active }) },