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
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:
@@ -316,9 +316,12 @@ export function useBacktestLaunch() {
|
|||||||
export function useResetPaperTrading() {
|
export function useResetPaperTrading() {
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
return useMutation({
|
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> }>(
|
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: () => {
|
onSuccess: () => {
|
||||||
qc.invalidateQueries({ queryKey: ['trading-status'] });
|
qc.invalidateQueries({ queryKey: ['trading-status'] });
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export function TradingPage() {
|
|||||||
|
|
||||||
{/* Paper Trading Reset */}
|
{/* Paper Trading Reset */}
|
||||||
<ResetCard
|
<ResetCard
|
||||||
onReset={() => resetTrading.mutate(0)}
|
onReset={(params) => resetTrading.mutate(params)}
|
||||||
isResetting={resetTrading.isPending}
|
isResetting={resetTrading.isPending}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -490,26 +490,101 @@ function ApprovalRow({ approval, onReview }: {
|
|||||||
|
|
||||||
|
|
||||||
function ResetCard({ onReset, isResetting }: {
|
function ResetCard({ onReset, isResetting }: {
|
||||||
onReset: () => void;
|
onReset: (params: { initial_capital?: number; reserve_pct?: number }) => void;
|
||||||
isResetting: boolean;
|
isResetting: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
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 (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Paper Trading Account</h2>
|
<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 className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-300">Full Reset</p>
|
<p className="text-sm text-gray-300">Full Reset</p>
|
||||||
<p className="text-[10px] text-gray-600">
|
<p className="text-[10px] text-gray-600">
|
||||||
Liquidates all broker positions, cancels open orders, wipes local trading history,
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowConfirm(true)}
|
onClick={() => setShowConfirm(true)}
|
||||||
disabled={isResetting}
|
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
|
Reset Everything
|
||||||
</button>
|
</button>
|
||||||
@@ -519,11 +594,20 @@ function ResetCard({ onReset, isResetting }: {
|
|||||||
<p className="text-sm text-red-300">
|
<p className="text-sm text-red-300">
|
||||||
This will <span className="font-semibold">permanently delete</span> all positions, orders,
|
This will <span className="font-semibold">permanently delete</span> all positions, orders,
|
||||||
trading decisions, stop levels, portfolio snapshots, and backtest data.
|
trading decisions, stop levels, portfolio snapshots, and backtest data.
|
||||||
All broker positions will be liquidated and capital will be set from the broker'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>
|
</p>
|
||||||
<div className="mt-3 flex gap-2">
|
<div className="mt-3 flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => { onReset(); setShowConfirm(false); }}
|
onClick={() => {
|
||||||
|
onReset({
|
||||||
|
initial_capital: useCustomCapital ? capital : undefined,
|
||||||
|
reserve_pct: reservePct / 100,
|
||||||
|
});
|
||||||
|
setShowConfirm(false);
|
||||||
|
}}
|
||||||
disabled={isResetting}
|
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"
|
className="rounded-md bg-red-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-red-700 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class CapitalRequest(BaseModel):
|
|||||||
"""Body for POST /api/trading/reset."""
|
"""Body for POST /api/trading/reset."""
|
||||||
|
|
||||||
initial_capital: float = 0.0
|
initial_capital: float = 0.0
|
||||||
|
reserve_pct: float | None = None
|
||||||
|
|
||||||
|
|
||||||
class BacktestRequest(BaseModel):
|
class BacktestRequest(BaseModel):
|
||||||
@@ -430,7 +431,8 @@ async def reset_paper_trading(body: CapitalRequest) -> dict[str, Any]:
|
|||||||
else:
|
else:
|
||||||
capital = 100_000.0
|
capital = 100_000.0
|
||||||
|
|
||||||
reserve_pct = engine.config.reserve_siphon_pct
|
reserve_pct = body.reserve_pct if body.reserve_pct is not None else engine.config.reserve_siphon_pct
|
||||||
|
reserve_pct = max(0.0, min(1.0, reserve_pct)) # clamp 0-100%
|
||||||
reserve = capital * reserve_pct
|
reserve = capital * reserve_pct
|
||||||
active = capital - reserve
|
active = capital - reserve
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user