FxPositions
Open currency positions: pair, long or short, size, entry and current rate, profit or loss in pips and money, coloured, with the total.
Demo
| Pair | Side | Size | Entry | Current | Pips | P/L in USD |
|---|---|---|---|---|---|---|
| EUR/USD | Long | 100,000EUR | 1.08105 | 1.08421 | profit +31.6 | profit +$316.00 |
| GBP/USD | Short | 50,000GBP | 1.27310 | 1.27532 | loss -22.2 | loss −$111.00 |
| USD/JPY | Short | 200,000USD | 157.020 | 156.251 | profit +76.9 | profit +$984.31 |
| EUR/GBP | Long | 75,000EUR | 0.85240 | 0.85026 | loss -21.4 | loss −$204.69 |
| AUD/NZD | Long | 30,000AUD | 1.09820 | 1.10010 | profit +19.0 | — |
| Total1 without a rate for USD left out | profit +$984.62 | |||||
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_positions.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/fx_positions.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/fx_positions.qwe
view FxPositionsDemo() = <FxPositions id="fx-positions-demo" account="USD" rates={{ USD: 1, EUR: 0.9224m, GBP: 0.7841m, JPY: 156.24m }} positions={[ { id: "T-20418", pair: "EUR/USD", side: "buy", size: 100000, entry: 1.08105m, current: 1.08421m, href: "#T-20418" }, { id: "T-20422", pair: "GBP/USD", side: "sell", size: 50000, entry: 1.27310m, current: 1.27532m }, { id: "T-20431", pair: "USD/JPY", side: "sell", size: 200000, entry: 157.020m, current: 156.251m }, { id: "T-20437", pair: "EUR/GBP", side: "buy", size: 75000, entry: 0.85240m, current: 0.85026m }, { id: "T-20440", pair: "AUD/NZD", side: "buy", size: 30000, entry: 1.09820m, current: 1.10010m }, ]}/>Source
ui/fx_positions.qwe
// FxPositions: open currency positions, a row each: the pair, long or short, the// size, the entry and the current rate, and the profit or loss so far in pips and// in the account's currency, green or red, with the total underneath.//// <FxPositions account="USD" rates={{ USD: 1, GBP: 0.7841m, JPY: 156.24m }} positions={[// { pair: "EUR/USD", side: "buy", size: 100000, entry: 1.08105m, current: 1.08421m },// { pair: "USD/JPY", side: "sell", size: 200000, entry: 157.020m, current: 156.251m }]}/>//// positions: a list of { pair, side, size, entry, current, id, href }: pair as "EUR/USD",// side "buy" (long the base) or "sell" (short), size in units of the base, entry the// rate it was opened at and current the rate it would close at now (decimals best;// numbers are read as decimals). id: names the row (the pair by default); href: a link// on the pair (to close or edit it). account: the currency profit and loss is shown in// ("USD"). A pair's profit is in its quote currency; it is turned into the account's// by the pair's own rate when the account is the base or the quote, else by rates: a// map of code to the units of it one `account` buys ({ GBP: 0.7841m }), and shown as// "—" when it is not there (and left out of the total). locale: how numbers are// written ("en-US"). label: the caption ("Open positions"). id: the element's id.//// fx_positions_pnl(position, account, rates) gives { pips, quote, money } for one// position (money null when it cannot be turned into the account's currency).import ui/theme.qwe use ui_clslet fx_positions_css = css`.ui-fxpos { min-width: 0; overflow-x: auto; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); font: 13px/1.35 var(--ui-font); color: var(--ui-fg); }.ui-fxpos-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }.ui-fxpos-table caption { padding: 12px 14px; text-align: left; font-size: 14px; font-weight: 650; }.ui-fxpos-table caption small { margin-left: 8px; color: var(--ui-muted); font-size: 12px; font-weight: 400; }.ui-fxpos-table th, .ui-fxpos-table td { padding: 9px 14px; text-align: right; white-space: nowrap; }.ui-fxpos-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-fxpos-table th:first-child, .ui-fxpos-table td:first-child { text-align: left; }.ui-fxpos-table tbody tr:not(:last-child) > * { border-bottom: 1px solid color-mix(in srgb, var(--ui-border) 60%, transparent); }.ui-fxpos-table tbody tr:hover { background: var(--ui-subtle); }.ui-fxpos-table tbody th { font-weight: 650; }.ui-fxpos-table tbody th a { color: inherit; text-decoration: none; }.ui-fxpos-table tbody th a:hover { color: var(--ui-accent); text-decoration: underline; }.ui-fxpos-table tbody th a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 3px; }.ui-fxpos-side { display: inline-block; min-width: 40px; padding: 1px 7px; border-radius: 4px; font-size: 11px; font-weight: 650; letter-spacing: .03em; text-align: center; text-transform: uppercase; }.ui-fxpos-buy { color: var(--ui-success); background: var(--ui-success-soft); }.ui-fxpos-sell { color: var(--ui-danger); background: var(--ui-danger-soft); }.ui-fxpos-size small { margin-left: 4px; color: var(--ui-muted); font-size: 11px; }.ui-fxpos-gain { --k: var(--ui-success); } .ui-fxpos-loss { --k: var(--ui-danger); } .ui-fxpos-even { --k: var(--ui-muted); }.ui-fxpos-pl { color: var(--k); font-weight: 650; }.ui-fxpos-none { color: var(--ui-muted); }.ui-fxpos-table tfoot th, .ui-fxpos-table tfoot td { border-top: 2px solid var(--ui-border); font-size: 14px; font-weight: 650; }.ui-fxpos-table tfoot small { display: block; color: var(--ui-muted); font-size: 11px; font-weight: 400; }`// A pip's size for "EUR/USD": 0.01 when the quote is priced in the hundreds, else 0.0001.fn fx_positions_pip(pair: str) -> dec { let parts = split(upper(pair), "/") contains(["JPY", "HUF", "KRW", "IDR", "VND", "CLP", "ISK", "THB", "INR", "PHP", "RUB"], len(parts) > 1 ? parts[1] : "") ? 0.01m : 0.0001m}// One position's result: { pips, quote, money }: pips signed (to one decimal), quote the// profit in the pair's quote currency, money in the account's (to its minor units, or// null when no rate turns it into it).fn fx_positions_pnl(p, account: str, rates: map) { let parts = split(upper(p.pair), "/") let base = parts[0] let quote = len(parts) > 1 ? parts[1] : parts[0] let sign = lower(p.side) == "sell" ? -1 : 1 let move = (dec(p.current) - dec(p.entry)) * sign let in_quote = move * dec(p.size) let r = rates[quote] let raw = quote == account ? in_quote : (base == account ? in_quote / dec(p.current) : (r != null && dec(r) > 0 ? in_quote / dec(r) : null)) let m = raw != null ? money.of(raw, account, "half_up") : null { pips: round(move / fx_positions_pip(p.pair), 1, "half_up"), quote: in_quote, money: m == null ? null : (type_of(m) == "error" ? round(raw, 2, "half_up") : m.amount), }}// An amount of the account's currency, signed: "+$316.00", "−$111.00".fn fx_positions_signed(d: dec, code: str, locale: str) -> str { let m = money.of(abs(d), code, "half_up") let text = type_of(m) == "error" ? `${format_number(abs(d), locale, 2)} ${code}` : money.format(m, locale) `${d > 0 ? "+" : (d < 0 ? "−" : "")}${text}`}view FxPositions(positions: list = [], account: str = "USD", rates: map = {}, locale: str = "en-US", label: str = "Open positions", id: str? = null, class: str? = null) { style global fx_positions_css let code = upper(account) let rows = positions | map((p) => { let result = fx_positions_pnl(p, code, rates) { ...p, result } }) let known = rows | filter((r) => r.result.money != null) let total = sum(known | map((r) => r.result.money)) let mood = (d) => d == null ? "ui-fxpos-even" : (d > 0 ? "ui-fxpos-gain" : (d < 0 ? "ui-fxpos-loss" : "ui-fxpos-even")) let word = (d) => d == null ? "" : (d > 0 ? "profit " : (d < 0 ? "loss " : "")) let places = (pair: str) => fx_positions_pip(pair) == 0.01m ? 3 : 5 <div class={ui_cls(["ui-fxpos", class])} id={id}> <table.ui-fxpos-table> <caption>{label}<small>{len(rows)} open, in {code}</small></caption> <thead><tr> <th scope="col">Pair</th><th scope="col">Side</th><th scope="col">Size</th><th scope="col">Entry</th> <th scope="col">Current</th><th scope="col">Pips</th><th scope="col">P/L <span.ui-sr>in {code}</span></th> </tr></thead> <tbody> {for r in rows { let key = get(r, "id") ?? r.pair let sell = lower(r.side) == "sell" <tr key={key} data-id={key} class={mood(r.result.money ?? r.result.pips)}> <th scope="row">{get(r, "href") != null ? <a href={r.href}>{r.pair}</a> : r.pair}</th> <td><span class={sell ? "ui-fxpos-side ui-fxpos-sell" : "ui-fxpos-side ui-fxpos-buy"}>{sell ? "Short" : "Long"}</span></td> <td.ui-fxpos-size>{format_number(dec(r.size), locale, 0)}<small>{split(upper(r.pair), "/")[0]}</small></td> <td>{format_number(round(dec(r.entry), places(r.pair), "half_up"), locale, places(r.pair))}</td> <td>{format_number(round(dec(r.current), places(r.pair), "half_up"), locale, places(r.pair))}</td> <td.ui-fxpos-pl data-col="pips"><span.ui-sr>{word(r.result.pips)}</span>{r.result.pips > 0 ? "+" : ""}{format_number(r.result.pips, locale, 1)}</td> <td.ui-fxpos-pl data-col="pl">{r.result.money != null ? [<span.ui-sr>{word(r.result.money)}</span>, fx_positions_signed(r.result.money, code, locale)] : <span.ui-fxpos-none title={`No rate for ${code}`}>—</span>}</td> </tr> }} </tbody> {len(rows) > 0 && <tfoot><tr class={mood(total)}> <th scope="row" colspan="6">Total {len(known) < len(rows) && <small>{len(rows) - len(known)} without a rate for {code} left out</small>} </th> <td.ui-fxpos-pl data-col="total"><span.ui-sr>{word(total)}</span>{fx_positions_signed(total, code, locale)}</td> </tr></tfoot>} </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 downloadsFxQuoteTicketAn order ticket for a currency deal: buy or sell, the amount, the quoted rate counting down to its expiry, the other currency's amount and Confirm.8 views · 6 downloadsFxRateTableA board of exchange rates: each pair with its flags, bid, ask, the spread in pips and the day's change from the open, coloured.7 views · 8 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 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 downloadsCurrencyConverterAn amount and two currencies with a swap button, the rate both ways and the converted amount, crossed through the base from the rates given.8 views · 11 downloads
Thread
Thread 0 posts
On FxPositions: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.