行情数据
由图表定义的统一品种与序列模型、负责转换上游协议的 Provider,以及决定由谁取数的路由层。
KLineChartQuant 的每一根 K 线、每一条分时和每一个品种,都经过同一套由前端定义的模型。数据源的私有协议不会直接进入图表,而是先由 Provider 转换。正因如此,同一张图、同一个比较功能、同一个 Agent 工具,才能同时跑在通达信、TradingView、MT5 或你自己的服务上。
统一模型
绝大多数数据由三种类型承载,均从 @363045841yyt/klinechart-core/market-data 导出。
| 类型 | 内容 |
|---|---|
InstrumentDescriptor | 身份字段(id、sourceId、symbol、name、assetClass、exchange),可选的 sessionId、currency、tickSize、lotSize,以及 capabilities |
BarSeries | 一页 K 线:instrumentId、period、adjustment、barAggregation、timezone、可选 volumeUnit、data 和 olderData |
TimeShareSeries | 单个交易日的分时:instrumentId、tradingDate、timezone、preClose、可选 volumeUnit、data |
几条约定让模型保持干净:
id是品种身份的唯一依据。代码相同、交易所不同,就是两个品种。providerRef是私有路由信息,只交给创建它的 Provider;搜索、UI、比较和图表都不读取。- 周期与复权选项读取
instrument.capabilities.bars。进入分时模式需要capabilities.timeShare === true,且sessionId已注册。 - 成交量按序列的
volumeUnit(share、lot、contract或baseAsset)展示,不默认所有市场都用“手”。 olderData(available、exhausted或unknown)由后端明确声明,前端不会从一页空数据去猜历史是否到头。
Provider 与注册表
MarketDataProvider 按能力组合模块,缺少哪个模块就表示不支持哪项能力。
interface MarketDataProvider {
readonly source: DataSourceDescriptor
probe(signal?: AbortSignal): Promise<SourceProbeResult>
readonly catalog?: InstrumentCatalog
readonly bars?: BarDataSource
readonly tradingCalendar?: TradingCalendarDataSource
readonly liveBars?: LiveBarsDataSource
readonly timeShare?: TimeShareDataSource
readonly timeShareRange?: TimeShareRangeDataSource
readonly depth?: DepthDataSource
}Provider 统一登记在 marketDataProviderRegistry,每个 Provider 有一份运行时配置:enabled、priority 和可选的 baseUrl。注册表只保存当前会话的配置,持久化(比如写入 localStorage)由宿主负责。
import { marketDataProviderRegistry } from '@363045841yyt/klinechart-core/market-data'
marketDataProviderRegistry.setConfig('gotdx', { baseUrl: 'http://127.0.0.1:8080' })
marketDataProviderRegistry.setConfig('mock', { enabled: false })
const enabled = marketDataProviderRegistry.getEnabled()导入 @363045841yyt/klinechart-core/market-data/sources 会注册内置 Provider:gotdx、baostock、finshare、tradingview、mock 和 mt5。注册顺序决定同优先级数据源的尝试顺序。各数据源覆盖的范围见连接器。
路由
图表运行时从不直接调用 Provider,而是由 ChartDataManager 交给 SourceRouter,由路由层决定谁来应答。
- 显式
sourceId(preferredSourceId)只走这一个源。源被禁用或不具备该能力时直接失败,不会悄悄换源。 auto按优先级取出已启用的 Provider,再按源级能力筛选:能力本身、assetClass、period和adjustment。尚未声明能力的源会先探测一次。- 品种解析在每个候选源的目录中按
symbol查找,提供了exchange和assetClass时一并收窄。 - 流转只发生在确定性拒绝时,即
UNSUPPORTED_CAPABILITY或INSTRUMENT_NOT_FOUND。网络故障或取消会直接终止,上游不稳定时会如实报错,而不是拿另一个源的数据掩盖过去。
所有候选源都拒绝时,路由层抛出 SourceRoutingError,附带完整的尝试链。
代码有歧义时
同一个代码可能对应多个品种,例如 000012 既可能是一只股票,也可能是一个国债指数。Core 不会替用户拍板。comparison_create 等 Agent 工具会返回 { status: "ambiguous", candidates } 且不写入任何状态;Agent 必须调用 ask_user,每个候选一个选项,再用用户选中的 source 和 exchange 重试。用户和 Agent 走的是同一组命令,这条规则对两者同样成立。
行情缓存
每个图表实例持有一个 MarketDataCache。UI、Agent 和 ChartDataManager 都从它取数,滚动加载和 Agent 查询复用同一批分页结果,不会重复请求。它用 limit/before 游标分页拉取历史,失败自动重试,合并相同的进行中请求,超过容量上限时按最近最少使用淘汰。
上限由设置项 marketDataCacheMaxMiB 控制:默认 50 MiB,可调范围 5 到 512。它位于设置面板的“数据”分组,修改即时生效,调低时立刻淘汰。
controller.settingsCommands.applyValues({ marketDataCacheMaxMiB: 128 })三种接入方式
| 方式 | 适用场景 | 参考 |
|---|---|---|
| REST 协议 | 你有自己的服务,能实现 market-data-v1:探测、品种搜索、K 线、分时 | HTTP API |
| 实时 K 线(SSE) | 你的服务能按数据源推送形成中和已收盘的 K 线 | 实时 K 线 |
| 进程内 Provider | 数据已经在浏览器里,或者你想自己封装某个客户端 SDK | Provider API |
走 REST 协议不需要写 Provider 代码,createMarketDataProvider 和 createHttpMarketDataTransport 根据地址直接装配:
import {
createHttpMarketDataTransport,
createMarketDataProvider,
marketDataProviderRegistry,
} from '@363045841yyt/klinechart-core/market-data'
const provider = createMarketDataProvider({
source: { id: 'my-feed', displayName: 'My feed', defaultBaseUrl: 'http://127.0.0.1:9000' },
transport: createHttpMarketDataTransport({
baseUrl: () => marketDataProviderRegistry.getConfig('my-feed').baseUrl ?? 'http://127.0.0.1:9000',
sourceLabel: 'my-feed',
}),
})
marketDataProviderRegistry.register(provider, { enabled: true })如果只是展示一份现成数据、没有任何行情源,组件仍然支持直接传入(customData 属性,或控制器的 setData)。