← back to Bertha

kalshi-dash/src/setup/steps/RunPredictions.jsx

88 lines

import React, { useState, useEffect } from 'react';
import { post } from '../../api';

export default function RunPredictions({ data, update, next, back }) {
  const [loading, setLoading] = useState(true);
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => { run(); }, []);

  async function run() {
    setLoading(true); setError(null);
    try {
      const r = await post('/kalshi', { action: 'run_predictions' });
      setResult(r);
      update({ predictionsRun: true });
    } catch { setError('Failed to generate predictions'); }
    setLoading(false);
  }

  const predictions = result?.predictions || [];
  const buySignals = predictions.filter(p => (p.edge_bps || 0) > 0);

  return (
    <div className="space-y-6">
      <div className="text-center">
        <h2 className="text-2xl font-bold mb-2">The Machine Is Thinking</h2>
        <p className="text-gray-400 text-sm">Running the numbers so you don't have to use that head of yours</p>
      </div>

      <div className="card">
        {loading ? (
          <div className="flex flex-col items-center justify-center py-12 gap-4">
            <div className="animate-spin w-10 h-10 border-3 border-green-500 border-t-transparent rounded-full" />
            <p className="text-sm text-gray-400">Crunching numbers... in my day we did this with an abacus</p>
          </div>
        ) : error ? (
          <div className="text-center py-8">
            <p className="text-red-400 text-sm mb-3">{error}</p>
            <button onClick={run} className="btn btn-s text-sm">Retry</button>
          </div>
        ) : (
          <div className="space-y-4">
            <div className="grid grid-cols-3 gap-4">
              <div className="text-center">
                <p className="stat-v text-amber-400">{predictions.length}</p>
                <p className="text-xs text-gray-500 mt-1">Opinions</p>
              </div>
              <div className="text-center">
                <p className="stat-v text-green-400">{buySignals.length}</p>
                <p className="text-xs text-gray-500 mt-1">Worth a Look</p>
              </div>
              <div className="text-center">
                <p className="stat-v text-blue-400">{result?.avg_edge_bps || 0}bps</p>
                <p className="text-xs text-gray-500 mt-1">Your Edge (God Willing)</p>
              </div>
            </div>

            {predictions.length > 0 && (
              <div className="border-t border-gray-800 pt-3">
                <table>
                  <thead><tr><th>Contract</th><th>Side</th><th>Price</th><th>Edge</th><th>Signal</th></tr></thead>
                  <tbody>
                    {predictions.slice(0, 6).map((p, i) => (
                      <tr key={i}>
                        <td className="max-w-[180px]"><span className="truncate block text-xs">{p.ticker || p.market}</span></td>
                        <td><span className={`badge ${p.side === 'yes' ? 'bg-green' : 'bg-red'}`}>{(p.side || 'yes').toUpperCase()}</span></td>
                        <td className="font-mono text-sm">{p.price_cents || '—'}c</td>
                        <td className={`font-mono text-sm ${(p.edge_bps || 0) > 0 ? 'text-green-400' : 'text-gray-500'}`}>{p.edge_bps || 0}bps</td>
                        <td><span className={`badge ${(p.edge_bps || 0) > 0 ? 'bg-green' : 'bg-yellow'}`}>{(p.edge_bps || 0) > 0 ? 'BUY' : 'HOLD'}</span></td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            )}
          </div>
        )}
      </div>

      <div className="flex items-center justify-between">
        <button onClick={back} className="text-sm text-gray-500 hover:text-gray-300">Back</button>
        <button onClick={next} disabled={loading} className="btn btn-p">{loading ? 'Processing...' : 'Continue'}</button>
      </div>
    </div>
  );
}