AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
翻譯準備中
High-performance financial charting library with Canvas/WebGL/WebGPU backends, 1–3ms GPU time per frame, plugin-based rendering, visual signal annotation, AI-native architecture. Seamlessly supports V
翻譯準備中
開發工具TypeScript · VueGitHub @363045841上線中營運者未確認
試用過了嗎? 點一下就好,不需要登入。
High-performance financial charting library with Canvas/WebGL/WebGPU backends, 1–3ms GPU time per frame, plugin-based rendering, visual signal annotation, AI-native architecture. Seamlessly supports Vue, React, and Angular.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
High-performance financial chart library with a single-frame generation time of just 2ms, stable scrolling at 190–200fps in a 200Hz environment, native support for AI Agent control, full-link ResizeObserver-driven crisp rendering, and a pluggable architecture.
A lightweight financial K-line charting library focused on quantitative trading scenarios. Agent is a first-class citizen — supports AI Agent direct control of chart operations, providing TradingView-level interaction experience.檢視完整 README ↗High-performance financial charting infrastructure with a single-frame generation time of just 2 ms, stable scrolling at 190–200 fps in a 200 Hz environment, a native AI-Native chart architecture, full-link ResizeObserver-driven crisp rendering, a QuickJS indicator runtime, and support for running scripts against the in-house KCQ financial analysis API to render indicators.
English | 简体中文
Crisp Rendering · High Performance · Optimized Interaction · Mobile-Friendly
A lightweight financial K-line charting library focused on quantitative trading scenarios. Agent is a first-class citizen — supports AI Agent direct control of chart operations, providing TradingView-level interaction experience.
KLineChartQuant treats the Agent as a first-class citizen of the chart, equal in standing to the human user. It is not a chat layer bolted on top: the Agent operates the chart through the same primitives as the UI, and the endpoints it acts on are exposed by the chart core itself.
The Agent is the user, and the user is the Agent: The Agent is not a guest of the chart; it is an operator with the same standing as the person in front of the screen. Everything a user can do through the UI, the Agent can do through the same entry points, and vice versa. Equality of access is the design premise, not a toggle.
The Agent serves the chart, not a stream of text: An embedded Agent exists to drive the chart the user is looking at, not to bury them in prose. Its output lands as real interaction on the surface the user sees—indicators, drawings, viewport changes—with conversation only as the means. The chart and its interaction remain the subject.
AI-Native architecture, not a parasitic layer: Tools are woven into the chart API primitives as aspects rather than wrapped in a separate layer, so there is one implementation, naturally consistent state, and zero bridge overhead. Zed is the reference: its agent is built into the editor's own foundation (GPUI). VS Code is the counter-example: Copilot and Claude Code enter through the extension layer, where the Claude extension is essentially a GUI wrapper around an external CLI. The former makes the Agent native; the latter makes it parasitic.
The Agent pushes the architecture toward stability and efficiency: Adding an Agent adds no architectural burden; it forces the architecture to be better. Because every capability must be exposed through a single API and unified primitives, state is consolidated into one source of truth and kept absolutely consistent—scattered state, derived copies, multi-write paths, parallel pipelines, and races cannot survive. The Agent and the user travel the same path, and that constraint settles into a more stable, testable chart core.
No blind use of MCP: The project evolved through three stages—an early JSON-configuration approach, then MCP, and finally an AI-Native architecture. MCP demands an intermediate layer or DSL that spends tokens, loses information, and keeps a second copy of state that invades frontend logic. The current design drops the bridge entirely: tools register directly on the chart core, and a single call reaches the kernel.
The reactive kernel is the Agent's foundation: StateKernel is the single source of truth—only actions may write, computed values derive automatically, and external consumers receive readonly signals, backed by batched atomic snapshots and frozen state. The kernel is zero-dependency, extremely light, and has controllable render timing. This lets the Agent share the user's exact state at near-zero cost and keeps tools a subset of actions rather than a parallel system.
@Tool-decorated domain primitives, and the Agent calls the same primitives as the UI. Tools are a subset of actions: one shared state, one execution path, no bridge layercompositeTo copy), single-command-buffer-per-frame submission with 4x MSAA, and per-instance geometry caching via ResourceTable. Automatic fallback chain: WebGPU → WebGL → Canvas2D. Reaching 190fps on 200Hz displays with per-frame GPU time under 1ms#kline-tooltip slot, with engine-provided hover data, position, and stylingKLineChartQuant submits drawing primitives directly to Canvas2D, WebGL2 or WebGPU. The design target is 200 FPS (5 ms per frame); the chart shows measured frame generation time and its share of that budget.
Benchmark: 1180 × 640 viewport, DPR 2, 4× MSAA, 300–10,000 visible K-lines, three pooled rounds per scenario. NVIDIA GeForce RTX 4060 Laptop GPU, headless Chrome 155. Reproduce it with bench/.
KLineChartQuant is a pnpm monorepo. The framework-agnostic core engine exposes a unified
ChartController (readonly signals + commands); Vue / React / Angular bindings only handle
mounting, event forwarding, and reactivity bridging. The AI Agent drives the chart through the
same @Tool primitives as the UI, sharing one state source instead of a bridge.
flowchart TB
subgraph app["UI Layer / Framework Bindings"]
UI["UI Layer"]
VuePkg["@363045841yyt/klinechart<br/>Vue 3 components · useChart"]
WCPkg["@363045841yyt/klinechart-web-component<br/><kline-chart> custom element"]
ReactPkg["@363045841yyt/klinechart-react<br/>KLineChart (direct core) · KLineChartWC (Web Component)"]
AngularPkg["@363045841yyt/klinechart-angular"]
Agent["AI Agent"]
AgentRt["@363045841yyt/klinechart-agent-runtime"]
end
subgraph core["Core Engine @363045841yyt/klinechart-core"]
Ctl["ChartController<br/>signals + commands"]
Chart["Chart facade"]
Kernel["StateKernel<br/>Reactive SSOT"]
Data["Data Layer<br/>SeriesRepository · Buffers"]
Pipe["Rendering Pipeline<br/>FrameTransaction · Scene/Layer"]
GPU["WebGPU / WebGL2 / Canvas2D"]
Plugin["Plugin Subsystem<br/>PluginHost · RendererPlugin"]
Biz["Indicators · Drawing<br/>Timeshare · Compare · Components"]
end
subgraph conn["Market Data Backends"]
Go["GoTDX-Connector<br/>gotdx :8080"]
Bn["Binance-Connector<br/>Binance spot K-line / trades :8091"]
Bs["Baostock-Tradingview-Connector<br/>BaoStock / TradingView :8000"]
Mt["KCQ-MT5-connector<br/>MT5 (Exness) :8090"]
end
UI --> VuePkg
UI --> ReactPkg
UI --> AngularPkg
VuePkg -->|"builds from"| WCPkg
WCPkg --> ReactPkg
Agent --> AgentRt
VuePkg --> Ctl
ReactPkg --> Ctl
AngularPkg --> Ctl
AgentRt -->|"@Tool primitives (same path as UI)"| Ctl
Ctl --> Chart
Chart --> Kernel
Chart --> Data
Chart --> Pipe
Chart --> Plugin
Plugin --> Biz
Biz --> Pipe
Pipe --> GPU
Go -->|market data| Data
Bn -->|market data| Data
Bs -->|market data| Data
Mt -->|"market data + SSE"| Data
Kernel --> Data
Kernel --> Pipe
ChartController; depends on no UI framework.computed() for derivation, effect() for DOM side effects.SeriesRepository + incremental buffers + fetch scheduler;
multi-source aggregation (gotdx / BaoStock / TradingView / MT5 / mock) and Binance Spot K-lines and trades.@363045841yyt/klinechart-react's KLineChart mounts the core directly
(client-only, no Vue runtime); KLineChartWC renders the full-UI <kline-chart> Custom Element
from @363045841yyt/klinechart-web-component, a standalone one-file bundle.@363045841yyt/klinechart-agent-runtime orchestrates the Agent, which
invokes the core's @Tool-registered primitives—the same entry points the UI uses.See docs/architecture/architecture.md for the full architecture document.
Indicators can be written as plain TypeScript instead of being compiled ahead of time. A script is one file that declares a single indicator through defineIndicator; the engine compiles it once, then runs it bar by bar and collects Plot points. Scripts are data-source and render agnostic — the host supplies the bars, the chart adapter renders the result.
defineIndicator({
name: 'Volume Tide',
position: Position.Sub,
onBar() {
const RED = '#F08080'
const RED_LIGHT = 'rgba(240, 128, 128, 0.5)'
const RED_LIGHTER = 'rgba(240, 128, 128, 0.15)'
const GREEN = '#008000'
const GREEN_LIGHT = 'rgba(0, 128, 0, 0.5)'
const GREEN_LIGHTER = 'rgba(0, 128, 0, 0.1)'
const GRAY_LIGHTER = 'rgba(128, 128, 128, 0.15)'
const barUp = ta.rising(close, 1)
const barDn = ta.falling(close, 1)
const volumeChange = ta.change(volume)
const volumeColor = barUp
? RED_LIGHTER
: barDn ? GREEN_LIGHTER : GRAY_LIGHTER
plot.Columns(volume, 'Volume columns', {
color: volumeColor,
})
const volumeChangeColor = barUp
? (volumeChange > 0 ? RED : RED_LIGHT)
: barDn
? (volumeChange > 0 ? GREEN : GREEN_LIGHT)
: Color.Gray
plot.Histogram(volumeChange, 'Volume change columns', {
color: volumeChangeColor,
size: 12,
})
plot.Line(0, 'Zero line', {
color: Color.Gray,
})
},
})
Everything a script needs is injected as a global before it runs:
| Global | Meaning |
|---|---|
open high low close volume time | price series; close reads the current bar, close[1] one bar back |
input.int input.float input.bool input.string | declare a parameter at the top level of onBar; the variable reads back the user's value |
plot.Line plot.LineBR plot.StepLine plot.StepLineBR plot.Columns plot.Histogram plot.Point plot.Shape plot.Char plot.Arrow | emit plot points; the method name selects the shape; each returns a handle |
plot.Candle plot.Bar | emit candles or OHLC bars from four prices; candles support separate body, wick, and border colors; return void |
fill(from, to, color, title, options?) | fill the area between two plots; from / to are handles returned by plot.* |
ta | built-in technical analysis library |
Position | Position.Main or Position.Sub |
Color | common Material 400 colors, or pass any CSS color string |
defineIndicator / defineLibrary / importLibrary | declare the indicator, declare a library, use a library |
defineIndicator also accepts an optional title for the long name and an optional info for the description shown in the indicator picker; name remains the stable identity and short label.
plot.Line connects valid points across null or NaN gaps, like Pine's plot.style_line. Use plot.LineBR to break the line at gaps, like plot.style_linebr. Both accept the same color, size, dash, and offset options and return handles for fill. Existing scripts that require gaps to break the line should use plot.LineBR.
plot.StepLine draws steps with vertical transitions halfway between valid points and connects across gaps. plot.StepLineBR breaks at gaps. They correspond to Pine's plot.style_stepline and plot.style_steplinebr, accept the same options as lines, and return handles for fill.
Trend calculations live in ta: ta.atr(length) returns Wilder ATR, and ta.supertrend(multiplier, atrPeriod) returns [trend, direction] series. Direction is -1 for up and 1 for down; both series are NaN during warm-up. Supertrend preserves the previous core indicator's initial up direction. The built-in SuperTrend script uses these results with two plot.LineBR calls.
ta.macd(source, fastLength, slowLength, signalLength) returns [DIF, DEA, DIF-DEA] series. Its EMAs use the first valid value as their seed, matching the previous core MACD; regular ta.ema retains its full-window seed. The built-in MACD script uses defaults 12/26/9, doubles the histogram, and colors columns by sign and momentum. Values start at the first valid input; the migrated script supports K-line charts.
Parameters are plain const declarations, so the variable name is the parameter identity — no inputs block and no params object:
onBar() {
const period = input.int(20, { title: 'Period', min: 1 })
const type = input.string('SMA', { options: ['SMA', 'EMA'] })
plot.Line(type === 'SMA' ? ta.sma(close, period) : ta.ema(close, period), 'ma')
}
The built-in ta library covers sma, ema, rma, swma, alma, wma, vwma, rsi, highest, lowest, highestbars, lowestbars, stoch, cmo, linreg, crossover, crossunder, rising, falling and change. highestbars / lowestbars return negative bar offsets (0 is the current bar) and resolve ties to the earliest bar, matching Pine. User libraries use the same defineLibrary call and join the same global namespace.
NaN; a script can never look aheadRegister scripts through the script service; each one then appears in the indicator picker as a normal entry:
import { createScriptService } from '@363045841yyt/klinechart-script-chart'
const scripts = await createScriptService()
await scripts.create(source)
See the script docs for the full declaration reference, libraries, and Plot API.
KLineChart requires a market data backend. Supported data sources:
| Data Source | Description | Docs |
|---|---|---|
gotdx | Tongdaxin (GOTDX) quotes: A-share / futures / MAC, served by GoTDX-Connector | GoTDX-Connector |
binance | Binance Spot K-lines and real tick-by-tick trades (footprint / order-flow), served by Binance-Connector | Binance-Connector |
baostock | BaoStock A-share daily / weekly / monthly & minute K-lines, served by Baostock-Tradingview-Connector | BaoStock |
tradingview | TradingView global instruments, served by Baostock-Tradingview-Connector | BaoStock |
mt5 | MT5 (Exness) local terminal: forex / metals / crypto CFDs, served by KCQ-MT5-connector | MT5 |
mock | Debug only: local MOCK-100 / MOCK-10000 K-lines, no backend needed, always online | — |
Backend repos live alongside this one (outside the monorepo).
Clone the data-source backends first (idempotent: skips directories that already exist):
pnpm setup:backends
Then run pnpm dev with a -c argument to start the frontend and the selected connectors together:
pnpm dev # frontend only (Vite dev server)
pnpm dev -c all # frontend + all backends (gotdx + binance + baostock; mt5 excluded)
pnpm dev -c gotdx baostock # frontend + selected backends
pnpm dev -c mt5 # frontend + MT5 local terminal (Windows + logged-in MT5 terminal)
pnpm dev -c tdx # aliases supported (tdx / g / b / bnb / m / all)
pnpm dev -c all --lan # same, dev server bound to 0.0.0.0 (LAN accessible)
Common shorthands:
pnpm dev:all # frontend + all backends
pnpm dev:g # frontend + gotdx (Tongdaxin)
pnpm dev:b # frontend + BaoStock / TradingView
pnpm dev:bnb # frontend + Binance spot market data
pnpm dev:mt5 # frontend + MT5 local terminal
pnpm dev:lan:all # frontend (0.0.0.0) + all backends
Parallel process logs stay in one terminal and are separated by colored source prefixes: [vite], [gotdx], [binance], [baostock], and [mt5].
In Windows PowerShell, run node scripts/dev.mjs -c gotdx (or use the other -c arguments above) to finish shutdown logs before the next prompt after Ctrl+C. pnpm dev adds a pnpm process that also receives Ctrl+C; the child script cannot control when pnpm prints [ELIFECYCLE].
Backend only (no frontend):
pnpm connector # all backends (mt5 excluded)
pnpm connector gotdx # gotdx (Tongdaxin) :8080
pnpm connector binance # binance (Binance spot K-line & trades) :8091
pnpm connector baostock # BaoStock / TradingView :8000
pnpm connector mt5 # MT5 local terminal :8090 (Windows + logged-in MT5 terminal)
For backend only with the same shutdown ordering, run node scripts/start-connector.mjs gotdx.
After pnpm setup:backends, no extra setup is needed. The dev server proxies /api/stock → :8000 (Baostock-Tradingview-Connector) and /api/public → :8080 (GoTDX-Connector).
npm install @363045841yyt/klinechart @363045841yyt/klinechart-core
Use the component:
<template>
<div class="app-container" :data-theme="currentTheme">
<KlineChart v-model:theme="currentTheme" :custom-data="customData" :settings="chartSettings" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ChartSettings } from '@363045841yyt/klinechart-core'
import { type CustomDataSource, KlineChart } from '@363045841yyt/klinechart'
import demoData from './demo-data.json'
const currentTheme = ref<'light' | 'dark'>('dark')
const customData = ref<CustomDataSource>(demoData as CustomDataSource)
const chartSettings: ChartSettings = {
showGridLines: true,
isAsiaMarket: true,
mainLeftAxisDisplaySetting: 'none',
theme: 'dark',
colorPresetSettings: {
dark: {
candleUpBody: '#e85d04',
candleDownBody: '#1b4332',
crosshairLine: '#faa307',
gridMajor: '#3e2723',
},
},
}
</script>
<style>
.app-container {
display: flex;
flex-direction: column;
height: 80vh;
}
.app-container[data-theme='dark'] {
background: #000;
color: #e5e7eb;
}
</style>
Import CSS in main.ts:
import '@363045841yyt/klinechart/style.css'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
Slot Usage — Custom Tooltip:
<KlineChart>
<template #kline-tooltip="{ hoverData, upColor, downColor }">
<div class="custom-tooltip">
<div class="custom-tooltip__title">
<span>{{ hoverData.stockCode }}</span>
<span>{{ formatTimestamp(hoverData.timestamp, { timeZone: 'Asia/Shanghai' }) }}</span>
</div>
<div
class="custom-tooltip__price"
:style="{ color: hoverData.close >= hoverData.open ? upColor : downColor }"
>
{{ hoverData.close.toFixed(2) }}
</div>
<div class="custom-tooltip__detail">
O: {{ hoverData.open.toFixed(2) }}<br />
H: {{ hoverData.high.toFixed(2) }}<br />
L: {{ hoverData.low.toFixed(2) }}<br />
C: {{ hoverData.close.toFixed(2) }}
</div>
</div>
</template>
</KlineChart>
Slot Usage — Custom Main-Pane Legend:
Providing #legend fully replaces the default Canvas legend. The slot scope is the full LegendTemplateContext (OHLC, timeshare, main indicators, comparisons, layout, colors).
<template #legend="{ index, currentBar, timeshare, indicators, comparisons, colors }">
<div class="my-legend">
<!-- Custom fields added to KLineData[] for PR #98 are exposed through currentBar -->
<div v-if="currentBar" class="my-legend__row">
<span :style="{ color: currentBar.color }">
开盘 {{ currentBar.open.toFixed(2) }} 最高 {{ currentBar.high.toFixed(2) }} 最低
{{ currentBar.low.toFixed(2) }} 收盘 {{ currentBar.close.toFixed(2) }}
</span>
<span v-if="currentBar.volumeText"> Vol {{ currentBar.volumeText }}</span>
</div>
<div v-if="timeshare" class="my-legend__row">
<span :style="{ color: timeshare.changeColor }">
现价 {{ timeshare.price.toFixed(2) }} 涨幅 {{ timeshare.changePercent.toFixed(2) }}%
</span>
</div>
<!-- Using main chart indicator legend data -->
<div v-for="indicator in indicators" :key="indicator.name" class="my-legend__row">
<span>{{ indicator.name }}:</span>
<template v-for="value in indicator.values" :key="value.label">
<span :style="{ color: value.color }">
{{ value.label }} {{ value.value.toFixed(3) }}
</span>
</template>
</div>
<!-- Using comparison commodity data -->
<div
v-for="comparison in comparisons"
:key="comparison.symbol"
class="my-legend__row"
:style="{ color: comparison.percentColor }"
>
{{ comparison.symbol }}
{{ comparison.percent > 0 ? '+' : '' }}{{ comparison.percent.toFixed(2) }}%
</div>
</div>
</template>
Use #toolbar-start and #toolbar-end to place host account or workspace
controls in the native toolbar. Both slots are optional and inherit the
resolved chart theme. They stay outside the draggable, scrolling chart controls.
<KlineChart>
<template #toolbar-start><AccountMenu /></template>
<template #toolbar-end><WorkspaceActions /></template>
</KlineChart>
The chart core exposes its domain capabilities as @Tool primitives. Both the UI and the Agent call them through the same path:
import { getRegisteredChartTools } from '@363045841yyt/klinechart-core/controllers'
getRegisteredChartTools() returns every tool with its parameter schema, safety level, and unified executor. Hand them to @363045841yyt/klinechart-agent-runtime, which orchestrates the Agent inside your app (browser or Electron) against a Provider profile — no MCP bridge, no side-channel state. See agent-runtime.
| Prop | Type | Default | Description |
|---|---|---|---|
| theme | 'light' | 'dark' | — | Chart theme. Use v-model:theme for two-way binding |
| isFullscreen | boolean | — | Controlled fullscreen state. Leave unbound for internal (non-controlled) mode |
| timezone | string | 'Asia/Shanghai' | Time zone for date/time display |
| yPaddingPx | number | 20 | Y-axis padding in pixels |
| minKWidth | number | 1 | Minimum K-line width (logical pixels) |
| maxKWidth | number | 200 | Maximum K-line width (logical pixels) |
| rightAxisWidth | number | 0 | Right price axis width |
| leftAxisWidth | number | 0 | Left price axis width (0 = hidden) |
| bottomAxisHeight | number | 24 | Bottom time axis height |
| priceLabelWidth | number | 60 | Price label extra width for showing change percentage |
| zoomLevels | number | 80 | Total number of zoom levels |
| initialZoomLevel | number | 3 | Initial zoom level (1 ~ zoomLevels) |
| customData | CustomDataSource | — | Inline data bundle: { symbol?, period?, data, comparisons? }. Bypasses the fetcher pipeline entirely. See example above |
| teleportContainer | string | HTMLElement | — | Teleport target for dropdowns/modals (CSS selector or element). Defaults to internal .chart-wrapper |
| Package | Description | npm |
|---|---|---|
@363045841yyt/klinechart-core | Headless chart engine + controllers | npm |
@363045841yyt/klinechart | Vue 3 bindings | npm |
@363045841yyt/klinechart-react | React bindings | npm |
@363045841yyt/klinechart-angular | Angular bindings | npm |
@363045841yyt/klinechart-agent-runtime | Framework-neutral Agent runtime (Pi orchestration + host contracts) | npm |
@363045841yyt/klinechart-script | Sandboxed script indicator engine (QuickJS runtime + language service) | npm |
@363045841yyt/klinechart-script-chart | Registers script indicators as chart indicators | npm |
@363045841yyt/klinechart-plot-contracts | Plot declaration contracts shared by the engine and the script runtime | npm |
agent-runtime and an AI runtime with OpenAI protocol support, driving the chart through the same @tool primitives the UI uses, plus @tool registration, drawing Agent tools, and a shared web/Electron Agent workspaceta technical-analysis library, and user libraries; plots flow through the normal rendering chain, so a script indicator behaves exactly like a built-in one