replace EV activation gate with cross-sectional 12-1 momentum ranking
The 5-year backtest confirmed the EV gate adds negative value (high threshold = worst expectancy) and that 12-1 month momentum is the one price signal with a plausible, right-signed cross-sectional IC (~0.05). So "qualified" now means: clears the R:R + confidence floors AND the ticker ranks in the top `min_momentum_percentile` of the universe by 12-1 momentum that week. - qualification.py: drop expected_value_r / the EV gate; add a momentum-percentile gate (duck-typed `momentum_percentile`, only enforced when attached + threshold set, else defers to floors). Mirrored in frontend qualification.ts. - activation config/schema: min_expected_value -> min_momentum_percentile (default 80 = top quintile). ActivationSettings, DashboardPage (ranks/【shows】 momentum instead of EV), and the BacktestPanel sweep follow. - backtest: rank each ISO week's universe by 12-1 momentum, assign a percentile, and qualify the top slice; the sweep now sweeps the percentile cutoff. Also offload the backtest's per-ticker compute to a worker thread so the heavy ~5y run no longer blocks the API event loop (the "backend offline" flicker). Production setups don't carry momentum_percentile yet — wiring the scanner to attach it (a universe momentum-rank step) is the next step; until then the live gate defers to floors while the backtest measures the momentum selection. 330 backend tests pass; frontend build clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { useActivationSettings, useUpdateActivationSettings } from '../../hooks/
|
||||
import { SkeletonTable } from '../ui/Skeleton';
|
||||
|
||||
const DEFAULTS: ActivationConfig = {
|
||||
min_expected_value: 0.15,
|
||||
min_momentum_percentile: 80,
|
||||
min_rr: 1.2,
|
||||
min_confidence: 55,
|
||||
min_target_probability: 0,
|
||||
@@ -40,26 +40,27 @@ export function ActivationSettings() {
|
||||
<h3 className="text-sm font-semibold text-gray-200">Activation Gate</h3>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
What counts as a signal worth acting on. Drives the Dashboard's "Qualified" metric, the
|
||||
Signals "Qualified only" view, and the Track Record's qualified stats. The core test is
|
||||
<span className="text-gray-300"> expected value</span> — probability-weighted asymmetry —
|
||||
so R:R and target probability no longer fight each other. All setups are still evaluated
|
||||
regardless; tune the EV floor against the Track Record's EV sweep to see what actually wins.
|
||||
Signals "Qualified only" view, and the Track Record's qualified stats. The core selection is
|
||||
<span className="text-gray-300"> cross-sectional momentum</span> — the ticker must rank in the
|
||||
top slice of the universe by 12-1 month momentum, the one signal the backtest showed predicts
|
||||
forward returns. R:R and confidence stay as floors. Tune the cutoff against the Track Record's
|
||||
momentum sweep to see what actually wins.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-gray-400">Min Expected Value (R)</span>
|
||||
<span className="text-xs text-gray-400">Min Momentum Percentile</span>
|
||||
<input
|
||||
type="number"
|
||||
min={-1}
|
||||
max={10}
|
||||
step={0.05}
|
||||
value={form.min_expected_value}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, min_expected_value: Number(e.target.value) }))}
|
||||
min={0}
|
||||
max={100}
|
||||
step={5}
|
||||
value={form.min_momentum_percentile}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, min_momentum_percentile: Number(e.target.value) }))}
|
||||
className="w-full input-glass px-3 py-2 text-sm"
|
||||
/>
|
||||
<span className="text-[11px] text-gray-600">p·R:R − (1−p), in R. 0.15 ≈ +0.15× risk/trade. The core gate.</span>
|
||||
<span className="text-[11px] text-gray-600">Ticker's 12-1 momentum rank. 80 = top 20% of the universe. 0 disables. The core gate.</span>
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-gray-400">Min Risk:Reward (1 : x)</span>
|
||||
@@ -89,7 +90,7 @@ export function ActivationSettings() {
|
||||
|
||||
<div className="border-t border-white/[0.06] pt-4">
|
||||
<p className="text-xs font-medium uppercase tracking-widest text-gray-500">Optional tighteners</p>
|
||||
<p className="mt-1 text-[11px] text-gray-600">Off by default — turn on to be more selective on top of the EV gate.</p>
|
||||
<p className="mt-1 text-[11px] text-gray-600">Off by default — turn on to be more selective on top of the momentum gate.</p>
|
||||
<div className="mt-3 grid gap-3 md:grid-cols-3">
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-gray-400">Min Target Probability (%)</span>
|
||||
|
||||
Reference in New Issue
Block a user