File size: 15,818 Bytes
5160d3e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# PolySignal

Dashboard web de inteligencia de mercados de prediccion en tiempo real.

## Que es

PolySignal analiza mercados de Polymarket cruzando noticias de Finnhub con modelos de IA (ModernFinBERT + Qwen3-8B) para generar senales de trading (alcista, bajista, neutral). Incluye simulador de posiciones con capital virtual, lista de seguimiento y alertas por Telegram.

**No ejecuta ordenes reales.** Es una herramienta de analisis e inteligencia.

**Idioma:** Espanol.
**Moneda base:** Euro (€).

## Stack

- **Backend:** Node.js 26 + Express.js 5 + Socket.io + node-cron
- **ORM:** Prisma 6 + SQLite
- **Frontend:** Vanilla JS + Vite 7 + Leaflet.js + Chart.js + Socket.io client
- **IA:** HuggingFace Spaces (ModernFinBERT + Qwen3-8B) + OpenRouter fallback
- **Datos:** Polymarket Gamma API + Finnhub REST
- **Deploy:** HuggingFace Spaces (Docker, puerto 7860)

## Estado del proyecto

El backend es **totalmente funcional**:

- API REST completa con autenticacion JWT.
- Pipeline de IA con cadena de fallback (Spaces HF β†’ API HF β†’ OpenRouter β†’ rule-based).
- Scheduler de tareas periodicas (sync mercados, generacion de senales, P&L, alertas).
- WebSocket en tiempo real para precios y senales.
- Base de datos SQLite con Prisma ORM

El frontend **consume datos reales del backend** y tiene **fallback a datos mock** cuando el backend no responde (modo demo sin configuracion).

## Mejoras de utilidad

Para que la app sea util mas alla de la demo visual, se han incorporado los siguientes ajustes β€” todos orientados a que las senales tengan *edge* real y no sean ruido bonito:

### 1. Fetch diversificado por tag (anti-monotonia)

El endpoint `/markets` de Polymarket ignora `tag_id` y siempre devuelve la home feed (politica US + World Cup). El cliente usa ahora el endpoint **`/events`** que SI respeta `tag_id`, con un catalogo curado de ~25 tags de alto valor (crypto-prices, fed, stock-market, tech, openai, middle-east, oil-industry, europe, taiwan-election, etc.) y aplana los mercados por tag.

Resultado: ~1000 mercados activos diarios distribuidos en 6 categorias (cripto, economia, geopolitica, ciencia, politica, entretenimiento) en lugar de los ~100 dominados por una unica categoria.

### 2. Whitelist de mercados analizables

`polymarket.client.js β†’ isAnalyzable()` flaggea como **no analizables** los mercados donde la IA no tiene edge plausible:

- Predicciones de palabras (*"Will Trump say nuclear?"*)
- Views de YouTubers, recuentos de tweets
- "Before GTA VI"-style memes
- Deportes y entretenimiento

`signals.service.js` salta la generacion para estos mercados y el frontend pinta el badge **"FUERA DE ALCANCE"** en lugar de fabricar confianza falsa. Asi, cada senal visible es defendible.

### 3. Ground truth de cripto via CoinGecko

`utils/coingecko.client.js` resuelve spot prices (BTC, ETH, SOL, DOGE, ADA, XRP) y los inyecta en el prompt de la IA para mercados de precio objetivo:

```
GROUND TRUTH: BTC spot $103,400. Target $150,000 (+45.1% required).
Use this to judge whether the implied probability is plausible given typical volatility.
```

Cache TTL 60s β€” respeta el rate limit gratuito de CoinGecko.

### 4. Edge gap explicito (impliedProb vs fairProb)

Cada `AISignal` persiste ahora `impliedProb`, `fairProb` y `edgePoints`. El pipeline mapea `(signal, confidence) β†’ fairProb`:

| Signal | Formula |
|--------|---------|
| bullish + conf 0.8 | fairProb = 0.5 + 0.8 Γ— 0.5 = 0.90 |
| bearish + conf 0.8 | fairProb = 0.5 βˆ’ 0.8 Γ— 0.5 = 0.10 |
| neutral | fairProb = 0.5 |

La tarjeta del mercado muestra: `Mercado 65% Β· IA 78% Β· Edge +13pp` β€” claim cuantitativo en lugar de prosa vaga.

### 5. Spread-aware sizing (Kelly con costes)

Polymarket expone `spread`, `bestBid`, `bestAsk` por mercado. `positions/kelly.js β†’ suggestSize()` resta el spread del edge bruto antes de calcular el tamano de posicion:

```
edgeNeto = |edgePoints/100| - spread
fraction = Quarter-Kelly(price, impliedProb + edgeNeto)
amount   = bankroll * min(0.25, fraction)
```

Mercados con `spread > 5Β’` se marcan como **ilΓ­quidos** y los botones de compra se desactivan. Endpoint publico: `GET /api/v1/positions/suggestion/:marketId`.

### 6. Distribucion geografica del mapa

`map.js` usa **jitter determinista** (hash del marketId β†’ desplazamiento en bounding-box del pais) para que multiples mercados del mismo pais no se apilen sobre la capital. Mercados sin pais (cripto, indices, AI) se reparten entre **40 hubs financieros** globales (NYC, Sao Paulo, Mumbai, Lagos, Moscu, Yakarta, Sydney, etc.) en vez de caer todos sobre [20,0].

## Estructura

```
polysignal/
β”œβ”€β”€ backend/                      # API REST + Servicios + Scheduler
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ prisma/
β”‚   β”‚   β”œβ”€β”€ schema.prisma         # Schema SQLite (User, Market, AISignal, Position, Watchlist, Alert)
β”‚   β”‚   β”œβ”€β”€ migrations/           # Migraciones de Prisma
β”‚   β”‚   └── seed.js               # Usuarios demo (admin + user)
β”‚   └── src/
β”‚       β”œβ”€β”€ index.js              # Entry point: HTTP server + Socket.io + scheduler
β”‚       β”œβ”€β”€ app.js                # Express: middlewares + rutas + manejo de errores
β”‚       β”œβ”€β”€ config.js             # Variables de entorno validadas con Zod
β”‚       β”œβ”€β”€ scheduler.js          # Jobs periodicos (cron): sync, senales IA, PnL, alertas
β”‚       β”œβ”€β”€ auth/                 # Autenticacion JWT + bcrypt
β”‚       β”‚   β”œβ”€β”€ auth.controller.js
β”‚       β”‚   β”œβ”€β”€ auth.service.js
β”‚       β”‚   β”œβ”€β”€ auth.routes.js
β”‚       β”‚   β”œβ”€β”€ auth.validators.js
β”‚       β”‚   └── jwt.js
β”‚       β”œβ”€β”€ markets/              # Mercados de Polymarket
β”‚       β”‚   β”œβ”€β”€ markets.controller.js
β”‚       β”‚   β”œβ”€β”€ markets.service.js
β”‚       β”‚   β”œβ”€β”€ markets.routes.js
β”‚       β”‚   β”œβ”€β”€ markets.validators.js
β”‚       β”‚   β”œβ”€β”€ markets.repository.js
β”‚       β”‚   └── polymarket.client.js
β”‚       β”œβ”€β”€ signals/              # Pipeline de IA (ModernFinBERT + Qwen3-8B)
β”‚       β”‚   β”œβ”€β”€ signals.controller.js
β”‚       β”‚   β”œβ”€β”€ signals.service.js
β”‚       β”‚   β”œβ”€β”€ signals.routes.js
β”‚       β”‚   β”œβ”€β”€ signals.repository.js
β”‚       β”‚   β”œβ”€β”€ aiPipeline.js     # Pipeline IA con fallback chain
β”‚       β”‚   └── finnhub.client.js # Noticias financieras
β”‚       β”œβ”€β”€ positions/            # Simulador de posiciones virtuales
β”‚       β”‚   β”œβ”€β”€ positions.controller.js
β”‚       β”‚   β”œβ”€β”€ positions.service.js
β”‚       β”‚   β”œβ”€β”€ positions.routes.js
β”‚       β”‚   β”œβ”€β”€ positions.validators.js
β”‚       β”‚   β”œβ”€β”€ positions.repository.js
β”‚       β”‚   └── kelly.js          # Criterio de Kelly (sizing)
β”‚       β”œβ”€β”€ watchlist/            # Lista de seguimiento
β”‚       β”‚   β”œβ”€β”€ watchlist.controller.js
β”‚       β”‚   β”œβ”€β”€ watchlist.service.js
β”‚       β”‚   β”œβ”€β”€ watchlist.routes.js
β”‚       β”‚   β”œβ”€β”€ watchlist.validators.js
β”‚       β”‚   └── watchlist.repository.js
β”‚       β”œβ”€β”€ alerts/               # Alertas por Telegram
β”‚       β”‚   β”œβ”€β”€ alerts.controller.js
β”‚       β”‚   β”œβ”€β”€ alerts.service.js
β”‚       β”‚   β”œβ”€β”€ alerts.routes.js
β”‚       β”‚   β”œβ”€β”€ alerts.repository.js
β”‚       β”‚   └── telegram.client.js
β”‚       β”œβ”€β”€ middlewares/          # Middlewares reutilizables
β”‚       β”‚   β”œβ”€β”€ validate.js       # Validacion Zod generica
β”‚       β”‚   β”œβ”€β”€ requireAuth.js    # Autenticacion JWT
β”‚       β”‚   β”œβ”€β”€ rateLimitLogin.js # Rate limit login
β”‚       β”‚   β”œβ”€β”€ errorHandler.js   # Manejo centralizado de errores
β”‚       β”‚   └── notFound.js       # 404
β”‚       β”œβ”€β”€ utils/                # Utilidades compartidas
β”‚       β”‚   β”œβ”€β”€ apiResponse.js    # Helpers de respuesta HTTP
β”‚       β”‚   β”œβ”€β”€ httpClient.js     # Cliente HTTP con retry + timeout
β”‚       β”‚   β”œβ”€β”€ logger.js         # Pino (logs estructurados)
β”‚       β”‚   └── prisma.js         # Singleton PrismaClient
β”‚       └── socket/
β”‚           └── broadcaster.js    # Emisor de eventos Socket.io
β”‚
β”œβ”€β”€ frontend/                     # SPA Vanilla JS con Vite
β”‚   β”œβ”€β”€ index.html                # Punto de entrada HTML
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ vite.config.js            # Proxy a backend + build config
β”‚   └── src/
β”‚       β”œβ”€β”€ main.js               # Entry point de Vite
β”‚       β”œβ”€β”€ app.js                # Logica principal SPA + Socket.io
β”‚       β”œβ”€β”€ api.js                # Cliente REST del backend
β”‚       β”œβ”€β”€ charts.js             # Chart.js (historial + sparklines)
β”‚       β”œβ”€β”€ map.js                # Leaflet (mapa mundial interactivo)
β”‚       β”œβ”€β”€ simulator.js          # Simulador de posiciones virtuales
β”‚       └── style.css             # Estilos dark terminal / fintech
β”‚
β”œβ”€β”€ spaces/                       # HuggingFace Spaces (ZeroGPU)
β”‚   β”œβ”€β”€ modernfinbert/            # Space de ModernFinBERT
β”‚   β”‚   β”œβ”€β”€ app.py
β”‚   β”‚   β”œβ”€β”€ requirements.txt
β”‚   β”‚   └── README.md
β”‚   └── qwen3-8b/                 # Space de Qwen3-8B
β”‚       β”œβ”€β”€ app.py
β”‚       β”œβ”€β”€ Dockerfile
β”‚       β”œβ”€β”€ requirements.txt
β”‚       └── README.md
β”‚
β”œβ”€β”€ package.json                  # Root con workspaces + scripts conjuntos
β”œβ”€β”€ docker-compose.yml            # Orquestacion local
β”œβ”€β”€ Dockerfile                    # Build para HuggingFace Spaces
β”œβ”€β”€ .env.example                  # Variables de entorno de ejemplo
β”œβ”€β”€ SECURITY_HEALTHCHECK.md       # Auditoria de seguridad y arquitectura
└── README.md
```

## Requisitos

- **Node.js >= 26.0.0**
- **npm >= 10** (workspaces)

## Instalacion rapida

```bash
# 1. Instalar dependencias (root + todos los workspaces)
npm install

# 2. Configurar variables de entorno
cp .env.example .env
# Editar .env con tus claves (HF_TOKEN, HF_SPACE_*, OPENROUTER_API_KEY, etc.)

# 3. Generar base de datos y cliente Prisma
npm run db:migrate
npm run db:generate

# 4. Iniciar en desarrollo
npm run dev:all      # Backend + Frontend Vite simultaneamente
```

## Desarrollo solo frontend

Si solo quieres visualizar el dashboard (funciona con datos mock):

```bash
cd frontend
npm install
npm run dev
# Abrir http://localhost:5173
```

El frontend consume datos mock localmente cuando el backend no responde, por lo que el dashboard es totalmente funcional para la demo sin configuracion adicional.

## Arquitectura del Backend

El backend sigue una arquitectura **Layered (Controller β†’ Service β†’ Repository)**:

| Capa | Responsabilidad | Ejemplo |
|------|----------------|---------|
| **Controller** | Recibir HTTP request, delegar a Service, responder | `markets.controller.js` |
| **Service** | Logica de negocio, validaciones, coordinacion | `markets.service.js` |
| **Repository** | Acceso a datos via Prisma ORM | `markets.repository.js` |
| **Client** | Integracion con APIs externas | `polymarket.client.js`, `finnhub.client.js` |
| **Middleware** | Cross-cutting concerns (auth, validacion, rate limiting) | `requireAuth.js`, `validate.js` |

### Pipeline de IA

```
Whitelist analyzable (skip predicciones-de-palabras, sports, memes)
    ↓
Noticias (Finnhub) β†’ relevantes por mercado
    ↓
Filtrado (ModernFinBERT Space / API directa)
    ↓ (descarta neutrales, score < 0.65)
Ground truth crypto (CoinGecko spot β†’ solo si aplica)
    ↓
Generacion de senal (Qwen3-8B Space / API directa)
    ↓
Fallback: OpenRouter (deepseek-chat)
    ↓
Fallback: Rule-based (precio del mercado)
    ↓
Calculo edge: impliedProb vs fairProb β†’ edgePoints
    ↓
Persistencia (SQLite) + Emision Socket.io
```

### Scheduler (node-cron)

| Job | Frecuencia | Descripcion |
|-----|-----------|-------------|
| syncMarkets | Cada 30s | Sincroniza precios + spread desde Polymarket Gamma (fetch diversificado por tag) |
| generateSignals | Cada 5 min | Genera senales IA para 40 mercados diversificados por categoria (solo analyzable=true) |
| updatePositionsPnL | Cada 30s | Recalcula P&L de posiciones abiertas |
| processAlerts | Cada 60s | Revisa watchlist y envia alertas Telegram |

## Arquitectura del Frontend

El frontend es una SPA construida con **Vite 7** como bundler y dev server.

### Caracteristicas visuales

- **Estetica dark terminal / fintech:** paleta `#0a0c10`, tipografias `Syne` + `DM Mono`.
- **Layout ajustable:** sidebar colapsable, paneles del dashboard colapsables individualmente.
- **Mapa global interactivo:** Leaflet con burbujas por pais (tamano = volumen, color = senal IA), jitter determinista para evitar apilamientos y 40 hubs financieros para mercados sin pais.
- **Panel de senales IA:** mercados con badges alcista/bajista/neutral, **fila de edge cuantitativa** (`Mercado X% Β· IA Y% Β· Edge Β±N pp`) y badge **"FUERA DE ALCANCE"** para mercados no analizables.
- **Detalle de mercado:** sparklines, historial 7d, analisis IA, simulador de posiciones con **sugerencia de tamano Quarter-Kelly cost-aware** (servida por `GET /positions/suggestion/:marketId`) y deshabilitacion automatica para mercados con spread > 5Β’.
- **Vistas adicionales:** Posiciones abiertas, Lista de seguimiento, Historial de alertas.

### Flujo de desarrollo

| Servicio | Comando | URL local |
|----------|---------|-----------|
| Backend (Express + Socket.io) | `npm run dev` | `http://localhost:7860` |
| Frontend (Vite + HMR) | `npm run dev:frontend` | `http://localhost:5173` |
| **Ambos a la vez** | **`npm run dev:all`** | β€” |

Vite esta configurado con un proxy que redirige automaticamente las peticiones a `/api` y `/socket.io` hacia el backend en el puerto `7860`, eliminando problemas de CORS durante el desarrollo local.

### Scripts disponibles

```bash
# Levantar solo el backend
npm run dev

# Levantar solo el frontend (Vite con hot reload)
npm run dev:frontend

# Levantar backend y frontend simultaneamente
npm run dev:all

# Build de produccion del frontend (genera frontend/dist/)
npm run build:frontend

# Preview del build de produccion
npm run preview:frontend

# Base de datos
npm run db:migrate      # Crear/actualizar migraciones
npm run db:generate     # Generar cliente Prisma
npm run db:studio       # Explorar BD con Prisma Studio
```

## Deploy en HuggingFace Spaces

1. Crear Space tipo "Docker"
2. Subir codigo (`git push`)
3. Configurar Secrets en la interfaz de HF con las variables de `.env`
4. El contenedor expone el puerto 7860 automaticamente

### Docker local (opcional)

```bash
# Build y run con docker-compose
docker-compose up --build

# O solo docker build
docker build -t polysignal .
docker run -p 7860:7860 --env-file .env polysignal
```

## Variables de entorno

```env
# HuggingFace
HF_TOKEN=                          # API key de HuggingFace (Inference API)
HF_SPACE_MODERNFINBERT_URL=        # URL del Space (ej: usuario/modernfinbert)
HF_SPACE_QWEN_URL=                 # URL del Space (ej: usuario/qwen3-8b)

# Fallbacks y datos
OPENROUTER_API_KEY=                # Fallback LLM si HF esta saturado
FINNHUB_API_KEY=                   # Noticias financieras (finnhub.io)

# Alertas
TELEGRAM_BOT_TOKEN=                # Bot de alertas (@BotFather)

# Base de datos y auth
DATABASE_URL=file:./backend/prisma/polysignal.db
JWT_SECRET=minimo-32-caracteres    # Secreto para firmar JWT

# Servidor
PORT=7860                          # Puerto requerido por HuggingFace Spaces
NODE_ENV=production                # development | production
```

## Equipo

Hackathon CIFO Barcelona La Violeta β€” 13-18 mayo 2026

## Licencia

MIT