Show stage-aware + consistency-adjusted profit target on dashboards
State API now returns effectiveProfitTarget = max(stage profit target, maxDay/consistency). When a big day forces the consistency rule, this reflects the actual amount needed to complete the stage — not just the base profit target. Main dashboard and account detail page now display this instead of the raw cfg.profitTarget. 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
9928f1cabd
commit
8204cd138b
@@ -247,7 +247,7 @@ export default function AccountPage() {
|
|||||||
? Math.round((account.amount - cfg.accountSize) * 100) / 100
|
? Math.round((account.amount - cfg.accountSize) * 100) / 100
|
||||||
: Math.round(fifoTotal * 100) / 100;
|
: Math.round(fifoTotal * 100) / 100;
|
||||||
const profitPct = cfg?.accountSize ? (totalProfit / cfg.accountSize) * 100 : null;
|
const profitPct = cfg?.accountSize ? (totalProfit / cfg.accountSize) * 100 : null;
|
||||||
const profitPassed = cfg != null && totalProfit >= cfg.profitTarget;
|
const profitPassed = cfg != null && totalProfit >= (account.effectiveProfitTarget ?? cfg.profitTarget);
|
||||||
// dailyTarget is computed server-side in the state API — single source of truth.
|
// dailyTarget is computed server-side in the state API — single source of truth.
|
||||||
const dailyTarget = account.dailyTarget ?? null;
|
const dailyTarget = account.dailyTarget ?? null;
|
||||||
const lossPassed = !hasLossLimit || (cfg != null && totalProfit >= cfg.minDayPnL);
|
const lossPassed = !hasLossLimit || (cfg != null && totalProfit >= cfg.minDayPnL);
|
||||||
@@ -379,7 +379,7 @@ export default function AccountPage() {
|
|||||||
<ObjRow
|
<ObjRow
|
||||||
passed={profitPassed}
|
passed={profitPassed}
|
||||||
label="Profit Target"
|
label="Profit Target"
|
||||||
value={cfg ? `$${fmt(totalProfit)} of $${fmt(cfg.profitTarget)}` : '—'}
|
value={cfg ? `$${fmt(totalProfit)} of $${fmt(account.effectiveProfitTarget ?? cfg.profitTarget)}` : '—'}
|
||||||
/>
|
/>
|
||||||
<ObjRow
|
<ObjRow
|
||||||
passed={daysPassed}
|
passed={daysPassed}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export async function GET() {
|
|||||||
: allFundTxns;
|
: allFundTxns;
|
||||||
let targetHit = false;
|
let targetHit = false;
|
||||||
let dailyTarget: { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null = null;
|
let dailyTarget: { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null = null;
|
||||||
|
let effectiveProfitTarget: number | null = null;
|
||||||
if (cfg && !isDead) {
|
if (cfg && !isDead) {
|
||||||
const withdrawalStages: { profit: number; consistency: number; minTradingDays: number }[] = (() => { try { return JSON.parse(cfg.withdrawal_stages ?? '[]'); } catch { return []; } })();
|
const withdrawalStages: { profit: number; consistency: number; minTradingDays: number }[] = (() => { try { return JSON.parse(cfg.withdrawal_stages ?? '[]'); } catch { return []; } })();
|
||||||
const effective = resolveEffectiveConfig(
|
const effective = resolveEffectiveConfig(
|
||||||
@@ -52,6 +53,15 @@ export async function GET() {
|
|||||||
const equityProfit = cash.amount - cfg.account_size;
|
const equityProfit = cash.amount - cfg.account_size;
|
||||||
const target = computeDailyTarget(effective.profitTarget, effective.consistency, 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;
|
||||||
|
|
||||||
|
// Effective profit target = max(stage target, consistency realTarget).
|
||||||
|
// If a big day forces the consistency rule, the account must reach maxDay/consistency
|
||||||
|
// in total trading profit for the stage, not just profitTarget.
|
||||||
|
const qualifying = cfg.min_day_pnl === 0 ? dailyPnL : dailyPnL.filter((d) => d.pnl >= cfg.min_day_pnl);
|
||||||
|
const maxDay = qualifying.length > 0 ? Math.max(...qualifying.map((d) => d.pnl)) : 0;
|
||||||
|
const consistencyReal = (effective.consistency > 0 && effective.consistency < 1 && maxDay > 0)
|
||||||
|
? maxDay / effective.consistency : 0;
|
||||||
|
effectiveProfitTarget = Math.max(effective.profitTarget, consistencyReal);
|
||||||
// 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
|
||||||
if (target) {
|
if (target) {
|
||||||
@@ -75,6 +85,7 @@ export async function GET() {
|
|||||||
totalProfit,
|
totalProfit,
|
||||||
targetHit,
|
targetHit,
|
||||||
dailyTarget,
|
dailyTarget,
|
||||||
|
effectiveProfitTarget,
|
||||||
dailyPnL,
|
dailyPnL,
|
||||||
fullDailyPnL: client.fullDailyPnL?.[acc.id] ?? loadDailyPnL(acc.id),
|
fullDailyPnL: client.fullDailyPnL?.[acc.id] ?? loadDailyPnL(acc.id),
|
||||||
fundTransactions: displayFundTxns,
|
fundTransactions: displayFundTxns,
|
||||||
|
|||||||
+1
-1
@@ -135,7 +135,7 @@ function AccountRow({ account, firm, hideDead, privacy }: { account: AccountStat
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-slate-600 tabular-nums">
|
<td className="px-4 py-3 text-slate-600 tabular-nums">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span>${cfg?.profitTarget?.toLocaleString() ?? '—'}</span>
|
<span>${(account.effectiveProfitTarget ?? cfg?.profitTarget ?? 0).toLocaleString(undefined, { maximumFractionDigits: 2 })}</span>
|
||||||
{account.dailyTarget && (
|
{account.dailyTarget && (
|
||||||
<span className="text-xs text-slate-400">${fmt(account.dailyTarget.amount)} today</span>
|
<span className="text-xs text-slate-400">${fmt(account.dailyTarget.amount)} today</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ export interface AccountState {
|
|||||||
targetHit: boolean;
|
targetHit: boolean;
|
||||||
/** Next trading day's target, computed server-side. null when account is dead or config is missing. */
|
/** Next trading day's target, computed server-side. null when account is dead or config is missing. */
|
||||||
dailyTarget: { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null;
|
dailyTarget: { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null;
|
||||||
|
/** Actual profit target to hit — max(stage profit target, consistency realTarget = maxDay/consistency). */
|
||||||
|
effectiveProfitTarget: number | null;
|
||||||
/** Current cycle daily P&L — used for target calculations. */
|
/** Current cycle daily P&L — used for target calculations. */
|
||||||
dailyPnL: { date: string; pnl: number }[];
|
dailyPnL: { date: string; pnl: number }[];
|
||||||
/** Full P&L history across all cycles — used for calendar and equity curve display. */
|
/** Full P&L history across all cycles — used for calendar and equity curve display. */
|
||||||
|
|||||||
Reference in New Issue
Block a user