Add activation thresholds: qualified-signal defaults and views
Admin-configurable thresholds (min R:R, default 2.0; min confidence, default 70%) defining what counts as an actionable signal: - Admin Settings: new Activation Thresholds panel (GET/PUT /admin/settings/activation) - GET /trades/activation exposes values to all users with access - Signals/Setups: filters initialize from activation values - Track Record: "Qualified signals only" toggle (default on) via min_rr/min_confidence params on /trades/performance; the confidence breakdown always covers the full population so the thresholds can be validated against outcomes - Dashboard: "Qualified" metric and qualified-first Top Setups - Outcome evaluator unchanged: every setup is still evaluated Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,16 @@ RECOMMENDATION_CONFIG_DEFAULTS: dict[str, float] = {
|
||||
DEFAULT_TICKER_UNIVERSE = "sp500"
|
||||
SUPPORTED_TICKER_UNIVERSES = {"sp500", "nasdaq100", "nasdaq_all"}
|
||||
|
||||
# Activation thresholds: what counts as a signal worth acting on.
|
||||
# Used as Signals-page default filters, the Dashboard's qualified-setup
|
||||
# metrics, and the Track Record's "qualified only" view. The outcome
|
||||
# evaluator deliberately ignores these — every setup gets evaluated so the
|
||||
# thresholds themselves can be validated against outcomes.
|
||||
ACTIVATION_DEFAULTS: dict[str, float] = {
|
||||
"activation_min_rr": 2.0,
|
||||
"activation_min_confidence": 70.0,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# User management
|
||||
@@ -143,6 +153,48 @@ async def update_setting(db: AsyncSession, key: str, value: str) -> SystemSettin
|
||||
return setting
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Activation thresholds
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def get_activation_config(db: AsyncSession) -> dict[str, float]:
|
||||
"""Return activation thresholds with public keys (min_rr, min_confidence)."""
|
||||
result = await db.execute(
|
||||
select(SystemSetting).where(SystemSetting.key.like("activation_%"))
|
||||
)
|
||||
config = dict(ACTIVATION_DEFAULTS)
|
||||
for setting in result.scalars().all():
|
||||
if setting.key in config:
|
||||
try:
|
||||
config[setting.key] = float(setting.value)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return {
|
||||
"min_rr": config["activation_min_rr"],
|
||||
"min_confidence": config["activation_min_confidence"],
|
||||
}
|
||||
|
||||
|
||||
async def update_activation_config(
|
||||
db: AsyncSession, updates: dict[str, float]
|
||||
) -> dict[str, float]:
|
||||
"""Update activation thresholds. Accepts public keys min_rr / min_confidence."""
|
||||
if "min_rr" in updates and updates["min_rr"] < 0:
|
||||
raise ValidationError("min_rr must be >= 0")
|
||||
if "min_confidence" in updates and not 0 <= updates["min_confidence"] <= 100:
|
||||
raise ValidationError("min_confidence must be between 0 and 100")
|
||||
|
||||
key_map = {
|
||||
"min_rr": "activation_min_rr",
|
||||
"min_confidence": "activation_min_confidence",
|
||||
}
|
||||
for public_key, storage_key in key_map.items():
|
||||
if public_key in updates:
|
||||
await update_setting(db, storage_key, str(float(updates[public_key])))
|
||||
|
||||
return await get_activation_config(db)
|
||||
|
||||
|
||||
def _recommendation_public_to_storage_key(key: str) -> str:
|
||||
return f"recommendation_{key}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user