Vest: a claim names the completed cycle it ends, a day's second and one after the reset too
Two of the cycles a claim ends never got a completion entry. A Vest day's second cycle is one: the auto-claim at the next tick beats the 30 s report, and the payout books into that day's fund row, so the latest fund date never moves for the catch-up. A cycle claimed right after the 20:00 ET reset is the other: the reset puts it back on stage 1, and all of its P&L sits in the previous claim's day row, so endedCycleCompletion reads none of it. Before a claim goes out, claimedCycle (trading-logic) checks whether the cycle stands complete: cycleCompletion 'today' or 'before' on the client's numbers. It keys the cycle the way the final day would (completionPlace on the payout list), so the master dedupes the two. afterClaim keeps the result in account_meta. The reporter names each kept cycle for the catch-up's week. It lists them last in the slice, newest first, under the master's per-slice cap, and adds no counter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4a8e027056
commit
e02838409b
+72
-2
@@ -742,6 +742,13 @@ const ROLL_SETTLE_MS = 5 * 60 * 1000;
|
||||
* still checked for a missed completion. */
|
||||
const ENDED_CYCLE_WINDOW_DAYS = 7;
|
||||
|
||||
/** Is `date` (a fund date, or a claimed cycle's — claimedCycle) within the
|
||||
* week its ended cycle is still named? False for an unparseable date. */
|
||||
export function withinEndedCycleWindow(date: string, rawTradeDate: string): boolean {
|
||||
const ageDays = (Date.parse(`${rawTradeDate}T00:00:00Z`) - Date.parse(`${date}T00:00:00Z`)) / (24 * 60 * 60 * 1000);
|
||||
return ageDays <= ENDED_CYCLE_WINDOW_DAYS; // NaN: false
|
||||
}
|
||||
|
||||
/** Nothing left to trade AND the days booked: with no min-day amount the
|
||||
* plan coasts at 0 while days are still owed, so count them too. */
|
||||
function cycleOwesNothing(
|
||||
@@ -924,8 +931,7 @@ export function endedCycleCompletion(opts: {
|
||||
const everyDay = cfg.tradesEveryDay === true;
|
||||
const combine = effectiveCombine(cfg);
|
||||
if (lastFundDate === null || start === null) return null;
|
||||
const ageDays = (Date.parse(`${rawTradeDate}T00:00:00Z`) - Date.parse(`${lastFundDate}T00:00:00Z`)) / (24 * 60 * 60 * 1000);
|
||||
if (!(ageDays <= ENDED_CYCLE_WINDOW_DAYS)) return null; // NaN included: an unparseable date
|
||||
if (!withinEndedCycleWindow(lastFundDate, rawTradeDate)) return null;
|
||||
if (sessionDoubtful(rawTradeDate, opts.cycleDailyPnL, opts.now ?? new Date(), everyDay, opts.client)) return null;
|
||||
|
||||
const latest = (a: string, b: string) => (b > a ? b : a);
|
||||
@@ -975,6 +981,70 @@ export function endedCycleCompletion(opts: {
|
||||
};
|
||||
}
|
||||
|
||||
/** A completed cycle a payout claim ended (claimedCycle). */
|
||||
export interface ClaimedCycle { date: string; stage: number; drain: boolean; cycle: string; cycleSeq: number }
|
||||
|
||||
/**
|
||||
* The cycle a payout claim about to go out ends (PlatformClient.claimProfit),
|
||||
* when it stands complete — cycleCompletion's 'today' or 'before' on the
|
||||
* client's numbers just before the claim — named and numbered as the final
|
||||
* day names it (completionPlace), so the master dedupes the two. date = the
|
||||
* claim's trade date. Null for a claim short of the rule (a Claim now below
|
||||
* the target) or on numbers not read yet.
|
||||
*
|
||||
* Taken before the claim books its payout, and kept once it goes through
|
||||
* (auto-trade afterClaim). The claim starts a new cycle at once, and two of
|
||||
* the cycles it can end no report would name otherwise. A Vest day's second
|
||||
* cycle: the auto-claim at the next tick beats the 30 s report, and its
|
||||
* payout books into that day's fund row, so the latest fund date doesn't
|
||||
* move for the catch-up. And one claimed right after the 20:00 ET reset,
|
||||
* which puts it back on the first stage, whose target the day's leftover
|
||||
* profit meets: all of its P&L sits in the day row of the cycle before it,
|
||||
* so endedCycleCompletion reads none of it.
|
||||
*/
|
||||
export function claimedCycle(
|
||||
client: {
|
||||
accountCashBalances: { [accountId: number]: { amount: number; realizedPnL: number } | undefined };
|
||||
dailyPnL: { [accountId: number]: { date: string; pnl: number }[] | undefined };
|
||||
fundTransactions?: { [accountId: number]: { date: string; amount: number }[] | undefined };
|
||||
lastFundDates?: { [accountId: number]: string | null | undefined };
|
||||
priorProfit?: { [accountId: number]: number | undefined };
|
||||
tradeDate?: (now: number) => string;
|
||||
sameDayClaims?: (accountId: number) => number;
|
||||
payoutDays?: (accountId: number) => string[] | undefined;
|
||||
},
|
||||
accountId: number,
|
||||
cfg: {
|
||||
profitTarget: number; consistency: number; minTradingDays: number; minDayPnL: number; accountSize: number;
|
||||
targetSameEquity: boolean; withdrawalStages: WithdrawalStage[]; splitEnabled: boolean;
|
||||
combineCmeHolidays?: boolean; tradesEveryDay?: boolean; dailyLossLimit?: number; stagesSameDay?: boolean;
|
||||
},
|
||||
now: Date = new Date(),
|
||||
): ClaimedCycle | null {
|
||||
const cash = client.accountCashBalances[accountId];
|
||||
const cycleDailyPnL = client.dailyPnL[accountId];
|
||||
const fundTransactions = client.fundTransactions?.[accountId];
|
||||
if (!cash || !cycleDailyPnL || !fundTransactions) return null;
|
||||
const effective = resolveEffectiveConfig(
|
||||
cfg.profitTarget, cfg.consistency, cfg.minTradingDays, cfg.targetSameEquity, cfg.withdrawalStages,
|
||||
client.priorProfit?.[accountId] ?? 0, fundTransactions, cfg.minDayPnL, stageInputsFor(client, accountId, cfg),
|
||||
);
|
||||
const rawTradeDate = tradeDateFor(client, now);
|
||||
const completion = cycleCompletion({
|
||||
accountId, rawTradeDate, client, cycleDailyPnL, rawRealizedPnL: cash.realizedPnL,
|
||||
combineCmeHolidays: cfg.combineCmeHolidays === true, tradesEveryDay: cfg.tradesEveryDay === true,
|
||||
effective, equityProfit: cash.amount - cfg.accountSize, splitEnabled: cfg.splitEnabled, now,
|
||||
});
|
||||
if (completion === 'none') return null;
|
||||
const cycle = client.lastFundDates?.[accountId] ?? '';
|
||||
const payoutDays = client.payoutDays?.(accountId) ?? fundTransactions.filter((f) => f.amount < 0).map((f) => f.date);
|
||||
const place = completionPlace({ payoutDays, fundTransactions, cycle });
|
||||
return {
|
||||
date: combineCmeSessions(effectiveCombine(cfg), [], rawTradeDate, 0).tradeDate,
|
||||
stage: place.stage, drain: effective.drain, cycle, cycleSeq: place.cycleSeq,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the next trading day's profit target for an account.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user