Charts

MacdChart

MACD from closing prices: the MACD and signal lines and the histogram between them, under a price chart or on its own.

#chart#data#trading#no-js

Demo

Preview
Wayne Enterprises, MACD 12 26 9
Wayne Enterprises, MACD 12 26 9: close, MACD (12, 26), signal (9) and histogram
TimeCloseMACDSignalHistogram
Mar 16, 2026$308.36———
Mar 17, 2026$305.02———
Mar 18, 2026$303.38———
Mar 19, 2026$304.73———
Mar 20, 2026$302.74———
Mar 23, 2026$301.49———
Mar 24, 2026$305.59———
Mar 25, 2026$308.12———
Mar 26, 2026$305.03———
Mar 27, 2026$307.03———
Mar 30, 2026$305.82———
Mar 31, 2026$310.51———
Apr 1, 2026$313.01———
Apr 2, 2026$312.74———
Apr 3, 2026$308.09———
Apr 6, 2026$305.90———
Apr 7, 2026$311.02———
Apr 8, 2026$312.47———
Apr 9, 2026$312.37———
Apr 10, 2026$311.58———
Apr 13, 2026$316.64———
Apr 14, 2026$319.87———
Apr 15, 2026$317.10———
Apr 16, 2026$317.51———
Apr 17, 2026$321.75———
Apr 20, 2026$316.944.9847——
Apr 21, 2026$319.104.9290——
Apr 22, 2026$318.604.7893——
Apr 23, 2026$323.735.0345——
Apr 24, 2026$325.215.2873——
Apr 27, 2026$322.235.1874——
Apr 28, 2026$327.695.4855——
Apr 29, 2026$326.925.5952——
Apr 30, 2026$321.655.19695.16550.0314
May 1, 2026$322.594.90075.1126-0.2119
May 4, 2026$326.564.92945.0759-0.1465
May 5, 2026$323.834.67804.9963-0.3183
May 6, 2026$328.244.77954.9530-0.1735
May 7, 2026$330.935.01914.96620.0529
May 8, 2026$334.045.39785.05250.3452
May 11, 2026$328.185.16545.07510.0903
May 12, 2026$325.774.73235.0065-0.2742
May 13, 2026$321.744.01754.8087-0.7912
May 14, 2026$323.683.56654.5603-0.9938
May 15, 2026$326.113.36644.3215-0.9552
May 18, 2026$325.863.15124.0875-0.9362
May 19, 2026$330.193.29223.9284-0.6362
May 20, 2026$326.093.03803.7503-0.7123
May 21, 2026$328.032.95903.5921-0.6330
May 22, 2026$334.353.36763.5472-0.1796
May 25, 2026$328.223.16033.4698-0.3095
May 26, 2026$323.792.60853.2975-0.6890
May 27, 2026$328.702.53813.1457-0.6075
May 28, 2026$323.882.06962.9304-0.8609
May 29, 2026$324.931.76262.6969-0.9342
Jun 1, 2026$324.951.50372.4582-0.9546
Jun 2, 2026$324.731.26612.2198-0.9537
Jun 3, 2026$323.971.00491.9768-0.9719
Jun 4, 2026$324.000.79121.7397-0.9485
Jun 5, 2026$322.580.50151.4921-0.9906
Jun 8, 2026$321.920.21611.2369-1.0207
Jun 9, 2026$323.250.09621.0087-0.9125
Jun 10, 2026$326.660.27320.8616-0.5885
Jun 11, 2026$328.480.55390.8001-0.2462
Jun 12, 2026$324.340.43720.7275-0.2903
Jun 15, 2026$326.370.50280.6826-0.1797
Jun 16, 2026$324.760.42000.6301-0.2100
Jun 17, 2026$320.16-0.01660.5007-0.5173
Jun 18, 2026$323.64-0.08080.3844-0.4653
Jun 19, 2026$325.350.00610.3088-0.3026
Jun 22, 2026$320.41-0.31990.1830-0.5029
Jun 23, 2026$319.69-0.62910.0206-0.6497
Jun 24, 2026$323.04-0.5969-0.1029-0.4940
Jun 25, 2026$322.86-0.5793-0.1982-0.3811
Jun 26, 2026$322.36-0.5987-0.2783-0.3205
Jun 29, 2026$319.38-0.8449-0.3916-0.4533
Jun 30, 2026$321.95-0.8231-0.4779-0.3452
Jul 1, 2026$319.26-1.0112-0.5846-0.4267
Jul 2, 2026$315.46-1.4502-0.7577-0.6925
Jul 3, 2026$311.43-2.0992-1.0260-1.0732
Jul 6, 2026$310.50-2.6578-1.3524-1.3055
Jul 7, 2026$308.41-3.2320-1.7283-1.5037
Jul 8, 2026$312.55-3.3147-2.0456-1.2691
Jul 9, 2026$310.92-3.4718-2.3308-1.1410
Jul 10, 2026$310.38-3.5984-2.5843-1.0140
Jul 13, 2026$309.07-3.7610-2.8197-0.9414
Jul 14, 2026$309.04-3.8480-3.0253-0.8227
Jul 15, 2026$305.63-4.1443-3.2491-0.8952
Jul 16, 2026$307.49-4.1809-3.4355-0.7454
Jul 17, 2026$310.80-3.8978-3.5279-0.3699
Jul 20, 2026$315.63-3.2463-3.47160.2253
Jul 21, 2026$318.79-2.4468-3.26670.8198
Jul 22, 2026$315.09-2.0877-3.03090.9432
Jul 23, 2026$313.55-1.9054-2.80580.9004
Jul 24, 2026$315.69-1.5702-2.55870.9885
Jul 27, 2026$319.64-0.9745-2.24181.2673
Jul 28, 2026$323.18-0.2143-1.83631.6220
Jul 29, 2026$327.210.7052-1.32802.0332
Jul 30, 2026$324.121.1711-0.82821.9993
Jul 31, 2026$328.531.8745-0.28772.1622
Aug 3, 2026$329.182.45620.26112.1950
Aug 4, 2026$335.403.38000.88492.4951
Aug 5, 2026$337.634.24321.55662.6867
Aug 6, 2026$343.205.31552.30843.0072
Aug 7, 2026$342.726.05683.05802.9987
Aug 10, 2026$349.607.11733.86993.2474
Aug 11, 2026$344.657.47234.59042.8819
Aug 12, 2026$343.487.57195.18672.3852
Aug 13, 2026$348.647.97525.74442.2308
Aug 14, 2026$349.028.23076.24161.9890
Aug 17, 2026$353.588.70086.73351.9673
Aug 18, 2026$350.988.76257.13931.6233
Aug 19, 2026$355.889.10207.53181.5701
Aug 20, 2026$357.799.41657.90881.5078
Aug 21, 2026$357.189.50708.22841.2786
Aug 24, 2026$363.169.94668.57201.3745
Aug 25, 2026$360.079.93118.84391.0873
Aug 26, 2026$358.649.69189.01340.6783
Aug 27, 2026$357.799.32609.07590.2500
Aug 28, 2026$352.908.54308.9694-0.4263
Aug 31, 2026$357.818.22408.8203-0.5963
Sep 1, 2026$359.938.04948.6661-0.6167
Sep 2, 2026$357.217.60388.4536-0.8498
Sep 3, 2026$357.137.16188.1953-1.0335
Sep 4, 2026$362.647.17337.9909-0.8176
Sep 7, 2026$361.527.01137.7950-0.7837
Sep 8, 2026$359.006.60347.5567-0.9532
Sep 9, 2026$365.036.68967.3833-0.6936
Sep 10, 2026$360.416.31247.1691-0.8567
Sep 11, 2026$359.625.88196.9116-1.0297
MACD 5 35 5, on its own
MACD 5 35 5, on its own: close, MACD (5, 35), signal (5) and histogram
TimeCloseMACDSignalHistogram
Mar 16, 2026308.36———
Mar 17, 2026305.02———
Mar 18, 2026303.38———
Mar 19, 2026304.73———
Mar 20, 2026302.74———
Mar 23, 2026301.49———
Mar 24, 2026305.59———
Mar 25, 2026308.12———
Mar 26, 2026305.03———
Mar 27, 2026307.03———
Mar 30, 2026305.82———
Mar 31, 2026310.51———
Apr 1, 2026313.01———
Apr 2, 2026312.74———
Apr 3, 2026308.09———
Apr 6, 2026305.90———
Apr 7, 2026311.02———
Apr 8, 2026312.47———
Apr 9, 2026312.37———
Apr 10, 2026311.58———
Apr 13, 2026316.64———
Apr 14, 2026319.87———
Apr 15, 2026317.10———
Apr 16, 2026317.51———
Apr 17, 2026321.75———
Apr 20, 2026316.94———
Apr 21, 2026319.10———
Apr 22, 2026318.60———
Apr 23, 2026323.73———
Apr 24, 2026325.21———
Apr 27, 2026322.23———
Apr 28, 2026327.69———
Apr 29, 2026326.92———
Apr 30, 2026321.65———
May 1, 2026322.599.9019——
May 4, 2026326.5610.2288——
May 5, 2026323.839.4869——
May 6, 2026328.2410.0691——
May 7, 2026330.9310.996410.13660.8598
May 8, 2026334.0412.240510.83791.4026
May 11, 2026328.1811.169410.94840.2210
May 12, 2026325.779.618710.5052-0.8865
May 13, 2026321.747.34479.4517-2.1069
May 14, 2026323.686.31598.4064-2.0905
May 15, 2026326.116.22617.6797-1.4535
May 18, 2026325.865.98497.1147-1.1299
May 19, 2026330.196.92497.0515-0.1265
May 20, 2026326.096.24976.7842-0.5345
May 21, 2026328.036.24776.6053-0.3577
May 22, 2026334.357.88637.03230.8539
May 25, 2026328.227.06917.04460.0246
May 26, 2026323.795.19326.4275-1.2343
May 27, 2026328.705.27986.0449-0.7651
May 28, 2026323.883.89765.3291-1.4315
May 29, 2026324.933.24694.6350-1.3882
Jun 1, 2026324.952.78264.0175-1.2350
Jun 2, 2026324.732.37763.4709-1.0933
Jun 3, 2026323.971.86742.9364-1.0690
Jun 4, 2026324.001.52002.4643-0.9443
Jun 5, 2026322.580.87861.9357-1.0571
Jun 8, 2026321.920.27521.3822-1.1070
Jun 9, 2026323.250.25961.0080-0.7484
Jun 10, 2026326.661.19221.06940.1228
Jun 11, 2026328.482.26291.46730.7957
Jun 12, 2026324.341.74511.55990.1852
Jun 15, 2026326.371.95071.69020.2605
Jun 16, 2026324.761.59681.6590-0.0623
Jun 17, 2026320.160.06661.1282-1.0616
Jun 18, 2026323.640.06860.7750-0.7064
Jun 19, 2026325.350.54360.6979-0.1543
Jun 22, 2026320.41-0.53970.2854-0.8250
Jun 23, 2026319.69-1.4117-0.2803-1.1314
Jun 24, 2026323.04-1.0041-0.5216-0.4825
Jun 25, 2026322.86-0.7788-0.6073-0.1715
Jun 26, 2026322.36-0.7615-0.6587-0.1028
Jun 29, 2026319.38-1.5642-0.9605-0.6037
Jun 30, 2026321.95-1.3268-1.0826-0.2442
Jul 1, 2026319.26-1.8999-1.3551-0.5449
Jul 2, 2026315.46-3.2812-1.9971-1.2841
Jul 3, 2026311.43-5.2096-3.0679-2.1416
Jul 6, 2026310.50-6.5856-4.2405-2.3451
Jul 7, 2026308.41-7.9105-5.4638-2.4467
Jul 8, 2026312.55-7.4483-6.1253-1.3230
Jul 9, 2026310.92-7.4721-6.5742-0.8979
Jul 10, 2026310.38-7.4987-6.8824-0.6163
Jul 13, 2026309.07-7.7405-7.1684-0.5721
Jul 14, 2026309.04-7.7577-7.3649-0.3929
Jul 15, 2026305.63-8.5721-7.7673-0.8048
Jul 16, 2026307.49-8.4095-7.9814-0.4281
Jul 17, 2026310.80-7.2319-7.73150.4996
Jul 20, 2026315.63-5.0149-6.82601.8111
Jul 21, 2026318.79-2.6483-5.43342.7851
Jul 22, 2026315.09-2.1370-4.33462.1976
Jul 23, 2026313.55-2.2033-3.62421.4209
Jul 24, 2026315.69-1.6098-2.95271.3429
Jul 27, 2026319.64-0.1090-2.00481.8958
Jul 28, 2026323.181.8212-0.72952.5507
Jul 29, 2026327.214.12230.88783.2345
Jul 30, 2026324.124.63652.13732.4991
Jul 31, 2026328.536.09933.45802.6413
Aug 3, 2026329.187.08804.66802.4200
Aug 4, 2026335.409.30706.21433.0927
Aug 5, 2026337.6311.15127.86003.2913
Aug 6, 2026343.2013.65329.79103.8621
Aug 7, 2026342.7214.842311.47483.3675
Aug 10, 2026349.6017.227213.39233.8350
Aug 11, 2026344.6517.034814.60642.4284
Aug 12, 2026343.4816.273215.16201.1112
Aug 13, 2026348.6416.925715.74991.1758
Aug 14, 2026349.0217.128616.20950.9191
Aug 17, 2026353.5818.205916.87491.3309
Aug 18, 2026350.9817.824717.19150.6332
Aug 19, 2026355.8818.615817.66630.9495
Aug 20, 2026357.7919.299718.21081.0889
Aug 21, 2026357.1819.203518.54170.6618
Aug 24, 2026363.1620.448419.17721.2711
Aug 25, 2026360.0719.995219.44990.5453
Aug 26, 2026358.6418.942319.2807-0.3384
Aug 27, 2026357.7917.692518.7513-1.0588
Aug 28, 2026352.9015.219717.5741-2.3544
Aug 31, 2026357.8114.744716.6310-1.8863
Sep 1, 2026359.9314.761516.0078-1.2463
Sep 2, 2026357.2113.743115.2529-1.5098
Sep 3, 2026357.1312.825214.4437-1.6184
Sep 4, 2026362.6413.540414.1426-0.6022
Sep 7, 2026361.5213.428813.9046-0.4759
Sep 8, 2026359.0012.409813.4064-0.9966
Sep 9, 2026365.0313.213413.3420-0.1286
Sep 10, 2026360.4112.191412.9585-0.7671
Sep 11, 2026359.6211.102712.3399-1.2372

Histogram bars are paler while they shrink; hover for the MACD, signal and histogram.

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

Example

The code of the preview above.

demos/macd_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 macd_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 macd_chart_demo_daily = macd_chart_demo_walk(47, 130, 312.6, "2026-03-16")view MacdChartDemo() =    <div.demo-stack>        <div id="macd_chart-demo-price">            <MacdChart label="Wayne Enterprises, MACD 12 26 9" show_label={true} candles={macd_chart_demo_daily} prefix="$"/>        </div>        <div id="macd_chart-demo-alone">            <MacdChart label="MACD 5 35 5, on its own" show_label={true} candles={macd_chart_demo_daily}                       fast={5} slow={35} signal={5} price={false} macd_height={110}/>        </div>        <p.demo-note>Histogram bars are paler while they shrink; hover for the MACD, signal and histogram.</p>    </div>

Source

ui/macd_chart.qwe
// MacdChart: the MACD of closing prices: the MACD line (the fast EMA less the slow// one), its signal line (an EMA of the MACD) and the histogram of the gap between// them, green above zero and red below, paler while it shrinks. Under a price chart// by default, or on its own. CSS only: hovering shows the close, MACD, signal and// histogram at that time, and screen readers get the numbers as a table.////   <MacdChart label="ACME, MACD 12 26 9" candles={bars} prefix="$"/>//   <MacdChart label="MACD 5 35 5" candles={bars} fast={5} slow={35} signal={5} price={false}/>//// 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.// fast, slow and signal: the periods of the two EMAs and of the signal line (12, 26// and 9); the MACD starts at the slow-th close, the signal and histogram signal - 1// closes later. price: the price chart above (true); height: its height in pixels// (200); macd_height: the MACD pane's (130). prefix and suffix: around every price// shown ("$"); decimals: digits after the point of prices (2), and of the MACD's// values, two more. 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 macd_chart_css = css`.ui-macd { --ui-macd-price: light-dark(#2a78d6, #3987e5); --ui-macd-line: light-dark(#2a78d6, #3987e5);  --ui-macd-signal: light-dark(#eb6834, #d95926);  display: grid; gap: 10px; min-width: 0; margin: 0; font: 12px/1.3 var(--ui-font); color: var(--ui-fg); }.ui-macd-title { margin: 0; font-size: 14px; font-weight: 600; }.ui-macd-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 0; padding: 0; list-style: none; color: var(--ui-muted); font-size: 13px; }.ui-macd-legend li { display: inline-flex; align-items: center; gap: 6px; }.ui-macd-key { width: 14px; height: 3px; border-radius: 2px; background: var(--c); }.ui-macd-kh { width: 10px; height: 10px; border-radius: 2px; background: linear-gradient(90deg, var(--ui-success) 50%, var(--ui-danger) 50%); }.ui-macd-cl { --c: var(--ui-macd-line); } .ui-macd-cs { --c: var(--ui-macd-signal); } .ui-macd-cp { --c: var(--ui-macd-price); }.ui-macd-up { --k: var(--ui-success); } .ui-macd-down { --k: var(--ui-danger); }.ui-macd-frame { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 8px; padding-top: 8px; }.ui-macd-plot { position: relative; }.ui-macd-pane, .ui-macd-yp { position: relative; height: var(--h); }.ui-macd-ipane, .ui-macd-yi { position: relative; height: var(--ih); }.ui-macd-pane + .ui-macd-ipane, .ui-macd-yp + .ui-macd-yi { margin-top: 18px; }.ui-macd-name { position: absolute; z-index: 1; top: 2px; left: 4px; color: var(--ui-muted); font-size: 11px; font-weight: 600; pointer-events: none; }.ui-macd-grid { position: absolute; left: 0; right: 0; height: 1px; background: color-mix(in srgb, var(--ui-border) 70%, transparent); }.ui-macd-zero { background: var(--ui-strong); }.ui-macd-svg { position: absolute; z-index: 1; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }.ui-macd-svg path { fill: none; stroke: var(--c); stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }.ui-macd-svg .ui-macd-fill { fill: var(--ui-macd-price); stroke: none; opacity: .08; }.ui-macd-cols { position: absolute; inset: 0; display: flex; }.ui-macd-col { position: relative; flex: 1; min-width: 0; }.ui-macd-col::before { content: ""; position: absolute; top: 0; bottom: 0; left: 50%; width: 1px; background: var(--ui-strong); opacity: 0; }.ui-macd-col:hover::before { opacity: .9; }.ui-macd-hist { position: absolute; left: 18%; right: 18%; min-height: 1px; background: var(--k); }.ui-macd-fade { opacity: .45; }.ui-macd-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-macd-col:hover .ui-macd-dot { display: block; }.ui-macd-tip { position: absolute; z-index: 20; top: 0; left: calc(50% + 10px); display: none; min-width: 150px; 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-macd-flip .ui-macd-tip { left: auto; right: calc(50% + 10px); }.ui-macd-col:hover .ui-macd-tip { display: block; }.ui-macd-tip strong { display: block; margin-bottom: 3px; font-weight: 600; white-space: nowrap; }.ui-macd-tip p { display: flex; align-items: center; gap: 6px; margin: 2px 0 0; color: var(--ui-muted); white-space: nowrap; }.ui-macd-tip b { margin-left: auto; padding-left: 12px; color: var(--ui-fg); font-weight: 600; font-variant-numeric: tabular-nums; }.ui-macd-tip .ui-macd-hv b { color: var(--k); }.ui-macd-y span { position: absolute; left: 0; transform: translateY(-50%); color: var(--ui-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }.ui-macd-y .ui-macd-tag { z-index: 1; left: -4px; padding: 1px 4px; border-radius: 3px; background: var(--c); color: var(--ui-bg); font-weight: 600; }.ui-macd-x { position: relative; height: 16px; margin-top: 6px; }.ui-macd-x span { position: absolute; top: 0; transform: translateX(-50%); color: var(--ui-muted); white-space: nowrap; }.ui-macd-x .ui-macd-first { transform: none; }.ui-macd-x .ui-macd-end { transform: translateX(-100%); }`// n (a whole number, 0 or more) with at least `width` digits.fn macd_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 macd_chart_group(n: int) -> str =    n < 1000 ? str(n) : macd_chart_group(floor(n / 1000)) + "," + macd_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 macd_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)}` : `.${macd_chart_pad(frac, digits)}`)    `${v < 0 && units > 0 ? "-" : ""}${macd_chart_group(floor(units / scale))}${tail}`}// A large number, short: 1500 is "1.5k", 2000000 is "2M", 0.25 is "0.25".fn macd_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 ? macd_chart_num(v)        : `${v < 0 ? "-" : ""}${macd_chart_group(floor(tenths / 10))}${tenths % 10 != 0 ? `.${tenths % 10}` : ""}${mark}`}// A change in percent, signed, to two decimals: "+1.25%", "-0.40%".fn macd_chart_signed(p: num) -> str = `${p > 0 ? "+" : (p < 0 && round(abs(p) * 100) > 0 ? "-" : "")}${macd_chart_num(abs(p), 2)}%`// A coordinate out of 100 (or a length), to two decimals.fn macd_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 macd_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 macd_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 macd_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 simple moving average of `period` values, null until there are that many.fn macd_chart_sma(xs: list, period: int) -> list {    let out = []    let total = 0    for i in range(len(xs)) {        total += xs[i]        if i >= period { total -= xs[i - period] }        out = out | push(i + 1 >= period ? total / period : null)    }    out}// The exponential moving average of `period` values (k = 2 / (period + 1)), started// from the simple average of the first `period`; null before that.fn macd_chart_ema(xs: list, period: int) -> list {    let k = 2 / (period + 1)    let out = []    let prev = null    for i in range(len(xs)) {        if i + 1 == period {            prev = sum(take(xs, period)) / period        } else if i + 1 > period {            prev = xs[i] * k + prev * (1 - k)        }        out = out | push(prev)    }    out}// The MACD of the closes: { macd, signal, hist }, three lists with null until each starts.fn macd_chart_macd(xs: list, fast: int, slow: int, signal: int) -> map {    let a = macd_chart_ema(xs, fast)    let b = macd_chart_ema(xs, slow)    let macd = for i in range(len(xs)) => a[i] == null || b[i] == null ? null : a[i] - b[i]    let start = index_of(macd | map((v) => v != null), true)    let sig0 = start == null ? [] : macd_chart_ema(drop(macd, start), signal)    let sig = for i in range(len(xs)) => start == null || i < start ? null : sig0[i - start]    let hist = for i in range(len(xs)) => macd[i] == null || sig[i] == null ? null : macd[i] - sig[i]    { macd, signal: sig, hist }}view MacdChart(candles: list = [], closes: list? = null, labels: list? = null, fast: int = 12, slow: int = 26,               signal: int = 9, price: bool = true, height: int = 200, macd_height: int = 130, prefix: str = "",               suffix: str = "", decimals: int = 2, label: str = "MACD chart", show_label: bool = false,               class: str? = null) {    style global macd_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 m = macd_chart_macd(vals, fast, slow, signal)    let sc = macd_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 ind = (m.macd + m.signal + m.hist) | filter((v) => v != null)    let reach = max([0.000001] + (ind | map((v) => abs(v))))    let isc = macd_chart_scale(0 - reach, reach, 6)    let iy = (v) => (isc.hi - v) * 100 / (isc.hi - isc.lo)    let x = (i) => (i + 0.5) * 100 / n    let pct = (v) => macd_chart_pct(v)    let money = (v) => `${v < 0 ? "-" : ""}${prefix}${macd_chart_num(abs(v), decimals)}${suffix}`    let tick = (t) => `${t < 0 ? "-" : ""}${prefix}${macd_chart_num(abs(t))}`    let fine = (v) => v == null ? "—" : macd_chart_num(v, decimals + 2)    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 pline = path(vals, y)    let when = (i) => i < len(times) ? macd_chart_when(times[i]) : `${i + 1}`    let shrinking = (i) => i > 0 && m.hist[i - 1] != null && abs(m.hist[i]) < abs(m.hist[i - 1])    let last_macd = n > 0 ? m.macd[-1] : null    let axis_width = max([5] + (sc.ticks | map((t) => len(tick(t)))) + (isc.ticks | map((t) => len(macd_chart_num(t)))) +                         [n > 0 ? len(money(vals[-1])) + 1 : 0, last_macd != null ? len(fine(last_macd)) + 1 : 0])    <figure class={ui_cls(["ui-macd", class])} aria-label={label} style={`--h: ${height}px; --ih: ${macd_height}px`}>        {show_label && <figcaption.ui-macd-title>{label}</figcaption>}        <ul.ui-macd-legend aria-hidden="true">            <li><span.ui-macd-key.ui-macd-cl></span>MACD ({fast}, {slow})</li>            <li><span.ui-macd-key.ui-macd-cs></span>Signal ({signal})</li>            <li><span.ui-macd-kh></span>Histogram</li>        </ul>        <div.ui-macd-frame aria-hidden="true">            <div.ui-macd-plot>                {price && <div.ui-macd-pane>                    {for t in sc.ticks => <span.ui-macd-grid style={`top: ${pct(y(t))}%`}></span>}                    <svg.ui-macd-svg.ui-macd-cp viewBox="0 0 100 100" preserveAspectRatio="none">                        {n > 0 && <path.ui-macd-fill d={`${pline} L${pct(x(n - 1))},100 L${pct(x(0))},100 Z`}/>}                        <path d={pline} vector-effect="non-scaling-stroke"/>                    </svg>                </div>}                <div.ui-macd-ipane>                    <span.ui-macd-name>MACD {fast} {slow} {signal}</span>                    {for t in isc.ticks => <span class={ui_cls(["ui-macd-grid", t == 0 && "ui-macd-zero"])} style={`top: ${pct(iy(t))}%`}></span>}                    <svg.ui-macd-svg viewBox="0 0 100 100" preserveAspectRatio="none">                        <path.ui-macd-cl d={path(m.macd, iy)} vector-effect="non-scaling-stroke"/>                        <path.ui-macd-cs d={path(m.signal, iy)} vector-effect="non-scaling-stroke"/>                    </svg>                </div>                <div.ui-macd-cols>                    {for i in range(n) => <div class={ui_cls(["ui-macd-col", n > 1 && i >= n / 2 && "ui-macd-flip"])}>                        {price && <div.ui-macd-pane><span.ui-macd-dot.ui-macd-cp style={`top: ${pct(y(vals[i]))}%`}></span></div>}                        <div.ui-macd-ipane>                            {m.hist[i] != null && <span class={ui_cls(["ui-macd-hist", m.hist[i] >= 0 ? "ui-macd-up" : "ui-macd-down", shrinking(i) && "ui-macd-fade"])}                                style={`top: ${pct(iy(m.hist[i] > 0 ? m.hist[i] : 0))}%; height: ${pct(abs(iy(m.hist[i]) - iy(0)))}%`}></span>}                            {m.macd[i] != null && <span.ui-macd-dot.ui-macd-cl style={`top: ${pct(iy(m.macd[i]))}%`}></span>}                        </div>                        <span.ui-macd-tip>                            <strong>{when(i)}</strong>                            <p>Close<b>{money(vals[i])}</b></p>                            <p><span.ui-macd-key.ui-macd-cl></span>MACD<b>{fine(m.macd[i])}</b></p>                            <p><span.ui-macd-key.ui-macd-cs></span>Signal<b>{fine(m.signal[i])}</b></p>                            <p class={ui_cls(["ui-macd-hv", m.hist[i] != null && (m.hist[i] >= 0 ? "ui-macd-up" : "ui-macd-down")])}>Histogram<b>{fine(m.hist[i])}</b></p>                        </span>                    </div>}                </div>            </div>            <div.ui-macd-y style={`width: ${axis_width}ch`}>                {price && <div.ui-macd-yp>                    {for t in sc.ticks => !(n > 0 && abs(y(t) - y(vals[-1])) * height < 1400) && <span style={`top: ${pct(y(t))}%`}>{tick(t)}</span>}                    {n > 0 && <span.ui-macd-tag.ui-macd-cp style={`top: ${pct(y(vals[-1]))}%`}>{money(vals[-1])}</span>}                </div>}                <div.ui-macd-yi>                    {for t in isc.ticks => !(last_macd != null && abs(iy(t) - iy(last_macd)) * macd_height < 1400) &&                        <span style={`top: ${pct(iy(t))}%`}>{t < 0 ? "-" : ""}{macd_chart_num(abs(t))}</span>}                    {last_macd != null && <span.ui-macd-tag.ui-macd-cl style={`top: ${pct(iy(last_macd))}%`}>{fine(last_macd)}</span>}                </div>            </div>            <div.ui-macd-x>                {len(times) == n && (for t in macd_chart_ticks(times, 7) => <span                    class={ui_cls([x(t.i) < 4 && "ui-macd-first", x(t.i) > 96 && "ui-macd-end"])} style={`left: ${pct(x(t.i))}%`}>{t.text}</span>)}            </div>            <span></span>        </div>        <table.ui-sr>            <caption>{label}: close, MACD ({fast}, {slow}), signal ({signal}) and histogram</caption>            <thead><tr><th scope="col">Time</th><th scope="col">Close</th><th scope="col">MACD</th><th scope="col">Signal</th><th scope="col">Histogram</th></tr></thead>            <tbody>                {for i in range(n) => <tr>                    <th scope="row">{when(i)}</th><td>{money(vals[i])}</td><td>{fine(m.macd[i])}</td><td>{fine(m.signal[i])}</td><td>{fine(m.hist[i])}</td>                </tr>}            </tbody>        </table>    </figure>}
Thread
75Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.