feat: add max open positions and position cap controls to trading dashboard
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/build-1 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

This commit is contained in:
Celes Renata
2026-04-28 19:16:50 +00:00
parent 23f2134754
commit 7e8d518946
3 changed files with 68 additions and 0 deletions
+2
View File
@@ -22,6 +22,8 @@ export interface TradingEngineStatus {
portfolio_heat: number; portfolio_heat: number;
portfolio_value: number; portfolio_value: number;
open_position_count: number; open_position_count: number;
max_open_positions: number;
absolute_position_cap: number;
last_decision_at: string | null; last_decision_at: string | null;
micro_trading_enabled: boolean; micro_trading_enabled: boolean;
uptime_seconds: number | null; uptime_seconds: number | null;
@@ -35,6 +35,8 @@ export function TradingOverview() {
const resume = useResumeTradingEngine(); const resume = useResumeTradingEngine();
const updateConfig = useUpdateTradingConfig(); const updateConfig = useUpdateTradingConfig();
const [selectedTier, setSelectedTier] = useState<string | null>(null); const [selectedTier, setSelectedTier] = useState<string | null>(null);
const [maxPositions, setMaxPositions] = useState<number | null>(null);
const [positionCap, setPositionCap] = useState<number | null>(null);
if (isLoading) return <LoadingSpinner />; if (isLoading) return <LoadingSpinner />;
if (!status) return <p className="text-gray-500">No trading status available</p>; if (!status) return <p className="text-gray-500">No trading status available</p>;
@@ -131,6 +133,68 @@ export function TradingOverview() {
<StatCard label="Portfolio Heat" value={fmtPct(status.portfolio_heat)} /> <StatCard label="Portfolio Heat" value={fmtPct(status.portfolio_heat)} />
</div> </div>
{/* Position Limits */}
<Card>
<h2 className="mb-3 text-sm font-medium text-gray-400">Position Limits</h2>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label htmlFor="max-positions" className="block text-xs text-gray-500 mb-1">
Max Open Positions
</label>
<div className="flex items-center gap-2">
<input
id="max-positions"
type="number"
min={1}
max={50}
value={maxPositions ?? status.max_open_positions ?? 10}
onChange={(e) => setMaxPositions(Number(e.target.value))}
className="w-20 rounded-md border border-surface-700 bg-surface-950 px-2 py-1.5 text-sm text-gray-200 focus:border-brand-500 focus:outline-none"
/>
<button
onClick={() => {
const val = maxPositions ?? status.max_open_positions ?? 10;
updateConfig.mutate({ max_open_positions: val });
}}
disabled={updateConfig.isPending}
className="rounded-md bg-brand-700 px-3 py-1.5 text-xs font-medium text-white hover:bg-brand-600 disabled:opacity-50"
>
Apply
</button>
<span className="text-xs text-gray-500">
Current: {status.open_position_count ?? 0} / {status.max_open_positions ?? 10}
</span>
</div>
</div>
<div>
<label htmlFor="position-cap" className="block text-xs text-gray-500 mb-1">
Absolute Position Cap ($)
</label>
<div className="flex items-center gap-2">
<input
id="position-cap"
type="number"
min={10}
step={10}
value={positionCap ?? status.absolute_position_cap ?? 50}
onChange={(e) => setPositionCap(Number(e.target.value))}
className="w-24 rounded-md border border-surface-700 bg-surface-950 px-2 py-1.5 text-sm text-gray-200 focus:border-brand-500 focus:outline-none"
/>
<button
onClick={() => {
const val = positionCap ?? status.absolute_position_cap ?? 50;
updateConfig.mutate({ absolute_position_cap: val });
}}
disabled={updateConfig.isPending}
className="rounded-md bg-brand-700 px-3 py-1.5 text-xs font-medium text-white hover:bg-brand-600 disabled:opacity-50"
>
Apply
</button>
</div>
</div>
</div>
</Card>
{/* Portfolio Heat Gauge */} {/* Portfolio Heat Gauge */}
<Card> <Card>
<h2 className="mb-2 text-sm font-medium text-gray-400">Portfolio Heat</h2> <h2 className="mb-2 text-sm font-medium text-gray-400">Portfolio Heat</h2>
+2
View File
@@ -314,6 +314,8 @@ async def trading_status() -> dict[str, Any]:
"reserve_pool": reserve_pool, "reserve_pool": reserve_pool,
"portfolio_heat": portfolio_heat, "portfolio_heat": portfolio_heat,
"open_positions": open_positions, "open_positions": open_positions,
"max_open_positions": engine.config.max_open_positions,
"absolute_position_cap": engine.config.absolute_position_cap,
"last_decision_at": None, "last_decision_at": None,
} }