feat(manifold): audit matching quality with ManifoldMatchResult and manifold_match_audit table
CI/CD / build-and-push (push) Successful in 14s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-05-27 15:57:48 +00:00
parent ae7c737153
commit 9abaae44fd
8 changed files with 431 additions and 84 deletions
+87 -4
View File
@@ -12,16 +12,19 @@ Polymarket might reflect in a slow-moving order book.
"""
import logging
import math
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Optional, TYPE_CHECKING
from bot.data.polymarket import Market, market_family_key
from bot.data.external import ExternalSignals
from bot.data.manifold import ManifoldMatchResult
if TYPE_CHECKING:
from bot.data.news import NewsClient
from bot.data.manifold import ManifoldClient
from bot.data.db import Database
log = logging.getLogger(__name__)
@@ -170,6 +173,19 @@ class TradingSignal:
feat_news_lo: float = 0.0
feat_mfld_lo: float = 0.0
feat_btc_dom_lo: float = 0.0
# ── Manifold match audit (propagated → Order → Trade → DB) ───────────────
# mfld_audit_id: UUID of the manifold_match_audit row; used to mark
# used_in_trade=TRUE after executor confirms the trade was executed.
mfld_audit_id: Optional[str] = None
mfld_market_id: Optional[str] = None
mfld_market_title: Optional[str] = None
mfld_market_url: Optional[str] = None
mfld_prob_raw: Optional[float] = None
mfld_prob_final: Optional[float] = None
mfld_inverted: bool = False
mfld_match_score: Optional[float] = None
mfld_match_reason: Optional[str] = None
mfld_match_status: Optional[str] = None
class BayesianStrategy:
@@ -201,10 +217,12 @@ class BayesianStrategy:
self,
news: Optional["NewsClient"] = None,
manifold: Optional["ManifoldClient"] = None,
db: Optional["Database"] = None,
) -> None:
self._signal_count = 0
self._news = news
self._manifold = manifold
self._db = db
self._news_queries_this_cycle = 0
# Per-cycle counters — reset by reset_cycle(), read by get_cycle_stats()
self._skip_family: int = 0
@@ -419,18 +437,72 @@ class BayesianStrategy:
# Signal 5: Manifold cross-market probability (politics + tech)
# Applies a log-odds adjustment proportional to divergence from prior.
# No query budget — 30 min cache means network cost is paid once per cycle.
# Now uses ManifoldMatchResult for stricter semantic validation and audit.
manifold_log_adj = 0.0
manifold_used = False
manifold_result: Optional[ManifoldMatchResult] = None
audit_id: Optional[str] = None
if (is_politics or is_tech) and self._manifold is not None:
manifold_prob = await self._manifold.get_probability(market.question)
if manifold_prob is not None:
manifold_result = await self._manifold.get_match(market.question)
# Persist audit record for ALL outcomes (accepted / rejected / no_results)
if self._db is not None:
if not market.id:
log.error(
"MANIFOLD_AUDIT: market.id is None/empty — skipping audit save | "
"question=%r", market.question[:60],
)
else:
audit_id = str(uuid.uuid4())
try:
await self._db.save_manifold_audit(
audit_id=audit_id,
poly_market_id=market.id,
poly_question=market.question,
search_query=manifold_result.search_query,
mfld_market_id=manifold_result.market_id,
mfld_market_title=manifold_result.market_title,
mfld_market_url=manifold_result.market_url,
prob_raw=manifold_result.prob_raw,
prob_final=manifold_result.prob_final,
inverted=manifold_result.inverted,
match_score=manifold_result.match_score,
match_reason=manifold_result.match_reason,
match_status=manifold_result.status,
)
except Exception as exc:
log.warning("Failed to save manifold audit: %s", exc)
audit_id = None
# Structured log — both forms for compatibility
log.info(
"MANIFOLD_MATCH poly='%s' mfld='%s' score=%s raw=%s final=%s"
" inverted=%s status=%s reason=%s",
market.question, manifold_result.market_title,
manifold_result.match_score, manifold_result.prob_raw,
manifold_result.prob_final, manifold_result.inverted,
manifold_result.status, manifold_result.match_reason,
)
log.info("MANIFOLD_MATCH", extra={
"poly_question": market.question,
"mfld_title": manifold_result.market_title,
"score": manifold_result.match_score,
"prob_raw": manifold_result.prob_raw,
"prob_final": manifold_result.prob_final,
"inverted": manifold_result.inverted,
"status": manifold_result.status,
"reason": manifold_result.match_reason,
})
if manifold_result.status == "accepted" and manifold_result.prob_final is not None:
manifold_used = True
self._manifold_fetched += 1
m_clamped = max(0.05, min(0.95, manifold_prob))
m_clamped = max(0.05, min(0.95, manifold_result.prob_final))
m_log = math.log(m_clamped / (1 - m_clamped))
p_log = math.log(prior / (1 - prior))
manifold_log_adj = (m_log - p_log) * MANIFOLD_LOGODDS_WEIGHT
sources.append(f"Manifold:{manifold_prob:.2f}")
sources.append(f"Manifold:{manifold_result.prob_final:.2f}")
# Confidence cap: macro/politics/tech signals are weaker proxies
confidence_cap = 0.65 if (is_macro or is_politics or is_tech or is_events) else 0.90
@@ -560,6 +632,17 @@ class BayesianStrategy:
feat_news_lo=feat_news_lo,
feat_mfld_lo=feat_mfld_lo,
feat_btc_dom_lo=feat_btc_dom_lo,
# Manifold match audit — propagated through Order → Trade → DB
mfld_audit_id=audit_id,
mfld_market_id=manifold_result.market_id if manifold_result else None,
mfld_market_title=manifold_result.market_title if manifold_result else None,
mfld_market_url=manifold_result.market_url if manifold_result else None,
mfld_prob_raw=manifold_result.prob_raw if manifold_result else None,
mfld_prob_final=manifold_result.prob_final if manifold_result else None,
mfld_inverted=manifold_result.inverted if manifold_result else False,
mfld_match_score=manifold_result.match_score if manifold_result else None,
mfld_match_reason=manifold_result.match_reason if manifold_result else None,
mfld_match_status=manifold_result.status if manifold_result else None,
)