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 *"
},
"devDependencies": {
"@biomejs/biome": "^1.8.3",
"@sveltejs/kit": "^2.5.20",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"sass": "^1.77.8",
"svelte": "^5.0.0-next.210",
"@biomejs/biome": "^1.9.0",
"@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.7",
"sass": "^1.78.0",
"svelte": "^5.0.0-next.246",
"svelte-adapter-bun": "^0.5.2",
"svelte-preprocess": "^6.0.2",
"vite": "^5.3.5"
"vite": "^5.4.5"
},
"type": "module",
"dependencies": {

View file

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

View file

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