Chưa phát hành
Đã có trên main, sẽ ra trong bản tớiCollected on main for the next release. Not on npm yet.
Mọi bản phát hành của biểu đồ, widget và các component framework, mới nhất ở trên.
Ghi chú phát hành được viết bằng tiếng Anh.
Collected on main for the next release. Not on npm yet.
On a phone the widget's toolbar ran off the screen: past the intervals, the chart type, indicators, alerts, settings, replay and the order buttons could not be reached, and their menus opened off the side. Now the widget lays itself out for its own width.
pageScroll, Tauri's content security policy and the chart's memory.React, Vue and Svelte components: 1.3.11. 1.28.0 was not published on its own: its changes, "Lighter on phones" below, came out in 1.29.0.
A finger now does on the chart what the mouse does, and a chart on a scrolling page can leave the scroll to the page.
click, barClick, signal markers); a double tap is a double-click: it resets an axis, or opens a drawing's settings.TOUCH_REACH).pageScroll (option and setPageScroll): a one-finger vertical swipe and the plain mouse wheel scroll the page; the chart pans with a sideways swipe and zooms with two fingers or Ctrl + wheel (a trackpad pinch). Presses on drawings, lines and axes stay the chart's.touchcancel), or a second finger landing for a pinch, calls the press off: no click, no coast, a dragged order or bracket line goes back where it was without an orderModify, and a drawing's point the press just placed is taken back. The browser's own long-press menu stays shut over the chart.Measured with the new phone run of the render benchmark (node scripts/bench-render.mjs --mobile: 390 × 844 at 3x, the CPU slowed 4x, an integrated GPU), one chart with four indicators: drawing the scene 34 → 25 ms, a live feed at two ticks a frame 18.5 → 11–13 ms, canvas memory 17.1 → 7.6 MB.
maxPixelRatio, setMaxPixelRatio, getMaxPixelRatio; Infinity for the screen's own): a 3x phone screen has 56% fewer pixels to draw, for lines barely softer. A change of ratio with the size the same (a window moved to another screen, the page zoomed) now resizes the canvases too. The finance charts take the same cap.pauseWhenHidden, on by default). An app embedding it in a WebView pauses it with suspend() and resume(); isSuspended() says whether it draws.IndicatorEngine.deferRecalculateFrom, flush).StreamManager.wake). A connection that drops schedules one retry, where the adapter's own disconnect and the heartbeat could each start one.appendBar, updateLastBar) rather than reloading the series, so the view stays where the user left it and only the end is recalculated; a new array is still a new series. Vue no longer walks every bar on each change, Svelte now sees bars pushed onto $state data, and both pass plain bars rather than their proxies.UI_ICONS, DRAWING_TOOL_ICONS, CHART_TYPE_ICONS, iconSvg), so the chart's own canvas draws it too: drawIcon(ctx, icon, x, y, size, color) in core. New icons: pin, search, enter, step back and step forward.widget.toast(message, kind, icon) takes an icon's name, shown before the text.features: { 'statusBar.rangeTimeframe': false } keeps the timeframe.minBarUnit (a chart option, or setMinBarUnit / getMinBarUnit) lets bar slots go from 0.25 px down to 0.02 px, so on a bare chart fitContent, zooming out and the 'All' range fit a long series, drawn a pixel column at a time. MIN_BAR_UNIT_LIMIT is exported.2024, Mar, 15, 13:00. At 5Y it reads 2022 · Jul · 2023 · Jul; on hourly bars over years it still shows the years, which it never did. The labels belong to bars, not to the screen: they move with their bars when panning and stay where they were when history is loaded in front, where they used to jump. Month names follow setNumberLocale; bars within a minute show their seconds (13:05:30), and labels wider than the spacing (a long month name, a formatter's own text) give way instead of running together. A time formatter now hears kind: 'year' | 'month' | 'day' | 'time'; 'date' is no longer given by the axis (deprecated): a daily bar's label is 'day', 'month' or 'year' by the step it starts.padPriceRange), so a series that rose more than about 13 times no longer drops to a linear scale when the padding went below zero.-5.00%, 0.00%, +5.00%) and round index points from the baseline, not prices turned into odd percents.barTimeUnit(data)) instead of guessed for each time. Millisecond bars before September 2001 are below 1e12 and were read as seconds, so after a 5Y view a scroll back dated them around the year 32,000: on the time axis, the crosshair, the tooltips, the measure tool, session breaks, data export and CSV import. barTimeUnit and MS_TIME_THRESHOLD are exported, and normalizeBarTime(time, unit) takes the series' unit.setVisibleRange, the zoom area and fitContent work on the bricks shown, not on the bars under them, and the crosshair reads the brick under it: on these types crosshairMove now gives the brick and its index in the series drawn (it gave the data bar at that index, which was another bar). Heikin-Ashi and the other one-bar-per-bar types still give the data bar.features: { selectionToolbar: false } turns it off.chart.getDrawingRect(id) gives the box a drawing covers on screen, cut to the plot (lines across the whole plot span it), or null when it is hidden or off screen: for hosts that put their own controls or notes over a drawing.fontFamily (chart.setLegend({ fontFamily }), the theme's font by default). Its volume and indicator rows are drawn at the OHLC size, and each value keeps its place while its digits change width, so the row no longer jitters on live ticks. In the widget the legend follows the look's font, at the size of the indicator rows listed under it.detectCandlePatterns(bars, options) finds 64 patterns of one to five bars, each on the bar it ends on: dojis, hammers, shooting stars, marubozu and belt holds; engulfing, harami, piercing line and dark cloud cover, tweezers, kicking, counterattack, necks and separating lines; morning and evening stars, abandoned babies, three white soldiers and black crows, three inside and outside, two crows, tri-stars, tasuki gaps, stick sandwich, advance block; three-line strikes, rising and falling three methods, mat hold, breakaways, concealing baby swallow, ladder bottom. Reversals count only after the trend they reverse; bodies are long or short against the average body before. CANDLE_PATTERNS lists them with names, short labels, direction and kind.candlepatterns indicator (134 indicators in all) draws an arrow under each bullish pattern and over each bearish one, with its short name once the bars are wide enough; a live bar recomputes only the last bars. Reversals, continuations and indecision can be turned on and off.22 more indicators, 133 in all, in the widget's indicator list too:
psy, with 25 and 75 marked) and New Psychological Line (newpsy), Volume Ratio (vr), AB Ratio (abratio, A and B lines), Intraday Intensity in percent (iii), Volume vs Average (volpct), Volume Change (volchange, each bar coloured against the one before), Trade Value (tradevalue, close × volume as an estimate of the value traded), Negative and Positive Volume Index (nvi, pvi, with an EMA signal).sonar), Sigma, the close in standard deviations from its average (sigma), Binary Wave, four trend votes from −4 to 4 (binarywave), Smoothed Rate of Change (sroc), Vertical Horizontal Filter (vhf), R-Squared (rsquared), Random Walk Index (rwi), True Range (tr).tsf), Weighted Close (wclose), the DeMark projected high and low for each bar (demark), and a moving-average Rainbow of up to 20 lines from a first period by a step (rainbow).CounterClockChart: average price against average volume, bar by bar, joined in time order and fading with age, with no time axis. getPhase() and counterClockPhase(points) name the last move's direction in eight phases, counter-clockwise from volume rising; counterClockPoints(bars, period) gives the points. Tooltip, onPointClick, a screen-reader label, dark and light themes.BarChart: values by category, series side by side (mode: 'grouped') or stacked with positives up and negatives down from zero, upright or sideways (orientation: 'horizontal'), coloured by sign for net flows (colorBySign), values on the bars (showValues). Hovering a category shows each series' value; onBarClick gets the category, series, value and index. Labels that don't fit along the bottom show every few categories.DonutChart: parts of a whole clockwise from twelve o'clock, a pie with innerRadius: 0, a caption and figure in the hole, each slice's share where it has room, a legend beside or under the ring; onSliceClick.ScatterChart: points on two value axes, bubbles whose area follows size, axis titles, labels, lines at zero; the axes leave room for the largest bubble; onPointClick.HistogramChart: values counted into bins (bins, binWidth on a grid from 0, or Sturges' rule), coloured by sign, with labelled marker lines (a mean, a VaR) that move a row down when they would overlap. histogramBins(values, options) is exported, and niceScale(min, max) for axes of your own.ariaLabel in your words) and draw in the dark or light theme; setOptions, update, setTheme, destroy. The other finance charts' canvases get the image role and setLabel too.CalendarChart: a value a day, YYYY-MM-DD, in week columns and weekday rows, a calendar year to a band and ten years at most (the oldest give way on a short chart); Monday to Friday with weekdays: 5, weeks from Sunday with weekStart: 0, month and day names in locale, a colour bar under it; onDayClick.MatrixChart: a coloured value for each row and column, square cells with the value printed where it fits, column names turned when they don't fit, half of a square matrix with triangle: 'lower' | 'upper'; onCellClick. correlationMatrix(series) gives Pearson correlations, pairing the positions both series have.HeatmapChart's grouped: true gathers cells by group, each group in a tile sized by its weights under its name and weighted change. The heatmap now describes itself to screen readers too (ariaLabel in your words).ColorScale is a type of its own, shared by the heatmap, calendar and matrix; a colour left out keeps the chart's default (the theme's up and down colours on the newer charts).Checked against the exchanges' rules of May 2025: the HOSE trading rules of 26 April 2025 and the HNX, UPCoM and VN30 futures rules.
MarketConfig.priceSteps: [{ from, step }]. HOSE shares step 10 dong below 10,000, 50 dong to 49,950 and 100 dong from 50,000; priceStepAt(price, market) and roundToPriceStep(price, market, 'nearest' | 'down' | 'up') read them.computePriceLimits rounds the ceiling down and the floor up to a valid price; a limit that would land on the reference moves one step off it, and a reference of one step keeps the floor there. chart.getPriceLimits() gives the day's prices.MARKET_HOSE (±7%, lots of 100, sessions from the 9:00 opening auction to the 14:30 closing one), MARKET_HOSE_ETF (ETFs and covered warrants, 10 dong at every price), MARKET_HNX (±10%, a closing auction and a post-close session to 15:00), MARKET_UPCOM (±15%, continuous trading 9:15–11:30 and 13:00–14:30) and MARKET_VN30F (VN30 index futures: 0.1 a point, ±7%, 100,000 dong a point, from the 8:45 opening auction). Each names timeZone: 'Asia/Ho_Chi_Minh'. Prices are in thousands of dong, as price boards show them; scaleMarketPrices(MARKET_HOSE, 1000) reads them in dong.vn30fLastTradingDay(year, month, isHoliday?) (the third Thursday, earlier when that is a holiday) and vn30fContracts(now) (this month's, the next, and the last months of the two quarters after: VN30F2611…).getCurrentSession(sessions, now, timeZone), by the time of day (weekends and holidays are the caller's); without a zone it reads this computer's clock, as before, and a zone it doesn't know gives null. Sessions can be postClose, and markets futures. The chart's session shading still comes from setSymbolInfo.chart.roundPrice and chart.getPriceStep(price?) use the step of the price's band (at the last close without a price), so prices from the chart's menus and the "+" by the price axis are valid ones. A symbol's minTick still comes first.VN_COLORS and createVNTheme use the price board's colours: green up, red down, purple ceiling, cyan floor, yellow reference. They were red up and blue down.MARKET_HOSE trades in lots of 100 (it said 10), HNX and UPCoM start continuous trading at 9:15, and UPCoM has no closing auction.MARKET_NYSE's after-hours session is postClose (it was preClose), and the preset names timeZone: 'America/New_York'.chart.setPriceLimits changed the market object it was given, so every chart sharing a preset got the same reference price. It now keeps its own copy.Preview builds: every pull request and every commit on main is published to pkg.pr.new (npm install https://pkg.pr.new/@tradecanvas/chart@<pull request or commit>), so a change can be tried before it is released. Shorter READMEs, in all six languages: what the library does, install, a quick start, frameworks, and links into the docs, which keep the API in detail. The npm page follows.
chart.requestRender() draws a frame soon, though nothing the chart knows of changed: after a plugin's own setting changed (an overlay's display mode, say), in a quiet market with no ticks to redraw it. The frame refits the scales to the data as a tick's would; requests before it make one frame, and a destroyed chart ignores them.enabled: false on an overlay plugin leaves it registered but undrawn; it is read every frame, so switching it and calling requestRender() shows the change at once.options on the React, Vue and Svelte components: chart options the props don't cover (maxBarSpacing, rightMargin, renderer…), merged over the component's defaults at mount; chartType, theme and autoScale still come from their props. A footprint chart's wide bars need a larger maxBarSpacing than the default 30.'100T') with a few bars, the empty space left of the first bar was labelled with times extrapolated from the bars' spacing ("01:44, 03:02…" before a market that opens at 9:00), and the crosshair showed one there too. Tick bars are not evenly spaced in time, so the time axis and the crosshair now give times to the bars only. The same holds on Renko, Kagi, point & figure, line break and range-bar charts. Time timeframes still label the empty future right of the newest bar. The Realtime page says so, in six languages.chart.getPriceStep(): the smallest step prices move in, the one roundPrice rounds to: the symbol's minTick, a fraction's unit (priceFormat: { denominator: 32 }), else one unit of the decimals prices show.overlayScale(context) for overlay plugins: in render(ctx, context), it places things where the chart draws them: timeToX / xToTime by bar time (between bars and past the newest in proportion), indexToX / xToIndex, priceToY / yToPrice on a log or inverted scale too, barWidth, and visible, the bars on screen (type FrameScale). Its bars are those of context.data, the series on screen: a Renko or Kagi chart's bricks, not the feed's bars. The plugin contracts don't change.@tradecanvas/chart for renderers that get the viewport alone (an indicator's own render, a chart type's renderer): priceToY, priceToYMapper, yToPrice, barIndexToX, xToBarIndex, timeToX, xToTime, timestampToBarIndex.overlayScale, and the API page getPriceStep, six languages; a recipe in the agent skill.pricePrecision or the market's ("22,000"). They all agree now, and the axis width is measured in those decimals. With a minTick, the scale's ticks stay on whole price steps: a stock that moves in 10 dong never shows 21,705.resolveSymbol once when it connects. An adapter that learns the symbol's details afterwards (on its first message, say) sends a symbolInfo event with the SymbolInfo, and the chart applies it: decimals, price step, hours. No need to remount a framework component to pass them. PollingAdapter subclasses can send it with emitEvent, as WebSocketAdapter ones already could.?ui=terminal or ?ui=capsule for such checks.React, Vue and Svelte components: 1.2.2. 1.15.0 was versioned but never published on its own; its changes (the second section below) ship in 1.16.0.
hsl(), oklch(), a name or with transparency shows in its swatch.styleChange and rendererChange events.trading.buyColor, .sellColor, .profitColor, .lossColor and .entryColor colour the orders and positions on the chart and their tags on the price axis, over the trading config's colours. markers.longColor, .shortColor and .neutralColor colour the signal markers, and tradeZones.profitColor, .lossColor and .activeColor the trade zones, over their own styles. drawings.handleColor fills a selected drawing's handles, white without it. The order ticket's and the bracket tool's previews take the trading colours too. Like the rest, they come on the host's layer or the user's, and read back with getStyleValue.widget.addHotkey({ keys: 'Alt+N', label: 'New note', onPress }). A shortcut runs while the widget was used last, not while typing in a field, composing text (an IME) or in a dialog; one without Ctrl or Alt only while focus is on the widget or nowhere. A letter is the one the keyboard layout types (Ctrl+Y is the Y key on a German keyboard too), by its place with Alt. It replaces a widget shortcut on the same keys; the chart's own keys (arrows, Delete, Ctrl+Z) still run, and addHotkey warns of those. It goes with the hotkeys switch and is listed in the shortcut sheet (?), which now leaves out the keys that do nothing (switched off, or a dialog the widget hasn't got) and the ones you replaced.--tcw-* variables, [data-tcw-part] (a part's switch names), [data-tcw-off] on the root, and [data-host-button] / [data-host-item] stay as they are through 1.x.chart.replayStart(), signal markers show once their bar is shown and trade zones once entered, each drawn open (no exit, no result) until its exit is reached. They all come back with replayStop(). revealMarks: false shows them all through the replay. The widget's replay does the same.startTime starts at the first bar at or after a time; hideHistory leaves the bars before the start out while the replay runs, so the chart shows only what it replays, and puts them back on replayStop().duration plays from where the replay is to the end in about that long, however many bars (30 days of 5-minute bars take as long as a day of them). A replay quicker than a bar a frame now runs on animation frames, several bars a frame, instead of a timer the browser holds to a few milliseconds a tick; a frame that comes late slows it rather than skip ahead.replayComplete ({ barIndex, time }) when the replay reaches its last bar, and replayStep now says how many bars the replay goes through (total).indicators takes { id, params, position } as well as ids; one whose inputs change is put back with them (syncIndicators, exported for your own components);data at mount a component opens no stream; stream={false} never opens one (and closes one it had), so a chart mounted before its bars arrive doesn't start the default Binance feed;features follows its changes after mount.replayRevealMarks: false shows them all, as before.hideHistory, the replay's indices (replayStep, getReplayBarIndex, a restart's startIndex) count from its start, a restart keeps the window, and indicators begin at the window's first bar.new ChartWidget(el, { features: { 'toolbar.replay': false, hotkeys: false } }), then widget.setFeatures({ sidebar: false }), getFeatures(), isFeatureOn(name). The names are listed in WIDGET_FEATURES and checked by TypeScript:alerts, settings, replay, symbolSearch, hotkeys, toasts…);toolbar.screenshot), a section of drawing tools (sidebar.fibonacci), a status bar item (statusBar.market), a button on an indicator's row or a pane (indicatorLegend.remove, pane.maximize), a menu entry (menu.chart.order, menu.drawing.lock), a key (hotkeys.tools).toolbar: false, drawingTools: false, statusBar: false, rangeBar: false, alerts: false and the rest work as before. The parts they leave out are now built and hidden, so they can be switched on later, and addToolbarButton puts a button on a toolbar switched off, to show with it (it used to return null with toolbar: false). drawingTools: false also switches the drawing settings off, and accountPanel: false the order ticket, as before. trading, watchlist, depthLadder and layouts stay options, as they bring data or storage; switches hide their buttons.addToolbarDropdown (a menu button, its entries asked for each time it opens), addSidebarButton, addStatusBarItem (text, or a button), and getSlot(name) for anything else: beside the toolbar's chart controls or its panel buttons, under the drawing sidebar's switches, on either end of the status bar, or over the chart. They follow the widget's theme and look.drawingMenuItems and indicatorMenuItems add entries at the end of a drawing's right-click menu and of an indicator's "more" menu, beside chartMenuItems.ChartWidgetGrid.setFeatures(patch), for the charts it adds later too.toasts off the widget's errors still show.chart.applyOverrides({ 'grid.vertical.visible': false, 'series.candlestick.upColor': '#26a69a' }). 89 keys, listed in CHART_STYLE_KEYS and checked by TypeScript:layer: 'host') stay through theme switches and are never saved; the user's (layer: 'user') win over them, are kept with the theme they were made on, and are saved with saveState() (snapshot version 3; version 2 saves still load). setOverrides puts a whole layer in place, resetOverrides takes keys away, getStyleValue(key) tells what a key resolves to, getStyle() gives the resolved looks, and styleChange says when they changed. getTheme() returns the theme as set, so setTheme(getTheme()) never bakes the overrides in.overrides option, ChartWidgetGrid.applyOverrides (every chart, and the charts it adds later), and an overrides prop on the React, Vue and Svelte components. The grid and crosshair options (grid.hLineColor, crosshair.vLine.style…), which were typed but did nothing, are now shorthand for their keys.updateIndicatorStyle(id, { plots: { signal: { lineStyle: 'dashed' } } })); a hidden plot shows no value tag and no legend value, and stretches no scale. setIndicatorDefaults(id, style) sets what every indicator of a kind starts with. setPaneStyle(instanceId, { background, separator }) gives a pane its own look, saved with its indicator and undone with it.rgb() and named colours (they appended a hex alpha to the colour);llms.txt say the charts draw with Canvas 2D or WebGL.type). Its name loses "(Classic)": its levels were the traditional ones all along.type: 'sma-ema').no and nn), Estonian, Malay and Persian join the 16, Persian right to left.rendererChange with { renderer: 'webgl', reason: 'contextRestored' }. Before, it stayed on Canvas 2D for good.rendererChange with reason: 'limit') and takes WebGL as soon as another chart lets go of its context, as a chart whose context was lost does. setMaxWebGLCharts(n), from the package, sets the limit (Infinity for none).WEBGL_debug_renderer_info: the GPU's name comes from RENDERER where that gives one.renderer: 'webgl', the depth heatmap, the volume profile and the market profile draw with WebGL too, under the bars, with volume under a profile as in Canvas 2D. A market profile showing its stats readout or TPO letters stays with Canvas 2D: text can't go between the GPU's background and its bars. The 2D canvas under the GPU's is now only there for that, a watermark, or what the GPU can't take.chart.pushDepthSnapshot(book, time) stamps a heatmap snapshot at time, to fill in books recorded earlier, oldest first; without time it takes the latest bar's, as before.pnpm bench:render has two more scenes: zoomed out on 1,000,000 bars with four indicators, and a depth heatmap.Everything since 1.7.0 in one release (1.8.0 and 1.9.0 weren't published on their own).
renderer: 'webgl' draws the plot and indicator panes with WebGL 2, on a canvas under the 2D scene: the grid, session shading and break lines, candles and volume directly, and indicators, indicator panes, compare lines and most other chart types (line, area, bars, Heikin-Ashi, hollow candles, Renko, line break, range bars, HLC area, step line, line with markers, equivolume, high-low) through their Canvas 2D drawing, recorded as GPU strokes, fills and rectangles, antialiased at their edges as Canvas 2D is. Text, drawings, orders, axes and the crosshair stay on Canvas 2D. 'auto' takes WebGL only on a hardware GPU; Canvas 2D stays the default.chart.setRenderer(mode) switches at runtime and resolves to what draws, chart.getRenderer() says which, and rendererChange reports each change and why ('unsupported', 'contextLost'). Where WebGL 2 is missing, or its context is lost, the chart carries on with Canvas 2D; a failure to load or compile is logged as a warning. The WebGL code is a chunk of its own (about 17 KB gzipped), loaded on first use, and a destroyed chart hands its GPU context back at once. Screenshots include what the GPU drew.render draws on the recording context like on any other, and falls back to Canvas 2D by itself where it needs to. Its render may then run twice in a frame, so it should draw without side effects. Text it draws goes on the 2D canvas, over the lines of the indicators drawn with it.SessionBreaks.render() still draws lines and labels; renderLines() draws the lines alone.pnpm bench:render --renderer=webgl (or --renderer=auto) measures the WebGL path.pnpm bench:render measures frames in headless Chrome on your own GPU, scene by scene.volumeUp / volumeDown, which were ignored. A theme that only changes the candle colours (or the widget's settings) gets volume in those colours. volumeColor(candleColor) gives the matching shade.candleUp / candleDown on the theme to keep the old candle colours.Everything since 1.4.0 in one release (1.5.0 and 1.6.0 were never published): the drawing toolkit, trading and workspace, panes and undo, alerts and replay, scales and comparisons, the widget's look, and watchlists, symbol info, tick charts and access. Wrappers (@tradecanvas/react / vue / svelte) 1.0.10 pick up the new core. The widget looks different after the update: Studio is the new default look (see *The widget's look* below); ui: 'terminal' is the closest to a square, ruled look.
100T for a bar per 100 trades, built from the feed's trades (Binance streams them).recordUndo adds changes of your own.ui option or setUI, on one chart or a whole grid.chart.setShapes).ui they stay yours to set in CSS (the widget's stylesheet now goes first in the page, so your rules win a tie). Menus grow to fit their labels instead of wrapping them.ui: 'terminal' for a square, ruled look, or set any token yourself.110'165), on every label of the price scale — axis, crosshair, legend, tooltips, orders, alerts, drawings (priceFormat). Times your way too (timeFormatter).addAlert's options.replayStart({ steps })).connect()ed feed sends, not only on setCurrentPrice.signalMarkerHover, signalMarkerClick).setPaneScale).chart.moveIndicatorToPane. The pane's other indicators stay, and those reading its lines follow it.setPaneCollapsed, setMaximizedPane, movePane). Saved layouts keep each pane's size, order, fold and maximise.5, 15m, 1h, 1D, Enter), and Alt+T / H / J / V / C / F for the trend line, horizontal line and ray, vertical line, cross line and Fibonacci retracement.chart.roundPrice).positionReverse among them), so a connected adapter acts on them; an adapter without reversePosition gets a close and a market order the other way. Orders take a stop-loss, a take-profit and a time in force that carry over to the position. Adapter authors: PositionModifyIntent.stopLoss / takeProfit can now be null, meaning remove it (a missing field still means keep it).executionFill says why (order, close, reverse, stop-loss, take-profit) and the P&L realised.LayoutStorage.widget.addToolbarButton() and chartMenuItems.ChartGrid.connectAll with one adapter object sent every chart the last symbol's bars (an adapter keeps one stream). It now takes a function that makes one per chart, as does ChartWidgetGrid's adapter option.ordersChange, positionsChange and stateChange. With several widgets on a page, Ctrl/Cmd+K and +P now go to the one used last instead of all of them.getDrawingOptions / setDrawingOptions, updateDrawing, beginDrawingEdit / endDrawingEdit, setDrawingToolDefaults.addDrawingAlert(id, …) fires when the price crosses a trend line, ray, horizontal line or a channel's lines where they are by then, as drawn on the chart (log scale included). The alert goes with its drawing and comes back with it on undo.fetchHistoryBefore(symbol, timeframe, before, limit); Binance, Bybit and Mock do, and WebSocketAdapter / PollingAdapter take it as an option. A connected chart loads a page when less than a screen of bars is left of the view, one request at a time, keeping the same bars on screen; it backs off after failures, and a reconnect keeps the paged-in history. New chart.prependBars(), setHistoryLoader(), loadMoreHistory(), hasMoreHistory(), isLoadingHistory(), the historyLoad event and StreamConfig.historyPageSize. ChartWidget shows a small pill while older bars load.TimeFrame takes any whole count of a unit ('7m', '90m', '2d'); parseTimeframe / isTimeFrame check one. Adapters list the timeframes their feed serves (supportedTimeframes), and the chart builds the others from the coarsest one that divides them: history, live bars and older pages alike (ResamplingAdapter, withResampling, resampleBars, pickBaseTimeframe). This also fixes Binance's 45m, 2d, 3M and other intervals it lacks, which used to fall back to 15m bars; a timeframe the feed cannot build is refused instead. TimeFrame now accepts any ${number}${unit}: exhaustive switches over it should use KnownTimeFrame. ChartWidget's timeframe menu takes a typed interval (7, 90, 2h, 3D), keeps it with a star and an × to remove it, and lists it in the command palette.chart.setTimezone('America/New_York') (or a fixed offset, or null for the browser's) and ChartOptions.timeZone set the zone for the time axis, crosshair, tooltip, day breaks, range presets and go-to-date. Day breaks used to follow the browser's zone whatever the chart showed. Session hours take a timeZone too. ChartWidget's settings list 28 zones by city with the offset in force. YTD now starts on the 1 January bar.addIndicator(…, { scale: 'left' }), setIndicatorScale), fit to its values, so a volume-like line no longer squashes the candles; setLeftPriceScaleVisible(true) mirrors the price scale on both sides. ChartWidget has a switch in its settings and a choice in each overlay's Style tab.searchSymbols and resolveSymbol. They return a symbol's name, exchange, price precision and step, exchange time zone and trading hours. Binance does.setTimezone('exchange') follows its exchange, and its hours feed the session shading.@tradecanvas/chart/widget/locales so a page loads only what it imports. Number formats for each language are in the settings.touch-action: none on its surface, so the page doesn't zoom instead.plots), their pane scale, default levels and how inputs are edited; a custom indicator needs no rendering code.indicatorSource(instanceId, key)), e.g. an SMA of RSI in RSI's pane./ja/docs/api/), with hreflang links and a sitemap covering every language.skills/tradecanvas: an agent skill with entry points, rules, recipes and a generated indicator reference.llms.txt and llms-full.txt on the site; the indicator catalog on the docs site is generated from the registry, and CI checks that the generated files are current and that the skill's examples type-check.Layouts that come back whole, ranges and dates, scale options, indicators listed on the chart, an icon set and reworked bar replay. Wrappers (@tradecanvas/react / vue / svelte) 1.0.7 pick up the new core.
setVisibleRangePreset('1D' … 'All') and goToTime(); ChartWidget shows the presets under the chart with a go-to-date button (Alt+G).setInvertScale, Alt+I), and the log scale now applies to candles, axis and grid as well as drawings and indicators.drawingMagnet, barCountdown, compareSymbols, dataExport, logScale, timeframes).createIcon, createToolIcon, createChartTypeIcon and the icon maps are exported.replayStop(); the replay ends paused on its last bar. ChartWidget asks for a start bar, offers a random bar, speeds in bars per second and "Back to realtime".Dragging that never sticks, a lighter two-canvas renderer, the TradeCanvas look by default, a redesigned gauge, crosshair tooltip and loader, and replay that follows its newest bar. Wrappers (@tradecanvas/react / vue / svelte) 1.0.6 pick up the new core.
panLimits.minVisibleBars; the time axis, grid, crosshair and drawings (magnet included) extend into the future. freePan: false keeps the old clamp. setVisibleRange() / fitContent() show exactly the requested range.features.tradingContextMenu: true to opt in).LayerType.Hover.DARK_THEME / LIGHT_THEME, indicators, orders, signal markers, drawing tools and the ChartWidget chrome share one palette — ink ground, mint/coral candles, amber accent. New TC_PALETTE and TC_SERIES_COLORS. Tests keep theme-neutral colours at 3:1 on both backgrounds and widget text at 4.5:1.pointer: 'needle' keeps the classic needle. New defaults: 240° sweep, thickness 0.14.0.00), locale, timezone and bar spacing; stays inside the plot. New setLocale, setPricePrecision, setTimezoneOffset, CrosshairTooltipContext and formatTooltipTime.ChartWidget): a run of candles lit one after another over a sweeping accent line; the "Loading chart…" text is for screen readers only (shown under reduced motion and on failure).More indicators and drawing tools, and a performance pass measured in a live browser profiler. Wrappers (@tradecanvas/react / vue / svelte) 1.0.5 pick up the new core.
IndicatorPlugin.update(); built-ins recompute only the forming bar (a tick with 4 indicators at 100k bars: ~96 ms → ~0.001 ms).IndicatorValueMap — array-backed drop-in Map for indicator values; full recalculation on a symbol/timeframe switch is ~2.5–3.5× faster.Viewport.getState() snapshots are cached instead of deep-cloned 8–10× per frame; indicator auto-scale scans only the visible range; Intl number/date formatters are cached (hover frames ~0.64 → ~0.23 ms).pricePrecision (or the axis's precision) in the configured numberLocale — PEPE-class prices no longer show "0.00".StreamManager, ChartWidget, Chart.connect); adapters detach every socket handler on disconnect.setData resets auto-scale so each switch fits the new data.locale / messages i18n with built-in Vietnamese; numberLocale fixes; watchlist refPrice precedence.widget.toggleReplay() opens bar replay from code.chartOptions.features no longer wipes the other feature defaults.Patch release for the core packages, plus the first public release of the framework wrappers.
visibleRangeChange now fires — along with the sibling priceRangeChange and zoomChange events. All three were typed and documented but never emitted anywhere in Chart; they now fire on every pan, zoom, resize, and data update, but only when that slice of viewport state actually changed. visibleRangeChange payload is { from, to } bar indices, priceRangeChange is { min, max }, zoomChange is { barWidth } CSS pixels per bar.@tradecanvas/react, @tradecanvas/vue, and @tradecanvas/svelte are now public (1.0.0) — thin reactive <TradeCanvas> components around @tradecanvas/chart with props for symbol / timeframe / theme / chart type / indicators / data / adapter / signal markers / trade zones, lifecycle cleanup, and access to the underlying Chart via onReady / ref / bind:chart. Pinned to @tradecanvas/chart@^1.First stable release — everything an open-source trading chart needs, batteries-included and zero-dependency. The public API is now semver-stable for the 1.x line. Cumulative since 0.9.0 (0.10–0.14 added 30+ indicators and several chart types); the headline additions:
WebSocketAdapter (live + REST history) and PollingAdapter (REST-only feeds) let any source plug in with ~20 lines: a URL and a parse function. The base handles the connection lifecycle, reconnect, decoding, and event emission.chart.connectExecution(adapter) turns the display-only trading overlay into a real trading surface. The chart routes its order/position intents into an ExecutionAdapter and renders the authoritative orders/positions it reports back (the adapter is the single source of truth). Ships a PaperExecutionAdapter sandbox; failures surface on a single executionError event.chart.startOrderDraft(side) drops a draggable order line; drag it to a level and confirmOrderDraft() places it (limit vs stop inferred from the level relative to the current price).registerPlugin, per-chart via new Chart(el, { plugins }), or imperatively via chart.plugins.register. Custom chart types and overlays render through the engine.pnpm bench (vitest bench). Downsampling 100k points to 1,600 runs in ~0.32 ms.The DataAdapter, ExecutionAdapter, and Plugin SDK interfaces (IndicatorPlugin / DrawingPlugin / ChartTypePlugin / OverlayPlugin), plus the chart event names and payloads, are now semver-stable for the 1.x line.
chart.replay() is renamed to chart.replayStart(), for consistency with replayPause / replayResume / replayStop / replaySeek.Major UX upgrade — pro-grade interaction across the board, with new analytics depth and workflow features.
Esc unpins.ns-resize over the price axis strip, ew-resize over the time axis strip.Ctrl/⌘+P) opens a fuzzy-search picker over the configured symbols[]. Replaces the previous click-to-cycle behaviour.?): categorized keyboard-shortcut reference.chart.replay*() API.watchlist: true): right-side panel listing the widget's symbols with last price, % change, and a mini sparkline. Click a row to switch chart.persistLayouts: true): per-symbol indicator stack, drawings, alerts, and chart type persist to localStorage. Switch symbol → state is auto-saved; switch back → state is restored. widget.clearSavedLayout(symbol?) resets.dragDropImport: true, default): drop an OHLCV file onto the chart and it loads instantly. Toast feedback for success / failure. New parseOHLCV exported for programmatic use.widget.toast(msg, kind?) for transient feedback.connected status pulse, refined scrollbars, reduced-motion support.chart.setVolumeProfileVisible(true).Mar 4, Apr '26, 2027). Auto-suppressed on daily-or-coarser timeframes.@tradecanvas/analytics — smaCrossStrategy, rsiReversionStrategy, donchianBreakoutStrategy, bollingerReversionStrategy. All return a StrategyFn ready to feed Backtester.run().runMonteCarlo(initialCash, trades, opts) shuffles realised trade order N times to expose path-dependence. Returns per-step P5/P25/P50/P75/P95 equity bands, final-equity percentiles, probability-of-profit, and worst-case max drawdown. Deterministic with a seed.Viewport.scalePriceRange(factor) and AxisDragHandler for custom axis interactions.MeasureOverlay and PinnedTooltip exports from @tradecanvas/core.VolumeProfileRenderer export from @tradecanvas/core.parseOHLCV, DragDropImporter exports from @tradecanvas/chart.ReplayManager.seekTo now emits a synchronous bar event so seeking while paused immediately repaints the chart slice.dragDropImport is enabled by default in the widget — pass false if a parent surface already handles drops.SessionBreaks auto-suppresses its lines on daily-or-coarser timeframes so weekly charts don't paint a wall of separators.features.tradingContextMenu flag is now wired through to the right-click handler. Previously the flag existed in FeaturesConfig but InteractionManager always called preventDefault() and routed to the trading manager regardless of whether the custom menu would render. The result: projects setting tradingContextMenu: false (or any config that kept trading on but the menu off) suppressed the native browser context menu without showing a replacement, leaving users with no right-click affordance at allChart.ts now passes features.tradingContextMenu into the TradingManager config (contextMenu.enabled)InteractionManager.onContextMenu only calls preventDefault() when the trading context menu actually opensProjects that don't use trading can now opt out cleanly:
new Chart(host, { features: { trading: false } }) // no trading at all
new Chart(host, { features: { tradingContextMenu: false } }) // keep orders/positions, drop the menuIn both cases, native browser right-click works on the chart as expected.
@tradecanvas/analytics package with a bar-by-bar Backtester. Strategy fn runs at close of each bar; orders placed on bar N fill on bar N+1 (market → next-bar open, limit/stop → when the next bar trades through the trigger). Returns fills, closed trades, equity curve, and summary metricsPortfolio class tracks cash, one net position, realized P&L, and the equity curve. Supports partial closes and position flips with correct realized-PnL accountingcomputeRiskMetrics() returns Sharpe, Sortino, Calmar, CAGR, max drawdown, win rate, profit factor, expectancy, average win/loss. Periods per year auto-detected from equity-curve timestamps; configurable risk-free rateFixedCommission, PercentCommission, PerShareCommission, NO_SLIPPAGE, PercentSlippage, RangeBasedSlippage. Pluggable via Backtester constructorReplayController in @tradecanvas/core. Decoupled from Chart so it can drive both UI playback and headless backtests. Supports start / pause / resume / step(n) / seek(index) / setSpeed(barsPerSecond) with typed bar / finished / stateChange eventschart.setChartType('equivolume')fibTimeZones tool draws vertical Fibonacci-interval lines (1, 2, 3, 5, 8, 13, 21, 34, 55) projected from a two-anchor time span. Useful for forecasting future swing points based on prior cycle duration/, /docs/*, /examples, /playground, /changelog, /embed) using @sveltejs/adapter-static. SEO-friendly per-page metadata, sitemap, robots.txt/docs/analytics includes a runnable backtest panel (SMA(10/30) cross on 365 days of deterministic synthetic data) with equity-curve canvas, drawdown shading, play/pause/scrub controls, and 8 live metric tiles@tradecanvas/analytics exporting Backtester, Portfolio, computeRiskMetrics, FixedCommission, PercentCommission, PerShareCommission, ZERO_COMMISSION, NO_SLIPPAGE, PercentSlippage, RangeBasedSlippage, plus StrategyContext, StrategyFn, BacktestResult, RiskMetrics, Fill, ClosedTrade, PortfolioPosition, EquityPoint, Side, OrderType, OrderStatus, TimeInForceReplayController exported from @tradecanvas/core and @tradecanvas/chart along with ReplayBarEvent, ReplayEventMap, ReplayOptions, ReplayStateChangeEvent, ReplayStatusEquivolumeRenderer exported from @tradecanvas/coreFibTimeZonesTool exported and auto-registered by registerBuiltInDrawingTools'equivolume' added to ChartType; 'fibTimeZones' added to DrawingToolTypeCHART_TYPES) lists Equivolume@tradecanvas/analytics package ships with 22 unit tests across Backtester, Portfolio, and RiskMetricsReplayController test suite (8 tests) covering step, seek, start/pause/resume, fake-timer playback, and startIndexpersistDrawings is enabledChartGrid class for 2/4/6 synchronized charts with linked crosshairs and shared time axis. Layouts: '1x2', '2x2', '2x3' and more. connectAll() for bulk data source bindingCtrl+K / Cmd+K quick search inside ChartWidget for indicators, chart types, drawing tools, timeframes, and actions (screenshot, theme toggle, settings). Keyboard navigation with arrow keys and EnterAlertManager.render() now wired into the overlay layer. Alert lines show bell icon, price label, and triggered/pending color states on the live chartchart.addSignalMarker() renders directional arrows on the overlay with confidence-based sizing, per-source color coding, and labels. Designed for bot/signal trading integrationschart.addTradeZone() visualizes entry→exit rectangles with P&L coloring, direction badges, and exit labels. Supports active (open) and closed trades with updateTradeZone()chart.getData() — public access to raw OHLC data serieschart.setCrosshairPosition(point) — programmatic crosshair placement for cross-chart syncChartGrid.connectAll(adapter, symbols, timeframe) — bulk connect all grid cells to live dataChartGrid.setLayout(layout) — dynamically switch grid layout, adding/removing charts as neededchart.addSignalMarker(marker) / setSignalMarkers() / clearSignalMarkers() / setSignalMarkerStyle()chart.addTradeZone(zone) / updateTradeZone() / setTradeZones() / clearTradeZones() / setTradeZoneStyle()signalMarkerAdd, signalMarkerRemove, tradeZoneAdd, tradeZoneRemoveSignalMarker, SignalDirection, SignalMarkerStyle, TradeZone, TradeZoneDirection, TradeZoneStyleanchorTime'rangeBars') — fixed price-range bars (each bar's high − low equals a configured range), with toRangeBars transformTradingPosition.closedQuantity — partial-close visualization as a left-edge dim band proportional to closed fractionTradingConfig.pnlThresholds: PnLThreshold[] — multi-stop color gradient driven by live P<radingConfig.positionLabel: string | (ctx) => string — token templating ({side}, {qty}, {openQty}, {closedQty}, {entry}, {price}, {pnl}, {pnlPct}, {pnlSign})IndicatorWorkerHost and bundled indicator.worker.js register all 33 built-in indicators. Promise-based host.calculate(...) offloads heavy compute off the render loop. Pass null for the worker to use the synchronous fallback (SSR, tests, safety net). Per-request timeout, ping() health check, terminate() cleans up pending workgetNumberParam / getIntParam helpers exported from @tradecanvas/coreBinanceAdapter REST and WS payloads now flow through typed parseRestKline / parseWsKline validators; any[] and any casts are goneTextAnnotationTool text resolution moved to a pure resolveAnnotationText helper (no more as string cast)ChartStateManager.deserialize validates the parsed shape — malformed drawings/orders/positions/indicators are filtered, missing or wrong-typed viewport fields fall back to defaultsWidgetStyles.ts (764 LOC of CSS-in-string) extracted into a sibling WidgetStyles.css file, imported via Vite's ?raw query and inlined at build time. The TS shim is now 31 LOC; CSS is editable as CSS (syntax highlighting, linting). Public API (injectWidgetStyles / removeWidgetStyles) unchanged.createChartRenderer and getDisplayData) extracted from Chart.ts into a new ChartTypeStrategy module with createRendererFor, transformDisplayData, and isTransformedChartType helpersAutoSaveScheduler (testable state machine, owner injects the save callback)computeIndicatorPriceRange (now correctly skips NaN/Infinity values)timeframeToMs private method from Chart.ts; uses the shared helper from @tradecanvas/commonsChart.ts: 1632 → 1536 LOC@tradecanvas/core and @tradecanvas/chart)DrawingManager CRUD, Binance kline parsers, ReconnectManager backoff/cap/give-up, TickAggregator (tick + pre-formed-bar paths, ring buffer eviction), ChartState JSON + localStorage round-trip + validator, IndicatorWorkerHost (worker + fallback), ChartTypeStrategy, AutoSaveScheduler, IndicatorPriceRange.github/workflows/test.yml (Node 20.19, pnpm 9.15, builds commons, runs pnpm test)pnpm test runs both packages@tradecanvas/chart/widget subpath export. One-line embed with complete toolbar, drawing sidebar, settings modal, and status bar. Zero framework dependencies, CSS scoped with tcw- prefixchart.on('orderModify', e => e.payload.orderId) infers payload type via ChartEventMap. Includes OrderModifyPayload, PositionModifyPayload, OrderPlacePayload, and 10 more typed payloadsnormalizeBar({ t, o, h, l, c, v }) converts wire format to OHLCBar. normalizeBarTime() auto-detects seconds vs millisecondschart.setTimeframe(tf) — switch timeframes on active stream without destroy/rebuildchart.appendBars(bars) — bulk append for reconnect catch-up (single indicator recalculate)chart.setStatusText(text) — display status in chart legend area (e.g. "LIVE · 8ms")chart.setCurrentPrice(price, pulseColor?) — optional pulse colorDARK_TERMINAL theme — fintech terminal palette (#0E0E0E bg, #00FF87/#FF3B4D candles, monospace font)DataAdapterEventType exported — adapters can implements DataAdapter with full type safetyDataAdapter interface clarifying observer pattern (reconnect to switch)OHLCBar.time documented as millisecondsnumberLocale option on Chart (BCP 47 format like 'en-US', 'de-DE', 'vi-VN')'en-US' → 65,234.00'de-DE' → 65.234,00'vi-VN' → 65.234,00chart.setNumberLocale('de-DE')KeyboardHandler was constructed then discarded with void new. Arrow keys, +/-, Home/End, and Space did nothing. Now properly wired to window with focus-aware handling (ignores keystrokes in inputs/textareas)updateLastBar() and updateLastBarFromTick() now recalculate indicators on every tick. Moving averages, Bollinger Bands, RSI, etc. now update in real-timeformatPrice() now uses toLocaleString for readability (65,234.00 vs 65234.00)getBoundingClientRect cached in InteractionManager — was called on every mousemove. Now invalidated only on pointerdown, resize, and scroll. Major reduction in layout flushes during pan/crosshairstructuredClone replaced in drag/resize hot paths with hand-rolled shallow clone (5-10x faster for DrawingState shape)type always wins)setValue() animation with easeOutCubicsetValue calls)beginPath+stroke loopsSparklineChart — tiny inline line/area chart for dashboards and KPI cardsDepthChart — bid/ask order book visualization with cumulative volume areasEquityCurveChart — portfolio equity with drawdown shading and benchmark comparisonHeatmapChart — colored cell grid with treemap layout (weighted by market cap)bind:this in {#each}version and release scripts (npm reserved lifecycle names)addIndicator() now calls updateViewportAndRender() for panel indicators so they appear immediatelynpm publish which doesn't convert workspace:*. Now using pnpm publish for proper version resolutiontc_sma_1), drawings (tc_drawing_1), alerts (tc_alert_1) to prevent collisionsViewport.setPriceRange() — new public method for direct price range controlIndicatorEngine.getOverlayPriceRange() — compute min/max of visible overlay indicator valuespublishConfig.access: "public" for scoped packagesprepublishOnly build scriptsInitial release of @tradecanvas/chart, @tradecanvas/core, and @tradecanvas/commons.