Smooth live prices
The price glides between updates, the live point pulses, the last digits are large, and a countdown shows when the candle closes.
Open-source chart for trading apps
One <trade-chart> tag gives you smooth real-time prices, candles, indicators, drawing tools and markers for your users' positions. Plain HTML, React, Vue, Angular or Blazor. Configure almost everything. 38 KB compressed, no dependencies.
Live prices from Binance's public market data (BTC, ETH, SOL). Drag to look back in time, scroll or pinch to zoom, use the drawing tools on the chart. Hover the chart for zoom, image download and full screen (or press F). Not investment advice.
The price glides between updates, the live point pulses, the last digits are large, and a countdown shows when the candle closes.
Candles, bars, Heikin-Ashi, line and area; SMA, EMA, Bollinger Bands, RSI, MACD and volume; trend lines, rectangles, Fibonacci and a measure tool; price alerts. No plugins to write.
Markers, entry lines, shaded zones and expiry lines with countdowns. Your app keeps its own trading logic.
A standard web component: plain HTML, React, Vue, Angular, Svelte or Blazor. Feed it from WebSocket, REST or SignalR.
Responsive from small phones to wall screens, with full screen on every device (iPhone included) and one-click image download. 38 KB compressed, no dependencies.
Sizes, fonts, axes, crosshair, legend, where the live price sits on screen, zoom limits, gestures, formats, every colour and every word, through one documented options object.
From npm, or straight from a CDN with a script tag.
npm i tickline
// in your app
import "tickline";
<!-- or, without a build step -->
<script src="https://unpkg.com/tickline@0.1.0/dist/tickline.umd.js"></script>
Give it a height. Attributes set the market name, timeframe, chart type, theme and indicators.
<trade-chart id="chart" symbol="BTC/USDT" timeframe="1m"
type="candles" theme="dark" indicators="sma:20, volume"
style="height: 480px"></trade-chart>
Load history once, then pass each live trade or quote. Times are milliseconds (UTC).
const chart = document.getElementById("chart");
// history: [{ time, open, high, low, close, volume }, ...]
chart.setData(await fetch("/api/candles?tf=1m").then((r) => r.json()));
// live: ticks are grouped into candles for you
socket.onmessage = (e) => {
const { time, price, volume } = JSON.parse(e.data);
chart.tick({ time, price, volume });
};
// more history when the user scrolls back
chart.addEventListener("tl-requesthistory", async (e) => {
const older = await fetch(`/api/candles?tf=1m&before=${e.detail.before}`).then((r) => r.json());
chart.prependData(older);
});
const now = Date.now(), entry = 68123.45, expiry = now + 60_000;
chart.addMarker({ time: now, price: entry, side: "buy", text: "$10" });
chart.addPriceLine({ price: entry, from: now, to: expiry, color: "#22c38e", label: "Entry" });
chart.addZone({ from: "top", to: entry, start: now, end: expiry, color: "rgba(34,195,142,.08)" });
const line = chart.addTimeLine({ time: expiry, countdown: true, label: "Expiry" });
chart.removeOverlay(line); // every add… returns an id
It's a standard custom element, so every framework can render it. Full examples are in examples/.
import { useEffect, useRef } from "react";
import "tickline";
export function PriceChart({ candles, lastTick }) {
const ref = useRef(null);
useEffect(() => { ref.current?.setData(candles); }, [candles]);
useEffect(() => { if (lastTick) ref.current?.tick(lastTick); }, [lastTick]);
return <trade-chart ref={ref} symbol="BTC/USDT" timeframe="1m" style={{ height: 480 }} />;
}
<!-- vite.config: vue({ template: { compilerOptions: { isCustomElement: (t) => t === "trade-chart" } } }) -->
<script setup>
import { ref, watch, onMounted } from "vue";
import "tickline";
const props = defineProps(["candles", "lastTick"]);
const chart = ref(null);
onMounted(() => chart.value.setData(props.candles));
watch(() => props.lastTick, (t) => t && chart.value.tick(t));
</script>
<template>
<trade-chart ref="chart" symbol="BTC/USDT" timeframe="1m" style="height: 480px" />
</template>
import { AfterViewInit, Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, ViewChild } from "@angular/core";
import "tickline";
@Component({
selector: "app-price-chart",
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `<trade-chart #chart symbol="BTC/USDT" timeframe="1m" style="height: 480px"></trade-chart>`,
})
export class PriceChartComponent implements AfterViewInit {
@ViewChild("chart") chart!: ElementRef<any>;
constructor(private prices: PriceService) {}
ngAfterViewInit() {
this.prices.history().subscribe((c) => this.chart.nativeElement.setData(c));
this.prices.ticks().subscribe((t) => this.chart.nativeElement.tick(t));
}
}
@* App.razor or index.html: <script src="_content/…/tickline.umd.js"></script> and tickline-interop.js *@
@inject IJSRuntime JS
<trade-chart id="chart" symbol="BTC/USDT" timeframe="1m" style="height: 480px"></trade-chart>
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
var candles = await Prices.GetCandlesAsync("BTCUSDT", "1m");
await JS.InvokeVoidAsync("tickline.setData", "chart", candles);
Prices.OnTick += t => JS.InvokeVoidAsync("tickline.tick", "chart", t); // e.g. from SignalR
}
}
// wwwroot/tickline-interop.js
window.tickline = {
setData: (id, candles) => document.getElementById(id).setData(candles),
tick: (id, t) => document.getElementById(id).tick(t),
};
The tickline-mcp server lets Claude, Cursor or VS Code read the docs, look up any setting, check your settings and write the integration code for your framework. Add it once:
claude mcp add tickline -- npx -y tickline-mcp
Setup for Claude Desktop, Cursor and VS Code: mcp/README.md.
One options object controls the chart: sizes, fonts, grid, axes, crosshair, legend, the live price, zoom limits, gestures, animation, formats, colours and every piece of text. Change something below and copy the code.
Every setting, its default and more examples (custom number and date formats, a sparkline, saving a user's chart) are in the configuration guide.
The most used parts. Everything else, with examples, is in the documentation and the API reference.
type | candles, hollow, bars, heikin-ashi, line, area |
timeframe | 5s, 1m, 15m, 1h, 4h, 1d, 1w, 1M … |
symbol | Name in the legend |
theme | dark (default) or light |
indicators | sma:20, ema:50, bb:20:2, rsi:14, macd:12:26:9, volume |
precision | Price decimals (default: from the data) |
price-line | short (default), full or none |
countdown | false hides the candle timer |
timezone | local (default) or minutes from UTC, e.g. 420 |
locale | de-DE, fr-FR, ja-JP … for numbers and dates |
font-size | Base font size in px (default 11) |
crosshair | snap (default), free or hidden |
grid | both, horizontal, vertical or none |
legend | false hides the legend |
min-visible-candles | Fewest candles you can zoom in to |
drawing-tools | false, or a list: trend, horizontal, fib |
controls | none hides all built-in buttons |
live-position | center, or x and y such as 0.8 0.5 |
watermark | Faint text behind the candles: {symbol} |
options | Any settings as JSON |
setData(candles) | Replace the history |
prependData(candles) | Add older candles |
tick({ time, price, volume }) | A live trade or quote |
update(candle) | A whole live candle (kline streams) |
addMarker / addPriceLine / addTimeLine / addZone | Overlays; return an id |
updateOverlay(id, patch) / removeOverlay(id) | Change or remove one |
setOptions(options) / options | Change any setting; read them all |
addIndicator(id, params, style) / removeIndicator(key) | Indicators in code, with colours, width, levels |
drawings / setDrawings(list) | Save and restore drawings |
setDrawingTool(tool) | line, trend, horizontal, vertical, rect, fib, measure |
zoomIn() / zoomOut() / goLive() / fitContent() | View |
setVisibleRange({ from, to }) / scrollToTime(t) | Show a time range |
addAlert({ price, label }) | Price alert; fires tl-alert |
downloadImage() / toBlob() | The chart as a PNG |
toggleFullscreen() | Full screen (also the F key) |
setLoading(true) | Loading placeholder |
getState() / setState(s) | Save and restore a user's chart |
tl-requesthistory | { before }: load older candles |
tl-crosshair | { time, price, candle } or null |
tl-click | A click on the chart |
tl-rangechange | Visible time range |
tl-followchange | Following the live edge or not |
tl-drawingschange | All drawings, to save |
tl-indicatorschange | The user closed an indicator |
tl-zoomlimit | Zoomed in as far as allowed |
tl-alert | { price, direction }: an alert was crossed |
tl-fullscreenchange | { fullscreen } |
Every colour is a CSS variable on the element:
trade-chart {
--tl-background: #0b0f17;
--tl-up: #22c38e;
--tl-down: #f0506e;
--tl-accent: #4c9aff; /* live price, buttons */
--tl-line: #4c9aff; /* line and area */
--tl-text: #7d8799;
--tl-grid: rgba(148, 163, 184, .07);
}
Keyboard: ← → pan, + − zoom, End back to live, Home fit all, Esc cancel drawing, Delete removes the selected drawing, F full screen.