Add privacy toggle, min-day P&L reservation, and extra-day MNQ trades
- Privacy button: masks account names beyond the first 5 chars with bullets; eye/eye-off icon toggles the mode in the header toolbar - computeDailyTarget: accepts minDayPnL + minTradingDays params; when a positive min floor is set and mandatory days remain, reserves future-day profit so each day hits the floor (cap = remaining - futureReserve, floor = minDayPnL); returns effectiveMinDay directly once profit target is met but days are not yet satisfied - auto-trade: passes minDayPnL/minTradingDays to computeDailyTarget; for zero-floor accounts that have met the profit target but still owe trading days, trades 1 MNQ in-and-out at market (extra-day mode) and bypasses the normal target=0 skip gate via isMnqExtraDay flag Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
49580c6bb4
commit
ce2075f603
+41
-5
@@ -60,6 +60,32 @@ function DetailsIcon() {
|
||||
);
|
||||
}
|
||||
|
||||
function EyeIcon() {
|
||||
return (
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function EyeOffIcon() {
|
||||
return (
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94" />
|
||||
<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19" />
|
||||
<line x1="1" y1="1" x2="23" y2="23" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function maskName(name: string, privacy: boolean): string {
|
||||
if (!privacy) return name;
|
||||
const visible = name.slice(0, 5);
|
||||
const hidden = name.slice(5);
|
||||
return hidden.length > 0 ? visible + '•'.repeat(hidden.length) : visible;
|
||||
}
|
||||
|
||||
function SortHeader({ label, col, sortKey, sortDir, onSort }: {
|
||||
label: string;
|
||||
col: SortKey;
|
||||
@@ -85,7 +111,7 @@ function SortHeader({ label, col, sortKey, sortDir, onSort }: {
|
||||
);
|
||||
}
|
||||
|
||||
function AccountRow({ account, firm, hideDead }: { account: AccountState; firm: FirmConfig; hideDead: boolean }) {
|
||||
function AccountRow({ account, firm, hideDead, privacy }: { account: AccountState; firm: FirmConfig; hideDead: boolean; privacy: boolean }) {
|
||||
const cfg = getAccountConfig(account.name, firm);
|
||||
const dead = isAccountDead(account);
|
||||
|
||||
@@ -96,8 +122,8 @@ function AccountRow({ account, firm, hideDead }: { account: AccountState; firm:
|
||||
return (
|
||||
<tr className={`border-b border-slate-100 hover:bg-slate-50 transition-colors${!account.active || dead ? ' opacity-50' : ''}`}>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center gap-2 font-medium text-slate-800 pl-4">
|
||||
{account.name}
|
||||
<div className="flex items-center gap-2 font-medium text-slate-800 pl-4 font-mono">
|
||||
{maskName(account.name, privacy)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-slate-600 tabular-nums">${fmt(account.amount)}</td>
|
||||
@@ -135,13 +161,14 @@ function AccountRow({ account, firm, hideDead }: { account: AccountState; firm:
|
||||
);
|
||||
}
|
||||
|
||||
function FirmRows({ state, firm, deleteMode, selected, onToggle, hideDead, sortKey, sortDir }: {
|
||||
function FirmRows({ state, firm, deleteMode, selected, onToggle, hideDead, privacy, sortKey, sortDir }: {
|
||||
state: FirmState;
|
||||
firm: FirmConfig;
|
||||
deleteMode: boolean;
|
||||
selected: boolean;
|
||||
onToggle: () => void;
|
||||
hideDead: boolean;
|
||||
privacy: boolean;
|
||||
sortKey: SortKey | null;
|
||||
sortDir: SortDir;
|
||||
}) {
|
||||
@@ -209,7 +236,7 @@ function FirmRows({ state, firm, deleteMode, selected, onToggle, hideDead, sortK
|
||||
</tr>
|
||||
) : (
|
||||
sortedAccounts.map((acc) => (
|
||||
<AccountRow key={acc.id} account={acc} firm={firm} hideDead={hideDead} />
|
||||
<AccountRow key={acc.id} account={acc} firm={firm} hideDead={hideDead} privacy={privacy} />
|
||||
))
|
||||
)
|
||||
)}
|
||||
@@ -232,6 +259,7 @@ export default function Home() {
|
||||
const [deleteMode, setDeleteMode] = useState(false);
|
||||
const [selected, setSelected] = useState<Set<number>>(new Set());
|
||||
const [hideDead, setHideDead] = useState(false);
|
||||
const [privacy, setPrivacy] = useState(false);
|
||||
const [sortKey, setSortKey] = useState<SortKey | null>(null);
|
||||
const [sortDir, setSortDir] = useState<SortDir>('asc');
|
||||
|
||||
@@ -396,6 +424,13 @@ export default function Home() {
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPrivacy((p) => !p)}
|
||||
className={`px-3 py-2 rounded-lg transition-colors inline-flex items-center ${privacy ? 'text-blue-600 bg-blue-50 hover:bg-blue-100' : 'text-slate-400 hover:text-slate-600 hover:bg-slate-100'}`}
|
||||
title={privacy ? 'Show account names' : 'Hide account names'}
|
||||
>
|
||||
{privacy ? <EyeOffIcon /> : <EyeIcon />}
|
||||
</button>
|
||||
<Link
|
||||
href="/settings"
|
||||
className="px-3 py-2 text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded-lg transition-colors inline-flex items-center"
|
||||
@@ -496,6 +531,7 @@ export default function Home() {
|
||||
selected={selected.has(cfg.id)}
|
||||
onToggle={() => toggleSelected(cfg.id)}
|
||||
hideDead={hideDead}
|
||||
privacy={privacy}
|
||||
sortKey={sortKey}
|
||||
sortDir={sortDir}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user