From b7430df16eef1a12ceb2230d154a881c27befd87 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Tue, 21 Apr 2026 19:47:26 -0500 Subject: [PATCH] Rewrite computeDailyTarget with stage-first structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the tangled first_day/consistency/min-day branches with a cleaner flow: 1. If profit target met, coast on min-day (or nothing) 2. Compute cappedByFuture (reserve future min-days) 3. Compute consistencyCap: - Day 1 of cycle: remaining × consistency - Day 2+: current maxDay - 0/100% consistency: no cap 4. Combine and floor at minDayPnL when mandatory days remain Fixes a bug where Stage 2+ Day 1 used the full profitTarget × consistency instead of remaining × consistency, allowing day 1 to exceed 50% of cycle-local profit. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/trading-logic.ts | 83 +++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 51 deletions(-) diff --git a/lib/trading-logic.ts b/lib/trading-logic.ts index 0f4494d..d8205c9 100644 --- a/lib/trading-logic.ts +++ b/lib/trading-logic.ts @@ -82,70 +82,51 @@ export function computeDailyTarget( consistency: number, totalProfit: number, dailyPnL: { date: string; pnl: number }[], - minDayPnL: number = 0, // -999 or 0 = no minimum per day + minDayPnL: number = 0, // 0 = no minimum per day 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 + const effectiveMinDay = Math.max(0, minDayPnL); + const currentProfit = equityProfit ?? totalProfit; + const remaining = profitTarget - currentProfit; - // --- Base target via consistency logic --- - let baseAmount: number; + // 1. Profit target already met — coast on min-day if mandatory days remain, else nothing to do. + if (remaining <= 0) { + const needsMoreDays = minTradingDays > daysTraded; + return { amount: needsMoreDays ? effectiveMinDay : 0, path: 'reduced_day' }; + } + + // 2. Min-day reservation: future mandatory days each reserve minDayPnL. + // cappedByFuture = remaining profit available after reserving. + const daysLeft = Math.max(1, minTradingDays - daysTraded); // includes today + const futureReserve = (daysLeft - 1) * effectiveMinDay; + const cappedByFuture = remaining - futureReserve; + + // 3. Consistency cap: + // - 0% or 100%: no constraint + // - Day 1 of cycle: max allowed = remaining × consistency + // - Day 2+ of cycle: max allowed = maxDay (keeps consistency ratio stable) + let consistencyCap: number; let path: 'first_day' | 'normal_day' | 'reduced_day'; - if (daysTraded === 0) { - // 0% or 100% consistency = no constraint; let min-day reservation drive the target - baseAmount = (consistency === 0 || consistency >= 1) ? 0 : profitTarget * consistency; + if (consistency === 0 || consistency >= 1) { + consistencyCap = Infinity; + path = daysTraded === 0 ? 'first_day' : 'reduced_day'; + } else if (daysTraded === 0) { + consistencyCap = remaining * consistency; path = 'first_day'; - } else if (consistency === 0 || consistency >= 1) { - // No consistency rule to satisfy — base amount is $0. - // The min-day reservation block below handles any mandatory-day targeting. - baseAmount = 0; - path = 'reduced_day'; - } 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)); - // Consistency-cap the daily target at maxDay (can't exceed maxDay without breaking consistency ratio). - // Let min-day block handle the lower bound and cappedByFuture logic. - baseAmount = maxDay; + consistencyCap = maxDay; path = 'normal_day'; } - // --- Min-day reservation (only when minDayPnL is a positive value) --- - const effectiveMinDay = minDayPnL > 0 ? minDayPnL : 0; + // 4. Combine caps and apply min-day floor when mandatory days remain. + const raw = Math.min(consistencyCap, cappedByFuture); + const mustFloor = minTradingDays > daysTraded; + const amount = mustFloor ? Math.max(effectiveMinDay, raw) : Math.max(0, raw); - if (effectiveMinDay > 0 && minTradingDays > daysTraded) { - 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. - // 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; - - // Target what's needed to stay on track for profitTarget (cappedByFuture), floored at minDayPnL. - // Cap at baseAmount when the consistency calc produced a positive value — ensures we don't - // exceed maxDay / (maxDay/consistency) constraint. When baseAmount is 0 (0/100% consistency - // or target already met), cappedByFuture drives the target directly. - const consistencyCap = baseAmount > 0 ? Math.min(baseAmount, cappedByFuture) : cappedByFuture; - const amount = Math.max(effectiveMinDay, consistencyCap); - 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 - effectiveProfit); - } - - return { amount: Math.round(Math.max(baseAmount, effectiveMinDay) * 100) / 100, path }; + return { amount: Math.round(amount * 100) / 100, path }; }