Some checks failed
deploy / deploy (push) Failing after 40s
Proof: targeted pair-native strategy, preflight, outcome, dashboard, and ops tests pass; full npm test passes 237/237; operator dashboard production bundle builds; ops watcher Python test passes. Assumptions: DB asset, pair, strategy config, and price route rows remain canonical; legacy EURe fields stay only for old-row/API compatibility; local shell has no Kubernetes context for direct live namespace recheck. Still fake: venue-native terminal fill ids and realized fee/PnL attribution remain unavailable; live deployment verification must happen through the repo workflow because manual cluster repair is out of scope.
345 lines
10 KiB
JavaScript
345 lines
10 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);
|
|
});
|
|
|
|
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,
|
|
},
|
|
};
|
|
}
|