90618d186f
Track Record: new "Reset" action (POST /admin/track-record/reset) deletes all trade setups so stats start fresh after material scoring/setup changes — live setups regenerate on the next scan. Guarded by a confirm dialog. Recommendation config: remove distance_penalty_factor, which was exposed in the admin UI but consumed nowhere (the touch-probability model superseded it). A knob that silently does nothing is worse than no knob. Remaining defaults are left as-is — they're reasonable, and the honest way to tune them is backtesting against accumulated outcomes, not invented "researched" numbers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 lines
3.9 KiB
TypeScript
100 lines
3.9 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import type { RecommendationConfig } from '../../lib/types';
|
|
import { useRecommendationSettings, useUpdateRecommendationSettings } from '../../hooks/useAdmin';
|
|
import { SkeletonTable } from '../ui/Skeleton';
|
|
|
|
const DEFAULTS: RecommendationConfig = {
|
|
high_confidence_threshold: 70,
|
|
moderate_confidence_threshold: 50,
|
|
confidence_diff_threshold: 20,
|
|
signal_alignment_weight: 0.15,
|
|
sr_strength_weight: 0.2,
|
|
momentum_technical_divergence_threshold: 30,
|
|
fundamental_technical_divergence_threshold: 40,
|
|
};
|
|
|
|
function NumberInput({
|
|
label,
|
|
value,
|
|
min,
|
|
max,
|
|
step,
|
|
onChange,
|
|
}: {
|
|
label: string;
|
|
value: number;
|
|
min: number;
|
|
max: number;
|
|
step?: number;
|
|
onChange: (v: number) => void;
|
|
}) {
|
|
return (
|
|
<label className="block space-y-1">
|
|
<span className="text-xs text-gray-400">{label}</span>
|
|
<input
|
|
type="number"
|
|
min={min}
|
|
max={max}
|
|
step={step}
|
|
value={value}
|
|
onChange={(e) => onChange(Number(e.target.value))}
|
|
className="w-full input-glass px-3 py-2 text-sm"
|
|
/>
|
|
</label>
|
|
);
|
|
}
|
|
|
|
export function RecommendationSettings() {
|
|
const { data, isLoading, isError, error } = useRecommendationSettings();
|
|
const update = useUpdateRecommendationSettings();
|
|
|
|
const [form, setForm] = useState<RecommendationConfig>(DEFAULTS);
|
|
|
|
useEffect(() => {
|
|
if (data) setForm(data);
|
|
}, [data]);
|
|
|
|
const setField = (field: keyof RecommendationConfig, value: number) => {
|
|
setForm((prev) => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
const onSave = () => {
|
|
update.mutate(form as unknown as Record<string, number>);
|
|
};
|
|
|
|
const onReset = () => {
|
|
setForm(DEFAULTS);
|
|
update.mutate(DEFAULTS as unknown as Record<string, number>);
|
|
};
|
|
|
|
if (isLoading) return <SkeletonTable rows={6} cols={2} />;
|
|
if (isError) return <p className="text-sm text-red-400">{(error as Error)?.message || 'Failed to load recommendation settings'}</p>;
|
|
|
|
return (
|
|
<div className="glass p-5 space-y-4">
|
|
<h3 className="text-sm font-semibold text-gray-200">Recommendation Configuration</h3>
|
|
|
|
<div className="grid gap-4 md:grid-cols-3">
|
|
<NumberInput label="High Confidence Threshold (%)" value={form.high_confidence_threshold} min={0} max={100} onChange={(v) => setField('high_confidence_threshold', v)} />
|
|
<NumberInput label="Moderate Confidence Threshold (%)" value={form.moderate_confidence_threshold} min={0} max={100} onChange={(v) => setField('moderate_confidence_threshold', v)} />
|
|
<NumberInput label="Confidence Difference Threshold (%)" value={form.confidence_diff_threshold} min={0} max={100} onChange={(v) => setField('confidence_diff_threshold', v)} />
|
|
|
|
<NumberInput label="Signal Alignment Weight" value={form.signal_alignment_weight} min={0} max={1} step={0.01} onChange={(v) => setField('signal_alignment_weight', v)} />
|
|
<NumberInput label="S/R Strength Weight" value={form.sr_strength_weight} min={0} max={1} step={0.01} onChange={(v) => setField('sr_strength_weight', v)} />
|
|
|
|
<NumberInput label="Momentum-Technical Divergence Threshold" value={form.momentum_technical_divergence_threshold} min={0} max={100} onChange={(v) => setField('momentum_technical_divergence_threshold', v)} />
|
|
<NumberInput label="Fundamental-Technical Divergence Threshold" value={form.fundamental_technical_divergence_threshold} min={0} max={100} onChange={(v) => setField('fundamental_technical_divergence_threshold', v)} />
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<button className="btn-primary px-4 py-2 text-sm" onClick={onSave} disabled={update.isPending}>
|
|
{update.isPending ? 'Saving…' : 'Save Configuration'}
|
|
</button>
|
|
<button className="px-4 py-2 text-sm rounded border border-white/[0.1] text-gray-300 hover:text-white" onClick={onReset} disabled={update.isPending}>
|
|
Reset to Defaults
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|