Add multi-factor conviction gate to activation
Make "qualified" mean an edge candidate, not just R:R + confidence. The gate now also requires (all admin-configurable, defaults on): - high conviction: recommended_action LONG_HIGH / SHORT_HIGH only - clean read: risk_level Low (no contradicting signals) - probable primary target: best target probability >= min (default 60) - Shared predicate: app/services/qualification.py + frontend/src/lib/qualification.ts (mirrored) - Activation config extended (min_target_probability, require_high_conviction, exclude_conflicts) with bool-aware get/update + validation - /trades/performance switched to ?qualified_only=true, applying the full gate server-side; confidence breakdown stays unfiltered - Dashboard "Qualified", Signals "Qualified only" toggle, and Track Record all use the one gate; Admin gains the new controls Sentiment provider runtime config (prior change) included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,16 +24,24 @@ async def session() -> AsyncSession:
|
||||
class TestActivationConfig:
|
||||
async def test_defaults_when_unset(self, session: AsyncSession):
|
||||
config = await get_activation_config(session)
|
||||
assert config == {"min_rr": 2.0, "min_confidence": 70.0}
|
||||
assert config == {
|
||||
"min_rr": 2.0,
|
||||
"min_confidence": 70.0,
|
||||
"min_target_probability": 60.0,
|
||||
"require_high_conviction": True,
|
||||
"exclude_conflicts": True,
|
||||
}
|
||||
|
||||
async def test_update_and_read_back(self, session: AsyncSession):
|
||||
updated = await update_activation_config(
|
||||
session, {"min_rr": 1.5, "min_confidence": 60.0}
|
||||
)
|
||||
assert updated == {"min_rr": 1.5, "min_confidence": 60.0}
|
||||
assert updated["min_rr"] == 1.5
|
||||
assert updated["min_confidence"] == 60.0
|
||||
|
||||
config = await get_activation_config(session)
|
||||
assert config == {"min_rr": 1.5, "min_confidence": 60.0}
|
||||
assert config["min_rr"] == 1.5
|
||||
assert config["min_confidence"] == 60.0
|
||||
|
||||
async def test_partial_update_keeps_other_value(self, session: AsyncSession):
|
||||
await update_activation_config(session, {"min_confidence": 80.0})
|
||||
@@ -41,6 +49,16 @@ class TestActivationConfig:
|
||||
assert config["min_rr"] == 2.0 # default untouched
|
||||
assert config["min_confidence"] == 80.0
|
||||
|
||||
async def test_conviction_flags_round_trip(self, session: AsyncSession):
|
||||
await update_activation_config(
|
||||
session,
|
||||
{"require_high_conviction": False, "exclude_conflicts": False, "min_target_probability": 45.0},
|
||||
)
|
||||
config = await get_activation_config(session)
|
||||
assert config["require_high_conviction"] is False
|
||||
assert config["exclude_conflicts"] is False
|
||||
assert config["min_target_probability"] == 45.0
|
||||
|
||||
async def test_rejects_negative_rr(self, session: AsyncSession):
|
||||
with pytest.raises(ValidationError):
|
||||
await update_activation_config(session, {"min_rr": -1.0})
|
||||
@@ -48,3 +66,7 @@ class TestActivationConfig:
|
||||
async def test_rejects_out_of_range_confidence(self, session: AsyncSession):
|
||||
with pytest.raises(ValidationError):
|
||||
await update_activation_config(session, {"min_confidence": 120.0})
|
||||
|
||||
async def test_rejects_out_of_range_target_probability(self, session: AsyncSession):
|
||||
with pytest.raises(ValidationError):
|
||||
await update_activation_config(session, {"min_target_probability": 150.0})
|
||||
|
||||
Reference in New Issue
Block a user