File size: 4,794 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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# MARKETS.md — Módulo de mercados

Referencia para el frontend: contrato HTTP, mapping de datos de Polymarket, evento socket y errores.

---

## Variables de entorno requeridas

```
DATABASE_URL=file:./backend/prisma/polysignal.db
PORT=7860
```

No se necesita clave de API para Polymarket Gamma (pública).

---

## Endpoints

### `GET /api/v1/markets`

Lista paginada de mercados activos sincronizados desde Polymarket. **No requiere autenticación.**

**Query params**

| Param | Tipo | Default | Descripción |
|---|---|---|---|
| `limit` | int (1-100) | `20` | Máximo de resultados |
| `offset` | int | `0` | Paginación por offset |
| `category` | string | — | Filtro: `politics` \| `crypto` \| `economics` \| `sports` |
| `status` | string | `active` | Filtro: `active` \| `closed` \| `resolved` |

**Respuesta `200`**

```json
{
  "ok": true,
  "data": [
    {
      "id": "559677",
      "question": "Will Hillary Clinton win the 2028 Democratic presidential nomination?",
      "category": null,
      "countryCode": null,
      "yesPrice": 0.0075,
      "noPrice": 0.9925,
      "volumeEur": 38608906.44,
      "liquidityEur": 2301398.64,
      "status": "active",
      "closesAt": "2028-11-07T00:00:00.000Z",
      "lastSynced": "2026-05-16T09:38:30.204Z"
    }
  ],
  "meta": {
    "total": 100,
    "limit": 1,
    "offset": 0
  }
}
```

> `category` y `countryCode` pueden ser `null` si Polymarket no los proporciona.

---

### `GET /api/v1/markets/:id`

Detalle de un mercado por su ID de Polymarket. **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": "559677",
    "question": "Will Hillary Clinton win the 2028 Democratic presidential nomination?",
    "category": null,
    "countryCode": null,
    "yesPrice": 0.0075,
    "noPrice": 0.9925,
    "volumeEur": 38608906.44,
    "liquidityEur": 2301398.64,
    "status": "active",
    "closesAt": "2028-11-07T00:00:00.000Z",
    "lastSynced": "2026-05-16T09:38:30.204Z"
  }
}
```

**Respuesta `404`**

```json
{
  "ok": false,
  "error": { "code": "NOT_FOUND", "message": "Market not found" }
}
```

---

### `GET /api/v1/markets/:id/signal`

Señal AI más reciente para un mercado. Ver [SIGNALS.md](SIGNALS.md) para el contrato completo.

---

## Mapping Polymarket Gamma API → `Market`

URL de origen: `https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=100`

| Campo Gamma API | Campo `Market` | Transformación |
|---|---|---|
| `id` | `id` | String (ID numérico de Polymarket) |
| `question` | `question` | Directo |
| `category` | `category` | Minúsculas; `null` si Gamma no lo envía |
| — | `countryCode` | `null` por defecto (no proporcionado por Gamma) |
| `outcomePrices[0]` | `yesPrice` | `parseFloat`; rango 0.0–1.0 |
| `outcomePrices[1]` | `noPrice` | `parseFloat`; rango 0.0–1.0 |
| `volume` | `volumeEur` | `parseFloat(volume) * 0.93` (USD→EUR tasa fija) |
| `liquidity` | `liquidityEur` | Igual que `volumeEur` |
| `active` + `closed` + `archived` | `status` | `active=true → "active"`, `closed=true → "closed"`, `archived=true → "resolved"` |
| `endDateIso` | `closesAt` | `new Date(endDateIso)` |
| — | `lastSynced` | `new Date()` en el momento del upsert |

La sincronización usa `prisma.market.upsert({ where: { id }, ... })` para evitar duplicados.

---

## Socket — evento `market_update`

Emitido por `src/socket/broadcaster.js` cada 30 s (tras `syncMarkets`).

**Nombre del evento:** `market_update`

**Payload**

```json
{
  "marketId": "0x1a2b...",
  "yesPrice": 0.63,
  "noPrice": 0.37,
  "volumeEur": 125000.00
}
```

**Uso en el frontend (Socket.io client)**

```js
import { io } from 'socket.io-client';

const socket = io('http://localhost:7860');
socket.on('market_update', ({ marketId, yesPrice, noPrice }) => {
  // actualizar estado local del mercado
});
```

En producción (HF Spaces) el frontend y backend comparten origen → usar `io()` sin URL.

---

## Ejemplos `curl`

```bash
# Listar mercados (primeros 5)
curl "http://localhost:7860/api/v1/markets?limit=5"

# Filtrar por estado
curl "http://localhost:7860/api/v1/markets?status=active&limit=10"

# Detalle de un mercado
curl "http://localhost:7860/api/v1/markets/559677"

# Señal AI del mercado
curl "http://localhost:7860/api/v1/markets/559677/signal"
```

---

## Códigos de error relevantes

| HTTP | Código | Cuándo |
|---|---|---|
| `400` | `VALIDATION_ERROR` | Parámetro inválido (ej. `limit` > 100) |
| `404` | `NOT_FOUND` | ID de mercado no existe en DB |
| `500` | `INTERNAL` | Error inesperado del servidor |

Los endpoints de markets **no requieren autenticación** — son datos públicos.