mirror of
https://github.com/Such-Software/hash-wallet-prices.git
synced 2026-10-17 05:51:28 +02:00
cexswap: volume-weight WOW price, ignore dead pairs
The naive mean across all WOW pairs let stale prints on zero-volume markets (WOW-ETH last traded at $0.000087) drag the cexswap component ~40% below its live markets, and the published WOW rate ~20% under fair. Weight by volume7d_usd and drop pairs with no 7-day volume; if every pair is dead the ticker falls back to the Nonlogs sample alone.
This commit is contained in:
+18
-12
@@ -116,9 +116,11 @@ async function fetchNonlogs(btcUsd: number): Promise<Record<string, number>> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Pull all spot markets from cexswap.cc. Endpoint already returns `last_usd`
|
* Pull all spot markets from cexswap.cc. Endpoint already returns `last_usd`
|
||||||
* per pair so no BTC conversion is needed. We average across all spot pairs
|
* per pair so no BTC conversion is needed. Prices are volume-weighted across
|
||||||
* for the same base ticker (e.g. WOW/USDT, WOW/BTC, WOW/XMR all converted to
|
* the base ticker's pairs by 7-day USD volume, and pairs with no 7-day volume
|
||||||
* USD by cexswap upstream) for robustness.
|
* are ignored entirely — a stale `last` print on a dead pair (WOW-ETH's
|
||||||
|
* ancient $0.000087, for example) must never drag the average. If every pair
|
||||||
|
* is dead the ticker is simply omitted and the Nonlogs sample stands alone.
|
||||||
*/
|
*/
|
||||||
async function fetchCexswap(): Promise<Record<string, number>> {
|
async function fetchCexswap(): Promise<Record<string, number>> {
|
||||||
const out: Record<string, number> = {};
|
const out: Record<string, number> = {};
|
||||||
@@ -127,22 +129,26 @@ async function fetchCexswap(): Promise<Record<string, number>> {
|
|||||||
cf: { cacheTtl: 30, cacheEverything: true },
|
cf: { cacheTtl: 30, cacheEverything: true },
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`cexswap http ${res.status}`);
|
if (!res.ok) throw new Error(`cexswap http ${res.status}`);
|
||||||
const body = (await res.json()) as
|
type CexswapRow = { base?: string; last_usd?: number | string; volume7d_usd?: number | string };
|
||||||
| { items?: Array<{ base?: string; last_usd?: number | string }> }
|
const body = (await res.json()) as { items?: CexswapRow[] } | CexswapRow[];
|
||||||
| Array<{ base?: string; last_usd?: number | string }>;
|
|
||||||
const items = Array.isArray(body) ? body : (body.items ?? []);
|
const items = Array.isArray(body) ? body : (body.items ?? []);
|
||||||
|
|
||||||
const buckets: Record<string, number[]> = {};
|
const num = (v: number | string | undefined): number =>
|
||||||
|
typeof v === "string" ? parseFloat(v) : (v ?? 0);
|
||||||
|
|
||||||
|
const buckets: Record<string, Array<{ usd: number; weight: number }>> = {};
|
||||||
for (const m of items) {
|
for (const m of items) {
|
||||||
const base = (m.base ?? "").toUpperCase();
|
const base = (m.base ?? "").toUpperCase();
|
||||||
if (!NICHE_TICKERS.includes(base as (typeof NICHE_TICKERS)[number])) continue;
|
if (!NICHE_TICKERS.includes(base as (typeof NICHE_TICKERS)[number])) continue;
|
||||||
const usd = typeof m.last_usd === "string" ? parseFloat(m.last_usd) : m.last_usd;
|
const usd = num(m.last_usd);
|
||||||
if (usd && usd > 0) {
|
const weight = num(m.volume7d_usd);
|
||||||
(buckets[base] ??= []).push(usd);
|
if (usd > 0 && weight > 0) {
|
||||||
|
(buckets[base] ??= []).push({ usd, weight });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [ticker, prices] of Object.entries(buckets)) {
|
for (const [ticker, samples] of Object.entries(buckets)) {
|
||||||
out[ticker] = prices.reduce((a, b) => a + b, 0) / prices.length;
|
const totalWeight = samples.reduce((a, s) => a + s.weight, 0);
|
||||||
|
out[ticker] = samples.reduce((a, s) => a + s.usd * s.weight, 0) / totalWeight;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user