SPB Git

Repository Public

agent-engine.test.ts 3.0 KB 40 lines typescript

1import { beforeAll, describe, expect, it } from 'vitest';2import type { AgentRow } from '../src/agents/routes.ts';34let checkWeights: typeof import('../src/agents/engine.ts').checkWeights, planOrder: typeof import('../src/agents/engine.ts').planOrder;5beforeAll(async () => {6  Object.assign(process.env, { SPW_ORIGIN: 'https://wallet.test', SPW_RP_ID: 'wallet.test', DATABASE_URL: 'postgres:///unused', SPW_SIGNER_SECRET: 'k'.repeat(48), SPW_WORKERS: 'off' });7  ({ checkWeights, planOrder } = await import('../src/agents/engine.ts'));8});9const agent = (risk: Partial<AgentRow['risk']> = {}) => ({ id: 'a', universe: ['ETH', 'LINK'], risk: { maxExposurePct: 50, maxLossPct: 5, maxOrderUsd: 25, maxDailyUsd: 100, maxOrdersPerDay: 12, mandateDays: 30, slippageBps: 50, ...risk } }) as unknown as AgentRow;10const hold = (usdc: number, eth = 0, link = 0) => [{ symbol: 'USDC', id: '0xusdc', decimals: 6, units: usdc }, { symbol: 'ETH', id: 'native', decimals: 18, units: eth }, { symbol: 'LINK', id: '0xlink', decimals: 18, units: link }];11const px = { ETH: 2000, LINK: 10 };1213describe('agent engine: weights', () => {14  it('accepts only universe symbols with weights in [0, 1] summing to at most 1', () => {15    expect(checkWeights({ ETH: 0.4 }, ['ETH', 'LINK'])).toEqual({ ETH: 0.4, LINK: 0 });16    for (const bad of [null, [], { BTC: 0.1 }, { ETH: -0.1 }, { ETH: 1.2 }, { ETH: NaN }, { ETH: '0.5' }, { ETH: 0.7, LINK: 0.7 }]) expect(() => checkWeights(bad, ['ETH', 'LINK'])).toThrow();17  });18});19describe('agent engine: order planning', () => {20  it('buys toward the target, capped by the order limit and the exposure limit', () => {21    const p = planOrder(agent(), { ETH: 1, LINK: 0 }, hold(100), px, 0, 0); // wants 100 %, exposure cap 50 % → 50 USD, order cap 2522    expect(p.order).toMatchObject({ fromSymbol: 'USDC', toSymbol: 'ETH', amountIn: 25_000000n });23    expect(planOrder(agent({ maxOrderUsd: 500 }), { ETH: 1, LINK: 0 }, hold(100), px, 0, 0).order!.amountIn).toBe(50_000000n);24  });25  it('sells before it buys, and never more than it holds', () => {26    const p = planOrder(agent(), { ETH: 0, LINK: 0.4 }, hold(60, 0.01, 0), px, 0, 0); // 20 USD of ETH to sell, 32 USD of LINK to buy27    expect(p.order).toMatchObject({ fromSymbol: 'ETH', toSymbol: 'USDC', amountIn: 10n ** 16n });28  });29  it('does nothing at target, below the threshold, or once a daily limit is reached', () => {30    expect(planOrder(agent(), { ETH: 0.2, LINK: 0 }, hold(80, 0.01), px, 0, 0).order).toBeNull();31    expect(planOrder(agent(), { ETH: 0.21, LINK: 0 }, hold(80, 0.01), px, 0, 0).order).toBeNull(); // 1 USD away32    expect(planOrder(agent(), { ETH: 0.5, LINK: 0 }, hold(100), px, 99.5, 0).note).toMatch(/daily volume/);33    expect(planOrder(agent(), { ETH: 0.5, LINK: 0 }, hold(100), px, 0, 12).note).toMatch(/order count/);34    expect(planOrder(agent(), { ETH: 0.5, LINK: 0 }, hold(0), px, 0, 0).order).toBeNull();35  });36  it('limits an order to the room left in the daily volume', () => {37    expect(planOrder(agent(), { ETH: 0.5, LINK: 0 }, hold(100), px, 90, 3).order!.amountIn).toBe(10_000000n);38  });39});40
Files main
Menu

Theme