Skip to content

Optional pre-trade resolution-risk guard (advisory, fail-open, off by default) - #4

Open
NicolasDuarte04 wants to merge 1 commit into
ImMike:mainfrom
NicolasDuarte04:feat/resolution-guard
Open

Optional pre-trade resolution-risk guard (advisory, fail-open, off by default)#4
NicolasDuarte04 wants to merge 1 commit into
ImMike:mainfrom
NicolasDuarte04:feat/resolution-guard

Conversation

@NicolasDuarte04

Copy link
Copy Markdown

Optional pre-trade resolution-risk guard (advisory, fail-open, off by default)

What this adds. An optional core/resolution_guard.py plus a small hook in the
cross-platform path that, when enabled, checks whether a text-matched
Polymarket/Kalshi pair actually settles on the same thing before the bot treats it
as a hedge. Off by default — with mode.resolution_guard_enabled: false (the
default) the bot behaves exactly as it does today.

Why. MarketMatcher.calculate_similarity() matches markets by text/entity
similarity, then check_arbitrage() routes the matched pair straight to an
opportunity. Text similarity isn't settlement equivalence: a Polymarket (UMA)
market can resolve a cancelled event to "No" while the matched Kalshi market
settles to "a fair price" — so a pair that looks like a hedge becomes one open
directional bet in the void/cancellation branch. This guard catches that class
(void-rule, scope, settlement-source/timing divergence) before the trade. As the
README notes, real markets are efficient and gaps are rare — and the gaps that
do survive are disproportionately the ones that survive because the legs aren't
fungible
, i.e. the dangerous ones.

Design — it cannot break the bot. Advisory and fail-open: if the gate is
unreachable, slow, errors, or unauthenticated, the guard returns degraded and
the bot proceeds exactly as if it were absent. resolution_guard_strict: true is
opt-in for operators who prefer to halt on uncertainty. It sends only the two
market references and the outcome leg — never positions, sizes, or strategy.
No
new dependency (httpx is already in requirements.txt). The guard fires only
when an edge is actually found, so it adds at most one short HTTP call per signal.

Worked example (a real, live verdict). On the World Cup pair Australia vs
Türkiye (2026-06-14) the gate returns matching_confidence: 0.98 but
semantically_fungible: false and execution_verdict: block, with a fatal
void_rule finding (one leg resolves "No" on cancellation, the other "to a fair
price"). See the README section for the full output.

Tests. tests/test_resolution_guard.py covers the fail-open paths (gate raises
→ trade allowed; 401/non-200 → trade allowed), the BLOCK path (divergence → skip),
the SAFE path, and strict-mode halt-on-uncertainty. The full existing suite stays
green (43 passed locally).

Files

  • core/resolution_guard.py (new) — the advisory client + allows_trade().
  • core/cross_platform_arb.py — carry polymarket_condition_id on MarketPair;
    optional guard config on CrossPlatformArbEngine; the skip hook in
    check_arbitrage.
  • utils/config_loader.py + config.yaml — three opt-in mode.resolution_guard_*
    knobs (default off).
  • run_with_dashboard.py — pass the config through to the engine.
  • README.md — short worked-example section.

Aside, not part of this PR: I noticed the repo has no LICENSE file (issue #2) —
without one, contribution terms here are undefined by default. Happy to also add an
MIT LICENSE (matching what the README implies) in a separate commit if useful.

Happy to adjust framing, defaults, or drop it behind a more conservative flag —
whatever fits the project. If it's not a fit, no worries; closing is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant