feat(bot): 5-phase strategy upgrade — edge neto, families, GNews priority, regimes
CI/CD / build-and-push (push) Successful in 2m30s

Phase 1 — Edge neto real (paper.py, bayesian.py, risk/manager.py, db.py):
- Trade records now store edge_gross, edge_net, prior_prob, final_prob,
  mid_price, spread_estimate, commission, family_key
- edge_net = edge_gross - SPREAD_ESTIMATE(0.02) - COMMISSION_RATE(0.02)
  NOTE: both constants are heuristics, not exact Polymarket exchange costs
- Execution gate changed from edge_gross > MIN_EDGE to edge_net > regime_min_edge

Phase 2 — Market families (polymarket.py):
- market_family_key(market) groups related markets:
    texas-republican-2026, fed-april-2026, openai-2026, etc.
- At most 1 trade per family per cycle; occupied_families propagated via main.py
- Family key logged on every TRADE and SKIP line

Phase 3 — GNews priority (news.py, bayesian.py, main.py):
- NewsClient.get_freshness() returns 1.0/0.75/0.40/0.10 by cache age
- gnews_priority(market, news) = uncertainty × volume_score × freshness
- Politics markets sorted by priority DESC before eval so best markets get
  the 5-query/cycle GNews budget first

Phase 4 — Regime min-edge by category/horizon (bayesian.py):
- politics >60d → 0.12, 30-60d → 0.10, <30d → 0.08
- tech / crypto/finance → 0.10
- All thresholds applied to edge_net (not edge_gross)

Phase 5 — Observability (bayesian.py, main.py):
- Structured skip labels: SKIP_UNSUPPORTED, SKIP_NO_SIGNALS,
  SKIP_PRIOR_EXTREME, SKIP_FAMILY, SKIP_GNEWS_PRIORITY, SKIP_EDGE_NET
- TRADE lines now include family_key, edge_gross, edge_net, regime_min, days
- schema.sql: 8 new cols on trades, 7 new cols on signals (via ALTER TABLE IF NOT EXISTS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-04-16 15:34:46 +00:00
parent a0cbdc0256
commit 63d9f637ff
8 changed files with 620 additions and 141 deletions
+26
View File
@@ -155,6 +155,32 @@ class NewsClient:
async def close(self) -> None:
await self._client.aclose()
def get_freshness(self, question: str) -> float:
"""
Return a freshness score [0.1, 1.0] for GNews priority calculation.
Score interpretation:
1.00 — never queried (maximum priority for GNews budget)
0.75 — last queried >6 h ago (cache expired, worth re-querying)
0.40 — queried 26 h ago (in-cache but moderately stale)
0.10 — queried <2 h ago (cache very fresh, low re-query value)
If the API key is absent, always returns 1.0 (key missing means the
query will be skipped anyway; don't penalise the priority score).
"""
if not self._api_key:
return 1.0
query = self._build_query(question)
cached = self._cache.get(query.lower())
if cached is None:
return 1.0
age_seconds = time.monotonic() - cached[0]
if age_seconds > 6 * 3600:
return 0.75
if age_seconds > 2 * 3600:
return 0.40
return 0.10
# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------