| # SIGNALS.md — Módulo de señales AI |
|
|
| Referencia para el frontend: señales generadas por la pipeline FinBERT + Qwen3-8B sobre mercados de Polymarket. |
|
|
| --- |
|
|
| ## Variables de entorno (opcionales) |
|
|
| | Variable | Descripción | |
| |---|---| |
| | `HF_TOKEN` | HuggingFace Pro token — FinBERT + Qwen3-8B | |
| | `OPENROUTER_API_KEY` | Fallback LLM si HuggingFace está saturado | |
| | `FINNHUB_API_KEY` | Noticias financieras para contexto | |
|
|
| Sin ninguna clave, el backend usa una señal **rule-based** derivada de los precios del mercado. |
|
|
| --- |
|
|
| ## Endpoints |
|
|
| ### `GET /api/v1/markets/:id/signal` |
|
|
| Señal AI más reciente para el mercado. **No requiere autenticación.** |
|
|
| **Params** |
|
|
| | Param | Tipo | Descripción | |
| |---|---|---| |
| | `id` | string | ID numérico de Polymarket (ej. `559677`) | |
|
|
| **Respuesta `200`** |
|
|
| ```json |
| { |
| "ok": true, |
| "data": { |
| "id": 81, |
| "marketId": "559677", |
| "signal": "bearish", |
| "confidence": 0.9, |
| "summary": "Market strongly favors NO at 99% probability. Downside momentum dominates.", |
| "keyRisk": "Unexpected positive developments could reverse this rapidly.", |
| "newsCount": 0, |
| "modelVersion": "Qwen3-8B", |
| "generatedAt": "2026-05-16T09:35:00.110Z" |
| } |
| } |
| ``` |
|
|
| **Campos** |
|
|
| | Campo | Tipo | Descripción | |
| |---|---|---| |
| | `id` | int | ID de la señal en DB | |
| | `marketId` | string | ID del mercado | |
| | `signal` | `"bullish"` \| `"bearish"` \| `"neutral"` | Dirección recomendada | |
| | `confidence` | float (0–1) | Nivel de confianza del modelo | |
| | `summary` | string | Resumen del análisis | |
| | `keyRisk` | string \| null | Principal riesgo identificado | |
| | `newsCount` | int | Noticias procesadas por Finnhub | |
| | `modelVersion` | string | Modelo que generó la señal (`Qwen3-8B`, `OpenRouter`, `rule-based`) | |
| | `generatedAt` | ISO 8601 | Timestamp de generación | |
|
|
| **Respuesta `404`** |
|
|
| ```json |
| { |
| "ok": false, |
| "error": { "code": "NOT_FOUND", "message": "No signal found for this market" } |
| } |
| ``` |
|
|
| --- |
|
|
| ## Pipeline de generación |
|
|
| 1. **Finnhub** → noticias relacionadas por keywords del `question` |
| 2. **FinBERT** (HuggingFace) → filtro de sentimiento; descarta noticias irrelevantes |
| 3. **Qwen3-8B** (HuggingFace) → genera señal JSON `{ signal, confidence, summary, keyRisk }` |
| 4. **OpenRouter** → fallback si HF está saturado (HTTP 503) |
| 5. **Rule-based** → fallback final: `yesPrice > 0.6 → bullish`, `yesPrice < 0.4 → bearish`, else `neutral` |
|
|
| La señal se persiste en `AISignal` y se emite por socket (`ai_signal`). |
|
|
| --- |
|
|
| ## Socket — evento `ai_signal` |
| |
| Emitido por `src/socket/broadcaster.js` cada 5 min (tras `generateSignals`). |
| |
| **Nombre del evento:** `ai_signal` |
|
|
| **Payload** |
|
|
| ```json |
| { |
| "marketId": "559677", |
| "signal": "bearish", |
| "confidence": 0.9, |
| "summary": "Market strongly favors NO at 99% probability." |
| } |
| ``` |
|
|
| **Uso en el frontend** |
|
|
| ```js |
| socket.on('ai_signal', ({ marketId, signal, confidence }) => { |
| // actualizar badge de señal en la tarjeta del mercado |
| }); |
| ``` |
|
|
| --- |
|
|
| ## Ejemplos `curl` |
|
|
| ```bash |
| # Señal más reciente de un mercado |
| curl "http://localhost:7860/api/v1/markets/559677/signal" |
| ``` |
|
|
| --- |
|
|
| ## Códigos de error |
|
|
| | HTTP | Código | Cuándo | |
| |---|---|---| |
| | `404` | `NOT_FOUND` | No hay señal generada aún para ese mercado | |
| | `500` | `INTERNAL` | Error inesperado del servidor | |
|
|