diff --git a/app/api/state/route.ts b/app/api/state/route.ts index 14309cc..684d840 100644 --- a/app/api/state/route.ts +++ b/app/api/state/route.ts @@ -49,7 +49,8 @@ export async function GET() { priorProfit, allFundTxns ); - const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.min_day_pnl, effective.minTradingDays); + const equityProfit = cash.amount - cfg.account_size; + const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.min_day_pnl, effective.minTradingDays, equityProfit); dailyTarget = target; // Condition 1: profit target already exceeded (target=0), still need days → any activity counts // Condition 2: target > 0 → must have made at least the computed daily target diff --git a/lib/auto-trade.ts b/lib/auto-trade.ts index 2180007..a0994ac 100644 --- a/lib/auto-trade.ts +++ b/lib/auto-trade.ts @@ -199,7 +199,8 @@ export async function runTrade(action: 'Buy' | 'Sell' | 'Auto', symbol: string) ); // Use the same target formula as the dashboard — skip if $0 (challenge complete) - const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays); + const equityProfit = cash.amount - cfg.accountSize; + const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit); // 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 @@ -224,7 +225,7 @@ export async function runTrade(action: 'Buy' | 'Sell' | 'Auto', symbol: string) // ── Phase 2: fire the batch simultaneously ── const tradeResults = await Promise.allSettled(batch.map(async (item) => { - const { client, acc, contract, firmConfig, dailyPnL, daysTraded } = item; + const { client, acc, contract, firmConfig, cash, dailyPnL, daysTraded } = item; const cfg = getAccountConfig(acc.name, firmConfig)!; const totalProfit = dailyPnL.reduce((sum, d) => sum + d.pnl, 0); const priorProfit = client.priorProfit?.[acc.id] ?? 0; @@ -264,7 +265,8 @@ export async function runTrade(action: 'Buy' | 'Sell' | 'Auto', symbol: string) }; } - const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays); + const equityProfit = cash.amount - cfg.accountSize; + const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit); const rawContracts = Math.max(1, Math.ceil(target.amount / 1000)); const contracts = cfg.maxPositionSize > 0 ? Math.min(rawContracts, cfg.maxPositionSize) : rawContracts; @@ -406,6 +408,7 @@ export async function copyTrade() { acc: { id: number; name: string; active: boolean }; contract: { name: string; tickSize: number }; firmConfig: FirmConfig; + cash: { amount: number; realizedPnL: number }; dailyPnL: { date: string; pnl: number }[]; daysTraded: number; }; @@ -440,10 +443,11 @@ export async function copyTrade() { cfg.profitTarget, cfg.consistency, cfg.minTradingDays, cfg.targetSameEquity, cfg.withdrawalStages, priorProfit, allFundTxns ); - const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays); + const equityProfit = cash.amount - cfg.accountSize; + const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit); if (target.amount <= 0) continue; - eligible.push({ firmName: firm.name, client, acc, contract, firmConfig, dailyPnL, daysTraded }); + eligible.push({ firmName: firm.name, client, acc, contract, firmConfig, cash, dailyPnL, daysTraded }); } })); @@ -457,7 +461,7 @@ export async function copyTrade() { // Fire orders (same as Phase 2 of runTrade) const tradeResults = await Promise.allSettled(batch.map(async (item) => { - const { client, acc, contract, firmConfig, dailyPnL } = item; + const { client, acc, contract, firmConfig, cash, dailyPnL } = item; const cfg = getAccountConfig(acc.name, firmConfig)!; const totalProfit = dailyPnL.reduce((sum, d) => sum + d.pnl, 0); const priorProfit = client.priorProfit?.[acc.id] ?? 0; @@ -466,7 +470,8 @@ export async function copyTrade() { cfg.profitTarget, cfg.consistency, cfg.minTradingDays, cfg.targetSameEquity, cfg.withdrawalStages, priorProfit, allFundTxns ); - const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays); + const equityProfit = cash.amount - cfg.accountSize; + const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit); const rawContracts = Math.max(1, Math.ceil(target.amount / 1000)); const contracts = cfg.maxPositionSize > 0 ? Math.min(rawContracts, cfg.maxPositionSize) : rawContracts; @@ -553,7 +558,8 @@ function hasRemainingConfiguredAccounts(): boolean { cfg.profitTarget, cfg.consistency, cfg.minTradingDays, cfg.targetSameEquity, cfg.withdrawalStages, priorProfit, allFundTxns ); - const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays); + const equityProfit = cash.amount - cfg.accountSize; + const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit); const isMnqExtraDay = cfg.minDayPnL <= 0 && effective.minTradingDays > daysTraded && totalProfit >= effective.profitTarget; diff --git a/lib/trading-logic.ts b/lib/trading-logic.ts index 244967c..c2fec62 100644 --- a/lib/trading-logic.ts +++ b/lib/trading-logic.ts @@ -84,10 +84,12 @@ export function computeDailyTarget( totalProfit: number, dailyPnL: { date: string; pnl: number }[], minDayPnL: number = 0, // -999 or 0 = no minimum per day - minTradingDays: number = 0 // 0 = no minimum trading days + minTradingDays: number = 0, // 0 = no minimum trading days + equityProfit?: number // amount − accountSize; used for profitTarget comparison. Defaults to totalProfit. ): { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } { const qualifyingDays = minDayPnL === 0 ? dailyPnL : dailyPnL.filter((d) => d.pnl >= minDayPnL); const daysTraded = qualifyingDays.length; + const effectiveProfit = equityProfit ?? totalProfit; // used for profitTarget comparison // --- Base target via consistency logic --- let baseAmount: number; @@ -102,14 +104,14 @@ export function computeDailyTarget( // The min-day reservation block below handles any mandatory-day targeting. baseAmount = 0; path = 'reduced_day'; - } else if (totalProfit >= profitTarget) { + } else if (effectiveProfit >= profitTarget) { // Profit target already met — stop solving for consistency, let min-day reservation handle remaining days baseAmount = 0; path = 'reduced_day'; } else { const maxDay = Math.max(...qualifyingDays.map((d) => d.pnl)); const realTarget = maxDay / consistency; - const needed = realTarget - totalProfit; + const needed = realTarget - totalProfit; // consistency calc stays on dailyPnL totalProfit if (needed > maxDay) { baseAmount = maxDay; @@ -124,7 +126,7 @@ export function computeDailyTarget( const effectiveMinDay = minDayPnL > 0 ? minDayPnL : 0; if (effectiveMinDay > 0 && minTradingDays > daysTraded) { - const remaining = profitTarget - totalProfit; // intentionally NOT clamped — can be negative + const remaining = profitTarget - effectiveProfit; // use equity-based profit to know how close we are to target if (remaining <= 0) { // Profit target already met but mandatory trading days not yet satisfied. @@ -137,18 +139,16 @@ export function computeDailyTarget( // 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); - // Target what's needed to stay on track for profitTarget (cappedByFuture), floored at minDayPnL. - // Consistency-based baseAmount is intentionally ignored here — profitTarget takes priority. - const amount = Math.max(floor, cappedByFuture); + // Target what's needed to stay on track for profitTarget (cappedByFuture), floored at minDayPnL unconditionally. + // If minDay × remainingDays >= remaining, we can coast on min-day (cappedByFuture < minDay → floor wins). + const amount = Math.max(effectiveMinDay, cappedByFuture); return { amount: Math.round(amount * 100) / 100, path }; } // When no consistency constraint and no min-day reservation applied, target the full remaining profit if (baseAmount <= 0 && (consistency === 0 || consistency >= 1)) { - baseAmount = Math.max(0, profitTarget - totalProfit); + baseAmount = Math.max(0, profitTarget - effectiveProfit); } return { amount: Math.round(Math.max(baseAmount, effectiveMinDay) * 100) / 100, path };