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.
Demo
| Pair | Bid | Ask | Spread in pips | Change in pips | Change % |
|---|---|---|---|---|---|
| EUR/USDEuro / US dollar | 1.08421 | 1.08429 | 0.8 | up +23.5 | +0.22% |
| GBP/USDPound / US dollar | 1.27532 | 1.27546 | 1.4 | down -27.1 | -0.21% |
| USD/JPYUS dollar / Yen | 156.238 | 156.251 | 1.3 | down -65.8 | -0.42% |
| USD/CHFUS dollar / Swiss franc | 0.88114 | 0.88131 | 1.7 | unchanged 0.0 | 0.00% |
| USD/PHPUS dollar / Philippine peso | 58.105 | 58.140 | 3.5 | up +13.3 | +0.23% |
| AUD/USDAussie / US dollar | 0.66412 | 0.66420 | 0.8 | โ | โ |
| Pair | Bid | Ask | Spread in pips | Change in pips | Change % |
|---|---|---|---|---|---|
| EUR/GBP | 0.85011 | 0.85026 | 1.5 | up +5.9 | +0.07% |
| EUR/JPY | 169.402 | 169.431 | 2.9 | down -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.qwemain.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>}Related
CrossRatesA matrix of cross rates among a few currencies, each worked out from their rates against one base, the diagonal left blank.6 views ยท 6 downloadsAmortizationTableA loan's payment schedule worked out from its amount, rate and term: each month's principal, interest and balance, grouped by year with the year's totals.7 views ยท 7 downloadsFxPositionsOpen currency positions: pair, long or short, size, entry and current rate, profit or loss in pips and money, coloured, with the total.7 views ยท 6 downloadsShareCapitalLedgerA member's share capital contributions with a running balance, and the progress toward the share capital the co-op requires.6 views ยท 5 downloadsBankStatementA monthly statement that prints: the period, opening and closing balance, totals paid in and out, and each transaction with the balance after.7 views ยท 6 downloadsCollateralListWhat secures a loan: each item's valuation and documents, and the loan-to-value ratio as a meter against the lender's limit.7 views ยท 5 downloads
Thread
Thread 0 posts
On FxRateTable: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.