fix(critical): complementary market family grouping + Manifold inversion guard
CI/CD / build-and-push (push) Successful in 2m23s
CI/CD / build-and-push (push) Successful in 2m23s
FASE 1 — market_family_key() general election fix
General elections now group by office, not by party, so complementary
markets ("Republicans win Ohio governor" / "Democrats win Ohio governor")
share the same family key (ohio-gubernatorial-2026). The second market
is blocked by the occupied_families check rather than traded as independent.
Primaries still keep the party (texas-republican-2026) because each party
runs its own separate primary race.
FASE 2 — Manifold party inversion guard
_detect_party() identifies the winning side in both the Polymarket question
and the matched Manifold title. If they are confirmed opposites (republican
vs democrat), the probability is inverted (1 - prob) before use.
Full audit log per query:
poly_question / manifold_title / manifold_url / match_score /
prob_raw / inverted / prob_final
Root cause of Ohio Manifold:0.95 on both sides: both queries matched the
same Manifold market ("Republicans win Ohio governor" prob=0.95). For the
"Democrats win" query the inversion now produces prob_final=0.05 instead of
blindly applying 0.95 to the wrong direction.
FASE 4 — startup contradiction scan
get_open_position_details() added to db.py. main.py checks all open
positions at startup, warns on any family with >1 position, and recommends
keeping the one with the highest edge_net. No auto-close.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+32
-5
@@ -132,20 +132,47 @@ def market_family_key(market: "Market") -> str:
|
||||
return f"fed-{month_m.group(1).lower()}-{year}"
|
||||
return f"fed-{year}"
|
||||
|
||||
# 2. US state + party (primary, senate, governor, etc.)
|
||||
# 2. US state + election event
|
||||
# Key design: general elections group by office, not by party, so
|
||||
# "Republicans win Ohio governor" and "Democrats win Ohio governor"
|
||||
# share the same family (ohio-gubernatorial-2026) and the bot can only
|
||||
# hold one position. Primaries keep the party because each party runs
|
||||
# its own primary (texas-republican-primary is distinct from texas-democrat-primary).
|
||||
state_m = _US_STATE_RE.search(q)
|
||||
party_m = _PARTY_RE.search(q)
|
||||
if state_m and party_m:
|
||||
etype_m = _ELECTION_TYPE_RE.search(q)
|
||||
|
||||
if state_m and (party_m or etype_m):
|
||||
state = re.sub(r"\s+", "-", state_m.group(1).lower())
|
||||
raw_party = party_m.group(1).lower()
|
||||
# "democrat" prefix covers "democrat", "democrats", "democratic"
|
||||
is_primary = etype_m is not None and "primary" in etype_m.group(1).lower()
|
||||
|
||||
if party_m and is_primary:
|
||||
# Primary race: party is the disambiguation (each party has its own primary)
|
||||
raw_party = party_m.group(1).lower()
|
||||
party = "democrat" if "democrat" in raw_party else "republican"
|
||||
return f"{state}-{party}-{year}"
|
||||
|
||||
if etype_m:
|
||||
# General election: family = office, not party
|
||||
# "Republicans win Ohio governor" == "Democrats win Ohio governor" → same race
|
||||
raw_etype = etype_m.group(1).lower()
|
||||
etype = {
|
||||
"president": "presidential",
|
||||
"mayor": "mayoral",
|
||||
"governor": "gubernatorial",
|
||||
}.get(raw_etype, raw_etype)
|
||||
return f"{state}-{etype}-{year}"
|
||||
|
||||
# Has party but no election type — preserve old behaviour (e.g. "Texas Republican")
|
||||
raw_party = party_m.group(1).lower() # type: ignore[union-attr]
|
||||
party = "democrat" if "democrat" in raw_party else "republican"
|
||||
return f"{state}-{party}-{year}"
|
||||
|
||||
# 3. Named non-US city / country
|
||||
for place_re, place_slug in _NAMED_PLACES:
|
||||
if place_re.search(q):
|
||||
etype_m = _ELECTION_TYPE_RE.search(q)
|
||||
if etype_m is None:
|
||||
etype_m = _ELECTION_TYPE_RE.search(q)
|
||||
if etype_m:
|
||||
raw_etype = etype_m.group(1).lower()
|
||||
# Normalise synonyms
|
||||
|
||||
Reference in New Issue
Block a user