Skip to main content
Glama

calculateWinRate

Determine win rates and profit metrics for trading accounts by analyzing trades over specified periods, with optional symbol filtering to refine insights.

Instructions

Calculate win rate and profit metrics for a configured account

Input Schema

NameRequiredDescriptionDefault
accountNameYesAccount name defined in the configuration file (e.g., 'bybit_main')
periodNoAnalysis period: '7d', '30d', or 'all'30d
symbolNoOptional trading symbol (e.g., 'BTC/USDT') to filter trades

Input Schema (JSON Schema)

{ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "accountName": { "description": "Account name defined in the configuration file (e.g., 'bybit_main')", "type": "string" }, "period": { "default": "30d", "description": "Analysis period: '7d', '30d', or 'all'", "enum": [ "7d", "30d", "all" ], "type": "string" }, "symbol": { "description": "Optional trading symbol (e.g., 'BTC/USDT') to filter trades", "type": "string" } }, "required": [ "accountName" ], "type": "object" }

Implementation Reference

  • Registration of the 'calculateWinRate' MCP tool using server.tool(), which includes the schema definition and the handler function.
    server.tool( "calculateWinRate", "Calculate win rate and profit metrics for a configured account", { 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(["7d", "30d", "all"]) .default("30d") .describe("Analysis period: '7d', '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 win rate calculation`, }, ], isError: true, }; } // 기간에 따른 since 값 계산 const now = Date.now(); let since; switch (period) { case "7d": since = now - 7 * 24 * 60 * 60 * 1000; // 7일 break; case "30d": since = now - 30 * 24 * 60 * 60 * 1000; // 30일 break; case "all": since = undefined; // 전체 데이터 break; } // 거래 데이터 가져오기 const trades = await exchange.fetchMyTrades(symbol, since, undefined); // 승률 및 수익률 계산 const metrics = calculateWinRateAndProfitMetrics(trades); return { content: [ { type: "text", text: JSON.stringify(metrics, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error calculating win rate for account '${accountName}': ${ (error as Error).message }`, }, ], isError: true, }; } } );
  • The handler function that executes the tool: fetches user's trades via CCXT, calls helper to compute metrics, returns JSON.
    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 win rate calculation`, }, ], isError: true, }; } // 기간에 따른 since 값 계산 const now = Date.now(); let since; switch (period) { case "7d": since = now - 7 * 24 * 60 * 60 * 1000; // 7일 break; case "30d": since = now - 30 * 24 * 60 * 60 * 1000; // 30일 break; case "all": since = undefined; // 전체 데이터 break; } // 거래 데이터 가져오기 const trades = await exchange.fetchMyTrades(symbol, since, undefined); // 승률 및 수익률 계산 const metrics = calculateWinRateAndProfitMetrics(trades); return { content: [ { type: "text", text: JSON.stringify(metrics, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error calculating win rate for account '${accountName}': ${ (error as Error).message }`, }, ], isError: true, }; } } );
  • Input schema using Zod: accountName (required), symbol (optional), period (enum with default).
    { 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(["7d", "30d", "all"]) .default("30d") .describe("Analysis period: '7d', '30d', or 'all'"), },
  • Helper function that implements the core logic: groups trades into positions, computes win rate, profit/loss metrics, expectancy, max consecutive wins/losses.
    function calculateWinRateAndProfitMetrics(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 positions: any[] = []; let currentPosition: any = null; trades.forEach((trade) => { if (!currentPosition) { currentPosition = { symbol: trade.symbol, side: trade.side, entryTime: trade.datetime, entryPrice: trade.price, amount: trade.amount, cost: trade.amount * trade.price, fees: trade.fee?.cost || 0, exitTime: null, exitPrice: null, profit: null, }; } else if (currentPosition.side !== trade.side && currentPosition.symbol === trade.symbol) { // 반대 방향 거래는 포지션 종료로 간주 currentPosition.exitTime = trade.datetime; currentPosition.exitPrice = trade.price; // 손익 계산 (매우 단순화된 버전) if (currentPosition.side === "buy") { // 매수 후 매도 currentPosition.profit = (trade.price - currentPosition.entryPrice) * currentPosition.amount - currentPosition.fees - (trade.fee?.cost || 0); } else { // 매도 후 매수 currentPosition.profit = (currentPosition.entryPrice - trade.price) * currentPosition.amount - currentPosition.fees - (trade.fee?.cost || 0); } positions.push(currentPosition); currentPosition = null; } else { // 같은 방향 거래는 포지션에 추가 (average down/up) const newAmount = currentPosition.amount + trade.amount; const newCost = currentPosition.cost + (trade.amount * trade.price); currentPosition.entryPrice = newCost / newAmount; currentPosition.amount = newAmount; currentPosition.cost = newCost; currentPosition.fees += trade.fee?.cost || 0; } }); // 완료된 포지션만 분석 const completedPositions = positions.filter(p => p.exitTime !== null); if (completedPositions.length === 0) { return { totalTrades: trades.length, completedPositions: 0, message: "No completed positions found for analysis.", }; } // 기본 지표 계산 let winCount = 0; let lossCount = 0; let totalProfit = 0; let totalLoss = 0; let maxConsecutiveWins = 0; let maxConsecutiveLosses = 0; let currentConsecutiveWins = 0; let currentConsecutiveLosses = 0; completedPositions.forEach(position => { if (position.profit > 0) { winCount++; totalProfit += position.profit; currentConsecutiveWins++; currentConsecutiveLosses = 0; maxConsecutiveWins = Math.max(maxConsecutiveWins, currentConsecutiveWins); } else { lossCount++; totalLoss += Math.abs(position.profit); currentConsecutiveLosses++; currentConsecutiveWins = 0; maxConsecutiveLosses = Math.max(maxConsecutiveLosses, currentConsecutiveLosses); } }); const totalPositions = completedPositions.length; const winRate = (winCount / totalPositions) * 100; const averageWin = winCount > 0 ? totalProfit / winCount : 0; const averageLoss = lossCount > 0 ? totalLoss / lossCount : 0; const profitFactor = totalLoss > 0 ? totalProfit / totalLoss : totalProfit > 0 ? Infinity : 0; const expectancy = (winRate / 100 * averageWin) - ((100 - winRate) / 100 * averageLoss); // R-multiple 계산 (평균 수익 / 평균 손실) const rMultiple = averageLoss > 0 ? averageWin / averageLoss : 0; return { totalTrades: trades.length, completedPositions: totalPositions, winCount, lossCount, winRate: winRate.toFixed(2) + "%", profitFactor: profitFactor.toFixed(2), netProfit: (totalProfit - totalLoss).toFixed(8), averageWin: averageWin.toFixed(8), averageLoss: averageLoss.toFixed(8), rMultiple: rMultiple.toFixed(2), expectancy: expectancy.toFixed(8), maxConsecutiveWins, maxConsecutiveLosses, firstTradeDate: completedPositions[0].entryTime, lastTradeDate: completedPositions[completedPositions.length - 1].exitTime, }; }
  • src/server.ts:375-375 (registration)
    Top-level call to registerAnalysisTools which includes the calculateWinRate tool registration.
    registerAnalysisTools(this.server, this);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lazy-dinosaur/ccxt-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server