feat: price chart
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { bech32ToHex } from "@snort/shared";
|
import { bech32ToHex, removeUndefined, unixNow } from "@snort/shared";
|
||||||
import { EventKind, RequestBuilder } from "@snort/system";
|
import { EventKind, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
@ -33,3 +33,30 @@ export function useRates(symbol: string, leaveOpen = true) {
|
|||||||
hight: Number(tag[5]),
|
hight: Number(tag[5]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useRateHistory(symbol: string, size: number, leaveOpen = false) {
|
||||||
|
const sub = useMemo(() => {
|
||||||
|
const rb = new RequestBuilder(`rates:${symbol}:history:${size}`);
|
||||||
|
rb.withOptions({ leaveOpen });
|
||||||
|
rb.withFilter()
|
||||||
|
.kinds([1009 as EventKind])
|
||||||
|
.authors([bech32ToHex(SnortPubkey)])
|
||||||
|
.tag("d", [symbol])
|
||||||
|
.since(unixNow() - size);
|
||||||
|
return rb;
|
||||||
|
}, [symbol, size]);
|
||||||
|
|
||||||
|
const feed = useRequestBuilder(sub);
|
||||||
|
|
||||||
|
return removeUndefined(feed.map(a => {
|
||||||
|
const tag = a.tags.find(a => a[0] === "d" && a[1] === symbol);
|
||||||
|
if (!tag) return undefined;
|
||||||
|
return {
|
||||||
|
time: a?.created_at,
|
||||||
|
ask: Number(tag[2]),
|
||||||
|
bid: Number(tag[3]),
|
||||||
|
low: Number(tag[4]),
|
||||||
|
hight: Number(tag[5]),
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable max-lines */
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||||
|
74
packages/app/src/Pages/wallet/price-chart.tsx
Normal file
74
packages/app/src/Pages/wallet/price-chart.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { FormattedNumber } from "react-intl";
|
||||||
|
import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
||||||
|
|
||||||
|
import { useRateHistory } from "@/Hooks/useRates";
|
||||||
|
|
||||||
|
export default function PriceChart({ interval, range }: { interval: number, range: number }) {
|
||||||
|
const history = useRateHistory("BTCUSD", range, true);
|
||||||
|
|
||||||
|
const reduced = useMemo(() => {
|
||||||
|
let min = Number.MAX_SAFE_INTEGER, max = 0;
|
||||||
|
let minAsk = Number.MAX_SAFE_INTEGER, maxAsk = 0;
|
||||||
|
const ret = history.reduce((acc, v) => {
|
||||||
|
const key = v.time - (v.time % interval);
|
||||||
|
acc[key] ??= { time: key, ask: 0, bid: 0 };
|
||||||
|
acc[key].ask = v.ask;
|
||||||
|
acc[key].bid = v.bid;
|
||||||
|
if (key < min) {
|
||||||
|
min = key
|
||||||
|
}
|
||||||
|
if (key > max) {
|
||||||
|
max = key;
|
||||||
|
}
|
||||||
|
if (v.ask > maxAsk) {
|
||||||
|
maxAsk = v.ask;
|
||||||
|
}
|
||||||
|
if (v.ask < minAsk) {
|
||||||
|
minAsk = v.ask;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, { time: number, ask: number | null, bid: number | null }>);
|
||||||
|
|
||||||
|
for (let x = min; x < max; x += interval) {
|
||||||
|
ret[x] ??= { time: x, ask: null, bid: null };
|
||||||
|
}
|
||||||
|
return { data: ret, min, max, minAsk, maxAsk };
|
||||||
|
}, [history, interval]);
|
||||||
|
|
||||||
|
const lastRate = useMemo(() => {
|
||||||
|
return history.reduce((acc, v) => {
|
||||||
|
if (acc.time < v.time) {
|
||||||
|
acc = v;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, { time: 0, ask: 0, bid: 0 } as { time: number, ask: number, bid: number })
|
||||||
|
}, [history]);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<h3 className="text-right">
|
||||||
|
<FormattedNumber value={lastRate.ask} style="currency" currency="USD" />
|
||||||
|
</h3>
|
||||||
|
<ResponsiveContainer height={250}>
|
||||||
|
<LineChart data={Object.values(reduced.data)}>
|
||||||
|
<XAxis
|
||||||
|
dataKey="time"
|
||||||
|
type="number"
|
||||||
|
scale="time"
|
||||||
|
domain={["dataMin", "dataMax"]}
|
||||||
|
hide={true}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
dataKey="ask"
|
||||||
|
type="number"
|
||||||
|
scale="auto"
|
||||||
|
domain={["dataMin - 100", "dataMax + 100"]}
|
||||||
|
tickFormatter={v => Number(v).toLocaleString()}
|
||||||
|
hide={true}
|
||||||
|
/>
|
||||||
|
<Tooltip content={() => ""} />
|
||||||
|
<Line dataKey="ask" type="monotone" dot={false} connectNulls={false} stroke="var(--primary-bg)" />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</>;
|
||||||
|
}
|
@ -380,12 +380,16 @@ export class Connection extends EventEmitter<ConnectionEvents> {
|
|||||||
} else if (cmd[0] === "SYNC") {
|
} else if (cmd[0] === "SYNC") {
|
||||||
const [_, id, eventSet, ...filters] = cmd;
|
const [_, id, eventSet, ...filters] = cmd;
|
||||||
const lastResortSync = () => {
|
const lastResortSync = () => {
|
||||||
const latest = eventSet.reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
|
if (filters.some(a => a.since || a.until)) {
|
||||||
const newFilters = filters.map(a => ({
|
this.queueReq(["REQ", id, ...filters], item.cb);
|
||||||
...a,
|
} else {
|
||||||
since: latest,
|
const latest = eventSet.reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
|
||||||
}));
|
const newFilters = filters.map(a => ({
|
||||||
this.queueReq(["REQ", id, ...newFilters], item.cb);
|
...a,
|
||||||
|
since: latest,
|
||||||
|
}));
|
||||||
|
this.queueReq(["REQ", id, ...newFilters], item.cb);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (this.Info?.software?.includes("strfry")) {
|
if (this.Info?.software?.includes("strfry")) {
|
||||||
const neg = new NegentropyFlow(id, this, eventSet, filters);
|
const neg = new NegentropyFlow(id, this, eventSet, filters);
|
||||||
|
Reference in New Issue
Block a user