Skip to content
markpaper

src/markets/status.test.ts

v0.2.0 · 3.7 KB

Download file
import { describe, expect, it } from 'vitest';
import { effectiveLongWeight, initialMarginFraction, maxLeverageFromWeights } from './leverage.js';
import { acceptedOrderTypes, canActivateMarket, isPostOnlyMarket, marketMode, resolveMarketMode } from './status.js';

describe('maxLeverageFromWeights', () => {
  it('follows 1 / (1 - weight): 0.98 -> 50x, 0.9 -> 10x, 0.8 -> 5x', () => {
    expect(maxLeverageFromWeights(0.98)).toBe(50);
    expect(maxLeverageFromWeights(0.9)).toBe(10);
    expect(maxLeverageFromWeights(0.8)).toBe(5);
    expect(maxLeverageFromWeights(0.5)).toBe(2);
    expect(maxLeverageFromWeights(0.001)).toBe(1);
  });

  it('rejects weights outside (0, 1)', () => {
    expect(() => maxLeverageFromWeights(0)).toThrow(RangeError);
    expect(() => maxLeverageFromWeights(1)).toThrow(RangeError);
    expect(() => maxLeverageFromWeights(Number.NaN)).toThrow(RangeError);
  });

  it('initial margin fraction is 1 - weight; effective weight prefers a valid subaccount value', () => {
    expect(initialMarginFraction(0.98)).toBeCloseTo(0.02, 12);
    expect(() => initialMarginFraction(1.2)).toThrow(RangeError);
    expect(effectiveLongWeight(0.95, 0.98)).toBe(0.95);
    expect(effectiveLongWeight(0, 0.98)).toBe(0.98);
    expect(effectiveLongWeight(null, 0.98)).toBe(0.98);
    expect(effectiveLongWeight(1, 0.98)).toBe(0.98);
  });
});

describe('marketMode', () => {
  it('live accepts everything', () => {
    const m = marketMode('live');
    expect(m.accepts).toEqual(['default', 'ioc', 'fok', 'post_only']);
    expect(m.postOnly).toBe(false);
    expect(m.verified).toBe(true);
  });

  it('post_only accepts ONLY post_only and refuses the rest with 2117', () => {
    const m = marketMode('post_only');
    expect(m.accepts).toEqual(['post_only']);
    expect(m.postOnly).toBe(true);
    expect(m.refusalCode).toBe(2117);
    expect(m.verified).toBe(true);
    expect(acceptedOrderTypes('post_only')).not.toContain('ioc');
    expect(acceptedOrderTypes('post_only')).not.toContain('default');
  });

  it('not_tradable accepts nothing (2069)', () => {
    const m = marketMode('not_tradable');
    expect(m.accepts).toEqual([]);
    expect(m.refusalCode).toBe(2069);
    expect(m.verified).toBe(true);
  });

  it('reduce_only statuses are taker reduce-only by name and marked unverified', () => {
    for (const s of ['reduce_only', 'soft_reduce_only']) {
      const m = marketMode(s);
      expect(m.accepts).toEqual(['ioc', 'fok']);
      expect(m.reduceOnlyOnly).toBe(true);
      expect(m.verified).toBe(false);
    }
  });

  it('unknown statuses fail closed', () => {
    const m = marketMode('something_new');
    expect(m.accepts).toEqual([]);
    expect(m.verified).toBe(false);
    expect(m.status).toBe('something_new');
  });
});

describe('resolveMarketMode / isPostOnlyMarket', () => {
  it('publishes the mode only from a fresh cache', () => {
    expect(resolveMarketMode('post_only', true)?.postOnly).toBe(true);
    expect(resolveMarketMode('post_only', false)).toBeUndefined();
    expect(resolveMarketMode(undefined, true)).toBeUndefined();
    expect(isPostOnlyMarket('post_only', true)).toBe(true);
    expect(isPostOnlyMarket('live', true)).toBe(false);
    expect(isPostOnlyMarket('live', false)).toBeUndefined();
    expect(isPostOnlyMarket(undefined, true)).toBeUndefined();
  });
});

describe('canActivateMarket', () => {
  it('blocks new activation on not_tradable / unknown, allows live and post_only', () => {
    expect(canActivateMarket('live')).toBe(true);
    expect(canActivateMarket('post_only')).toBe(true);
    expect(canActivateMarket('not_tradable')).toBe(false);
    expect(canActivateMarket('weird')).toBe(false);
    expect(canActivateMarket(undefined)).toBe(false);
  });
});
All files