diff --git a/app/api/firms/route.ts b/app/api/firms/route.ts index e204edb..cef3cb0 100644 --- a/app/api/firms/route.ts +++ b/app/api/firms/route.ts @@ -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, diff --git a/app/api/state/route.ts b/app/api/state/route.ts index 9c9e275..0ea099d 100644 --- a/app/api/state/route.ts +++ b/app/api/state/route.ts @@ -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; diff --git a/lib/auto-trade.ts b/lib/auto-trade.ts index fd73530..6a37e9c 100644 --- a/lib/auto-trade.ts +++ b/lib/auto-trade.ts @@ -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' }; diff --git a/lib/firm-catalog.ts b/lib/firm-catalog.ts index 716a094..22296d3 100644 --- a/lib/firm-catalog.ts +++ b/lib/firm-catalog.ts @@ -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 { diff --git a/lib/master-protocol.ts b/lib/master-protocol.ts index b51d117..958383f 100644 --- a/lib/master-protocol.ts +++ b/lib/master-protocol.ts @@ -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; diff --git a/lib/reporter.ts b/lib/reporter.ts index 952ead4..3eac01a 100644 --- a/lib/reporter.ts +++ b/lib/reporter.ts @@ -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) || diff --git a/lib/trading-logic.ts b/lib/trading-logic.ts index 78c37a3..5e9e4c9 100644 --- a/lib/trading-logic.ts +++ b/lib/trading-logic.ts @@ -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, diff --git a/types.ts b/types.ts index d9c1afa..1b11794 100644 --- a/types.ts +++ b/types.ts @@ -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) }