mirror of
https://gitea.gf4.pw/gf4/haveno-markets.git
synced 2024-11-23 10:37:37 -07:00
implement /markets
This commit is contained in:
parent
f0e8c417eb
commit
a01060ba62
12
package.json
12
package.json
@ -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": {
|
||||||
|
@ -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>
|
@ -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>
|
||||||
|
6
src/routes/markets/+page.server.js
Normal file
6
src/routes/markets/+page.server.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { trades } from "$lib/server/context";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
|
export function load() {
|
||||||
|
return { trades: get(trades) };
|
||||||
|
}
|
@ -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>
|
@ -1 +0,0 @@
|
|||||||
Under construction
|
|
Loading…
Reference in New Issue
Block a user