import test from 'node:test'; import assert from 'node:assert/strict';
import { parseOrder, check, priceValid, sizeValid, isolatedLiq, bookImpact, tierMaxLeverage } from '../src/engine.js';
const meta = { universe: [{ name: 'BTC', szDecimals: 5, maxLeverage: 40, marginTableId: 56 }, { name: 'OLD', szDecimals: 0, maxLeverage: 3, isDelisted: true, marginTableId: 3 }, { name: 'ISO', szDecimals: 1, maxLeverage: 10, onlyIsolated: true, marginTableId: 10 }, { name: 'CAP', szDecimals: 0, maxLeverage: 5, marginTableId: 5 }], marginTables: [[56, { marginTiers: [{ lowerBound: '0.0', maxLeverage: 40 }, { lowerBound: '150000000.0', maxLeverage: 20 }] }]] };
const ctx = [{ funding: '0.0000125', markPx: '80000', oraclePx: '79990', openInterest: '40000', dayNtlVlm: '2000000000' }, { funding: '0', markPx: '1', oraclePx: '1', openInterest: '0', dayNtlVlm: '0' }, { funding: '0.0001', markPx: '2', oraclePx: '2', openInterest: '1000', dayNtlVlm: '100000' }, { funding: '0', markPx: '5', oraclePx: '5', openInterest: '100', dayNtlVlm: '100000' }];
const book = { time: 1, levels: [[{ px: '79999', sz: '1' }, { px: '79990', sz: '5' }], [{ px: '80001', sz: '1' }, { px: '80010', sz: '5' }]] };
const M = { meta, ctx, book, oiCap: new Set(['CAP']), now: 0 };
test('valid market long is OK with liq + funding + impact', () => {
const r = check(parseOrder({ coin: 'BTC', side: 'long', size: '0.01', leverage: 10 }), M);
assert.equal(r.verdict, 'OK'); assert.ok(r.liquidation.estimate_isolated < 80000 && r.liquidation.estimate_isolated > 71000); assert.equal(r.funding.direction, 'you pay'); assert.ok(r.execution.est_impact_bps > 0);
});
test('size decimals, min notional, leverage → REJECT_LIKELY with fixes', () => {
const r = check(parseOrder({ coin: 'BTC', side: 'short', size: '0.0000011', leverage: 60 }), M);
assert.equal(r.verdict, 'REJECT_LIKELY'); const codes = r.issues.map((i) => i.code);
assert.ok(codes.includes('size_decimals') && codes.includes('min_notional') && codes.includes('leverage'));
assert.equal(r.issues.find((i) => i.code === 'leverage').fix, 40);
});
test('price tick rule', () => {
assert.equal(priceValid('80000', 5).ok, true); assert.equal(priceValid('80000.5', 5).ok, false); assert.equal(priceValid('1.23456', 0).ok, false); assert.equal(priceValid('1.2345', 0).ok, true); assert.equal(priceValid('0.000012345', 0).ok, false);
assert.equal(sizeValid('1.5', 0).ok, false); assert.equal(sizeValid('0.00001', 5).ok, true);
});
test('delisted, isolated-only, OI cap, unknown coin', () => {
assert.ok(check(parseOrder({ coin: 'OLD', side: 'long', notional: 100 }), M).issues.some((i) => i.code === 'delisted'));
assert.ok(check(parseOrder({ coin: 'ISO', side: 'long', notional: 100, margin: 'cross' }), M).issues.some((i) => i.code === 'isolated_only'));
assert.ok(check(parseOrder({ coin: 'CAP', side: 'long', notional: 100 }), M).issues.some((i) => i.code === 'oi_cap'));
assert.equal(check(parseOrder({ coin: 'CAP', side: 'short', notional: 100, reduceOnly: 'true' }), M).issues.length, 0);
assert.throws(() => check(parseOrder({ coin: 'NOPE', side: 'long', notional: 100 }), M), (e) => e.status === 404 && e.code === 'unknown_coin');
});
test('liq formula and tiers and book walk', () => {
const l = isolatedLiq(100, 1, 1, 10, 50); assert.ok(Math.abs(l.price - (100 - (10 - 1) / 1 / (1 - 0.01))) < 1e-9);
assert.equal(tierMaxLeverage({ marginTiers: [{ lowerBound: '0', maxLeverage: 40 }, { lowerBound: '150000000', maxLeverage: 20 }] }, 40, 2e8), 20);
const b = bookImpact(book, 1, 3); assert.equal(b.filled_fraction, 1); assert.ok(b.avg_fill > 80001);
assert.equal(bookImpact(book, 1, 100).filled_fraction < 1, true);
});
test('input errors', () => {
for (const q of [{}, { coin: 'BTC' }, { coin: 'BTC', side: 'up', size: 1 }, { coin: 'BTC', side: 'long' }, { coin: 'BTC', side: 'long', size: -1 }, { coin: 'B TC;', side: 'long', size: 1 }]) assert.throws(() => parseOrder(q));
});
test('fail-closed in the pure check: missing OI-cap list or book never yields OK', () => {
const o = parseOrder({ coin: 'BTC', side: 'long', size: '0.01', leverage: 10 });
const a = check(o, { ...M, oiCap: null, failed: ['perpsAtOpenInterestCap'] });
assert.equal(a.verdict, 'CAUTION'); assert.equal(a.confidence, 'low');
const b = check(o, { ...M, book: null, failed: ['l2Book'] });
assert.equal(b.verdict, 'CAUTION'); assert.equal(b.confidence, 'medium'); assert.ok(b.warnings.some((w) => w.code === 'impact_unknown'));
assert.equal(check(o, M).confidence, 'high');
assert.throws(() => check(o, { ...M, ctx: [{ ...M.ctx[0], markPx: 'NaN' }, ...M.ctx.slice(1)] }), (e) => e.code === 'upstream_unavailable');
assert.match(check(o, M).disclaimer, /Not financial advice; the exchange decides whether an order is accepted\./);
});
agentgate-source: code mirror on Cloudflare Pages. All repos