Skip to content
markpaper

src/markets/markets.test.ts

v0.1.0 · 3 KB

Download file
import { describe, expect, it } from 'vitest';
import { fixtureRefdata } from '../testing/fixtures.js';
import {
  createMarketCache,
  createQuant,
  decodeRefdata,
  listingOf,
  mergeRefdata,
  parseSymbol,
  selectBand,
} from './index.js';

describe('reference data and market grids', () => {
  it('preserves symbol case and rejects mismatched quote semantics', () => {
    expect(parseSymbol('kTEST-USD')).toEqual({ base: 'kTEST', quote: 'USD' });
    const market = decodeRefdata([fixtureRefdata()]).markets.get('BTC-USD')!;
    expect(listingOf({ ...market, quote: 'EUR' }).status).toBe('unlisted');
    expect(listingOf({ ...market, status: 'FUTURE_STATUS' }).status).toBe('unknown');
  });
  it('quantizes nondecimal lot and tick sizes without float drift', () => {
    const quant = createQuant(decodeRefdata([fixtureRefdata()]).markets.get('BTC-USD')!);
    expect(quant.szToStr(0.37999999999999995)).toBe('0.38');
    expect(quant.pxToStrDir(120.24999999999999, 'down')).toBe('120.25');
    expect(quant.minSz).toBe(0.06);
  });
  it('rejects metadata that cannot produce finite numeric quantities', () => {
    const market = decodeRefdata([fixtureRefdata()]).markets.get('BTC-USD')!;
    expect(() => createQuant({ ...market, minQty: '1e400' })).toThrow();
  });
  it('rejects duplicate symbols rather than selecting one', () => {
    const rows = [
      fixtureRefdata('BTC-USD'),
      fixtureRefdata('BTC-USD'),
      fixtureRefdata('ETH-USD'),
      fixtureRefdata('TESTA-USD'),
      fixtureRefdata('TESTB-USD'),
    ];
    const decoded = decodeRefdata(rows);
    expect(decoded.bad.has('BTC-USD')).toBe(true);
    expect(decoded.markets.has('BTC-USD')).toBe(false);
  });
  it('freezes only a watched changed grid, preserving other updates', () => {
    const before = decodeRefdata([fixtureRefdata(), fixtureRefdata('ETH-USD')]);
    const after = decodeRefdata([{ ...fixtureRefdata(), lot_size: '0.04' }, fixtureRefdata('ETH-USD')]);
    const merged = mergeRefdata(before.markets, after, ['BTC-USD']);
    expect(merged.frozen.get('BTC-USD')?.reason).toBe('grid_changed');
    expect(merged.markets.get('BTC-USD')?.lot).toBe('0.02');
    expect(merged.markets.has('ETH-USD')).toBe(true);
  });
  it('returns stale refdata bands for display only', () => {
    expect(selectBand({ refBand: { min: '40', max: '240' }, freshMs: 30, now: 100 })).toMatchObject({
      fresh: false,
      source: 'refdata',
    });
  });
  it('deduplicates refreshes and keeps stale cache failure visible', async () => {
    let calls = 0,
      now = 0;
    const cache = createMarketCache({
      ttlMs: 10,
      retryBackoffMs: 20,
      watched: () => [],
      now: () => now,
      fetchRefdata: async () => {
        calls++;
        if (calls > 1) throw new Error('Unavailable');
        return { payload: [fixtureRefdata()] };
      },
    });
    await Promise.all([cache.get(), cache.get()]);
    expect(calls).toBe(1);
    now = 20;
    await cache.get();
    expect(cache.info().fresh).toBe(false);
    expect(cache.info().lastError).toBe('Unavailable');
  });
});
All files