refactor: simplify opportunity actions to entry/watch/avoid with confidence

- Remove dead scoring code (_score_candidate, _action_for, etc.) and
  align action decisions directly with score_opportunity_signal metrics.
- Reduce action surface from trigger/setup/chase/skip to entry/watch/avoid.
- Add confidence field (0..100) mapped from edge_score.
- Update evaluate/optimize ground-truth mapping and tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 01:08:34 +08:00
parent d3408dabba
commit 003212de99
5 changed files with 72 additions and 346 deletions

View File

@@ -156,11 +156,11 @@ def _path_stats(entry: float, future_rows: list[list[Any]], take_profit: float,
def _is_correct(action: str, trigger_path: dict[str, Any], setup_path: dict[str, Any]) -> bool:
if action == "trigger":
if action == "entry":
return str(trigger_path["event"]) == "target"
if action == "setup":
if action == "watch":
return str(setup_path["event"]) == "target"
if action in {"skip", "chase"}:
if action == "avoid":
return str(setup_path["event"]) != "target"
return False
@@ -274,7 +274,7 @@ def evaluate_opportunity_dataset(
metrics["opportunity_score"] = round(opportunity_score, 4)
metrics["position_weight"] = 0.0
metrics["research_score"] = 0.0
action, reasons = _action_for_opportunity(score, metrics, thresholds)
action, reasons, _confidence = _action_for_opportunity(score, metrics, thresholds)
candidates.append(
{
"symbol": symbol,
@@ -304,7 +304,7 @@ def evaluate_opportunity_dataset(
"forward_return": _round_float(trigger_path["final_return"]),
"max_upside": _round_float(trigger_path["max_upside"]),
"max_drawdown": _round_float(trigger_path["max_drawdown"]),
"trade_return": _round_float(trigger_path["exit_return"]) if candidate["action"] == "trigger" else 0.0,
"trade_return": _round_float(trigger_path["exit_return"]) if candidate["action"] == "entry" else 0.0,
"trigger_event": trigger_path["event"],
"setup_event": setup_path["event"],
"metrics": candidate["metrics"],
@@ -321,16 +321,16 @@ def evaluate_opportunity_dataset(
bucket["count"] += 1
bucket["correct"] += 1 if judgment["correct"] else 0
bucket["forward_returns"].append(judgment["forward_return"])
if action == "trigger":
if action == "entry":
bucket["trade_returns"].append(judgment["trade_return"])
if action == "trigger":
if action == "entry":
trigger_returns.append(judgment["trade_return"])
by_action_result = {action: _finalize_bucket(bucket) for action, bucket in sorted(by_action.items())}
incorrect_examples = [item for item in judgments if not item["correct"]][:max_examples]
examples = judgments[:max_examples]
trigger_count = by_action_result.get("trigger", {}).get("count", 0)
trigger_correct = by_action_result.get("trigger", {}).get("correct", 0)
trigger_count = by_action_result.get("entry", {}).get("count", 0)
trigger_correct = by_action_result.get("entry", {}).get("correct", 0)
return {
"summary": {
**_finalize_bucket(overall),
@@ -377,7 +377,7 @@ def _objective(result: dict[str, Any]) -> float:
trigger_coverage = min(trigger_rate / 0.08, 1.0)
return round(
0.45 * _as_float(summary.get("accuracy"))
+ 0.20 * _as_float(by_action.get("setup", {}).get("accuracy"))
+ 0.20 * _as_float(by_action.get("watch", {}).get("accuracy"))
+ 0.25 * _as_float(trade.get("win_rate"))
+ 6.0 * bounded_trade_return
+ 0.05 * trigger_coverage,