diff --git a/bot/main.py b/bot/main.py index 1868fc6..03e4928 100644 --- a/bot/main.py +++ b/bot/main.py @@ -10,7 +10,7 @@ from datetime import datetime, timezone from bot.data.polymarket import PolymarketClient, market_family_key from bot.data.external import ExternalDataClient from bot.data.news import NewsClient -from bot.strategy.bayesian import BayesianStrategy, gnews_priority +from bot.strategy.bayesian import BayesianStrategy, gnews_priority, MAX_NEWS_QUERIES_PER_CYCLE from bot.risk.manager import RiskManager from bot.executor.paper import PaperExecutor from bot.metrics.tracker import MetricsTracker @@ -124,9 +124,46 @@ async def run_trading_loop( occupied_families.add(signal.family_key) cycle_trades += 1 - log.info("Cycle complete — trades this cycle: %d", cycle_trades) + # 8. [CYCLE SUMMARY] — one block per cycle, stable format for grep/compare + stats = strategy.get_cycle_stats() + n_total = len(markets) + n_uncertainty = sum(1 for m in markets if 0.35 <= m.yes_price <= 0.65) + n_eval = stats["evaluated_count"] + def _pct(n: int, denom: int) -> str: + if denom == 0: + return "0% (0/0)" + return f"{n * 100 // denom}% ({n}/{denom})" + gnews_cap = strategy._news_queries_this_cycle # already updated by reset below - # 8. Update daily metrics + log.info( + "[CYCLE SUMMARY]\n" + " markets_total: %d\n" + " markets_uncertainty_zone: %d (prior 0.35-0.65)\n" + " max_edge_gross: %+.3f\n" + " max_edge_net: %+.3f\n" + " pct_edge_gross_gt_002: %s\n" + " pct_edge_gross_gt_004: %s\n" + " blocked_by_family: %d\n" + " blocked_by_prior_extreme: %d\n" + " blocked_by_edge_net_nonpositive:%d\n" + " blocked_by_edge_net_below_regime:%d\n" + " trades_executed: %d\n" + " gnews_queries_used: %d/%d", + n_total, + n_uncertainty, + stats["max_edge_gross"], + stats["max_edge_net"], + _pct(stats["gross_gt_002"], n_total), + _pct(stats["gross_gt_004"], n_total), + stats["skip_family"], + stats["skip_prior_extreme"], + stats["skip_edge_net_nonpositive"], + stats["skip_edge_net_below_regime"], + cycle_trades, + stats["gnews_queries_used"], MAX_NEWS_QUERIES_PER_CYCLE, + ) + + # 9. Update daily metrics await metrics.update_daily_summary() except Exception as e: diff --git a/bot/strategy/bayesian.py b/bot/strategy/bayesian.py index 51b70fe..c3daa14 100644 --- a/bot/strategy/bayesian.py +++ b/bot/strategy/bayesian.py @@ -184,10 +184,41 @@ class BayesianStrategy: self._signal_count = 0 self._news = news self._news_queries_this_cycle = 0 + # Per-cycle counters — reset by reset_cycle(), read by get_cycle_stats() + self._skip_family: int = 0 + self._skip_prior_extreme: int = 0 + self._skip_edge_net_nonpositive: int = 0 # edge_net <= 0 + self._skip_edge_net_below_regime: int = 0 # 0 < edge_net < regime_min + # (edge_gross, edge_net, regime_min) for every market that reached the + # edge computation stage (passed prior-extreme, family, unsupported filters) + self._evaluated_edges: list[tuple[float, float, float]] = [] def reset_cycle(self) -> None: """Call once at the start of each trading cycle to reset per-cycle counters.""" self._news_queries_this_cycle = 0 + self._skip_family = 0 + self._skip_prior_extreme = 0 + self._skip_edge_net_nonpositive = 0 + self._skip_edge_net_below_regime = 0 + self._evaluated_edges = [] + + def get_cycle_stats(self) -> dict: + """Return per-cycle counters for the [CYCLE SUMMARY] log block.""" + edges = self._evaluated_edges + all_gross = [g for g, n, r in edges] + all_net = [n for g, n, r in edges] + return { + "skip_family": self._skip_family, + "skip_prior_extreme": self._skip_prior_extreme, + "skip_edge_net_nonpositive": self._skip_edge_net_nonpositive, + "skip_edge_net_below_regime": self._skip_edge_net_below_regime, + "gnews_queries_used": self._news_queries_this_cycle, + "max_edge_gross": max(all_gross) if all_gross else 0.0, + "max_edge_net": max(all_net) if all_net else 0.0, + "evaluated_count": len(edges), + "gross_gt_002": sum(1 for g in all_gross if g > 0.02), + "gross_gt_004": sum(1 for g in all_gross if g > 0.04), + } async def evaluate( self, @@ -260,12 +291,14 @@ class BayesianStrategy: prior = max(0.05, min(0.95, market.yes_price)) if market.yes_price < 0.08: + self._skip_prior_extreme += 1 log.info( "SKIP_PRIOR_EXTREME %-50s | cat=%-12s | prior=%.3f | reason=prior<0.08", market.question[:50], category, market.yes_price, ) return None if market.yes_price > 0.92: + self._skip_prior_extreme += 1 log.info( "SKIP_PRIOR_EXTREME %-50s | cat=%-12s | prior=%.3f | reason=prior>0.92", market.question[:50], category, market.yes_price, @@ -275,6 +308,7 @@ class BayesianStrategy: # ── Phase 2: family deduplication ──────────────────────────────────── family = market_family_key(market) if family in occupied_families: + self._skip_family += 1 log.info( "SKIP_FAMILY %-50s | cat=%-12s | family=%s", market.question[:50], category, family, @@ -366,6 +400,9 @@ class BayesianStrategy: # mid_price falls back to yes_price; live order-book data is a future enhancement mid_price = market.yes_price + # Record for cycle summary — every market that reached edge computation + self._evaluated_edges.append((edge_gross, edge_net, regime_min)) + # Confidence based on signal agreement agreement = sum(1 for a in adjustments if (a > 0) == (total_adj > 0)) confidence = min(confidence_cap, 0.4 + (agreement / max(len(adjustments), 1)) * 0.5) @@ -378,6 +415,11 @@ class BayesianStrategy: can_trade = passed_net and confidence >= MIN_CONFIDENCE if not can_trade: + # Increment the appropriate edge-net counter + if edge_net <= 0: + self._skip_edge_net_nonpositive += 1 + else: + self._skip_edge_net_below_regime += 1 skip_parts: list[str] = [] if not passed_gross: skip_parts.append(f"edge_gross={edge_gross:.3f}<{regime_min:.2f}(regime)")