Use equity-based profit for profitTarget comparison

Instead of relying on dailyPnL sum (which can miss reports or be out
of sync with account balance), use (amount - accountSize) as the actual
profit when comparing against profitTarget. Consistency calc still uses
dailyPnL totalProfit for realTarget.

Also unconditionally floor the min-day reservation at minDayPnL — if
equity + (days × minDay) >= target, we coast on min-day.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-04-20 20:08:16 -05:00
co-authored by Claude Opus 4.6
parent 1dbaebb7d8
commit cd0f1e6fac
3 changed files with 26 additions and 19 deletions
+2 -1
View File
@@ -49,7 +49,8 @@ export async function GET() {
priorProfit, priorProfit,
allFundTxns 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; dailyTarget = target;
// Condition 1: profit target already exceeded (target=0), still need days → any activity counts // 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 // Condition 2: target > 0 → must have made at least the computed daily target
+14 -8
View File
@@ -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) // 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 // 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 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 ── // ── Phase 2: fire the batch simultaneously ──
const tradeResults = await Promise.allSettled(batch.map(async (item) => { 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 cfg = getAccountConfig(acc.name, firmConfig)!;
const totalProfit = dailyPnL.reduce((sum, d) => sum + d.pnl, 0); const totalProfit = dailyPnL.reduce((sum, d) => sum + d.pnl, 0);
const priorProfit = client.priorProfit?.[acc.id] ?? 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 rawContracts = Math.max(1, Math.ceil(target.amount / 1000));
const contracts = cfg.maxPositionSize > 0 ? Math.min(rawContracts, cfg.maxPositionSize) : rawContracts; 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 }; acc: { id: number; name: string; active: boolean };
contract: { name: string; tickSize: number }; contract: { name: string; tickSize: number };
firmConfig: FirmConfig; firmConfig: FirmConfig;
cash: { amount: number; realizedPnL: number };
dailyPnL: { date: string; pnl: number }[]; dailyPnL: { date: string; pnl: number }[];
daysTraded: number; daysTraded: number;
}; };
@@ -440,10 +443,11 @@ export async function copyTrade() {
cfg.profitTarget, cfg.consistency, cfg.minTradingDays, cfg.targetSameEquity, cfg.withdrawalStages, priorProfit, allFundTxns 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; 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) // Fire orders (same as Phase 2 of runTrade)
const tradeResults = await Promise.allSettled(batch.map(async (item) => { 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 cfg = getAccountConfig(acc.name, firmConfig)!;
const totalProfit = dailyPnL.reduce((sum, d) => sum + d.pnl, 0); const totalProfit = dailyPnL.reduce((sum, d) => sum + d.pnl, 0);
const priorProfit = client.priorProfit?.[acc.id] ?? 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 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 rawContracts = Math.max(1, Math.ceil(target.amount / 1000));
const contracts = cfg.maxPositionSize > 0 ? Math.min(rawContracts, cfg.maxPositionSize) : rawContracts; 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 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 const isMnqExtraDay = cfg.minDayPnL <= 0
&& effective.minTradingDays > daysTraded && effective.minTradingDays > daysTraded
&& totalProfit >= effective.profitTarget; && totalProfit >= effective.profitTarget;
+10 -10
View File
@@ -84,10 +84,12 @@ export function computeDailyTarget(
totalProfit: number, totalProfit: number,
dailyPnL: { date: string; pnl: number }[], dailyPnL: { date: string; pnl: number }[],
minDayPnL: number = 0, // -999 or 0 = no minimum per day 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' } { ): { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } {
const qualifyingDays = minDayPnL === 0 ? dailyPnL : dailyPnL.filter((d) => d.pnl >= minDayPnL); const qualifyingDays = minDayPnL === 0 ? dailyPnL : dailyPnL.filter((d) => d.pnl >= minDayPnL);
const daysTraded = qualifyingDays.length; const daysTraded = qualifyingDays.length;
const effectiveProfit = equityProfit ?? totalProfit; // used for profitTarget comparison
// --- Base target via consistency logic --- // --- Base target via consistency logic ---
let baseAmount: number; let baseAmount: number;
@@ -102,14 +104,14 @@ export function computeDailyTarget(
// The min-day reservation block below handles any mandatory-day targeting. // The min-day reservation block below handles any mandatory-day targeting.
baseAmount = 0; baseAmount = 0;
path = 'reduced_day'; 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 // Profit target already met — stop solving for consistency, let min-day reservation handle remaining days
baseAmount = 0; baseAmount = 0;
path = 'reduced_day'; path = 'reduced_day';
} else { } else {
const maxDay = Math.max(...qualifyingDays.map((d) => d.pnl)); const maxDay = Math.max(...qualifyingDays.map((d) => d.pnl));
const realTarget = maxDay / consistency; const realTarget = maxDay / consistency;
const needed = realTarget - totalProfit; const needed = realTarget - totalProfit; // consistency calc stays on dailyPnL totalProfit
if (needed > maxDay) { if (needed > maxDay) {
baseAmount = maxDay; baseAmount = maxDay;
@@ -124,7 +126,7 @@ export function computeDailyTarget(
const effectiveMinDay = minDayPnL > 0 ? minDayPnL : 0; const effectiveMinDay = minDayPnL > 0 ? minDayPnL : 0;
if (effectiveMinDay > 0 && minTradingDays > daysTraded) { 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) { if (remaining <= 0) {
// Profit target already met but mandatory trading days not yet satisfied. // 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 // Cap: don't take more than what's available after reserving future days
const cappedByFuture = remaining - futureReserve; 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. // Target what's needed to stay on track for profitTarget (cappedByFuture), floored at minDayPnL unconditionally.
// Consistency-based baseAmount is intentionally ignored here — profitTarget takes priority. // If minDay × remainingDays >= remaining, we can coast on min-day (cappedByFuture < minDay → floor wins).
const amount = Math.max(floor, cappedByFuture); const amount = Math.max(effectiveMinDay, cappedByFuture);
return { amount: Math.round(amount * 100) / 100, path }; return { amount: Math.round(amount * 100) / 100, path };
} }
// When no consistency constraint and no min-day reservation applied, target the full remaining profit // When no consistency constraint and no min-day reservation applied, target the full remaining profit
if (baseAmount <= 0 && (consistency === 0 || consistency >= 1)) { 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 }; return { amount: Math.round(Math.max(baseAmount, effectiveMinDay) * 100) / 100, path };