remove min_target_probability gate + add chart time-range presets
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 39s
Deploy / deploy (push) Successful in 24s

min_target_probability is gone: it filtered on the probability model the
calibration has repeatedly shown to be weak and overconfident, it was redundant
with the momentum gate, and as an off-by-default knob it just invited bad tuning.
Removed from the backend gate, activation config/schema, the frontend mirror
(qualifiesSetup / activationSummary), and ActivationSettings. The probability
model stays where it does real work (primary-target selection + display).

Charts: with multi-year history the all-bars default was unreadable. Added
time-range presets (1M / 3M / 6M / YTD / 1Y / 3Y / 5Y / All), defaulting to 1Y;
clicking a preset always re-applies (snaps back after a manual zoom). Y-axis
autoscale and wheel-zoom / drag-pan were already there.

339 backend tests pass; frontend build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 09:24:35 +02:00
parent 605f95098c
commit f48d8705de
9 changed files with 68 additions and 63 deletions
@@ -7,7 +7,6 @@ const DEFAULTS: ActivationConfig = {
min_momentum_percentile: 80,
min_rr: 1.2,
min_confidence: 55,
min_target_probability: 0,
require_high_conviction: false,
exclude_conflicts: false,
};
@@ -91,20 +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 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>
<input
type="number"
min={0}
max={100}
step={1}
value={form.min_target_probability}
onChange={(e) => setForm((prev) => ({ ...prev, min_target_probability: Number(e.target.value) }))}
className="w-full input-glass px-3 py-2 text-sm"
/>
<span className="text-[11px] text-gray-600">Best target's probability must clear this. 0 disables.</span>
</label>
<div className="mt-3 grid gap-3 md:grid-cols-2">
<label className="flex cursor-pointer items-start gap-2.5 text-sm text-gray-300">
<input
type="checkbox"
@@ -51,6 +51,26 @@ interface TooltipState {
const MIN_VISIBLE_BARS = 10;
type RangePreset = '1M' | '3M' | '6M' | 'YTD' | '1Y' | '3Y' | '5Y' | 'All';
const RANGE_PRESETS: RangePreset[] = ['1M', '3M', '6M', 'YTD', '1Y', '3Y', '5Y', 'All'];
const PRESET_MONTHS: Record<string, number> = { '1M': 1, '3M': 3, '6M': 6, '1Y': 12, '3Y': 36, '5Y': 60 };
const DEFAULT_PRESET: RangePreset = '1Y';
/** First bar index to show for a time-range preset (data is ascending by date). */
function startIndexForPreset(data: OHLCVBar[], preset: RangePreset): number {
if (preset === 'All' || data.length === 0) return 0;
const last = new Date(data[data.length - 1].date);
let cutoff: Date;
if (preset === 'YTD') {
cutoff = new Date(last.getFullYear(), 0, 1);
} else {
cutoff = new Date(last);
cutoff.setMonth(cutoff.getMonth() - PRESET_MONTHS[preset]);
}
const idx = data.findIndex((b) => new Date(b.date) >= cutoff);
return idx < 0 ? 0 : idx;
}
export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup, currentPrice }: CandlestickChartProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const overlayCanvasRef = useRef<HTMLCanvasElement>(null);
@@ -67,11 +87,13 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
start: 0,
end: data.length,
});
const [preset, setPreset] = useState<RangePreset>(DEFAULT_PRESET);
// Reset visible range when data changes
// Apply the active time-range preset when the data or preset changes (so the
// default view is a readable window, not the whole multi-year history).
useEffect(() => {
setVisibleRange({ start: 0, end: data.length });
}, [data]);
setVisibleRange({ start: startIndexForPreset(data, preset), end: data.length });
}, [data, preset]);
const draw = useCallback(() => {
const canvas = canvasRef.current;
@@ -627,12 +649,33 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
}
return (
<div ref={containerRef} className="relative w-full" style={{ height: 400 }}>
<canvas
ref={canvasRef}
className="w-full"
style={{ height: 400 }}
/>
<div className="w-full">
<div className="mb-2 flex flex-wrap items-center gap-1">
{RANGE_PRESETS.map((p) => (
<button
key={p}
type="button"
onClick={() => {
// Re-apply the range directly so clicking the active preset still
// snaps back after a manual wheel-zoom / pan.
setPreset(p);
setVisibleRange({ start: startIndexForPreset(data, p), end: data.length });
}}
className={`rounded px-2 py-1 text-[11px] font-medium tabular-nums transition-colors ${
preset === p ? 'bg-white/10 text-blue-300' : 'text-gray-500 hover:text-gray-300'
}`}
>
{p}
</button>
))}
<span className="ml-1 text-[10px] text-gray-600">scroll to zoom · drag to pan</span>
</div>
<div ref={containerRef} className="relative w-full" style={{ height: 400 }}>
<canvas
ref={canvasRef}
className="w-full"
style={{ height: 400 }}
/>
<canvas
ref={overlayCanvasRef}
className="absolute top-0 left-0 w-full cursor-crosshair"
@@ -643,11 +686,12 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
onMouseLeave={handleMouseLeave}
onWheel={handleWheel}
/>
<div
ref={tooltipRef}
className="glass absolute pointer-events-none px-3 py-2 text-xs shadow-2xl z-50"
style={{ display: 'none' }}
/>
<div
ref={tooltipRef}
className="glass absolute pointer-events-none px-3 py-2 text-xs shadow-2xl z-50"
style={{ display: 'none' }}
/>
</div>
</div>
);
}
-4
View File
@@ -46,9 +46,6 @@ export function qualifiesSetup(setup: TradeSetup, config: ActivationConfig): boo
return false;
}
if (config.exclude_conflicts && (setup.risk_level ?? '') !== 'Low') return false;
if (config.min_target_probability > 0 && bestTargetProbability(setup) < config.min_target_probability) {
return false;
}
return true;
}
@@ -59,6 +56,5 @@ export function activationSummary(config: ActivationConfig): string {
parts.push(`R:R ≥ ${config.min_rr.toFixed(1)}`, `conf ≥ ${config.min_confidence.toFixed(0)}%`);
if (config.require_high_conviction) parts.push('high-conviction');
if (config.exclude_conflicts) parts.push('clean');
if (config.min_target_probability > 0) parts.push(`target ≥ ${config.min_target_probability.toFixed(0)}%`);
return parts.join(' · ');
}
-1
View File
@@ -162,7 +162,6 @@ export interface ActivationConfig {
min_momentum_percentile: number;
min_rr: number;
min_confidence: number;
min_target_probability: number;
require_high_conviction: boolean;
exclude_conflicts: boolean;
}