| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576 |
- import { describe, it, expect } from "vitest";
- import { RiskAllocator } from "../packages/registry/src/riskAllocator";
- import type { SymbolRuntimeState } from "../packages/registry/src/types";
- function state(params: {
- symbol: string;
- maxNotional: number;
- maxBase: number;
- score?: number;
- status?: SymbolRuntimeState["status"];
- }): SymbolRuntimeState {
- return {
- config: {
- symbol: params.symbol,
- maxNotional: params.maxNotional,
- maxBase: params.maxBase
- },
- status: params.status ?? "active",
- updatedAt: Date.now(),
- score: params.score
- };
- }
- describe("RiskAllocator", () => {
- it("allocates proportionally by score across active symbols", () => {
- const allocator = new RiskAllocator({ totalNotional: 1_000_000, totalBase: 10 });
- const allocations = allocator.allocate([
- state({ symbol: "BTC", maxNotional: 1_000_000, maxBase: 10, score: 2 }),
- state({ symbol: "ETH", maxNotional: 800_000, maxBase: 8, score: 1 }),
- state({ symbol: "SOL", maxNotional: 500_000, maxBase: 5, score: 0.5 })
- ]);
- expect(allocations.size).toBe(3);
- const btc = allocations.get("BTC");
- const eth = allocations.get("ETH");
- const sol = allocations.get("SOL");
- expect(btc).toBeDefined();
- expect(eth).toBeDefined();
- expect(sol).toBeDefined();
- expect(btc!.notionalLimit).toBeCloseTo(571_428.571, 2);
- expect(btc!.baseLimit).toBeCloseTo(5.714, 3);
- expect(eth!.notionalLimit).toBeCloseTo(285_714.285, 2);
- expect(eth!.baseLimit).toBeCloseTo(2.857, 3);
- expect(sol!.notionalLimit).toBeCloseTo(142_857.142, 2);
- expect(sol!.baseLimit).toBeCloseTo(1.428, 2);
- });
- it("omits inactive or zero-score symbols and enforces per-symbol limits", () => {
- const allocator = new RiskAllocator({ totalNotional: 100_000, totalBase: 10 });
- const allocations = allocator.allocate([
- state({ symbol: "BTC", maxNotional: 40_000, maxBase: 4, score: 10 }),
- state({ symbol: "ETH", maxNotional: 80_000, maxBase: 8, score: 5, status: "paused" }),
- state({ symbol: "SOL", maxNotional: 10_000, maxBase: 1, score: 0 })
- ]);
- expect(allocations.size).toBe(1);
- expect(allocations.get("BTC")).toEqual({ notionalLimit: 40_000, baseLimit: 4 });
- });
- it("respects minimum thresholds configured in options", () => {
- const allocator = new RiskAllocator(
- { totalNotional: 100_000, totalBase: 10 },
- { minNotionalPerSymbol: 30_000, minBasePerSymbol: 3 }
- );
- const allocations = allocator.allocate([
- state({ symbol: "BTC", maxNotional: 25_000, maxBase: 5, score: 1 }),
- state({ symbol: "ETH", maxNotional: 80_000, maxBase: 8, score: 3 })
- ]);
- expect(allocations.get("BTC")).toEqual({ notionalLimit: 25_000, baseLimit: 3 });
- expect(allocations.get("ETH")).toEqual({ notionalLimit: 75_000, baseLimit: 7.5 });
- });
- });
|