@363045841yyt/klinechart-core 0.8.2 → 0.8.3

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 (88) hide show
  1. package/dist/controllers/createChartController.d.ts.map +1 -1
  2. package/dist/controllers/createChartController.js +31 -0
  3. package/dist/controllers/createChartController.js.map +1 -1
  4. package/dist/controllers/types.d.ts +16 -0
  5. package/dist/controllers/types.d.ts.map +1 -1
  6. package/dist/data-fetchers/baostock.d.ts +9 -2
  7. package/dist/data-fetchers/baostock.d.ts.map +1 -1
  8. package/dist/data-fetchers/baostock.js +78 -9
  9. package/dist/data-fetchers/baostock.js.map +1 -1
  10. package/dist/data-fetchers/fetcherDefinitionRegistry.d.ts +13 -0
  11. package/dist/data-fetchers/fetcherDefinitionRegistry.d.ts.map +1 -0
  12. package/dist/data-fetchers/fetcherDefinitionRegistry.js +36 -0
  13. package/dist/data-fetchers/fetcherDefinitionRegistry.js.map +1 -0
  14. package/dist/data-fetchers/gotdx.d.ts +9 -2
  15. package/dist/data-fetchers/gotdx.d.ts.map +1 -1
  16. package/dist/data-fetchers/gotdx.js +72 -5
  17. package/dist/data-fetchers/gotdx.js.map +1 -1
  18. package/dist/data-fetchers/hundred-mock.d.ts +9 -2
  19. package/dist/data-fetchers/hundred-mock.d.ts.map +1 -1
  20. package/dist/data-fetchers/hundred-mock.js +66 -4
  21. package/dist/data-fetchers/hundred-mock.js.map +1 -1
  22. package/dist/data-fetchers/index.d.ts +7 -5
  23. package/dist/data-fetchers/index.d.ts.map +1 -1
  24. package/dist/data-fetchers/index.js +6 -5
  25. package/dist/data-fetchers/index.js.map +1 -1
  26. package/dist/data-fetchers/router.d.ts.map +1 -1
  27. package/dist/data-fetchers/router.js +14 -18
  28. package/dist/data-fetchers/router.js.map +1 -1
  29. package/dist/data-fetchers/thousand-mock.d.ts +9 -2
  30. package/dist/data-fetchers/thousand-mock.d.ts.map +1 -1
  31. package/dist/data-fetchers/thousand-mock.js +66 -4
  32. package/dist/data-fetchers/thousand-mock.js.map +1 -1
  33. package/dist/data-fetchers/tradingview.d.ts +9 -2
  34. package/dist/data-fetchers/tradingview.d.ts.map +1 -1
  35. package/dist/data-fetchers/tradingview.js +73 -9
  36. package/dist/data-fetchers/tradingview.js.map +1 -1
  37. package/dist/data-fetchers/types.d.ts +21 -0
  38. package/dist/data-fetchers/types.d.ts.map +1 -0
  39. package/dist/data-fetchers/types.js +2 -0
  40. package/dist/data-fetchers/types.js.map +1 -0
  41. package/dist/engine/renderers/Indicator/ichimoku.d.ts.map +1 -1
  42. package/dist/engine/renderers/Indicator/ichimoku.js +8 -5
  43. package/dist/engine/renderers/Indicator/ichimoku.js.map +1 -1
  44. package/dist/engine/renderers/Indicator/sar.d.ts.map +1 -1
  45. package/dist/engine/renderers/Indicator/sar.js +3 -3
  46. package/dist/engine/renderers/Indicator/sar.js.map +1 -1
  47. package/dist/engine/renderers/Indicator/supertrend.d.ts.map +1 -1
  48. package/dist/engine/renderers/Indicator/supertrend.js +3 -3
  49. package/dist/engine/renderers/Indicator/supertrend.js.map +1 -1
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +1 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/mcp/chartBridge.d.ts +47 -0
  55. package/dist/mcp/chartBridge.d.ts.map +1 -0
  56. package/dist/mcp/chartBridge.js +167 -0
  57. package/dist/mcp/chartBridge.js.map +1 -0
  58. package/dist/mcp/index.d.ts +3 -0
  59. package/dist/mcp/index.d.ts.map +1 -0
  60. package/dist/mcp/index.js +2 -0
  61. package/dist/mcp/index.js.map +1 -0
  62. package/dist/mcp/types.d.ts +17 -0
  63. package/dist/mcp/types.d.ts.map +1 -0
  64. package/dist/mcp/types.js +2 -0
  65. package/dist/mcp/types.js.map +1 -0
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/package.json +1 -1
  69. package/src/controllers/createChartController.ts +34 -0
  70. package/src/controllers/types.ts +9 -0
  71. package/src/data-fetchers/__tests__/fetcherRegistry.test.ts +192 -0
  72. package/src/data-fetchers/baostock.ts +54 -22
  73. package/src/data-fetchers/fetcherDefinitionRegistry.ts +50 -0
  74. package/src/data-fetchers/gotdx.ts +28 -6
  75. package/src/data-fetchers/hundred-mock.ts +21 -4
  76. package/src/data-fetchers/index.ts +19 -5
  77. package/src/data-fetchers/router.ts +27 -18
  78. package/src/data-fetchers/thousand-mock.ts +21 -4
  79. package/src/data-fetchers/tradingview.ts +30 -11
  80. package/src/data-fetchers/types.ts +27 -0
  81. package/src/engine/renderers/Indicator/ichimoku.ts +10 -4
  82. package/src/engine/renderers/Indicator/sar.ts +3 -3
  83. package/src/engine/renderers/Indicator/supertrend.ts +3 -4
  84. package/src/index.ts +1 -0
  85. package/src/mcp/chartBridge.ts +220 -0
  86. package/src/mcp/index.ts +2 -0
  87. package/src/mcp/types.ts +19 -0
  88. package/src/version.ts +1 -1
@@ -1,5 +1,6 @@
1
1
  import type { RendererPluginWithHost, RenderContext, PluginHost } from '../../../plugin'
2
2
  import { RENDERER_PRIORITY } from '../../../plugin'
3
+ import { resolveThemeColors } from '../../../tokens'
3
4
  import type { KLineData } from '../../../types/price'
4
5
  import type { IchimokuRenderState } from '../../indicators/ichimokuState'
5
6
  import { createIchimokuStateKey, EMPTY_ICHIMOKU_STATE } from '../../indicators/ichimokuState'
@@ -14,8 +15,6 @@ const KIJUN_COLOR = '#2563eb'
14
15
  const SPAN_A_COLOR = '#16a34a'
15
16
  const SPAN_B_COLOR = '#dc2626'
16
17
  const CHIKOU_COLOR = '#7c3aed'
17
- const CLOUD_BULL = 'rgba(34, 197, 94, 0.15)'
18
- const CLOUD_BEAR = 'rgba(239, 68, 68, 0.15)'
19
18
 
20
19
  type Point = { x: number; y: number }
21
20
 
@@ -58,6 +57,7 @@ export function createIchimokuRendererPlugin(options: IchimokuRendererOptions =
58
57
 
59
58
  draw(context: RenderContext) {
60
59
  const { ctx, pane, range, scrollLeft, kLineCenters, lineWebGLSurface } = context
60
+ const colors = resolveThemeColors(context.theme, context.isAsiaMarket, context.colorPresetSettings)
61
61
  const stateKey = resolveKey()
62
62
  if (!stateKey) return
63
63
  const state = pluginHost?.getSharedState<IchimokuRenderState>(stateKey)
@@ -94,7 +94,7 @@ export function createIchimokuRendererPlugin(options: IchimokuRendererOptions =
94
94
  if (params.showCloud && cloudSegs.length >= 2) {
95
95
  ctx.save()
96
96
  ctx.translate(-scrollLeft, 0)
97
- fillCloud(ctx, cloudSegs)
97
+ fillCloud(ctx, cloudSegs, colors.candleUpBody, colors.candleDownBody)
98
98
  ctx.restore()
99
99
  }
100
100
 
@@ -153,11 +153,16 @@ function drawLine(ctx: CanvasRenderingContext2D, pts: Point[], color: string): v
153
153
  function fillCloud(
154
154
  ctx: CanvasRenderingContext2D,
155
155
  segs: { x: number; ya: number; yb: number; bull: boolean }[],
156
+ bullColor: string,
157
+ bearColor: string,
158
+ alpha = 0.15,
156
159
  ): void {
160
+ ctx.save()
161
+ ctx.globalAlpha = alpha
157
162
  for (let i = 0; i < segs.length - 1; i++) {
158
163
  const a = segs[i]!
159
164
  const b = segs[i + 1]!
160
- ctx.fillStyle = a.bull ? CLOUD_BULL : CLOUD_BEAR
165
+ ctx.fillStyle = a.bull ? bullColor : bearColor
161
166
  ctx.beginPath()
162
167
  ctx.moveTo(a.x, a.ya)
163
168
  ctx.lineTo(b.x, b.ya)
@@ -166,6 +171,7 @@ function fillCloud(
166
171
  ctx.closePath()
167
172
  ctx.fill()
168
173
  }
174
+ ctx.restore()
169
175
  }
170
176
 
171
177
  export function getIchimokuTitleInfo(
@@ -1,5 +1,6 @@
1
1
  import type { RendererPluginWithHost, RenderContext, PluginHost } from '../../../plugin'
2
2
  import { RENDERER_PRIORITY } from '../../../plugin'
3
+ import { resolveThemeColors } from '../../../tokens'
3
4
  import type { KLineData } from '../../../types/price'
4
5
  import type { SARRenderState } from '../../indicators/sarState'
5
6
  import { createSARStateKey, EMPTY_SAR_STATE } from '../../indicators/sarState'
@@ -9,8 +10,6 @@ import type { IndicatorScheduler, SARSchedulerConfig } from '../../indicators/sc
9
10
  import { calcSARData } from '../../indicators/calculators'
10
11
  import { createValuePointVisibleStateComposer } from '../../indicators/visibleStateComposers'
11
12
 
12
- const SAR_UP_COLOR = '#22c55e'
13
- const SAR_DOWN_COLOR = '#ef4444'
14
13
  const DOT_RADIUS = 1.5
15
14
  const TAU = Math.PI * 2
16
15
 
@@ -59,6 +58,7 @@ export function createSARRendererPlugin(options: SARRendererOptions = {}): Rende
59
58
 
60
59
  draw(context: RenderContext) {
61
60
  const { ctx, pane, range, scrollLeft, kLineCenters } = context
61
+ const colors = resolveThemeColors(context.theme, context.isAsiaMarket, context.colorPresetSettings)
62
62
 
63
63
  const stateKey = resolveKey()
64
64
  if (!stateKey) return
@@ -77,7 +77,7 @@ export function createSARRendererPlugin(options: SARRendererOptions = {}): Rende
77
77
  const centerX = kLineCenters[i - range.start]
78
78
  if (centerX === undefined) continue
79
79
  const y = pane.yAxis.priceToY(point.value)
80
- ctx.fillStyle = point.trend === 'up' ? SAR_UP_COLOR : SAR_DOWN_COLOR
80
+ ctx.fillStyle = point.trend === 'up' ? colors.candleUpBody : colors.candleDownBody
81
81
  ctx.beginPath()
82
82
  ctx.arc(centerX, y, DOT_RADIUS, 0, TAU)
83
83
  ctx.fill()
@@ -1,5 +1,6 @@
1
1
  import type { RendererPluginWithHost, RenderContext, PluginHost } from '../../../plugin'
2
2
  import { RENDERER_PRIORITY } from '../../../plugin'
3
+ import { resolveThemeColors } from '../../../tokens'
3
4
  import type { KLineData } from '../../../types/price'
4
5
  import type { SuperTrendRenderState } from '../../indicators/supertrendState'
5
6
  import { createSuperTrendStateKey, EMPTY_SUPERTREND_STATE } from '../../indicators/supertrendState'
@@ -9,9 +10,6 @@ import type { IndicatorScheduler, SuperTrendSchedulerConfig } from '../../indica
9
10
  import { calcSuperTrendData } from '../../indicators/calculators'
10
11
  import { createValuePointVisibleStateComposer } from '../../indicators/visibleStateComposers'
11
12
 
12
- const ST_UP_COLOR = '#22c55e'
13
- const ST_DOWN_COLOR = '#ef4444'
14
-
15
13
  export interface SuperTrendRendererOptions {
16
14
  paneId?: string
17
15
  }
@@ -51,6 +49,7 @@ export function createSuperTrendRendererPlugin(options: SuperTrendRendererOption
51
49
 
52
50
  draw(context: RenderContext) {
53
51
  const { ctx, pane, range, scrollLeft, kLineCenters } = context
52
+ const colors = resolveThemeColors(context.theme, context.isAsiaMarket, context.colorPresetSettings)
54
53
  const stateKey = resolveKey()
55
54
  if (!stateKey) return
56
55
  const state = pluginHost?.getSharedState<SuperTrendRenderState>(stateKey)
@@ -77,7 +76,7 @@ export function createSuperTrendRendererPlugin(options: SuperTrendRendererOption
77
76
  const y = pane.yAxis.priceToY(point.value)
78
77
 
79
78
  if (prevX !== null && prevTrend === point.trend) {
80
- ctx.strokeStyle = point.trend === 'up' ? ST_UP_COLOR : ST_DOWN_COLOR
79
+ ctx.strokeStyle = point.trend === 'up' ? colors.candleUpBody : colors.candleDownBody
81
80
  ctx.beginPath()
82
81
  ctx.moveTo(prevX, prevY!)
83
82
  ctx.lineTo(centerX, y)
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './reactivity'
2
2
  export * from './controllers'
3
+ export * from './mcp'
3
4
  export { VERSION } from './version'
4
5
  export * from './tokens'
5
6
  export { formatTimestamp } from './utils/dateFormat'
@@ -0,0 +1,220 @@
1
+ import type { ToolCall, ToolResult, ControllerDescription, ToolCallHandler } from './types'
2
+
3
+ export interface ChartBridgeOptions {
4
+ wsUrl: string
5
+ onToolCall: ToolCallHandler
6
+ sessionId?: string
7
+ autoReconnect?: boolean
8
+ reconnectDelay?: number
9
+ heartbeatInterval?: number
10
+ wsImpl?: new (url: string) => WebSocket
11
+ }
12
+
13
+ export type ChartBridgeEvent =
14
+ | 'connected'
15
+ | 'disconnected'
16
+ | 'error'
17
+ | 'stateChanged'
18
+
19
+ type MessageHandler = (...args: unknown[]) => void
20
+
21
+ export class ChartBridge {
22
+ readonly sessionId: string
23
+ private readonly autoReconnect: boolean
24
+ private readonly reconnectDelay: number
25
+ private readonly heartbeatInterval: number
26
+ private readonly onToolCall: ToolCallHandler
27
+
28
+ private readonly wsImpl: new (url: string) => WebSocket
29
+ private ws: WebSocket | null = null
30
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null
31
+ private heartbeatTimer: ReturnType<typeof setInterval> | null = null
32
+ private destroyed = false
33
+
34
+ private listeners = new Map<ChartBridgeEvent, Set<MessageHandler>>()
35
+
36
+ onConnected?: () => void
37
+ onDisconnected?: () => void
38
+ onError?: (err: Error) => void
39
+ onStateChange?: () => void
40
+
41
+ constructor(options: ChartBridgeOptions) {
42
+ this.sessionId = options.sessionId ?? crypto.randomUUID()
43
+ this.autoReconnect = options.autoReconnect ?? true
44
+ this.reconnectDelay = options.reconnectDelay ?? 3000
45
+ this.heartbeatInterval = options.heartbeatInterval ?? 30_000
46
+ this.onToolCall = options.onToolCall
47
+ this.wsImpl = options.wsImpl ?? WebSocket
48
+ this.wsUrl = options.wsUrl
49
+ }
50
+
51
+ private wsUrl: string
52
+
53
+ async connect(): Promise<void> {
54
+ if (this.destroyed) return
55
+ this.disconnect()
56
+
57
+ return new Promise((resolve, reject) => {
58
+ try {
59
+ const ws = new this.wsImpl(this.wsUrl)
60
+
61
+ ws.onopen = () => {
62
+ this.ws = ws
63
+ console.info(
64
+ `[ChartBridge] WS opened → sending register (sessionId=${this.sessionId})`,
65
+ )
66
+ ws.send(JSON.stringify({ type: 'register', sessionId: this.sessionId }))
67
+ this.startHeartbeat()
68
+ this.onConnected?.()
69
+ this.emit('connected')
70
+ resolve()
71
+ }
72
+
73
+ ws.onmessage = (event: MessageEvent) => {
74
+ let msg: Record<string, unknown>
75
+ try {
76
+ msg = JSON.parse(event.data as string)
77
+ } catch {
78
+ return
79
+ }
80
+ this.handleMessage(msg)
81
+ }
82
+
83
+ ws.onclose = () => {
84
+ console.warn(
85
+ `[ChartBridge] WS closed, autoReconnect=${this.autoReconnect}`,
86
+ )
87
+ this.ws = null
88
+ this.stopHeartbeat()
89
+ this.onDisconnected?.()
90
+ this.emit('disconnected')
91
+ if (this.autoReconnect && !this.destroyed) {
92
+ this.scheduleReconnect()
93
+ }
94
+ }
95
+
96
+ ws.onerror = () => {
97
+ console.error(`[ChartBridge] WS error — connection failed`)
98
+ const err = new Error('WebSocket connection failed')
99
+ this.onError?.(err)
100
+ this.emit('error', err)
101
+ reject(err)
102
+ }
103
+ } catch (err) {
104
+ reject(err)
105
+ }
106
+ })
107
+ }
108
+
109
+ disconnect(): void {
110
+ this.ws?.close()
111
+ this.ws = null
112
+ this.cancelReconnect()
113
+ this.stopHeartbeat()
114
+ }
115
+
116
+ destroy(): void {
117
+ this.destroyed = true
118
+ this.disconnect()
119
+ this.listeners.clear()
120
+ }
121
+
122
+ private handleMessage(msg: Record<string, unknown>): void {
123
+ switch (msg.type) {
124
+ case 'registered':
125
+ break
126
+
127
+ case 'tool:call': {
128
+ const call = msg.call as ToolCall
129
+ const requestId = msg.requestId as string
130
+ this.dispatchToolCall(requestId, call)
131
+ break
132
+ }
133
+
134
+ case 'ping': {
135
+ this.ws?.send(JSON.stringify({ type: 'pong' }))
136
+ break
137
+ }
138
+ }
139
+ }
140
+
141
+ private async dispatchToolCall(requestId: string, call: ToolCall): Promise<void> {
142
+ const result = await this.onToolCall(call)
143
+ this.sendResult(requestId, result)
144
+
145
+ if (this.onStateChange) {
146
+ this.onStateChange()
147
+ }
148
+ this.emit('stateChanged')
149
+ }
150
+
151
+ private sendResult(requestId: string, result: ToolResult): void {
152
+ this.ws?.send(
153
+ JSON.stringify({
154
+ type: 'tool:result',
155
+ requestId,
156
+ result,
157
+ }),
158
+ )
159
+ }
160
+
161
+ sendStateUpdate(
162
+ descriptions: Record<string, ControllerDescription>,
163
+ ): void {
164
+ this.ws?.send(
165
+ JSON.stringify({
166
+ type: 'state:update',
167
+ sessionId: this.sessionId,
168
+ descriptions,
169
+ }),
170
+ )
171
+ }
172
+
173
+ private startHeartbeat(): void {
174
+ this.stopHeartbeat()
175
+ this.heartbeatTimer = setInterval(() => {
176
+ if (this.ws?.readyState === 1) {
177
+ this.ws.send(JSON.stringify({ type: 'ping' }))
178
+ }
179
+ }, this.heartbeatInterval)
180
+ }
181
+
182
+ private stopHeartbeat(): void {
183
+ if (this.heartbeatTimer !== null) {
184
+ clearInterval(this.heartbeatTimer)
185
+ this.heartbeatTimer = null
186
+ }
187
+ }
188
+
189
+ private scheduleReconnect(): void {
190
+ this.cancelReconnect()
191
+ this.reconnectTimer = setTimeout(() => {
192
+ if (!this.destroyed) {
193
+ this.connect()
194
+ }
195
+ }, this.reconnectDelay)
196
+ }
197
+
198
+ private cancelReconnect(): void {
199
+ if (this.reconnectTimer !== null) {
200
+ clearTimeout(this.reconnectTimer)
201
+ this.reconnectTimer = null
202
+ }
203
+ }
204
+
205
+ private emit(event: ChartBridgeEvent, ...args: unknown[]): void {
206
+ this.listeners.get(event)?.forEach((fn) => fn(...args))
207
+ }
208
+
209
+ on(event: ChartBridgeEvent, handler: MessageHandler): () => void {
210
+ if (!this.listeners.has(event)) {
211
+ this.listeners.set(event, new Set())
212
+ }
213
+ this.listeners.get(event)!.add(handler)
214
+ return () => this.listeners.get(event)?.delete(handler)
215
+ }
216
+
217
+ off(event: ChartBridgeEvent, handler: MessageHandler): void {
218
+ this.listeners.get(event)?.delete(handler)
219
+ }
220
+ }
@@ -0,0 +1,2 @@
1
+ export { ChartBridge, type ChartBridgeOptions, type ChartBridgeEvent } from './chartBridge'
2
+ export type { ToolCall, ToolResult, ControllerDescription, ToolCallHandler } from './types'
@@ -0,0 +1,19 @@
1
+ export interface ToolCall {
2
+ name: string
3
+ input: Record<string, unknown>
4
+ }
5
+
6
+ export interface ToolResult {
7
+ success: boolean
8
+ error?: string
9
+ data?: unknown
10
+ }
11
+
12
+ export type ToolCallHandler = (call: ToolCall) => ToolResult | Promise<ToolResult>
13
+
14
+ export interface ControllerDescription {
15
+ controllerId: string
16
+ summary: string
17
+ facts: Readonly<Record<string, string | number | boolean | null>>
18
+ warnings?: ReadonlyArray<string>
19
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.8.2"
1
+ export const VERSION = "0.8.3"