Skip to content
markpaper

src/numbers/quantize.test.ts

v0.2.1 · 4.9 KB

Download file
import { describe, expect, it } from 'vitest';
import { LighterNumbersError } from './errors.js';
import {
  decimalsOf,
  formatSize,
  fromWire,
  isOnPriceGrid,
  isOnSizeGrid,
  priceFromWire,
  priceTick,
  priceToWire,
  quantizePrice,
  quantizeSize,
  scaledIntegerFromString,
  scaledIntegerToString,
  sizeFromWire,
  sizeLot,
  sizeToWire,
  toWire,
} from './quantize.js';

describe('sizeLot / priceTick', () => {
  it('builds exact powers of ten (the 10 ** -4 trap)', () => {
    expect(10 ** -4).not.toBe(0.0001); // the trap the knowledge base warns about
    expect(sizeLot(4)).toBe(0.0001);
    expect(sizeLot(0)).toBe(1);
    expect(sizeLot(3)).toBe(0.001);
    expect(priceTick(2)).toBe(0.01);
    expect(priceTick(5)).toBe(0.00001);
  });

  it('rejects bad decimals', () => {
    expect(() => sizeLot(-1)).toThrow(LighterNumbersError);
    expect(() => sizeLot(1.5)).toThrow(LighterNumbersError);
    expect(() => sizeLot(13)).toThrow(LighterNumbersError);
  });
});

describe('quantizeSize', () => {
  it('floors by default and ceils on request', () => {
    expect(quantizeSize(0.00718, 4)).toBe(0.0071);
    expect(quantizeSize(0.00718, 4, 'ceil')).toBe(0.0072);
    expect(quantizeSize(2.9, 0)).toBe(2);
    expect(quantizeSize(2.1, 0, 'ceil')).toBe(3);
  });

  it('does not move a size that already lies on the grid', () => {
    expect(quantizeSize(0.3, 1)).toBe(0.3);
    expect(quantizeSize(0.3, 1, 'ceil')).toBe(0.3);
    expect(quantizeSize(0.1 + 0.2, 1)).toBe(0.3); // 0.30000000000000004
    expect(quantizeSize(0.1 + 0.2, 1, 'ceil')).toBe(0.3);
    expect(quantizeSize(5, 0)).toBe(5);
  });

  it('clamps negatives to zero and rejects non-finite', () => {
    expect(quantizeSize(-1, 2)).toBe(0);
    expect(() => quantizeSize(Number.NaN, 2)).toThrow(LighterNumbersError);
    expect(() => quantizeSize(Number.POSITIVE_INFINITY, 2)).toThrow(LighterNumbersError);
  });

  it('formatSize returns the wire string with fixed decimals', () => {
    expect(formatSize(0.00718, 4)).toBe('0.0071');
    expect(formatSize(3, 2)).toBe('3.00');
  });
});

describe('quantizePrice', () => {
  it('rounds to the tick as an exact string', () => {
    expect(quantizePrice(41.236, 2)).toBe('41.24');
    expect(quantizePrice(1605.344, 2)).toBe('1605.34');
    expect(quantizePrice(7, 3)).toBe('7.000');
  });

  it('floors and ceils without moving on-grid prices', () => {
    expect(quantizePrice(41.236, 2, 'floor')).toBe('41.23');
    expect(quantizePrice(41.231, 2, 'ceil')).toBe('41.24');
    expect(quantizePrice(41.23, 2, 'floor')).toBe('41.23');
    expect(quantizePrice(41.23, 2, 'ceil')).toBe('41.23');
  });

  it('isOnPriceGrid checks the string, not a float', () => {
    expect(isOnPriceGrid('52.37', 2)).toBe(true);
    expect(isOnPriceGrid('52.371', 2)).toBe(false);
    expect(isOnPriceGrid('52', 2)).toBe(true);
    expect(isOnPriceGrid('-1', 2)).toBe(false);
    expect(isOnPriceGrid('1e3', 2)).toBe(false);
  });

  it('isOnSizeGrid tolerates float noise', () => {
    expect(isOnSizeGrid(0.1 + 0.2, 1)).toBe(true);
    expect(isOnSizeGrid(0.35, 1)).toBe(false);
  });
});

describe('wire encoding', () => {
  it('encodes base_amount and price as exact integers', () => {
    expect(sizeToWire(0.0071, 4)).toBe(71);
    expect(sizeToWire(1.005, 3)).toBe(1005);
    expect(priceToWire('1605.34')).toBe(160534);
    expect(priceToWire('1605.34', 2)).toBe(160534);
    expect(priceToWire('1605.3', 2)).toBe(160530);
    expect(priceToWire('0.48213')).toBe(48213);
    expect(toWire(0.01, '1605.34', 4)).toEqual({ base_amount: 100, price: 160534 });
  });

  it('rejects a price string with more decimals than the grid', () => {
    expect(() => priceToWire('1605.345', 2)).toThrow(LighterNumbersError);
    expect(() => scaledIntegerFromString('abc', 2)).toThrow(LighterNumbersError);
    expect(() => sizeToWire(-0.5, 2)).toThrow(LighterNumbersError);
  });

  it('decodes back to the same values', () => {
    expect(sizeFromWire(71, 4)).toBe(0.0071);
    expect(priceFromWire(160534, 2)).toBe('1605.34');
    expect(priceFromWire(5, 3)).toBe('0.005');
    expect(priceFromWire(7, 0)).toBe('7');
    expect(fromWire({ base_amount: 100, price: 160534 }, 4, 2)).toEqual({ size: 0.01, priceStr: '1605.34' });
    expect(scaledIntegerToString(0, 2)).toBe('0.00');
    expect(() => scaledIntegerToString(-1, 2)).toThrow(LighterNumbersError);
    expect(() => scaledIntegerToString(1.5, 2)).toThrow(LighterNumbersError);
  });

  it('decimalsOf counts fractional digits', () => {
    expect(decimalsOf('1605.34')).toBe(2);
    expect(decimalsOf('7')).toBe(0);
  });

  it('round-trips a full grid of sizes without drift', () => {
    for (let lots = 0; lots < 2000; lots++) {
      const size = Number((lots * 0.0001).toFixed(4));
      expect(sizeToWire(size, 4)).toBe(lots);
      expect(sizeFromWire(lots, 4)).toBe(size);
      expect(quantizeSize(size, 4)).toBe(size);
      expect(quantizeSize(size, 4, 'ceil')).toBe(size);
    }
  });
});
All files