Add per-account-type splitEnabled flag from the catalog

When the master marks an account type splitEnabled=false, big days trade
as one unsplit leg (the legacy one-and-done behavior); min-day bumps are
unaffected. Missing field — older master or pre-toggle cached catalog —
defaults to enabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-04 12:15:08 -05:00
co-authored by Claude Fable 5
parent 3ef9486977
commit a36e227e74
8 changed files with 18 additions and 7 deletions
+1
View File
@@ -36,6 +36,7 @@ export async function GET() {
maxLoss: a.maxLoss,
maxPositionSize: a.maxPositionSize,
targetSameEquity: a.targetSameEquity,
splitEnabled: a.splitEnabled,
withdrawalStages: a.withdrawalStages,
})),
catalogUpdatedAt: firm.updatedAt,
+2 -1
View File
@@ -69,7 +69,8 @@ export async function GET() {
const equityProfit = cash.amount - cfg.accountSize;
const plan = computeDayPlan(
acc.id, tradeDate, effective.profitTarget, effective.consistency,
dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit, cash.realizedPnL
dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit, cash.realizedPnL,
cfg.splitEnabled
);
dailyTarget = plan ? { amount: plan.dayTarget, path: plan.path } : null;
remainingToday = plan?.remainingToday ?? null;
+2 -1
View File
@@ -79,7 +79,8 @@ function evaluateAccountForTrade(client: any, acc: { id: number; name: string; a
const equityProfit = cash.amount - cfg.accountSize;
const plan = computeDayPlan(
acc.id, tradeDate, effective.profitTarget, effective.consistency,
dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit, cash.realizedPnL
dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit, cash.realizedPnL,
cfg.splitEnabled
);
if (!plan) return { ...base, eligible: false, reason: 'invalid balance' };
+3 -1
View File
@@ -154,7 +154,9 @@ function validatePayload(raw: unknown): FirmCatalogPayload {
if (typeof type[key] !== 'number') throw new Error(`account type ${j} is missing ${key}`);
}
if (!isStages(type.withdrawalStages)) throw new Error(`account type ${j} has bad withdrawalStages`);
return type as unknown as CatalogAccountType;
// Lenient like platform: a master from before the split toggle
// doesn't send the field, and those account types all split.
return { ...type, splitEnabled: type.splitEnabled !== false } as unknown as CatalogAccountType;
});
return {
+2
View File
@@ -70,6 +70,8 @@ export interface CatalogAccountType {
maxLoss: number;
maxPositionSize: number;
targetSameEquity: boolean;
/** False = big days trade as one unsplit leg instead of random ≥$800 pieces. */
splitEnabled: boolean;
withdrawalStages: { profit: number; consistency: number; minTradingDays: number }[];
/** ISO. Ties between two matching account types are broken by this. */
updatedAt: string;
+2 -1
View File
@@ -85,7 +85,8 @@ function collectFirmStats() {
const equityProfit = cash.amount - cfg.accountSize;
const plan = computeDayPlan(
acc.id, currentTradeDateCT(), cfg.profitTarget, cfg.consistency,
dailyPnL, cfg.minDayPnL, cfg.minTradingDays, equityProfit, cash.realizedPnL
dailyPnL, cfg.minDayPnL, cfg.minTradingDays, equityProfit, cash.realizedPnL,
cfg.splitEnabled
);
const targetHit = !plan ? false :
((plan.dayTarget === 0 && Math.abs(cash.realizedPnL) > 0 && (client.daysTraded[acc.id] ?? 0) <= cfg.minTradingDays) ||
+5 -3
View File
@@ -242,7 +242,8 @@ export interface DayPlan {
/** dayTarget minus what's already realized today; what's left to trade. */
remainingToday: number;
path: 'first_day' | 'normal_day' | 'reduced_day';
/** False for strict-consistency accounts past their first trade ever — single unsplit trade. */
/** False when the account type disables splitting, or for strict-consistency
* accounts past their first trade ever — single unsplit trade. */
splitAllowed: boolean;
}
@@ -264,7 +265,8 @@ export function computeDayPlan(
minDayPnL: number,
minTradingDays: number,
equityProfit: number | null | undefined,
realizedPnL: number
realizedPnL: number,
splitEnabled: boolean = true
): DayPlan | null {
if (typeof equityProfit !== 'number' || !Number.isFinite(equityProfit)) return null;
@@ -277,7 +279,7 @@ export function computeDayPlan(
if (!target) return null;
const qualifyingDays = minDayPnL === 0 ? priorDays : priorDays.filter((d) => d.pnl >= minDayPnL);
const splitAllowed = consistency === 0 || consistency >= 1 || qualifyingDays.length === 0;
const splitAllowed = splitEnabled && (consistency === 0 || consistency >= 1 || qualifyingDays.length === 0);
return {
dayTarget: target.amount,
+1
View File
@@ -11,6 +11,7 @@ export interface AccountConfig {
maxLoss: number; // 0 = no limit; positive = account is "Dead" when loss exceeds this
maxPositionSize: number; // 0 = no limit; positive = max contracts per trade
targetSameEquity: boolean; // if true, reduce profitTarget by priorProfit after withdrawal
splitEnabled: boolean; // false = big days trade as one unsplit leg instead of random ≥$800 pieces
withdrawalStages: { profit: number; consistency: number; minTradingDays: number }[]; // per-stage fresh profit + consistency (empty = use base values)
}