← back to Ken

kalshi-dash/src/pages/Trading.jsx

395 lines

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

/* ─── Stat card ─── */
function StatCard({ label, value, color }) {
  return (
    <div className="card">
      <p className="stat-label mb-2">{label}</p>
      <p className="stat-v" style={{ color }}>{value}</p>
    </div>
  );
}

/* ─── Config row ─── */
function ConfigRow({ label, value, highlight }) {
  return (
    <div
      className="flex justify-between items-center px-3 py-2 rounded"
      style={{ background: 'var(--surface-el)' }}
    >
      <span className="text-sm" style={{ color: 'var(--text-muted)' }}>{label}</span>
      <span
        className="mono text-sm font-semibold"
        style={{ color: highlight || 'var(--text)' }}
      >
        {value}
      </span>
    </div>
  );
}

/* ─── Env toggle button ─── */
function EnvBtn({ label, active, color, onClick, disabled }) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className="flex-1 py-2 rounded-lg text-sm font-bold transition"
      style={{
        background: active ? `${color}22` : 'var(--surface-el)',
        color: active ? color : 'var(--text-muted)',
        border: `1px solid ${active ? color + '55' : 'var(--border)'}`,
      }}
    >
      {label}
    </button>
  );
}

/* ─── Main Trading page ─── */
export default function Trading() {
  const [mode, setMode] = useState(null);
  const [history, setHistory] = useState(null);
  const [system, setSystem] = useState(null);
  const [loading, setLoading] = useState(true);
  const [actionLoading, setActionLoading] = useState('');

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

  async function load() {
    try {
      const [m, h, s] = await Promise.all([
        post('/trading/mode', { action: 'get' }),
        api('/trading/history'),
        api('/system'),
      ]);
      setMode(m);
      setHistory(h);
      setSystem(s);
    } catch {}
    setLoading(false);
  }

  async function doAction(action, body = {}) {
    setActionLoading(action);
    try {
      await post('/trading/mode', { action, ...body });
      await load();
    } catch {}
    setActionLoading('');
  }

  if (loading) {
    return (
      <div className="flex items-center justify-center h-64">
        <div
          className="animate-spin w-8 h-8 border-2 border-t-transparent rounded-full pulse-cyan"
          style={{ borderColor: 'var(--cyan)', borderTopColor: 'transparent' }}
        />
      </div>
    );
  }

  const tc = mode?.trade_config || {};
  const ts = mode?.trader_state || {};
  const summary = history?.summary || {};
  const trades = history?.trades || [];
  const mem = system?.memory || {};

  const totalPnl = parseInt(summary.total_pnl || 0);
  const pnlColor = totalPnl > 0 ? 'var(--green)' : totalPnl < 0 ? 'var(--red)' : 'var(--text-muted)';

  return (
    <div className="space-y-6 fade-in">

      {/* ── Page Header ── */}
      <div className="flex items-center justify-between">
        <div>
          <div className="flex items-center gap-3">
            {/* Dollar icon */}
            <span className="text-2xl" style={{ color: 'var(--green)' }}>$</span>
            <h1 className="heading text-3xl font-bold" style={{ color: 'var(--text)' }}>
              Trading
            </h1>
            {/* Environment badge */}
            <span className={`badge ${mode?.env === 'prod' ? 'bg-green' : 'bg-orange'}`}>
              {(mode?.env || 'demo').toUpperCase()}
            </span>
            {mode?.safe_mode && (
              <span className="badge bg-orange">SAFE MODE</span>
            )}
            {mode?.auto_trader_enabled && (
              <span className="badge bg-green">AUTO ON</span>
            )}
          </div>
          <p className="section-subtitle mt-1">Mode control, execution, and P&amp;L</p>
        </div>
        <button onClick={load} className="btn btn-o btn-sm">↻ Refresh</button>
      </div>

      {/* ── Trading Controls ── */}
      <div className="grid grid-cols-3 gap-4">

        {/* Environment */}
        <div className="card-glow">
          <p className="stat-label mb-3">Environment</p>
          <div className="flex gap-2 mb-3">
            <EnvBtn
              label="DEMO"
              active={mode?.env === 'demo'}
              color="var(--orange)"
              onClick={() => doAction('switch_env', { env: 'demo' })}
              disabled={actionLoading === 'switch_env'}
            />
            <EnvBtn
              label="PROD"
              active={mode?.env === 'prod'}
              color="var(--green)"
              onClick={() => doAction('switch_env', { env: 'prod' })}
              disabled={actionLoading === 'switch_env'}
            />
          </div>
          <p className="text-xs" style={{ color: 'var(--text-muted)' }}>
            API keys: {mode?.kalshi_connected ? (
              <span style={{ color: 'var(--green)' }}>Configured</span>
            ) : (
              <span style={{ color: 'var(--red)' }}>Not set</span>
            )}
          </p>
        </div>

        {/* Safe mode */}
        <div className="card-glow">
          <p className="stat-label mb-3">Safe Mode</p>
          <button
            onClick={() => doAction('toggle_safe_mode')}
            disabled={actionLoading === 'toggle_safe_mode'}
            className="btn w-full btn-lg font-bold transition"
            style={{
              background: mode?.safe_mode ? 'rgba(247,147,26,0.15)' : 'var(--red-dim)',
              color: mode?.safe_mode ? 'var(--orange)' : 'var(--red)',
              border: `1px solid ${mode?.safe_mode ? 'rgba(247,147,26,0.4)' : 'rgba(255,92,92,0.4)'}`,
            }}
          >
            {actionLoading === 'toggle_safe_mode' ? '...' : (
              mode?.safe_mode ? 'SAFE MODE — ON' : 'SAFE MODE — OFF'
            )}
          </button>
          <p className="text-xs mt-2" style={{ color: 'var(--text-muted)' }}>
            {mode?.safe_mode ? 'No real orders will be placed' : (
              <span style={{ color: 'var(--red)' }}>Real orders will be executed!</span>
            )}
          </p>
        </div>

        {/* Auto trader */}
        <div className="card-glow">
          <p className="stat-label mb-3">Auto Trader</p>
          <button
            onClick={() => doAction('toggle_auto_trader')}
            disabled={actionLoading === 'toggle_auto_trader'}
            className="btn w-full btn-lg font-bold transition"
            style={{
              background: mode?.auto_trader_enabled ? 'var(--green-dim)' : 'var(--surface-el)',
              color: mode?.auto_trader_enabled ? 'var(--green)' : 'var(--text-muted)',
              border: `1px solid ${mode?.auto_trader_enabled ? 'rgba(0,195,137,0.4)' : 'var(--border)'}`,
            }}
          >
            {actionLoading === 'toggle_auto_trader' ? '...' : (
              mode?.auto_trader_enabled ? 'AUTO TRADER: ON' : 'AUTO TRADER: OFF'
            )}
          </button>
          <p className="text-xs mt-2" style={{ color: 'var(--text-muted)' }}>
            Min confidence: <span className="mono">{tc.min_confidence}%</span>
            &nbsp;·&nbsp;
            Budget: <span className="mono">${(tc.budget_cents / 100).toFixed(0)}</span>
          </p>
        </div>
      </div>

      {/* ── Portfolio Stats ── */}
      <div className="grid grid-cols-6 gap-3">
        <StatCard label="Total Trades" value={summary.total_trades || 0}                                                                         color="var(--orange)" />
        <StatCard label="Open"         value={summary.open_trades || 0}                                                                          color="var(--blue)" />
        <StatCard label="Wins"         value={summary.winning_trades || 0}                                                                       color="var(--green)" />
        <StatCard label="Losses"       value={summary.losing_trades || 0}                                                                        color="var(--red)" />
        <StatCard label="Total P&L"    value={summary.total_pnl ? `$${(totalPnl / 100).toFixed(2)}` : '$0.00'}                                  color={pnlColor} />
        <StatCard label="Exposure"     value={`$${(parseInt(summary.open_exposure || 0) / 100).toFixed(2)}`}                                     color="var(--purple)" />
      </div>

      {/* ── Config + System ── */}
      <div className="grid grid-cols-2 gap-4">

        {/* Trade Config */}
        <div className="card">
          <p className="section-title mb-3" style={{ fontSize: '1rem' }}>
            <span style={{ color: 'var(--cyan)' }}>⚙</span> Trade Config
          </p>
          <div className="space-y-2">
            <ConfigRow label="Budget"          value={`$${(tc.budget_cents / 100).toFixed(0)}`}            highlight="var(--cyan)" />
            <ConfigRow label="Max per trade"   value={`$${(tc.max_position_cents / 100).toFixed(0)}`} />
            <ConfigRow label="Max positions"   value={tc.max_open_positions} />
            <ConfigRow label="Daily loss limit" value={`$${(tc.daily_loss_limit_cents / 100).toFixed(0)}`} highlight="var(--red)" />
            <ConfigRow label="Min confidence"  value={`${tc.min_confidence}%`}                             highlight="var(--orange)" />
            <ConfigRow label="Take profit"     value={`${tc.take_profit_pct}%`}                            highlight="var(--green)" />
            <ConfigRow label="Stop loss"       value={`${tc.stop_loss_pct}%`}                              highlight="var(--red)" />
          </div>
        </div>

        {/* System Health */}
        <div className="card">
          <p className="section-title mb-3" style={{ fontSize: '1rem' }}>
            <span style={{ color: 'var(--cyan)' }}>♥</span> System Health
          </p>
          <div className="space-y-2">
            <ConfigRow label="Memory (RSS)"     value={`${mem.rss_mb || '?'} MB`} />
            <ConfigRow label="Heap Used"        value={`${mem.heapUsed_mb || '?'} MB`} />
            <ConfigRow label="Heap Total"       value={`${mem.heapTotal_mb || '?'} MB`} />
            <ConfigRow label="Uptime"           value={`${system?.uptime_hours || '?'}h`}                   highlight="var(--blue)" />
            <ConfigRow label="Cached Markets"   value={system?.cachedMarkets || 0}                          highlight="var(--cyan)" />
            <ConfigRow label="PID"              value={system?.pid || '?'} />
            <ConfigRow
              label="Available Budget"
              value={`$${((ts.available || 0) / 100).toFixed(2)}`}
              highlight="var(--green)"
            />
          </div>
        </div>
      </div>

      {/* ── Trade History ── */}
      <div className="card overflow-x-auto" style={{ padding: 0 }}>
        {/* Table header */}
        <div
          className="flex items-center justify-between px-5 py-4"
          style={{ borderBottom: '1px solid var(--border)' }}
        >
          <p className="section-title" style={{ fontSize: '1rem' }}>
            <span style={{ color: 'var(--orange)' }}>◈</span> Trade History
          </p>
          {trades.length > 0 && (
            <span className="badge bg-orange">{trades.length} trades</span>
          )}
        </div>

        <table>
          <thead>
            <tr>
              <th>Time</th>
              <th>Market</th>
              <th>Side</th>
              <th>Qty</th>
              <th>Entry</th>
              <th>Exit</th>
              <th>P&amp;L</th>
              <th>Type</th>
              <th>Status</th>
            </tr>
          </thead>
          <tbody>
            {trades.length === 0 ? (
              <tr>
                <td colSpan={9} className="text-center py-12" style={{ color: 'var(--text-muted)' }}>
                  <div className="flex flex-col items-center gap-2">
                    <span className="text-2xl" style={{ color: 'var(--text-muted)' }}>$</span>
                    <span className="text-sm">No trades yet — signals are generating, waiting for execution</span>
                  </div>
                </td>
              </tr>
            ) : (
              trades.map((t, i) => {
                const pnl = parseInt(t.pnl_cents) || 0;
                const pnlColor = pnl > 0 ? 'var(--green)' : pnl < 0 ? 'var(--red)' : 'var(--text-muted)';
                const statusClass =
                  t.status === 'open'    ? 'bg-blue'   :
                  t.status === 'profit'  ? 'bg-green'  :
                  t.status === 'stopped' ? 'bg-red'    : 'bg-orange';

                return (
                  <tr key={i}>
                    {/* Time */}
                    <td className="whitespace-nowrap">
                      <span className="mono text-xs" style={{ color: 'var(--text-muted)' }}>
                        {new Date(t.created_at).toLocaleDateString()}{' '}
                        {new Date(t.created_at).toLocaleTimeString()}
                      </span>
                    </td>

                    {/* Market */}
                    <td className="max-w-[200px]">
                      <a
                        href={`https://kalshi.com/search?query=${encodeURIComponent((t.ticker || '').split('-')[0])}`}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="text-sm truncate block hover:underline transition heading"
                        style={{ color: 'var(--cyan)' }}
                      >
                        {t.title || t.ticker}
                      </a>
                    </td>

                    {/* Side */}
                    <td>
                      <span className={`badge ${t.side === 'yes' ? 'bg-green' : 'bg-red'}`}>
                        {(t.side || '').toUpperCase()}
                      </span>
                    </td>

                    {/* Qty */}
                    <td>
                      <span className="mono text-sm" style={{ color: 'var(--text-dim)' }}>
                        {t.count}
                      </span>
                    </td>

                    {/* Entry price */}
                    <td>
                      <span className="mono text-sm" style={{ color: 'var(--text-dim)' }}>
                        {t.price_cents}c
                      </span>
                    </td>

                    {/* Exit price */}
                    <td>
                      <span className="mono text-sm" style={{ color: t.exit_price ? 'var(--text-dim)' : 'var(--text-muted)' }}>
                        {t.exit_price ? `${t.exit_price}c` : '—'}
                      </span>
                    </td>

                    {/* P&L */}
                    <td>
                      <span className="mono text-sm font-bold" style={{ color: pnlColor }}>
                        {pnl !== 0
                          ? `${pnl > 0 ? '+' : ''}$${(pnl / 100).toFixed(2)}`
                          : '—'
                        }
                      </span>
                    </td>

                    {/* Signal type */}
                    <td>
                      <span className="text-xs" style={{ color: 'var(--text-muted)' }}>
                        {t.signal_type}
                      </span>
                    </td>

                    {/* Status */}
                    <td>
                      <span className={`badge ${statusClass}`}>{t.status}</span>
                    </td>
                  </tr>
                );
              })
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}