MMashDiv

Chart Engine 1.1.2

Pre-release

This version isn’t published on updates.mashdiv.com yet — the notes are available to preview, but it can’t be downloaded until it’s released.

24 August 2026

This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.

DRAWING TOOLSCHART STYLESSMART MONEYPRICE SCALELOG SCALESESSION AXISDATA WINDOWALERTSMULTI-SELECTPANE DRAWINGSINDICATOR SOURCEORDERSTOUCHWORKSPACE SYNCHISTORYDEFECT FIXES

Chart Engine v1.1.2

Release Date: August 24, 2026 Tags: DRAWING TOOLS, CHART STYLES, SMART MONEY, PRICE SCALE, LOG SCALE, SESSION AXIS, DATA WINDOW, ALERTS, MULTI-SELECT, PANE DRAWINGS, INDICATOR SOURCE, ORDERS, TOUCH, WORKSPACE SYNC, HISTORY, DEFECT FIXES

Overview

This release is the result of surveying the whole chart against what a best-in-class terminal does, and the survey's finding was not the one expected: the indicator catalogue was never the problem. 227 indicators, 138 chart patterns, signals, divergence, multi-timeframe, replay, alerts and a strategy builder were all already shipping. What was thin was the layer between that catalogue and the trader - and a surprising amount of it turned out to be code that was written, exported, and reached by nothing.

Two catalogues were thin, and both are now filled in: the drawing tools go from 73 to 132, and the chart styles from five to eighteen.

So the bulk of what follows is not new capability. It is capability that existed and had no way in: a 520-line order renderer gated on a prop no mount site passed; a 1,052-line templates panel with no button; seventeen per-tool setters with zero callers; two flags the engine honoured in four places and nothing could set. Where a control looked live and did nothing, that is called out as a fix rather than an addition, because that is what it was.

Two of the findings were outright defects, and they are first.

Update Instructions

pnpm updator

Chart Engine is delivered as a single prebuilt bundle, and pnpm updator replaces that bundle and rebuilds the frontend around it. No configuration change, no data change, no seeding.

If you have customised the chart engine's own source, rebuild the bundle before the frontend build:

pnpm build:chart-engine

Upgrade Notes

A new table: chart_workspace

The chart's saved state now has a durable server-side copy, so a workspace follows the account to another machine instead of living only in one browser.

  • Added - one table, chart_workspace, holding opaque key/value pairs per user. No migration is required: the model file is picked up by the existing auto-sync on boot.
  • Two routes, both user-authenticated: GET /api/user/chart-workspace and PUT /api/user/chart-workspace. No admin surface and no configuration.
  • The server never parses what it stores. Only the client can interpret 132 drawing schemas, and a server that half-understood them would be a second place for them to rot.
  • Nothing blocks on it. A chart that cannot reach the server behaves exactly as it did before there was one.

Saved chart state is now scoped to the signed-in account

Everything the chart persists — drawings, indicators, templates, alerts, indicator favourites, saved strategies, the journal, the settings sheet and the per-symbol viewport — used fixed browser storage keys. On any machine used by more than one account, the second account to sign in opened the chart onto the first one's markup.

  • Changed — every key is scoped to the user id. Two accounts sharing a browser now have two workspaces.
  • Anything saved before this update keeps working. With no account resolved — signed out, still hydrating, a public page — the keys are exactly what earlier builds wrote, so nothing is orphaned. The existing set becomes visible to whoever signs in first after the update and stays with them.
  • Scoping is the browser-side half. The server copy above is what makes a workspace follow an account to another machine.

Four more timeframes

2h, 6h, 12h and 3d are added. Every market type's backend already served all four — the client's vocabulary was the narrow end of the pipe.

  • 8h is deliberately not offered: the spot exchange serves it and the ecosystem, forex and DEX feeds do not.
  • 1M is deliberately not offered: a calendar month has no fixed millisecond length, and the client's viewport arithmetic divides by one.

The in-memory candle limit is now 20,000

It was 2,000 — a day and a half of 1-minute bars, crossed by any trader panning back through a session. Combined with the paging fix below, backward paging now works over a useful range. The renderers slice to the visible window before drawing, so the cost of a held bar is the six numbers it occupies.

Compatibility

  • One new table (chart_workspace, above) created by the existing boot-time auto-sync, and two new user routes. No configuration change, no seeding, and nothing existing is altered.
  • One new optional integration prop, workspaceId. Omitting it leaves the previous behaviour, minus the account scoping.
  • onPlaceSpotOrder and spotOrders were already declared on the component and are now genuinely used. A host that passes neither sees no trading affordance on the chart, exactly as before.

Fixed

The magnet recorded a different price from the one it drew

The Magnet button highlighted itself and did nothing. DrawingEngine.snapEnabled defaults to true and its snap context defaults to null, and snapping bails when either is missing — so with the magnet on, no drawn point snapped, and with it off, nothing was being disabled. The two lines that wire it were written in a 1,055-line hook that is exported from the package barrel and mounted by nothing.

Meanwhile the crosshair snapped unconditionally. The chart drew the crosshair sitting exactly on a wick high and then stored the raw pointer price in the drawing — the number on screen and the number recorded were different, which is the one thing a measuring tool must never do.

  • Fixed — the engine is wired, and the crosshair's snap follows the same toggle. Either everything under the pointer snaps to the bar or nothing does.

Paging back through history discarded the history it had just fetched

prependCandles merged the fetched batch with the held array, sorted ascending, and trimmed with slice(-2000) — which keeps the newest bars, i.e. exactly the batch of old bars it had just spent a request downloading. Once the array reached the cap the action was a guaranteed no-op: every prepended bar was dropped, the oldest-loaded cursor came back off an unchanged array, and the chart re-issued the identical request on the next pan, forever.

  • Fixed — nothing is discarded. The cap is an admission limit: as many of the new bars as fit are taken, newest-first so the result stays contiguous, and if they did not all fit the chart reports that there is no more history rather than inviting the same request again. Trimming the other end was not the fix either — the newest bars carry the live edge.

A fifth indicator panel drew over the candles

The panel layout capped at four panels; the render loop did not. An indicator beyond the cap was handed no position, and the renderer defaults to the top-left corner of the price pane — so a fifth indicator drew its grid, its zero line, its series and its header on top of the price chart.

  • Fixed — nothing is painted that the layout did not place, and the layout now fits the panels inside the height it is given (see below), which is what makes a fifth panel possible at all.

Four indicator panels could give the price chart a negative height

The layout function took a totalHeight argument and never read it. Four panels at their default 150px claimed 600px of a 400px chart, and the price pane's height was computed by subtracting that.

  • Fixed — the price pane keeps a minimum share of the chart whatever the panels ask for; panels shrink proportionally to fit, and the panel cap goes from four to six now that a count is no longer the safety mechanism.

On a narrow chart, the indicator readings were drawn over the OHLC card

The legend has two layouts and picks between them by measuring the chart area: a single flowing row on a wide chart, a stacked card under 600px, and with a candle hovered that card is 72px tall against the row's 16px. The data window below it was placed at a fixed offset chosen for the row, and gated on a separate "is this mobile" flag that only the binary mobile layout ever sets — so on the spot, DEX and forex terminals at phone width, every indicator's reading was painted straight through the card's Open/High/Low/Close numbers, one figure on top of another.

  • Fixed — the legend reports the box it drew and the data window is stacked under it, so both layouts are correct by construction rather than by a constant that happened to fit one of them.
  • Fixed — the data window stops at the bottom of the price pane instead of running on into the indicator panels below it.
  • Fixed — the bar-close countdown, which shares that row from the opposite edge, stands down on the widths where a long ticker's card reaches its slot. The prices are the data; the countdown is chrome.

The screenshot button captured the wrong canvas

It used the first canvas in the document. On the spot/futures terminal that is a sparkline in the market list, so the button silently downloaded a 60×20 thumbnail of a market the trader was not looking at, named after the one they were. Even where the chart was first, it captured one of three layers — no crosshair, no legend, no price line.

  • Fixed — the chart's layers are composited over the theme background. What you see is what you get.

The templates panel had no button

A 1,052-line panel was mounted and unreachable: its open handler existed as a prop type and a destructure, and no toolbar row called it. Separately, saving a template read a legacy drawings array the drawing engine never writes to — so every template ever saved recorded zero drawings while the panel said drawings were included, and loading one wiped whatever was on screen and put nothing back.

  • Fixed — a templates button on the toolbar, and both halves of the save read and write the drawing engine.

The drawing toolbar's Lock button locked nothing

It was component-local state. It lit up, it swapped its own icon, and the flag never left the component — while the engine honoured locked in four places and nothing in the package could set it.

  • Fixed — it locks every drawing, and it reports on rather than mirroring state, so it stays correct after a per-drawing lock, an undo, or loading a set that was saved locked.

The Delete key did nothing

The drawing engine's selection callback was an empty body with a "sync with store if needed" comment. It was needed: the store's selected-drawing id is what every shortcut reads, and its only writers were the order hit tests.

  • Fixed — selection is mirrored, and Delete/Backspace removes the selected drawing. A locked drawing is not deleted, because that is what locking is for.

Fibonacci level changes did not survive a reload

Seven of the eight Fibonacci tools' setLevels updated the in-memory field and not the drawing's saved state, while their constructors restored from that saved state — so a custom ladder lasted until the drawing was next read back.

  • Fixed — the ladder is saved, sorted, with the drawing.

The backtest ran at a hardcoded 85% payout

Nothing passed the real one. A strategy with a 55% win rate is profitable at 85% and loses money at 70%, so the panel was reporting the wrong sign on the edge, with a confident equity curve under it.

  • Fixed — the payout for the selected duration and order type is passed through. A host that has not resolved one yet keeps the previous default rather than backtesting at zero.

The backtest's trade list was built and never shown

The backtester produced every entry, exit, direction and P/L, and the results panel read none of it — so a run could report a 41% win rate and a -$1,240 net with no way to see which trades produced it. The winning and losing counts beside it were reconstructed by rounding the win rate back into a count, which could disagree with the total shown above them.

  • Fixed — the trades are listed, newest first, with a stated cap. The counts are counted.

The replay clock read 00:00 for the whole replay

Both of the control bar's clocks are derived from the replay's total duration, and nothing passed one. Its step buttons moved ±5% of the entire replay — 25 candles a click on a 500-bar replay — and a custom start date was never bounded to the loaded candles, which could reverse the scrubber's direction.

  • Fixed — the clocks read, the step buttons move one candle, and the requested range is clamped and ordered against the data that actually loaded.

Indicator panel heights were dropped when saved

panelHeight was the one persisted-worthy field the save omitted, so a resized panel reverted on reload — and since the height is what the panel layout budgets against, the whole stack jumped back to its defaults.

  • Fixed — heights persist. And they can now be set: see below.

Divergence detection silently ignored half its own sources

The detector supports RSI, MACD, Stochastic, CCI, OBV and MFI, has an extractor for each of the six, and the chart maps all six — and then a config list threw three of them away. A trader with a CCI or MFI panel got no divergences from it and no reason why.

  • Fixed — all six are detected.

Clicking an indicator panel with a drawing tool armed recorded a fabricated price

Below the price pane there is no price scale. The pixel-to-price conversion extrapolates past its own bottom edge, so a click in the RSI panel produced a price below the visible range — sometimes negative — and a drawing placed from it was stored with that number and drawn far off screen. It looked like the click did nothing while the drawing list quietly grew.

  • Fixed — a drawing point can only come from the price pane, and the cursor says so. Drawing on sub-panels needs each panel's own value scale threaded through the drawing engine and is not in this release.

On a touchscreen, every press panned the chart

A single-finger press started a pan immediately and consumed the gesture. Two consequences: there was no way to read a candle, because a touchscreen has no hover and the tap was taken; and the drawing toolbar — fully present on mobile — was unusable, because arming a tool and touching the chart panned it.

  • Fixed — the pan is deferred until the finger has actually travelled. A tap places the crosshair and reads the candle; a tap with a tool armed places a point. Pinch-to-zoom is unchanged.

Added

Fifty-nine more drawing tools, and a category for how crypto is actually read

The toolbar carried 73 tools. It now carries 132. The additions are not a scattering across the existing categories - they close specific gaps a trader notices the moment they look for one:

  • Smart Money Concepts, a new category and the largest single addition: Order Block, Breaker Block, Fair Value Gap, Liquidity Level, Liquidity Void, Market Structure, Balanced Price Range, Optimal Trade Entry, Power of Three (AMD), Range Equilibrium and Session Range. This vocabulary is how a large part of the crypto audience talks about a chart, and until now every one of those marks had to be drawn by hand out of rectangles and rays.

  • Elliott wave, as five separate labelled tools rather than one generic counter - Impulse (12345), Correction (ABC), Triangle (ABCDE), Double Combo (WXY) and Triple Combo (WXYXZ) - plus XABCD, Three Drives, Cup and Handle, Wyckoff Range, Broadening Formation, Diamond, Pennant, Manual Zigzag, Sine Line and Time Cycles.

  • Volume and position work: Anchored Volume Profile, Fixed Range Volume Profile, Fixed Range VWAP, TPO / Market Profile, Anchored TWAP, Measured Move, and the four risk tools - Leveraged Position, Scaled Position, Liquidation Ladder and Ghost Feed.

  • Lines and levels: Speed Resistance Lines, Raff Channel, Tirone Levels, Quadrant Lines, Cyclic Lines, Round Number Grid, Funding Grid, UTC Period Levels and OHLC Ray.

  • Annotation and geometry: Anchored Text, Price Note, Table, Image, and six shapes - Circle, Sector, Curve, S-Curve, Path and Rotated Rectangle - plus Gann Arcs, Gann Grid, Pitchfan and Trend-Based Fib Time.

  • Added 59 drawing tools, bringing the toolbar to 132 across nine categories.

  • Added the Smart Money category to the toolbar's flyout, its search and its keyboard shortcuts, on the same footing as the eight that were already there.

Thirteen more chart styles

The chart drew five series styles: candlestick, bar, Heikin Ashi, line and area. It now draws eighteen.

Eight are alternative readings of the same bars — Hollow Candles, Volume Candles (body width scaled by volume), High-Low, Line with Markers, Step Line, HLC Area, Baseline (one colour above a reference level and another below it, defaulting to the first bar on screen) and Columns.

The other five re-bin the series by price movement rather than by time, which is a different way of looking at a market rather than a different way of drawing it: Renko, Line Break, Kagi, Point & Figure and Range Bars. Each takes a box size and, where it applies, a reversal count; both are in the chart's settings and are shown only for the styles that read them. The settings feed the render key, so a change appears immediately rather than at the next candle. Leave them alone and the box size is derived from ATR, which is the sensible default for a market you have not tuned.

  • Added thirteen chart styles, bringing the picker to eighteen.
  • Added box size and reversal settings for the five price-binned styles.

Manual price scaling

Drag the price axis to stretch or compress the price scale. This takes the chart off automatic fitting and holds the range — the remedy for a single spike wick squashing 200 candles into a third of the pane, which previously had none.

The affordance was already there and lying: the axis set a vertical-resize cursor on hover, and pressing it fell through to the horizontal pan. An Auto chip appears on the axis while the scale is manual, and double-clicking the axis hands it back. It is released automatically by a market change, a timeframe change and Reset view.

A percentage price axis

Price or Percentage, in the chart's settings. In percentage mode every axis label and the crosshair's own readout show the move from the first candle on screen.

Deliberately a labelling change and nothing else — the geometry is identical, so no drawing, order or indicator can disagree with the axis. A logarithmic scale is a different proposition: it changes the mapping itself, at some 768 call sites plus a dozen private copies of the same function, including inside the indicator renderers where an RSI's 0–100 range makes a logarithm meaningless. Converting a subset would produce a chart whose candles are log-scaled and whose trend lines are not — every drawing pointing at the wrong price — so it is not in this release.

Indicator values at the crosshair

A row per visible indicator, under the legend, showing that indicator's value at the candle under the pointer. Previously the chart could draw five curves and tell you the value of none of them.

A bar-close countdown

How long the live candle has left, on the price axis, turning amber for the last ten seconds. Measured from the candle's own open plus one interval, so it agrees with the exchange's bar grid rather than with a wall clock.

Indicator and drawing alerts

Alerts were three price crosses against a fixed number. Now:

  • On an indicator — a threshold and a direction per output line, in the indicator's settings. These are candle-close alerts: indicator values are recomputed when a candle completes, so an RSI that pokes above 70 mid-candle and closes at 68 has not crossed 70.
  • On a drawn line — horizontal lines and rays, trend lines, rays, extended lines, arrows and info lines. The level it watches is re-resolved from the line's own geometry on every evaluation, which is the point of setting it on a sloping line. Where the line has no price at that moment — past the end of a bounded segment — the alert waits rather than firing on an imaginary extension.

Both appear in the existing price-alerts panel, labelled with what they watch. Like price alerts, they fire only while the tab is open.

The trader's own orders on the chart

Entry lines, side colour, fill progress, stop rails and hover tooltips for spot and futures orders. The renderer is 520 lines and has been wired all the way down for some time, gated on a prop that no mount site passed.

Trade from the chart

Right-click the price you want on a spot or futures chart and a ticket opens pre-filled at that price — a limit order, with the price and amount both editable and nothing sent until Buy or Sell is pressed.

Drag a panel divider; move an indicator between panel and overlay

Panel heights were fixed at whatever number a registry held. The divider above a panel is now a handle, the height persists with the indicator, and an indicator's settings carry a Position control that moves it between its own panel and the price chart.

A properties dialog for the tools that have parameters

Seventeen per-tool setters had zero callers, so a Fibonacci retracement was permanently on its default ladder, a volume profile stuck on its default row count, and a position tool's stop wherever the geometry put it. Right-click → settings now edits: the Fibonacci ladder (with a reset), profile row counts, and the stop, target and size on the long, short and leveraged position tools.

Gann's two tools are not included: their angle setters take two different shapes behind one name, and one form cannot drive both correctly.

A drawing list, and per-drawing lock and hide

Both flags were honoured by the engine and settable by nothing. Each drawing can now be locked (no drag, no reshape, no restyle, no delete) or hidden, from its own menu — and the toolbar's stacked-layers button lists everything on the chart with show, lock and delete per row. That list is what makes hiding safe: a hidden drawing is neither drawn nor hit-testable, so without it, hidden would be indistinguishable from deleted.

Search across every drawing tool

The tool search function existed with exactly one reference in the package — a re-export. The category flyout now has a search box that spans all 132 tools, so finding a Gann fan does not require knowing it lives under Gann.

CSV export of the loaded candles

In the toolbar's overflow menu. One row per candle, ISO-8601 UTC time, then open, high, low, close and volume. Exports everything loaded rather than only the visible window, so the file does not change with the scroll position.

More bars on screen

Zooming out past roughly 231 bars used to promote the interval, so no amount of zooming could show more than ~231 bars of any interval — a month of hourly data was undisplayable. The promote threshold and the fetch size are now separate numbers, and the zoom ceiling is measured at the width where a bar is still a distinguishable mark: about 740 bars on a 1920px screen.


Added - second pass

The items below completed the same review's Tier 2 and Tier 3 lists.

A logarithmic price scale

Price / Percentage / Log, in the chart's settings. Log gives equal percentage moves equal height, so $1 to $2 occupies as much of the pane as $100 to $200. On multi-year or low-priced history that is the difference between a readable chart and a flat line with a spike on the end.

The difficulty was never the arithmetic. The mapping from a price to a pixel has around a thousand call sites across 159 files, and a chart whose candles are log-scaled and whose trend lines are not is strictly worse than one with no log scale - every drawing on it points at the wrong price, silently. It is done with one transform read by one function, so candles, drawings, orders, alert lines, overlay indicators, the crosshair and every hit test move together or not at all. Fourteen private copies of that formula, scattered across the drawing engine, the overlay indicators, the profit zones and the analysis layers, were routed through it as part of the change.

Indicator panes are unaffected: they scale their own values with a different function, necessarily, since an RSI's 0-100 has no logarithm at 0.

The axis labels change with it, from evenly-spaced round numbers to the 1 / 2 / 5 sequence a log scale is read with - a repeated step puts nine of ten ticks in the top quarter of the pane over a range like 100 to 100,000.

A session (ordinal) time axis

Session hours packs the candles evenly and drops the time a market was shut. A week of 15-minute forex bars spends roughly two thirds of the chart drawing the weekend; this removes it. Off by default, and a no-op on a market that trades continuously.

Same discipline as the log scale, with one addition: an ordinal position cannot be computed from a timestamp alone, so the mapping carries the bar times. With no table it is chronological, which means it cannot half-apply.

Multi-select

Ctrl-click (Cmd-click on a Mac) adds a drawing to the selection. Several selected drawings move together when any one of them is dragged, and delete, lock and hide apply to all of them - a locked member is skipped rather than blocking the others. Dragging a handle still reshapes only the drawing you grabbed, because reshaping is a question about one drawing's geometry.

The selection keeps a primary - the one the properties dialog edits and the style row shows - because a dialog cannot edit four fib ladders at once.

Drawing on indicator panes

A horizontal line on RSI 70, a trend line across a MACD. Anchored to that indicator's own values and mapped with the range the pane is actually drawn with, so the line stays on 70 as the window moves.

This also completes a correctness fix from the first pass. A click in a panel used to record a price extrapolated past the bottom of the price scale - a number the trader never pointed at - and the drawing was stored with it and painted off screen. Placement was refused there as an interim measure; now it works.

A drawing belongs to one pane. Once its first point is down, the rest must go in the same pane: a trend line with one end on the price chart and one on an RSI is two numbers on unrelated scales.

Gann parameters

Five Gann tools ship six parameter setters between them and every one had no caller, so every Gann drawing in the product has been stuck on its constructor's defaults since it was written.

They could not simply be added to the properties dialog: gann-fan.setAngles takes ratio objects and gann-square.setAngles takes plain degrees - the same method name on two tools in the same family, taking incompatible arguments. The schema now declares the shape each tool wants, so the form can only produce what that tool's own setter accepts. Fans and arcs take ratios (1x8, 1x1, 2x1), squares take degrees, the box takes its two division lists, and the fixed square takes a grid size.

An indicator computed on another indicator

"RSI of a moving average", "a moving average of the MACD". The Source control in an indicator's settings offers Price plus every line of every other indicator on the chart.

The eligibility rule is derived, not a hand-kept list: an indicator can take another's output when it declares a price source (so it reads one number per bar) and does not require volume. Anything else reads a candle's range, and a line has none - ATR would be 0 on every bar and Stochastic would divide by zero, both of which look like plausible output. Because the rule reads metadata every indicator already declares, it cannot go stale as indicators are added.

Dependencies are computed first, and a cycle - two indicators pointed at each other - is broken rather than hung.

The workspace follows the account

Scoping storage per account fixed two people sharing a browser. It did not fix the complaint: a second machine, a second browser or a private window gave you the default chart, and clearing site data lost months of markup.

There is now a server copy - one fetch on sign-in, one debounced save of whatever changed. Two rules make it safe:

  • The server fills gaps; it never overwrites. A key this browser already has is left alone, because replacing what a trader is looking at with another device's snapshot mid-session is the one behaviour nobody forgives. A machine with nothing has nothing to overwrite, and that is the case the feature exists for.
  • Only changed keys are sent. A full-workspace payload per edit would let a stale tab overwrite another device's work by touching one unrelated line.

Local storage remains what the chart renders from, so a failed sync is not an error state - it is the previous behaviour.


Not in this release

Custom scripted indicators. The remaining Tier 3 item, and the one that should not ship as part of a sweep. Executing user-authored code in a trading product needs a real sandbox - a worker with no DOM, no network, an execution timeout and a memory cap - before it needs an editor, and a script saved to the server and later run elsewhere is remote code execution unless that isolation is right first. It is its own piece of work with its own review, not a line item.