done for the day
This commit is contained in:
parent
84ab5946e7
commit
fd23b2e5a6
125
src/App.tsx
125
src/App.tsx
@ -4,7 +4,7 @@ import QortCandlestickChart, {
|
|||||||
} from './components/QortCandlestickChart';
|
} from './components/QortCandlestickChart';
|
||||||
import {
|
import {
|
||||||
aggregateCandles,
|
aggregateCandles,
|
||||||
aggregateDailyCandles,
|
// aggregateDailyCandles,
|
||||||
Trade,
|
Trade,
|
||||||
} from './utils/qortTrades';
|
} from './utils/qortTrades';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
@ -66,57 +66,47 @@ const App: React.FC = () => {
|
|||||||
const [isFiltering, setIsFiltering] = useState(false);
|
const [isFiltering, setIsFiltering] = useState(false);
|
||||||
const [isUpdating, setIsUpdating] = useState<Record<string, boolean>>({});
|
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 {
|
function getLatestTradeTimestamp(trades: Trade[]): number {
|
||||||
if (!trades || !trades.length) return 0;
|
if (!trades || !trades.length) return 0;
|
||||||
return Math.max(...trades.map((t) => t.tradeTimestamp));
|
return Math.max(...trades.map((t) => t.tradeTimestamp));
|
||||||
}
|
}
|
||||||
|
|
||||||
function fastPercentileFilter(trades: Trade[], lower = 0.01, upper = 0.99) {
|
// function fastPercentileFilter(trades: Trade[], lower = 0.01, upper = 0.99) {
|
||||||
// 1. Extract price array (one pass)
|
// // 1. Extract price array (one pass)
|
||||||
const prices = [];
|
// const prices = [];
|
||||||
const validTrades = [];
|
// const validTrades = [];
|
||||||
for (const t of trades) {
|
// for (const t of trades) {
|
||||||
const qort = parseFloat(t.qortAmount);
|
// const qort = parseFloat(t.qortAmount);
|
||||||
const price = parseFloat(t.foreignAmount) / qort;
|
// const price = parseFloat(t.foreignAmount) / qort;
|
||||||
if (isFinite(price) && price > 0) {
|
// if (isFinite(price) && price > 0) {
|
||||||
prices.push(price);
|
// prices.push(price);
|
||||||
validTrades.push({ trade: t, price });
|
// validTrades.push({ trade: t, price });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// 2. Get percentiles (sort once)
|
// // 2. Get percentiles (sort once)
|
||||||
prices.sort((a, b) => a - b);
|
// prices.sort((a, b) => a - b);
|
||||||
const min = prices[Math.floor(prices.length * lower)];
|
// const min = prices[Math.floor(prices.length * lower)];
|
||||||
const max = prices[Math.ceil(prices.length * upper) - 1];
|
// const max = prices[Math.ceil(prices.length * upper) - 1];
|
||||||
// 3. Filter in single pass
|
// // 3. Filter in single pass
|
||||||
return validTrades
|
// return validTrades
|
||||||
.filter(({ price }) => price >= min && price <= max)
|
// .filter(({ price }) => price >= min && price <= max)
|
||||||
.map(({ trade }) => trade);
|
// .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 ---
|
// --- LocalStorage LOAD on mount ---
|
||||||
@ -146,13 +136,15 @@ const App: React.FC = () => {
|
|||||||
console.log('Filter effect skipped - waiting for cacheLoaded');
|
console.log('Filter effect skipped - waiting for cacheLoaded');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsFiltering(true);
|
setIsFiltering(true);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// --- Determine minTimestamp ---
|
// --- Determine minTimestamp ---
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const periodObj = PERIODS.find((p) => p.label === period);
|
const periodObj = PERIODS.find((p) => p.label === period);
|
||||||
let minTimestamp = 0;
|
let minTimestamp = 0;
|
||||||
let useDaily = false;
|
// let useDaily = false;
|
||||||
if (periodObj) {
|
if (periodObj) {
|
||||||
if ('days' in periodObj && periodObj.days !== undefined) {
|
if ('days' in periodObj && periodObj.days !== undefined) {
|
||||||
now.setDate(now.getDate() - periodObj.days);
|
now.setDate(now.getDate() - periodObj.days);
|
||||||
@ -164,12 +156,12 @@ const App: React.FC = () => {
|
|||||||
) {
|
) {
|
||||||
now.setMonth(now.getMonth() - periodObj.months);
|
now.setMonth(now.getMonth() - periodObj.months);
|
||||||
minTimestamp = now.getTime();
|
minTimestamp = now.getTime();
|
||||||
// For 1M or more, use daily candles
|
// For 1M or more, use daily candles----------------------------------------------DISABLED FORCING DAILY CANDLES, AND MODIFIED FILTERING.
|
||||||
if (periodObj.months >= 1) useDaily = true;
|
// if (periodObj.months >= 6) useDaily = false;
|
||||||
} else if ('months' in periodObj && periodObj.months === null) {
|
} else if ('months' in periodObj && periodObj.months === null) {
|
||||||
// 'All'
|
// 'All'
|
||||||
minTimestamp = 0;
|
minTimestamp = 0;
|
||||||
useDaily = true;
|
// useDaily = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- Filter trades ---
|
// --- Filter trades ---
|
||||||
@ -177,14 +169,16 @@ const App: React.FC = () => {
|
|||||||
let filtered = minTimestamp
|
let filtered = minTimestamp
|
||||||
? trades.filter((t) => t.tradeTimestamp >= minTimestamp)
|
? trades.filter((t) => t.tradeTimestamp >= minTimestamp)
|
||||||
: trades;
|
: trades;
|
||||||
filtered = fastPercentileFilter(filtered, 0.01, 0.99);
|
filtered = fastPercentileFilter(filtered, 0.00005, 0.995);
|
||||||
|
|
||||||
// --- Aggregate ---
|
// // --- Aggregate ---
|
||||||
if (useDaily) {
|
// if (useDaily) {
|
||||||
setCandles(aggregateDailyCandles(filtered));
|
// setCandles(aggregateDailyCandles(filtered));
|
||||||
} else {
|
// } else {
|
||||||
setCandles(aggregateCandles(filtered, interval));
|
// setCandles(aggregateCandles(filtered, interval));
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
setCandles(aggregateCandles(filtered, interval));
|
||||||
setIsFiltering(false);
|
setIsFiltering(false);
|
||||||
}, 10);
|
}, 10);
|
||||||
}, [interval, period, selectedChain, allChainTrades, cacheLoaded]);
|
}, [interval, period, selectedChain, allChainTrades, cacheLoaded]);
|
||||||
@ -300,10 +294,11 @@ const App: React.FC = () => {
|
|||||||
total={null}
|
total={null}
|
||||||
chain={selectedChain}
|
chain={selectedChain}
|
||||||
/>
|
/>
|
||||||
<Container maxWidth="xl" disableGutters>
|
<Container maxWidth={false} disableGutters>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
|
width: '100vw',
|
||||||
background: theme.palette.background.default,
|
background: theme.palette.background.default,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
p: { xs: 1, md: 3 },
|
p: { xs: 1, md: 3 },
|
||||||
@ -313,11 +308,11 @@ const App: React.FC = () => {
|
|||||||
<Paper
|
<Paper
|
||||||
elevation={5}
|
elevation={5}
|
||||||
sx={{
|
sx={{
|
||||||
p: { xs: 1, md: 4 },
|
width: '100%',
|
||||||
maxWidth: 1800,
|
margin: '36px 0 0 0', // Remove 'auto' to allow full width
|
||||||
margin: '36px auto 0 auto',
|
|
||||||
background: theme.palette.background.paper,
|
background: theme.palette.background.paper,
|
||||||
boxShadow: theme.shadows[6],
|
boxShadow: theme.shadows[6],
|
||||||
|
p: { xs: 1, md: 4 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Action Button */}
|
{/* Action Button */}
|
||||||
@ -449,13 +444,13 @@ const App: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
{/* Chart */}
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: 1800,
|
height: { xs: 320, md: 520, lg: '60vh' }, // adapt for screen
|
||||||
height: { xs: 320, md: 520 },
|
|
||||||
mx: 'auto',
|
mx: 'auto',
|
||||||
|
minHeight: 240,
|
||||||
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<QortCandlestickChart
|
<QortCandlestickChart
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Chart from 'react-apexcharts';
|
import Chart from 'react-apexcharts';
|
||||||
import { ApexOptions } from 'apexcharts';
|
import { ApexOptions } from 'apexcharts';
|
||||||
|
import { Box, useTheme } from '@mui/material';
|
||||||
|
|
||||||
export interface Candle {
|
export interface Candle {
|
||||||
x: number; // timestamp
|
x: number; // timestamp
|
||||||
@ -39,11 +40,12 @@ const QortCandlestickChart: React.FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const smaData = showSMA ? calculateSMA(candles, 7) : [];
|
const smaData = showSMA ? calculateSMA(candles, 7) : [];
|
||||||
const intervalLabel = interval === 24 * 60 * 60 * 1000 ? '1d' : '1h';
|
const intervalLabel = interval === 24 * 60 * 60 * 1000 ? '1d' : '1h';
|
||||||
// const [yMin, setYMin] = useState(undefined);
|
const theme = useTheme();
|
||||||
// const [yMax, setYMax] = useState(undefined);
|
|
||||||
const options: ApexOptions = {
|
const options: ApexOptions = {
|
||||||
chart: {
|
chart: {
|
||||||
type: 'candlestick',
|
type: 'candlestick',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
background: background,
|
background: background,
|
||||||
toolbar: {
|
toolbar: {
|
||||||
show: true,
|
show: true,
|
||||||
@ -66,25 +68,29 @@ const QortCandlestickChart: React.FC<Props> = ({
|
|||||||
title: {
|
title: {
|
||||||
text: `QORT/${pairLabel} Price (${intervalLabel} Candles) (${themeMode === 'dark' ? 'Dark' : 'Light'} Theme)`,
|
text: `QORT/${pairLabel} Price (${intervalLabel} Candles) (${themeMode === 'dark' ? 'Dark' : 'Light'} Theme)`,
|
||||||
align: 'center',
|
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
|
offsetY: 8, //adjust if necessary
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: 'datetime',
|
type: 'datetime',
|
||||||
labels: { style: { colors: textColor } },
|
labels: { style: { colors: theme.palette.text.primary } },
|
||||||
axisBorder: { color: textColor },
|
axisBorder: { color: textColor },
|
||||||
axisTicks: { color: textColor },
|
axisTicks: { color: textColor },
|
||||||
tooltip: { enabled: true },
|
tooltip: { enabled: true },
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
tooltip: { enabled: true },
|
tooltip: { enabled: true },
|
||||||
labels: { style: { colors: textColor } },
|
labels: { style: { colors: theme.palette.text.primary } },
|
||||||
axisBorder: { color: textColor },
|
axisBorder: { color: textColor },
|
||||||
axisTicks: { color: textColor },
|
axisTicks: { color: textColor },
|
||||||
},
|
},
|
||||||
theme: { mode: themeMode },
|
theme: { mode: themeMode },
|
||||||
legend: {
|
legend: {
|
||||||
labels: { colors: textColor },
|
labels: { colors: theme.palette.text.primary },
|
||||||
show: showSMA && smaData.length > 0,
|
show: showSMA && smaData.length > 0,
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
@ -93,21 +99,14 @@ const QortCandlestickChart: React.FC<Props> = ({
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
theme: themeMode,
|
theme: themeMode,
|
||||||
},
|
},
|
||||||
responsive: [
|
// plotOptions: {
|
||||||
{
|
// candlestick: {
|
||||||
breakpoint: 800,
|
// // Width can be a number (pixels) or a string (percentage)
|
||||||
options: {
|
// // e.g., width: 8 (pixels) or width: '80%' (of grid slot)
|
||||||
chart: { height: 320 },
|
// // @ts-expect-error: width is supported at runtime even if not in types
|
||||||
title: { style: { fontSize: '1rem' } },
|
// width: '100%',
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
|
||||||
breakpoint: 1200,
|
|
||||||
options: {
|
|
||||||
chart: { height: 400 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const series = [
|
const series = [
|
||||||
@ -118,7 +117,22 @@ const QortCandlestickChart: React.FC<Props> = ({
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user