VolumeProfile
Volume traded at each price level as bars beside the price, split into buying and selling, with the point of control and the value area marked.
Demo
Point of control $92.50 · value area (70%) $87.00 to $94.00
| Price | Volume | Bought | Sold |
|---|---|---|---|
| $97.00 to $98.00 | 370,357 | 167,209 | 203,149 |
| $96.00 to $97.00 | 1,368,980 | 522,527 | 846,453 |
| $95.00 to $96.00 | 5,957,210 | 3,052,939 | 2,904,272 |
| $94.00 to $95.00 | 13,769,503 | 6,749,493 | 7,020,010 |
| $93.00 to $94.00 | 26,189,306 | 12,702,787 | 13,486,519 |
| $92.00 to $93.00 | 34,516,408 | 16,595,134 | 17,921,274 |
| $91.00 to $92.00 | 32,003,523 | 18,490,401 | 13,513,122 |
| $90.00 to $91.00 | 24,176,747 | 12,379,352 | 11,797,395 |
| $89.00 to $90.00 | 19,659,514 | 7,966,206 | 11,693,308 |
| $88.00 to $89.00 | 15,990,180 | 7,950,714 | 8,039,466 |
| $87.00 to $88.00 | 18,654,295 | 9,068,650 | 9,585,646 |
| $86.00 to $87.00 | 13,261,814 | 3,767,853 | 9,493,961 |
| $85.00 to $86.00 | 13,133,244 | 7,654,807 | 5,478,437 |
| $84.00 to $85.00 | 9,454,927 | 5,927,983 | 3,526,943 |
| $83.00 to $84.00 | 3,225,240 | 2,831,921 | 393,318 |
Point of control 59,300 · value area (68%) 59,000 to 60,200
| Price | Volume | Bought | Sold |
|---|---|---|---|
| 61,000 to 61,200 | 44,158 | 0 | 44,158 |
| 60,800 to 61,000 | 128,560 | 0 | 128,560 |
| 60,600 to 60,800 | 128,956 | 0 | 128,956 |
| 60,400 to 60,600 | 166,146 | 62,780 | 103,367 |
| 60,200 to 60,400 | 364,224 | 192,007 | 172,216 |
| 60,000 to 60,200 | 516,972 | 275,464 | 241,507 |
| 59,800 to 60,000 | 617,903 | 315,912 | 301,991 |
| 59,600 to 59,800 | 713,784 | 302,369 | 411,415 |
| 59,400 to 59,600 | 774,898 | 294,407 | 480,491 |
| 59,200 to 59,400 | 1,121,926 | 413,425 | 708,501 |
| 59,000 to 59,200 | 758,330 | 249,582 | 508,748 |
| 58,800 to 59,000 | 373,966 | 161,226 | 212,740 |
| 58,600 to 58,800 | 208,530 | 77,286 | 131,244 |
| 58,400 to 58,600 | 51,585 | 17,523 | 34,062 |
| 58,200 to 58,400 | 20,353 | 17,523 | 2,830 |
| 58,000 to 58,200 | 2,891 | 2,891 | 0 |
Hover a level for its volume, and how much of it closed up or down.
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/volume_profile.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/volume_profile.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/volume_profile.qwe
// Prices from a seeded random walk (Park–Miller), the same on every render: `count`// candles from the day `from` on, skipping weekends, daily or hourly (10:00 to 16:00),// rounded to `places` decimals.fn volume_profile_demo_walk(seed: int, count: int, start: num, from: str, hourly: bool = false, places: int = 2) -> list { let s = seed let rnd = () => { s = s * 16807 % 2147483647 s / 2147483647 } let scale = pow(10, places) let cut = (v) => round(v * scale) / scale let p = start let day = from let hour = 10 let out = [] while len(out) < count { if date.weekday(day) <= 5 { let open = cut(p * (1 + (rnd() - 0.5) * 0.006)) let close = cut(open * (1 + (rnd() - 0.485) * (hourly ? 0.012 : 0.035))) let high = cut(max([open, close]) * (1 + rnd() * 0.01)) let low = cut(min([open, close]) * (1 - rnd() * 0.01)) let volume = round((hourly ? 90000 : 900000) * (1 + rnd() * 1.5 + abs(close - open) * 30 / open)) out = out | push({ time: hourly ? `${day}T${hour < 10 ? "0" : ""}${hour}:00:00Z` : day, open, high, low, close, volume }) p = close } if hourly && hour < 16 && date.weekday(day) <= 5 { hour += 1 } else { hour = 10 day = date.add_days(day, 1) } } out}let volume_profile_demo_daily = volume_profile_demo_walk(31, 126, 88.4, "2026-04-01")let volume_profile_demo_hourly = volume_profile_demo_walk(8, 35, 61250, "2026-10-19", true, 0)view VolumeProfileDemo() = <div.demo-stack> <div id="volume_profile-demo-daily"> <VolumeProfile label="Hooli, six months" show_label={true} candles={volume_profile_demo_daily} prefix="$"/> </div> <div id="volume_profile-demo-hourly"> <VolumeProfile label="BTC-USD, a week of hours" show_label={true} candles={volume_profile_demo_hourly} rows={16} value_area={68} decimals={0} height={220}/> </div> <p.demo-note>Hover a level for its volume, and how much of it closed up or down.</p> </div>Source
ui/volume_profile.qwe
// VolumeProfile: how much was traded at each price level, as horizontal bars beside// the price, split into buying (candles that closed up) and selling (closed down).// The point of control, the level that traded most, is marked across the chart, and// the value area, the levels around it holding 70% of the volume, is shaded with its// high and low on the axis. CSS only: hovering a level shows its range, volume and// split, and screen readers get the levels as a table.//// <VolumeProfile label="ACME, 6 months" candles={bars} prefix="$"/>// <VolumeProfile label="BTC-USD, 5 days" candles={hourly} rows={32} value_area={68} decimals={0}/>//// candles: a list of { time, open, high, low, close, volume }, oldest first (time a// date or a date-time). Each candle's volume is spread evenly over the levels its low// to high crosses. rows: about how many price levels (24); they start and end on round// prices, so there may be a few more or fewer. value_area: the share of the// volume, in percent, the value area holds (70). height: the plot's height in pixels// (300). prefix and suffix: around every price shown ("$"); decimals: digits after the// point of prices (2). label: what the chart shows; it names it for screen readers and// captions their table. show_label also writes it above the chart.import ui/theme.qwe use ui_clslet volume_profile_css = css`.ui-vp { --ui-vp-line: light-dark(#2a78d6, #3987e5); --ui-vp-poc: light-dark(#eda100, #c98500); display: grid; gap: 10px; min-width: 0; margin: 0; font: 12px/1.3 var(--ui-font); color: var(--ui-fg); container-type: inline-size; }.ui-vp-title { margin: 0; font-size: 14px; font-weight: 600; }.ui-vp-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 0; padding: 0; list-style: none; color: var(--ui-muted); font-size: 13px; }.ui-vp-legend li { display: inline-flex; align-items: center; gap: 6px; }.ui-vp-key { width: 10px; height: 10px; border-radius: 3px; flex: none; background: var(--c); }.ui-vp-buy { --c: var(--ui-success); } .ui-vp-sell { --c: var(--ui-danger); }.ui-vp-pockey { --c: var(--ui-vp-poc); height: 3px; width: 14px; }.ui-vp-frame { display: grid; grid-template-columns: minmax(0, 1fr) minmax(90px, 32%) auto; column-gap: 8px; padding-top: 8px; }.ui-vp-plot, .ui-vp-prof, .ui-vp-y { position: relative; height: var(--h); }.ui-vp-grid { position: absolute; left: 0; right: 0; height: 1px; background: color-mix(in srgb, var(--ui-border) 70%, transparent); }.ui-vp-va { position: absolute; left: 0; right: 0; background: color-mix(in srgb, var(--ui-vp-poc) 7%, transparent); border-block: 1px dashed color-mix(in srgb, var(--ui-vp-poc) 45%, transparent); }.ui-vp-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }.ui-vp-line { fill: none; stroke: var(--ui-vp-line); stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }.ui-vp-pocline { position: absolute; z-index: 1; left: 0; right: 0; border-top: 2px solid var(--ui-vp-poc); pointer-events: none; }.ui-vp-row { position: absolute; left: 0; right: 0; display: flex; align-items: stretch; padding: 1px 0; }.ui-vp-row:hover { background: color-mix(in srgb, var(--ui-subtle) 80%, transparent); }.ui-vp-bar { display: flex; height: 100%; min-width: 1px; border-radius: 0 2px 2px 0; overflow: hidden; opacity: .5; }.ui-vp-in .ui-vp-bar { opacity: .85; }.ui-vp-row:hover .ui-vp-bar { opacity: 1; }.ui-vp-bar span { height: 100%; background: var(--c); }.ui-vp-poc .ui-vp-bar { box-shadow: 0 0 0 2px var(--ui-vp-poc); opacity: 1; }.ui-vp-tip { position: absolute; z-index: 20; right: calc(100% + 8px); top: 50%; display: none; min-width: 160px; padding: 7px 10px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); box-shadow: var(--ui-shadow); color: var(--ui-fg); font-size: 12px; transform: translateY(-50%); pointer-events: none; }.ui-vp-row:hover .ui-vp-tip { display: block; }.ui-vp-tip strong { display: block; margin-bottom: 3px; font-weight: 600; white-space: nowrap; }.ui-vp-tip p { display: flex; align-items: center; gap: 6px; margin: 2px 0 0; color: var(--ui-muted); white-space: nowrap; }.ui-vp-tip b { margin-left: auto; padding-left: 12px; color: var(--ui-fg); font-weight: 600; font-variant-numeric: tabular-nums; }.ui-vp-y span { position: absolute; left: 0; transform: translateY(-50%); color: var(--ui-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }.ui-vp-y .ui-vp-tag { z-index: 1; left: -4px; padding: 1px 4px; border-radius: 3px; background: var(--ui-vp-poc); color: var(--ui-bg); font-weight: 600; }.ui-vp-y .ui-vp-edge { color: var(--ui-fg); font-weight: 500; }.ui-vp-x { position: relative; height: 16px; margin-top: 6px; }.ui-vp-x span { position: absolute; top: 0; transform: translateX(-50%); color: var(--ui-muted); white-space: nowrap; }.ui-vp-x .ui-vp-first { transform: none; }.ui-vp-x .ui-vp-end { transform: translateX(-100%); }.ui-vp-note { margin: 0; color: var(--ui-muted); font-size: 13px; }.ui-vp-note b { color: var(--ui-fg); font-weight: 600; font-variant-numeric: tabular-nums; }`// n (a whole number, 0 or more) with at least `width` digits.fn volume_profile_pad(n: int, width: int) -> str { let s = str(n) len(s) < width ? repeat("0", width - len(s)) + s : s}// A whole number with commas: 1234567 is "1,234,567".fn volume_profile_group(n: int) -> str = n < 1000 ? str(n) : volume_profile_group(floor(n / 1000)) + "," + volume_profile_pad(n % 1000, 3)// A value as it reads, with commas and `places` digits after the point (up to two as// needed when that is null): 48210.5 is "48,210.50" with 2. Worked out in whole// numbers, so the server and the browser write the same text.fn volume_profile_num(v: num, places: int? = null) -> str { let digits = places == null ? 2 : (places < 0 ? 0 : (places > 6 ? 6 : places)) let scale = [1, 10, 100, 1000, 10000, 100000, 1000000][digits] let units = round(abs(v) * scale) let frac = units % scale let tail = digits == 0 || (places == null && frac == 0) ? "" : (places == null && frac % 10 == 0 ? `.${floor(frac / 10)}` : `.${volume_profile_pad(frac, digits)}`) `${v < 0 && units > 0 ? "-" : ""}${volume_profile_group(floor(units / scale))}${tail}`}// A large number, short: 1500 is "1.5k", 2000000 is "2M", 0.25 is "0.25".fn volume_profile_short(v: num) -> str { let a = abs(v) let unit = a >= 1000000000 ? 1000000000 : (a >= 1000000 ? 1000000 : (a >= 1000 ? 1000 : 1)) let tenths = round(a * 10 / unit) let mark = unit == 1000000000 ? "B" : (unit == 1000000 ? "M" : "k") unit == 1 ? volume_profile_num(v) : `${v < 0 ? "-" : ""}${volume_profile_group(floor(tenths / 10))}${tenths % 10 != 0 ? `.${tenths % 10}` : ""}${mark}`}// A change in percent, signed, to two decimals: "+1.25%", "-0.40%".fn volume_profile_signed(p: num) -> str = `${p > 0 ? "+" : (p < 0 && round(abs(p) * 100) > 0 ? "-" : "")}${volume_profile_num(abs(p), 2)}%`// A coordinate out of 100 (or a length), to two decimals.fn volume_profile_pct(n: num) -> str { let r = round(n * 100) / 100 r == floor(r) ? str(floor(r)) : str(r)}// The axis from lo to hi, widened to round ticks: about `steps` steps of 1, 2, 2.5 or 5 x 10^n.fn volume_profile_scale(lo: num, hi: num, steps: int = 5) -> map { let span = hi - lo == 0 ? (lo == 0 ? 1 : abs(lo) / 10) : hi - lo let raw = span / steps let mag = 1 while mag * 10 <= raw { mag = mag * 10 } while mag > raw { mag = mag / 10 } let r = raw / mag let step = (r <= 1 ? 1 : (r <= 2 ? 2 : (r <= 2.5 ? 2.5 : (r <= 5 ? 5 : 10)))) * mag let a = floor(lo / step + 0.000001) let b0 = ceil(hi / step - 0.000001) let b = b0 == a ? a + 1 : b0 { lo: a * step, hi: b * step, ticks: for i in range(b - a + 1) => (a + i) * step }}// A candle's time as it reads: "Mar 2, 2026" for a date, "Mar 2, 14:30" for a date-time.fn volume_profile_when(t) -> str = type_of(t) != "str" || !date.valid(t) ? str(t) : (len(t) > 10 ? date.format(t, "MMM D, HH:mm") : date.format(t, "MMM D, YYYY"))// Where the time axis is labelled: about `limit` of { i, text }, at the starts of years,// months or days when the times cross them ("2026", "Mar", "Mar 2"), else every few// candles ("14:30", "Mar 2").fn volume_profile_ticks(times: list, limit: int) -> list { let n = len(times) let thin = (idx: list) => { let step = ceil(len(idx) / limit) range(len(idx)) | filter((k) => k % step == 0) | map((k) => idx[k]) } let spaced = (fmt_at) => { let step = ceil(n / limit) range(n) | filter((i) => i % step == floor(step / 2)) | map((i) => { i, text: fmt_at(i) }) } if n == 0 { return [] } if !all(times, (t) => type_of(t) == "str" && date.valid(t)) { return spaced((i) => str(times[i])) } let days = times | map((t) => slice(t, 0, 10)) let changes = (keys: list) => range(1, n) | filter((i) => keys[i] != keys[i - 1]) let day_starts = changes(days) let month_starts = changes(days | map((d) => slice(d, 0, 7))) let year_starts = changes(days | map((d) => slice(d, 0, 4))) let picked = if any(times, (t) => len(t) > 10) { len(day_starts) >= 3 ? thin(day_starts) | map((i) => { i, text: date.format(days[i], "MMM D") }) : spaced((i) => index_of(day_starts, i) != null ? date.format(days[i], "MMM D") : date.format(times[i], "HH:mm")) } else if len(year_starts) >= 3 && len(month_starts) > limit * 2 { thin(year_starts) | map((i) => { i, text: slice(days[i], 0, 4) }) } else if len(month_starts) >= 2 { thin(month_starts) | map((i) => { i, text: index_of(year_starts, i) != null ? slice(days[i], 0, 4) : date.format(days[i], "MMM") }) } else { spaced((i) => date.format(days[i], "MMM D")) } // Starts that fall close together (a short month) keep the first. let gap = n / (limit * 1.5) reduce(picked, [], (kept, t) => len(kept) == 0 || t.i - kept[-1].i >= gap ? kept + [t] : kept)}// The levels: `rows` of { lo, hi, buy, sell, total } from lo to hi, each candle's volume// spread over the levels its low to high crosses, as buying when it closed up.fn volume_profile_levels(candles: list, lo: num, hi: num, rows: int) -> list { let size = (hi - lo) / rows let buy = for _ in range(rows) => 0 let sell = for _ in range(rows) => 0 for c in candles { let v = c.volume ?? 0 let a = c.low < c.high ? c.low : c.close let b = c.low < c.high ? c.high : c.close let first = floor((a - lo) / size) let last = floor((b - lo) / size) let from = first < 0 ? 0 : (first >= rows ? rows - 1 : first) let to = last < 0 ? 0 : (last >= rows ? rows - 1 : last) for k in range(from, to + 1) { let bot = lo + k * size let top = bot + size let part = b > a ? (min([b, top]) - max([a, bot])) / (b - a) : 1 let share = v * (part > 0 ? part : (from == to ? 1 : 0)) if c.close >= c.open { buy = set(buy, k, buy[k] + share) } else { sell = set(sell, k, sell[k] + share) } } } for k in range(rows) => { lo: lo + k * size, hi: lo + (k + 1) * size, buy: buy[k], sell: sell[k], total: buy[k] + sell[k] }}// The value area: the indexes of the levels around the point of control (`poc`) that// hold `share` percent of the volume, grown each step toward the busier side.fn volume_profile_area(levels: list, poc: int, share: num) -> map { let total = sum(levels | map((l) => l.total)) let a = poc let b = poc let held = levels[poc].total while held < total * share / 100 && (a > 0 || b < len(levels) - 1) { let below = a > 0 ? levels[a - 1].total : -1 let above = b < len(levels) - 1 ? levels[b + 1].total : -1 if above >= below { b += 1 held += above } else { a -= 1 held += below } } { from: a, to: b }}view VolumeProfile(candles: list, rows: int = 24, value_area: num = 70, height: int = 300, prefix: str = "", suffix: str = "", decimals: int = 2, label: str = "Volume profile", show_label: bool = false, class: str? = null) { style global volume_profile_css let n = len(candles) let count = rows < 1 ? 1 : rows let lo = n == 0 ? 0 : min(candles | map((c) => c.low)) let hi0 = n == 0 ? 1 : max(candles | map((c) => c.high)) let hi = hi0 > lo ? hi0 : lo + 1 let grid = volume_profile_scale(lo, hi, count) let levels = volume_profile_levels(candles, grid.lo, grid.hi, len(grid.ticks) - 1) let top_volume = max(levels | map((l) => l.total)) let poc = index_of(levels | map((l) => l.total), top_volume) ?? 0 let va = volume_profile_area(levels, poc, value_area) let total = sum(levels | map((l) => l.total)) let sc = volume_profile_scale(lo, hi, 5) let y = (v) => (sc.hi - v) * 100 / (sc.hi - sc.lo) let x = (i) => (i + 0.5) * 100 / n let pct = (v) => volume_profile_pct(v) let price = (v) => `${v < 0 ? "-" : ""}${prefix}${volume_profile_num(abs(v), decimals)}${suffix}` let tick = (t) => `${t < 0 ? "-" : ""}${prefix}${volume_profile_num(abs(t))}` let vol = (v) => volume_profile_num(v, 0) let mid = (l) => (l.lo + l.hi) / 2 let poc_price = mid(levels[poc]) let vah = levels[va.to].hi let val = levels[va.from].lo let marks = [{ v: poc_price, cls: "ui-vp-tag", text: price(poc_price) }, { v: vah, cls: "ui-vp-edge", text: price(vah) }, { v: val, cls: "ui-vp-edge", text: price(val) }] let near = (t) => any(marks, (m) => abs(y(t) - y(m.v)) * height < 1400) let line = n == 0 ? "" : "M" + join(range(n) | map((i) => `${pct(x(i))},${pct(y(candles[i].close))}`), " L") let axis_width = max([4] + (sc.ticks | map((t) => len(tick(t)))) + (marks | map((m) => len(m.text) + 1))) <figure class={ui_cls(["ui-vp", class])} aria-label={label} style={`--h: ${height}px`}> {show_label && <figcaption.ui-vp-title>{label}</figcaption>} <ul.ui-vp-legend aria-hidden="true"> <li><span.ui-vp-key.ui-vp-buy></span>Bought (closed up)</li> <li><span.ui-vp-key.ui-vp-sell></span>Sold (closed down)</li> <li><span.ui-vp-key.ui-vp-pockey></span>Point of control</li> </ul> <div.ui-vp-frame aria-hidden="true"> <div.ui-vp-plot> {for t in sc.ticks => <span.ui-vp-grid style={`top: ${pct(y(t))}%`}></span>} <span.ui-vp-va style={`top: ${pct(y(vah))}%; height: ${pct(y(val) - y(vah))}%`}></span> <svg.ui-vp-svg viewBox="0 0 100 100" preserveAspectRatio="none"> <path.ui-vp-line d={line} vector-effect="non-scaling-stroke"/> </svg> <span.ui-vp-pocline style={`top: ${pct(y(poc_price))}%`}></span> </div> <div.ui-vp-prof> <span.ui-vp-va style={`top: ${pct(y(vah))}%; height: ${pct(y(val) - y(vah))}%`}></span> <span.ui-vp-pocline style={`top: ${pct(y(poc_price))}%`}></span> {for [k, l] in enumerate(levels) => <div class={ui_cls(["ui-vp-row", k >= va.from && k <= va.to && "ui-vp-in", k == poc && "ui-vp-poc"])} style={`top: ${pct(y(l.hi))}%; height: ${pct(y(l.lo) - y(l.hi))}%`}> <span.ui-vp-bar style={`width: ${top_volume > 0 ? pct(l.total * 100 / top_volume) : 0}%`}> <span.ui-vp-buy style={`width: ${l.total > 0 ? pct(l.buy * 100 / l.total) : 0}%`}></span> <span.ui-vp-sell style={`width: ${l.total > 0 ? pct(l.sell * 100 / l.total) : 0}%`}></span> </span> <span.ui-vp-tip> <strong>{price(l.lo)} – {price(l.hi)}{k == poc ? " · point of control" : ""}</strong> <p>Volume<b>{vol(l.total)}</b></p> <p>Bought<b>{vol(l.buy)}</b></p> <p>Sold<b>{vol(l.sell)}</b></p> <p>Share<b>{total > 0 ? volume_profile_num(l.total * 100 / total, 1) : "0"}%</b></p> </span> </div>} </div> <div.ui-vp-y style={`width: ${axis_width}ch`}> {for t in sc.ticks => !near(t) && <span style={`top: ${pct(y(t))}%`}>{tick(t)}</span>} {for m in marks => <span class={m.cls} style={`top: ${pct(y(m.v))}%`}>{m.text}</span>} </div> <div.ui-vp-x> {for t in volume_profile_ticks(candles | map((c) => c.time), 6) => <span class={ui_cls([x(t.i) < 4 && "ui-vp-first", x(t.i) > 96 && "ui-vp-end"])} style={`left: ${pct(x(t.i))}%`}>{t.text}</span>} </div> </div> <p.ui-vp-note>Point of control <b>{price(poc_price)}</b> · value area ({volume_profile_num(value_area)}%) <b>{price(val)}</b> to <b>{price(vah)}</b></p> <table.ui-sr> <caption>{label}: volume by price, highest first</caption> <thead><tr><th scope="col">Price</th><th scope="col">Volume</th><th scope="col">Bought</th><th scope="col">Sold</th></tr></thead> <tbody> {for l in reverse(levels) => <tr> <th scope="row">{price(l.lo)} to {price(l.hi)}</th><td>{vol(l.total)}</td><td>{vol(l.buy)}</td><td>{vol(l.sell)}</td> </tr>} </tbody> </table> </figure>}Related
BollingerChartPrices with Bollinger bands of a period and a number of deviations, closes outside them marked, as a line or candles.7 views · 5 downloadsCandlestickChartOHLC candles, green up and red down, with a volume pane, a date axis, moving averages and the details of each candle on hover.5 views · 7 downloadsDepthChartAn order book's bids and asks as stepped areas of the size waiting at each price or better, around the mid price, with the spread.5 views · 7 downloadsHeikinAshiChartHeikin-Ashi candles worked out from OHLC, which smooth the noise so trends show as runs of one colour, with the real close on hover.5 views · 6 downloadsLineBreakChartA three-line break chart from closing prices: lines go on while the close makes new ends, and turn only past the last three lines.5 views · 6 downloadsLiveCandlesCandles built from ticks as they come: the open candle grows, a new one starts each interval, with the last price and the time left.6 views · 6 downloads
Thread
Thread 0 posts
On VolumeProfile: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.