done for the day

This commit is contained in:
crowetic 2025-06-06 20:45:57 -07:00
parent 84ab5946e7
commit fd23b2e5a6
2 changed files with 96 additions and 87 deletions

View File

@ -4,7 +4,7 @@ import QortCandlestickChart, {
} from './components/QortCandlestickChart';
import {
aggregateCandles,
aggregateDailyCandles,
// aggregateDailyCandles,
Trade,
} from './utils/qortTrades';
import Button from '@mui/material/Button';
@ -66,57 +66,47 @@ const App: React.FC = () => {
const [isFiltering, setIsFiltering] = useState(false);
const [isUpdating, setIsUpdating] = useState<Record<string, boolean>>({});
// function filterOutliersPercentile(
// trades: Trade[],
// lower = 0.01,
// upper = 0.99
// ): Trade[] {
// if (trades.length < 10) return trades;
// // Compute prices
// const prices = trades
// .map((t) => parseFloat(t.foreignAmount) / parseFloat(t.qortAmount))
// .filter((x) => isFinite(x) && x > 0);
// // Sort prices
// const sorted = [...prices].sort((a, b) => a - b);
// const lowerIdx = Math.floor(sorted.length * lower);
// const upperIdx = Math.ceil(sorted.length * upper) - 1;
// const min = sorted[lowerIdx];
// const max = sorted[upperIdx];
// // Filter trades within percentile range
// return trades.filter((t) => {
// const price = parseFloat(t.foreignAmount) / parseFloat(t.qortAmount);
// return price >= min && price <= max;
// });
// }
function getLatestTradeTimestamp(trades: Trade[]): number {
if (!trades || !trades.length) return 0;
return Math.max(...trades.map((t) => t.tradeTimestamp));
}
function fastPercentileFilter(trades: Trade[], lower = 0.01, upper = 0.99) {
// 1. Extract price array (one pass)
const prices = [];
const validTrades = [];
for (const t of trades) {
const qort = parseFloat(t.qortAmount);
const price = parseFloat(t.foreignAmount) / qort;
if (isFinite(price) && price > 0) {
prices.push(price);
validTrades.push({ trade: t, price });
}
}
// 2. Get percentiles (sort once)
prices.sort((a, b) => a - b);
const min = prices[Math.floor(prices.length * lower)];
const max = prices[Math.ceil(prices.length * upper) - 1];
// 3. Filter in single pass
return validTrades
.filter(({ price }) => price >= min && price <= max)
.map(({ trade }) => trade);
// function fastPercentileFilter(trades: Trade[], lower = 0.01, upper = 0.99) {
// // 1. Extract price array (one pass)
// const prices = [];
// const validTrades = [];
// for (const t of trades) {
// const qort = parseFloat(t.qortAmount);
// const price = parseFloat(t.foreignAmount) / qort;
// if (isFinite(price) && price > 0) {
// prices.push(price);
// validTrades.push({ trade: t, price });
// }
// }
// // 2. Get percentiles (sort once)
// prices.sort((a, b) => a - b);
// const min = prices[Math.floor(prices.length * lower)];
// const max = prices[Math.ceil(prices.length * upper) - 1];
// // 3. Filter in single pass
// return validTrades
// .filter(({ price }) => price >= min && price <= max)
// .map(({ trade }) => trade);
// }
function fastPercentileFilter(trades: Trade[], lower = 0.002, upper = 0.998) {
if (trades.length < 200) return trades;
const prices = trades
.map((t) => parseFloat(t.foreignAmount) / parseFloat(t.qortAmount))
.filter((x) => isFinite(x) && x > 0);
const sorted = [...prices].sort((a, b) => a - b);
const lowerIdx = Math.floor(sorted.length * lower);
const upperIdx = Math.ceil(sorted.length * upper) - 1;
const min = sorted[lowerIdx];
const max = sorted[upperIdx];
return trades.filter((t) => {
const price = parseFloat(t.foreignAmount) / parseFloat(t.qortAmount);
return price >= min && price <= max;
});
}
// --- LocalStorage LOAD on mount ---
@ -146,13 +136,15 @@ const App: React.FC = () => {
console.log('Filter effect skipped - waiting for cacheLoaded');
return;
}
setIsFiltering(true);
setTimeout(() => {
// --- Determine minTimestamp ---
const now = new Date();
const periodObj = PERIODS.find((p) => p.label === period);
let minTimestamp = 0;
let useDaily = false;
// let useDaily = false;
if (periodObj) {
if ('days' in periodObj && periodObj.days !== undefined) {
now.setDate(now.getDate() - periodObj.days);
@ -164,12 +156,12 @@ const App: React.FC = () => {
) {
now.setMonth(now.getMonth() - periodObj.months);
minTimestamp = now.getTime();
// For 1M or more, use daily candles
if (periodObj.months >= 1) useDaily = true;
// For 1M or more, use daily candles----------------------------------------------DISABLED FORCING DAILY CANDLES, AND MODIFIED FILTERING.
// if (periodObj.months >= 6) useDaily = false;
} else if ('months' in periodObj && periodObj.months === null) {
// 'All'
minTimestamp = 0;
useDaily = true;
// useDaily = false;
}
}
// --- Filter trades ---
@ -177,14 +169,16 @@ const App: React.FC = () => {
let filtered = minTimestamp
? trades.filter((t) => t.tradeTimestamp >= minTimestamp)
: trades;
filtered = fastPercentileFilter(filtered, 0.01, 0.99);
filtered = fastPercentileFilter(filtered, 0.00005, 0.995);
// --- Aggregate ---
if (useDaily) {
setCandles(aggregateDailyCandles(filtered));
} else {
setCandles(aggregateCandles(filtered, interval));
}
// // --- Aggregate ---
// if (useDaily) {
// setCandles(aggregateDailyCandles(filtered));
// } else {
// setCandles(aggregateCandles(filtered, interval));
// }
setCandles(aggregateCandles(filtered, interval));
setIsFiltering(false);
}, 10);
}, [interval, period, selectedChain, allChainTrades, cacheLoaded]);
@ -300,10 +294,11 @@ const App: React.FC = () => {
total={null}
chain={selectedChain}
/>
<Container maxWidth="xl" disableGutters>
<Container maxWidth={false} disableGutters>
<Box
sx={{
minHeight: '100vh',
width: '100vw',
background: theme.palette.background.default,
color: theme.palette.text.primary,
p: { xs: 1, md: 3 },
@ -313,11 +308,11 @@ const App: React.FC = () => {
<Paper
elevation={5}
sx={{
p: { xs: 1, md: 4 },
maxWidth: 1800,
margin: '36px auto 0 auto',
width: '100%',
margin: '36px 0 0 0', // Remove 'auto' to allow full width
background: theme.palette.background.paper,
boxShadow: theme.shadows[6],
p: { xs: 1, md: 4 },
}}
>
{/* Action Button */}
@ -449,13 +444,13 @@ const App: React.FC = () => {
</Button>
))}
</Box>
{/* Chart */}
<Box
sx={{
width: '100%',
maxWidth: 1800,
height: { xs: 320, md: 520 },
height: { xs: 320, md: 520, lg: '60vh' }, // adapt for screen
mx: 'auto',
minHeight: 240,
position: 'relative',
}}
>
<QortCandlestickChart

View File

@ -1,6 +1,7 @@
import React from 'react';
import Chart from 'react-apexcharts';
import { ApexOptions } from 'apexcharts';
import { Box, useTheme } from '@mui/material';
export interface Candle {
x: number; // timestamp
@ -39,11 +40,12 @@ const QortCandlestickChart: React.FC<Props> = ({
}) => {
const smaData = showSMA ? calculateSMA(candles, 7) : [];
const intervalLabel = interval === 24 * 60 * 60 * 1000 ? '1d' : '1h';
// const [yMin, setYMin] = useState(undefined);
// const [yMax, setYMax] = useState(undefined);
const theme = useTheme();
const options: ApexOptions = {
chart: {
type: 'candlestick',
width: '100%',
height: '100%',
background: background,
toolbar: {
show: true,
@ -66,25 +68,29 @@ const QortCandlestickChart: React.FC<Props> = ({
title: {
text: `QORT/${pairLabel} Price (${intervalLabel} Candles) (${themeMode === 'dark' ? 'Dark' : 'Light'} Theme)`,
align: 'center',
style: { color: textColor, fontWeight: 700, fontSize: '1.11rem' },
style: {
color: theme.palette.text.primary,
fontWeight: 700,
fontSize: '1.11rem',
},
offsetY: 8, //adjust if necessary
},
xaxis: {
type: 'datetime',
labels: { style: { colors: textColor } },
labels: { style: { colors: theme.palette.text.primary } },
axisBorder: { color: textColor },
axisTicks: { color: textColor },
tooltip: { enabled: true },
},
yaxis: {
tooltip: { enabled: true },
labels: { style: { colors: textColor } },
labels: { style: { colors: theme.palette.text.primary } },
axisBorder: { color: textColor },
axisTicks: { color: textColor },
},
theme: { mode: themeMode },
legend: {
labels: { colors: textColor },
labels: { colors: theme.palette.text.primary },
show: showSMA && smaData.length > 0,
},
grid: {
@ -93,21 +99,14 @@ const QortCandlestickChart: React.FC<Props> = ({
tooltip: {
theme: themeMode,
},
responsive: [
{
breakpoint: 800,
options: {
chart: { height: 320 },
title: { style: { fontSize: '1rem' } },
},
},
{
breakpoint: 1200,
options: {
chart: { height: 400 },
},
},
],
// plotOptions: {
// candlestick: {
// // Width can be a number (pixels) or a string (percentage)
// // e.g., width: 8 (pixels) or width: '80%' (of grid slot)
// // @ts-expect-error: width is supported at runtime even if not in types
// width: '100%',
// },
// },
};
const series = [
@ -118,7 +117,22 @@ const QortCandlestickChart: React.FC<Props> = ({
];
return (
<Chart options={options} series={series} type="candlestick" height={420} />
<Box
sx={{
width: '100%',
height: { xs: 280, sm: 420, md: 540, lg: '60vh' },
minHeight: 240,
maxWidth: '100vw',
}}
>
<Chart
options={options}
series={series}
type="candlestick"
width="100%"
height="100%"
/>
</Box>
);
};