Vycka12
UI: hide Timeframe interval for VPIN, Dollar Bars, AggTrades, Liquidations
926428a
import { useState, useEffect } from 'react'
import './App.css'
const API_BASE = (import.meta.env.VITE_API_URL || 'https://vycka12-binance-data-backend.hf.space') + '/api';
function App() {
const [symbols, setSymbols] = useState(['BTC/USDT']);
const [selectedSymbol, setSelectedSymbol] = useState('BTC/USDT');
const [interval, setInterval] = useState('1m');
const [dataType, setDataType] = useState('Klines (OHLCV)');
// Date setup (last 7 days default)
const defaultEnd = new Date().toISOString().split('T')[0];
const defaultStart = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
const [startDate, setStartDate] = useState(defaultStart);
const [endDate, setEndDate] = useState(defaultEnd);
// Status and data
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [successMsg, setSuccessMsg] = useState(null);
const [previewData, setPreviewData] = useState([]);
const [csvData, setCsvData] = useState(null);
const [modalCmd, setModalCmd] = useState(null);
const [dollarThreshold, setDollarThreshold] = useState(1000000);
const [aggMode, setAggMode] = useState('Standard (Klines + Liq)');
// Progress simulation (since real SSE would require more backend work)
const [progress, setProgress] = useState(0);
const intervals = ['1s', '1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', '1d', '3d', '1w', '1mo'];
const dataTypes = ['Klines (OHLCV)', 'Liquidations', 'AggTrades', 'Dollar Bars (ML Ready)', 'VPIN (Flow Toxicity)', 'Time-Series Aggregator (Cloud)'];
useEffect(() => {
fetch(`${API_BASE}/symbols`)
.then(res => res.json())
.then(data => {
if (data.symbols && data.symbols.length > 0) {
setSymbols(data.symbols);
if (data.symbols.includes('BTC/USDT')) {
setSelectedSymbol('BTC/USDT');
} else {
setSelectedSymbol(data.symbols[0]);
}
}
})
.catch(err => console.error("Error fetching symbols:", err));
}, []);
// Simulate progress bar — slower for bigger date ranges
useEffect(() => {
let intervalId;
if (loading && progress < 90) {
const start = new Date(startDate);
const end = new Date(endDate);
const daysDiff = Math.max(1, (end - start) / (1000 * 60 * 60 * 24));
// Slower increments for bigger date ranges
const increment = daysDiff > 365 ? 0.5 : daysDiff > 30 ? 2 : 10;
intervalId = window.setInterval(() => {
setProgress(p => Math.min(p + Math.random() * increment, 90));
}, 1000);
} else if (!loading && progress > 0 && progress < 100) {
setProgress(100);
setTimeout(() => setProgress(0), 1000);
}
return () => clearInterval(intervalId);
}, [loading, progress, startDate, endDate]);
const handleDownload = async () => {
setLoading(true);
setError(null);
setSuccessMsg(null);
setPreviewData([]);
setCsvData(null);
setModalCmd(null);
setProgress(2);
try {
const response = await fetch(`${API_BASE}/download`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: selectedSymbol,
interval: interval,
data_type: dataType,
start_date: startDate,
end_date: endDate,
threshold: dollarThreshold,
agg_mode: aggMode
})
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.detail || "Įvyko nežinoma klaida");
}
if (result.success) {
let msg = `✅ Apdorojimas baigtas! Iš viso: ${result.row_count} eilučių.`;
if (result.hf_url) {
msg += ` Failas automatiškai įkeltas į Hugging Face!`;
}
setSuccessMsg(msg);
setPreviewData(result.preview);
setCsvData(result.csv_data);
if (result.hf_url) {
// You could also store this URL in state if you want to show a dedicated button
console.log("HF URL:", result.hf_url);
}
} else {
setError(result.message || "Duomenų nerasta.");
}
} catch (err) {
setError(`❌ Klaida: ${err.message}`);
} finally {
setLoading(false);
}
};
const downloadCsvFile = () => {
if (!csvData) return;
const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${selectedSymbol.replace('/', '_')}_${dataType.split(' ')[0]}_${startDate}_${endDate}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
};
return (
<div className="app-container">
{/* Sidebar */}
<aside className="sidebar glass-panel">
<h2>⚙️ Konfigūracija</h2>
<div className="form-group">
<label>Pasirinkite simbolį</label>
<select
className="form-control"
value={selectedSymbol}
onChange={e => setSelectedSymbol(e.target.value)}
>
{symbols.map(s => <option key={s} value={s}>{s}</option>)}
</select>
</div>
{['Klines (OHLCV)', 'Time-Series Aggregator (Cloud)'].includes(dataType) && (
<div className="form-group">
<label>Intervalas (Timeframe)</label>
<select
className="form-control"
value={interval}
onChange={e => setInterval(e.target.value)}
>
{intervals.map(i => <option key={i} value={i}>{i}</option>)}
</select>
</div>
)}
<div className="form-group">
<label>Duomenų tipas</label>
<div className="radio-group">
{dataTypes.map(type => (
<label key={type} className="radio-label">
<input
type="radio"
name="dataType"
value={type}
checked={dataType === type}
onChange={e => setDataType(e.target.value)}
/>
{type}
</label>
))}
</div>
</div>
{dataType === 'Dollar Bars (ML Ready)' && (
<div className="form-group">
<label>Dollar Threshold ($)</label>
<input
type="number"
className="form-control"
value={dollarThreshold}
onChange={e => setDollarThreshold(Number(e.target.value))}
step="100000"
/>
</div>
)}
{dataType === 'VPIN (Flow Toxicity)' && (
<div className="form-group">
<label>Buckets Per Day (Resolution)</label>
<input
type="number"
className="form-control"
value={dollarThreshold} // Reusing threshold state for buckets_per_day
onChange={e => setDollarThreshold(Number(e.target.value))}
step="10"
/>
<small style={{ color: 'var(--text-secondary)', fontSize: '0.75rem' }}>Rekomenduojama: 50-100</small>
</div>
)}
{dataType === 'Time-Series Aggregator (Cloud)' && (
<div className="form-group">
<label>Cloud Aggregation Mode</label>
<div className="radio-group">
{['Standard (Klines + Liq)', 'Dollar Bars'].map(mode => (
<label key={mode} className="radio-label">
<input
type="radio"
name="aggMode"
value={mode}
checked={aggMode === mode}
onChange={e => setAggMode(e.target.value)}
/>
{mode}
</label>
))}
</div>
</div>
)}
<div className="date-row">
<div className="form-group">
<label>Pradžia</label>
<input
type="date"
className="form-control"
value={startDate}
onChange={e => setStartDate(e.target.value)}
/>
</div>
<div className="form-group">
<label>Pabaiga</label>
<input
type="date"
className="form-control"
value={endDate}
onChange={e => setEndDate(e.target.value)}
/>
</div>
</div>
<button
className="btn-primary"
onClick={handleDownload}
disabled={loading}
>
{loading ? '⏳ Apdorojama...' : '🚀 Vykdyti'}
</button>
</aside>
{/* Main Content */}
<main className="main-content">
<header className="dashboard-header glass-panel">
<h1>📊 Binance {dataType} Dashboard</h1>
<div className="status-bar">
<div className="status-item">Simbolis: <span>{selectedSymbol}</span></div>
<div className="status-item">Intervalas: <span>{interval}</span></div>
<div className="status-item">Laikotarpis: <span>{startDate} iki {endDate}</span></div>
</div>
</header>
<section className="results-area glass-panel">
{loading ? (
<div className="loader-container">
<div className="loader-neon"></div>
<h3 className="gradient-text">Kraunami duomenys...</h3>
<p className="loader-info">Apdorojama: <b>{dataType}</b></p>
{(() => {
const days = Math.max(1, (new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24));
if (days > 365) return <span className="loader-timer">⏱️ Didelis laikotarpis ({Math.round(days / 365)} m.) — gali užtrukti 2-5 min.</span>;
if (days > 30) return <span className="loader-timer">⏱️ ~{Math.round(days / 30)} mėn. duomenų — gali užtrukti ~1-2 min.</span>;
return null;
})()}
<div className="progress-bar-container">
<div className="progress-bar" style={{ width: `${progress}%` }}></div>
</div>
</div>
) : (
<>
{error && <div className="alert alert-error">{error}</div>}
{successMsg && <div className="alert alert-success">{successMsg}</div>}
{modalCmd && (
<div className="alert alert-info" style={{ flexDirection: 'column' }}>
<strong>🌩️ Modal Cloud Data Processing</strong>
<span>Ši funkcija skirta didelių duomenų kiekių apjungimui (2+ metai). Vykdykite terminale:</span>
<div className="code-block">{modalCmd}</div>
</div>
)}
{!modalCmd && previewData.length > 0 ? (
<>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h3 style={{ color: 'var(--text-primary)' }}>Duomenų peržiūra (Paskutinės 100 eilučių)</h3>
<button className="btn-primary" onClick={downloadCsvFile} style={{ marginTop: 0, padding: '8px 16px', fontSize: '0.9rem' }}>
💾 Atsisiųsti CSV failą
</button>
</div>
<div className="table-container">
<table>
<thead>
<tr>
{Object.keys(previewData[0]).map(key => (
<th key={key}>{key}</th>
))}
</tr>
</thead>
<tbody>
{previewData.map((row, i) => (
<tr key={i}>
{Object.values(row).map((val, j) => (
<td key={j}>{val}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</>
) : (
!error && !loading && !modalCmd && (
<div className="empty-state">
Pasirinkite nustatymus ir spauskite "Vykdyti", kad atsisiųstumėte duomenis.
</div>
)
)}
</>
)}
</section>
</main>
</div>
)
}
export default App