Skip to Content
Conceptos básicosCiclo de Vida del Mercado

Ciclo de Vida del Mercado: Suspensiones y Eliminaciones

Los mercados salen del tablero constantemente — una casa suspende tras un gol, retira un umbral de hándicap o total cuando la línea se mueve, o liquida el evento. SharpAPI señala cada una de estas transiciones explícitamente en cada superficie de consumo. Nunca necesitas inferir que un mercado cerró esperando a que su precio parezca obsoleto.

Esta página es el mapa: qué señal se dispara cuándo, en qué superficie, y cómo mantener limpio tu estado local.

Las Dos Formas en que un Mercado Sale del TableroEnlace permanente a esta sección

Las casas de apuestas retiran mercados del tablero de dos maneras distintas, y SharpAPI refleja ambas fielmente:

ModoQué hace la casaQué emite SharpAPI
EliminaciónLa casa retira el mercado por completo (modo dominante — p. ej. Pinnacle retirando un peldaño de hándicap/total, DK/FD suspendiendo durante una jugada de anotación, un evento liquidándose)El id de la fila aparece en removed[] en /odds/delta y en un evento odds:removed en el stream SSE y WebSocket. La fila está ausente del siguiente snapshot de /odds.
Suspensión en el sitioLa casa mantiene el mercado publicado pero lo marca como cerrado — el precio queda congelado y no apostableLa fila permanece presente con is_active: false. La transición se envía como un odds:update con is_active: false, más un evento dedicado odds:locked en el stream.

SharpAPI nunca fabrica una fila que una casa no publicó. Un mercado eliminado se señala con un evento de eliminación explícito que lleva su id — no con una fila de precio “cerrado” sintética. Los ids de cuotas son deterministas — un hash estable de casa, evento, mercado, línea y selección — así que un mercado que regresa llega bajo el mismo id, y borrar-al-eliminar más upsert-al-actualizar mantiene tu estado exactamente sincronizado con el tablero.

Matriz de Señales por SuperficieEnlace permanente a esta sección

SuperficieSeñal de eliminaciónSeñal de suspensión
GET /odds (polling de snapshots)Fila ausente de la siguiente respuestais_active: false en la fila
GET /odds/deltaremoved[] — objetos {id, sportsbook, removed_at}is_active: false en filas de data[]
SSE /streamodds:removed — {ids: [...], count, book}odds:update con is_active: false, más odds:locked
WebSocketEvento odds:removedodds:update con is_active: false, más odds:locked

Si haces polling de /odds plano, la eliminación se comunica por ausencia: compara cada snapshot con el anterior y elimina las filas cuyos ids ya no aparecen. Funciona, pero te obliga a ti a calcular el diff — para avisos de eliminación explícitos, usa /odds/delta (su array removed[] nombra cada id retirado desde tu último poll) o el stream (push, sin polling alguno).

El endpoint delta retiene las eliminaciones durante 10 minutos. Haz polling a la cadencia recomendada (encadena since desde el meta.server_time de la página terminal — la página con has_more: false) y nunca perderás ninguna; consulta Delta de Cuotas para los flags de seguridad removed_truncated y since_clamped.

Escaleras de Umbrales: Hándicaps y TotalesEnlace permanente a esta sección

Los mercados de hándicap (spread) y total son escaleras — un conjunto de peldaños en distintas líneas (-2.5, -3, -3.5, …; O/U 2.25, 2.5, 2.75, …). Cuando la línea principal se mueve, las casas — Pinnacle en particular — retiran los peldaños en los umbrales antiguos y publican nuevos. Cada peldaño es su propia fila de cuotas con su propio id, así que:

  • Un peldaño retirado (el mercado de umbral que desaparece) dispara odds:removed / aparece en removed[], exactamente como cualquier otra eliminación.
  • El peldaño nuevo llega como una fila fresca vía odds:update (o en el siguiente snapshot/delta), con los flags is_main_line / is_alternate_line indicándote dónde se sitúa en la escalera.
  • Un peldaño que regresa (común en juego en vivo — las casas recentran las escaleras constantemente) vuelve a llegar bajo el mismo id determinista.

La rotación de la escalera es intensa durante el juego en vivo: los peldaños alternativos van y vienen muchas veces por partido. Trata una eliminación como “fuera del tablero ahora mismo”, no como un cierre terminal — solo la liquidación del evento es definitiva.

total 2.5 (id …_total_over_2.5) ── la línea se mueve a 3 ──▶ odds:removed [..._total_over_2.5] odds:update [..._total_over_3] (peldaño nuevo)

Patrón de cliente recomendadoEnlace permanente a esta sección

Indexa tu estado local por id de cuota y aplica tres reglas, en orden de eventos:

  1. odds:update (o una fila en data[]) → upsert de la fila.
  2. odds:removed (o un id en removed[]) → borrar la fila.
  3. is_active: false → conservar la fila pero marcarla como no apostable (atenuarla); una reapertura llega como un odds:update normal con is_active: true y un precio fresco.
# Delta-polling loop: explicit removals, no client-side diffing since = initial_timestamp while True: offset = 0 while True: r = get(f"/api/v1/odds/delta?since={since}&sportsbook=pinnacle" f"&limit=500&offset={offset}").json() for row in r["data"]: local_state[row["id"]] = row # upsert (covers is_active flips) for gone in r.get("removed", []): local_state.pop(gone["id"], None) # delete — the close signal if not r["pagination"]["has_more"]: if r.get("overflow"): # scan stopped short: since would gap raise RuntimeError("re-baseline from /odds, then resume with the " "first baseline page's updated_at as since") since = r["meta"]["server_time"] # terminal page: real watermark break if r["pagination"]["next_offset"] is None: raise RuntimeError("re-baseline from /odds, then resume with the " "first baseline page's updated_at as since") offset = r["pagination"]["next_offset"] sleep(5)
// SSE: push-based, the same three rules es.addEventListener('odds:update', (e) => { for (const row of JSON.parse(e.data).odds) localState.set(row.id, row); }); es.addEventListener('odds:removed', (e) => { for (const id of JSON.parse(e.data).ids) localState.delete(id); });

Precios Congelados y Protecciones contra Precios ObsoletosEnlace permanente a esta sección

Dos flags a nivel de fila te protegen de actuar sobre un precio que la casa ya no honra:

CampoSignificado
is_activefalse = el mercado está suspendido/cerrado con el precio congelado en su último valor. Atenúalo; no lo apuestes, no lo alimentes a cálculos de EV. Ausente se trata como true.
is_stale_pregame_pricetrue en una fila en vivo que aún lleva un precio pre-partido que no se ha movido desde el inicio — filtra con ?is_stale_pregame_price=false si solo quieres precios reajustados en vivo.

Los propios motores de EV y arbitraje de SharpAPI excluyen las patas con is_active: false, de modo que los endpoints de oportunidades nunca muestran una ventaja contra un precio congelado y no apostable.

Pinnacle mayormente elimina los mercados suspendidos (→ odds:removed) y marca un subconjunto menor como cerrado en el sitio (→ is_active: false / odds:locked). Las casas minoristas de EE. UU. como DraftKings y FanDuel retiran los mercados por completo durante las jugadas de anotación — verás eliminaciones, no cambios de is_active. Maneja ambos modos y estarás cubierto para todas las casas.

RelacionadoEnlace permanente a esta sección

  • Delta de Cuotas — referencia de removed[], ventana de retención, patrón de polling
  • Stream SSE — odds:removed, odds:locked, is_active en odds:update
  • WebSocket — los mismos eventos sobre WS
  • En Vivo vs. Pre-Partido — comportamiento de suspensión de las casas en juego en vivo
Last updated on