SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%

Ask vs RIV: auction bids are not asks — excluded from discounts, min ask and market depth (Yahoo! Auctions ¥1 opening bids surfaced as −90 % deals)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed 13 days ago (Sep 11, 2026) parent 4a63bb9

2 changed files +6 −3

modified apps/web/src/lib/queries/depth.ts +1 −1
@@ -28,7 +28,7 @@ export const getAssetDepth = cache(async (assetId: string, variantId: string | n
28 28 const scope: AssetDepth['scope'] = target && !single ? 'variant' : 'asset';
29 29 const asks = await rows<{ price_usd: unknown }>(sql`
30 30 SELECT l.price_usd FROM listings l
31 − WHERE l.asset_id = ${assetId} AND l.availability = 'available' AND l.price_usd > 0
31 + WHERE l.asset_id = ${assetId} AND l.availability = 'available' AND l.price_usd > 0 AND l.listing_type <> 'auction'
32 32 ${scope === 'variant' ? sql`AND l.variant_id = ${target}` : sql``}
33 33 AND NOT (l.grader IS NOT NULL AND l.grade IS NULL)`);
34 34 const depth = marketDepth(asks.map((a) => num(a.price_usd)), riv);
modified workers/valuation/run.ts +5 −2
@@ -151,7 +151,9 @@ export async function valueAsset(assetId: string, opts: { now?: Date; rebuildHis
151 151 if (out.riv !== null && out.basis === 'transactions' && out.confidence >= ASK_MIN_CONFIDENCE && out.sampleSize >= ASK_MIN_SAMPLE) {
152 152 const lo = out.riv * ASK_ANOMALY_LOW_RATIO;
153 153 const hi = out.riv * ASK_ANOMALY_HIGH_RATIO;
154 − const scope = and(eq(listings.variantId, v.id), eq(listings.availability, 'available'), sql`${listings.priceUsd} > 0`);
154 + // Auction lots are excluded: a current/opening bid (often ¥1 on Yahoo! Auctions) is not an asking price.
155 + // Bid-vs-RIV belongs to auction intelligence with fees (§33–§35), not to the deal rails.
156 + const scope = and(eq(listings.variantId, v.id), eq(listings.availability, 'available'), sql`${listings.priceUsd} > 0`, sql`${listings.listingType} <> 'auction'`);
155 157 await db()
156 158 .update(listings)
157 159 .set({ discountToRiv: sql`round((${listings.priceUsd} - ${out.riv}) / ${out.riv}, 4)`, flags: sql`array_remove(${listings.flags}, 'riv_anomaly')` })
@@ -268,7 +270,8 @@ async function snapshotValue(assetId: string, variantId: string, at: Date): Prom
268 270
269 271 async function activeListingStats(assetId: string, variantId: string | null): Promise<{ count: number; minAsk: number | null }> {
270 272 const [row] = await db()
271 − .select({ n: sql<number>`count(*)::int`, min: sql<number | null>`min(${listings.priceUsd})` })
273 + // count = every live listing; min ask = fixed-price / best-offer / ask only (an auction bid is not an ask)
274 + .select({ n: sql<number>`count(*)::int`, min: sql<number | null>`min(${listings.priceUsd}) filter (where ${listings.listingType} <> 'auction' and ${listings.priceUsd} > 0)` })
272 275 .from(listings)
273 276 .where(and(eq(listings.assetId, assetId), eq(listings.availability, 'available'), variantId ? eq(listings.variantId, variantId) : undefined));
274 277 return { count: row?.n ?? 0, minAsk: row?.min === null || row?.min === undefined ? null : Number(row.min) };
275 278