fix(critical): complementary market family grouping + Manifold inversion guard
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:
chemavx
2026-04-17 10:26:29 +00:00
parent 0cdb0758c4
commit ebdcff5a6e
4 changed files with 161 additions and 30 deletions
+32 -5
View File
@@ -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