trading-platform/apps/mcp-mt4-connector/src/tools/positions.ts
rckrdmrd a7cca885f0 feat: Major platform documentation and architecture updates
Changes include:
- Updated architecture documentation
- Enhanced module definitions (OQI-001 to OQI-008)
- ML integration documentation updates
- Trading strategies documentation
- Orchestration and inventory updates
- Docker configuration updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:33:35 -06:00

316 lines
8.0 KiB
TypeScript

/**
* 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<typeof Mt4GetPositionsInputSchema>;
export interface Mt4GetPositionsResult {
success: boolean;
data?: {
positions: MT4Position[];
totalProfit: number;
count: number;
};
error?: string;
}
export async function mt4_get_positions(
params: Mt4GetPositionsInput
): Promise<Mt4GetPositionsResult> {
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<typeof Mt4ClosePositionInputSchema>;
export interface Mt4ClosePositionResult {
success: boolean;
data?: TradeResult;
error?: string;
}
export async function mt4_close_position(
params: Mt4ClosePositionInput
): Promise<Mt4ClosePositionResult> {
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}`,
},
],
};
}