feat(manifold): audit matching quality with ManifoldMatchResult and manifold_match_audit table
CI/CD / build-and-push (push) Successful in 14s
CI/CD / build-and-push (push) Successful in 14s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user