implement /markets

This commit is contained in:
wireless_purple 2024-09-13 20:55:38 +00:00
parent f0e8c417eb
commit a01060ba62
7 changed files with 248 additions and 95 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -10,14 +10,14 @@
"lint": "biome check *" "lint": "biome check *"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.8.3", "@biomejs/biome": "^1.9.0",
"@sveltejs/kit": "^2.5.20", "@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^3.1.1", "@sveltejs/vite-plugin-svelte": "^4.0.0-next.7",
"sass": "^1.77.8", "sass": "^1.78.0",
"svelte": "^5.0.0-next.210", "svelte": "^5.0.0-next.246",
"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.4.5"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {

View File

@ -15,76 +15,82 @@ import {
TimeScale, TimeScale,
} from "svelte-lightweight-charts"; } from "svelte-lightweight-charts";
let {data} = $props(); let { data } = $props();
const grouped = Object.groupBy(data.trades, ({ currency }) => currency); const grouped = Object.groupBy(data.trades, ({ currency }) => currency);
let interval = $state("86400000"); let interval = $state("86400000");
let key = $state("USD"); let key = $state("USD");
let trades = $derived((() => { let trades = $derived(
let trades = grouped[key] (() => {
.map((e) => { let trades = grouped[key]
return {
time: new Date(e.date),
value: getPrice(e.price, e.currency),
};
})
.toSorted((a, b) => (a.time - b.time));
trades = Object.groupBy(
trades,
({ time }) => new Date(time - (time % interval)) / 1000,
);
for (const intervalDate in trades) {
trades[intervalDate] = trades[intervalDate].reduce((a, c) => {
return {
open: a.open ?? c.value,
close: c.value,
high: (c.value > a.high ? c.value : a.high) ?? c.value,
low: (c.value < a.low ? c.value : a.low) ?? c.value,
};
}, {});
trades[intervalDate].time = Number.parseInt(intervalDate, 10);
}
return Object.values(trades);
})());
let [volume, swaps] = $derived((() => {
let volume = Object.groupBy(
data.trades
.map((e) => { .map((e) => {
return { return {
volume: e.xmrAmount, time: new Date(e.date),
time: e.date, value: getPrice(e.price, e.currency),
}; };
}) })
.toSorted((a, b) => (a.time - b.time)), .toSorted((a, b) => a.time - b.time);
({ time }) => new Date(time - (time % interval)) / 1000,
); trades = Object.groupBy(
let swaps = {}; trades,
for (const intervalDate in volume) { ({ time }) => new Date(time - (time % interval)) / 1000,
swaps[intervalDate] = volume[intervalDate].reduce(
(a) => {
return {
value: a.value + 1,
};
},
{ value: 0 },
); );
volume[intervalDate] = volume[intervalDate].reduce( for (const intervalDate in trades) {
(a, c) => { trades[intervalDate] = trades[intervalDate].reduce((a, c) => {
return { return {
value: a.value + c.volume / 10 ** 12, open: a.open ?? c.value,
close: c.value,
high: (c.value > a.high ? c.value : a.high) ?? c.value,
low: (c.value < a.low ? c.value : a.low) ?? c.value,
}; };
}, }, {});
{ value: 0 }, trades[intervalDate].time = Number.parseInt(intervalDate, 10);
}
return Object.values(trades);
})(),
);
let [volume, swaps] = $derived(
(() => {
let volume = Object.groupBy(
data.trades
.map((e) => {
return {
volume: e.xmrAmount,
time: e.date,
};
})
.toSorted((a, b) => a.time - b.time),
({ time }) => new Date(time - (time % interval)) / 1000,
); );
let swaps = {};
for (const intervalDate in volume) {
swaps[intervalDate] = volume[intervalDate].reduce(
(a) => {
return {
value: a.value + 1,
};
},
{ value: 0 },
);
volume[intervalDate].time = Number.parseInt(intervalDate, 10); volume[intervalDate] = volume[intervalDate].reduce(
swaps[intervalDate].time = Number.parseInt(intervalDate, 10); (a, c) => {
} return {
return [Object.values(volume), Object.values(swaps)]; value: a.value + c.volume / 10 ** 12,
})()); };
let precision = $derived(getSignificantDigits(trades.flatMap((e) => [e.open, e.close]))); },
{ value: 0 },
);
volume[intervalDate].time = Number.parseInt(intervalDate, 10);
swaps[intervalDate].time = Number.parseInt(intervalDate, 10);
}
return [Object.values(volume), Object.values(swaps)];
})(),
);
let precision = $derived(
getSignificantDigits(trades.flatMap((e) => [e.open, e.close])),
);
const chartLayout = { const chartLayout = {
background: { background: {
@ -188,6 +194,6 @@ let w = $state();
{/each} {/each}
</tbody> </tbody>
</table> </table>
<h4><a href="trades">View more »</a></h4> <h4><a href="markets">View more »</a></h4>
</div> </div>
</div> </div>

View File

@ -10,38 +10,42 @@ import {
import { CandlestickSeries, Chart, TimeScale } from "svelte-lightweight-charts"; import { CandlestickSeries, Chart, TimeScale } from "svelte-lightweight-charts";
const market = $page.params.market; const market = $page.params.market;
let {data} = $props(); let { data } = $props();
const interval = 86400000; const interval = 86400000;
let trades = $derived((() => { let trades = $derived(
let trades = data.trades (() => {
.map((e) => { let trades = data.trades
return { .map((e) => {
time: new Date(e.date), return {
value: getPrice(e.price, e.currency, false, false), time: new Date(e.date),
}; value: getPrice(e.price, e.currency, false, false),
}) };
.toSorted((a, b) => (a.time - b.time)); })
.toSorted((a, b) => a.time - b.time);
trades = Object.groupBy( trades = Object.groupBy(
trades, trades,
({ time }) => new Date(time - (time % interval)) / 1000, ({ time }) => new Date(time - (time % interval)) / 1000,
); );
for (const intervalDate in trades) { for (const intervalDate in trades) {
trades[intervalDate] = trades[intervalDate].reduce((a, c) => { trades[intervalDate] = trades[intervalDate].reduce((a, c) => {
return { return {
open: a.open ?? c.value, open: a.open ?? c.value,
close: c.value, close: c.value,
high: (c.value > a.high ? c.value : a.high) ?? c.value, high: (c.value > a.high ? c.value : a.high) ?? c.value,
low: (c.value < a.low ? c.value : a.low) ?? c.value, low: (c.value < a.low ? c.value : a.low) ?? c.value,
}; };
}, {}); }, {});
trades[intervalDate].time = Number.parseInt(intervalDate, 10); trades[intervalDate].time = Number.parseInt(intervalDate, 10);
} }
return Object.values(trades); return Object.values(trades);
})()); })(),
);
let precision = $derived(getSignificantDigits(trades.flatMap((e) => [e.open, e.close]))); let precision = $derived(
getSignificantDigits(trades.flatMap((e) => [e.open, e.close])),
);
let w = $state(); let w = $state();
const chartLayout = { const chartLayout = {
@ -117,7 +121,7 @@ const BUY_SELL = isMoneroQuote(market) ? ["SELL", "BUY"] : ["BUY", "SELL"];
</div> </div>
<div class="row"> <div class="row">
<div class="col card"> <div class="col card">
<h4>Last Trades</h4> <h4>Latest Trades</h4>
<table> <table>
<tbody> <tbody>
<tr> <tr>

View File

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

View File

@ -1 +1,139 @@
Under construction <svelte:options runes={true} />
<script>
import { formatPrice, getAsset } from "$lib/formatPrice";
import {
Chart,
HistogramSeries,
LineSeries,
PriceScale,
TimeScale,
} from "svelte-lightweight-charts";
let { data } = $props();
let interval = $state("86400000");
let [volume, swaps] = $derived(
(() => {
let volume = Object.groupBy(
data.trades
.map((e) => {
return {
volume: e.xmrAmount,
time: e.date,
};
})
.toSorted((a, b) => a.time - b.time),
({ time }) => new Date(time - (time % interval)) / 1000,
);
let swaps = {};
for (const intervalDate in volume) {
swaps[intervalDate] = volume[intervalDate].reduce(
(a) => {
return {
value: a.value + 1,
};
},
{ value: 0 },
);
volume[intervalDate] = volume[intervalDate].reduce(
(a, c) => {
return {
value: a.value + c.volume / 10 ** 12,
};
},
{ value: 0 },
);
volume[intervalDate].time = Number.parseInt(intervalDate, 10);
swaps[intervalDate].time = Number.parseInt(intervalDate, 10);
}
return [Object.values(volume), Object.values(swaps)];
})(),
);
const chartLayout = {
background: {
color: "#090020",
},
textColor: "#f6efff",
};
const gridLayout = {
vertLines: {
visible: false,
},
horzLines: {
color: "#FFF5",
},
};
let w = $state();
</script>
<svelte:head>
<title>Markets - Haveno Markets</title>
</svelte:head>
<div class="row">
<div class="col card" style="flex:1;" bind:clientWidth={w}>
<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={500} 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 class="row">
<div class="card col">
<h4>Markets</h4>
<table>
<tbody>
<tr>
<th>Currency</th>
<th>Price</th>
<th>Volume (XMR)</th>
<th>Trades</th>
</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}
<tr>
<td><a href="market/{market[0].currency}">{getAsset(market[0].currency).name} ({market[0].currency})</a></td>
<td>{formatPrice(market[0].price, market[0].currency, true, false)}</td>
<td>{formatPrice(market.reduce((a,b) => a + b.xmrAmount, 0), "XMR", false, false)}</td>
<td>{market.length}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="card col">
<h4>Latest Trades</h4>
<table>
<tbody>
<tr>
<th>Date</th>
<th>Price</th>
<th>Amount (XMR)</th>
<th>Amount</th>
</tr>
{#each data.trades.slice(0, 64) as trade}
<tr>
<td>{new Date(trade.date).toISOString().replace("T", " ").replace(/\.\d*Z/, "")}</td>
<td>{formatPrice(trade.price, trade.currency, true, false)}</td>
<td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td>
<td>{formatPrice(trade.amount, trade.currency, false, false)} <span class="trade-currency">{trade.currency}</span></td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>

View File

@ -1 +0,0 @@
Under construction