/** * MT4 Trading Tools * * - mt4_execute_trade: Execute BUY/SELL market orders * - mt4_modify_position: Modify SL/TP of existing positions */ import { z } from 'zod'; import { getMT4Client, TradeResult } from '../services/mt4-client'; // ========================================== // mt4_execute_trade // ========================================== /** * mt4_execute_trade - Execute a market order (BUY or SELL) * * @description Opens a new trading position with optional SL/TP levels. * Supports market orders only (pending orders not implemented). * * @param symbol - Trading symbol (e.g., "XAUUSD", "EURUSD") * @param action - Trade direction: "buy" or "sell" * @param lots - Volume in lots (e.g., 0.01, 0.1, 1.0) * @param stopLoss - Optional stop loss price * @param takeProfit - Optional take profit price * @param slippage - Optional max slippage in points (default: 3) * @param magic - Optional magic number for EA identification * @param comment - Optional order comment * @returns Trade result with ticket number * * @example * // Simple buy order * const result = await mt4_execute_trade({ * symbol: "XAUUSD", * action: "buy", * lots: 0.1 * }); * * // Buy with SL/TP * const result = await mt4_execute_trade({ * symbol: "XAUUSD", * action: "buy", * lots: 0.1, * stopLoss: 2640.00, * takeProfit: 2680.00, * comment: "AI Signal" * }); */ export const mt4ExecuteTradeSchema = { name: 'mt4_execute_trade', description: 'Execute a market order (BUY or SELL) on MT4 with optional stop loss and take profit', inputSchema: { type: 'object' as const, properties: { symbol: { type: 'string', description: 'Trading symbol (e.g., XAUUSD, EURUSD, GBPUSD)', }, action: { type: 'string', enum: ['buy', 'sell'], description: 'Trade direction: buy or sell', }, lots: { type: 'number', description: 'Volume in lots (e.g., 0.01, 0.1, 1.0)', }, stopLoss: { type: 'number', description: 'Optional: Stop loss price level', }, takeProfit: { type: 'number', description: 'Optional: Take profit price level', }, slippage: { type: 'number', description: 'Optional: Maximum slippage in points (default: 3)', }, magic: { type: 'number', description: 'Optional: Magic number for EA identification (default: 12345)', }, comment: { type: 'string', description: 'Optional: Order comment (max 31 chars)', }, }, required: ['symbol', 'action', 'lots'] as string[], }, }; export const Mt4ExecuteTradeInputSchema = z.object({ symbol: z.string().min(1).max(20), action: z.enum(['buy', 'sell']), lots: z.number().positive().max(100), stopLoss: z.number().positive().optional(), takeProfit: z.number().positive().optional(), slippage: z.number().int().min(0).max(100).optional(), magic: z.number().int().optional(), comment: z.string().max(31).optional(), }); export type Mt4ExecuteTradeInput = z.infer; export interface Mt4ExecuteTradeResult { success: boolean; data?: TradeResult & { symbol: string; action: string; lots: number; }; error?: string; } export async function mt4_execute_trade( params: Mt4ExecuteTradeInput ): Promise { try { const client = getMT4Client(); // Check connection const isConnected = await client.isConnected(); if (!isConnected) { return { success: false, error: 'MT4 terminal is not connected', }; } // Validate SL/TP logic (basic check) if (params.stopLoss !== undefined && params.takeProfit !== undefined) { if (params.action === 'buy') { // For buy: SL should be below current price, TP above if (params.stopLoss >= params.takeProfit) { return { success: false, error: 'For BUY orders, stop loss must be below take profit', }; } } else { // For sell: SL should be above current price, TP below if (params.stopLoss <= params.takeProfit) { return { success: false, error: 'For SELL orders, stop loss must be above take profit', }; } } } // Execute trade const result = await client.executeTrade({ symbol: params.symbol.toUpperCase(), action: params.action, lots: params.lots, stopLoss: params.stopLoss, takeProfit: params.takeProfit, slippage: params.slippage, magic: params.magic, comment: params.comment, }); if (result.success) { return { success: true, data: { ...result, symbol: params.symbol.toUpperCase(), action: params.action, lots: params.lots, }, }; } return { success: false, error: result.message || 'Trade execution failed', }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }; } } export async function handleMt4ExecuteTrade( params: unknown ): Promise<{ content: Array<{ type: string; text: string }> }> { const validatedParams = Mt4ExecuteTradeInputSchema.parse(params); const result = await mt4_execute_trade(validatedParams); if (result.success && result.data) { const formattedOutput = ` Trade Executed Successfully =========================== Ticket: #${result.data.ticket} Symbol: ${result.data.symbol} Direction: ${result.data.action.toUpperCase()} Volume: ${result.data.lots} lots ${validatedParams.stopLoss ? `Stop Loss: ${validatedParams.stopLoss}` : 'Stop Loss: Not set'} ${validatedParams.takeProfit ? `Take Profit: ${validatedParams.takeProfit}` : 'Take Profit: Not set'} Message: ${result.data.message || 'Order placed successfully'} `.trim(); return { content: [ { type: 'text', text: formattedOutput, }, ], }; } return { content: [ { type: 'text', text: `Error executing trade: ${result.error}`, }, ], }; } // ========================================== // mt4_modify_position // ========================================== /** * mt4_modify_position - Modify SL/TP of an existing position * * @description Updates the stop loss and/or take profit levels of an open position. * * @param ticket - Position ticket number to modify * @param stopLoss - New stop loss price (null to remove) * @param takeProfit - New take profit price (null to remove) * @returns Modification result * * @example * // Set both SL and TP * const result = await mt4_modify_position({ * ticket: 123456, * stopLoss: 2640.00, * takeProfit: 2680.00 * }); * * // Update only TP * const result = await mt4_modify_position({ * ticket: 123456, * takeProfit: 2700.00 * }); */ export const mt4ModifyPositionSchema = { name: 'mt4_modify_position', description: 'Modify stop loss and/or take profit of an existing position', inputSchema: { type: 'object' as const, properties: { ticket: { type: 'number', description: 'Position ticket number to modify', }, stopLoss: { type: 'number', description: 'New stop loss price level (optional)', }, takeProfit: { type: 'number', description: 'New take profit price level (optional)', }, }, required: ['ticket'] as string[], }, }; export const Mt4ModifyPositionInputSchema = z.object({ ticket: z.number().int().positive(), stopLoss: z.number().positive().optional(), takeProfit: z.number().positive().optional(), }); export type Mt4ModifyPositionInput = z.infer; export interface Mt4ModifyPositionResult { success: boolean; data?: TradeResult; error?: string; } export async function mt4_modify_position( params: Mt4ModifyPositionInput ): Promise { try { const client = getMT4Client(); // Check connection const isConnected = await client.isConnected(); if (!isConnected) { return { success: false, error: 'MT4 terminal is not connected', }; } // Validate at least one parameter is provided if (params.stopLoss === undefined && params.takeProfit === undefined) { return { success: false, error: 'At least one of stopLoss or takeProfit must be provided', }; } // Verify position exists const position = await client.getPosition(params.ticket); if (!position) { return { success: false, error: `Position with ticket ${params.ticket} not found`, }; } // Validate SL/TP based on position type const sl = params.stopLoss; const tp = params.takeProfit; if (sl !== undefined && tp !== undefined) { if (position.type === 'buy') { if (sl >= tp) { return { success: false, error: 'For BUY positions, stop loss must be below take profit', }; } } else { if (sl <= tp) { return { success: false, error: 'For SELL positions, stop loss must be above take profit', }; } } } // Modify position const result = await client.modifyPosition({ ticket: params.ticket, stopLoss: params.stopLoss, takeProfit: params.takeProfit, }); 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 handleMt4ModifyPosition( params: unknown ): Promise<{ content: Array<{ type: string; text: string }> }> { const validatedParams = Mt4ModifyPositionInputSchema.parse(params); const result = await mt4_modify_position(validatedParams); if (result.success && result.data) { const formattedOutput = ` Position Modified Successfully ============================== Ticket: #${validatedParams.ticket} ${validatedParams.stopLoss !== undefined ? `New Stop Loss: ${validatedParams.stopLoss}` : 'Stop Loss: Unchanged'} ${validatedParams.takeProfit !== undefined ? `New Take Profit: ${validatedParams.takeProfit}` : 'Take Profit: Unchanged'} Message: ${result.data.message || 'Position modified successfully'} `.trim(); return { content: [ { type: 'text', text: formattedOutput, }, ], }; } return { content: [ { type: 'text', text: `Error modifying position: ${result.error}`, }, ], }; }