diff --git a/src/App.tsx b/src/App.tsx index e293f49..98e29fc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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>({}); - // 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} /> - + { {/* Action Button */} @@ -449,13 +444,13 @@ const App: React.FC = () => { ))} - {/* Chart */} = ({ }) => { 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 = ({ 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 = ({ 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 = ({ ]; return ( - + + + ); };