Vest: the payout projection runs on Vest's day, and stages count each payout
projectPerfectPath takes the raw trade date (default: the Central one): the reporter passes its tradeDateFor day and the account page the one /api/state now gives each account. From 5 PM CT to 20:00 ET it took tomorrow as today while every input was Vest's, so the day's claim moved a day late, the next day's stage-1 payout dropped, and an open Vest-day row counted twice. Without same-day stages, the stage comes from the client's payout list (payoutDays, one per payout) when it has read one, not the negative fund rows: Vest books a day's two claims as one row, so the engine, claimDue, the stage label and the projection stayed a stage behind the report. StageInputs.payouts carries the count; a client with no list counts rows as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
032ba1ca9c
commit
4a8e027056
@@ -221,6 +221,7 @@ function collectFirmStats() {
|
||||
fundTransactions: fundTxns,
|
||||
balance: cash.amount,
|
||||
realizedPnL: cash.realizedPnL,
|
||||
rawTradeDate: rawToday,
|
||||
stageInputs: stageInputsFor(client, acc.id, cfg),
|
||||
payout: {
|
||||
withdrawalPct: cfg.withdrawalPct,
|
||||
|
||||
+28
-8
@@ -37,6 +37,19 @@ export interface StageInputs {
|
||||
dailyLossLimit?: number;
|
||||
stagesSameDay?: boolean;
|
||||
sameDayWithdrawals?: number;
|
||||
/** Every payout the account has made, one per payout, where a fund row
|
||||
* can hold several (PlatformClient.payoutDays — Vest books a day's
|
||||
* claims as one row). Absent = one per negative fund row. */
|
||||
payouts?: number;
|
||||
}
|
||||
|
||||
/** The payouts that pick an account's stage: today's for a type whose
|
||||
* stages reset each day, else every one — the client's own count when it
|
||||
* gives one (StageInputs.payouts), else the negative fund rows. */
|
||||
export function stagePayouts(fundTransactions: { date: string; amount: number }[], inputs: StageInputs = {}): number {
|
||||
if (inputs.stagesSameDay) return Math.max(0, Math.floor(inputs.sameDayWithdrawals ?? 0));
|
||||
if (typeof inputs.payouts === 'number' && Number.isFinite(inputs.payouts)) return Math.max(0, Math.floor(inputs.payouts));
|
||||
return fundTransactions.filter((f) => f.amount < 0).length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,9 +86,7 @@ export function resolveEffectiveConfig(
|
||||
// Same-day stages (Vest): only today's payouts count, so every new
|
||||
// day starts back at stage 1 — the room a payout frees lasts only
|
||||
// until the firm's next daily reset.
|
||||
const withdrawalCount = inputs.stagesSameDay
|
||||
? Math.max(0, Math.floor(inputs.sameDayWithdrawals ?? 0))
|
||||
: fundTransactions.filter((f) => f.amount < 0).length;
|
||||
const withdrawalCount = stagePayouts(fundTransactions, inputs);
|
||||
if (withdrawalCount === 0) {
|
||||
return { profitTarget, consistency, minTradingDays, minDayPnL, drain: false, dailyLossLimit }; // Stage 1 = base values
|
||||
}
|
||||
@@ -480,6 +491,9 @@ export function projectPerfectPath(opts: {
|
||||
};
|
||||
/** Defaults to the current time. */
|
||||
now?: Date;
|
||||
/** The raw trade date the inputs follow (tradeDateFor — Vest's turns at
|
||||
* 20:00 ET); defaults to the Central one at `now`. */
|
||||
rawTradeDate?: string;
|
||||
}): ProjectedDay[] {
|
||||
const { accountId, cfg } = opts;
|
||||
const horizon = opts.horizonDays ?? 40;
|
||||
@@ -490,7 +504,7 @@ export function projectPerfectPath(opts: {
|
||||
const dd = Math.max(0, opts.payout?.drawdown ?? 0);
|
||||
const everyDay = cfg.tradesEveryDay === true;
|
||||
|
||||
const rawToday = currentTradeDateCT(opts.now);
|
||||
const rawToday = opts.rawTradeDate ?? currentTradeDateCT(opts.now);
|
||||
// The firm's own day view: a combining firm folds holiday-session P&L
|
||||
// into its clearing date — a Labor-Day trade and the Tuesday after it are
|
||||
// ONE trading day, exactly as the firm counts min days. Identity view
|
||||
@@ -551,11 +565,13 @@ export function projectPerfectPath(opts: {
|
||||
const sameDay = opts.stageInputs?.stagesSameDay === true;
|
||||
/** The path's own payouts, by the date they are claimed on. */
|
||||
const paidOn = new Map<string, number>();
|
||||
/** The account's real payouts, one per payout (stagePayouts). */
|
||||
const paidReal = stagePayouts(opts.fundTransactions, { payouts: opts.stageInputs?.payouts });
|
||||
/** Payouts before the stage traded on `day`: that day's own for a
|
||||
* same-day type, else every withdrawal so far. */
|
||||
const paidBefore = (day: string): number => (sameDay
|
||||
? (day === today ? Math.max(0, Math.floor(opts.stageInputs?.sameDayWithdrawals ?? 0)) : 0) + (paidOn.get(day) ?? 0)
|
||||
: funds.filter((f) => f.amount < 0).length);
|
||||
: paidReal + withdrawals);
|
||||
/** A same-day type trades its next stage on the date a payout lands
|
||||
* (runClaims claims, then trades again on the room the claim freed) —
|
||||
* once through its stages a day: the last one repeats, and the path
|
||||
@@ -566,7 +582,7 @@ export function projectPerfectPath(opts: {
|
||||
const effectiveOn = (day: string) => resolveEffectiveConfig(
|
||||
cfg.profitTarget, cfg.consistency, cfg.minTradingDays, cfg.targetSameEquity,
|
||||
cfg.withdrawalStages, 0, funds, cfg.minDayPnL,
|
||||
sameDay ? { stagesSameDay: true, sameDayWithdrawals: paidBefore(day) } : {},
|
||||
sameDay ? { stagesSameDay: true, sameDayWithdrawals: paidBefore(day) } : { payouts: paidBefore(day) },
|
||||
);
|
||||
/** The payout parameters of the stage being completed: per-stage
|
||||
* overrides when set, else the type defaults. */
|
||||
@@ -1429,17 +1445,21 @@ export function dllReserve(dailyLossLimit: number, notional = 0, slippageFractio
|
||||
export const DLL_MIN_STOP = 50;
|
||||
|
||||
/** The stage inputs for one account: the type's daily loss limit, and for a
|
||||
* type whose stages reset each day, the client's count of today's payouts. */
|
||||
* type whose stages reset each day, the client's count of today's payouts —
|
||||
* for any other, of every payout, where the client lists them one by one
|
||||
* (payoutDays) and has read them. */
|
||||
export function stageInputsFor(
|
||||
client: { sameDayClaims?: (accountId: number) => number },
|
||||
client: { sameDayClaims?: (accountId: number) => number; payoutDays?: (accountId: number) => string[] | undefined },
|
||||
accountId: number,
|
||||
cfg: { dailyLossLimit?: number; stagesSameDay?: boolean },
|
||||
): StageInputs {
|
||||
const sameDay = cfg.stagesSameDay === true;
|
||||
const payouts = !sameDay && typeof client.payoutDays === 'function' ? client.payoutDays(accountId)?.length : undefined;
|
||||
return {
|
||||
dailyLossLimit: cfg.dailyLossLimit ?? 0,
|
||||
stagesSameDay: sameDay,
|
||||
sameDayWithdrawals: sameDay && typeof client.sameDayClaims === 'function' ? client.sameDayClaims(accountId) : 0,
|
||||
...(payouts !== undefined ? { payouts } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user