ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1700 lines
60 KiB
Markdown
1700 lines
60 KiB
Markdown
---
|
|
id: "INTEGRACION-LLM-FINE-TUNING"
|
|
title: "Integracion LLM con Fine-Tuning para Trading Agent"
|
|
type: "Documentation"
|
|
project: "trading-platform"
|
|
version: "1.0.0"
|
|
created_date: "2026-01-04"
|
|
updated_date: "2026-01-04"
|
|
author: "Orquestador Agent - Trading Platform"
|
|
---
|
|
|
|
# Integracion LLM con Fine-Tuning para Trading Agent
|
|
|
|
**Version:** 1.0.0
|
|
**Fecha:** 2026-01-04
|
|
**Modulo:** OQI-010-llm-trading-integration
|
|
**Hardware:** GPU NVIDIA 16GB VRAM
|
|
|
|
---
|
|
|
|
## Tabla de Contenidos
|
|
|
|
1. [Vision General](#vision-general)
|
|
2. [Arquitectura Unificada](#arquitectura-unificada)
|
|
3. [Modelo LLM y Fine-Tuning](#modelo-llm-y-fine-tuning)
|
|
4. [Integracion MCP Servers](#integracion-mcp-servers)
|
|
5. [Gestion de Riesgo](#gestion-de-riesgo)
|
|
6. [Analisis de Predicciones ML](#analisis-de-predicciones-ml)
|
|
7. [API para Frontend](#api-para-frontend)
|
|
8. [Persistencia en PostgreSQL](#persistencia-en-postgresql)
|
|
9. [Pipeline de Fine-Tuning](#pipeline-de-fine-tuning)
|
|
10. [Implementacion](#implementacion)
|
|
11. [Testing y Validacion](#testing-y-validacion)
|
|
|
|
---
|
|
|
|
## Vision General
|
|
|
|
### Objetivo
|
|
|
|
Crear un agente LLM inteligente que funcione como cerebro del sistema de trading, capaz de:
|
|
|
|
1. **Analizar y explicar** predicciones de los modelos ML (AMD, ICT/SMC, Range Predictor)
|
|
2. **Gestionar riesgo** de forma autonoma con reglas configurables
|
|
3. **Orquestar operaciones** via MCP servers (MT4 y Binance)
|
|
4. **Tomar decisiones** basadas en confluencia de senales
|
|
5. **Aprender y adaptarse** mediante fine-tuning con datos de estrategias
|
|
|
|
### Flujo de Alto Nivel
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
|
│ LLM TRADING AGENT │
|
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ LAYER 1: INPUT SOURCES │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ ML Engine │ │ Market │ │ User │ │ Portfolio │ │ │
|
|
│ │ │ Signals │ │ Data │ │ Commands │ │ State │ │ │
|
|
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
|
|
│ └─────────┼────────────────┼────────────────┼────────────────┼──────────────┘ │
|
|
│ │ │ │ │ │
|
|
│ ▼ ▼ ▼ ▼ │
|
|
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ LAYER 2: LLM CORE (Fine-Tuned) │ │
|
|
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
|
|
│ │ │ chatgpt-oss / Llama 3 8B │ │ │
|
|
│ │ │ Fine-tuned with Trading Strategies │ │ │
|
|
│ │ │ 16GB VRAM Local GPU │ │ │
|
|
│ │ └──────────────────────────────┬──────────────────────────────────────┘ │ │
|
|
│ │ │ │ │
|
|
│ │ ┌──────────────────────────────▼──────────────────────────────────────┐ │ │
|
|
│ │ │ REASONING ENGINE │ │ │
|
|
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │
|
|
│ │ │ │ AMD/ICT │ │ Risk │ │ Decision │ │ │ │
|
|
│ │ │ │ Analysis │ │ Assessment │ │ Making │ │ │ │
|
|
│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │
|
|
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
|
|
│ └───────────────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ LAYER 3: ACTION LAYER │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │MCP MT4 │ │MCP Binance │ │ Risk │ │ Alert │ │ │
|
|
│ │ │Connector │ │Connector │ │ Manager │ │ System │ │ │
|
|
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
|
|
│ └─────────┼────────────────┼────────────────┼────────────────┼──────────────┘ │
|
|
│ │ │ │ │ │
|
|
│ ▼ ▼ ▼ ▼ │
|
|
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ LAYER 4: PERSISTENCE │ │
|
|
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
|
|
│ │ │ PostgreSQL (Schema: llm + ml) │ │ │
|
|
│ │ │ - Predictions - Decisions - Risk Events - Trade History │ │ │
|
|
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
|
|
│ └───────────────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Arquitectura Unificada
|
|
|
|
### Componentes del Sistema
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
│ COMPONENTES PRINCIPALES │
|
|
├──────────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ 1. LLM SERVICE (apps/llm-agent/) │
|
|
│ ├── Core LLM Engine (chatgpt-oss fine-tuned) │
|
|
│ ├── Trading Tools (12+ herramientas) │
|
|
│ ├── Context Manager (Redis) │
|
|
│ ├── Risk Assessment Module (NUEVO) │
|
|
│ └── MCP Orchestrator (NUEVO) │
|
|
│ │
|
|
│ 2. MCP SERVERS │
|
|
│ ├── mcp-mt4-connector/ (existente) │
|
|
│ │ └── 6 tools: account, positions, quotes, trading │
|
|
│ └── mcp-binance-connector/ (NUEVO) │
|
|
│ └── 8 tools: market, account, orders, positions │
|
|
│ │
|
|
│ 3. ML ENGINE (apps/ml-engine/) │
|
|
│ ├── AMD Detector │
|
|
│ ├── Range Predictor │
|
|
│ ├── Signal Generator │
|
|
│ ├── ICT/SMC Detector │
|
|
│ └── Predictions API │
|
|
│ │
|
|
│ 4. RISK MANAGEMENT SERVICE (NUEVO) │
|
|
│ ├── Position Sizing Calculator │
|
|
│ ├── Drawdown Monitor │
|
|
│ ├── Exposure Tracker │
|
|
│ └── Circuit Breaker │
|
|
│ │
|
|
│ 5. PERSISTENCE LAYER │
|
|
│ ├── PostgreSQL (predictions, decisions, risk_events) │
|
|
│ └── Redis (context, sessions, cache) │
|
|
│ │
|
|
└──────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Puertos y Servicios
|
|
|
|
| Servicio | Puerto | Descripcion |
|
|
|----------|--------|-------------|
|
|
| Frontend | 3080 | React SPA |
|
|
| Backend API | 3081 | Express.js REST |
|
|
| WebSocket | 3082 | Real-time updates |
|
|
| ML Engine | 3083 | FastAPI predictions |
|
|
| Data Service | 3084 | Market data |
|
|
| LLM Agent | 3085 | Trading copilot |
|
|
| Trading Agents | 3086 | Atlas, Orion, Nova |
|
|
| MCP MT4 | 3605 | MT4 connector |
|
|
| MCP Binance | 3606 | Binance connector (NUEVO) |
|
|
| Ollama | 11434 | LLM inference |
|
|
| PostgreSQL | 5432 | Database |
|
|
| Redis | 6379 | Cache |
|
|
|
|
---
|
|
|
|
## Modelo LLM y Fine-Tuning
|
|
|
|
### Modelo Base Recomendado
|
|
|
|
Para 16GB de VRAM, las opciones son:
|
|
|
|
| Modelo | VRAM | Context | Ventajas | Fine-Tuning |
|
|
|--------|------|---------|----------|-------------|
|
|
| **Llama 3 8B** | 10GB | 8K | Balance calidad/velocidad | LoRA |
|
|
| **Mistral 7B** | 8GB | 8K | Rapido, buena calidad | LoRA |
|
|
| **Qwen2 7B** | 9GB | 32K | Largo contexto | LoRA |
|
|
| **Phi-3 Medium** | 7GB | 4K | Eficiente, razonamiento | LoRA |
|
|
|
|
**Recomendacion:** Llama 3 8B con quantizacion Q5_K_M para fine-tuning con LoRA.
|
|
|
|
### Dataset de Fine-Tuning
|
|
|
|
El fine-tuning se realizara con definiciones de estrategias de trading:
|
|
|
|
```yaml
|
|
# Estructura del dataset de fine-tuning
|
|
dataset:
|
|
name: trading_data_strategies_v1
|
|
format: JSONL
|
|
|
|
categories:
|
|
- amd_phases:
|
|
description: "Fases Accumulation-Manipulation-Distribution"
|
|
samples: 5000+
|
|
|
|
- ict_concepts:
|
|
description: "Conceptos ICT/SMC (OTE, FVG, BOS, CHOCH)"
|
|
samples: 3000+
|
|
|
|
- risk_management:
|
|
description: "Reglas de gestion de riesgo"
|
|
samples: 2000+
|
|
|
|
- trade_decisions:
|
|
description: "Decisiones de trading con razonamiento"
|
|
samples: 10000+
|
|
|
|
- market_analysis:
|
|
description: "Analisis de mercado y contexto"
|
|
samples: 5000+
|
|
```
|
|
|
|
### Formato del Dataset
|
|
|
|
```jsonl
|
|
{"instruction": "Analiza esta senal de trading y determina si es una buena entrada", "input": "Simbolo: XAUUSD, Fase AMD: Accumulation (78% confianza), Killzone: London Open, RSI: 42, MACD: positivo, Precio cerca de soporte", "output": "ANALISIS:\n\n1. FASE AMD: Accumulation (78%)\n - Las instituciones estan acumulando posiciones largas\n - Esta es una fase FAVORABLE para entradas LONG\n\n2. KILLZONE: London Open\n - Alta liquidez y probabilidad de movimiento direccional\n - Horario optimo para entradas\n\n3. INDICADORES:\n - RSI 42: Zona neutral, no sobrecomprado\n - MACD positivo: Momentum alcista\n\n4. ESTRUCTURA:\n - Precio cerca de soporte: Punto de entrada con riesgo definido\n\nDECISION: LONG recomendado\n- Entry: Nivel de soporte actual\n- Stop Loss: 1-2% debajo del soporte\n- Take Profit: Siguiente resistencia (R:R minimo 1:2)\n\nRIESGO: 2% maximo del capital"}
|
|
{"instruction": "Que accion tomar cuando el mercado esta en fase de Manipulation?", "input": "Fase AMD actual: Manipulation (85%), precio haciendo spike hacia arriba, volume bajo", "output": "ALERTA: FASE DE MANIPULACION DETECTADA\n\n1. ACCION RECOMENDADA: NO OPERAR / ESPERAR\n\n2. RAZONAMIENTO:\n - La fase de Manipulation (85% confianza) indica caza de stops\n - Los spikes con bajo volumen son senales clasicas de manipulacion\n - Las instituciones estan barriendo liquidez antes del movimiento real\n\n3. QUE ESPERAR:\n - Transicion a fase de Distribution o Re-accumulation\n - Confirmacion de direccion con volumen\n - Nueva estructura de mercado clara\n\n4. REGLA DE RIESGO:\n - NUNCA entrar durante Manipulation\n - Si hay posiciones abiertas: ajustar stops, considerar cierre parcial\n\nESTADO: STANDBY - Esperar siguiente fase"}
|
|
```
|
|
|
|
### Proceso de Fine-Tuning
|
|
|
|
```python
|
|
# fine_tuning/train_lora.py
|
|
|
|
from datasets import load_dataset
|
|
from peft import LoraConfig, get_peft_model
|
|
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer
|
|
|
|
# Configuracion LoRA para 16GB VRAM
|
|
lora_config = LoraConfig(
|
|
r=16, # Rank de LoRA
|
|
lora_alpha=32, # Scaling factor
|
|
target_modules=[
|
|
"q_proj", "k_proj", "v_proj",
|
|
"o_proj", "gate_proj", "up_proj", "down_proj"
|
|
],
|
|
lora_dropout=0.05,
|
|
bias="none",
|
|
task_type="CAUSAL_LM"
|
|
)
|
|
|
|
# Training config optimizado para GPU 16GB
|
|
training_args = TrainingArguments(
|
|
output_dir="./trading-llm-finetuned",
|
|
per_device_train_batch_size=4,
|
|
gradient_accumulation_steps=4,
|
|
num_train_epochs=3,
|
|
learning_rate=2e-4,
|
|
fp16=True, # Mixed precision para ahorrar VRAM
|
|
logging_steps=10,
|
|
save_strategy="epoch",
|
|
evaluation_strategy="epoch",
|
|
warmup_ratio=0.1,
|
|
optim="adamw_8bit", # Optimizer de 8 bits
|
|
max_grad_norm=0.3,
|
|
)
|
|
|
|
# Metricas de evaluacion
|
|
def compute_metrics(eval_preds):
|
|
# Evaluar calidad de decisiones de trading
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
## Integracion MCP Servers
|
|
|
|
### MCP MT4 Connector (Existente)
|
|
|
|
Ya implementado en `apps/mcp-mt4-connector/`:
|
|
|
|
```typescript
|
|
// Tools disponibles
|
|
const MT4_TOOLS = [
|
|
"mt4_get_account", // Balance, equity, margin
|
|
"mt4_get_positions", // Posiciones abiertas
|
|
"mt4_get_quote", // Precio bid/ask
|
|
"mt4_execute_trade", // Ejecutar orden
|
|
"mt4_close_position", // Cerrar posicion
|
|
"mt4_modify_position" // Modificar SL/TP
|
|
];
|
|
```
|
|
|
|
### MCP Binance Connector (NUEVO)
|
|
|
|
Nueva app a crear en `apps/mcp-binance-connector/`:
|
|
|
|
```
|
|
mcp-binance-connector/
|
|
├── src/
|
|
│ ├── index.ts # Entry point
|
|
│ ├── config.ts # Configuration
|
|
│ ├── services/
|
|
│ │ └── binance-client.ts # CCXT wrapper
|
|
│ └── tools/
|
|
│ ├── index.ts # Tool registry
|
|
│ ├── market.ts # Market data tools
|
|
│ ├── account.ts # Account info
|
|
│ ├── orders.ts # Order management
|
|
│ └── positions.ts # Position tracking
|
|
├── docs/
|
|
│ ├── ARCHITECTURE.md
|
|
│ └── MCP-TOOLS-SPEC.md
|
|
├── package.json
|
|
├── tsconfig.json
|
|
└── README.md
|
|
```
|
|
|
|
### Tools del MCP Binance
|
|
|
|
```typescript
|
|
// tools/index.ts
|
|
|
|
export const BINANCE_TOOLS = [
|
|
// Market Data (Read-only, bajo riesgo)
|
|
{
|
|
name: "binance_get_ticker",
|
|
description: "Obtiene precio actual de un simbolo",
|
|
parameters: {
|
|
symbol: { type: "string", required: true }
|
|
},
|
|
risk: "low"
|
|
},
|
|
{
|
|
name: "binance_get_orderbook",
|
|
description: "Obtiene order book con profundidad",
|
|
parameters: {
|
|
symbol: { type: "string", required: true },
|
|
limit: { type: "number", default: 20 }
|
|
},
|
|
risk: "low"
|
|
},
|
|
{
|
|
name: "binance_get_klines",
|
|
description: "Obtiene velas historicas",
|
|
parameters: {
|
|
symbol: { type: "string", required: true },
|
|
interval: { type: "string", enum: ["1m","5m","15m","1h","4h","1d"] },
|
|
limit: { type: "number", default: 100 }
|
|
},
|
|
risk: "low"
|
|
},
|
|
|
|
// Account Info (Read-only, bajo riesgo)
|
|
{
|
|
name: "binance_get_account",
|
|
description: "Obtiene balance y estado de cuenta",
|
|
parameters: {},
|
|
risk: "low"
|
|
},
|
|
{
|
|
name: "binance_get_positions",
|
|
description: "Obtiene posiciones abiertas (futures)",
|
|
parameters: {},
|
|
risk: "low"
|
|
},
|
|
|
|
// Order Management (Alto riesgo, requiere confirmacion)
|
|
{
|
|
name: "binance_create_order",
|
|
description: "Crea orden de mercado o limite",
|
|
parameters: {
|
|
symbol: { type: "string", required: true },
|
|
side: { type: "string", enum: ["BUY", "SELL"], required: true },
|
|
type: { type: "string", enum: ["MARKET", "LIMIT", "STOP_MARKET"] },
|
|
quantity: { type: "number", required: true },
|
|
price: { type: "number" },
|
|
stopPrice: { type: "number" }
|
|
},
|
|
risk: "high",
|
|
requiresConfirmation: true
|
|
},
|
|
{
|
|
name: "binance_cancel_order",
|
|
description: "Cancela orden pendiente",
|
|
parameters: {
|
|
symbol: { type: "string", required: true },
|
|
orderId: { type: "string", required: true }
|
|
},
|
|
risk: "medium"
|
|
},
|
|
{
|
|
name: "binance_close_position",
|
|
description: "Cierra posicion completa",
|
|
parameters: {
|
|
symbol: { type: "string", required: true }
|
|
},
|
|
risk: "high",
|
|
requiresConfirmation: true
|
|
}
|
|
];
|
|
```
|
|
|
|
### Orquestacion de MCP desde LLM
|
|
|
|
```python
|
|
# core/mcp_orchestrator.py
|
|
|
|
from typing import Dict, Any, List
|
|
import httpx
|
|
|
|
class MCPOrchestrator:
|
|
"""
|
|
Orquesta llamadas a multiples MCP servers
|
|
"""
|
|
|
|
def __init__(self, config: Dict):
|
|
self.mt4_url = config.get('mcp_mt4_url', 'http://localhost:3605')
|
|
self.binance_url = config.get('mcp_binance_url', 'http://localhost:3606')
|
|
|
|
async def call_tool(
|
|
self,
|
|
server: str, # "mt4" or "binance"
|
|
tool: str,
|
|
params: Dict
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Llama a una herramienta MCP
|
|
"""
|
|
url = self.mt4_url if server == "mt4" else self.binance_url
|
|
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
response = await client.post(
|
|
f"{url}/tools/{tool}",
|
|
json=params
|
|
)
|
|
return response.json()
|
|
|
|
async def get_combined_portfolio(self) -> Dict[str, Any]:
|
|
"""
|
|
Obtiene portfolio combinado de MT4 y Binance
|
|
"""
|
|
mt4_account = await self.call_tool("mt4", "mt4_get_account", {})
|
|
binance_account = await self.call_tool("binance", "binance_get_account", {})
|
|
|
|
return {
|
|
"mt4": mt4_account,
|
|
"binance": binance_account,
|
|
"total_equity": mt4_account.get("equity", 0) + binance_account.get("total_balance", 0)
|
|
}
|
|
|
|
async def execute_trade_on_best_venue(
|
|
self,
|
|
symbol: str,
|
|
action: str,
|
|
size: float,
|
|
stop_loss: float,
|
|
take_profit: float
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Ejecuta trade en el mejor venue disponible
|
|
"""
|
|
# Logica para determinar mejor venue
|
|
# MT4 para forex/metales, Binance para crypto
|
|
|
|
if symbol in ["XAUUSD", "EURUSD", "GBPUSD", "USDJPY"]:
|
|
return await self.call_tool("mt4", "mt4_execute_trade", {
|
|
"symbol": symbol,
|
|
"action": action,
|
|
"size": size,
|
|
"stop_loss": stop_loss,
|
|
"take_profit": take_profit
|
|
})
|
|
else:
|
|
return await self.call_tool("binance", "binance_create_order", {
|
|
"symbol": symbol,
|
|
"side": action,
|
|
"type": "MARKET",
|
|
"quantity": size
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Gestion de Riesgo
|
|
|
|
### Risk Manager Integrado
|
|
|
|
```python
|
|
# services/risk_manager.py
|
|
|
|
from typing import Dict, Any, Optional
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
|
|
class RiskLevel(Enum):
|
|
MINIMAL = "minimal" # 0.5% max per trade
|
|
CONSERVATIVE = "conservative" # 1% max per trade
|
|
MODERATE = "moderate" # 2% max per trade
|
|
AGGRESSIVE = "aggressive" # 3% max per trade
|
|
|
|
@dataclass
|
|
class RiskLimits:
|
|
max_position_size_pct: float # % del capital por posicion
|
|
max_daily_drawdown_pct: float # % max perdida diaria
|
|
max_total_exposure_pct: float # % max exposicion total
|
|
max_correlated_positions: int # Max posiciones correlacionadas
|
|
max_trades_per_day: int # Max trades por dia
|
|
|
|
class RiskManager:
|
|
"""
|
|
Gestor de riesgo integrado con el LLM Agent
|
|
"""
|
|
|
|
# Limites por nivel de riesgo
|
|
RISK_PROFILES = {
|
|
RiskLevel.MINIMAL: RiskLimits(
|
|
max_position_size_pct=0.5,
|
|
max_daily_drawdown_pct=1.0,
|
|
max_total_exposure_pct=5.0,
|
|
max_correlated_positions=2,
|
|
max_trades_per_day=3
|
|
),
|
|
RiskLevel.CONSERVATIVE: RiskLimits(
|
|
max_position_size_pct=1.0,
|
|
max_daily_drawdown_pct=2.0,
|
|
max_total_exposure_pct=10.0,
|
|
max_correlated_positions=3,
|
|
max_trades_per_day=5
|
|
),
|
|
RiskLevel.MODERATE: RiskLimits(
|
|
max_position_size_pct=2.0,
|
|
max_daily_drawdown_pct=5.0,
|
|
max_total_exposure_pct=20.0,
|
|
max_correlated_positions=5,
|
|
max_trades_per_day=10
|
|
),
|
|
RiskLevel.AGGRESSIVE: RiskLimits(
|
|
max_position_size_pct=3.0,
|
|
max_daily_drawdown_pct=10.0,
|
|
max_total_exposure_pct=30.0,
|
|
max_correlated_positions=8,
|
|
max_trades_per_day=20
|
|
)
|
|
}
|
|
|
|
def __init__(self, risk_level: RiskLevel, capital: float):
|
|
self.risk_level = risk_level
|
|
self.limits = self.RISK_PROFILES[risk_level]
|
|
self.capital = capital
|
|
self.daily_pnl = 0.0
|
|
self.current_exposure = 0.0
|
|
self.trades_today = 0
|
|
|
|
def can_open_position(
|
|
self,
|
|
position_size: float,
|
|
stop_loss_pips: float,
|
|
pip_value: float
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Verifica si se puede abrir una nueva posicion
|
|
"""
|
|
# Calcular riesgo de la posicion
|
|
position_risk = stop_loss_pips * pip_value * position_size
|
|
position_risk_pct = (position_risk / self.capital) * 100
|
|
|
|
checks = {
|
|
"position_size_ok": position_risk_pct <= self.limits.max_position_size_pct,
|
|
"daily_drawdown_ok": abs(self.daily_pnl) < self.limits.max_daily_drawdown_pct,
|
|
"exposure_ok": self.current_exposure + position_risk_pct <= self.limits.max_total_exposure_pct,
|
|
"trades_limit_ok": self.trades_today < self.limits.max_trades_per_day
|
|
}
|
|
|
|
return {
|
|
"allowed": all(checks.values()),
|
|
"checks": checks,
|
|
"position_risk_pct": position_risk_pct,
|
|
"recommended_size": self._calculate_safe_size(stop_loss_pips, pip_value)
|
|
}
|
|
|
|
def _calculate_safe_size(
|
|
self,
|
|
stop_loss_pips: float,
|
|
pip_value: float
|
|
) -> float:
|
|
"""
|
|
Calcula tamano de posicion seguro basado en limites
|
|
"""
|
|
max_risk = self.capital * (self.limits.max_position_size_pct / 100)
|
|
safe_size = max_risk / (stop_loss_pips * pip_value)
|
|
return round(safe_size, 2)
|
|
|
|
def check_circuit_breaker(self) -> Dict[str, Any]:
|
|
"""
|
|
Verifica si se activa el circuit breaker
|
|
"""
|
|
daily_loss_pct = abs(self.daily_pnl)
|
|
|
|
if daily_loss_pct >= self.limits.max_daily_drawdown_pct:
|
|
return {
|
|
"triggered": True,
|
|
"reason": "daily_drawdown_limit",
|
|
"message": f"Se alcanzo el limite de perdida diaria ({daily_loss_pct:.2f}%)",
|
|
"action": "STOP_TRADING"
|
|
}
|
|
|
|
if daily_loss_pct >= self.limits.max_daily_drawdown_pct * 0.8:
|
|
return {
|
|
"triggered": False,
|
|
"warning": True,
|
|
"message": f"Cerca del limite de perdida diaria ({daily_loss_pct:.2f}%)",
|
|
"action": "REDUCE_RISK"
|
|
}
|
|
|
|
return {"triggered": False, "warning": False}
|
|
```
|
|
|
|
### Reglas de Riesgo para LLM
|
|
|
|
```python
|
|
# prompts/risk_rules.py
|
|
|
|
RISK_RULES_PROMPT = """
|
|
## REGLAS DE GESTION DE RIESGO (OBLIGATORIAS)
|
|
|
|
Como Trading Agent, DEBES seguir estas reglas de riesgo SIEMPRE:
|
|
|
|
### 1. TAMANO DE POSICION
|
|
- NUNCA arriesgar mas del {max_position_pct}% del capital en una operacion
|
|
- Calcular tamano basado en distancia al stop loss
|
|
- Formula: Size = (Capital * Risk%) / (StopLoss_pips * Pip_value)
|
|
|
|
### 2. DRAWDOWN DIARIO
|
|
- Limite de perdida diaria: {max_daily_dd}%
|
|
- Si se alcanza, DETENER operaciones por el resto del dia
|
|
- Alertar al usuario cuando llegue al 80% del limite
|
|
|
|
### 3. EXPOSICION TOTAL
|
|
- Maximo {max_exposure}% del capital expuesto simultaneamente
|
|
- Incluye todas las posiciones abiertas
|
|
- Considerar correlacion entre pares
|
|
|
|
### 4. STOP LOSS OBLIGATORIO
|
|
- TODA operacion DEBE tener stop loss definido
|
|
- NUNCA mover stop loss hacia atras (aumentar riesgo)
|
|
- Solo mover a breakeven o trailing
|
|
|
|
### 5. CORRELACION
|
|
- Maximo {max_correlated} posiciones correlacionadas
|
|
- EUR/USD y GBP/USD se consideran correlacionados
|
|
- BTC y ETH se consideran correlacionados
|
|
|
|
### 6. CIRCUIT BREAKER
|
|
- Despues de {max_consecutive_losses} perdidas consecutivas: pausa 1 hora
|
|
- Despues de alcanzar daily DD: stop hasta manana
|
|
- KILL SWITCH manual siempre disponible
|
|
|
|
### FORMATO DE RESPUESTA CUANDO SE EJECUTA TRADE:
|
|
|
|
**VALIDACION DE RIESGO:**
|
|
- Tamano posicion: {size} lots = {risk_pct}% del capital
|
|
- Exposicion actual: {current_exp}% -> {new_exp}%
|
|
- Trades hoy: {trades_today} de {max_trades}
|
|
- Estado: [APROBADO/RECHAZADO]
|
|
"""
|
|
```
|
|
|
|
---
|
|
|
|
## Analisis de Predicciones ML
|
|
|
|
### Integracion ML Engine
|
|
|
|
```python
|
|
# services/ml_analyzer.py
|
|
|
|
from typing import Dict, Any, List
|
|
import httpx
|
|
|
|
class MLAnalyzer:
|
|
"""
|
|
Analiza y procesa predicciones del ML Engine
|
|
"""
|
|
|
|
def __init__(self, ml_engine_url: str = "http://localhost:3083"):
|
|
self.ml_engine_url = ml_engine_url
|
|
|
|
async def get_full_analysis(self, symbol: str) -> Dict[str, Any]:
|
|
"""
|
|
Obtiene analisis completo de todos los modelos ML
|
|
"""
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
# Obtener predicciones de todos los modelos
|
|
tasks = [
|
|
client.get(f"{self.ml_engine_url}/api/amd_phase", params={"symbol": symbol}),
|
|
client.get(f"{self.ml_engine_url}/api/range", params={"symbol": symbol}),
|
|
client.get(f"{self.ml_engine_url}/api/signal", params={"symbol": symbol}),
|
|
client.get(f"{self.ml_engine_url}/api/ict_context", params={"symbol": symbol}),
|
|
]
|
|
|
|
responses = await asyncio.gather(*tasks)
|
|
|
|
return {
|
|
"symbol": symbol,
|
|
"amd_phase": responses[0].json(),
|
|
"range_prediction": responses[1].json(),
|
|
"signal": responses[2].json(),
|
|
"ict_context": responses[3].json(),
|
|
"confluence_score": self._calculate_confluence(responses)
|
|
}
|
|
|
|
def _calculate_confluence(self, predictions: List) -> float:
|
|
"""
|
|
Calcula score de confluencia entre modelos
|
|
"""
|
|
amd = predictions[0].json()
|
|
signal = predictions[2].json()
|
|
ict = predictions[3].json()
|
|
|
|
score = 0.0
|
|
|
|
# AMD en fase favorable
|
|
if amd.get("phase") in ["accumulation", "re_accumulation"]:
|
|
if signal.get("direction") == "LONG":
|
|
score += 0.3
|
|
elif amd.get("phase") == "distribution":
|
|
if signal.get("direction") == "SHORT":
|
|
score += 0.3
|
|
|
|
# Killzone activa
|
|
if ict.get("killzone_active"):
|
|
score += 0.2
|
|
|
|
# OTE zone correcta
|
|
if ict.get("ote_zone") == "discount" and signal.get("direction") == "LONG":
|
|
score += 0.2
|
|
elif ict.get("ote_zone") == "premium" and signal.get("direction") == "SHORT":
|
|
score += 0.2
|
|
|
|
# Confianza del modelo
|
|
signal_confidence = signal.get("confidence", 0)
|
|
score += signal_confidence * 0.3
|
|
|
|
return min(score, 1.0)
|
|
|
|
def generate_explanation(self, analysis: Dict[str, Any]) -> str:
|
|
"""
|
|
Genera explicacion en lenguaje natural del analisis
|
|
"""
|
|
amd = analysis["amd_phase"]
|
|
signal = analysis["signal"]
|
|
ict = analysis["ict_context"]
|
|
confluence = analysis["confluence_score"]
|
|
|
|
explanation = f"""
|
|
## Analisis ML para {analysis["symbol"]}
|
|
|
|
### 1. Fase AMD
|
|
- Fase actual: **{amd.get("phase", "N/A")}** ({amd.get("confidence", 0)*100:.1f}% confianza)
|
|
- Interpretacion: {self._explain_amd_phase(amd.get("phase"))}
|
|
|
|
### 2. Senal de Trading
|
|
- Direccion: **{signal.get("direction", "N/A")}**
|
|
- Confianza: {signal.get("confidence", 0)*100:.1f}%
|
|
- Entry: {signal.get("entry_price", "N/A")}
|
|
- Stop Loss: {signal.get("stop_loss", "N/A")}
|
|
- Take Profit: {signal.get("take_profit", "N/A")}
|
|
|
|
### 3. Contexto ICT/SMC
|
|
- Killzone: {ict.get("killzone", "None")} {"(ACTIVA)" if ict.get("killzone_active") else ""}
|
|
- Zona OTE: {ict.get("ote_zone", "N/A")}
|
|
- FVG detectado: {"Si" if ict.get("fvg_present") else "No"}
|
|
|
|
### 4. Score de Confluencia
|
|
**{confluence*100:.0f}%** - {"Alta confluencia, senal fuerte" if confluence > 0.7 else "Confluencia media" if confluence > 0.5 else "Baja confluencia, precaucion"}
|
|
"""
|
|
return explanation
|
|
|
|
def _explain_amd_phase(self, phase: str) -> str:
|
|
explanations = {
|
|
"accumulation": "Las instituciones estan acumulando. Favorable para LONG.",
|
|
"manipulation": "Fase de caza de stops. EVITAR nuevas entradas.",
|
|
"distribution": "Las instituciones estan distribuyendo. Favorable para SHORT.",
|
|
"re_accumulation": "Consolidacion antes de continuacion alcista."
|
|
}
|
|
return explanations.get(phase, "Fase no identificada")
|
|
```
|
|
|
|
---
|
|
|
|
## API para Frontend
|
|
|
|
### Endpoints de Predicciones
|
|
|
|
```python
|
|
# api/predictions.py
|
|
|
|
from fastapi import APIRouter, HTTPException, Depends
|
|
from pydantic import BaseModel
|
|
from typing import List, Optional
|
|
|
|
router = APIRouter(prefix="/api/v1/predictions", tags=["predictions"])
|
|
|
|
class PredictionRequest(BaseModel):
|
|
symbol: str
|
|
timeframe: str = "5m"
|
|
|
|
class PredictionResponse(BaseModel):
|
|
symbol: str
|
|
timestamp: str
|
|
amd_phase: dict
|
|
signal: dict
|
|
range_prediction: dict
|
|
ict_context: dict
|
|
confluence_score: float
|
|
explanation: str
|
|
risk_assessment: dict
|
|
|
|
class HistoricalPrediction(BaseModel):
|
|
id: str
|
|
symbol: str
|
|
timestamp: str
|
|
prediction: dict
|
|
outcome: Optional[dict] = None
|
|
accuracy: Optional[float] = None
|
|
|
|
@router.post("/analyze", response_model=PredictionResponse)
|
|
async def analyze_symbol(request: PredictionRequest):
|
|
"""
|
|
Analiza un simbolo con todos los modelos ML
|
|
y retorna prediccion con explicacion
|
|
"""
|
|
ml_analyzer = MLAnalyzer()
|
|
analysis = await ml_analyzer.get_full_analysis(request.symbol)
|
|
explanation = ml_analyzer.generate_explanation(analysis)
|
|
|
|
return PredictionResponse(
|
|
symbol=request.symbol,
|
|
timestamp=datetime.utcnow().isoformat(),
|
|
amd_phase=analysis["amd_phase"],
|
|
signal=analysis["signal"],
|
|
range_prediction=analysis["range_prediction"],
|
|
ict_context=analysis["ict_context"],
|
|
confluence_score=analysis["confluence_score"],
|
|
explanation=explanation,
|
|
risk_assessment=await get_risk_assessment(request.symbol, analysis)
|
|
)
|
|
|
|
@router.get("/history/{symbol}", response_model=List[HistoricalPrediction])
|
|
async def get_prediction_history(
|
|
symbol: str,
|
|
limit: int = 50,
|
|
include_outcomes: bool = True
|
|
):
|
|
"""
|
|
Obtiene historial de predicciones para un simbolo
|
|
"""
|
|
predictions = await prediction_repository.get_history(
|
|
symbol=symbol,
|
|
limit=limit,
|
|
include_outcomes=include_outcomes
|
|
)
|
|
return predictions
|
|
|
|
@router.get("/accuracy/{symbol}")
|
|
async def get_model_accuracy(symbol: str, days: int = 30):
|
|
"""
|
|
Obtiene metricas de accuracy del modelo para un simbolo
|
|
"""
|
|
return await prediction_repository.get_accuracy_metrics(symbol, days)
|
|
|
|
@router.get("/active-signals")
|
|
async def get_active_signals():
|
|
"""
|
|
Obtiene todas las senales activas con confluencia > 60%
|
|
"""
|
|
return await prediction_repository.get_active_signals(min_confluence=0.6)
|
|
```
|
|
|
|
### WebSocket para Predicciones en Tiempo Real
|
|
|
|
```python
|
|
# api/websocket.py
|
|
|
|
from fastapi import WebSocket, WebSocketDisconnect
|
|
from typing import Dict, Set
|
|
import asyncio
|
|
import json
|
|
|
|
class PredictionWebSocketManager:
|
|
def __init__(self):
|
|
self.connections: Dict[str, Set[WebSocket]] = {}
|
|
|
|
async def connect(self, websocket: WebSocket, symbol: str):
|
|
await websocket.accept()
|
|
if symbol not in self.connections:
|
|
self.connections[symbol] = set()
|
|
self.connections[symbol].add(websocket)
|
|
|
|
def disconnect(self, websocket: WebSocket, symbol: str):
|
|
if symbol in self.connections:
|
|
self.connections[symbol].discard(websocket)
|
|
|
|
async def broadcast_prediction(self, symbol: str, prediction: dict):
|
|
if symbol in self.connections:
|
|
message = json.dumps(prediction)
|
|
for connection in self.connections[symbol]:
|
|
try:
|
|
await connection.send_text(message)
|
|
except:
|
|
self.disconnect(connection, symbol)
|
|
|
|
manager = PredictionWebSocketManager()
|
|
|
|
@router.websocket("/ws/predictions/{symbol}")
|
|
async def prediction_websocket(websocket: WebSocket, symbol: str):
|
|
await manager.connect(websocket, symbol)
|
|
try:
|
|
while True:
|
|
# Enviar prediccion cada 5 segundos
|
|
prediction = await ml_analyzer.get_full_analysis(symbol)
|
|
await websocket.send_json(prediction)
|
|
await asyncio.sleep(5)
|
|
except WebSocketDisconnect:
|
|
manager.disconnect(websocket, symbol)
|
|
```
|
|
|
|
---
|
|
|
|
## Persistencia en PostgreSQL
|
|
|
|
### Nuevas Tablas
|
|
|
|
```sql
|
|
-- Schema: ml (agregar a existente)
|
|
|
|
-- Tabla de predicciones del LLM Agent
|
|
CREATE TABLE IF NOT EXISTS ml.llm_predictions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
symbol VARCHAR(20) NOT NULL,
|
|
timeframe VARCHAR(10) NOT NULL,
|
|
|
|
-- Prediccion
|
|
amd_phase VARCHAR(50),
|
|
amd_confidence DECIMAL(5,4),
|
|
signal_direction VARCHAR(10),
|
|
signal_confidence DECIMAL(5,4),
|
|
entry_price DECIMAL(20,8),
|
|
stop_loss DECIMAL(20,8),
|
|
take_profit DECIMAL(20,8),
|
|
|
|
-- Contexto ICT
|
|
killzone VARCHAR(50),
|
|
ote_zone VARCHAR(20),
|
|
|
|
-- Confluencia
|
|
confluence_score DECIMAL(5,4),
|
|
|
|
-- Explicacion generada por LLM
|
|
explanation TEXT,
|
|
|
|
-- Metadata
|
|
model_version VARCHAR(50),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
CONSTRAINT chk_signal_direction CHECK (signal_direction IN ('LONG', 'SHORT', 'HOLD'))
|
|
);
|
|
|
|
-- Indice para busquedas rapidas
|
|
CREATE INDEX idx_llm_predictions_symbol_time ON ml.llm_predictions(symbol, created_at DESC);
|
|
|
|
-- Tabla de outcomes para tracking de accuracy
|
|
CREATE TABLE IF NOT EXISTS ml.prediction_outcomes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
prediction_id UUID REFERENCES ml.llm_predictions(id),
|
|
|
|
-- Resultado real
|
|
actual_direction VARCHAR(10),
|
|
actual_high DECIMAL(20,8),
|
|
actual_low DECIMAL(20,8),
|
|
|
|
-- Metricas de precision
|
|
direction_correct BOOLEAN,
|
|
target_reached BOOLEAN,
|
|
stop_hit BOOLEAN,
|
|
pnl_pips DECIMAL(10,2),
|
|
pnl_percentage DECIMAL(10,4),
|
|
|
|
-- Tiempo de resolucion
|
|
resolved_at TIMESTAMPTZ,
|
|
resolution_candles INT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Tabla de decisiones del LLM Agent
|
|
CREATE TABLE IF NOT EXISTS ml.llm_decisions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
prediction_id UUID REFERENCES ml.llm_predictions(id),
|
|
|
|
-- Decision
|
|
decision_type VARCHAR(50), -- 'TRADE', 'ALERT', 'WAIT', 'CLOSE'
|
|
action_taken VARCHAR(50),
|
|
reasoning TEXT,
|
|
|
|
-- Risk assessment
|
|
risk_level VARCHAR(20),
|
|
position_size DECIMAL(10,4),
|
|
risk_pct DECIMAL(5,4),
|
|
|
|
-- Execution
|
|
executed BOOLEAN DEFAULT FALSE,
|
|
execution_venue VARCHAR(20), -- 'MT4', 'BINANCE'
|
|
order_id VARCHAR(100),
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Tabla de eventos de riesgo
|
|
CREATE TABLE IF NOT EXISTS ml.risk_events (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID,
|
|
|
|
event_type VARCHAR(50), -- 'CIRCUIT_BREAKER', 'DAILY_LIMIT', 'EXPOSURE_LIMIT'
|
|
severity VARCHAR(20),
|
|
details JSONB,
|
|
|
|
action_taken VARCHAR(100),
|
|
resolved BOOLEAN DEFAULT FALSE,
|
|
resolved_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Funcion para calcular accuracy
|
|
CREATE OR REPLACE FUNCTION ml.calculate_prediction_accuracy(
|
|
p_symbol VARCHAR,
|
|
p_days INT DEFAULT 30
|
|
)
|
|
RETURNS TABLE(
|
|
total_predictions INT,
|
|
direction_accuracy DECIMAL,
|
|
target_hit_rate DECIMAL,
|
|
avg_pnl_pips DECIMAL,
|
|
profit_factor DECIMAL
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
COUNT(*)::INT as total_predictions,
|
|
AVG(CASE WHEN o.direction_correct THEN 1 ELSE 0 END)::DECIMAL as direction_accuracy,
|
|
AVG(CASE WHEN o.target_reached THEN 1 ELSE 0 END)::DECIMAL as target_hit_rate,
|
|
AVG(o.pnl_pips)::DECIMAL as avg_pnl_pips,
|
|
CASE
|
|
WHEN SUM(CASE WHEN o.pnl_pips < 0 THEN ABS(o.pnl_pips) ELSE 0 END) > 0
|
|
THEN SUM(CASE WHEN o.pnl_pips > 0 THEN o.pnl_pips ELSE 0 END) /
|
|
SUM(CASE WHEN o.pnl_pips < 0 THEN ABS(o.pnl_pips) ELSE 0 END)
|
|
ELSE 0
|
|
END::DECIMAL as profit_factor
|
|
FROM ml.llm_predictions p
|
|
JOIN ml.prediction_outcomes o ON p.id = o.prediction_id
|
|
WHERE p.symbol = p_symbol
|
|
AND p.created_at >= NOW() - (p_days || ' days')::INTERVAL;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMENT ON TABLE ml.llm_predictions IS 'Predicciones generadas por el LLM Trading Agent';
|
|
COMMENT ON TABLE ml.prediction_outcomes IS 'Resultados reales de las predicciones para tracking de accuracy';
|
|
COMMENT ON TABLE ml.llm_decisions IS 'Decisiones tomadas por el LLM Agent';
|
|
COMMENT ON TABLE ml.risk_events IS 'Eventos de gestion de riesgo';
|
|
```
|
|
|
|
### Repository Pattern
|
|
|
|
```python
|
|
# repositories/prediction_repository.py
|
|
|
|
from typing import List, Optional, Dict, Any
|
|
import asyncpg
|
|
from datetime import datetime, timedelta
|
|
|
|
class PredictionRepository:
|
|
def __init__(self, pool: asyncpg.Pool):
|
|
self.pool = pool
|
|
|
|
async def save_prediction(self, prediction: Dict[str, Any]) -> str:
|
|
"""Guarda una nueva prediccion"""
|
|
query = """
|
|
INSERT INTO ml.llm_predictions (
|
|
symbol, timeframe, amd_phase, amd_confidence,
|
|
signal_direction, signal_confidence, entry_price,
|
|
stop_loss, take_profit, killzone, ote_zone,
|
|
confluence_score, explanation, model_version
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
|
RETURNING id
|
|
"""
|
|
async with self.pool.acquire() as conn:
|
|
row = await conn.fetchrow(query,
|
|
prediction["symbol"],
|
|
prediction["timeframe"],
|
|
prediction.get("amd_phase", {}).get("phase"),
|
|
prediction.get("amd_phase", {}).get("confidence"),
|
|
prediction.get("signal", {}).get("direction"),
|
|
prediction.get("signal", {}).get("confidence"),
|
|
prediction.get("signal", {}).get("entry_price"),
|
|
prediction.get("signal", {}).get("stop_loss"),
|
|
prediction.get("signal", {}).get("take_profit"),
|
|
prediction.get("ict_context", {}).get("killzone"),
|
|
prediction.get("ict_context", {}).get("ote_zone"),
|
|
prediction.get("confluence_score"),
|
|
prediction.get("explanation"),
|
|
prediction.get("model_version", "v1.0")
|
|
)
|
|
return str(row["id"])
|
|
|
|
async def save_outcome(self, prediction_id: str, outcome: Dict[str, Any]):
|
|
"""Guarda el resultado de una prediccion"""
|
|
query = """
|
|
INSERT INTO ml.prediction_outcomes (
|
|
prediction_id, actual_direction, actual_high, actual_low,
|
|
direction_correct, target_reached, stop_hit,
|
|
pnl_pips, pnl_percentage, resolved_at, resolution_candles
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
"""
|
|
async with self.pool.acquire() as conn:
|
|
await conn.execute(query,
|
|
prediction_id,
|
|
outcome["actual_direction"],
|
|
outcome["actual_high"],
|
|
outcome["actual_low"],
|
|
outcome["direction_correct"],
|
|
outcome["target_reached"],
|
|
outcome["stop_hit"],
|
|
outcome["pnl_pips"],
|
|
outcome["pnl_percentage"],
|
|
outcome.get("resolved_at", datetime.utcnow()),
|
|
outcome.get("resolution_candles", 0)
|
|
)
|
|
|
|
async def get_history(
|
|
self,
|
|
symbol: str,
|
|
limit: int = 50,
|
|
include_outcomes: bool = True
|
|
) -> List[Dict]:
|
|
"""Obtiene historial de predicciones"""
|
|
query = """
|
|
SELECT
|
|
p.*,
|
|
o.direction_correct,
|
|
o.target_reached,
|
|
o.pnl_pips
|
|
FROM ml.llm_predictions p
|
|
LEFT JOIN ml.prediction_outcomes o ON p.id = o.prediction_id
|
|
WHERE p.symbol = $1
|
|
ORDER BY p.created_at DESC
|
|
LIMIT $2
|
|
"""
|
|
async with self.pool.acquire() as conn:
|
|
rows = await conn.fetch(query, symbol, limit)
|
|
return [dict(row) for row in rows]
|
|
|
|
async def get_accuracy_metrics(
|
|
self,
|
|
symbol: str,
|
|
days: int = 30
|
|
) -> Dict[str, Any]:
|
|
"""Obtiene metricas de accuracy"""
|
|
async with self.pool.acquire() as conn:
|
|
row = await conn.fetchrow(
|
|
"SELECT * FROM ml.calculate_prediction_accuracy($1, $2)",
|
|
symbol, days
|
|
)
|
|
return dict(row) if row else {}
|
|
```
|
|
|
|
---
|
|
|
|
## Pipeline de Fine-Tuning
|
|
|
|
### Generacion de Dataset
|
|
|
|
```python
|
|
# fine_tuning/generate_dataset.py
|
|
|
|
import json
|
|
from typing import List, Dict
|
|
import asyncpg
|
|
|
|
class DatasetGenerator:
|
|
"""
|
|
Genera dataset de fine-tuning a partir de:
|
|
1. Predicciones historicas con outcomes
|
|
2. Decisiones correctas del sistema
|
|
3. Reglas de trading documentadas
|
|
"""
|
|
|
|
async def generate_from_predictions(
|
|
self,
|
|
pool: asyncpg.Pool,
|
|
min_accuracy: float = 0.7
|
|
) -> List[Dict]:
|
|
"""
|
|
Genera ejemplos de predicciones exitosas
|
|
"""
|
|
query = """
|
|
SELECT
|
|
p.symbol, p.amd_phase, p.amd_confidence,
|
|
p.signal_direction, p.signal_confidence,
|
|
p.killzone, p.ote_zone, p.confluence_score,
|
|
o.direction_correct, o.target_reached, o.pnl_pips
|
|
FROM ml.llm_predictions p
|
|
JOIN ml.prediction_outcomes o ON p.id = o.prediction_id
|
|
WHERE o.direction_correct = true
|
|
AND p.confluence_score >= $1
|
|
"""
|
|
|
|
async with pool.acquire() as conn:
|
|
rows = await conn.fetch(query, min_accuracy)
|
|
|
|
examples = []
|
|
for row in rows:
|
|
example = self._create_training_example(dict(row))
|
|
examples.append(example)
|
|
|
|
return examples
|
|
|
|
def _create_training_example(self, prediction: Dict) -> Dict:
|
|
"""Crea ejemplo de entrenamiento en formato instruction/input/output"""
|
|
|
|
instruction = "Analiza esta senal de trading y proporciona tu recomendacion"
|
|
|
|
input_text = f"""
|
|
Simbolo: {prediction['symbol']}
|
|
Fase AMD: {prediction['amd_phase']} ({prediction['amd_confidence']*100:.0f}% confianza)
|
|
Senal: {prediction['signal_direction']} ({prediction['signal_confidence']*100:.0f}%)
|
|
Killzone: {prediction['killzone']}
|
|
Zona OTE: {prediction['ote_zone']}
|
|
Confluencia: {prediction['confluence_score']*100:.0f}%
|
|
"""
|
|
|
|
output_text = self._generate_analysis_output(prediction)
|
|
|
|
return {
|
|
"instruction": instruction,
|
|
"input": input_text.strip(),
|
|
"output": output_text
|
|
}
|
|
|
|
def generate_from_strategy_docs(self, docs_path: str) -> List[Dict]:
|
|
"""
|
|
Genera ejemplos a partir de documentacion de estrategias
|
|
"""
|
|
examples = []
|
|
|
|
# AMD Phase examples
|
|
examples.extend(self._amd_examples())
|
|
|
|
# ICT/SMC examples
|
|
examples.extend(self._ict_examples())
|
|
|
|
# Risk management examples
|
|
examples.extend(self._risk_examples())
|
|
|
|
return examples
|
|
|
|
def _amd_examples(self) -> List[Dict]:
|
|
return [
|
|
{
|
|
"instruction": "Explica que es la fase de Accumulation en AMD",
|
|
"input": "",
|
|
"output": """# Fase de Accumulation (Acumulacion)
|
|
|
|
## Definicion
|
|
La fase de Accumulation es cuando las instituciones (smart money) estan comprando activamente mientras el precio se mantiene en un rango. Es la primera fase del ciclo AMD.
|
|
|
|
## Caracteristicas
|
|
1. **Precio**: Se mueve lateralmente o con leve tendencia alcista
|
|
2. **Volumen**: Incrementa en impulsos alcistas, decrece en retrocesos
|
|
3. **Estructura**: Minimos mas altos dentro del rango
|
|
4. **Duracion**: Puede durar dias o semanas
|
|
|
|
## Como Tradear
|
|
- **Direccion preferida**: LONG
|
|
- **Entrada**: En retrocesos hacia la parte baja del rango
|
|
- **Stop Loss**: Debajo del ultimo minimo significativo
|
|
- **Take Profit**: Parte alta del rango o ruptura
|
|
|
|
## Senales de Confirmacion
|
|
- Rechazo de precios bajos con volumen
|
|
- Velas de absorcion en soporte
|
|
- RSI formando divergencias alcistas
|
|
- MACD cruzando hacia arriba
|
|
|
|
## Transicion
|
|
La fase de Accumulation termina cuando:
|
|
1. El precio rompe la resistencia con volumen
|
|
2. Comienza la fase de Markup (tendencia alcista)
|
|
3. O inicia fase de Manipulation (falsa ruptura)"""
|
|
},
|
|
{
|
|
"instruction": "Que hacer cuando se detecta fase de Manipulation",
|
|
"input": "El mercado muestra fase de Manipulation con 85% de confianza",
|
|
"output": """# ALERTA: Fase de Manipulation Detectada
|
|
|
|
## Accion Recomendada: NO OPERAR
|
|
|
|
La fase de Manipulation es la mas peligrosa para traders retail. Las instituciones estan barriendo liquidez (stops) antes del movimiento real.
|
|
|
|
## Porque NO Operar
|
|
1. **Alta probabilidad de stops**: Las instituciones buscan liquidez en zonas obvias
|
|
2. **Movimientos erraticos**: El precio puede ir en ambas direcciones rapidamente
|
|
3. **Senales falsas**: Los indicadores tecnicos generan senales contradictorias
|
|
4. **Volumen enganoso**: Spikes de volumen que no indican direccion real
|
|
|
|
## Que Esperar
|
|
- Transicion a Distribution (movimiento bajista)
|
|
- O transicion a Re-accumulation (continuacion alcista)
|
|
- Tiempo estimado: 1-4 horas en timeframes de 5min
|
|
|
|
## Si Tienes Posiciones Abiertas
|
|
1. Mover stop loss a breakeven si es posible
|
|
2. Considerar cierre parcial (50%)
|
|
3. NO agregar a la posicion
|
|
4. Estar listo para cierre manual si es necesario
|
|
|
|
## Siguiente Paso
|
|
Esperar confirmacion de nueva fase con:
|
|
- Cambio en estructura de mercado
|
|
- Volumen direccional claro
|
|
- Confluencia de indicadores"""
|
|
}
|
|
]
|
|
|
|
def save_dataset(self, examples: List[Dict], output_path: str):
|
|
"""Guarda dataset en formato JSONL"""
|
|
with open(output_path, 'w') as f:
|
|
for example in examples:
|
|
f.write(json.dumps(example, ensure_ascii=False) + '\n')
|
|
```
|
|
|
|
### Script de Entrenamiento
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# fine_tuning/train.sh
|
|
|
|
# Configuracion
|
|
MODEL_NAME="meta-llama/Meta-Llama-3-8B-Instruct"
|
|
OUTPUT_DIR="./trading-llm-finetuned"
|
|
DATASET_PATH="./data/trading_strategies.jsonl"
|
|
|
|
# Verificar GPU
|
|
nvidia-smi
|
|
|
|
# Activar ambiente
|
|
source activate trading-llm
|
|
|
|
# Ejecutar entrenamiento
|
|
python train_lora.py \
|
|
--model_name $MODEL_NAME \
|
|
--dataset_path $DATASET_PATH \
|
|
--output_dir $OUTPUT_DIR \
|
|
--num_epochs 3 \
|
|
--batch_size 4 \
|
|
--learning_rate 2e-4 \
|
|
--lora_r 16 \
|
|
--lora_alpha 32 \
|
|
--max_length 2048 \
|
|
--fp16 \
|
|
--gradient_checkpointing
|
|
|
|
# Merge LoRA weights
|
|
python merge_lora.py \
|
|
--base_model $MODEL_NAME \
|
|
--lora_weights $OUTPUT_DIR \
|
|
--output_dir "${OUTPUT_DIR}-merged"
|
|
|
|
# Convertir a GGUF para Ollama
|
|
python convert_to_gguf.py \
|
|
--model_path "${OUTPUT_DIR}-merged" \
|
|
--output_path "${OUTPUT_DIR}.gguf" \
|
|
--quantization q5_K_M
|
|
|
|
echo "Fine-tuning completado. Modelo en: ${OUTPUT_DIR}.gguf"
|
|
```
|
|
|
|
---
|
|
|
|
## Implementacion
|
|
|
|
### Docker Compose Completo
|
|
|
|
```yaml
|
|
# docker-compose.llm-advanced.yaml
|
|
|
|
version: '3.8'
|
|
|
|
services:
|
|
# Ollama con modelo fine-tuned
|
|
ollama:
|
|
image: ollama/ollama:latest
|
|
container_name: trading-ollama
|
|
ports:
|
|
- "11434:11434"
|
|
volumes:
|
|
- ollama_data:/root/.ollama
|
|
- ./models:/models # Para modelos custom
|
|
deploy:
|
|
resources:
|
|
reservations:
|
|
devices:
|
|
- driver: nvidia
|
|
count: 1
|
|
capabilities: [gpu]
|
|
environment:
|
|
- OLLAMA_MODELS=/models
|
|
restart: unless-stopped
|
|
|
|
# LLM Agent Service
|
|
llm-agent:
|
|
build:
|
|
context: ./apps/llm-agent
|
|
dockerfile: Dockerfile
|
|
container_name: trading-llm-agent
|
|
ports:
|
|
- "3085:3085"
|
|
environment:
|
|
- OLLAMA_URL=http://ollama:11434
|
|
- LLM_MODEL=trading-trading:latest # Modelo fine-tuned
|
|
- REDIS_URL=redis://redis:6379
|
|
- DATABASE_URL=postgresql://user:pass@postgres:5432/trading
|
|
- ML_ENGINE_URL=http://ml-engine:3083
|
|
- MCP_MT4_URL=http://mcp-mt4:3605
|
|
- MCP_BINANCE_URL=http://mcp-binance:3606
|
|
depends_on:
|
|
- ollama
|
|
- redis
|
|
- postgres
|
|
restart: unless-stopped
|
|
|
|
# MCP MT4 Connector
|
|
mcp-mt4:
|
|
build:
|
|
context: ./apps/mcp-mt4-connector
|
|
dockerfile: Dockerfile
|
|
container_name: trading-mcp-mt4
|
|
ports:
|
|
- "3605:3605"
|
|
environment:
|
|
- MT4_GATEWAY_HOST=${MT4_GATEWAY_HOST}
|
|
- MT4_GATEWAY_TOKEN=${MT4_GATEWAY_TOKEN}
|
|
restart: unless-stopped
|
|
|
|
# MCP Binance Connector (NUEVO)
|
|
mcp-binance:
|
|
build:
|
|
context: ./apps/mcp-binance-connector
|
|
dockerfile: Dockerfile
|
|
container_name: trading-mcp-binance
|
|
ports:
|
|
- "3606:3606"
|
|
environment:
|
|
- BINANCE_API_KEY=${BINANCE_API_KEY}
|
|
- BINANCE_API_SECRET=${BINANCE_API_SECRET}
|
|
- BINANCE_TESTNET=${BINANCE_TESTNET:-true}
|
|
restart: unless-stopped
|
|
|
|
# ML Engine
|
|
ml-engine:
|
|
build:
|
|
context: ./apps/ml-engine
|
|
dockerfile: Dockerfile
|
|
container_name: trading-ml-engine
|
|
ports:
|
|
- "3083:3083"
|
|
environment:
|
|
- DATABASE_URL=postgresql://user:pass@postgres:5432/trading
|
|
- REDIS_URL=redis://redis:6379
|
|
volumes:
|
|
- ml_models:/app/models
|
|
restart: unless-stopped
|
|
|
|
# Redis
|
|
redis:
|
|
image: redis:7-alpine
|
|
container_name: trading-redis
|
|
ports:
|
|
- "6379:6379"
|
|
volumes:
|
|
- redis_data:/data
|
|
restart: unless-stopped
|
|
|
|
# PostgreSQL
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
container_name: trading-postgres
|
|
ports:
|
|
- "5432:5432"
|
|
environment:
|
|
- POSTGRES_USER=user
|
|
- POSTGRES_PASSWORD=pass
|
|
- POSTGRES_DB=trading
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
ollama_data:
|
|
redis_data:
|
|
postgres_data:
|
|
ml_models:
|
|
```
|
|
|
|
### Script de Inicializacion
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# scripts/init_llm_system.sh
|
|
|
|
echo "=== Trading Platform LLM Trading System Setup ==="
|
|
|
|
# 1. Verificar GPU
|
|
echo "[1/6] Verificando GPU..."
|
|
nvidia-smi || { echo "Error: GPU no detectada"; exit 1; }
|
|
|
|
# 2. Iniciar servicios base
|
|
echo "[2/6] Iniciando servicios base..."
|
|
docker-compose -f docker-compose.llm-advanced.yaml up -d postgres redis
|
|
|
|
# 3. Esperar a que postgres este listo
|
|
echo "[3/6] Esperando PostgreSQL..."
|
|
sleep 10
|
|
docker exec trading-postgres pg_isready
|
|
|
|
# 4. Ejecutar migraciones
|
|
echo "[4/6] Ejecutando migraciones SQL..."
|
|
docker exec -i trading-postgres psql -U user -d trading < ./apps/database/ddl/schemas/ml/llm_predictions.sql
|
|
|
|
# 5. Iniciar Ollama y cargar modelo
|
|
echo "[5/6] Iniciando Ollama..."
|
|
docker-compose -f docker-compose.llm-advanced.yaml up -d ollama
|
|
sleep 10
|
|
|
|
# Verificar si existe modelo fine-tuned
|
|
if [ -f "./models/trading-trading.gguf" ]; then
|
|
echo "Cargando modelo fine-tuned..."
|
|
docker exec trading-ollama ollama create trading-trading -f /models/Modelfile
|
|
else
|
|
echo "Modelo fine-tuned no encontrado, usando Llama 3..."
|
|
docker exec trading-ollama ollama pull llama3:8b-instruct-q5_K_M
|
|
fi
|
|
|
|
# 6. Iniciar resto de servicios
|
|
echo "[6/6] Iniciando servicios de aplicacion..."
|
|
docker-compose -f docker-compose.llm-advanced.yaml up -d
|
|
|
|
echo ""
|
|
echo "=== Setup Completado ==="
|
|
echo "LLM Agent: http://localhost:3085"
|
|
echo "ML Engine: http://localhost:3083"
|
|
echo "MCP MT4: http://localhost:3605"
|
|
echo "MCP Binance: http://localhost:3606"
|
|
echo ""
|
|
```
|
|
|
|
---
|
|
|
|
## Testing y Validacion
|
|
|
|
### Test Cases
|
|
|
|
```python
|
|
# tests/test_llm_trading.py
|
|
|
|
import pytest
|
|
import httpx
|
|
from datetime import datetime
|
|
|
|
LLM_URL = "http://localhost:3085"
|
|
ML_URL = "http://localhost:3083"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_llm_analyze_with_confluence():
|
|
"""Test analisis con confluencia de modelos"""
|
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
response = await client.post(
|
|
f"{LLM_URL}/api/v1/analyze",
|
|
json={"symbol": "XAUUSD", "timeframe": "5m"}
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Verificar campos requeridos
|
|
assert "amd_phase" in data
|
|
assert "signal" in data
|
|
assert "confluence_score" in data
|
|
assert "explanation" in data
|
|
assert "risk_assessment" in data
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_risk_management_validation():
|
|
"""Test validacion de riesgo antes de trade"""
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
response = await client.post(
|
|
f"{LLM_URL}/api/v1/validate-trade",
|
|
json={
|
|
"symbol": "XAUUSD",
|
|
"direction": "LONG",
|
|
"size": 0.1,
|
|
"stop_loss_pips": 50
|
|
}
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
assert "allowed" in data
|
|
assert "checks" in data
|
|
assert "recommended_size" in data
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mcp_binance_connection():
|
|
"""Test conexion MCP Binance"""
|
|
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
response = await client.get("http://localhost:3606/health")
|
|
assert response.status_code == 200
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prediction_persistence():
|
|
"""Test persistencia de predicciones"""
|
|
# 1. Generar prediccion
|
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
response = await client.post(
|
|
f"{LLM_URL}/api/v1/predictions/analyze",
|
|
json={"symbol": "BTCUSDT"}
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
# 2. Verificar en historial
|
|
history_response = await client.get(
|
|
f"{LLM_URL}/api/v1/predictions/history/BTCUSDT?limit=1"
|
|
)
|
|
assert history_response.status_code == 200
|
|
assert len(history_response.json()) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fine_tuned_trading_knowledge():
|
|
"""Test conocimiento de trading del modelo fine-tuned"""
|
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
response = await client.post(
|
|
f"{LLM_URL}/api/v1/chat",
|
|
json={
|
|
"message": "Explica que es la fase de Accumulation en AMD y como tradearla",
|
|
"session_id": "test_knowledge"
|
|
}
|
|
)
|
|
assert response.status_code == 200
|
|
content = response.json()["response"]
|
|
|
|
# Verificar conocimiento especifico
|
|
assert "acumulacion" in content.lower() or "accumulation" in content.lower()
|
|
assert "instituciones" in content.lower() or "smart money" in content.lower()
|
|
```
|
|
|
|
### Metricas de Validacion
|
|
|
|
| Metrica | Target | Como Medir |
|
|
|---------|--------|------------|
|
|
| Response Time | <5s | pytest benchmark |
|
|
| Direction Accuracy | >65% | Historical outcomes |
|
|
| Confluence Score Reliability | >70% | Correlation with outcomes |
|
|
| Risk Limit Adherence | 100% | Audit logs |
|
|
| MCP Uptime | >99% | Health checks |
|
|
| Fine-tuning Quality | Perplexity <3.0 | Eval dataset |
|
|
|
|
---
|
|
|
|
## Proximos Pasos
|
|
|
|
### Fase 1: Infraestructura (1-2 semanas)
|
|
1. [ ] Crear MCP Binance Connector
|
|
2. [ ] Implementar DDL nuevas tablas
|
|
3. [ ] Configurar Docker Compose completo
|
|
4. [ ] Setup pipeline de CI/CD
|
|
|
|
### Fase 2: Core LLM (2-3 semanas)
|
|
1. [ ] Generar dataset de fine-tuning
|
|
2. [ ] Entrenar modelo con LoRA
|
|
3. [ ] Integrar modelo en Ollama
|
|
4. [ ] Implementar Risk Manager
|
|
|
|
### Fase 3: Integracion (1-2 semanas)
|
|
1. [ ] Conectar ML Engine con LLM
|
|
2. [ ] Implementar MCP Orchestrator
|
|
3. [ ] API de predicciones para frontend
|
|
4. [ ] WebSocket real-time
|
|
|
|
### Fase 4: Testing y Deployment (1 semana)
|
|
1. [ ] Tests de integracion
|
|
2. [ ] Backtesting de decisiones
|
|
3. [ ] Documentacion final
|
|
4. [ ] Deployment a produccion
|
|
|
|
---
|
|
|
|
**Documento Generado:** 2026-01-04
|
|
**Autor:** Orquestador Agent - Trading Platform
|
|
**Version:** 1.0.0
|