Skip to content
markpaper

src/orders/size.test.ts

v0.2.1 · 5.7 KB

Download file
import { describe, expect, it } from 'vitest';
import { planOrderSize } from './size.js';

// Market shapes from the knowledge base: SNDK-like (lot 0.0001, min_base 0.01 ~ $16 at ~1605),
// CRWV-like (lot 0.01, min_base 0.04 ~ $3.6 at ~90), LIT-like (lot 5 units), VVV-like (min_base 0.5 ~ $7.3 at 14.6).
const sndk = { sizeDecimals: 4, minBaseAmount: 0.01, minQuoteUsd: 10 };
const crwv = { sizeDecimals: 2, minBaseAmount: 0.04, minQuoteUsd: 10 };
const lit = { sizeDecimals: 0, minBaseAmount: 5, minQuoteUsd: 10 };
const vvv = { sizeDecimals: 1, minBaseAmount: 0.5, minQuoteUsd: 10 };

describe('planOrderSize: opens and partial reduces', () => {
  it('passes $10 but fails the lot minimum -> skip (the 21706 case)', () => {
    const p = planOrderSize({ intent: 'open', size: 0.0071, px: 1605, ...sndk });
    expect(p).toMatchObject({ action: 'skip', reason: 'below_min_base_amount', size: 0.0071 });
  });

  it('passes the lot minimum but fails $10 -> skip', () => {
    const p = planOrderSize({ intent: 'increase', size: 0.04, px: 90, ...crwv });
    expect(p).toMatchObject({ action: 'skip', reason: 'below_min_quote_amount', size: 0.04 });
  });

  it('floors and places when both minimums pass', () => {
    const p = planOrderSize({ intent: 'open', size: 0.01239, px: 1605, ...sndk });
    expect(p).toMatchObject({ action: 'place', size: 0.0123, sizeStr: '0.0123', bumped: false, reduceOnly: false });
    if (p.action === 'place') expect(p.notional).toBeCloseTo(19.7415, 6);
  });

  it('never bumps a partial reduce; marks it reduceOnly', () => {
    const p = planOrderSize({ intent: 'partialReduce', size: 3, px: 0.91, ...lit });
    expect(p).toMatchObject({ action: 'skip', reason: 'below_min_base_amount' });
    const ok = planOrderSize({ intent: 'partialReduce', size: 14.9, px: 0.91, ...lit });
    expect(ok).toMatchObject({ action: 'place', size: 14, reduceOnly: true, bumped: false });
  });

  it('skips sizes that round to zero', () => {
    expect(planOrderSize({ intent: 'open', size: 0.00004, px: 1605, ...sndk })).toMatchObject({
      action: 'skip',
      reason: 'size_rounds_to_zero',
    });
    expect(planOrderSize({ intent: 'open', size: 0, px: 1605, ...sndk })).toMatchObject({
      action: 'skip',
      reason: 'size_rounds_to_zero',
    });
  });

  it('applies the lot-error tolerance to resting orders only', () => {
    // lot 0.01: 0.109 floors to 0.10, 8.3% of the requested size lost
    const resting = planOrderSize({ intent: 'open', size: 0.109, px: 200, ...crwv, sizeTolerancePct: 5 });
    expect(resting).toMatchObject({ action: 'skip', reason: 'lot_error_above_tolerance' });
    const ioc = planOrderSize({ intent: 'open', size: 0.109, px: 200, ...crwv, sizeTolerancePct: 5, tif: 'Ioc' });
    expect(ioc).toMatchObject({ action: 'place', size: 0.1 });
    const within = planOrderSize({ intent: 'open', size: 0.109, px: 200, ...crwv, sizeTolerancePct: 10 });
    expect(within).toMatchObject({ action: 'place', size: 0.1 });
  });

  it('handles a missing price: skip for opens, defer for exits', () => {
    expect(planOrderSize({ intent: 'open', size: 1, px: 0, ...crwv })).toEqual({
      action: 'skip',
      reason: 'no_reference_price',
    });
    expect(planOrderSize({ intent: 'partialReduce', size: 1, px: Number.NaN, ...crwv })).toEqual({
      action: 'defer',
      reason: 'no_reference_price',
    });
    expect(planOrderSize({ intent: 'fullClose', size: 1, px: -1, ...crwv })).toEqual({
      action: 'defer',
      reason: 'no_reference_price',
    });
  });

  it('works without minimums', () => {
    expect(planOrderSize({ intent: 'open', size: 0.0001, px: 1, sizeDecimals: 4 })).toMatchObject({
      action: 'place',
      size: 0.0001,
    });
  });

  it('rejects an unknown intent', () => {
    expect(() => planOrderSize({ intent: 'close' as never, size: 1, px: 1, sizeDecimals: 0 })).toThrow(RangeError);
  });
});

describe('planOrderSize: full close', () => {
  it('ceils and bumps above BOTH minimums (the VVV case: lot minimum alone stays under $10)', () => {
    // 0.3 units at 14.6: ceil -> 0.3, min_base 0.5 = $7.30 < $10 -> needs 0.7 = $10.22
    const p = planOrderSize({ intent: 'fullClose', size: 0.3, px: 14.6, ...vvv });
    expect(p).toMatchObject({ action: 'place', size: 0.7, sizeStr: '0.7', bumped: true, reduceOnly: true });
  });

  it('bumps to min_base_amount when that is the stricter one (SNDK)', () => {
    const p = planOrderSize({ intent: 'fullClose', size: 0.0071, px: 1605, ...sndk });
    expect(p).toMatchObject({ action: 'place', size: 0.01, bumped: true });
  });

  it('keeps ceil(size) when it already passes both', () => {
    const p = planOrderSize({ intent: 'fullClose', size: 0.6231, px: 1605, ...sndk });
    expect(p).toMatchObject({ action: 'place', size: 0.6231, bumped: false });
    const lots = planOrderSize({ intent: 'fullClose', size: 877, px: 0.91, ...lit });
    expect(lots).toMatchObject({ action: 'place', size: 877, bumped: false });
  });

  it('guards the 9.999999999999998 float product', () => {
    // 0.7 * 14.285714285714286 = 9.999999999999998 as floats; the plan must not stop one lot short.
    const p = planOrderSize({
      intent: 'fullClose',
      size: 0.7,
      px: 14.285714285714286,
      sizeDecimals: 1,
      minQuoteUsd: 10,
    });
    expect(p.action).toBe('place');
    if (p.action === 'place') expect(p.size * 14.285714285714286).toBeGreaterThanOrEqual(10);
  });

  it('a dust position still yields at least one lot above the minimums', () => {
    const p = planOrderSize({ intent: 'fullClose', size: 0.00001, px: 1605, ...sndk });
    expect(p).toMatchObject({ action: 'place', size: 0.01, bumped: true });
    expect(planOrderSize({ intent: 'fullClose', size: 0, px: 1605, ...sndk })).toMatchObject({
      action: 'skip',
      reason: 'nothing_to_close',
    });
  });
});
All files