Optional pre-trade resolution-risk guard (advisory, fail-open, off by default) - #4
Open
NicolasDuarte04 wants to merge 1 commit into
Open
Optional pre-trade resolution-risk guard (advisory, fail-open, off by default)#4NicolasDuarte04 wants to merge 1 commit into
NicolasDuarte04 wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Optional pre-trade resolution-risk guard (advisory, fail-open, off by default)
What this adds. An optional
core/resolution_guard.pyplus a small hook in thecross-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(thedefault) the bot behaves exactly as it does today.
Why.
MarketMatcher.calculate_similarity()matches markets by text/entitysimilarity, then
check_arbitrage()routes the matched pair straight to anopportunity. 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
degradedandthe bot proceeds exactly as if it were absent.
resolution_guard_strict: trueisopt-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 (
httpxis already inrequirements.txt). The guard fires onlywhen 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.98butsemantically_fungible: falseandexecution_verdict: block, with a fatalvoid_rulefinding (one leg resolves "No" on cancellation, the other "to a fairprice"). See the README section for the full output.
Tests.
tests/test_resolution_guard.pycovers 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— carrypolymarket_condition_idonMarketPair;optional guard config on
CrossPlatformArbEngine; the skip hook incheck_arbitrage.utils/config_loader.py+config.yaml— three opt-inmode.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
LICENSEfile (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.