fix(prices): a last trade needs volume behind it before it can price a coin

This morning we stopped a one-cent pool on cexswap from pricing WOW. The same
rule was missing on the nonlogs last-trade path, where the test was volume
greater than zero, which one print satisfies.

Today it cost us the desk. We sold 209 WOW by hand at 0.0361, near four times
fair, into a bid standing above our whole ask ladder. That print became
WOW-USDT's last trade. Its book is 42% wide, so the midpoint was refused and
the print stood in for it, and the route carried $12 of daily volume of which
$7.56 was that one trade. WOW marked 15.6 sat against a consensus near 10.7,
and wowdesk's divergence rail then held every WOW market it makes, correctly:
the feed was quoting our own trade back at us.

A last trade must now clear $25 of 24h volume, the same floor cexswap got and
the same one the desk uses for a consensus venue. A live book keeps needing no
floor, because it is somebody else's standing offer and has already passed
both-sides, ask-above-bid and the spread limit.

Verified against the live payloads, since this repo has no test framework:
WOW-BTC prices from its book at $0.00918 with $105 of volume and is unchanged.
WOW-USDT drops, having $12. The blended mark moves 15.63 sat to 12.00, and
divergence against the desk's consensus moves 33.0% to 2.1%, inside its 8%
limit, so WOW can quote again.
This commit is contained in:
jwinterm
2026-09-17 20:59:04 -04:00
parent 7ae7c48f12
commit 81fbbb6d40
+21 -2
View File
@@ -131,6 +131,25 @@ const MAX_BOOK_SPREAD_PCT = 35;
*/ */
const MAX_LAST_TRADE_AGE_HOURS = 12; const MAX_LAST_TRADE_AGE_HOURS = 12;
/**
* A route priced from a LAST TRADE, rather than from a live book, has to clear
* this much 24h volume to vote. `> 0` was the old rule and it is not a rule at
* all: one print qualifies.
*
* 2026-09-17 is the case for it. We sold 209 WOW by hand at 0.0361, near four
* times fair, into a bid that stood above our whole ask ladder. That print
* became WOW-USDT's last trade; its book is 42% wide so the midpoint was
* refused and the print stood in for it; and the route carried $12 of daily
* volume, of which $7.56 was that trade. The mark went to 15.6 sat against a
* consensus near 10.7, and the desk's own divergence rail then held every WOW
* market it makes. A feed that reports our own trade back to us as the market
* is the reflexivity this whole file exists to avoid.
*
* A live book needs no such floor: it is a standing offer from somebody else,
* and it already passed both-sides, ask-above-bid and the spread limit.
*/
const MIN_LAST_TRADE_VOL_USD = 25;
/** /**
* Weight given to a route priced from a live two-sided book that reports no * Weight given to a route priced from a live two-sided book that reports no
* volume. It has to be non-zero or the sample divides into nothing and counts * volume. It has to be non-zero or the sample divides into nothing and counts
@@ -260,7 +279,7 @@ async function fetchNonlogs(btcUsd: number): Promise<Record<string, number>> {
const btcRow = markets[`${ticker}-BTC`]; const btcRow = markets[`${ticker}-BTC`];
const { price: btcPrice, fromBook: btcFromBook } = priced(btcRow); const { price: btcPrice, fromBook: btcFromBook } = priced(btcRow);
const btcVol = num(btcRow?.quote_volume); // volume in BTC const btcVol = num(btcRow?.quote_volume); // volume in BTC
if (btcPrice > 0 && (btcVol > 0 || btcFromBook)) { if (btcPrice > 0 && (btcVol * btcUsd >= MIN_LAST_TRADE_VOL_USD || btcFromBook)) {
samples.push([ samples.push([
btcPrice * btcUsd, btcPrice * btcUsd,
Math.max(btcVol * btcUsd, btcFromBook ? BOOK_ONLY_WEIGHT_USD : 0), Math.max(btcVol * btcUsd, btcFromBook ? BOOK_ONLY_WEIGHT_USD : 0),
@@ -270,7 +289,7 @@ async function fetchNonlogs(btcUsd: number): Promise<Record<string, number>> {
const usdtRow = markets[`${ticker}-USDT`]; const usdtRow = markets[`${ticker}-USDT`];
const { price: usdtPrice, fromBook: usdtFromBook } = priced(usdtRow); const { price: usdtPrice, fromBook: usdtFromBook } = priced(usdtRow);
const usdtVol = num(usdtRow?.quote_volume); // volume in USDT ≈ USD const usdtVol = num(usdtRow?.quote_volume); // volume in USDT ≈ USD
if (usdtPrice > 0 && (usdtVol > 0 || usdtFromBook)) { if (usdtPrice > 0 && (usdtVol >= MIN_LAST_TRADE_VOL_USD || usdtFromBook)) {
samples.push([ samples.push([
usdtPrice, usdtPrice,
Math.max(usdtVol, usdtFromBook ? BOOK_ONLY_WEIGHT_USD : 0), Math.max(usdtVol, usdtFromBook ? BOOK_ONLY_WEIGHT_USD : 0),