feat(manifold): decouple Manifold from edge model (observational-only)
CI/CD / build-and-push (push) Successful in 8s
CI/CD / build-and-push (push) Successful in 8s
Per-category coverage audit showed coverage_rate=0.0 across every category in the bot's current universe, so any edge Manifold produced was false edge. Retire it as an ACTIVE trading signal while keeping the full audit/coverage/cooldown trail for a future reactivation decision. Two module-level flags in bayesian.py (read from env): - MANIFOLD_SIGNAL_ENABLED (default False): when False, Manifold never touches the edge model — manifold_log_adj stays 0.0 (no posterior shift), no confidence bump, feat_mfld_lo=0.0 (so it can never be the dominant feature), no trade contribution, and mfld_audit_id is not propagated so the audit's used_in_trade stays FALSE. - MANIFOLD_AUDIT_ENABLED (default True): matcher still runs; audit/coverage rows and cooldowns are still written. The matcher is only called when a flag is on. When signal is disabled, logs and reasoning carry "Manifold: observational_only". Endpoints /api/metrics/manifold-matches and /api/metrics/manifold-coverage, cooldowns, audit tables and existing trades are unchanged. No code or tables removed. Other signals, thresholds, exposure, risk manager and the executor are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
823914789d
commit
3a353c7e5b
@@ -12,6 +12,7 @@ Polymarket might reflect in a slow-moving order book.
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
@@ -61,6 +62,27 @@ NEWS_LOGODDS_WEIGHT = 1.5
|
|||||||
# Weaker than NEWS_LOGODDS_WEIGHT because Manifold can have illiquid/stale markets.
|
# Weaker than NEWS_LOGODDS_WEIGHT because Manifold can have illiquid/stale markets.
|
||||||
MANIFOLD_LOGODDS_WEIGHT = 0.6
|
MANIFOLD_LOGODDS_WEIGHT = 0.6
|
||||||
|
|
||||||
|
|
||||||
|
def _env_bool(name: str, default: bool) -> bool:
|
||||||
|
return os.getenv(name, str(default)).strip().lower() in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
|
||||||
|
# ── Manifold activation flags ──────────────────────────────────────────────────
|
||||||
|
# Manifold has been retired as an ACTIVE trading signal: a per-category coverage
|
||||||
|
# audit (see /api/metrics/manifold-coverage) showed coverage_rate=0.0 across every
|
||||||
|
# category in the bot's current universe, so any edge it produced was false edge.
|
||||||
|
#
|
||||||
|
# MANIFOLD_SIGNAL_ENABLED (default False): when False, Manifold is observational
|
||||||
|
# only — its probability never touches the edge model: no manifold_log_adj, no
|
||||||
|
# confidence bump, feat_mfld_lo stays 0.0 (so it can never be the dominant
|
||||||
|
# feature), and it never contributes to a trade.
|
||||||
|
# MANIFOLD_AUDIT_ENABLED (default True): when True the matcher still runs and
|
||||||
|
# audit/coverage rows + cooldowns are written, preserving the trail so we can
|
||||||
|
# decide later whether to reactivate Manifold in a universe with real coverage.
|
||||||
|
# The matcher is only called when at least one flag is on.
|
||||||
|
MANIFOLD_SIGNAL_ENABLED = _env_bool("MANIFOLD_SIGNAL_ENABLED", False)
|
||||||
|
MANIFOLD_AUDIT_ENABLED = _env_bool("MANIFOLD_AUDIT_ENABLED", True)
|
||||||
|
|
||||||
# GNews free tier: 100 req/day. We limit to 5 queries per trading cycle
|
# GNews free tier: 100 req/day. We limit to 5 queries per trading cycle
|
||||||
# (politics markets only) and rely on 6 h cache to stay within budget.
|
# (politics markets only) and rely on 6 h cache to stay within budget.
|
||||||
MAX_NEWS_QUERIES_PER_CYCLE = 5
|
MAX_NEWS_QUERIES_PER_CYCLE = 5
|
||||||
@@ -471,7 +493,8 @@ class BayesianStrategy:
|
|||||||
manifold_result: Optional[ManifoldMatchResult] = None
|
manifold_result: Optional[ManifoldMatchResult] = None
|
||||||
audit_id: Optional[str] = None
|
audit_id: Optional[str] = None
|
||||||
|
|
||||||
if (is_politics or is_tech) and self._manifold is not None:
|
if ((is_politics or is_tech) and self._manifold is not None
|
||||||
|
and (MANIFOLD_AUDIT_ENABLED or MANIFOLD_SIGNAL_ENABLED)):
|
||||||
# ── Cooldown gate ────────────────────────────────────────────────
|
# ── Cooldown gate ────────────────────────────────────────────────
|
||||||
# Skip markets whose Manifold verdict was recently settled to a
|
# Skip markets whose Manifold verdict was recently settled to a
|
||||||
# stable value. A skip is equivalent to a no-signal: the matcher is
|
# stable value. A skip is equivalent to a no-signal: the matcher is
|
||||||
@@ -497,8 +520,10 @@ class BayesianStrategy:
|
|||||||
if not in_cooldown:
|
if not in_cooldown:
|
||||||
manifold_result = await self._manifold.get_match(market.question)
|
manifold_result = await self._manifold.get_match(market.question)
|
||||||
|
|
||||||
# Persist audit record for ALL outcomes (accepted / rejected / no_results)
|
# Persist audit record for ALL outcomes (accepted / rejected / no_results).
|
||||||
if self._db is not None:
|
# Gated by MANIFOLD_AUDIT_ENABLED so the audit/coverage trail and
|
||||||
|
# cooldowns can be kept even while Manifold is observational-only.
|
||||||
|
if MANIFOLD_AUDIT_ENABLED and self._db is not None:
|
||||||
if not market.id:
|
if not market.id:
|
||||||
log.error(
|
log.error(
|
||||||
"MANIFOLD_AUDIT: market.id is None/empty — skipping audit save | "
|
"MANIFOLD_AUDIT: market.id is None/empty — skipping audit save | "
|
||||||
@@ -564,7 +589,10 @@ class BayesianStrategy:
|
|||||||
"reason": manifold_result.match_reason,
|
"reason": manifold_result.match_reason,
|
||||||
})
|
})
|
||||||
|
|
||||||
if manifold_result.status == "accepted" and manifold_result.prob_final is not None:
|
if (MANIFOLD_SIGNAL_ENABLED
|
||||||
|
and manifold_result.status == "accepted"
|
||||||
|
and manifold_result.prob_final is not None):
|
||||||
|
# ACTIVE signal path — only when explicitly enabled.
|
||||||
manifold_used = True
|
manifold_used = True
|
||||||
self._manifold_fetched += 1
|
self._manifold_fetched += 1
|
||||||
m_clamped = max(0.05, min(0.95, manifold_result.prob_final))
|
m_clamped = max(0.05, min(0.95, manifold_result.prob_final))
|
||||||
@@ -572,6 +600,16 @@ class BayesianStrategy:
|
|||||||
p_log = math.log(prior / (1 - prior))
|
p_log = math.log(prior / (1 - prior))
|
||||||
manifold_log_adj = (m_log - p_log) * MANIFOLD_LOGODDS_WEIGHT
|
manifold_log_adj = (m_log - p_log) * MANIFOLD_LOGODDS_WEIGHT
|
||||||
sources.append(f"Manifold:{manifold_result.prob_final:.2f}")
|
sources.append(f"Manifold:{manifold_result.prob_final:.2f}")
|
||||||
|
elif not MANIFOLD_SIGNAL_ENABLED:
|
||||||
|
# Observational-only: matched/audited but NEVER fed to the edge
|
||||||
|
# model. manifold_log_adj stays 0.0 → no confidence bump,
|
||||||
|
# feat_mfld_lo=0.0 (cannot be dominant), no trade contribution.
|
||||||
|
log.info(
|
||||||
|
"Manifold: observational_only — signal disabled "
|
||||||
|
"(MANIFOLD_SIGNAL_ENABLED=false) | market=%s status=%s",
|
||||||
|
market.id, manifold_result.status,
|
||||||
|
)
|
||||||
|
sources.append("Manifold: observational_only")
|
||||||
|
|
||||||
# Confidence cap: macro/politics/tech signals are weaker proxies
|
# 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
|
confidence_cap = 0.65 if (is_macro or is_politics or is_tech or is_events) else 0.90
|
||||||
@@ -701,8 +739,12 @@ class BayesianStrategy:
|
|||||||
feat_news_lo=feat_news_lo,
|
feat_news_lo=feat_news_lo,
|
||||||
feat_mfld_lo=feat_mfld_lo,
|
feat_mfld_lo=feat_mfld_lo,
|
||||||
feat_btc_dom_lo=feat_btc_dom_lo,
|
feat_btc_dom_lo=feat_btc_dom_lo,
|
||||||
# Manifold match audit — propagated through Order → Trade → DB
|
# Manifold match audit — propagated through Order → Trade → DB.
|
||||||
mfld_audit_id=audit_id,
|
# mfld_audit_id is the hook main.py uses to flip the audit row's
|
||||||
|
# used_in_trade=TRUE; suppress it when observational so the trail
|
||||||
|
# truthfully shows Manifold drove no trades. The mfld_* fields below
|
||||||
|
# stay as observational record (feat_mfld_lo is already 0.0).
|
||||||
|
mfld_audit_id=(audit_id if MANIFOLD_SIGNAL_ENABLED else None),
|
||||||
mfld_market_id=manifold_result.market_id if manifold_result else None,
|
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_title=manifold_result.market_title if manifold_result else None,
|
||||||
mfld_market_url=manifold_result.market_url if manifold_result else None,
|
mfld_market_url=manifold_result.market_url if manifold_result else None,
|
||||||
|
|||||||
Reference in New Issue
Block a user