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:
co-authored by
Claude Opus 5.5
parent
675b4c89f8
commit
a2d9df5ba4
+13
-40
@@ -44,8 +44,9 @@ import { OrderNotPlacedError, isOrderNotPlaced, errorMessage } from './platform-
|
||||
import {
|
||||
dxAccountId, accountDisplayName, accountCodeOf, encodeContractName, decodeContractName, roundToStep,
|
||||
lastUtcReset, ctTradeDayStart, timeOf, parseDurationMs, ciNum, isFundType, signedFundValue, newOrderCode,
|
||||
inTradingHours, realizedVolPerHour, scoreCandidate, candidateRejection,
|
||||
inTradingHours, realizedVolPerHour, scoreCandidate, candidateRejection, parseTransfers, marginRateOf,
|
||||
} from './dxtrade-helpers';
|
||||
import type { DxTxn } from './dxtrade-helpers';
|
||||
import { hyperliquidCoins, hyperliquidCoinFor, hyperliquidQuote, hyperliquidHourlyCloses } from './hyperliquid-feed';
|
||||
|
||||
// ── timings ──────────────────────────────────────────────────────────────────
|
||||
@@ -80,7 +81,9 @@ const PICK_TTL_MS = 45_000;
|
||||
const INSTRUMENT_TTL_MS = 60 * 60_000;
|
||||
const ACCOUNT_INSTRUMENT_TTL_MS = 10 * 60_000;
|
||||
const CANDLE_TTL_MS = 15 * 60_000;
|
||||
const CANDLE_HOURS = 48;
|
||||
/** Volatility lookback: a week, so one quiet weekend (or one busy day) can't
|
||||
* swing a multi-day hold estimate — the week's rhythm is priced in. */
|
||||
const CANDLE_HOURS = 7 * 24;
|
||||
/** History is fetched this far back at most (or from registration). */
|
||||
const HISTORY_MAX_DAYS = 180;
|
||||
/** Balance the books can't explain by this much holds the account. */
|
||||
@@ -129,15 +132,6 @@ interface DxOrder {
|
||||
rejectReason: string | undefined;
|
||||
}
|
||||
|
||||
interface DxTxn {
|
||||
key: string;
|
||||
account: string;
|
||||
type: string;
|
||||
value: number;
|
||||
time: number;
|
||||
orderCode: string | undefined;
|
||||
}
|
||||
|
||||
/** DXtrade answered with an error body. */
|
||||
class DxApiError extends Error {
|
||||
constructor(message: string, readonly status: number, readonly code: number | undefined) {
|
||||
@@ -191,32 +185,6 @@ function parseOrder(raw: unknown): DxOrder | null {
|
||||
};
|
||||
}
|
||||
|
||||
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 : [];
|
||||
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 : '';
|
||||
out.push({
|
||||
key: code || `${transfer.transferCode ?? ''}|${tx.type}|${time}|${value}|${tx.orderCode ?? ''}|${tx.tradeCode ?? ''}`,
|
||||
account,
|
||||
type: tx.type,
|
||||
value,
|
||||
time,
|
||||
orderCode: typeof tx.orderCode === 'string' ? tx.orderCode : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function chunks<T>(items: T[], size: number): T[][] {
|
||||
const out: T[][] = [];
|
||||
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
||||
@@ -249,7 +217,7 @@ export class DXTradeClient implements PlatformClient {
|
||||
private userPath: string | null = null;
|
||||
|
||||
public accountList: PlatformAccount[] = [];
|
||||
public accountCashBalances: { [accountId: number]: { amount: number; realizedPnL: number; equity?: number } } = {};
|
||||
public accountCashBalances: { [accountId: number]: { amount: number; realizedPnL: number; equity?: number; availableFunds?: number } } = {};
|
||||
public positions: {
|
||||
[accountId: number]: { contractId: string; netPos: number; netPrice: number; timestamp: Date };
|
||||
} = {};
|
||||
@@ -689,7 +657,9 @@ export class DXTradeClient implements PlatformClient {
|
||||
for (const t of map.values()) {
|
||||
if (!isFundType(t.type) && currentTradeDateCT(new Date(t.time)) === today) realized += t.value;
|
||||
}
|
||||
this.accountCashBalances[acc.id] = { amount: balance, realizedPnL: round2(realized), equity: ciNum(m, 'equity') };
|
||||
this.accountCashBalances[acc.id] = {
|
||||
amount: balance, realizedPnL: round2(realized), equity: ciNum(m, 'equity'), availableFunds: ciNum(m, 'availableFunds'),
|
||||
};
|
||||
// No floor to report — except for an account the firm has
|
||||
// closed for good (a breach): the balance itself, so the
|
||||
// dashboards and the reporter count it dead, not just inactive.
|
||||
@@ -1256,6 +1226,7 @@ export class DXTradeClient implements PlatformClient {
|
||||
: Math.floor((maxOrder.value * (ciNum(inst, 'lotSize') || 1)) / step);
|
||||
}
|
||||
const minContracts = minOrder > 0 ? Math.ceil(minOrder / step - 1e-9) : 1;
|
||||
const marginRate = marginRateOf(det);
|
||||
const rejection = !(tick > 0)
|
||||
? 'no price increment'
|
||||
: candidateRejection({
|
||||
@@ -1281,7 +1252,8 @@ export class DXTradeClient implements PlatformClient {
|
||||
board.push({
|
||||
symbol: cand.symbol,
|
||||
score,
|
||||
note: `spread ${(spread * 100).toFixed(3)}%${vol?.vol ? `, σ ${(vol.vol * Math.sqrt(24) * 100).toFixed(2)}%/day` : ''}`,
|
||||
note: `spread ${(spread * 100).toFixed(3)}%${vol?.vol ? `, σ ${(vol.vol * Math.sqrt(24) * 100).toFixed(2)}%/day` : ''}`
|
||||
+ (marginRate ? `, ${+(1 / marginRate).toFixed(1)}:1` : ''),
|
||||
contract: {
|
||||
id: cand.symbol,
|
||||
name: encodeContractName(cand.symbol, step),
|
||||
@@ -1293,6 +1265,7 @@ export class DXTradeClient implements PlatformClient {
|
||||
spreadFraction: spread,
|
||||
maxContracts,
|
||||
minContracts,
|
||||
marginRate,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user