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); }); test('strategy emits actionable exact-in BTC -> USDC command from DB price route', () => { const config = makeBtcUsdcDbConfig(); const result = evaluateTradeOpportunity({ demandEvent: { payload: { quote_id: 'quote-usdc-1', pair: config.activePair, asset_in: config.tradingBtc.assetId, asset_out: config.tradingUsdc.assetId, request_kind: 'exact_in', amount_in: '10000', min_deadline_ms: '60000', }, }, priceEvent: makeBtcUsdcPriceEvent(), inventoryEvent: makeBtcUsdcInventoryEvent(), config, armed: true, now: Date.parse('2026-04-02T10:00:05.000Z'), }); assert.equal(result.decision.decision, 'actionable'); assert.equal(result.decision.direction, 'btc_to_usdc'); assert.equal(result.decision.edge_bps, '49'); assert.equal(result.decision.price_route_id, 'btc-usdc:v1'); assert.equal(result.decision.notional, '8.000000'); assert.equal(result.decision.notional_symbol, 'USDC'); assert.equal(result.command.quote_output.amount_out, '7960800'); assert.equal(result.command.asset_out_decimals, 6); }); test('strategy blocks BTC -> USDC when route-specific reference price is stale', () => { const config = makeBtcUsdcDbConfig(); const result = evaluateTradeOpportunity({ demandEvent: { payload: { quote_id: 'quote-usdc-stale', pair: config.activePair, asset_in: config.tradingBtc.assetId, asset_out: config.tradingUsdc.assetId, request_kind: 'exact_in', amount_in: '10000', }, }, priceEvent: makeBtcUsdcPriceEvent(), inventoryEvent: makeBtcUsdcInventoryEvent(), 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); }); test('strategy blocks BTC -> USDC when price event lacks USDC route fields', () => { const config = makeBtcUsdcDbConfig(); const result = evaluateTradeOpportunity({ demandEvent: { payload: { quote_id: 'quote-usdc-missing-price', pair: config.activePair, asset_in: config.tradingBtc.assetId, asset_out: config.tradingUsdc.assetId, request_kind: 'exact_in', amount_in: '10000', }, }, priceEvent: makePriceEvent(), inventoryEvent: makeBtcUsdcInventoryEvent(), config, armed: true, now: Date.parse('2026-04-02T10:00:05.000Z'), }); assert.equal(result.decision.decision_reason, 'reference_price_missing'); assert.equal(result.command, undefined); }); function makeBtcUsdcDbConfig() { const tradingBtc = { assetId: 'nep141:nbtc.bridge.near', symbol: 'BTC', decimals: 8, chain: 'btc:mainnet', }; const tradingUsdc = { assetId: 'nep141:gnosis-0x2a22f9c3b484c3629090feed35f17ff8f88f76f0.omft.near', symbol: 'USDC', decimals: 6, chain: 'gnosis', }; const activePair = `${tradingBtc.assetId}->${tradingUsdc.assetId}`; const strategyConfig = { configId: `${activePair}:v1`, version: 1, edgeBps: 49, maxNotional: '150', priceMaxAgeMs: 30_000, inventoryMaxAgeMs: 30_000, minNotional: '0', }; const priceRoute = { routeId: 'btc-usdc:v1', source: 'btc_usdc_reference', baseAssetId: tradingBtc.assetId, quoteAssetId: tradingUsdc.assetId, }; const pair = { pairId: activePair, key: activePair, assetIn: tradingBtc, assetOut: tradingUsdc, asset_in: tradingBtc.assetId, asset_out: tradingUsdc.assetId, enabled: true, observeEnabled: true, makerEnabled: true, takerEnabled: false, canTrade: true, blockReason: null, strategyConfig, priceRoute, }; return { tradingBtc, tradingUsdc, activePair, ok: true, tradingConfigLoaded: true, requireDbTradingConfig: true, assetRegistry: new Map([ [tradingBtc.assetId, tradingBtc], [tradingUsdc.assetId, tradingUsdc], ]), pairByKey: new Map([[activePair, pair]]), strategyGrossThresholdPct: 0.49, strategyMaxNotionalEure: 150, strategyPriceMaxAgeMs: 30_000, strategyInventoryMaxAgeMs: 30_000, }; } function makeBtcUsdcPriceEvent(overrides = {}) { return { ingested_at: new Date('2026-04-02T10:00:00.000Z').toISOString(), payload: { price_id: 'price-usdc-1', pair: 'nep141:nbtc.bridge.near->nep141:gnosis-0x2a22f9c3b484c3629090feed35f17ff8f88f76f0.omft.near', price_route_id: 'btc-usdc:v1', eur_per_btc: '75000.00000000', eure_per_btc: '75000.00000000', btc_per_eur: '0.000013333333', btc_per_eure: '0.000013333333', usd_per_btc: '80000.00000000', usdc_per_btc: '80000.00000000', btc_per_usd: '0.000012500000', btc_per_usdc: '0.000012500000', ...overrides, }, }; } function makeBtcUsdcInventoryEvent(overrides = {}) { return { ingested_at: new Date('2026-04-02T10:00:00.000Z').toISOString(), payload: { inventory_id: 'inventory-usdc-1', spendable: { 'nep141:nbtc.bridge.near': '1000000', 'nep141:gnosis-0x2a22f9c3b484c3629090feed35f17ff8f88f76f0.omft.near': '1000000000', }, pending_inbound: { 'nep141:nbtc.bridge.near': '0', 'nep141:gnosis-0x2a22f9c3b484c3629090feed35f17ff8f88f76f0.omft.near': '0', }, ...overrides, }, }; }