analyze-logs.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. #!/usr/bin/env tsx
  2. /**
  3. * 日志分析工具 - 用于快速复盘和检查
  4. *
  5. * 使用方法:
  6. * tsx scripts/analyze-logs.ts [date]
  7. * 例如: tsx scripts/analyze-logs.ts 2025-10-02
  8. *
  9. * 功能:
  10. * 1. 交易概览(总订单数、成功率、失败原因)
  11. * 2. 账户状态变化(余额、仓位)
  12. * 3. Delta 历史记录
  13. * 4. 保证金警告历史
  14. * 5. 性能指标(周期耗时)
  15. */
  16. import { readFileSync, existsSync, readdirSync } from 'fs';
  17. import { join } from 'path';
  18. interface LogEntry {
  19. timestamp: string;
  20. level: string;
  21. message: string;
  22. [key: string]: any;
  23. }
  24. class LogAnalyzer {
  25. private logs: LogEntry[] = [];
  26. private auditLogs: LogEntry[] = [];
  27. constructor(private date: string) {}
  28. public async analyze(): Promise<void> {
  29. console.log(`\n╔════════════════════════════════════════════════╗`);
  30. console.log(`║ 日志分析报告 - ${this.date} `);
  31. console.log(`╚════════════════════════════════════════════════╝\n`);
  32. // 加载日志文件
  33. this.loadLogs();
  34. if (this.logs.length === 0 && this.auditLogs.length === 0) {
  35. console.log(`❌ 未找到 ${this.date} 的日志文件`);
  36. return;
  37. }
  38. // 各项分析
  39. this.analyzeOverview();
  40. this.analyzeOrders();
  41. this.analyzeBalance();
  42. this.analyzePositions();
  43. this.analyzeDelta();
  44. this.analyzeMarginWarnings();
  45. this.analyzePerformance();
  46. this.analyzeErrors();
  47. }
  48. private loadLogs(): void {
  49. const logsDir = 'logs';
  50. const auditDir = 'logs/audit';
  51. // 主日志
  52. const mainLogFile = join(logsDir, `trading-${this.date}.log`);
  53. if (existsSync(mainLogFile)) {
  54. const content = readFileSync(mainLogFile, 'utf-8');
  55. this.logs = content
  56. .split('\n')
  57. .filter(line => line.trim())
  58. .map(line => {
  59. try {
  60. return JSON.parse(line);
  61. } catch {
  62. return null;
  63. }
  64. })
  65. .filter(Boolean) as LogEntry[];
  66. console.log(`✅ 加载主日志: ${this.logs.length} 条`);
  67. }
  68. // 审计日志
  69. const auditLogFile = join(auditDir, `audit-${this.date}.log`);
  70. if (existsSync(auditLogFile)) {
  71. const content = readFileSync(auditLogFile, 'utf-8');
  72. this.auditLogs = content
  73. .split('\n')
  74. .filter(line => line.trim())
  75. .map(line => {
  76. try {
  77. return JSON.parse(line);
  78. } catch {
  79. return null;
  80. }
  81. })
  82. .filter(Boolean) as LogEntry[];
  83. console.log(`✅ 加载审计日志: ${this.auditLogs.length} 条\n`);
  84. }
  85. }
  86. private analyzeOverview(): void {
  87. console.log(`\n📊 === 交易概览 ===`);
  88. const sessionStarts = this.auditLogs.filter(l => l.message === 'SESSION_START');
  89. const sessionEnds = this.auditLogs.filter(l => l.message === 'SESSION_END');
  90. const cycleStarts = this.auditLogs.filter(l => l.message === 'CYCLE_START');
  91. const cycleEnds = this.auditLogs.filter(l => l.message === 'CYCLE_END');
  92. console.log(` 会话数: ${sessionStarts.length}`);
  93. console.log(` 交易周期数: ${cycleStarts.length}`);
  94. console.log(` 完成周期数: ${cycleEnds.length}`);
  95. if (sessionStarts.length > 0) {
  96. const firstSession = sessionStarts[0];
  97. console.log(` 首次启动: ${firstSession.timestamp}`);
  98. }
  99. if (sessionEnds.length > 0) {
  100. const lastSession = sessionEnds[sessionEnds.length - 1];
  101. console.log(` 最后停止: ${lastSession.timestamp}`);
  102. if (lastSession.totalCycles) {
  103. console.log(` 总周期数: ${lastSession.totalCycles}`);
  104. }
  105. }
  106. }
  107. private analyzeOrders(): void {
  108. console.log(`\n📋 === 订单分析 ===`);
  109. const orderSubmits = this.auditLogs.filter(l => l.message === 'ORDER_SUBMIT');
  110. const orderSuccess = this.auditLogs.filter(l => l.message === 'ORDER_SUCCESS');
  111. const orderFailed = this.auditLogs.filter(l => l.message === 'ORDER_FAILED');
  112. const orderCancelled = this.auditLogs.filter(l => l.message === 'ORDER_CANCELLED');
  113. const orderFilled = this.auditLogs.filter(l => l.message === 'ORDER_FILLED');
  114. console.log(` 提交订单: ${orderSubmits.length}`);
  115. console.log(` 成功订单: ${orderSuccess.length}`);
  116. console.log(` 失败订单: ${orderFailed.length}`);
  117. console.log(` 取消订单: ${orderCancelled.length}`);
  118. console.log(` 成交订单: ${orderFilled.length}`);
  119. if (orderSubmits.length > 0) {
  120. const successRate = ((orderSuccess.length / orderSubmits.length) * 100).toFixed(2);
  121. console.log(` 成功率: ${successRate}%`);
  122. }
  123. // 按账户分组
  124. const ordersByAccount = this.groupBy(orderSubmits, 'accountId');
  125. console.log(`\n 按账户分布:`);
  126. for (const [accountId, orders] of Object.entries(ordersByAccount)) {
  127. console.log(` ${accountId}: ${orders.length} 单`);
  128. }
  129. // 失败原因分析
  130. if (orderFailed.length > 0) {
  131. console.log(`\n ❌ 失败原因分析:`);
  132. const errorGroups = this.groupBy(orderFailed, 'error');
  133. for (const [error, orders] of Object.entries(errorGroups)) {
  134. console.log(` ${error}: ${orders.length} 次`);
  135. }
  136. }
  137. // 取消原因分析
  138. if (orderCancelled.length > 0) {
  139. console.log(`\n 🚫 取消原因分析:`);
  140. const reasonGroups = this.groupBy(orderCancelled, 'reason');
  141. for (const [reason, orders] of Object.entries(reasonGroups)) {
  142. console.log(` ${reason || '超时自动取消'}: ${orders.length} 次`);
  143. }
  144. }
  145. }
  146. private analyzeBalance(): void {
  147. console.log(`\n💰 === 账户余额变化 ===`);
  148. const balanceUpdates = this.auditLogs.filter(l => l.message === 'BALANCE_UPDATE');
  149. if (balanceUpdates.length === 0) {
  150. console.log(` 无余额更新记录`);
  151. return;
  152. }
  153. const byAccount = this.groupBy(balanceUpdates, 'accountId');
  154. for (const [accountId, updates] of Object.entries(byAccount)) {
  155. if (updates.length === 0) continue;
  156. const first = updates[0];
  157. const last = updates[updates.length - 1];
  158. console.log(`\n 账户: ${accountId}`);
  159. console.log(` 起始余额: ${first.total?.toFixed(2) || 'N/A'} (可用: ${first.available?.toFixed(2) || 'N/A'})`);
  160. console.log(` 结束余额: ${last.total?.toFixed(2) || 'N/A'} (可用: ${last.available?.toFixed(2) || 'N/A'})`);
  161. if (first.total && last.total) {
  162. const change = last.total - first.total;
  163. const changePercent = ((change / first.total) * 100).toFixed(2);
  164. const changeStr = change >= 0 ? `+${change.toFixed(2)}` : change.toFixed(2);
  165. console.log(` 变化: ${changeStr} (${changePercent}%)`);
  166. }
  167. console.log(` 更新次数: ${updates.length}`);
  168. }
  169. }
  170. private analyzePositions(): void {
  171. console.log(`\n📈 === 仓位变化 ===`);
  172. const positionUpdates = this.auditLogs.filter(l => l.message === 'POSITION_UPDATE');
  173. if (positionUpdates.length === 0) {
  174. console.log(` 无仓位更新记录`);
  175. return;
  176. }
  177. const byAccount = this.groupBy(positionUpdates, 'accountId');
  178. for (const [accountId, updates] of Object.entries(byAccount)) {
  179. if (updates.length === 0) continue;
  180. const last = updates[updates.length - 1];
  181. console.log(`\n 账户: ${accountId}`);
  182. console.log(` 交易对: ${last.symbol}`);
  183. console.log(` 仓位: ${last.size}`);
  184. console.log(` 入场价: ${last.entryPrice?.toFixed(2) || 'N/A'}`);
  185. console.log(` 标记价: ${last.markPrice?.toFixed(2) || 'N/A'}`);
  186. console.log(` 盈亏: ${last.pnl?.toFixed(2) || 'N/A'}`);
  187. console.log(` 更新次数: ${updates.length}`);
  188. }
  189. }
  190. private analyzeDelta(): void {
  191. console.log(`\n⚖️ === Delta 分析 ===`);
  192. const deltaAssessments = this.auditLogs.filter(l => l.message === 'DELTA_ASSESSMENT');
  193. if (deltaAssessments.length === 0) {
  194. console.log(` 无 Delta 评估记录`);
  195. return;
  196. }
  197. console.log(` 评估次数: ${deltaAssessments.length}`);
  198. // 统计超过容忍度的次数
  199. const exceedCount = deltaAssessments.filter(d => d.exceedsTolerance).length;
  200. console.log(` 超过容忍度: ${exceedCount} 次`);
  201. // Delta 范围
  202. const deltas = deltaAssessments.map(d => d.weightedDelta).filter(Boolean);
  203. if (deltas.length > 0) {
  204. const minDelta = Math.min(...deltas);
  205. const maxDelta = Math.max(...deltas);
  206. const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length;
  207. console.log(` Delta 范围: [${minDelta.toFixed(6)}, ${maxDelta.toFixed(6)}]`);
  208. console.log(` Delta 平均: ${avgDelta.toFixed(6)}`);
  209. }
  210. // 最近的 Delta 评估
  211. const recent = deltaAssessments.slice(-5);
  212. console.log(`\n 最近 5 次评估:`);
  213. recent.forEach((d, i) => {
  214. const exceed = d.exceedsTolerance ? '⚠️ ' : '✅';
  215. console.log(` ${exceed} ${d.timestamp}: Delta=${d.weightedDelta?.toFixed(6)}, ${d.recommendation}`);
  216. });
  217. }
  218. private analyzeMarginWarnings(): void {
  219. console.log(`\n🚨 === 保证金警告 ===`);
  220. const warnings = this.auditLogs.filter(l => l.message === 'MARGIN_WARNING');
  221. const reduceSignals = this.auditLogs.filter(l => l.message === 'REDUCE_SIGNAL');
  222. if (warnings.length === 0 && reduceSignals.length === 0) {
  223. console.log(` ✅ 无保证金警告`);
  224. return;
  225. }
  226. if (warnings.length > 0) {
  227. console.log(` 警告次数: ${warnings.length}`);
  228. const byLevel = this.groupBy(warnings, 'level');
  229. for (const [level, items] of Object.entries(byLevel)) {
  230. console.log(` ${level}: ${items.length} 次`);
  231. }
  232. }
  233. if (reduceSignals.length > 0) {
  234. console.log(`\n 减仓信号: ${reduceSignals.length} 次`);
  235. console.log(`\n 减仓详情:`);
  236. reduceSignals.forEach(signal => {
  237. console.log(` [${signal.timestamp}] ${signal.accountId}`);
  238. console.log(` 等级: ${signal.level}`);
  239. console.log(` 利用率: ${(signal.currentUtilization * 100).toFixed(2)}%`);
  240. console.log(` 减仓比例: ${(signal.reducePercent * 100).toFixed(0)}%`);
  241. console.log(` 取消订单: ${signal.ordersToCancel}`);
  242. console.log(` 平仓数: ${signal.positionsToReduce}`);
  243. });
  244. }
  245. }
  246. private analyzePerformance(): void {
  247. console.log(`\n⚡ === 性能分析 ===`);
  248. const cycleEnds = this.auditLogs.filter(l => l.message === 'CYCLE_END');
  249. if (cycleEnds.length === 0) {
  250. console.log(` 无性能数据`);
  251. return;
  252. }
  253. const durations = cycleEnds.map(c => c.duration).filter(Boolean);
  254. if (durations.length > 0) {
  255. const min = Math.min(...durations);
  256. const max = Math.max(...durations);
  257. const avg = durations.reduce((a, b) => a + b, 0) / durations.length;
  258. console.log(` 周期耗时:`);
  259. console.log(` 最快: ${min}ms`);
  260. console.log(` 最慢: ${max}ms`);
  261. console.log(` 平均: ${avg.toFixed(0)}ms`);
  262. // 检查慢周期
  263. const slowCycles = cycleEnds.filter(c => c.duration > 10000);
  264. if (slowCycles.length > 0) {
  265. console.log(`\n ⚠️ 慢周期 (>10s): ${slowCycles.length} 次`);
  266. }
  267. }
  268. }
  269. private analyzeErrors(): void {
  270. console.log(`\n❌ === 错误分析 ===`);
  271. const errors = this.logs.filter(l => l.level === 'error');
  272. const exceptions = this.logs.filter(l => l.message === 'EXCEPTION');
  273. if (errors.length === 0 && exceptions.length === 0) {
  274. console.log(` ✅ 无错误记录`);
  275. return;
  276. }
  277. console.log(` 错误数: ${errors.length}`);
  278. console.log(` 异常数: ${exceptions.length}`);
  279. if (errors.length > 0) {
  280. console.log(`\n 错误分布:`);
  281. const errorMessages = errors.map(e => e.message).slice(0, 10);
  282. const grouped = errorMessages.reduce((acc, msg) => {
  283. acc[msg] = (acc[msg] || 0) + 1;
  284. return acc;
  285. }, {} as Record<string, number>);
  286. Object.entries(grouped)
  287. .sort((a, b) => b[1] - a[1])
  288. .slice(0, 5)
  289. .forEach(([msg, count]) => {
  290. console.log(` ${msg}: ${count} 次`);
  291. });
  292. }
  293. }
  294. private groupBy<T extends Record<string, any>>(
  295. items: T[],
  296. key: string
  297. ): Record<string, T[]> {
  298. return items.reduce((acc, item) => {
  299. const group = item[key] || 'unknown';
  300. if (!acc[group]) acc[group] = [];
  301. acc[group].push(item);
  302. return acc;
  303. }, {} as Record<string, T[]>);
  304. }
  305. }
  306. // 主函数
  307. async function main() {
  308. const date = process.argv[2] || new Date().toISOString().split('T')[0];
  309. const analyzer = new LogAnalyzer(date);
  310. await analyzer.analyze();
  311. console.log(`\n✅ 分析完成\n`);
  312. }
  313. main().catch(console.error);