Vest: a claim counts on the fresh day only once Vest dates it there, and a day's second cycle gets its own key
A claim's stage time is now taken before the request goes out (sentAt): Vest dates it no earlier, so a claim answered just after 20:00 ET no longer puts the fresh Vest day on stage 2 before the history read shows its WITHDRAW. One Vest dates after the turn reads as the day before until then, the cautious way round. Claims saved without it count from their answer. Completions count one per payout on both paths (completionPlace): the catch-up's stage no longer counts a Vest day's two claims as one, and a cycle a day's second claim started carries cycleSeq, so it can be told from that day's first. The master ignores it until it keys on it; old masters and old instances are unchanged. bookHistory's claim bookkeeping (WITHDRAWs, lapse, fund rows, the balance the books check expects) is now the pure readClaims, tested on the case that held an account for good: a claim that never went in, read an hour on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9fe7fc0ea3
commit
35512cd504
@@ -36,11 +36,16 @@ export interface AccountTypeStats {
|
||||
* cycle ready to withdraw, 'drain' = a drain stage's days booked;
|
||||
* cycle = the date of the fund transaction that started the cycle ('' for
|
||||
* the account's first cycle) — firm + name + cycle identify one
|
||||
* completed cycle. The list also carries caught-up entries: for up to 7
|
||||
* completed cycle, but where one day started several (Vest: claim,
|
||||
* trade again, claim again — a fund row holds the day's payouts):
|
||||
* cycleSeq = which of that day's cycles, 1 for the second and so on,
|
||||
* sent only when above 0 (trading-logic completionPlace). A master that
|
||||
* keys on it tells them apart; one that doesn't folds them into the
|
||||
* day's first. The list also carries caught-up entries: for up to 7
|
||||
* days after a fund transaction starts a new cycle, it includes the cycle
|
||||
* that just ended if that cycle had met the rule. Caught-up entries are
|
||||
* not counted in `completed`. Optional: older instances omit it. */
|
||||
completedAccounts?: { name: string; date: string; stage: number; kind: 'pass' | 'payout' | 'drain'; cycle: string }[];
|
||||
completedAccounts?: { name: string; date: string; stage: number; kind: 'pass' | 'payout' | 'drain'; cycle: string; cycleSeq?: number }[];
|
||||
/** Live accounts by trading days completed toward the current target:
|
||||
* {daysTraded: accountCount}. Feeds the master's payout calendar, so
|
||||
* accounts that are dead, DLL-locked, or in the red are left out — the
|
||||
|
||||
@@ -148,11 +148,12 @@ export interface PlatformClient {
|
||||
/** Payouts made since the firm's last daily reset — what picks the stage
|
||||
* of an account type whose stages reset each day. */
|
||||
sameDayClaims?(accountId: number): number;
|
||||
/** Payouts taken from the account, one per withdrawal, where a fund row
|
||||
* can hold several (Vest books a whole day's claims as one); undefined
|
||||
* until the platform's own record of them has been read. Without it,
|
||||
* each negative fund row counts as one. */
|
||||
withdrawalCount?(accountId: number): number | undefined;
|
||||
/** Each payout taken from the account, by the day it counts on — one
|
||||
* entry per payout, where a fund row can hold several (Vest books a
|
||||
* whole day's claims as one); undefined until the platform's own record
|
||||
* of them has been read. Without it, each negative fund row is one, on
|
||||
* its date. */
|
||||
payoutDays?(accountId: number): string[] | undefined;
|
||||
/** Request a payout of `amount` from the account to the login's own main
|
||||
* account, where the platform takes one by API (Vest's Claim Profit).
|
||||
* Resolves once the platform accepted it and the books were updated;
|
||||
|
||||
+28
-14
@@ -20,7 +20,7 @@ import { getClients } from './clients';
|
||||
import { getAccountTypeById, getKeptRuleIdsForFirm, getTradableFirms } from './firm-catalog';
|
||||
import { getAccountConfig } from './account-type-resolver';
|
||||
import { getSchedulerStatus } from './auto-trade';
|
||||
import { botStopsActive, combineCmeSessions, computeDayPlan, currentTradeDateCT, cycleCompletion, endedCycleCompletion, LEG_COMPLETION_EPSILON, projectPerfectPath, resolveEffectiveConfig, stageInputsFor } from './trading-logic';
|
||||
import { botStopsActive, combineCmeSessions, computeDayPlan, currentTradeDateCT, completionPlace, cycleCompletion, endedCycleCompletion, LEG_COMPLETION_EPSILON, projectPerfectPath, resolveEffectiveConfig, stageInputsFor } from './trading-logic';
|
||||
import type { AccountTypeStats, FirmStats, InstrumentStats, SymbolStats } from './master-protocol';
|
||||
import { accountNextLeg, accountLegShape } from './leg-estimate';
|
||||
import { sizingHoldHours } from './person-size';
|
||||
@@ -104,14 +104,17 @@ function collectFirmStats() {
|
||||
// standing, while a wrong zero would clear one early.
|
||||
const loadedFundTxns = client.fundTransactions?.[acc.id];
|
||||
const fundTxns = loadedFundTxns ?? [];
|
||||
// One per payout: a client whose fund rows hold a whole day's
|
||||
// (Vest: two claims the same day, one row) counts them itself,
|
||||
// and says nothing until it has read them — falling back to the
|
||||
// rows there would report fewer and then more again.
|
||||
const withdrawals = client.withdrawalCount
|
||||
? client.withdrawalCount(acc.id)
|
||||
: loadedFundTxns?.filter((f) => f.amount < 0).length;
|
||||
const withdrawalsTaken = withdrawals ?? fundTxns.filter((f) => f.amount < 0).length;
|
||||
// One per payout, on its day: a client whose fund rows hold a
|
||||
// whole day's (Vest: two claims the same day, one row) lists them
|
||||
// itself, and says nothing until it has read them — falling back
|
||||
// to the rows there would report fewer and then more again.
|
||||
const rowPayoutDays = (rows: { date: string; amount: number }[]) => rows.filter((f) => f.amount < 0).map((f) => f.date);
|
||||
const payoutDays = client.payoutDays
|
||||
? client.payoutDays(acc.id)
|
||||
: loadedFundTxns && rowPayoutDays(loadedFundTxns);
|
||||
const payoutDaysTaken = payoutDays ?? rowPayoutDays(fundTxns);
|
||||
const withdrawals = payoutDays?.length;
|
||||
const withdrawalsTaken = payoutDaysTaken.length;
|
||||
accounts.push({
|
||||
name: acc.name,
|
||||
size: cfg?.accountSize ?? 0,
|
||||
@@ -289,12 +292,19 @@ function collectFirmStats() {
|
||||
cfg,
|
||||
});
|
||||
if (ended) {
|
||||
// Counted one per payout, as the final day counts this
|
||||
// cycle, so the two name it alike.
|
||||
const place = completionPlace({
|
||||
payoutDays: payoutDaysTaken, fundTransactions: fundTxns, cycle: ended.cycle,
|
||||
end: client.lastFundDates?.[acc.id] ?? undefined,
|
||||
});
|
||||
(slice.completedAccounts ??= []).push({
|
||||
name: acc.name,
|
||||
date: ended.date,
|
||||
stage: ended.stage,
|
||||
stage: place.stage,
|
||||
kind: completionKind(cfg, ended.drain),
|
||||
cycle: ended.cycle,
|
||||
...(place.cycleSeq > 0 ? { cycleSeq: place.cycleSeq } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -334,14 +344,18 @@ function collectFirmStats() {
|
||||
dayEntry.targetHit++;
|
||||
dayEntry.completed = (dayEntry.completed ?? 0) + 1;
|
||||
}
|
||||
// The fund transaction that started this cycle, and its
|
||||
// place among that day's payouts — the catch-up names the
|
||||
// same cycle by the same key.
|
||||
const cycle = client.lastFundDates?.[acc.id] ?? '';
|
||||
const place = completionPlace({ payoutDays: payoutDaysTaken, fundTransactions: fundTxns, cycle });
|
||||
(slice.completedAccounts ??= []).push({
|
||||
name: acc.name,
|
||||
date: combined.tradeDate,
|
||||
stage: 1 + withdrawalsTaken,
|
||||
stage: place.stage,
|
||||
kind: completionKind(cfg, effective.drain),
|
||||
// The fund transaction that started this cycle — the
|
||||
// catch-up names the same cycle by the same key.
|
||||
cycle: client.lastFundDates?.[acc.id] ?? '',
|
||||
cycle,
|
||||
...(place.cycleSeq > 0 ? { cycleSeq: place.cycleSeq } : {}),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -772,6 +772,32 @@ export function cycleCompletion(opts: {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* A completed cycle's place among the account's payouts, counted one per
|
||||
* payout: `payoutDays` holds each payout's day once per payout
|
||||
* (PlatformClient.payoutDays — a Vest day's two claims share a fund row —
|
||||
* else the negative fund rows' dates). stage = 1 + the payouts before the
|
||||
* cycle's own: those before `end`, the fund date that ended it (the
|
||||
* catch-up), else every one (today's final day). cycleSeq = the payouts on
|
||||
* the cycle's start day (`cycle`, '' = the account's first) that came
|
||||
* before it, beyond the one whose fund row names that day — 0 but for a
|
||||
* cycle that a second payout the same day started, which `cycle` alone
|
||||
* can't tell from that day's first.
|
||||
*/
|
||||
export function completionPlace(o: {
|
||||
payoutDays: string[];
|
||||
fundTransactions: { date: string; amount: number }[];
|
||||
cycle: string;
|
||||
end?: string;
|
||||
}): { stage: number; cycleSeq: number } {
|
||||
const stage = 1 + o.payoutDays.filter((d) => o.end === undefined || d < o.end).length;
|
||||
if (o.cycle === '') return { stage, cycleSeq: 0 };
|
||||
// A deposit's day (Vest's activation) names the cycle it started; each
|
||||
// payout that day starts another.
|
||||
const named = o.fundTransactions.some((f) => f.date === o.cycle && f.amount < 0) ? 1 : 0;
|
||||
return { stage, cycleSeq: Math.max(0, o.payoutDays.filter((d) => d === o.cycle).length - named) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch-up for a missed final day: did the cycle the latest fund transaction
|
||||
* just ended stand complete — profit target, consistency and minimum days all
|
||||
|
||||
+40
-29
@@ -53,8 +53,8 @@ import { vestSessionFor, dropVestSession, listVestSessions, refuseVestSession }
|
||||
import {
|
||||
vestAccountId, vestAccountName, vestDayOf, lastVestResetMs, nextVestResetMs, parseCapitalAccount, parsePosition, parseOpenOrder,
|
||||
parseTxn, bookTxns, awaitingLegs, unrealizedPnl, hourlyVolatility, marketOpen, num, toMs, formatDecimal, resetSafeStop,
|
||||
parseHistoryPosition, jwtClaimStrings, accountScopedToken, VEST_STATUS_ACTIVE, VEST_TXN, VEST_STOP_SLIPPAGE_FRACTION,
|
||||
VestApiError, VestNotSentError, nothingWentIn, tokenForRequest, settleClaims, claimFundRows, payoutCount, payoutsSince,
|
||||
parseHistoryPosition, jwtClaimStrings, accountScopedToken, VEST_STATUS_ACTIVE, VEST_STOP_SLIPPAGE_FRACTION,
|
||||
VestApiError, VestNotSentError, nothingWentIn, tokenForRequest, settleClaims, readClaims, payoutDaysOf, payoutsSince,
|
||||
} from './vest-helpers';
|
||||
import type { VestCapitalAccount, VestPosition, VestOpenOrder, VestTxn, VestTrade, OwnClaim } from './vest-helpers';
|
||||
|
||||
@@ -747,7 +747,11 @@ export class VestClient implements PlatformClient {
|
||||
const raw = JSON.parse(loadAccountMeta(accountId, CLAIMS_META) ?? '[]');
|
||||
list = Array.isArray(raw)
|
||||
? raw.filter((c) => Number.isFinite(c?.at) && Number.isFinite(c?.amount) && Number.isFinite(c?.base))
|
||||
.map((c): OwnClaim => ({ at: c.at, amount: c.amount, base: c.base, ...(c.unknown === true ? { unknown: true as const } : {}) }))
|
||||
.map((c): OwnClaim => ({
|
||||
at: c.at, amount: c.amount, base: c.base,
|
||||
...(Number.isFinite(c.sentAt) ? { sentAt: c.sentAt } : {}),
|
||||
...(c.unknown === true ? { unknown: true as const } : {}),
|
||||
}))
|
||||
: [];
|
||||
} catch {
|
||||
list = [];
|
||||
@@ -773,16 +777,19 @@ export class VestClient implements PlatformClient {
|
||||
* show yet. One of unknown outcome still not shown an hour on never went
|
||||
* in: it's dropped here (settleClaims). */
|
||||
private outstandingClaims(accountId: number, readAt: number): OwnClaim[] {
|
||||
const list = this.claimsOf(accountId);
|
||||
const { pending, lapsed } = settleClaims(list, this.withdrawals.get(accountId) ?? [], readAt);
|
||||
if (lapsed.length > 0) {
|
||||
const name = this.accountList.find((a) => a.id === accountId)?.name ?? accountId;
|
||||
this.saveClaims(accountId, list.filter((c) => !lapsed.includes(c)));
|
||||
for (const c of lapsed) console.warn(`${this.tag} ${name}: the claim of $${c.amount} with an unknown outcome never showed on Vest — taken as not made`);
|
||||
}
|
||||
const { pending, lapsed } = settleClaims(this.claimsOf(accountId), this.withdrawals.get(accountId) ?? [], readAt);
|
||||
this.forgetLapsed(accountId, lapsed);
|
||||
return pending;
|
||||
}
|
||||
|
||||
/** Drop claims of unknown outcome that never went in (settleClaims). */
|
||||
private forgetLapsed(accountId: number, lapsed: OwnClaim[]): void {
|
||||
if (lapsed.length === 0) return;
|
||||
const name = this.accountList.find((a) => a.id === accountId)?.name ?? accountId;
|
||||
this.saveClaims(accountId, this.claimsOf(accountId).filter((c) => !lapsed.includes(c)));
|
||||
for (const c of lapsed) console.warn(`${this.tag} ${name}: the claim of $${c.amount} with an unknown outcome never showed on Vest — taken as not made`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Today's realized P&L as the day plan wants it: since the latest claim
|
||||
* this client made since the last reset — the plan's new cycle starts
|
||||
@@ -808,15 +815,15 @@ export class VestClient implements PlatformClient {
|
||||
return payoutsSince(this.withdrawals.get(accountId) ?? [], this.claimsOf(accountId), lastVestResetMs(Date.now()));
|
||||
}
|
||||
|
||||
/** See PlatformClient: one per payout — Vest's WITHDRAW transactions, and
|
||||
* this client's claims they don't show yet (payoutCount) — where a fund
|
||||
* row holds a whole Vest day's. Undefined until the account's history
|
||||
* is read. */
|
||||
public withdrawalCount(accountId: number): number | undefined {
|
||||
/** See PlatformClient: one per payout, on its Vest day — Vest's WITHDRAW
|
||||
* transactions, and this client's claims they don't show yet
|
||||
* (payoutDaysOf) — where a fund row holds a whole Vest day's. Undefined
|
||||
* until the account's history is read. */
|
||||
public payoutDays(accountId: number): string[] | undefined {
|
||||
const withdrawals = this.withdrawals.get(accountId);
|
||||
const fundRows = this.fundTransactions[accountId];
|
||||
if (!withdrawals || !fundRows || !this.historyLoadedIds.has(accountId)) return undefined;
|
||||
return payoutCount(withdrawals, this.claimsOf(accountId), fundRows);
|
||||
return payoutDaysOf(withdrawals, this.claimsOf(accountId), fundRows);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -851,6 +858,10 @@ export class VestClient implements PlatformClient {
|
||||
throw new Error(`$${claimed} is more than the $${round2(cash.amount - meta.initialCapital)} above Vest's starting capital of $${meta.initialCapital}`);
|
||||
}
|
||||
|
||||
// Taken before it goes out, which Vest dates it no earlier than: a
|
||||
// claim answered after 20:00 ET isn't the fresh day's payout until
|
||||
// Vest says so (payoutsSince).
|
||||
const sentAt = Date.now();
|
||||
let data: { trader_amount?: unknown } | undefined;
|
||||
try {
|
||||
({ data } = await this.request<{ trader_amount?: unknown }>('trade', 'POST', '/v3/capital/withdraw', {
|
||||
@@ -868,7 +879,7 @@ export class VestClient implements PlatformClient {
|
||||
// every further claim on the account until Vest's transactions
|
||||
// say either way (claimPending).
|
||||
if (!nothingWentIn(err)) {
|
||||
this.saveClaims(accountId, [...this.claimsOf(accountId), { at: Date.now(), amount: claimed, base: round2(cash.amount - claimed), unknown: true }]);
|
||||
this.saveClaims(accountId, [...this.claimsOf(accountId), { at: Date.now(), sentAt, amount: claimed, base: round2(cash.amount - claimed), unknown: true }]);
|
||||
this.claimSeq.set(accountId, this.syncGeneration);
|
||||
console.error(`${this.tag} ${account.name}: claim of $${claimed} — outcome unknown (${errorMessage(err)}); no further claim on it until Vest's transactions settle it`);
|
||||
void this.fetchDaysTraded([accountId]).catch(() => { /* the hourly read retries */ });
|
||||
@@ -877,7 +888,7 @@ export class VestClient implements PlatformClient {
|
||||
}
|
||||
const received = num(data?.trader_amount) ?? null;
|
||||
const now = Date.now();
|
||||
this.saveClaims(accountId, [...this.claimsOf(accountId), { at: now, amount: claimed, base: round2(cash.amount - claimed) }]);
|
||||
this.saveClaims(accountId, [...this.claimsOf(accountId), { at: now, sentAt, amount: claimed, base: round2(cash.amount - claimed) }]);
|
||||
this.claimSeq.set(accountId, this.syncGeneration);
|
||||
|
||||
// Book it now: today's fund row (with whatever else that day holds),
|
||||
@@ -1328,23 +1339,23 @@ export class VestClient implements PlatformClient {
|
||||
.sort((x, y) => x.date.localeCompare(y.date));
|
||||
saveDailyPnL(account.id, account.name, fresh);
|
||||
|
||||
const withdrawals = txns.filter((t) => t.type === VEST_TXN.WITHDRAW).map((t) => ({ at: t.time, amount: t.amount }));
|
||||
this.withdrawals.set(account.id, withdrawals);
|
||||
const readAt = Date.now();
|
||||
const read = readClaims({
|
||||
txns, funds, claims: this.claimsOf(account.id), stored: loadFundTransactions(account.id), readAt,
|
||||
opening: hasDeposit ? 0 : a?.initialCapital ?? 0,
|
||||
});
|
||||
this.withdrawals.set(account.id, read.withdrawals);
|
||||
this.historyReadAt.set(account.id, readAt);
|
||||
// Settled here, not only in claimPending: its callers wait on
|
||||
// historyReady, which a claim that never went in would hold below
|
||||
// for good.
|
||||
const outstanding = this.outstandingClaims(account.id, readAt);
|
||||
this.forgetLapsed(account.id, read.lapsed);
|
||||
|
||||
// A claim Vest dates on the other side of 20:00 ET than it was booked
|
||||
// here leaves that day's row to this client alone: taken back, so the
|
||||
// payout counts once, on Vest's day.
|
||||
const { rows: fundRows, dropped } = claimFundRows({
|
||||
funds, claims: this.claimsOf(account.id), withdrawals, stored: loadFundTransactions(account.id),
|
||||
});
|
||||
if (dropped.length > 0) this.dropClaimDays(account, dropped);
|
||||
this.applyFundRows(account, fundRows);
|
||||
if (read.dropped.length > 0) this.dropClaimDays(account, read.dropped);
|
||||
this.applyFundRows(account, read.rows);
|
||||
|
||||
// Books check: the balance must be what the transactions add up to
|
||||
// (the opening capital included). A payout or credit booked under a
|
||||
@@ -1352,9 +1363,9 @@ export class VestClient implements PlatformClient {
|
||||
// cycle as if nothing happened.
|
||||
const cash = this.accountCashBalances[account.id];
|
||||
// A claim this client just made may not show in Vest's transactions
|
||||
// yet: count it until its WITHDRAW row does (same amount, near time).
|
||||
const unbooked = outstanding.reduce((s, c) => s + c.amount, 0);
|
||||
const expected = txns.reduce((s, t) => s + (t.kind === 'margin' ? 0 : t.amount), 0) + (hasDeposit ? 0 : a?.initialCapital ?? 0) - unbooked;
|
||||
// yet: readClaims counts it until its WITHDRAW row does (same amount,
|
||||
// near time).
|
||||
const expected = read.expected;
|
||||
if (account.active && cash && !this.positions[account.id] && Math.abs(cash.amount - expected) >= BOOKS_TOLERANCE) {
|
||||
this.booksHeld.add(account.id);
|
||||
this.lastFetchErrors[account.name] = `balance ${round2(cash.amount)} vs transactions ${round2(expected)} (types ${types.join(',')}) — held until they agree`;
|
||||
|
||||
+50
-16
@@ -553,10 +553,12 @@ export const CLAIM_MATCH_MS = 30 * 60_000;
|
||||
* WITHDRAW matches in a history read this long after it never happened. */
|
||||
export const UNKNOWN_CLAIM_EXPIRES_MS = 60 * 60_000;
|
||||
|
||||
/** A claim this client sent: the amount, when, and the balance it left.
|
||||
* unknown = the request failed in transit, so it may or may not have gone
|
||||
* in — it holds further claims, but nothing is booked on it. */
|
||||
export interface OwnClaim { at: number; amount: number; base: number; unknown?: true }
|
||||
/** A claim this client sent: the amount, when (at = once it was answered;
|
||||
* sentAt = before it went out, absent on claims saved before it was kept),
|
||||
* and the balance it left. unknown = the request failed in transit, so it
|
||||
* may or may not have gone in — it holds further claims, but nothing is
|
||||
* booked on it. */
|
||||
export interface OwnClaim { at: number; sentAt?: number; amount: number; base: number; unknown?: true }
|
||||
|
||||
/** A WITHDRAW transaction: when, and its (negative) amount. */
|
||||
export interface VestWithdrawal { at: number; amount: number }
|
||||
@@ -613,23 +615,55 @@ export function claimFundRows(o: {
|
||||
return { rows, dropped };
|
||||
}
|
||||
|
||||
/** Payouts taken from the account, one per claim: every WITHDRAW Vest's
|
||||
* transactions show (claims made by hand among them), and each claim of
|
||||
* this client's they don't show yet — a fund row can't say, it holds a
|
||||
* whole Vest day's. Never fewer than the negative fund rows, the count
|
||||
* before: a day whose fund transactions net negative with no WITHDRAW
|
||||
* still counts. */
|
||||
export function payoutCount(withdrawals: VestWithdrawal[], claims: OwnClaim[], fundRows: { amount: number }[]): number {
|
||||
const each = withdrawals.length + claims.filter((c) => !c.unknown && !claimWithdrawal(withdrawals, c)).length;
|
||||
return Math.max(each, fundRows.filter((f) => f.amount < 0).length);
|
||||
/**
|
||||
* What a history read makes of this client's claims (VestClient.bookHistory):
|
||||
* Vest's WITHDRAWs; the claims that lapse and those still outstanding
|
||||
* (settleClaims); the fund rows and the stored days to take back
|
||||
* (claimFundRows); and the balance the transactions add up to — `opening`
|
||||
* (the capital of an account Vest funded without a deposit row) included,
|
||||
* the claims they don't show yet taken out — that the books check holds the
|
||||
* account to. A lapsed claim is out of it: counted, one that never went in
|
||||
* would hold the account for good.
|
||||
*/
|
||||
export function readClaims(o: {
|
||||
txns: VestTxn[]; funds: Map<string, number>; claims: OwnClaim[]; stored: { date: string }[]; readAt: number; opening: number;
|
||||
}): {
|
||||
withdrawals: VestWithdrawal[]; lapsed: OwnClaim[]; outstanding: OwnClaim[];
|
||||
rows: { date: string; amount: number }[]; dropped: string[]; expected: number;
|
||||
} {
|
||||
const withdrawals = o.txns.filter((t) => t.type === VEST_TXN.WITHDRAW).map((t) => ({ at: t.time, amount: t.amount }));
|
||||
const { pending, lapsed } = settleClaims(o.claims, withdrawals, o.readAt);
|
||||
const { rows, dropped } = claimFundRows({ funds: o.funds, claims: o.claims.filter((c) => !lapsed.includes(c)), withdrawals, stored: o.stored });
|
||||
const unbooked = pending.reduce((s, c) => s + c.amount, 0);
|
||||
const expected = o.txns.reduce((s, t) => s + (t.kind === 'margin' ? 0 : t.amount), 0) + o.opening - unbooked;
|
||||
return { withdrawals, lapsed, outstanding: pending, rows, dropped, expected };
|
||||
}
|
||||
|
||||
/** Each payout taken from the account, by its Vest day, one entry per
|
||||
* payout: every WITHDRAW Vest's transactions show (claims made by hand
|
||||
* among them), and each claim of this client's they don't show yet, on the
|
||||
* day it was booked — a fund row can't say how many, it holds a whole Vest
|
||||
* day's. A day whose fund rows net negative with neither still counts once,
|
||||
* as it did before. Sorted. */
|
||||
export function payoutDaysOf(withdrawals: VestWithdrawal[], claims: OwnClaim[], fundRows: { date: string; amount: number }[]): string[] {
|
||||
const days = [
|
||||
...withdrawals.map((w) => vestDayOf(w.at)),
|
||||
...claims.filter((c) => !c.unknown && !claimWithdrawal(withdrawals, c)).map((c) => vestDayOf(c.at)),
|
||||
];
|
||||
for (const f of fundRows) if (f.amount < 0 && !days.includes(f.date)) days.push(f.date);
|
||||
return days.sort();
|
||||
}
|
||||
|
||||
/** Payouts since `since` (the last 20:00 ET): this client's claims — each at
|
||||
* its WITHDRAW's time once Vest shows it, Vest's day being the one it
|
||||
* counts on — or the WITHDRAWs Vest shows, whichever counts more (a claim
|
||||
* made by hand shows only there). */
|
||||
* counts on, and until then at when it was sent — or the WITHDRAWs Vest
|
||||
* shows, whichever counts more (a claim made by hand shows only there).
|
||||
* Sent, not answered: Vest dates it no earlier (clocks in sync), so a
|
||||
* claim answered just after 20:00 ET can't put the fresh day on a payout
|
||||
* stage before Vest says it's that day's; one Vest dates after the turn
|
||||
* reads as the day before until then, the cautious way round. */
|
||||
export function payoutsSince(withdrawals: VestWithdrawal[], claims: OwnClaim[], since: number): number {
|
||||
const mine = claims.filter((c) => !c.unknown && (claimWithdrawal(withdrawals, c)?.at ?? c.at) >= since).length;
|
||||
const mine = claims.filter((c) => !c.unknown && (claimWithdrawal(withdrawals, c)?.at ?? c.sentAt ?? c.at) >= since).length;
|
||||
const seen = withdrawals.filter((w) => w.at >= since).length;
|
||||
return Math.max(mine, seen);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user