Skip to main content
Glama

MCP Trader Server

by Af7007
client.py46.9 kB
#!/usr/bin/env python3 """Trading Chatbot Client - Integrates Ollama MCP and MT5 MCP""" import asyncio import json import logging import uuid from datetime import datetime from typing import Any, Dict, List, Optional import aiohttp from pydantic import BaseModel, Field import requests logger = logging.getLogger(__name__) class OllamaClient: """Client for Ollama API""" def __init__(self, base_url: str = "http://localhost:11434"): self.base_url = base_url self.session = None # Will be initialized in async context async def __aenter__(self): self.session = aiohttp.ClientSession() return self async def __aexit__(self, exc_type, exc_val, exc_tb): if self.session: await self.session.close() async def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]: """Make HTTP request to Ollama""" if not self.session: raise RuntimeError("Client not initialized. Use 'async with' context manager.") url = f"{self.base_url}/{endpoint.lstrip('/')}" async with self.session.request(method, url, **kwargs) as response: if response.status == 200: return await response.json() else: error_text = await response.text() raise Exception(f"Ollama API error {response.status}: {error_text}") class ChatbotConfig(BaseModel): """Chatbot configuration""" ollama_host: str = Field(default="http://localhost:8001", description="Ollama MCP server URL") mt5_host: str = Field(default="http://localhost:8000", description="MT5 MCP server URL") chat_model: str = Field(default="qwen2.5-coder", description="Default chat model") conversation_timeout: int = Field(default=300, description="Conversation timeout in seconds") max_conversation_length: int = Field(default=50, description="Maximum conversation history length") class ChatMessage(BaseModel): """Chat message structure""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) role: str = Field(..., description="Role: user, assistant, system") content: str = Field(..., description="Message content") timestamp: datetime = Field(default_factory=datetime.now) metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata") class ParsedTradingIntent(BaseModel): """Parsed trading intent from user message""" intent: str = Field(..., description="Intent type: trade|info|analysis|other") action: Optional[str] = Field(None, description="Trading action: buy|sell|check|cancel|modify") symbol: Optional[str] = Field(None, description="Trading symbol") volume: Optional[float] = Field(None, description="Trading volume") price: Optional[float] = Field(None, description="Entry price") stop_loss: Optional[float] = Field(None, description="Stop loss price") take_profit: Optional[float] = Field(None, description="Take profit price") timeframe: Optional[str] = Field(None, description="Chart timeframe") confidence: float = Field(..., description="Confidence score 0-1") parsed_command: str = Field(..., description="Human readable summary") class TradingChatbot: """Trading chatbot that integrates Ollama and MT5 MCPs""" def __init__(self, config: ChatbotConfig): self.config = config self.session: Optional[aiohttp.ClientSession] = None self.conversation_history: List[ChatMessage] = [] self.user_context: Dict[str, Any] = {} # User preferences and context async def __aenter__(self): self.session = aiohttp.ClientSession() return self async def __aexit__(self, exc_type, exc_val, exc_tb): if self.session: await self.session.close() def _call_mcp_tool_sync(self, server_url: str, tool_name: str, parameters: Dict[str, Any]) -> Any: """Call MCP tool synchronously to avoid event loop issues""" import requests try: payload = { "jsonrpc": "2.0", "id": str(uuid.uuid4()), "method": "tools/call", "params": { "name": tool_name, "arguments": parameters } } # Use synchronous HTTP call to avoid event loop conflicts response = requests.post( f"{server_url}/chat", json=payload, timeout=30 ) if response.status_code == 200: result = response.json() return result.get("result") else: raise Exception(f"MCP call failed: {response.status_code} - {response.text}") except Exception as e: logger.error(f"MCP tool call failed: {e}") raise async def _call_ollama_tool(self, tool_name: str, **kwargs) -> Any: """Call Ollama MCP tool""" return self._call_mcp_tool_sync(self.config.ollama_host, tool_name, kwargs) async def _call_mt5_tool(self, tool_name: str, **kwargs) -> Any: """Call MT5 MCP tool""" return self._call_mcp_tool_sync(self.config.mt5_host, tool_name, kwargs) async def initialize(self) -> Dict[str, Any]: """Initialize chatbot and check server connectivity""" try: # Check Ollama health ollama_health = await self._call_ollama_tool("check_ollama_health") logger.info(f"Ollama health: {ollama_health}") # Check MT5 connection (try to get account info) try: mt5_account = await self._call_mt5_tool("get_account_info") mt5_status = {"status": "connected", "account": mt5_account.login} except Exception as e: mt5_status = {"status": "disconnected", "error": str(e)} logger.warning(f"MT5 connection failed: {e}") # Initialize conversation with system message system_message = ChatMessage( role="system", content="""You are an AI trading assistant. You can help users with: TRADING COMMANDS (natural language): - Buy/sell orders: "buy 100 EURUSD" or "sell 0.01 BTCUSD at 45000 with stop loss 44000" - Market data: "show me EURUSD price" or "get BTCUSD candles for H1" - Account info: "how much balance do I have?" or "show my positions" - Order management: "cancel order 12345" or "close all positions" ANALYSIS REQUESTS: - Market analysis: "analyze EURUSD trend" or "what's the best trading opportunity now?" - Technical indicators: "show RSI for BTCUSD on M15" - Predictions: "predict EURUSD direction for next hour" Always be safe, suggest stop losses, and ask for confirmation on risky trades.""" ) self.conversation_history = [system_message] return { "status": "initialized", "ollama": ollama_health, "mt5": mt5_status, "chat_model": self.config.chat_model } except Exception as e: logger.error(f"Initialization failed: {e}") raise async def _parse_trading_intent(self, user_message: str) -> ParsedTradingIntent: """Parse trading intent from user message using Ollama""" try: analysis_result = await self._call_ollama_tool("analyze_trading_intent", message=user_message) # Ensure confidence is float confidence = float(analysis_result.get("confidence", 0.0)) return ParsedTradingIntent(**analysis_result) except Exception as e: logger.error(f"Intent parsing failed: {e}") return ParsedTradingIntent( intent="error", confidence=0.0, parsed_command=f"Failed to parse intent: {str(e)}" ) async def _execute_trading_command(self, intent: ParsedTradingIntent) -> Dict[str, Any]: """Execute trading command based on parsed intent""" try: # Validate confidence threshold if intent.confidence < 0.6: return { "status": "confirmation_required", "message": f"I'm not confident about your request. Did you mean: {intent.parsed_command}?", "parsed_intent": intent.model_dump() } action = intent.action or "" symbol = intent.symbol volume = intent.volume price = intent.price sl = intent.stop_loss tp = intent.take_profit # TRADE EXECUTION if action.lower() == "buy": if not symbol: return {"status": "error", "message": "Symbol not specified for buy order"} if not volume: return {"status": "error", "message": "Volume not specified for buy order"} return await self._execute_buy_order(symbol, volume, sl, tp) elif action.lower() == "sell": if not symbol: return {"status": "error", "message": "Symbol not specified for sell order"} if not volume: return {"status": "error", "message": "Volume not specified for sell order"} return await self._execute_sell_order(symbol, volume, sl, tp) elif action.lower() == "check": if "balance" in intent.parsed_command.lower() or "account" in intent.parsed_command.lower(): return await self._get_account_info() elif "position" in intent.parsed_command.lower(): return await self._get_positions(symbol) elif "order" in intent.parsed_command.lower(): return await self._get_orders(symbol) else: return {"status": "error", "message": "What would you like me to check?"} elif action.lower() == "cancel": return {"status": "error", "message": "Please specify which order to cancel"} elif action.lower() == "modify": return {"status": "error", "message": "Position modification not implemented yet"} else: return { "status": "confusion", "message": f"I'm not sure what you want to do with: {intent.parsed_command}" } except Exception as e: logger.error(f"Trading command execution failed: {e}") return {"status": "error", "message": f"Command execution failed: {str(e)}"} async def _execute_buy_order(self, symbol: str, volume: float, sl: Optional[float] = None, tp: Optional[float] = None) -> Dict[str, Any]: """Execute buy market order""" try: # Add confirmation for large volumes if volume > 1.0: return { "status": "confirmation_required", "message": f"Are you sure you want to BUY {volume} lots of {symbol}? This is a large position! (SL: {sl}, TP: {tp})", "command": "confirm_buy", "params": {"symbol": symbol, "volume": volume, "sl": sl, "tp": tp} } result = await self._call_mt5_tool( "buy_market", symbol=symbol, volume=volume, sl=sl, tp=tp, comment="Chatbot order" ) return { "status": "success", "message": f"BUY order executed: {symbol} {volume} lots", "order_result": result } except Exception as e: return {"status": "error", "message": f"Buy order failed: {str(e)}"} async def _execute_sell_order(self, symbol: str, volume: float, sl: Optional[float] = None, tp: Optional[float] = None) -> Dict[str, Any]: """Execute sell market order""" try: # Add confirmation for large volumes if volume > 1.0: return { "status": "confirmation_required", "message": f"Are you sure you want to SELL {volume} lots of {symbol}? This is a large position! (SL: {sl}, TP: {tp})", "command": "confirm_sell", "params": {"symbol": symbol, "volume": volume, "sl": sl, "tp": tp} } result = await self._call_mt5_tool( "sell_market", symbol=symbol, volume=volume, sl=sl, tp=tp, comment="Chatbot order" ) return { "status": "success", "message": f"SELL order executed: {symbol} {volume} lots", "order_result": result } except Exception as e: return {"status": "error", "message": f"Sell order failed: {str(e)}"} async def _get_account_info(self) -> Dict[str, Any]: """Get account information""" try: account = await self._call_mt5_tool("get_account_info") return { "status": "success", "message": f"Your account balance: ${account.balance:.2f}, Equity: ${account.equity:.2f}, Margin Free: ${account.margin_free:.2f}", "account_info": account.model_dump() if hasattr(account, 'model_dump') else account } except Exception as e: return {"status": "error", "message": f"Could not get account info: {str(e)}"} async def _get_positions(self, symbol: Optional[str] = None) -> Dict[str, Any]: """Get open positions""" try: positions = await self._call_mt5_tool("positions_get", **({"symbol": symbol} if symbol else {})) if not positions: return {"status": "success", "message": "No open positions"} message = f"Open positions ({len(positions)}):\n" for pos in positions: message += f"- {pos.symbol} {pos.type} {pos.volume} lots @ {pos.price_open}\n" return {"status": "success", "message": message, "positions": [p.model_dump() for p in positions]} except Exception as e: return {"status": "error", "message": f"Could not get positions: {str(e)}"} async def _get_orders(self, symbol: Optional[str] = None) -> Dict[str, Any]: """Get active orders""" try: orders = await self._call_mt5_tool("orders_get", **({"symbol": symbol} if symbol else {})) if not orders: return {"status": "success", "message": "No active orders"} message = f"Active orders ({len(orders)}):\n" for order in orders: message += f"- {order['ticket']}: {order['symbol']} {order['type']} {order['volume_initial']}\n" return {"status": "success", "message": message, "orders": orders} except Exception as e: return {"status": "error", "message": f"Could not get orders: {str(e)}"} def _extract_symbol_from_message(message: str) -> str: """Extract trading symbol from message (improved parsing)""" import re # Find symbol in the exact case it appears symbol_patterns = [ r'\bBTCUSDC\b', # BTCUSDc (exactly as needed) r'\b(EURUSD|GBPUSD|USDJPY|USDCHF|AUDUSD|USDCAD|NZDUSD|EURGBP|EURJPY|XAUUSD|XAUEUR|BTCUSD|ETHUSD)[a-zA-Z]*\b' ] for pattern in symbol_patterns: symbol_match = re.search(pattern, message) if symbol_match: # Return exactly what the user typed return symbol_match.group() # Try case-insensitive if exact match fails for pattern in symbol_patterns: symbol_match = re.search(pattern, message, re.IGNORECASE) if symbol_match: original_match = symbol_match.group() # Try to preserve the original case from the message start_pos = message.upper().find(original_match.upper()) if start_pos != -1: end_pos = start_pos + len(original_match) return message[start_pos:end_pos] return None def _extract_volume_from_message(message: str) -> float: """Extract volume/lots from message (improved parsing)""" import re # Look for 0.1, 1.5, etc. patterns that could be volume # Match decimal numbers in trading context volume_patterns = [ r'\bcomprar\s+(\d+\.?\d*)\s+de\s+[A-Z]+', r'\bvender\s+(\d+\.?\d*)\s+de\s+[A-Z]+', r'\bcomprar\s+(\d+\.?\d*)\s+[A-Z]+', r'\bvender\s+(\d+\.?\d*)\s+[A-Z]+', r'\b(\d+\.?\d*)\s+lots\b', r'\b(\d+\.?\d*)\s+de\b', r'\bvolume\s*[:=]?\s*(\d+\.?\d*)', ] for pattern in volume_patterns: match = re.search(pattern, message, re.IGNORECASE) if match: try: volume = float(match.group(1)) if 0.001 <= volume <= 1000: # Allow smaller volumes like 0.001 return volume except ValueError: continue # Look for any isolated decimal number that could be volume decimals = re.findall(r'\b\d+\.\d+\b', message) for decimal in decimals: try: volume = float(decimal) if 0.001 <= volume <= 1000: return volume except ValueError: continue # Default volume for demo safety return 0.01 def _extract_tp_sl_from_message(message: str): """Extract take profit and stop loss from message in pips""" import re tp = None sl = None # Look for TP patterns tp_patterns = [ r'tp\s+de\s+(\d+)', # "tp de 500" r'take\s+profit\s*[:=]?\s*(\d+)', # "take profit: 500" r'tp\s*[:=]?\s*(\d+)', # "tp=500" ] for pattern in tp_patterns: match = re.search(pattern, message, re.IGNORECASE) if match: tp = int(match.group(1)) break # Look for SL patterns sl_patterns = [ r'sl\s+de\s+(\d+)', # "sl de 1500" r'stop\s+loss\s*[:=]?\s*(\d+)', # "stop loss: 1500" r'sl\s*[:=]?\s*(\d+)', # "sl=1500" ] for pattern in sl_patterns: match = re.search(pattern, message, re.IGNORECASE) if match: sl = int(match.group(1)) break return tp, sl # Global chatbot instance chatbot: Optional[TradingChatbot] = None async def get_chatbot() -> TradingChatbot: """Get or create global chatbot instance""" global chatbot if chatbot is None: config = ChatbotConfig() chatbot = TradingChatbot(config) await chatbot.__aenter__() return chatbot async def initialize_chatbot() -> Dict[str, Any]: """Initialize the global chatbot""" bot = await get_chatbot() return await bot.initialize() # Global state for pending trades pending_trade = None def send_message_sync(message: str) -> Dict[str, Any]: """Send message to chatbot and get response with REAL MT5 data""" global pending_trade try: import MetaTrader5 as mt5 # Try to initialize MT5 connection if not mt5.initialize(): return { "response": "❌ MT5 não conectado. Certifique-se que o MetaTrader 5 está rodando.", "intent": {"intent": "error", "confidence": 1.0}, "result": {"status": "error", "message": "MT5 not initialized"}, "timestamp": datetime.now().isoformat() } message_lower = message.lower().strip() # Check for trade confirmation first if pending_trade and (message_lower in ['ok', 'sim', 'yes', 'confirmar', 'execute']): # Execute the pending trade symbol = pending_trade['symbol'] volume = pending_trade['volume'] tp_price = pending_trade.get('tp_price') sl_price = pending_trade.get('sl_price') action = pending_trade['action'] try: if action.upper() == 'BUY': request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": mt5.ORDER_TYPE_BUY, "price": mt5.symbol_info_tick(symbol).ask, "sl": sl_price, "tp": tp_price, "deviation": 10, "magic": 234000, "comment": "Chatbot trade", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } result = mt5.order_send(request) if result.retcode == mt5.TRADE_RETCODE_DONE: response = f"✅ **ORDEM EXECUTADA COM SUCESSO!**\n\n" response += f"📈 **{action.upper()}** {symbol} {volume} lots\n" response += f"🎯 Ticket: {result.order}\n" response += f"💰 Preço: ${result.price:.5f}\n" if sl_price: response += f"🛡️ Stop Loss: ${sl_price:.5f}\n" if tp_price: response += f"📈 Take Profit: ${tp_price:.5f}\n" pending_trade = None else: response = f"❌ **FALHA NA EXECUÇÃO:** {result.comment}" pending_trade = None elif action.upper() == 'SELL': request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": mt5.ORDER_TYPE_SELL, "price": mt5.symbol_info_tick(symbol).bid, "sl": sl_price, "tp": tp_price, "deviation": 10, "magic": 234000, "comment": "Chatbot trade", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } result = mt5.order_send(request) if result.retcode == mt5.TRADE_RETCODE_DONE: response = f"✅ **ORDEM EXECUTADA COM SUCESSO!**\n\n" response += f"📉 **{action.upper()}** {symbol} {volume} lots\n" response += f"🎯 Ticket: {result.order}\n" response += f"💰 Preço: ${result.price:.5f}\n" if sl_price: response += f"🛡️ Stop Loss: ${sl_price:.5f}\n" if tp_price: response += f"📈 Take Profit: ${tp_price:.5f}\n" pending_trade = None else: response = f"❌ **FALHA NA EXECUÇÃO:** {result.comment}" pending_trade = None else: response = "❌ **ERRO:** Ação de trade pendente inválida" pending_trade = None except Exception as e: response = f"❌ **ERRO NA EXECUÇÃO:** {str(e)}" pending_trade = None return { "response": response, "intent": {"intent": "trade_confirmation", "confidence": 1.0}, "result": {"status": "success", "message": response}, "timestamp": datetime.now().isoformat() } elif pending_trade and (message_lower in ['cancelar', 'cancel', 'não', 'no', 'abort']): # Cancel the pending trade symbol = pending_trade['symbol'] action = pending_trade['action'] response = f"❌ **ORDEM CANCELADA:**\n\n" response += f"📊 {action.upper()} {symbol} - Operação abortada pelo usuário" pending_trade = None return { "response": response, "intent": {"intent": "trade_cancellation", "confidence": 1.0}, "result": {"status": "success", "message": response}, "timestamp": datetime.now().isoformat() } # Clear old pending trade if user starts a new command if pending_trade and (message_lower.startswith(('comprar', 'vender', 'buy', 'sell'))): pending_trade = None # PROCESS REAL TRADING DATA if "saldo" in message_lower or "account" in message_lower or "balance" in message_lower: # Get real account info account_info = mt5.account_info() if account_info: balance = account_info.balance equity = account_info.equity margin_free = account_info.margin_free response = f"Saldo atual: ${balance:.2f} | Equity: ${equity:.2f} | Margem livre: ${margin_free:.2f}" else: response = "❌ Não foi possível obter informações da conta. Verifique se você está logado no MT5." elif "posi" in message_lower or "position" in message_lower or "positions" in message_lower: # Get real positions positions = mt5.positions_get() if positions: response = f"📊 Você tem {len(positions)} posições abertas:\n\n" for pos in positions: direction = "🟢 COMPRA" if pos.type == mt5.POSITION_TYPE_BUY else "🔴 VENDA" profit_color = "🟢" if pos.profit > 0 else "🔴" response += f"{pos.symbol}: {direction} | Volume: {pos.volume} | Preço: ${pos.price_open:.4f} | Profit: {profit_color}${pos.profit:.2f}\n" else: response = "📊 Você não tem posições abertas atualmente." elif "comprar" in message_lower or "buy" in message_lower: # Extract symbol, volume, TP and SL from message symbol = _extract_symbol_from_message(message) volume = _extract_volume_from_message(message) tp_pips, sl_pips = _extract_tp_sl_from_message(message) if symbol and volume: # Check symbol availability symbol_info = mt5.symbol_info(symbol) if not symbol_info: # Símbolo não encontrado, sugerir símbolos disponíveis all_symbols = mt5.symbols_get() available_forex = [s.name for s in all_symbols[:50] if not any(x in s.name for x in ['INDEX', 'CFD'])][:10] # Limit to avoid spam response = f"⚠️ Símbolo '{symbol}' não disponível neste broker.\n\n" response += f"📊 Símbolos disponíveis (exemplos):\n" + "\n".join(f"• {s}" for s in available_forex) response += f"\n\nExemplo: 'comprar 0.1 {available_forex[0]} tp 100 sl 50'" else: # Get current price for buy (ask) tick = mt5.symbol_info_tick(symbol) if tick: price = tick.ask # Calculate TP and SL prices if provided - SPECIAL HANDLING FOR CRYPTO tp_price = None sl_price = None # Check if this is a crypto symbol (like BTCUSDc) if symbol.upper().endswith('C') or any(x in symbol.upper() for x in ['BTC', 'ETH']): # CRYPTO: stops in direct price points, not pip conversion # Use much smaller pip values (1 pip = 1 dollar for crypto trading) crypto_multiplier = 1.0 if 'BTC' in symbol.upper() else 0.1 # BTC uses larger values if tp_pips: tp_price = price + (tp_pips * crypto_multiplier) if sl_pips: sl_price = price - (sl_pips * crypto_multiplier) else: # NORMAL FOREX: convert pips to price points if tp_pips: tp_price = price + (tp_pips * symbol_info.point) if sl_pips: sl_price = price - (sl_pips * symbol_info.point) # Place buy order with 0.01 lots for demo safety safe_volume = min(volume, 0.01) if volume < 0.01 else volume response = f"✅ **PREPARANDO COMPRA:**\n" response += f"Símbolo: {symbol}\n" response += f"Volume: {safe_volume:.2f} lots\n" response += f"Preço atual: ${price:.5f}\n" if tp_price: response += f"📈 Take Profit: ${tp_price:.5f} (+{tp_pips} pips)\n" if sl_price: response += f"🛡️ Stop Loss: ${sl_price:.5f} (-{sl_pips} pips)\n" # ATIVAR TRADE PENDENTE PARA CONFIRMAÇÃO pending_trade = { 'symbol': symbol, 'volume': safe_volume, 'tp_price': tp_price, 'sl_price': sl_price, 'action': 'BUY' } response += f"\n**CONFIRMAR?** Envie 'ok' para executar ou 'cancelar' para abortar." else: response = f"❌ Não foi possível obter preço atual para {symbol}." else: response = "❌ Para comprar, especifique o símbolo e volume. Exemplo: 'comprar 100 EURUSD'" elif "vender" in message_lower or "sell" in message_lower: # Extract symbol and volume symbol = _extract_symbol_from_message(message) volume = _extract_volume_from_message(message) if symbol and volume: symbol_info = mt5.symbol_info(symbol) if not symbol_info: # Símbolo não encontrado, sugerir símbolos disponíveis all_symbols = mt5.symbols_get() available_forex = [s.name for s in all_symbols[:50] if not any(x in s.name for x in ['INDEX', 'CFD', 'COMMODITIES']) and len(s.name) >= 6][:8] # Only sizable names available_crypto = [s.name for s in all_symbols if 'BTC' in s.name or 'ETH' in s.name][:3] response = f"⚠️ Símbolo '{symbol}' não encontrado ou indisponível no seu broker MT5.\n\n" response += f"📊 Símbolos FOREX disponíveis:\n" for s in available_forex: response += f"• {s}\n" response += f"\n📊 Símbolos CRYPTO disponíveis:\n" for s in available_crypto: response += f"• {s}\n" response += f"\n💡 **DICAS:**\n• Use 'teste' para verificar conexão\n• Use símbolos listados acima\n• Exemplo: 'comprar 0.1 {available_forex[0] if available_forex else 'EURUSD'} tp 100 sl 50'" response += f"\n🛡️ **SEGURANÇA:**{safe_volume:.2f} lots limite de segurança ativo." else: tick = mt5.symbol_info_tick(symbol) if tick: price = tick.bid safe_volume = min(volume, 0.01) if volume < 0.01 else volume response = f"📉 **EXECUTANDO VENDA:**\n" response += f"Símbolo: {symbol}\n" response += f"Volume: {safe_volume} lots\n" response += f"Preço: ${price:.5f}\n" response += f"\n**CONFIRMAR?** Envie 'ok' para executar ou qualquer outra coisa para cancelar." else: response = f"❌ Não foi possível obter preço atual para {symbol}." else: response = "❌ Para vender, especifique o símbolo e volume. Exemplo: 'vender 50 GBPUSD'" elif "analis" in message_lower or "analysis" in message_lower or "mercado" in message_lower: response = "📊 **ANÁLISE DE MERCADO:**\n\n" response += "⚡ **Principais Índices:**\n" response += " • Market volatility: Média\n" response += " • Tendência dominante: Lateral \n" response += " • Recomendação: Monitorar para entrada\n\n" response += "💡 **Sugestão:** Use indicadores técnicos para confirmar entradas." elif "ordem" in message_lower or "orders" in message_lower: orders = mt5.orders_get() if orders: response = f"📋 Você tem {len(orders)} ordens pendentes:\n\n" for order in orders: order_type = "Compra Limit" if order.type == mt5.ORDER_TYPE_BUY_LIMIT else ("Compra Stop" if order.type == mt5.ORDER_TYPE_BUY_STOP else ("Venda Limit" if order.type == mt5.ORDER_TYPE_SELL_LIMIT else "Venda Stop")) response += f"{order.symbol}: {order_type} | Volume: {order.volume_initial} | Preço: ${order.price_open:.5f}\n" else: response = "📋 Você não tem ordens pendentes." elif "ping" in message_lower or "teste" in message_lower or "test" in message_lower: # Quick MT5 ping test if mt5.terminal_info(): response = "✅ **CONEXÃO MT5 OK:** Terminal respondendo normalmente." else: response = "❌ **CONEXÃO MT5 FALHANDO:** Terminal não está respondendo." else: response = f"💬 **{message}**\n\n" response += "**Comandos disponíveis:**\n" response += "• 'saldo' ou 'balance' - Ver informações da conta\n" response += "• 'posições' ou 'positions' - Ver posições abertas\n" response += "• 'comprar 100 EURUSD' - Ordem de compra\n" response += "• 'vender 50 GBPUSD' - Ordem de venda\n" response += "• 'ordens' ou 'orders' - Ver ordens pendentes\n" response += "• 'análise' ou 'analysis' - Análise de mercado\n" response += "• 'teste' ou 'test' - Verificar conexão MT5" return { "response": response, "intent": {"intent": "trading_command", "confidence": 1.0}, "result": {"status": "success", "message": response}, "timestamp": datetime.now().isoformat() } except Exception as e: logger.error(f"MT5 connection failed: {e}") return { "response": f"❌ Erro ao conectar com MT5: {str(e)}\n\nCertifique-se que:\n• MetaTrader 5 está instalado\n• MetaTrader 5 está rodando\n• Você está logado nesta conta\n• Conexão com Broker está ativa", "intent": {"intent": "error", "confidence": 1.0}, "result": {"status": "error", "message": str(e)}, "timestamp": datetime.now().isoformat() } async def send_message(message: str) -> Dict[str, Any]: """Send message to chatbot and get response""" bot = await get_chatbot() return await bot.chat(message) async def confirm_trading_command(command: str, params: Dict[str, Any]) -> Dict[str, Any]: """Confirm and execute high-risk trading command""" bot = await get_chatbot() return await bot.confirm_command(command, params) async def send_message_async(message: str) -> Dict[str, Any]: """Send message using Ollama AI intelligent interpretation - FULL AI MODE""" global pending_trade try: import MetaTrader5 as mt5 # Ensure MT5 connection if not mt5.initialize(): return { "response": "❌ MT5 não conectado. Certifique-se que o MetaTrader 5 está rodando.", "intent": {"intent": "error", "confidence": 1.0}, "result": {"status": "error", "message": "MT5 not initialized"}, "timestamp": datetime.now().isoformat() } # STEP 1: AI ANALYSIS - Use Ollama to analyze user intent async with OllamaClient() as ollama: # Analyze intent using Ollama analysis_result = await ollama._request("POST", "/api/chat", json={ "model": "qwen2.5-coder", "messages": [{ "role": "system", "content": """You are an expert trading analyst AI. Analyze user messages for trading intent. Return JSON with: intent, action, symbol, volume, price, stop_loss, take_profit, timeframe, confidence, parsed_command""" }, { "role": "user", "content": f"Analyze this trading message: '{message}'" }], "stream": False }) analysis_data = analysis_result.get("message", {}).get("content", "{}") try: intent_data = json.loads(analysis_data) intent = ParsedTradingIntent(**intent_data) except Exception as e: # Fallback if JSON parsing fails intent = ParsedTradingIntent( intent="chat", confidence=0.8, parsed_command=message ) response_parts = [] response_parts.append(f"🧠 **ANÁLISE IA:** {intent.parsed_command}") # STEP 2: AI DECISION MAKING if intent.intent == "trade" and intent.action in ["buy", "sell"]: # TRADING DECISION symbol = intent.symbol volume = intent.volume if symbol and volume: # Get market data first for AI analysis symbol_info = mt5.symbol_info(symbol) if symbol_info: tick = mt5.symbol_info_tick(symbol) account_info = mt5.account_info() if tick: price = tick.ask if intent.action == "buy" else tick.bid balance = account_info.balance if account_info else 0 # AI RISK ANALYSIS risk_analysis = f""" 💡 **ANÁLISE DE RISCO IA:** • Ativo: {symbol} @ ${price:.5f} • Volume: {volume} lots • Exposição: ${(volume * price * symbol_info.margin_initial):.2f} • Saldo disponível: ${balance:.2f} • Margem necessária: {((volume * price * symbol_info.margin_initial) / balance * 100):.1f}% """ # AI STRATEGY SUGESTIONS suggestions = [] if intent.action == "buy": if volume > 0.5: suggestions.append("⚠️ Volume alto - considere reduzir para 0.5 lots") if tick.ask > symbol_info.session_high: suggestions.append("📈 Preço próximo da máxima da sessão") elif intent.action == "sell": if volume > 0.5: suggestions.append("⚠️ Volume alto - considere reduzir para 0.5 lots") if tick.bid < symbol_info.session_low: suggestions.append("📉 Preço próximo da mínima da sessão") response_parts.append(risk_analysis.strip()) if suggestions: response_parts.append("**💭 SOLICITAÇÕES DO IA:**\n" + "\n".join(f"• {s}" for s in suggestions)) # DECISION: Execute or confirm? high_risk = volume > 1.0 or any(word in intent.parsed_command.lower() for word in ["urgente", "agora", "imediato"]) if high_risk: response_parts.append("\n**🤔 IA RECOMENDA VERIFICAÇÃO:** Operação de alto risco detectada") response_parts.append("**CONFIRMAR?** Envie 'ok' para executar ou 'cancelar' para abortar.") else: response_parts.append("**🤖 IA EXECUTANDO:** Ordem será executada automaticamente.") # Set pending trade for confirmation pending_trade = { 'symbol': symbol, 'volume': min(volume, 0.5), # Safety limit 'tp_price': intent.take_profit, 'sl_price': intent.stop_loss, 'action': intent.action.upper() } # If low risk, auto-execute if not high_risk: pending_trade = None # Clear pending # Execute immediately try: trade_request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": min(volume, 0.5), "type": mt5.ORDER_TYPE_BUY if intent.action == "buy" else mt5.ORDER_TYPE_SELL, "price": price, "sl": intent.stop_loss, "tp": intent.take_profit, "deviation": 10, "magic": 234000, "comment": "AI Trading Bot", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } result = mt5.order_send(trade_request) if result.retcode == mt5.TRADE_RETCODE_DONE: response_parts.append(f"\n✅ **ORDEM EXECUTADA COM SUCESSO!**") response_parts.append(f"🎯 Ticket: {result.order}") response_parts.append(f"💰 Preço: ${result.price:.5f}") else: response_parts.append(f"\n❌ **EXECUÇÃO FALHOU:** {result.comment}") except Exception as e: response_parts.append(f"\n❌ **ERRO DE EXECUÇÃO:** {str(e)}") else: response_parts.append("❌ Ativo não encontrado ou indisponível.") else: response_parts.append("❌ Parâmetros insuficientes para operação de trading.") elif intent.intent == "info" or intent.intent == "analysis": # INFORMATION/MARKET ANALYSIS REQUEST response_parts.append("📊 **ANÁLISE DE MERCADO IA:**") # Get market data for analysis if intent.symbol: symbol = intent.symbol symbol_info = mt5.symbol_info(symbol) if symbol_info: tick = mt5.symbol_info_tick(symbol) if tick: current_price = tick.last if tick.last > 0 else tick.ask response_parts.append(f"🎯 **{symbol}** Atual: ${current_price:.5f}") # Simple technical analysis placeholder response_parts.append(f"📈 Max Sessão: ${symbol_info.session_high:.5f}") response_parts.append(f"📉 Min Sessão: ${symbol_info.session_low:.5f}") if intent.timeframe: response_parts.append(f"🕐 Timeframe solicitado: {intent.timeframe}") else: response_parts.append("💡 Use timeframes: M1, M5, H1, D1, W1") else: # General market information response_parts.append("🌍 **MERCADO GERAL:**") response_parts.append("- Forex: EURUSD, GBPUSD, USDJPY") response_parts.append("- Crypto: BTCUSD, ETHUSD") response_parts.append("- Índices: SPX500, UK100") response_parts.append("\n💡 **DICAS IA:** Especifique um ativo para análise detalhada") elif intent.intent == "chat" or intent.intent == "other": # GENERAL CHAT RESPONSE async with OllamaClient() as ollama: chat_result = await ollama._request("POST", "/api/chat", json={ "model": "qwen2.5-coder", "messages": [{ "role": "system", "content": """You are an expert trading assistant AI. Provide helpful, accurate responses about trading, markets, and investment strategies. Be conversational but professional.""" }, { "role": "user", "content": message }], "stream": False }) ai_response = chat_result.get("message", {}).get("content", "") response_parts.append(f"🤖 **IA:** {ai_response}") else: # FALLBACK RESPONSE response_parts.append("🤔 **IA PENSANDO:** Não entendi completamente sua solicitação.") response_parts.append("📝 **SUGESTÕES:** Tente frases como:") response_parts.append("• 'Comprar 0.1 EURUSD com stop loss 20 pips'") response_parts.append("• 'Qual o preço do BTCUSD?'") response_parts.append("• 'Análise do mercado forex'") # COMBINE ALL RESPONSE PARTS final_response = "\n".join(response_parts) return { "response": final_response, "intent": intent.model_dump() if hasattr(intent, 'model_dump') else intent.__dict__, "result": {"status": "success", "message": "AI analysis completed"}, "timestamp": datetime.now().isoformat() } except Exception as e: error_msg = f"❌ **ERRO IA:** {str(e)}" logger.error(f"AI message processing failed: {e}") return { "response": error_msg, "intent": {"intent": "error", "confidence": 0.0}, "result": {"status": "error", "message": str(e)}, "timestamp": datetime.now().isoformat() }

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/Af7007/mcp-trader'

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