Charts

RsiChart

The relative strength index of closing prices with overbought and oversold lines, under a price chart or on its own.

#chart#data#trading#no-js

Demo

Preview
Stark Industries, RSI 14
Stark Industries, RSI 14: close and RSI 14
TimeCloseRSI
Apr 1, 2026$144.01—
Apr 2, 2026$143.27—
Apr 3, 2026$140.83—
Apr 6, 2026$140.60—
Apr 7, 2026$142.39—
Apr 8, 2026$143.11—
Apr 9, 2026$141.11—
Apr 10, 2026$140.45—
Apr 13, 2026$140.21—
Apr 14, 2026$139.06—
Apr 15, 2026$140.62—
Apr 16, 2026$139.05—
Apr 17, 2026$139.54—
Apr 20, 2026$140.53—
Apr 21, 2026$138.8534.1
Apr 22, 2026$137.8632.0
Apr 23, 2026$138.1733.4
Apr 24, 2026$137.3231.5
Apr 27, 2026$139.4640.6
Apr 28, 2026$141.7648.6
Apr 29, 2026$142.2550.1
Apr 30, 2026$141.2547.0
May 1, 2026$142.4951.0
May 4, 2026$143.1653.1
May 5, 2026$145.6859.9
May 6, 2026$144.2955.2
May 7, 2026$146.6360.8
May 8, 2026$146.4460.2
May 11, 2026$147.2362.0
May 12, 2026$145.6256.3
May 13, 2026$147.5560.9
May 14, 2026$148.3162.6
May 15, 2026$149.7565.7
May 18, 2026$148.8262.2
May 19, 2026$150.1365.0
May 20, 2026$149.4562.4
May 21, 2026$147.2054.5
May 22, 2026$148.1256.9
May 25, 2026$149.2459.7
May 26, 2026$149.7160.8
May 27, 2026$149.5060.0
May 28, 2026$149.9261.1
May 29, 2026$149.7860.5
Jun 1, 2026$148.6255.6
Jun 2, 2026$147.4351.0
Jun 3, 2026$146.5247.7
Jun 4, 2026$147.8552.5
Jun 5, 2026$146.8048.7
Jun 8, 2026$148.2353.6
Jun 9, 2026$150.4360.0
Jun 10, 2026$147.5850.4
Jun 11, 2026$148.8253.8
Jun 12, 2026$146.5147.2
Jun 15, 2026$144.8543.1
Jun 16, 2026$144.9243.3
Jun 17, 2026$144.9143.3
Jun 18, 2026$144.2241.4
Jun 19, 2026$146.8550.2
Jun 22, 2026$146.8950.3
Jun 23, 2026$146.6549.6
Jun 24, 2026$148.1754.4
Jun 25, 2026$148.5555.6
Jun 26, 2026$147.0350.1
Jun 29, 2026$145.2044.5
Jun 30, 2026$143.5640.1
Jul 1, 2026$141.0334.5
Jul 2, 2026$142.2839.0
Jul 3, 2026$140.1434.6
Jul 6, 2026$140.7436.8
Jul 7, 2026$140.0435.3
Jul 8, 2026$138.9633.1
Jul 9, 2026$138.5932.4
Jul 10, 2026$137.9631.1
Jul 13, 2026$139.2536.6
Jul 14, 2026$137.6433.0
Jul 15, 2026$137.8433.9
Jul 16, 2026$136.5531.1
Jul 17, 2026$135.7229.5, oversold
Jul 20, 2026$136.6933.9
Jul 21, 2026$138.4741.3
Jul 22, 2026$136.7336.9
Jul 23, 2026$137.2839.1
Jul 24, 2026$134.8133.5
Jul 27, 2026$132.8329.9, oversold
Jul 28, 2026$134.5336.3
Jul 29, 2026$133.5134.3
Jul 30, 2026$134.2737.1
Jul 31, 2026$135.5341.6
Aug 3, 2026$135.4441.4
Aug 4, 2026$134.2638.4
Aug 5, 2026$135.1541.8
Aug 6, 2026$133.9038.6
Aug 7, 2026$131.8734.0
Aug 10, 2026$132.3035.8
Aug 11, 2026$133.8841.8
Aug 12, 2026$133.4740.7
Aug 13, 2026$132.3337.8
Aug 14, 2026$130.3433.4
Aug 17, 2026$131.4537.8
Aug 18, 2026$132.0240.0
Aug 19, 2026$133.6545.9
Aug 20, 2026$134.3448.2
Aug 21, 2026$134.6349.2
Aug 24, 2026$133.3745.2
Aug 25, 2026$131.6140.2
Aug 26, 2026$131.8541.2
Aug 27, 2026$134.4550.3
Aug 28, 2026$132.2744.1
Aug 31, 2026$129.8738.5
Sep 1, 2026$130.8041.6
Sep 2, 2026$129.7439.2
Sep 3, 2026$130.4741.7
Sep 4, 2026$129.2238.7
Sep 7, 2026$128.0336.1
Sep 8, 2026$128.3737.4
Sep 9, 2026$129.4441.5
Sep 10, 2026$128.6639.5
Sep 11, 2026$127.1335.8
Sep 14, 2026$125.5832.5
Sep 15, 2026$123.5228.7, oversold
RSI 5, 80 and 20 lines, on its own
RSI 5, 80 and 20 lines, on its own: close and RSI 5
TimeCloseRSI
Apr 1, 2026144.01—
Apr 2, 2026143.27—
Apr 3, 2026140.83—
Apr 6, 2026140.60—
Apr 7, 2026142.39—
Apr 8, 2026143.1142.4
Apr 9, 2026141.1129.8
Apr 10, 2026140.4526.6
Apr 13, 2026140.2125.3
Apr 14, 2026139.0619.7, oversold
Apr 15, 2026140.6241.6
Apr 16, 2026139.0531.0
Apr 17, 2026139.5437.2
Apr 20, 2026140.5348.9
Apr 21, 2026138.8535.1
Apr 22, 2026137.8629.0
Apr 23, 2026138.1733.5
Apr 24, 2026137.3227.5
Apr 27, 2026139.4653.6
Apr 28, 2026141.7668.7
Apr 29, 2026142.2571.2
Apr 30, 2026141.2559.1
May 1, 2026142.4967.6
May 4, 2026143.1671.6
May 5, 2026145.6882.0, overbought
May 6, 2026144.2965.5
May 7, 2026146.6375.8
May 8, 2026146.4473.5
May 11, 2026147.2377.0
May 12, 2026145.6257.6
May 13, 2026147.5569.2
May 14, 2026148.3172.9
May 15, 2026149.7578.9
May 18, 2026148.8267.0
May 19, 2026150.1373.9
May 20, 2026149.4565.0
May 21, 2026147.2043.5
May 22, 2026148.1251.7
May 25, 2026149.2460.4
May 26, 2026149.7163.8
May 27, 2026149.5060.9
May 28, 2026149.9264.9
May 29, 2026149.7862.2
Jun 1, 2026148.6243.6
Jun 2, 2026147.4331.5
Jun 3, 2026146.5224.9
Jun 4, 2026147.8545.7
Jun 5, 2026146.8035.9
Jun 8, 2026148.2353.0
Jun 9, 2026150.4369.0
Jun 10, 2026147.5844.5
Jun 11, 2026148.8253.5
Jun 12, 2026146.5138.9
Jun 15, 2026144.8531.2
Jun 16, 2026144.9231.9
Jun 17, 2026144.9131.8
Jun 18, 2026144.2227.5
Jun 19, 2026146.8556.1
Jun 22, 2026146.8956.4
Jun 23, 2026146.6553.4
Jun 24, 2026148.1767.2
Jun 25, 2026148.5569.9
Jun 26, 2026147.0349.2
Jun 29, 2026145.2034.0
Jun 30, 2026143.5625.3
Jul 1, 2026141.0316.9, oversold
Jul 2, 2026142.2831.0
Jul 3, 2026140.1422.7
Jul 6, 2026140.7429.3
Jul 7, 2026140.0426.1
Jul 8, 2026138.9621.5
Jul 9, 2026138.5920.0, oversold
Jul 10, 2026137.9617.4, oversold
Jul 13, 2026139.2538.0
Jul 14, 2026137.6427.4
Jul 15, 2026137.8430.4
Jul 16, 2026136.5522.7
Jul 17, 2026135.7218.9, oversold
Jul 20, 2026136.6934.9
Jul 21, 2026138.4755.2
Jul 22, 2026136.7340.0
Jul 23, 2026137.2845.9
Jul 24, 2026134.8129.6
Jul 27, 2026132.8321.8
Jul 28, 2026134.5339.0
Jul 29, 2026133.5133.5
Jul 30, 2026134.2741.2
Jul 31, 2026135.5352.7
Aug 3, 2026135.4451.8
Aug 4, 2026134.2640.4
Aug 5, 2026135.1550.6
Aug 6, 2026133.9038.9
Aug 7, 2026131.8726.5
Aug 10, 2026132.3032.2
Aug 11, 2026133.8850.1
Aug 12, 2026133.4746.1
Aug 13, 2026132.3336.2
Aug 14, 2026130.3424.7
Aug 17, 2026131.4538.4
Aug 18, 2026132.0244.8
Aug 19, 2026133.6559.8
Aug 20, 2026134.3464.9
Aug 21, 2026134.6367.1
Aug 24, 2026133.3750.2
Aug 25, 2026131.6134.8
Aug 26, 2026131.8538.1
Aug 27, 2026134.4562.9
Aug 28, 2026132.2744.3
Aug 31, 2026129.8731.5
Sep 1, 2026130.8039.9
Sep 2, 2026129.7433.9
Sep 3, 2026130.4741.5
Sep 4, 2026129.2233.3
Sep 7, 2026128.0327.0
Sep 8, 2026128.3731.7
Sep 9, 2026129.4445.3
Sep 10, 2026128.6638.3
Sep 11, 2026127.1327.8
Sep 14, 2026125.5820.7
Sep 15, 2026123.5214.5, oversold

The RSI line turns red when overbought and green when oversold; hover for the close and RSI.

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/rsi_chart.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/rsi_chart.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/rsi_chart.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 rsi_chart_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 rsi_chart_demo_daily = rsi_chart_demo_walk(29, 120, 142.8, "2026-04-01")view RsiChartDemo() =    <div.demo-stack>        <div id="rsi_chart-demo-price">            <RsiChart label="Stark Industries, RSI 14" show_label={true} candles={rsi_chart_demo_daily} prefix="$"/>        </div>        <div id="rsi_chart-demo-alone">            <RsiChart label="RSI 5, 80 and 20 lines, on its own" show_label={true} candles={rsi_chart_demo_daily}                      period={5} overbought={80} oversold={20} price={false}/>        </div>        <p.demo-note>The RSI line turns red when overbought and green when oversold; hover for the close and RSI.</p>    </div>

Source

ui/rsi_chart.qwe
// RsiChart: the relative strength index of closing prices over a period (14 by// default, with Wilder's smoothing), from 0 to 100, with the overbought (70) and// oversold (30) lines and the band between them; the line turns red above the one// and green below the other. Under a price chart by default, or on its own. CSS only:// hovering shows the close and the RSI at that time, and screen readers get the// numbers as a table.////   <RsiChart label="ACME, RSI 14" candles={bars} prefix="$"/>//   <RsiChart label="RSI 9" candles={bars} period={9} overbought={80} oversold={20} price={false}/>//   <RsiChart label="Index" closes={[4012.5, 4020.1, 4031.8]} labels={["Mon", "Tue", "Wed"]}/>//// candles: a list of { time, close } (OHLC candles do), oldest first, time a date or a// date-time; or closes: the numbers alone, with labels for the axis if wanted.// period: the RSI's (14); nothing shows until there are period + 1 closes.// overbought and oversold: where its lines are drawn (70 and 30). price: the price// chart above the RSI (true); height: its height in pixels (200); rsi_height: the RSI// pane's (110). 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 rsi_chart_css = css`.ui-rsi { --ui-rsi-line: light-dark(#6250d6, #9085e9); --ui-rsi-price: light-dark(#2a78d6, #3987e5);  display: grid; gap: 10px; min-width: 0; margin: 0; font: 12px/1.3 var(--ui-font); color: var(--ui-fg); }.ui-rsi-title { margin: 0; font-size: 14px; font-weight: 600; }.ui-rsi-frame { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 8px; padding-top: 8px; }.ui-rsi-plot { position: relative; }.ui-rsi-pane, .ui-rsi-yp { position: relative; height: var(--h); }.ui-rsi-ipane, .ui-rsi-yi { position: relative; height: var(--ih); }.ui-rsi-pane + .ui-rsi-ipane, .ui-rsi-yp + .ui-rsi-yi { margin-top: 18px; }.ui-rsi-name { position: absolute; z-index: 1; top: 2px; left: 4px; color: var(--ui-muted); font-size: 11px; font-weight: 600; pointer-events: none; }.ui-rsi-grid { position: absolute; left: 0; right: 0; height: 1px; background: color-mix(in srgb, var(--ui-border) 70%, transparent); }.ui-rsi-band { position: absolute; left: 0; right: 0; background: color-mix(in srgb, var(--ui-rsi-line) 8%, transparent); }.ui-rsi-level { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--ui-strong); }.ui-rsi-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }.ui-rsi-svg path { fill: none; stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }.ui-rsi-price { stroke: var(--ui-rsi-price); }.ui-rsi-fill { fill: var(--ui-rsi-price) !important; stroke: none; opacity: .08; }.ui-rsi-line { stroke: var(--ui-rsi-line); }.ui-rsi-hot path { stroke: var(--ui-danger); }.ui-rsi-cold path { stroke: var(--ui-success); }.ui-rsi-cols { position: absolute; inset: 0; display: flex; }.ui-rsi-col { position: relative; flex: 1; min-width: 0; }.ui-rsi-col::before { content: ""; position: absolute; top: 0; bottom: 0; left: 50%; width: 1px; background: var(--ui-strong); opacity: 0; }.ui-rsi-col:hover::before { opacity: .9; }.ui-rsi-dot { position: absolute; left: 50%; display: none; width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; border-radius: 50%;  background: var(--c); box-shadow: 0 0 0 2px var(--ui-bg); }.ui-rsi-dp { --c: var(--ui-rsi-price); } .ui-rsi-di { --c: var(--ui-rsi-line); }.ui-rsi-col:hover .ui-rsi-dot { display: block; }.ui-rsi-tip { position: absolute; z-index: 20; top: 0; left: calc(50% + 10px); display: none; min-width: 140px; 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; pointer-events: none; }.ui-rsi-flip .ui-rsi-tip { left: auto; right: calc(50% + 10px); }.ui-rsi-col:hover .ui-rsi-tip { display: block; }.ui-rsi-tip strong { display: block; margin-bottom: 3px; font-weight: 600; white-space: nowrap; }.ui-rsi-tip p { display: flex; align-items: center; gap: 6px; margin: 2px 0 0; color: var(--ui-muted); white-space: nowrap; }.ui-rsi-tip b { margin-left: auto; padding-left: 12px; color: var(--ui-fg); font-weight: 600; font-variant-numeric: tabular-nums; }.ui-rsi-tip .ui-rsi-state { color: var(--ui-muted); font-size: 11px; }.ui-rsi-tip .ui-rsi-over { color: var(--ui-danger); } .ui-rsi-tip .ui-rsi-under { color: var(--ui-success); }.ui-rsi-y span { position: absolute; left: 0; transform: translateY(-50%); color: var(--ui-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }.ui-rsi-y .ui-rsi-tag { z-index: 1; left: -4px; padding: 1px 4px; border-radius: 3px; background: var(--c); color: var(--ui-bg); font-weight: 600; }.ui-rsi-x { position: relative; height: 16px; margin-top: 6px; }.ui-rsi-x span { position: absolute; top: 0; transform: translateX(-50%); color: var(--ui-muted); white-space: nowrap; }.ui-rsi-x .ui-rsi-first { transform: none; }.ui-rsi-x .ui-rsi-end { transform: translateX(-100%); }`// n (a whole number, 0 or more) with at least `width` digits.fn rsi_chart_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 rsi_chart_group(n: int) -> str =    n < 1000 ? str(n) : rsi_chart_group(floor(n / 1000)) + "," + rsi_chart_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 rsi_chart_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)}` : `.${rsi_chart_pad(frac, digits)}`)    `${v < 0 && units > 0 ? "-" : ""}${rsi_chart_group(floor(units / scale))}${tail}`}// A large number, short: 1500 is "1.5k", 2000000 is "2M", 0.25 is "0.25".fn rsi_chart_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 ? rsi_chart_num(v)        : `${v < 0 ? "-" : ""}${rsi_chart_group(floor(tenths / 10))}${tenths % 10 != 0 ? `.${tenths % 10}` : ""}${mark}`}// A change in percent, signed, to two decimals: "+1.25%", "-0.40%".fn rsi_chart_signed(p: num) -> str = `${p > 0 ? "+" : (p < 0 && round(abs(p) * 100) > 0 ? "-" : "")}${rsi_chart_num(abs(p), 2)}%`// A coordinate out of 100 (or a length), to two decimals.fn rsi_chart_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 rsi_chart_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 rsi_chart_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 rsi_chart_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 RSI of each close (Wilder's): null for the first `period`, then 100 - 100 / (1 + the// average gain / the average loss), the averages smoothed as (last * (period - 1) + now) / period.fn rsi_chart_rsi(xs: list, period: int) -> list {    let out = []    let gain = 0    let loss = 0    for i in range(len(xs)) {        let d = i == 0 ? 0 : xs[i] - xs[i - 1]        let up = d > 0 ? d : 0        let down = d < 0 ? 0 - d : 0        if i > 0 && i <= period {            gain += up / period            loss += down / period        } else if i > period {            gain = (gain * (period - 1) + up) / period            loss = (loss * (period - 1) + down) / period        }        out = out | push(i < period || period < 1 ? null : (loss == 0 ? (gain == 0 ? 50 : 100) : 100 - 100 / (1 + gain / loss)))    }    out}view RsiChart(candles: list = [], closes: list? = null, labels: list? = null, period: int = 14, overbought: num = 70,              oversold: num = 30, price: bool = true, height: int = 200, rsi_height: int = 110, prefix: str = "",              suffix: str = "", decimals: int = 2, label: str = "RSI chart", show_label: bool = false, class: str? = null) {    style global rsi_chart_css    let vals = closes ?? (candles | map((c) => c.close))    let times = closes != null ? (labels ?? []) : (candles | map((c) => c.time))    let n = len(vals)    let rsi = rsi_chart_rsi(vals, period)    let sc = rsi_chart_scale(n == 0 ? 0 : min(vals), n == 0 ? 1 : max(vals), 4)    let y = (v) => (sc.hi - v) * 100 / (sc.hi - sc.lo)    let ri = (v) => 100 - v    let x = (i) => (i + 0.5) * 100 / n    let pct = (v) => rsi_chart_pct(v)    let money = (v) => `${v < 0 ? "-" : ""}${prefix}${rsi_chart_num(abs(v), decimals)}${suffix}`    let tick = (t) => `${t < 0 ? "-" : ""}${prefix}${rsi_chart_num(abs(t))}`    let path = (list_vals: list, at) => {        let idx = range(n) | filter((i) => list_vals[i] != null)        len(idx) == 0 ? "" : "M" + join(idx | map((i) => `${pct(x(i))},${pct(at(list_vals[i]))}`), " L")    }    let rline = path(rsi, ri)    let pline = path(vals, y)    let last_rsi = n > 0 ? rsi[-1] : null    let state = (v) => v == null ? "" : (v >= overbought ? "Overbought" : (v <= oversold ? "Oversold" : ""))    let when = (i) => i < len(times) ? rsi_chart_when(times[i]) : `${i + 1}`    let axis_width = max([4] + (sc.ticks | map((t) => len(tick(t)))) + [n > 0 ? len(money(vals[-1])) + 1 : 0])    let near = (t) => price && n > 0 && abs(y(t) - y(vals[-1])) * height < 1400    <figure class={ui_cls(["ui-rsi", class])} aria-label={label} style={`--h: ${height}px; --ih: ${rsi_height}px`}>        {show_label && <figcaption.ui-rsi-title>{label}</figcaption>}        <div.ui-rsi-frame aria-hidden="true">            <div.ui-rsi-plot>                {price && <div.ui-rsi-pane>                    {for t in sc.ticks => <span.ui-rsi-grid style={`top: ${pct(y(t))}%`}></span>}                    <svg.ui-rsi-svg viewBox="0 0 100 100" preserveAspectRatio="none">                        {n > 0 && <path.ui-rsi-fill d={`${pline} L${pct(x(n - 1))},100 L${pct(x(0))},100 Z`}/>}                        <path.ui-rsi-price d={pline} vector-effect="non-scaling-stroke"/>                    </svg>                </div>}                <div.ui-rsi-ipane>                    <span.ui-rsi-name>RSI {period}</span>                    <span.ui-rsi-band style={`top: ${pct(ri(overbought))}%; height: ${pct(overbought - oversold)}%`}></span>                    <span.ui-rsi-grid style="top: 50%"></span>                    <span.ui-rsi-level style={`top: ${pct(ri(overbought))}%`}></span>                    <span.ui-rsi-level style={`top: ${pct(ri(oversold))}%`}></span>                    <svg.ui-rsi-svg viewBox="0 0 100 100" preserveAspectRatio="none">                        <path.ui-rsi-line d={rline} vector-effect="non-scaling-stroke"/>                    </svg>                    <svg.ui-rsi-svg.ui-rsi-hot viewBox="0 0 100 100" preserveAspectRatio="none"                         style={`clip-path: inset(0 0 ${pct(100 - ri(overbought))}% 0)`}>                        <path d={rline} vector-effect="non-scaling-stroke"/>                    </svg>                    <svg.ui-rsi-svg.ui-rsi-cold viewBox="0 0 100 100" preserveAspectRatio="none"                         style={`clip-path: inset(${pct(ri(oversold))}% 0 0 0)`}>                        <path d={rline} vector-effect="non-scaling-stroke"/>                    </svg>                </div>                <div.ui-rsi-cols>                    {for i in range(n) => <div class={ui_cls(["ui-rsi-col", n > 1 && i >= n / 2 && "ui-rsi-flip"])}>                        {price && <div.ui-rsi-pane><span.ui-rsi-dot.ui-rsi-dp style={`top: ${pct(y(vals[i]))}%`}></span></div>}                        <div.ui-rsi-ipane>{rsi[i] != null && <span.ui-rsi-dot.ui-rsi-di style={`top: ${pct(ri(rsi[i]))}%`}></span>}</div>                        <span.ui-rsi-tip>                            <strong>{when(i)}</strong>                            <p>Close<b>{money(vals[i])}</b></p>                            <p>RSI {period}<b>{rsi[i] == null ? "—" : rsi_chart_num(rsi[i], 1)}</b></p>                            {state(rsi[i]) != "" && <p class={`ui-rsi-state ${rsi[i] >= overbought ? "ui-rsi-over" : "ui-rsi-under"}`}>{state(rsi[i])}</p>}                        </span>                    </div>}                </div>            </div>            <div.ui-rsi-y style={`width: ${axis_width}ch`}>                {price && <div.ui-rsi-yp>                    {for t in sc.ticks => !near(t) && <span style={`top: ${pct(y(t))}%`}>{tick(t)}</span>}                    {n > 0 && <span.ui-rsi-tag.ui-rsi-dp style={`top: ${pct(y(vals[-1]))}%`}>{money(vals[-1])}</span>}                </div>}                <div.ui-rsi-yi>                    {for t in [overbought, 50, oversold] => !(last_rsi != null && abs(ri(t) - ri(last_rsi)) * rsi_height < 1300) &&                        <span style={`top: ${pct(ri(t))}%`}>{rsi_chart_num(t)}</span>}                    {last_rsi != null && <span.ui-rsi-tag.ui-rsi-di style={`top: ${pct(ri(last_rsi))}%`}>{rsi_chart_num(last_rsi, 1)}</span>}                </div>            </div>            <div.ui-rsi-x>                {len(times) == n && (for t in rsi_chart_ticks(times, 7) => <span                    class={ui_cls([x(t.i) < 4 && "ui-rsi-first", x(t.i) > 96 && "ui-rsi-end"])} style={`left: ${pct(x(t.i))}%`}>{t.text}</span>)}            </div>            <span></span>        </div>        <table.ui-sr>            <caption>{label}: close and RSI {period}</caption>            <thead><tr><th scope="col">Time</th><th scope="col">Close</th><th scope="col">RSI</th></tr></thead>            <tbody>                {for i in range(n) => <tr>                    <th scope="row">{when(i)}</th><td>{money(vals[i])}</td>                    <td>{rsi[i] == null ? "—" : rsi_chart_num(rsi[i], 1)}{state(rsi[i]) != "" ? `, ${lower(state(rsi[i]))}` : ""}</td>                </tr>}            </tbody>        </table>    </figure>}
Thread
58Useful? Like it, or add to its thread below.

Thread 0 posts

On RsiChart: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.