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:
@@ -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 [ts−24h, 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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user