DXtrade: count closed-trade P&L, week-long volatility, broker leverage caps, swap in the take-profit

- Transfers: a close books its SETTLEMENT and COMMISSION under one
  transaction code, and keying by the code alone let the commission
  overwrite the P&L. Keys now carry the type, so the day-start balance,
  daily-loss room, realized P&L, daily history and books check all see
  closed-trade profit and loss.
- Volatility from a week of hourly closes (was 48 h), still measured once
  per trade date, so one quiet weekend can't triple a hold estimate.
- Each symbol's margin rate from the account's instrument details caps
  the position at available funds / rate (Tradeify: 5:1 crypto, 2:1
  stocks); the account type's max notional stays as an optional ceiling.
- Firms with overnight financing add the swap a winning leg is expected
  to pay (the winners' mean hold, (A²+2AB)/3σ²) to its take-profit, and
  the sizing and hold estimate count it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-26 18:11:42 -05:00
co-authored by Claude Opus 5.5
parent 675b4c89f8
commit a2d9df5ba4
10 changed files with 309 additions and 81 deletions
+59
View File
@@ -155,6 +155,65 @@ export function signedFundValue(type: string, value: number): number {
return type === 'WITHDRAWAL' ? -Math.abs(value) : Math.abs(value);
}
export interface DxTxn {
key: string;
account: string;
type: string;
value: number;
time: number;
orderCode: string | undefined;
}
/**
* The cash transactions in a /transfers answer, flattened. A close books its
* SETTLEMENT (the trade's P&L) and its COMMISSION under one transaction code,
* so the key carries the type — and a running count, should a transfer ever
* hold two of one type — or one would overwrite the other.
*/
export function parseTransfers(body: unknown): DxTxn[] {
const transfers = (body as { cashTransfers?: unknown[] })?.cashTransfers ?? [];
const out: DxTxn[] = [];
for (const t of transfers) {
const transfer = t as Record<string, unknown>;
const txns = Array.isArray(transfer.cashTransactions) ? transfer.cashTransactions : [];
const seen = new Map<string, number>();
for (const x of txns) {
const tx = x as Record<string, unknown>;
const account = accountCodeOf(tx.account) ?? accountCodeOf(transfer.account);
const value = ciNum(tx, 'value');
const time = timeOf(tx.transactionTime ?? transfer.transactionTime);
if (!account || value === undefined || !Number.isFinite(time) || typeof tx.type !== 'string') continue;
const code = typeof tx.transactionCode === 'string' ? tx.transactionCode : '';
const base = code
? `${code}|${tx.type}`
: `${transfer.transferCode ?? ''}|${tx.type}|${time}|${value}|${tx.orderCode ?? ''}|${tx.tradeCode ?? ''}`;
const n = seen.get(base) ?? 0;
seen.set(base, n + 1);
out.push({
key: n === 0 ? base : `${base}|${n}`,
account,
type: tx.type,
value,
time,
orderCode: typeof tx.orderCode === 'string' ? tx.orderCode : undefined,
});
}
}
return out;
}
/**
* The margin an account posts per dollar of notional on an instrument — its
* leverage cap, inverted (0.2 = 5:1). A tiered rate reads as its steepest
* tier. undefined when the details don't say.
*/
export function marginRateOf(details: unknown): number | undefined {
const rate = (details as { marginRate?: { tiers?: { value?: unknown }[]; value?: unknown } } | undefined)?.marginRate;
const values = [...(rate?.tiers ?? []).map((t) => Number(t?.value)), Number(rate?.value)]
.filter((v) => Number.isFinite(v) && v > 0 && v <= 1);
return values.length > 0 ? Math.max(...values) : undefined;
}
/** A client order code: unique, ≤ 64 chars, and recognisably ours. */
export function newOrderCode(tag: string): string {
const rand = Math.random().toString(36).slice(2, 8);