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
+24
View File
@@ -202,6 +202,30 @@ async def main() -> None:
if PAPER_MODE:
await executor.initialize()
# Contradiction scan: warn if any two open positions share a family_key.
# This can happen when the family logic was less strict on a prior deploy.
# Bot does NOT auto-close — operator decides which position to keep.
positions = await db.get_open_position_details()
family_map: dict[str, list[dict]] = {}
for pos in positions:
fk = pos.get("family_key") or ""
if fk:
family_map.setdefault(fk, []).append(pos)
for fk, members in family_map.items():
if len(members) > 1:
best = max(members, key=lambda p: p.get("edge_net") or 0.0)
log.warning(
"CONTRADICTION family=%s has %d open positions — recommend keeping market_id=%s (edge_net=%.3f):",
fk, len(members), best["market_id"], best.get("edge_net") or 0.0,
)
for m in members:
marker = "KEEP" if m["market_id"] == best["market_id"] else "REVIEW"
log.warning(
" [%s] %s | dir=%s | edge_net=%.3f | %s",
marker, m["market_id"], m["direction"],
m.get("edge_net") or 0.0, m["question"][:60],
)
try:
await run_trading_loop(poly, external, strategy, risk, executor, metrics, db)
finally: