Add daily target logic and consistency reference line to equity curve

- lib/trading-logic.ts: computeDailyTarget() computes the next trading
  day's profit target via two paths:
  • No positive days yet → profitTarget × consistency (first day)
  • Positive days exist → maxDay / consistency gives the total profit
    needed to satisfy the consistency rule; target maxDay when far away,
    or the exact remaining amount when close
- Account detail page: display "Next Trading Day Amount" in Objectives card
- Equity curve: add indigo dashed reference line for the consistency target
  (maxDay / consistency), labelled top-left to avoid overlapping the amber
  profit-target line (top-right)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Senofy
2026-03-08 15:26:40 -05:00
co-authored by Claude Sonnet 4.6
parent 645041c600
commit 532c2e2279
2 changed files with 73 additions and 1 deletions
+35 -1
View File
@@ -15,6 +15,7 @@ import {
Dot,
} from 'recharts';
import type { FirmConfig, FirmState, AccountState, AccountConfig } from '@/types';
import { computeDailyTarget } from '@/lib/trading-logic';
interface DailyPnL {
date: string;
@@ -236,8 +237,18 @@ export default function AccountPage() {
: Math.round(fifoTotal * 100) / 100;
const profitPct = cfg?.accountSize ? (totalProfit / cfg.accountSize) * 100 : null;
const profitPassed = cfg != null && totalProfit >= cfg.profitTarget;
const dailyTarget = cfg && !isDead
? computeDailyTarget(cfg.profitTarget, cfg.consistency, totalProfit, dailyPnL)
: null;
const lossPassed = !hasLossLimit || (cfg != null && totalProfit >= cfg.minDayPnL);
// Consistency target: the total profit level at which the best day no longer
// violates the consistency ratio. Only meaningful once a positive day exists.
const maxDayPnL = dailyPnL.length > 0 ? Math.max(...dailyPnL.filter(d => d.pnl > 0).map(d => d.pnl)) : 0;
const consistencyTarget = cfg && maxDayPnL > 0
? Math.round(maxDayPnL / cfg.consistency * 100) / 100
: null;
// Build equity curve: FIFO daily increments, origin at $0
let running = 0;
const equityData = [
@@ -260,7 +271,7 @@ export default function AccountPage() {
const equityValues = equityData.map((d) => d.equity);
// Y-axis domain: include profit target so its reference line stays visible
const rawMin = Math.min(0, ...equityValues);
const rawMax = Math.max(0, ...equityValues, cfg?.profitTarget ?? 0);
const rawMax = Math.max(0, ...equityValues, cfg?.profitTarget ?? 0, consistencyTarget ?? 0);
// Stroke gradient split: use only the actual equity range, NOT the profit target.
// The SVG gradient bounding box is the line's bbox, so inflating by profitTarget
@@ -342,6 +353,14 @@ export default function AccountPage() {
value={`$${fmt(cfg!.minDayPnL)}`}
/>
)}
{dailyTarget != null && (
<div className="flex items-center justify-between py-3 border-b border-slate-100 last:border-0">
<span className="text-slate-500 text-sm">Next Trading Day Amount</span>
<span className="text-slate-800 font-bold tabular-nums text-sm">
${fmt(dailyTarget.amount)}
</span>
</div>
)}
</div>
</div>
@@ -421,6 +440,21 @@ export default function AccountPage() {
}}
/>
)}
{consistencyTarget != null && (
<ReferenceLine
y={consistencyTarget}
stroke="#6366f1"
strokeWidth={1.5}
strokeDasharray="4 3"
label={{
value: `$${fmt(consistencyTarget)} consistency`,
position: 'insideTopLeft',
fontSize: 11,
fontWeight: 600,
fill: '#4f46e5',
}}
/>
)}
{/* Green fill: positive equity only, fills down to y=0 */}
<Area
type="monotone"