---
id: "INTEGRACION-METATRADER4"
title: "Integracion MetaTrader4 via MetaAPI"
type: "Documentation"
project: "trading-platform"
version: "1.0.0"
updated_date: "2026-01-04"
---

# Integracion MetaTrader4 via MetaAPI

**Version:** 1.0.0
**Fecha:** 2025-12-08
**Modulo:** Trading Operations
**Autor:** Trading Strategist - Trading Platform

---

## Tabla de Contenidos

1. [Vision General](#vision-general)
2. [Arquitectura de Integracion](#arquitectura-de-integracion)
3. [MetaAPI Setup](#metaapi-setup)
4. [Account Management](#account-management)
5. [Trade Execution](#trade-execution)
6. [Price Adjustments](#price-adjustments)
7. [API Endpoints](#api-endpoints)
8. [Error Handling](#error-handling)
9. [Implementacion](#implementacion)
10. [Testing](#testing)

---

## Vision General

### Objetivo

Integrar MetaTrader4 (MT4) como broker de ejecucion para:

1. **Conectar cuentas MT4** de diferentes brokers
2. **Ejecutar operaciones** (open, modify, close)
3. **Monitorear posiciones** en tiempo real
4. **Sincronizar datos** de precios
5. **Gestionar multiples cuentas**

### Por que MetaAPI?

| Ventaja | Descripcion |
|---------|-------------|
| Cloud-based | No requiere MT4 local corriendo |
| Multi-broker | Soporta cualquier broker MT4 |
| API REST/WS | Facil integracion |
| Reliability | 99.9% uptime |
| Security | Encriptacion end-to-end |

---

## Arquitectura de Integracion

### Diagrama de Flujo

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                    METATRADER4 INTEGRATION ARCHITECTURE                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌─────────────────────┐                                                    │
│  │   Trading Platform      │                                                    │
│  │   Trading Service   │                                                    │
│  │                     │                                                    │
│  │  ┌───────────────┐  │     ┌─────────────────────────────────────────┐   │
│  │  │ Trade Manager │  │     │              MetaAPI Cloud               │   │
│  │  │               │  │     │                                         │   │
│  │  │ - Open/Close  │◄─┼────▶│  ┌──────────┐  ┌──────────┐            │   │
│  │  │ - Modify SL/TP│  │     │  │ Account  │  │ Trading  │            │   │
│  │  │ - Get Status  │  │     │  │ Manager  │  │  API     │            │   │
│  │  └───────────────┘  │     │  └────┬─────┘  └────┬─────┘            │   │
│  │                     │     │       │             │                   │   │
│  │  ┌───────────────┐  │     │       │             │                   │   │
│  │  │Price Adjuster │  │     │  ┌────▼─────────────▼────┐              │   │
│  │  │               │  │     │  │    MetaAPI Server     │              │   │
│  │  │ - Broker diff │  │     │  │   (Connection Pool)   │              │   │
│  │  │ - Slippage    │  │     │  └───────────┬───────────┘              │   │
│  │  └───────────────┘  │     │              │                          │   │
│  │                     │     └──────────────┼──────────────────────────┘   │
│  │  ┌───────────────┐  │                    │                              │
│  │  │Account Sync   │  │                    │                              │
│  │  │               │  │                    │ WebSocket/HTTP               │
│  │  │ - Multi-acct  │  │                    │                              │
│  │  │ - Balance     │  │                    │                              │
│  │  └───────────────┘  │                    │                              │
│  └─────────────────────┘                    │                              │
│                                             │                              │
│            ┌────────────────────────────────┼────────────────────────────┐ │
│            │                                │                            │ │
│            ▼                                ▼                            ▼ │
│  ┌──────────────────┐            ┌──────────────────┐         ┌──────────┐│
│  │   Broker A       │            │   Broker B       │         │ Broker C ││
│  │   (IC Markets)   │            │   (Pepperstone)  │         │ (XM)     ││
│  │                  │            │                  │         │          ││
│  │  ┌────────────┐  │            │  ┌────────────┐  │         │  ┌────┐  ││
│  │  │    MT4     │  │            │  │    MT4     │  │         │  │MT4 │  ││
│  │  │  Terminal  │  │            │  │  Terminal  │  │         │  │    │  ││
│  │  └────────────┘  │            │  └────────────┘  │         │  └────┘  ││
│  └──────────────────┘            └──────────────────┘         └──────────┘│
│                                                                            │
└────────────────────────────────────────────────────────────────────────────┘
```

### Componentes

1. **Trade Manager**: Gestiona operaciones CRUD
2. **Price Adjuster**: Compensa diferencias de precios entre brokers
3. **Account Sync**: Sincroniza estado de cuentas
4. **MetaAPI Client**: Wrapper para API de MetaAPI

---

## MetaAPI Setup

### Registro y Credenciales

```bash
# 1. Crear cuenta en metaapi.cloud
# 2. Obtener API token
# 3. Provisionar cuentas MT4

# Environment variables
export METAAPI_TOKEN="your-api-token"
export METAAPI_DOMAIN="agiliumtrade.agiliumtrade.ai"
```

### Configuracion

```yaml
# config/metaapi_config.yaml

metaapi:
  token: ${METAAPI_TOKEN}
  domain: agiliumtrade.agiliumtrade.ai

  accounts:
    - id: "account-id-1"
      name: "IC Markets - Main"
      broker: "ICMarkets"
      server: "ICMarketsSC-Demo"
      type: "demo"
      enabled: true

    - id: "account-id-2"
      name: "Pepperstone - Hedge"
      broker: "Pepperstone"
      server: "Pepperstone-Demo"
      type: "demo"
      enabled: true

  settings:
    connection_timeout: 60  # segundos
    reconnect_attempts: 5
    request_timeout: 30
    sync_mode: "user"  # or "broker"

  risk:
    max_slippage_pips: 3
    max_spread_pips: 5
    requote_retries: 3
```

### Provisionamiento de Cuenta

```python
# services/metaapi_setup.py

from metaapi_cloud_sdk import MetaApi

async def provision_account(
    login: str,
    password: str,
    server: str,
    name: str
) -> dict:
    """
    Provisiona una nueva cuenta MT4 en MetaAPI

    Args:
        login: MT4 login number
        password: MT4 password
        server: Broker server name
        name: Friendly name for account

    Returns:
        Account configuration dict
    """
    api = MetaApi(token=METAAPI_TOKEN)

    account = await api.metatrader_account_api.create_account({
        'name': name,
        'type': 'cloud',
        'login': login,
        'password': password,
        'server': server,
        'platform': 'mt4',
        'magic': 123456,  # Magic number for our trades
        'application': 'Trading PlatformIA',
        'keywords': ['trading', 'ml-trading']
    })

    # Deploy and wait for connection
    await account.deploy()
    await account.wait_connected()

    return {
        'id': account.id,
        'name': name,
        'server': server,
        'state': account.state,
        'connection_status': account.connection_status
    }
```

---

## Account Management

### MetaAPI Client Wrapper

```python
# services/metaapi_client.py

from metaapi_cloud_sdk import MetaApi
from typing import Dict, List, Optional
import asyncio

class MetaAPIClient:
    """
    Wrapper para MetaAPI con funcionalidades de trading
    """

    def __init__(self, config: Dict):
        self.config = config
        self.api = MetaApi(token=config['token'])
        self.accounts: Dict[str, any] = {}
        self.connections: Dict[str, any] = {}

    async def initialize(self):
        """Inicializa conexiones a todas las cuentas"""
        for acc_config in self.config['accounts']:
            if acc_config['enabled']:
                await self.connect_account(acc_config['id'])

    async def connect_account(self, account_id: str):
        """Conecta a una cuenta MT4"""
        try:
            account = await self.api.metatrader_account_api.get_account(account_id)

            # Ensure deployed
            if account.state != 'DEPLOYED':
                await account.deploy()

            # Wait for connection
            await account.wait_connected()

            # Get streaming connection
            connection = account.get_streaming_connection()
            await connection.connect()
            await connection.wait_synchronized()

            self.accounts[account_id] = account
            self.connections[account_id] = connection

            return {
                'account_id': account_id,
                'state': account.state,
                'connected': True
            }

        except Exception as e:
            return {
                'account_id': account_id,
                'connected': False,
                'error': str(e)
            }

    async def get_account_info(self, account_id: str) -> Dict:
        """Obtiene informacion de la cuenta"""
        connection = self.connections.get(account_id)
        if not connection:
            raise ValueError(f"Account {account_id} not connected")

        info = connection.terminal_state.account_information

        return {
            'account_id': account_id,
            'broker': info.broker,
            'balance': info.balance,
            'equity': info.equity,
            'margin': info.margin,
            'free_margin': info.free_margin,
            'margin_level': info.margin_level,
            'leverage': info.leverage,
            'currency': info.currency
        }

    async def get_positions(self, account_id: str) -> List[Dict]:
        """Obtiene posiciones abiertas"""
        connection = self.connections.get(account_id)
        if not connection:
            raise ValueError(f"Account {account_id} not connected")

        positions = connection.terminal_state.positions

        return [{
            'id': p.id,
            'symbol': p.symbol,
            'type': p.type,  # POSITION_TYPE_BUY or POSITION_TYPE_SELL
            'volume': p.volume,
            'open_price': p.open_price,
            'current_price': p.current_price,
            'profit': p.profit,
            'swap': p.swap,
            'commission': p.commission,
            'stop_loss': p.stop_loss,
            'take_profit': p.take_profit,
            'time': p.time,
            'magic': p.magic
        } for p in positions]

    async def get_orders(self, account_id: str) -> List[Dict]:
        """Obtiene ordenes pendientes"""
        connection = self.connections.get(account_id)
        if not connection:
            raise ValueError(f"Account {account_id} not connected")

        orders = connection.terminal_state.orders

        return [{
            'id': o.id,
            'symbol': o.symbol,
            'type': o.type,
            'volume': o.volume,
            'open_price': o.open_price,
            'stop_loss': o.stop_loss,
            'take_profit': o.take_profit,
            'time': o.time,
            'expiration': o.expiration_time
        } for o in orders]

    async def get_symbol_price(self, account_id: str, symbol: str) -> Dict:
        """Obtiene precio actual de un simbolo"""
        connection = self.connections.get(account_id)
        if not connection:
            raise ValueError(f"Account {account_id} not connected")

        price = connection.terminal_state.price(symbol)

        return {
            'symbol': symbol,
            'bid': price.bid,
            'ask': price.ask,
            'spread': price.ask - price.bid,
            'time': price.time
        }

    async def close(self):
        """Cierra todas las conexiones"""
        for connection in self.connections.values():
            await connection.close()
```

---

## Trade Execution

### Trade Manager

```python
# services/trade_manager.py

from typing import Dict, Optional
from dataclasses import dataclass
from enum import Enum

class OrderType(Enum):
    MARKET_BUY = "ORDER_TYPE_BUY"
    MARKET_SELL = "ORDER_TYPE_SELL"
    LIMIT_BUY = "ORDER_TYPE_BUY_LIMIT"
    LIMIT_SELL = "ORDER_TYPE_SELL_LIMIT"
    STOP_BUY = "ORDER_TYPE_BUY_STOP"
    STOP_SELL = "ORDER_TYPE_SELL_STOP"

@dataclass
class TradeRequest:
    symbol: str
    action: str  # "BUY" or "SELL"
    volume: float
    stop_loss: float
    take_profit: float
    comment: Optional[str] = None
    magic: int = 123456

@dataclass
class TradeResult:
    success: bool
    order_id: Optional[str] = None
    position_id: Optional[str] = None
    executed_price: Optional[float] = None
    executed_volume: Optional[float] = None
    slippage: Optional[float] = None
    error: Optional[str] = None

class TradeManager:
    """
    Gestiona ejecucion de trades via MetaAPI
    """

    def __init__(self, metaapi_client: MetaAPIClient, config: Dict):
        self.client = metaapi_client
        self.config = config
        self.price_adjuster = PriceAdjuster(config.get('price_adjustments', {}))

    async def execute_market_order(
        self,
        account_id: str,
        request: TradeRequest
    ) -> TradeResult:
        """
        Ejecuta orden de mercado

        Args:
            account_id: ID de cuenta MetaAPI
            request: TradeRequest con detalles de la orden

        Returns:
            TradeResult con resultado de ejecucion
        """
        try:
            connection = self.client.connections.get(account_id)
            if not connection:
                return TradeResult(success=False, error="Account not connected")

            # Get current price
            price_info = await self.client.get_symbol_price(account_id, request.symbol)

            # Adjust prices for broker
            adjusted_sl, adjusted_tp = self.price_adjuster.adjust_levels(
                request.symbol,
                request.action,
                price_info['bid'] if request.action == 'SELL' else price_info['ask'],
                request.stop_loss,
                request.take_profit
            )

            # Check spread
            if price_info['spread'] > self.config['risk']['max_spread_pips'] * self._get_pip_value(request.symbol):
                return TradeResult(
                    success=False,
                    error=f"Spread too high: {price_info['spread']}"
                )

            # Execute order
            if request.action == 'BUY':
                result = await connection.create_market_buy_order(
                    symbol=request.symbol,
                    volume=request.volume,
                    stop_loss=adjusted_sl,
                    take_profit=adjusted_tp,
                    options={
                        'comment': request.comment or 'Trading Platform',
                        'clientId': f'OQ_{int(time.time())}',
                        'magic': request.magic,
                        'slippage': self.config['risk']['max_slippage_pips']
                    }
                )
            else:
                result = await connection.create_market_sell_order(
                    symbol=request.symbol,
                    volume=request.volume,
                    stop_loss=adjusted_sl,
                    take_profit=adjusted_tp,
                    options={
                        'comment': request.comment or 'Trading Platform',
                        'clientId': f'OQ_{int(time.time())}',
                        'magic': request.magic,
                        'slippage': self.config['risk']['max_slippage_pips']
                    }
                )

            # Check result
            if result.string_code == 'TRADE_RETCODE_DONE':
                expected_price = price_info['ask'] if request.action == 'BUY' else price_info['bid']
                slippage = abs(result.price - expected_price)

                return TradeResult(
                    success=True,
                    order_id=result.order_id,
                    position_id=result.position_id,
                    executed_price=result.price,
                    executed_volume=result.volume,
                    slippage=slippage
                )
            else:
                return TradeResult(
                    success=False,
                    error=f"{result.string_code}: {result.message}"
                )

        except Exception as e:
            return TradeResult(success=False, error=str(e))

    async def modify_position(
        self,
        account_id: str,
        position_id: str,
        stop_loss: Optional[float] = None,
        take_profit: Optional[float] = None
    ) -> TradeResult:
        """Modifica SL/TP de una posicion"""
        try:
            connection = self.client.connections.get(account_id)
            if not connection:
                return TradeResult(success=False, error="Account not connected")

            result = await connection.modify_position(
                position_id=position_id,
                stop_loss=stop_loss,
                take_profit=take_profit
            )

            if result.string_code == 'TRADE_RETCODE_DONE':
                return TradeResult(success=True, position_id=position_id)
            else:
                return TradeResult(
                    success=False,
                    error=f"{result.string_code}: {result.message}"
                )

        except Exception as e:
            return TradeResult(success=False, error=str(e))

    async def close_position(
        self,
        account_id: str,
        position_id: str,
        volume: Optional[float] = None  # None = close all
    ) -> TradeResult:
        """Cierra una posicion (parcial o total)"""
        try:
            connection = self.client.connections.get(account_id)
            if not connection:
                return TradeResult(success=False, error="Account not connected")

            if volume:
                result = await connection.close_position_partially(
                    position_id=position_id,
                    volume=volume
                )
            else:
                result = await connection.close_position(position_id=position_id)

            if result.string_code == 'TRADE_RETCODE_DONE':
                return TradeResult(
                    success=True,
                    position_id=position_id,
                    executed_price=result.price,
                    executed_volume=result.volume
                )
            else:
                return TradeResult(
                    success=False,
                    error=f"{result.string_code}: {result.message}"
                )

        except Exception as e:
            return TradeResult(success=False, error=str(e))

    async def close_all_positions(
        self,
        account_id: str,
        symbol: Optional[str] = None
    ) -> List[TradeResult]:
        """Cierra todas las posiciones (opcionalmente filtradas por simbolo)"""
        positions = await self.client.get_positions(account_id)

        if symbol:
            positions = [p for p in positions if p['symbol'] == symbol]

        results = []
        for position in positions:
            result = await self.close_position(account_id, position['id'])
            results.append(result)

        return results

    def _get_pip_value(self, symbol: str) -> float:
        """Obtiene valor del pip para un simbolo"""
        if 'JPY' in symbol:
            return 0.01
        elif symbol in ['XAUUSD', 'GOLD']:
            return 0.1
        else:
            return 0.0001
```

---

## Price Adjustments

### Manejo de Diferencias de Precio por Broker

```python
# services/price_adjuster.py

from typing import Dict, Tuple, Optional
from dataclasses import dataclass

@dataclass
class BrokerAdjustment:
    """Ajustes especificos por broker"""
    spread_offset: float = 0.0  # Pips de diferencia promedio
    price_offset: float = 0.0   # Offset de precio
    sl_buffer: float = 0.0      # Buffer adicional para SL
    tp_buffer: float = 0.0      # Buffer adicional para TP

class PriceAdjuster:
    """
    Ajusta precios para compensar diferencias entre brokers

    Las diferencias pueden surgir por:
    - Diferentes proveedores de liquidez
    - Diferentes markups en spread
    - Diferencias en feeds de precio
    - Latencia en actualizacion de precios
    """

    def __init__(self, config: Dict):
        self.config = config
        self.adjustments = self._load_adjustments()

    def _load_adjustments(self) -> Dict[str, BrokerAdjustment]:
        """Carga ajustes por broker desde config"""
        adjustments = {}

        for broker_name, adj_config in self.config.get('brokers', {}).items():
            adjustments[broker_name] = BrokerAdjustment(
                spread_offset=adj_config.get('spread_offset', 0.0),
                price_offset=adj_config.get('price_offset', 0.0),
                sl_buffer=adj_config.get('sl_buffer', 0.0),
                tp_buffer=adj_config.get('tp_buffer', 0.0)
            )

        return adjustments

    def adjust_levels(
        self,
        symbol: str,
        action: str,
        entry_price: float,
        stop_loss: float,
        take_profit: float,
        broker: Optional[str] = None
    ) -> Tuple[float, float]:
        """
        Ajusta SL y TP considerando el broker

        Args:
            symbol: Par de trading
            action: "BUY" o "SELL"
            entry_price: Precio de entrada
            stop_loss: Stop loss original
            take_profit: Take profit original
            broker: Nombre del broker (opcional)

        Returns:
            Tuple con (adjusted_sl, adjusted_tp)
        """
        pip_value = self._get_pip_value(symbol)

        # Get broker adjustments
        if broker and broker in self.adjustments:
            adj = self.adjustments[broker]
        else:
            adj = BrokerAdjustment()  # Default values

        # Calculate adjusted levels
        if action == 'BUY':
            # For BUY: SL below entry, TP above entry
            adjusted_sl = stop_loss - (adj.sl_buffer * pip_value)
            adjusted_tp = take_profit + (adj.tp_buffer * pip_value)
        else:
            # For SELL: SL above entry, TP below entry
            adjusted_sl = stop_loss + (adj.sl_buffer * pip_value)
            adjusted_tp = take_profit - (adj.tp_buffer * pip_value)

        return adjusted_sl, adjusted_tp

    def calculate_real_slippage(
        self,
        expected_price: float,
        executed_price: float,
        symbol: str
    ) -> float:
        """Calcula slippage real en pips"""
        pip_value = self._get_pip_value(symbol)
        return abs(executed_price - expected_price) / pip_value

    def is_price_acceptable(
        self,
        expected_price: float,
        actual_price: float,
        symbol: str,
        max_slippage_pips: float = 3.0
    ) -> bool:
        """Verifica si el precio es aceptable"""
        slippage = self.calculate_real_slippage(expected_price, actual_price, symbol)
        return slippage <= max_slippage_pips

    def _get_pip_value(self, symbol: str) -> float:
        """Obtiene valor del pip"""
        if 'JPY' in symbol:
            return 0.01
        elif symbol in ['XAUUSD', 'GOLD']:
            return 0.1
        else:
            return 0.0001

# Configuration example
"""
price_adjustments:
  brokers:
    ICMarkets:
      spread_offset: 0.2
      price_offset: 0.0
      sl_buffer: 0.5  # Agregar 0.5 pips al SL
      tp_buffer: 0.0

    Pepperstone:
      spread_offset: 0.3
      price_offset: 0.1
      sl_buffer: 0.3
      tp_buffer: 0.0

    XM:
      spread_offset: 0.5
      price_offset: 0.2
      sl_buffer: 1.0
      tp_buffer: 0.0
"""
```

---

## API Endpoints

### FastAPI Trading Service

```python
# api/trading_api.py

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional

app = FastAPI(title="Trading Platform Trading Service")

class TradeRequestModel(BaseModel):
    symbol: str
    action: str  # "BUY" or "SELL"
    volume: float
    stop_loss: float
    take_profit: float
    comment: Optional[str] = None

class ModifyRequestModel(BaseModel):
    stop_loss: Optional[float] = None
    take_profit: Optional[float] = None

# Dependency injection
trade_manager: TradeManager = None

@app.on_event("startup")
async def startup():
    global trade_manager
    config = load_config()
    metaapi_client = MetaAPIClient(config['metaapi'])
    await metaapi_client.initialize()
    trade_manager = TradeManager(metaapi_client, config)

# Account endpoints
@app.get("/api/accounts")
async def get_accounts():
    """Lista todas las cuentas conectadas"""
    accounts = []
    for acc_id in trade_manager.client.accounts:
        info = await trade_manager.client.get_account_info(acc_id)
        accounts.append(info)
    return {"accounts": accounts}

@app.get("/api/accounts/{account_id}")
async def get_account(account_id: str):
    """Obtiene informacion de una cuenta"""
    try:
        info = await trade_manager.client.get_account_info(account_id)
        return info
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

@app.get("/api/accounts/{account_id}/positions")
async def get_positions(account_id: str):
    """Obtiene posiciones abiertas de una cuenta"""
    try:
        positions = await trade_manager.client.get_positions(account_id)
        return {"positions": positions}
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

@app.get("/api/accounts/{account_id}/orders")
async def get_orders(account_id: str):
    """Obtiene ordenes pendientes"""
    try:
        orders = await trade_manager.client.get_orders(account_id)
        return {"orders": orders}
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

# Trading endpoints
@app.post("/api/accounts/{account_id}/trade")
async def execute_trade(account_id: str, request: TradeRequestModel):
    """Ejecuta una operacion de trading"""
    trade_request = TradeRequest(
        symbol=request.symbol,
        action=request.action,
        volume=request.volume,
        stop_loss=request.stop_loss,
        take_profit=request.take_profit,
        comment=request.comment
    )

    result = await trade_manager.execute_market_order(account_id, trade_request)

    if not result.success:
        raise HTTPException(status_code=400, detail=result.error)

    return {
        "success": True,
        "order_id": result.order_id,
        "position_id": result.position_id,
        "executed_price": result.executed_price,
        "slippage": result.slippage
    }

@app.put("/api/accounts/{account_id}/positions/{position_id}")
async def modify_position(
    account_id: str,
    position_id: str,
    request: ModifyRequestModel
):
    """Modifica una posicion existente"""
    result = await trade_manager.modify_position(
        account_id,
        position_id,
        request.stop_loss,
        request.take_profit
    )

    if not result.success:
        raise HTTPException(status_code=400, detail=result.error)

    return {"success": True, "position_id": position_id}

@app.delete("/api/accounts/{account_id}/positions/{position_id}")
async def close_position(account_id: str, position_id: str, volume: Optional[float] = None):
    """Cierra una posicion"""
    result = await trade_manager.close_position(account_id, position_id, volume)

    if not result.success:
        raise HTTPException(status_code=400, detail=result.error)

    return {
        "success": True,
        "position_id": position_id,
        "executed_price": result.executed_price
    }

@app.delete("/api/accounts/{account_id}/positions")
async def close_all_positions(account_id: str, symbol: Optional[str] = None):
    """Cierra todas las posiciones"""
    results = await trade_manager.close_all_positions(account_id, symbol)

    return {
        "success": all(r.success for r in results),
        "closed_count": sum(1 for r in results if r.success),
        "failed_count": sum(1 for r in results if not r.success)
    }

# Market data endpoints
@app.get("/api/accounts/{account_id}/price/{symbol}")
async def get_price(account_id: str, symbol: str):
    """Obtiene precio actual de un simbolo"""
    try:
        price = await trade_manager.client.get_symbol_price(account_id, symbol)
        return price
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

# Health check
@app.get("/api/health")
async def health():
    connected_accounts = len(trade_manager.client.connections)
    return {
        "status": "healthy" if connected_accounts > 0 else "degraded",
        "connected_accounts": connected_accounts
    }
```

---

## Error Handling

### Manejo de Errores Comunes

```python
# services/error_handler.py

from enum import Enum
from typing import Optional
import asyncio

class MT4Error(Enum):
    INVALID_STOPS = 10016
    INVALID_VOLUME = 10014
    MARKET_CLOSED = 10018
    NOT_ENOUGH_MONEY = 10019
    OFF_QUOTES = 10004
    REQUOTE = 10004
    TRADE_DISABLED = 10021
    NO_CONNECTION = 10006

class TradeErrorHandler:
    """
    Maneja errores de trading con retry logic
    """

    def __init__(self, config: Dict):
        self.max_retries = config.get('max_retries', 3)
        self.retry_delay = config.get('retry_delay', 1.0)

    async def execute_with_retry(
        self,
        operation: callable,
        *args,
        **kwargs
    ) -> TradeResult:
        """
        Ejecuta operacion con retry automatico

        Retries en:
        - Requotes
        - Off quotes
        - Connection issues

        No retry en:
        - Invalid stops
        - Not enough money
        - Market closed
        """
        last_error = None

        for attempt in range(self.max_retries):
            try:
                result = await operation(*args, **kwargs)

                if result.success:
                    return result

                # Check if error is retryable
                error_code = self._extract_error_code(result.error)

                if error_code in [MT4Error.REQUOTE, MT4Error.OFF_QUOTES]:
                    last_error = result.error
                    await asyncio.sleep(self.retry_delay * (attempt + 1))
                    continue

                elif error_code == MT4Error.NO_CONNECTION:
                    # Try to reconnect
                    await self._try_reconnect()
                    await asyncio.sleep(self.retry_delay * (attempt + 1))
                    continue

                else:
                    # Non-retryable error
                    return result

            except Exception as e:
                last_error = str(e)
                await asyncio.sleep(self.retry_delay * (attempt + 1))

        return TradeResult(
            success=False,
            error=f"Max retries exceeded. Last error: {last_error}"
        )

    def _extract_error_code(self, error_string: str) -> Optional[MT4Error]:
        """Extrae codigo de error del mensaje"""
        for error in MT4Error:
            if str(error.value) in error_string:
                return error
        return None

    async def _try_reconnect(self):
        """Intenta reconectar a MetaAPI"""
        # Implementation depends on MetaAPI client
        pass

    def format_error_message(self, error: str) -> str:
        """Formatea mensaje de error para usuario"""
        error_messages = {
            MT4Error.INVALID_STOPS: "Stop Loss o Take Profit invalido. Verifica la distancia minima.",
            MT4Error.INVALID_VOLUME: "Volumen invalido. Verifica el tamano de lote.",
            MT4Error.MARKET_CLOSED: "El mercado esta cerrado.",
            MT4Error.NOT_ENOUGH_MONEY: "Margen insuficiente para abrir la posicion.",
            MT4Error.REQUOTE: "Requote - el precio cambio. Reintentando...",
            MT4Error.TRADE_DISABLED: "Trading deshabilitado para este simbolo.",
        }

        error_code = self._extract_error_code(error)
        if error_code and error_code in error_messages:
            return error_messages[error_code]

        return f"Error de trading: {error}"
```

---

## Implementacion

### Docker Compose

**IMPORTANTE:** Los puertos deben seguir la politica definida en `/core/devtools/environment/DEVENV-PORTS.md`

**Puertos asignados a trading-platform:**
- Rango base: 3600
- Backend API: 3600
- ML Engine: 3601
- LLM Service: 3602
- Trading Service: 3603
- Database: 5438 (o 5432 compartido)
- Redis: 6385

```yaml
# docker-compose.trading.yaml

version: '3.8'

services:
  trading-service:
    build:
      context: .
      dockerfile: Dockerfile.trading
    container_name: trading-trading
    ports:
      - "3603:3603"  # Trading service (base 3600 + 3)
    environment:
      - METAAPI_TOKEN=${METAAPI_TOKEN}
      - ML_ENGINE_URL=http://ml-engine:3601
      - REDIS_URL=redis://redis:6385
      - DATABASE_URL=postgresql://trading_user:trading_dev_2025@postgres:5438/trading_data
    volumes:
      - ./config:/app/config
      - ./logs:/app/logs
    depends_on:
      - redis
      - postgres
    restart: unless-stopped

volumes:
  trading_logs:
```

---

## Testing

### Test Suite

```python
# tests/test_trading.py

import pytest
from unittest.mock import AsyncMock, patch

@pytest.fixture
def trade_manager():
    config = {
        'risk': {
            'max_slippage_pips': 3,
            'max_spread_pips': 5
        }
    }
    client = AsyncMock()
    return TradeManager(client, config)

@pytest.mark.asyncio
async def test_execute_buy_order(trade_manager):
    """Test BUY order execution"""
    # Mock price
    trade_manager.client.get_symbol_price = AsyncMock(return_value={
        'symbol': 'XAUUSD',
        'bid': 2650.00,
        'ask': 2650.50,
        'spread': 0.50
    })

    # Mock execution
    trade_manager.client.connections['test_account'] = AsyncMock()
    trade_manager.client.connections['test_account'].create_market_buy_order = AsyncMock(
        return_value=AsyncMock(
            string_code='TRADE_RETCODE_DONE',
            order_id='123456',
            position_id='654321',
            price=2650.50,
            volume=0.1
        )
    )

    request = TradeRequest(
        symbol='XAUUSD',
        action='BUY',
        volume=0.1,
        stop_loss=2645.00,
        take_profit=2660.00
    )

    result = await trade_manager.execute_market_order('test_account', request)

    assert result.success
    assert result.order_id == '123456'
    assert result.executed_price == 2650.50

@pytest.mark.asyncio
async def test_spread_too_high(trade_manager):
    """Test rejection when spread is too high"""
    trade_manager.client.get_symbol_price = AsyncMock(return_value={
        'symbol': 'XAUUSD',
        'bid': 2650.00,
        'ask': 2652.00,  # 20 pips spread
        'spread': 2.00
    })

    request = TradeRequest(
        symbol='XAUUSD',
        action='BUY',
        volume=0.1,
        stop_loss=2645.00,
        take_profit=2660.00
    )

    result = await trade_manager.execute_market_order('test_account', request)

    assert not result.success
    assert 'Spread too high' in result.error
```

---

**Documento Generado:** 2025-12-08
**Trading Strategist - Trading Platform**
