File size: 3,241 Bytes
fa81843
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// frontend/src/components/LedgerTable.tsx

import React from 'react';

export interface LedgerItem {
  timestamp: string;
  action: 'BUY' | 'SELL';
  ticker: string;
  shares: number;
  market_price: number;
  execution_price: number;
  commission: number;
  total_value: number;
  total_cost?: number;
  revenue?: number;
  realized_pnl?: number;
  cash_remaining: number;
}

interface LedgerTableProps {
  ledger: LedgerItem[];
  onRowClick?: (item: LedgerItem) => void;
}

export const LedgerTable: React.FC<LedgerTableProps> = ({ ledger, onRowClick }) => {
  const formatTime = (timeStr: string) => {
    // 简化时间显示,只保留 "YYYY-MM-DD HH:MM"
    try {
      const parts = timeStr.split(' ');
      if (parts.length >= 2) {
        return `${parts[0]} ${parts[1].substring(0, 5)}`;
      }
    } catch (e) {}
    return timeStr;
  };

  return (
    <div className="card" style={{ overflowX: 'auto' }}>
      <h3 className="card-title">交易流水账单 (Transaction Ledger)</h3>
      {ledger.length === 0 ? (
        <p style={{ color: 'var(--color-text-secondary)', margin: 0 }}>
          回测期间系统保持空仓防守,未触发交易信号。
        </p>
      ) : (
        <table className="ledger-table">
          <thead>
            <tr>
              <th>交易时间</th>
              <th>操作</th>
              <th>代码</th>
              <th>股数</th>
              <th>成交均价</th>
              <th>( 盘面价格 )</th>
              <th>交易手续费</th>
              <th style={{ textAlign: 'right' }}>净收益 PnL</th>
            </tr>
          </thead>
          <tbody>
            {[...ledger].reverse().map((item, index) => {
              const pnl = item.realized_pnl;
              const hasPnl = pnl !== undefined && item.action === 'SELL';
              const pnlColor = hasPnl ? (pnl >= 0 ? 'var(--color-green)' : 'var(--color-red)') : 'inherit';
              const pnlText = hasPnl ? `${pnl >= 0 ? '+' : ''}$${pnl.toFixed(2)}` : '--';

              return (
                <tr 
                  key={index}
                  onClick={() => onRowClick && onRowClick(item)}
                  style={{ cursor: onRowClick ? 'pointer' : 'default' }}
                >
                  <td>{formatTime(item.timestamp)}</td>
                  <td>
                    <span className={`action-badge ${item.action.toLowerCase()}`}>
                      {item.action === 'BUY' ? 'BUY / 买入' : 'SELL / 平仓'}
                    </span>
                  </td>
                  <td style={{ fontWeight: 700 }}>{item.ticker}</td>
                  <td>{item.shares}</td>
                  <td>${item.execution_price.toFixed(2)}</td>
                  <td style={{ color: 'var(--color-text-secondary)', fontSize: '0.85rem' }}>
                    ${item.market_price.toFixed(2)}
                  </td>
                  <td style={{ color: 'var(--color-red)' }}>${item.commission.toFixed(2)}</td>
                  <td style={{ textAlign: 'right', fontWeight: 700, color: pnlColor }}>
                    {pnlText}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      )}
    </div>
  );
};