#!/usr/bin/env tsx /** * 日志分析工具 - 用于快速复盘和检查 * * 使用方法: * tsx scripts/analyze-logs.ts [date] * 例如: tsx scripts/analyze-logs.ts 2025-10-02 * * 功能: * 1. 交易概览(总订单数、成功率、失败原因) * 2. 账户状态变化(余额、仓位) * 3. Delta 历史记录 * 4. 保证金警告历史 * 5. 性能指标(周期耗时) */ import { readFileSync, existsSync, readdirSync } from 'fs'; import { join } from 'path'; interface LogEntry { timestamp: string; level: string; message: string; [key: string]: any; } class LogAnalyzer { private logs: LogEntry[] = []; private auditLogs: LogEntry[] = []; constructor(private date: string) {} public async analyze(): Promise { console.log(`\n╔════════════════════════════════════════════════╗`); console.log(`║ 日志分析报告 - ${this.date} `); console.log(`╚════════════════════════════════════════════════╝\n`); // 加载日志文件 this.loadLogs(); if (this.logs.length === 0 && this.auditLogs.length === 0) { console.log(`❌ 未找到 ${this.date} 的日志文件`); return; } // 各项分析 this.analyzeOverview(); this.analyzeOrders(); this.analyzeBalance(); this.analyzePositions(); this.analyzeDelta(); this.analyzeMarginWarnings(); this.analyzePerformance(); this.analyzeErrors(); } private loadLogs(): void { const logsDir = 'logs'; const auditDir = 'logs/audit'; // 主日志 const mainLogFile = join(logsDir, `trading-${this.date}.log`); if (existsSync(mainLogFile)) { const content = readFileSync(mainLogFile, 'utf-8'); this.logs = content .split('\n') .filter(line => line.trim()) .map(line => { try { return JSON.parse(line); } catch { return null; } }) .filter(Boolean) as LogEntry[]; console.log(`✅ 加载主日志: ${this.logs.length} 条`); } // 审计日志 const auditLogFile = join(auditDir, `audit-${this.date}.log`); if (existsSync(auditLogFile)) { const content = readFileSync(auditLogFile, 'utf-8'); this.auditLogs = content .split('\n') .filter(line => line.trim()) .map(line => { try { return JSON.parse(line); } catch { return null; } }) .filter(Boolean) as LogEntry[]; console.log(`✅ 加载审计日志: ${this.auditLogs.length} 条\n`); } } private analyzeOverview(): void { console.log(`\n📊 === 交易概览 ===`); const sessionStarts = this.auditLogs.filter(l => l.message === 'SESSION_START'); const sessionEnds = this.auditLogs.filter(l => l.message === 'SESSION_END'); const cycleStarts = this.auditLogs.filter(l => l.message === 'CYCLE_START'); const cycleEnds = this.auditLogs.filter(l => l.message === 'CYCLE_END'); console.log(` 会话数: ${sessionStarts.length}`); console.log(` 交易周期数: ${cycleStarts.length}`); console.log(` 完成周期数: ${cycleEnds.length}`); if (sessionStarts.length > 0) { const firstSession = sessionStarts[0]; console.log(` 首次启动: ${firstSession.timestamp}`); } if (sessionEnds.length > 0) { const lastSession = sessionEnds[sessionEnds.length - 1]; console.log(` 最后停止: ${lastSession.timestamp}`); if (lastSession.totalCycles) { console.log(` 总周期数: ${lastSession.totalCycles}`); } } } private analyzeOrders(): void { console.log(`\n📋 === 订单分析 ===`); const orderSubmits = this.auditLogs.filter(l => l.message === 'ORDER_SUBMIT'); const orderSuccess = this.auditLogs.filter(l => l.message === 'ORDER_SUCCESS'); const orderFailed = this.auditLogs.filter(l => l.message === 'ORDER_FAILED'); const orderCancelled = this.auditLogs.filter(l => l.message === 'ORDER_CANCELLED'); const orderFilled = this.auditLogs.filter(l => l.message === 'ORDER_FILLED'); console.log(` 提交订单: ${orderSubmits.length}`); console.log(` 成功订单: ${orderSuccess.length}`); console.log(` 失败订单: ${orderFailed.length}`); console.log(` 取消订单: ${orderCancelled.length}`); console.log(` 成交订单: ${orderFilled.length}`); if (orderSubmits.length > 0) { const successRate = ((orderSuccess.length / orderSubmits.length) * 100).toFixed(2); console.log(` 成功率: ${successRate}%`); } // 按账户分组 const ordersByAccount = this.groupBy(orderSubmits, 'accountId'); console.log(`\n 按账户分布:`); for (const [accountId, orders] of Object.entries(ordersByAccount)) { console.log(` ${accountId}: ${orders.length} 单`); } // 失败原因分析 if (orderFailed.length > 0) { console.log(`\n ❌ 失败原因分析:`); const errorGroups = this.groupBy(orderFailed, 'error'); for (const [error, orders] of Object.entries(errorGroups)) { console.log(` ${error}: ${orders.length} 次`); } } // 取消原因分析 if (orderCancelled.length > 0) { console.log(`\n 🚫 取消原因分析:`); const reasonGroups = this.groupBy(orderCancelled, 'reason'); for (const [reason, orders] of Object.entries(reasonGroups)) { console.log(` ${reason || '超时自动取消'}: ${orders.length} 次`); } } } private analyzeBalance(): void { console.log(`\n💰 === 账户余额变化 ===`); const balanceUpdates = this.auditLogs.filter(l => l.message === 'BALANCE_UPDATE'); if (balanceUpdates.length === 0) { console.log(` 无余额更新记录`); return; } const byAccount = this.groupBy(balanceUpdates, 'accountId'); for (const [accountId, updates] of Object.entries(byAccount)) { if (updates.length === 0) continue; const first = updates[0]; const last = updates[updates.length - 1]; console.log(`\n 账户: ${accountId}`); console.log(` 起始余额: ${first.total?.toFixed(2) || 'N/A'} (可用: ${first.available?.toFixed(2) || 'N/A'})`); console.log(` 结束余额: ${last.total?.toFixed(2) || 'N/A'} (可用: ${last.available?.toFixed(2) || 'N/A'})`); if (first.total && last.total) { const change = last.total - first.total; const changePercent = ((change / first.total) * 100).toFixed(2); const changeStr = change >= 0 ? `+${change.toFixed(2)}` : change.toFixed(2); console.log(` 变化: ${changeStr} (${changePercent}%)`); } console.log(` 更新次数: ${updates.length}`); } } private analyzePositions(): void { console.log(`\n📈 === 仓位变化 ===`); const positionUpdates = this.auditLogs.filter(l => l.message === 'POSITION_UPDATE'); if (positionUpdates.length === 0) { console.log(` 无仓位更新记录`); return; } const byAccount = this.groupBy(positionUpdates, 'accountId'); for (const [accountId, updates] of Object.entries(byAccount)) { if (updates.length === 0) continue; const last = updates[updates.length - 1]; console.log(`\n 账户: ${accountId}`); console.log(` 交易对: ${last.symbol}`); console.log(` 仓位: ${last.size}`); console.log(` 入场价: ${last.entryPrice?.toFixed(2) || 'N/A'}`); console.log(` 标记价: ${last.markPrice?.toFixed(2) || 'N/A'}`); console.log(` 盈亏: ${last.pnl?.toFixed(2) || 'N/A'}`); console.log(` 更新次数: ${updates.length}`); } } private analyzeDelta(): void { console.log(`\n⚖️ === Delta 分析 ===`); const deltaAssessments = this.auditLogs.filter(l => l.message === 'DELTA_ASSESSMENT'); if (deltaAssessments.length === 0) { console.log(` 无 Delta 评估记录`); return; } console.log(` 评估次数: ${deltaAssessments.length}`); // 统计超过容忍度的次数 const exceedCount = deltaAssessments.filter(d => d.exceedsTolerance).length; console.log(` 超过容忍度: ${exceedCount} 次`); // Delta 范围 const deltas = deltaAssessments.map(d => d.weightedDelta).filter(Boolean); if (deltas.length > 0) { const minDelta = Math.min(...deltas); const maxDelta = Math.max(...deltas); const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length; console.log(` Delta 范围: [${minDelta.toFixed(6)}, ${maxDelta.toFixed(6)}]`); console.log(` Delta 平均: ${avgDelta.toFixed(6)}`); } // 最近的 Delta 评估 const recent = deltaAssessments.slice(-5); console.log(`\n 最近 5 次评估:`); recent.forEach((d, i) => { const exceed = d.exceedsTolerance ? '⚠️ ' : '✅'; console.log(` ${exceed} ${d.timestamp}: Delta=${d.weightedDelta?.toFixed(6)}, ${d.recommendation}`); }); } private analyzeMarginWarnings(): void { console.log(`\n🚨 === 保证金警告 ===`); const warnings = this.auditLogs.filter(l => l.message === 'MARGIN_WARNING'); const reduceSignals = this.auditLogs.filter(l => l.message === 'REDUCE_SIGNAL'); if (warnings.length === 0 && reduceSignals.length === 0) { console.log(` ✅ 无保证金警告`); return; } if (warnings.length > 0) { console.log(` 警告次数: ${warnings.length}`); const byLevel = this.groupBy(warnings, 'level'); for (const [level, items] of Object.entries(byLevel)) { console.log(` ${level}: ${items.length} 次`); } } if (reduceSignals.length > 0) { console.log(`\n 减仓信号: ${reduceSignals.length} 次`); console.log(`\n 减仓详情:`); reduceSignals.forEach(signal => { console.log(` [${signal.timestamp}] ${signal.accountId}`); console.log(` 等级: ${signal.level}`); console.log(` 利用率: ${(signal.currentUtilization * 100).toFixed(2)}%`); console.log(` 减仓比例: ${(signal.reducePercent * 100).toFixed(0)}%`); console.log(` 取消订单: ${signal.ordersToCancel}`); console.log(` 平仓数: ${signal.positionsToReduce}`); }); } } private analyzePerformance(): void { console.log(`\n⚡ === 性能分析 ===`); const cycleEnds = this.auditLogs.filter(l => l.message === 'CYCLE_END'); if (cycleEnds.length === 0) { console.log(` 无性能数据`); return; } const durations = cycleEnds.map(c => c.duration).filter(Boolean); if (durations.length > 0) { const min = Math.min(...durations); const max = Math.max(...durations); const avg = durations.reduce((a, b) => a + b, 0) / durations.length; console.log(` 周期耗时:`); console.log(` 最快: ${min}ms`); console.log(` 最慢: ${max}ms`); console.log(` 平均: ${avg.toFixed(0)}ms`); // 检查慢周期 const slowCycles = cycleEnds.filter(c => c.duration > 10000); if (slowCycles.length > 0) { console.log(`\n ⚠️ 慢周期 (>10s): ${slowCycles.length} 次`); } } } private analyzeErrors(): void { console.log(`\n❌ === 错误分析 ===`); const errors = this.logs.filter(l => l.level === 'error'); const exceptions = this.logs.filter(l => l.message === 'EXCEPTION'); if (errors.length === 0 && exceptions.length === 0) { console.log(` ✅ 无错误记录`); return; } console.log(` 错误数: ${errors.length}`); console.log(` 异常数: ${exceptions.length}`); if (errors.length > 0) { console.log(`\n 错误分布:`); const errorMessages = errors.map(e => e.message).slice(0, 10); const grouped = errorMessages.reduce((acc, msg) => { acc[msg] = (acc[msg] || 0) + 1; return acc; }, {} as Record); Object.entries(grouped) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .forEach(([msg, count]) => { console.log(` ${msg}: ${count} 次`); }); } } private groupBy>( items: T[], key: string ): Record { return items.reduce((acc, item) => { const group = item[key] || 'unknown'; if (!acc[group]) acc[group] = []; acc[group].push(item); return acc; }, {} as Record); } } // 主函数 async function main() { const date = process.argv[2] || new Date().toISOString().split('T')[0]; const analyzer = new LogAnalyzer(date); await analyzer.analyze(); console.log(`\n✅ 分析完成\n`); } main().catch(console.error);