Vest: a crypto leg's stop holds back the engine's default slippage, not NDX's
Crypto (exchangeInfo asset "crypto": BTC, ETH, SOL, HYPE) trades on weekends when NDX, SPX and GC are shut, but its stop slippage on Vest is unmeasured, and its book is a fraction of NDX's: about $10M an hour on a weekend against $600M, with a batch's stops firing together. Its stops now leave 0.1% of the position unspent (the engine's default) instead of the 0.02% measured on NDX stop-outs; every other symbol keeps 0.02%. The share rides on the picked contract and is used wherever a Vest leg's stop is sized: the batch's plan, the leg's own stop, the dashboard's next stop, and the 7:55 PM ET reset guard, which now sums each open position's own share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
618976bf5f
commit
10da0ca83e
+2
-2
@@ -1426,7 +1426,7 @@ function sizeLeg(item: SizingItem, contract: LegContract, legAmount: number, tra
|
||||
maxLossRoom: floorRoom,
|
||||
obfuscationDollars: obfuscation,
|
||||
availableFunds: live.availableFunds ?? live.amount,
|
||||
slippageFraction: item.client.stopSlippageFraction,
|
||||
slippageFraction: contract.stopSlippageFraction ?? item.client.stopSlippageFraction,
|
||||
});
|
||||
return { ...plan, pct, obfuscation, maxLossRoom: floorRoom };
|
||||
}
|
||||
@@ -1577,7 +1577,7 @@ async function fireLeg(
|
||||
const dllFee = pct > 0 ? entryFee : (measured ?? MAX_COMMISSION_PER_CONTRACT * contracts);
|
||||
const notional = fill.price * pv * contracts;
|
||||
const dllDollars = dllOn && !plan.lastStand
|
||||
? dllStopDollars({ room: item.dllRoom, dailyLossLimit: dll, entryFee: dllFee, commissionPct: pct, notional, slippageFraction: client.stopSlippageFraction })
|
||||
? dllStopDollars({ room: item.dllRoom, dailyLossLimit: dll, entryFee: dllFee, commissionPct: pct, notional, slippageFraction: legContract.stopSlippageFraction ?? client.stopSlippageFraction })
|
||||
: Infinity;
|
||||
const ticks = stopTicks({ obfuscationDollars: obfuscation, dllDollars, pointValue: pv, contracts, tickSize: legContract.tickSize });
|
||||
if (ticks === 0) {
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ export function rankNextLeg(o: {
|
||||
maxLossRoom: floorRoom,
|
||||
obfuscationDollars: o.obfuscationDollars,
|
||||
availableFunds: funds,
|
||||
slippageFraction: o.client.stopSlippageFraction,
|
||||
slippageFraction: c.stopSlippageFraction ?? o.client.stopSlippageFraction,
|
||||
});
|
||||
const p = plan(true);
|
||||
if (!p.sizing || p.cost === null) return [];
|
||||
|
||||
@@ -53,6 +53,10 @@ export interface PlatformContract {
|
||||
* says: no leg starts that wouldn't be done by then (clearsClose), each
|
||||
* account's at its own size (fireLeg). */
|
||||
closesAt?: number;
|
||||
/** This instrument's share of notional a stop may fill past its price,
|
||||
* where it differs from the client's stopSlippageFraction (Vest: crypto,
|
||||
* unmeasured, keeps the engine's default). */
|
||||
stopSlippageFraction?: number;
|
||||
}
|
||||
|
||||
/** What an order is for, where the caller knows (see sendOrder). */
|
||||
|
||||
+12
-5
@@ -53,7 +53,7 @@ import { vestSessionFor, dropVestSession, listVestSessions, refuseVestSession }
|
||||
import {
|
||||
vestAccountId, vestAccountName, vestDayOf, lastVestResetMs, nextVestResetMs, parseCapitalAccount, parsePosition, parseOpenOrder,
|
||||
parseTxn, bookTxns, awaitingLegs, unknownOpenFill, unrealizedPnl, hourlyVolatility, marketOpen, longClosure, num, toMs, formatDecimal, resetSafeStop,
|
||||
parseHistoryPosition, jwtClaimStrings, accountScopedToken, VEST_STATUS_ACTIVE, VEST_STOP_SLIPPAGE_FRACTION,
|
||||
parseHistoryPosition, jwtClaimStrings, accountScopedToken, VEST_STATUS_ACTIVE, VEST_STOP_SLIPPAGE_FRACTION, vestStopSlippageFraction,
|
||||
vestStatusName, awaitingActivation,
|
||||
VestApiError, VestNotSentError, nothingWentIn, tokenForRequest, settleClaims, readClaims, payoutDaysOf, payoutsSince,
|
||||
} from './vest-helpers';
|
||||
@@ -155,6 +155,9 @@ interface SymbolSpec {
|
||||
marginRate: number;
|
||||
tradingStatus: string;
|
||||
capitalTradingStatus: string;
|
||||
/** exchangeInfo's asset class: "crypto", "forex", "stock" (indices and
|
||||
* commodities too). */
|
||||
asset: string;
|
||||
}
|
||||
|
||||
export interface VestConfig {
|
||||
@@ -558,6 +561,7 @@ export class VestClient implements PlatformClient {
|
||||
marginRate: capitalIm && capitalIm > 0 ? capitalIm : 0,
|
||||
tradingStatus: String(s.tradingStatus ?? ''),
|
||||
capitalTradingStatus: String(s.capitalTradingStatus ?? s.tradingStatus ?? ''),
|
||||
asset: String(s.asset ?? ''),
|
||||
});
|
||||
}
|
||||
if (next.size > 0) {
|
||||
@@ -1230,13 +1234,15 @@ export class VestClient implements PlatformClient {
|
||||
|| (x.currentStop !== null && y.currentStop !== null ? lossAt(x.pos, x.currentStop) - lossAt(y.pos, y.currentStop) : 0)
|
||||
|| x.pos.openedAt - y.pos.openedAt || x.pos.positionId.localeCompare(y.pos.positionId));
|
||||
let committed = 0;
|
||||
let notionalSoFar = 0;
|
||||
// The slippage reserve the positions so far hold together, in
|
||||
// dollars: each its own symbol's share of its notional.
|
||||
let slippageSoFar = 0;
|
||||
for (const { pos, currentStop } of held) {
|
||||
const spec = specs.get(pos.symbol);
|
||||
if (!spec) {
|
||||
// Can't be fitted without its tick, but it still holds
|
||||
// its loss against the room the others share.
|
||||
notionalSoFar += pos.quantity * pos.openPrice;
|
||||
slippageSoFar += this.stopSlippageFraction * pos.quantity * pos.openPrice;
|
||||
if (currentStop !== null) committed += lossAt(pos, currentStop);
|
||||
else if (!guard.unfittable.has(pos.positionId)) {
|
||||
guard.unfittable.add(pos.positionId);
|
||||
@@ -1248,13 +1254,13 @@ export class VestClient implements PlatformClient {
|
||||
const mark = this.marks.get(pos.symbol)?.mark ?? NaN;
|
||||
const stopLeg = pos.stopLosses.find((l) => l.status === 'awaiting') ?? null;
|
||||
const notional = pos.quantity * (Number.isFinite(mark) ? mark : pos.openPrice);
|
||||
notionalSoFar += notional;
|
||||
slippageSoFar += vestStopSlippageFraction(spec.asset) * notional;
|
||||
const exitFee = spec.takerFee * notional;
|
||||
const decision = resetSafeStop({
|
||||
side: pos.side, quantity: pos.quantity, openPrice: pos.openPrice, mark,
|
||||
balance: cash.amount - committed, dailyLossPct: a.maxDailyLossPct, maxDrawdownFloor: a.maxDrawdownLimit,
|
||||
floorBalance: cash.amount,
|
||||
reserve: dllReserve(cash.amount * a.maxDailyLossPct, notionalSoFar, this.stopSlippageFraction),
|
||||
reserve: dllReserve(cash.amount * a.maxDailyLossPct, slippageSoFar, 1),
|
||||
exitFeeRate: spec.takerFee, currentStop, tick: spec.tick,
|
||||
});
|
||||
const stopAt = decision.action === 'stop' ? decision.price : decision.action === 'none' ? currentStop : null;
|
||||
@@ -2022,6 +2028,7 @@ export class VestClient implements PlatformClient {
|
||||
minContracts: 1,
|
||||
marginRate: spec.marginRate || undefined,
|
||||
closesAt: gateAt ?? undefined,
|
||||
stopSlippageFraction: vestStopSlippageFraction(spec.asset),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,6 +148,18 @@ export function nextVestResetMs(now: number): number {
|
||||
* past the trigger on average, 2.25 at worst — the 0.1% default held back
|
||||
* about 30. The flat 10% of the limit still applies under it. */
|
||||
export const VEST_STOP_SLIPPAGE_FRACTION = 0.0002;
|
||||
/** Crypto's (asset "crypto" in exchangeInfo: BTC, ETH, SOL, HYPE): unmeasured,
|
||||
* so the engine's default share (trading-logic's DLL_SLIPPAGE_FRACTION).
|
||||
* Vest's BTC book trades ~$10M an hour on a weekend against NDX's ~$600M, and
|
||||
* a batch's stops fire together: a square-root impact estimate scaled to the
|
||||
* NDX measurement puts the worst fill near 7bp. To come down once crypto
|
||||
* stop-outs are measured, as NDX's was. */
|
||||
export const VEST_CRYPTO_STOP_SLIPPAGE_FRACTION = 0.001;
|
||||
|
||||
/** The stop-slippage share for a symbol of this exchangeInfo asset class. */
|
||||
export function vestStopSlippageFraction(asset: string | undefined): number {
|
||||
return asset === 'crypto' ? VEST_CRYPTO_STOP_SLIPPAGE_FRACTION : VEST_STOP_SLIPPAGE_FRACTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* What to do with an open position before the daily reset so the reset can't
|
||||
|
||||
Reference in New Issue
Block a user