Building a TradingView-Style Market Data System: A Comprehensive Guide from Chart Selection to Data Layer Deep Waters
TickDB Research ·
Our team has been building market data charting systems for years. From the earliest market K-line integrations to cross-market dashboards spanning A-shares, Hong Kong stocks, and US stocks, the pitfalls we've encountered and the detours we've taken could fill several retrospective articles. This piece is our internally refined experience—it's not a tutorial, it's an engineering notebook.
I. A Problem That Plagued Us for a Long Time
WebSocket client shows connected=True, heartbeat is normal, logs are clean, but K-lines on the chart have stopped. Not lagging—completely frozen.
This isn't unique to us. Three completely independent financial systems have recorded the same failure pattern:
- QuantConnect Brokerage Adapter Layer (Issue #31): "The keep-alive timer only checks IsOpen and sends KeepAliveRequest, never verifies if a response was received."
- Polymarket Real-time Quote Push: "The server sometimes stops pushing data while keeping the socket open—no close frame, no exception."
- PetroSa Trading Engine (Issue #609): "socket.connected=True but data events stop firing."
Common characteristic: Connection state and data state are two different things. Reconnection logic based on errors or close events will never trigger because there are no errors, and no close.
This is what we later called the "data layer deep waters." Most Chinese discussions focus on chart library selection—ECharts vs TradingView, Canvas vs WebGL. But what really determines whether a project can go live and survive is: Datafeed API async traps, multi-market fault-tolerant architecture, base date drift in adjustment processing, WebSocket silent freeze countermeasures, open-source library licensing and commercial pitfalls, and the reasonable boundaries of AI integration. These engineering details are rarely explained systematically.
The content below follows this logic:
Entry Point: Chart Library Selection and Rendering Engine Physical Limits
↓
Engineering Deep Waters I: Three Fatal Traps in Datafeed API
↓
Engineering Deep Waters II: Multi-Market Expansion and Fallback-First Fault Tolerance Architecture
↓
Engineering: Testing, Accessibility, and Licensing Pitfalls
↓
AI Era: Reasonable Positioning
↓
Decision Map: Build Your Own vs. Buy a Ready-Made Framework
↓
Production-Ready Data Layer: Hands-On Testing and Limitations of the TickDB API
II. Before Selection, Understand the Rendering Engine's Physical Limits
When we first encountered chart stuttering, our first reaction was also "Canvas isn't working anymore, switch to WebGL." Later we discovered that before discussing library selection, we need to know the physical ceilings of various rendering technologies.
| Rendering Tech | Interaction Performance Target | Representative Libraries | Applicable Scenarios |
|---|---|---|---|
| SVG | Stable within thousands of nodes | Recharts, Highcharts (default) | Reports, low-frequency data, high accessibility requirements |
| Canvas 2D | Deeply optimizable within tens of thousands of nodes, preferred for high-frequency time series | Lightweight Charts, uPlot, Chart.js | 2D K-lines, high-frequency time series |
| WebGL | Clear advantage at 100k+ nodes | PixiJS, regl, Three.js | Million-level points, heatmaps, 3D |
| WebGPU | Million-level data GPU acceleration | ChartGPU | Cutting-edge applications, GPU-side downsampling |
Regarding whether Canvas or WebGL is faster, the industry has contradictory test results. One dataset shows WebGL dominates in scatter plot scenarios; another shows that a deeply optimized Canvas implementation is faster than a certain general-purpose WebGL implementation during K-line panning and zooming.
The truth is: Performance conclusions are highly dependent on data scale, scenario, implementation quality, and optimization depth. "WebGL is definitely faster than Canvas" is marketing talk. Our approach: prioritize deeply optimized Canvas for 2D K-line charts; only consider WebGL for million-level data, heatmaps, or complex 3D scenarios.
A little-known browser trap: Browsers like Chrome impose limits on the number of active WebGL/WebGPU contexts per page. If a multi-chart dashboard creates a separate context for each chart, later charts may crash during rendering. Self-developed solutions need to consider context pooling.
Selection Matrix:
| Library | Rendering | Size | License | Positioning |
|---|---|---|---|---|
| TradingView Lightweight Charts | Canvas 2D | ~35KB | Apache 2.0 (watermark required) | Ultra-small footprint, mobile-optimized, basic K-lines |
| TradingView Advanced Charts | Canvas | Larger | Proprietary license (requires enterprise application) | Full-featured technical analysis terminal |
| Apache ECharts | Canvas/SVG/WebGL | ~135KB (lean) | Apache 2.0 | General visualization, large dashboards |
| uPlot | Canvas 2D | Extremely small | MIT | Ultra-high-frequency time series, zero dependencies (verify with official docs) |
| ChartGPU | WebGPU | Extremely small | MIT | Cutting-edge GPU acceleration, poor compatibility with older devices (verify with official docs) |
| Highcharts Stock | SVG/Canvas | Larger | Commercial license (pricing on官网) | Enterprise reporting with extreme accessibility requirements |
III. Three Fatal Traps in Datafeed API
If you choose to integrate TradingView's official chart libraries (Advanced Charts / Charting Library), the Datafeed API's async mechanism is an obstacle you must overcome.
Note: Lightweight Charts does not use Datafeed API. It has its own simplified API (
addCandlestickSeriesetc.), don't mix them.
Architecture Overview:
Frontend: TradingView Charting Library (Advanced Charts)
│ Datafeed API (implement /config, /symbols, /history etc. endpoints)
↓
Data Adapter Layer (what you implement)
Real-time data via subscribeBars callback, not independent HTTP endpoints
│
↓
Backend: Data Sources (TickDB / self-built / hybrid)
REST + WebSocket + Data Standardization + Caching
Trap I: Macro-Task Async Execution to Prevent Stack Overflow
All callbacks in Datafeed API (such as historyCallback) must execute asynchronously. If triggered synchronously, continuous execution within the same MacroTask of the Event Loop causes Uncaught RangeError: Maximum call stack size exceeded.
// ❌ Wrong: synchronous callback trigger
function getBars(symbolInfo, resolution, periodParams, onResult, onError) {
const data = loadFromMemory(symbolInfo, resolution);
onResult(data); // Stack overflow on consecutive requests
}
// ✅ Correct: push to next macro task
function getBars(symbolInfo, resolution, periodParams, onResult, onError) {
const data = loadFromMemory(symbolInfo, resolution);
setTimeout(() => {
onResult(data);
}, 0);
}
Trap II: Request Chasing and Infinite Loops
When the chart requests 329 K-lines but the server only returns 157, the charting library will automatically calculate the difference and ask again. If the backend has reached the furthest point in history, you must set noData: true in the return object (corresponding to {s: "no_data"} in UDF protocol). Missing this flag doesn't result in an error—it results in infinite request chasing, directly overwhelming the backend.
function getBars(symbolInfo, resolution, periodParams, onResult, onError) {
const { from, to, countBack } = periodParams;
fetchKline(symbolInfo.ticker, resolution, from, to, countBack)
.then(data => {
if (data.length === 0 || isEarliestHistory(from)) {
onResult([], { noData: true });
} else {
onResult(data);
}
})
.catch(onError);
}
Trap III: Data Backfill on Reconnection
When the network recovers, reconnecting WebSocket alone cannot fill missing historical K-lines. You must execute in sequence:
WebSocket disconnects → Network recovers
↓
1. Reconnect WebSocket (restore real-time stream)
↓
2. resetCache() Clear charting library internal cache
↓
3. resetData() Force trigger getBars to fill time gaps
↓
4. Resend subscription requests
ws.onclose = () => {
reconnectWebSocket().then(() => {
chartWidget.activeChart().resetCache();
chartWidget.activeChart().resetData();
resubscribeAll();
});
};
Timestamp Alignment: K-line timestamps must align to the start time of the K-line. When a 5-minute candle receives a Tick at 10:02, the timestamp must be passed as 10:00.
Note that TickDB returns timestamp in milliseconds, while Lightweight Charts' time field expects Unix seconds. Convert units first, then align:
// ❌ Wrong: treating milliseconds as seconds
{ time: tick.timestamp, close: tick.price }
// ✅ Correct: milliseconds → seconds, then align to 5-minute boundary
const interval_s = 300; // 5 minutes = 300 seconds
const ts_s = Math.floor(tick.timestamp / 1000); // milliseconds → seconds
{ time: Math.floor(ts_s / interval_s) * interval_s, close: tick.price }
The consequence of wrong timestamps is continuous K-line redrawing corruption—it looks like a data source problem, but it's actually a frontend timestamp handling error.
IV. Multi-Market Expansion and Fallback-First Fault Tolerance Architecture
When building multi-market systems, A-shares and Hong Kong stocks have several unique challenges we have encountered:
| Challenge | Specific Manifestation | Engineering Solution |
|---|---|---|
| Code and Market Suffix | 600028 needs conversion to 600028.SH or SH600028 | Build unified Symbol normalization layer |
| Trading Calendar and Holidays | Spring Festival adjustments cause K-line gaps | Explicitly configure session_holidays for market closures |
| Adjustment Factor Differences | US stocks dynamically adjust OHLC, A-shares distinguish forward/backward/no adjustment | Standardize output in data pipeline |
The fault tolerance paradigm from Vietnam market practice (VNIBB architecture) is highly instructive for multi-market systems:
Data Request
↓ Failure
Level 1: Primary API (VNStock / TickDB / Exchange Direct)
↓ Failure
Level 2: Web Scraper (Fallback data source)
↓ Failure
Level 3: Local Database Historical Archive
↓ Failure
Level 4: Stale Cache
↓ Failure
Throw DataNotFoundError
Core principle: Data APIs for specific markets are typically less stable; the backend must assume explosion-isolation responsibility rather than letting the frontend directly perceive data source failures.
Data Quality Correction: SEC petition petn4-886 (2026) disclosed an industry-level issue—platforms including TradingView and thinkorswim have long-unfixed defects in handling reverse splits for small and mid-cap stocks. Specific case: AIXC experienced 5 reverse splits, none adjusted, resulting in charts showing impossible price jumps of "$28 one day, $1.50 the next" for years. Our approach: the backend must have automatic price discontinuity detection and dynamic historical OHLC scaling correction capabilities.
V. Engineering: Testing, Accessibility, and Licensing
Testing Pyramid:
┌──────────────┐
│ Compliance │ ← Audit logs, permission isolation, data masking
├──────────────┤
│ Stability │ ← Long-running, network reconnection, extreme market conditions
├──────────────┤
│ Performance │ ← FPS, memory, CPU/GPU
├──────────────┤
│ Interaction │ ← Zoom, pan, crosshair cursor
├──────────────┤
│ Visual Regression│ ← Playwright + Pixelmatch
├──────────────┤
│ Component │ ← Chart instances, theme switching
├──────────────┤
│ Interface │ ← Datafeed, WebSocket
├──────────────┤
│ Unit │ ← K-line aggregation, indicator algorithms
└──────────────┘
Key Trap: Visual regression testing has "false positive" issues across OS platforms. Our solution is Playwright + Pixelmatch, handling pixel-level differences by adjusting thresholds, maxDiffPixels, and maxDiffPixelRatio parameters.
Canvas Chart Accessibility Deficits: Research shows Canvas charts are nearly inaccessible to screen reader users, with information extraction accuracy far below that of sighted users. WCAG 2.2 compliance requires: providing text alternatives, not relying solely on color for information, achieving 3:1 contrast ratio between bars and background, and supporting keyboard-only operation. Engineering implementation: configure hidden data tables or aria-label in the DOM.
<canvas id="chart" aria-label="Kweichow Moutai daily K-line chart, September 2026"></canvas>
<table class="sr-only" aria-hidden="false">
<caption>Kweichow Moutai Daily K-Line Data</caption>
<tr><th>Date</th><th>Open</th><th>High</th><th>Low</th><th>Close</th></tr>
<tr><td>2026-09-25</td><td>...</td><td>...</td><td>...</td><td>...</td></tr>
</table>
Licensing and Hidden Costs:
| Product | License | Source Code | Restrictions |
|---|---|---|---|
| Lightweight Charts | Apache 2.0 | Open source | TradingView watermark required |
| Advanced Charts | Proprietary license | Closed source | Personal projects, research, private deployment prohibited |
| Trading Platform | Proprietary license | Closed source | Requires Broker API integration |
| Highcharts Stock | Commercial license | Closed source | Pricing on官网 |
Hidden Cost: Non-Display Fee. Once a system involves automated trading or risk control, exchanges charge expensive monthly fixed licensing fees (Category 1/2/3). Our approach: during initial system design, completely isolate "display-only market data" from "algorithmic risk control market data" at the network and service layers.
VI. AI Era: Reasonable Positioning
Our team's judgment on AI's role in charting systems is: Co-pilot, not main engine; sidecar, not main chain.
Suitable AI scenarios: Natural language queries, chart pattern recognition, indicator recommendations, anomaly detection.
Unsuitable scenarios: Real-time main-chain signal decisions, automated trade execution, replacing deterministic indicator calculations with LLMs.
Safe Decoupling Architecture: VNIBB's read-only Sidecar (MCP) pattern is worth learning from—encapsulate market and financial data as lightweight read-only MCP services. AI Agents extract data through secure controlled interfaces, with UI presenting evidence panels with source citations.
Validation Checklist When Evaluating AI Features:
- What is the model? CNN, ViT, LLM, or rule engine?
- What is the training data? Does it cover multiple markets and timeframes?
- What is the inference latency? Can it operate in real-time?
- Is it explainable? Will regulators accept it?
- Will it send user data to external APIs?
VII. Decision Map and Production-Ready Data Layer Solution
7.1 Build vs Buy Decision Matrix
| Scenario | Recommended Path | Core Rationale |
|---|---|---|
| Single market, daily frequency research | Build (free data source + open source charts) | Controllable cost, simple requirements |
| Single market, real-time dashboard | Hybrid (open source charts + unified data service) | Data layer maintenance cost exceeds expectations |
| Multi-market dashboard | Unified data service primary | Field standardization, timezone alignment cost is extremely high |
| Production-grade trading terminal | Professional solution + self-built indicator engine | Licensing, compliance, performance all require professional investment |
| Institutional private deployment | Fully self-built or enterprise service | Data not leaving domain, auditing, permissions |
7.2 The Cost of the Data Layer: What You Lose Without a Unified Data Source
Early on, we also tried a fully self-built data layer, then discovered three costs that are unavoidable:
Cost I: Temporal Error. Prices look correct, but is it pre-market, close, or real-time price? Without explicit session markers, strategy signals may trigger in the wrong time window.
Cost II: Blind Spots. Long-term reliance on prices that "look right" without verifying market state means not knowing how much cross-session data has polluted your backtesting inputs. You discover it months later—not through an alarm, but through losses.
Cost III: Systemic Contamination. Once temporal errors enter the data pipeline, all downstream calculations run on the wrong basis, accumulating systemic bias without awareness.
These three costs share a common trait: They are not problems with any particular data service, but objective complexities of the data layer itself.
7.3 TickDB实测代码示例
The following code is based on our actual verification results using the TickDB API.
REST获取A股快照:
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.tickdb.ai/v1"
headers = {"X-API-Key": API_KEY}
resp = requests.get(
f"{BASE_URL}/market/ticker",
params={"symbols": "600519.SH"},
headers=headers
)
# Return fields: symbol, name, type, last_price, open, prev_close,
# volume_24h, high_24h, low_24h, timestamp
# US stocks additionally include: pre_market_quote, post_market_quote, overnight_quote
获取多市场交易时段:
for market in ["CN", "HK", "US"]:
resp = requests.get(
f"{BASE_URL}/market/trading-sessions",
params={"market": market},
headers=headers
)
print(f"{market}: {resp.json()}")
# 实测结果(截至 2026-09-25):
# US: [400-930 pre-market, 930-1600 regular, 1600-2000 after-hours] (three segments)
# CN: [930-1130, 1300-1457] (two segments, lunch break)
# HK: [930-1200, 1300-1600] (two segments, longer lunch break)
# FX: [] (currently in POC stage)
获取K线数据:
resp = requests.get(
f"{BASE_URL}/market/kline",
params={
"symbol": "AAPL.US",
"interval": "1d",
"limit": 100,
"type": "stock"
},
headers=headers
)
# Return fields: time, open, high, low, close, volume, quote_volume
# Response includes adjust field (current default "none")
WebSocket实时订阅:
import asyncio
import websockets
import json
async def subscribe_ticker():
url = f"wss://api.tickdb.ai/v1/realtime?api_key={API_KEY}"
async with websockets.connect(url) as ws:
await ws.send(json.dumps({
"cmd": "subscribe",
"data": {
"channel": "ticker",
"symbols": ["AAPL.US"],
"type": "stock"
}
}))
await ws.send(json.dumps({"cmd": "ping"}))
async for message in ws:
print(json.loads(message))
# Supported WebSocket channels: ticker, depth, trade
# Note: kline channel does not exist; K-lines retrieved via REST
7.4 Three-Layer Value Architecture
Immediate Use: Use get_ticker to confirm price, quote time, and trading status markers in one call. The three separate nested objects—pre_market_quote, post_market_quote, and overnight_quote—respectively correspond to three extended sessions.
Strategy Enhancement: Use get_trading_sessions to combine trading day, trading sessions, and K-line timestamps into a time qualification chain. Let strategies automatically silence during market closes, auctions, or halts without manual market calendar maintenance.
System Building: get_stock_info provides EPS, BPS, and dividend yield (dividend_yield) for US and Hong Kong stocks, supporting basic fundamental information cards.
7.5 Scenario Example: Cross-Market Dashboard Team
A small dashboard team spanning A-shares, Hong Kong stocks, and US stocks can combine as follows:
- Use
get_trading_sessionsto get the session structure for all three markets for the day - Use
get_tickerto get snapshots, and determine which session the current price belongs to through the three nested objects - Use WebSocket ticker channel to receive real-time updates
- Application layer maintains
statefield for each ticker, distinguishing "snapshot state" and "realtime state"
The dashboard can explicitly tell users "this stock's current price comes from which session" rather than letting users mistakenly think pre-market prices are real-time.
7.6 Boundary Notes
| Capability | Current Status |
|---|---|
K-line adjust field | Present in response, current value none; adjustment basis still needs declaration at application layer |
| Corporate actions endpoint | Currently not provided as standalone endpoint; needs application layer implementation |
| WebSocket reconnection | Provides ticker/depth/trade three-channel subscription; reconnection logic needs application layer implementation |
| FX trading sessions | Currently in POC stage |
A-share stock-info | Contains EPS and BPS, no dividend yield (US/HK stocks include dividend yield) |
These boundaries are not shortcomings; they are part of understanding a data service's true capability scope.
VIII. FAQ
Q1: Can TradingView's charting libraries be used commercially for free? Lightweight Charts is fully open source under Apache 2.0, can be used commercially but requires watermark retention; Advanced Charts and Trading Platform are proprietary licenses requiring enterprise application, personal projects prohibited.
Q2: Why does WebSocket show normal connection but data doesn't move? Industry-wide "silent freeze" issue. Connection is Open at the transport layer, but server stopped pushing data. Solution is to implement receive-side liveness check at the application layer.
Q3: Is Canvas or WebGL faster? Depends on scenario. Prioritize deeply optimized Canvas for 2D K-line charts; only consider WebGL for million-level data. Don't trust absolutist conclusions.
Q4: Which is better for backtesting—forward or backward adjustment? Backward adjustment or "true price backtesting mode" is more suitable for rigorous backtesting. Forward adjustment leads to unreproducible backtest results and overestimated returns.
Q5: What pitfall is easiest to hit in multi-market systems? Field standardization, timezone alignment, and trading calendar maintenance. Failure to handle these causes K-line gaps or look-ahead bias.
Q6: Can AI be used for real-time trading decisions? Currently not recommended. AI's reasonable positioning is as a sidepath analysis layer—pattern recognition, natural language queries, anomaly detection.
Q7: Self-build data layer or purchase? No standard answer. Single-market daily frequency can be self-built; multi-market real-time dashboards recommend purchase or hybrid; production-grade trading terminals need professional solutions. Key is to calculate hidden costs first.
References
- SEC Rulemaking Petition petn4-886, 2026. https://www.sec.gov/cgi-bin/correspondence
- RFC 6455 - The WebSocket Protocol, IETF. https://datatracker.ietf.org/doc/html/rfc6455
- TradingView Datafeed API Official Documentation. https://www.tradingview.com/charting-library-docs/
- TradingView UDF Protocol Official Documentation. https://www.tradingview.com/charting-library-docs/latest/connecting_data/UDF/
- TradingView Lightweight Charts Official Documentation. https://tradingview.github.io/lightweight-charts/
- WCAG 2.2 - Web Content Accessibility Guidelines, W3C. https://www.w3.org/TR/WCAG22/
- Exegy Market Data Fees Report. https://www.exegy.com/
- QuantConnect Lean.Brokerages.Tastytrade Issue #31 + PR #35. https://github.com/QuantConnect/Lean.Brokerages.Tastytrade/issues/31
- PetroSa TradeEngine Issue #609. https://github.com/PetroSa2/petrosa-tradeengine/issues/609
- Polymarket RTDS WebSocket Observations (DEV Community). https://dev.to/bluewhale-quant-lab/
- OpenBB Platform Architecture Documentation (Diogo Sousa). https://openbb.co/
- VNIBB Vietnam Financial Analysis Platform Architecture (Kohnnn). https://github.com/Kohnnn/vnibb
- ASSETS '21 Paper - Chart Accessibility Research. https://dl.acm.org/conference/assets
The difficulty of the data layer is not "whether it can connect," but "how to ensure every piece of data is correct at every point in time after connecting."
If this article can leave you with only one thing, we hope it's this four-dimension self-checklist:
- Connection Layer: Can subscription state automatically recover after disconnection?
- Data Layer: Is the adjustment basis explicitly recorded in the system?
- Cross-Market Layer: Are multi-market ticker identifiers unified?
- State Layer: Can the dashboard distinguish "snapshot state" from "realtime state"?
Go through these four questions for your project. You'll find that some of the parts that "appear to run" are actually just waiting for an alarm that will never ring.
This is some experience we've accumulated while building market data charting systems. If you're doing something similar, feel free to exchange ideas.