feat(bot): add [CYCLE SUMMARY] diagnostic block at end of each cycle
CI/CD / build-and-push (push) Successful in 2m16s
CI/CD / build-and-push (push) Successful in 2m16s
BayesianStrategy now tracks per-cycle counters (reset each cycle): - skip_prior_extreme, skip_family - skip_edge_net_nonpositive (edge_net ≤ 0) - skip_edge_net_below_regime (0 < edge_net < regime_min) - evaluated_edges list for max/pct computations main.py logs one structured [CYCLE SUMMARY] block per cycle with: markets_total, markets_uncertainty_zone, max_edge_gross, max_edge_net, pct_edge_gross_gt_002, pct_edge_gross_gt_004, all blocked_by_* counters, trades_executed, gnews_queries_used/cap Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+40
-3
@@ -10,7 +10,7 @@ from datetime import datetime, timezone
|
|||||||
from bot.data.polymarket import PolymarketClient, market_family_key
|
from bot.data.polymarket import PolymarketClient, market_family_key
|
||||||
from bot.data.external import ExternalDataClient
|
from bot.data.external import ExternalDataClient
|
||||||
from bot.data.news import NewsClient
|
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.risk.manager import RiskManager
|
||||||
from bot.executor.paper import PaperExecutor
|
from bot.executor.paper import PaperExecutor
|
||||||
from bot.metrics.tracker import MetricsTracker
|
from bot.metrics.tracker import MetricsTracker
|
||||||
@@ -124,9 +124,46 @@ async def run_trading_loop(
|
|||||||
occupied_families.add(signal.family_key)
|
occupied_families.add(signal.family_key)
|
||||||
cycle_trades += 1
|
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()
|
await metrics.update_daily_summary()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -184,10 +184,41 @@ class BayesianStrategy:
|
|||||||
self._signal_count = 0
|
self._signal_count = 0
|
||||||
self._news = news
|
self._news = news
|
||||||
self._news_queries_this_cycle = 0
|
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:
|
def reset_cycle(self) -> None:
|
||||||
"""Call once at the start of each trading cycle to reset per-cycle counters."""
|
"""Call once at the start of each trading cycle to reset per-cycle counters."""
|
||||||
self._news_queries_this_cycle = 0
|
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(
|
async def evaluate(
|
||||||
self,
|
self,
|
||||||
@@ -260,12 +291,14 @@ class BayesianStrategy:
|
|||||||
prior = max(0.05, min(0.95, market.yes_price))
|
prior = max(0.05, min(0.95, market.yes_price))
|
||||||
|
|
||||||
if market.yes_price < 0.08:
|
if market.yes_price < 0.08:
|
||||||
|
self._skip_prior_extreme += 1
|
||||||
log.info(
|
log.info(
|
||||||
"SKIP_PRIOR_EXTREME %-50s | cat=%-12s | prior=%.3f | reason=prior<0.08",
|
"SKIP_PRIOR_EXTREME %-50s | cat=%-12s | prior=%.3f | reason=prior<0.08",
|
||||||
market.question[:50], category, market.yes_price,
|
market.question[:50], category, market.yes_price,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
if market.yes_price > 0.92:
|
if market.yes_price > 0.92:
|
||||||
|
self._skip_prior_extreme += 1
|
||||||
log.info(
|
log.info(
|
||||||
"SKIP_PRIOR_EXTREME %-50s | cat=%-12s | prior=%.3f | reason=prior>0.92",
|
"SKIP_PRIOR_EXTREME %-50s | cat=%-12s | prior=%.3f | reason=prior>0.92",
|
||||||
market.question[:50], category, market.yes_price,
|
market.question[:50], category, market.yes_price,
|
||||||
@@ -275,6 +308,7 @@ class BayesianStrategy:
|
|||||||
# ── Phase 2: family deduplication ────────────────────────────────────
|
# ── Phase 2: family deduplication ────────────────────────────────────
|
||||||
family = market_family_key(market)
|
family = market_family_key(market)
|
||||||
if family in occupied_families:
|
if family in occupied_families:
|
||||||
|
self._skip_family += 1
|
||||||
log.info(
|
log.info(
|
||||||
"SKIP_FAMILY %-50s | cat=%-12s | family=%s",
|
"SKIP_FAMILY %-50s | cat=%-12s | family=%s",
|
||||||
market.question[:50], category, family,
|
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 falls back to yes_price; live order-book data is a future enhancement
|
||||||
mid_price = market.yes_price
|
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
|
# Confidence based on signal agreement
|
||||||
agreement = sum(1 for a in adjustments if (a > 0) == (total_adj > 0))
|
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)
|
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
|
can_trade = passed_net and confidence >= MIN_CONFIDENCE
|
||||||
|
|
||||||
if not can_trade:
|
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] = []
|
skip_parts: list[str] = []
|
||||||
if not passed_gross:
|
if not passed_gross:
|
||||||
skip_parts.append(f"edge_gross={edge_gross:.3f}<{regime_min:.2f}(regime)")
|
skip_parts.append(f"edge_gross={edge_gross:.3f}<{regime_min:.2f}(regime)")
|
||||||
|
|||||||
Reference in New Issue
Block a user