Rewrite computeDailyTarget with stage-first structure
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) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fa2aad38cb
commit
b7430df16e
+32
-51
@@ -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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user