@363045841yyt/klinechart-core 0.8.5 → 0.8.6
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.
- package/dist/controllers/createChartController.js +1 -1
- package/dist/engine/controller/interaction.d.ts +6 -0
- package/dist/engine/controller/interaction.d.ts.map +1 -1
- package/dist/engine/controller/interaction.js +51 -8
- package/dist/engine/controller/interaction.js.map +1 -1
- package/dist/engine/indicators/calculators.d.ts.map +1 -1
- package/dist/engine/indicators/calculators.js +20 -3
- package/dist/engine/indicators/calculators.js.map +1 -1
- package/dist/engine/indicators/ichimokuState.d.ts +2 -0
- package/dist/engine/indicators/ichimokuState.d.ts.map +1 -1
- package/dist/engine/indicators/ichimokuState.js.map +1 -1
- package/dist/engine/indicators/visibleStateComposers.d.ts +14 -0
- package/dist/engine/indicators/visibleStateComposers.d.ts.map +1 -1
- package/dist/engine/indicators/visibleStateComposers.js +34 -0
- package/dist/engine/indicators/visibleStateComposers.js.map +1 -1
- package/dist/engine/renderers/Indicator/ichimoku.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/ichimoku.js +25 -3
- package/dist/engine/renderers/Indicator/ichimoku.js.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +92 -0
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
- package/dist/engine/renderers/crosshair.js +1 -1
- package/dist/engine/renderers/crosshair.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/chartBridge.d.ts.map +1 -1
- package/dist/mcp/chartBridge.js +2 -1
- package/dist/mcp/chartBridge.js.map +1 -1
- package/dist/tokens/theme-dark.js +1 -1
- package/dist/utils/uuid.d.ts +2 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +10 -0
- package/dist/utils/uuid.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/controllers/createChartController.ts +1 -1
- package/src/engine/controller/interaction.ts +56 -9
- package/src/engine/indicators/__tests__/ichimoku.test.ts +3 -3
- package/src/engine/indicators/calculators.ts +22 -3
- package/src/engine/indicators/ichimokuState.ts +2 -0
- package/src/engine/indicators/visibleStateComposers.ts +51 -0
- package/src/engine/renderers/Indicator/ichimoku.ts +23 -3
- package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +102 -0
- package/src/engine/renderers/crosshair.ts +1 -1
- package/src/index.ts +1 -0
- package/src/mcp/chartBridge.ts +2 -1
- package/src/tokens/__tests__/__snapshots__/baseline.test.ts.snap +1 -1
- package/src/tokens/theme-dark.ts +1 -1
- package/src/utils/uuid.ts +8 -0
- package/src/version.ts +1 -1
|
@@ -8,7 +8,8 @@ import { Indicator } from '../../indicators/indicatorDefinitionRegistry'
|
|
|
8
8
|
import { resolveStateKey, type TitleInfo, type TitleValueItem, type GetTitleInfoFn } from '../../indicators/indicatorMetadata'
|
|
9
9
|
import type { IndicatorScheduler, IchimokuSchedulerConfig } from '../../indicators/scheduler'
|
|
10
10
|
import { calcIchimokuData } from '../../indicators/calculators'
|
|
11
|
-
import {
|
|
11
|
+
import { createIchimokuVisibleStateComposer } from '../../indicators/visibleStateComposers'
|
|
12
|
+
import { getPhysicalKLineConfig } from '../../utils/klineConfig'
|
|
12
13
|
|
|
13
14
|
const TENKAN_COLOR = '#dc2626'
|
|
14
15
|
const KIJUN_COLOR = '#2563eb'
|
|
@@ -50,7 +51,7 @@ export function createIchimokuRendererPlugin(options: IchimokuRendererOptions =
|
|
|
50
51
|
description: '一目均衡表渲染器(WebGL 线 + Canvas2D 云图)',
|
|
51
52
|
debugName: 'Ichimoku',
|
|
52
53
|
paneId,
|
|
53
|
-
priority: RENDERER_PRIORITY.
|
|
54
|
+
priority: RENDERER_PRIORITY.INDICATOR,
|
|
54
55
|
|
|
55
56
|
onInstall(host: PluginHost) { pluginHost = host },
|
|
56
57
|
getDeclaredNamespaces() { const key = resolveKey(); return key ? [key] : [] },
|
|
@@ -90,6 +91,25 @@ export function createIchimokuRendererPlugin(options: IchimokuRendererOptions =
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
// 未来云:在数据末尾延伸 displacement 根 spanA/spanB 线及云段
|
|
95
|
+
const dataLen = (context.data as unknown[]).length
|
|
96
|
+
if (dataLen < series.length) {
|
|
97
|
+
const physConfig = getPhysicalKLineConfig(context.kWidth, context.kGap, context.dpr)
|
|
98
|
+
const futureEnd = Math.min(dataLen + params.displacement, series.length)
|
|
99
|
+
for (let i = dataLen; i < futureEnd; i++) {
|
|
100
|
+
const p = series[i]
|
|
101
|
+
if (!p) continue
|
|
102
|
+
const leftPx = physConfig.startXPx + i * physConfig.unitPx
|
|
103
|
+
const wickXPx = leftPx + (physConfig.kWidthPx - 1) / 2
|
|
104
|
+
const centerX = wickXPx / context.dpr
|
|
105
|
+
if (params.showSpanA && p.spanA !== undefined) spanAPts.push({ x: centerX, y: toY(p.spanA) })
|
|
106
|
+
if (params.showSpanB && p.spanB !== undefined) spanBPts.push({ x: centerX, y: toY(p.spanB) })
|
|
107
|
+
if (params.showCloud && p.spanA !== undefined && p.spanB !== undefined) {
|
|
108
|
+
cloudSegs.push({ x: centerX, ya: toY(p.spanA), yb: toY(p.spanB), bull: p.spanA > p.spanB })
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
93
113
|
// Cloud fill (Canvas2D only)
|
|
94
114
|
if (params.showCloud && cloudSegs.length >= 2) {
|
|
95
115
|
ctx.save()
|
|
@@ -209,7 +229,7 @@ export function getIchimokuTitleInfo(
|
|
|
209
229
|
allowMainPane: true,
|
|
210
230
|
mainPane: { rendererName: 'ichimoku_main', toActiveConfig: (params, active) => ({ ...params, showTenkan: active, showKijun: active, showSpanA: active, showSpanB: active, showChikou: active, showCloud: active }) },
|
|
211
231
|
scale: { indicatorKey: 'ichimoku', label: 'Ichimoku', decimals: 2 },
|
|
212
|
-
visibleState: { compose:
|
|
232
|
+
visibleState: { compose: createIchimokuVisibleStateComposer('ichimoku', EMPTY_ICHIMOKU_STATE, ['tenkan', 'kijun', 'spanA', 'spanB', 'chikou']) },
|
|
213
233
|
runtime: { defaultConfig:{tenkanPeriod:9,kijunPeriod:26,spanBPeriod:52,displacement:26,showTenkan:true,showKijun:true,showSpanA:true,showSpanB:true,showCloud:true,showChikou:true}, computeKey:'calcIchimokuData', compute:(data,c)=>calcIchimokuData(data,c.tenkanPeriod,c.kijunPeriod,c.spanBPeriod,c.displacement) },
|
|
214
234
|
})
|
|
215
235
|
class IchimokuDefinition {
|
|
@@ -81,6 +81,102 @@ export function createMainIndicatorLegendRendererPlugin(options: {
|
|
|
81
81
|
const targetIndex = crosshairIndex ?? Math.min(range.end - 1, klineData.length - 1)
|
|
82
82
|
const rows: Array<{ draw: (rowIndex: number) => void }> = []
|
|
83
83
|
|
|
84
|
+
if (typeof crosshairIndex === 'number') {
|
|
85
|
+
const k = klineData[targetIndex]
|
|
86
|
+
if (k) {
|
|
87
|
+
const isUp = k.close >= k.open
|
|
88
|
+
const volText = typeof k.volume === 'number' ? formatVolumeShort(k.volume) : null
|
|
89
|
+
const upColor = isUp ? colors.candleUpBody : colors.candleDownBody
|
|
90
|
+
|
|
91
|
+
if (context.paneWidth >= 400) {
|
|
92
|
+
rows.push({
|
|
93
|
+
draw: (rowIndex: number) => {
|
|
94
|
+
let x = legendX
|
|
95
|
+
const y = config.yPaddingPx / 2 + legendYOffset + rowIndex * lineHeight
|
|
96
|
+
|
|
97
|
+
overlayCtx.fillStyle = colors.text.primary
|
|
98
|
+
overlayCtx.fillText('O ', x, y)
|
|
99
|
+
x += measureTextWidth(overlayCtx, 'O ')
|
|
100
|
+
overlayCtx.fillStyle = upColor
|
|
101
|
+
overlayCtx.fillText(k.open.toFixed(2), x, y)
|
|
102
|
+
x += measureTextWidth(overlayCtx, k.open.toFixed(2)) + gap
|
|
103
|
+
|
|
104
|
+
overlayCtx.fillStyle = colors.text.primary
|
|
105
|
+
overlayCtx.fillText('H ', x, y)
|
|
106
|
+
x += measureTextWidth(overlayCtx, 'H ')
|
|
107
|
+
overlayCtx.fillText(k.high.toFixed(2), x, y)
|
|
108
|
+
x += measureTextWidth(overlayCtx, k.high.toFixed(2)) + gap
|
|
109
|
+
|
|
110
|
+
overlayCtx.fillText('L ', x, y)
|
|
111
|
+
x += measureTextWidth(overlayCtx, 'L ')
|
|
112
|
+
overlayCtx.fillText(k.low.toFixed(2), x, y)
|
|
113
|
+
x += measureTextWidth(overlayCtx, k.low.toFixed(2)) + gap
|
|
114
|
+
|
|
115
|
+
overlayCtx.fillStyle = colors.text.primary
|
|
116
|
+
overlayCtx.fillText('C ', x, y)
|
|
117
|
+
x += measureTextWidth(overlayCtx, 'C ')
|
|
118
|
+
overlayCtx.fillStyle = upColor
|
|
119
|
+
overlayCtx.fillText(k.close.toFixed(2), x, y)
|
|
120
|
+
x += measureTextWidth(overlayCtx, k.close.toFixed(2)) + gap
|
|
121
|
+
|
|
122
|
+
if (volText) {
|
|
123
|
+
overlayCtx.fillStyle = colors.text.tertiary
|
|
124
|
+
overlayCtx.fillText('Vol ', x, y)
|
|
125
|
+
x += measureTextWidth(overlayCtx, 'Vol ')
|
|
126
|
+
overlayCtx.fillStyle = colors.text.primary
|
|
127
|
+
overlayCtx.fillText(volText, x, y)
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
} else {
|
|
132
|
+
rows.push({
|
|
133
|
+
draw: (rowIndex: number) => {
|
|
134
|
+
let x = legendX
|
|
135
|
+
const y = config.yPaddingPx / 2 + legendYOffset + rowIndex * lineHeight
|
|
136
|
+
|
|
137
|
+
overlayCtx.fillStyle = colors.text.primary
|
|
138
|
+
overlayCtx.fillText('O ', x, y)
|
|
139
|
+
x += measureTextWidth(overlayCtx, 'O ')
|
|
140
|
+
overlayCtx.fillStyle = upColor
|
|
141
|
+
overlayCtx.fillText(k.open.toFixed(2), x, y)
|
|
142
|
+
x += measureTextWidth(overlayCtx, k.open.toFixed(2)) + gap
|
|
143
|
+
|
|
144
|
+
overlayCtx.fillStyle = colors.text.primary
|
|
145
|
+
overlayCtx.fillText('H ', x, y)
|
|
146
|
+
x += measureTextWidth(overlayCtx, 'H ')
|
|
147
|
+
overlayCtx.fillText(k.high.toFixed(2), x, y)
|
|
148
|
+
x += measureTextWidth(overlayCtx, k.high.toFixed(2)) + gap
|
|
149
|
+
|
|
150
|
+
overlayCtx.fillText('L ', x, y)
|
|
151
|
+
x += measureTextWidth(overlayCtx, 'L ')
|
|
152
|
+
overlayCtx.fillText(k.low.toFixed(2), x, y)
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
rows.push({
|
|
156
|
+
draw: (rowIndex: number) => {
|
|
157
|
+
let x = legendX
|
|
158
|
+
const y = config.yPaddingPx / 2 + legendYOffset + rowIndex * lineHeight
|
|
159
|
+
|
|
160
|
+
overlayCtx.fillStyle = colors.text.primary
|
|
161
|
+
overlayCtx.fillText('C ', x, y)
|
|
162
|
+
x += measureTextWidth(overlayCtx, 'C ')
|
|
163
|
+
overlayCtx.fillStyle = upColor
|
|
164
|
+
overlayCtx.fillText(k.close.toFixed(2), x, y)
|
|
165
|
+
x += measureTextWidth(overlayCtx, k.close.toFixed(2)) + gap
|
|
166
|
+
|
|
167
|
+
if (volText) {
|
|
168
|
+
overlayCtx.fillStyle = colors.text.tertiary
|
|
169
|
+
overlayCtx.fillText('Vol ', x, y)
|
|
170
|
+
x += measureTextWidth(overlayCtx, 'Vol ')
|
|
171
|
+
overlayCtx.fillStyle = colors.text.primary
|
|
172
|
+
overlayCtx.fillText(volText, x, y)
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
84
180
|
const scheduler = pluginHost && typeof pluginHost.getService === 'function'
|
|
85
181
|
? pluginHost.getService<IndicatorScheduler>('indicatorScheduler')
|
|
86
182
|
: undefined
|
|
@@ -237,3 +333,9 @@ function findBaselineByTimestamp(data: ReadonlyArray<KLineData>, timestamp: numb
|
|
|
237
333
|
}
|
|
238
334
|
return null
|
|
239
335
|
}
|
|
336
|
+
|
|
337
|
+
function formatVolumeShort(v: number): string {
|
|
338
|
+
if (v >= 1e8) return (v / 1e8).toFixed(2) + '亿'
|
|
339
|
+
if (v >= 1e4) return (v / 1e4).toFixed(2) + '万'
|
|
340
|
+
return v.toFixed(2)
|
|
341
|
+
}
|
|
@@ -30,7 +30,7 @@ export function createCrosshairRendererPlugin(options: {
|
|
|
30
30
|
const colors = resolveThemeColors(context.theme, context.isAsiaMarket, context.colorPresetSettings)
|
|
31
31
|
const state = options.getCrosshairState()
|
|
32
32
|
|
|
33
|
-
if (
|
|
33
|
+
if (!state.pos) return
|
|
34
34
|
|
|
35
35
|
const { x } = state.pos
|
|
36
36
|
const isActive = pane.id === state.activePaneId
|
package/src/index.ts
CHANGED
package/src/mcp/chartBridge.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ToolCall, ToolResult, ControllerDescription, ToolCallHandler } from './types'
|
|
2
|
+
import { generateUUID } from '../utils/uuid'
|
|
2
3
|
|
|
3
4
|
export interface ChartBridgeOptions {
|
|
4
5
|
wsUrl: string
|
|
@@ -42,7 +43,7 @@ export class ChartBridge {
|
|
|
42
43
|
onStateChange?: () => void
|
|
43
44
|
|
|
44
45
|
constructor(options: ChartBridgeOptions) {
|
|
45
|
-
this.sessionId = options.sessionId ??
|
|
46
|
+
this.sessionId = options.sessionId ?? generateUUID()
|
|
46
47
|
this.autoReconnect = options.autoReconnect ?? true
|
|
47
48
|
this.reconnectDelay = options.reconnectDelay ?? 3000
|
|
48
49
|
this.maxReconnectDelay = options.maxReconnectDelay ?? 30_000
|
|
@@ -64,7 +64,7 @@ exports[`theme baseline — dark > CSS declaration block (snapshot) 1`] = `
|
|
|
64
64
|
--klc-color-tag-bg-transparent: transparent;
|
|
65
65
|
--klc-color-tag-bg-active: #1890ff;
|
|
66
66
|
--klc-color-tag-bg-active-hover: #40a9ff;
|
|
67
|
-
--klc-color-tag-bg-hover: #
|
|
67
|
+
--klc-color-tag-bg-hover: #262C36;
|
|
68
68
|
--klc-color-border-dark: rgba(255, 255, 255, 0.15);
|
|
69
69
|
--klc-color-border-medium: rgba(255, 255, 255, 0.12);
|
|
70
70
|
--klc-color-border-light: rgba(255, 255, 255, 0.08);
|
package/src/tokens/theme-dark.ts
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function generateUUID(): string {
|
|
2
|
+
if (typeof crypto.randomUUID === 'function') return crypto.randomUUID()
|
|
3
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16))
|
|
4
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x40
|
|
5
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80
|
|
6
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0'))
|
|
7
|
+
return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex.slice(6, 8).join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10).join('')}`
|
|
8
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.8.
|
|
1
|
+
export const VERSION = "0.8.6"
|