analyzeConsecutiveProfitLoss
Analyze consecutive winning and losing trades from cryptocurrency exchange accounts to identify trading patterns and performance trends.
Instructions
Analyze consecutive winning and losing trades
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| accountName | Yes | Account name defined in the configuration file (e.g., 'bybit_main') | |
| symbol | No | Optional trading symbol (e.g., 'BTC/USDT') to filter trades | |
| period | No | Analysis period: '30d' or 'all' | all |
Implementation Reference
- src/tools/analysis-tools.ts:183-258 (registration)Tool registration using server.tool() including name, description, input schema, and inline handler function for analyzeConsecutiveProfitLoss.server.tool( "analyzeConsecutiveProfitLoss", "Analyze consecutive winning and losing trades", { accountName: z .string() .describe( "Account name defined in the configuration file (e.g., 'bybit_main')" ), symbol: z .string() .optional() .describe("Optional trading symbol (e.g., 'BTC/USDT') to filter trades"), period: z .enum(["30d", "all"]) .default("all") .describe("Analysis period: '30d' or 'all'"), }, async ({ accountName, symbol, period }) => { try { const exchange = ccxtServer.getExchangeInstance(accountName); // fetchMyTrades 메서드가 지원되는지 확인 if (!exchange.has["fetchMyTrades"]) { return { content: [ { type: "text", text: `Account '${accountName}' (Exchange: ${exchange.id}) does not support fetching personal trades for consecutive analysis`, }, ], isError: true, }; } // 기간에 따른 since 값 계산 const now = Date.now(); let since; switch (period) { case "30d": since = now - 30 * 24 * 60 * 60 * 1000; // 30일 break; case "all": default: since = undefined; // 전체 데이터 break; } // 거래 내역 가져오기 const trades = await exchange.fetchMyTrades(symbol, since, undefined); // 최대 연속 손실/이익 계산 const analysis = analyzeConsecutiveTrades(trades); return { content: [ { type: "text", text: JSON.stringify(analysis, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error analyzing consecutive trades for account '${accountName}': ${ (error as Error).message }`, }, ], isError: true, }; } }
- src/tools/analysis-tools.ts:201-257 (handler)Handler function fetches trades from the CCXT exchange for the given account and period, then calls the analyzeConsecutiveTrades helper to perform the analysis.async ({ accountName, symbol, period }) => { try { const exchange = ccxtServer.getExchangeInstance(accountName); // fetchMyTrades 메서드가 지원되는지 확인 if (!exchange.has["fetchMyTrades"]) { return { content: [ { type: "text", text: `Account '${accountName}' (Exchange: ${exchange.id}) does not support fetching personal trades for consecutive analysis`, }, ], isError: true, }; } // 기간에 따른 since 값 계산 const now = Date.now(); let since; switch (period) { case "30d": since = now - 30 * 24 * 60 * 60 * 1000; // 30일 break; case "all": default: since = undefined; // 전체 데이터 break; } // 거래 내역 가져오기 const trades = await exchange.fetchMyTrades(symbol, since, undefined); // 최대 연속 손실/이익 계산 const analysis = analyzeConsecutiveTrades(trades); return { content: [ { type: "text", text: JSON.stringify(analysis, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error analyzing consecutive trades for account '${accountName}': ${ (error as Error).message }`, }, ], isError: true, }; }
- src/tools/analysis-tools.ts:187-199 (schema)Zod input schema defining parameters: accountName (string), symbol (optional string), period (enum ["30d", "all"], default "all").accountName: z .string() .describe( "Account name defined in the configuration file (e.g., 'bybit_main')" ), symbol: z .string() .optional() .describe("Optional trading symbol (e.g., 'BTC/USDT') to filter trades"), period: z .enum(["30d", "all"]) .default("all") .describe("Analysis period: '30d' or 'all'"),
- src/tools/analysis-tools.ts:562-676 (helper)Core helper function analyzeConsecutiveTrades that implements the logic: pairs trades to determine win/loss outcomes, calculates maximum consecutive winning/losing streaks including start/end dates.function analyzeConsecutiveTrades(trades: any[]) { if (!trades || trades.length === 0) { return { totalTrades: 0, message: "No trades found for the specified period.", }; } // 거래를 시간순으로 정렬 trades.sort((a, b) => a.timestamp - b.timestamp); // 간단한 분석을 위해 거래를 승/패로 변환 (단순화된 버전) const tradeResults: boolean[] = []; let currentSide = null; let entryPrice = 0; trades.forEach((trade) => { if (currentSide === null) { // 첫 거래는 진입으로 간주 currentSide = trade.side; entryPrice = trade.price; } else if (currentSide !== trade.side) { // 반대 방향 거래는 포지션 종료로 간주 const isWin = (currentSide === 'buy' && trade.price > entryPrice) || (currentSide === 'sell' && trade.price < entryPrice); tradeResults.push(isWin); // 새로운 포지션 시작 currentSide = trade.side; entryPrice = trade.price; } }); // 연속 승/패 분석 let maxConsecutiveWins = 0; let maxConsecutiveLosses = 0; let currentConsecutiveWins = 0; let currentConsecutiveLosses = 0; let currentWinStreak = 0; let currentLossStreak = 0; let maxWinStreak = { count: 0, startIndex: 0, endIndex: 0 }; let maxLossStreak = { count: 0, startIndex: 0, endIndex: 0 }; tradeResults.forEach((isWin, index) => { if (isWin) { currentConsecutiveWins++; currentConsecutiveLosses = 0; if (currentWinStreak === 0) { currentWinStreak = 1; currentLossStreak = 0; } else { currentWinStreak++; } if (currentWinStreak > maxWinStreak.count) { maxWinStreak = { count: currentWinStreak, startIndex: index - currentWinStreak + 1, endIndex: index }; } } else { currentConsecutiveLosses++; currentConsecutiveWins = 0; if (currentLossStreak === 0) { currentLossStreak = 1; currentWinStreak = 0; } else { currentLossStreak++; } if (currentLossStreak > maxLossStreak.count) { maxLossStreak = { count: currentLossStreak, startIndex: index - currentLossStreak + 1, endIndex: index }; } } maxConsecutiveWins = Math.max(maxConsecutiveWins, currentConsecutiveWins); maxConsecutiveLosses = Math.max(maxConsecutiveLosses, currentConsecutiveLosses); }); // 최대 연속 승/패 시 총 손익 계산 const maxWinStreakTrades = maxWinStreak.count > 0 ? tradeResults.slice(maxWinStreak.startIndex, maxWinStreak.endIndex + 1) : []; const maxLossStreakTrades = maxLossStreak.count > 0 ? tradeResults.slice(maxLossStreak.startIndex, maxLossStreak.endIndex + 1) : []; return { totalCompletedTrades: tradeResults.length, winCount: tradeResults.filter(result => result).length, lossCount: tradeResults.filter(result => !result).length, maxConsecutiveWins, maxConsecutiveLosses, maxWinStreak: { count: maxWinStreak.count, startDate: maxWinStreak.count > 0 ? trades[maxWinStreak.startIndex].datetime : null, endDate: maxWinStreak.count > 0 ? trades[maxWinStreak.endIndex].datetime : null, }, maxLossStreak: { count: maxLossStreak.count, startDate: maxLossStreak.count > 0 ? trades[maxLossStreak.startIndex].datetime : null, endDate: maxLossStreak.count > 0 ? trades[maxLossStreak.endIndex].datetime : null, }, currentStreakType: currentConsecutiveWins > 0 ? "win" : "loss", currentStreakCount: Math.max(currentConsecutiveWins, currentConsecutiveLosses), }; }