feat(manifold): add persistent cooldowns to reduce redundant evaluations
CI/CD / build-and-push (push) Successful in 8s

The trading loop re-evaluated the same ~22 politics/tech markets every ~60s,
flooding manifold_match_audit with ~76k rows (~3,500 attempts/market) of which
none carried new information, making the metrics uninterpretable.

Add per-market persistent cooldowns:
- New table manifold_eval_cooldown (poly_market_id PK, last_evaluated_at,
  last_status, retry_after, cooldown_reason) created via run_migrations.
- bayesian.evaluate() consults the cooldown BEFORE calling the matcher and skips
  the call entirely while now() < retry_after — no matcher call, no audit row,
  no signal (equivalent to no_results). After a real evaluation it upserts the
  cooldown with a verdict-dependent backoff: no_results/low_score/outcome_mismatch/
  ambiguous_inversion -> 24h, conditional_market -> 7d, accepted -> 1h.
- manifold.py stays a pure client/matcher with no DB access.
- New db methods get_manifold_cooldown / upsert_manifold_cooldown.
- /api/metrics/manifold-matches summary gains unique_markets
  {evaluated, accepted, coverage_rate} for per-market (not per-attempt) coverage.

Matching logic, MANIFOLD_MATCHER_VERSION, MANIFOLD_LOGODDS_WEIGHT, edge/exposure
thresholds and existing audit rows are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-06-08 12:19:31 +00:00
co-authored by Claude Opus 4.8
parent df988a36b6
commit 98abd96fd2
4 changed files with 212 additions and 59 deletions
+57
View File
@@ -553,6 +553,46 @@ class Database:
poly_outcome_type, mfld_outcome_type, matcher_version,
)
async def get_manifold_cooldown(self, poly_market_id: str) -> Optional[dict]:
"""Return the cooldown row for a market, or None if no cooldown is recorded.
The caller decides whether the cooldown is still active by comparing
now() against retry_after (see bayesian.evaluate()).
"""
async with self._pool.acquire() as conn:
row = await conn.fetchrow(
"SELECT poly_market_id, last_evaluated_at, last_status, "
"retry_after, cooldown_reason FROM manifold_eval_cooldown "
"WHERE poly_market_id = $1",
poly_market_id,
)
return dict(row) if row else None
async def upsert_manifold_cooldown(
self,
poly_market_id: str,
last_status: str,
retry_after,
cooldown_reason: Optional[str] = None,
) -> None:
"""Insert or refresh the cooldown for a market after a real evaluation.
last_evaluated_at is stamped server-side with now(); retry_after is the
caller-computed earliest re-evaluation time.
"""
async with self._pool.acquire() as conn:
await conn.execute("""
INSERT INTO manifold_eval_cooldown (
poly_market_id, last_evaluated_at, last_status,
retry_after, cooldown_reason
) VALUES ($1, now(), $2, $3, $4)
ON CONFLICT (poly_market_id) DO UPDATE SET
last_evaluated_at = now(),
last_status = EXCLUDED.last_status,
retry_after = EXCLUDED.retry_after,
cooldown_reason = EXCLUDED.cooldown_reason
""", poly_market_id, last_status, retry_after, cooldown_reason)
async def mark_manifold_audit_used(self, audit_id: str) -> None:
async with self._pool.acquire() as conn:
await conn.execute(
@@ -592,6 +632,15 @@ class Database:
WHERE matcher_version = 'legacy_pre_outcome_guard'
AND match_status = 'accepted'
""")
unique_markets = await conn.fetchrow("""
SELECT
COUNT(DISTINCT poly_market_id) AS evaluated,
COUNT(DISTINCT poly_market_id) FILTER (
WHERE match_status = 'accepted'
AND matcher_version = $1
) AS accepted
FROM manifold_match_audit
""", MANIFOLD_MATCHER_VERSION)
mfld_dominated = await conn.fetchrow("""
SELECT COUNT(*) AS cnt FROM trades
WHERE (excluded_from_metrics IS NOT TRUE)
@@ -627,6 +676,14 @@ class Database:
int(legacy["accepted_without_outcome_type"] or 0),
},
"trades_dominated_by_mfld": int(mfld_dominated["cnt"] or 0),
"unique_markets": {
"evaluated": int(unique_markets["evaluated"] or 0),
"accepted": int(unique_markets["accepted"] or 0),
"coverage_rate": (
float(unique_markets["accepted"]) / float(unique_markets["evaluated"])
if unique_markets["evaluated"] else None
),
},
},
"recent_matches": [dict(r) for r in rows],
}