From 6c59164e6ba59f4c890d2cdc40b584a41747864e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 08:16:51 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20Preis-L=C3=BCcken-Warnung,=20Pair-Typisi?= =?UTF-8?q?erung,=20Nebenl=C3=A4ufigkeits-Doku=20im=20Poller=20(Review=20T?= =?UTF-8?q?ask=206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- src/server/signals/poller.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/server/signals/poller.ts b/src/server/signals/poller.ts index ae56090..dd93056 100644 --- a/src/server/signals/poller.ts +++ b/src/server/signals/poller.ts @@ -5,6 +5,7 @@ import { getCandles } from '../market/candle-store'; import { fetchTransfers, getBlockNumber, getBlockTs } from './onchain'; import { matchCoins, parseTruthFeed } from './truth'; import { COIN_KEYWORDS, MIN_NOTIONAL_USD, TRUTH_DEDUPE_MS, TRUTH_FEED_URL } from './watchlist'; +import type { Pair } from '../types'; const M15 = 15 * 60 * 1000; /** Obergrenze Blöcke je Zyklus (~4 getLogs-Calls); Ethereum macht ~25 Blöcke/5min — reichlich Aufholpuffer. */ @@ -33,9 +34,9 @@ export function dedupeTruthEvents( return batch.filter((e) => accepted.has(e.url)); } -/** Letzter 15m-Close ≤ ts als USD-Proxy (USDT≈USD). null wenn keine Candle vorhanden. */ -async function priceAt(instrument: string, ts: number): Promise { - const candles = await getCandles(instrument as any, ts - 24 * 3600_000, ts + M15); +/** Close der jüngsten Candle im Fenster [ts−24h, ts+15m) als USD-Proxy (USDT≈USD). null wenn keine Candle vorhanden. */ +async function priceAt(instrument: Pair, ts: number): Promise { + const candles = await getCandles(instrument, ts - 24 * 3600_000, ts + M15); return candles.length > 0 ? candles[candles.length - 1].close : null; } @@ -58,6 +59,10 @@ export async function pollOnchain(): Promise { if (!blockTs.has(t.blockNumber)) blockTs.set(t.blockNumber, await getBlockTs(t.blockNumber)); const ts = blockTs.get(t.blockNumber)!; const price = t.instrument ? await priceAt(t.instrument, ts) : null; + if (t.instrument && price === null) { + // Candle-Lücke (frisches Listing / Backfill fehlt) — sichtbar machen statt still verwerfen + console.warn(`Trump-Transfer ohne Preis verworfen: ${t.symbol} ${t.amount} (${t.txHash})`); + } if (!passesNotional(t.amount, price)) continue; await db .insert(trumpEvents) @@ -105,7 +110,11 @@ export async function pollTruth(): Promise { return inserted; } -/** Beide Quellen, Fehler isoliert (eine tote Quelle stoppt die andere nicht). */ +/** + * Beide Quellen, Fehler isoliert (eine tote Quelle stoppt die andere nicht). + * Annahme: läuft nie nebenläufig (5-min-Loop ist seriell, Engine hat cycling-Guard) — + * das Truth-Dedupe liest existing vor den Inserts und wäre bei Parallelläufen lückenhaft. + */ export async function pollSignals(): Promise { const results = await Promise.allSettled([pollOnchain(), pollTruth()]); for (const r of results) {