Add per-stage withdrawal targets with consistency and min trading days
- Add withdrawal stage system: each stage defines profit target, consistency, and min trading days for post-withdrawal challenge cycles - Target Same Equity mode accounts for withdrawn amounts when computing effective profit target (profitTarget - remainingProfit) - Store fund transaction timestamps for time-aware cycle filtering (withdrawals before 9 AM CT include that day in new cycle) - Expose full P&L history (fullDailyPnL) for calendar/equity curve display across all cycles, with DB fallback for pre-restart data - Show stage number (#1, #2, etc.) on calendar cells - Hide consistency reference line when consistency is 0% or 100% - Settings UI: "After First W/D" column with same-equity checkbox, expandable stage sub-rows with profit/consistency/days inputs - Default target_same_equity to 1 for new and existing account configs 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
c54073e4b8
commit
70b1362d3e
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
|
||||
const SIZE_PRESETS = [5_000, 10_000, 25_000, 50_000, 75_000, 100_000, 150_000];
|
||||
@@ -14,6 +14,8 @@ interface AccountConfig {
|
||||
minTradingDays: number;
|
||||
accountSize: number;
|
||||
maxPositionSize: number;
|
||||
targetSameEquity: boolean;
|
||||
withdrawalStages: { profit: number; consistency: number; minTradingDays: number }[];
|
||||
}
|
||||
|
||||
interface FirmConfig {
|
||||
@@ -51,6 +53,8 @@ function newRow(): RowState {
|
||||
minTradingDays: 5,
|
||||
accountSize: 50_000,
|
||||
maxPositionSize: 0,
|
||||
targetSameEquity: true,
|
||||
withdrawalStages: [],
|
||||
dirty: true,
|
||||
saving: false,
|
||||
error: '',
|
||||
@@ -148,6 +152,8 @@ export default function FirmSettingsPage() {
|
||||
minTradingDays: row.minTradingDays,
|
||||
accountSize: row.accountSize,
|
||||
maxPositionSize: row.maxPositionSize,
|
||||
targetSameEquity: row.targetSameEquity,
|
||||
withdrawalStages: row.withdrawalStages,
|
||||
};
|
||||
|
||||
if (row.id < 0) {
|
||||
@@ -257,20 +263,22 @@ export default function FirmSettingsPage() {
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-slate-400">Min Day P&L</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-slate-400">Min Trading Days</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-slate-400">Max Contracts</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-slate-400">After First W/D</th>
|
||||
<th className="px-3 py-3 w-32" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.length === 0 && firmName && (
|
||||
<tr>
|
||||
<td colSpan={8} className="px-4 py-6 text-center text-sm text-slate-400 italic">
|
||||
<td colSpan={9} className="px-4 py-6 text-center text-sm text-slate-400 italic">
|
||||
No account types yet
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{rows.map((row) => (
|
||||
<tr key={row.id} className="border-b border-slate-100 last:border-0">
|
||||
<React.Fragment key={row.id}>
|
||||
<tr className="border-b border-slate-100 last:border-0">
|
||||
|
||||
{/* Prefix */}
|
||||
<td className="px-4 py-2.5">
|
||||
@@ -405,6 +413,21 @@ export default function FirmSettingsPage() {
|
||||
/>
|
||||
</td>
|
||||
|
||||
{/* After Withdrawal */}
|
||||
<td className="px-4 py-2.5">
|
||||
<label className="flex items-center gap-1.5 cursor-pointer select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={row.targetSameEquity}
|
||||
onChange={(e) =>
|
||||
updateRow(row.id, { targetSameEquity: e.target.checked })
|
||||
}
|
||||
className="rounded border-slate-300 text-blue-500 focus:ring-blue-400"
|
||||
/>
|
||||
<span className="text-xs text-slate-600">Same equity</span>
|
||||
</label>
|
||||
</td>
|
||||
|
||||
{/* Actions */}
|
||||
<td className="px-3 py-2.5">
|
||||
<div className="flex items-center justify-end gap-1.5">
|
||||
@@ -449,11 +472,114 @@ export default function FirmSettingsPage() {
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Stage sub-rows — shown when targetSameEquity is OFF */}
|
||||
{!row.targetSameEquity && (
|
||||
<>
|
||||
{row.withdrawalStages.map((stage, idx) => {
|
||||
const isLast = idx === row.withdrawalStages.length - 1;
|
||||
const showPlus = isLast && row.withdrawalStages.length > 1;
|
||||
return (
|
||||
<tr key={`${row.id}-stage-${idx}`} className="bg-slate-50/60 border-b border-slate-100 last:border-0">
|
||||
<td colSpan={9} className="pl-10 pr-4 py-1.5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-slate-400 w-16 shrink-0">
|
||||
Stage {idx + 2}{showPlus ? '+' : ''}
|
||||
</span>
|
||||
<div className="relative flex items-center">
|
||||
<span className="absolute left-2 text-slate-400 text-xs pointer-events-none">$</span>
|
||||
<input
|
||||
type="number"
|
||||
className="border border-slate-200 rounded px-1.5 py-1 pl-4 text-xs text-slate-800 focus:outline-none focus:ring-1 focus:ring-blue-400 bg-white w-24"
|
||||
value={stage.profit}
|
||||
min={0}
|
||||
step={100}
|
||||
placeholder="Profit"
|
||||
onChange={(e) => {
|
||||
const next = row.withdrawalStages.map((s, i) =>
|
||||
i === idx ? { ...s, profit: Number(e.target.value) } : s
|
||||
);
|
||||
updateRow(row.id, { withdrawalStages: next });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{row.consistency > 0 && (
|
||||
<div className="relative flex items-center">
|
||||
<input
|
||||
type="number"
|
||||
className="border border-slate-200 rounded px-1.5 py-1 pr-5 text-xs text-slate-800 focus:outline-none focus:ring-1 focus:ring-blue-400 bg-white w-20"
|
||||
value={Math.round(stage.consistency * 100)}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
placeholder="Cons%"
|
||||
onChange={(e) => {
|
||||
const next = row.withdrawalStages.map((s, i) =>
|
||||
i === idx ? { ...s, consistency: Number(e.target.value) / 100 } : s
|
||||
);
|
||||
updateRow(row.id, { withdrawalStages: next });
|
||||
}}
|
||||
/>
|
||||
<span className="absolute right-1.5 text-slate-400 text-xs pointer-events-none">%</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
type="number"
|
||||
className="border border-slate-200 rounded px-1.5 py-1 text-xs text-slate-800 focus:outline-none focus:ring-1 focus:ring-blue-400 bg-white w-12"
|
||||
value={stage.minTradingDays}
|
||||
min={0}
|
||||
step={1}
|
||||
placeholder="Days"
|
||||
onChange={(e) => {
|
||||
const next = row.withdrawalStages.map((s, i) =>
|
||||
i === idx ? { ...s, minTradingDays: Number(e.target.value) } : s
|
||||
);
|
||||
updateRow(row.id, { withdrawalStages: next });
|
||||
}}
|
||||
/>
|
||||
<span className="text-slate-400 text-[10px] shrink-0">days</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
const next = row.withdrawalStages.filter((_, i) => i !== idx);
|
||||
updateRow(row.id, { withdrawalStages: next });
|
||||
}}
|
||||
className="text-slate-300 hover:text-red-400 text-sm leading-none"
|
||||
title="Remove stage"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr className="bg-slate-50/60 border-b border-dashed border-slate-200">
|
||||
<td colSpan={9} className="pl-10 pr-4 py-1">
|
||||
<button
|
||||
onClick={() =>
|
||||
updateRow(row.id, {
|
||||
withdrawalStages: [
|
||||
...row.withdrawalStages,
|
||||
{ profit: row.profitTarget, consistency: row.consistency, minTradingDays: row.minTradingDays },
|
||||
],
|
||||
})
|
||||
}
|
||||
className="text-xs text-blue-400 hover:text-blue-600"
|
||||
>
|
||||
+ Add stage
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
{/* Add row */}
|
||||
<tr className="border-t border-dashed border-slate-200">
|
||||
<td colSpan={8} className="px-2 py-2">
|
||||
<td colSpan={9} className="px-2 py-2">
|
||||
<button
|
||||
onClick={() => setRows((prev) => [...prev, newRow()])}
|
||||
className="w-full py-1.5 text-sm text-slate-400 hover:text-slate-600 hover:bg-slate-50 rounded-lg transition-colors"
|
||||
|
||||
Reference in New Issue
Block a user