Finance

FxRateTable

A board of exchange rates: each pair with its flags, bid, ask, the spread in pips and the day's change from the open, coloured.

#finance#forex#table#data#no-js

Demo

Preview
Exchange ratesIndicative, 14:05 UTC
PairBidAskSpread in pipsChange in pipsChange %
EUR/USDEuro / US dollar1.084211.084290.8up +23.5+0.22%
GBP/USDPound / US dollar1.275321.275461.4down -27.1-0.21%
USD/JPYUS dollar / Yen156.238156.2511.3down -65.8-0.42%
USD/CHFUS dollar / Swiss franc0.881140.881311.7unchanged 0.00.00%
USD/PHPUS dollar / Philippine peso58.10558.1403.5up +13.3+0.23%
AUD/USDAussie / US dollar0.664120.664200.8โ€”โ€”
Branch board
PairBidAskSpread in pipsChange in pipsChange %
EUR/GBP0.850110.850261.5up +5.9+0.07%
EUR/JPY169.402169.4312.9down -32.8-0.19%

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

Example

The code of the preview above.

demos/fx_rate_table.qwe
view FxRateTableDemo() =    <div.demo-stack>        <FxRateTable id="fx-rate-table-demo" updated="Indicative, 14:05 UTC" rows={[            { pair: "EUR/USD", name: "Euro / US dollar", bid: 1.08421m, ask: 1.08429m, open: 1.08190m },            { pair: "GBP/USD", name: "Pound / US dollar", bid: 1.27532m, ask: 1.27546m, open: 1.27810m },            { pair: "USD/JPY", name: "US dollar / Yen", bid: 156.238m, ask: 156.251m, open: 156.902m },            { pair: "USD/CHF", name: "US dollar / Swiss franc", bid: 0.88114m, ask: 0.88131m, open: 0.881225m },            { pair: "USD/PHP", name: "US dollar / Philippine peso", bid: 58.105m, ask: 58.140m, open: 57.990m },            { pair: "AUD/USD", name: "Aussie / US dollar", bid: 0.66412m, ask: 0.66420m },        ]}/>        <div style="max-width: 560px">            <FxRateTable label="Branch board" flags={false} compact={true} rows={[                { pair: "EUR/GBP", bid: 0.85011m, ask: 0.85026m, open: 0.84960m },                { pair: "EUR/JPY", bid: 169.402m, ask: 169.431m, open: 169.744m },            ]}/>        </div>    </div>

Source

ui/fx_rate_table.qwe
// FxRateTable: a board of exchange rates, a row per currency pair: the pair with its// flags, the bid and the ask, the spread between them in pips, and the day's change// from the open in pips and percent, green up and red down.////   <FxRateTable rows={[{ pair: "EUR/USD", bid: 1.08421m, ask: 1.08429m, open: 1.08190m },//                       { pair: "USD/JPY", bid: 156.238m, ask: 156.251m, open: 156.902m }]} updated="14:05 UTC"/>//// rows: a list of { pair, bid, ask, open, pip, decimals, name }: pair as "EUR/USD" (base// over quote), bid and ask the prices (decimals best; numbers are read as decimals),// open the day's opening mid price (the change is worked out from it; without one the// change is left blank). pip: the size of a pip (0.01 when the quote is JPY, HUF or// another whose price runs in the hundreds; 0.0001 otherwise). decimals: digits shown// (one past the pip, so 1.08421 and 156.238). name: shown under the pair ("Euro / US// dollar"). flags: the currencies' flags before the codes (true). locale: how numbers// are written ("en-US"). label: what the table is called, its caption ("Exchange// rates"; screen readers hear it too). updated: when the prices are from, beside the// caption ("14:05 UTC"). compact: tighter rows (false). id: the element's id.//// fx_rate_table_pip(pair) and fx_rate_table_flag(code) are what it uses, for other code.import ui/theme.qwe use ui_clslet fx_rate_table_css = css`.ui-fxrates { min-width: 0; overflow-x: auto; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface);  font: 14px/1.35 var(--ui-font); color: var(--ui-fg); }.ui-fxrates-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }.ui-fxrates-table caption { padding: 12px 14px; text-align: left; font-weight: 650; caption-side: top; }.ui-fxrates-table caption small { margin-left: 8px; color: var(--ui-muted); font-size: 12px; font-weight: 400; }.ui-fxrates-table th, .ui-fxrates-table td { padding: 10px 14px; text-align: right; white-space: nowrap; }.ui-fxrates-table thead th { border-top: 1px solid var(--ui-border); border-bottom: 1px solid var(--ui-border); background: var(--ui-subtle);  color: var(--ui-muted); font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }.ui-fxrates-table th:first-child, .ui-fxrates-table td:first-child { text-align: left; }.ui-fxrates-table tbody tr:not(:last-child) > * { border-bottom: 1px solid color-mix(in srgb, var(--ui-border) 60%, transparent); }.ui-fxrates-table tbody tr:hover { background: var(--ui-subtle); }.ui-fxrates-compact .ui-fxrates-table th, .ui-fxrates-compact .ui-fxrates-table td { padding-top: 6px; padding-bottom: 6px; }.ui-fxrates-pair { display: flex; align-items: center; gap: 10px; font-weight: 400; }.ui-fxrates-flags { position: relative; flex: none; width: 34px; height: 22px; font-size: 17px; line-height: 1; }.ui-fxrates-flags span { position: absolute; top: 0; }.ui-fxrates-flags span:last-child { left: 13px; top: 5px; }.ui-fxrates-code b { font-weight: 650; }.ui-fxrates-code small { display: block; color: var(--ui-muted); font-size: 12px; }.ui-fxrates-price { font-weight: 600; }.ui-fxrates-spread { color: var(--ui-muted); }.ui-fxrates-up { --k: var(--ui-success); } .ui-fxrates-down { --k: var(--ui-danger); } .ui-fxrates-flat { --k: var(--ui-muted); }.ui-fxrates-chg { color: var(--k); font-weight: 600; }.ui-fxrates-pill { display: inline-block; min-width: 62px; padding: 2px 7px; border-radius: 4px; background: color-mix(in srgb, var(--k) 14%, transparent);  color: var(--k); font-weight: 600; text-align: right; }.ui-fxrates-none { color: var(--ui-muted); }`let fx_rate_table_flags = { USD: "๐Ÿ‡บ๐Ÿ‡ธ", EUR: "๐Ÿ‡ช๐Ÿ‡บ", GBP: "๐Ÿ‡ฌ๐Ÿ‡ง", JPY: "๐Ÿ‡ฏ๐Ÿ‡ต", CHF: "๐Ÿ‡จ๐Ÿ‡ญ", CAD: "๐Ÿ‡จ๐Ÿ‡ฆ", AUD: "๐Ÿ‡ฆ๐Ÿ‡บ", NZD: "๐Ÿ‡ณ๐Ÿ‡ฟ", CNY: "๐Ÿ‡จ๐Ÿ‡ณ",                            HKD: "๐Ÿ‡ญ๐Ÿ‡ฐ", SGD: "๐Ÿ‡ธ๐Ÿ‡ฌ", INR: "๐Ÿ‡ฎ๐Ÿ‡ณ", PHP: "๐Ÿ‡ต๐Ÿ‡ญ", MXN: "๐Ÿ‡ฒ๐Ÿ‡ฝ", BRL: "๐Ÿ‡ง๐Ÿ‡ท", ZAR: "๐Ÿ‡ฟ๐Ÿ‡ฆ", SEK: "๐Ÿ‡ธ๐Ÿ‡ช", NOK: "๐Ÿ‡ณ๐Ÿ‡ด",                            DKK: "๐Ÿ‡ฉ๐Ÿ‡ฐ", PLN: "๐Ÿ‡ต๐Ÿ‡ฑ", KRW: "๐Ÿ‡ฐ๐Ÿ‡ท", THB: "๐Ÿ‡น๐Ÿ‡ญ", IDR: "๐Ÿ‡ฎ๐Ÿ‡ฉ", MYR: "๐Ÿ‡ฒ๐Ÿ‡พ", AED: "๐Ÿ‡ฆ๐Ÿ‡ช", TRY: "๐Ÿ‡น๐Ÿ‡ท", HUF: "๐Ÿ‡ญ๐Ÿ‡บ",                            CZK: "๐Ÿ‡จ๐Ÿ‡ฟ", ILS: "๐Ÿ‡ฎ๐Ÿ‡ฑ", SAR: "๐Ÿ‡ธ๐Ÿ‡ฆ", VND: "๐Ÿ‡ป๐Ÿ‡ณ", NGN: "๐Ÿ‡ณ๐Ÿ‡ฌ" }// A currency's flag, or null for one without (gold, a crypto coin).fn fx_rate_table_flag(code: str) -> str? = fx_rate_table_flags[upper(code)]// The size of a pip for a pair "EUR/USD": 0.01 when its price runs in the hundreds// (the quote is JPY, HUF, KRW, ...), else 0.0001.fn fx_rate_table_pip(pair: str) -> dec {    let parts = split(upper(pair), "/")    let quote = len(parts) > 1 ? parts[1] : ""    contains(["JPY", "HUF", "KRW", "IDR", "VND", "CLP", "ISK", "THB", "INR", "PHP", "RUB"], quote) ? 0.01m : 0.0001m}// The decimals a pip has: 4 for 0.0001.fn fx_rate_table_pip_places(pip: dec) -> int {    let p = abs(pip)    p >= 1 ? 0 : (p >= 0.1m ? 1 : (p >= 0.01m ? 2 : (p >= 0.001m ? 3 : (p >= 0.0001m ? 4 : 5))))}// A signed number as it reads: "+12.4", "-0.21", "0.0".fn fx_rate_table_signed(d: dec, places: int, locale: str) -> str {    let r = round(d, places, "half_up")    `${r > 0 ? "+" : ""}${format_number(r, locale, places)}`}view FxRateTable(rows: list = [], flags: bool = true, locale: str = "en-US", label: str = "Exchange rates", updated: str? = null,                 compact: bool = false, id: str? = null, class: str? = null) {    style global fx_rate_table_css    let any_open = any(rows, (r) => get(r, "open") != null)    <div class={ui_cls(["ui-fxrates", compact && "ui-fxrates-compact", class])} id={id}>        <table.ui-fxrates-table>            <caption>{label}{updated != null && <small>{updated}</small>}</caption>            <thead><tr>                <th scope="col">Pair</th>                <th scope="col">Bid</th>                <th scope="col">Ask</th>                <th scope="col">Spread <span.ui-sr>in pips</span></th>                {any_open && <th scope="col">Change <span.ui-sr>in pips</span></th>}                {any_open && <th scope="col">Change %</th>}            </tr></thead>            <tbody>                {for r in rows {                    let parts = split(upper(r.pair), "/")                    let pip = get(r, "pip") != null ? dec(r.pip) : fx_rate_table_pip(r.pair)                    let places = get(r, "decimals") ?? fx_rate_table_pip_places(pip) + 1                    let bid = dec(r.bid)                    let ask = dec(r.ask)                    let mid = (bid + ask) / 2                    let open = get(r, "open") != null ? dec(r.open) : null                    let pips = open != null ? (mid - open) / pip : null                    let pct = open != null && open != 0 ? (mid - open) * 100 / open : null                    let side = pips == null ? "ui-fxrates-flat" : (round(pips, 1, "half_up") == 0 ? "ui-fxrates-flat" : (pips > 0 ? "ui-fxrates-up" : "ui-fxrates-down"))                    <tr key={r.pair} class={side} data-pair={r.pair}>                        <th scope="row"><span.ui-fxrates-pair>                            {flags && <span.ui-fxrates-flags aria-hidden="true">                                <span>{fx_rate_table_flag(parts[0]) ?? ""}</span><span>{len(parts) > 1 ? fx_rate_table_flag(parts[1]) ?? "" : ""}</span>                            </span>}                            <span.ui-fxrates-code><b>{r.pair}</b>{get(r, "name") != null && <small>{r.name}</small>}</span>                        </span></th>                        <td.ui-fxrates-price data-col="bid">{format_number(round(bid, places, "half_up"), locale, places)}</td>                        <td.ui-fxrates-price data-col="ask">{format_number(round(ask, places, "half_up"), locale, places)}</td>                        <td.ui-fxrates-spread data-col="spread">{format_number(round((ask - bid) / pip, 1, "half_up"), locale, 1)}</td>                        {any_open && <td.ui-fxrates-chg data-col="pips">                            {pips == null ? <span.ui-fxrates-none>โ€”</span> : [                                <span.ui-sr>{side == "ui-fxrates-up" ? "up " : (side == "ui-fxrates-down" ? "down " : "unchanged ")}</span>,                                fx_rate_table_signed(pips, 1, locale)]}                        </td>}                        {any_open && <td data-col="pct">                            {pct == null ? <span.ui-fxrates-none>โ€”</span>                                : <span.ui-fxrates-pill>{fx_rate_table_signed(pct, 2, locale)}%</span>}                        </td>}                    </tr>                }}            </tbody>        </table>    </div>}
Thread
78Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.