fix: Preis-Lücken-Warnung, Pair-Typisierung, Nebenläufigkeits-Doku im Poller (Review Task 6)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 08:16:51 +00:00
parent a2e4362444
commit 6c59164e6b

View File

@@ -5,6 +5,7 @@ import { getCandles } from '../market/candle-store';
import { fetchTransfers, getBlockNumber, getBlockTs } from './onchain'; import { fetchTransfers, getBlockNumber, getBlockTs } from './onchain';
import { matchCoins, parseTruthFeed } from './truth'; import { matchCoins, parseTruthFeed } from './truth';
import { COIN_KEYWORDS, MIN_NOTIONAL_USD, TRUTH_DEDUPE_MS, TRUTH_FEED_URL } from './watchlist'; import { COIN_KEYWORDS, MIN_NOTIONAL_USD, TRUTH_DEDUPE_MS, TRUTH_FEED_URL } from './watchlist';
import type { Pair } from '../types';
const M15 = 15 * 60 * 1000; const M15 = 15 * 60 * 1000;
/** Obergrenze Blöcke je Zyklus (~4 getLogs-Calls); Ethereum macht ~25 Blöcke/5min — reichlich Aufholpuffer. */ /** 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)); return batch.filter((e) => accepted.has(e.url));
} }
/** Letzter 15m-Close ≤ ts als USD-Proxy (USDT≈USD). null wenn keine Candle vorhanden. */ /** Close der jüngsten Candle im Fenster [ts24h, ts+15m) als USD-Proxy (USDT≈USD). null wenn keine Candle vorhanden. */
async function priceAt(instrument: string, ts: number): Promise<number | null> { async function priceAt(instrument: Pair, ts: number): Promise<number | null> {
const candles = await getCandles(instrument as any, ts - 24 * 3600_000, ts + M15); const candles = await getCandles(instrument, ts - 24 * 3600_000, ts + M15);
return candles.length > 0 ? candles[candles.length - 1].close : null; return candles.length > 0 ? candles[candles.length - 1].close : null;
} }
@@ -58,6 +59,10 @@ export async function pollOnchain(): Promise<number> {
if (!blockTs.has(t.blockNumber)) blockTs.set(t.blockNumber, await getBlockTs(t.blockNumber)); if (!blockTs.has(t.blockNumber)) blockTs.set(t.blockNumber, await getBlockTs(t.blockNumber));
const ts = blockTs.get(t.blockNumber)!; const ts = blockTs.get(t.blockNumber)!;
const price = t.instrument ? await priceAt(t.instrument, ts) : null; 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; if (!passesNotional(t.amount, price)) continue;
await db await db
.insert(trumpEvents) .insert(trumpEvents)
@@ -105,7 +110,11 @@ export async function pollTruth(): Promise<number> {
return inserted; 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<void> { export async function pollSignals(): Promise<void> {
const results = await Promise.allSettled([pollOnchain(), pollTruth()]); const results = await Promise.allSettled([pollOnchain(), pollTruth()]);
for (const r of results) { for (const r of results) {