feat: add paper trading capital controls — API endpoint + UI with presets, fix status/metrics to read real state, fix migration duplicates
This commit is contained in:
@@ -397,6 +397,15 @@ export function useSetTradingMode() {
|
||||
});
|
||||
}
|
||||
|
||||
export function useSetTradingCapital() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (initial_capital: number) =>
|
||||
apiPut<{ initial_capital: number; active_pool: number; reserve_pool: number }>('trading', '/api/trading/capital', { initial_capital }),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['trading-config'] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function usePendingApprovals() {
|
||||
return useGet<Approval[]>(['pending-approvals'], 'query', '/api/admin/trading/approvals');
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
||||
import {
|
||||
useTradingConfig,
|
||||
useSetTradingMode,
|
||||
useSetTradingCapital,
|
||||
usePendingApprovals,
|
||||
useReviewApproval,
|
||||
useActiveLockouts,
|
||||
@@ -19,6 +20,7 @@ export function TradingPage() {
|
||||
const { data: macroStatus } = useMacroStatus();
|
||||
const { data: competitiveStatus } = useCompetitiveStatus();
|
||||
const setMode = useSetTradingMode();
|
||||
const setCapital = useSetTradingCapital();
|
||||
const reviewApproval = useReviewApproval();
|
||||
const toggleMacro = useToggleMacro();
|
||||
const toggleCompetitive = useToggleCompetitive();
|
||||
@@ -87,6 +89,9 @@ export function TradingPage() {
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Paper Trading Capital */}
|
||||
<CapitalCard onSetCapital={(amount) => setCapital.mutate(amount)} isPending={setCapital.isPending} />
|
||||
|
||||
{/* Macro Signal Layer Toggle */}
|
||||
<Card>
|
||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Macro Signal Layer</h2>
|
||||
@@ -292,3 +297,77 @@ function ApprovalRow({ approval, onReview }: {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function CapitalCard({ onSetCapital, isPending }: { onSetCapital: (amount: number) => void; isPending: boolean }) {
|
||||
const [amount, setAmount] = useState('100000');
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
|
||||
const presets = [10000, 50000, 100000, 500000];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Paper Trading Capital</h2>
|
||||
<div className="flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<label htmlFor="capital-input" className="mb-1 block text-xs text-gray-500">Initial Capital ($)</label>
|
||||
<input
|
||||
id="capital-input"
|
||||
type="number"
|
||||
min="100"
|
||||
step="1000"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
className="w-40 rounded-md border border-surface-700 bg-surface-900 px-3 py-1.5 text-sm text-gray-200 focus:border-brand-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{presets.map((p) => (
|
||||
<button
|
||||
key={p}
|
||||
onClick={() => setAmount(String(p))}
|
||||
className={`rounded-md border px-2 py-1.5 text-xs font-medium ${
|
||||
amount === String(p)
|
||||
? 'border-brand-500 bg-brand-600/20 text-brand-300'
|
||||
: 'border-surface-700 bg-surface-900 text-gray-400 hover:bg-surface-800'
|
||||
}`}
|
||||
>
|
||||
${(p / 1000).toFixed(0)}k
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowConfirm(true)}
|
||||
disabled={isPending || !amount || Number(amount) <= 0}
|
||||
className="rounded-md bg-brand-600 px-4 py-1.5 text-sm font-medium text-white hover:bg-brand-700 disabled:opacity-50"
|
||||
>
|
||||
Set Capital
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showConfirm && (
|
||||
<div className="mt-4 rounded-lg border border-orange-700/50 bg-orange-900/20 p-4">
|
||||
<p className="text-sm text-orange-300">
|
||||
This will reset the paper trading pools to <span className="font-semibold">${Number(amount).toLocaleString()}</span>.
|
||||
Any existing pool balances will be overwritten.
|
||||
</p>
|
||||
<div className="mt-3 flex gap-2">
|
||||
<button
|
||||
onClick={() => { onSetCapital(Number(amount)); setShowConfirm(false); }}
|
||||
disabled={isPending}
|
||||
className="rounded-md bg-orange-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-orange-700 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? 'Setting…' : 'Confirm'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowConfirm(false)}
|
||||
className="rounded-md border border-surface-700 px-3 py-1.5 text-sm text-gray-400 hover:bg-surface-800"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user