| # 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 |
|
|