feat: show max-hold session countdown
This commit is contained in:
@@ -22,6 +22,21 @@ function pnlColor(v: number): string {
|
||||
return 'text-gray-300';
|
||||
}
|
||||
|
||||
function maxHoldText(trade: PaperTrade, compact = false): string | null {
|
||||
const remaining = trade.sessions_remaining;
|
||||
if (remaining == null) return null;
|
||||
if (remaining <= 0) return 'exits today';
|
||||
if (compact) return `${remaining} ${remaining === 1 ? 'session' : 'sessions'} left`;
|
||||
const held = trade.sessions_held ?? 0;
|
||||
return `${held} held · ${remaining} remaining`;
|
||||
}
|
||||
|
||||
function maxHoldColor(trade: PaperTrade): string {
|
||||
return trade.sessions_remaining != null && trade.sessions_remaining <= 5
|
||||
? 'text-amber-300'
|
||||
: 'text-gray-400';
|
||||
}
|
||||
|
||||
function DirTag({ direction }: { direction: string }) {
|
||||
const isLong = direction === 'long';
|
||||
return (
|
||||
@@ -116,6 +131,13 @@ function TradeDetail({ trade, exitLabel, exitMode, atrMultiplier, trailingPct, o
|
||||
}
|
||||
/>
|
||||
<Detail label="exit rule" value={exitLabel ?? 'target/stop'} />
|
||||
{maxHoldText(trade) && (
|
||||
<Detail
|
||||
label="max hold"
|
||||
value={maxHoldText(trade)}
|
||||
valueClass={maxHoldColor(trade)}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-end">
|
||||
<button
|
||||
onClick={onClose}
|
||||
@@ -173,13 +195,14 @@ export function OpenTradesPanel() {
|
||||
const trailingPct = policy?.trailing_pct ?? 12;
|
||||
const exitLabel = policy
|
||||
? policy.mode === 'atr_trailing'
|
||||
? `${atrMultiplier.toFixed(1)}x ATR trailing stop / ${policy.hold_days}d max`
|
||||
? `${atrMultiplier.toFixed(1)}x ATR trailing stop / ${policy.hold_days} sessions max`
|
||||
: policy.mode === 'trailing'
|
||||
? `trailing ${Math.round(trailingPct)}%`
|
||||
: policy.mode === 'time'
|
||||
? `${policy.hold_days}d hold`
|
||||
? `${policy.hold_days}-session hold`
|
||||
: 'target/stop'
|
||||
: null;
|
||||
const hasMaxHold = exitMode === 'atr_trailing' || exitMode === 'time';
|
||||
|
||||
const rows = trades ?? [];
|
||||
|
||||
@@ -217,6 +240,7 @@ export function OpenTradesPanel() {
|
||||
{rows.map((t) => {
|
||||
const p = tradePnl(t);
|
||||
const open = expandedId === t.id;
|
||||
const holdText = maxHoldText(t, true);
|
||||
return (
|
||||
<li key={t.id}>
|
||||
<div
|
||||
@@ -230,7 +254,11 @@ export function OpenTradesPanel() {
|
||||
}
|
||||
}}
|
||||
aria-expanded={open}
|
||||
className="grid w-full cursor-pointer grid-cols-[110px_1fr_60px_16px] items-center gap-3 rounded-lg px-2 py-2.5 text-left transition-colors hover:bg-white/[0.03] sm:grid-cols-[130px_150px_1fr_70px_16px]"
|
||||
className={`grid w-full cursor-pointer grid-cols-[110px_1fr_60px_16px] items-center gap-3 rounded-lg px-2 py-2.5 text-left transition-colors hover:bg-white/[0.03] ${
|
||||
hasMaxHold
|
||||
? 'sm:grid-cols-[130px_150px_1fr_70px_16px] lg:grid-cols-[130px_150px_1fr_110px_70px_16px]'
|
||||
: 'sm:grid-cols-[130px_150px_1fr_70px_16px]'
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<Link
|
||||
@@ -246,6 +274,14 @@ export function OpenTradesPanel() {
|
||||
{formatPrice(t.entry_price)} → {t.current_price != null ? formatPrice(t.current_price) : '—'}
|
||||
</span>
|
||||
<RBar r={p?.r ?? null} max={rMax} />
|
||||
{hasMaxHold && (
|
||||
<span
|
||||
className={`num hidden text-right text-[11px] lg:block ${maxHoldColor(t)}`}
|
||||
title="Maximum holding period; the stop may close this trade sooner."
|
||||
>
|
||||
{holdText ?? '—'}
|
||||
</span>
|
||||
)}
|
||||
<span className={`num text-right text-[13px] font-semibold ${p?.r != null ? pnlColor(p.r) : 'text-gray-500'}`}>
|
||||
{p?.r != null ? `${p.r >= 0 ? '+' : ''}${p.r.toFixed(2)}R` : '—'}
|
||||
</span>
|
||||
|
||||
@@ -237,6 +237,8 @@ export interface PaperTrade {
|
||||
close_reason: 'time' | 'trailing' | 'stop' | 'target' | 'manual' | null;
|
||||
trailing_stop: number | null;
|
||||
trailing_distance_pct: number | null;
|
||||
sessions_held: number | null;
|
||||
sessions_remaining: number | null;
|
||||
}
|
||||
|
||||
export interface ExitPolicy {
|
||||
|
||||
Reference in New Issue
Block a user