Dashboard

Estado en tiempo real del sistema, posiciones y actividad
Precios de Mercado
BTC—
ETH—
SOL—
BNB—
Posiciones Hoy

—

Posiciones Total

—

TradingView Signals
0
Waiting

No signals yet

Posiciones Abiertas

—
PositionID Date / Time Symbol Side Entry Price Current Price Qty Unrealized PnL Stop Loss Take Profit Origen Action
Sin posiciones abiertas

Actividad

Cargando...

Última actualización: —

Bots

Gestiona y monitoriza todos tus bots de trading

Control de Bots

Actualización 5s

Conectando...

Bot Strategy Market Status Performance (7d) Total P&L Trades Win Rate Last Activity Actions
Cargando...

Backtest

Resultados y análisis histórico por estrategia

Estrategias

1 base + 2 de investigación con resultados
Próximas estrategias Los nuevos backtests aparecerán como bloques separados

EMA Crossover (VWAP+RSI+ADX)

Backtest agrupado por estrategia. Los paneles siguientes pertenecen únicamente a EMA Crossover (VWAP+RSI+ADX).

Actual Ver analisis completo ->

Mejores Parámetros por Activo

—

Cargando...

Historial de Iteraciones

Cargando...

Conclusiones Clave

    Configuración del Backtest

    Curva de Capital — Retorno (%) por Activo

    Trend Pullback (RSI+ATR)

    EMA 50/200 + RSI + salidas por ATR. Réplica fiel de pine/ema_rsi_atr_frequent.v1.pine, backtest sobre datos locales (Binance 2024–2026), comparado en 3 timeframes.

    No rentable (15m–4h) Ver analisis completo ->

    Resultados por Timeframe y Activo

    SL 1.5×ATR · TP 2.2×ATR · Fee 0.05%/orden · 1%/trade · 10.000 USDT/activo · 15m–1d
    TFActivoTradesWin RatePFMax DDPnL (USDT)
    15mBTC112138.5%0.751.5%−148.63
    15mETH105440.9%0.871.2%−110.85
    15mSOL115540.6%0.891.4%−133.78
    15mBNB114639.6%0.811.4%−135.91
    15mTOTAL4476———−529.17
    1hBTC28143.8%0.950.3%−23.16
    1hETH28340.3%0.990.4%−16.71
    1hSOL29939.1%0.861.0%−66.92
    1hBNB30238.7%0.850.7%−51.77
    1hTOTAL1165———−158.56
    4hBTC8438.1%0.850.4%−21.39
    4hETH8737.9%0.950.5%−12.30
    4hSOL8338.6%0.810.7%−46.49
    4hBNB6445.3%1.040.4%+0.44
    4hTOTAL318———−79.74
    1dBTC1330.8%0.480.4%−31.25
    1dETH1225.0%0.430.5%−41.45
    1dSOL1241.7%0.830.7%−13.39
    1dBNB955.6%1.990.2%+25.03
    1dTOTAL46———−61.06

    Curva de Capital — Retorno (%) por Activo

    Supertrend ATR

    Backtest sobre datos locales (Binance 2024–2026), motor event-driven, comparado en 3 timeframes.

    No rentable (15m–4h) Ver analisis completo ->

    Resultados por Timeframe y Activo

    SL 1.2% · TP 1.5% · Fee 0.05%/orden · 1%/trade · 10.000 USDT/activo
    TFActivoTradesWin RatePFMax DDPnL (USDT)
    15mBTC108742.7%0.861.7%−159.62
    15mETH141246.0%0.991.3%−80.36
    15mSOL170745.5%0.971.4%−120.62
    15mBNB124244.8%0.941.1%−110.20
    15mTOTAL5448———−470.79
    1hBTC46643.1%0.880.8%−62.93
    1hETH50345.3%0.960.5%−38.27
    1hSOL51843.6%0.900.7%−63.05
    1hBNB49346.5%1.010.5%−22.64
    1hTOTAL1980———−186.90
    4hBTC12550.4%1.180.1%+7.60
    4hETH12544.8%0.940.2%−11.30
    4hSOL12336.6%0.670.4%−38.34
    4hBNB12140.5%0.790.3%−24.98
    4hTOTAL494———−67.02

    Curva de Capital — Retorno (%) por Activo

    RSI Momentum

    Backtest sobre datos locales (Binance 2024–2026), motor event-driven, comparado en 3 timeframes.

    No rentable (15m–4h) Ver analisis completo ->

    Resultados por Timeframe y Activo

    SL 1.2% · TP 1.5% · Fee 0.05%/orden · 1%/trade · 10.000 USDT/activo
    TFActivoTradesWin RatePFMax DDPnL (USDT)
    15mBTC138645.2%0.961.3%−111.15
    15mETH206745.9%0.981.6%−124.19
    15mSOL278645.9%0.991.9%−164.63
    15mBNB169444.1%0.912.0%−182.94
    15mTOTAL7933———−582.92
    1hBTC73747.1%1.030.4%−21.30
    1hETH97343.2%0.881.5%−130.14
    1hSOL119845.7%0.971.0%−80.28
    1hBNB85346.4%1.000.7%−39.70
    1hTOTAL3761———−271.42
    4hBTC29041.0%0.810.6%−55.58
    4hETH33446.1%0.990.4%−18.40
    4hSOL37242.2%0.850.6%−59.55
    4hBNB32348.3%1.080.4%+1.26
    4hTOTAL1319———−132.27

    Curva de Capital — Retorno (%) por Activo

    Donchian Breakout

    Canal de ruptura de 20 períodos. Backtest sobre datos locales (Binance 2024–2026), motor event-driven, comparado en 3 timeframes.

    No rentable (15m–4h–1d) Ver analisis completo ->

    Resultados por Timeframe y Activo

    SL 1.2% · TP 1.5% · Fee 0.05%/orden · 1%/trade · 10.000 USDT/activo
    TFActivoTradesWin RatePFMax DDPnL (USDT)
    15mBTC181443.2%0.882.5%−242.38
    15mETH273445.8%0.982.0%−172.47
    15mSOL355743.5%0.894.5%−435.02
    15mBNB216645.3%0.961.8%−165.91
    15mTOTAL10271———−1015.78
    4hBTC34248.0%1.070.3%−1.83
    4hETH35342.8%0.870.5%−51.12
    4hSOL38235.9%0.651.3%−125.96
    4hBNB32745.0%0.950.5%−28.19
    4hTOTAL1404———−207.10
    1dBTC6841.2%0.810.2%−12.80
    1dETH5927.1%0.430.3%−33.50
    1dSOL6423.4%0.350.4%−42.62
    1dBNB5530.9%0.520.3%−25.56
    1dTOTAL246———−114.48

    Curva de Capital — Retorno (%) por Activo

    Bollinger Bands Mean Reversion

    Reversión a la media con bandas de Bollinger 20/2 y salidas por ATR. Primera estrategia de reversión del catálogo. Backtest sobre datos locales (Binance 2024–2026), 3 timeframes.

    No rentable (15m–4h–1d) Ver analisis completo ->

    Resultados por Timeframe y Activo

    SL 1.5×ATR · TP 2.0×ATR · Fee 0.05%/orden · 1%/trade · 10.000 USDT/activo
    TFActivoTradesWin RatePFMax DDPnL (USDT)
    15mBTC562642.6%0.835.7%−566.13
    15mETH566741.9%0.856.4%−637.76
    15mSOL547942.7%0.925.2%−515.52
    15mBNB557342.8%0.885.1%−505.71
    15mTOTAL22345———−2225.13
    4hBTC37941.2%0.910.7%−62.47
    4hETH36840.8%0.990.7%−27.30
    4hSOL33740.4%0.891.8%−106.32
    4hBNB37742.2%0.980.9%−30.64
    4hTOTAL1461———−226.74
    1dBTC5843.1%1.030.4%+3.15
    1dETH6136.1%0.801.2%−64.06
    1dSOL5839.7%0.931.0%−26.93
    1dBNB5839.7%1.040.6%+5.89
    1dTOTAL235———−81.94

    Curva de Capital — Retorno (%) por Activo

    Estrategia

    Catalogo publico como fuente unica de documentacion

    Estrategias

    Cargando catalogo...

    Cargando catalogo publico de estrategias...

    Documentacion

    La documentacion larga de cada estrategia vive en las paginas publicas del catalogo. Esta consola mantiene solo el acceso rapido para evitar duplicar contenido.

    Arquitectura

    Diseño técnico, componentes y flujo del sistema · Multi-tenant

    Flujo General

    De la señal a la orden y la notificación
    📊
    TradingView
    Detecta señal (EMA+VWAP+RSI+ADX) y dispara alerta al webhook del usuario
    →
    🔐
    POST /webhook/{token}
    El token en la URL identifica y autentica al usuario; resuelve su engine via O(1) lookup en el registry
    →
    ⚙️
    UserEngine (por usuario)
    Verifica bot activo, circuit breaker y calcula SL/TP/qty con el capital y config propios del usuario
    →
    🏦
    Bitunix API
    Ejecuta orden MARKET con las credenciales descifradas del usuario; obtiene positionId
    →
    💾
    Persistencia por usuario
    data/{user_id}/positions.json + trades.csv · cuentas y config en users.db
    →
    ✈️
    Telegram (próximo)
    Notificación de apertura/cierre y alertas de fallo

    Componentes del Sistema

    🚀
    main.py
    Punto de entrada

    Orquesta el sistema multi-tenant. Los módulo-globals de la versión anterior fueron reemplazados por el UserEngineRegistry. Endpoints principales: POST /webhook/{token} (routing per-user), GET /health, POST /api/keys (credenciales), GET /api/me, POST /api/me/email, /api/config, /api/positions/history. El legacy POST /webhook devuelve HTTP 410 Gone — los operadores deben apuntar sus alertas de TradingView a /webhook/{token}. Implementa _place_order_with_retry (2 reintentos con backoff, salvo errores permanentes 10002/10003/10007). Todos los endpoints de datos están protegidos por la dependencia get_current_user.

    ⚙️
    config.py
    Configuración de servidor

    Carga con pydantic-settings las variables de entorno de nivel servidor (ya no contiene API keys ni config de trading per-user — esas viven en users.db). Variables actuales: SECRET_KEY (sesiones), MASTER_KEY (Fernet), ADMIN_USERNAME/ADMIN_PASSWORD (seed), HTTPS_ONLY, bitunix_base_url y defaults de riesgo globales. Convención de proyecto: todo el código lee config via get_settings(), nunca os.environ directamente. Fail-closed: arranca con error si SECRET_KEY o MASTER_KEY están ausentes.

    🔗
    exchange/bitunix_client.py
    Cliente HTTP

    Cliente async para la API REST de Bitunix Futures. Recibe api_key, api_secret, base_url y dry_run por parámetro via engine.exchange_client() — nunca los lee de un singleton global (refactor "keystone" multi-tenant). Firma con SHA256 de doble pasada (digest = SHA256(nonce+ts+key+qs+body), sign = SHA256(digest+secret)). Gotcha crítico: en modo HEDGE, cualquier query string en GET /position causa error 10007; get_all_positions() siempre se llama sin parámetros y se filtra en Python. Cachea el positionId tras cada apertura.

    📡
    main.py · _price_feed_loop
    Feed de precios (compartido)

    Polling cada 10s al endpoint público de tickers de Bitunix (GET /api/v1/futures/market/tickers, sin auth) para los símbolos configurados (BTC, ETH, SOL, BNB). Es el único global que sobrevive al refactor multi-tenant — los precios son públicos, no per-user. Usa el markPrice como referencia de SL/TP y gate de slippage: sin precio fresco (más viejo que price_max_age_sec), el bot NO opera.

    📋
    webhook/signals.py
    Modelo de señales

    Define WebhookPayload — el modelo Pydantic que valida el JSON de TradingView. Campos: symbol, side, action, price, token (ahora opcional e ignorado — el token de la URL es autoritativo). Propiedades derivadas: is_open, exchange_side, position_side, price_float.

    🛡️
    risk/manager.py
    Gestión de riesgo

    Calcula el tamaño de posición según riesgo por operación (% del capital del usuario) y distancia al SL. Cada UserEngine tiene su propia instancia de RiskManager inicializada con el capital y porcentajes del usuario. Circuit breaker diario: si las pérdidas acumuladas superan el límite per-user, bloquea nuevas operaciones hasta el día siguiente. El PnL diario se restaura desde data/{user_id}/trades.csv al arrancar.

    📌
    state/position_tracker.py
    Seguimiento de posiciones

    Mantiene en memoria las posiciones abiertas por símbolo. Cada UserEngine tiene su propio PositionTracker con estado en data/{user_id}/positions.json. Cada Position incluye exchange_position_id (requerido por Bitunix HEDGE para cerrar) e initiator (bot / manual / exchange). reconcile_from_exchange() sincroniza con el exchange al arrancar.

    📝
    logs/trade_logger.py
    Logger de trades

    Escribe cada apertura y cierre en data/{user_id}/trades.csv. Campos: timestamp, symbol, side, action, entry_price, exit_price, quantity, pnl, commission, sl_price, tp_price, initiator, mode (live/dry), comment. Incluye migración automática del CSV cuando se añaden campos nuevos. Además expone setup_logging(), que configura el logging del sistema con dos handlers: consola (stdout) en formato JSON por línea (vía JsonFormatter) y archivo logs/bot.log en texto pipe-delimited. El JSON existe solo en la consola; el archivo nunca es JSON.

    🖥️
    web-private/
    Dashboard frontend

    Frontend estático (HTML + CSS + JS vanilla) servido en /bot-manager-console/. Ahora requiere login — un apiFetch wrapper redirige a /auth/login ante cualquier 401. Menú de cuenta en la esquina superior derecha con panel de ajustes (username, email, webhook URL, claves Bitunix). Polling a /health cada 5s para precios, posiciones y fallos recientes.

    🔑
    auth/
    Autenticación y sesiones

    Cuatro módulos: routes.py — endpoints de login/logout (GET /auth/login, POST /auth/login, POST /auth/logout); security.py — hashing con hashlib.scrypt (stdlib, sin dependencias extra; sal aleatoria de 16 bytes, parámetros OWASP mínimos, comparación con hmac.compare_digest); session.py — configura SessionMiddleware de Starlette con SECRET_KEY y la flag https_only; deps.py — dependencia FastAPI get_current_user que gatea todos los endpoints de datos; un request sin sesión válida → 401. No hay registro abierto: el primer usuario admin se siembra desde .env en el primer arranque.

    🗄️
    db/
    Persistencia de cuentas

    Cuatro módulos sobre SQLite: schema.py — DDL de la tabla users (id, username, password_hash+salt, webhook_token, enc_api_key/secret, email, capital/risk/sl/tp/leverage/slippage/dry_run, is_admin) + migración idempotente de columnas nuevas (e.g. email via PRAGMA table_info); users.py — queries CRUD; seed.py — siembra idempotente del admin desde .env y migra la cuenta original (user_id=1) copiando la posición viva si existía; connection.py — path canónico de users.db y helper get_conn().

    🔒
    crypto/fernet_box.py
    Cifrado de credenciales

    Cifrado simétrico Fernet (biblioteca cryptography) de las API keys de cada usuario. MASTER_KEY se lee via get_settings() — nunca os.environ directo. Fail-closed: lanza RuntimeError si la clave está ausente o vacía. decrypt() devuelve None ante cualquier InvalidToken — nunca propaga internals. Las credenciales descifradas solo existen en memoria durante la vida del UserEngine; ningún endpoint las devuelve (write-only). POST /api/keys hace una verificación de conectividad en vivo con Bitunix antes de habilitar trading real.

    🏗️
    engine/
    Motor multi-tenant

    Dos módulos: user_engine.py — UserEngine agrupa el RiskManager, PositionTracker, estado de bots, credenciales descifradas, config per-user, locks por símbolo y paths de datos del usuario; expone engine.exchange_client() que construye un BitunixClient con las creds del usuario. registry.py — UserEngineRegistry mantiene el índice token → engine (O(1) en el hot-path del webhook); carga engines al arrancar con stagger de 1s entre usuarios para no saturar Bitunix; recarga atómica bajo lock (swap sin corrupción de requests en vuelo); fail-closed: NULL o creds no descifrarbles → sin engine.

    ✈️
    notifications/telegram.py (próximo)
    Notificaciones

    Bot de Telegram para notificaciones en tiempo real: mensaje al abrir/cerrar una posición (símbolo, lado, precio, qty, PnL) y alerta cuando un webhook llega pero la orden falla. Se integrará como llamada asíncrona al final de _execute_open y _execute_close, sin bloquear el flujo principal.

    Ciclo de Vida de una Señal

    De punta a punta
    1. 1
      Detección en TradingView

      El indicador Pine Script evalúa en cada vela de 15m: cruce EMA9/EMA21, precio respecto a VWAP y EMA200, RSI en zona neutral (40-60), ADX > umbral y volumen sobre la media. Si todas las condiciones se cumplen, dispara la alerta con acción de webhook.

    2. 2
      Recepción del webhook

      TradingView envía POST /webhook/{token} con JSON: {"symbol":"BTCUSDT","side":"buy","action":"open","price":"60000"}. El campo token en el body es opcional e ignorado — el token en la URL es el autoritativo. La URL debe ser pública; el servidor expone el endpoint en internet.

    3. 3
      Resolución del usuario y autenticación

      FastAPI busca el token en el UserEngineRegistry. Token completamente desconocido → 401. Token válido pero sin engine activo (sin claves configuradas) → 403. Ambas respuestas tienen la misma forma para no filtrar si el token existe. A continuación Pydantic valida el schema del payload; si falla → 422.

    4. 4
      Guardia del bot (per-user)

      Se comprueba engine.bot_active[symbol] en el UserEngine del usuario. Si el bot está pausado para ese usuario, la señal se descarta con rejected: bot is paused. No se toca el exchange.

    5. 5
      Gestión de riesgo (per-user)

      El RiskManager del usuario comprueba su circuit breaker diario. Si el trading está permitido, calcula SL (entry × (1 ± sl_pct%)), TP (entry × (1 ± tp_pct%)) y el tamaño de posición con el capital del usuario: qty = (capital × risk_pct) / (entry − SL).

    6. 6
      Ejecución con retry

      Se envía la orden MARKET a Bitunix via _place_order_with_retry usando las credenciales descifradas del usuario (engine.exchange_client()). En caso de fallo transitorio, reintenta hasta 2 veces con backoff lineal (2s, luego 4s). Los errores permanentes (10002 parámetro, 10003 saldo, 10007 firma) no se reintentan. Un fallo definitivo se registra en engine.recent_failures y aparece en el banner rojo del dashboard.

    7. 7
      Captura del positionId

      Tras confirmar la apertura, el sistema espera 0.8s y llama a get_all_positions() (sin query string — evita error 10007 de Bitunix HEDGE). El positionId se cachea en el objeto Position y se persiste en data/{user_id}/positions.json. Los cierres posteriores lo usan directamente sin ningún GET adicional.

    8. 8
      Registro y persistencia

      La posición se guarda en el PositionTracker del usuario (memoria + data/{user_id}/positions.json). El trade se loggea en data/{user_id}/trades.csv con initiator="bot", mode="live"|"dry" y la comisión estimada al 0.05% (round-trip). El dashboard refleja el cambio en el siguiente ciclo de polling (máx. 5s).

    9. 9
      Notificación Telegram (próximo)

      Llamada asíncrona al bot de Telegram con los detalles de la operación (símbolo, lado, precio, qty). En el caso de cierre, incluye el PnL realizado y la comisión. No bloquea el flujo principal — se lanza como tarea en background.

    Estado y Persistencia

    🗃️
    users.db (SQLite)
    Base de datos de cuentas y configuración. Tabla users: id, username, password_hash+salt (scrypt), webhook_token, enc_api_key/enc_api_secret (Fernet), email (nullable), capital_usdt, risk/sl/tp/leverage/slippage/dry_run per-user, is_admin. Schema idempotente con migración automática de columnas nuevas al arrancar. La config de trading ya no se escribe en .env en runtime.
    🔑
    users.db → tabla exchanges
    Una fila por exchange por usuario (multi-exchange). Campos: type (bitunix/kucoin), enc_api_key/enc_api_secret/enc_passphrase (Fernet), base_url, enabled, is_active. Un índice único parcial garantiza un solo exchange activo por usuario; el engine carga las credenciales del activo al arrancar.
    💾
    data/{user_id}/bot_state.json
    Estado activo/pausado de cada bot, aislado por usuario. Se escribe en cada Start/Stop desde el dashboard y sobrevive a reinicios del servidor. Formato: {"BTCUSDT": true, "ETHUSDT": false, ...}
    📍
    data/{user_id}/positions.json
    Posiciones abiertas del usuario. Campos: symbol, side, entry_price, quantity, sl_price, tp_price, opened_at, order_id, initiator (bot/manual/exchange) y exchange_position_id. Se reconcilia con el exchange real al arrancar.
    📊
    data/{user_id}/trades.csv
    Historial completo de trades del usuario. Un registro por apertura y cierre. Campos clave: pnl, commission (estimada 0.05% round-trip), initiator (bot/manual/exchange) y mode (live/dry). Fuente de verdad para stats de bots, PnL diario y circuit breaker. Incluye migración automática al añadir campos nuevos.
    📄
    logs/bot.log
    Log de actividad del sistema. Formato de texto delimitado por |: TS | LEVEL | module | msg (una línea por evento, no JSON). El mismo evento sí se emite como JSON, pero solo en la consola/stdout; el archivo siempre es pipe-delimited. El dashboard lee las últimas 50 entradas y las filtra por símbolo en el cliente. Nivel configurable en caliente desde Configuración (DEBUG / INFO).
    🔒
    .env
    Variables de nivel servidor: SECRET_KEY (firma de sesiones), MASTER_KEY (Fernet, para cifrar API keys en users.db), ADMIN_USERNAME/ADMIN_PASSWORD (seed del primer arranque), HTTPS_ONLY. La config de trading per-user y las API keys se guardan en users.db — el runtime ya no escribe .env. Nunca se commitea. Se incluye .env.example como referencia.

    Stack Tecnológico

    Dependencias y versiones clave
    Frontend
    HTML5 + CSS3 + JS Vanilla
    Sin frameworks ni bundlers. El dashboard es HTML/CSS/JS puro servido como archivos estáticos. Cero dependencias en el cliente — carga instantánea y sin build step.
    CSS Custom Properties
    Todo el sistema de diseño (colores, radios, espaciado) se define en :root como variables CSS. El tema oscuro se aplica globalmente sin preprocesadores.
    apiFetch + Polling
    Wrapper sobre fetch() que redirige a /auth/login ante cualquier respuesta 401. El estado se sincroniza cada 5 segundos con /health y /api/logs. No hay WebSocket — polling simple, predecible y sin estado de conexión que gestionar.
    SVG inline (sparklines)
    Las minigráficas de rendimiento en la tab Bots son SVG generados programáticamente con polyline. Sin canvas, sin librerías de gráficos — solo vectores calculados en tiempo de render.
    Backend
    Python 3.11+
    Lenguaje base. Se usa asyncio nativo para el cliente HTTP concurrente y el feed de precios.
    FastAPI
    Framework ASGI de alto rendimiento. Gestiona el webhook per-user, los endpoints REST protegidos y sirve el frontend como StaticFiles. La validación de payloads es automática via Pydantic.
    Uvicorn
    Servidor ASGI basado en uvloop. Ejecuta el event loop async de Python con rendimiento cercano a Go/Node para I/O intensivo.
    Pydantic v2
    Validación y serialización de datos con anotaciones de tipo Python. Usado en WebhookPayload (señales) y Settings (configuración de servidor desde .env).
    pydantic-settings
    Carga tipada de variables de entorno de nivel servidor desde .env. Acceso via get_settings() — convención de proyecto para no leer os.environ directamente.
    httpx
    Cliente HTTP async usado para las llamadas a las APIs REST de los exchanges (Bitunix y KuCoin). Soporta async with y timeouts configurables, lo que evita bloquear el event loop.
    ExchangePort + factory (multi-exchange)
    Abstracción (typing.Protocol) con el contrato común de un exchange — place_order, set_leverage, get_all_positions, etc. create_exchange_client() instancia BitunixClient o KuCoinClient según el exchange activo del usuario, sin que el engine sepa cuál es.
    cryptography (Fernet)
    Cifrado simétrico autenticado de las API keys de cada usuario en reposo. MASTER_KEY en .env; las claves descifradas solo existen en memoria durante la ejecución del engine del usuario.
    Starlette SessionMiddleware
    Gestión de sesiones HTTP con cookie firmada (itsdangerous bajo el capó). Firmada con SECRET_KEY; la flag HTTPS_ONLY activa Secure en producción.
    stdlib sqlite3 + hashlib.scrypt
    sqlite3 gestiona users.db sin dependencias extra. hashlib.scrypt (stdlib) hashea passwords con sal aleatoria de 16 bytes y parámetros OWASP mínimos; comparación en tiempo constante con hmac.compare_digest.
    python-dotenv
    Carga el fichero .env en las variables de entorno del proceso antes de que pydantic-settings las lea. Estándar de facto para configuración local en Python.
    Análisis y Backtesting
    pandas
    Manipulación de series temporales OHLCV. El backtesting carga los CSV de velas, calcula todos los indicadores sobre DataFrame y aplica la lógica de señales vectorizada.
    numpy
    Operaciones numéricas vectorizadas bajo pandas. Cálculo de medias móviles exponenciales (EWM), VWAP acumulado y normalización de series.
    plotly
    Genera la curva de capital interactiva (equity_curve.html) que se embebe en la tab de Backtesting via <iframe>. Exporta HTML standalone sin dependencias externas.

    Seguridad

    Firma, cifrado, hashing y autenticación
    SHA256 (doble pasada) — firma Bitunix
    Firma de todas las peticiones a la API de Bitunix. digest = SHA256(nonce+ts+key+qs+body) y sign = SHA256(digest+secret) — dos pasadas de SHA256, no HMAC. La firma viaja en el header; Bitunix rechaza cualquier petición que no la lleve.
    Fernet — cifrado de credenciales en reposo
    Las API keys de Bitunix de cada usuario se almacenan cifradas con Fernet en users.db. Solo se descifran en memoria para la llamada al exchange. Ningún endpoint las devuelve — POST /api/keys es write-only.
    scrypt — hashing de passwords
    Passwords almacenados como {salt_hex}${hash_hex} con hashlib.scrypt (stdlib). Sin dependencias de terceros para hashing. Comparación en tiempo constante para prevenir timing attacks.
    Sesiones firmadas — get_current_user
    Cookie de sesión firmada con SECRET_KEY via SessionMiddleware. La dependencia FastAPI get_current_user gatea todos los endpoints de datos — un request sin sesión válida recibe 401 y el frontend redirige a /auth/login.
    URL webhook secreta por usuario
    Cada usuario tiene un token secreto único (secrets.token_urlsafe(32)) que forma su URL personal: /webhook/{token}. El token en la URL es el único mecanismo de autenticación del webhook — el campo token en el body JSON es ignorado.

    Infraestructura y Despliegue

    Cómo está montado el servidor, las bases de datos y los sistemas
    🌐
    Internet
    TradingView y navegadores → bipbiptrading.com (HTTPS:443)
    →
    🔒
    Apache + Plesk
    AlmaLinux. Termina TLS (Let's Encrypt) y hace reverse-proxy al 127.0.0.1:8000
    →
    🐳
    Docker · Uvicorn + FastAPI
    Contenedor bipbip-bot, escucha solo en 127.0.0.1:8000 con --proxy-headers

    El contenedor es la única pieza que ejecuta lógica. Apache (gestionado por Plesk, nunca a mano) es el único proceso expuesto a internet; el puerto 8000 es local. Dentro del contenedor, FastAPI sirve dos sitios estáticos y la API, y habla con las bases de datos montadas como volúmenes y con los exchanges externos.

    🗃️
    SQLite · users.db
    Volumen ./users.db

    Base de datos relacional (WAL, sin ORM). Tabla users (cuentas, config de trading, credenciales legacy) y tabla exchanges (una fila por exchange por usuario, con credenciales cifradas y un único exchange activo). Montada como volumen para sobrevivir a redeploys.

    📁
    data/{user_id}/
    Volumen ./data

    Estado de runtime por usuario en archivos: positions.json (posiciones abiertas, escritura atómica), trades.csv (historial, fuente de verdad del PnL) y bot_state.json (bots activos/pausados). Segundo volumen persistente junto a users.db.

    🖥️
    Superficies HTTP
    StaticFiles + API

    / → web-public/ (sitio público + catálogo SEO). /bot-manager-console → web-private/ (esta consola). /backtest-results → gráficos de backtest. /webhook/{token} → señales. El mount de / va último porque captura todo.

    🏦
    Bitunix Futures
    Exchange · LIVE

    API REST externa (fapi.bitunix.com) para órdenes, posiciones y leverage. También alimenta el feed de precios compartido (polling de tickers cada 10s). Exchange por defecto en producción.

    🏦
    KuCoin Futures
    Exchange · dry-run

    Segundo exchange vía la abstracción ExchangePort + factory. Implementado de punta a punta pero forzado a dry-run hasta aprobación explícita; todavía no envía órdenes reales.

    📡
    TradingView Webhooks
    Fuente de señales

    Las alertas de Pine Script disparan HTTP POST /webhook/{token} al cumplirse las condiciones de la estrategia: TradingView es el motor de detección de señales, el bot no calcula indicadores en vivo. El endpoint legacy /webhook sin token devuelve 410 Gone.

    📈
    Binance OHLCV
    Datos de backtest

    Velas históricas descargadas de Binance vía download_data.py, usadas exclusivamente para backtesting. El bot en vivo opera sobre Bitunix/KuCoin, no sobre estos datos.

    🤖
    Sistemas de I+D (GGA)
    Mismo host

    Pipeline GGA y ciclo de I+D (tools/) que investigan, backtestean y publican estrategias. Se disparan bajo demanda (admin) e invocan el CLI de Claude; el SSG regenera el catálogo en web-public/estrategias/. No intervienen en el camino de trading en vivo.

    Entorno de Desarrollo con IA

    Claude Code · Engram · Gentleman AI · GGA Multi-Agente · Codex · Copilot · Skills · Memory
    👤
    Javier Barranco
    Mensaje en el chat del IDE
    →
    🖥️
    Claude Code
    CLI / extensión VS Code. Conecta el modelo con el sistema de archivos y las herramientas
    →
    🧠
    Claude Opus 4.8
    El modelo de IA que procesa el mensaje y decide qué herramientas usar
    →
    🔧
    Herramientas
    Read · Write · Edit · Bash · Grep · Agent · Skill
    →
    💾
    Engram / Memory
    Persistencia entre sesiones: decisiones, bugs, convenciones
    🧠
    Claude Opus 4.8
    Modelo de IA

    El modelo de lenguaje que procesa tus mensajes, razona sobre el código, llama herramientas y genera respuestas. Tiene un contexto de conversación activo, pero sin memoria entre sesiones salvo que use Engram o el sistema de archivos de memoria.

    🖥️
    Claude Code
    CLI / VS Code Extension

    La capa que conecta Claude con tu entorno local. Provee las herramientas nativas (Read, Write, Edit, Bash, Grep, Glob), gestiona permisos, muestra aprobaciones y carga el contexto del proyecto (CLAUDE.md, archivos abiertos, selección del IDE).

    🎩
    Gentleman AI
    Persona — solo output style

    No es un sistema separado — es un conjunto de reglas en CLAUDE.md que define cómo Claude se comunica: tono apasionado y directo, Rioplatense con voseo, CAPS para énfasis, senior architect de 15+ años. Afecta solo el texto del chat, nunca el código generado ni los artefactos técnicos. No confundir con GGA (Gentleman Guardian Angel): GGA es el orquestador del flujo multi-agente — ver más abajo. Mismo apodo, capas distintas.

    🗄️
    Engram
    MCP Server · memoria persistente

    Proceso externo que expone herramientas de memoria via MCP (Model Context Protocol). Claude lo llama con mem_save (guardar decisión/bug/convención), mem_search (búsqueda semántica) y mem_context (sesión reciente). Persiste en SQLite/vectores — sobrevive a reinicios, compresiones de contexto y nuevas sesiones. Tipos de observación: bugfix · decision · architecture · discovery · pattern · preference.

    📁
    File Memory
    Memoria secundaria en archivos

    Sistema de archivos .md en ~/.claude/projects/…/memory/ que Claude escribe directamente con la herramienta Write. Complementa a Engram para notas rápidas sobre el usuario, feedback y preferencias. El índice MEMORY.md se carga en cada sesión automáticamente.

    📋
    Skills
    Instrucciones especializadas por tarea

    Archivos SKILL.md con instrucciones detalladas para tareas específicas (SDD phases, code-review, etc.). Claude los lee antes de ejecutar esa tarea. Se invocan con el Skill tool al escribir /sdd-apply, /code-review, etc. en el chat.

    🤖
    Sub-agentes
    Instancias Claude con contexto fresco

    Instancias independientes de Claude spawneadas con el Agent tool. Empiezan sin memoria del contexto actual — reciben todo via briefing en el prompt. Se usan para tareas paralelas, exploración de código sin contaminar el contexto principal, o revisiones adversariales. Tipos: Explore, sdd-apply, sdd-verify, general-purpose, Plan, entre otros.

    📐
    SDD — Spec-Driven Development
    Metodología de desarrollo

    Flujo estructurado para cambios no triviales: explore → propose → spec → design → tasks → apply → verify → archive. Cada fase es un sub-agente especializado. Los artefactos (specs, diseños, tareas) se persisten en Engram o en archivos openspec/. Evita que Claude implemente sin haber entendido primero el problema y acordado el enfoque.

    Modelo Multi-Agente — Gentleman Guardian Angel (GGA)

    Una capa de gobernanza por encima de Claude Code

    Más allá de la sesión mono-agente de arriba, el proyecto define un equipo de IA con roles y autoridad acotada, documentado en AGENTS.md (fuente de autoridad) y DELEGATION.md (qué archivo pertenece a quién). La regla de oro: ningún agente activa trading real, sube leverage, ni toca credenciales sin aprobación del Product Owner. Claude es el revisor vinculante en todo lo crítico. El trabajo sustancial arranca un paso antes, en la etapa de planificación SDD (ver abajo): define el qué y el porqué antes de que la cadena de agentes ejecute el cómo.

    🧩

    Etapa SDD — Spec-Driven Development

    Capa de planificación

    Antes de que un solo agente toque código, SDD captura el cambio como artefactos persistidos (en Engram / OpenSpec, con trazabilidad): proposal → spec → design → tasks. Su salida —el checklist de tareas— es exactamente lo que alimenta al pipeline GGA: una tarea de SDD = una fase de GGA. SDD decide QUÉ construir y POR QUÉ; la cadena de agentes de abajo ejecuta el CÓMO; y al cerrar, verify valida el resultado contra el spec y archive sincroniza las specs y cierra el ciclo.

    🧭
    Explore
    Investiga el código y compara enfoques
    →
    📝
    Propose
    Intención, alcance y enfoque del cambio
    →
    📋
    Spec
    Requisitos y escenarios (delta specs)
    →
    📐
    Design
    Decisiones de arquitectura y enfoque técnico
    →
    ✅
    Tasks
    Checklist ordenado — cada ítem alimenta una fase GGA
    ↓ cada tarea del checklist se despacha como UNA fase del pipeline GGA ↓
    🧠
    Architect · Claude
    Diseña: file contract + criterios de aceptación + restricciones de seguridad
    →
    🛠️
    Implementer · Codex
    Implementa dentro del file contract, en un git worktree aislado
    →
    🛡️
    Safety Guards
    Denylist de paths + scan de live-trading sobre el diff. Violación → CHANGES_REQUESTED
    →
    🔍
    Reviewer · Copilot
    Revisión read-only del diff. Hallazgos advisory (no vinculantes)
    →
    ✅
    Approver · Claude
    Veredicto vinculante vs. el diseño: APPROVED | CHANGES_REQUESTED
    →
    👤
    Product Owner
    Único que aprueba activar trading real / merge a producción
    🎩
    GGA — Gentleman Guardian Angel
    Orquestador del equipo

    Coordina a los agentes, rutea tareas, carga skills del proyecto y mantiene la ejecución del flujo. No override las decisiones de arquitectura de Claude. Se configura en .gga (provider, patrones de archivos incluidos/excluidos, RULES_FILE=AGENTS.md y los comandos de cada adaptador de agente).

    ⛓️
    Pipeline GGA
    Cadena de delegación local auditada

    Orquestador local que ejecuta la cadena de AGENTS.md como una corrida determinista y auditada: Architect (Claude) → Implementer (Codex) → Reviewer (Copilot) → Approver (Claude). No hay canal in-process entre agentes — cada uno es un subproceso de su CLI headless. El implementer trabaja en un git worktree aislado (gga/run-<ts>); dev y master nunca se tocan. Todos los artefactos quedan bajo .gga-runs/<ts>/. Nunca mergea, pushea, deploya, corre la app ni contacta a Bitunix. Salida final: APPROVED / CHANGES_REQUESTED / PIPELINE_ABORTED (exit 0/1/2).

    🧠
    Claude Code
    Lead Architect · Architect

    Define arquitectura, estándares técnicos, file contracts y criterios de aceptación antes de implementar. Es el revisor vinculante para seguridad, cambios estructurales y cualquier área crítica de trading: webhook, Risk Manager, Bitunix, leverage, SL/TP, persistencia y despliegue. En el pipeline abre la cadena como Architect y la cierra como Approver.

    🛠️
    Codex
    Senior Software Engineer · Implementer

    Implementa features, refactoriza, escribe tests y arregla bugs. No puede cambiar arquitectura, reglas de risk management ni comportamiento de trading en producción sin revisión de Claude. En el pipeline corre como codex exec --sandbox workspace-write, con la escritura confinada al worktree de la corrida. Firma sus commits como codex.

    🔍
    GitHub Copilot
    Reviewer · solo advisory

    Genera implementaciones alternativas, sugiere optimizaciones y revisa cambios. Sus recomendaciones son advisory: nunca es el único revisor que despeja un cambio de riesgo/seguridad — la revisión de Claude lo override. Usa el Copilot CLI agéntico (gh copilot), no la extensión legacy de suggest/explain. En el rol de reviewer se fija a herramientas read-only (--available-tools view,grep,glob) para que no pueda editar el árbol.

    👤
    Product Owner
    Javier Barranco

    Define objetivos de negocio y es el único que aprueba la activación de trading real, integraciones de exchange y releases a producción. Ningún agente de IA puede activar dinero real sin su aprobación explícita — es el gate final por encima de todo el flujo.

    ▶️
    Cómo ejecutar una tarea multi-agente
    Una corrida completa de la cadena de delegación

    Desde la raíz del repo, con los CLIs instalados (claude, codex, gh copilot):

    python tools/gga_pipeline.py "<tarea>" --run-tests --allow-dirty

    "<tarea>" — descripción de la tarea entre comillas: lo que Claude diseña (file contract + criterios) y Codex implementa en el worktree aislado.
    --run-tests — tras implementar, corre python -m compileall . + pytest (solo dry-run, nunca coloca órdenes); si fallan, el veredicto es CHANGES_REQUESTED.
    --allow-dirty — arranca aunque el árbol de trabajo tenga cambios sin commitear (por defecto el pipeline aborta si el árbol está sucio).

    La corrida queda auditada en .gga-runs/<ts>/ (diseño, patch, hallazgos, veredicto y prompts). Termina con APPROVED (exit 0), CHANGES_REQUESTED (exit 1) o PIPELINE_ABORTED (exit 2). En APPROVED conserva la rama gga/run-<ts> para que la inspecciones y mergees a mano — el pipeline nunca mergea ni pushea.

    Archivos clave del entorno

    📜
    ~/.claude/CLAUDE.md
    Instrucciones globales para todas las sesiones. Define la persona Gentleman AI, reglas de comportamiento, protocolo de memoria Engram, protocolo de agentes y asignación de modelos por fase SDD. Se carga automáticamente al inicio de cada conversación.
    🗂️
    ~/.claude/projects/…/memory/MEMORY.md
    Índice de la File Memory. Una línea por entrada apuntando al archivo .md con el contenido. Se carga en cada sesión para darle a Claude contexto persistente sobre el usuario, el proyecto y el feedback acumulado.
    🔌
    MCP — Model Context Protocol
    Protocolo estándar que conecta Claude con procesos externos como Engram. Claude llama herramientas MCP igual que llama herramientas nativas. La diferencia: las nativas (Read, Bash…) corren en el proceso de Claude Code; las MCP corren en procesos externos con su propia base de datos.
    📋
    AGENTS.md
    Fuente de autoridad del modelo multi-agente. Define los roles (Claude Lead Architect, Codex Senior Engineer, Copilot Reviewer, GGA Orquestador, Javier Product Owner), los componentes críticos, los archivos protegidos y las reglas de seguridad de trading. Cuando algo entra en conflicto, AGENTS.md gana.
    🗺️
    DELEGATION.md
    Mapea cada componente del repo a su autoridad: Claude-owned (gate de revisión obligatorio), Codex Tier-A (end-to-end con review advisory de Copilot) y Tier-B (Codex implementa contra diseño aprobado por Claude). Derivado estrictamente de AGENTS.md.
    ⚙️
    .gga
    Config de GGA: PROVIDER, FILE_PATTERNS/EXCLUDE_PATTERNS (excluye .env, state/, logs/, *.db, data/), RULES_FILE=AGENTS.md y los comandos de cada adaptador: CLAUDE_CMD, CODEX_CMD, COPILOT_CMD.
    📐
    tools/gga_pipeline.SPEC.md
    Especificación técnica del pipeline local: guards de denylist y live-trading, worktree aislado, layout de .gga-runs/<ts>/, semántica de exit codes y decisiones de handoff que el implementer debe respetar. Autoridad: AGENTS.md.

    Resumen de actores

    Actor Qué es Qué NO es
    🧠 Claude Opus 4.8El modelo de IA que razona y genera códigoUn agente autónomo — necesita tus instrucciones
    🖥️ Claude CodeCLI/extensión que provee herramientas al modeloEl modelo en sí — es la infraestructura que lo rodea
    🎩 Gentleman AIPersona: tono, idioma, filosofía de respuestaUn sistema separado — es solo configuración en CLAUDE.md
    💾 EngramMemoria persistente entre sesiones (MCP server)Memoria de trabajo — eso es el contexto de la sesión
    📝 File MemoryArchivos .md de notas sobre usuario y proyectoSustituto de Engram — son complementarios
    🧩 SkillsInstrucciones especializadas por tarea (/sdd-apply…)Código ejecutable — son Markdown que Claude lee
    👥 Sub-agentesInstancias Claude para tareas paralelas o aisladasInstancias con memoria — empiezan siempre desde cero
    📋 SDDMetodología de desarrollo spec-first con agentesUna herramienta — es un flujo de trabajo sobre Claude Code
    😇 GGAOrquestador del equipo multi-agente (config en .gga)Gentleman AI — ese es la persona; GGA coordina agentes
    🛠️ CodexImplementer: features, refactors, tests, bugfixesAutoridad sobre arquitectura/riesgo — eso es de Claude
    🔍 GitHub CopilotReviewer advisory (Copilot CLI agéntico, read-only)Revisor vinculante — Claude override; ni la extensión legacy
    ⚙️ gga_pipeline.pyCorrida local auditada de la cadena de delegaciónCI/daemon — una tarea, una corrida; no mergea ni deploya
    👤 Product OwnerJavier: único que aprueba trading real y producciónUn agente de IA — es el gate humano final

    Endpoints

    API interna · Exchange · Webhooks
    📊 Señales Entrantes — TradingView Webhook Alerts
    Direction Trigger Payload Description Auth
    INBOUND Alert → POST /webhook symbol · side · action · price · token TradingView dispara una alerta HTTP cuando el script Pine detecta un cruce EMA9/EMA21 válido con todos los filtros activos. El bot valida el token y procesa el payload con Pydantic. WEBHOOK_SECRET
    🔧 API Interna — FastAPI (localhost:8000)
    Method Endpoint Operation Description Called by
    POST /webhook Receive signal Receives TradingView alerts. Validates token secreto, comprueba si el bot está activo para ese activo y ejecuta la orden via Risk Manager. TradingView
    GET /health System status Retorna estado del bot por activo, modo dry-run, precio BTC (markPrice de Bitunix), resumen de posiciones, PnL diario, capital y detalle individual de posiciones (positions_detail). Dashboard · 5 s
    POST /bot/{symbol}/start Start bot Marca el bot como activo para el símbolo indicado y persiste el estado en state/bot_state.json. Los webhooks entrantes vuelven a ejecutarse. Dashboard
    POST /bot/{symbol}/stop Stop bot Marca el bot como pausado para ese símbolo y persiste el estado. Los webhooks para ese activo se rechazan silenciosamente sin tocar el exchange. Dashboard
    GET /api/logs Activity log Lee las últimas 50 líneas de logs/bot.log, parsea el formato estructurado TS | LEVEL | module | msg y retorna un array JSON. El filtrado por activo es client-side. Dashboard · 5 s
    GET /api/backtest/summary Backtest data Retorna los mejores parámetros por activo, historial de iteraciones, conclusiones clave y config del backtest. Datos derivados de STRATEGY_LOG.md. Backtesting tab
    GET /api/bots/stats Per-bot stats Agrega logs/trades.csv por activo/iniciador: nº de trades, win rate, PnL total y de 7 días, y última actividad. Acepta ?mode=live|dry|backtest (en backtest sirve _BACKTEST_STATS precalculado). Bots tab
    GET /api/positions/history Trade history Lee logs/trades.csv y retorna todas las entradas reales (excluye mode=dry) en orden cronológico inverso. Fuente del historial de posiciones del dashboard. Posiciones tab
    POST /api/positions/reconcile Sync exchange Consulta Bitunix bajo demanda y sincroniza el tracker con las posiciones reales — detecta cierres por SL/TP o aperturas manuales sin esperar a un reinicio. En dry-run se omite (status: skipped). Posiciones tab
    GET /api/config Read config Retorna la configuración efectiva en runtime: modo dry-run, capital, % de riesgo, SL/TP, leverage, slippage y log level actuales. Configuración tab
    POST /api/config Update config Modifica parámetros en caliente (dry-run, capital, riesgo, SL/TP, log level) sin reiniciar. El cambio de dry_run se persiste; valores inválidos se rechazan con 422. Configuración tab
    GET /api/debug/positions Raw diagnostics Devuelve la respuesta cruda de get_all_positions() de Bitunix sin procesar. Solo para diagnóstico — no lo consume el dashboard. Manual / debug
    GET /api/exchange/ping Connectivity test Llama a get_all_positions() en Bitunix para verificar que la API key es válida y hay conectividad real. En dry-run retorna una respuesta simulada. Exchange tab
    POST /api/exchange/order Manual order Abre o cierra una posición manualmente. Acepta symbol, side, action, price (opcional), sl_pct, tp_pct. Respeta el Risk Manager y el modo dry-run. Exchange tab
    GET /bot-manager-console/ Dashboard UI Sirve el dashboard frontend como archivos estáticos via FastAPI StaticFiles. Se monta al arrancar, después de todas las rutas API para que no las tape. Browser
    GET /backtest-results/ Chart assets Sirve el fichero equity_curve.html generado por Plotly, embebido en la pestaña Backtesting via <iframe>. Sin API — solo archivos estáticos. Backtesting tab
    🏦 Exchange Externo — Bitunix Futures REST API Base URL: https://fapi.bitunix.com · Firma / Auth ↗
    Method Endpoint Operation Description When
    GET /api/v1/futures/market/tickers Tickers (precios) Precios públicos (sin auth) de los símbolos configurados. El feed lee markPrice cada 10s y alimenta /health y el gate de slippage. Param: symbols=BTCUSDT,ETHUSDT,…. Price feed · 10 s
    POST /api/v1/futures/trade/place_order Place order Abre una posición MARKET con tradeSide=OPEN, SL y TP opcionales. Campos clave: symbol, side, orderType, qty, tradeSide. On signal · Manual
    POST /api/v1/futures/trade/place_order CLOSE Close position Cierra posición con tradeSide=CLOSE, reduceOnly=true. Mismo endpoint, distinto tradeSide. On close · Manual
    POST /api/v1/futures/trade/cancel_order Cancel order Cancela una orden pendiente por orderId. Payload: {"symbol", "orderId"}. On demand
    POST /api/v1/futures/trade/cancel_all_orders Cancel all orders Cancela todas las órdenes pendientes de un símbolo en una sola llamada. On demand
    GET /api/v1/futures/trade/get_pending_orders Pending orders Lista las órdenes activas (limit, stop, etc.) aún no ejecutadas. Acepta ?symbol=. On demand
    GET /api/v1/futures/trade/get_history_orders Order history Historial de órdenes ejecutadas y canceladas. Acepta ?symbol=. On demand
    GET /api/v1/futures/position/get_pending_positions Open positions Posiciones abiertas actualmente. Se llama al arrancar el servidor y en el Ping del Exchange tab. Acepta ?symbol=. Startup · Ping
    GET /api/v1/futures/position/get_history_positions Position history Historial de posiciones cerradas. Acepta ?symbol=. On demand
    GET /api/v1/futures/account Account balance Balance disponible en USDT. No usado en el flujo automático actual. On demand
    🏦 Exchange Externo — KuCoin Futures REST API Base URL: https://api-futures.kucoin.com · símbolos en notación contrato (BTCUSDT ↔ XBTUSDTM) · hoy forzado a dry-run · Firma / Auth ↗
    Method Endpoint Operation Description When
    GET /api/v1/positions Open positions Posiciones abiertas. Fuente de get_all_positions, get_position_id y get_position_info (filtran por símbolo en memoria). Se llama al arrancar, en el Ping y tras abrir para capturar el positionId. Startup · Ping
    POST /api/v1/orders Place order Abre una posición MARKET. size en contratos enteros. Campos: symbol (XBTUSDTM), side, type, size, leverage. On signal · Manual
    POST /api/v1/orders CLOSE Close position Cierra con reduceOnly=true. Mismo endpoint que la apertura, distinto flag. On close · Manual
    DELETE /api/v1/orders/{orderId} Cancel order Cancela una orden pendiente por orderId (en el path). On demand
    DELETE /api/v1/orders Cancel all orders Cancela todas las órdenes de un símbolo. Param: ?symbol=XBTUSDTM. On demand
    GET /api/v1/orders Orders (pending / history) get_pending_orders y get_history_orders comparten endpoint con distinto filtro (?status=active / done, ?symbol=). On demand
    GET /api/v1/stopOrders Pending TP/SL Órdenes stop (TP/SL) pendientes. Por verificar contra los docs oficiales de KuCoin Futures — marcado [ASSUMPTION] en el cliente. On demand
    GET /api/v1/history-positions Position history Historial de posiciones cerradas. Por verificar contra los docs oficiales — marcado [ASSUMPTION] en el cliente. On demand
    POST /api/v2/changeCrossUserLeverage Set leverage Fija el leverage cross del símbolo antes de abrir. Payload: {"symbol", "leverage"}. Before order
    GET /api/v1/account-overview Account balance Balance disponible. Param: ?currency=USDT. Usado por el probe de credenciales al guardar claves. Ping · Probe
    🏦 Exchange Externo — Saxo Bank OpenAPI (Forex) PLANIFICADO Base URL: gateway.saxobank.com/openapi (LIVE) · /sim/openapi (paper) · OAuth2 · proyecto pendiente (ver Roadmap) · OpenAPI ref ↗
    Method Endpoint Operation Description When
    POST logonvalidation.net/token OAuth2 token Renueva el access_token (vida ~20 min) con el refresh_token. Saxo usa OAuth2, no firma HMAC como Bitunix/KuCoin. Refresh · ~20 min
    GET /ref/v1/instruments Instrument lookup Resuelve el Uic de un par FX (EURUSD → Uic) necesario para construir órdenes. Resultado cacheado. Antes de operar
    POST /trade/v2/orders Place order Orden FxSpot: AccountKey, Uic, AssetType, BuySell, Amount, OrderType (+ órdenes relacionadas/OCO para SL/TP). On signal · Manual
    DELETE /trade/v2/orders/{orderId} Cancel order Cancela una orden pendiente por id (requiere AccountKey). On demand
    GET /port/v1/positions Open positions Posiciones abiertas de la cuenta, normalizadas a la forma que espera el engine. Startup · Ping
    GET /port/v1/balances Account balance Balance y margen de la cuenta. Usado por el probe de conexión contra el entorno SIM. Ping · Probe
    GET /trade/v1/infoprices FX prices Marks de precios FX para el feed y el gate de slippage del engine cuando el exchange activo es Saxo. Price feed
    📡 Exchange Externo — Binance REST API (Public)
    Method Endpoint Operation Description Lifecycle
    GET api.binance.com /api/v3/ticker/price Price poll Polling cada 10s a la API pública de Binance para obtener precios de BTC, ETH, SOL y BNB simultáneamente. Sin autenticación. Alimenta /health → prices y los precios del dashboard. Se usa Binance porque el WebSocket de Bitunix no resuelve DNS correctamente. Continuous · 10s

    Cómo Iniciar el Sistema

    1
    Crear el entorno
    python -m venv .venv && .venv\Scripts\activate
    2
    Instalar dependencias
    pip install -r requirements.txt
    3
    Configurar variables
    cp .env.example .env   # editar con tus credenciales
    4
    Iniciar el servidor
    python main.py
    5
    Abrir el dashboard
    http://localhost:8000/bot-manager-console/

    Posiciones

    Posiciones abiertas e historial completo de operativas

    Posiciones Abiertas

    —
    PositionID Date / Time Symbol Side Entry Price Current Price Qty Unrealized PnL Stop Loss Take Profit Origen Action
    Sin posiciones abiertas

    Historial de Posiciones

    PositionID Date / Time Symbol Side Entry Price Exit Price Qty PnL Commission Origen
    Cargando historial...

    Exchange Console

    Operativa manual y monitorización directa del exchange
    Mode —
    BTC —
    ETH —
    SOL —
    BNB —
    Daily P&L —
    Capital —

    Configured exchanges

    Orders, positions and health data use the active exchange engine.

    Loading exchanges...

    New Order

    DRY RUN
    Symbol
    Direction
    Stop Loss %
    Take Profit %
    SL Price —
    TP Price —
    Est. Qty —
    Risk : Reward —

    Open Positions

    0 positions

    No open positions

    Response Log

    No requests yet — place an order or run a ping

    Exchanges

    Análisis y decisiones de integración de exchanges (solo admin)

    Exchanges crypto

    Exchanges crypto integrados en el bot (Bitunix, KuCoin) y otros recomendados a evaluar para futuros perpetuos.

    BrokerEncaje técnicoPeroValoración
    Bitunix API REST con firma doble-pass SHA256 (httpx async). Es el cliente nativo base del bot (BitunixClient): request/response que encaja directo con el ExchangePort. Futuros perpetuos en USDT. Exchange joven y de menor tamaño (~$5B/día, 2M usuarios); sin licencia MiCA todavía (solo MSB en EE.UU./Canadá y VASP en Filipinas), acceso a retail UE incierto tras julio 2026; liquidez más ligera en algunos pares que los tier-1 y perfil con tolerancia no-KYC. Operativo ↗
    KuCoin API REST con firma HMAC-SHA256 + passphrase en base64 (símbolo XBTUSDTM). Cliente nativo KuCoinClient añadido en la migración multi-exchange; encaja con el ExchangePort, con cantidad en contratos enteros. Settlement con el DOJ de EE.UU. en 2024: 300 M$ de multa y salida obligatoria del mercado estadounidense (operar sin licencia, lavado/AML). Historial de compliance manchado, aunque obtuvo licencia MiCA en Austria (FMA, 2025) para operar en el EEA. Operativo ↗
    Binance API REST madura con firma HMAC-SHA256 (request/response, encaja con el ExchangePort) más WebSocket para streaming. Soportada por CCXT y wrappers Python maduros; máxima liquidez de futuros (300+ pares perp). Mayor sanción regulatoria del sector: 4.300 M$ al DOJ de EE.UU. (2023) por fallos AML y violación de sanciones; el fundador CZ se declaró culpable y fue condenado a prisión. Futuros perp para retail UE restringidos (ESMA/MiFID limita apalancamiento); KYC estricto. MiCA (spot) obtenido en 2025. Restringido
    Bybit API v5 unificada (REST + WebSocket) con firma HMAC, construida para derivados sobre una cuenta unificada. Request/response compatible con el ExchangePort; soportada por CCXT. Sufrió el mayor hack de la historia cripto: 1.500 M$ en ETH robados (feb 2025, atribuido al grupo Lazarus de Corea del Norte, vía una vulnerabilidad en Safe Wallet). Con sede en Dubái; la licencia MiCA (Austria, 2025) cubre spot y la oferta de futuros perp a retail UE es limitada. Riesgoso
    OKX API v5 completa (REST + WebSocket) con firma HMAC + passphrase, como KuCoin. Futuros y opciones bajo margen unificado; request/response compatible con el ExchangePort; soportada por CCXT. Se declaró culpable ante el DOJ de EE.UU. (2025) y pagó 504 M$ por operar sin licencia y fallos AML (facilitó >5.000 M$ en transacciones sospechosas), con monitor de compliance hasta 2027. Con base en Seychelles; requiere passphrase adicional. Futuros perp a retail UE restringidos; MiCA vía Malta (MFSA). Cuestionable
    Kraken API REST de Kraken Futures (entidad separada) con firma propia, request/response. Bien documentada y soportada por CCXT, aunque con menos pares de futuros que los grandes. Menor liquidez y cobertura de pares de futuros que Binance/Bybit; los futuros van por una entidad separada (Kraken Futures), no disponible en todas las jurisdicciones. Antecedente regulatorio menor: 30 M$ a la SEC (2023) por su programa de staking no registrado en EE.UU. Confiable

    Exchanges Forex

    Brokers de Forex evaluados para la integración.

    BrokerEncaje técnicoPeroValoración
    FXMC Solo plataforma MetaTrader 4, sin API REST propia. La automatización requeriría Expert Advisors en MQL4 o un puente de terceros (p. ej. MetaApi); no encaja con el ExchangePort del bot. No regulado, perfil de fraude (WikiFX 1.59). Descartado
    FXCM API híbrida REST + Socket.IO (streaming) con token de acceso; parte del stack (namespace O2G2 de ForexConnect) está deprecada. El modelo de socket persistente no encaja con el patrón request/response del ExchangePort. Expulsado del mercado de EE.UU. (multa CFTC, 2017) y futuro de la API incierto. Descartado
    OANDA API v20 REST limpia (request/response, httpx async) con Bearer token + account_id: el encaje ideal con el ExchangePort… pero sin acceso para clientes de la UE (ver columna Pero). Sin API REST para clientes de la UE desde la migración a OANDA TMS (Polonia, 2023). Descartado
    FOREX.com API REST + streaming Lightstreamer con sesión (login → session ID). La ejecución va acoplada al feed (necesita Bid/Offer + Audit ID del stream), lo que rompe el patrón request/response del ExchangePort; sin SDK Python oficial. Documentación cerrada y sin SDK Python oficial. Descartado
    Interactive Brokers API madura vía TWS/IB Gateway (socket) con el wrapper ib_async, o Client Portal Web API (REST). Requiere un proceso Gateway permanente: no es REST stateless puro, no encaja directo con el ExchangePort sin infraestructura extra. Requiere un proceso IB Gateway permanente + re-login/2FA: infraestructura adicional. Finalista. Finalista
    Saxo Bank OpenAPI REST/OAuth2 limpia (POST /trade/v2/orders): request/response que encaja con el ExchangePort sin procesos externos. Documentación pública, entorno SIM self-service (mapea al modo paper) y SDK Python (saxo-openapi). Comisiones/depósito premium y tokens OAuth2 de vida corta (~40 min). Elegido. Integrando

    Análisis de broker para Crypto — Bitunix

    Bitunix ↗

    Exchange de futuros perpetuos crypto; cliente base e histórico del bot.

    Fortalezas
    • API REST nativa con firma doble-pass SHA256; es el cliente original sobre el que se construyó el bot.
    • 500+ pares de futuros perpetuos y motor de alto rendimiento (hasta 200x de apalancamiento).
    • Sin incidentes de hackeo desde su lanzamiento (2021); fondos en cold storage + fondo de garantía (Care Fund de 30 M$).
    Riesgos / Pero
    • Exchange joven y de menor tamaño (~5.000 M$/día, 2M usuarios) frente a los tier-1.
    • Sin licencia MiCA todavía (solo MSB en EE.UU./Canadá y VASP en Filipinas); acceso a retail UE incierto tras julio 2026.
    • Liquidez más ligera en algunos pares y perfil con tolerancia no-KYC.

    Integrado y en producción como exchange base del bot.

    Análisis de broker para Crypto — KuCoin

    KuCoin ↗

    Exchange crypto incorporado en la migración multi-exchange.

    Fortalezas
    • API REST HMAC-SHA256 + passphrase; integrado en la fase 2 de la migración multi-exchange.
    • Exchange grande y establecido; licencia MiCA (Austria, FMA, 2025) que habilita operar en los 30 países del EEA.
    • Amplia cobertura de mercados y buena liquidez.
    Riesgos / Pero
    • Settlement con el DOJ de EE.UU. (2024): 300 M$ de multa y salida obligatoria del mercado estadounidense (operar sin licencia, lavado/AML).
    • Historial de compliance manchado (la subsidiaria en Viena se ha visto como arbitraje regulatorio).
    • Particularidades de la API: cantidad en contratos enteros (fail-closed en fraccional), símbolo XBTUSDTM y TP/SL nativo limitado.

    Integrado; en el bot arranca forzado a dry_run pendiente de aprobación del Product Owner.

    Análisis de broker para Forex — Saxo Bank

    Saxo Bank 🔧

    Evaluación de brokers para incorporar operativa Forex, filtrados por dos requisitos: que acepten residencia España/UE y que ofrezcan una API utilizable para el bot.

    Brokers evaluados
    • FXMC — no regulado, perfil de fraude (WikiFX 1.59). Descartado.
    • FXCM — expulsado del mercado de EE.UU. (multa CFTC, 2017) y API en estado incierto (Socket.IO). Descartado.
    • OANDA — técnicamente ideal, pero sin API REST v20 para clientes de la UE desde la migración a OANDA TMS (Polonia, 2023). Descartado.
    • FOREX.com — documentación cerrada y ejecución de órdenes acoplada al streaming propietario (Lightstreamer), sin SDK Python oficial. Descartado.
    • Interactive Brokers — finalista. API completa y comisiones bajas, pero exige un proceso IB Gateway permanente (infraestructura adicional + re-login/2FA).
    • Saxo Bank — elegido.
    Por qué Saxo
    • OpenAPI REST limpia (POST /trade/v2/orders): encaja con el puerto de exchange existente, sin procesos externos como el Gateway de IBKR.
    • Documentación pública y entorno de simulación (SIM) self-service: se mapea directamente al modo paper/dry_run del bot.
    • Banco europeo regulado (MFSA/MiFID) que da servicio a residentes en España.
    • Soporte nativo de stop-loss / take-profit (órdenes relacionadas).
    Trade-offs asumidos
    • Comisiones y depósito mínimo más altos (perfil premium frente a IBKR).
    • Autenticación OAuth2 con tokens de vida corta (access ~20 min / refresh ~40 min): requiere lógica de renovación y un procedimiento de re-login tras caídas prolongadas. La autenticación por certificado (sin login) es solo para clientes institucionales.

    Integración pendiente de diseño. Saxo arrancará en SIM/paper; la activación de trading real requiere aprobación del Product Owner.

    Users

    Gestion de usuarios administradores y operadores

    Crear usuario

    Dry run por defecto

    Usuarios

    ID Usuario Email Rol API Modo Nuevo password
    Cargando...

    Trading

    Modo de operación y parámetros del sistema

    Configuración en TradingView

    Conectá tus señales de TradingView con el bot en 3 pasos. Una vez configurada la alerta, cada señal dispara una orden automáticamente.

    1. Seleccioná el activo. Abrí en TradingView el gráfico del par que vas a operar (BTCUSDT, ETHUSDT, SOLUSDT o BNBUSDT).
    2. Cargá el Pine Script. Abrí el Pine Editor, pegá el script de la estrategia y pulsá "Add to chart". El script ya arma el mensaje de la alerta con tu token y el campo ts (protección anti-replay) — no hace falta escribir el JSON a mano.
    3. Creá la alerta con webhook:
      • Condición: la estrategia que cargaste en el gráfico.
      • Mensaje: {{strategy.order.alert_message}} (el Pine lo completa solo).
      • Notificaciones → Webhook URL: activá la casilla y pegá tu URL personal: https://bipbiptrading.com/webhook/TU_TOKEN

    Tu token de webhook es personal y secreto — no lo compartas. Probá una alerta en modo DRY RUN antes de operar en real.

    Modo de Operación

    Controla si las órdenes se envían al exchange real o solo se simulan.

    Cargando...

    Parámetros del Sistema

    Los cambios se aplican en runtime y se persisten en la base de datos del usuario.

    USDT
    % del capital (0.01–10)
    % del capital (0.1–50)
    % del precio de entrada
    % del precio de entrada
    Multiplicador · el bot lo aplica al abrir
    % · rechaza si el precio se movió más que esto
    Aplica inmediatamente

    Roadmap

    Tareas pendientes y fases del pipeline GGA acordadas durante el desarrollo

    Backlog de proyectos

    Iniciativas grandes que se ejecutan por fases vía el pipeline GGA. Cada card agrupa las peticiones de su proyecto.

    #3. Integración exchange Saxo Bank
    Descripción del proyecto

    Primer broker de Forex (Saxo Bank OpenAPI). A diferencia de Bitunix/KuCoin, autentica con OAuth2 (no HMAC estático), opera FX spot (no perpetuos), exige resolución de instrumentos (Uic), su propio feed de precios FX y un entorno SIM para paper. Una petición por fase para tools/gga_pipeline.py, en orden, mergeando cada fase antes de la siguiente. En PowerShell: $task = @' … '@ y luego python tools/gga_pipeline.py $task --run-tests --allow-dirty.

    Fases de desarrollo con pipeline GGA
    🟢 Fase 1 — Fundación: modelo de credenciales OAuth2 + tipo "saxo"
    🔴 Fase 2 — SaxoClient aislado (OpenAPI REST + OAuth + SIM)
    🔴 Fase 3 — Routing del engine + gestión de credenciales OAuth
    🔴 Fase 4 — Feed de precios FX + símbolos/validación de webhooks Forex
    🔴 Fase 5 — UI del tab Exchange para Saxo (OAuth + Forex)
    #4. Generación de estrategias guiada (filtros + multi-activo)
    Descripción del proyecto

    Hoy la generación manual de estrategias (tab I+D) es un disparo a ciegas: el prompt elige cualquier estrategia y el backtest está hardcodeado a crypto (BTC/ETH/SOL/BNB). Este proyecto añade filtros para guiar el research — tipo de activo (crypto, forex, commodity, acciones), familia (trend, mean-reversion, breakout, momentum, volatilidad) y timeframe — y abre el backtest a multi-activo. Una petición por fase para tools/gga_pipeline.py, en orden, mergeando cada fase antes de la siguiente. En PowerShell: $task = @' … '@ y luego python tools/gga_pipeline.py $task --run-tests --allow-dirty.

    Fases de desarrollo con pipeline GGA
    🟢 Fase 1 — Parametrizar la generación (backend + prompt + pipeline)
    🟢 Fase 2 — UI de filtros en el tab I+D
    🟢 Fase 3 — Capa de datos multi-activo (DataProvider + yfinance)
    🔴 Fase 4 — Ejecución forex en vivo (depende del proyecto Saxo) ⚠️ alto riesgo
    #5. Refactor Backtester v.2
    Descripción del proyecto

    Refactor del motor de backtesting para eliminar los sesgos que invalidan los resultados actuales: la entrada se ejecuta en el cierre de la misma vela que genera la señal (look-ahead), el ratio de Sharpe está mal anualizado fuera de 15m, y las salidas SL/TP asumen un fill perfecto sin slippage. Además la optimización de ADX es in-sample (overfitting) y el loop de simulación no tiene ni un test. El objetivo es que los números del backtester sean confiables ANTES de seguir optimizando estrategias.

    Fases de desarrollo con pipeline GGA
    🔴 Fase 1 — Fundación: red de tests sobre el loop de simulación (_run, _run_atr, _stats)
    🔴 Fase 2 — Corrección look-ahead: fills a la apertura de la vela siguiente (open[i+1])
    🔴 Fase 3 — Métricas correctas: Sharpe anualizado por timeframe + Sortino/Calmar + benchmark buy&hold
    🔴 Fase 4 — Realismo de ejecución: modelo de slippage + penalización gap-through en SL/TP
    🔴 Fase 5 — Validación robusta: walk-forward / out-of-sample para la optimización de parámetros
    #6. Integración Feed Noticias v.1
    Descripción del proyecto

    Agregador de noticias de mercado en español para los activos que opera el bot (BTC, ETH, SOL, BNB y pares FX), vía feeds RSS nativos en español (Cointelegraph ES, CriptoNoticias, Bit2Me News, FXStreet ES, Investing.com ES) — sin API key ni traducción. Un loop de refresco en segundo plano parsea con feedparser, normaliza, deduplica y mapea por activo; un endpoint GET /api/news sirve el caché y un panel en la consola lo muestra por activo. v.1 enfocada (RSS + panel); sentiment, Finnhub, traducción y feed a I+D quedan para v.2. Una petición por fase para tools/gga_pipeline.py, en orden, mergeando cada fase antes de la siguiente.

    Fases de desarrollo con pipeline GGA
    🔴 Fase 1 — Agregador de feeds RSS (backend + loop de refresco)
    🔴 Fase 2 — Endpoint GET /api/news (autenticado, sirve el caché)
    🔴 Fase 3 — Panel de noticias en el dashboard (frontend)
    #7. Catálogo de Indicadores v.1
    Descripción del proyecto

    Espejar el sistema de catálogo de Estrategias para una nueva entidad: Indicadores. En vez de clonar el generador SSG (deuda técnica desde el día uno), se generaliza el builder actual a un motor de catálogo parametrizado por entidad que sirve Estrategias E Indicadores desde una sola base de código. Cada indicador (EMA, VWAP, RSI, ADX, ATR, Bollinger, Supertrend, Donchian, Volume MA, SMA) tendrá su ficha pública SEO y una card en la consola que navega a /indicadores/<slug>. Las páginas nuevas nacen con noindex,nofollow para respetar el estado temporal del sitio.

    Fases de desarrollo con pipeline GGA
    🔴 Fase 1 — Generalizar el motor SSG: build.py parametrizado por entidad; Estrategias pasa a ser una instancia
    🔴 Fase 2 — Capa de datos de Indicadores: 10 fichas JSON + _site.json con base_path /indicadores
    🔴 Fase 3 — Generar y servir el catálogo: web-public/indicadores/, middleware en main.py, regla Apache, noindex
    🔴 Fase 4 — Tab Indicadores en la consola: nav + sección + loadIndicatorCatalog() desde index.json
    🔴 Fase 5 — Tests y wiring: tests parametrizados del motor (regresión) + tests del middleware

    Backlog de tareas

    Roadmap de mejoras acordadas durante el desarrollo.

    TAREAS RELEASE V.0.4
    #39. Login sin rate limiting 🔴
    POST /auth/login (auth/routes.py:44) permite fuerza bruta ilimitada. Sumar slowapi (~5/min por IP) o lockout por usuario tras N intentos fallidos.
    #47. reload_engine vacía el tracker in-memory 🔴
    engine/registry.py reconstruye el PositionTracker vacío al rotar keys/exchange sin transferir estado. Un webhook en esa ventana ve has_position()=False y puede duplicar. Copiar posiciones del engine viejo o recargar de disco tras el swap.
    #34. Feed de precios solo-Bitunix usado para slippage de KuCoin 🔴
    _slippage_ok (main.py:80,143) valida contra el mark de Bitunix para todos los usuarios, incluido KuCoin. Hoy lo tapa el force_dry_run de KuCoin, pero rompe el día que vaya a live. Feed de precios por tipo de exchange.
    #40. forwarded-allow-ips=* confía headers de cualquier IP 🔴
    docker-compose.yml:36 hace que uvicorn confíe X-Forwarded-* de cualquier origen → spoofing de IP/proto si la app queda expuesta directa. Limitarlo a 127.0.0.1 (el proxy corre en el mismo host).
    #41. Endurecer sesión y hashing 🔴
    Sesión sin max_age (cookie robada = acceso permanente), SameSite=Lax→Strict, y scrypt en N=2^14 (mínimo OWASP) → subir a 2^17 con re-hash on login. Todo en main.py:476-481 y auth/security.py:17.
    #45. Race de posición duplicada con multi-worker 🔴
    El lock por símbolo (main.py:554) es solo in-process. Con --workers N, dos webhooks abren posición doble. Mínimo: forzar --workers 1 + assert al arranque; ideal: idempotencia en SQLite (INSERT OR IGNORE).
    #46. KuCoin TP/SL lanza NotImplementedError 🔴
    kucoin_client.py:256 levanta excepción tras setear leverage → KuCoin queda "abre pero siempre falla". Strippear TP/SL en el path KuCoin o implementar las conditional orders.
    #48. XSS almacenado en visor de logs y tabla de I+D 🔴
    Datos del server (mensajes de log, output del pipeline R&D) se inyectan en innerHTML sin escapar (app.js:879, app.js:232). El helper _esc() ya existe y se usa en la tabla de Users — falta aplicarlo acá. Fix de una tarde.
    #54. Contenido de tabs admin servido a todos los usuarios 🔴
    Los endpoints admin sí están gateados en backend, pero el HTML de los tabs admin (roadmap, arquitectura, prompts del pipeline I+D) viaja a cualquier usuario autenticado — visible con DevTools. No es bypass de auth, es fuga de info interna. Servirlo bajo endpoint autenticado o documentar el riesgo como aceptado.
    TAREAS RELEASE V.0.5
    #42. current_price hardcodeado a BTC en /health 🟠
    No es legacy sin uso: el frontend lo consume (app.js usa d.current_price como fallback y se lo pasa a renderExchPositions). Eliminarlo requiere también migrar el frontend al dict prices — no es un borrado limpio backend-only.
    #43. Redondear qty al step size por exchange/símbolo 🟠
    El sizing usa round(qty, 4) arbitrario (risk/manager.py:116). Bitunix tiene step size / min order size por símbolo (puede generar qty bajo el mínimo); KuCoin exige contratos enteros y un float lo hace reventar en runtime → el open falla en silencio. Pasar step_size/min_qty por exchange y snap con floor. Parte restante del sizing (#5).
    #44. Reorganizar carpeta logs/ (datos vs logs)
    logs/ mezcla código (trade_logger.py) con datos generados. trades.csv no es un log sino la fuente de verdad de operaciones. Separar a futuro: data/trades.csv, logs/bot.log, core/trade_logger.py. Refactor de bajo beneficio inmediato — solo cosmético/estructural.
    #49. Split de main.py (1997 líneas) en routers/ 🟠
    God-file con app, middleware, feed de precios, 30+ endpoints, todos los modelos Pydantic y la lógica de negocio. Split mecánico por dominio (routers/webhook.py, health.py, config.py, keys.py, exchanges.py, positions.py, admin.py) → main.py a ~150 líneas. Mayor impacto / menor riesgo.
    #50. Migraciones versionadas (tabla schema_version) 🟠
    db/schema.py usa una función ad-hoc por columna, sin versión ni rollback; no escala más allá de 4-5. Tabla schema_migrations + run_migrations() que aplica (version, sql) en orden.
    #51. Pinnear dependencias (lock file) 🟠
    requirements.txt usa >= en casi todo y mezcla dev con runtime. Un pip install futuro puede traer un major que rompe. Generar lock con hashes y separar dev/runtime.
    #52. _ensure_csv_header fuera del hot path de trades
    logs/trade_logger.py lee y reescribe el CSV en cada trade. Moverlo a la init del engine (una vez).
    #53. Limpiar código muerto
    Singleton UserEngineRegistry() duplicado nunca usado (engine/registry.py:208), alias _get_engine_or_401 = _get_engine_or_403 (main.py:108), imports a mitad de módulo y monkeypatches obsoletos en tests/conftest.py:143-147.
    #55. setInterval sin limpiar (polling) 🟠
    app.js:1914 arranca fetchHealth/fetchLogs cada 5s sin guardar los IDs; en re-runs se apilan. Guardar IDs y gatear el arranque.
    #56. Sin tests de frontend 🟠
    Cero cobertura JS frente a 232 tests de backend. Smokes con Playwright sobre login → render del dashboard → arranque del polling, sin refactorizar el monolito.
    #57. Accesibilidad: aria-live + focus trap 🟠
    Toasts sin aria-live (los lectores de pantalla pierden señales/trades), y el drawer de cuenta y el modal de Pine no atrapan ni restauran el foco. Jerarquía de headings rota (h2/h2/h2).
    #58. Sidebar sin breakpoint mobile 🟠
    style.css deja el sidebar fijo en 220px sin colapso ni hamburguesa <768px; en mobile el contenido queda apretado. Varias tablas anchas sin overflow-x: auto.
    TAREAS RELEASE V.0.6
    #59. Telegram Bot — notificación de operativas 🟠
    Mensaje automático al abrir o cerrar una posición. Incluir símbolo, lado, precio, qty y PnL en el cierre.
    #60. Telegram Bot — alerta de fallos de orden 🟠
    Notificar cuando llega un webhook de TradingView pero la orden no se ejecuta en el exchange. El banner en dashboard ya existe; falta el envío a Telegram.
    #61. Notificación en dashboard al recibir webhook 🟠
    Mostrar un indicador visual en tiempo real cuando llega una señal de TradingView, sin tener que consultar los logs.
    #62. Backtest stats — PnL en USDT 🟠
    El modo Backtest muestra trades y win rate pero no PnL total. Pendiente de los valores reales del backtest para hardcodearlos por símbolo.
    #63. DataProvider — chequeo de cobertura en ventanas yfinance truncadas 🟠
    Integridad de investigación. YFinanceProvider (backtest/data_provider.py) sirve intradía solo ~60d (15m) / ~730d (1h); un backtest forex con since=2024-01-01 recibe un frame truncado pero no vacío, lo escribe y lo backtestea como historia completa → "rentabilidad" sobre 60 días que se publica en el catálogo SEO. El EmptyDataError que el diseño prometía (criterio #7) no se implementó; solo hay un RuntimeError para frame totalmente vacío (download_data.py:63-64). Falta: comparar el rango devuelto vs el since pedido y fallar/flaggear como baja-confianza si es mucho más corto.
    #64. Volumen inexistente en Forex (yfinance) 🟠
    Los tickers =X de Yahoo devuelven volumen 0; _flatten_yfinance (data_provider.py:162-164) lo setea a 0.0 en silencio. Cualquier estrategia con VWAP o filtro de volumen (p. ej. la estrella EMA+VWAP) se degrada sin aviso sobre FX. Avisar/bloquear estrategias volume-dependientes para forex.
    #65. Acciones: usar precios ajustados (splits/dividendos) 🟠
    data_provider.py:139 usa auto_adjust=False y Close crudo (descarta Adj Close). En backtests largos de equities los splits/dividendos meten saltos artificiales. Usar auto_adjust=True (ajusta el OHLC) para la clase stock.
    #66. Commodities backtesteadas con futuros, no spot 🟠
    XAUUSD→GC=F, WTI→CL=F (data_provider.py:30-33) son futuros (front-month, con roll gaps), no el spot que el símbolo implica. Menor (commodities quedan research-only), pero documentar el mismatch de instrumento para no leer mal los resultados.
    #67. Refactor Backtester v.2 · Fase 1 — Red de tests del loop de simulación (_run/_run_atr/_stats) 🟠
    Caracterización del comportamiento ACTUAL del motor: tests sobre casos conocidos (trade ganador, detección de SL/TP, fees) que sirvan de red de seguridad antes de tocar el código.
    #68. Refactor Backtester v.2 · Fase 2 — Fills a la apertura de la vela siguiente (corrección look-ahead) 🟠
    Introducir ExecutionModel y mover el fill de close[i] a open[i+1]; actualizar los valores esperados en los tests y documentar el delta de resultados vs. la versión anterior.
    #69. Refactor Backtester v.2 · Fase 3 — Métricas correctas: Sharpe por timeframe + Sortino/Calmar + benchmark 🟠
    Extraer backtest/metrics.py, corregir BARS_PER_YEAR por timeframe, agregar Sortino, Calmar, máximo de pérdidas consecutivas, duración media de trade y benchmark buy&hold sobre la misma ventana.
    #70. Refactor Backtester v.2 · Fase 4 — Modelo de slippage + penalización gap-through en SL/TP 🟠
    Agregar slippage configurable y penalización gap-through en ExecutionModel: si la vela abre por debajo del SL, el fill es al open (peor precio), no al precio del stop.
    #71. Refactor Backtester v.2 · Fase 5 — Walk-forward / out-of-sample para la optimización 🟠
    Implementar backtest/validation.py con walk-forward anchored y rolling; reescribir optimize_adx para optimizar en el set de entrenamiento y reportar resultados en el set de test (fuera de muestra).
    #72. Catálogo de Indicadores v.1 · Fase 1 — Generalizar el motor SSG (parametrizado por entidad) 🟠
    Extraer tools/strategy_site/build.py a tools/catalog_site/build.py parametrizado por entidad (entity, data_dir, out_dir, base_path); Estrategias se reescribe como instancia del motor genérico; los tests de strategy_site siguen verdes como red de regresión.
    #73. Catálogo de Indicadores v.1 · Fase 2 — Capa de datos: 10 fichas JSON de indicadores 🟠
    Crear content/indicators/_site.json (base_path /indicadores) y 10 fichas JSON (EMA, VWAP, RSI, ADX, ATR, Bollinger, Supertrend, Donchian, Volume MA, SMA) con contenido derivado de backtest/strategy.py y de los bloques indicators[] de cada estrategia.
    #74. Catálogo de Indicadores v.1 · Fase 3 — Generar y servir /indicadores/ (middleware + Apache + noindex) 🟠
    Correr el motor para producir web-public/indicadores/; agregar el middleware en main.py para URLs limpias /indicadores/<slug>; crear la regla Apache; verificar que las páginas generadas incluyen meta robots noindex,nofollow.
    #75. Catálogo de Indicadores v.1 · Fase 4 — Tab Indicadores en la consola 🟠
    Agregar el botón de nav, la sección tab-indicadores y loadIndicatorCatalog() en app.js consumiendo /indicadores/index.json; las cards navegan a /indicadores/<slug>.
    #76. Catálogo de Indicadores v.1 · Fase 5 — Tests parametrizados del motor + middleware 🟠
    Crear tests/test_catalog_site_build.py con tests parametrizados del motor para ambas entidades (regresión Estrategias + Indicadores) y tests/test_public_indicator_pages.py para el middleware de URLs limpias; documentar el comando de build de ambos catálogos.

    Proyectos completados

    Proyectos terminados — todas sus fases integradas.

    #1. Migración a multi-exchange
    Descripción del proyecto

    Peticiones detalladas para tools/gga_pipeline.py, una por fase. Ejecutar en orden y mergear cada fase antes de la siguiente (cada una asume que la anterior ya está en la base). En PowerShell: $task = @' … '@ y luego python tools/gga_pipeline.py $task --run-tests --allow-dirty.

    Fases de desarrollo con pipeline GGA
    🟢 Fase 1 — Fundación (modelo de datos + ExchangePort)
    🟢 Fase 2 — KuCoinClient (aislado)
    🟢 Fase 3 — Routing del engine + credenciales por exchange ⚠️ alto riesgo
    🟢 Fase 4 — UI del tab Exchange
    #2. Refactorización web SEO estrategias
    Descripción del proyecto

    Catálogo público de estrategias orientado a SEO: cada estrategia es un dato (content/strategies/*.json) que tools/strategy_site/build.py convierte en una página estática pre-renderizada en web-public/estrategias/. Peticiones detalladas para tools/gga_pipeline.py, una por fase. Ejecutar en orden y mergear cada fase antes de la siguiente (cada una asume que la anterior ya está en la base). En PowerShell: $task = @' … '@ y luego python tools/gga_pipeline.py $task --run-tests --allow-dirty.

    Fases de desarrollo con pipeline GGA
    🟢 Fase 1 — Completar el contenido del catálogo
    🟢 Fase 2 — Backend de captura de newsletter
    🟢 Fase 3 — Infra SEO (imágenes OG, URLs limpias, deploy)
    🟢 Fase 4 — Unificar consola y catálogo

    Tareas Completadas

    Trabajo ya entregado, agrupado por release.

    RELEASE NOTES V.0.3
    #29. Refactorización web SEO estrategias - Fase 1 — Completar el contenido del catálogo 🟠
    Cada estrategia es un dato (content/strategies/*.json) que tools/strategy_site/build.py convierte en una página estática pre-renderizada en web-public/estrategias/, optimizada para SEO. Se ejecuta por fases vía el pipeline GGA. Escala a 100+ estrategias.
    #30. Refactorización web SEO estrategias - Fase 2 — Backend de captura de newsletter 🟠
    Endpoint público POST /api/newsletter/subscribe (email validado, inserción idempotente) + tabla newsletter_subscribers en SQLite + endpoint admin para listar suscriptores + handler fetch en el formulario de las páginas. 251 tests pasan.
    #31. Refactorización web SEO estrategias - Fase 3 — Infra SEO (OG, URLs limpias, deploy) 🟠
    Imágenes Open Graph 1200x630 por estrategia (Pillow build-only), URLs limpias sin .html con snippet Apache para el operador, y regeneración del catálogo en un stage del Dockerfile (sin meter deps de build en runtime). Primera fase APPROVED por el pipeline; 253 tests pasan.
    #32. Refactorización web SEO estrategias - Fase 4 — Unificar consola y catálogo 🟠
    El tab Estrategia ahora pinta las cards desde /estrategias/index.json (generado por build.py) y enlaza a las páginas públicas /estrategias/<slug>; se eliminó la documentación inline duplicada del index.html monolítico. 256 tests pasan.
    #33. Reconciliación de positionId frágil (sleep 0.8s) 🟠
    El asyncio.sleep(0.8) fijo se reemplazó por un helper _capture_position_id() que reintenta con backoff 0.5/1/2s en ambos paths (webhook y orden manual). Si tras los reintentos no aparece el positionId, ahora devuelve status=error (HTTP 502) con un motivo que pide reconciliar, en vez del engañoso ok+warning. 280 tests pasan.
    #35. KuCoinAPIError ignora el fast-fail de errores permanentes 🔴
    _place_order_with_retry (main.py:117,124) solo trata BitunixAPIError como permanente; un error de auth de KuCoin se reintenta a ciegas dos veces. Agregar rama except KuCoinAPIError con su set de códigos permanentes.
    #36. Cierre con fallo de exchange no registra la pérdida 🔴
    Si el close falla (main.py:265), no se llama a tracker.close_position ni record_trade_result: el tracker sigue "abierto" y el límite de pérdida diaria no ve la pérdida hasta el próximo reconcile. Hay ventana donde is_trading_allowed() miente.
    #37. Money math en float (PnL y límite de pérdida diaria) 🔴
    risk/manager.py acumula PnL en floats; deriva error con muchos trades. Migrar a decimal.Decimal o, mínimo, clamp a 2dp antes de comparar con el circuit-breaker.
    #38. Webhook sin protección de replay 🔴
    POST /webhook/{token} (main.py:489) no valida timestamp ni nonce: un body capturado se reenvía a voluntad (open → close → open). Exigir un campo ts y rechazar señales fuera de ±30s; opcional, nonce con TTL.
    #77. Cards de la página Trading a ancho completo (uniformidad de layout)
    La página Trading (#tab-configuracion) usaba un wrapper .cfg-page con max-width: 860px, por lo que sus cards quedaban más angostas que las del resto de páginas. Se quitó el límite para que ocupen todo el ancho del contenido, uniformando el layout con las demás pestañas.
    RELEASE NOTES V.0.2
    #24. Multi-exchange · Fase 1 — Fundación: modelo de datos + ExchangePort 🟠
    Tabla exchanges por usuario (credenciales cifradas por exchange + passphrase) con migración idempotente que copia las creds Bitunix existentes. Abstracción ExchangePort que BitunixClient cumple. Aditivo, sin cambiar el trading.
    #25. Multi-exchange · Fase 2 — KuCoinClient (HMAC + passphrase) 🟠
    Cliente KuCoin Futures que implementa ExchangePort, con su firma propia (HMAC-SHA256 + passphrase). Aislado y testeado contra la interfaz.
    #26. Multi-exchange · Fase 3 — Routing del engine + credenciales por exchange 🟠
    El engine construye el cliente del exchange activo del usuario; alta/baja y selección de exchange por usuario. Tocó el camino del dinero — review vinculante de Claude y aprobación del Product Owner.
    #27. Multi-exchange · Fase 4 — UI: tab Exchange con selector de N exchanges 🟠
    Tab Exchange reestructurado para listar, seleccionar y gestionar N exchanges (Bitunix + KuCoin), con paneles y credenciales por exchange.
    #28. Catálogo web · Fase 1 — Completar el contenido del catálogo 🟠
    Documentación completa de las 3 estrategias restantes (Trend Pullback, Supertrend ATR, RSI Momentum) portada desde la consola al catálogo: indicadores, señales, gestión de riesgo, tablas de backtest reales y fuentes.
    RELEASE NOTES V.0.1
    #1. Aplicación desplegada en el VPS de profesionalhosting
    El bot quedó corriendo en el VPS con proceso persistente. Deploy documentado en el runbook del proyecto (Plesk + Apache reverse proxy).
    #2. Webhook expuesto con URL pública
    El endpoint /webhook/{token} quedó accesible desde internet: TradingView ya puede dispararle POST.
    #3. Alert de TradingView configurada
    Alerta creada apuntando a la URL pública con el payload correcto (token, symbol, side, action, price).
    #4. except del webhook: validación separada de errores internos 🟠
    El webhook usaba except (ValidationError, Exception) que enmascaraba bugs internos como 422. Ahora ValidationError y JSON malformado dan 422; cualquier error interno propaga a 500 sin tragarse. 47 tests verdes.
    #5. Imports movidos al top del módulo
    Sacados los import csv as _csv / datetime as _dt de dentro de las funciones (/health, get_bots_stats, positions_history en main.py y restore_daily_pnl en user_engine.py). Ahora se importan una vez arriba.
    #6. 🟠 Eliminado get_position() muerto del cliente Bitunix
    Se borró bitunix_client.get_position() — el método con query string que disparaba el error 10007 de firma. Tenía cero callers; get_position_id() y get_position_info() (ambos vía get_all_positions, sin query string) ya cubrían su función.
    #7. 🟠 El webhook ahora valida el símbolo 🟠
    POST /webhook/{token} aplica un whitelist explícito contra BOT_ASSETS (fail-closed) antes de cualquier lógica de orden: un símbolo desconocido se rechaza con "unsupported symbol".
    #8. 🟠 Config en vivo sin reiniciar 🟠
    Con el refactor a motores por usuario (UserEngine), POST /api/config actualiza engine.config en caliente y persiste en users.db. _compute_sl_tp, el slippage y el sizing leen de engine.config, así que sl_pct/capital/leverage aplican sin reiniciar.
    #9. 🟠 Fuente de precio = Bitunix (WebSocket muerto eliminado) 🟠
    El price feed ahora hace polling al endpoint de tickers de Bitunix (markPrice), no a Binance. Se eliminó el PriceWebSocketClient sin usar. El precio de referencia y las posiciones viven en el mismo exchange.
    #10. Doc de arquitectura: formato de logs incorrecto
    Aclarado en el tab Arquitectura que el JSON existe solo en la consola/stdout (vía JsonFormatter) mientras que logs/bot.log es pipe-delimited. Completada la tarjeta de trade_logger.py, que ocultaba su rol de configurar el logging del sistema.
    #11. Slippage check + precio de fill real 🔴
    El webhook rechaza la orden si el precio de la señal difiere del mercado más que el slippage máx. (configurable, 0.5% por defecto). Tras abrir, se registra el avgOpenPrice real del exchange como entry_price (webhook y manual), corrigiendo el PnL.
    #12. 🔴 Position sizing con leverage y margen 🔴
    Leverage configurable (5x por defecto) que el bot aplica con set_leverage() al abrir. Validación de margen (can_open): si el margen requerido + el ya usado supera el capital, rechaza la orden. Configurable desde el panel.
    #13. 🔴 SL/TP con fuente única de verdad 🔴
    config.py ahora usa 1.2/1.5 (lo backtesteado). _compute_sl_tp lee de get_settings() y ManualOrderRequest hereda de config si no se especifica. Eliminada la triple fuente de verdad.
    #14. 🔴 Lock por símbolo — sin doble apertura 🔴
    asyncio.Lock por símbolo serializa open/close concurrentes. Webhook y operativa manual comparten el mismo lock, haciendo atómico el check-then-act.
    #15. 🔴 PnL neto de comisión 🔴
    close_position() resta la comisión; el PnL neto va al risk_manager, al CSV y al realized_pnl. El circuit breaker y los win rates ya no sobreestiman ganancias. Bonus: el cierre por webhook en dry-run ya no llama al exchange.
    #16. 🔴 Día del circuit breaker en UTC 🔴
    DailyStats y _restore_daily_pnl ahora usan datetime.now(timezone.utc), alineados con los timestamps UTC de los trades. El "día" ya no se rompe entre medianoche local y UTC.
    #17. 🟡 Log usa _effective_dry_run() 🟠
    El log del manual order ya usa el valor efectivo de dry-run, no la instancia global vieja.
    #18. Comisiones del exchange en historial 🟠
    Columna Commission en el historial de posiciones con el fee estimado round-trip (apertura + cierre) al 0.05% taker de Bitunix.
    #19. Bot "Exchange" en catálogo de bots
    Las posiciones abiertas directamente en Bitunix (sin pasar por la app) se identifican con initiator="exchange" y aparecen bajo el bot Exchange.
    #20. Columna "Last Activity" de bots
    Muestra el tiempo relativo del último trade en CSV para cada bot (hace 2h, hace 3d…).
    #21. Historial de posiciones limpio
    Solo muestra cierres (sin duplicados de apertura) y sin la columna Action innecesaria.
    #22. Retry en webhook + banner de fallos 🟠
    Reintentos con backoff en órdenes fallidas y banner rojo en dashboard con el detalle del error.
    #23. exchange_position_id en runtime 🟠
    Al abrir una posición, el sistema espera 0.8s y consulta el positionId del exchange para guardarlo localmente. Los cierres ya no necesitan un GET extra.

    Diseño técnico — Integración exchange Saxo Bank

    Documento técnico previo a las 5 fases del pipeline GGA. Define el cómo —arquitectura, componentes, datos, endpoints y pruebas— en la etapa SDD, antes de ejecutar.

    Resumen del diseño técnico

    Enfoque y decisiones

    Saxo Bank es el primer broker de Forex del bot. A diferencia de Bitunix y KuCoin (firma HMAC estática, perpetuos cripto), Saxo autentica con OAuth2 (access token de vida corta + refresh token), opera FX spot, exige resolver el instrumento a un Uic antes de operar, tiene su propio feed de precios FX y un entorno SIM para paper trading. La integración es ADITIVA y se entrega en 5 fases del pipeline GGA (una tarea SDD = una fase): no cambia el path live de Bitunix/KuCoin. Como con KuCoin, Saxo arranca forzado a SIM/dry-run hasta aprobación explícita del Product Owner.

    Diagrama de arquitectura

    Elementos implicados
    📊
    TradingView
    Alerta de señal FX (EURUSD, GBPUSD…)
    →
    🔐
    Webhook
    Valida token y símbolo FX (/webhook/{token})
    →
    ⚙️
    UserEngine
    Riesgo, SL/TP, sizing; rutea al exchange activo
    →
    🏭
    factory
    type "saxo" → SaxoClient
    →
    🏦
    SaxoClient
    OAuth2 + resolución de Uic + órdenes FxSpot
    →
    ☁️
    Saxo OpenAPI
    SIM (paper) / LIVE (gateway.saxobank.com)

    En paralelo, un feed de precios FX propio (no el de Bitunix) alimenta el slippage y el PnL de los símbolos Forex, y un refresh de token OAuth2 mantiene vivo el access token (~20 min) con el refresh token cifrado.

    Componentes implicados

    Archivos y módulos
    🆕
    exchange/saxo_client.py
    NUEVO

    SaxoClient conforme a ExchangePort: OAuth2 (refresh de token), resolución y cache de Uic, órdenes FxSpot (+ OCO para SL/TP), normalización de posiciones al shape del engine y soporte dry-run/SIM.

    🏭
    exchange/factory.py
    Modificado

    Agregar la rama "saxo" a create_exchange_client() con sus parámetros OAuth (app key/secret, tokens, environment).

    🗃️
    db/schema.py · db/exchanges.py
    Modificado

    Columnas OAuth en exchanges (migración idempotente) y helpers para leer/guardar tokens cifrados del exchange activo tipo saxo.

    ⚙️
    engine/registry.py · user_engine.py
    Sin cambio de lógica

    El routing al exchange activo ya existe; saxo entra por la factory. Verificar que un usuario saxo cargue creds OAuth en memoria (fail-closed).

    🔗
    main.py
    Modificado

    Endpoints OAuth (authorize/callback), gestión de exchange saxo en /api/exchanges, feed de precios FX y validación de símbolos Forex en el webhook.

    🖥️
    web-private/ (index.html, app.js)
    Modificado

    UI del tab Exchange para Saxo: conectar por OAuth, badge SIM, selección de instrumentos FX. Nunca persiste secretos en el cliente.

    Modelo de datos afectado

    Tabla exchanges — columnas nuevas (nullable, solo type=saxo)
    CampoTipoDescripción
    typetext (CHECK)Añade el valor 'saxo' al CHECK existente (bitunix | kucoin | saxo).
    enc_app_keytext (Fernet)App key OAuth2 cifrada.
    enc_app_secrettext (Fernet)App secret OAuth2 cifrado.
    enc_access_tokentext (Fernet)Access token (~20 min) cifrado; se refresca en runtime.
    enc_refresh_tokentext (Fernet)Refresh token cifrado para renovar el access token.
    token_expires_atinteger (epoch)Expiración del access token; dispara el refresh.
    environmenttext'sim' | 'live' (default 'sim') — nunca live sin aprobación del PO.

    Las columnas existentes enc_api_key / enc_api_secret / enc_passphrase quedan NULL para filas saxo. Migración idempotente, sin tocar filas bitunix/kucoin.

    Endpoints a utilizar

    Internos (FastAPI) y externos (Saxo OpenAPI)
    🔧 API Interna — FastAPI
    MétodoRutaUsoDescripción
    GET/api/exchanges/saxo/authorizeNUEVOInicia el flujo OAuth2: redirige a la pantalla de consentimiento de Saxo.
    GET/api/exchanges/saxo/callbackNUEVORecibe el code, lo intercambia por access/refresh token y los guarda cifrados.
    POST/api/exchangesExtendidoAlta de exchange type=saxo con app key/secret y environment (write-only).
    POST/api/exchanges/activeExistenteActiva Saxo como exchange del usuario.
    INBOUND/webhook/{token}ExistenteSeñales FX; valida que el símbolo sea un par Forex cuando saxo está activo.
    🏦 Exchange Externo — Saxo Bank OpenAPI
    MétodoRutaUsoDescripción
    POST{auth}/tokenOAuth2Renueva el access token (~20 min) con el refresh token.
    GET/ref/v1/instrumentsUic lookupResuelve el Uic de un par FX (EURUSD → Uic); resultado cacheado.
    POST/trade/v2/ordersOrdenOrden FxSpot: AccountKey, Uic, BuySell, Amount, OrderType (+ OCO para SL/TP).
    DELETE/trade/v2/orders/{orderId}CancelarCancela una orden pendiente.
    GET/port/v1/positionsPosicionesPosiciones abiertas; se normalizan al shape del engine.
    GET/trade/v1/infopricesPrecio FXPrecio del instrumento para el feed FX (slippage / PnL).

    Tests a realizar

    Cobertura que acompaña la implementación
    OAuth2: refresh de token
    Renueva un access token expirado con el refresh token; un refresh inválido falla closed (no se opera, no engine).
    SaxoClient conforme a ExchangePort
    isinstance(SaxoClient(...), ExchangePort) y mismas firmas/return shapes que el engine consume.
    dry-run / SIM sin red live
    En SIM/dry-run, place_order/cancel no pegan al gateway LIVE (mock aserta que no hubo llamada).
    Resolución de Uic + cache
    EURUSD → Uic correcto; la segunda llamada usa cache y no vuelve a pegar a /ref/v1/instruments.
    Normalización de posiciones
    Una respuesta de /port/v1/positions de Saxo se normaliza al shape que espera el engine (symbol, side, qty, avg price, id).
    Routing al exchange activo
    Con saxo activo, las órdenes van por SaxoClient; Bitunix y KuCoin siguen idénticos (sin regresión).
    Validación de símbolos FX
    El webhook acepta pares FX válidos y rechaza símbolos no-Forex cuando el exchange activo es saxo.
    Secretos nunca expuestos
    App secret, access/refresh token nunca aparecen en logs ni en respuestas de endpoints (GET /api/exchanges sin secretos).
    Saxo arranca en SIM
    Un alta de saxo queda en environment=sim/dry-run; no hay activación live automática sin aprobación del Product Owner.

    Prueba proyecto desarrollado

    Runbook de validación end-to-end (en SIM)

    Cómo verificar de punta a punta que la integración Saxo funciona, siempre en SIM, antes de que el Product Owner apruebe el paso a LIVE. Cada paso confirma una pieza del flujo de la sección anterior.

    1. Preparar el entorno SIM — crear una app OAuth2 en el portal de developers de Saxo (entorno SIM), obtener app key / app secret, y tener una cuenta SIM con saldo de paper.
    2. Dar de alta el exchange — en el tab Exchange, agregar uno type=saxo con la app key/secret y environment=sim (write-only, nunca se muestran los secretos).
    3. Conectar por OAuth2 — lanzar /api/exchanges/saxo/authorize, completar el consentimiento en Saxo y volver por /callback; verificar que el exchange queda con badge SIM y tokens cifrados guardados.
    4. Activar Saxo — POST /api/exchanges/active con type=saxo; confirmar que /health refleja el exchange activo en dry-run/SIM.
    5. Orden manual de prueba — desde la consola de exchange, abrir una orden FxSpot en un par FX (p. ej. EURUSD); verificar en los logs que resuelve el Uic y envía a /trade/v2/orders en SIM (sin pegar a LIVE).
    6. Señal por webhook — disparar una alerta de TradingView (o un POST manual) a /webhook/{token} con un símbolo FX; verificar que el engine la valida, calcula SL/TP y sizing, y rutea por SaxoClient.
    7. Verificar posición, precio y cierre — la posición aparece en Posiciones con precio del feed FX; cerrar la posición y comprobar que se registra el trade y el PnL.
    8. Refresh de token — dejar pasar >~20 min (o forzar la expiración) y confirmar que el access token se renueva solo y la operativa continúa sin reconectar.
    9. No regresión — cambiar el exchange activo a Bitunix/KuCoin y comprobar que operan idéntico; los símbolos cripto siguen funcionando.
    10. Suite automática — correr las pruebas; deben quedar verdes.
    python -m compileall . && pytest -q tests/

    Criterio de aprobación: todos los pasos OK en SIM + suite verde. Recién entonces el Product Owner puede aprobar el paso a LIVE (cambiar environment a 'live').

    Fases pipeline GGA

    Una tarea SDD = una fase del pipeline

    Las fases derivadas de este diseño, que el pipeline GGA ejecuta en orden (mergeando cada una antes de la siguiente):

    🟢 Fase 1 — Fundación: modelo de credenciales OAuth2 + tipo "saxo" d4db8ca
    Build the FOUNDATION for integrating Saxo Bank as a new exchange type, WITHOUT changing any current live-trading behavior. Bitunix and KuCoin must keep working exactly as today.
    
    Context: the multi-exchange foundation is already in master — exchange/port.py (ExchangePort Protocol), an `exchanges` table with per-user rows (type, Fernet-encrypted api_key/api_secret + nullable passphrase, base_url, enabled, one active per user), db/exchanges.py read helpers, exchange/factory.py routing, and per-exchange UI. Both Bitunix (HMAC) and KuCoin (HMAC + passphrase) authenticate with a STATIC api_key/api_secret pair. Saxo is DIFFERENT: it authenticates with OAuth2 — an app key/secret plus a short-lived access token and a refresh token — not a static signing secret.
    
    Deliver three ADDITIVE pieces:
    1. OAuth2 credential model: extend the exchanges storage so a "saxo" row can hold, Fernet-encrypted: app_key, app_secret, refresh_token and the current access_token, plus a token_expiry timestamp (the expiry itself can be plaintext). Add NULLABLE columns; do not break Bitunix/KuCoin rows. Provide an IDEMPOTENT migration (running it twice is a no-op).
    2. Recognize "saxo" as a valid exchange type across the type validators/enums, WITHOUT wiring any client or routing yet.
    3. db/ read+write helpers to fetch and UPDATE a user's Saxo OAuth tokens (decrypted only in memory), so a later refresh job can persist rotated tokens.
    
    HARD CONSTRAINTS (exchange integration is a Critical Component per AGENTS.md):
    - Do NOT change order placement, Risk Manager, leverage, SL/TP, daily-loss, or webhook auth.
    - Do NOT build a SaxoClient or any routing in this phase.
    - Tokens/secrets stay Fernet-encrypted at rest; never plaintext, never logged, never returned by any endpoint.
    - Bitunix and KuCoin rows and flows stay byte-for-byte equivalent.
    
    Tests: idempotent migration; a "saxo" row round-trips the encrypted OAuth fields; existing bitunix/kucoin rows are unaffected; type validators accept "saxo".
    🔴 Fase 2 — SaxoClient aislado (OpenAPI REST + OAuth + SIM)
    Implement a Saxo Bank OpenAPI client for Forex trading. This phase is ISOLATED: do NOT wire it into the engine, the registry, the factory, or any live path yet.
    
    Context: Phase 1 added OAuth2 credential storage for a "saxo" exchange type (encrypted app_key, app_secret, refresh_token, access_token, token_expiry) and read/write helpers. exchange/port.py defines ExchangePort, which both BitunixClient and KuCoinClient conform to. The port was shaped around crypto PERPETUAL futures (place_order, set_leverage, get_position_id, positionId, etc.).
    
    Deliver:
    1. A SaxoClient class (exchange/saxo_client.py) implementing ExchangePort with the SAME method names and return shapes the engine consumes. Saxo trades SPOT FX (AssetType FxSpot) via OpenAPI REST, so NORMALIZE Saxo responses into the engine-expected keys (symbol, side, size/qty, entryPrice, positionId, etc.). Document every normalization.
    2. OAuth2 token manager: use the refresh_token to obtain/renew the access token (Saxo access tokens are short-lived, ~20 min), refresh PROACTIVELY before expiry, be safe under concurrency, and surface rotated tokens so the caller can persist them via the Phase 1 helpers. Never block a trade on an avoidable token refresh.
    3. Instrument (Uic) resolution: map an FX symbol (e.g. EURUSD) to its Saxo Uic via /ref/v1/instruments, with caching. Orders need Uic + AssetType.
    4. Map ExchangePort onto OpenAPI: place_order → POST /trade/v2/orders (AccountKey, Uic, AssetType FxSpot, BuySell, Amount, OrderType, plus related/OCO orders for SL/TP); positions → /port/v1/positions; balances → /port/v1/balances. For methods with no FX equivalent (e.g. set_leverage — FX margin is account-level, not per-order) implement a DOCUMENTED no-op or the closest correct behavior and STATE it in the implementer report.
    5. dry_run via the Saxo SIM environment (the /sim/openapi gateway), which is real paper trading; live uses the LIVE gateway. Mask app_key in logs; never log secrets, tokens or signing material.
    
    CRITICAL: follow Saxo's OFFICIAL OpenAPI docs for exact endpoints, the OAuth2 token endpoint, order payloads, AssetType and response shapes. Do NOT invent endpoints. State any uncertain assumption in the implementer report.
    
    CONSTRAINTS:
    - Do NOT modify BitunixClient, KuCoinClient, the engine, the registry, the factory, Risk Manager, or any live path. This phase only ADDS exchange/saxo_client.py and its tests.
    - Secrets/tokens never appear in logs or endpoint responses.
    
    Tests: unit tests with mocked HTTP for the OAuth2 refresh flow, Uic resolution, a place_order FxSpot payload (live + dry_run/SIM), SL/TP related-order construction, and normalization of a sample Saxo positions/balances response into the engine-expected shape. Assert SaxoClient satisfies ExchangePort.
    🔴 Fase 3 — Routing del engine + gestión de credenciales OAuth ⚠️ alto riesgo
    Wire Saxo routing into the engine and add per-user Saxo OAuth credential management. This phase touches the LIVE money path: be conservative and keep existing Bitunix/KuCoin behavior identical.
    
    Context (Phases 1 and 2 are MERGED): OAuth2 storage for "saxo" exists; exchange/saxo_client.py SaxoClient conforms to ExchangePort with an OAuth2 token manager, Uic resolution and SIM dry_run. exchange/factory.py builds BitunixClient/KuCoinClient from an ActiveExchange; db/exchanges.py get_active_exchange returns decrypted creds in memory; UserEngine.exchange_client() and the registry route by the active exchange.
    
    Deliver:
    1. Extend the factory to return SaxoClient for type "saxo", owning its OAuth constructor differences (app_key/secret + tokens instead of api_key/secret). A user whose active exchange is Saxo routes to SaxoClient; Bitunix/KuCoin users are completely unaffected.
    2. Per-user Saxo onboarding: implement the OAuth2 flow so a user can connect a Saxo account — either the authorization-code redirect flow (redirect to Saxo, capture the code at a callback, exchange it for refresh+access tokens) or accept a manually issued developer token — and store everything encrypted. Reuse the connectivity-probe-before-enable pattern (a real SIM auth check); on failure do not store / force dry_run.
    3. A background token-refresh task (or refresh-on-load) that keeps each Saxo engine's access token valid using the stored refresh_token, persisting rotated tokens via the Phase 1 helpers, so sessions do not die after ~20 min.
    
    HARD CONSTRAINTS (Critical Component, AGENTS.md, requires Product Owner approval):
    - Bitunix and KuCoin users MUST keep trading identically. Users with no Saxo row are untouched.
    - Do NOT change order placement, Risk Manager, leverage, SL/TP, daily-loss, or webhook auth.
    - Fail-closed: missing/undecryptable/expired-and-unrefreshable Saxo creds means no engine loaded.
    - Saxo starts in SIM/dry_run; do NOT enable live Saxo trading automatically — live requires explicit Product Owner approval.
    - Tokens/secrets stay Fernet-encrypted at rest; never plaintext, never logged, never returned.
    
    Tests: the factory returns SaxoClient for type saxo; routing picks the active exchange; Bitunix/KuCoin users still route identically; a failing SIM probe is not stored / forces dry_run; the refresh task rotates and persists tokens; secrets never appear in endpoint responses.
    🔴 Fase 4 — Feed de precios FX + símbolos/validación de webhooks Forex
    Make the engine handle FOREX correctly for Saxo: price feed, symbol mapping and webhook validation. The current price feed and slippage check are crypto/Bitunix-only.
    
    Context (Phases 1-3 MERGED): Saxo routing works and SaxoClient resolves FX Uic and trades FxSpot. BUT the engine's shared price cache is fed only by Bitunix tickers, and the webhook/signal model expects crypto-perpetual symbols (e.g. BTCUSDT). _slippage_ok and the open path read a Bitunix mark for every user regardless of the active exchange (a known audit finding).
    
    Deliver:
    1. A per-exchange-type price source: when the active engine is Saxo, obtain FX marks from Saxo (e.g. /trade/v1/infoprices or the SaxoClient) for the symbols that user trades, instead of the Bitunix ticker cache. The slippage reference and the "no mark price" guard must use the ACTIVE exchange's price, with messages that name the real exchange (not a hardcoded "Bitunix").
    2. FX symbol handling in the webhook/signal layer: accept TradingView FX symbols (e.g. EURUSD / EUR_USD), validate them for a Saxo engine, and pass them to Uic resolution. Keep crypto symbol validation unchanged for Bitunix/KuCoin engines.
    3. Ensure PnL/position math uses FX-appropriate fields (Amount in base currency, FxSpot semantics) without breaking the crypto path.
    
    CONSTRAINTS:
    - Do NOT regress the Bitunix/KuCoin price feed, slippage or symbol validation.
    - Do NOT change Risk Manager math, daily-loss or webhook auth.
    - No secrets in logs.
    
    Tests: a Saxo engine's slippage check uses an FX mark (not a Bitunix one); an FX symbol validates for Saxo and is handled correctly for a crypto engine; the "no mark price" rejection names the active exchange; the Bitunix/KuCoin paths are unchanged.
    🔴 Fase 5 — UI del tab Exchange para Saxo (OAuth + Forex)
    Add Saxo Bank to the dashboard Exchange tab and account drawer, including the OAuth connect flow. FRONTEND ONLY (web-private/: index.html, app.js, style.css). The Phase 1-4 backend is already in master.
    
    Backend already available (do NOT change it): GET /api/exchanges lists configured exchanges (metadata only, including a "saxo" type and whether it is connected / in SIM); the Saxo onboarding endpoint(s) from Phase 3 start the OAuth flow or accept a token and run a SIM probe; POST /api/exchanges/active switches the active exchange; /health drives the status bar for the active engine.
    
    Deliver (vanilla HTML/CSS/JS, match existing patterns, no new deps):
    1. Let the user add and manage a Saxo exchange: trigger the OAuth2 connect flow (redirect to Saxo and back, or paste a developer token), show connection + token status (connected, SIM/dry_run, expiry/refresh) and surface the probe result. Saxo has NO api_secret/passphrase form like Bitunix/KuCoin — show OAuth-appropriate fields only.
    2. Generalize the Exchange tab selector and status bar so Saxo appears alongside Bitunix/KuCoin, shows it is in SIM/dry_run pending live approval, and can be set active via POST /api/exchanges/active.
    3. FX-aware hints where the UI assumes crypto symbols (e.g. the symbol/instrument helper), without breaking the crypto experience.
    4. Bump the ?v= query string on app.js and style.css so browsers refetch.
    
    CONSTRAINTS:
    - FRONTEND ONLY: do NOT touch Python/backend, webhook, risk or order code.
    - Never persist tokens/secrets in client storage; forms are write-only; the GET never returns secrets.
    - HTML-escape all dynamic values.
    - Keep the experience identical for users who only have Bitunix/KuCoin.
    - Keep index.html balanced (equal div open/close).

    Estadísticas ejecución Pipeline GGA

    Veredicto, agentes, tiempo y costo por fase
    FaseVeredictoAgentesTiempoCostoCommit
    Fase 1 — Fundación OAuth2APPROVED🧠→🛠️→🔍→✅~17 min$2.93d4db8ca
    Fase 2 — SaxoClient aisladoPendiente————
    Fase 3 — Routing del engine ⚠️Pendiente————
    Fase 4 — Feed FX + webhooks ForexPendiente————
    Fase 5 — UI tab ExchangePendiente————

    Fase 1 corrida hoy por el pipeline; Fases 2-5 pendientes.

    Estimación proyecto desarrollado por un humano

    Senior Software Engineer · por fase

    Tiempo orientativo para un Senior Software Engineer (el rol del agente Implementer) implementando cada fase a mano —código + tests + revisión—, sin el pipeline multi-agente.

    FaseEstimaciónNotas
    Fase 1 — Fundación OAuth22-3 díascolumnas cifradas + migración idempotente + helpers de token
    Fase 2 — SaxoClient aislado5-7 díasOpenAPI REST + flujo OAuth2 + resolución de Uic + entorno SIM + normalización (la más densa)
    Fase 3 — Routing del engine ⚠️3-5 díastoca el path live + rotación de tokens; conservador
    Fase 4 — Feed FX + webhooks Forex2-3 díasfeed de precios propio + validación de símbolos Forex
    Fase 5 — UI tab Exchange Saxo2-3 díasconexión OAuth + Forex en el frontend
    Total estimado~14-21 días (≈3-4 semanas)jornadas de ~8h, un solo ingeniero

    I+D

    Investigación y desarrollo de estrategias — fases del pipeline GGA
    I+D Estrategia & Backtesting

    El centro de I+D de estrategias: investigamos estrategias de trading en fuentes fiables, las programamos en Pine, las backtesteamos sobre datos locales en 15m / 4h / 1d y las publicamos en las tres superficies web (tab Estrategia, tab Backtest y el catálogo SEO público). Todo el ciclo lo ejecuta el pipeline multi-agente GGA (Architect → Implementer → Reviewer → Approver), integrando cada fase a la rama gga/rnd — dev y master nunca se tocan solos. Abajo: los resultados de cada estrategia, cómo generar una a demanda, la automatización programada, y las fases del pipeline. Detalle completo en NEW_STRATEGY_WORKFLOW.md.

    Resultados I+D

    Cada estrategia generada, su veredicto del backtest y los links a su página y su curva. Se actualiza sola cuando el pipeline termina una corrida.

    Cargando resultados I+D…

    Generación de estrategia manual

    Dispará el pipeline GGA a demanda para generar UNA estrategia nueva de punta a punta: el architect (con WebSearch) investiga una estrategia guiada por los filtros, la implementa, la backtestea según el brief y la publica, integrando cada fase a gga/rnd (dev y master intactos). El resultado aparece en Resultados I+D al terminar.

    ⚠️ Corre en el server: tarda ~20–40 min, cuesta tokens, y necesita los CLIs claude / codex / gh copilot + API keys en el host. Si no están, el botón avisa.

    Qué pasa por detrás
    1. Disparo. El botón llama a POST /api/rnd/run (solo admin) y el server lanza tools/rnd_autonomous.py como proceso independiente que sobrevive a la sesión web (logs en .gga-runs/).
    2. Research — elegir la estrategia. Un claude -p con el prompt tools/rnd_autonomous_prompt.txt busca con WebSearch UNA estrategia nueva alineada con el asset_type, la familia y el timeframe elegidos, evitando duplicados del catálogo y resultados previos. Escribe el brief (indicadores, parámetros, reglas, universo y timeframes) en content/strategy_queue/<slug>.json.
    3. Arranque del pipeline. tools/rnd_cycle.py toma ese brief y crea/usa la rama gga/rnd (forkeada de dev), donde se acumulan las fases aprobadas. dev y master quedan intactos.
    4. Fase 1 — Pine + backtest (código). El pipeline GGA implementa el script Pine v6 (comisión 0.05%, slippage, SL/TP dinámico, 4 alertconditions con payload de webhook), las señales en backtest/strategy.py y el runner backtest/run_<slug>.py usando el universo y los timeframes definidos por el brief. Tiene que pasar compileall + pytest.
    5. Integración. El cambio del implementer se commitea en su rama gga/run-<ts> y se mergea (--no-ff) a gga/rnd.
    6. Backtest real. Como el sandbox no tiene los CSV de datos, el runner se corre en el árbol principal y descarga/lee OHLCV mediante el proveedor del asset_type: Binance/ccxt para crypto y yfinance para forex, commodity y stock. Genera backtest/results/<slug>_summary.json (mejor activo/TF, profit factor, rentable o no) y la curva de capital en HTML.
    7. Fase 2 — Páginas del dashboard. Agrega la estrategia a la consola con los números reales del summary: ficha en Estrategia (indicadores, paneles LONG/SHORT, salidas), tabla de resultados + curva en Backtest, y la card en ambas listas.
    8. Fase 3 — Catálogo público. Crea content/strategies/<slug>.json y corre tools/strategy_site/build.py para regenerar las páginas SEO de web-public/estrategias/ y el sitemap.
    9. Registro del resultado. Escribe el veredicto (rentable / no rentable, mejor activo, PF) en content/rnd_results.json, que alimenta la tabla Resultados I+D.
    10. Cierre. Todo queda en gga/rnd; el ciclo termina avisando que revises y mergees gga/rnd → dev cuando estés conforme. Nunca hace auto-merge.

    Automatización programada

    Una routine de Claude Code (agente programado en la nube, con WebSearch) corre el ciclo completo en cada disparo: investiga una estrategia → ejecuta Fases 1-3 vía tools/gga_pipeline.py → integra el patch de cada fase a gga/rnd (git apply) y corre el backtest con los datos locales → agrega una fila a Resultados I+D. La frecuencia (diaria o días concretos) y la hora se configuran al crear o editar la routine (cron). dev y master nunca se tocan automáticamente — gga/rnd se mergea a mano a dev cuando lo apruebes.

    Investigar estrategia - Arquitectura
    Descripción investigación automatizada

    Una petición por fase para tools/gga_pipeline.py, en orden, cada una asumiendo la anterior ya integrada. La Fase 0 (investigación) la corre el architect con web. En PowerShell: $task = @' … '@ y luego python tools/gga_pipeline.py $task --run-tests --allow-dirty.

    Detalle de la arquitectura

    La investigación automatizada arranca con un agente Claude con WebSearch (tools/rnd_autonomous.py) que investiga una estrategia y escribe un brief en content/strategy_queue/. Ese brief alimenta a tools/rnd_cycle.py, que orquesta las fases del pipeline GGA (una tarea = una fase), integra cada fase aprobada a gga/rnd, corre el backtest donde están los datos y registra el resultado en Resultados I+D. dev y master nunca se tocan automáticamente.

    🔎
    Investigación
    rnd_autonomous.py · Claude + WebSearch
    →
    📝
    Brief
    content/strategy_queue/<slug>.json
    →
    ⛓️
    rnd_cycle.py
    Orquesta las fases GGA → integra a gga/rnd
    →
    📊
    Resultados I+D
    Backtest + fila en rnd_results.json
    Fases del pipeline GGA
    Fase 0 — Investigación (automatizable: architect/routine con web)
    El architect del pipeline (claude -p) tiene WebSearch confirmado (2026-06-09), así que la research puede correr dentro del pipeline o en la routine programada (ambos son Claude con web). También se puede hacer a mano. Producir un brief y pegarlo en el $task de la Fase 1:
    
    BRIEF:
      name:        <Nombre, p.ej. "Donchian Breakout">
      slug:        <kebab-slug, p.ej. donchian-breakout>
      type:        <Trend-following | Mean-reversion | Breakout | Momentum>
      sources:     <2+ URLs fiables: QuantifiedStrategies, AlgoTest, research de exchange, papers>
      indicators:  <nombre, params, qué mide, cómo se usa>
      entry_long / entry_short / exit / params: <reglas exactas y umbrales numéricos>
      published_metrics: <PF / win rate / timeframe que reporta la fuente, para los caveats honestos>
    
    Regla: nunca afirmar que una estrategia es "rentable" desde un blog. El brief registra lo que la fuente AFIRMA; el backtest (Fase 1) decide la verdad.
    Fase 1 — Pine script + backtest (15m / 4h / 1d)
    Implement the new strategy '<NAME>' (slug <slug>) in the backtesting stack. STRATEGY BRIEF: <PASTE THE PHASE-0 BRIEF>. Deliverables, matching existing conventions exactly:
    1) pine/<slug>.v1.pine — Pine v6 strategy() copied from pine/supertrend_atr.v1.pine: overlay, default_qty_type=strategy.percent_of_equity default_qty_value=1, commission_type=strategy.commission.percent commission_value=0.05, slippage=2, dynamic SL/TP, and 4 alertcondition() webhook payloads shaped {"token":"{{strategy.order.alert_message}}","strategy":"<NAME>","symbol":"{{ticker}}","side":"buy|sell","price":"{{close}}","time":"{{time}}","action":"open|close"}.
    2) backtest/strategy.py — add calculate_<slug>_signals(df, ...) returning boolean signal_long/signal_short columns (and an 'atr' column if exits are ATR-based), pandas/numpy only (no TA libs), mirroring calculate_supertrend_signals / calculate_rsi_momentum_signals.
    3) backtest/run_<slug>.py — backtest BTC/ETH/SOL/BNB across timeframes 15m, 4h and 1d (15m from backtest/data/<ASSET>USDT_15m.csv; 4h and 1d resampled from it: open=first, high=max, low=min, close=last, volume=sum). Reuse run_backtest._run for fixed-% SL/TP, or run_bipbip_v2._run_atr for ATR exits. Print a per-TF/per-asset table (Trades, Win Rate, PF, Max DD, PnL) like run_new_strategies.py, and write the numbers to backtest/results/<slug>_summary.json for the next phase.
    4) Extend backtest/make_strategy_charts.py to emit backtest/results/equity_<slug>.html (per-asset 15m equity curve), then run it.
    Run run_<slug>.py and make_strategy_charts.py to produce the numbers and chart. Never touch live trading, .env, state/, logs/, data/*.csv, users.db. python -m compileall and pytest must stay green.
    Fase 2 — Páginas del dashboard (tabs Estrategia + Backtest)
    Add strategy '<NAME>' (slug <slug>) to web-private/index.html using the real numbers in backtest/results/<slug>_summary.json (15m/4h/1d).
    A) In #tab-estrategia add a <div class='backtest-strategy-section'> matching the existing 'Supertrend ATR' section: a strategy-section-header whose <h3> equals the card name EXACTLY (the scroll handler in app.js matches card name to <h3>); a 'Resumen e Investigación' panel with the research summary + a <p class='strategy-sources'> of source links (target='_blank' rel='noopener noreferrer') + overview-pills; an 'Indicadores' panel (indicator-card grid); LONG/SHORT two-col signal-panels with condition-list; a 'Salidas y nota metodológica' panel; and a 'Backtesting criptomonedas' panel with a 'Ver Backtest' button using openStrategyBacktestFromSection(this) plus a table (Activo/Veredicto/Evidencia) grounded in the backtest per asset.
    B) In #tab-backtest add the matching section: a 'Resultados por Timeframe y Activo' table (15m/4h/1d, PnL cells using pnl-pos/pnl-neg), a 'Conclusiones' findings-list, a 'Configuración del Backtest' panel, and a 'Curva de Capital — Retorno (%) por Activo' panel embedding <iframe src='/backtest-results/equity_<slug>.html' class='chart-frame' frameborder='0'>.
    C) Add the strategy card (span.strategy-card-name = '<NAME>') to BOTH tab card lists.
    D) Bump the ?v= query string on the app.js and style.css tags in index.html so browsers refetch.
    Keep the HTML balanced (equal <div>/</div>). Do not touch the backend, the webhook, or live trading.
    Fase 3 — Catálogo SEO público
    Add strategy '<NAME>' (slug <slug>) to the public catalog. Create content/strategies/<slug>.json following the schema in tools/strategy_site/README.md and the existing content/strategies/supertrend-atr.json: slug, status (live|research), name, tagline, updated (ISO date), seo{title,description,keywords}, meta{timeframe,assets,type,sessions}, pills, overview (HTML), indicators[{name,badge,color,what,how}], signals{long,short}, risk[], backtest{intro,columns,rows} filled with the real 15m/4h/1d numbers, learnings[], sources[] with the Phase-0 research URLs. Then run 'python tools/strategy_site/build.py' to regenerate web-public/estrategias/ and web-public/sitemap.xml. Never hand-edit the generated web-public/estrategias/*.html.

    FAQ

    Preguntas frecuentes sobre el funcionamiento del sistema
    ¿Para qué necesito Bipbip Trading Bot Manager si puedo conectar TradingView con Bitunix y enviar las órdenes directamente?

    Hay una premisa que conviene aclarar: TradingView no puede ejecutar órdenes en Bitunix directamente.

    • Los webhooks de TradingView solo envían un HTTP POST a una URL. No autentican ni firman peticiones.
    • La API de Bitunix exige que cada orden venga firmada con SHA256 (doble pasada) usando tu api_secret. Una alerta de TradingView no sabe firmar eso (y no querrías exponer tu secret en una alerta).
    • Bitunix no es un "broker integrado" en el panel de trading de TradingView, así que no existe un botón para ejecutar en Bitunix desde el gráfico.

    Por lo tanto, entre la señal de TradingView y la orden en Bitunix siempre tiene que haber algo en el medio que firme la petición. Ese puente es Bipbip Trading Bot Manager — no es opcional, es obligatorio. La alternativa sería un servicio de terceros (3Commas, Alertatron…), al que tendrías que pagar y confiarle tu API key.

    ¿Qué te aporta tu propio bot frente a un tercero o a operar a mano?
    • Tu API secret vive en tu servidor, no se lo entregas a una empresa externa.
    • Gestión de riesgo propia: tamaño de posición por % de riesgo, circuit breaker diario y validación de margen.
    • Lógica de ejecución: cálculo de SL/TP, leverage automático, control de slippage, reintentos ante fallos y reconciliación con el exchange.
    • Sin cuota mensual y sin depender de que un tercero esté online.
    • Ejecución 24/7 sin tu intervención: aplica tu estrategia con reglas fijas, sin demoras y sin que la emoción mueva tu stop.

    La clave: la alternativa real al bot no es "TradingView directo" (eso no existe), sino vos mirando el gráfico y ejecutando a mano en Bitunix. El bot te quita de esa ecuación y ejecuta tu estrategia de forma disciplinada.