| name | worldcup-prediction |
| description | World Cup football match prediction pipeline producing calibrated probability distributions (1X2, totals, top scorelines), edge analysis vs de-vigged market odds, fractional-Kelly sizing with a strict no-bet rule, and qualification scenario simulation. Use this skill whenever the user asks to predict, analyse, or preview a World Cup (or other football) match, asks about win/draw/loss chances, expected goals or scorelines, odds value, line movement, betting edge, Kelly staking, group standings or qualification scenarios, or asks to run the agent pipeline / generate a match prediction report — even if they just say "分析这场比赛" or "这场怎么看". |
World Cup Prediction Skill
A five-agent, market-anchored prediction pipeline. Core design rules (do
not violate):
- One frozen snapshot. Every run starts by building a timestamped
bundle via
scripts/snapshot.py. All agents read that file only. Never
fetch live data mid-pipeline.
- Numbers come from scripts, not debate. Agents emit structured JSON;
scripts/aggregator.py produces the final probabilities. The
consistency review may force factual corrections and re-emission — it
may never negotiate the fused number.
- Market is the anchor. De-vig snapshot odds with Shin's method;
market weight 0.5, agents 1/4/5 split the rest equally (fixed by spec).
- Distributions, never point picks. Output 1X2 probabilities, totals
lines, top-5 scorelines with probabilities. No single-score "prediction",
no corners market.
- Read-only. No order placement, no automated betting, no
circumvention of bookmaker controls. Kelly output is informational.
- Every run is scored. Log all vectors with
scripts/scorekeeper.py
before kickoff; settle after; report Brier/RPS/logloss/CLV.
- Skill changes are decided by FULL-SAMPLE evidence, never a single match.
Before adding/removing/reverting any rule (totals lean, guard, vote
construction, draw-boost…), backtest it across ALL settled results — win-rate
AND ROI for bets (1X2, totals/over-under, scorelines, cards) and mean Brier
for probabilities — and keep whatever wins on the accumulated record. One
result is noise: do NOT flip-flop a rule because the last game went against
it (lesson: the favourite-vs-minnow OVER lean was added → reverted on ONE
loss → the full sample showed it was +EV all along, 5/8, ROI +13.5%). Re-
evaluate only as n grows meaningfully, not per match.
Requirements & conventions
- Python deps:
pip install numpy scipy (everything else is stdlib).
- API keys via env vars (see references/data_sources.md); never commit keys.
- Suggested layout: snapshots in
~/worldcup/snapshots/, scorekeeper db at
~/worldcup/logs/predictions.jsonl, fitted params in ~/worldcup/.
- If The Odds API returns
matched: false, read all_fixture_names in the
snapshot and add the variant to assets/team_aliases.json.
- In-tournament form outranks everything. This World Cup's already-played
matches are the single most representative data on a team's current squad,
form, and conditions — they carry the HIGHEST weight, and EVERY agent must
reference them from matchday 2 on:
- models: after each matchday, append the completed WC results to the
training CSV with the top
importance (~2.0, above any friendly/qualifier)
and refit (dixon_coles.py --importance-pow 1, elo.py). Time-decay +
importance then both favour them.
- agent3: read HOW each side actually set up and performed in their WC game
(formation, set-piece events, who started) over pre-tournament friendlies.
- agent_benham: prefer WC npxG once available over friendly/qualifier npxG.
- agent4/agent5: a team's WC result/performance is A-tier evidence and the
first reference point; friendlies are discounted next to it.
Matchday 1 has no prior WC games — say so and fall back to the 24-month base.
MD1 openers also run draw-heavy (rust/caginess/lopsided sides easing off):
turn on
--opener-draw-boost at aggregate (see step 12) and expect favourites
to convert less often than the 24m base or the market implies.
Modes
- fast_mode (kickoff within 90 min, or user asks a quick view): snapshot
→ market de-vig → injuries/news one-paragraph summary → weather → edge +
no-bet check. No five-agent run; say explicitly that it is the quick view.
- full_mode (default for pre-match reports): the complete pipeline below.
- in_play_mode (match is live; user asks for a live read). ⚠️ This is the
ONE mode that BREAKS rule #1 on purpose — it uses live data, not a frozen
snapshot, and never feeds the pre-match scorekeeper log. Workflow:
- Carry the pre-match
lambda_home/lambda_away from the last
dixon_coles predict as the prior (do NOT re-fit live).
- Pull LIVE state: current score from The Odds API /scores (scores are
populated while
completed:false); minute + red cards from a live feed
(ESPN live); in-play h2h odds from /odds.
- python scripts/in_play.py --lambda-home .. --lambda-away ..
--minute M --home-score H --away-score A [--red-home/--red-away]
[--live-odds H D A]
- Report live 1X2 / totals / top final scorelines + edge, LABELLED live
and timestamped. Live Kelly uses a SMALLER fraction than pre-match and
respects suspensions/latency (state it). Game-state effects beyond red
cards (leader killing the game) are not modelled — flag, don't fake.
- scenario_mode (display / mock-up only). For presentations where a
probability is hand-set for illustration, use
scripts/scenario_report.py --base REAL.json --set score:0-1=.. --set under_1.5=... It emits a bundle hard-flagged scenario:true do_not_log:true
with overrides_applied (showing each real→set value), written ONLY under
scenarios/. RULES: the rendered report MUST keep one short footer marking
it a scenario/展示版; a scenario bundle is NEVER passed to scorekeeper log,
NEVER settled, and NEVER presented as the model's real output. This is how
"free to adjust for a demo" and "never pollute the settled record" stay
true at once.
Pipeline (run in this order)
0. Read references/data_sources.md (keys, endpoints, free-tier limits)
1. SNAPSHOT python scripts/snapshot.py --fixture-id .. --home .. --away ..
--venue-id .. --kickoff-utc .. --out snapshots/
Before this, gather news via web tools (ESPN/theScore/RotoWire
injury+lineup pages) AND player ratings. For player data use the
FotMob semi-auto pipeline (FBref/Sofascore block bots even in a
real browser; FotMob works — see data_sources.md):
scripts/fotmob_ratings.sh <home-slug> --name "Home"
scripts/fotmob_ratings.sh <away-slug> --name "Away"
(slugs in assets/fotmob_team_ids.json). RELIABLE: squad heights +
aerial threats → grounds agent3 key_duels; VARIABLE: rating/xG/xA
leaders (present if FotMob tracked the team's recent comp). Merge
into player_ratings; summarise news+ratings into a JSON file,
pass via --extra-json so everything is frozen in the bundle.
FRESHNESS: a snapshot built >90 min out uses PREDICTED XIs —
its bets are provisional. Before acting on a bet, take a
SECOND snapshot at ~T-75min once official XIs drop, with
--lineups-confirmed, and diff the two with line_movement.py.
The bundle records minutes_to_kickoff + lineups_confirmed so
kelly.py / the report can flag provisional sizing automatically.
1b. AGENT QUALIFIER (runs FIRST; every matchday). For EACH side, set the
qualification STANCE that shapes how they play this match.
ALWAYS build the group state from the LIVE data layer first — it
pulls current standings + remaining fixtures from football-data.org
and fills each remaining fixture's probs/λ from the REFIT DC, so
the sim is never run on stale or hand-typed numbers (re-run after
every result; needs FOOTBALL_DATA_TOKEN):
python scripts/build_group_state.py --params ~/worldcup/dc_params.json
--out group_state.json --target <Home> --bracket-out bH.json
python scripts/build_group_state.py --params ~/worldcup/dc_params.json
--target <Away> --bracket-out bA.json # reuse same state
python scripts/bracket_sim.py --state bH.json # per side
NEVER hand-author group.json — if build_group_state emits warnings
(unmapped team / missing score), fix the alias, don't fake a number.
bracket_sim returns finish_probs + the R32 opponent TIER per finish (1st→a
3rd-placer=easiest, 2nd→runner-up, 3rd→a winner=hardest) +
recommended_target_rank. Pick each side's target rank (ability ×
opponent tier) and derive `this_match_intent` (win_and_max_GD /
win / damage_limit_or_steal_point / rotate_rest). Stance feeds
agent2/agent3 and agent1's bounded λ only — NEVER the fused number.
Stance exists from matchday 1 (GD is the tiebreaker). See
references/agents.md "Agent Qualifier".
2. AGENT 2 Off-field intel report. Classify every news item strictly per
references/source_credibility.md. From matchday 2-3, when the
table makes results matter, run the stakes tools (matchday 1
openers have no standings — skip). Feed the SAME live
group_state.json built in step 1b (build_group_state.py) — do not
rebuild it by hand:
python scripts/qualification_sim.py --state group_state.json # qualify% / winner%
python scripts/strategic_scenario.py --state group_state.json
--r32-from-bracket bracket_<team>.json # last-round value
model; --r32-from-bracket auto-derives the R32 opponent
strength per finishing slot from the bracket_sim output above
(tier1 winner=0.68 / tier2=0.55 / tier3 third-placer=0.42) —
no hand-typed r32_map
⭐ REQUIRED — this is ONE value model, not a set of named cases.
A side maximises EXPECTED TOURNAMENT VALUE: each finishing slot has
value V = qualify-base + a bonus that DECREASES with the R32
opponent that slot inherits (easier path = more value, so V(1st)
can be < V(2nd) when topping the group draws the favourite); 0 if
eliminated. The script reports per side, AGAINST THE CURRENT TABLE:
- ev_by_result {win,draw,loss} and target_result = argmax EV
- chase_value = EV(win) − EV(draw) (≤0 ⇒ no reason to win)
- defend_value = EV(draw) − EV(loss) (large ⇒ must not lose)
- posture (a region of that space, with a one-line read):
must_win | press_for_win (chase≫defend) | secure_first
(defend≫chase ⇒ bank the point, win only if cheap) |
protect_result | prefer_not_to_win (dodge the favourite) |
win_or_out (only a win survives) | chase_seed_no_fear |
indifferent_safe (rotate).
READ the gradients against the live form, then state the totals/
draw implication: secure_first / protect_result / prefer_not_to_win
/ indifferent_safe ⇒ lower tempo, UNDER + DRAW lean; win_or_out /
must_win / press_for_win ⇒ higher tempo. Then apply the modifiers
before trusting it: teams rarely actively LOSE (prefer_not_to_win =
a softening, not放水); the OPPONENT's posture too (both content ⇒
mutual low-event draw); feasibility (can agent3 execute it?); and
downstream certainty (earlier MD / unfinished groups ⇒ discount the
dodge). Fold into strategic_pressure + the totals/draw read as
CONTEXT only — never let it move the fused number. best-3rd & GD
tiebreaks need qualification_sim (enumerator flags gd_can_decide).
3. AGENT 3 Tactical features (structured fields only; essay = appendix).
⭐⭐ DATA-DRIVEN (required, esp. KNOCKOUTS where tactics decide):
GROUND the tactical read in OFFICIAL FIFA team statistics, not
narrative. Pull them first:
python scripts/fifa_fetch.py teams # 48 teams, ~32 metrics each
python scripts/fifa_fetch.py matchup --home X --away Y
The matchup prints both sides' value+[tournament rank/48] for
possession, xG, shots, on-target, CONVERSION% (finishing
efficiency = goals vs xG), shots-in-box, headed shots, crosses +
accuracy, corners, defensive_pressures, forced_turnovers,
ball_recovery_time (lower=higher press), clean_sheets, GK save%.
Read the REAL style from the numbers — they often OVERTURN the
narrative (e.g. NED-MAR: Morocco out-possesses & out-xGs the
"attacking" Dutch, but NL convert 24% [rank 2] vs Morocco 12%
[23] and NL keep 0 clean sheets → the true edges are NL finishing
vs Morocco control+waste, NOT "Dutch attack vs Moroccan bus").
Derive style_matchup / press-vs-build / wide-vs-central /
finishing-efficiency / set-piece threat (corners+headed shots)
from the stats; combine with the predicted XIs + formations +
positional coords from api.fifa.com (snapshot/lineups) for the
key_duels. (`fifa_fetch.py players --team X` adds player-level
goals/assists/shots when the rate-limit allows.)
Write structured fields to agent3_features.json, then HARD-GATE
before agent1 (set_piece for BOTH sides is mandatory — a Bosnia
corner was the only goal missed in the Canada-Bosnia review):
python scripts/validate_features.py --inputs agent3_features.json
Knockouts: weight agent3 MORE — single-elimination, no rotation,
tactical matchup + finishing efficiency + set-pieces decide tight
games. Still CONTEXT: colours style/tempo/key_duels and feeds
agent1 ONLY via the bounded λ channel — never the fused number.
4. AGENT 1 Fit/refresh models if needed:
python scripts/dixon_coles.py fit --matches data.csv --out dc.json
python scripts/elo.py fit --matches data.csv --out elo.json
Predict with bounded adjustments derived from agents 2/3:
python scripts/dixon_coles.py predict --params dc.json
--home X --away Y --home-adj -0.08
python scripts/elo.py predict --ratings elo.json --home X --away Y
Neutral venue is the DEFAULT; add --hosts only when USA,
Mexico or Canada play at home.
⭐ TOTALS correction is now BUILT IN (--totals-boost, default 1.10,
TOTALS ONLY — 1X2/scorelines/E[goals] stay pure DC). backtest.py
on 2098 intl matches, TWO independent hold-outs, found the pure
model SYSTEMATICALLY UNDER-PREDICTS overs (60-75% fav band: pred
0.448 vs actual 0.614); +10% lambda was the best correction in
BOTH windows. (Symmetric over-dispersion --totals-dispersion did
~nothing — the bias is in the mean, not the variance; kept only as
an option.) The boost ALSO holds on 233 real major-tournament
matches (StatsBomb open data): actual over-2.5 0.468 vs boost-off
0.405 vs boost-1.10 0.460. Re-validate as the WC sample grows:
python scripts/backtest.py --matches ~/worldcup/intl_matches_24m.csv
--cutoff <ISO> # 1X2+totals OOS scoring, bias-by-fav-strength
diagnosis, dispersion/game-state/boost sweeps
python scripts/statsbomb_fetch.py matches # free tournament
results; `events` mode pulls xG/set-pieces for tournament-only
feature builds (no scraping; research-licensed)
python scripts/statsbomb_features.py # per-team xG/set-piece
python scripts/statsbomb_validate.py # go/no-go on a feature
⭐ VALIDATED on 262 major-tournament matches: a per-team SET-PIECE
rating is NOISE (split-half repeatability r=0.12 vs open-play 0.53)
— do NOT build one / do not weight a team's past set-piece output
as a stable prior (it's ~45% of pattern-xG but unpredictable per
team). xG > goals as a forward signal (0.285 vs 0.203) → feed xG
via the existing Benham xg_home/xg_away correction, not a new model.
⭐ HEAT also tested (heat_backfill.py + heat_backtest.py, 1381
matches): NO general heat->goals effect — raw corr is a climate-
zone CONFOUND, within-country detrended r=-0.004. Do NOT add a heat
lambda term. (Only >=34C noon extremes might matter; needs 2026
exact-venue data to confirm — track prospectively, don't model now.)
Optionally compute context edges first (rest/travel/altitude):
python scripts/context_factors.py --home X --away Y --venue-id V
--kickoff-utc T [--home-last-utc.. --away-last-utc..
--home-venue-last.. --away-venue-last..]
Apply npxG corrections (references/metrics.md) via
--xg-home/--xg-away, and context via --home-adj/--away-adj.
Optionally walk the match (phase-by-phase + fitness/game-state):
python scripts/tactical_progression.py --lambda-home .. --lambda-away ..
[--fade-side .. --fade-from-min .. --fade-strength ..
--fragile-side ..]
It returns half/phase xG, first-goal timing, and a CAPPED
(±0.06) fitness nudge for --home-adj/--away-adj. Its
gamestate_caveat is CONTEXT only. Confidence guard: progression
detail never raises 1X2 confidence (the pro report it imitates
was detailed and still wrong — Qatar drew the 81% favourite).
Then blend via script (never by hand). ALWAYS pass the shin-
devigged market probs + the Elo-underdog guard: when DC agrees
with the market on the favourite but Elo alone flips to the
underdog by ≥0.15, the guard down-weights Elo (Elo has been wrong
every such time — USA-Paraguay, NED-JPN, SWE-TUN; backtest −0.07
Brier, zero effect on consensus / both-models-dissent games):
python scripts/blend_models.py --dc dc_out.json --elo elo_out.json
--market <Hs> <Ds> <As> --underdog-elo-guard
(guard only fires on the lone-Elo-dissent pattern; it leaves
50/50 untouched otherwise. Devig the snapshot odds early for this;
it does not pre-empt the formal MARKET step.)
5. AGENT BENHAM Underlying-performance vector (npxG/structure, not goals
or reputation). Joins the fusion. See references/agents.md +
references/metrics.md.
6. AGENT 4 Own vector, anchored on market; every >3pp deviation cites
A/B-tier evidence.
7. AGENT 5 Red team: structured challenges first, then its own vector.
7b. AGENT CARDS Discipline/bookings analyst (NON-voting; separate output
value, never touches 1X2). Fetch the assigned referee + their
cards/game average AND both teams' recent cards/game (web/FotMob;
fouls/game as a flagged proxy if exact cards are paywalled);
missing data → neutral baseline + low_confidence, never faked.
Build cards_state.json with bounded tactical multipliers from
agent3 (physical/grind) + qualifier (must-win tension), then:
python scripts/cards_model.py --state cards_state.json
Edge only if a cards market exists (most WC fixtures: none →
context). See references/agents.md "Agent Cards".
7c. AGENT FLOW Cross-venue money-flow analyst (NON-voting; never touches 1X2).
Aggregates REAL-MONEY signals — Polymarket + on-chain (Azuro/
Overtime/SX) prediction markets + Betfair Exchange order flow +
book bets%/handle% splits — into a qualitative read that FEEDS
agent_bloom.
⚠️ POLYMARKET IS MANDATORY every prediction (it needs NO key — there
is no reason to skip it). One resolver builds the state (finds the
event by slug, falls back to title-matching, maps the 3 sub-markets
to home/draw/away "Yes" tokens), then flow_analyzer pulls live mids:
python scripts/poly_lookup.py --home France --away Senegal
--date 2026-06-16 --emit-flow-state
--market-fair <Hs> <Ds> <As> --dc-totals <O15> <O25> <O35>
--bet-candidate <our_value_outcome> --out flow_state.json
python scripts/flow_analyzer.py --state flow_state.json
(<Hs Ds As> = Pinnacle shin-fair probs; <O15 O25 O35> = DC's
over_1.5/2.5/3.5. poly_lookup ALSO pulls the GOALS money — full-match
O/U totals, spread, BTTS, exact-score (the '-more-markets' and
'-exact-score' sibling events) — and flow_analyzer's goals_flow
compares the money's P(over) to DC's. missing_legs → partial, never
faked.)
Betfair (strongest order-flow signal) needs an ACTIVATED LIVE app key
+ cert login (delayed key has no matched volume) — env BETFAIR_APP_KEY
/USERNAME/PASSWORD/CERT/KEY; if those are unset, say "Betfair blind"
explicitly (do not silently drop it). On-chain subgraphs config-driven.
⭐ In the report, ALWAYS state whether the real money CONFIRMS or
CONTRADICTS our edge — a liquid Polymarket agreeing with Pinnacle is
evidence AGAINST a model-only lean (e.g. France-Senegal: $3.79M PM
priced the draw at 21.4% ≈ Pinnacle 20.9%, killing a model draw edge).
Read-only: NEVER places bets / evades limits/KYC/geo. Degrades
cleanly per venue; missing data → skipped + low confidence, never
faked. See references/agents.md "Agent Flow" + data_sources.md.
8. AGENT BLOOM Market/execution audit (no vector): price deviation vs shin
fair probs, cross-market coherence, line-movement/steam,
agent_flow's money-flow read, execution feasibility. Emits a
verdict, may raise a challenge.
9. REVIEW ≤2 rounds. Factual errors → targeted agent corrects & re-emits.
10. MARKET python scripts/odds_utils.py devig --odds H D A --method shin
(prefer Pinnacle from the snapshot). For totals use
--method auto (2-way markets use power, not shin). If an earlier snapshot of this
fixture exists, also run:
python scripts/line_movement.py --old OLD.json --new NEW.json
And cross-book best-price / dispersion:
python scripts/line_shopping.py --snapshot SNAP.json --market h2h
11. VALIDATE python scripts/validate_vector.py --inputs vectors.json
(hard-fails on malformed vectors; >3pp market deviations
without documented evidence are flagged)
12. AGGREGATE python scripts/aggregator.py --inputs vectors.json --market-weight 0.5
[--opener-draw-boost 0.02-0.03 — RECOMMENDED ON for any
matchday-1 opener, NOT just cagey ones. WC2026 MD1 ran very
draw-heavy: 3/8 early + the 6/15 slate Spain 0-0 / Belgium 1-1
/ Saudi 1-1 (3/4 draws). It applies EVEN to lopsided openers —
Spain (89% fav) drew Cape Verde 0-0; do NOT disable it because
"the favourite is too strong" (that was a settled miss). Cap
0.05; still off for knockout/MD2+ games where the market
re-prices on real form. Small-n: a prior, not a guarantee.]
The market entry's probs = market_snapshot.fair_probs_shin
from the snapshot bundle.
12b. BET PREFLIGHT (only if an edge looks bettable AND kickoff is near).
Gather the CONFIRMED XIs from the fastest source (data_sources.md
speed table: Sofascore/FotMob/official X), write them into a new
extra-json, then run the one-command ~T-75min refresh:
scripts/refresh_and_diff.sh FIXTURE HOME AWAY VENUE KO \
NEW_EXTRA OLD_SNAPSHOT
It rebuilds the snapshot (--lineups-confirmed), diffs it, and
runs the NEWS RADAR. If the radar says MATERIAL MOVE and you
cannot explain it from news, STOP — informed money saw something
you haven't; find it before betting. Re-run EDGE on the fresh
snapshot's market.
12b. RULE 1 ALWAYS run the DC-relative betting signals (totals + 1X2) — the
single most discriminating per-match signal on the settled sample
(88% vs 43-44% split). Needs the agent1 DC predict JSON + the shin
market probs:
python scripts/rule1.py --dc dc_out.json --market <Hs> <Ds> <As>
Returns: TOTALS — bet OVER 2.5 only if DC over_2.5 >= 0.45 (else
skip); 1X2 — gap = market_fav - dc_fav: gap<0.15 & fav>=0.55 →
back the favourite, gap>=0.15 → FADE it (over-priced favourite DC
distrusts; won only 43%). It INFORMS the EDGE/bet decision below
(and the report's bet section), never overrides the fused 1X2.
IN-SAMPLE caveat + small n are in its output — keep it small and
re-calibrate as the settled count grows.
13. EDGE For any outcome with positive edge:
python scripts/kelly.py --prob P --prob-low BAND_LOW --odds O
--market-type {1x2|asian_handicap|totals|btts|player_props}
[--no-lineup --minutes-to-kickoff N]
Respect no_bet / provisional_only verbatim. Lineup-dependent
markets without a confirmed XI (>90 min out) are provisional.
⭐ When logging the bet, TAG it AND record the bet-time market
price (FIXED PROCESS — from the money-detection step):
bet_ledger.py bet .. --kind edge|challenge [--edge-pct E] --mkt-odds M
edge = Kelly/Rule-1 gave a genuine positive edge; challenge = the
forced bet-every-match minimum with no real edge (size at floor).
⭐⭐ CLV FIXED PROCESS (do NOT skip — CLV is the only low-variance
edge proof): the CLOSING price (line right before KO) must be
recorded for EVERY bet. Capture it as close to kickoff as possible
via line_read.py / poly_lookup, then:
bet_ledger.py close --match F --closing-odds <closing price>
If not active at KO, obtain the close at settle — capture via the
tools or ASK THE USER for it; never settle a bet with no closing
price (settle prints a loud warning if missing).
Cross-check against RULE 1: prefer bets where Rule 1 and Kelly
agree (e.g. Rule-1 "back favourite" + positive Kelly edge); when
Rule 1 says FADE the favourite, do not back it even if some model
shows a thin edge.
13b. AGENT DESK Closing synthesis & recommendation (NON-voting; runs LAST so
it sees the fused numbers + kelly + every soft layer). HYBRID:
the script enforces discipline, Claude writes the prose. Build
desk_state.json (fused 1X2 + band + totals + kelly/no_bet +
soft: qualifier/intel/tactical/cards/flow/bloom), then:
python scripts/desk_synthesizer.py --state desk_state.json
It returns a synthesis scaffold + signal-coherence (aligned vs
CONFLICTS) + typed recommendations (main / alt_angle / watch /
stand_down). HARD: a leg Kelly/band killed is NEVER a `main`
(→ watch/stand_down); confidence capped by evidence tier;
conflicts surfaced, not hidden. Owns the report's closing
"研判 + 推荐" section. See references/agents.md "Agent Desk".
14. LOG scorekeeper.py log for market/agent1/agent_benham/agent4/agent5/final.
Also log each agent_desk recommendation:
scorekeeper.py log-rec --fixture-id F --rec-type {main|alt_angle|
watch|stand_down|context} --market .. --selection .. [--ref-price]
15. REPORT Render the report template below.
16. SETTLE After full time: scorekeeper.py settle --closing-odds H D A;
settle the recommendations too (hit-rate / ROI / shadow-P&L of
what the discipline skipped):
scorekeeper.py settle-rec --fixture-id F --result R [--total-goals N]
⭐ MANDATORY — record the closing price so CLV is captured (use
`bet_ledger.py close` pre-KO, or pass --closing-odds here; if the
close is unknown, ASK THE USER before settling) —
over <~50 bets W/L is mostly noise; a +mean CLV (beating the
close) is the low-variance proof the process has edge:
bet_ledger.py settle --match F --result R --closing-odds <price>
periodically show scorekeeper.py report (prob/Brier) AND
scorekeeper.py rec-report (recommendation hit-rate by type) AND
bet_ledger.py status (now: CLV mean + beat-close% + edge-vs-
challenge ROI split).
Agent role contracts, I/O schemas and the review protocol:
references/agents.md (read before running agents).
Model theory and World Cup-specific adjustments: references/models.md.
Report template (final output to user)
- Match context — fixture, kickoff (UTC + venue local), venue + roof,
stage, standings pressure, qualification probabilities if relevant
1b. Qualification & stance (REQUIRED, from agent_qualifier) — for BOTH sides:
finish_probs, the target rank + which R32 opponent TIER each finish gives
(1st→3rd-placer easiest …), and the resulting this_match_intent
(win_and_max_GD / damage_limit_or_steal_point / rotate_rest …). State the
GD/suspension angle. NOTE it is context that colours tactics — it does NOT
move the fused 1X2. Matchday 1: ambition-driven; matchday 3: concrete result.
⭐ W/D/L → value → behaviour (REQUIRED on matchday 2-3, from
strategic_scenario — ONE value model, no named cases): for each side give
ev_by_result + target_result, the two gradients chase_value / defend_value,
and the posture with its read (e.g. secure_first = a win is best but not-
losing matters far more → bank the point; prefer_not_to_win = winning tops
into a harder R32 path). Tie the totals & draw read to the posture
explicitly, then note the modifiers actually applied (opponent's posture,
feasibility, downstream certainty, "teams rarely actively lose"). Flag if
best-3rd / GD tiebreaks apply (cite qualification_sim, not the enumerator).
- Team news — injuries/suspensions/lineups, each with tier + freshness
- Market — snapshot odds, shin-fair probabilities, best price across books
- consensus + dispersion flags (line_shopping), line movement since open
if available, money-flow signals (labelled per data_sources.md)
3b. Money flow (from agent_flow, if any venue data) — cross-venue implied probs
(Pinnacle / Betfair / Polymarket / on-chain), dispersion, money-vs-tickets,
flow_read + recommendation + confidence. State which venues were live vs
skipped (esp. Betfair needs a live key). Read-only signal — does NOT move the
fused 1X2; it informs the Bloom view / bet decision only.
ALSO REQUIRED — goals/score money (goals_flow): a DC-totals-vs-money-totals
table (over_1.5/2.5/3.5: DC P(over) | Polymarket P(over) | Δ | vol), the
spread money, and the top exact-score money. Two distinct signals:
(a) the money-vs-DC DIVERGENCE ("lean toward money") = INFO ONLY, ~coin-flip
(2/3 this WC), do NOT bet off it;
(b) the favourite-vs-minnow OVER 2.5 lean (over_lean, fav>=0.65) = a SMALL,
FULL-SAMPLE +EV bet (5/8 = 62.5%, break-even 54%, ROI ~+13.5%). Decide on
the ACCUMULATED record, not single games — the POR-COD 1-1 loss is within
variance. Size small (high variance, bimodal goals); re-evaluate only as
n grows; do NOT flip-flop the rule on one result.
- Weather — values + impact score, scaled by roof relevance
- Probabilities — fused 1X2 + fair odds + disagreement band; totals;
Asian handicap line (from the score matrix); top-5 scorelines;
per-agent vectors in an appendix
5c. Cards / discipline (REQUIRED, from agent_cards) — expected total cards +
most-likely count, over/under lines, per-team split, P(≥1 red), and the
referee name + their cards/game average that drives it. State whether team
averages are exact or estimated (low_confidence) and cite sources. Cards are
high-variance and this value does NOT affect the 1X2 / scorekeeper Brier.
Hard behavioural constraints
- Never present a pick as certain ("必买", "稳了", "lock") — always
probabilities with the disagreement band.
- If
no_bet is true, the report must say so prominently; do not soften it.
- If snapshot sources failed/skipped, list them in Caveats — never
fabricate odds, lineups, news, or weather.
- C-tier-only claims never adjust model inputs.
- Do not place bets, automate wagering, or advise on evading bookmaker
limits/KYC. If asked, decline that part and continue the analysis.
- If signs of problem gambling appear (chasing losses, distress,
bet-everything requests), pause the analysis and respond with care;
mention responsible-gambling support.