Require equityProfit; return null when balance invalid
computeDailyTarget now requires equityProfit (amount - accountSize) and returns null when it's undefined/null/NaN. 0 is still a valid value. - Removed totalProfit parameter (was only used as fallback) - Callers handle null by skipping the account (eligibility) or throwing (execution paths) - State API sets dailyTarget to null when no valid balance, avoids incorrect targetHit computation 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
b7430df16e
commit
670ee9fbeb
@@ -50,14 +50,15 @@ export async function GET() {
|
|||||||
allFundTxns
|
allFundTxns
|
||||||
);
|
);
|
||||||
const equityProfit = cash.amount - cfg.account_size;
|
const equityProfit = cash.amount - cfg.account_size;
|
||||||
const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.min_day_pnl, effective.minTradingDays, equityProfit);
|
const target = computeDailyTarget(effective.profitTarget, effective.consistency, 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
|
||||||
targetHit =
|
if (target) {
|
||||||
// If we are just flipping take any activity as target hit
|
targetHit =
|
||||||
(target.amount === 0 && Math.abs(cash.realizedPnL) > 0 && client.daysTraded[acc.id] <= effective.minTradingDays) ||
|
(target.amount === 0 && Math.abs(cash.realizedPnL) > 0 && client.daysTraded[acc.id] <= effective.minTradingDays) ||
|
||||||
(cash.realizedPnL >= target.amount);
|
(cash.realizedPnL >= target.amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
+10
-6
@@ -200,7 +200,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 equityProfit = cash.amount - cfg.accountSize;
|
const equityProfit = cash.amount - cfg.accountSize;
|
||||||
const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
const target = computeDailyTarget(effective.profitTarget, effective.consistency, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
||||||
|
if (!target) continue; // skip accounts without a valid balance
|
||||||
|
|
||||||
// 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
|
||||||
@@ -266,7 +267,8 @@ export async function runTrade(action: 'Buy' | 'Sell' | 'Auto', symbol: string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const equityProfit = cash.amount - cfg.accountSize;
|
const equityProfit = cash.amount - cfg.accountSize;
|
||||||
const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
const target = computeDailyTarget(effective.profitTarget, effective.consistency, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
||||||
|
if (!target) throw new Error(`${acc.name}: invalid balance, cannot compute target`);
|
||||||
|
|
||||||
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;
|
||||||
@@ -444,8 +446,8 @@ export async function copyTrade() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const equityProfit = cash.amount - cfg.accountSize;
|
const equityProfit = cash.amount - cfg.accountSize;
|
||||||
const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
const target = computeDailyTarget(effective.profitTarget, effective.consistency, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
||||||
if (target.amount <= 0) continue;
|
if (!target || target.amount <= 0) continue;
|
||||||
|
|
||||||
eligible.push({ firmName: firm.name, client, acc, contract, firmConfig, cash, dailyPnL, daysTraded });
|
eligible.push({ firmName: firm.name, client, acc, contract, firmConfig, cash, dailyPnL, daysTraded });
|
||||||
}
|
}
|
||||||
@@ -471,7 +473,8 @@ export async function copyTrade() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const equityProfit = cash.amount - cfg.accountSize;
|
const equityProfit = cash.amount - cfg.accountSize;
|
||||||
const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
const target = computeDailyTarget(effective.profitTarget, effective.consistency, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
||||||
|
if (!target) throw new Error(`${acc.name}: invalid balance, cannot compute target`);
|
||||||
|
|
||||||
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;
|
||||||
@@ -559,7 +562,8 @@ function hasRemainingConfiguredAccounts(): boolean {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const equityProfit = cash.amount - cfg.accountSize;
|
const equityProfit = cash.amount - cfg.accountSize;
|
||||||
const target = computeDailyTarget(effective.profitTarget, effective.consistency, totalProfit, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
const target = computeDailyTarget(effective.profitTarget, effective.consistency, dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit);
|
||||||
|
if (!target) continue; // no valid balance → skip this account
|
||||||
const isMnqExtraDay = cfg.minDayPnL <= 0
|
const isMnqExtraDay = cfg.minDayPnL <= 0
|
||||||
&& effective.minTradingDays > daysTraded
|
&& effective.minTradingDays > daysTraded
|
||||||
&& totalProfit >= effective.profitTarget;
|
&& totalProfit >= effective.profitTarget;
|
||||||
|
|||||||
+5
-5
@@ -50,11 +50,11 @@ function collectFirmStats() {
|
|||||||
const cfg = getAccountConfig(acc.name, f.accounts);
|
const cfg = getAccountConfig(acc.name, f.accounts);
|
||||||
if (cfg) {
|
if (cfg) {
|
||||||
const dailyPnL = client.dailyPnL[acc.id] ?? [];
|
const dailyPnL = client.dailyPnL[acc.id] ?? [];
|
||||||
const totalProfit = dailyPnL.reduce((sum: number, d: { pnl: number }) => sum + d.pnl, 0);
|
const equityProfit = cash.amount - cfg.account_size;
|
||||||
const target = computeDailyTarget(cfg.profit_target, cfg.consistency, totalProfit, dailyPnL, cfg.min_day_pnl, cfg.min_trading_days);
|
const target = computeDailyTarget(cfg.profit_target, cfg.consistency, dailyPnL, cfg.min_day_pnl, cfg.min_trading_days, equityProfit);
|
||||||
const targetHit =
|
const targetHit = !target ? false :
|
||||||
(target.amount === 0 && Math.abs(cash.realizedPnL) > 0 && (client.daysTraded[acc.id] ?? 0) <= cfg.min_trading_days) ||
|
((target.amount === 0 && Math.abs(cash.realizedPnL) > 0 && (client.daysTraded[acc.id] ?? 0) <= cfg.min_trading_days) ||
|
||||||
(cash.realizedPnL >= target.amount);
|
(cash.realizedPnL >= target.amount));
|
||||||
|
|
||||||
if (targetHit || cash.realizedPnL !== 0) {
|
if (targetHit || cash.realizedPnL !== 0) {
|
||||||
accountsTraded++;
|
accountsTraded++;
|
||||||
|
|||||||
+10
-6
@@ -80,16 +80,20 @@ export const POINT_VALUES: { [symbol: string]: number } = {
|
|||||||
export function computeDailyTarget(
|
export function computeDailyTarget(
|
||||||
profitTarget: number,
|
profitTarget: number,
|
||||||
consistency: number,
|
consistency: number,
|
||||||
totalProfit: number,
|
|
||||||
dailyPnL: { date: string; pnl: number }[],
|
dailyPnL: { date: string; pnl: number }[],
|
||||||
minDayPnL: number = 0, // 0 = no minimum per day
|
minDayPnL: number, // 0 = no minimum per day
|
||||||
minTradingDays: number = 0, // 0 = no minimum trading days
|
minTradingDays: number, // 0 = no minimum trading days
|
||||||
equityProfit?: number // amount − accountSize; used for profitTarget comparison. Defaults to totalProfit.
|
equityProfit: number | null | undefined // amount − accountSize. Null/undefined → skip (return null). 0 is valid.
|
||||||
): { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } {
|
): { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null {
|
||||||
|
// Don't run if we don't have a real account balance to compute against — 0 is valid.
|
||||||
|
if (typeof equityProfit !== 'number' || !Number.isFinite(equityProfit)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
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 effectiveMinDay = Math.max(0, minDayPnL);
|
const effectiveMinDay = Math.max(0, minDayPnL);
|
||||||
const currentProfit = equityProfit ?? totalProfit;
|
const currentProfit = equityProfit;
|
||||||
const remaining = profitTarget - currentProfit;
|
const remaining = profitTarget - currentProfit;
|
||||||
|
|
||||||
// 1. Profit target already met — coast on min-day if mandatory days remain, else nothing to do.
|
// 1. Profit target already met — coast on min-day if mandatory days remain, else nothing to do.
|
||||||
|
|||||||
Reference in New Issue
Block a user