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**
```json
{
"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`**
```json
{
"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`**
```json
{
"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`**
```json
{
"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`
```bash
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 |