riskAllocatorAllocation.test.ts 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { describe, it, expect } from "vitest";
  2. import { RiskAllocator } from "../packages/registry/src/riskAllocator";
  3. import type { SymbolRuntimeState } from "../packages/registry/src/types";
  4. function state(params: {
  5. symbol: string;
  6. maxNotional: number;
  7. maxBase: number;
  8. score?: number;
  9. status?: SymbolRuntimeState["status"];
  10. }): SymbolRuntimeState {
  11. return {
  12. config: {
  13. symbol: params.symbol,
  14. maxNotional: params.maxNotional,
  15. maxBase: params.maxBase
  16. },
  17. status: params.status ?? "active",
  18. updatedAt: Date.now(),
  19. score: params.score
  20. };
  21. }
  22. describe("RiskAllocator", () => {
  23. it("allocates proportionally by score across active symbols", () => {
  24. const allocator = new RiskAllocator({ totalNotional: 1_000_000, totalBase: 10 });
  25. const allocations = allocator.allocate([
  26. state({ symbol: "BTC", maxNotional: 1_000_000, maxBase: 10, score: 2 }),
  27. state({ symbol: "ETH", maxNotional: 800_000, maxBase: 8, score: 1 }),
  28. state({ symbol: "SOL", maxNotional: 500_000, maxBase: 5, score: 0.5 })
  29. ]);
  30. expect(allocations.size).toBe(3);
  31. const btc = allocations.get("BTC");
  32. const eth = allocations.get("ETH");
  33. const sol = allocations.get("SOL");
  34. expect(btc).toBeDefined();
  35. expect(eth).toBeDefined();
  36. expect(sol).toBeDefined();
  37. expect(btc!.notionalLimit).toBeCloseTo(571_428.571, 2);
  38. expect(btc!.baseLimit).toBeCloseTo(5.714, 3);
  39. expect(eth!.notionalLimit).toBeCloseTo(285_714.285, 2);
  40. expect(eth!.baseLimit).toBeCloseTo(2.857, 3);
  41. expect(sol!.notionalLimit).toBeCloseTo(142_857.142, 2);
  42. expect(sol!.baseLimit).toBeCloseTo(1.428, 2);
  43. });
  44. it("omits inactive or zero-score symbols and enforces per-symbol limits", () => {
  45. const allocator = new RiskAllocator({ totalNotional: 100_000, totalBase: 10 });
  46. const allocations = allocator.allocate([
  47. state({ symbol: "BTC", maxNotional: 40_000, maxBase: 4, score: 10 }),
  48. state({ symbol: "ETH", maxNotional: 80_000, maxBase: 8, score: 5, status: "paused" }),
  49. state({ symbol: "SOL", maxNotional: 10_000, maxBase: 1, score: 0 })
  50. ]);
  51. expect(allocations.size).toBe(1);
  52. expect(allocations.get("BTC")).toEqual({ notionalLimit: 40_000, baseLimit: 4 });
  53. });
  54. it("respects minimum thresholds configured in options", () => {
  55. const allocator = new RiskAllocator(
  56. { totalNotional: 100_000, totalBase: 10 },
  57. { minNotionalPerSymbol: 30_000, minBasePerSymbol: 3 }
  58. );
  59. const allocations = allocator.allocate([
  60. state({ symbol: "BTC", maxNotional: 25_000, maxBase: 5, score: 1 }),
  61. state({ symbol: "ETH", maxNotional: 80_000, maxBase: 8, score: 3 })
  62. ]);
  63. expect(allocations.get("BTC")).toEqual({ notionalLimit: 25_000, baseLimit: 3 });
  64. expect(allocations.get("ETH")).toEqual({ notionalLimit: 75_000, baseLimit: 7.5 });
  65. });
  66. });