optimizations

This commit is contained in:
wireless_purple 2024-08-06 00:15:47 +00:00
parent cdfbf26c98
commit bb10145f56
8 changed files with 304 additions and 277 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -14,7 +14,7 @@
"@sveltejs/kit": "^2.5.20", "@sveltejs/kit": "^2.5.20",
"@sveltejs/vite-plugin-svelte": "^3.1.1", "@sveltejs/vite-plugin-svelte": "^3.1.1",
"sass": "^1.77.8", "sass": "^1.77.8",
"svelte": "^4.2.18", "svelte": "^5.0.0-next.210",
"svelte-adapter-bun": "^0.5.2", "svelte-adapter-bun": "^0.5.2",
"svelte-preprocess": "^6.0.2", "svelte-preprocess": "^6.0.2",
"vite": "^5.3.5" "vite": "^5.3.5"

View file

@ -1,10 +1,15 @@
import { watch } from "fs"; import { watch } from "fs";
import { writable } from "svelte/store"; import { derived, writable } from "svelte/store";
const offers = writable([]); const offers = writable([]);
const trades = writable([]); const trades = writable([]);
const crypto = writable([]); const crypto = writable([]);
const fiat = writable([]); const fiat = writable([]);
const liquidity = derived(offers, ($offers) =>
Object.values($offers)
.flat()
.reduce((a, b) => a + Number.parseInt(b.amount), 0),
);
const formatTrades = (e) => { const formatTrades = (e) => {
return e.map((e) => { return e.map((e) => {
@ -20,11 +25,26 @@ const formatTrades = (e) => {
}); });
}; };
const formatOffers = (e) => { const formatOffers = (e) => {
return Object.groupBy(e, ({ currencyCode }) => currencyCode); return Object.groupBy(
e.map((e) => {
return {
direction: e.direction,
currencyCode: e.currencyCode,
amount: e.amount,
price: e.price,
paymentMethod: e.paymentMethod,
primaryMarketAmount: e.primaryMarketAmount,
};
}),
({ currencyCode }) => currencyCode,
);
}; };
const formatCrypto = (e) => { const formatCrypto = (e) => {
e[e.findIndex((e) => e.code === "XMR")].precision = 12; e.push({
e[e.findIndex((e) => e.code === "XMR")].sign = "ɱ"; precision: 12,
code: "XMR",
sign: "ɱ",
});
return e; return e;
}; };
const formatFiat = (e) => { const formatFiat = (e) => {
@ -47,16 +67,16 @@ Bun.file(`${import.meta.env.VITE_DB_PATH}trade_statistics.json`)
.then((j) => { .then((j) => {
trades.set(formatTrades(j)); trades.set(formatTrades(j));
}); });
Bun.file(`${import.meta.env.VITE_DB_PATH}crypto_currency_list.json`) Bun.file(`${import.meta.env.VITE_DB_PATH}active_traditional_currency_list.json`)
.json()
.then((j) => {
crypto.set(formatCrypto(j));
});
Bun.file(`${import.meta.env.VITE_DB_PATH}/traditional_currency_list.json`)
.json() .json()
.then((j) => { .then((j) => {
fiat.set(formatFiat(j)); fiat.set(formatFiat(j));
}); });
Bun.file(`${import.meta.env.VITE_DB_PATH}active_crypto_currency_list.json`)
.json()
.then((j) => {
crypto.set(formatCrypto(j));
});
const watcher = watch(import.meta.env.VITE_DB_PATH, async (_, filename) => { const watcher = watch(import.meta.env.VITE_DB_PATH, async (_, filename) => {
const file = Bun.file(import.meta.env.VITE_DB_PATH + filename); const file = Bun.file(import.meta.env.VITE_DB_PATH + filename);
@ -84,4 +104,4 @@ process.on("SIGINT", () => {
process.exit(0); process.exit(0);
}); });
export { offers, trades, crypto, fiat }; export { offers, trades, crypto, fiat, liquidity };

View file

@ -15,153 +15,153 @@ Object.groupBy ||= (values, keyFinder) => {
</script> </script>
<div class="col app"> <div class="col app">
<div class="row header" style="align-content:center;"> <div class="row header" style="align-content:center;">
<span> <span>
<img src="/haveno_logo.png" alt="" style="height:1em;"/> <img src="/haveno_logo.png" alt="" style="height:1em;"/>
<a href="https://haveno.exchange">haveno.exchange</a> <a href="https://haveno.exchange">haveno.exchange</a>
</span> </span>
<a href="/">haveno.markets</a> <a href="/">haveno.markets</a>
<span> <span>
<img src="/monero_logo.png" alt="" style="height:1em;"/> <img src="/monero_logo.png" alt="" style="height:1em;"/>
<a href="https://xmrchain.net">xmrchain.net</a> <a href="https://xmrchain.net">xmrchain.net</a>
</span> </span>
</div> </div>
<div class="col container"> <div class="col container">
<slot></slot> <slot></slot>
</div> </div>
<div class="col footer"> <div class="col footer">
<span> <span>
Links: Links:
<a href="https://haveno.markets">Clearnet</a> | <a href="https://haveno.markets">Clearnet</a> |
<a href="http://lsj5o4i7iiogz6q4rstiv75nk7kots2f2nhuga75y7s23leskz2uh6id.onion">Tor</a> | <a href="http://lsj5o4i7iiogz6q4rstiv75nk7kots2f2nhuga75y7s23leskz2uh6id.onion">Tor</a> |
<a href="http://haveno-markets.i2p">I2P</a> <a href="http://haveno-markets.i2p">I2P</a>
<a href="http://okoeicsihmjkqcqaiqow3arcrzm5ascwhpxq34incxg6a5z4tjza.b32.i2p">(b32)</a> <a href="http://okoeicsihmjkqcqaiqow3arcrzm5ascwhpxq34incxg6a5z4tjza.b32.i2p">(b32)</a>
</span> </span>
<span> <span style="display:flex;gap:.2em;">
Data from: Data from:
<a href="https://haveno-reto.com" style="display:inline-flex;gap:.2em;align-items:center;"> <a href="https://haveno-reto.com" style="display:inline-flex;gap:.2em;align-items:center;">
<img src="/haveno-reto_logo.svg" alt="" style="height:1em;width:1em;"/> <img src="/haveno-reto_logo.svg" alt="" style="height:1em;width:1em;"/>
haveno-reto haveno-reto
</a> </a>
</span> </span>
<span> <span>
Donations: Donations:
<a style="word-break:break-all;" href="monero:84LnuW3YpCQirNMN6y6Px1E3DfwnqwXVRARi9eHjzeSVFJqEQmJCxkP5WpkysbcktqUNhXxQLowhJGSknNjJWZNQ7FKp5bu"> <a style="word-break:break-all;" href="monero:84LnuW3YpCQirNMN6y6Px1E3DfwnqwXVRARi9eHjzeSVFJqEQmJCxkP5WpkysbcktqUNhXxQLowhJGSknNjJWZNQ7FKp5bu">
84LnuW3YpCQirNMN6y6Px1E3DfwnqwXVRARi9eHjzeSVFJqEQmJCxkP5WpkysbcktqUNhXxQLowhJGSknNjJWZNQ7FKp5bu 84LnuW3YpCQirNMN6y6Px1E3DfwnqwXVRARi9eHjzeSVFJqEQmJCxkP5WpkysbcktqUNhXxQLowhJGSknNjJWZNQ7FKp5bu
</a> </a>
</span> </span>
</div> </div>
</div> </div>
<style lang="scss" global> <style lang="scss" global>
.app { .app {
display:flex; display:flex;
width:100%; width:100%;
justify-content:center; justify-content:center;
min-height: 100dvh; min-height: 100dvh;
} }
.container { .container {
width:80%!important; width:80%!important;
} }
.header { .header {
background-color:#5555; background-color:#5555;
padding:1em 0; padding:1em 0;
} }
.footer { .footer {
background-color:#4444; background-color:#4444;
margin-top:auto; margin-top:auto;
text-align:center; text-align:center;
} }
.col{ .col{
display:flex; display:flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width:100%; width:100%;
height:100%; height:100%;
} }
.row { .row {
display:flex; display:flex;
flex-direction: row; flex-direction: row;
width:100%; width:100%;
height:100%; height:100%;
justify-content: space-around; justify-content: space-around;
} }
body { body {
background-color:#290040; background-color:#290040;
color:white; color:white;
margin:0; margin:0;
padding:0; padding:0;
} }
.card { .card {
margin:1em; margin:1em;
padding:.5em; padding:.5em;
background-color:#552A64; background-color:#552A64;
border-radius: 5px; border-radius: 5px;
:global(h4) { :global(h4) {
text-align: center; text-align: center;
color:#F1482D; color:#F1482D;
} }
table { table {
width: 100%; width: 100%;
border-collapse:collapse; border-collapse:collapse;
th, td { th, td {
text-align:right; text-align:right;
padding:.3em; padding:.3em;
} }
th:first-child, td:first-child{ th:first-child, td:first-child{
text-align:left; text-align:left;
} }
tr:nth-child(2n){ tr:nth-child(2n){
background-color: #0002; background-color: #0002;
} }
} }
} }
a, .price { a, .price {
text-decoration: none; text-decoration: none;
color:#f60; color:#f60;
} }
a:hover{ a:hover{
text-decoration: underline; text-decoration: underline;
} }
.price { .price {
font-size:2em; font-size:2em;
margin-bottom:.4em; margin-bottom:.4em;
} }
h4 { h4 {
margin:.4em; margin:.4em;
} }
.trade-currency { .trade-currency {
font-size:1em; font-size:1em;
color:#fff6; color:#fff6;
font-weight:bold; font-weight:bold;
font-family: monospace; font-family: monospace;
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
.row { .row {
flex-direction: column; flex-direction: column;
} }
.container { .container {
width: 97%!important; width: 97%!important;
} }
.card { .card {
margin:.5em 0; margin:.5em 0;
padding:.5em 0; padding:.5em 0;
} }
.header { .header {
padding:.2em 0; padding:.2em 0;
} }
} }
.header > * { .header > * {
display:flex; display:flex;
width:33.3%; width:33.3%;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap:.2em; gap:.2em;
} }
@media only screen and (max-width: 600px){ @media only screen and (max-width: 600px){
.header > * { .header > * {
width:initial; width:initial;
} }
} }
</style> </style>

View file

@ -1,6 +1,6 @@
import { offers, trades } from "$lib/server/context"; import { liquidity, trades } from "$lib/server/context";
import { get } from "svelte/store"; import { get } from "svelte/store";
export function load() { export function load() {
return { trades: get(trades), offers: get(offers) }; return { trades: get(trades), liquidity: get(liquidity) };
} }

View file

@ -111,9 +111,6 @@ const gridLayout = {
}, },
}; };
let w; let w;
const liquidity = Object.values(data.offers)
.flat()
.reduce((a, b) => a + Number.parseInt(b.amount), 0);
</script> </script>
<svelte:head> <svelte:head>
@ -125,77 +122,81 @@ const liquidity = Object.values(data.offers)
<h4>XMR/USD</h4> <h4>XMR/USD</h4>
<span class="price">{formatPrice(grouped["USD"][0].price, "USD", true)}</span> <span class="price">{formatPrice(grouped["USD"][0].price, "USD", true)}</span>
</div> </div>
<div class="col card"> <div class="col card">
<h4>Liquidity</h4> <h4>Liquidity</h4>
<span class="price">{formatPrice(liquidity, "XMR", true, false)}</span> <span class="price">{formatPrice(data.liquidity, "XMR", true, false)}</span>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col card" style="flex:1;" bind:clientWidth={w}> <div class="col card" style="flex:1;" bind:clientWidth={w}>
<h4>Price XMR/<select bind:value={key}> <h4>Price XMR/<select bind:value={key}>
{#each Object.keys(grouped) as key} {#each Object.keys(grouped) as key}
<option>{key}</option> <option>{key}</option>
{/each} {/each}
</select></h4> </select></h4>
<Chart width={w-20} height={300} container={{class:"row"}} layout={chartLayout} grid={gridLayout}> <Chart width={w-20} height={300} container={{class:"row"}} layout={chartLayout} grid={gridLayout}>
<CandlestickSeries data={trades} reactive={true} priceFormat={{minMove:10**-precision, precision:precision}}></CandlestickSeries> <CandlestickSeries data={trades} reactive={true} priceFormat={{minMove:10**-precision, precision:precision}}></CandlestickSeries>
<TimeScale rightBarStaysOnScroll={true} rightOffset={0}/>
</Chart>
</div>
<div class="col card" style="flex:1">
<h4>
<select bind:value={interval}>
<option value="3600000">Hourly</option>
<option value="86400000">Daily</option>
<option value="604800000">Weekly</option>
</select> Volume</h4>
<Chart width={w-20} height={300} container={{class:"row"}} layout={chartLayout} grid={gridLayout}>
<LineSeries data={volume} reactive={true} priceFormat={{precision:2, minMove:.01}}>
<PriceScale scaleMargins={{bottom:.4, top:.1}}/>
</LineSeries>
<HistogramSeries data={swaps} reactive={true} priceScaleId="" priceFormat={{precision:0, minMove:1}}>
<PriceScale scaleMargins={{top:.7, bottom:0}}/>
</HistogramSeries>
<TimeScale rightBarStaysOnScroll={true} rightOffset={0}/> <TimeScale rightBarStaysOnScroll={true} rightOffset={0}/>
</Chart> </Chart>
</div> </div>
<div class="col card" style="flex:1">
<h4>
<select bind:value={interval}>
<option value="3600000">Hourly</option>
<option value="86400000">Daily</option>
<option value="604800000">Weekly</option>
</select> Volume</h4>
<Chart width={w-20} height={300} container={{class:"row"}} layout={chartLayout} grid={gridLayout}>
<LineSeries data={volume} reactive={true} priceFormat={{precision:2, minMove:.01}}>
<PriceScale scaleMargins={{bottom:.4, top:.1}}/>
</LineSeries>
<HistogramSeries data={swaps} reactive={true} priceScaleId="" priceFormat={{precision:0, minMove:1}}>
<PriceScale scaleMargins={{top:.7, bottom:0}}/>
</HistogramSeries>
<TimeScale rightBarStaysOnScroll={true} rightOffset={0}/>
</Chart>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="card col"> <div class="card col">
<h4>Markets</h4> <h4>Markets</h4>
<table> <table>
<tr> <tbody>
<th>Currency</th> <tr>
<th>Price</th> <th>Currency</th>
<th>Trades</th> <th>Price</th>
</tr> <th>Trades</th>
{#each Object.values(Object.groupBy(data.trades, ({currency}) => currency)).toSorted((a,b) => b.length - a.length || (b[0].currency < a[0].currency ? 1 : -1)).slice(0, 16) as market} </tr>
<tr> {#each Object.values(Object.groupBy(data.trades, ({currency}) => currency)).toSorted((a,b) => b.length - a.length || (b[0].currency < a[0].currency ? 1 : -1)).slice(0, 16) as market}
<td><a href="market/{market[0].currency}">{getAsset(market[0].currency).name} ({market[0].currency})</a></td> <tr>
<td>{formatPrice(market[0].price, market[0].currency, true, false)}</td> <td><a href="market/{market[0].currency}">{getAsset(market[0].currency).name} ({market[0].currency})</a></td>
<td>{market.length}</td> <td>{formatPrice(market[0].price, market[0].currency, true, false)}</td>
</tr> <td>{market.length}</td>
{/each} </tr>
</table> {/each}
<h4><a href="markets">View more »</a></h4> </tbody>
</table>
<h4><a href="markets">View more »</a></h4>
</div> </div>
<div class="card col"> <div class="card col">
<h4>Trades</h4> <h4>Trades</h4>
<table> <table>
<tr> <tbody>
<th>Date</th> <tr>
<th>Amount (XMR)</th> <th>Date</th>
<th>Amount</th> <th>Amount (XMR)</th>
</tr> <th>Amount</th>
{#each data.trades.slice(0, 16) as trade} </tr>
<tr> {#each data.trades.slice(0, 16) as trade}
<td>{new Date(trade.date).toISOString().replace("T", " ").replace(/\.\d*Z/, "")}</td> <tr>
<td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td> <td>{new Date(trade.date).toISOString().replace("T", " ").replace(/\.\d*Z/, "")}</td>
<td>{formatPrice(trade.amount, trade.currency, false, false)} <span class="trade-currency">{trade.currency}</span></td> <td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td>
</tr> <td>{formatPrice(trade.amount, trade.currency, false, false)} <span class="trade-currency">{trade.currency}</span></td>
{/each} </tr>
{/each}
</tbody>
</table> </table>
<h4><a href="trades">View more »</a></h4> <h4><a href="trades">View more »</a></h4>
</div> </div>

View file

@ -71,69 +71,75 @@ const BUY_SELL = isMoneroQuote(market) ? ["SELL", "BUY"] : ["BUY", "SELL"];
<title>{marketPair} - Haveno Markets</title> <title>{marketPair} - Haveno Markets</title>
</svelte:head> </svelte:head>
<div class="row"> <div class="row">
<div class="col card" bind:clientWidth={w}> <div class="col card" bind:clientWidth={w}>
<h4>{marketPair}</h4> <h4>{marketPair}</h4>
<span class="price">{formatPrice(data.trades?.[0]?.price, market, true, false)}</span> <span class="price">{formatPrice(data.trades?.[0]?.price, market, true, false)}</span>
<Chart width={w-20} height={500} container={{class:"row"}} layout={chartLayout} grid={gridLayout}> <Chart width={w-20} height={500} container={{class:"row"}} layout={chartLayout} grid={gridLayout}>
<CandlestickSeries data={trades} reactive={true} priceFormat={{minMove:10**-precision, precision:precision}}></CandlestickSeries> <CandlestickSeries data={trades} reactive={true} priceFormat={{minMove:10**-precision, precision:precision}}></CandlestickSeries>
<TimeScale rightBarStaysOnScroll={true} rightOffset={0}/> <TimeScale rightBarStaysOnScroll={true} rightOffset={0}/>
</Chart> </Chart>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col card"> <div class="col card">
<h4>Buy Offers</h4> <h4>Buy Offers</h4>
<table> <table>
<tr> <tbody>
<th>Price</th> <tr>
<th>Amount (XMR)</th> <th>Price</th>
<th>Amount ({market})</th> <th>Amount (XMR)</th>
</tr> <th>Amount ({market})</th>
{#each data.offers[BUY_SELL[0]]?.toSorted((a,b) => a.price < b.price ? 1 : -1)||[] as offer} </tr>
<tr title={offer.paymentMethod}> {#each data.offers[BUY_SELL[0]]?.toSorted((a,b) => a.price < b.price ? 1 : -1)||[] as offer}
<td>{formatPrice(offer.price, market, false, false)}</td> <tr title={offer.paymentMethod}>
<td>{formatPrice(offer.amount, "XMR", false, false)}</td> <td>{formatPrice(offer.price, market, false, false)}</td>
<td>{formatPrice(offer.primaryMarketAmount, market, false, false)}</td> <td>{formatPrice(offer.amount, "XMR", false, false)}</td>
</tr> <td>{formatPrice(offer.primaryMarketAmount, market, false, false)}</td>
{/each} </tr>
{/each}
</tbody>
</table> </table>
</div> </div>
<div class="col card"> <div class="col card">
<h4>Sell Offers</h4> <h4>Sell Offers</h4>
<table> <table>
<tr> <tbody>
<th>Price</th> <tr>
<th>Amount (XMR)</th> <th>Price</th>
<th>Amount ({market})</th> <th>Amount (XMR)</th>
</tr> <th>Amount ({market})</th>
{#each data.offers[BUY_SELL[1]]?.toSorted((a,b) => a.price > b.price ? 1 : -1)||[] as offer} </tr>
<tr title={offer.paymentMethod}> {#each data.offers[BUY_SELL[1]]?.toSorted((a,b) => a.price > b.price ? 1 : -1)||[] as offer}
<td>{formatPrice(offer.price, market, false, false)}</td> <tr title={offer.paymentMethod}>
<td>{formatPrice(offer.amount, "XMR", false, false)}</td> <td>{formatPrice(offer.price, market, false, false)}</td>
<td>{formatPrice(offer.primaryMarketAmount, market, false, false)}</td> <td>{formatPrice(offer.amount, "XMR", false, false)}</td>
</tr> <td>{formatPrice(offer.primaryMarketAmount, market, false, false)}</td>
{/each} </tr>
{/each}
</tbody>
</table> </table>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col card"> <div class="col card">
<h4>Last Trades</h4> <h4>Last Trades</h4>
<table> <table>
<tr> <tbody>
<th>Date</th> <tr>
<th>Price</th> <th>Date</th>
<th>Amount (XMR)</th> <th>Price</th>
<th>Amount ({market})</th> <th>Amount (XMR)</th>
</tr> <th>Amount ({market})</th>
{#each data.trades as trade} </tr>
<tr> {#each data.trades as trade}
<td>{new Date(trade.date).toISOString().replace("T", " ").replace(/\.\d*Z/, "")}</td> <tr>
<td>{formatPrice(trade.price, trade.currency, false, false)}</td> <td>{new Date(trade.date).toISOString().replace("T", " ").replace(/\.\d*Z/, "")}</td>
<td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td> <td>{formatPrice(trade.price, trade.currency, false, false)}</td>
<td>{formatPrice(trade.amount, trade.currency, false, false)}</td> <td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td>
</tr> <td>{formatPrice(trade.amount, trade.currency, false, false)}</td>
{/each} </tr>
</table> {/each}
</div> </tbody>
</table>
</div>
</div> </div>

View file

@ -1,12 +1,12 @@
import adapter from "svelte-adapter-bun"; import adapter from "svelte-adapter-bun";
import preprocess from "svelte-preprocess"; import { sveltePreprocess } from "svelte-preprocess";
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
kit: { kit: {
adapter: adapter(), adapter: adapter(),
}, },
preprocess: [preprocess()], preprocess: sveltePreprocess(),
}; };
export default config; export default config;