feat: enhanced paper trading reset with capital and reserve controls
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled

- Add initial capital input (toggle between broker balance or custom amount)
- Add reserve/active pool split slider (0-50%, default 20%)
- Backend accepts reserve_pct in reset request body
- Note in UI that Alpaca balance reset requires Alpaca dashboard
- Confirmation dialog shows exact capital and split being applied
This commit is contained in:
Celes Renata
2026-04-30 05:49:38 +00:00
parent fa18b1a7c2
commit 5209cc522e
3 changed files with 98 additions and 9 deletions
+5 -2
View File
@@ -316,9 +316,12 @@ export function useBacktestLaunch() {
export function useResetPaperTrading() {
const qc = useQueryClient();
return useMutation({
mutationFn: (initial_capital: number = 0) =>
mutationFn: (params: { initial_capital?: number; reserve_pct?: number } = {}) =>
apiPost<{ reset: boolean; initial_capital: number; active_pool: number; reserve_pool: number; broker: Record<string, number> }>(
'trading', '/api/trading/reset', { initial_capital },
'trading', '/api/trading/reset', {
initial_capital: params.initial_capital ?? 0,
reserve_pct: params.reserve_pct ?? undefined,
},
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['trading-status'] });
+90 -6
View File
@@ -116,7 +116,7 @@ export function TradingPage() {
{/* Paper Trading Reset */}
<ResetCard
onReset={() => resetTrading.mutate(0)}
onReset={(params) => resetTrading.mutate(params)}
isResetting={resetTrading.isPending}
/>
@@ -490,26 +490,101 @@ function ApprovalRow({ approval, onReview }: {
function ResetCard({ onReset, isResetting }: {
onReset: () => void;
onReset: (params: { initial_capital?: number; reserve_pct?: number }) => void;
isResetting: boolean;
}) {
const [showConfirm, setShowConfirm] = useState(false);
const [capitalInput, setCapitalInput] = useState('100000');
const [reservePct, setReservePct] = useState(20);
const [useCustomCapital, setUseCustomCapital] = useState(false);
const capital = parseFloat(capitalInput) || 0;
const reserveAmount = capital * (reservePct / 100);
const activeAmount = capital - reserveAmount;
return (
<Card>
<h2 className="mb-3 text-sm font-medium text-gray-400">Paper Trading Account</h2>
{/* Capital & Reserve Configuration */}
<div className="mb-4 space-y-3 rounded-lg border border-surface-700 bg-surface-950 p-4">
<div className="flex items-center gap-3">
<button
onClick={() => setUseCustomCapital(!useCustomCapital)}
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 focus:ring-offset-surface-900 ${
useCustomCapital ? 'bg-brand-600' : 'bg-surface-700'
}`}
role="switch"
aria-checked={useCustomCapital}
aria-label="Set custom initial capital"
>
<span className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transition-transform ${
useCustomCapital ? 'translate-x-4' : 'translate-x-0'
}`} />
</button>
<span className="text-sm text-gray-300">Set initial capital</span>
<span className="text-[10px] text-gray-600">(otherwise uses broker account balance)</span>
</div>
{useCustomCapital && (
<div className="flex items-center gap-2">
<label htmlFor="reset-capital" className="text-xs text-gray-500">Capital $</label>
<input
id="reset-capital"
type="number"
min={0}
step={1000}
value={capitalInput}
onChange={(e) => setCapitalInput(e.target.value)}
className="w-36 rounded-md border border-surface-700 bg-surface-900 px-2 py-1 text-sm font-mono text-gray-200"
/>
</div>
)}
<div>
<div className="flex items-center justify-between">
<label htmlFor="reset-reserve" className="text-xs text-gray-500">
Reserve pool: {reservePct}%
</label>
<span className="text-xs text-gray-600">
Active: {100 - reservePct}%
</span>
</div>
<input
id="reset-reserve"
type="range"
min={0}
max={50}
step={5}
value={reservePct}
onChange={(e) => setReservePct(Number(e.target.value))}
className="mt-1 w-full accent-brand-600"
/>
{useCustomCapital && capital > 0 && (
<div className="mt-1 flex justify-between text-[10px] text-gray-600">
<span>Reserve: ${reserveAmount.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
<span>Active: ${activeAmount.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
)}
</div>
</div>
{/* Reset Button */}
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-300">Full Reset</p>
<p className="text-[10px] text-gray-600">
Liquidates all broker positions, cancels open orders, wipes local trading history,
and syncs capital from the broker account.
and sets capital from {useCustomCapital ? 'the amount above' : 'the broker account balance'}.
</p>
<p className="mt-1 text-[10px] text-gray-600 italic">
Note: To reset the Alpaca paper account balance itself, use the Alpaca dashboard.
</p>
</div>
<button
onClick={() => setShowConfirm(true)}
disabled={isResetting}
className="rounded-md border border-red-700/50 bg-red-900/20 px-3 py-1.5 text-sm font-medium text-red-400 hover:bg-red-900/40 disabled:opacity-50"
className="shrink-0 rounded-md border border-red-700/50 bg-red-900/20 px-3 py-1.5 text-sm font-medium text-red-400 hover:bg-red-900/40 disabled:opacity-50"
>
Reset Everything
</button>
@@ -519,11 +594,20 @@ function ResetCard({ onReset, isResetting }: {
<p className="text-sm text-red-300">
This will <span className="font-semibold">permanently delete</span> all positions, orders,
trading decisions, stop levels, portfolio snapshots, and backtest data.
All broker positions will be liquidated and capital will be set from the broker&apos;s account balance.
All broker positions will be liquidated.
{useCustomCapital
? ` Capital will be set to $${capital.toLocaleString()} (${reservePct}% reserve / ${100 - reservePct}% active).`
: ` Capital will be set from the broker's account balance (${reservePct}% reserve / ${100 - reservePct}% active).`}
</p>
<div className="mt-3 flex gap-2">
<button
onClick={() => { onReset(); setShowConfirm(false); }}
onClick={() => {
onReset({
initial_capital: useCustomCapital ? capital : undefined,
reserve_pct: reservePct / 100,
});
setShowConfirm(false);
}}
disabled={isResetting}
className="rounded-md bg-red-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-red-700 disabled:opacity-50"
>