import test from 'node:test'; import assert from 'node:assert/strict'; import { evaluateTradeOpportunity } from '../src/core/strategy.mjs'; function makeConfig() { const tradingBtc = { assetId: 'nep141:btc.omft.near', symbol: 'BTC', decimals: 8, chain: 'btc:mainnet', }; const tradingEure = { assetId: 'nep141:eure.omft.near', symbol: 'EURe', decimals: 18, chain: 'eth:100', }; return { tradingBtc, tradingEure, activePair: `${tradingBtc.assetId}->${tradingEure.assetId}`, assetRegistry: new Map([ [tradingBtc.assetId, tradingBtc], [tradingEure.assetId, tradingEure], ]), strategyGrossThresholdPct: 2, strategyMaxNotionalEure: 5, strategyPriceMaxAgeMs: 30_000, strategyInventoryMaxAgeMs: 30_000, }; } function makePriceEvent(overrides = {}) { return { ingested_at: new Date('2026-04-02T10:00:00.000Z').toISOString(), payload: { price_id: 'price-1', pair: 'nep141:btc.omft.near->nep141:eure.omft.near', eur_per_btc: '100000.00000000', eure_per_btc: '100000.00000000', btc_per_eur: '0.000010000000', btc_per_eure: '0.000010000000', ...overrides, }, }; } function makeInventoryEvent(overrides = {}) { return { ingested_at: new Date('2026-04-02T10:00:00.000Z').toISOString(), payload: { inventory_id: 'inventory-1', spendable: { 'nep141:btc.omft.near': '1000000', 'nep141:eure.omft.near': '10000000000000000000', }, pending_inbound: { 'nep141:btc.omft.near': '0', 'nep141:eure.omft.near': '0', }, ...overrides, }, }; } test('strategy emits actionable exact-in BTC -> EURe command when armed and inventory is fresh', () => { const config = makeConfig(); const result = evaluateTradeOpportunity({ demandEvent: { payload: { quote_id: 'quote-1', pair: config.activePair, asset_in: config.tradingBtc.assetId, asset_out: config.tradingEure.assetId, request_kind: 'exact_in', amount_in: '5000', min_deadline_ms: '60000', }, }, priceEvent: makePriceEvent(), inventoryEvent: makeInventoryEvent(), config, armed: true, now: Date.parse('2026-04-02T10:00:05.000Z'), }); assert.equal(result.decision.decision, 'actionable'); assert.equal(result.decision.decision_reason, 'actionable'); assert.ok(result.command); assert.equal(result.command.quote_output.amount_out, '4900000000000000000'); }); test('strategy blocks when pending deposit exists but credited inventory is insufficient', () => { const config = makeConfig(); const result = evaluateTradeOpportunity({ demandEvent: { payload: { quote_id: 'quote-2', pair: config.activePair, asset_in: config.tradingBtc.assetId, asset_out: config.tradingEure.assetId, request_kind: 'exact_in', amount_in: '5000', min_deadline_ms: '60000', }, }, priceEvent: makePriceEvent(), inventoryEvent: makeInventoryEvent({ spendable: { 'nep141:btc.omft.near': '1000000', 'nep141:eure.omft.near': '1000000000000000000', }, pending_inbound: { 'nep141:btc.omft.near': '0', 'nep141:eure.omft.near': '5000000000000000000', }, }), config, armed: true, now: Date.parse('2026-04-02T10:00:05.000Z'), }); assert.equal(result.decision.decision, 'rejected'); assert.equal(result.decision.decision_reason, 'pending_deposit_not_credited'); assert.equal(result.command, undefined); }); test('strategy blocks stale prices before command emission', () => { const config = makeConfig(); const result = evaluateTradeOpportunity({ demandEvent: { payload: { quote_id: 'quote-3', pair: config.activePair, asset_in: config.tradingEure.assetId, asset_out: config.tradingBtc.assetId, request_kind: 'exact_out', amount_out: '10000', min_deadline_ms: '60000', }, }, priceEvent: makePriceEvent(), inventoryEvent: makeInventoryEvent(), config, armed: true, now: Date.parse('2026-04-02T10:00:45.000Z'), }); assert.equal(result.decision.decision_reason, 'stale_reference_price'); assert.equal(result.command, undefined); });