/** * MT4 Position Tools * * - mt4_get_positions: List all open positions * - mt4_close_position: Close a specific position */ import { z } from 'zod'; import { getMT4Client, MT4Position, TradeResult } from '../services/mt4-client'; // ========================================== // mt4_get_positions // ========================================== /** * mt4_get_positions - List all open positions * * @description Retrieves all currently open positions from MT4 terminal. * Can optionally filter by symbol. * * @param symbol - Optional symbol to filter positions (e.g., "XAUUSD") * @returns Array of open positions with details * * @example * const result = await mt4_get_positions({}); * // Returns all positions * * const result = await mt4_get_positions({ symbol: "XAUUSD" }); * // Returns only XAUUSD positions */ export const mt4GetPositionsSchema = { name: 'mt4_get_positions', description: 'List all open trading positions from MT4. Optionally filter by symbol.', inputSchema: { type: 'object' as const, properties: { symbol: { type: 'string', description: 'Optional: Filter positions by symbol (e.g., XAUUSD, EURUSD)', }, }, required: [] as string[], }, }; export const Mt4GetPositionsInputSchema = z.object({ symbol: z.string().optional(), }); export type Mt4GetPositionsInput = z.infer; export interface Mt4GetPositionsResult { success: boolean; data?: { positions: MT4Position[]; totalProfit: number; count: number; }; error?: string; } export async function mt4_get_positions( params: Mt4GetPositionsInput ): Promise { try { const client = getMT4Client(); // Check connection const isConnected = await client.isConnected(); if (!isConnected) { return { success: false, error: 'MT4 terminal is not connected', }; } // Get all positions let positions = await client.getPositions(); // Filter by symbol if specified if (params.symbol) { positions = positions.filter( p => p.symbol.toUpperCase() === params.symbol!.toUpperCase() ); } // Calculate total profit const totalProfit = positions.reduce((sum, p) => sum + p.profit, 0); return { success: true, data: { positions, totalProfit, count: positions.length, }, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }; } } export async function handleMt4GetPositions( params: unknown ): Promise<{ content: Array<{ type: string; text: string }> }> { const validatedParams = Mt4GetPositionsInputSchema.parse(params); const result = await mt4_get_positions(validatedParams); if (result.success && result.data) { if (result.data.count === 0) { return { content: [ { type: 'text', text: params && (params as Mt4GetPositionsInput).symbol ? `No open positions found for ${(params as Mt4GetPositionsInput).symbol}` : 'No open positions found', }, ], }; } const positionLines = result.data.positions.map(p => { const direction = p.type.toUpperCase(); const profitSign = p.profit >= 0 ? '+' : ''; const slInfo = p.stopLoss !== null ? `SL: ${p.stopLoss}` : 'SL: None'; const tpInfo = p.takeProfit !== null ? `TP: ${p.takeProfit}` : 'TP: None'; return ` #${p.ticket} | ${p.symbol} | ${direction} ${p.lots} lots Open: ${p.openPrice} | Current: ${p.currentPrice} ${slInfo} | ${tpInfo} P/L: ${profitSign}${p.profit.toFixed(2)} | Swap: ${p.swap.toFixed(2)} Opened: ${p.openTime} Magic: ${p.magic} | Comment: ${p.comment || 'None'}`; }); const formattedOutput = ` Open Positions (${result.data.count}) ${'='.repeat(30)} ${positionLines.join('\n---\n')} Total P/L: ${result.data.totalProfit >= 0 ? '+' : ''}${result.data.totalProfit.toFixed(2)} `.trim(); return { content: [ { type: 'text', text: formattedOutput, }, ], }; } return { content: [ { type: 'text', text: `Error: ${result.error}`, }, ], }; } // ========================================== // mt4_close_position // ========================================== /** * mt4_close_position - Close a trading position * * @description Closes an open position by ticket number. * Can optionally close partial volume. * * @param ticket - Position ticket number to close * @param lots - Optional: Partial volume to close (default: close all) * @param slippage - Optional: Maximum slippage in points (default: 3) * @returns Trade result with success status * * @example * // Close entire position * const result = await mt4_close_position({ ticket: 123456 }); * * // Close partial position * const result = await mt4_close_position({ ticket: 123456, lots: 0.5 }); */ export const mt4ClosePositionSchema = { name: 'mt4_close_position', description: 'Close an open trading position by ticket number. Can close partially.', inputSchema: { type: 'object' as const, properties: { ticket: { type: 'number', description: 'Position ticket number to close', }, lots: { type: 'number', description: 'Optional: Partial volume to close. If not specified, closes entire position.', }, slippage: { type: 'number', description: 'Optional: Maximum slippage in points (default: 3)', }, }, required: ['ticket'] as string[], }, }; export const Mt4ClosePositionInputSchema = z.object({ ticket: z.number().int().positive(), lots: z.number().positive().optional(), slippage: z.number().int().min(0).max(100).optional(), }); export type Mt4ClosePositionInput = z.infer; export interface Mt4ClosePositionResult { success: boolean; data?: TradeResult; error?: string; } export async function mt4_close_position( params: Mt4ClosePositionInput ): Promise { try { const client = getMT4Client(); // Check connection const isConnected = await client.isConnected(); if (!isConnected) { return { success: false, error: 'MT4 terminal is not connected', }; } // Verify position exists const position = await client.getPosition(params.ticket); if (!position) { return { success: false, error: `Position with ticket ${params.ticket} not found`, }; } // Validate lots if specified if (params.lots !== undefined && params.lots > position.lots) { return { success: false, error: `Requested lots (${params.lots}) exceeds position size (${position.lots})`, }; } // Close position const result = await client.closePosition({ ticket: params.ticket, lots: params.lots, slippage: params.slippage, }); return { success: result.success, data: result, error: result.success ? undefined : result.message, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }; } } export async function handleMt4ClosePosition( params: unknown ): Promise<{ content: Array<{ type: string; text: string }> }> { const validatedParams = Mt4ClosePositionInputSchema.parse(params); const result = await mt4_close_position(validatedParams); if (result.success && result.data) { const formattedOutput = ` Position Closed Successfully ============================ Ticket: ${validatedParams.ticket} ${validatedParams.lots ? `Closed Volume: ${validatedParams.lots} lots` : 'Closed: Entire position'} Message: ${result.data.message || 'Position closed'} `.trim(); return { content: [ { type: 'text', text: formattedOutput, }, ], }; } return { content: [ { type: 'text', text: `Error closing position: ${result.error}`, }, ], }; }