Justo cuando comenzó la Copa del Mundo, el mercado nos dio una pequeña lección impactante: los equipos fuertes no son inmunes a los fracasos, solo que la forma de fracasar suele ser más creativa de lo que imaginamos.
Por ejemplo, España 0:0 Cabo Verde. Antes del partido, solo con mirar la fuerza, el ranking y la profundidad de la plantilla, la intuición de la mayoría sería simple: España debería ganar, ¿verdad? Pero lo más interesante y desconcertante del fútbol para los traders está precisamente aquí. Crees que estás comprando "victoria del equipo fuerte", pero en realidad estás comprando un drama completo de 90 minutos de destino. Sin gol en los primeros 15 minutos, la cuota se mueve; 0:0 a la media hora, el mercado comienza a fruncir el ceño; a los 70 minutos sin novedades, la aparentemente sólida victoria del equipo fuerte de repente ya no es tan sólida.
En este punto surge una contradicción clásica: el arbitraje es bueno, la predicción es difícil.
El verdadero arbitraje, en teoría, debería ser libre de riesgo. Por ejemplo, encontrar un conjunto de contratos mutuamente excluyentes y exhaustivos que, sin importar lo que suceda al final, se paguen en conjunto, y cuyo costo de compra sea menor que el monto del pago. Suena genial, como la siesta de la versión trader. Pero el problema está aquí: esas oportunidades son tan escasas que, cuando aparecen, suelen desaparecer rápidamente y la capacidad no siempre es suficiente.
La predicción es otra historia. Puedes juzgar que el equipo fuerte probablemente gane, puedes analizar la alineación, el estado de forma, el calendario, las lesiones, y también puedes observar las probabilidades implícitas en las cuotas, pero el partido no va a cooperar solo porque analicemos con esmero. El fútbol es así, especialmente: una tarjeta roja temprana, un palo, un VAR absurdo, pueden convertir una lógica perfecta en tres segundos de silencio en el análisis post-partido.
Así que la pregunta es: si el arbitraje puro es demasiado escaso y la predicción pura es demasiado aleatoria, ¿podemos colocarnos en el medio? El mercado ya nos ha dado algunas probabilidades previas, como el precio de la victoria del equipo fuerte, el precio del 0:0, el precio del 0:1. Estos precios no surgen de la nada; reflejan la valoración colectiva del mercado sobre diferentes caminos. Entonces, ¿podemos usar esas probabilidades previas, combinadas con un modelo matemático propio, para construir un conjunto imperfecto pero más protector, y abrir una ventana de "convergencia de caminos"?
Este es el punto de partida de esta idea.
No es un arbitraje libre de riesgo tradicional, ni una mera predicción intuitiva. Es más como un compromiso: primero reconocer que las probabilidades principales dadas por el mercado tienen valor de referencia, luego usar caminos de baja probabilidad para proteger las partes más frágiles de la predicción principal, y al mismo tiempo usar un modelo simple para verificar si el precio sigue siendo aceptable.
Supongamos un partido donde Brasil es claramente superior a Haití. El mercado da un precio para la victoria de Brasil de 0.89, lo que indica que ganar Brasil es un camino principal fuerte. Pero no compramos solo la victoria de Brasil; también observamos dos caminos de protección: 0:0 y 0:1. Porque para una apuesta a la victoria de un equipo fuerte, el guión inicial más incómodo no suele ser todos los resultados, sino que el partido no se abre o el equipo débil marca primero.
Entonces la combinación inicial es:
text
Brasil gana Sí
0:0 Sí
0:1 Sí
Suponiendo precios:
text
Brasil gana = 0.89
0:0 = 0.016
0:1 = 0.011
Costo total de las tres patas:
text
C = 0.89 + 0.016 + 0.011 = 0.917
Este conjunto no es un evento exhaustivo. No cubre 1:1, 0:2, 2:2, ni todos los guiones extraños. Por lo tanto, si se mantiene hasta el final del partido, por supuesto puede perder, y puede perder de manera directa. Pero tiene una característica muy importante: combina las tres probabilidades que el mercado ya ha dado, formando una cesta de caminos alrededor de la victoria principal.
Si al final Brasil gana, el conjunto paga 1. Si al final es 0:0 o 0:1, también paga 1. Es decir, lo que se compra es:
text
Brasil gana ∪ 0:0 ∪ 0:1
Si ocurre uno de estos caminos, el pago al vencimiento es 1. El costo es 0.917, hay un margen de 0.083 dentro de los caminos cubiertos. Pero la clave real no está en el final, sino en el camino.
Si Brasil marca temprano y el marcador llega a 1:0, entonces las patas de protección 0:0 y 0:1 básicamente se vuelven cero, pero el precio de la victoria de Brasil subirá. Siempre que el precio de venta de la victoria de Brasil en ese momento supere el costo total inicial, más un pequeño beneficio objetivo, podemos cerrar la pata principal y liquidar la combinación anticipadamente.
La fórmula es simple:
text
Condición de take profit = Oferta de Brasil gana >= Costo total inicial C + Beneficio objetivo
Por ejemplo, estableciendo beneficio objetivo en 0.02:
text
Precio de activación = 0.917 + 0.02 = 0.937
Si después del 1:0 la oferta de Brasil gana llega a 0.95, entonces:
text
Beneficio bloqueado = 0.95 - 0.917 = 0.033
En este caso, no estamos ganando dinero por el resultado final, sino porque el camino del partido entró en una rama favorable, vendiendo la combinación antes de tiempo. Este proceso es un poco como si el mercado te abriera una ventana pequeña; no te quedes en la ventana escribiendo poesía, primero pasa por ella.
Pero aquí hay otro problema: las tres patas se ven baratas, pero ¿siempre vale la pena comprarlas? No necesariamente. El precio de mercado es solo la primera capa de información. Necesitamos nuestra propia regla de referencia, aunque sea simple.
Aquí uso el modelo más básico de Poisson para los goles.
El marcador de fútbol puede aproximarse como un proceso aleatorio de goles de ambos equipos en 90 minutos. Suponiendo que el número esperado de goles del equipo local en 90 minutos es λ_local y el del equipo visitante es λ_visitante, entonces la probabilidad de que el local meta i goles y el visitante j goles se puede escribir como:
text
P(i, j) = Pois(i; λ_local) × Pois(j; λ_visitante)
donde:
text
Pois(k; λ) = e^(-λ) × λ^k / k!
Por supuesto, es muy burdo. El fútbol no son dos máquinas de lotería independientes; después de un gol, la táctica cambia, una tarjeta roja cambia, la mentalidad cambia. Pero su ventaja es que es simple y transparente, al menos nos permite no tomar decisiones solo por intuición.
Antes del partido, el modelo de Poisson ya puede darnos una referencia inicial. Podemos dar manualmente λ_local y λ_visitante, o dejar que el programa los infiera a partir de las cuotas de marcadores exactos del mercado. Por ejemplo, las cuotas para 0:0, 0:1, 1:0, 1:1, 2:0, 2:1, 3:0, cada una es un punto de probabilidad dado por el mercado. El programa debe encontrar un par de λ_local y λ_visitante que haga que las probabilidades de marcador calculadas por el modelo de Poisson se acerquen lo más posible a estos precios de mercado.
En otras palabras, el mercado dice: "Creo que estos marcadores valen aproximadamente ese dinero". El modelo de Poisson susurra: "Entonces intentaré ajustarme para ver qué intensidad goleadora se esconde detrás de estos precios".
En la estrategia se puede configurar:
python
CALIBRATE_LAMBDA_FROM_MARKET = True
MODEL_SCORE_SAMPLES = "0-0,0-1,1-0,1-1,2-0,2-1,3-0"
Estos marcadores solo se usan para modelar, no participan en las órdenes. Las patas que realmente se negocian siguen siendo:
text
Victoria del equipo objetivo
0:0
0:1
El código usa búsqueda en cuadrícula para ajustar λ. No es complejo, pero es muy intuitivo.
python
def fit_lambdas_from_score_markets(quotes, model_score_legs, event_state=None):
samples = []
live_score = event_state.get("score_tuple") if event_state else None
minute = event_state.get("elapsed") if event_state else None
is_live = bool(live_score and minute not in [None, ""])
for leg in model_score_legs:
q = quotes.get(leg["name"])
p_market = quote_probability(q)
if p_market is None:
continue
target_h, target_a = parse_score_text(leg["score"])
if is_live:
current_h, current_a = live_score
if current_h > target_h or current_a > target_a:
continue
samples.append((target_h - current_h, target_a - current_a, p_market, leg["score"]))
else:
samples.append((target_h, target_a, p_market, leg["score"]))
if len(samples) < 2:
return None
best = None
for ih in range(5, 501, 5):
lh = ih / 100.0
for ia in range(5, 501, 5):
la = ia / 100.0
err = 0.0
for add_h, add_a, p_market, _score in samples:
p_model = poisson_pmf(add_h, lh) * poisson_pmf(add_a, la)
err += (p_model - p_market) ** 2
if best is None or err < best["err"]:
best = {"lambda_home": lh, "lambda_away": la, "err": err, "samples": samples}
if is_live:
ratio = max(0.01, max(0.0, 90.0 - float(minute)) / 90.0)
best["lambda_home"] = best["lambda_home"] / ratio
best["lambda_away"] = best["lambda_away"] / ratio
best["source"] = "live_score_markets"
else:
best["source"] = "pre_match_score_markets"
return best
Antes del partido, esta λ es solo una calibración inicial. Como el partido aún no ha comenzado, no hay una trayectoria en tiempo real que actualizar, el marcador por defecto sigue siendo 0:0 y el tiempo restante es de 90 minutos. En esta fase lo usamos como filtro de entrada:
text
Probabilidad de cobertura del modelo = P(equipo objetivo gana) + P(0:0) + P(0:1)
Y luego exigimos:
text
Probabilidad de cobertura del modelo - Costo de mercado >= Margen de seguridad
Solo permitimos abrir posición cuando el precio de mercado es lo suficientemente barato y el modelo considera que esta cesta tiene una ligera ventaja.
Lo realmente interesante ocurre una vez que el partido comienza.
En cuanto el partido empieza, el modelo de Poisson deja de ser estático. Supongamos que el partido llega al minuto 30 y el marcador sigue 0:0; entonces el tiempo restante es solo de 60 minutos, y la intensidad goleadora futura debe escalarse según el tiempo restante:
text
λ_local_restante = λ_local × (90 - t) / 90
λ_visitante_restante = λ_visitante × (90 - t) / 90
Si el marcador actual ya es 1:0, entonces los resultados 0:0 y 0:1 ya no pueden ocurrir. En ese caso, el modelo debe partir del marcador actual y calcular únicamente cuántos goles se marcarán en el tiempo restante. Solo los marcadores que aún pueden ocurrir al final del partido, como 2:0, 2:1, 3:0, 1:1, etc., son elegibles para participar en la nueva estimación.
Esta es la clave de la actualización en tiempo real de Poisson: no se trata de reducir mecánicamente la λ previa al partido minuto a minuto, sino de reestimar cómo podría desarrollarse el resto del partido combinando el marcador actual, el tiempo restante y las cuotas de marcadores exactos que aún son posibles.
La estrategia final se convierte en tres niveles de decisión.
El primer nivel es el costo de mercado:
text
win_ask + 0:0_ask + 0:1_ask <= Costo máximo permitido
El segundo nivel es el filtro de Poisson:
text
Probabilidad de cobertura del modelo - Costo de mercado >= Margen de seguridad
El tercer nivel es la convergencia de la trayectoria:
text
Valor bid actual de la combinación >= Costo inicial + Beneficio objetivo
Solo cuando se cumplen simultáneamente el primer y el segundo nivel se considera la entrada. Una vez dentro, ya no se depende del modelo para fantasear sobre el resultado final, sino que se usa el bid real del mercado para decidir si se puede cerrar la posición. El modelo se encarga de mejorar la calidad de la entrada; el mercado decide si se puede materializar la ganancia.
La búsqueda de contratos también se mantiene lo más simple posible. El slug de los mercados de la Copa Mundial en Polymarket sigue un patrón muy regular. Por ejemplo, el slug del evento de un partido es:
python
EVENT_SLUG = "fifwc-aut-jor-2026-06-17"
Si estamos protegiendo que gane Jordania, el sufijo del contrato de victoria correspondiente es:
python
WIN_SUFFIX = "jor"
Entonces los tres contratos necesarios para la operación se pueden construir directamente:
python
def yes_symbol(slug):
return slug + "_USDC.Yes"
def build_legs():
legs = [
{
"name": "win",
"slug": EVENT_SLUG + "-" + WIN_SUFFIX,
"symbol": yes_symbol(EVENT_SLUG + "-" + WIN_SUFFIX),
"kind": "win",
}
]
for score in parse_scores(PROTECT_SCORES):
legs.append(
{
"name": "score_" + score.replace("-", "_"),
"slug": EVENT_SLUG + "-exact-score-" + score,
"symbol": yes_symbol(EVENT_SLUG + "-exact-score-" + score),
"kind": "score",
"score": score,
}
)
return legs
Aquí es mejor no usar el nombre del equipo para búsquedas difusas. Por ejemplo, si buscamos "Jordan", es fácil que aparezcan Michael B. Jordan, Jordan Pickford, Jordan Spieth, y antes de que la estrategia de fútbol comience, el mundo del espectáculo, el portero inglés y el golf ya están sentados a la mesa. Usar el slug del evento para construir los contratos es mucho más limpio.
Durante el partido también necesitamos el marcador en tiempo real. En la primera versión se puede obtener directamente del evento de Polymarket Gamma:
python
def get_event_state():
data = get_json(GAMMA_BASE + "/events", slug=EVENT_SLUG)
e = data[0]
return {
"title": e.get("title"),
"score": e.get("score"),
"score_tuple": parse_score(e.get("score")),
"elapsed": e.get("elapsed"),
"period": e.get("period"),
"live": bool(e.get("live")),
"ended": bool(e.get("ended")),
"start_time": e.get("startTime"),
}
Calcular el valor actual vendible de la cesta usando el bid real del mercado:
python
def basket_bid_value(legs, quotes):
total = 0.0
for leg in legs:
pos = positions.get(leg["name"], {})
amount = float(pos.get("amount", 0))
if amount <= 0:
continue
q = quotes.get(leg["name"])
if not q or q["bid"] is None:
continue
total += amount * q["bid"]
return total
Juicio de toma de ganancias:
python
cost = current_position_cost()
value = basket_bid_value(legs, quotes)
target = cost + TARGET_PROFIT * SHARES
if value >= target:
close_all()
Esta es la versión mínima. No pretende haber resuelto la predicción del fútbol ni afirmar que puede hacer arbitraje de forma estable. Simplemente intenta estudiar esa zona gris entre "arbitraje" y "predicción".
Lo hermoso del arbitraje es la certeza, pero la certeza es escasa. La tentación de la predicción radica en el gran espacio que ofrece, pero también conlleva mucha aleatoriedad. Lo que hace esta estrategia es tomar el precio de predicción que ofrece el mercado como material, usar un juicio principal de alta probabilidad, junto con dos rutas de protección de baja probabilidad, y luego aplicar un filtro grueso con un modelo de Poisson, para tratar de construir una ventana que pueda converger anticipadamente durante el transcurso del partido.
Los riesgos deben ser claros. Esta combinación no cubre todos los sucesos posibles, no es un arbitraje sin riesgo. Es adecuada para partidos con una brecha significativa entre el fuerte y el débil, donde la ruta principal sea lo suficientemente clara y el precio de las rutas de protección sea lo suficientemente bajo. Si el partido es igualado, o si las patas de protección ya son caras, forzarlas no tiene sentido. Lo más problemático es que no cubre todas las rutas adversas, como 1:1, 0:2, 2:2, que podrían causar pérdidas significativas en la combinación. Por lo tanto, es necesario establecer un límite máximo de posición, una pérdida máxima y reglas de stop loss; no se debe tratar esto como un arbitraje solo porque "parece un arbitraje".
El modelo de Poisson tampoco es magia. Es solo una base matemática muy simple que nos ayuda a pasar de "creo que este precio está bien" a "al menos he comprobado este precio con un modelo transparente". Se equivocará, el mercado también se equivocará, y nosotros nos equivocaremos aún más. La parte más honesta del trading es probablemente reconocer que todo esto puede estar equivocado, y tratar de que el error no vuelque la mesa de una sola vez.
Este intento es muy superficial. Es más como hacer una pregunta: cuando las oportunidades de arbitraje puro son cada vez más escasas y la predicción pura es demasiado difícil, ¿podemos aprovechar las probabilidades que ya ofrece el mercado para construir algunas operaciones más estructuradas? No se trata de soñar con eliminar el riesgo, sino de desglosarlo y ver por qué caminos podría venir.
Quizás no sea la respuesta, pero es un pequeño agujero que vale la pena seguir excavando. La predicción se encarga de decirnos qué camino principal podría ser más amplio, y la idea del arbitraje nos recuerda que no solo miremos la dirección, sino también la estructura de la combinación. Quizás haya mucho más espacio por explorar entre ambos.
Código fuente de la estrategia: Estrategia de convergencia de rutas de fútbol de Polymarket
- 1




