Vest: an entry never closes a hand trade, and a flatten never opens one
sendOrder takes an optional intent. On Vest an 'open' is refused when the account already holds the symbol, where it used to close that position as the "entry"; the leg's exits then found nothing and its flatten opened a fresh position with no TP or stop. A 'close' only closes or reduces, and on a flat account is refused with NothingToCloseError, which flattenAndFail reports as nothing to flatten instead of FLATTEN FAILED. Entries, the extra-day round trip and flattens pass their intent; the manual order route and the other platforms keep today's behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
beddbdcf6c
commit
e90f21ed91
+40
-3
@@ -1,5 +1,6 @@
|
||||
// Checks for the Vest client's pure helpers (lib/vest-helpers.ts) and the
|
||||
// relayed-session store (lib/vest-session.ts).
|
||||
// Checks for the Vest client's pure helpers (lib/vest-helpers.ts), the
|
||||
// relayed-session store (lib/vest-session.ts) and the order refusals it
|
||||
// throws (lib/platform-client.ts).
|
||||
//
|
||||
// npm run check
|
||||
//
|
||||
@@ -12,9 +13,10 @@ import {
|
||||
isVestWalletAddress, vestAccountId, num, toMs, formatDecimal, vestDayOf, lastVestResetMs, parseCapitalAccount,
|
||||
vestAccountName, parsePosition, awaitingLegs, parseOpenOrder, unrealizedPnl, parseTxn, bookTxns, txnKind, VEST_TXN,
|
||||
hourlyVolatility, marketOpen, MIN_HOURLY_RETURNS, nextVestResetMs, resetSafeStop, parseHistoryPosition, jwtClaimStrings,
|
||||
accountScopedToken,
|
||||
accountScopedToken, vestOrderEffect,
|
||||
} from '../lib/vest-helpers.ts';
|
||||
import { putVestSession, vestSessionFor, dropVestSession, listVestSessions, refuseVestSession } from '../lib/vest-session.ts';
|
||||
import { OrderNotPlacedError, NothingToCloseError, isOrderNotPlaced, isNothingToClose } from '../lib/platform-client.ts';
|
||||
|
||||
const iso = (s) => Date.parse(s);
|
||||
|
||||
@@ -142,6 +144,41 @@ test('positions, their legs and unrealized P&L', () => {
|
||||
assert.equal(parseOpenOrder({ symbol: 'x' }), null);
|
||||
});
|
||||
|
||||
test('an entry never closes a position, a flatten never opens one', () => {
|
||||
const effect = (held, side, intent) => vestOrderEffect({ symbol: 'NDX-USD-PERP', held, side, intent });
|
||||
// No intent: the position decides, as before (the manual order route).
|
||||
assert.deepEqual(effect(null, 'long'), { effect: 'open' });
|
||||
assert.deepEqual(effect('short', 'long'), { effect: 'close' });
|
||||
assert.match(effect('long', 'long').refused, /adding to an open NDX-USD-PERP position/);
|
||||
// An entry on an account that already holds the symbol — a hand short the
|
||||
// engine's synced positions didn't show yet — is refused, not turned into
|
||||
// a close of that short.
|
||||
assert.deepEqual(effect(null, 'long', 'open'), { effect: 'open' });
|
||||
const entry = effect('short', 'long', 'open');
|
||||
assert.match(entry.refused, /short NDX-USD-PERP position is already open/);
|
||||
assert.equal(entry.nothingToClose, undefined);
|
||||
assert.match(effect('long', 'long', 'open').refused, /adding to an open/);
|
||||
// A flatten closes the leg it was sent for; on a flat account it opens
|
||||
// nothing and says there is nothing to close.
|
||||
assert.deepEqual(effect('long', 'short', 'close'), { effect: 'close' });
|
||||
const flat = effect(null, 'short', 'close');
|
||||
assert.match(flat.refused, /no NDX-USD-PERP position to close/);
|
||||
assert.equal(flat.nothingToClose, true);
|
||||
// Holding the flatten's own side is not the leg: refused, and not "flat".
|
||||
const wrong = effect('short', 'short', 'close');
|
||||
assert.match(wrong.refused, /holds a short NDX-USD-PERP position, not one to close/);
|
||||
assert.equal(wrong.nothingToClose, undefined);
|
||||
|
||||
// The refusal the client throws for it: still "never placed" (the leg is
|
||||
// un-counted), and told apart from every other refusal.
|
||||
const nothing = new NothingToCloseError('no NDX-USD-PERP position to close — nothing sent');
|
||||
assert.equal(isOrderNotPlaced(nothing), true);
|
||||
assert.equal(isNothingToClose(nothing), true);
|
||||
assert.equal(isNothingToClose(new OrderNotPlacedError('Vest refused the order: 400')), false);
|
||||
assert.equal(isNothingToClose(new Error('outcome unknown')), false);
|
||||
assert.equal(isNothingToClose(undefined), false);
|
||||
});
|
||||
|
||||
test('transactions book into daily P&L and fund rows by Vest day', () => {
|
||||
assert.equal(txnKind(VEST_TXN.FEE), 'trade');
|
||||
assert.equal(txnKind(VEST_TXN.REALIZED_PNL), 'trade');
|
||||
|
||||
Reference in New Issue
Block a user