← back to Bertha

src/app/dashboard/risk/page.js

142 lines

'use client';
import { useState, useEffect } from 'react';

export default function RiskPage() {
  const [risk, setRisk] = useState(null);
  const [loading, setLoading] = useState(true);

  async function loadRisk() {
    try {
      const res = await fetch('/api/risk');
      const data = await res.json();
      setRisk(data);
    } catch (err) { console.error(err); }
    setLoading(false);
  }

  useEffect(() => { loadRisk(); const i = setInterval(loadRisk, 10000); return () => clearInterval(i); }, []);

  async function toggleKillSwitch() {
    const action = risk?.state?.kill_switch_active ? 'deactivate' : 'activate';
    const reason = action === 'activate' ? prompt('Kill switch reason:') : null;
    if (action === 'activate' && !reason) return;
    try {
      await fetch('/api/risk', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: `kill_switch_${action}`, reason }) });
      loadRisk();
    } catch (err) { alert('Error: ' + err.message); }
  }

  async function updateConfig(key, value) {
    try {
      const config = { ...risk.state.config, [key]: value };
      await fetch('/api/risk', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'update_config', config }) });
      loadRisk();
    } catch (err) { alert('Error: ' + err.message); }
  }

  if (loading) return <div className="flex items-center justify-center h-64"><div className="animate-spin w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full" /></div>;

  const s = risk?.state || {};
  const c = s.config || {};

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-2xl font-bold">Risk Management</h1>
          <p className="text-gray-500 text-sm">Exposure limits, kill switches, and sizing controls</p>
        </div>
        <button onClick={toggleKillSwitch} className={`btn ${s.kill_switch_active ? 'btn-success' : 'btn-danger'}`}>
          {s.kill_switch_active ? 'Deactivate Kill Switch' : 'Activate Kill Switch'}
        </button>
      </div>

      {s.kill_switch_active && (
        <div className="bg-red-500/10 border border-red-500/30 rounded-xl p-4 flex items-center gap-3">
          <span className="text-2xl">&#128680;</span>
          <div>
            <p className="font-semibold text-red-400">KILL SWITCH ACTIVE</p>
            <p className="text-sm text-gray-400">{s.kill_reason || 'No reason specified'}</p>
          </div>
        </div>
      )}

      <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
        <div className="card">
          <p className="text-xs text-gray-500 uppercase mb-1">Bankroll</p>
          <p className="stat-value text-green-400">${parseFloat(s.bankroll || 50).toFixed(2)}</p>
        </div>
        <div className="card">
          <p className="text-xs text-gray-500 uppercase mb-1">Daily P&L</p>
          <p className={`stat-value ${parseFloat(s.daily_pnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'}`}>${parseFloat(s.daily_pnl || 0).toFixed(2)}</p>
        </div>
        <div className="card">
          <p className="text-xs text-gray-500 uppercase mb-1">Open Exposure</p>
          <p className="stat-value text-blue-400">${parseFloat(s.open_exposure || 0).toFixed(2)}</p>
        </div>
        <div className="card">
          <p className="text-xs text-gray-500 uppercase mb-1">Max Drawdown</p>
          <p className="stat-value text-purple-400">{parseFloat(s.max_drawdown_pct || 0).toFixed(1)}%</p>
        </div>
      </div>

      {/* Risk Configuration */}
      <div className="card">
        <h2 className="font-semibold mb-4">Risk Configuration</h2>
        <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
          {[
            { key: 'paper_mode', label: 'Paper Trading Mode', type: 'toggle', desc: 'When ON, no real orders are placed' },
            { key: 'max_bet_usd', label: 'Max Bet Size ($)', type: 'number', step: 0.05 },
            { key: 'max_daily_loss_usd', label: 'Max Daily Loss ($)', type: 'number', step: 0.50 },
            { key: 'max_daily_loss_pct', label: 'Max Daily Loss (%)', type: 'number', step: 1 },
            { key: 'max_open_exposure_usd', label: 'Max Open Exposure ($)', type: 'number', step: 0.50 },
            { key: 'max_drawdown_pct', label: 'Max Drawdown (%)', type: 'number', step: 1 },
            { key: 'min_edge_threshold', label: 'Min Edge Threshold', type: 'number', step: 0.005 },
            { key: 'min_liquidity', label: 'Min Market Liquidity ($)', type: 'number', step: 10 },
            { key: 'fractional_kelly', label: 'Kelly Fraction', type: 'number', step: 0.05 },
          ].map(item => (
            <div key={item.key} className="flex items-center justify-between bg-gray-800/30 rounded-lg p-3">
              <div>
                <p className="text-sm font-medium">{item.label}</p>
                {item.desc && <p className="text-xs text-gray-500">{item.desc}</p>}
              </div>
              {item.type === 'toggle' ? (
                <button onClick={() => updateConfig(item.key, !c[item.key])}
                  className={`w-12 h-6 rounded-full transition-colors ${c[item.key] ? 'bg-green-500' : 'bg-gray-600'} relative`}>
                  <span className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${c[item.key] ? 'left-6' : 'left-0.5'}`} />
                </button>
              ) : (
                <input type="number" step={item.step} value={c[item.key] ?? ''} onChange={e => updateConfig(item.key, parseFloat(e.target.value))}
                  className="w-24 bg-gray-800 border border-gray-700 rounded px-2 py-1 text-sm text-right font-mono" />
              )}
            </div>
          ))}
        </div>
      </div>

      {/* Edge Gate Simulation */}
      <div className="card">
        <h2 className="font-semibold mb-3">Edge Gate Simulator</h2>
        <p className="text-gray-500 text-sm mb-4">Test if a hypothetical trade would pass risk gates</p>
        <div className="grid grid-cols-4 gap-3">
          <div><label className="text-xs text-gray-500">P(Yes)</label><input id="sim-pyes" type="number" step="0.01" defaultValue="0.65" className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-sm font-mono" /></div>
          <div><label className="text-xs text-gray-500">Market Mid</label><input id="sim-mid" type="number" step="0.01" defaultValue="0.55" className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-sm font-mono" /></div>
          <div><label className="text-xs text-gray-500">Spread</label><input id="sim-spread" type="number" step="0.01" defaultValue="0.04" className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-sm font-mono" /></div>
          <div><label className="text-xs text-gray-500">Liquidity ($)</label><input id="sim-liq" type="number" step="10" defaultValue="500" className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-sm font-mono" /></div>
        </div>
        <button onClick={async () => {
          const res = await fetch('/api/risk', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
            action: 'evaluate',
            pYes: parseFloat(document.getElementById('sim-pyes').value),
            marketMid: parseFloat(document.getElementById('sim-mid').value),
            spread: parseFloat(document.getElementById('sim-spread').value),
            liquidity: parseFloat(document.getElementById('sim-liq').value),
          })});
          const data = await res.json();
          alert(JSON.stringify(data.evaluation, null, 2));
        }} className="btn btn-primary mt-3">Simulate</button>
      </div>
    </div>
  );
}