File size: 3,283 Bytes
71b8eb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# 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 |