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:
Senofy
2026-03-09 13:45:26 -05:00
co-authored by Claude Sonnet 4.6
parent 49580c6bb4
commit ce2075f603
3 changed files with 135 additions and 21 deletions
+42 -4
View File
@@ -101,8 +101,14 @@ export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
if (cash.realizedPnL !== 0) continue;
// Use the same target formula as the dashboard — skip if $0 (challenge complete)
const target = computeDailyTarget(cfg.profitTarget, cfg.consistency, totalProfit, dailyPnL);
if (target.amount <= 0) continue;
const target = computeDailyTarget(cfg.profitTarget, cfg.consistency, totalProfit, dailyPnL, cfg.minDayPnL, cfg.minTradingDays);
// Allow through if it's an MNQ extra-day trade: no min day P&L, profit done, days still needed
const isMnqExtraDay = cfg.minDayPnL <= 0
&& cfg.minTradingDays > daysTraded
&& totalProfit >= cfg.profitTarget;
if (target.amount <= 0 && !isMnqExtraDay) continue;
allEligible.push({ firmName: firm.name, client, acc, contract, firmConfig, cash, dailyPnL, daysTraded });
}
@@ -120,10 +126,42 @@ export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
// ── Phase 2: fire the batch simultaneously ──
const tradeResults = await Promise.allSettled(batch.map(async (item) => {
const { client, acc, contract, firmConfig, cash, dailyPnL, daysTraded } = item;
const { client, acc, contract, firmConfig, dailyPnL, daysTraded } = item;
const cfg = getAccountConfig(acc.name, firmConfig)!;
const totalProfit = dailyPnL.reduce((sum, d) => sum + d.pnl, 0);
const target = computeDailyTarget(cfg.profitTarget, cfg.consistency, totalProfit, dailyPnL);
// Extra-day mode: profit target already met, no min day P&L, days still needed.
// Just trade 1 MNQ in and out at market immediately — P&L doesn't matter.
const isExtraDay = cfg.minDayPnL <= 0
&& cfg.minTradingDays > daysTraded
&& totalProfit >= cfg.profitTarget;
if (isExtraDay) {
const mnqContract = await client.findFrontMonthContract('MNQ');
if (!mnqContract) throw new Error('MNQ contract not found for extra-day trade');
const fill = await client.sendOrder(acc.id, mnqContract.name, 1, action, 'Market');
const exitAction: 'Buy' | 'Sell' = action === 'Buy' ? 'Sell' : 'Buy';
const exitFill = await client.sendOrder(acc.id, mnqContract.name, 1, exitAction, 'Market');
console.log(`[auto-trade] ${acc.name} (${item.firmName}) extra-day: ${action} 1xMNQ @ ${fill.price} | exited @ ${exitFill.price} (market)`);
return {
account: acc.name,
firm: item.firmName,
status: 'filled',
contracts: 1,
target: 0,
grossTarget: 0,
totalCommission: 0,
targetPath: 'extra_day',
entryPrice: fill.price,
exitPrice: exitFill.price,
commission: 0,
};
}
const target = computeDailyTarget(cfg.profitTarget, cfg.consistency, totalProfit, dailyPnL, cfg.minDayPnL, cfg.minTradingDays);
const rawContracts = Math.max(1, Math.ceil(target.amount / 1000));
const contracts = cfg.maxPositionSize > 0 ? Math.min(rawContracts, cfg.maxPositionSize) : rawContracts;
+52 -12
View File
@@ -20,28 +20,68 @@ export const POINT_VALUES: { [symbol: string]: number } = {
*
* if needed > maxDay → target maxDay (still a long way from the real target; trade a normal day)
* else → target needed (close to the real target; aim for exactly what's left)
*
* Min-day reservation (only when minDayPnL > 0):
* When there are still mandatory trading days remaining, today's target is capped so that
* enough profit is reserved for each future mandatory day to meet minDayPnL.
* Cap = (profitTarget - totalProfit) (remainingDaysAfterToday × minDayPnL)
* Floor = minDayPnL (we must make at least this today)
*/
export function computeDailyTarget(
profitTarget: number,
consistency: number,
totalProfit: number,
dailyPnL: { date: string; pnl: number }[]
dailyPnL: { date: string; pnl: number }[],
minDayPnL: number = 0, // -999 or 0 = no minimum per day
minTradingDays: number = 0 // 0 = no minimum trading days
): { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } {
const positiveDays = dailyPnL.filter((d) => d.pnl > 0);
const daysTraded = positiveDays.length;
if (positiveDays.length === 0) {
return {
amount: Math.round(profitTarget * consistency * 100) / 100,
path: 'first_day',
};
// --- Base target via consistency logic ---
let baseAmount: number;
let path: 'first_day' | 'normal_day' | 'reduced_day';
if (daysTraded === 0) {
baseAmount = profitTarget * consistency;
path = 'first_day';
} else {
const maxDay = Math.max(...positiveDays.map((d) => d.pnl));
const realTarget = maxDay / consistency;
const needed = realTarget - totalProfit;
if (needed > maxDay) {
baseAmount = maxDay;
path = 'normal_day';
} else {
baseAmount = Math.max(0, needed);
path = 'reduced_day';
}
}
const maxDay = Math.max(...positiveDays.map((d) => d.pnl));
const realTarget = maxDay / consistency;
const needed = realTarget - totalProfit;
// --- Min-day reservation (only when minDayPnL is a positive value) ---
const effectiveMinDay = minDayPnL > 0 ? minDayPnL : 0;
if (needed > maxDay) {
return { amount: Math.round(maxDay * 100) / 100, path: 'normal_day' };
if (effectiveMinDay > 0 && minTradingDays > daysTraded) {
const remaining = profitTarget - totalProfit; // intentionally NOT clamped — can be negative
if (remaining <= 0) {
// Profit target already met but mandatory trading days not yet satisfied.
// Trade exactly minDayPnL each remaining day.
return { amount: effectiveMinDay, path };
}
const remainingMandatoryDays = minTradingDays - daysTraded; // includes today
const futureReserve = (remainingMandatoryDays - 1) * effectiveMinDay;
// Cap: don't take more than what's available after reserving future days
const cappedByFuture = remaining - futureReserve;
// Floor: must make at least minDayPnL today (or whatever is left if less)
const floor = Math.min(effectiveMinDay, remaining);
const amount = Math.max(floor, Math.min(baseAmount, cappedByFuture));
return { amount: Math.round(amount * 100) / 100, path };
}
return { amount: Math.round(Math.max(0, needed) * 100) / 100, path: 'reduced_day' };
return { amount: Math.round(baseAmount * 100) / 100, path };
}