unrip/test/strategy.test.mjs
philipp 365acf7b7f
All checks were successful
deploy / deploy (push) Successful in 46s
Add maker timing competitiveness truth
Proof: quote-to-relay maker timing now propagates through ingest, normalized quotes, strategy decisions, commands, executor results, quote outcomes, lifecycle rows, dashboard summaries, and runtime alerts; relay failures preserve original text while classifying quote_not_found_or_finished; targeted tests, full npm test, and operator dashboard build passed before commit.

Assumptions: response-age policy stays disabled by default and is only activated through DB-backed pair strategy config after operators review timing evidence; unrelated pre-existing dirty worktree files were left unstaged.

Still fake: relay acceptance is not settlement or realized PnL; live policy thresholds still require post-deploy evidence before enabling skips for production pairs.
2026-05-18 23:47:52 +02:00

488 lines
15 KiB
JavaScript

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_at, '2026-04-02T10:00:05.000Z');
assert.equal(result.decision.decision_reason, 'actionable');
assert.ok(result.command);
assert.equal(result.command.decision_at, '2026-04-02T10:00:05.000Z');
assert.equal(result.command.direction, 'btc_to_eure');
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, 'base_to_quote');
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.decision.eure_notional, null);
assert.equal(result.command.direction, 'base_to_quote');
assert.equal(result.command.decision_at, '2026-04-02T10:00:05.000Z');
assert.equal(result.command.max_notional_eure, null);
assert.equal(result.command.quote_output.amount_out, '7960800');
assert.equal(result.command.notional_symbol, 'USDC');
assert.equal(result.command.expected_inventory_delta_units[config.tradingBtc.assetId], '10000');
assert.equal(result.command.expected_inventory_delta_units[config.tradingUsdc.assetId], '-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);
});
test('maker response age policy skips stale BTC -> USDC quotes without emitting a relay command', () => {
const config = makeBtcUsdcDbConfig({
strategyConfigOverrides: {
makerMaxQuoteAgeEnabled: true,
makerMaxQuoteAgeMs: 100,
makerLatencyPolicyReason: 'test stale response protection',
},
});
const result = evaluateTradeOpportunity({
demandEvent: {
payload: {
quote_id: 'quote-usdc-too-old',
pair: config.activePair,
asset_in: config.tradingBtc.assetId,
asset_out: config.tradingUsdc.assetId,
request_kind: 'exact_in',
amount_in: '10000',
maker_timing: {
quote_received_at: '2026-04-02T10:00:04.800Z',
},
},
},
priceEvent: makeBtcUsdcPriceEvent(),
inventoryEvent: makeBtcUsdcInventoryEvent(),
config,
armed: true,
strategyReceivedAt: '2026-04-02T10:00:05.000Z',
now: Date.parse('2026-04-02T10:00:05.000Z'),
});
assert.equal(result.decision.decision, 'blocked');
assert.equal(result.decision.decision_reason, 'maker_quote_too_old');
assert.equal(result.decision.quote_age_at_decision_ms, 200);
assert.equal(result.decision.response_policy.enabled, true);
assert.equal(result.decision.response_policy.max_quote_age_ms, 100);
assert.equal(result.command, undefined);
});
test('maker response age policy skips when quote timing prerequisites are missing', () => {
const config = makeBtcUsdcDbConfig({
strategyConfigOverrides: {
makerMaxQuoteAgeEnabled: true,
makerMaxQuoteAgeMs: 100,
},
});
const result = evaluateTradeOpportunity({
demandEvent: {
payload: {
quote_id: 'quote-usdc-no-timing',
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,
strategyReceivedAt: '2026-04-02T10:00:05.000Z',
now: Date.parse('2026-04-02T10:00:05.000Z'),
});
assert.equal(result.decision.decision, 'blocked');
assert.equal(result.decision.decision_reason, 'maker_quote_age_unavailable');
assert.equal(result.decision.response_policy.valid, true);
assert.equal(result.command, undefined);
});
test('maker response age policy skips invalid enabled config', () => {
const config = makeBtcUsdcDbConfig({
strategyConfigOverrides: {
makerMaxQuoteAgeEnabled: true,
makerMaxQuoteAgeMs: null,
},
});
const result = evaluateTradeOpportunity({
demandEvent: {
payload: {
quote_id: 'quote-usdc-invalid-policy',
pair: config.activePair,
asset_in: config.tradingBtc.assetId,
asset_out: config.tradingUsdc.assetId,
request_kind: 'exact_in',
amount_in: '10000',
maker_timing: {
quote_received_at: '2026-04-02T10:00:04.950Z',
},
},
},
priceEvent: makeBtcUsdcPriceEvent(),
inventoryEvent: makeBtcUsdcInventoryEvent(),
config,
armed: true,
strategyReceivedAt: '2026-04-02T10:00:05.000Z',
now: Date.parse('2026-04-02T10:00:05.000Z'),
});
assert.equal(result.decision.decision, 'blocked');
assert.equal(result.decision.decision_reason, 'maker_quote_response_policy_invalid');
assert.equal(result.decision.response_policy.valid, false);
assert.equal(result.command, undefined);
});
test('disabled maker response age policy preserves current BTC -> USDC actionability', () => {
const config = makeBtcUsdcDbConfig({
strategyConfigOverrides: {
makerMaxQuoteAgeEnabled: false,
makerMaxQuoteAgeMs: null,
},
});
const result = evaluateTradeOpportunity({
demandEvent: {
payload: {
quote_id: 'quote-usdc-policy-disabled',
pair: config.activePair,
asset_in: config.tradingBtc.assetId,
asset_out: config.tradingUsdc.assetId,
request_kind: 'exact_in',
amount_in: '10000',
maker_timing: {
quote_received_at: '2026-04-02T09:59:59.000Z',
},
},
},
priceEvent: makeBtcUsdcPriceEvent(),
inventoryEvent: makeBtcUsdcInventoryEvent(),
config,
armed: true,
strategyReceivedAt: '2026-04-02T10:00:05.000Z',
now: Date.parse('2026-04-02T10:00:05.000Z'),
});
assert.equal(result.decision.decision, 'actionable');
assert.equal(result.decision.response_policy.enabled, false);
assert.ok(result.command);
});
function makeBtcUsdcDbConfig({ strategyConfigOverrides = {} } = {}) {
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',
makerMaxQuoteAgeEnabled: false,
makerMaxQuoteAgeMs: null,
makerLatencyPolicyReason: null,
...strategyConfigOverrides,
};
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,
},
};
}