Add price prediction feature to dashboard
Enter a hypothetical BTC price to instantly see predicted portfolio value, predicted P&L, and difference vs current value — all calculated client-side. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,13 @@ const styles = {
|
|||||||
adminBtn: { background: 'none', border: '1px solid #f7931a', color: '#f7931a', borderRadius: '8px', padding: '0.4rem 1rem', cursor: 'pointer', textDecoration: 'none', fontSize: '1rem' },
|
adminBtn: { background: 'none', border: '1px solid #f7931a', color: '#f7931a', borderRadius: '8px', padding: '0.4rem 1rem', cursor: 'pointer', textDecoration: 'none', fontSize: '1rem' },
|
||||||
headerBtns: { display: 'flex', gap: '0.5rem', alignItems: 'center' },
|
headerBtns: { display: 'flex', gap: '0.5rem', alignItems: 'center' },
|
||||||
statsGrid: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1rem', marginBottom: '1.5rem' },
|
statsGrid: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1rem', marginBottom: '1.5rem' },
|
||||||
|
predictionSection: { background: '#1a1a1a', border: '1px solid #333', borderRadius: '12px', padding: '1rem', marginBottom: '1.5rem' },
|
||||||
|
predictionHeader: { color: '#888', fontSize: '0.8rem', marginBottom: '0.75rem' },
|
||||||
|
predictionRow: { display: 'flex', gap: '1rem', alignItems: 'flex-start', flexWrap: 'wrap' },
|
||||||
|
predictionInputWrap: { display: 'flex', flexDirection: 'column', gap: '0.3rem' },
|
||||||
|
predictionLabel: { color: '#888', fontSize: '0.8rem' },
|
||||||
|
predictionInput: { background: '#111', border: '1px solid #444', borderRadius: '8px', color: '#fff', padding: '0.55rem 0.75rem', fontSize: '1rem', width: '160px', outline: 'none' },
|
||||||
|
predictionCards: { display: 'flex', gap: '1rem', flex: 1, flexWrap: 'wrap' },
|
||||||
statCard: { background: '#1a1a1a', padding: '1rem', borderRadius: '12px', border: '1px solid #333' },
|
statCard: { background: '#1a1a1a', padding: '1rem', borderRadius: '12px', border: '1px solid #333' },
|
||||||
statLabel: { color: '#888', fontSize: '0.8rem', marginBottom: '0.3rem' },
|
statLabel: { color: '#888', fontSize: '0.8rem', marginBottom: '0.3rem' },
|
||||||
statValue: { fontSize: '1.2rem', fontWeight: 700 },
|
statValue: { fontSize: '1.2rem', fontWeight: 700 },
|
||||||
@@ -53,6 +60,7 @@ export default function Dashboard() {
|
|||||||
const [candlesAll, setCandlesAll] = useState(null);
|
const [candlesAll, setCandlesAll] = useState(null);
|
||||||
const [fullscreenChart, setFullscreenChart] = useState(false);
|
const [fullscreenChart, setFullscreenChart] = useState(false);
|
||||||
const [chartView, setChartView] = useState('both'); // 'both' | 'portfolio' | 'history'
|
const [chartView, setChartView] = useState('both'); // 'both' | 'portfolio' | 'history'
|
||||||
|
const [predictionPrice, setPredictionPrice] = useState('');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const authHeaders = () => ({
|
const authHeaders = () => ({
|
||||||
@@ -107,6 +115,12 @@ export default function Dashboard() {
|
|||||||
setFullscreenChart(f => !f);
|
setFullscreenChart(f => !f);
|
||||||
}, [fullscreenChart, candlesAll, fetchAllCandles]);
|
}, [fullscreenChart, candlesAll, fetchAllCandles]);
|
||||||
|
|
||||||
|
const predPrice = parseFloat(predictionPrice);
|
||||||
|
const predValid = stats && predictionPrice !== '' && predPrice > 0;
|
||||||
|
const predValue = predValid ? +(stats.total_btc * predPrice).toFixed(2) : null;
|
||||||
|
const predPL = predValid ? +(predValue - stats.total_invested).toFixed(2) : null;
|
||||||
|
const predVsCurrent = predValid ? +(predValue - stats.portfolio_value).toFixed(2) : null;
|
||||||
|
|
||||||
const plHighlight = stats
|
const plHighlight = stats
|
||||||
? stats.profit_loss >= 0 ? 'positive' : 'negative'
|
? stats.profit_loss >= 0 ? 'positive' : 'negative'
|
||||||
: 'neutral';
|
: 'neutral';
|
||||||
@@ -143,6 +157,42 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div style={styles.predictionSection}>
|
||||||
|
<div style={styles.predictionHeader}>Price Prediction</div>
|
||||||
|
<div style={styles.predictionRow}>
|
||||||
|
<div style={styles.predictionInputWrap}>
|
||||||
|
<label style={styles.predictionLabel}>BTC Price (€)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
placeholder="e.g. 100000"
|
||||||
|
value={predictionPrice}
|
||||||
|
onChange={e => setPredictionPrice(e.target.value)}
|
||||||
|
style={styles.predictionInput}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{predValid && (
|
||||||
|
<div style={styles.predictionCards}>
|
||||||
|
<StatCard
|
||||||
|
label="Predicted Value"
|
||||||
|
value={`€${predValue.toLocaleString()}`}
|
||||||
|
highlight="neutral"
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
label="Predicted P&L"
|
||||||
|
value={`${predPL >= 0 ? '+' : ''}€${predPL.toLocaleString()}`}
|
||||||
|
highlight={predPL >= 0 ? 'positive' : 'negative'}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
label="vs. Current"
|
||||||
|
value={`${predVsCurrent >= 0 ? '+' : ''}€${predVsCurrent.toLocaleString()}`}
|
||||||
|
highlight={predVsCurrent >= 0 ? 'positive' : 'negative'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={styles.tabs}>
|
<div style={styles.tabs}>
|
||||||
{[['both', 'Both'], ['portfolio', 'Portfolio'], ['history', 'BTC Candles']].map(([key, label]) => (
|
{[['both', 'Both'], ['portfolio', 'Portfolio'], ['history', 'BTC Candles']].map(([key, label]) => (
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user