mirror of
https://github.com/Such-Software/hash-wallet-prices.git
synced 2026-10-17 05:51:28 +02:00
nonlogs: ignore markets with no 24h volume, like the cexswap leg already does
The cexswap leg drops pairs with no 7-day volume and says why: a stale last print on a dead pair must never drag the average. The nonlogs leg did not, and averaged WOW-USDT — a 0.00723 print with null volume — against a WOW-BTC route with real trades behind it. Today that pulls the nonlogs leg 15% low, and since the leg is half the WOW blend, the published price 8.4% low: 0.009830 against 0.010653. The drag grows as WOW moves away from whenever that print happened. Both routes are now volume-weighted by 24h quote volume, and a venue with no volume anywhere is omitted so the other venue's sample stands alone — the same shape as the cexswap function. This feed is what Hash Bags, Smirk, wowlet and the desk's own fair value display, so the number moves for all of them on deploy.
This commit is contained in:
+42
-16
@@ -3,7 +3,9 @@
|
|||||||
*
|
*
|
||||||
* One Cloudflare Worker that:
|
* One Cloudflare Worker that:
|
||||||
* - Fetches USD spot prices from Kraken for majors (BTC/XMR/LTC/DOGE/ETH/BCH/XNO).
|
* - Fetches USD spot prices from Kraken for majors (BTC/XMR/LTC/DOGE/ETH/BCH/XNO).
|
||||||
* - Fetches WOW from NonKYC (Wownero is too small for major CEX listings).
|
* - Fetches WOW from Nonlogs + cexswap.cc (too small for major CEX listings).
|
||||||
|
* Both legs ignore markets with no 24h volume: a stale last print is not a
|
||||||
|
* price, and this feed is what Hash Bags, Smirk and wowlet display.
|
||||||
* - Caches results in KV every 60s via cron, then serves from KV on request.
|
* - Caches results in KV every 60s via cron, then serves from KV on request.
|
||||||
*
|
*
|
||||||
* Endpoints:
|
* Endpoints:
|
||||||
@@ -110,9 +112,20 @@ async function fetchKraken(): Promise<Record<string, number>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pull all markets from Nonlogs in one call. Returns per-ticker USD price
|
* Pull all markets from Nonlogs in one call. Returns a per-ticker USD price,
|
||||||
* by averaging the (TICKER-BTC × BTC/USD) and (TICKER-USDT) routes when both
|
* volume-weighting the (TICKER-BTC × BTC/USD) and (TICKER-USDT) routes by
|
||||||
* exist. Algorithm taken from ~/src/smirk-backend/src/infra/prices.rs.
|
* their 24h quote volume.
|
||||||
|
*
|
||||||
|
* Routes with no 24h volume are ignored, for the same reason the cexswap
|
||||||
|
* function ignores them: `last_price` on a market nobody has traded is
|
||||||
|
* whatever the last person paid, whenever that was, and averaging it against
|
||||||
|
* a live route drags the published price. WOW-USDT here is the live example —
|
||||||
|
* a 0.00723 print with null volume against a WOW-BTC route trading at the
|
||||||
|
* equivalent of 0.00816, which pulled the Nonlogs leg about 6% low and, since
|
||||||
|
* the leg is half the WOW blend, every product's displayed price with it.
|
||||||
|
*
|
||||||
|
* If no route on this venue has volume, the ticker is omitted entirely and
|
||||||
|
* the cexswap sample stands alone.
|
||||||
*/
|
*/
|
||||||
async function fetchNonlogs(btcUsd: number): Promise<Record<string, number>> {
|
async function fetchNonlogs(btcUsd: number): Promise<Record<string, number>> {
|
||||||
const out: Record<string, number> = {};
|
const out: Record<string, number> = {};
|
||||||
@@ -123,23 +136,36 @@ async function fetchNonlogs(btcUsd: number): Promise<Record<string, number>> {
|
|||||||
cf: { cacheTtl: 30, cacheEverything: true },
|
cf: { cacheTtl: 30, cacheEverything: true },
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`nonlogs http ${res.status}`);
|
if (!res.ok) throw new Error(`nonlogs http ${res.status}`);
|
||||||
const body = (await res.json()) as { markets?: Record<string, { last_price?: string }> };
|
type NonlogsRow = { last_price?: string | null; quote_volume?: string | null };
|
||||||
|
const body = (await res.json()) as { markets?: Record<string, NonlogsRow> };
|
||||||
const markets = body.markets ?? {};
|
const markets = body.markets ?? {};
|
||||||
|
|
||||||
|
const num = (v: string | null | undefined): number => {
|
||||||
|
const n = parseFloat(v ?? "");
|
||||||
|
return Number.isFinite(n) ? n : 0;
|
||||||
|
};
|
||||||
|
|
||||||
for (const ticker of NICHE_TICKERS) {
|
for (const ticker of NICHE_TICKERS) {
|
||||||
const sources: number[] = [];
|
// [usd price, weight in USD] per route.
|
||||||
const btcPair = markets[`${ticker}-BTC`]?.last_price;
|
const samples: Array<[number, number]> = [];
|
||||||
if (btcPair) {
|
|
||||||
const p = parseFloat(btcPair);
|
const btcRow = markets[`${ticker}-BTC`];
|
||||||
if (p > 0) sources.push(p * btcUsd);
|
const btcPrice = num(btcRow?.last_price);
|
||||||
|
const btcVol = num(btcRow?.quote_volume); // volume in BTC
|
||||||
|
if (btcPrice > 0 && btcVol > 0) {
|
||||||
|
samples.push([btcPrice * btcUsd, btcVol * btcUsd]);
|
||||||
}
|
}
|
||||||
const usdtPair = markets[`${ticker}-USDT`]?.last_price;
|
|
||||||
if (usdtPair) {
|
const usdtRow = markets[`${ticker}-USDT`];
|
||||||
const p = parseFloat(usdtPair);
|
const usdtPrice = num(usdtRow?.last_price);
|
||||||
if (p > 0) sources.push(p);
|
const usdtVol = num(usdtRow?.quote_volume); // volume in USDT ≈ USD
|
||||||
|
if (usdtPrice > 0 && usdtVol > 0) {
|
||||||
|
samples.push([usdtPrice, usdtVol]);
|
||||||
}
|
}
|
||||||
if (sources.length) {
|
|
||||||
out[ticker] = sources.reduce((a, b) => a + b, 0) / sources.length;
|
if (samples.length) {
|
||||||
|
const weight = samples.reduce((a, [, w]) => a + w, 0);
|
||||||
|
out[ticker] = samples.reduce((a, [p, w]) => a + p * w, 0) / weight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
Reference in New Issue
Block a user