| |
|
|
| 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) => { |
| |
| 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> |
| ); |
| }; |
|
|