All checks were successful
deploy / deploy (push) Successful in 1m3s
Proof: Strategy tests now cover USDC -> BTC maker responses using BTC inventory with zero USDC, pending outbound units are subtracted before approval, lifecycle rows expose maker send/receive terms and inventory check details, targeted dashboard tests pass, full npm test passes, and the operator dashboard bundle builds. Assumptions: pending_outbound in inventory snapshots represents units unavailable for new maker commitments; this change does not skip quotes because of relay-error risk and does not loosen edge, notional, arming, pair enablement, stale price, or stale inventory checks. Still fake: relay acceptance is still only submission evidence; venue-native terminal fill ids and fee-complete realized PnL remain unavailable.
657 lines
21 KiB
JavaScript
657 lines
21 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 uses BTC inventory, not USDC inventory, for USDC -> BTC maker responses', () => {
|
|
const config = makeBtcUsdcDbConfig({ includeReversePair: true });
|
|
const reversePair = `${config.tradingUsdc.assetId}->${config.tradingBtc.assetId}`;
|
|
const result = evaluateTradeOpportunity({
|
|
demandEvent: {
|
|
payload: {
|
|
quote_id: 'quote-usdc-to-btc-inventory-side',
|
|
pair: reversePair,
|
|
asset_in: config.tradingUsdc.assetId,
|
|
asset_out: config.tradingBtc.assetId,
|
|
request_kind: 'exact_in',
|
|
amount_in: '8000000',
|
|
min_deadline_ms: '60000',
|
|
},
|
|
},
|
|
priceEvent: makeBtcUsdcPriceEvent(),
|
|
inventoryEvent: makeBtcUsdcInventoryEvent({
|
|
spendable: {
|
|
[config.tradingBtc.assetId]: '1000000',
|
|
[config.tradingUsdc.assetId]: '0',
|
|
},
|
|
}),
|
|
config,
|
|
armed: true,
|
|
now: Date.parse('2026-04-02T10:00:05.000Z'),
|
|
});
|
|
|
|
assert.equal(result.decision.decision, 'actionable');
|
|
assert.equal(result.decision.direction, 'quote_to_base');
|
|
assert.equal(result.decision.inventory_asset, config.tradingBtc.assetId);
|
|
assert.equal(result.decision.inventory_available, '1000000');
|
|
assert.equal(result.command.expected_inventory_delta_units[config.tradingBtc.assetId], '-9951');
|
|
assert.equal(result.command.expected_inventory_delta_units[config.tradingUsdc.assetId], '8000000');
|
|
});
|
|
|
|
test('strategy subtracts pending outbound from maker inventory before approving USDC -> BTC responses', () => {
|
|
const config = makeBtcUsdcDbConfig({ includeReversePair: true });
|
|
const reversePair = `${config.tradingUsdc.assetId}->${config.tradingBtc.assetId}`;
|
|
const result = evaluateTradeOpportunity({
|
|
demandEvent: {
|
|
payload: {
|
|
quote_id: 'quote-usdc-to-btc-pending-outbound',
|
|
pair: reversePair,
|
|
asset_in: config.tradingUsdc.assetId,
|
|
asset_out: config.tradingBtc.assetId,
|
|
request_kind: 'exact_in',
|
|
amount_in: '8000000',
|
|
min_deadline_ms: '60000',
|
|
},
|
|
},
|
|
priceEvent: makeBtcUsdcPriceEvent(),
|
|
inventoryEvent: makeBtcUsdcInventoryEvent({
|
|
spendable: {
|
|
[config.tradingBtc.assetId]: '9951',
|
|
[config.tradingUsdc.assetId]: '0',
|
|
},
|
|
pending_outbound: {
|
|
[config.tradingBtc.assetId]: '1',
|
|
[config.tradingUsdc.assetId]: '0',
|
|
},
|
|
}),
|
|
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_outbound_reserved');
|
|
assert.equal(result.decision.inventory_required, '9951');
|
|
assert.equal(result.decision.inventory_spendable, '9951');
|
|
assert.equal(result.decision.inventory_pending_outbound, '1');
|
|
assert.equal(result.decision.inventory_available, '9950');
|
|
assert.equal(result.command, undefined);
|
|
});
|
|
|
|
test('strategy rejects dust exact-out BTC -> USDC below configured min notional after integer rounding', () => {
|
|
const config = makeBtcUsdcDbConfig({
|
|
strategyConfigOverrides: {
|
|
edgeBps: 20,
|
|
minNotional: '1',
|
|
maxNotional: '400',
|
|
},
|
|
});
|
|
const result = evaluateTradeOpportunity({
|
|
demandEvent: {
|
|
payload: {
|
|
quote_id: 'quote-usdc-dust-exact-out',
|
|
pair: config.activePair,
|
|
asset_in: config.tradingBtc.assetId,
|
|
asset_out: config.tradingUsdc.assetId,
|
|
request_kind: 'exact_out',
|
|
amount_out: '2016',
|
|
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, 'rejected');
|
|
assert.equal(result.decision.decision_reason, 'min_notional_not_met');
|
|
assert.equal(result.decision.edge_bps, '20');
|
|
assert.equal(result.decision.threshold_pct, '0.2');
|
|
assert.equal(result.decision.notional, '0.002016');
|
|
assert.equal(result.decision.min_notional, '1');
|
|
assert.equal(result.decision.proposed_amount_in, '3');
|
|
assert.ok(Number(result.decision.gross_edge_pct) >= Number(result.decision.threshold_pct));
|
|
assert.equal(result.command, undefined);
|
|
});
|
|
|
|
test('strategy emits exact-out BTC -> USDC command at min notional only when rounded edge clears threshold', () => {
|
|
const config = makeBtcUsdcDbConfig({
|
|
strategyConfigOverrides: {
|
|
edgeBps: 20,
|
|
minNotional: '1',
|
|
maxNotional: '400',
|
|
},
|
|
});
|
|
const result = evaluateTradeOpportunity({
|
|
demandEvent: {
|
|
payload: {
|
|
quote_id: 'quote-usdc-min-exact-out',
|
|
pair: config.activePair,
|
|
asset_in: config.tradingBtc.assetId,
|
|
asset_out: config.tradingUsdc.assetId,
|
|
request_kind: 'exact_out',
|
|
amount_out: '1000000',
|
|
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.decision_reason, 'actionable');
|
|
assert.equal(result.decision.notional, '1.000000');
|
|
assert.equal(result.decision.min_notional, '1');
|
|
assert.equal(result.command.quote_output.amount_in, '1253');
|
|
assert.ok(Number(result.decision.gross_edge_pct) >= Number(result.decision.threshold_pct));
|
|
});
|
|
|
|
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 = {}, includeReversePair = false } = {}) {
|
|
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 reversePair = `${tradingUsdc.assetId}->${tradingBtc.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,
|
|
};
|
|
const reverseStrategyConfig = {
|
|
...strategyConfig,
|
|
configId: `${reversePair}:v1`,
|
|
};
|
|
const reversePairConfig = {
|
|
...pair,
|
|
pairId: reversePair,
|
|
key: reversePair,
|
|
assetIn: tradingUsdc,
|
|
assetOut: tradingBtc,
|
|
asset_in: tradingUsdc.assetId,
|
|
asset_out: tradingBtc.assetId,
|
|
strategyConfig: reverseStrategyConfig,
|
|
};
|
|
const pairEntries = [[activePair, pair]];
|
|
if (includeReversePair) pairEntries.push([reversePair, reversePairConfig]);
|
|
|
|
return {
|
|
tradingBtc,
|
|
tradingUsdc,
|
|
activePair,
|
|
ok: true,
|
|
tradingConfigLoaded: true,
|
|
requireDbTradingConfig: true,
|
|
assetRegistry: new Map([
|
|
[tradingBtc.assetId, tradingBtc],
|
|
[tradingUsdc.assetId, tradingUsdc],
|
|
]),
|
|
pairByKey: new Map(pairEntries),
|
|
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',
|
|
},
|
|
pending_outbound: {
|
|
'nep141:nbtc.bridge.near': '0',
|
|
'nep141:gnosis-0x2a22f9c3b484c3629090feed35f17ff8f88f76f0.omft.near': '0',
|
|
},
|
|
...overrides,
|
|
},
|
|
};
|
|
}
|