fix: remove broken capital controls, reset now queries broker for real balance
- Removed PUT /api/trading/capital (set capital) — only touched in-memory state - Removed POST /api/trading/capital/adjust (add/withdraw) — same problem - Reset endpoint now: liquidates Alpaca positions, cancels orders, clears DB, then queries Alpaca for real portfolio_value to set engine capital - Frontend: replaced CapitalCard with simple ResetCard (one button) - Removed useSetTradingCapital and useAdjustCapital hooks
This commit is contained in:
@@ -426,15 +426,6 @@ 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() {
|
export function usePendingApprovals() {
|
||||||
return useGet<Approval[]>(['pending-approvals'], 'query', '/api/admin/trading/approvals');
|
return useGet<Approval[]>(['pending-approvals'], 'query', '/api/admin/trading/approvals');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,12 +309,13 @@ export function useBacktestLaunch() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Full paper trading reset: clears all positions, orders, decisions, and resets capital. */
|
/** Full paper trading reset: liquidates broker positions, cancels orders,
|
||||||
|
* clears all local trading state, and syncs capital from the broker. */
|
||||||
export function useResetPaperTrading() {
|
export function useResetPaperTrading() {
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (initial_capital: number) =>
|
mutationFn: (initial_capital: number = 0) =>
|
||||||
apiPost<{ reset: boolean; initial_capital: number; active_pool: number; reserve_pool: 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 },
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -326,21 +327,6 @@ export function useResetPaperTrading() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add or subtract capital from the active pool without resetting anything else. */
|
|
||||||
export function useAdjustCapital() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: (amount: number) =>
|
|
||||||
apiPost<{ adjusted: number; active_pool: number; reserve_pool: number; total_value: number }>(
|
|
||||||
'trading', '/api/trading/capital/adjust', { amount },
|
|
||||||
),
|
|
||||||
onSuccess: () => {
|
|
||||||
qc.invalidateQueries({ queryKey: ['trading-status'] });
|
|
||||||
qc.invalidateQueries({ queryKey: ['trading-metrics'] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Update notification preferences. */
|
/** Update notification preferences. */
|
||||||
export function useUpdateNotificationConfig() {
|
export function useUpdateNotificationConfig() {
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
|
|||||||
+25
-138
@@ -2,7 +2,6 @@ import { useState } from 'react';
|
|||||||
import {
|
import {
|
||||||
useTradingConfig,
|
useTradingConfig,
|
||||||
useSetTradingMode,
|
useSetTradingMode,
|
||||||
useSetTradingCapital,
|
|
||||||
usePendingApprovals,
|
usePendingApprovals,
|
||||||
useReviewApproval,
|
useReviewApproval,
|
||||||
useActiveLockouts,
|
useActiveLockouts,
|
||||||
@@ -11,7 +10,7 @@ import {
|
|||||||
useCompetitiveStatus,
|
useCompetitiveStatus,
|
||||||
useToggleCompetitive,
|
useToggleCompetitive,
|
||||||
} from '../api/hooks';
|
} from '../api/hooks';
|
||||||
import { useResetPaperTrading, useAdjustCapital } from '../api/tradingHooks';
|
import { useResetPaperTrading } from '../api/tradingHooks';
|
||||||
import { StatusBadge, LoadingSpinner, Card } from '../components/ui';
|
import { StatusBadge, LoadingSpinner, Card } from '../components/ui';
|
||||||
|
|
||||||
export function TradingPage() {
|
export function TradingPage() {
|
||||||
@@ -21,9 +20,7 @@ export function TradingPage() {
|
|||||||
const { data: macroStatus } = useMacroStatus();
|
const { data: macroStatus } = useMacroStatus();
|
||||||
const { data: competitiveStatus } = useCompetitiveStatus();
|
const { data: competitiveStatus } = useCompetitiveStatus();
|
||||||
const setMode = useSetTradingMode();
|
const setMode = useSetTradingMode();
|
||||||
const setCapital = useSetTradingCapital();
|
|
||||||
const resetTrading = useResetPaperTrading();
|
const resetTrading = useResetPaperTrading();
|
||||||
const adjustCapital = useAdjustCapital();
|
|
||||||
const reviewApproval = useReviewApproval();
|
const reviewApproval = useReviewApproval();
|
||||||
const toggleMacro = useToggleMacro();
|
const toggleMacro = useToggleMacro();
|
||||||
const toggleCompetitive = useToggleCompetitive();
|
const toggleCompetitive = useToggleCompetitive();
|
||||||
@@ -92,14 +89,10 @@ export function TradingPage() {
|
|||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Paper Trading Capital */}
|
{/* Paper Trading Reset */}
|
||||||
<CapitalCard
|
<ResetCard
|
||||||
onSetCapital={(amount) => setCapital.mutate(amount)}
|
onReset={() => resetTrading.mutate(0)}
|
||||||
onReset={(amount) => resetTrading.mutate(amount)}
|
|
||||||
onAdjust={(amount) => adjustCapital.mutate(amount)}
|
|
||||||
isPending={setCapital.isPending}
|
|
||||||
isResetting={resetTrading.isPending}
|
isResetting={resetTrading.isPending}
|
||||||
isAdjusting={adjustCapital.isPending}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Macro Signal Layer Toggle */}
|
{/* Macro Signal Layer Toggle */}
|
||||||
@@ -309,77 +302,45 @@ function ApprovalRow({ approval, onReview }: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function CapitalCard({ onSetCapital, onReset, onAdjust, isPending, isResetting, isAdjusting }: {
|
function ResetCard({ onReset, isResetting }: {
|
||||||
onSetCapital: (amount: number) => void;
|
onReset: () => void;
|
||||||
onReset: (amount: number) => void;
|
|
||||||
onAdjust: (amount: number) => void;
|
|
||||||
isPending: boolean;
|
|
||||||
isResetting: boolean;
|
isResetting: boolean;
|
||||||
isAdjusting: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
const [amount, setAmount] = useState('100000');
|
|
||||||
const [adjustAmount, setAdjustAmount] = useState('');
|
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
|
||||||
|
|
||||||
const presets = [10000, 50000, 100000, 500000];
|
|
||||||
const busy = isPending || isResetting || isAdjusting;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Paper Trading Capital</h2>
|
<h2 className="mb-3 text-sm font-medium text-gray-400">Paper Trading Account</h2>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
{/* Set Capital */}
|
|
||||||
<div className="flex flex-wrap items-end gap-3">
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="capital-input" className="mb-1 block text-xs text-gray-500">Initial Capital ($)</label>
|
<p className="text-sm text-gray-300">Full Reset</p>
|
||||||
<input
|
<p className="text-[10px] text-gray-600">
|
||||||
id="capital-input"
|
Liquidates all broker positions, cancels open orders, wipes local trading history,
|
||||||
type="number"
|
and syncs capital from the broker account.
|
||||||
min="100"
|
</p>
|
||||||
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>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowConfirm(true)}
|
onClick={() => setShowConfirm(true)}
|
||||||
disabled={busy || !amount || Number(amount) <= 0}
|
disabled={isResetting}
|
||||||
className="rounded-md bg-brand-600 px-4 py-1.5 text-sm font-medium text-white hover:bg-brand-700 disabled:opacity-50"
|
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"
|
||||||
>
|
>
|
||||||
Set Capital
|
Reset Everything
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showConfirm && (
|
{showConfirm && (
|
||||||
<div className="mt-4 rounded-lg border border-orange-700/50 bg-orange-900/20 p-4">
|
<div className="mt-3 rounded-lg border border-red-700/50 bg-red-900/20 p-4">
|
||||||
<p className="text-sm text-orange-300">
|
<p className="text-sm text-red-300">
|
||||||
This will reset the paper trading pools to <span className="font-semibold">${Number(amount).toLocaleString()}</span>.
|
This will <span className="font-semibold">permanently delete</span> all positions, orders,
|
||||||
Any existing pool balances will be overwritten.
|
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.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-3 flex gap-2">
|
<div className="mt-3 flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => { onSetCapital(Number(amount)); setShowConfirm(false); }}
|
onClick={() => { onReset(); setShowConfirm(false); }}
|
||||||
disabled={busy}
|
disabled={isResetting}
|
||||||
className="rounded-md bg-orange-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-orange-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"
|
||||||
>
|
>
|
||||||
{isPending ? 'Setting…' : 'Confirm'}
|
{isResetting ? 'Resetting…' : 'Yes, Reset Everything'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowConfirm(false)}
|
onClick={() => setShowConfirm(false)}
|
||||||
@@ -390,80 +351,6 @@ function CapitalCard({ onSetCapital, onReset, onAdjust, isPending, isResetting,
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Add / Subtract Capital */}
|
|
||||||
<div className="mt-4 border-t border-surface-700 pt-4">
|
|
||||||
<h3 className="mb-2 text-xs font-medium text-gray-500">Adjust Capital</h3>
|
|
||||||
<div className="flex items-end gap-2">
|
|
||||||
<div>
|
|
||||||
<label htmlFor="adjust-input" className="mb-1 block text-xs text-gray-500">Amount ($)</label>
|
|
||||||
<input
|
|
||||||
id="adjust-input"
|
|
||||||
type="number"
|
|
||||||
step="100"
|
|
||||||
value={adjustAmount}
|
|
||||||
onChange={(e) => setAdjustAmount(e.target.value)}
|
|
||||||
placeholder="5000"
|
|
||||||
className="w-36 rounded-md border border-surface-700 bg-surface-900 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-600 focus:border-brand-500 focus:outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => { onAdjust(Math.abs(Number(adjustAmount))); setAdjustAmount(''); }}
|
|
||||||
disabled={busy || !adjustAmount || Number(adjustAmount) <= 0}
|
|
||||||
className="rounded-md bg-green-700 px-3 py-1.5 text-sm font-medium text-white hover:bg-green-600 disabled:opacity-50"
|
|
||||||
>
|
|
||||||
+ Add
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => { onAdjust(-Math.abs(Number(adjustAmount))); setAdjustAmount(''); }}
|
|
||||||
disabled={busy || !adjustAmount || Number(adjustAmount) <= 0}
|
|
||||||
className="rounded-md bg-red-700 px-3 py-1.5 text-sm font-medium text-white hover:bg-red-600 disabled:opacity-50"
|
|
||||||
>
|
|
||||||
− Withdraw
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{isAdjusting && <p className="mt-1 text-xs text-gray-500">Adjusting…</p>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Full Reset */}
|
|
||||||
<div className="mt-4 border-t border-surface-700 pt-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xs font-medium text-gray-500">Full Reset</h3>
|
|
||||||
<p className="text-[10px] text-gray-600">Wipes all positions, orders, decisions, and history. Resets capital to the amount above.</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowResetConfirm(true)}
|
|
||||||
disabled={busy || !amount || Number(amount) <= 0}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
Reset Everything
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{showResetConfirm && (
|
|
||||||
<div className="mt-3 rounded-lg border border-red-700/50 bg-red-900/20 p-4">
|
|
||||||
<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.
|
|
||||||
Capital will be reset to <span className="font-semibold">${Number(amount).toLocaleString()}</span>.
|
|
||||||
</p>
|
|
||||||
<div className="mt-3 flex gap-2">
|
|
||||||
<button
|
|
||||||
onClick={() => { onReset(Number(amount)); setShowResetConfirm(false); }}
|
|
||||||
disabled={busy}
|
|
||||||
className="rounded-md bg-red-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-red-700 disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{isResetting ? 'Resetting…' : 'Yes, Reset Everything'}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowResetConfirm(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>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-119
@@ -60,9 +60,9 @@ class ConfigUpdateRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class CapitalRequest(BaseModel):
|
class CapitalRequest(BaseModel):
|
||||||
"""Body for PUT /api/trading/capital."""
|
"""Body for POST /api/trading/reset."""
|
||||||
|
|
||||||
initial_capital: float
|
initial_capital: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
class BacktestRequest(BaseModel):
|
class BacktestRequest(BaseModel):
|
||||||
@@ -300,131 +300,29 @@ async def resume_engine() -> dict[str, bool]:
|
|||||||
return {"paused": False}
|
return {"paused": False}
|
||||||
|
|
||||||
|
|
||||||
@app.put("/api/trading/capital")
|
|
||||||
async def set_capital(body: CapitalRequest) -> dict[str, Any]:
|
|
||||||
"""Set or reset the paper trading capital.
|
|
||||||
|
|
||||||
Allocates the given amount as initial capital, splitting between
|
|
||||||
active pool and reserve pool based on the current reserve_siphon_pct.
|
|
||||||
"""
|
|
||||||
if engine is None:
|
|
||||||
raise HTTPException(503, "Engine not initialised")
|
|
||||||
|
|
||||||
capital = body.initial_capital
|
|
||||||
if capital <= 0:
|
|
||||||
raise HTTPException(400, "initial_capital must be positive")
|
|
||||||
|
|
||||||
reserve_pct = engine.config.reserve_siphon_pct
|
|
||||||
reserve = capital * reserve_pct
|
|
||||||
active = capital - reserve
|
|
||||||
|
|
||||||
ps = engine.portfolio_state
|
|
||||||
previous = {
|
|
||||||
"total_value": ps.total_value if ps else 0.0,
|
|
||||||
"active_pool": ps.active_pool if ps else 0.0,
|
|
||||||
"reserve_pool": ps.reserve_pool if ps else 0.0,
|
|
||||||
}
|
|
||||||
|
|
||||||
from services.trading.models import PortfolioState
|
|
||||||
engine.portfolio_state = PortfolioState(
|
|
||||||
total_value=capital,
|
|
||||||
cash=capital,
|
|
||||||
active_pool=active,
|
|
||||||
reserve_pool=reserve,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Record in reserve pool ledger
|
|
||||||
if engine.pool:
|
|
||||||
try:
|
|
||||||
await engine.pool.execute(
|
|
||||||
"INSERT INTO reserve_pool_ledger (amount, balance_after, trigger_type, notes) "
|
|
||||||
"VALUES ($1, $2, 'capital_reset', $3)",
|
|
||||||
reserve, reserve,
|
|
||||||
f"Capital set to ${capital:,.2f} (active=${active:,.2f}, reserve=${reserve:,.2f})",
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass # Non-critical
|
|
||||||
|
|
||||||
return {
|
|
||||||
"initial_capital": capital,
|
|
||||||
"active_pool": active,
|
|
||||||
"reserve_pool": reserve,
|
|
||||||
"reserve_siphon_pct": reserve_pct,
|
|
||||||
"previous": previous,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/trading/capital/adjust")
|
|
||||||
async def adjust_capital(body: dict[str, Any]) -> dict[str, Any]:
|
|
||||||
"""Add or subtract capital from the active pool without resetting anything.
|
|
||||||
|
|
||||||
Body: { "amount": 5000 } to add, { "amount": -2000 } to subtract.
|
|
||||||
Positions, orders, decisions, and history are untouched.
|
|
||||||
"""
|
|
||||||
if engine is None:
|
|
||||||
raise HTTPException(503, "Engine not initialised")
|
|
||||||
|
|
||||||
amount = body.get("amount", 0)
|
|
||||||
if not isinstance(amount, (int, float)) or amount == 0:
|
|
||||||
raise HTTPException(400, "amount must be a non-zero number")
|
|
||||||
|
|
||||||
ps = engine.portfolio_state
|
|
||||||
if ps is None:
|
|
||||||
raise HTTPException(400, "No portfolio state — set capital first")
|
|
||||||
|
|
||||||
new_active = ps.active_pool + amount
|
|
||||||
if new_active < 0:
|
|
||||||
raise HTTPException(400, f"Cannot subtract ${abs(amount):,.2f} — only ${ps.active_pool:,.2f} available in active pool")
|
|
||||||
|
|
||||||
previous_active = ps.active_pool
|
|
||||||
ps.active_pool = new_active
|
|
||||||
ps.cash = ps.cash + amount
|
|
||||||
ps.total_value = ps.total_value + amount
|
|
||||||
|
|
||||||
# Record in reserve pool ledger for audit
|
|
||||||
if engine.pool:
|
|
||||||
try:
|
|
||||||
trigger = "manual_adjustment"
|
|
||||||
note = f"{'Added' if amount > 0 else 'Withdrew'} ${abs(amount):,.2f} (active pool: ${previous_active:,.2f} → ${new_active:,.2f})"
|
|
||||||
await engine.pool.execute(
|
|
||||||
"INSERT INTO reserve_pool_ledger (amount, balance_after, trigger_type, notes) "
|
|
||||||
"VALUES ($1, $2, $3, $4)",
|
|
||||||
amount, new_active, trigger, note,
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return {
|
|
||||||
"adjusted": amount,
|
|
||||||
"active_pool": ps.active_pool,
|
|
||||||
"reserve_pool": ps.reserve_pool,
|
|
||||||
"total_value": ps.total_value,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/trading/reset")
|
@app.post("/api/trading/reset")
|
||||||
async def reset_paper_trading(body: CapitalRequest) -> dict[str, Any]:
|
async def reset_paper_trading(body: CapitalRequest) -> dict[str, Any]:
|
||||||
"""Full paper trading reset: clear all positions, orders, decisions,
|
"""Full paper trading reset: liquidate all broker positions, cancel open
|
||||||
stop levels, and snapshots, then reset capital to the specified amount.
|
orders, clear all local trading state, then query the broker for the
|
||||||
|
real account balance and set the engine's capital from that.
|
||||||
|
|
||||||
Also liquidates all positions and cancels all open orders on the
|
If initial_capital is provided and > 0, it overrides the broker balance
|
||||||
broker (Alpaca) so the paper account starts clean.
|
(useful when the broker is unavailable or you want a specific amount).
|
||||||
|
|
||||||
This is a destructive operation — all trading history is wiped.
|
This is a destructive operation — all trading history is wiped.
|
||||||
"""
|
"""
|
||||||
if engine is None:
|
if engine is None:
|
||||||
raise HTTPException(503, "Engine not initialised")
|
raise HTTPException(503, "Engine not initialised")
|
||||||
|
|
||||||
capital = body.initial_capital
|
# --- Reset broker (Alpaca) state and query real balance ---
|
||||||
if capital <= 0:
|
broker_result: dict[str, Any] = {
|
||||||
raise HTTPException(400, "initial_capital must be positive")
|
"orders_cancelled": 0,
|
||||||
|
"positions_closed": 0,
|
||||||
reserve_pct = engine.config.reserve_siphon_pct
|
"portfolio_value": 0.0,
|
||||||
reserve = capital * reserve_pct
|
"cash": 0.0,
|
||||||
active = capital - reserve
|
"buying_power": 0.0,
|
||||||
|
}
|
||||||
# --- Reset broker (Alpaca) state ---
|
broker_capital: float | None = None
|
||||||
broker_result: dict[str, Any] = {"orders_cancelled": 0, "positions_closed": 0}
|
|
||||||
try:
|
try:
|
||||||
from services.adapters.broker_adapter import AlpacaBrokerAdapter
|
from services.adapters.broker_adapter import AlpacaBrokerAdapter
|
||||||
from services.shared.config import load_config as _load_config
|
from services.shared.config import load_config as _load_config
|
||||||
@@ -438,16 +336,39 @@ async def reset_paper_trading(body: CapitalRequest) -> dict[str, Any]:
|
|||||||
)
|
)
|
||||||
broker_result["orders_cancelled"] = await adapter.cancel_all_orders()
|
broker_result["orders_cancelled"] = await adapter.cancel_all_orders()
|
||||||
broker_result["positions_closed"] = await adapter.close_all_positions()
|
broker_result["positions_closed"] = await adapter.close_all_positions()
|
||||||
|
|
||||||
|
# Query the real account balance after liquidation
|
||||||
|
acct = await adapter.get_account()
|
||||||
|
broker_result["portfolio_value"] = acct.portfolio_value
|
||||||
|
broker_result["cash"] = acct.cash
|
||||||
|
broker_result["buying_power"] = acct.buying_power
|
||||||
|
broker_capital = acct.portfolio_value
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Broker reset: cancelled %d orders, closed %d positions",
|
"Broker reset: cancelled %d orders, closed %d positions, "
|
||||||
|
"portfolio_value=%.2f, cash=%.2f",
|
||||||
broker_result["orders_cancelled"],
|
broker_result["orders_cancelled"],
|
||||||
broker_result["positions_closed"],
|
broker_result["positions_closed"],
|
||||||
|
acct.portfolio_value,
|
||||||
|
acct.cash,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info("No broker API key configured — skipping broker reset")
|
logger.info("No broker API key configured — skipping broker reset")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Broker reset failed — continuing with DB/engine reset")
|
logger.exception("Broker reset failed — continuing with DB/engine reset")
|
||||||
|
|
||||||
|
# Determine capital: explicit override > broker balance > default 100k
|
||||||
|
if body.initial_capital > 0:
|
||||||
|
capital = body.initial_capital
|
||||||
|
elif broker_capital and broker_capital > 0:
|
||||||
|
capital = broker_capital
|
||||||
|
else:
|
||||||
|
capital = 100_000.0
|
||||||
|
|
||||||
|
reserve_pct = engine.config.reserve_siphon_pct
|
||||||
|
reserve = capital * reserve_pct
|
||||||
|
active = capital - reserve
|
||||||
|
|
||||||
# --- Clear trading state in the database ---
|
# --- Clear trading state in the database ---
|
||||||
if engine.pool:
|
if engine.pool:
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user