DXtrade: an entry never closes a hand trade, and a flatten never opens one
DXtrade dropped the intent the engine now passes, so on the live Tradeify247 accounts an entry could still close a hand position as the "entry" and the leg's flatten then OPEN a position with no exits. The open/close decision moves out of vest-helpers into platform-client's orderEffect, which both clients now follow to pick OPEN or CLOSE. A flatten's flat read is "nothing to flatten" only when the client's own record agrees. One empty read just after a fill the client saw may be the gateway lagging, so it stays a plain refusal and keeps the FLATTEN FAILED alarm. A check now fails if an engine order drops its intent or a client stops going by orderEffect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e90f21ed91
commit
378530595e
@@ -1131,3 +1131,73 @@ test('the day after a claim: a fresh cycle that aims at the next stage\'s target
|
||||
assert.ok(plan.dayTarget >= 3000 && plan.dayTarget <= 3025, `dayTarget ${plan.dayTarget}`);
|
||||
assert.equal(plan.remainingToday, plan.dayTarget);
|
||||
});
|
||||
|
||||
// ── what an order is for (lib/platform-client.ts orderEffect) ────────────────
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { orderEffect, OrderNotPlacedError, NothingToCloseError, isOrderNotPlaced, isNothingToClose } from '../lib/platform-client.ts';
|
||||
|
||||
test('an entry never closes a position, a flatten never opens one (Vest, DXtrade)', () => {
|
||||
/** The route, or the refusal thrown. `recorded`: the client's own record
|
||||
* of the symbol, by default what the fresh read shows. */
|
||||
const effect = (held, side, intent, recorded = held) => {
|
||||
try {
|
||||
return orderEffect({ symbol: 'NDX-USD-PERP', held, recorded, side, intent });
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
};
|
||||
// No intent: the position decides, as before (the manual order route).
|
||||
assert.equal(effect(null, 'long'), 'open');
|
||||
assert.equal(effect('short', 'long'), 'close');
|
||||
assert.match(effect('long', 'long').message, /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 (the leg is
|
||||
// un-counted), not turned into a close of that short.
|
||||
assert.equal(effect(null, 'long', 'open'), 'open');
|
||||
for (const recorded of ['short', null]) {
|
||||
const entry = effect('short', 'long', 'open', recorded);
|
||||
assert.equal(isOrderNotPlaced(entry), true);
|
||||
assert.equal(isNothingToClose(entry), false);
|
||||
assert.match(entry.message, /short NDX-USD-PERP position is already open/);
|
||||
}
|
||||
assert.match(effect('long', 'long', 'open').message, /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 when the record agrees.
|
||||
assert.equal(effect('long', 'short', 'close'), 'close');
|
||||
const flat = effect(null, 'short', 'close');
|
||||
assert.ok(flat instanceof NothingToCloseError);
|
||||
assert.equal(isOrderNotPlaced(flat), true);
|
||||
assert.equal(isNothingToClose(flat), true);
|
||||
assert.match(flat.message, /no NDX-USD-PERP position to close/);
|
||||
// A flat read just after a fill the client saw (the leg still on record)
|
||||
// may be the gateway lagging: nothing sent, but not "flat" — the flatten
|
||||
// keeps its alarm.
|
||||
const lagging = effect(null, 'short', 'close', 'long');
|
||||
assert.ok(lagging instanceof OrderNotPlacedError);
|
||||
assert.equal(isNothingToClose(lagging), false);
|
||||
assert.match(lagging.message, /fresh read shows no NDX-USD-PERP position, but this client last saw a long one open/);
|
||||
// Holding the flatten's own side is not the leg: refused, and not "flat".
|
||||
const wrong = effect('short', 'short', 'close');
|
||||
assert.ok(wrong instanceof OrderNotPlacedError);
|
||||
assert.equal(isNothingToClose(wrong), false);
|
||||
assert.match(wrong.message, /holds a short NDX-USD-PERP position, not one to close/);
|
||||
|
||||
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('the engine says what each order is for, and both position-based clients go by it', () => {
|
||||
const source = (file) => readFileSync(new URL(`../lib/${file}`, import.meta.url), 'utf8');
|
||||
// An order sent without its intent lets the position decide again: an
|
||||
// entry closing a hand trade, a flatten opening a position with no exits.
|
||||
const calls = [...source('auto-trade.ts').matchAll(/client\.sendOrder\(([^;]+?)\);/g)].map((m) => m[1].trim());
|
||||
assert.ok(calls.length >= 4, `found ${calls.length} sendOrder calls`);
|
||||
for (const args of calls) assert.match(args, /, '(open|close)'$/, `sendOrder(${args}) names no intent`);
|
||||
for (const file of ['vest-class.ts', 'dxtrade-class.ts']) {
|
||||
const src = source(file);
|
||||
assert.match(src, /return await this\.sendOrderAndWait\([^)]*, intent\);/, `${file}: sendOrder drops the intent`);
|
||||
assert.match(src, /= orderEffect\(\{[^}]*\bintent\b[^}]*\}\) === 'close' \? held : undefined;/, `${file}: orderEffect doesn't pick the route`);
|
||||
}
|
||||
});
|
||||
|
||||
+3
-40
@@ -1,6 +1,5 @@
|
||||
// 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).
|
||||
// Checks for the Vest client's pure helpers (lib/vest-helpers.ts) and the
|
||||
// relayed-session store (lib/vest-session.ts).
|
||||
//
|
||||
// npm run check
|
||||
//
|
||||
@@ -13,10 +12,9 @@ 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, vestOrderEffect,
|
||||
accountScopedToken,
|
||||
} 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);
|
||||
|
||||
@@ -144,41 +142,6 @@ 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