Add scanner diagnostics for zero-candidate trade stalls
This commit is contained in:
@@ -128,6 +128,16 @@ class SmartVolatilityScanner:
|
||||
if not fluct_rows:
|
||||
return []
|
||||
|
||||
diagnostics: dict[str, int | float] = {
|
||||
"total_rows": len(fluct_rows),
|
||||
"missing_code": 0,
|
||||
"invalid_price": 0,
|
||||
"low_volatility": 0,
|
||||
"connection_error": 0,
|
||||
"unexpected_error": 0,
|
||||
"qualified": 0,
|
||||
}
|
||||
|
||||
volume_rank_bonus: dict[str, float] = {}
|
||||
for idx, row in enumerate(volume_rows):
|
||||
code = _extract_stock_code(row)
|
||||
@@ -139,6 +149,7 @@ class SmartVolatilityScanner:
|
||||
for stock in fluct_rows:
|
||||
stock_code = _extract_stock_code(stock)
|
||||
if not stock_code:
|
||||
diagnostics["missing_code"] += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
@@ -168,7 +179,11 @@ class SmartVolatilityScanner:
|
||||
volume_ratio = max(volume_ratio, volume / prev_day_volume)
|
||||
|
||||
volatility_pct = max(abs(change_rate), intraday_range_pct)
|
||||
if price <= 0 or volatility_pct < 0.8:
|
||||
if price <= 0:
|
||||
diagnostics["invalid_price"] += 1
|
||||
continue
|
||||
if volatility_pct < 0.8:
|
||||
diagnostics["low_volatility"] += 1
|
||||
continue
|
||||
|
||||
volatility_score = min(volatility_pct / 10.0, 1.0) * 85.0
|
||||
@@ -189,14 +204,22 @@ class SmartVolatilityScanner:
|
||||
score=score,
|
||||
)
|
||||
)
|
||||
diagnostics["qualified"] += 1
|
||||
|
||||
except ConnectionError as exc:
|
||||
diagnostics["connection_error"] += 1
|
||||
logger.warning("Failed to analyze %s: %s", stock_code, exc)
|
||||
continue
|
||||
except Exception as exc:
|
||||
diagnostics["unexpected_error"] += 1
|
||||
logger.error("Unexpected error analyzing %s: %s", stock_code, exc)
|
||||
continue
|
||||
|
||||
logger.info(
|
||||
"Domestic scan diagnostics: %s (volatility_threshold=0.8, top_n=%d)",
|
||||
diagnostics,
|
||||
self.top_n,
|
||||
)
|
||||
logger.info("Domestic ranking scan found %d candidates", len(candidates))
|
||||
candidates.sort(key=lambda c: c.score, reverse=True)
|
||||
return candidates[: self.top_n]
|
||||
@@ -242,6 +265,14 @@ class SmartVolatilityScanner:
|
||||
if not fluct_rows:
|
||||
return []
|
||||
|
||||
diagnostics: dict[str, int | float] = {
|
||||
"total_rows": len(fluct_rows),
|
||||
"missing_code": 0,
|
||||
"invalid_price": 0,
|
||||
"low_volatility": 0,
|
||||
"qualified": 0,
|
||||
}
|
||||
|
||||
volume_rank_bonus: dict[str, float] = {}
|
||||
try:
|
||||
volume_rows = await self.overseas_broker.fetch_overseas_rankings(
|
||||
@@ -266,6 +297,7 @@ class SmartVolatilityScanner:
|
||||
for row in fluct_rows:
|
||||
stock_code = _extract_stock_code(row)
|
||||
if not stock_code:
|
||||
diagnostics["missing_code"] += 1
|
||||
continue
|
||||
|
||||
price = _extract_last_price(row)
|
||||
@@ -275,7 +307,11 @@ class SmartVolatilityScanner:
|
||||
volatility_pct = max(abs(change_rate), intraday_range_pct)
|
||||
|
||||
# Volatility-first filter (not simple gainers/value ranking).
|
||||
if price <= 0 or volatility_pct < 0.8:
|
||||
if price <= 0:
|
||||
diagnostics["invalid_price"] += 1
|
||||
continue
|
||||
if volatility_pct < 0.8:
|
||||
diagnostics["low_volatility"] += 1
|
||||
continue
|
||||
|
||||
volatility_score = min(volatility_pct / 10.0, 1.0) * 85.0
|
||||
@@ -295,7 +331,14 @@ class SmartVolatilityScanner:
|
||||
score=score,
|
||||
)
|
||||
)
|
||||
diagnostics["qualified"] += 1
|
||||
|
||||
logger.info(
|
||||
"Overseas ranking scan diagnostics for %s: %s (volatility_threshold=0.8, top_n=%d)",
|
||||
market.code,
|
||||
diagnostics,
|
||||
self.top_n,
|
||||
)
|
||||
if candidates:
|
||||
logger.info(
|
||||
"Overseas ranking scan found %d candidates for %s",
|
||||
@@ -320,6 +363,14 @@ class SmartVolatilityScanner:
|
||||
len(symbols),
|
||||
market.name,
|
||||
)
|
||||
diagnostics: dict[str, int | float] = {
|
||||
"total_rows": len(symbols),
|
||||
"invalid_price": 0,
|
||||
"low_volatility": 0,
|
||||
"connection_error": 0,
|
||||
"unexpected_error": 0,
|
||||
"qualified": 0,
|
||||
}
|
||||
candidates: list[ScanCandidate] = []
|
||||
for stock_code in symbols:
|
||||
try:
|
||||
@@ -333,7 +384,11 @@ class SmartVolatilityScanner:
|
||||
intraday_range_pct = _extract_intraday_range_pct(output, price)
|
||||
volatility_pct = max(abs(change_rate), intraday_range_pct)
|
||||
|
||||
if price <= 0 or volatility_pct < 0.8:
|
||||
if price <= 0:
|
||||
diagnostics["invalid_price"] += 1
|
||||
continue
|
||||
if volatility_pct < 0.8:
|
||||
diagnostics["low_volatility"] += 1
|
||||
continue
|
||||
|
||||
score = min(volatility_pct / 10.0, 1.0) * 100.0
|
||||
@@ -351,10 +406,19 @@ class SmartVolatilityScanner:
|
||||
score=score,
|
||||
)
|
||||
)
|
||||
diagnostics["qualified"] += 1
|
||||
except ConnectionError as exc:
|
||||
diagnostics["connection_error"] += 1
|
||||
logger.warning("Failed to analyze overseas %s: %s", stock_code, exc)
|
||||
except Exception as exc:
|
||||
diagnostics["unexpected_error"] += 1
|
||||
logger.error("Unexpected error analyzing overseas %s: %s", stock_code, exc)
|
||||
logger.info(
|
||||
"Overseas fallback scan diagnostics for %s: %s (volatility_threshold=0.8, top_n=%d)",
|
||||
market.code,
|
||||
diagnostics,
|
||||
self.top_n,
|
||||
)
|
||||
logger.info(
|
||||
"Overseas symbol fallback scan found %d candidates for %s",
|
||||
len(candidates),
|
||||
|
||||
Reference in New Issue
Block a user