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:
+24
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user