feat: Major platform documentation and architecture updates
Changes include: - Updated architecture documentation - Enhanced module definitions (OQI-001 to OQI-008) - ML integration documentation updates - Trading strategies documentation - Orchestration and inventory updates - Docker configuration updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9422e3d3a9
commit
a7cca885f0
339
AGENTS.md
Normal file
339
AGENTS.md
Normal file
@ -0,0 +1,339 @@
|
||||
# Guia para Agentes de IA - Trading Platform (OrbiQuant)
|
||||
|
||||
**Version:** 1.0
|
||||
**Ultima actualizacion:** 2026-01-04
|
||||
**Basado en:** Estandar-SCRUM.md (Principio SIMCO)
|
||||
|
||||
---
|
||||
|
||||
## 1. Estructura del Proyecto
|
||||
|
||||
### Ubicaciones Clave
|
||||
|
||||
| Tipo | Ubicacion |
|
||||
|------|-----------|
|
||||
| Documentacion general | `/docs/` |
|
||||
| Planificacion | `/docs/planning/` |
|
||||
| Definicion de Modulos | `/docs/02-definicion-modulos/` |
|
||||
| Backlog | `/docs/04-fase-backlog/` |
|
||||
| Transversal | `/docs/90-transversal/` |
|
||||
| Guias de Desarrollo | `/docs/95-guias-desarrollo/` |
|
||||
| Quick Reference | `/docs/96-quick-reference/` |
|
||||
| ADRs | `/docs/97-adr/` |
|
||||
| Tareas | `/docs/planning/tasks/` |
|
||||
| Bugs | `/docs/planning/bugs/` |
|
||||
| Tablero Kanban | `/docs/planning/Board.md` |
|
||||
| Orquestacion | `/orchestration/` |
|
||||
|
||||
### Estructura de un Modulo (Epica)
|
||||
|
||||
```
|
||||
docs/02-definicion-modulos/OQI-XXX-{nombre}/
|
||||
├── _MAP.md # Indice del modulo
|
||||
├── README.md # Descripcion del modulo
|
||||
├── historias-usuario/ # User Stories (US-*.md)
|
||||
├── requerimientos/ # Requerimientos Funcionales (RF-*.md)
|
||||
├── especificaciones/ # Especificaciones Tecnicas (ET-*.md)
|
||||
└── implementacion/ # Trazabilidad de implementacion
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Prefijos de Nomenclatura
|
||||
|
||||
| Prefijo | Tipo | Ejemplo | Descripcion |
|
||||
|---------|------|---------|-------------|
|
||||
| OQI- | Epica/Modulo | OQI-001-fundamentos-auth | Modulos principales |
|
||||
| US- | Historia de Usuario | US-AUTH-001 | User Stories |
|
||||
| TASK- | Tarea | TASK-001 | Tareas ejecutables |
|
||||
| BUG- | Bug | BUG-001 | Defectos/errores |
|
||||
| RF- | Requerimiento Funcional | RF-AUTH-001 | Requerimientos |
|
||||
| ET- | Especificacion Tecnica | ET-AUTH-001 | Especificaciones |
|
||||
| ADR- | Decision Record | ADR-001 | Decisiones arquitectonicas |
|
||||
|
||||
### Categorias de User Stories
|
||||
|
||||
| Sufijo | Modulo | Epica |
|
||||
|--------|--------|-------|
|
||||
| AUTH | Autenticacion | OQI-001 |
|
||||
| EDU | Educacion | OQI-002 |
|
||||
| TRD | Trading Charts | OQI-003 |
|
||||
| INV | Investment Accounts | OQI-004 |
|
||||
| PAY | Payments/Stripe | OQI-005 |
|
||||
| ML | ML Signals | OQI-006 |
|
||||
| LLM | LLM Agent | OQI-007 |
|
||||
| PFM | Portfolio Manager | OQI-008 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Como Trabajar con Tareas
|
||||
|
||||
### Tomar una Tarea
|
||||
|
||||
1. **Identificar tarea** en `/docs/planning/Board.md` (columna "Por Hacer")
|
||||
2. **Leer archivo** `TASK-XXX.md` correspondiente
|
||||
3. **Editar YAML front-matter**:
|
||||
```yaml
|
||||
status: "In Progress"
|
||||
assignee: "@NombreAgente"
|
||||
started_date: "YYYY-MM-DD"
|
||||
```
|
||||
4. **Mover tarea** a columna "En Progreso" en Board.md
|
||||
5. **Commit**: `git commit -m "Start TASK-XXX: [descripcion breve]"`
|
||||
|
||||
### Completar una Tarea
|
||||
|
||||
1. **Verificar** TODOS los criterios de aceptacion cumplidos
|
||||
2. **Editar YAML front-matter**:
|
||||
```yaml
|
||||
status: "Done"
|
||||
completed_date: "YYYY-MM-DD"
|
||||
actual_hours: X
|
||||
```
|
||||
3. **Agregar seccion** "## Notas de Implementacion" con detalles
|
||||
4. **Mover tarea** a columna "Hecho" en Board.md
|
||||
5. **Commit**: `git commit -m "Fixes TASK-XXX: [descripcion breve]"`
|
||||
|
||||
### Reportar Bloqueo
|
||||
|
||||
1. Cambiar `status: "Blocked"`
|
||||
2. Agregar seccion "## Bloqueo" con:
|
||||
- Descripcion del bloqueo
|
||||
- Dependencias faltantes
|
||||
- Accion requerida
|
||||
3. Notificar en Board.md
|
||||
|
||||
---
|
||||
|
||||
## 4. Como Trabajar con Bugs
|
||||
|
||||
### Reportar un Bug
|
||||
|
||||
1. **Crear archivo** `/docs/planning/bugs/BUG-XXX-descripcion.md`
|
||||
2. **Usar plantilla YAML**:
|
||||
```yaml
|
||||
---
|
||||
id: "BUG-XXX"
|
||||
title: "Descripcion del bug"
|
||||
type: "Bug"
|
||||
status: "Open"
|
||||
severity: "P0|P1|P2|P3"
|
||||
priority: "Critica|Alta|Media|Baja"
|
||||
assignee: ""
|
||||
affected_module: "Backend|Frontend|Database"
|
||||
steps_to_reproduce:
|
||||
- "Paso 1"
|
||||
- "Paso 2"
|
||||
expected_behavior: "Lo que deberia pasar"
|
||||
actual_behavior: "Lo que pasa realmente"
|
||||
created_date: "YYYY-MM-DD"
|
||||
---
|
||||
```
|
||||
3. **Incluir secciones**: Descripcion, Contexto, Impacto
|
||||
4. **Commit**: `git commit -m "Report BUG-XXX: [descripcion]"`
|
||||
|
||||
### Resolver un Bug
|
||||
|
||||
1. Editar YAML: `status: "Done"`, agregar `resolved_date`
|
||||
2. Documentar solucion en seccion "## Solucion Implementada"
|
||||
3. Agregar referencia al commit: `fix_commit: "abc123"`
|
||||
4. **Commit**: `git commit -m "Fix BUG-XXX: [descripcion]"`
|
||||
|
||||
---
|
||||
|
||||
## 5. Formato YAML Front-Matter
|
||||
|
||||
### Historia de Usuario (US)
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: "US-AUTH-001"
|
||||
title: "Registro con Email"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
assignee: "@Backend-Agent"
|
||||
epic: "OQI-001"
|
||||
story_points: 5
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
```
|
||||
|
||||
### Tarea (TASK)
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: "TASK-001"
|
||||
title: "Implementar endpoint POST /auth/register"
|
||||
type: "Task"
|
||||
status: "Done"
|
||||
priority: "P1"
|
||||
assignee: "@Backend-Agent"
|
||||
parent_us: "US-AUTH-001"
|
||||
epic: "OQI-001"
|
||||
estimated_hours: 4
|
||||
actual_hours: 4.5
|
||||
created_date: "2025-12-05"
|
||||
completed_date: "2025-12-05"
|
||||
---
|
||||
```
|
||||
|
||||
### Requerimiento Funcional (RF)
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: "RF-AUTH-001"
|
||||
title: "OAuth Multi-proveedor"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "auth"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
```
|
||||
|
||||
### Especificacion Tecnica (ET)
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: "ET-AUTH-001"
|
||||
title: "OAuth Providers Implementation"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-AUTH-001"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Convenciones de Commit
|
||||
|
||||
```
|
||||
<tipo>(<scope>): <descripcion>
|
||||
|
||||
Tipos:
|
||||
- feat: Nueva funcionalidad
|
||||
- fix: Correccion de bug
|
||||
- docs: Documentacion
|
||||
- refactor: Refactoring
|
||||
- test: Tests
|
||||
- chore: Mantenimiento
|
||||
|
||||
Scopes comunes:
|
||||
- auth, education, trading, investment, payments, ml, llm, portfolio
|
||||
- database, backend, frontend (capas)
|
||||
- US-XXX, TASK-XXX, BUG-XXX (referencias)
|
||||
|
||||
Ejemplos:
|
||||
- feat(auth): Implement OAuth 2.0 with Google
|
||||
- fix(BUG-001): Resolve login redirect issue
|
||||
- docs(US-AUTH-001): Add acceptance criteria
|
||||
- Start TASK-XXX: Begin implementation
|
||||
- Fixes TASK-XXX: Complete implementation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Estados Validos
|
||||
|
||||
### Para Tareas y User Stories
|
||||
|
||||
| Estado | Descripcion |
|
||||
|--------|-------------|
|
||||
| Backlog | En cola, no planificado |
|
||||
| To Do | Planificado para sprint actual |
|
||||
| In Progress | En desarrollo activo |
|
||||
| Blocked | Bloqueado por dependencia |
|
||||
| In Review | En revision/testing |
|
||||
| Done | Completado y validado |
|
||||
|
||||
### Para Bugs
|
||||
|
||||
| Estado | Descripcion |
|
||||
|--------|-------------|
|
||||
| Open | Reportado, pendiente |
|
||||
| In Progress | En investigacion/correccion |
|
||||
| Fixed | Corregido, pendiente validacion |
|
||||
| Done | Corregido y validado |
|
||||
| Won't Fix | No se corregira (documentar razon) |
|
||||
|
||||
---
|
||||
|
||||
## 8. Archivos Importantes
|
||||
|
||||
| Archivo | Proposito |
|
||||
|---------|-----------|
|
||||
| `/docs/planning/Board.md` | Tablero Kanban actual |
|
||||
| `/docs/planning/config.yml` | Configuracion del proyecto |
|
||||
| `/docs/04-fase-backlog/DEFINITION-OF-READY.md` | Criterios para Ready |
|
||||
| `/docs/04-fase-backlog/DEFINITION-OF-DONE.md` | Criterios para Done |
|
||||
| `/docs/_MAP.md` | Mapa de navegacion principal |
|
||||
| `/docs/02-definicion-modulos/_MAP.md` | Indice de modulos |
|
||||
|
||||
---
|
||||
|
||||
## 9. Validaciones Antes de Commit
|
||||
|
||||
- [ ] YAML front-matter valido (sin errores de sintaxis)
|
||||
- [ ] Campo `id` presente y unico
|
||||
- [ ] Campo `status` actualizado correctamente
|
||||
- [ ] Board.md actualizado si cambio estado
|
||||
- [ ] Referencias cruzadas verificadas
|
||||
- [ ] Criterios de aceptacion actualizados (si aplica)
|
||||
- [ ] _MAP.md actualizado si se agrego/elimino archivo
|
||||
|
||||
---
|
||||
|
||||
## 10. Flujo de Trabajo Recomendado
|
||||
|
||||
```
|
||||
1. Consultar Board.md para ver tareas disponibles
|
||||
2. Seleccionar tarea de "Por Hacer"
|
||||
3. Leer archivo TASK-XXX.md completo
|
||||
4. Verificar dependencias resueltas
|
||||
5. Cambiar status a "In Progress"
|
||||
6. Ejecutar trabajo
|
||||
7. Documentar notas de implementacion
|
||||
8. Verificar criterios de aceptacion
|
||||
9. Cambiar status a "Done"
|
||||
10. Actualizar Board.md
|
||||
11. Commit con mensaje apropiado
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Modulos del Proyecto
|
||||
|
||||
| Epica | Nombre | Descripcion | Estado |
|
||||
|-------|--------|-------------|--------|
|
||||
| OQI-001 | Fundamentos Auth | Autenticacion multi-proveedor | Implementado |
|
||||
| OQI-002 | Education | Cursos y lecciones de trading | Implementado |
|
||||
| OQI-003 | Trading Charts | Charts con indicadores tecnicos | Implementado |
|
||||
| OQI-004 | Investment Accounts | Cuentas de inversion | Implementado |
|
||||
| OQI-005 | Payments Stripe | Pagos y suscripciones | Implementado |
|
||||
| OQI-006 | ML Signals | Senales con Machine Learning | Implementado |
|
||||
| OQI-007 | LLM Agent | Agente conversacional | Implementado |
|
||||
| OQI-008 | Portfolio Manager | Gestion de portafolio | Implementado |
|
||||
|
||||
---
|
||||
|
||||
## 12. Contacto y Soporte
|
||||
|
||||
Para dudas sobre el proceso:
|
||||
- Revisar `/docs/README.md` para vision general
|
||||
- Consultar `/orchestration/directivas/` para directivas
|
||||
- Ver ejemplos en modulos completados (OQI-001 a OQI-008)
|
||||
|
||||
---
|
||||
|
||||
**Creado:** 2026-01-04
|
||||
**Mantenido por:** Architecture Team
|
||||
**Version:** 1.0
|
||||
52
apps/mcp-binance-connector/.env.example
Normal file
52
apps/mcp-binance-connector/.env.example
Normal file
@ -0,0 +1,52 @@
|
||||
# MCP Binance Connector Configuration
|
||||
# Copy this file to .env and configure values
|
||||
|
||||
# ==========================================
|
||||
# Server Configuration
|
||||
# ==========================================
|
||||
PORT=3606
|
||||
NODE_ENV=development
|
||||
|
||||
# ==========================================
|
||||
# MCP Authentication
|
||||
# ==========================================
|
||||
MCP_API_KEY=your_mcp_api_key_here
|
||||
|
||||
# ==========================================
|
||||
# Binance API Configuration
|
||||
# ==========================================
|
||||
BINANCE_API_KEY=your_binance_api_key
|
||||
BINANCE_API_SECRET=your_binance_api_secret
|
||||
|
||||
# ==========================================
|
||||
# Network Configuration
|
||||
# ==========================================
|
||||
# Use testnet by default (set to false for production)
|
||||
BINANCE_TESTNET=true
|
||||
BINANCE_FUTURES_TESTNET=true
|
||||
|
||||
# ==========================================
|
||||
# Risk Limits
|
||||
# ==========================================
|
||||
# Maximum value for a single order in USDT
|
||||
MAX_ORDER_VALUE_USDT=1000
|
||||
# Maximum daily trading volume in USDT
|
||||
MAX_DAILY_VOLUME_USDT=10000
|
||||
# Maximum allowed leverage
|
||||
MAX_LEVERAGE=20
|
||||
# Maximum position size as percentage of equity
|
||||
MAX_POSITION_SIZE_PCT=5
|
||||
|
||||
# ==========================================
|
||||
# Request Configuration
|
||||
# ==========================================
|
||||
# Timeout for requests to Binance (ms)
|
||||
REQUEST_TIMEOUT=10000
|
||||
# Maximum retries for failed requests
|
||||
MAX_RETRIES=3
|
||||
|
||||
# ==========================================
|
||||
# Logging
|
||||
# ==========================================
|
||||
LOG_LEVEL=info
|
||||
LOG_FILE=logs/mcp-binance.log
|
||||
31
apps/mcp-binance-connector/.gitignore
vendored
Normal file
31
apps/mcp-binance-connector/.gitignore
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Test coverage
|
||||
coverage/
|
||||
|
||||
# Misc
|
||||
*.tsbuildinfo
|
||||
57
apps/mcp-binance-connector/Dockerfile
Normal file
57
apps/mcp-binance-connector/Dockerfile
Normal file
@ -0,0 +1,57 @@
|
||||
# MCP Binance Connector Dockerfile
|
||||
# OrbiQuant Trading Platform
|
||||
# Version: 1.0.0
|
||||
|
||||
# ==========================================
|
||||
# Build Stage
|
||||
# ==========================================
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Copy source and build
|
||||
COPY tsconfig.json ./
|
||||
COPY src ./src
|
||||
RUN npm run build
|
||||
|
||||
# ==========================================
|
||||
# Production Stage
|
||||
# ==========================================
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install production dependencies only
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production && npm cache clean --force
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder /app/dist ./dist
|
||||
|
||||
# Create non-root user for security
|
||||
RUN addgroup -g 1001 -S nodejs && \
|
||||
adduser -S mcpuser -u 1001 -G nodejs
|
||||
|
||||
# Create logs directory
|
||||
RUN mkdir -p logs && chown -R mcpuser:nodejs logs
|
||||
|
||||
# Switch to non-root user
|
||||
USER mcpuser
|
||||
|
||||
# Environment configuration
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3606
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3606
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:3606/health || exit 1
|
||||
|
||||
# Start application
|
||||
CMD ["node", "dist/index.js"]
|
||||
345
apps/mcp-binance-connector/README.md
Normal file
345
apps/mcp-binance-connector/README.md
Normal file
@ -0,0 +1,345 @@
|
||||
# MCP Binance Connector
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Date:** 2026-01-04
|
||||
**System:** OrbiQuant Trading Platform + NEXUS v3.4 + SIMCO
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
MCP Server that exposes Binance cryptocurrency exchange capabilities as tools for AI agents. This service enables AI agents to:
|
||||
|
||||
- Query market data (prices, order books, candles)
|
||||
- Monitor account balances
|
||||
- View and manage open orders
|
||||
- Execute trades (buy/sell with market, limit, stop orders)
|
||||
|
||||
Uses [CCXT](https://github.com/ccxt/ccxt) library for Binance API integration.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Navigate to the project
|
||||
cd /home/isem/workspace-v1/projects/trading-platform/apps/mcp-binance-connector
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Configure environment
|
||||
cp .env.example .env
|
||||
# Edit .env with your Binance API credentials
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `PORT` | MCP Server port | 3606 |
|
||||
| `MCP_API_KEY` | API key for MCP authentication | - |
|
||||
| `BINANCE_API_KEY` | Binance API key | - |
|
||||
| `BINANCE_API_SECRET` | Binance API secret | - |
|
||||
| `BINANCE_TESTNET` | Use Binance testnet | true |
|
||||
| `MAX_ORDER_VALUE_USDT` | Max order value limit | 1000 |
|
||||
| `MAX_DAILY_VOLUME_USDT` | Max daily trading volume | 10000 |
|
||||
| `MAX_LEVERAGE` | Max leverage allowed | 20 |
|
||||
| `LOG_LEVEL` | Logging level | info |
|
||||
|
||||
### Example .env
|
||||
|
||||
```env
|
||||
PORT=3606
|
||||
BINANCE_API_KEY=your_api_key_here
|
||||
BINANCE_API_SECRET=your_api_secret_here
|
||||
BINANCE_TESTNET=true
|
||||
MAX_ORDER_VALUE_USDT=1000
|
||||
MAX_DAILY_VOLUME_USDT=10000
|
||||
LOG_LEVEL=info
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Start Server
|
||||
|
||||
```bash
|
||||
# Development (with hot reload)
|
||||
npm run dev
|
||||
|
||||
# Production
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
### Health Check
|
||||
|
||||
```bash
|
||||
curl http://localhost:3606/health
|
||||
```
|
||||
|
||||
### List Available Tools
|
||||
|
||||
```bash
|
||||
curl http://localhost:3606/tools
|
||||
```
|
||||
|
||||
### Execute a Tool
|
||||
|
||||
```bash
|
||||
# Get BTC price
|
||||
curl -X POST http://localhost:3606/tools/binance_get_ticker \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"parameters": {"symbol": "BTCUSDT"}}'
|
||||
|
||||
# Get order book
|
||||
curl -X POST http://localhost:3606/tools/binance_get_orderbook \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"parameters": {"symbol": "ETHUSDT", "limit": 10}}'
|
||||
|
||||
# Get candlestick data
|
||||
curl -X POST http://localhost:3606/tools/binance_get_klines \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"parameters": {"symbol": "BTCUSDT", "interval": "1h", "limit": 24}}'
|
||||
|
||||
# Get account balance (requires API keys)
|
||||
curl -X POST http://localhost:3606/tools/binance_get_account \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"parameters": {}}'
|
||||
|
||||
# Create order (requires API keys) - HIGH RISK
|
||||
curl -X POST http://localhost:3606/tools/binance_create_order \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"parameters": {"symbol": "BTCUSDT", "side": "buy", "type": "market", "amount": 0.001}}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Tools Available
|
||||
|
||||
| Tool | Description | Risk Level |
|
||||
|------|-------------|------------|
|
||||
| `binance_get_ticker` | Get current price and 24h stats | LOW |
|
||||
| `binance_get_orderbook` | Get order book depth | LOW |
|
||||
| `binance_get_klines` | Get OHLCV candles | LOW |
|
||||
| `binance_get_account` | Get account balances | MEDIUM |
|
||||
| `binance_get_open_orders` | List open orders | MEDIUM |
|
||||
| `binance_create_order` | Create buy/sell order | HIGH (*) |
|
||||
| `binance_cancel_order` | Cancel pending order | MEDIUM |
|
||||
|
||||
(*) Tools marked with HIGH risk require explicit confirmation and pass through risk checks.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
mcp-binance-connector/
|
||||
├── README.md # This file
|
||||
├── package.json # Dependencies
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
├── .env.example # Environment template
|
||||
├── Dockerfile # Container configuration
|
||||
└── src/
|
||||
├── index.ts # Server entry point
|
||||
├── config.ts # Configuration management
|
||||
├── utils/
|
||||
│ └── logger.ts # Winston logger
|
||||
├── services/
|
||||
│ └── binance-client.ts # CCXT wrapper
|
||||
├── middleware/
|
||||
│ └── risk-check.ts # Pre-trade risk validation
|
||||
└── tools/
|
||||
├── index.ts # Tool registry
|
||||
├── market.ts # Market data tools
|
||||
├── account.ts # Account tools
|
||||
└── orders.ts # Order management tools
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Type Check
|
||||
|
||||
```bash
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run lint:fix
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
### Build Image
|
||||
|
||||
```bash
|
||||
docker build -t mcp-binance-connector:1.0.0 .
|
||||
```
|
||||
|
||||
### Run Container
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name mcp-binance-connector \
|
||||
-p 3606:3606 \
|
||||
-e BINANCE_API_KEY=your_key \
|
||||
-e BINANCE_API_SECRET=your_secret \
|
||||
-e BINANCE_TESTNET=true \
|
||||
mcp-binance-connector:1.0.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with Claude
|
||||
|
||||
### MCP Configuration
|
||||
|
||||
Add to your Claude/MCP configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"binance": {
|
||||
"url": "http://localhost:3606",
|
||||
"transport": "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Agent Prompts
|
||||
|
||||
```
|
||||
"What's the current Bitcoin price?"
|
||||
-> Uses binance_get_ticker({ symbol: "BTCUSDT" })
|
||||
|
||||
"Show me the ETH order book"
|
||||
-> Uses binance_get_orderbook({ symbol: "ETHUSDT" })
|
||||
|
||||
"Get the last 50 hourly candles for BTC"
|
||||
-> Uses binance_get_klines({ symbol: "BTCUSDT", interval: "1h", limit: 50 })
|
||||
|
||||
"Check my Binance balance"
|
||||
-> Uses binance_get_account()
|
||||
|
||||
"Buy 0.01 BTC at market price"
|
||||
-> Uses binance_create_order({ symbol: "BTCUSDT", side: "buy", type: "market", amount: 0.01 })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risk Management
|
||||
|
||||
The connector includes built-in risk checks:
|
||||
|
||||
1. **Maximum Order Value**: Orders exceeding `MAX_ORDER_VALUE_USDT` are rejected
|
||||
2. **Daily Volume Limit**: Trading stops when `MAX_DAILY_VOLUME_USDT` is reached
|
||||
3. **Balance Check**: Buy orders verify sufficient balance
|
||||
4. **Testnet Default**: Testnet is enabled by default for safety
|
||||
5. **High-Risk Confirmation**: Orders require explicit confirmation flag
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Runtime
|
||||
- `express` - HTTP server
|
||||
- `ccxt` - Cryptocurrency exchange library
|
||||
- `zod` - Input validation
|
||||
- `winston` - Logging
|
||||
- `dotenv` - Environment configuration
|
||||
- `@modelcontextprotocol/sdk` - MCP protocol
|
||||
|
||||
### Development
|
||||
- `typescript` - Type safety
|
||||
- `ts-node-dev` - Development server
|
||||
- `jest` - Testing framework
|
||||
- `eslint` - Code linting
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Binance Account** with API keys (optional for public data)
|
||||
2. **Testnet API Keys** for testing (recommended)
|
||||
3. **Node.js** >= 20.0.0
|
||||
|
||||
### Getting Binance API Keys
|
||||
|
||||
1. Log into [Binance](https://www.binance.com)
|
||||
2. Go to API Management
|
||||
3. Create a new API key
|
||||
4. Enable Spot Trading permissions
|
||||
5. (Optional) For testnet: [Binance Testnet](https://testnet.binance.vision/)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot connect to Binance
|
||||
|
||||
```bash
|
||||
# Check connectivity
|
||||
curl https://api.binance.com/api/v3/ping
|
||||
|
||||
# If using testnet, check testnet connectivity
|
||||
curl https://testnet.binance.vision/api/v3/ping
|
||||
```
|
||||
|
||||
### Authentication errors
|
||||
|
||||
```bash
|
||||
# Verify API keys are set
|
||||
cat .env | grep BINANCE
|
||||
|
||||
# Check health endpoint for config status
|
||||
curl http://localhost:3606/health
|
||||
```
|
||||
|
||||
### Order rejected by risk check
|
||||
|
||||
The order may exceed configured limits. Check:
|
||||
- `MAX_ORDER_VALUE_USDT` - single order limit
|
||||
- `MAX_DAILY_VOLUME_USDT` - daily trading limit
|
||||
- Available balance for buy orders
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [MCP Protocol](https://modelcontextprotocol.io)
|
||||
- [CCXT Documentation](https://docs.ccxt.com)
|
||||
- [Binance API](https://binance-docs.github.io/apidocs/)
|
||||
- Architecture: `/docs/01-arquitectura/MCP-BINANCE-CONNECTOR-SPEC.md`
|
||||
- MT4 Connector: `/apps/mcp-mt4-connector/` (reference implementation)
|
||||
|
||||
---
|
||||
|
||||
**Maintained by:** @PERFIL_MCP_DEVELOPER
|
||||
**Project:** OrbiQuant Trading Platform
|
||||
54
apps/mcp-binance-connector/package.json
Normal file
54
apps/mcp-binance-connector/package.json
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "mcp-binance-connector",
|
||||
"version": "1.0.0",
|
||||
"description": "MCP Server for Binance trading operations via CCXT",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "ts-node-dev --respawn src/index.ts",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"lint:fix": "eslint src/**/*.ts --fix",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"health-check": "curl -s http://localhost:${PORT:-3606}/health || echo 'Server not running'"
|
||||
},
|
||||
"keywords": [
|
||||
"mcp",
|
||||
"model-context-protocol",
|
||||
"anthropic",
|
||||
"claude",
|
||||
"binance",
|
||||
"crypto",
|
||||
"trading",
|
||||
"ccxt"
|
||||
],
|
||||
"author": "OrbiQuant Trading Platform",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"ccxt": "^4.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"winston": "^3.11.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
||||
"@typescript-eslint/parser": "^6.13.0",
|
||||
"eslint": "^8.54.0",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
159
apps/mcp-binance-connector/src/config.ts
Normal file
159
apps/mcp-binance-connector/src/config.ts
Normal file
@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Configuration Module
|
||||
*
|
||||
* Manages environment variables and creates Binance clients via CCXT.
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
import ccxt from 'ccxt';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// ==========================================
|
||||
// Configuration Interface
|
||||
// ==========================================
|
||||
|
||||
export interface BinanceConfig {
|
||||
apiKey: string;
|
||||
apiSecret: string;
|
||||
testnet: boolean;
|
||||
futuresTestnet: boolean;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
export interface RiskConfig {
|
||||
maxOrderValueUsdt: number;
|
||||
maxDailyVolumeUsdt: number;
|
||||
maxLeverage: number;
|
||||
maxPositionSizePct: number;
|
||||
}
|
||||
|
||||
export interface ServerConfig {
|
||||
port: number;
|
||||
nodeEnv: string;
|
||||
mcpApiKey: string;
|
||||
logLevel: string;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Configuration Loading
|
||||
// ==========================================
|
||||
|
||||
export const binanceConfig: BinanceConfig = {
|
||||
apiKey: process.env.BINANCE_API_KEY || '',
|
||||
apiSecret: process.env.BINANCE_API_SECRET || '',
|
||||
testnet: process.env.BINANCE_TESTNET === 'true',
|
||||
futuresTestnet: process.env.BINANCE_FUTURES_TESTNET === 'true',
|
||||
timeout: parseInt(process.env.REQUEST_TIMEOUT || '10000', 10),
|
||||
};
|
||||
|
||||
export const riskConfig: RiskConfig = {
|
||||
maxOrderValueUsdt: parseFloat(process.env.MAX_ORDER_VALUE_USDT || '1000'),
|
||||
maxDailyVolumeUsdt: parseFloat(process.env.MAX_DAILY_VOLUME_USDT || '10000'),
|
||||
maxLeverage: parseInt(process.env.MAX_LEVERAGE || '20', 10),
|
||||
maxPositionSizePct: parseFloat(process.env.MAX_POSITION_SIZE_PCT || '5'),
|
||||
};
|
||||
|
||||
export const serverConfig: ServerConfig = {
|
||||
port: parseInt(process.env.PORT || '3606', 10),
|
||||
nodeEnv: process.env.NODE_ENV || 'development',
|
||||
mcpApiKey: process.env.MCP_API_KEY || '',
|
||||
logLevel: process.env.LOG_LEVEL || 'info',
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// Binance Client Factory
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Create a Binance Spot client
|
||||
*/
|
||||
export function createBinanceSpotClient(): ccxt.binance {
|
||||
const isTestnet = binanceConfig.testnet;
|
||||
|
||||
const client = new ccxt.binance({
|
||||
apiKey: binanceConfig.apiKey,
|
||||
secret: binanceConfig.apiSecret,
|
||||
sandbox: isTestnet,
|
||||
options: {
|
||||
defaultType: 'spot',
|
||||
adjustForTimeDifference: true,
|
||||
},
|
||||
enableRateLimit: true,
|
||||
rateLimit: 100,
|
||||
timeout: binanceConfig.timeout,
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Binance Futures client
|
||||
*/
|
||||
export function createBinanceFuturesClient(): ccxt.binance {
|
||||
const isTestnet = binanceConfig.futuresTestnet;
|
||||
|
||||
const client = new ccxt.binance({
|
||||
apiKey: binanceConfig.apiKey,
|
||||
secret: binanceConfig.apiSecret,
|
||||
sandbox: isTestnet,
|
||||
options: {
|
||||
defaultType: 'future',
|
||||
adjustForTimeDifference: true,
|
||||
},
|
||||
enableRateLimit: true,
|
||||
rateLimit: 100,
|
||||
timeout: binanceConfig.timeout,
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Configuration Validation
|
||||
// ==========================================
|
||||
|
||||
export function validateConfig(): { valid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
// Binance API keys are optional for public endpoints
|
||||
// but required for account/trading operations
|
||||
if (!binanceConfig.apiKey && serverConfig.nodeEnv === 'production') {
|
||||
errors.push('BINANCE_API_KEY is required in production');
|
||||
}
|
||||
|
||||
if (!binanceConfig.apiSecret && serverConfig.nodeEnv === 'production') {
|
||||
errors.push('BINANCE_API_SECRET is required in production');
|
||||
}
|
||||
|
||||
// Validate risk limits
|
||||
if (riskConfig.maxOrderValueUsdt <= 0) {
|
||||
errors.push('MAX_ORDER_VALUE_USDT must be positive');
|
||||
}
|
||||
|
||||
if (riskConfig.maxLeverage < 1 || riskConfig.maxLeverage > 125) {
|
||||
errors.push('MAX_LEVERAGE must be between 1 and 125');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Exports
|
||||
// ==========================================
|
||||
|
||||
export default {
|
||||
binance: binanceConfig,
|
||||
risk: riskConfig,
|
||||
server: serverConfig,
|
||||
createBinanceSpotClient,
|
||||
createBinanceFuturesClient,
|
||||
validateConfig,
|
||||
};
|
||||
332
apps/mcp-binance-connector/src/index.ts
Normal file
332
apps/mcp-binance-connector/src/index.ts
Normal file
@ -0,0 +1,332 @@
|
||||
/**
|
||||
* MCP Server: Binance Connector
|
||||
*
|
||||
* Exposes Binance trading capabilities as MCP tools for AI agents.
|
||||
* Uses CCXT library to communicate with Binance API.
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
import express, { Request, Response, NextFunction } from 'express';
|
||||
import dotenv from 'dotenv';
|
||||
import { mcpToolSchemas, toolHandlers, getAllToolDefinitions, toolRequiresConfirmation, getToolRiskLevel } from './tools';
|
||||
import { getBinanceClient } from './services/binance-client';
|
||||
import { serverConfig, binanceConfig, validateConfig } from './config';
|
||||
import { logger } from './utils/logger';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const PORT = serverConfig.port;
|
||||
const SERVICE_NAME = 'mcp-binance-connector';
|
||||
const VERSION = '1.0.0';
|
||||
|
||||
// ==========================================
|
||||
// Middleware
|
||||
// ==========================================
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
// Request logging
|
||||
app.use((req: Request, _res: Response, next: NextFunction) => {
|
||||
logger.info(`${req.method} ${req.path}`, {
|
||||
ip: req.ip,
|
||||
userAgent: req.get('user-agent'),
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
||||
// MCP API Key authentication (optional, for protected endpoints)
|
||||
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
const mcpKey = req.headers['x-mcp-api-key'];
|
||||
|
||||
// Skip auth if MCP_API_KEY is not configured
|
||||
if (!serverConfig.mcpApiKey) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mcpKey !== serverConfig.mcpApiKey) {
|
||||
res.status(401).json({ error: 'Invalid MCP API key' });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// Health & Status Endpoints
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Health check endpoint
|
||||
*/
|
||||
app.get('/health', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const client = getBinanceClient();
|
||||
const binanceConnected = await client.isConnected();
|
||||
const binanceConfigured = client.isConfigured();
|
||||
|
||||
res.json({
|
||||
status: binanceConnected ? 'healthy' : 'degraded',
|
||||
service: SERVICE_NAME,
|
||||
version: VERSION,
|
||||
timestamp: new Date().toISOString(),
|
||||
testnet: binanceConfig.testnet,
|
||||
dependencies: {
|
||||
binance: binanceConnected ? 'connected' : 'disconnected',
|
||||
binanceApiConfigured: binanceConfigured,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
res.json({
|
||||
status: 'unhealthy',
|
||||
service: SERVICE_NAME,
|
||||
version: VERSION,
|
||||
timestamp: new Date().toISOString(),
|
||||
testnet: binanceConfig.testnet,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* List available MCP tools
|
||||
*/
|
||||
app.get('/tools', (_req: Request, res: Response) => {
|
||||
res.json({
|
||||
tools: mcpToolSchemas.map((tool) => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
riskLevel: (tool as { riskLevel?: string }).riskLevel,
|
||||
requiresConfirmation: (tool as { requiresConfirmation?: boolean }).requiresConfirmation,
|
||||
})),
|
||||
count: mcpToolSchemas.length,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Get specific tool schema
|
||||
*/
|
||||
app.get('/tools/:toolName', (req: Request, res: Response) => {
|
||||
const { toolName } = req.params;
|
||||
const tool = mcpToolSchemas.find((t) => t.name === toolName);
|
||||
|
||||
if (!tool) {
|
||||
res.status(404).json({
|
||||
error: `Tool '${toolName}' not found`,
|
||||
availableTools: mcpToolSchemas.map((t) => t.name),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(tool);
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// MCP Tool Execution Endpoints
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Execute an MCP tool
|
||||
* POST /tools/:toolName
|
||||
* Body: { parameters: {...} }
|
||||
*/
|
||||
app.post('/tools/:toolName', authMiddleware, async (req: Request, res: Response) => {
|
||||
const { toolName } = req.params;
|
||||
const { parameters = {} } = req.body;
|
||||
|
||||
// Validate tool exists
|
||||
const handler = toolHandlers[toolName];
|
||||
if (!handler) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: `Tool '${toolName}' not found`,
|
||||
availableTools: Object.keys(toolHandlers),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(`Executing tool: ${toolName}`, {
|
||||
parameters,
|
||||
riskLevel: getToolRiskLevel(toolName),
|
||||
requiresConfirmation: toolRequiresConfirmation(toolName),
|
||||
});
|
||||
|
||||
const result = await handler(parameters);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
tool: toolName,
|
||||
result,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Tool execution failed: ${toolName}`, { error, parameters });
|
||||
|
||||
// Handle Zod validation errors
|
||||
if (error && typeof error === 'object' && 'issues' in error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Validation error',
|
||||
details: (error as { issues: unknown[] }).issues,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// MCP Protocol Endpoints (Standard)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* MCP Initialize
|
||||
* Returns server capabilities
|
||||
*/
|
||||
app.post('/mcp/initialize', (_req: Request, res: Response) => {
|
||||
res.json({
|
||||
protocolVersion: '2024-11-05',
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
serverInfo: {
|
||||
name: SERVICE_NAME,
|
||||
version: VERSION,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* MCP List Tools
|
||||
* Returns all available tools in MCP format
|
||||
*/
|
||||
app.post('/mcp/tools/list', (_req: Request, res: Response) => {
|
||||
res.json({
|
||||
tools: getAllToolDefinitions(),
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* MCP Call Tool
|
||||
* Execute a tool with parameters
|
||||
*/
|
||||
app.post('/mcp/tools/call', authMiddleware, async (req: Request, res: Response) => {
|
||||
const { name, arguments: args = {} } = req.body;
|
||||
|
||||
if (!name) {
|
||||
res.status(400).json({
|
||||
error: {
|
||||
code: 'invalid_request',
|
||||
message: 'Tool name is required',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = toolHandlers[name];
|
||||
if (!handler) {
|
||||
res.status(404).json({
|
||||
error: {
|
||||
code: 'unknown_tool',
|
||||
message: `Tool '${name}' not found`,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await handler(args);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
// Handle Zod validation errors
|
||||
if (error && typeof error === 'object' && 'issues' in error) {
|
||||
res.status(400).json({
|
||||
error: {
|
||||
code: 'invalid_params',
|
||||
message: 'Invalid tool parameters',
|
||||
data: (error as { issues: unknown[] }).issues,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
error: {
|
||||
code: 'internal_error',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Error Handler
|
||||
// ==========================================
|
||||
|
||||
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
|
||||
logger.error('Unhandled error', { error: err });
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
message: err.message,
|
||||
});
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Start Server
|
||||
// ==========================================
|
||||
|
||||
// Validate configuration before starting
|
||||
const configValidation = validateConfig();
|
||||
if (!configValidation.valid) {
|
||||
logger.warn('Configuration warnings', { errors: configValidation.errors });
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log('');
|
||||
console.log('================================================================');
|
||||
console.log(' MCP Binance Connector - OrbiQuant Trading Platform ');
|
||||
console.log('================================================================');
|
||||
console.log(` Service: ${SERVICE_NAME}`);
|
||||
console.log(` Version: ${VERSION}`);
|
||||
console.log(` Port: ${PORT}`);
|
||||
console.log(` Environment: ${serverConfig.nodeEnv}`);
|
||||
console.log(` Testnet Mode: ${binanceConfig.testnet ? 'ENABLED' : 'DISABLED'}`);
|
||||
console.log(` API Configured: ${binanceConfig.apiKey ? 'Yes' : 'No'}`);
|
||||
console.log('----------------------------------------------------------------');
|
||||
console.log(' Endpoints:');
|
||||
console.log(` - Health: http://localhost:${PORT}/health`);
|
||||
console.log(` - Tools: http://localhost:${PORT}/tools`);
|
||||
console.log('----------------------------------------------------------------');
|
||||
console.log(' MCP Tools Available:');
|
||||
mcpToolSchemas.forEach((tool) => {
|
||||
const risk = (tool as { riskLevel?: string }).riskLevel || 'N/A';
|
||||
const confirm = (tool as { requiresConfirmation?: boolean }).requiresConfirmation ? ' (!)' : '';
|
||||
console.log(` - ${tool.name} [${risk}]${confirm}`);
|
||||
});
|
||||
console.log('================================================================');
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Graceful Shutdown
|
||||
// ==========================================
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
logger.info('Received SIGTERM, shutting down gracefully...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('Received SIGINT, shutting down gracefully...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
export default app;
|
||||
209
apps/mcp-binance-connector/src/middleware/risk-check.ts
Normal file
209
apps/mcp-binance-connector/src/middleware/risk-check.ts
Normal file
@ -0,0 +1,209 @@
|
||||
/**
|
||||
* Risk Check Middleware
|
||||
*
|
||||
* Pre-trade risk validation to ensure orders comply with risk limits.
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
import { riskConfig } from '../config';
|
||||
import { getBinanceClient } from '../services/binance-client';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
// ==========================================
|
||||
// Types
|
||||
// ==========================================
|
||||
|
||||
export interface RiskCheckParams {
|
||||
symbol: string;
|
||||
side: 'buy' | 'sell';
|
||||
amount: number;
|
||||
price?: number;
|
||||
}
|
||||
|
||||
export interface RiskCheckResult {
|
||||
allowed: boolean;
|
||||
reason?: string;
|
||||
warnings?: string[];
|
||||
orderValue?: number;
|
||||
}
|
||||
|
||||
// Daily volume tracking (in-memory, resets on restart)
|
||||
let dailyVolume = 0;
|
||||
let lastVolumeResetDate = new Date().toDateString();
|
||||
|
||||
// ==========================================
|
||||
// Risk Check Functions
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Reset daily volume counter at midnight
|
||||
*/
|
||||
function checkAndResetDailyVolume(): void {
|
||||
const today = new Date().toDateString();
|
||||
if (today !== lastVolumeResetDate) {
|
||||
dailyVolume = 0;
|
||||
lastVolumeResetDate = today;
|
||||
logger.info('Daily volume counter reset');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the quote asset from a symbol (e.g., USDT from BTCUSDT)
|
||||
*/
|
||||
function getQuoteAsset(symbol: string): string {
|
||||
const stablecoins = ['USDT', 'BUSD', 'USDC', 'TUSD', 'DAI'];
|
||||
for (const stable of stablecoins) {
|
||||
if (symbol.endsWith(stable)) {
|
||||
return stable;
|
||||
}
|
||||
}
|
||||
return 'USDT';
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform comprehensive risk check before order execution
|
||||
*/
|
||||
export async function performRiskCheck(params: RiskCheckParams): Promise<RiskCheckResult> {
|
||||
const { symbol, side, amount, price } = params;
|
||||
const warnings: string[] = [];
|
||||
|
||||
try {
|
||||
checkAndResetDailyVolume();
|
||||
|
||||
const client = getBinanceClient();
|
||||
|
||||
// 1. Get current price if not provided
|
||||
let orderPrice = price;
|
||||
if (!orderPrice) {
|
||||
try {
|
||||
orderPrice = await client.getCurrentPrice(symbol);
|
||||
} catch (error) {
|
||||
logger.warn(`Could not fetch current price for ${symbol}, using amount as value estimate`);
|
||||
orderPrice = 1; // Fallback
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Calculate order value in quote currency (usually USDT)
|
||||
const orderValue = amount * orderPrice;
|
||||
|
||||
// 3. Check maximum order value
|
||||
if (orderValue > riskConfig.maxOrderValueUsdt) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Order value ${orderValue.toFixed(2)} USDT exceeds maximum ${riskConfig.maxOrderValueUsdt} USDT`,
|
||||
orderValue,
|
||||
};
|
||||
}
|
||||
|
||||
// 4. Check daily volume limit
|
||||
if (dailyVolume + orderValue > riskConfig.maxDailyVolumeUsdt) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Daily volume limit reached. Current: ${dailyVolume.toFixed(2)} USDT, Limit: ${riskConfig.maxDailyVolumeUsdt} USDT`,
|
||||
orderValue,
|
||||
};
|
||||
}
|
||||
|
||||
// 5. Check if API keys are configured for trading
|
||||
if (!client.isConfigured()) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: 'Binance API keys are not configured. Cannot execute trades.',
|
||||
orderValue,
|
||||
};
|
||||
}
|
||||
|
||||
// 6. Verify we can connect to Binance
|
||||
const connected = await client.isConnected();
|
||||
if (!connected) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: 'Cannot connect to Binance. Please check your network and API configuration.',
|
||||
orderValue,
|
||||
};
|
||||
}
|
||||
|
||||
// 7. Check balance for buy orders (if we have account access)
|
||||
if (side === 'buy') {
|
||||
try {
|
||||
const account = await client.getAccount();
|
||||
const quoteAsset = getQuoteAsset(symbol);
|
||||
const quoteBalance = account.balances.find(b => b.asset === quoteAsset);
|
||||
const available = quoteBalance?.free ?? 0;
|
||||
|
||||
if (available < orderValue) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Insufficient ${quoteAsset} balance. Required: ${orderValue.toFixed(2)}, Available: ${available.toFixed(2)}`,
|
||||
orderValue,
|
||||
};
|
||||
}
|
||||
|
||||
// Warning if using more than 50% of available balance
|
||||
if (orderValue > available * 0.5) {
|
||||
warnings.push(`This order uses ${((orderValue / available) * 100).toFixed(1)}% of your available ${quoteAsset}`);
|
||||
}
|
||||
} catch (error) {
|
||||
warnings.push('Could not verify account balance');
|
||||
logger.warn('Balance check failed', { error });
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Check for large order warning
|
||||
if (orderValue > riskConfig.maxOrderValueUsdt * 0.5) {
|
||||
warnings.push(`Large order: ${orderValue.toFixed(2)} USDT (${((orderValue / riskConfig.maxOrderValueUsdt) * 100).toFixed(0)}% of max)`);
|
||||
}
|
||||
|
||||
// All checks passed
|
||||
return {
|
||||
allowed: true,
|
||||
orderValue,
|
||||
warnings: warnings.length > 0 ? warnings : undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error('Risk check failed', { error, params });
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Risk check error: ${message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record executed trade volume
|
||||
*/
|
||||
export function recordTradeVolume(orderValue: number): void {
|
||||
checkAndResetDailyVolume();
|
||||
dailyVolume += orderValue;
|
||||
logger.info(`Trade recorded. Daily volume: ${dailyVolume.toFixed(2)} USDT`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current daily volume
|
||||
*/
|
||||
export function getDailyVolume(): number {
|
||||
checkAndResetDailyVolume();
|
||||
return dailyVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remaining daily volume allowance
|
||||
*/
|
||||
export function getRemainingDailyVolume(): number {
|
||||
checkAndResetDailyVolume();
|
||||
return Math.max(0, riskConfig.maxDailyVolumeUsdt - dailyVolume);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Exports
|
||||
// ==========================================
|
||||
|
||||
export default {
|
||||
performRiskCheck,
|
||||
recordTradeVolume,
|
||||
getDailyVolume,
|
||||
getRemainingDailyVolume,
|
||||
};
|
||||
471
apps/mcp-binance-connector/src/services/binance-client.ts
Normal file
471
apps/mcp-binance-connector/src/services/binance-client.ts
Normal file
@ -0,0 +1,471 @@
|
||||
/**
|
||||
* Binance Client Service
|
||||
*
|
||||
* CCXT wrapper for Binance operations.
|
||||
* Provides a unified interface for both Spot and Futures trading.
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
import ccxt, { Ticker, OrderBook, OHLCV, Balance, Order, Trade } from 'ccxt';
|
||||
import { createBinanceSpotClient, createBinanceFuturesClient, binanceConfig } from '../config';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
// ==========================================
|
||||
// Types
|
||||
// ==========================================
|
||||
|
||||
export interface BinanceTicker {
|
||||
symbol: string;
|
||||
price: number;
|
||||
bid: number;
|
||||
ask: number;
|
||||
high24h: number;
|
||||
low24h: number;
|
||||
volume24h: number;
|
||||
change24h: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface BinanceOrderBook {
|
||||
symbol: string;
|
||||
bids: [number, number][];
|
||||
asks: [number, number][];
|
||||
spread: number;
|
||||
spreadPercentage: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface BinanceKline {
|
||||
timestamp: number;
|
||||
open: number;
|
||||
high: number;
|
||||
low: number;
|
||||
close: number;
|
||||
volume: number;
|
||||
}
|
||||
|
||||
export interface BinanceAccountBalance {
|
||||
asset: string;
|
||||
free: number;
|
||||
locked: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface BinanceAccount {
|
||||
accountType: string;
|
||||
balances: BinanceAccountBalance[];
|
||||
canTrade: boolean;
|
||||
canWithdraw: boolean;
|
||||
updateTime: number;
|
||||
}
|
||||
|
||||
export interface BinanceOrder {
|
||||
id: string;
|
||||
symbol: string;
|
||||
side: string;
|
||||
type: string;
|
||||
price: number | null;
|
||||
amount: number;
|
||||
filled: number;
|
||||
remaining: number;
|
||||
status: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export interface CreateOrderParams {
|
||||
symbol: string;
|
||||
side: 'buy' | 'sell';
|
||||
type: 'market' | 'limit' | 'stop_loss' | 'take_profit';
|
||||
amount: number;
|
||||
price?: number;
|
||||
stopPrice?: number;
|
||||
}
|
||||
|
||||
export interface OrderResult {
|
||||
success: boolean;
|
||||
order?: BinanceOrder;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Binance Client Class
|
||||
// ==========================================
|
||||
|
||||
export class BinanceClient {
|
||||
private spotClient: ccxt.binance;
|
||||
private futuresClient: ccxt.binance;
|
||||
private marketsLoaded: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.spotClient = createBinanceSpotClient();
|
||||
this.futuresClient = createBinanceFuturesClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if client is properly configured
|
||||
*/
|
||||
isConfigured(): boolean {
|
||||
return binanceConfig.apiKey !== '' && binanceConfig.apiSecret !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test connectivity to Binance
|
||||
*/
|
||||
async isConnected(): Promise<boolean> {
|
||||
try {
|
||||
await this.spotClient.fetchTime();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load markets if not already loaded
|
||||
*/
|
||||
private async ensureMarketsLoaded(): Promise<void> {
|
||||
if (!this.marketsLoaded) {
|
||||
await this.spotClient.loadMarkets();
|
||||
this.marketsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Market Data Methods
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Get ticker for a symbol
|
||||
*/
|
||||
async getTicker(symbol: string): Promise<BinanceTicker> {
|
||||
try {
|
||||
await this.ensureMarketsLoaded();
|
||||
const ticker: Ticker = await this.spotClient.fetchTicker(symbol);
|
||||
|
||||
return {
|
||||
symbol: ticker.symbol,
|
||||
price: ticker.last ?? 0,
|
||||
bid: ticker.bid ?? 0,
|
||||
ask: ticker.ask ?? 0,
|
||||
high24h: ticker.high ?? 0,
|
||||
low24h: ticker.low ?? 0,
|
||||
volume24h: ticker.baseVolume ?? 0,
|
||||
change24h: ticker.percentage ?? 0,
|
||||
timestamp: ticker.timestamp ?? Date.now(),
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get ticker for ${symbol}`, { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order book for a symbol
|
||||
*/
|
||||
async getOrderBook(symbol: string, limit: number = 20): Promise<BinanceOrderBook> {
|
||||
try {
|
||||
await this.ensureMarketsLoaded();
|
||||
const orderbook: OrderBook = await this.spotClient.fetchOrderBook(symbol, limit);
|
||||
|
||||
const topBid = orderbook.bids[0]?.[0] ?? 0;
|
||||
const topAsk = orderbook.asks[0]?.[0] ?? 0;
|
||||
const spread = topAsk - topBid;
|
||||
const spreadPercentage = topBid > 0 ? (spread / topBid) * 100 : 0;
|
||||
|
||||
return {
|
||||
symbol,
|
||||
bids: orderbook.bids.slice(0, limit) as [number, number][],
|
||||
asks: orderbook.asks.slice(0, limit) as [number, number][],
|
||||
spread,
|
||||
spreadPercentage,
|
||||
timestamp: orderbook.timestamp ?? Date.now(),
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get order book for ${symbol}`, { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OHLCV (klines/candles) for a symbol
|
||||
*/
|
||||
async getKlines(
|
||||
symbol: string,
|
||||
interval: string = '5m',
|
||||
limit: number = 100
|
||||
): Promise<BinanceKline[]> {
|
||||
try {
|
||||
await this.ensureMarketsLoaded();
|
||||
const ohlcv: OHLCV[] = await this.spotClient.fetchOHLCV(symbol, interval, undefined, limit);
|
||||
|
||||
return ohlcv.map((candle) => ({
|
||||
timestamp: candle[0] as number,
|
||||
open: candle[1] as number,
|
||||
high: candle[2] as number,
|
||||
low: candle[3] as number,
|
||||
close: candle[4] as number,
|
||||
volume: candle[5] as number,
|
||||
}));
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get klines for ${symbol}`, { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Account Methods
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Get account balance
|
||||
*/
|
||||
async getAccount(): Promise<BinanceAccount> {
|
||||
try {
|
||||
if (!this.isConfigured()) {
|
||||
throw new Error('Binance API keys not configured');
|
||||
}
|
||||
|
||||
const balance: Balance = await this.spotClient.fetchBalance();
|
||||
|
||||
// Filter non-zero balances
|
||||
const balances: BinanceAccountBalance[] = Object.entries(balance.total)
|
||||
.filter(([_, amount]) => (amount as number) > 0)
|
||||
.map(([asset, total]) => ({
|
||||
asset,
|
||||
free: (balance.free[asset] as number) ?? 0,
|
||||
locked: (balance.used[asset] as number) ?? 0,
|
||||
total: total as number,
|
||||
}));
|
||||
|
||||
return {
|
||||
accountType: 'SPOT',
|
||||
balances,
|
||||
canTrade: true,
|
||||
canWithdraw: true,
|
||||
updateTime: Date.now(),
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to get account info', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get open orders
|
||||
*/
|
||||
async getOpenOrders(symbol?: string): Promise<BinanceOrder[]> {
|
||||
try {
|
||||
if (!this.isConfigured()) {
|
||||
throw new Error('Binance API keys not configured');
|
||||
}
|
||||
|
||||
const orders: Order[] = await this.spotClient.fetchOpenOrders(symbol);
|
||||
|
||||
return orders.map((order) => ({
|
||||
id: order.id,
|
||||
symbol: order.symbol,
|
||||
side: order.side,
|
||||
type: order.type,
|
||||
price: order.price,
|
||||
amount: order.amount,
|
||||
filled: order.filled,
|
||||
remaining: order.remaining,
|
||||
status: order.status,
|
||||
createdAt: order.timestamp ?? Date.now(),
|
||||
}));
|
||||
} catch (error) {
|
||||
logger.error('Failed to get open orders', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trade history
|
||||
*/
|
||||
async getTradeHistory(symbol: string, limit: number = 50): Promise<Trade[]> {
|
||||
try {
|
||||
if (!this.isConfigured()) {
|
||||
throw new Error('Binance API keys not configured');
|
||||
}
|
||||
|
||||
return await this.spotClient.fetchMyTrades(symbol, undefined, limit);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get trade history for ${symbol}`, { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Order Methods
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Create a new order
|
||||
*/
|
||||
async createOrder(params: CreateOrderParams): Promise<OrderResult> {
|
||||
try {
|
||||
if (!this.isConfigured()) {
|
||||
throw new Error('Binance API keys not configured');
|
||||
}
|
||||
|
||||
await this.ensureMarketsLoaded();
|
||||
|
||||
let order: Order;
|
||||
|
||||
switch (params.type) {
|
||||
case 'market':
|
||||
order = await this.spotClient.createMarketOrder(
|
||||
params.symbol,
|
||||
params.side,
|
||||
params.amount
|
||||
);
|
||||
break;
|
||||
|
||||
case 'limit':
|
||||
if (!params.price) {
|
||||
return { success: false, error: 'Price is required for limit orders' };
|
||||
}
|
||||
order = await this.spotClient.createLimitOrder(
|
||||
params.symbol,
|
||||
params.side,
|
||||
params.amount,
|
||||
params.price
|
||||
);
|
||||
break;
|
||||
|
||||
case 'stop_loss':
|
||||
if (!params.stopPrice) {
|
||||
return { success: false, error: 'Stop price is required for stop loss orders' };
|
||||
}
|
||||
order = await this.spotClient.createOrder(
|
||||
params.symbol,
|
||||
'stop_loss',
|
||||
params.side,
|
||||
params.amount,
|
||||
undefined,
|
||||
{ stopPrice: params.stopPrice }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'take_profit':
|
||||
if (!params.stopPrice) {
|
||||
return { success: false, error: 'Stop price is required for take profit orders' };
|
||||
}
|
||||
order = await this.spotClient.createOrder(
|
||||
params.symbol,
|
||||
'take_profit',
|
||||
params.side,
|
||||
params.amount,
|
||||
undefined,
|
||||
{ stopPrice: params.stopPrice }
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
return { success: false, error: `Unsupported order type: ${params.type}` };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
order: {
|
||||
id: order.id,
|
||||
symbol: order.symbol,
|
||||
side: order.side,
|
||||
type: order.type,
|
||||
price: order.price ?? order.average ?? null,
|
||||
amount: order.amount,
|
||||
filled: order.filled,
|
||||
remaining: order.remaining,
|
||||
status: order.status,
|
||||
createdAt: order.timestamp ?? Date.now(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error('Failed to create order', { error, params });
|
||||
return { success: false, error: message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel an order
|
||||
*/
|
||||
async cancelOrder(orderId: string, symbol: string): Promise<OrderResult> {
|
||||
try {
|
||||
if (!this.isConfigured()) {
|
||||
throw new Error('Binance API keys not configured');
|
||||
}
|
||||
|
||||
const result = await this.spotClient.cancelOrder(orderId, symbol);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
order: {
|
||||
id: result.id,
|
||||
symbol: result.symbol,
|
||||
side: result.side,
|
||||
type: result.type,
|
||||
price: result.price,
|
||||
amount: result.amount,
|
||||
filled: result.filled,
|
||||
remaining: result.remaining,
|
||||
status: 'CANCELLED',
|
||||
createdAt: result.timestamp ?? Date.now(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error('Failed to cancel order', { error, orderId, symbol });
|
||||
return { success: false, error: message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all orders for a symbol
|
||||
*/
|
||||
async cancelAllOrders(symbol: string): Promise<{ success: boolean; cancelledCount: number; error?: string }> {
|
||||
try {
|
||||
if (!this.isConfigured()) {
|
||||
throw new Error('Binance API keys not configured');
|
||||
}
|
||||
|
||||
const result = await this.spotClient.cancelAllOrders(symbol);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
cancelledCount: Array.isArray(result) ? result.length : 0,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error('Failed to cancel all orders', { error, symbol });
|
||||
return { success: false, cancelledCount: 0, error: message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current price for a symbol (helper method)
|
||||
*/
|
||||
async getCurrentPrice(symbol: string): Promise<number> {
|
||||
const ticker = await this.getTicker(symbol);
|
||||
return ticker.price;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Singleton Instance
|
||||
// ==========================================
|
||||
|
||||
let clientInstance: BinanceClient | null = null;
|
||||
|
||||
export function getBinanceClient(): BinanceClient {
|
||||
if (!clientInstance) {
|
||||
clientInstance = new BinanceClient();
|
||||
}
|
||||
return clientInstance;
|
||||
}
|
||||
|
||||
export function resetBinanceClient(): void {
|
||||
clientInstance = null;
|
||||
}
|
||||
265
apps/mcp-binance-connector/src/tools/account.ts
Normal file
265
apps/mcp-binance-connector/src/tools/account.ts
Normal file
@ -0,0 +1,265 @@
|
||||
/**
|
||||
* Binance Account Tools
|
||||
*
|
||||
* - binance_get_account: Get account balance and status
|
||||
* - binance_get_open_orders: Get all open orders
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { getBinanceClient, BinanceAccount, BinanceOrder } from '../services/binance-client';
|
||||
|
||||
// ==========================================
|
||||
// binance_get_account
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Tool: binance_get_account
|
||||
* Get account balance and status
|
||||
*/
|
||||
export const binanceGetAccountSchema = {
|
||||
name: 'binance_get_account',
|
||||
description: 'Get Binance account balance and status. Shows all assets with non-zero balance.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {},
|
||||
required: [] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const BinanceGetAccountInputSchema = z.object({});
|
||||
|
||||
export type BinanceGetAccountInput = z.infer<typeof BinanceGetAccountInputSchema>;
|
||||
|
||||
export interface BinanceGetAccountResult {
|
||||
success: boolean;
|
||||
data?: BinanceAccount & { totalUsdtEstimate?: number };
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function binance_get_account(
|
||||
_params: BinanceGetAccountInput
|
||||
): Promise<BinanceGetAccountResult> {
|
||||
try {
|
||||
const client = getBinanceClient();
|
||||
|
||||
if (!client.isConfigured()) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Binance API keys are not configured',
|
||||
};
|
||||
}
|
||||
|
||||
const connected = await client.isConnected();
|
||||
if (!connected) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Cannot connect to Binance. Please check your network.',
|
||||
};
|
||||
}
|
||||
|
||||
const account = await client.getAccount();
|
||||
|
||||
// Estimate total value in USDT
|
||||
let totalUsdtEstimate = 0;
|
||||
for (const balance of account.balances) {
|
||||
if (balance.asset === 'USDT' || balance.asset === 'BUSD' || balance.asset === 'USDC') {
|
||||
totalUsdtEstimate += balance.total;
|
||||
} else if (balance.total > 0) {
|
||||
try {
|
||||
const price = await client.getCurrentPrice(`${balance.asset}USDT`);
|
||||
totalUsdtEstimate += balance.total * price;
|
||||
} catch {
|
||||
// Skip if no USDT pair exists
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
...account,
|
||||
totalUsdtEstimate,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleBinanceGetAccount(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = BinanceGetAccountInputSchema.parse(params);
|
||||
const result = await binance_get_account(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const d = result.data;
|
||||
|
||||
// Sort balances by total value
|
||||
const sortedBalances = [...d.balances].sort((a, b) => {
|
||||
// USDT first, then by total
|
||||
if (a.asset === 'USDT') return -1;
|
||||
if (b.asset === 'USDT') return 1;
|
||||
return b.total - a.total;
|
||||
});
|
||||
|
||||
let balancesStr = sortedBalances
|
||||
.slice(0, 20) // Top 20 assets
|
||||
.map((b) => {
|
||||
const lockedStr = b.locked > 0 ? ` (Locked: ${b.locked.toFixed(8)})` : '';
|
||||
return ` ${b.asset.padEnd(8)} Free: ${b.free.toFixed(8)}${lockedStr}`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
const formattedOutput = `
|
||||
Binance Account Information
|
||||
${'='.repeat(35)}
|
||||
Account Type: ${d.accountType}
|
||||
Can Trade: ${d.canTrade ? 'Yes' : 'No'}
|
||||
Can Withdraw: ${d.canWithdraw ? 'Yes' : 'No'}
|
||||
|
||||
Estimated Total Value
|
||||
---------------------
|
||||
~$${d.totalUsdtEstimate?.toFixed(2) ?? 'N/A'} USDT
|
||||
|
||||
Asset Balances (${d.balances.length} with balance)
|
||||
${'='.repeat(35)}
|
||||
${balancesStr}
|
||||
${d.balances.length > 20 ? `\n ... and ${d.balances.length - 20} more assets` : ''}
|
||||
|
||||
Last Update: ${new Date(d.updateTime).toISOString()}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: formattedOutput }],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// binance_get_open_orders
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Tool: binance_get_open_orders
|
||||
* Get all open (pending) orders
|
||||
*/
|
||||
export const binanceGetOpenOrdersSchema = {
|
||||
name: 'binance_get_open_orders',
|
||||
description: 'Get all open (pending) orders. Optionally filter by symbol.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Optional: Filter by trading pair symbol (e.g., BTCUSDT)',
|
||||
},
|
||||
},
|
||||
required: [] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const BinanceGetOpenOrdersInputSchema = z.object({
|
||||
symbol: z.string().min(1).max(20).transform((s) => s.toUpperCase()).optional(),
|
||||
});
|
||||
|
||||
export type BinanceGetOpenOrdersInput = z.infer<typeof BinanceGetOpenOrdersInputSchema>;
|
||||
|
||||
export interface BinanceGetOpenOrdersResult {
|
||||
success: boolean;
|
||||
data?: {
|
||||
orders: BinanceOrder[];
|
||||
count: number;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function binance_get_open_orders(
|
||||
params: BinanceGetOpenOrdersInput
|
||||
): Promise<BinanceGetOpenOrdersResult> {
|
||||
try {
|
||||
const client = getBinanceClient();
|
||||
|
||||
if (!client.isConfigured()) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Binance API keys are not configured',
|
||||
};
|
||||
}
|
||||
|
||||
const orders = await client.getOpenOrders(params.symbol);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
orders,
|
||||
count: orders.length,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleBinanceGetOpenOrders(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = BinanceGetOpenOrdersInputSchema.parse(params);
|
||||
const result = await binance_get_open_orders(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const d = result.data;
|
||||
|
||||
if (d.count === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `No open orders${validatedParams.symbol ? ` for ${validatedParams.symbol}` : ''}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
let ordersStr = d.orders
|
||||
.map((o) => {
|
||||
const priceStr = o.price ? `$${o.price.toFixed(8)}` : 'MARKET';
|
||||
const filledPct = o.amount > 0 ? ((o.filled / o.amount) * 100).toFixed(1) : '0';
|
||||
return ` #${o.id}
|
||||
Symbol: ${o.symbol} | ${o.side.toUpperCase()} | ${o.type.toUpperCase()}
|
||||
Price: ${priceStr} | Amount: ${o.amount.toFixed(8)}
|
||||
Filled: ${o.filled.toFixed(8)} (${filledPct}%) | Remaining: ${o.remaining.toFixed(8)}
|
||||
Status: ${o.status} | Created: ${new Date(o.createdAt).toISOString()}`;
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
const formattedOutput = `
|
||||
Open Orders${validatedParams.symbol ? ` - ${validatedParams.symbol}` : ''}
|
||||
${'='.repeat(35)}
|
||||
Total Orders: ${d.count}
|
||||
|
||||
${ordersStr}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: formattedOutput }],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
||||
};
|
||||
}
|
||||
288
apps/mcp-binance-connector/src/tools/index.ts
Normal file
288
apps/mcp-binance-connector/src/tools/index.ts
Normal file
@ -0,0 +1,288 @@
|
||||
/**
|
||||
* MCP Tools Index
|
||||
*
|
||||
* Exports all Binance MCP tools and their schemas for registration
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
// Import handlers for use in toolHandlers map
|
||||
import { handleBinanceGetTicker, handleBinanceGetOrderbook, handleBinanceGetKlines } from './market';
|
||||
import { handleBinanceGetAccount, handleBinanceGetOpenOrders } from './account';
|
||||
import { handleBinanceCreateOrder, handleBinanceCancelOrder } from './orders';
|
||||
|
||||
// ==========================================
|
||||
// Market Tools Exports
|
||||
// ==========================================
|
||||
|
||||
export {
|
||||
binanceGetTickerSchema,
|
||||
binance_get_ticker,
|
||||
handleBinanceGetTicker,
|
||||
BinanceGetTickerInputSchema,
|
||||
type BinanceGetTickerInput,
|
||||
type BinanceGetTickerResult,
|
||||
binanceGetOrderbookSchema,
|
||||
binance_get_orderbook,
|
||||
handleBinanceGetOrderbook,
|
||||
BinanceGetOrderbookInputSchema,
|
||||
type BinanceGetOrderbookInput,
|
||||
type BinanceGetOrderbookResult,
|
||||
binanceGetKlinesSchema,
|
||||
binance_get_klines,
|
||||
handleBinanceGetKlines,
|
||||
BinanceGetKlinesInputSchema,
|
||||
type BinanceGetKlinesInput,
|
||||
type BinanceGetKlinesResult,
|
||||
} from './market';
|
||||
|
||||
// ==========================================
|
||||
// Account Tools Exports
|
||||
// ==========================================
|
||||
|
||||
export {
|
||||
binanceGetAccountSchema,
|
||||
binance_get_account,
|
||||
handleBinanceGetAccount,
|
||||
BinanceGetAccountInputSchema,
|
||||
type BinanceGetAccountInput,
|
||||
type BinanceGetAccountResult,
|
||||
binanceGetOpenOrdersSchema,
|
||||
binance_get_open_orders,
|
||||
handleBinanceGetOpenOrders,
|
||||
BinanceGetOpenOrdersInputSchema,
|
||||
type BinanceGetOpenOrdersInput,
|
||||
type BinanceGetOpenOrdersResult,
|
||||
} from './account';
|
||||
|
||||
// ==========================================
|
||||
// Order Tools Exports
|
||||
// ==========================================
|
||||
|
||||
export {
|
||||
binanceCreateOrderSchema,
|
||||
binance_create_order,
|
||||
handleBinanceCreateOrder,
|
||||
BinanceCreateOrderInputSchema,
|
||||
type BinanceCreateOrderInput,
|
||||
type BinanceCreateOrderResult,
|
||||
binanceCancelOrderSchema,
|
||||
binance_cancel_order,
|
||||
handleBinanceCancelOrder,
|
||||
BinanceCancelOrderInputSchema,
|
||||
type BinanceCancelOrderInput,
|
||||
type BinanceCancelOrderResult,
|
||||
} from './orders';
|
||||
|
||||
// ==========================================
|
||||
// Tool Registry
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* All available MCP tools with their schemas
|
||||
* Follows MCP protocol format
|
||||
*/
|
||||
export const mcpToolSchemas = [
|
||||
// Market Data Tools (Low Risk)
|
||||
{
|
||||
name: 'binance_get_ticker',
|
||||
description: 'Get the current price and 24-hour statistics for a Binance trading pair',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT, ETHUSDT, BNBUSDT)',
|
||||
},
|
||||
},
|
||||
required: ['symbol'] as string[],
|
||||
},
|
||||
riskLevel: 'LOW',
|
||||
},
|
||||
{
|
||||
name: 'binance_get_orderbook',
|
||||
description: 'Get the order book (bids and asks) with the specified depth for a trading pair',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Order book depth (5, 10, 20, 50, or 100). Default: 20',
|
||||
},
|
||||
},
|
||||
required: ['symbol'] as string[],
|
||||
},
|
||||
riskLevel: 'LOW',
|
||||
},
|
||||
{
|
||||
name: 'binance_get_klines',
|
||||
description: 'Get historical candlestick (OHLCV) data for technical analysis',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT)',
|
||||
},
|
||||
interval: {
|
||||
type: 'string',
|
||||
description: 'Candle interval: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w. Default: 5m',
|
||||
enum: ['1m', '5m', '15m', '30m', '1h', '4h', '1d', '1w'],
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Number of candles to retrieve (max 500). Default: 100',
|
||||
},
|
||||
},
|
||||
required: ['symbol'] as string[],
|
||||
},
|
||||
riskLevel: 'LOW',
|
||||
},
|
||||
|
||||
// Account Tools (Medium Risk)
|
||||
{
|
||||
name: 'binance_get_account',
|
||||
description: 'Get Binance account balance and status. Shows all assets with non-zero balance.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {},
|
||||
required: [] as string[],
|
||||
},
|
||||
riskLevel: 'MEDIUM',
|
||||
},
|
||||
{
|
||||
name: 'binance_get_open_orders',
|
||||
description: 'Get all open (pending) orders. Optionally filter by symbol.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Optional: Filter by trading pair symbol (e.g., BTCUSDT)',
|
||||
},
|
||||
},
|
||||
required: [] as string[],
|
||||
},
|
||||
riskLevel: 'MEDIUM',
|
||||
},
|
||||
|
||||
// Order Tools (High Risk)
|
||||
{
|
||||
name: 'binance_create_order',
|
||||
description: 'Create a new buy or sell order on Binance. HIGH RISK - Ensure you validate with the user before executing.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT, ETHUSDT)',
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
enum: ['buy', 'sell'],
|
||||
description: 'Order direction: buy or sell',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['market', 'limit', 'stop_loss', 'take_profit'],
|
||||
description: 'Order type. Default: market',
|
||||
},
|
||||
amount: {
|
||||
type: 'number',
|
||||
description: 'Amount of the base asset to buy/sell',
|
||||
},
|
||||
price: {
|
||||
type: 'number',
|
||||
description: 'Price per unit (required for limit orders)',
|
||||
},
|
||||
stopPrice: {
|
||||
type: 'number',
|
||||
description: 'Stop price (required for stop_loss and take_profit orders)',
|
||||
},
|
||||
},
|
||||
required: ['symbol', 'side', 'amount'] as string[],
|
||||
},
|
||||
riskLevel: 'HIGH',
|
||||
requiresConfirmation: true,
|
||||
},
|
||||
{
|
||||
name: 'binance_cancel_order',
|
||||
description: 'Cancel a pending order by order ID and symbol',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
description: 'Order ID to cancel',
|
||||
},
|
||||
},
|
||||
required: ['symbol', 'orderId'] as string[],
|
||||
},
|
||||
riskLevel: 'MEDIUM',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Tool handler routing map
|
||||
* Maps tool names to their handler functions
|
||||
*/
|
||||
export const toolHandlers: Record<
|
||||
string,
|
||||
(params: unknown) => Promise<{ content: Array<{ type: string; text: string }> }>
|
||||
> = {
|
||||
// Market tools
|
||||
binance_get_ticker: handleBinanceGetTicker,
|
||||
binance_get_orderbook: handleBinanceGetOrderbook,
|
||||
binance_get_klines: handleBinanceGetKlines,
|
||||
|
||||
// Account tools
|
||||
binance_get_account: handleBinanceGetAccount,
|
||||
binance_get_open_orders: handleBinanceGetOpenOrders,
|
||||
|
||||
// Order tools
|
||||
binance_create_order: handleBinanceCreateOrder,
|
||||
binance_cancel_order: handleBinanceCancelOrder,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all tool definitions for MCP protocol
|
||||
*/
|
||||
export function getAllToolDefinitions() {
|
||||
return mcpToolSchemas.map((tool) => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool by name
|
||||
*/
|
||||
export function getToolByName(name: string) {
|
||||
return mcpToolSchemas.find((tool) => tool.name === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool requires confirmation
|
||||
*/
|
||||
export function toolRequiresConfirmation(name: string): boolean {
|
||||
const tool = mcpToolSchemas.find((t) => t.name === name);
|
||||
return (tool as { requiresConfirmation?: boolean })?.requiresConfirmation === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool risk level
|
||||
*/
|
||||
export function getToolRiskLevel(name: string): string {
|
||||
const tool = mcpToolSchemas.find((t) => t.name === name);
|
||||
return (tool as { riskLevel?: string })?.riskLevel ?? 'UNKNOWN';
|
||||
}
|
||||
392
apps/mcp-binance-connector/src/tools/market.ts
Normal file
392
apps/mcp-binance-connector/src/tools/market.ts
Normal file
@ -0,0 +1,392 @@
|
||||
/**
|
||||
* Binance Market Data Tools
|
||||
*
|
||||
* - binance_get_ticker: Get current price and 24h stats
|
||||
* - binance_get_orderbook: Get order book depth
|
||||
* - binance_get_klines: Get OHLCV candles
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { getBinanceClient, BinanceTicker, BinanceOrderBook, BinanceKline } from '../services/binance-client';
|
||||
|
||||
// ==========================================
|
||||
// binance_get_ticker
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Tool: binance_get_ticker
|
||||
* Get current price and 24h statistics for a trading pair
|
||||
*/
|
||||
export const binanceGetTickerSchema = {
|
||||
name: 'binance_get_ticker',
|
||||
description: 'Get the current price and 24-hour statistics for a Binance trading pair',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT, ETHUSDT, BNBUSDT)',
|
||||
},
|
||||
},
|
||||
required: ['symbol'] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const BinanceGetTickerInputSchema = z.object({
|
||||
symbol: z.string().min(1).max(20).transform((s) => s.toUpperCase()),
|
||||
});
|
||||
|
||||
export type BinanceGetTickerInput = z.infer<typeof BinanceGetTickerInputSchema>;
|
||||
|
||||
export interface BinanceGetTickerResult {
|
||||
success: boolean;
|
||||
data?: BinanceTicker;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function binance_get_ticker(
|
||||
params: BinanceGetTickerInput
|
||||
): Promise<BinanceGetTickerResult> {
|
||||
try {
|
||||
const client = getBinanceClient();
|
||||
const ticker = await client.getTicker(params.symbol);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: ticker,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleBinanceGetTicker(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = BinanceGetTickerInputSchema.parse(params);
|
||||
const result = await binance_get_ticker(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const d = result.data;
|
||||
const changeSymbol = d.change24h >= 0 ? '+' : '';
|
||||
|
||||
const formattedOutput = `
|
||||
Binance Ticker: ${d.symbol}
|
||||
${'='.repeat(35)}
|
||||
Current Price: $${d.price.toFixed(getPriceDecimals(d.symbol))}
|
||||
Bid: $${d.bid.toFixed(getPriceDecimals(d.symbol))}
|
||||
Ask: $${d.ask.toFixed(getPriceDecimals(d.symbol))}
|
||||
|
||||
24h Statistics
|
||||
--------------
|
||||
High: $${d.high24h.toFixed(getPriceDecimals(d.symbol))}
|
||||
Low: $${d.low24h.toFixed(getPriceDecimals(d.symbol))}
|
||||
Volume: ${formatVolume(d.volume24h)}
|
||||
Change: ${changeSymbol}${d.change24h.toFixed(2)}%
|
||||
|
||||
Last Update: ${new Date(d.timestamp).toISOString()}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: formattedOutput }],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// binance_get_orderbook
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Tool: binance_get_orderbook
|
||||
* Get order book (bids and asks) with specified depth
|
||||
*/
|
||||
export const binanceGetOrderbookSchema = {
|
||||
name: 'binance_get_orderbook',
|
||||
description: 'Get the order book (bids and asks) with the specified depth for a trading pair',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Order book depth (5, 10, 20, 50, or 100). Default: 20',
|
||||
},
|
||||
},
|
||||
required: ['symbol'] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const BinanceGetOrderbookInputSchema = z.object({
|
||||
symbol: z.string().min(1).max(20).transform((s) => s.toUpperCase()),
|
||||
limit: z.number().int().min(5).max(100).default(20),
|
||||
});
|
||||
|
||||
export type BinanceGetOrderbookInput = z.infer<typeof BinanceGetOrderbookInputSchema>;
|
||||
|
||||
export interface BinanceGetOrderbookResult {
|
||||
success: boolean;
|
||||
data?: BinanceOrderBook;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function binance_get_orderbook(
|
||||
params: BinanceGetOrderbookInput
|
||||
): Promise<BinanceGetOrderbookResult> {
|
||||
try {
|
||||
const client = getBinanceClient();
|
||||
const orderbook = await client.getOrderBook(params.symbol, params.limit);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: orderbook,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleBinanceGetOrderbook(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = BinanceGetOrderbookInputSchema.parse(params);
|
||||
const result = await binance_get_orderbook(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const d = result.data;
|
||||
const decimals = getPriceDecimals(d.symbol);
|
||||
|
||||
// Format top 10 levels
|
||||
const topBids = d.bids.slice(0, 10);
|
||||
const topAsks = d.asks.slice(0, 10);
|
||||
|
||||
let bidsStr = topBids
|
||||
.map(([price, qty]) => ` $${price.toFixed(decimals)} | ${qty.toFixed(6)}`)
|
||||
.join('\n');
|
||||
|
||||
let asksStr = topAsks
|
||||
.map(([price, qty]) => ` $${price.toFixed(decimals)} | ${qty.toFixed(6)}`)
|
||||
.join('\n');
|
||||
|
||||
const formattedOutput = `
|
||||
Order Book: ${d.symbol}
|
||||
${'='.repeat(35)}
|
||||
Spread: $${d.spread.toFixed(decimals)} (${d.spreadPercentage.toFixed(4)}%)
|
||||
|
||||
Top ${topAsks.length} Asks (Sell Orders)
|
||||
${'-'.repeat(25)}
|
||||
${asksStr}
|
||||
|
||||
Top ${topBids.length} Bids (Buy Orders)
|
||||
${'-'.repeat(25)}
|
||||
${bidsStr}
|
||||
|
||||
Timestamp: ${new Date(d.timestamp).toISOString()}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: formattedOutput }],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// binance_get_klines
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Tool: binance_get_klines
|
||||
* Get historical OHLCV candles for technical analysis
|
||||
*/
|
||||
export const binanceGetKlinesSchema = {
|
||||
name: 'binance_get_klines',
|
||||
description: 'Get historical candlestick (OHLCV) data for technical analysis',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT)',
|
||||
},
|
||||
interval: {
|
||||
type: 'string',
|
||||
description: 'Candle interval: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w. Default: 5m',
|
||||
enum: ['1m', '5m', '15m', '30m', '1h', '4h', '1d', '1w'],
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Number of candles to retrieve (max 500). Default: 100',
|
||||
},
|
||||
},
|
||||
required: ['symbol'] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const BinanceGetKlinesInputSchema = z.object({
|
||||
symbol: z.string().min(1).max(20).transform((s) => s.toUpperCase()),
|
||||
interval: z.enum(['1m', '5m', '15m', '30m', '1h', '4h', '1d', '1w']).default('5m'),
|
||||
limit: z.number().int().min(1).max(500).default(100),
|
||||
});
|
||||
|
||||
export type BinanceGetKlinesInput = z.infer<typeof BinanceGetKlinesInputSchema>;
|
||||
|
||||
export interface BinanceGetKlinesResult {
|
||||
success: boolean;
|
||||
data?: {
|
||||
symbol: string;
|
||||
interval: string;
|
||||
candles: BinanceKline[];
|
||||
count: number;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function binance_get_klines(
|
||||
params: BinanceGetKlinesInput
|
||||
): Promise<BinanceGetKlinesResult> {
|
||||
try {
|
||||
const client = getBinanceClient();
|
||||
const klines = await client.getKlines(params.symbol, params.interval, params.limit);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
symbol: params.symbol,
|
||||
interval: params.interval,
|
||||
candles: klines,
|
||||
count: klines.length,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleBinanceGetKlines(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = BinanceGetKlinesInputSchema.parse(params);
|
||||
const result = await binance_get_klines(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const d = result.data;
|
||||
const decimals = getPriceDecimals(d.symbol);
|
||||
|
||||
// Get last 5 candles for display
|
||||
const recentCandles = d.candles.slice(-5);
|
||||
|
||||
let candlesStr = recentCandles
|
||||
.map((c) => {
|
||||
const time = new Date(c.timestamp).toISOString().slice(0, 16).replace('T', ' ');
|
||||
const direction = c.close >= c.open ? 'UP' : 'DOWN';
|
||||
return ` ${time} | O:${c.open.toFixed(decimals)} H:${c.high.toFixed(decimals)} L:${c.low.toFixed(decimals)} C:${c.close.toFixed(decimals)} | V:${formatVolume(c.volume)} | ${direction}`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
// Calculate basic stats
|
||||
const closes = d.candles.map((c) => c.close);
|
||||
const high = Math.max(...d.candles.map((c) => c.high));
|
||||
const low = Math.min(...d.candles.map((c) => c.low));
|
||||
const avgVolume = d.candles.reduce((sum, c) => sum + c.volume, 0) / d.candles.length;
|
||||
|
||||
const formattedOutput = `
|
||||
Klines: ${d.symbol} (${d.interval})
|
||||
${'='.repeat(45)}
|
||||
Retrieved: ${d.count} candles
|
||||
|
||||
Period Statistics
|
||||
-----------------
|
||||
Highest High: $${high.toFixed(decimals)}
|
||||
Lowest Low: $${low.toFixed(decimals)}
|
||||
Avg Volume: ${formatVolume(avgVolume)}
|
||||
|
||||
Recent Candles (last 5)
|
||||
-----------------------
|
||||
${candlesStr}
|
||||
|
||||
First Candle: ${new Date(d.candles[0].timestamp).toISOString()}
|
||||
Last Candle: ${new Date(d.candles[d.candles.length - 1].timestamp).toISOString()}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: formattedOutput }],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Helper Functions
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Get appropriate decimal places for price display
|
||||
*/
|
||||
function getPriceDecimals(symbol: string): number {
|
||||
const upper = symbol.toUpperCase();
|
||||
|
||||
// Stablecoins and fiat pairs
|
||||
if (upper.includes('USD') && !upper.startsWith('BTC') && !upper.startsWith('ETH')) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// BTC pairs
|
||||
if (upper === 'BTCUSDT' || upper === 'BTCBUSD') {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// ETH pairs
|
||||
if (upper === 'ETHUSDT' || upper === 'ETHBUSD') {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Small value coins
|
||||
if (upper.includes('SHIB') || upper.includes('DOGE') || upper.includes('PEPE')) {
|
||||
return 8;
|
||||
}
|
||||
|
||||
// Default
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format large volume numbers
|
||||
*/
|
||||
function formatVolume(volume: number): string {
|
||||
if (volume >= 1_000_000_000) {
|
||||
return `${(volume / 1_000_000_000).toFixed(2)}B`;
|
||||
}
|
||||
if (volume >= 1_000_000) {
|
||||
return `${(volume / 1_000_000).toFixed(2)}M`;
|
||||
}
|
||||
if (volume >= 1_000) {
|
||||
return `${(volume / 1_000).toFixed(2)}K`;
|
||||
}
|
||||
return volume.toFixed(4);
|
||||
}
|
||||
334
apps/mcp-binance-connector/src/tools/orders.ts
Normal file
334
apps/mcp-binance-connector/src/tools/orders.ts
Normal file
@ -0,0 +1,334 @@
|
||||
/**
|
||||
* Binance Order Management Tools
|
||||
*
|
||||
* - binance_create_order: Create a new order (HIGH RISK)
|
||||
* - binance_cancel_order: Cancel a pending order
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { getBinanceClient, BinanceOrder, CreateOrderParams } from '../services/binance-client';
|
||||
import { performRiskCheck, recordTradeVolume } from '../middleware/risk-check';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
// ==========================================
|
||||
// binance_create_order
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Tool: binance_create_order
|
||||
* Create a new buy or sell order
|
||||
* HIGH RISK - Requires confirmation
|
||||
*/
|
||||
export const binanceCreateOrderSchema = {
|
||||
name: 'binance_create_order',
|
||||
description: 'Create a new buy or sell order on Binance. HIGH RISK - Ensure you validate with the user before executing.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT, ETHUSDT)',
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
enum: ['buy', 'sell'],
|
||||
description: 'Order direction: buy or sell',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['market', 'limit', 'stop_loss', 'take_profit'],
|
||||
description: 'Order type. Default: market',
|
||||
},
|
||||
amount: {
|
||||
type: 'number',
|
||||
description: 'Amount of the base asset to buy/sell',
|
||||
},
|
||||
price: {
|
||||
type: 'number',
|
||||
description: 'Price per unit (required for limit orders)',
|
||||
},
|
||||
stopPrice: {
|
||||
type: 'number',
|
||||
description: 'Stop price (required for stop_loss and take_profit orders)',
|
||||
},
|
||||
},
|
||||
required: ['symbol', 'side', 'amount'] as string[],
|
||||
},
|
||||
riskLevel: 'HIGH',
|
||||
requiresConfirmation: true,
|
||||
};
|
||||
|
||||
export const BinanceCreateOrderInputSchema = z.object({
|
||||
symbol: z.string().min(1).max(20).transform((s) => s.toUpperCase()),
|
||||
side: z.enum(['buy', 'sell']),
|
||||
type: z.enum(['market', 'limit', 'stop_loss', 'take_profit']).default('market'),
|
||||
amount: z.number().positive(),
|
||||
price: z.number().positive().optional(),
|
||||
stopPrice: z.number().positive().optional(),
|
||||
});
|
||||
|
||||
export type BinanceCreateOrderInput = z.infer<typeof BinanceCreateOrderInputSchema>;
|
||||
|
||||
export interface BinanceCreateOrderResult {
|
||||
success: boolean;
|
||||
data?: {
|
||||
order: BinanceOrder;
|
||||
riskWarnings?: string[];
|
||||
};
|
||||
error?: string;
|
||||
riskCheckFailed?: boolean;
|
||||
}
|
||||
|
||||
export async function binance_create_order(
|
||||
params: BinanceCreateOrderInput
|
||||
): Promise<BinanceCreateOrderResult> {
|
||||
try {
|
||||
const client = getBinanceClient();
|
||||
|
||||
if (!client.isConfigured()) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Binance API keys are not configured',
|
||||
};
|
||||
}
|
||||
|
||||
// 1. Perform risk check
|
||||
const riskCheck = await performRiskCheck({
|
||||
symbol: params.symbol,
|
||||
side: params.side,
|
||||
amount: params.amount,
|
||||
price: params.price,
|
||||
});
|
||||
|
||||
if (!riskCheck.allowed) {
|
||||
logger.warn('Order rejected by risk check', {
|
||||
params,
|
||||
reason: riskCheck.reason,
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: riskCheck.reason,
|
||||
riskCheckFailed: true,
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Validate order parameters
|
||||
if (params.type === 'limit' && !params.price) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Price is required for limit orders',
|
||||
};
|
||||
}
|
||||
|
||||
if ((params.type === 'stop_loss' || params.type === 'take_profit') && !params.stopPrice) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Stop price is required for ${params.type} orders`,
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Create the order
|
||||
const orderParams: CreateOrderParams = {
|
||||
symbol: params.symbol,
|
||||
side: params.side,
|
||||
type: params.type,
|
||||
amount: params.amount,
|
||||
price: params.price,
|
||||
stopPrice: params.stopPrice,
|
||||
};
|
||||
|
||||
const result = await client.createOrder(orderParams);
|
||||
|
||||
if (result.success && result.order) {
|
||||
// Record trade volume for daily limit tracking
|
||||
if (riskCheck.orderValue) {
|
||||
recordTradeVolume(riskCheck.orderValue);
|
||||
}
|
||||
|
||||
logger.info('Order created successfully', {
|
||||
orderId: result.order.id,
|
||||
symbol: params.symbol,
|
||||
side: params.side,
|
||||
amount: params.amount,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
order: result.order,
|
||||
riskWarnings: riskCheck.warnings,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to create order',
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Order creation failed', { error, params });
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleBinanceCreateOrder(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = BinanceCreateOrderInputSchema.parse(params);
|
||||
const result = await binance_create_order(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const o = result.data.order;
|
||||
const priceStr = o.price ? `$${o.price.toFixed(8)}` : 'MARKET';
|
||||
|
||||
let warningsStr = '';
|
||||
if (result.data.riskWarnings && result.data.riskWarnings.length > 0) {
|
||||
warningsStr = `\n\nWarnings:\n${result.data.riskWarnings.map((w) => ` - ${w}`).join('\n')}`;
|
||||
}
|
||||
|
||||
const formattedOutput = `
|
||||
Order Created Successfully
|
||||
${'='.repeat(35)}
|
||||
Order ID: ${o.id}
|
||||
Symbol: ${o.symbol}
|
||||
Side: ${o.side.toUpperCase()}
|
||||
Type: ${o.type.toUpperCase()}
|
||||
Price: ${priceStr}
|
||||
Amount: ${o.amount.toFixed(8)}
|
||||
Filled: ${o.filled.toFixed(8)}
|
||||
Status: ${o.status}
|
||||
Created: ${new Date(o.createdAt).toISOString()}${warningsStr}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: formattedOutput }],
|
||||
};
|
||||
}
|
||||
|
||||
const errorPrefix = result.riskCheckFailed ? 'Risk Check Failed: ' : 'Error: ';
|
||||
return {
|
||||
content: [{ type: 'text', text: `${errorPrefix}${result.error}` }],
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// binance_cancel_order
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Tool: binance_cancel_order
|
||||
* Cancel a pending order
|
||||
*/
|
||||
export const binanceCancelOrderSchema = {
|
||||
name: 'binance_cancel_order',
|
||||
description: 'Cancel a pending order by order ID and symbol',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading pair symbol (e.g., BTCUSDT)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
description: 'Order ID to cancel',
|
||||
},
|
||||
},
|
||||
required: ['symbol', 'orderId'] as string[],
|
||||
},
|
||||
riskLevel: 'MEDIUM',
|
||||
};
|
||||
|
||||
export const BinanceCancelOrderInputSchema = z.object({
|
||||
symbol: z.string().min(1).max(20).transform((s) => s.toUpperCase()),
|
||||
orderId: z.string().min(1),
|
||||
});
|
||||
|
||||
export type BinanceCancelOrderInput = z.infer<typeof BinanceCancelOrderInputSchema>;
|
||||
|
||||
export interface BinanceCancelOrderResult {
|
||||
success: boolean;
|
||||
data?: {
|
||||
cancelledOrder: BinanceOrder;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function binance_cancel_order(
|
||||
params: BinanceCancelOrderInput
|
||||
): Promise<BinanceCancelOrderResult> {
|
||||
try {
|
||||
const client = getBinanceClient();
|
||||
|
||||
if (!client.isConfigured()) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Binance API keys are not configured',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await client.cancelOrder(params.orderId, params.symbol);
|
||||
|
||||
if (result.success && result.order) {
|
||||
logger.info('Order cancelled successfully', {
|
||||
orderId: params.orderId,
|
||||
symbol: params.symbol,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
cancelledOrder: result.order,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Failed to cancel order',
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Order cancellation failed', { error, params });
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleBinanceCancelOrder(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = BinanceCancelOrderInputSchema.parse(params);
|
||||
const result = await binance_cancel_order(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const o = result.data.cancelledOrder;
|
||||
|
||||
const formattedOutput = `
|
||||
Order Cancelled Successfully
|
||||
${'='.repeat(35)}
|
||||
Order ID: ${o.id}
|
||||
Symbol: ${o.symbol}
|
||||
Side: ${o.side.toUpperCase()}
|
||||
Type: ${o.type.toUpperCase()}
|
||||
Original Amount: ${o.amount.toFixed(8)}
|
||||
Filled Before Cancel: ${o.filled.toFixed(8)}
|
||||
Status: ${o.status}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: formattedOutput }],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
||||
};
|
||||
}
|
||||
67
apps/mcp-binance-connector/src/utils/logger.ts
Normal file
67
apps/mcp-binance-connector/src/utils/logger.ts
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Logger Utility
|
||||
*
|
||||
* Winston-based logging for the MCP Binance Connector.
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
import winston from 'winston';
|
||||
import { serverConfig } from '../config';
|
||||
|
||||
const { combine, timestamp, printf, colorize, errors } = winston.format;
|
||||
|
||||
// Custom log format
|
||||
const logFormat = printf(({ level, message, timestamp, ...metadata }) => {
|
||||
let msg = `${timestamp} [${level}]: ${message}`;
|
||||
|
||||
if (Object.keys(metadata).length > 0) {
|
||||
msg += ` ${JSON.stringify(metadata)}`;
|
||||
}
|
||||
|
||||
return msg;
|
||||
});
|
||||
|
||||
// Create logger instance
|
||||
export const logger = winston.createLogger({
|
||||
level: serverConfig.logLevel,
|
||||
format: combine(
|
||||
errors({ stack: true }),
|
||||
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
logFormat
|
||||
),
|
||||
defaultMeta: { service: 'mcp-binance-connector' },
|
||||
transports: [
|
||||
// Console transport
|
||||
new winston.transports.Console({
|
||||
format: combine(
|
||||
colorize(),
|
||||
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
logFormat
|
||||
),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Add file transport in production
|
||||
if (serverConfig.nodeEnv === 'production') {
|
||||
logger.add(
|
||||
new winston.transports.File({
|
||||
filename: process.env.LOG_FILE || 'logs/mcp-binance.log',
|
||||
maxsize: 10 * 1024 * 1024, // 10MB
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
|
||||
logger.add(
|
||||
new winston.transports.File({
|
||||
filename: 'logs/mcp-binance-error.log',
|
||||
level: 'error',
|
||||
maxsize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default logger;
|
||||
23
apps/mcp-binance-connector/tsconfig.json
Normal file
23
apps/mcp-binance-connector/tsconfig.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "tests"]
|
||||
}
|
||||
31
apps/mcp-mt4-connector/.env.example
Normal file
31
apps/mcp-mt4-connector/.env.example
Normal file
@ -0,0 +1,31 @@
|
||||
# MCP MT4 Connector Configuration
|
||||
# Copy this file to .env and configure values
|
||||
|
||||
# ==========================================
|
||||
# Server Configuration
|
||||
# ==========================================
|
||||
PORT=3605
|
||||
NODE_ENV=development
|
||||
|
||||
# ==========================================
|
||||
# MT4 Gateway Connection
|
||||
# ==========================================
|
||||
# Host where mt4-gateway is running
|
||||
MT4_GATEWAY_HOST=localhost
|
||||
# Port of the mt4-gateway service
|
||||
MT4_GATEWAY_PORT=8081
|
||||
# Authentication token for mt4-gateway
|
||||
MT4_GATEWAY_AUTH_TOKEN=your-secret-token-here
|
||||
|
||||
# ==========================================
|
||||
# Request Configuration
|
||||
# ==========================================
|
||||
# Timeout for requests to MT4 Gateway (ms)
|
||||
REQUEST_TIMEOUT=10000
|
||||
# Maximum retries for failed requests
|
||||
MAX_RETRIES=3
|
||||
|
||||
# ==========================================
|
||||
# Logging
|
||||
# ==========================================
|
||||
LOG_LEVEL=info
|
||||
31
apps/mcp-mt4-connector/.gitignore
vendored
Normal file
31
apps/mcp-mt4-connector/.gitignore
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Test coverage
|
||||
coverage/
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
277
apps/mcp-mt4-connector/README.md
Normal file
277
apps/mcp-mt4-connector/README.md
Normal file
@ -0,0 +1,277 @@
|
||||
# MCP MT4 Connector
|
||||
|
||||
**Version:** 0.1.0
|
||||
**Date:** 2026-01-04
|
||||
**System:** OrbiQuant Trading Platform + NEXUS v3.4 + SIMCO
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
MCP Server that exposes MetaTrader 4 (MT4) trading capabilities as tools for AI agents. This service enables AI agents to:
|
||||
- Query account information
|
||||
- Monitor open positions
|
||||
- Execute trades (BUY/SELL)
|
||||
- Manage positions (modify SL/TP, close)
|
||||
- Get real-time price quotes
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Navigate to the project
|
||||
cd /home/isem/workspace-v1/projects/trading-platform/apps/mcp-mt4-connector
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Configure environment
|
||||
cp .env.example .env
|
||||
# Edit .env with your MT4 Gateway credentials
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `PORT` | MCP Server port | 3605 |
|
||||
| `MT4_GATEWAY_HOST` | MT4 Gateway hostname | localhost |
|
||||
| `MT4_GATEWAY_PORT` | MT4 Gateway port | 8081 |
|
||||
| `MT4_GATEWAY_AUTH_TOKEN` | Authentication token | secret |
|
||||
| `REQUEST_TIMEOUT` | Request timeout (ms) | 10000 |
|
||||
| `LOG_LEVEL` | Logging level | info |
|
||||
|
||||
### Example .env
|
||||
```env
|
||||
PORT=3605
|
||||
MT4_GATEWAY_HOST=localhost
|
||||
MT4_GATEWAY_PORT=8081
|
||||
MT4_GATEWAY_AUTH_TOKEN=your-secure-token
|
||||
REQUEST_TIMEOUT=10000
|
||||
LOG_LEVEL=info
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Start Server
|
||||
|
||||
```bash
|
||||
# Development (with hot reload)
|
||||
npm run dev
|
||||
|
||||
# Production
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
### Health Check
|
||||
|
||||
```bash
|
||||
curl http://localhost:3605/health
|
||||
```
|
||||
|
||||
### List Available Tools
|
||||
|
||||
```bash
|
||||
curl http://localhost:3605/tools
|
||||
```
|
||||
|
||||
### Execute a Tool
|
||||
|
||||
```bash
|
||||
# Get account info
|
||||
curl -X POST http://localhost:3605/tools/mt4_get_account \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"parameters": {}}'
|
||||
|
||||
# Get price quote
|
||||
curl -X POST http://localhost:3605/tools/mt4_get_quote \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"parameters": {"symbol": "XAUUSD"}}'
|
||||
|
||||
# Execute trade
|
||||
curl -X POST http://localhost:3605/tools/mt4_execute_trade \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"parameters": {"symbol": "XAUUSD", "action": "buy", "lots": 0.1}}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Tools Available
|
||||
|
||||
| Tool | Description | Risk |
|
||||
|------|-------------|------|
|
||||
| `mt4_get_account` | Get account balance, equity, margin | Low |
|
||||
| `mt4_get_positions` | List open positions | Low |
|
||||
| `mt4_get_quote` | Get current bid/ask price | Low |
|
||||
| `mt4_execute_trade` | Execute BUY/SELL order | HIGH |
|
||||
| `mt4_close_position` | Close a position | HIGH |
|
||||
| `mt4_modify_position` | Modify SL/TP | Medium |
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
mcp-mt4-connector/
|
||||
├── README.md # This file
|
||||
├── package.json # Dependencies
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
├── .env.example # Environment template
|
||||
├── .gitignore # Git ignore rules
|
||||
├── docs/
|
||||
│ ├── ARCHITECTURE.md # Architecture documentation
|
||||
│ └── MCP-TOOLS-SPEC.md # Detailed tool specifications
|
||||
└── src/
|
||||
├── index.ts # Server entry point
|
||||
├── tools/
|
||||
│ ├── index.ts # Tool exports
|
||||
│ ├── account.ts # mt4_get_account
|
||||
│ ├── positions.ts # mt4_get_positions, mt4_close_position
|
||||
│ ├── trading.ts # mt4_execute_trade, mt4_modify_position
|
||||
│ └── quotes.ts # mt4_get_quote
|
||||
└── services/
|
||||
└── mt4-client.ts # MT4 Gateway HTTP client
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Type Check
|
||||
|
||||
```bash
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run lint:fix
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with Claude
|
||||
|
||||
### MCP Configuration
|
||||
|
||||
Add to your Claude/MCP configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"mt4": {
|
||||
"url": "http://localhost:3605",
|
||||
"transport": "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Agent Prompts
|
||||
|
||||
```
|
||||
"Check my MT4 account balance"
|
||||
→ Uses mt4_get_account
|
||||
|
||||
"What's the current gold price?"
|
||||
→ Uses mt4_get_quote({ symbol: "XAUUSD" })
|
||||
|
||||
"Buy 0.1 lots of XAUUSD with stop loss at 2640"
|
||||
→ Uses mt4_execute_trade({ symbol: "XAUUSD", action: "buy", lots: 0.1, stopLoss: 2640 })
|
||||
|
||||
"Close my profitable gold positions"
|
||||
→ Uses mt4_get_positions({ symbol: "XAUUSD" }) + mt4_close_position for each
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Runtime
|
||||
- `express` - HTTP server
|
||||
- `axios` - HTTP client
|
||||
- `zod` - Input validation
|
||||
- `dotenv` - Environment configuration
|
||||
- `@modelcontextprotocol/sdk` - MCP protocol
|
||||
|
||||
### Development
|
||||
- `typescript` - Type safety
|
||||
- `ts-node-dev` - Development server
|
||||
- `jest` - Testing framework
|
||||
- `eslint` - Code linting
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **MT4 Gateway** running on configured host:port
|
||||
2. **MT4 Terminal** connected with EA Bridge active
|
||||
3. **Node.js** >= 18.0.0
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot connect to MT4 Gateway
|
||||
```bash
|
||||
# Check if mt4-gateway is running
|
||||
curl http://localhost:8081/status
|
||||
|
||||
# Verify environment variables
|
||||
cat .env | grep MT4
|
||||
```
|
||||
|
||||
### Tool execution fails
|
||||
```bash
|
||||
# Check health endpoint for dependency status
|
||||
curl http://localhost:3605/health
|
||||
|
||||
# Check server logs
|
||||
npm run dev # Logs will show in console
|
||||
```
|
||||
|
||||
### Invalid parameters error
|
||||
```bash
|
||||
# Verify tool schema
|
||||
curl http://localhost:3605/tools/mt4_execute_trade
|
||||
|
||||
# Check parameter names match schema
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [MCP Protocol](https://modelcontextprotocol.io)
|
||||
- MT4 Gateway: `apps/mt4-gateway/`
|
||||
- SIMCO-MCP: `orchestration/directivas/simco/SIMCO-MCP.md`
|
||||
- Architecture: `docs/ARCHITECTURE.md`
|
||||
- Tool Specs: `docs/MCP-TOOLS-SPEC.md`
|
||||
|
||||
---
|
||||
|
||||
**Maintained by:** @PERFIL_MCP_DEVELOPER
|
||||
**Project:** OrbiQuant Trading Platform
|
||||
272
apps/mcp-mt4-connector/docs/ARCHITECTURE.md
Normal file
272
apps/mcp-mt4-connector/docs/ARCHITECTURE.md
Normal file
@ -0,0 +1,272 @@
|
||||
# MCP MT4 Connector - Architecture
|
||||
|
||||
**Version:** 0.1.0
|
||||
**Date:** 2026-01-04
|
||||
**System:** OrbiQuant Trading Platform
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The MCP MT4 Connector is a Model Context Protocol (MCP) server that exposes MetaTrader 4 trading capabilities as tools that AI agents can use. It acts as a bridge between MCP-compatible AI systems (like Claude) and the MT4 trading terminal.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ AI Agent (Claude) │
|
||||
│ │
|
||||
│ "Execute a buy order for 0.1 lots of XAUUSD with SL at 2640" │
|
||||
└─────────────────────────────────────┬───────────────────────────────────┘
|
||||
│ MCP Protocol
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ MCP MT4 Connector (Port 3605) │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Express Server │ │
|
||||
│ │ │ │
|
||||
│ │ /health - Health check endpoint │ │
|
||||
│ │ /tools - List available tools │ │
|
||||
│ │ /tools/:name - Execute specific tool │ │
|
||||
│ │ /mcp/* - MCP protocol endpoints │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Tool Handlers │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │
|
||||
│ │ │ account.ts │ │positions.ts │ │ trading.ts │ │ quotes.ts │ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ MT4 Client Service │ │
|
||||
│ │ │ │
|
||||
│ │ - HTTP client wrapper for mt4-gateway │ │
|
||||
│ │ - Request/response handling │ │
|
||||
│ │ - Error management │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────┬───────────────────────────────────┘
|
||||
│ HTTP/REST
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ MT4 Gateway (Port 8081) │
|
||||
│ │
|
||||
│ Python service that communicates with MT4 EA Bridge │
|
||||
└─────────────────────────────────────┬───────────────────────────────────┘
|
||||
│ Local Socket/HTTP
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ MT4 Terminal + EA Bridge │
|
||||
│ │
|
||||
│ Windows MT4 with Expert Advisor running │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Details
|
||||
|
||||
### 1. Express Server (`src/index.ts`)
|
||||
|
||||
The main entry point that:
|
||||
- Hosts the MCP server on port 3605
|
||||
- Provides REST endpoints for tool execution
|
||||
- Implements MCP protocol endpoints
|
||||
- Handles health checks and service discovery
|
||||
|
||||
**Endpoints:**
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/health` | GET | Health check with MT4 connection status |
|
||||
| `/tools` | GET | List all available MCP tools |
|
||||
| `/tools/:name` | GET | Get specific tool schema |
|
||||
| `/tools/:name` | POST | Execute tool with parameters |
|
||||
| `/mcp/initialize` | POST | MCP initialization handshake |
|
||||
| `/mcp/tools/list` | POST | MCP tool listing |
|
||||
| `/mcp/tools/call` | POST | MCP tool execution |
|
||||
|
||||
### 2. Tool Handlers (`src/tools/`)
|
||||
|
||||
Individual tool implementations following the MCP tool pattern:
|
||||
|
||||
| File | Tools | Description |
|
||||
|------|-------|-------------|
|
||||
| `account.ts` | `mt4_get_account` | Account information retrieval |
|
||||
| `positions.ts` | `mt4_get_positions`, `mt4_close_position` | Position management |
|
||||
| `trading.ts` | `mt4_execute_trade`, `mt4_modify_position` | Trade execution |
|
||||
| `quotes.ts` | `mt4_get_quote` | Price data retrieval |
|
||||
|
||||
Each tool handler:
|
||||
- Defines Zod validation schemas
|
||||
- Implements the core logic
|
||||
- Formats responses for MCP protocol
|
||||
- Handles errors gracefully
|
||||
|
||||
### 3. MT4 Client Service (`src/services/mt4-client.ts`)
|
||||
|
||||
HTTP client wrapper that:
|
||||
- Manages connection to mt4-gateway
|
||||
- Handles authentication (Bearer token)
|
||||
- Provides typed interfaces for all operations
|
||||
- Manages request timeouts and retries
|
||||
|
||||
---
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Example: Execute Trade
|
||||
|
||||
```
|
||||
1. Agent Request
|
||||
POST /mcp/tools/call
|
||||
{
|
||||
"name": "mt4_execute_trade",
|
||||
"arguments": {
|
||||
"symbol": "XAUUSD",
|
||||
"action": "buy",
|
||||
"lots": 0.1,
|
||||
"stopLoss": 2640,
|
||||
"takeProfit": 2680
|
||||
}
|
||||
}
|
||||
|
||||
2. Tool Handler (trading.ts)
|
||||
- Validates input with Zod schema
|
||||
- Checks MT4 connection status
|
||||
- Validates SL/TP logic
|
||||
- Calls MT4Client.executeTrade()
|
||||
|
||||
3. MT4 Client Service
|
||||
- Formats request payload
|
||||
- Sends HTTP POST to mt4-gateway
|
||||
- Receives and parses response
|
||||
|
||||
4. MT4 Gateway
|
||||
- Forwards to EA Bridge
|
||||
- EA executes trade on MT4
|
||||
- Returns result
|
||||
|
||||
5. Response to Agent
|
||||
{
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": "Trade Executed Successfully\n..."
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PORT` | 3605 | MCP server port |
|
||||
| `MT4_GATEWAY_HOST` | localhost | MT4 Gateway host |
|
||||
| `MT4_GATEWAY_PORT` | 8081 | MT4 Gateway port |
|
||||
| `MT4_GATEWAY_AUTH_TOKEN` | secret | Authentication token |
|
||||
| `REQUEST_TIMEOUT` | 10000 | Request timeout (ms) |
|
||||
| `LOG_LEVEL` | info | Logging level |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Error Types
|
||||
|
||||
1. **Connection Errors**
|
||||
- MT4 Gateway unreachable
|
||||
- MT4 Terminal disconnected
|
||||
|
||||
2. **Validation Errors**
|
||||
- Invalid parameters (Zod)
|
||||
- Invalid SL/TP configuration
|
||||
|
||||
3. **Trading Errors**
|
||||
- Insufficient margin
|
||||
- Market closed
|
||||
- Invalid symbol
|
||||
|
||||
### Error Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Error message description"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Authentication**
|
||||
- Bearer token for mt4-gateway communication
|
||||
- No external network exposure by default
|
||||
|
||||
2. **Validation**
|
||||
- All inputs validated with Zod schemas
|
||||
- Type-safe throughout the codebase
|
||||
|
||||
3. **Rate Limiting**
|
||||
- Consider adding rate limiting for production
|
||||
- Respect MT4 order frequency limits
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Purpose |
|
||||
|---------|---------|
|
||||
| `express` | HTTP server |
|
||||
| `axios` | HTTP client |
|
||||
| `zod` | Input validation |
|
||||
| `dotenv` | Environment configuration |
|
||||
| `@modelcontextprotocol/sdk` | MCP protocol types |
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
npm install
|
||||
cp .env.example .env
|
||||
# Edit .env with your configuration
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
### Docker (Future)
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
COPY dist ./dist
|
||||
EXPOSE 3605
|
||||
CMD ["node", "dist/index.js"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- MCP Protocol: https://modelcontextprotocol.io
|
||||
- MT4 Bridge Client: `apps/mt4-gateway/src/providers/mt4_bridge_client.py`
|
||||
- Trading Platform: `projects/trading-platform/`
|
||||
- SIMCO-MCP Directive: `orchestration/directivas/simco/SIMCO-MCP.md`
|
||||
428
apps/mcp-mt4-connector/docs/MCP-TOOLS-SPEC.md
Normal file
428
apps/mcp-mt4-connector/docs/MCP-TOOLS-SPEC.md
Normal file
@ -0,0 +1,428 @@
|
||||
# MCP MT4 Connector - Tools Specification
|
||||
|
||||
**Version:** 0.1.0
|
||||
**Date:** 2026-01-04
|
||||
**Total Tools:** 6
|
||||
|
||||
---
|
||||
|
||||
## Tool Overview
|
||||
|
||||
| Tool Name | Description | Risk Level |
|
||||
|-----------|-------------|------------|
|
||||
| `mt4_get_account` | Get account information | Low |
|
||||
| `mt4_get_positions` | List open positions | Low |
|
||||
| `mt4_get_quote` | Get current price quote | Low |
|
||||
| `mt4_execute_trade` | Execute market order | HIGH |
|
||||
| `mt4_close_position` | Close a position | HIGH |
|
||||
| `mt4_modify_position` | Modify SL/TP | Medium |
|
||||
|
||||
---
|
||||
|
||||
## mt4_get_account
|
||||
|
||||
### Description
|
||||
Retrieves comprehensive account information from the connected MT4 terminal including balance, equity, margin, leverage, and broker details.
|
||||
|
||||
### Parameters
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| - | - | - | No parameters required |
|
||||
|
||||
### Return Value
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"balance": 10000.00,
|
||||
"equity": 10250.50,
|
||||
"margin": 500.00,
|
||||
"freeMargin": 9750.50,
|
||||
"marginLevel": 2050.10,
|
||||
"profit": 250.50,
|
||||
"currency": "USD",
|
||||
"leverage": 100,
|
||||
"name": "Demo Account",
|
||||
"server": "ICMarkets-Demo",
|
||||
"company": "IC Markets"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
```typescript
|
||||
// Get account info
|
||||
const result = await mt4_get_account({});
|
||||
|
||||
// Response content:
|
||||
// MT4 Account Information
|
||||
// =======================
|
||||
// Account Name: Demo Account
|
||||
// Server: ICMarkets-Demo
|
||||
// Broker: IC Markets
|
||||
// Leverage: 1:100
|
||||
//
|
||||
// Financial Summary
|
||||
// -----------------
|
||||
// Balance: 10000.00 USD
|
||||
// Equity: 10250.50 USD
|
||||
// Profit/Loss: +250.50 USD
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | Message | Solution |
|
||||
|------|---------|----------|
|
||||
| - | MT4 terminal is not connected | Check MT4 Gateway connection |
|
||||
|
||||
---
|
||||
|
||||
## mt4_get_positions
|
||||
|
||||
### Description
|
||||
Lists all currently open trading positions from the MT4 terminal. Can optionally filter by symbol.
|
||||
|
||||
### Parameters
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `symbol` | string | No | Filter positions by symbol (e.g., XAUUSD) |
|
||||
|
||||
### Return Value
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"positions": [
|
||||
{
|
||||
"ticket": 123456,
|
||||
"symbol": "XAUUSD",
|
||||
"type": "buy",
|
||||
"lots": 0.10,
|
||||
"openPrice": 2650.50,
|
||||
"currentPrice": 2655.00,
|
||||
"stopLoss": 2640.00,
|
||||
"takeProfit": 2680.00,
|
||||
"profit": 45.00,
|
||||
"swap": -1.20,
|
||||
"openTime": "2026-01-04T10:30:00Z",
|
||||
"magic": 12345,
|
||||
"comment": "AI Signal"
|
||||
}
|
||||
],
|
||||
"totalProfit": 45.00,
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
```typescript
|
||||
// Get all positions
|
||||
const result = await mt4_get_positions({});
|
||||
|
||||
// Get only XAUUSD positions
|
||||
const goldPositions = await mt4_get_positions({ symbol: "XAUUSD" });
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | Message | Solution |
|
||||
|------|---------|----------|
|
||||
| - | MT4 terminal is not connected | Check MT4 Gateway connection |
|
||||
|
||||
---
|
||||
|
||||
## mt4_get_quote
|
||||
|
||||
### Description
|
||||
Retrieves the current bid/ask prices for a trading symbol. Also calculates the spread in points/pips.
|
||||
|
||||
### Parameters
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `symbol` | string | Yes | Trading symbol (e.g., XAUUSD, EURUSD) |
|
||||
|
||||
### Return Value
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"symbol": "XAUUSD",
|
||||
"bid": 2650.50,
|
||||
"ask": 2650.80,
|
||||
"spread": 0.30,
|
||||
"timestamp": "2026-01-04T12:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
```typescript
|
||||
// Get gold price
|
||||
const quote = await mt4_get_quote({ symbol: "XAUUSD" });
|
||||
|
||||
// Response content:
|
||||
// Price Quote: XAUUSD
|
||||
// =========================
|
||||
// Bid: 2650.50
|
||||
// Ask: 2650.80
|
||||
// Spread: 0.30 (3.0 pips)
|
||||
// Time: 2026-01-04T12:00:00.000Z
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | Message | Solution |
|
||||
|------|---------|----------|
|
||||
| - | No quote data available for {symbol} | Verify symbol is available on broker |
|
||||
|
||||
---
|
||||
|
||||
## mt4_execute_trade
|
||||
|
||||
### Description
|
||||
Opens a new trading position with a market order. Supports BUY and SELL orders with optional stop loss and take profit levels.
|
||||
|
||||
### Parameters
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `symbol` | string | Yes | Trading symbol (e.g., XAUUSD) |
|
||||
| `action` | string | Yes | Trade direction: "buy" or "sell" |
|
||||
| `lots` | number | Yes | Volume in lots (e.g., 0.01, 0.1, 1.0) |
|
||||
| `stopLoss` | number | No | Stop loss price level |
|
||||
| `takeProfit` | number | No | Take profit price level |
|
||||
| `slippage` | number | No | Maximum slippage in points (default: 3) |
|
||||
| `magic` | number | No | Magic number for EA identification (default: 12345) |
|
||||
| `comment` | string | No | Order comment (max 31 chars) |
|
||||
|
||||
### Return Value
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"success": true,
|
||||
"ticket": 123456,
|
||||
"message": "Order placed successfully",
|
||||
"symbol": "XAUUSD",
|
||||
"action": "buy",
|
||||
"lots": 0.1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
```typescript
|
||||
// Simple buy order
|
||||
const result = await mt4_execute_trade({
|
||||
symbol: "XAUUSD",
|
||||
action: "buy",
|
||||
lots: 0.1
|
||||
});
|
||||
|
||||
// Buy with risk management
|
||||
const result = await mt4_execute_trade({
|
||||
symbol: "XAUUSD",
|
||||
action: "buy",
|
||||
lots: 0.1,
|
||||
stopLoss: 2640.00,
|
||||
takeProfit: 2680.00,
|
||||
comment: "AI Signal - Gold Long"
|
||||
});
|
||||
|
||||
// Sell order
|
||||
const result = await mt4_execute_trade({
|
||||
symbol: "EURUSD",
|
||||
action: "sell",
|
||||
lots: 0.5,
|
||||
stopLoss: 1.1050,
|
||||
takeProfit: 1.0900
|
||||
});
|
||||
```
|
||||
|
||||
### Validation Rules
|
||||
- For BUY orders: stopLoss must be below takeProfit
|
||||
- For SELL orders: stopLoss must be above takeProfit
|
||||
- Lots must be positive and reasonable (max 100)
|
||||
|
||||
### Errors
|
||||
| Code | Message | Solution |
|
||||
|------|---------|----------|
|
||||
| - | For BUY orders, stop loss must be below take profit | Fix SL/TP levels |
|
||||
| - | For SELL orders, stop loss must be above take profit | Fix SL/TP levels |
|
||||
| - | Trade execution failed | Check margin, market hours |
|
||||
|
||||
---
|
||||
|
||||
## mt4_close_position
|
||||
|
||||
### Description
|
||||
Closes an open position by ticket number. Can optionally close only a partial volume.
|
||||
|
||||
### Parameters
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `ticket` | number | Yes | Position ticket number to close |
|
||||
| `lots` | number | No | Partial volume to close (default: close all) |
|
||||
| `slippage` | number | No | Maximum slippage in points (default: 3) |
|
||||
|
||||
### Return Value
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"success": true,
|
||||
"ticket": 123456,
|
||||
"message": "Position closed"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
```typescript
|
||||
// Close entire position
|
||||
const result = await mt4_close_position({
|
||||
ticket: 123456
|
||||
});
|
||||
|
||||
// Close partial position (0.5 of 1.0 lots)
|
||||
const result = await mt4_close_position({
|
||||
ticket: 123456,
|
||||
lots: 0.5
|
||||
});
|
||||
```
|
||||
|
||||
### Errors
|
||||
| Code | Message | Solution |
|
||||
|------|---------|----------|
|
||||
| - | Position with ticket {x} not found | Verify ticket number |
|
||||
| - | Requested lots exceeds position size | Reduce lots parameter |
|
||||
|
||||
---
|
||||
|
||||
## mt4_modify_position
|
||||
|
||||
### Description
|
||||
Modifies the stop loss and/or take profit levels of an existing open position.
|
||||
|
||||
### Parameters
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `ticket` | number | Yes | Position ticket number to modify |
|
||||
| `stopLoss` | number | No | New stop loss price level |
|
||||
| `takeProfit` | number | No | New take profit price level |
|
||||
|
||||
### Return Value
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"success": true,
|
||||
"ticket": 123456,
|
||||
"message": "Position modified successfully"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
```typescript
|
||||
// Set both SL and TP
|
||||
const result = await mt4_modify_position({
|
||||
ticket: 123456,
|
||||
stopLoss: 2640.00,
|
||||
takeProfit: 2680.00
|
||||
});
|
||||
|
||||
// Update only take profit (trailing)
|
||||
const result = await mt4_modify_position({
|
||||
ticket: 123456,
|
||||
takeProfit: 2700.00
|
||||
});
|
||||
|
||||
// Set only stop loss (risk management)
|
||||
const result = await mt4_modify_position({
|
||||
ticket: 123456,
|
||||
stopLoss: 2650.00
|
||||
});
|
||||
```
|
||||
|
||||
### Validation Rules
|
||||
- At least one of stopLoss or takeProfit must be provided
|
||||
- For BUY positions: stopLoss must be below takeProfit
|
||||
- For SELL positions: stopLoss must be above takeProfit
|
||||
|
||||
### Errors
|
||||
| Code | Message | Solution |
|
||||
|------|---------|----------|
|
||||
| - | At least one of stopLoss or takeProfit must be provided | Add SL or TP |
|
||||
| - | Position with ticket {x} not found | Verify ticket number |
|
||||
| - | For BUY positions, stop loss must be below take profit | Fix SL/TP levels |
|
||||
|
||||
---
|
||||
|
||||
## Common Error Responses
|
||||
|
||||
### Connection Error
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "MT4 terminal is not connected"
|
||||
}
|
||||
```
|
||||
|
||||
### Validation Error
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Validation error",
|
||||
"details": [
|
||||
{
|
||||
"path": ["symbol"],
|
||||
"message": "Required"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Trading Error
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Trade execution failed: Insufficient margin"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples with AI Agent
|
||||
|
||||
### Scenario 1: Check Account and Open Trade
|
||||
```
|
||||
Agent: "Check my account balance and if equity is above 10000, buy 0.1 lots of XAUUSD"
|
||||
|
||||
1. Call mt4_get_account({})
|
||||
2. Parse response, check equity > 10000
|
||||
3. Call mt4_execute_trade({ symbol: "XAUUSD", action: "buy", lots: 0.1 })
|
||||
```
|
||||
|
||||
### Scenario 2: Risk Management
|
||||
```
|
||||
Agent: "Set stop loss at 2640 and take profit at 2680 for my gold position"
|
||||
|
||||
1. Call mt4_get_positions({ symbol: "XAUUSD" })
|
||||
2. Get ticket number from response
|
||||
3. Call mt4_modify_position({ ticket: 123456, stopLoss: 2640, takeProfit: 2680 })
|
||||
```
|
||||
|
||||
### Scenario 3: Close Profitable Trades
|
||||
```
|
||||
Agent: "Close all profitable gold positions"
|
||||
|
||||
1. Call mt4_get_positions({ symbol: "XAUUSD" })
|
||||
2. Filter positions where profit > 0
|
||||
3. For each: Call mt4_close_position({ ticket: ticketNumber })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 0.1.0 | 2026-01-04 | Initial release with 6 core tools |
|
||||
7170
apps/mcp-mt4-connector/package-lock.json
generated
Normal file
7170
apps/mcp-mt4-connector/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
apps/mcp-mt4-connector/package.json
Normal file
53
apps/mcp-mt4-connector/package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "mcp-mt4-connector",
|
||||
"version": "0.1.0",
|
||||
"description": "MCP Server for MT4 trading operations via EA Bridge",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "ts-node-dev --respawn src/index.ts",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"lint:fix": "eslint src/**/*.ts --fix",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"health-check": "curl -s http://localhost:${PORT:-3605}/health || echo 'Server not running'"
|
||||
},
|
||||
"keywords": [
|
||||
"mcp",
|
||||
"model-context-protocol",
|
||||
"anthropic",
|
||||
"claude",
|
||||
"mt4",
|
||||
"metatrader",
|
||||
"trading",
|
||||
"forex"
|
||||
],
|
||||
"author": "OrbiQuant Trading Platform",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"axios": "^1.6.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
||||
"@typescript-eslint/parser": "^6.13.0",
|
||||
"eslint": "^8.54.0",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
291
apps/mcp-mt4-connector/src/index.ts
Normal file
291
apps/mcp-mt4-connector/src/index.ts
Normal file
@ -0,0 +1,291 @@
|
||||
/**
|
||||
* MCP Server: MT4 Connector
|
||||
*
|
||||
* Exposes MT4 trading capabilities as MCP tools for AI agents.
|
||||
* Communicates with mt4-gateway service to execute trading operations.
|
||||
*
|
||||
* @version 0.1.0
|
||||
* @author OrbiQuant Trading Platform
|
||||
*/
|
||||
|
||||
import express, { Request, Response, NextFunction } from 'express';
|
||||
import dotenv from 'dotenv';
|
||||
import { mcpToolSchemas, toolHandlers } from './tools';
|
||||
import { getMT4Client } from './services/mt4-client';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3605;
|
||||
const SERVICE_NAME = 'mcp-mt4-connector';
|
||||
const VERSION = '0.1.0';
|
||||
|
||||
// ==========================================
|
||||
// Middleware
|
||||
// ==========================================
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
// Request logging
|
||||
app.use((req: Request, _res: Response, next: NextFunction) => {
|
||||
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Health & Status Endpoints
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Health check endpoint
|
||||
*/
|
||||
app.get('/health', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const client = getMT4Client();
|
||||
const mt4Connected = await client.isConnected();
|
||||
|
||||
res.json({
|
||||
status: 'ok',
|
||||
service: SERVICE_NAME,
|
||||
version: VERSION,
|
||||
timestamp: new Date().toISOString(),
|
||||
dependencies: {
|
||||
mt4Gateway: mt4Connected ? 'connected' : 'disconnected',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
res.json({
|
||||
status: 'degraded',
|
||||
service: SERVICE_NAME,
|
||||
version: VERSION,
|
||||
timestamp: new Date().toISOString(),
|
||||
dependencies: {
|
||||
mt4Gateway: 'error',
|
||||
},
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* List available MCP tools
|
||||
*/
|
||||
app.get('/tools', (_req: Request, res: Response) => {
|
||||
res.json({
|
||||
tools: mcpToolSchemas,
|
||||
count: mcpToolSchemas.length,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Get specific tool schema
|
||||
*/
|
||||
app.get('/tools/:toolName', (req: Request, res: Response) => {
|
||||
const { toolName } = req.params;
|
||||
const tool = mcpToolSchemas.find(t => t.name === toolName);
|
||||
|
||||
if (!tool) {
|
||||
res.status(404).json({
|
||||
error: `Tool '${toolName}' not found`,
|
||||
availableTools: mcpToolSchemas.map(t => t.name),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(tool);
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// MCP Tool Execution Endpoints
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Execute an MCP tool
|
||||
* POST /tools/:toolName
|
||||
* Body: { parameters: {...} }
|
||||
*/
|
||||
app.post('/tools/:toolName', async (req: Request, res: Response) => {
|
||||
const { toolName } = req.params;
|
||||
const { parameters = {} } = req.body;
|
||||
|
||||
// Validate tool exists
|
||||
const handler = toolHandlers[toolName];
|
||||
if (!handler) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: `Tool '${toolName}' not found`,
|
||||
availableTools: Object.keys(toolHandlers),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[${new Date().toISOString()}] Executing tool: ${toolName}`);
|
||||
console.log(`Parameters: ${JSON.stringify(parameters)}`);
|
||||
|
||||
const result = await handler(parameters);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
tool: toolName,
|
||||
result,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[${new Date().toISOString()}] Tool error: ${toolName}`, error);
|
||||
|
||||
// Handle Zod validation errors
|
||||
if (error && typeof error === 'object' && 'issues' in error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Validation error',
|
||||
details: (error as { issues: unknown[] }).issues,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// MCP Protocol Endpoints (Standard)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* MCP Initialize
|
||||
* Returns server capabilities
|
||||
*/
|
||||
app.post('/mcp/initialize', (_req: Request, res: Response) => {
|
||||
res.json({
|
||||
protocolVersion: '2024-11-05',
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
serverInfo: {
|
||||
name: SERVICE_NAME,
|
||||
version: VERSION,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* MCP List Tools
|
||||
* Returns all available tools in MCP format
|
||||
*/
|
||||
app.post('/mcp/tools/list', (_req: Request, res: Response) => {
|
||||
res.json({
|
||||
tools: mcpToolSchemas.map(tool => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* MCP Call Tool
|
||||
* Execute a tool with parameters
|
||||
*/
|
||||
app.post('/mcp/tools/call', async (req: Request, res: Response) => {
|
||||
const { name, arguments: args = {} } = req.body;
|
||||
|
||||
if (!name) {
|
||||
res.status(400).json({
|
||||
error: {
|
||||
code: 'invalid_request',
|
||||
message: 'Tool name is required',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = toolHandlers[name];
|
||||
if (!handler) {
|
||||
res.status(404).json({
|
||||
error: {
|
||||
code: 'unknown_tool',
|
||||
message: `Tool '${name}' not found`,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await handler(args);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
// Handle Zod validation errors
|
||||
if (error && typeof error === 'object' && 'issues' in error) {
|
||||
res.status(400).json({
|
||||
error: {
|
||||
code: 'invalid_params',
|
||||
message: 'Invalid tool parameters',
|
||||
data: (error as { issues: unknown[] }).issues,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
error: {
|
||||
code: 'internal_error',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Error Handler
|
||||
// ==========================================
|
||||
|
||||
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
|
||||
console.error(`[${new Date().toISOString()}] Unhandled error:`, err);
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
message: err.message,
|
||||
});
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Start Server
|
||||
// ==========================================
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log('');
|
||||
console.log('╔══════════════════════════════════════════════════════════╗');
|
||||
console.log('║ MCP MT4 Connector - Trading Platform ║');
|
||||
console.log('╠══════════════════════════════════════════════════════════╣');
|
||||
console.log(`║ Service: ${SERVICE_NAME.padEnd(45)}║`);
|
||||
console.log(`║ Version: ${VERSION.padEnd(45)}║`);
|
||||
console.log(`║ Port: ${String(PORT).padEnd(48)}║`);
|
||||
console.log('╠══════════════════════════════════════════════════════════╣');
|
||||
console.log('║ Endpoints: ║');
|
||||
console.log(`║ - Health: http://localhost:${PORT}/health`.padEnd(63) + '║');
|
||||
console.log(`║ - Tools: http://localhost:${PORT}/tools`.padEnd(63) + '║');
|
||||
console.log('╠══════════════════════════════════════════════════════════╣');
|
||||
console.log('║ MCP Tools Available: ║');
|
||||
mcpToolSchemas.forEach(tool => {
|
||||
console.log(`║ - ${tool.name.padEnd(54)}║`);
|
||||
});
|
||||
console.log('╚══════════════════════════════════════════════════════════╝');
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Graceful Shutdown
|
||||
// ==========================================
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('Received SIGTERM, shutting down gracefully...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Received SIGINT, shutting down gracefully...');
|
||||
process.exit(0);
|
||||
});
|
||||
375
apps/mcp-mt4-connector/src/services/mt4-client.ts
Normal file
375
apps/mcp-mt4-connector/src/services/mt4-client.ts
Normal file
@ -0,0 +1,375 @@
|
||||
/**
|
||||
* MT4 Client Service
|
||||
*
|
||||
* HTTP client wrapper for communicating with mt4-gateway.
|
||||
* This service mirrors the functionality of mt4_bridge_client.py
|
||||
* but is written in TypeScript for the MCP Server.
|
||||
*/
|
||||
|
||||
import axios, { AxiosInstance, AxiosError } from 'axios';
|
||||
|
||||
// ==========================================
|
||||
// Types
|
||||
// ==========================================
|
||||
|
||||
export interface MT4AccountInfo {
|
||||
balance: number;
|
||||
equity: number;
|
||||
margin: number;
|
||||
freeMargin: number;
|
||||
marginLevel: number | null;
|
||||
profit: number;
|
||||
currency: string;
|
||||
leverage: number;
|
||||
name: string;
|
||||
server: string;
|
||||
company: string;
|
||||
}
|
||||
|
||||
export interface MT4Position {
|
||||
ticket: number;
|
||||
symbol: string;
|
||||
type: 'buy' | 'sell';
|
||||
lots: number;
|
||||
openPrice: number;
|
||||
currentPrice: number;
|
||||
stopLoss: number | null;
|
||||
takeProfit: number | null;
|
||||
profit: number;
|
||||
swap: number;
|
||||
openTime: string;
|
||||
magic: number;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
export interface MT4Tick {
|
||||
symbol: string;
|
||||
bid: number;
|
||||
ask: number;
|
||||
timestamp: string;
|
||||
spread: number;
|
||||
}
|
||||
|
||||
export interface TradeResult {
|
||||
success: boolean;
|
||||
ticket?: number;
|
||||
message: string;
|
||||
errorCode?: number;
|
||||
}
|
||||
|
||||
export interface TradeRequest {
|
||||
action: 'buy' | 'sell';
|
||||
symbol: string;
|
||||
lots: number;
|
||||
stopLoss?: number;
|
||||
takeProfit?: number;
|
||||
price?: number;
|
||||
slippage?: number;
|
||||
magic?: number;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface ClosePositionRequest {
|
||||
ticket: number;
|
||||
lots?: number;
|
||||
slippage?: number;
|
||||
}
|
||||
|
||||
export interface ModifyPositionRequest {
|
||||
ticket: number;
|
||||
stopLoss?: number;
|
||||
takeProfit?: number;
|
||||
}
|
||||
|
||||
export interface MT4ClientConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
authToken: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// MT4 Client Class
|
||||
// ==========================================
|
||||
|
||||
export class MT4Client {
|
||||
private client: AxiosInstance;
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(config: MT4ClientConfig) {
|
||||
this.baseUrl = `http://${config.host}:${config.port}`;
|
||||
|
||||
this.client = axios.create({
|
||||
baseURL: this.baseUrl,
|
||||
timeout: config.timeout || 10000,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${config.authToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the MT4 terminal is connected
|
||||
*/
|
||||
async isConnected(): Promise<boolean> {
|
||||
try {
|
||||
const response = await this.client.get('/status');
|
||||
return response.data?.connected ?? false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MT4 account information
|
||||
*/
|
||||
async getAccountInfo(): Promise<MT4AccountInfo> {
|
||||
try {
|
||||
const response = await this.client.get('/account');
|
||||
const data = response.data;
|
||||
|
||||
return {
|
||||
balance: data.balance ?? 0,
|
||||
equity: data.equity ?? 0,
|
||||
margin: data.margin ?? 0,
|
||||
freeMargin: data.freeMargin ?? 0,
|
||||
marginLevel: data.marginLevel ?? null,
|
||||
profit: data.profit ?? 0,
|
||||
currency: data.currency ?? 'USD',
|
||||
leverage: data.leverage ?? 100,
|
||||
name: data.name ?? '',
|
||||
server: data.server ?? '',
|
||||
company: data.company ?? '',
|
||||
};
|
||||
} catch (error) {
|
||||
throw this.handleError(error, 'Failed to get account info');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current tick (quote) for a symbol
|
||||
*/
|
||||
async getTick(symbol: string): Promise<MT4Tick> {
|
||||
try {
|
||||
const response = await this.client.get(`/tick/${symbol}`);
|
||||
const data = response.data;
|
||||
|
||||
const bid = data.bid ?? 0;
|
||||
const ask = data.ask ?? 0;
|
||||
|
||||
return {
|
||||
symbol,
|
||||
bid,
|
||||
ask,
|
||||
timestamp: data.time ?? new Date().toISOString(),
|
||||
spread: Math.round((ask - bid) * 100000) / 100000,
|
||||
};
|
||||
} catch (error) {
|
||||
throw this.handleError(error, `Failed to get tick for ${symbol}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all open positions
|
||||
*/
|
||||
async getPositions(): Promise<MT4Position[]> {
|
||||
try {
|
||||
const response = await this.client.get('/positions');
|
||||
const data = response.data;
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.map((p: Record<string, unknown>) => ({
|
||||
ticket: (p.ticket as number) ?? 0,
|
||||
symbol: (p.symbol as string) ?? '',
|
||||
type: (p.type as 'buy' | 'sell') ?? 'buy',
|
||||
lots: (p.lots as number) ?? 0,
|
||||
openPrice: (p.openPrice as number) ?? 0,
|
||||
currentPrice: (p.currentPrice as number) ?? 0,
|
||||
stopLoss: (p.stopLoss as number | null) ?? null,
|
||||
takeProfit: (p.takeProfit as number | null) ?? null,
|
||||
profit: (p.profit as number) ?? 0,
|
||||
swap: (p.swap as number) ?? 0,
|
||||
openTime: (p.openTime as string) ?? new Date().toISOString(),
|
||||
magic: (p.magic as number) ?? 0,
|
||||
comment: (p.comment as string) ?? '',
|
||||
}));
|
||||
} catch (error) {
|
||||
throw this.handleError(error, 'Failed to get positions');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific position by ticket
|
||||
*/
|
||||
async getPosition(ticket: number): Promise<MT4Position | null> {
|
||||
const positions = await this.getPositions();
|
||||
return positions.find(p => p.ticket === ticket) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a trade (buy or sell)
|
||||
*/
|
||||
async executeTrade(request: TradeRequest): Promise<TradeResult> {
|
||||
try {
|
||||
const payload: Record<string, unknown> = {
|
||||
action: request.action,
|
||||
symbol: request.symbol,
|
||||
lots: request.lots,
|
||||
slippage: request.slippage ?? 3,
|
||||
magic: request.magic ?? 12345,
|
||||
comment: request.comment ?? 'MCP-MT4',
|
||||
};
|
||||
|
||||
if (request.stopLoss !== undefined) {
|
||||
payload.stopLoss = request.stopLoss;
|
||||
}
|
||||
if (request.takeProfit !== undefined) {
|
||||
payload.takeProfit = request.takeProfit;
|
||||
}
|
||||
if (request.price !== undefined) {
|
||||
payload.price = request.price;
|
||||
}
|
||||
|
||||
const response = await this.client.post('/trade', payload);
|
||||
const data = response.data;
|
||||
|
||||
return {
|
||||
success: data.success ?? false,
|
||||
ticket: data.ticket,
|
||||
message: data.message ?? '',
|
||||
errorCode: data.errorCode,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: this.getErrorMessage(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a position
|
||||
*/
|
||||
async closePosition(request: ClosePositionRequest): Promise<TradeResult> {
|
||||
try {
|
||||
const payload: Record<string, unknown> = {
|
||||
action: 'close',
|
||||
ticket: request.ticket,
|
||||
slippage: request.slippage ?? 3,
|
||||
};
|
||||
|
||||
if (request.lots !== undefined) {
|
||||
payload.lots = request.lots;
|
||||
}
|
||||
|
||||
const response = await this.client.post('/trade', payload);
|
||||
const data = response.data;
|
||||
|
||||
return {
|
||||
success: data.success ?? false,
|
||||
ticket: request.ticket,
|
||||
message: data.message ?? '',
|
||||
errorCode: data.errorCode,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
ticket: request.ticket,
|
||||
message: this.getErrorMessage(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify a position (SL/TP)
|
||||
*/
|
||||
async modifyPosition(request: ModifyPositionRequest): Promise<TradeResult> {
|
||||
try {
|
||||
const payload: Record<string, unknown> = {
|
||||
action: 'modify',
|
||||
ticket: request.ticket,
|
||||
};
|
||||
|
||||
if (request.stopLoss !== undefined) {
|
||||
payload.stopLoss = request.stopLoss;
|
||||
}
|
||||
if (request.takeProfit !== undefined) {
|
||||
payload.takeProfit = request.takeProfit;
|
||||
}
|
||||
|
||||
const response = await this.client.post('/trade', payload);
|
||||
const data = response.data;
|
||||
|
||||
return {
|
||||
success: data.success ?? false,
|
||||
ticket: request.ticket,
|
||||
message: data.message ?? '',
|
||||
errorCode: data.errorCode,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
ticket: request.ticket,
|
||||
message: this.getErrorMessage(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle axios errors and convert to meaningful messages
|
||||
*/
|
||||
private handleError(error: unknown, context: string): Error {
|
||||
const message = this.getErrorMessage(error);
|
||||
return new Error(`${context}: ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract error message from various error types
|
||||
*/
|
||||
private getErrorMessage(error: unknown): string {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const axiosError = error as AxiosError;
|
||||
if (axiosError.response) {
|
||||
return `HTTP ${axiosError.response.status}: ${JSON.stringify(axiosError.response.data)}`;
|
||||
}
|
||||
if (axiosError.code === 'ECONNREFUSED') {
|
||||
return 'Connection refused - MT4 Gateway is not running';
|
||||
}
|
||||
if (axiosError.code === 'ETIMEDOUT') {
|
||||
return 'Connection timeout - MT4 Gateway is not responding';
|
||||
}
|
||||
return axiosError.message;
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return 'Unknown error';
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Singleton Instance
|
||||
// ==========================================
|
||||
|
||||
let clientInstance: MT4Client | null = null;
|
||||
|
||||
export function getMT4Client(): MT4Client {
|
||||
if (!clientInstance) {
|
||||
const config: MT4ClientConfig = {
|
||||
host: process.env.MT4_GATEWAY_HOST || 'localhost',
|
||||
port: parseInt(process.env.MT4_GATEWAY_PORT || '8081', 10),
|
||||
authToken: process.env.MT4_GATEWAY_AUTH_TOKEN || 'secret',
|
||||
timeout: parseInt(process.env.REQUEST_TIMEOUT || '10000', 10),
|
||||
};
|
||||
clientInstance = new MT4Client(config);
|
||||
}
|
||||
return clientInstance;
|
||||
}
|
||||
|
||||
export function resetMT4Client(): void {
|
||||
clientInstance = null;
|
||||
}
|
||||
143
apps/mcp-mt4-connector/src/tools/account.ts
Normal file
143
apps/mcp-mt4-connector/src/tools/account.ts
Normal file
@ -0,0 +1,143 @@
|
||||
/**
|
||||
* mt4_get_account - Get MT4 account information
|
||||
*
|
||||
* @description Retrieves comprehensive account information from the connected MT4 terminal
|
||||
* including balance, equity, margin, leverage, and broker details.
|
||||
*
|
||||
* @returns Account information object with balance, equity, margin details
|
||||
*
|
||||
* @example
|
||||
* const result = await mt4_get_account({});
|
||||
* // Returns:
|
||||
* // {
|
||||
* // balance: 10000.00,
|
||||
* // equity: 10250.50,
|
||||
* // margin: 500.00,
|
||||
* // freeMargin: 9750.50,
|
||||
* // marginLevel: 2050.10,
|
||||
* // profit: 250.50,
|
||||
* // currency: "USD",
|
||||
* // leverage: 100,
|
||||
* // name: "Demo Account",
|
||||
* // server: "ICMarkets-Demo",
|
||||
* // company: "IC Markets"
|
||||
* // }
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { getMT4Client, MT4AccountInfo } from '../services/mt4-client';
|
||||
|
||||
// ==========================================
|
||||
// Schema Definition
|
||||
// ==========================================
|
||||
|
||||
export const mt4GetAccountSchema = {
|
||||
name: 'mt4_get_account',
|
||||
description: 'Get MT4 account information including balance, equity, margin, and broker details',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {},
|
||||
required: [] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
// Input validation schema (no params required)
|
||||
export const Mt4GetAccountInputSchema = z.object({});
|
||||
|
||||
export type Mt4GetAccountInput = z.infer<typeof Mt4GetAccountInputSchema>;
|
||||
|
||||
// ==========================================
|
||||
// Tool Implementation
|
||||
// ==========================================
|
||||
|
||||
export interface Mt4GetAccountResult {
|
||||
success: boolean;
|
||||
data?: MT4AccountInfo;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function mt4_get_account(
|
||||
_params: Mt4GetAccountInput
|
||||
): Promise<Mt4GetAccountResult> {
|
||||
try {
|
||||
const client = getMT4Client();
|
||||
|
||||
// Check connection first
|
||||
const isConnected = await client.isConnected();
|
||||
if (!isConnected) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'MT4 terminal is not connected',
|
||||
};
|
||||
}
|
||||
|
||||
// Get account info
|
||||
const accountInfo = await client.getAccountInfo();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: accountInfo,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// MCP Tool Handler
|
||||
// ==========================================
|
||||
|
||||
export async function handleMt4GetAccount(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
// Validate input
|
||||
const validatedParams = Mt4GetAccountInputSchema.parse(params);
|
||||
|
||||
// Execute tool
|
||||
const result = await mt4_get_account(validatedParams);
|
||||
|
||||
// Format response for MCP
|
||||
if (result.success && result.data) {
|
||||
const formattedOutput = `
|
||||
MT4 Account Information
|
||||
=======================
|
||||
Account Name: ${result.data.name}
|
||||
Server: ${result.data.server}
|
||||
Broker: ${result.data.company}
|
||||
Leverage: 1:${result.data.leverage}
|
||||
|
||||
Financial Summary
|
||||
-----------------
|
||||
Balance: ${result.data.balance.toFixed(2)} ${result.data.currency}
|
||||
Equity: ${result.data.equity.toFixed(2)} ${result.data.currency}
|
||||
Profit/Loss: ${result.data.profit >= 0 ? '+' : ''}${result.data.profit.toFixed(2)} ${result.data.currency}
|
||||
|
||||
Margin Details
|
||||
--------------
|
||||
Used Margin: ${result.data.margin.toFixed(2)} ${result.data.currency}
|
||||
Free Margin: ${result.data.freeMargin.toFixed(2)} ${result.data.currency}
|
||||
Margin Level: ${result.data.marginLevel !== null ? result.data.marginLevel.toFixed(2) + '%' : 'N/A'}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formattedOutput,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error: ${result.error}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
212
apps/mcp-mt4-connector/src/tools/index.ts
Normal file
212
apps/mcp-mt4-connector/src/tools/index.ts
Normal file
@ -0,0 +1,212 @@
|
||||
/**
|
||||
* MCP Tools Index
|
||||
*
|
||||
* Exports all MT4 MCP tools and their schemas for registration
|
||||
*/
|
||||
|
||||
// Import handlers for use in toolHandlers map
|
||||
import { handleMt4GetAccount } from './account';
|
||||
import { handleMt4GetPositions, handleMt4ClosePosition } from './positions';
|
||||
import { handleMt4ExecuteTrade, handleMt4ModifyPosition } from './trading';
|
||||
import { handleMt4GetQuote } from './quotes';
|
||||
|
||||
// Account tools
|
||||
export {
|
||||
mt4GetAccountSchema,
|
||||
mt4_get_account,
|
||||
handleMt4GetAccount,
|
||||
Mt4GetAccountInputSchema,
|
||||
type Mt4GetAccountInput,
|
||||
type Mt4GetAccountResult,
|
||||
} from './account';
|
||||
|
||||
// Position tools
|
||||
export {
|
||||
mt4GetPositionsSchema,
|
||||
mt4_get_positions,
|
||||
handleMt4GetPositions,
|
||||
Mt4GetPositionsInputSchema,
|
||||
mt4ClosePositionSchema,
|
||||
mt4_close_position,
|
||||
handleMt4ClosePosition,
|
||||
Mt4ClosePositionInputSchema,
|
||||
type Mt4GetPositionsInput,
|
||||
type Mt4GetPositionsResult,
|
||||
type Mt4ClosePositionInput,
|
||||
type Mt4ClosePositionResult,
|
||||
} from './positions';
|
||||
|
||||
// Trading tools
|
||||
export {
|
||||
mt4ExecuteTradeSchema,
|
||||
mt4_execute_trade,
|
||||
handleMt4ExecuteTrade,
|
||||
Mt4ExecuteTradeInputSchema,
|
||||
mt4ModifyPositionSchema,
|
||||
mt4_modify_position,
|
||||
handleMt4ModifyPosition,
|
||||
Mt4ModifyPositionInputSchema,
|
||||
type Mt4ExecuteTradeInput,
|
||||
type Mt4ExecuteTradeResult,
|
||||
type Mt4ModifyPositionInput,
|
||||
type Mt4ModifyPositionResult,
|
||||
} from './trading';
|
||||
|
||||
// Quote tools
|
||||
export {
|
||||
mt4GetQuoteSchema,
|
||||
mt4_get_quote,
|
||||
handleMt4GetQuote,
|
||||
Mt4GetQuoteInputSchema,
|
||||
type Mt4GetQuoteInput,
|
||||
type Mt4GetQuoteResult,
|
||||
} from './quotes';
|
||||
|
||||
// ==========================================
|
||||
// Tool Registry
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* All available MCP tools with their schemas
|
||||
*/
|
||||
export const mcpToolSchemas = [
|
||||
{
|
||||
name: 'mt4_get_account',
|
||||
description: 'Get MT4 account information including balance, equity, margin, and broker details',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {},
|
||||
required: [] as string[],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mt4_get_positions',
|
||||
description: 'List all open trading positions from MT4. Optionally filter by symbol.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Optional: Filter positions by symbol (e.g., XAUUSD, EURUSD)',
|
||||
},
|
||||
},
|
||||
required: [] as string[],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mt4_execute_trade',
|
||||
description: 'Execute a market order (BUY or SELL) on MT4 with optional stop loss and take profit',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading symbol (e.g., XAUUSD, EURUSD, GBPUSD)',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['buy', 'sell'],
|
||||
description: 'Trade direction: buy or sell',
|
||||
},
|
||||
lots: {
|
||||
type: 'number',
|
||||
description: 'Volume in lots (e.g., 0.01, 0.1, 1.0)',
|
||||
},
|
||||
stopLoss: {
|
||||
type: 'number',
|
||||
description: 'Optional: Stop loss price level',
|
||||
},
|
||||
takeProfit: {
|
||||
type: 'number',
|
||||
description: 'Optional: Take profit price level',
|
||||
},
|
||||
slippage: {
|
||||
type: 'number',
|
||||
description: 'Optional: Maximum slippage in points (default: 3)',
|
||||
},
|
||||
magic: {
|
||||
type: 'number',
|
||||
description: 'Optional: Magic number for EA identification (default: 12345)',
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
description: 'Optional: Order comment (max 31 chars)',
|
||||
},
|
||||
},
|
||||
required: ['symbol', 'action', 'lots'] as string[],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mt4_close_position',
|
||||
description: 'Close an open trading position by ticket number. Can close partially.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
ticket: {
|
||||
type: 'number',
|
||||
description: 'Position ticket number to close',
|
||||
},
|
||||
lots: {
|
||||
type: 'number',
|
||||
description: 'Optional: Partial volume to close. If not specified, closes entire position.',
|
||||
},
|
||||
slippage: {
|
||||
type: 'number',
|
||||
description: 'Optional: Maximum slippage in points (default: 3)',
|
||||
},
|
||||
},
|
||||
required: ['ticket'] as string[],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mt4_modify_position',
|
||||
description: 'Modify stop loss and/or take profit of an existing position',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
ticket: {
|
||||
type: 'number',
|
||||
description: 'Position ticket number to modify',
|
||||
},
|
||||
stopLoss: {
|
||||
type: 'number',
|
||||
description: 'New stop loss price level (optional)',
|
||||
},
|
||||
takeProfit: {
|
||||
type: 'number',
|
||||
description: 'New take profit price level (optional)',
|
||||
},
|
||||
},
|
||||
required: ['ticket'] as string[],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mt4_get_quote',
|
||||
description: 'Get current price quote (bid/ask/spread) for a trading symbol',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading symbol to get quote for (e.g., XAUUSD, EURUSD, GBPUSD)',
|
||||
},
|
||||
},
|
||||
required: ['symbol'] as string[],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Tool handler routing map
|
||||
*/
|
||||
export const toolHandlers: Record<
|
||||
string,
|
||||
(params: unknown) => Promise<{ content: Array<{ type: string; text: string }> }>
|
||||
> = {
|
||||
mt4_get_account: handleMt4GetAccount,
|
||||
mt4_get_positions: handleMt4GetPositions,
|
||||
mt4_execute_trade: handleMt4ExecuteTrade,
|
||||
mt4_close_position: handleMt4ClosePosition,
|
||||
mt4_modify_position: handleMt4ModifyPosition,
|
||||
mt4_get_quote: handleMt4GetQuote,
|
||||
};
|
||||
315
apps/mcp-mt4-connector/src/tools/positions.ts
Normal file
315
apps/mcp-mt4-connector/src/tools/positions.ts
Normal file
@ -0,0 +1,315 @@
|
||||
/**
|
||||
* MT4 Position Tools
|
||||
*
|
||||
* - mt4_get_positions: List all open positions
|
||||
* - mt4_close_position: Close a specific position
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { getMT4Client, MT4Position, TradeResult } from '../services/mt4-client';
|
||||
|
||||
// ==========================================
|
||||
// mt4_get_positions
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* mt4_get_positions - List all open positions
|
||||
*
|
||||
* @description Retrieves all currently open positions from MT4 terminal.
|
||||
* Can optionally filter by symbol.
|
||||
*
|
||||
* @param symbol - Optional symbol to filter positions (e.g., "XAUUSD")
|
||||
* @returns Array of open positions with details
|
||||
*
|
||||
* @example
|
||||
* const result = await mt4_get_positions({});
|
||||
* // Returns all positions
|
||||
*
|
||||
* const result = await mt4_get_positions({ symbol: "XAUUSD" });
|
||||
* // Returns only XAUUSD positions
|
||||
*/
|
||||
|
||||
export const mt4GetPositionsSchema = {
|
||||
name: 'mt4_get_positions',
|
||||
description: 'List all open trading positions from MT4. Optionally filter by symbol.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Optional: Filter positions by symbol (e.g., XAUUSD, EURUSD)',
|
||||
},
|
||||
},
|
||||
required: [] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const Mt4GetPositionsInputSchema = z.object({
|
||||
symbol: z.string().optional(),
|
||||
});
|
||||
|
||||
export type Mt4GetPositionsInput = z.infer<typeof Mt4GetPositionsInputSchema>;
|
||||
|
||||
export interface Mt4GetPositionsResult {
|
||||
success: boolean;
|
||||
data?: {
|
||||
positions: MT4Position[];
|
||||
totalProfit: number;
|
||||
count: number;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function mt4_get_positions(
|
||||
params: Mt4GetPositionsInput
|
||||
): Promise<Mt4GetPositionsResult> {
|
||||
try {
|
||||
const client = getMT4Client();
|
||||
|
||||
// Check connection
|
||||
const isConnected = await client.isConnected();
|
||||
if (!isConnected) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'MT4 terminal is not connected',
|
||||
};
|
||||
}
|
||||
|
||||
// Get all positions
|
||||
let positions = await client.getPositions();
|
||||
|
||||
// Filter by symbol if specified
|
||||
if (params.symbol) {
|
||||
positions = positions.filter(
|
||||
p => p.symbol.toUpperCase() === params.symbol!.toUpperCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate total profit
|
||||
const totalProfit = positions.reduce((sum, p) => sum + p.profit, 0);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
positions,
|
||||
totalProfit,
|
||||
count: positions.length,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMt4GetPositions(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = Mt4GetPositionsInputSchema.parse(params);
|
||||
const result = await mt4_get_positions(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
if (result.data.count === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: params && (params as Mt4GetPositionsInput).symbol
|
||||
? `No open positions found for ${(params as Mt4GetPositionsInput).symbol}`
|
||||
: 'No open positions found',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const positionLines = result.data.positions.map(p => {
|
||||
const direction = p.type.toUpperCase();
|
||||
const profitSign = p.profit >= 0 ? '+' : '';
|
||||
const slInfo = p.stopLoss !== null ? `SL: ${p.stopLoss}` : 'SL: None';
|
||||
const tpInfo = p.takeProfit !== null ? `TP: ${p.takeProfit}` : 'TP: None';
|
||||
|
||||
return `
|
||||
#${p.ticket} | ${p.symbol} | ${direction} ${p.lots} lots
|
||||
Open: ${p.openPrice} | Current: ${p.currentPrice}
|
||||
${slInfo} | ${tpInfo}
|
||||
P/L: ${profitSign}${p.profit.toFixed(2)} | Swap: ${p.swap.toFixed(2)}
|
||||
Opened: ${p.openTime}
|
||||
Magic: ${p.magic} | Comment: ${p.comment || 'None'}`;
|
||||
});
|
||||
|
||||
const formattedOutput = `
|
||||
Open Positions (${result.data.count})
|
||||
${'='.repeat(30)}
|
||||
${positionLines.join('\n---\n')}
|
||||
|
||||
Total P/L: ${result.data.totalProfit >= 0 ? '+' : ''}${result.data.totalProfit.toFixed(2)}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formattedOutput,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error: ${result.error}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// mt4_close_position
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* mt4_close_position - Close a trading position
|
||||
*
|
||||
* @description Closes an open position by ticket number.
|
||||
* Can optionally close partial volume.
|
||||
*
|
||||
* @param ticket - Position ticket number to close
|
||||
* @param lots - Optional: Partial volume to close (default: close all)
|
||||
* @param slippage - Optional: Maximum slippage in points (default: 3)
|
||||
* @returns Trade result with success status
|
||||
*
|
||||
* @example
|
||||
* // Close entire position
|
||||
* const result = await mt4_close_position({ ticket: 123456 });
|
||||
*
|
||||
* // Close partial position
|
||||
* const result = await mt4_close_position({ ticket: 123456, lots: 0.5 });
|
||||
*/
|
||||
|
||||
export const mt4ClosePositionSchema = {
|
||||
name: 'mt4_close_position',
|
||||
description: 'Close an open trading position by ticket number. Can close partially.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
ticket: {
|
||||
type: 'number',
|
||||
description: 'Position ticket number to close',
|
||||
},
|
||||
lots: {
|
||||
type: 'number',
|
||||
description: 'Optional: Partial volume to close. If not specified, closes entire position.',
|
||||
},
|
||||
slippage: {
|
||||
type: 'number',
|
||||
description: 'Optional: Maximum slippage in points (default: 3)',
|
||||
},
|
||||
},
|
||||
required: ['ticket'] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const Mt4ClosePositionInputSchema = z.object({
|
||||
ticket: z.number().int().positive(),
|
||||
lots: z.number().positive().optional(),
|
||||
slippage: z.number().int().min(0).max(100).optional(),
|
||||
});
|
||||
|
||||
export type Mt4ClosePositionInput = z.infer<typeof Mt4ClosePositionInputSchema>;
|
||||
|
||||
export interface Mt4ClosePositionResult {
|
||||
success: boolean;
|
||||
data?: TradeResult;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function mt4_close_position(
|
||||
params: Mt4ClosePositionInput
|
||||
): Promise<Mt4ClosePositionResult> {
|
||||
try {
|
||||
const client = getMT4Client();
|
||||
|
||||
// Check connection
|
||||
const isConnected = await client.isConnected();
|
||||
if (!isConnected) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'MT4 terminal is not connected',
|
||||
};
|
||||
}
|
||||
|
||||
// Verify position exists
|
||||
const position = await client.getPosition(params.ticket);
|
||||
if (!position) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Position with ticket ${params.ticket} not found`,
|
||||
};
|
||||
}
|
||||
|
||||
// Validate lots if specified
|
||||
if (params.lots !== undefined && params.lots > position.lots) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Requested lots (${params.lots}) exceeds position size (${position.lots})`,
|
||||
};
|
||||
}
|
||||
|
||||
// Close position
|
||||
const result = await client.closePosition({
|
||||
ticket: params.ticket,
|
||||
lots: params.lots,
|
||||
slippage: params.slippage,
|
||||
});
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
data: result,
|
||||
error: result.success ? undefined : result.message,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMt4ClosePosition(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = Mt4ClosePositionInputSchema.parse(params);
|
||||
const result = await mt4_close_position(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const formattedOutput = `
|
||||
Position Closed Successfully
|
||||
============================
|
||||
Ticket: ${validatedParams.ticket}
|
||||
${validatedParams.lots ? `Closed Volume: ${validatedParams.lots} lots` : 'Closed: Entire position'}
|
||||
Message: ${result.data.message || 'Position closed'}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formattedOutput,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error closing position: ${result.error}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
193
apps/mcp-mt4-connector/src/tools/quotes.ts
Normal file
193
apps/mcp-mt4-connector/src/tools/quotes.ts
Normal file
@ -0,0 +1,193 @@
|
||||
/**
|
||||
* mt4_get_quote - Get current price quote for a symbol
|
||||
*
|
||||
* @description Retrieves the current bid/ask prices for a trading symbol.
|
||||
* Also calculates the spread in points.
|
||||
*
|
||||
* @param symbol - Trading symbol to get quote for (e.g., "XAUUSD")
|
||||
* @returns Current bid, ask, spread, and timestamp
|
||||
*
|
||||
* @example
|
||||
* const result = await mt4_get_quote({ symbol: "XAUUSD" });
|
||||
* // Returns:
|
||||
* // {
|
||||
* // symbol: "XAUUSD",
|
||||
* // bid: 2650.50,
|
||||
* // ask: 2650.80,
|
||||
* // spread: 0.30,
|
||||
* // timestamp: "2026-01-04T12:00:00.000Z"
|
||||
* // }
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { getMT4Client, MT4Tick } from '../services/mt4-client';
|
||||
|
||||
// ==========================================
|
||||
// Schema Definition
|
||||
// ==========================================
|
||||
|
||||
export const mt4GetQuoteSchema = {
|
||||
name: 'mt4_get_quote',
|
||||
description: 'Get current price quote (bid/ask/spread) for a trading symbol',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading symbol to get quote for (e.g., XAUUSD, EURUSD, GBPUSD)',
|
||||
},
|
||||
},
|
||||
required: ['symbol'] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const Mt4GetQuoteInputSchema = z.object({
|
||||
symbol: z.string().min(1).max(20),
|
||||
});
|
||||
|
||||
export type Mt4GetQuoteInput = z.infer<typeof Mt4GetQuoteInputSchema>;
|
||||
|
||||
// ==========================================
|
||||
// Tool Implementation
|
||||
// ==========================================
|
||||
|
||||
export interface Mt4GetQuoteResult {
|
||||
success: boolean;
|
||||
data?: MT4Tick;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function mt4_get_quote(
|
||||
params: Mt4GetQuoteInput
|
||||
): Promise<Mt4GetQuoteResult> {
|
||||
try {
|
||||
const client = getMT4Client();
|
||||
|
||||
// Check connection
|
||||
const isConnected = await client.isConnected();
|
||||
if (!isConnected) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'MT4 terminal is not connected',
|
||||
};
|
||||
}
|
||||
|
||||
// Get tick data
|
||||
const tick = await client.getTick(params.symbol.toUpperCase());
|
||||
|
||||
// Validate we got valid data
|
||||
if (tick.bid === 0 && tick.ask === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `No quote data available for ${params.symbol}. Symbol may not be available on this broker.`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: tick,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// MCP Tool Handler
|
||||
// ==========================================
|
||||
|
||||
export async function handleMt4GetQuote(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = Mt4GetQuoteInputSchema.parse(params);
|
||||
const result = await mt4_get_quote(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
// Determine decimal places based on symbol
|
||||
const decimals = getDecimalPlaces(result.data.symbol);
|
||||
const spreadPips = calculateSpreadPips(result.data.spread, result.data.symbol);
|
||||
|
||||
const formattedOutput = `
|
||||
Price Quote: ${result.data.symbol}
|
||||
${'='.repeat(25)}
|
||||
Bid: ${result.data.bid.toFixed(decimals)}
|
||||
Ask: ${result.data.ask.toFixed(decimals)}
|
||||
Spread: ${result.data.spread.toFixed(decimals)} (${spreadPips.toFixed(1)} pips)
|
||||
Time: ${result.data.timestamp}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formattedOutput,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error: ${result.error}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Helper Functions
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Determine decimal places based on symbol type
|
||||
*/
|
||||
function getDecimalPlaces(symbol: string): number {
|
||||
const upperSymbol = symbol.toUpperCase();
|
||||
|
||||
// JPY pairs have 3 decimals, most forex 5 decimals
|
||||
if (upperSymbol.includes('JPY')) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
// Gold and metals typically 2 decimals
|
||||
if (upperSymbol.startsWith('XAU') || upperSymbol.startsWith('XAG')) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Indices vary, use 2 as default
|
||||
if (
|
||||
upperSymbol.includes('US30') ||
|
||||
upperSymbol.includes('US500') ||
|
||||
upperSymbol.includes('NAS')
|
||||
) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Default forex pairs
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate spread in pips based on symbol
|
||||
*/
|
||||
function calculateSpreadPips(spread: number, symbol: string): number {
|
||||
const upperSymbol = symbol.toUpperCase();
|
||||
|
||||
// JPY pairs: 1 pip = 0.01
|
||||
if (upperSymbol.includes('JPY')) {
|
||||
return spread * 100;
|
||||
}
|
||||
|
||||
// Gold: 1 pip = 0.10
|
||||
if (upperSymbol.startsWith('XAU')) {
|
||||
return spread * 10;
|
||||
}
|
||||
|
||||
// Default forex: 1 pip = 0.0001
|
||||
return spread * 10000;
|
||||
}
|
||||
402
apps/mcp-mt4-connector/src/tools/trading.ts
Normal file
402
apps/mcp-mt4-connector/src/tools/trading.ts
Normal file
@ -0,0 +1,402 @@
|
||||
/**
|
||||
* MT4 Trading Tools
|
||||
*
|
||||
* - mt4_execute_trade: Execute BUY/SELL market orders
|
||||
* - mt4_modify_position: Modify SL/TP of existing positions
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { getMT4Client, TradeResult } from '../services/mt4-client';
|
||||
|
||||
// ==========================================
|
||||
// mt4_execute_trade
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* mt4_execute_trade - Execute a market order (BUY or SELL)
|
||||
*
|
||||
* @description Opens a new trading position with optional SL/TP levels.
|
||||
* Supports market orders only (pending orders not implemented).
|
||||
*
|
||||
* @param symbol - Trading symbol (e.g., "XAUUSD", "EURUSD")
|
||||
* @param action - Trade direction: "buy" or "sell"
|
||||
* @param lots - Volume in lots (e.g., 0.01, 0.1, 1.0)
|
||||
* @param stopLoss - Optional stop loss price
|
||||
* @param takeProfit - Optional take profit price
|
||||
* @param slippage - Optional max slippage in points (default: 3)
|
||||
* @param magic - Optional magic number for EA identification
|
||||
* @param comment - Optional order comment
|
||||
* @returns Trade result with ticket number
|
||||
*
|
||||
* @example
|
||||
* // Simple buy order
|
||||
* const result = await mt4_execute_trade({
|
||||
* symbol: "XAUUSD",
|
||||
* action: "buy",
|
||||
* lots: 0.1
|
||||
* });
|
||||
*
|
||||
* // Buy with SL/TP
|
||||
* const result = await mt4_execute_trade({
|
||||
* symbol: "XAUUSD",
|
||||
* action: "buy",
|
||||
* lots: 0.1,
|
||||
* stopLoss: 2640.00,
|
||||
* takeProfit: 2680.00,
|
||||
* comment: "AI Signal"
|
||||
* });
|
||||
*/
|
||||
|
||||
export const mt4ExecuteTradeSchema = {
|
||||
name: 'mt4_execute_trade',
|
||||
description: 'Execute a market order (BUY or SELL) on MT4 with optional stop loss and take profit',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
symbol: {
|
||||
type: 'string',
|
||||
description: 'Trading symbol (e.g., XAUUSD, EURUSD, GBPUSD)',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['buy', 'sell'],
|
||||
description: 'Trade direction: buy or sell',
|
||||
},
|
||||
lots: {
|
||||
type: 'number',
|
||||
description: 'Volume in lots (e.g., 0.01, 0.1, 1.0)',
|
||||
},
|
||||
stopLoss: {
|
||||
type: 'number',
|
||||
description: 'Optional: Stop loss price level',
|
||||
},
|
||||
takeProfit: {
|
||||
type: 'number',
|
||||
description: 'Optional: Take profit price level',
|
||||
},
|
||||
slippage: {
|
||||
type: 'number',
|
||||
description: 'Optional: Maximum slippage in points (default: 3)',
|
||||
},
|
||||
magic: {
|
||||
type: 'number',
|
||||
description: 'Optional: Magic number for EA identification (default: 12345)',
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
description: 'Optional: Order comment (max 31 chars)',
|
||||
},
|
||||
},
|
||||
required: ['symbol', 'action', 'lots'] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const Mt4ExecuteTradeInputSchema = z.object({
|
||||
symbol: z.string().min(1).max(20),
|
||||
action: z.enum(['buy', 'sell']),
|
||||
lots: z.number().positive().max(100),
|
||||
stopLoss: z.number().positive().optional(),
|
||||
takeProfit: z.number().positive().optional(),
|
||||
slippage: z.number().int().min(0).max(100).optional(),
|
||||
magic: z.number().int().optional(),
|
||||
comment: z.string().max(31).optional(),
|
||||
});
|
||||
|
||||
export type Mt4ExecuteTradeInput = z.infer<typeof Mt4ExecuteTradeInputSchema>;
|
||||
|
||||
export interface Mt4ExecuteTradeResult {
|
||||
success: boolean;
|
||||
data?: TradeResult & {
|
||||
symbol: string;
|
||||
action: string;
|
||||
lots: number;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function mt4_execute_trade(
|
||||
params: Mt4ExecuteTradeInput
|
||||
): Promise<Mt4ExecuteTradeResult> {
|
||||
try {
|
||||
const client = getMT4Client();
|
||||
|
||||
// Check connection
|
||||
const isConnected = await client.isConnected();
|
||||
if (!isConnected) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'MT4 terminal is not connected',
|
||||
};
|
||||
}
|
||||
|
||||
// Validate SL/TP logic (basic check)
|
||||
if (params.stopLoss !== undefined && params.takeProfit !== undefined) {
|
||||
if (params.action === 'buy') {
|
||||
// For buy: SL should be below current price, TP above
|
||||
if (params.stopLoss >= params.takeProfit) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'For BUY orders, stop loss must be below take profit',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// For sell: SL should be above current price, TP below
|
||||
if (params.stopLoss <= params.takeProfit) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'For SELL orders, stop loss must be above take profit',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute trade
|
||||
const result = await client.executeTrade({
|
||||
symbol: params.symbol.toUpperCase(),
|
||||
action: params.action,
|
||||
lots: params.lots,
|
||||
stopLoss: params.stopLoss,
|
||||
takeProfit: params.takeProfit,
|
||||
slippage: params.slippage,
|
||||
magic: params.magic,
|
||||
comment: params.comment,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
...result,
|
||||
symbol: params.symbol.toUpperCase(),
|
||||
action: params.action,
|
||||
lots: params.lots,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: result.message || 'Trade execution failed',
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMt4ExecuteTrade(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = Mt4ExecuteTradeInputSchema.parse(params);
|
||||
const result = await mt4_execute_trade(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const formattedOutput = `
|
||||
Trade Executed Successfully
|
||||
===========================
|
||||
Ticket: #${result.data.ticket}
|
||||
Symbol: ${result.data.symbol}
|
||||
Direction: ${result.data.action.toUpperCase()}
|
||||
Volume: ${result.data.lots} lots
|
||||
${validatedParams.stopLoss ? `Stop Loss: ${validatedParams.stopLoss}` : 'Stop Loss: Not set'}
|
||||
${validatedParams.takeProfit ? `Take Profit: ${validatedParams.takeProfit}` : 'Take Profit: Not set'}
|
||||
Message: ${result.data.message || 'Order placed successfully'}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formattedOutput,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error executing trade: ${result.error}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// mt4_modify_position
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* mt4_modify_position - Modify SL/TP of an existing position
|
||||
*
|
||||
* @description Updates the stop loss and/or take profit levels of an open position.
|
||||
*
|
||||
* @param ticket - Position ticket number to modify
|
||||
* @param stopLoss - New stop loss price (null to remove)
|
||||
* @param takeProfit - New take profit price (null to remove)
|
||||
* @returns Modification result
|
||||
*
|
||||
* @example
|
||||
* // Set both SL and TP
|
||||
* const result = await mt4_modify_position({
|
||||
* ticket: 123456,
|
||||
* stopLoss: 2640.00,
|
||||
* takeProfit: 2680.00
|
||||
* });
|
||||
*
|
||||
* // Update only TP
|
||||
* const result = await mt4_modify_position({
|
||||
* ticket: 123456,
|
||||
* takeProfit: 2700.00
|
||||
* });
|
||||
*/
|
||||
|
||||
export const mt4ModifyPositionSchema = {
|
||||
name: 'mt4_modify_position',
|
||||
description: 'Modify stop loss and/or take profit of an existing position',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
ticket: {
|
||||
type: 'number',
|
||||
description: 'Position ticket number to modify',
|
||||
},
|
||||
stopLoss: {
|
||||
type: 'number',
|
||||
description: 'New stop loss price level (optional)',
|
||||
},
|
||||
takeProfit: {
|
||||
type: 'number',
|
||||
description: 'New take profit price level (optional)',
|
||||
},
|
||||
},
|
||||
required: ['ticket'] as string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const Mt4ModifyPositionInputSchema = z.object({
|
||||
ticket: z.number().int().positive(),
|
||||
stopLoss: z.number().positive().optional(),
|
||||
takeProfit: z.number().positive().optional(),
|
||||
});
|
||||
|
||||
export type Mt4ModifyPositionInput = z.infer<typeof Mt4ModifyPositionInputSchema>;
|
||||
|
||||
export interface Mt4ModifyPositionResult {
|
||||
success: boolean;
|
||||
data?: TradeResult;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function mt4_modify_position(
|
||||
params: Mt4ModifyPositionInput
|
||||
): Promise<Mt4ModifyPositionResult> {
|
||||
try {
|
||||
const client = getMT4Client();
|
||||
|
||||
// Check connection
|
||||
const isConnected = await client.isConnected();
|
||||
if (!isConnected) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'MT4 terminal is not connected',
|
||||
};
|
||||
}
|
||||
|
||||
// Validate at least one parameter is provided
|
||||
if (params.stopLoss === undefined && params.takeProfit === undefined) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'At least one of stopLoss or takeProfit must be provided',
|
||||
};
|
||||
}
|
||||
|
||||
// Verify position exists
|
||||
const position = await client.getPosition(params.ticket);
|
||||
if (!position) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Position with ticket ${params.ticket} not found`,
|
||||
};
|
||||
}
|
||||
|
||||
// Validate SL/TP based on position type
|
||||
const sl = params.stopLoss;
|
||||
const tp = params.takeProfit;
|
||||
|
||||
if (sl !== undefined && tp !== undefined) {
|
||||
if (position.type === 'buy') {
|
||||
if (sl >= tp) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'For BUY positions, stop loss must be below take profit',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (sl <= tp) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'For SELL positions, stop loss must be above take profit',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modify position
|
||||
const result = await client.modifyPosition({
|
||||
ticket: params.ticket,
|
||||
stopLoss: params.stopLoss,
|
||||
takeProfit: params.takeProfit,
|
||||
});
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
data: result,
|
||||
error: result.success ? undefined : result.message,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMt4ModifyPosition(
|
||||
params: unknown
|
||||
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
||||
const validatedParams = Mt4ModifyPositionInputSchema.parse(params);
|
||||
const result = await mt4_modify_position(validatedParams);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const formattedOutput = `
|
||||
Position Modified Successfully
|
||||
==============================
|
||||
Ticket: #${validatedParams.ticket}
|
||||
${validatedParams.stopLoss !== undefined ? `New Stop Loss: ${validatedParams.stopLoss}` : 'Stop Loss: Unchanged'}
|
||||
${validatedParams.takeProfit !== undefined ? `New Take Profit: ${validatedParams.takeProfit}` : 'Take Profit: Unchanged'}
|
||||
Message: ${result.data.message || 'Position modified successfully'}
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formattedOutput,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error modifying position: ${result.error}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
23
apps/mcp-mt4-connector/tsconfig.json
Normal file
23
apps/mcp-mt4-connector/tsconfig.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "tests"]
|
||||
}
|
||||
@ -13,23 +13,23 @@ services:
|
||||
# ===========================================================================
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: orbiquant-postgres
|
||||
image: postgres:16-alpine
|
||||
container_name: orbiquantia-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: orbiquant_trading
|
||||
POSTGRES_USER: orbiquant_user
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-orbiquant_dev_2025}
|
||||
POSTGRES_DB: orbiquantia_platform
|
||||
POSTGRES_USER: orbiquantia
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-orbiquantia_dev_2025}
|
||||
POSTGRES_INITDB_ARGS: "-E UTF8"
|
||||
ports:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
- "${POSTGRES_PORT:-5433}:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./apps/database/schemas:/docker-entrypoint-initdb.d:ro
|
||||
networks:
|
||||
- orbiquant-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U orbiquant_user -d orbiquant_trading"]
|
||||
test: ["CMD-SHELL", "pg_isready -U orbiquantia -d orbiquantia_platform"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@ -66,9 +66,9 @@ services:
|
||||
PORT: ${BACKEND_API_PORT:-3081}
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_NAME: orbiquant_trading
|
||||
DB_USER: orbiquant_user
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD:-orbiquant_dev_2025}
|
||||
DB_NAME: orbiquantia_platform
|
||||
DB_USER: orbiquantia
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD:-orbiquantia_dev_2025}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
ML_ENGINE_URL: http://ml-engine:3083
|
||||
@ -131,9 +131,9 @@ services:
|
||||
PYTHONUNBUFFERED: 1
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_NAME: orbiquant_trading
|
||||
DB_USER: orbiquant_user
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD:-orbiquant_dev_2025}
|
||||
DB_NAME: orbiquantia_platform
|
||||
DB_USER: orbiquantia
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD:-orbiquantia_dev_2025}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
PORT: ${ML_ENGINE_PORT:-3083}
|
||||
@ -163,9 +163,9 @@ services:
|
||||
PYTHONUNBUFFERED: 1
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_NAME: orbiquant_trading
|
||||
DB_USER: orbiquant_user
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD:-orbiquant_dev_2025}
|
||||
DB_NAME: orbiquantia_platform
|
||||
DB_USER: orbiquantia
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD:-orbiquantia_dev_2025}
|
||||
POLYGON_API_KEY: ${POLYGON_API_KEY}
|
||||
METAAPI_TOKEN: ${METAAPI_TOKEN}
|
||||
METAAPI_ACCOUNT_ID: ${METAAPI_ACCOUNT_ID}
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "NOTA-DISCREPANCIA-PUERTOS-2025-12-08"
|
||||
title: "Discrepancia de Puertos Detectada"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# NOTA: Discrepancia de Puertos Detectada
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "ARQUITECTURA-GENERAL"
|
||||
title: "Arquitectura General - OrbiQuant IA"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Arquitectura General - OrbiQuant IA
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
414
docs/00-vision-general/Curso_Basico.md
Normal file
414
docs/00-vision-general/Curso_Basico.md
Normal file
@ -0,0 +1,414 @@
|
||||
Modulo 1
|
||||
Elementos básicos
|
||||
Mercado de derivados y OTC
|
||||
Derivados
|
||||
El mercado de derivados es donde se negocian instrumentos financieros cuyo valor depende (o deriva) del precio de otro activo, como acciones, divisas, materias primas, índices, etc.
|
||||
Algunos ejemplos de derivados son:
|
||||
• Futuros
|
||||
• Opciones
|
||||
• CFDs (Contratos por diferencia)
|
||||
• Swaps
|
||||
OTC
|
||||
OTC significa "fuera de mercado" o "sobre el mostrador". Es decir, son operaciones que no pasan por una bolsa formal como la de Nueva York o Chicago.
|
||||
En el mercado OTC:
|
||||
• Las operaciones se hacen directamente entre dos partes, como un trader y un broker.
|
||||
• Hay más flexibilidad en los contratos (pueden ser a medida).
|
||||
• Es común en productos como CFDs, divisas (forex), swaps, etc.
|
||||
Tipos de gráficos
|
||||
1.- Gráfico de líneas
|
||||
• Qué muestra: Solo el precio de cierre de cada periodo (por ejemplo, cada día, hora o minuto).
|
||||
• Cómo se ve: Una línea continua que conecta los precios de cierre.
|
||||
• Ventajas:
|
||||
o Simple y limpio, ideal para ver la tendencia general.
|
||||
o Útil para principiantes que no quieren sobrecargarse de información.
|
||||
• Desventaja: No muestra el rango completo de precios (apertura, máximo y mínimo).
|
||||
• Ejemplo 1:
|
||||
|
||||
2.- Gráfico de barras
|
||||
• Qué muestra: También OHLC, como las velas, pero de forma diferente.
|
||||
• Cómo se ve:
|
||||
o Una línea vertical representa el rango (mínimo a máximo).
|
||||
o Una línea corta a la izquierda marca la apertura.
|
||||
o Una línea corta a la derecha marca el cierre.
|
||||
• Ventajas:
|
||||
o Más compacto que las velas, puede mostrar más datos en menos espacio.
|
||||
• Desventaja: Menos visual y atractivo que las velas japonesas.
|
||||
|
||||
3.- Gráfico de velas japonesas
|
||||
• Qué muestra: Apertura, máximo, mínimo y cierre (OHLC) de cada periodo.
|
||||
• Cómo se ve: Cada vela tiene un cuerpo (de color verde o rojo) y "mechas" arriba y abajo.
|
||||
o Cuerpo: distancia entre el precio de apertura y cierre.
|
||||
o Mechas: muestran los precios más altos y bajos alcanzados.
|
||||
• Ventajas:
|
||||
o Muy visual, permite detectar patrones de comportamiento del mercado.
|
||||
o Ideal para análisis técnico.
|
||||
• Desventaja: Puede parecer complejo al principio por la cantidad de información.
|
||||
|
||||
Siendo este último gráfico el más relevante para nosotros, pues será el principal que observaremos al estar en la operativa.
|
||||
Velas japonesas
|
||||
Material de apoyo, ejemplos y su uso:
|
||||
Modulo 2
|
||||
2.- Factores y herramientas
|
||||
Tipos de broker
|
||||
ECN (Electronic Communication Network)
|
||||
• Cómo funciona: Te conecta directamente con una red de participantes del mercado (otros traders, bancos, instituciones). Tú operas con precios reales del mercado.
|
||||
• Ventaja: Spreads ultra bajos, alta transparencia, sin mesa de negociación.
|
||||
• Desventaja: Comisiones por operación, puede requerir mayor capital mínimo.
|
||||
• Ideal para: Traders profesionales, scalpers, o quienes quieren máxima precisión.
|
||||
STP (Straight Through Processing)
|
||||
• Cómo funciona: El broker no opera contra ti, sino que pasa tus órdenes directamente a sus proveedores de liquidez (como bancos o instituciones grandes).
|
||||
• Ventaja: Menos conflicto de interés, spreads más realistas.
|
||||
• Desventaja: Puede haber comisiones por operación, y en momentos de alta volatilidad la ejecución puede no ser tan instantánea.
|
||||
• Ideal para: Traders intermedios o con enfoque más técnico.
|
||||
Market Maker (creador de mercado)
|
||||
• Cómo funciona: El broker crea el mercado interno. Es decir, toma el otro lado de tu operación. Si tú compras, ellos venden, y viceversa.
|
||||
• Ventaja: Spreads bajos, ejecución rápida, acceso con poco capital.
|
||||
• Desventaja: Puede haber conflicto de interés (porque ganan cuando tú pierdes, aunque muchos están regulados para evitar abusos).
|
||||
• Ideal para: Traders principiantes o con capital pequeño.
|
||||
Híbrido (STP + Market Maker)
|
||||
• Muchos brokers usan un modelo mixto. Por ejemplo, pueden ejecutar operaciones pequeñas como Market Maker y operaciones grandes pasarlas a proveedores de liquidez (STP).
|
||||
• Buscan ofrecer lo mejor de ambos mundos: costos bajos y buena ejecución.
|
||||
Regulaciones perspectiva global
|
||||
# Regulador País / Región Nivel de Protección Comentarios clave
|
||||
1 FCA (Financial Conduct Authority) Reino Unido 🇬🇧 Muy alto 🟢🟢🟢 Supervisión estricta, protección de fondos, política clara contra conflictos.
|
||||
2 ASIC (Australian Securities & Investments Commission) Australia 🇦🇺 Muy alto 🟢🟢🟢 Requiere transparencia, separación de fondos, protección al retail.
|
||||
3 CySEC (Cyprus Securities and Exchange Commission) Chipre 🇨🇾 (UE) Alto 🟢🟢 Muy común en brokers europeos, cumple con normativa MiFID II.
|
||||
4 BaFin (Federal Financial Supervisory Authority) Alemania 🇩🇪 Alto 🟢🟢 Muy sólida, regulaciones conservadoras, parte de la UE.
|
||||
5 CNMV (Comisión Nacional del Mercado de Valores) España 🇪🇸 Alto 🟢🟢 Buen nivel de supervisión, dentro del marco europeo.
|
||||
6 FINMA (Swiss Financial Market Supervisory Authority) Suiza 🇨🇭 Alto 🟢🟢 Muy confiable, aunque pocos brokers CFDs están bajo esta licencia.
|
||||
7 CFTC / NFA (Commodity Futures Trading Commission / National Futures Association) EE.UU. 🇺🇸 Muy alto 🟢🟢🟢 pero... Muy estricta, pero no permite CFDs para retail en EE.UU.
|
||||
8 DFSA (Dubai Financial Services Authority) Dubái 🇦🇪 Medio 🟡 Creciendo en prestigio, aún en desarrollo.
|
||||
9 FSA (Financial Services Authority) Seychelles / Mauricio / Belice 🌴 Bajo 🔴 Regulación suave, común en brokers offshore. Cuidado.
|
||||
10 No regulado / Offshore sin supervisión Ninguno Muy bajo 🔴🔴🔴 Riesgo muy alto. No recomendable.
|
||||
Terminal operativa (Metatrader 4 y 5)
|
||||
¿Qué es MetaTrader (MT4 y MT5)?
|
||||
MetaTrader es una plataforma de trading muy popular que permite operar en mercados como forex, CFDs, acciones, índices, criptos, etc.
|
||||
• MT4: Lanzado en 2005. Muy usado en forex y CFDs.
|
||||
• MT5: Lanzado en 2010. Más moderno, con más funciones y acceso a más mercados (acciones reales, futuros, etc.).
|
||||
Funciones Básicas (tanto en MT4 como en MT5)
|
||||
Función Descripción
|
||||
📊 Gráficos en tiempo real Puedes analizar el movimiento del precio con velas, líneas o barras.
|
||||
🧰 Indicadores técnicos Vienen con indicadores como RSI, MACD, Medias Móviles, etc.
|
||||
🎯 Órdenes de compra/venta Ejecuta operaciones con distintos tipos de órdenes (market, stop, limit).
|
||||
🧱 Trading automático (EA) Puedes usar o crear Expert Advisors para operar de forma automática.
|
||||
🗂️ Gestión de múltiples activos Puedes operar varios pares o instrumentos al mismo tiempo.
|
||||
📝 Historial y análisis Ver resultados de tus operaciones pasadas y analizar tu rendimiento.
|
||||
Diferencias clave entre MT4 y MT5
|
||||
Característica MT4 MT5
|
||||
🛠️ Año de lanzamiento 2005 2010
|
||||
🎯 Mercados disponibles Principalmente Forex y CFDs Forex, CFDs, acciones, futuros, criptos
|
||||
🧠 Tipo de arquitectura 32 bits 64 bits (más rápido y eficiente)
|
||||
📅 Calendario económico ❌ No integrado ✅ Integrado
|
||||
🧮 Tipos de órdenes 4 tipos 6 tipos (más flexibilidad)
|
||||
📊 Timeframes de gráficos 9 21 (más opciones para análisis)
|
||||
💻 Lenguaje de programación MQL4 (más limitado) MQL5 (más potente, tipo C++)
|
||||
🔄 Compatibilidad con EAs Solo EAs de MT4 Solo EAs de MT5 (no son intercambiables)
|
||||
Terminal de análisis (Tradingview)
|
||||
¿Qué es TradingView?
|
||||
TradingView es una plataforma en línea para análisis técnico y social. Permite ver gráficos en tiempo real de acciones, criptomonedas, forex, índices y más, desde cualquier navegador o app.
|
||||
Es muy popular entre traders por su interfaz intuitiva, herramientas visuales y comunidad activa.
|
||||
Funciones básicas e importantes
|
||||
Función Descripción
|
||||
📈 Gráficos interactivos Personalizables, con múltiples tipos: velas, líneas, barras, Heikin Ashi…
|
||||
🧰 Indicadores técnicos Vienen más de 100 por defecto (RSI, MACD, EMA, etc.)
|
||||
✏️ Herramientas de dibujo Líneas, retrocesos de Fibonacci, canales, patrones armónicos, etc.
|
||||
⏱️ Múltiples marcos de tiempo Desde segundos hasta meses
|
||||
💬 Ideas de la comunidad Miles de análisis públicos de otros traders para inspirarte o aprender
|
||||
🧠 Pine Script (avanzado) Lenguaje para crear tus propios indicadores o estrategias automáticas
|
||||
🔔 Alertas Te avisa cuando el precio cumple una condición que tú defines
|
||||
🖥️ Multipantalla y diseño Puedes ver varios activos a la vez, en la misma pantalla
|
||||
Nota: Existe una versión gratuita y otra de paga, difieren en su potencia y accesibilidad a herramientas, etc.
|
||||
Para nuestro uso inicial usaremos la versión gratuita que nos permite operar perfectamente funcional.
|
||||
Tipos de cuenta
|
||||
ECN, Standard, Cent, Zero, FixAPI y Social Trading (PAMM y Copy)
|
||||
|
||||
|
||||
Creación de cuentas y conexión a terminal operativa
|
||||
Crea una cuenta en un broker 100% B2B, hiper regulado, con las mejores condiciones de mercado y retiros instantáneos.
|
||||
CXM una excelente opción para operar con confianza y precisión.
|
||||
|
||||
|
||||
|
||||
|
||||
link de apertura ⬇⬇⬇
|
||||
CXM
|
||||
QR de apertura ⬇⬇⬇
|
||||
|
||||
Modulo 3
|
||||
Introducción al IPDA
|
||||
Interbank Price Delivery Algorithmic, estructura y narrativa del precio
|
||||
Comencemos con la pregunta inicial base ⬇️⬇️⬇️
|
||||
🧠 ¿Qué es el IPDA (Interbank Price Delivery Algorithmic).pdf
|
||||
¿Cómo es visualmente?
|
||||
El IPDA crea fractales en formación A+B+C
|
||||
Formación de fractal alcista
|
||||
|
||||
Formación de fractal bajista
|
||||
|
||||
Durante la creación de un fractal completo, cuando los puntos ABC están listos para el siguiente movimiento del precio, sucede una actualización en la estructura del IPDA para el siguiente fractal que se forme (fractal 2), donde el punto C se transforma en un Nuevo punto A (Ai).
|
||||
Al ser superado el anterior punto B, iniciamos la creación de un nuevo fractal Ai + Bi + Ci
|
||||
Ejemplo:
|
||||
Fractal alcista 1 completado, fractal alcista 2 en formación
|
||||
|
||||
Fractal bajista 1 completado, fractal bajista 2 en formación
|
||||
|
||||
Es así como llegamos a la siguiente nomenclatura que nos ayudará a crear una narrativa inicial del precio y entender la tendencia.
|
||||
Modificamos A+B+C por HH, HL, LH, LL
|
||||
Tabla descriptiva:
|
||||
|
||||
Ejemplo visual comparativa de HH y HL (tendencia alcista)
|
||||
|
||||
Ejemplo visual de LL y LH (tendencia bajista)
|
||||
|
||||
🎯 En resumen:
|
||||
• HH + HL = Mercado subiendo con fuerza (tendencia alcista).
|
||||
• LH + LL = Mercado cayendo (tendencia bajista).
|
||||
• Mezcla de ambos = Rango o posible cambio de tendencia.
|
||||
Swing High y Swing Low
|
||||
Durante la creación de la estructura del precio, tenemos nuevos máximos y nuevos mínimos respectivamente, pero para determinar si dicho punto es o no valido para la estructura debemos incluir nueva información, información disponible en el grafico de velas japonesas.
|
||||
¿Qué es un Swing High y un Swing Low?
|
||||
Término Definición simple
|
||||
Swing High Es un pico o punto más alto, rodeado por dos velas a su izquierda y derecha con máximos más bajos.
|
||||
Swing Low Es un valle o punto más bajo, rodeado por dos velas a su izquierda y derecha con mínimos más altos.
|
||||
Swing High
|
||||
Es una vela alcista seguida de 2 velas bajistas
|
||||
|
||||
Swing Low
|
||||
Es una vela bajista seguida de 2 velas alcistas
|
||||
|
||||
Existe un aumento de relevancia Segun el time frame, mientras más alto sea el time frame en el que se observan las velas, más contundente podría ser nuestro Swing High/Low respectivamente.
|
||||
Switch Market (Shift Market), BOS y BIS
|
||||
🧠 GLOSARIO ICT
|
||||
Concepto Significado básico
|
||||
BOS (Break of Structure) El mercado confirma la continuidad de la tendencia actual.
|
||||
SHIFT (Market Structure Shift) El mercado cambia su dirección: de alcista a bajista o viceversa.
|
||||
BIOS (Bias) Es la dirección más probable en que se moverá el mercado (tu “norte”).
|
||||
Switch Market Es cuando el mercado hace un SHIFT, cambia de tendencia (ej. de bullish a bearish), y te da señales claras para entrar en sentido contrario.
|
||||
🔄 ¿Qué es un Market Structure Shift (o "Switch Market")?
|
||||
Un SHIFT es cuando el mercado rompe la estructura contraria.
|
||||
Ejemplo:
|
||||
• El mercado iba haciendo HH y HL (alcista).
|
||||
• Pero rompe un HL anterior → esto no es BOS, es un cambio: ahora hay intención bajista.
|
||||
📌 El SHIFT es una señal de que algo cambió, y puede indicar una reversión.
|
||||
Ejemplo visual Switch Market bajista
|
||||
|
||||
Ejemplo visual Switch Market Alcista
|
||||
|
||||
¿Qué es un BOS (Break of Structure)?
|
||||
Imagina que el mercado está haciendo Higher Highs y Higher Lows (tendencia alcista).
|
||||
Cuando rompe el último Higher High (HH) y hace un nuevo HH más alto → eso es un BOS alcista.
|
||||
Es una confirmación de que el precio sigue su rumbo.
|
||||
▶️ ¿Qué confirma un BOS?
|
||||
• Que la tendencia continúa.
|
||||
• Que el smart money sigue en control en esa dirección.
|
||||
Ejemplo Visual BOS bajista creado por un SW Market y seguido de BIS
|
||||
|
||||
Modulo 4
|
||||
1.- Tendencia
|
||||
Se dice que la tendencia es tu amiga o aliada, se tiene una tendencia cuando un conjunto de máximos y mínimos tienen una misma orientación.
|
||||
¿Qué es la tendencia en los mercados financieros?
|
||||
La tendencia es la dirección general en la que se mueve el precio de un activo durante un periodo de tiempo. Hay tres tipos principales:
|
||||
1. Tendencia alcista (bullish): el precio sube y forma mínimos y máximos cada vez más altos.
|
||||
o Ejemplo: El precio pasa de 100 → 105 → 110 → 115.
|
||||
2. Tendencia bajista (bearish): el precio baja y forma mínimos y máximos cada vez más bajos.
|
||||
o Ejemplo: El precio cae de 100 → 95 → 90 → 85.
|
||||
3. Tendencia lateral (rango o consolidación): el precio no tiene una dirección clara, se mueve entre un soporte y una resistencia.
|
||||
o Ejemplo: El precio oscila entre 98 y 102 por varios días.
|
||||
Otros detalles importantes:
|
||||
• Una tendencia no significa que el precio suba o baje siempre en línea recta. Hay retrocesos (correcciones) normales dentro de una tendencia.
|
||||
• La temporalidad importa: algo puede estar bajista en 5 minutos y alcista en 1 hora.
|
||||
• Las tendencias fuertes suelen respetar zonas clave de soporte o resistencia.
|
||||
• Nunca operes en contra de la tendencia, a menos que tengas una estrategia avanzada (no recomendada para principiantes).
|
||||
2.- Key levels (KL, KLi, KLii)
|
||||
Es definido como un precio especifico en el que los agentes institucionales inyectan liquidez a la estructura del precio, suelen ser atractores del precio, y también detonadores de nuevas tendencias. Son precios que suelen tener terminación en 00 y hasta 000.
|
||||
¿Como se verían visualmente?
|
||||
aquí hay un ejemplo de cómo se ven los KL en el índice Nasdaq, grafico semanal.
|
||||
|
||||
Una vez detectado un KL, es posible crear una proyección interna sobre otros precios relevante en la estructura macro, tomando como referencia los KL ya negociados buscaremos el 50% de esa área, y una vez identificado el punto medio es importante destacar un nuevo KL interno (KLi) mismo precio que tendrá un uso similar a su sucesor macro, ya que son atractores del precio e inyectores de liquidez.
|
||||
¿Como se vería visualmente?
|
||||
Aquí un ejemplo de cómo se ven los KLi en el índice Nasdaq, conservando los KL previos
|
||||
|
||||
Como punto de acceso adicional, existen precios usados como confluencia adicional, para el uso de la estrategia los definimos como Key Level interno de grado inferior (KLii), y se encuentran en el 50% del area disponible entre KL y KLi.
|
||||
¿Cómo se verían visualmente?
|
||||
Aquí un ejemplo de cómo se ven los KLii en el índice Nasdaq, conservando los KL y KLi previos
|
||||
|
||||
3.- Power of three
|
||||
¿Qué es el Power of Three (POT)?
|
||||
El Power of Three es un patrón de comportamiento del precio durante una sesión o ciclo de mercado, que refleja la manipulación institucional intencional del precio antes de su verdadero movimiento. Según la teoría del IPDA, los grandes participantes (instituciones, bancos, algoritmos interbancarios) manipulan el mercado para maximizar liquidez y tomar el lado opuesto del público minorista.
|
||||
Las 3 Fases del Power of Three (AMD)
|
||||
1. Acumulación (Accumulation):
|
||||
o El precio se mueve de forma lateral o con pequeñas trampas.
|
||||
o Objetivo: Crear una zona de liquidez o engañar al trader minorista.
|
||||
o Se acumulan órdenes de compra/venta en zonas clave (como zonas de liquidez, equal highs/lows, etc.).
|
||||
2. Manipulación
|
||||
o Aquí el precio se mueve hacia la dirección institucional real, rompiendo estructuras o zonas manipuladas.
|
||||
o Este movimiento busca capturar la liquidez que se generó en la fase de acumulación.
|
||||
3. Distribución - Expansión (Expansión / Real Move; Distribution / Reversal or Take Profit):
|
||||
o Movimiento impulsivo fuerte en una dirección (el "true move").
|
||||
o El precio desacelera o se revierte.
|
||||
o Objetivo: Cerrar operaciones institucionales, tomar beneficios, o preparar una nueva trampa.
|
||||
o Suele terminar el ciclo diario o la sesión, y puede dar inicio a otro patrón POT.
|
||||
Esquema visual de líneas POT/AMD, en un proceso de creación de un fractal con orientación alcista.
|
||||
|
||||
Esquema visual de líneas POT/AMD, en un proceso de creación de un fractal con orientación bajista.
|
||||
|
||||
Ejemplo X: AMD/POT alcista con volumen
|
||||
|
||||
Detalles clave desde la teoría IPDA:
|
||||
• El precio es entregado a través de algoritmos interbancarios diseñados para buscar liquidez y ejecutar órdenes institucionales.
|
||||
• El POT refleja la lógica detrás de este algoritmo: crear desequilibrio (imbalance), inducir a los trades a cometer un error (liquidez inducida), ejecutar el movimiento real (expansión), y luego estabilizar.
|
||||
• IPDA y POT van de la mano en el análisis de cómo se mueve el precio en función del "Smart Money" y nuestro método/lenguaje de estudio ICT.
|
||||
4.- Matrices IPDA
|
||||
Aqui es donde comienza a ponerse interesante, pues una vez que tengamos claro como leer una Matriz IPDA, es posible que la idea de que esto es un mercado aleatorio comienza a no tener tanto sentido y por el contrario comienza un entendimiento aún más profundo de la estructura.
|
||||
Una matriz está compuesta por distintos factores, los principales son:
|
||||
1.- Fractal en formación
|
||||
2.- Un punto máximo, un punto mínimo y un punto de equilibrio
|
||||
3.- Temporalidad
|
||||
4.- Zona Premium y zona Discount
|
||||
¿Como se ve una matriz en el grafico?
|
||||
Aquí un ejemplo visual de una matriz que ya ha completado su fractal y adicional a ello a culminado con el movimiento expansivo.
|
||||
Al ser una matriz alcista una vez completado el fractal e ingresado en zona de Discount es posible determinar que el siguiente movimiento será alcista
|
||||
|
||||
Mismo ejemplo usando POT/AMD, durante la creación de dicho fractal alcista.
|
||||
|
||||
4.1.- Uniendo las primeras piezas del juego
|
||||
Tomando en consideración las confluencias que conocemos hasta este momento, podríamos ingresar en un trade alcista (una compra; BUY), usando nuevamente el Ejemplo X:
|
||||
|
||||
1.- Ubicamos la zona de acumulación con los LL iguales creados al cierre del día anterior.
|
||||
2.- Una vez ha sido superado ese nivel, trazamos los LH y LL de dicho movimiento (manipulación).
|
||||
3.- El Switch market es nuestra referencia y nuestra ventaja estadística para la operativa, cuando lo observamos trazamos la matriz con una caja de GANN sin vertices o un fibonacci (indicadores disponibles en todas las terminales)
|
||||
|
||||
4.- Tomaremos el trade una vez haya creado un descuento o retroceso a la zona Discount IPDA, en la matriz creada por el ultimo LL y el switch market.
|
||||
5.- EL Stop Loss (SL) irá por debajo del último LL, creado en la manipulación y el Take Profit (TP) ira exactamente a 2 veces el SL, es decir un trade donde arriesgamos 1 y buscamos ganar 2.
|
||||
Trade 1 a 2
|
||||
|
||||
Módulo 5
|
||||
Killer zone y momentum
|
||||
1.- Killer zone:
|
||||
Son los horarios que competen a la apertura y cierre de las principales bolsas de valores del mundo.
|
||||
Bolsa de Valores Apertura Cierre Zona horaria
|
||||
Asia 17:00 20:59 (UTC -6)
|
||||
London 23:00 02:59 (UTC -6)
|
||||
New York 07:30 12:00 (UTC -6)
|
||||
La Killer zone es una de las confluencias más importantes para la toma de Trades de alta probabilidad, pues es el horario cuando las “manos más grandes están dentro del juego” buscando oportunidades.
|
||||
Durante la creación de la estructura, el precio tiene horarios específicos de terminaos por el IPDA, y son importantes ya que suelen ser puntos de atracción del precio y puntos de reacción del precio.
|
||||
Aquí los enlistamos e iremos añadiendo a un gráfico de timeframe de 1 minuto, para una mejor comprensión sobre su uso, y visualización.
|
||||
1.- New York Open (07:30 UTC -6).
|
||||
Una vela relevante pues a partir del OPEN de la primera vela del primer minuto en este horario, inicia también el mercado “real”, en el CME inician las cotizaciones.
|
||||
|
||||
2.- Inicio IPDA, Real inicio del mercado, NY (22:00 UTC -6).
|
||||
Una vela relevante, en el OPEN de dicha vela representa el reinicio de los algoritmos bancarios implícitos en el IPDA.
|
||||
|
||||
3.- High y Low de la Sesión Asiática (17:00 - 20:59 UTC-6) y Sesión de Londres (23:00 - 02:59 UTC -6)
|
||||
Representa la liquidez creada durante la sesión de trading 100% electrónico, y nos da el previo a la Kill zone de New york, mismas que tendrán distinta relevancia según el activo que estés operando, y destacamos New York, ya que es la principal sesión, con mayor liquidez (según el activo).
|
||||
|
||||
4.- Liquidez adicional: High y Low de Lunch NY (09:30 - 11:29 UTC -6)
|
||||
Un rango donde suelen haber continuaciones, regresos y expansiones, un rango realmente importante para el desarrollo del día y la sesión.
|
||||
|
||||
5.- Last Cumulation LAC (13:15 - 13:44 UTC -6)
|
||||
El precio se prepara para el cierre, y suele crear liquidez o tomarla.
|
||||
|
||||
6.- Movement complete MOC (13:50 - 13:59 UTC -6)
|
||||
Durante este breve “rango final”, suele tener las últimas extensiones o momento de volatilidad antes del cierre de bolsa
|
||||
|
||||
Módulo 6
|
||||
Order Blocks
|
||||
Son zonas del gráfico donde las manos grandes (bancos, fondos de inversión, etc.) han colocado órdenes importantes, ya sea de compra o de venta, tiene distintos timeframes y distintas interpretaciones.
|
||||
¿Qué caracteriza a un Order Block?
|
||||
Un Order Block típicamente es:
|
||||
• La última vela contraria antes de un movimiento fuerte del precio.
|
||||
o Por ejemplo, una vela bajista antes de una gran subida (order block de compra).
|
||||
o O una vela alcista antes de una gran caída (order block de venta).
|
||||
• Zona de consolidación previa al rompimiento.
|
||||
• Lugar donde probablemente se activaron muchas órdenes institucionales.
|
||||
¿Por qué son útiles?
|
||||
• Pueden funcionar como zonas de entrada precisas, con buen riesgo-beneficio.
|
||||
• Marcan niveles de interés institucional, donde podría haber reversión o continuación.
|
||||
• Ayudan a evitar entrar en zonas manipuladas.
|
||||
Tipos de Order Blocks
|
||||
• Order Block High Timeframe (OB)
|
||||
• Order Block Low Timeframe (OB)
|
||||
• Order Block en Quiebre (OBQ)
|
||||
• Killer Block (KOB)
|
||||
• Ghost Block (GOB)
|
||||
• Propulsion Block (POB)
|
||||
Todos los anteriores los tendrás disponibles en cada Timeframe, su interpretación depende de su tamaño, volumen, estructura, tiempo de creación, ETC.
|
||||
Hablemos de los Order Blocks de High time frame, los encontraremos en el grafico semanal (W), Diario (D), y 4 horas (4H).
|
||||
Estos bloques a pesar de ser enormes y para nada recomendables para buscar una entrada por el tamaño que tendría tu prospecto Stop Loss (en una estrategia Intraday), si te pueden ayudar a tener una primera orientación sobre la posible dirección del día, tomando en cuenta el activo que estemos analizando.
|
||||
Crucial Timeframe, o mejor dicho los timeframe que usaremos para considerar tomar una posición en el mercado (1H, 15m). en este caso los OB que encontremos, sí tendrán oportunidad de darnos acceso a un trade de probabilidad y de riesgo bien gestionado usando la estructura del precio a nuestro favor, y fungen excelente en todo tipo de estrategias.
|
||||
Ejemplo visual, estructura actual Nasdaq (01/06/25) podemos observar en este grafico de Timeframe Diario, que tenemos actualmente 2 Order blocks (OB) alcistas y 2 Order blocks En quiebre (OBQ) siendo 1 alcista y 1 bajista.
|
||||
|
||||
La estructura se va creando entorno a estos bloques, mismos que delimitan e impulsan el precio en una dirección según sea su interpretación
|
||||
Ejemplo visual mismo activo Nasdaq (01/06/25) en un time frame de 4H podemos observar la estructura creándose internamente con los OB ahora disponibles.
|
||||
|
||||
Cada time frame tendrá acceso a distintos OB dentro de los OB del time frame superior, dándote una mayor perspectiva de cual podría ser el siguiente movimiento del precio, y también dándote acceso a nuevos objetivos y zonas de posible entrada.
|
||||
Order Block en Quiebre (OBQ)
|
||||
El quiebre de estructura o de OB, hace referencia a momentos del mercado donde la tendencia actual es cambiada por el inicio de un proceso de giro o cambio de tendencia tras una toma de liquidez relevante (proceso de absorción; profundizaremos en el tema más adelante en los siguientes módulos), es decir se crea un Shift Market, y para tener una confluencia adicional que confirme el giro, este debe ser acompañado por un OBQ, para tener la primera referencia visual.
|
||||
Ejemplo visual, donde únicamente conservare los OBQ del anterior ejemplo con Nasdaq
|
||||
|
||||
Un OBQ es válido si una vela de su mismo time frame tiene un Close por debajo o por encima (respectivamente) del Open de la vela en cuestión OBQ, adicional al cierre como ya habíamos mencionado, mientras más alto sea el timeframe en el que estamos observando el OBQ en cuestión, será de mayor probabilidad para una posible entrada y mientras menor sea el timeframe habrá una mayor probabilidad de que el precio NO respete la zona.
|
||||
Ejemplo visual OBQ en timeframe de 1H, tras un proceso de absorción, donde el precio supera los anteriores máximos, para posteriormente hacer el quiebre del Bloque que inició el movimiento, Nasdaq (01/06/25)
|
||||
|
||||
Killer Block (KOB)
|
||||
Es un bloque especial, importante ya que suele ser muy protagonista en el movimiento actual y en un futuro suele ser testeado en distintas ocasiones, ya que posee liquidez prácticamente garantizada.
|
||||
Su característica principal, es que suele crear un HH o un LL, y marca el final de una tendencia.
|
||||
Ejemplo visual usando el anterior con Nasdaq time frame 1H, podemos observar en el máximo nuevo creado tras el proceso de absorción o toma de liquidez de máximos anteriores, se crea en la cima un KOB.
|
||||
|
||||
Ghost Block (GOB)
|
||||
Caracterizado por ser difícil de detectar, únicamente dejando rastro de mechas y su contundencia y relevancia para toma de decisiones es de grado menor, conocerlos como una confluencia más, pienso que puede ser positivo.
|
||||
Ejemplo visual usando el anterior ejemplo, donde se presentó un GOB en 1H, Nasdaq (01/06/25), y aunque no fue el principal factor del movimiento alcista, si formo parte del protagonismo del movimiento.
|
||||
|
||||
Propulsion Block (POB)
|
||||
Ejemplo visual de POB, timeframe 4H, el precio crea su estructura y presenta distintos tipos de bloques, pero el re-testeo del POB, crea la oportunidad de entrar en un fuerte movimiento bajista. Se caracteriza por ser una vela contraria al movimiento expansivo actual, y su re-testeo suele indicar trade de alta probabilidad.
|
||||
|
||||
Módulo 7
|
||||
Imbalances
|
||||
Fair Value Gap
|
||||
Se refiere a un desequilibrio o ineficiencia en el precio que ocurre cuando el mercado se mueve bruscamente en una dirección, dejando un vacío entre velas consecutivas sin que se hayan ejecutado transacciones en esa zona. Este hueco representa una zona donde no hubo oferta y demanda equilibradas.
|
||||
Este mismo será la base para comprender el resto de los tipos de imbalances disponibles en el chart (grafico), ya que su interacción, una vez que han sido creados, puede tener distintas interpretaciones, lo que se vuelve un tema profundo.
|
||||
Un FVG está representado por 3 velas, donde la vela 2 es la creadora del movimiento expansivo y las velas 1 y 3 con sus respectivas mechas, en confluencia con el cuerpo de la vela 2, forman el high y el low de nuestro FVG.
|
||||
Ejemplo visual, FVG bajista.
|
||||
|
||||
|
||||
Ejemplo visual, FVG alcista.
|
||||
|
||||
|
||||
El FVG es una buena confluencia para la creación de una estrategia, ya que suelen ser importantes zonas de reacción y atractores del precio. el IPDA está programado para balancear las órdenes. y crear momentum berish o bullish (bajista o alcista). donde no hubo oportunidad de tenerlo.
|
||||
El FVG está presente en todas las temporalidades, y su interpretación dependerá de:
|
||||
• La estructura actual del precio.
|
||||
• Tendencia macro (1h y 4h).
|
||||
iFVG (inversión Fair Value Gap)
|
||||
Es una transformación del FVG cotidiano, la característica principal de este imbalance es que el precio a cruzado delante del FVG inicial, no respetando la interpretación inicial y usándolo precisamente al revés.
|
||||
Es decir, un FVG alcista al convertirse en iFVG podríamos considerar que su interpretación ahora será bajista.
|
||||
Y lo mismo si el FVG inicial fuera bajista al transformarse en iFVG ahora su interpretación deberá ser alcista
|
||||
Ejemplo visual iFVG bajista
|
||||
|
||||
|
||||
Ejemplo visual iFVG alcista
|
||||
|
||||
|
||||
BPR (Balance Price Range)
|
||||
Al igual que el iFVG su interpretación deberá modificarse según el testeo de este imbalance, la diferencia con el iFVG únicamente se centra en que el BPR es “respetado” en esencia de imbalance, a pesar de ser cruzado por completo por enfrente por parte del precio
|
||||
Ejemplo visual BPR
|
||||
|
||||
|
||||
|
||||
|
||||
VFVG (Volume Fair Value Gap)
|
||||
Representa un momento en el gráfico donde hubo un cierre y una apertura de 2 velas consecutivas, con una diferencia de puntos que crea este imbalance, pero con la cualidad de que las mechas de dichas velas coinciden dentro del imbalance y el cierre y apertura de las velas creadoras, son el máximo y el mínimo respectivamente para este imbalance.
|
||||
Suelen ser muy esporádicos, y suelen aparecer en momentos de mucha volatilidad, representando así un atractor y zona de reacción para el precio.
|
||||
Ejemplo visual de VFVG
|
||||
|
||||
NWOG (New Weekly Open Gap)
|
||||
Su aparición tiene en esencia una importante participación durante el desarrollo de la vela semanal y de la estructura semanal. Suele ser un catalizador y también uno de los atractores del precio con mayor relevancia.
|
||||
Durante la apertura de la bolsa Asiática el día Domingo, podemos observar el NWOG, es muy importante tenerlo en cuenta si se requiere tener un BIAS semanal con claridad.
|
||||
Ejemplo visual de NWOG
|
||||
|
||||
NWOG creado de viernes a domingo.
|
||||
NDOG (New Daily Open Gap)
|
||||
Suele aparecer cuando la sesión americana termina con un movimiento expansivo o se tiene un proceso de alta volatilidad o se requiere tomar un punto de liquidez sin oportunidad de nuevos trades.
|
||||
Al reiniciar la sesión asiática, sin haber cambio de semana, el NDOG suele ser importante, pero con menor nivel de relevancia que el NWOG.
|
||||
Ejemplo visual de NDOG
|
||||
|
||||
NDOG creado de jueves para viernes durante el desarrollo semanal
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "STACK-TECNOLOGICO"
|
||||
title: "Stack Tecnologico - OrbiQuant IA"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Stack Tecnologico - OrbiQuant IA
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "VISION-PRODUCTO"
|
||||
title: "Vision del Producto - OrbiQuant IA"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Vision del Producto - OrbiQuant IA
|
||||
|
||||
**Version:** 1.0.0
|
||||
@ -115,6 +124,87 @@ Empoderar a personas de todos los niveles de experiencia para que puedan inverti
|
||||
|
||||
**Objetivo:** Aprender de la comunidad y validar estrategias.
|
||||
|
||||
### 5. Wallet Completo
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ SISTEMA DE WALLET │
|
||||
├─────────────────────────────────────────┤
|
||||
│ DEPOSITOS │
|
||||
│ ├── Tarjeta credito/debito (Stripe) │
|
||||
│ ├── SPEI (transferencia Mexico) │
|
||||
│ └── Crypto (BTC, ETH, USDT) │
|
||||
│ │
|
||||
│ RETIROS │
|
||||
│ ├── Cuenta bancaria (KYC requerido) │
|
||||
│ ├── Wallet crypto │
|
||||
│ └── Instantaneo a wallet interno │
|
||||
│ │
|
||||
│ TRANSFERENCIAS │
|
||||
│ ├── P2P entre usuarios (0% comision) │
|
||||
│ ├── Fondear Money Managers │
|
||||
│ └── Comprar en Marketplace │
|
||||
│ │
|
||||
│ RENDIMIENTOS │
|
||||
│ ├── Deposito automatico de ganancias │
|
||||
│ ├── Historial detallado │
|
||||
│ └── Reportes fiscales │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Objetivo:** Un wallet universal para todas las operaciones de la plataforma.
|
||||
|
||||
**Limites:**
|
||||
|
||||
| Operacion | Minimo | Maximo |
|
||||
|-----------|--------|--------|
|
||||
| Deposito tarjeta | $10 USD | $10,000 USD |
|
||||
| Deposito SPEI | $100 MXN | $500,000 MXN |
|
||||
| Deposito crypto | $10 equiv | $50,000/dia |
|
||||
| Retiro banco | $50 USD | $25,000 USD |
|
||||
| Retiro crypto | $50 equiv | $25,000/dia |
|
||||
| Transferencia P2P | $1 USD | $5,000 USD |
|
||||
|
||||
### 6. Marketplace de Productos
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ MARKETPLACE │
|
||||
├─────────────────────────────────────────┤
|
||||
│ SENALES ML PREMIUM │
|
||||
│ ├── Basic Pack: 50 senales ($9) │
|
||||
│ ├── Pro Pack: 200 senales ($29) │
|
||||
│ └── Unlimited: ilimitadas ($49/mes) │
|
||||
│ │
|
||||
│ ASESORIA FINANCIERA │
|
||||
│ ├── 30 minutos ($49) │
|
||||
│ ├── 60 minutos ($89) │
|
||||
│ └── 90 minutos ($119) │
|
||||
│ │
|
||||
│ VISUALIZACION PREMIUM │
|
||||
│ ├── Indicadores ML avanzados │
|
||||
│ ├── Predictor de Rango │
|
||||
│ ├── AMD Detector │
|
||||
│ └── Signal Overlay ($19/mes) │
|
||||
│ │
|
||||
│ CURSOS PREMIUM │
|
||||
│ ├── Trading avanzado ($29-199) │
|
||||
│ ├── Masterclasses │
|
||||
│ └── Certificaciones especiales │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Objetivo:** Monetizacion adicional con productos de alto valor.
|
||||
|
||||
**Modelo de Ingresos Marketplace:**
|
||||
|
||||
| Producto | Precio | Margen |
|
||||
|----------|--------|--------|
|
||||
| Senales Premium | $9-49/mes | 90% |
|
||||
| Asesoria | $49-119/sesion | 70% (30% asesor) |
|
||||
| Visualizacion | $19/mes | 95% |
|
||||
| Cursos | $29-199 | 85% |
|
||||
|
||||
---
|
||||
|
||||
## Modelo de Suscripcion
|
||||
@ -229,3 +319,6 @@ Empoderar a personas de todos los niveles de experiencia para que puedan inverti
|
||||
- [Arquitectura General](./ARQUITECTURA-GENERAL.md)
|
||||
- [Modelo de Negocio](./MODELO-NEGOCIO.md)
|
||||
- [Stack Tecnologico](./STACK-TECNOLOGICO.md)
|
||||
- [Modulo Payments](../02-definicion-modulos/OQI-005-payments-stripe/README.md)
|
||||
- [Modulo Marketplace](../02-definicion-modulos/OQI-009-marketplace/README.md)
|
||||
- [Analisis Wallet y Marketplace](../99-analisis/ANALISIS-SAAS-WALLET-MARKETPLACE.md)
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
---
|
||||
id: "MAP-00-vision-general"
|
||||
title: "Mapa de 00-vision-general"
|
||||
type: "Index"
|
||||
project: "trading-platform"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# _MAP: Vision General
|
||||
|
||||
**Ultima actualizacion:** 2025-12-05
|
||||
|
||||
252
docs/01-arquitectura/ARQUITECTURA-INTEGRACION-MT4-MCP-LLM.md
Normal file
252
docs/01-arquitectura/ARQUITECTURA-INTEGRACION-MT4-MCP-LLM.md
Normal file
@ -0,0 +1,252 @@
|
||||
---
|
||||
id: "ARQUITECTURA-INTEGRACION-MT4-MCP-LLM"
|
||||
title: "Arquitectura de Integracion MT4-MCP-LLM"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
created_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Arquitectura de Integracion MT4-MCP-LLM
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2026-01-04
|
||||
**Autor:** Tech-Leader Agent
|
||||
**Estado:** En Desarrollo
|
||||
|
||||
---
|
||||
|
||||
## Vision General
|
||||
|
||||
Este documento describe la arquitectura de integracion entre:
|
||||
- **MetaTrader4 (MT4)** - Plataforma de trading forex
|
||||
- **MCP Server** - Model Context Protocol para herramientas
|
||||
- **LLM Agent** - Copiloto de trading con IA
|
||||
- **ML Engine** - Modelos de prediccion de mercado
|
||||
|
||||
---
|
||||
|
||||
## Diagrama de Arquitectura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ORBIQUANT IA - TRADING PLATFORM ARCHITECTURE │
|
||||
├─────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ USER INTERFACES │ │
|
||||
│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
|
||||
│ │ │ Web App │ │ Chat Widget │ │ Telegram Bot │ │ CLI │ │ │
|
||||
│ │ │ (React UI) │ │ (LLM UI) │ │ (Future) │ │ (Future) │ │ │
|
||||
│ │ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │ │
|
||||
│ └──────────┼──────────────────┼──────────────────┼──────────────────┼─────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ └──────────────────┼──────────────────┴──────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ API GATEWAY (Backend Express) │ │
|
||||
│ │ :3000 │ │
|
||||
│ └────────────────────────────────────┬────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────────┼────────────────────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐ │
|
||||
│ │ LLM AGENT │ │ ML ENGINE │ │ TRADING AGENTS │ │
|
||||
│ │ :8003 │ │ :8001 │ │ :8004 │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ ┌────────┐ ┌────────┐ │ │
|
||||
│ │ │ Ollama LLM │ │ │ │ AMD Detector │ │ │ │ Atlas │ │ Orion │ │ │
|
||||
│ │ │ Llama 3 8B │ │ │ │ RangePredict │ │ │ │ (Cons) │ │ (Mod) │ │ │
|
||||
│ │ │ (GPU) │ │ │ │ TPSLClass │ │ │ └────────┘ └────────┘ │ │
|
||||
│ │ └──────────────┘ │ │ └──────────────┘ │ │ │ │
|
||||
│ │ │ │ │ │ ┌────────┐ │ │
|
||||
│ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ │ Nova │ │ │
|
||||
│ │ │Trading Tools │◄┼─────┼─│ Signal API │ │ │ │ (Aggr) │ │ │
|
||||
│ │ │ - MT4 Tools │ │ │ └──────────────┘ │ │ └────────┘ │ │
|
||||
│ │ │ - ML Tools │ │ │ │ │ │ │
|
||||
│ │ │ - Strategy │ │ │ │ │ │ │
|
||||
│ │ └──────────────┘ │ │ │ │ │ │
|
||||
│ └────────┬─────────┘ └──────────────────┘ └──────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ MCP SERVER MT4 CONNECTOR │ │
|
||||
│ │ :3605 │ │
|
||||
│ │ ┌────────────────────────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ MCP Tools: │ │ │
|
||||
│ │ │ - mt4_get_account - mt4_execute_trade - mt4_get_quote │ │ │
|
||||
│ │ │ - mt4_get_positions - mt4_close_position - mt4_modify_position │ │ │
|
||||
│ │ └────────────────────────────────────────────────────────────────────────┘ │ │
|
||||
│ └──────────────────────────────────────┬───────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ MT4 GATEWAY SERVICE │ │
|
||||
│ │ :8005 │ │
|
||||
│ │ ┌────────────────────────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ FastAPI Endpoints: │ │ │
|
||||
│ │ │ - GET /api/mt4/account - GET /api/mt4/positions │ │ │
|
||||
│ │ │ - POST /api/mt4/trade - DELETE /api/mt4/positions/{id} │ │ │
|
||||
│ │ │ - GET /api/mt4/tick/{symbol} - PUT /api/mt4/positions/{id} │ │ │
|
||||
│ │ └─────────────────────────────────────┬──────────────────────────────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌─────────────────────────────────────▼──────────────────────────────────┐ │ │
|
||||
│ │ │ MT4 Bridge Client (aiohttp) │ │ │
|
||||
│ │ │ - Comunicacion con EA Bridge │ │ │
|
||||
│ │ └────────────────────────────────────────────────────────────────────────┘ │ │
|
||||
│ └──────────────────────────────────────┬───────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ │ HTTP/WebSocket │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ METATRADER 4 TERMINALS │ │
|
||||
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
|
||||
│ │ │ EA Bridge #1 │ │ EA Bridge #2 │ │ EA Bridge #3 │ │ │
|
||||
│ │ │ (IC Markets) │ │ (Pepperstone) │ │ (XM) │ │ │
|
||||
│ │ │ :8081 │ │ :8082 │ │ :8083 │ │ │
|
||||
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
|
||||
│ └──────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ DATA LAYER │ │
|
||||
│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
|
||||
│ │ │ PostgreSQL │ │ Redis │ │ Binance API │ │ Market Data │ │ │
|
||||
│ │ │ :5432 │ │ :6379 │ │ (external) │ │ (external) │ │ │
|
||||
│ │ └───────────────┘ └───────────────┘ └───────────────┘ └───────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└───────────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flujo de Operaciones
|
||||
|
||||
### 1. Flujo de Chat con LLM
|
||||
|
||||
```
|
||||
Usuario (Chat)
|
||||
│
|
||||
▼
|
||||
LLM Agent (:8003)
|
||||
│
|
||||
├── Procesa mensaje
|
||||
├── Detecta intencion
|
||||
│
|
||||
▼
|
||||
¿Requiere Tool?
|
||||
│
|
||||
├─[SI]─► Ejecuta Tool
|
||||
│ │
|
||||
│ ├── MT4 Tool ──► MCP Server ──► MT4 Gateway ──► MT4
|
||||
│ ├── ML Tool ──► ML Engine
|
||||
│ └── Strategy Tool ──► Trading Agents
|
||||
│ │
|
||||
│ ▼
|
||||
│ Procesa resultado
|
||||
│
|
||||
└─[NO]─► Genera respuesta directa
|
||||
│
|
||||
▼
|
||||
Respuesta al Usuario
|
||||
```
|
||||
|
||||
### 2. Flujo de Ejecucion de Trade
|
||||
|
||||
```
|
||||
LLM Agent (Tool: execute_trade)
|
||||
│
|
||||
▼
|
||||
MCP Server MT4 (:3605)
|
||||
│ (Valida request, formatea)
|
||||
▼
|
||||
MT4 Gateway (:8005)
|
||||
│ (Prepara orden, risk check)
|
||||
▼
|
||||
MT4 Bridge Client
|
||||
│ (HTTP POST /trade)
|
||||
▼
|
||||
EA Bridge (MT4 Terminal)
|
||||
│ (Ejecuta orden)
|
||||
▼
|
||||
Broker Server
|
||||
│ (Confirma ejecucion)
|
||||
▼
|
||||
Response ──► MT4 Gateway ──► MCP Server ──► LLM Agent ──► Usuario
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Puertos de Servicios
|
||||
|
||||
| Servicio | Puerto | Descripcion |
|
||||
|----------|--------|-------------|
|
||||
| Frontend | 5173 | React UI (Vite) |
|
||||
| Backend API | 3000 | Express.js Gateway |
|
||||
| ML Engine | 8001 | FastAPI - Modelos ML |
|
||||
| Data Service | 8002 | FastAPI - Market Data |
|
||||
| LLM Agent | 8003 | FastAPI - Copiloto AI |
|
||||
| Trading Agents | 8004 | FastAPI - Atlas/Orion/Nova |
|
||||
| MT4 Gateway | 8005 | FastAPI - Bridge MT4 |
|
||||
| MCP Server MT4 | 3605 | MCP Tools MT4 |
|
||||
| PostgreSQL | 5432 | Base de datos |
|
||||
| Redis | 6379 | Cache y sesiones |
|
||||
| Ollama | 11434 | LLM Server (GPU) |
|
||||
| EA Bridge #1 | 8081 | MT4 Terminal 1 |
|
||||
| EA Bridge #2 | 8082 | MT4 Terminal 2 |
|
||||
| EA Bridge #3 | 8083 | MT4 Terminal 3 |
|
||||
|
||||
---
|
||||
|
||||
## Componentes Implementados
|
||||
|
||||
### Completados
|
||||
|
||||
| Componente | Ubicacion | Estado |
|
||||
|------------|-----------|--------|
|
||||
| MT4 Bridge Client | `apps/mt4-gateway/src/providers/mt4_bridge_client.py` | OK |
|
||||
| LLM Tools MT4 | `apps/llm-agent/src/tools/mt4_tools.py` | OK |
|
||||
| LLM Tools ML | `apps/llm-agent/src/tools/ml_tools.py` | OK |
|
||||
| ML Engine | `apps/ml-engine/` | OK |
|
||||
| Trading Agents | `apps/trading-agents/` | OK |
|
||||
|
||||
### En Desarrollo
|
||||
|
||||
| Componente | Ubicacion | Estado |
|
||||
|------------|-----------|--------|
|
||||
| MT4 Gateway API | `apps/mt4-gateway/src/api/` | En progreso |
|
||||
| MCP Server MT4 | `apps/mcp-mt4-connector/` | En progreso |
|
||||
| Fine-tuning Pipeline | `apps/llm-agent/fine-tuning/` | En progreso |
|
||||
| Strategy Analysis Tools | `apps/llm-agent/src/tools/strategy_analysis.py` | En progreso |
|
||||
|
||||
---
|
||||
|
||||
## Seguridad
|
||||
|
||||
### Autenticacion
|
||||
|
||||
- JWT tokens para API Gateway
|
||||
- API keys para servicios internos
|
||||
- Auth token para EA Bridge
|
||||
|
||||
### Validaciones
|
||||
|
||||
- Risk checks antes de ejecutar trades
|
||||
- Validacion de volumenes y simbolos
|
||||
- Rate limiting en endpoints criticos
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- `docs/01-arquitectura/INTEGRACION-METATRADER4.md`
|
||||
- `docs/01-arquitectura/INTEGRACION-LLM-LOCAL.md`
|
||||
- `orchestration/PROXIMA-ACCION.md`
|
||||
- `core/mcp-servers/README.md`
|
||||
|
||||
---
|
||||
|
||||
*Documento generado por Tech-Leader Agent*
|
||||
*OrbiQuant IA Trading Platform*
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "ARQUITECTURA-MULTI-AGENTE-MT4"
|
||||
title: "Arquitectura Multi-Agente MT4"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Arquitectura Multi-Agente MT4
|
||||
|
||||
**Fecha:** 2025-12-12
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "ARQUITECTURA-UNIFICADA"
|
||||
title: "Arquitectura Unificada - OrbiQuant IA Trading Platform"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Arquitectura Unificada - OrbiQuant IA Trading Platform
|
||||
|
||||
**Versión:** 2.0.0
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "DIAGRAMA-INTEGRACIONES"
|
||||
title: "Diagrama de Integraciones - OrbiQuant IA Trading Platform"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Diagrama de Integraciones - OrbiQuant IA Trading Platform
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "INTEGRACION-API-MASSIVE"
|
||||
title: "Integracion API Massive - Pipeline de Datos"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Integracion API Massive - Pipeline de Datos
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
1699
docs/01-arquitectura/INTEGRACION-LLM-FINE-TUNING.md
Normal file
1699
docs/01-arquitectura/INTEGRACION-LLM-FINE-TUNING.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "INTEGRACION-LLM-LOCAL"
|
||||
title: "Integracion LLM Local - chatgpt-oss 16GB"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Integracion LLM Local - chatgpt-oss 16GB
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
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
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "INTEGRACION-TRADINGAGENT"
|
||||
title: "Integracion TradingAgent - OrbiQuant IA"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Integración TradingAgent - OrbiQuant IA
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
1255
docs/01-arquitectura/MCP-BINANCE-CONNECTOR-SPEC.md
Normal file
1255
docs/01-arquitectura/MCP-BINANCE-CONNECTOR-SPEC.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "README"
|
||||
title: "Fundamentos y Autenticación"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# OQI-001: Fundamentos y Autenticación
|
||||
|
||||
## Resumen Ejecutivo
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
---
|
||||
id: "MAP-OQI-001-fundamentos-auth"
|
||||
title: "Mapa de OQI-001-fundamentos-auth"
|
||||
type: "Index"
|
||||
project: "trading-platform"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# _MAP: OQI-001 - Fundamentos y Autenticación
|
||||
|
||||
**Ultima actualizacion:** 2025-12-05
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-AUTH-001"
|
||||
title: "OAuth Providers Implementation"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-AUTH-001"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-AUTH-001: Especificación Técnica - OAuth Providers
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-AUTH-002"
|
||||
title: "JWT Tokens Implementation"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-AUTH-002"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-AUTH-002: Especificación Técnica - JWT Tokens
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-AUTH-003"
|
||||
title: "Database Schema for Auth"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-AUTH-002"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-AUTH-003: Especificación Técnica - Esquema de Base de Datos
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-AUTH-004"
|
||||
title: "API Endpoints for Auth"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-AUTH-002"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-AUTH-004: Especificación Técnica - API Endpoints
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-AUTH-005"
|
||||
title: "Security Implementation"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-AUTH-005"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-AUTH-005: Especificación Técnica - Seguridad
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-001"
|
||||
title: "Registro con Email"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 5
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-001: Registro con Email
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-002"
|
||||
title: "Login con Email"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-002: Login con Email
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-003"
|
||||
title: "Login con Google"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 5
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-003: Login con Google
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-004"
|
||||
title: "OAuth Facebook"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-004: OAuth Facebook
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-005"
|
||||
title: "OAuth Twitter/X"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-005: OAuth Twitter/X
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-006"
|
||||
title: "OAuth Apple Sign In"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-006: OAuth Apple Sign In
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-007"
|
||||
title: "OAuth GitHub"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Media"
|
||||
epic: "OQI-001"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-007: OAuth GitHub
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-008"
|
||||
title: "Autenticacion con SMS (Twilio)"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 5
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-008: Autenticación con SMS (Twilio)
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-009"
|
||||
title: "Autenticacion con WhatsApp"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Media"
|
||||
epic: "OQI-001"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-009: Autenticación con WhatsApp
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-010"
|
||||
title: "Configurar 2FA"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 5
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-010: Configurar 2FA
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-011"
|
||||
title: "Recuperacion de Contrasena"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-011: Recuperación de Contraseña
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-AUTH-012"
|
||||
title: "Gestion de Sesiones"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 5
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-AUTH-012: Gestión de Sesiones
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-AUTH-001"
|
||||
title: "OAuth Multi-proveedor"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "auth"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-AUTH-001: OAuth Multi-proveedor
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-AUTH-002"
|
||||
title: "Autenticacion por Email"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "auth"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-AUTH-002: Autenticación por Email
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-AUTH-003"
|
||||
title: "Autenticacion por Telefono"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "auth"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-AUTH-003: Autenticación por Teléfono
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-AUTH-004"
|
||||
title: "Two-Factor Authentication (2FA)"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "auth"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-AUTH-004: Two-Factor Authentication (2FA)
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-AUTH-005"
|
||||
title: "Gestion de Sesiones"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "auth"
|
||||
epic: "OQI-001"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-AUTH-005: Gestión de Sesiones
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "README"
|
||||
title: "Modulo Educativo"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# OQI-002: Modulo Educativo
|
||||
|
||||
**Estado:** ✅ Implementado
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
---
|
||||
id: "MAP-OQI-002-education"
|
||||
title: "Mapa de OQI-002-education"
|
||||
type: "Index"
|
||||
project: "trading-platform"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# _MAP: OQI-002 - Módulo Educativo
|
||||
|
||||
**Última actualización:** 2025-12-05
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-EDU-001"
|
||||
title: "Schema Education Database"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-EDU-001"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-EDU-001: Modelo de Datos - Schema Education
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-EDU-002"
|
||||
title: "API REST Education Module"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-EDU-002"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-EDU-002: API REST - Endpoints del Módulo Educativo
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-EDU-003"
|
||||
title: "Frontend Components Education"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-EDU-002"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-EDU-003: Componentes Frontend - React + TypeScript
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-EDU-004"
|
||||
title: "Video Streaming System"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-EDU-002"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-EDU-004: Sistema de Streaming de Video
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-EDU-005"
|
||||
title: "Quiz and Evaluation Engine"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-EDU-004"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-EDU-005: Motor de Evaluaciones y Quizzes
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-EDU-006"
|
||||
title: "Gamification System"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-EDU-006"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-EDU-006: Sistema de Gamificación
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "README"
|
||||
title: "Especificaciones Técnicas - OQI-002 Módulo Educativo"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# Especificaciones Técnicas - OQI-002 Módulo Educativo
|
||||
|
||||
Este directorio contiene las especificaciones técnicas detalladas para el módulo educativo de OrbiQuant IA.
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-EDU-001"
|
||||
title: "Ver Catalogo de Cursos"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-002"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-EDU-001: Ver Catálogo de Cursos
|
||||
|
||||
## Metadata
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-EDU-002"
|
||||
title: "Ver Detalle de Curso"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-002"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-EDU-002: Ver Detalle de Curso
|
||||
|
||||
## Metadata
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-EDU-003"
|
||||
title: "Iniciar Leccion"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-002"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-EDU-003: Iniciar Lección
|
||||
|
||||
## Metadata
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-EDU-004"
|
||||
title: "Ver Video de Leccion"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-002"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-EDU-004: Ver Video de Lección
|
||||
|
||||
## Metadata
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-EDU-005"
|
||||
title: "Completar Leccion"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-002"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-EDU-005: Completar Lección
|
||||
|
||||
## Metadata
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-EDU-006"
|
||||
title: "Realizar Quiz"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-002"
|
||||
story_points: 5
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-EDU-006: Realizar Quiz
|
||||
|
||||
## Metadata
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-EDU-007"
|
||||
title: "Ver Progreso Educativo"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-002"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-EDU-007: Ver Progreso Educativo
|
||||
|
||||
## Metadata
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "US-EDU-008"
|
||||
title: "Obtener Certificado"
|
||||
type: "User Story"
|
||||
status: "Done"
|
||||
priority: "Media"
|
||||
epic: "OQI-002"
|
||||
story_points: 3
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# US-EDU-008: Obtener Certificado
|
||||
|
||||
## Metadata
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# TRACEABILITY.yml - OQI-002 Módulo Educativo
|
||||
# Mapeo de requerimientos a implementación
|
||||
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
epic: OQI-002
|
||||
name: "Módulo Educativo - Cursos de Trading"
|
||||
updated: "2025-12-05"
|
||||
status: pending
|
||||
updated: "2026-01-04"
|
||||
status: in_progress
|
||||
|
||||
# Resumen de trazabilidad
|
||||
summary:
|
||||
@ -71,7 +71,14 @@ requirements:
|
||||
|
||||
RF-EDU-002:
|
||||
name: "Sistema de Lecciones"
|
||||
status: pending
|
||||
status: implemented
|
||||
implementation_notes:
|
||||
- date: "2026-01-04"
|
||||
changes:
|
||||
- "Creado Lesson.tsx para visualización de lecciones con video player"
|
||||
- "Soporte para contenido: video, article, text, quiz, exercise"
|
||||
- "Navegación entre lecciones con sidebar de progreso"
|
||||
- "Integrado en App.tsx con rutas /education/courses/:courseSlug/lesson/:lessonId"
|
||||
specs:
|
||||
- ET-EDU-001
|
||||
- ET-EDU-004
|
||||
@ -164,7 +171,15 @@ requirements:
|
||||
|
||||
RF-EDU-004:
|
||||
name: "Sistema de Quizzes"
|
||||
status: pending
|
||||
status: implemented
|
||||
implementation_notes:
|
||||
- date: "2026-01-04"
|
||||
changes:
|
||||
- "Creado Quiz.tsx con estados: intro, in_progress, submitted"
|
||||
- "Timer de cuenta regresiva para quizzes con límite de tiempo"
|
||||
- "Soporte para tipos: multiple_choice, true_false, multi_select"
|
||||
- "Sistema de puntos y XP integrado"
|
||||
- "Ruta: /education/courses/:courseSlug/lesson/:lessonId/quiz"
|
||||
specs:
|
||||
- ET-EDU-005
|
||||
user_stories:
|
||||
@ -462,3 +477,67 @@ notes:
|
||||
- "Certificados con QR de verificación único"
|
||||
- "Quizzes con intentos ilimitados pero score máximo registrado"
|
||||
- "Sistema de puntos: 10 por lección, 50 por quiz aprobado, 100 por certificado"
|
||||
|
||||
# Implementación 2026-01-04
|
||||
recent_changes:
|
||||
- date: "2026-01-04"
|
||||
developer: "Claude Code"
|
||||
session: 1
|
||||
changes:
|
||||
- type: frontend
|
||||
files:
|
||||
- apps/frontend/src/modules/education/pages/Lesson.tsx
|
||||
- apps/frontend/src/modules/education/pages/Quiz.tsx
|
||||
- apps/frontend/src/App.tsx (rutas añadidas)
|
||||
- apps/frontend/src/types/education.types.ts (props añadidas)
|
||||
description: "Páginas de lección y quiz implementadas"
|
||||
- type: database
|
||||
files:
|
||||
- apps/database/seeds/prod/education/01-education-courses.sql
|
||||
description: "Seeds de cursos ICT/IPDA con 1 curso, 7 módulos, 28 lecciones, 5 quizzes"
|
||||
issues_found:
|
||||
- "DDL loader no carga 00-extensions.sql ni 01-enums.sql correctamente"
|
||||
- "Script usa DB_NAME=orbiquant pero Docker usa orbiquant_trading/orbiquant_platform"
|
||||
|
||||
- date: "2026-01-04"
|
||||
developer: "Claude Code"
|
||||
session: 2
|
||||
changes:
|
||||
- type: database_scripts
|
||||
files:
|
||||
- apps/database/scripts/create-database.sh
|
||||
description: "Correcciones críticas al DDL loader"
|
||||
fixes:
|
||||
- "Agregada carga de 00-extensions.sql antes de enums"
|
||||
- "Búsqueda de enums en 00-enums.sql y 01-enums.sql"
|
||||
- "Defaults unificados: DB_NAME=orbiquantia_platform, DB_PORT=5433"
|
||||
- "Credenciales: orbiquantia/orbiquantia_dev_2025"
|
||||
- type: database_ddl
|
||||
files:
|
||||
- apps/database/ddl/schemas/auth/tables/01-users.sql
|
||||
- apps/database/ddl/schemas/auth/tables/99-deferred-constraints.sql (nuevo)
|
||||
description: "Resolución de dependencia circular en auth.users"
|
||||
fixes:
|
||||
- "Removido constraint password_or_oauth (PostgreSQL no soporta subqueries en CHECK)"
|
||||
- "Documentado en 99-deferred-constraints.sql con alternativa de trigger"
|
||||
- type: database_seeds
|
||||
files:
|
||||
- apps/database/seeds/prod/education/01-education-courses.sql
|
||||
description: "Correcciones de columnas en seeds de educación"
|
||||
fixes:
|
||||
- "Lecciones: removido duration_minutes (28 filas corregidas)"
|
||||
- "Quizzes: passing_score → passing_score_percentage"
|
||||
- "Questions: single_choice → multiple_choice"
|
||||
- "Questions: correct_answers removido (respuesta en options.isCorrect)"
|
||||
- "Questions: formato options actualizado a [{id,text,isCorrect}]"
|
||||
validation:
|
||||
status: passed
|
||||
database_recreated: true
|
||||
counts:
|
||||
categories: 5
|
||||
courses: 1
|
||||
modules: 7
|
||||
lessons: 28
|
||||
quizzes: 5
|
||||
questions: 14
|
||||
command: "DB_PORT=5433 ./drop-and-recreate-database.sh"
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-EDU-001"
|
||||
title: "Catalogo de Cursos"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "education"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-EDU-001: Catálogo de Cursos
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-EDU-002"
|
||||
title: "Sistema de Lecciones"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "education"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-EDU-002: Sistema de Lecciones
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-EDU-003"
|
||||
title: "Tracking de Progreso"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "education"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-EDU-003: Tracking de Progreso
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-EDU-004"
|
||||
title: "Sistema de Quizzes"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
module: "education"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-EDU-004: Sistema de Quizzes
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-EDU-005"
|
||||
title: "Sistema de Certificados"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Media"
|
||||
module: "education"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-EDU-005: Sistema de Certificados
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "RF-EDU-006"
|
||||
title: "Sistema de Gamificacion"
|
||||
type: "Requirement"
|
||||
status: "Done"
|
||||
priority: "Media"
|
||||
module: "education"
|
||||
epic: "OQI-002"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# RF-EDU-006: Sistema de Gamificación
|
||||
|
||||
**Versión:** 1.0.0
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
---
|
||||
id: "README"
|
||||
title: "Trading y Charts"
|
||||
type: "Documentation"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# OQI-003: Trading y Charts
|
||||
|
||||
## Resumen Ejecutivo
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
---
|
||||
id: "MAP-OQI-003-trading-charts"
|
||||
title: "Mapa de OQI-003-trading-charts"
|
||||
type: "Index"
|
||||
project: "trading-platform"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# _MAP: OQI-003 - Trading y Charts
|
||||
|
||||
**Última actualización:** 2025-12-05
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
---
|
||||
id: "ET-TRD-001"
|
||||
title: "Market Data Integration"
|
||||
type: "Specification"
|
||||
status: "Done"
|
||||
rf_parent: "RF-TRD-001"
|
||||
epic: "OQI-003"
|
||||
version: "1.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-TRD-001: Especificación Técnica - Market Data Integration
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
---
|
||||
id: "ET-TRD-002"
|
||||
title: "Especificación Técnica - WebSocket Connections"
|
||||
type: "Technical Specification"
|
||||
status: "Done"
|
||||
priority: "Alta"
|
||||
epic: "OQI-003"
|
||||
project: "trading-platform"
|
||||
version: "1.0.0"
|
||||
created_date: "2025-12-05"
|
||||
updated_date: "2026-01-04"
|
||||
---
|
||||
|
||||
# ET-TRD-002: Especificación Técnica - WebSocket Connections
|
||||
|
||||
**Version:** 1.0.0
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user