fix(signal_v2-phase4-task2): code review fixes — sell-first ordering + anomaly test + defensive .get
- generate_signals now evaluates sell before buy; buy candidates with a same-cycle sell signal are skipped (resolves silent overwrite of state.signals[ticker]). - Added test_sell_signal_triggers_on_anomaly_path covering _try_anomaly path (previously 0% covered). - Fixed stale test comment referencing deprecated relative spread formula. - _check_buy_hard_gate uses dict.get(..., 0) for defense against partial upstream state. - _compute_buy_confidence clamps screener_norm to >= 0 for future Top-N changes.
This commit is contained in:
@@ -85,7 +85,7 @@ def test_silent_when_chronos_median_negative(dedup_mock):
|
||||
|
||||
|
||||
def test_silent_when_distribution_spread_too_wide(dedup_mock):
|
||||
# spread = (0.5 - (-0.5)) / max(0.001, 0.001) = 1000 → > 0.6
|
||||
# spread = q90 - q10 = 0.5 - (-0.5) = 1.0 > 0.6 → hard gate fails
|
||||
state = _make_state_with_buy_candidate(
|
||||
chronos_median=0.001, chronos_q10=-0.5, chronos_q90=0.5,
|
||||
)
|
||||
@@ -143,3 +143,30 @@ def test_silent_when_dedup_recently_sent(dedup_mock):
|
||||
generate_signals(state, dedup_mock, _settings())
|
||||
assert "005930" not in state.signals
|
||||
dedup_mock.record.assert_not_called()
|
||||
|
||||
|
||||
def test_sell_signal_triggers_on_anomaly_path(dedup_mock):
|
||||
"""Anomaly sell: median < -1%, momentum strong_down, low bid_ratio, confidence > threshold."""
|
||||
state = PollState()
|
||||
state.portfolio = {"holdings": [{
|
||||
"ticker": "005930", "name": "삼성전자",
|
||||
"avg_price": 75000, "current_price": 70000,
|
||||
"pnl_pct": -0.067, # within stop_loss tolerance (default -0.07): NOT triggering stop_loss
|
||||
"quantity": 100, "broker": "키움",
|
||||
}]}
|
||||
state.screener_preview = {"items": []}
|
||||
state.chronos_predictions["005930"] = {
|
||||
"median": -0.025, "q10": -0.05, "q90": 0.005, "conf": 0.85,
|
||||
}
|
||||
state.minute_momentum["005930"] = "strong_down"
|
||||
state.asking_price["005930"] = {"current_price": 70000, "bid_ratio": 0.30}
|
||||
# bid_ratio 0.30 < (1 - 0.6) = 0.4 → anomaly bid_ratio gate passes
|
||||
# confidence = 0.85*0.5 + 1.0*0.3 + 1.0*0.2 = 0.425 + 0.3 + 0.2 = 0.925 > 0.7
|
||||
|
||||
generate_signals(state, dedup_mock, _settings())
|
||||
|
||||
assert "005930" in state.signals
|
||||
sig = state.signals["005930"]
|
||||
assert sig["action"] == "sell"
|
||||
assert sig["context"]["sell_reason"] == "anomaly"
|
||||
assert sig["confidence_webai"] > 0.7
|
||||
|
||||
Reference in New Issue
Block a user