polysignal_hackaton / backend /docs /POSITIONS.md
blackmistcode's picture
Add files using upload-large-folder tool
71b8eb2 verified
|
Raw
History Blame Contribute Delete
4.71 kB

POSITIONS.md — Módulo de posiciones

Simulador de capital virtual. Las posiciones no generan órdenes reales en Polymarket — son un tracking local de apuestas simuladas en euros.

Todos los endpoints requieren Authorization: Bearer <token>.


Endpoints

POST /api/v1/positions

Abre una posición nueva en un mercado activo.

Body

{
  "marketId": "559677",
  "outcome": "YES",
  "amountEur": 100
}
Campo Tipo Descripción
marketId string ID del mercado (debe existir y estar active)
outcome "YES" | "NO" Lado de la apuesta
amountEur float > 0 Importe a invertir en euros

Respuesta 201

{
  "ok": true,
  "data": {
    "id": 1,
    "userId": 1,
    "marketId": "559677",
    "outcome": "YES",
    "amountEur": 100,
    "entryPrice": 0.0075,
    "currentPrice": 0.0075,
    "pnl": 0,
    "kellyFraction": 0.25,
    "status": "open",
    "openedAt": "2026-05-16T09:14:55.750Z",
    "closedAt": null,
    "market": {
      "id": "559677",
      "question": "Will Hillary Clinton win the 2028 Democratic presidential nomination?",
      "yesPrice": 0.0075,
      "noPrice": 0.9925,
      "status": "active"
    }
  }
}

Campos de respuesta relevantes

Campo Descripción
entryPrice Precio en el momento de abrir (yesPrice o noPrice según outcome)
currentPrice Precio actual (actualizado por updatePositionsPnL cada 30s)
pnl Profit & Loss en EUR (negativo = pérdida)
kellyFraction Fracción de Kelly calculada (capada al 0.25)
status "open" | "closed"

GET /api/v1/positions

Lista todas las posiciones del usuario autenticado (abiertas y cerradas).

Query params

Param Tipo Default Descripción
limit int (1-100) 20 Máximo de resultados
offset int 0 Paginación por offset

Respuesta 200

{
  "ok": true,
  "data": [
    {
      "id": 1,
      "userId": 1,
      "marketId": "559677",
      "outcome": "YES",
      "amountEur": 100,
      "entryPrice": 0.0075,
      "currentPrice": 0.0075,
      "pnl": 0,
      "kellyFraction": 0.25,
      "status": "closed",
      "openedAt": "2026-05-16T09:14:55.750Z",
      "closedAt": "2026-05-16T09:15:04.155Z",
      "market": {
        "id": "559677",
        "question": "Will Hillary Clinton win the 2028 Democratic presidential nomination?",
        "yesPrice": 0.0075,
        "noPrice": 0.9925,
        "status": "active"
      }
    }
  ]
}

DELETE /api/v1/positions/:id

Cierra una posición abierta. Calcula el P&L final con el precio actual.

Params

Param Tipo Descripción
id int ID de la posición

Respuesta 200

{
  "ok": true,
  "data": {
    "id": 1,
    "status": "closed",
    "closedAt": "2026-05-16T09:15:04.155Z",
    "pnl": -99.25
  }
}

Errores

HTTP Código Cuándo
404 NOT_FOUND Posición no existe o no pertenece al usuario
409 CONFLICT La posición ya está cerrada

Criterio de Kelly

kellyFraction = (pWin - pLoss) / pWin capado al 25% y nunca negativo.

  • pWin = precio del outcome elegido (yesPrice o noPrice)
  • pLoss = 1 - pWin

El resultado es informativo — el usuario decide cuánto invertir.


Socket — actualización de P&L

El job updatePositionsPnL recalcula currentPrice y pnl de todas las posiciones abiertas cada 30s. No emite evento de socket propio; el frontend puede re-fetch GET /positions tras recibir market_update.


Ejemplos curl

TOKEN=$(curl -s -X POST http://localhost:7860/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@polysignal.test","password":"Admin123!"}' | jq -r '.data.token')

# Abrir posición
curl -s -X POST http://localhost:7860/api/v1/positions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"marketId":"559677","outcome":"YES","amountEur":50}' | jq

# Listar posiciones
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:7860/api/v1/positions | jq

# Cerrar posición (id=1)
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" http://localhost:7860/api/v1/positions/1 | jq

Códigos de error

HTTP Código Cuándo
400 VALIDATION_ERROR Body inválido (outcome no es YES/NO, amountEur <= 0)
401 UNAUTHORIZED Sin token o token inválido
404 NOT_FOUND Mercado o posición no existe
409 CONFLICT Mercado no activo o posición ya cerrada
500 INTERNAL Error inesperado