# Polymarket und Wash Trading: Volumenfallen in Data API und Matching-Logs

> Canonical: https://vxi.me/de/posts/polymarket-election-volume-wash-trading-api-price-discovery/
> Locale: de-de
> Published: 2026-10-21
> Updated: 2026-10-21
> Markdown: https://vxi.me/de/posts/polymarket-election-volume-wash-trading-api-price-discovery.md

[Eine WIRED-Recherche vom 15. Oktober 2026](https://www.wired.com/story/over-dollar600-million-traded-on-polymarket-election-contracts-after-races-ended/) rückte zwei Wahlmärkte in den Fokus. Unter Berufung auf MSR Decode schrieb WIRED, dass etwa 284 Millionen US-Dollar an Handel im Äthiopien-Markt auf wiederholte Käufe und Verkäufe zwischen gepaarten Konten zurückgingen. Polymarket erklärte, die Vorgänge untersucht und die betroffenen Konten für den Handel gesperrt zu haben.

Der Vorwurf verdient Prüfung. Seine Grundlage sind Zyklen zwischen Handelspartnern, nicht bloß ein hohes Volumen unter dem Namen eines unterlegenen Kandidaten. Wer dafür selbst die API öffnet, kann allerdings schon bei der ersten Auswertung falsch zählen. Steht der gesamte Betrag unter einem Kandidatennamen für Wetten auf dessen Sieg? Bedeuten drei Zeilen mit demselben Hash drei doppelte Trades?

Ich habe die kumulierten Beträge beider Events, zwei kleine Handelszeitfenster und drei Matching-Transaktionen auf Polygon geprüft. Ein hohes Volumen zeigt, wo man genauer hinschauen sollte; entscheidend ist die Transaktionsstruktur. In den zwei kleinen Fenstern entfielen mehr als 98 Prozent des nominalen Umsatzes auf **Käufe und Verkäufe von No-Token zusammen**. Das heißt nicht, dass 98 Prozent des Geldes für den Kauf von No eingesetzt wurden. Auch No-Kontrakte nahe einem Dollar können wiederholt den Besitzer wechseln. Sowohl normaler Handel als auch künstliche Kreisläufe können dieselbe Verteilung erzeugen; unterscheiden lassen sie sich erst anhand von Gegenparteien und Geldflüssen.

Für die Kontroverse und die Zahlendefinitionen genügt zunächst der Betragsvergleich. Wer einen Collector oder Indexer baut, kann zusätzlich das Python-Beispiel und die Regeln für Matching-Logs durcharbeiten.

## Event, Condition, Token und kumulierter Umsatz

Ein Wahlevent bei Polymarket kann mehrere Kandidatenmärkte enthalten. Jeder hat eine eigene Condition sowie die beiden Ergebnis-Token Yes und No. Beide Token können jeweils gekauft oder verkauft werden.

```text
event: das gesamte Wahlevent
  └─ condition: gewinnt ein bestimmter Kandidat?
       ├─ Yes token → BUY / SELL
       └─ No token  → BUY / SELL
```

Meine gespeicherten Gamma-API-Snapshots enthalten folgende Werte. Bei beiden Events entspricht die Summe von `volume` über die Teilmärkte dem Event-Gesamtwert. Dollarbeträge in den Tabellen behalten die Zahlenschreibweise der Quellen bei.

| Offizielles Gamma-Event | Teilmärkte | Gemeldeter kumulierter Umsatz |
| --- | ---: | ---: |
| [Next Prime Minister of Ethiopia](https://gamma-api.polymarket.com/events/slug/next-prime-minister-of-ethiopia) | 33 | $286,138,162.70 |
| [(Old) Romania Election](https://gamma-api.polymarket.com/events/slug/romania-presidential-election) | 6 | $371,837,992.48 |
| **Summe** | **39** | **$657,976,155.18** |

Die **rund 658 Millionen Dollar sind hier der kumulierte Umsatz über die gesamte Historie im API-Snapshot**. Die WIRED-Überschrift bezieht sich dagegen auf Handel nach feststehendem Wahlausgang. Ich habe diese Gesamtbeträge nicht an den jeweiligen Zeitpunkten getrennt, zu denen die Ergebnisse klar wurden. Das ist ein Größenvergleich, keine Reproduktion des Handelsvolumens nach dem Ergebnis.

## Welcher Nenner gehört zu MSRs 284 Millionen?

Teilt man die gerundeten $284,000,000 aus dem Bericht durch Gammas $286,138,162.70, erhält man etwa **99.25%**. Die Division stimmt; Zähler und Nenner beziehen sich aber auf unterschiedliche Grundgesamtheiten.

[MSRs öffentliche Studie](https://msrdecode.com/cases/artificial-volume-census.html) und die zugehörige [JSON-Tabelle](https://msrdecode.com/artificial-volume-census.json) liefern einen eigenen Nenner. Die Untersuchung umfasst **2026-04-28 bis 09-09** und die Polygon-Blöcke `86107336–93524955`. Sieben äthiopische Teilmarktzeilen mit `event_id=411239` enthalten jeweils `turnover_usd` und das von der Studie markierte `recycled_usd`. Ihre Summen:

| Quelle und Umfang | Betrag | Gemessene Größe |
| --- | ---: | --- |
| MSR-Tabelle: 7 Märkte im untersuchten Blockbereich | $283,964,639.89 | Summe von `recycled_usd` |
| Dieselbe Tabelle, dieselben 7 Märkte und Blöcke | $285,027,224.40 | Zugehöriger Nenner `turnover_usd` |
| Gamma-Snapshot: alle 33 Märkte des Events | $286,138,162.70 | Gemeldeter kumulierter Umsatz |

**Innerhalb derselben Tabelle ergibt sich $283,964,639.89 / $285,027,224.40 ≈ 99.63%**. Das entspricht MSRs auf eine Nachkommastelle gerundeten 99.6%. Dieser hohe Anteil ist das Ergebnis für genau diese sieben Märkte im untersuchten Zeitraum.

Für einen Abgleich mit Gamma müssten die sieben `condition_id`-Werte, Anfangs- und Endblöcke sowie die Zählregeln für wirtschaftliche Fills übereinstimmen. MSR bewertet direkte Anteilstrades zum Geldpreis und die Erzeugung beziehungsweise Zusammenführung von Paaren mit einem Dollar pro Paar; jeder wirtschaftliche Fill zählt einmal. Unterschiedliche Pfade brauchen passende Betragsdefinitionen. `size × price` einer einzelnen API-Seite ersetzt sie nicht pauschal.

Reproduziert habe ich hier die Addition der öffentlichen Tabelle. MSR veröffentlicht die genauen Schwellenwerte seiner Episode-Klassifizierung nicht. Auch ist nicht geklärt, ob der öffentliche Census und die für WIRED beauftragte Analyse dieselbe eingefrorene Datenversion verwenden. Für eine unabhängige Rekonstruktion der Klassifizierung fehlen diese Informationen. Quelle und Nenner hinter „etwa 284 Millionen“ lassen sich dennoch nachprüfen.

## No-Umsatz in zwei kleinen Zeitfenstern

Der Markt für Adanech Abiebie meldet kumuliert etwa 83,46 Millionen Dollar, der für Nicolae Ciucă rund 326,5 Millionen. Letzterer entspricht ungefähr 87,8 Prozent des Rumänien-Events. Der Kandidatenname ist auffällig, doch das tatsächlich gehandelte Asset muss bis zum Yes- oder No-Token aufgelöst werden.

Ich habe pro Markt die jüngsten 250 Taker-Datensätze gelesen und UTC-Fenster gewählt, deren beide Grenzen von den zurückgegebenen Zeitstempeln vollständig umschlossen wurden.

| Markt und UTC-Fenster | Datensätze | Summe `size × price` | No-Betrag: BUY + SELL |
| --- | ---: | ---: | ---: |
| Adanech, 2026-09-01 bis 09-30, Ende exklusiv | 83 | $2,997.93 | $2,987.15; 99.64% |
| Ciucă, gesamter 2025-04-30 | 128 | $95,930.59 | $94,609.25; 98.62% |

`outcome=No, side=BUY` bedeutet No kaufen; `outcome=No, side=SELL` bedeutet No verkaufen. Beides geht in den Tabellenumsatz ein. Nettokäufe oder Positionsänderungen müssen getrennt nach Konto, Anteilsmenge und Richtung berechnet werden.

Zusammen sind das etwa 98.900 Dollar, nur **0.0241%** des kumulierten Umsatzes der beiden Teilmärkte. Die Auswahl ist nicht zufällig. Ich habe diese Fenster gewählt, weil jeweils eine Seite vollständig ausreichte und sich Felder und Formeln so kontrollieren ließen. Sie illustrieren Definitionen, nicht die gesamte Historie beider Märkte.

## Data API v2: Die aktuelle erste Seite ist kein historischer Snapshot

Der [offizielle OpenAPI-Vertrag](https://data-api.polymarket.com/v2/openapi.json) definiert `condition` als Filter für `GET /v2/trades`, die Liste im obersten Feld `data` und `condition_id` in jedem `Trade`. Der Seitencursor steht in `pagination.next_cursor`. `taker_only=true` liefert die Taker-Seite. Bei einer Condition-Abfrage ignoriert der Endpunkt `start/end`; nach Zeit muss deshalb nach dem Abruf gefiltert werden.

**Eine Anfrage mit `limit=250` ohne Cursor liefert die aktuell jüngste Seite, nicht meinen gespeicherten historischen Snapshot.** Neue Einträge oder Korrekturen können ihren Zeitumfang verändern. Das folgende Skript summiert nur, wenn die Seite beide Grenzen überschreitet. Die 83 Datensätze samt Beträgen sind Ergebnisse des aufbewahrten Snapshots, keine zugesicherten Ausgaben bei jedem späteren Aufruf.

<details>
<summary>Python-Beispiel zur Prüfung des Zeitfensters</summary>

Als `window_check.py` speichern und mit `python window_check.py` starten. Das Programm lädt die aktuelle Seite und prüft Condition, Reihenfolge, Beträge und Zeitgrenzen, bevor es Summen ausgibt.

```python
import json
from datetime import datetime, timezone
from decimal import Decimal
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

CONDITION = '0x7d0aaf81bbd3fd73b6a1651cce08a452c0cbf9c0cbb4520ce0f981065b639d88'
START = int(datetime(2026, 9, 1, tzinfo=timezone.utc).timestamp())
END = int(datetime(2026, 9, 30, tzinfo=timezone.utc).timestamp())

def summarize(payload):
    rows = payload.get('data')
    if not isinstance(rows, list) or not rows:
        raise ValueError('Missing or empty v2 data list')
    if any(r['condition_id'] != CONDITION for r in rows):
        raise ValueError('Unexpected condition_id')
    times = [int(r['timestamp']) for r in rows]
    if times != sorted(times, reverse=True):
        raise ValueError('Unexpected trade ordering')
    if times[0] < END:
        raise ValueError('Upper bound unproven: older pages cannot fix this')
    if times[-1] >= START:
        raise ValueError('Window not bracketed: follow pagination.next_cursor')
    sample = [r for r in rows if START <= int(r['timestamp']) < END]
    amounts = {'Yes': Decimal('0'), 'No': Decimal('0')}
    for r in sample:
        size, price = Decimal(str(r['size'])), Decimal(str(r['price']))
        if not size.is_finite() or not price.is_finite() or size < 0 or not 0 <= price <= 1:
            raise ValueError('Invalid size or price')
        if r['outcome'] not in amounts:
            raise ValueError('Unlabelled outcome: inspect before aggregation')
        amounts[r['outcome']] += size * price
    return len(sample), sum(amounts.values()), amounts['No']

def build_request():
    params = urlencode({'condition': CONDITION, 'limit': 250, 'taker_only': 'true'})
    return Request('https://data-api.polymarket.com/v2/trades?' + params,
                   headers={'Accept': 'application/json',
                            'User-Agent': 'PolymarketVolumeCheck/1.0'})

if __name__ == '__main__':
    try:
        with urlopen(build_request(), timeout=20) as response:
            payload = json.load(response, parse_float=Decimal)
    except HTTPError as error:
        raise SystemExit(f'HTTP {error.code}: stop; check API access or Retry-After')
    except URLError as error:
        raise SystemExit(f'Network error: {error.reason}')
    count, total, no_total = summarize(payload)
    print('rows:', count)
    print('all:', total.quantize(Decimal('0.01')))
    print('No:', no_total.quantize(Decimal('0.01')))
```

Der Request nennt einen ausdrücklichen User-Agent. Ein Header garantiert jedoch keine Freigabe aus jedem Netzwerk. Bei 403 sind die Zugangsbedingungen zu prüfen; bei 429/503 sollte die Antwort `Retry-After` berücksichtigt werden. Ein Netzwerkfehler ist kein leerer Datensatz.

`Window not bracketed` bedeutet, dass die Seite nicht weit genug zurückreicht. Dann mit denselben Filtern `pagination.next_cursor` folgen. Dieses Beispiel blättert nicht automatisch. `Upper bound unproven` bedeutet dagegen, dass sogar der jüngste Eintrag vor dem Fensterende liegt; ältere Seiten lösen das nicht. Marktende und Datenabdeckung müssen separat geklärt werden, statt die Prüfung zu löschen und eine Summe zu erzwingen.

Diese Prüfung ist bewusst konservativ. Ein Markt kann schon aufgehört haben zu handeln, sodass selbst die vollständige Historie keine Einträge nach dem Endpunkt enthält. Verifizierte Vollständigkeit oder eine belegte Endgrenze erlauben einen anderen Abdeckungsnachweis; das ist aber nicht die hier verwendete Bedingung, dass eine Seite beide Grenzen überquert.

</details>

## CTF Exchange V2: Erst Logs identifizieren, dann Beträge definieren

Eine zusätzliche Adanech-Seite mit `taker_only=false` enthält 100 Zeilen, aber nur 49 verschiedene Transaktionshashes. 48 Hashes erscheinen mehrfach, maximal dreimal. Aus `100 - 49 = 51` vermeintliche Wash Trades abzuleiten verwechselt Antwortzeilen mit Blockchain-Transaktionen. Nur die erste Zeile je Hash zu behalten kann wiederum unterschiedliche Datensätze innerhalb einer Transaktion entfernen.

Der [fest gepinnte CTF-Exchange-V2-Quellcode](https://github.com/Polymarket/ctf-exchange-v2/blob/ccc0596074f4dfd62c944fbca4de252893b82b4b/src/exchange/mixins/Trading.sol) erklärt die Ereignishierarchie: Ein `matchOrders` verarbeitet eine Taker-Order und mehrere Maker-Orders. Jeder Maker-Fill emittiert ein `OrderFilled`. Nach der Taker-Verarbeitung folgen ein zusätzliches zusammenfassendes `OrderFilled` und unmittelbar danach `OrdersMatched`. Die letzten beiden entstehen in [`_emitTakerFilledEvents`](https://github.com/Polymarket/ctf-exchange-v2/blob/ccc0596074f4dfd62c944fbca4de252893b82b4b/src/exchange/mixins/Events.sol).

Die drei geprüften Transaktionen enthalten folgende Ereigniszahlen. **Die Tabelle belegt deren Anzahl, nicht identische Geldbeträge auf beiden Seiten.**

| Polygon-Transaktion | `OrderFilled` | `OrdersMatched` | Ereignisebenen |
| --- | ---: | ---: | --- |
| [A](https://polygon.blockscout.com/tx/0x3bbc4f9c0363cf8fc1a89031183a6ae2bd9eb972a1d9b59acf87021118e0f5f6) | 2 | 1 | 1 Maker-Detail + 1 Taker-Summe |
| [B](https://polygon.blockscout.com/tx/0x243ebb2cddf06e31f85fe3cdcb94a7d839fd029cb4d58e68ede64cbe7a994dc3) | 3 | 1 | 2 Maker-Details + 1 Taker-Summe |
| [C](https://polygon.blockscout.com/tx/0xee9d91888a8109cb284d7c2a9980694738db07534786f985c501ee0b1376bfd8) | 2 | 1 | 1 Maker-Detail + 1 Taker-Summe |

Es sind Order-Datensätze; ein Wallet kann mehrere Orders haben. Auch `OrderFilled.maker` der Taker-Zusammenfassung enthält `takerOrder.maker`, also den Ersteller der aggressiven Order. Allein der Feldname `maker` identifiziert deshalb keine passive Ausführung.

### Direkter Handel desselben Tokens und Yes/No-Paarbildung sind verschiedene Rechnungen

Ein **konstruiertes BUY-gegen-SELL-Beispiel desselben Ergebnis-Tokens** zeigt zunächst die doppelte Summierung: Jemand kauft 10 Anteile zu je $0.60 und bedient Verkaufsorders über 4 und 6 Anteile. Die Maker-Details betragen $2.40 und $3.60, die Taker-Summe $6. Werden alle drei als unabhängige wirtschaftliche Beträge addiert, entstehen fälschlich $12. Das ist keine Rekonstruktion der Beträge von A/B/C.

MINT/MERGE über Yes und No benötigt eine andere Rechnung. In einem **konstruierten MINT ohne Gebühren und Restbeträge** bezahlt eine Partei $4 für 10 Yes-Anteile, die andere $6 für 10 No-Anteile. Zusammen entstehen $10 Sicherheiten. Es sind Zahlungen für unterschiedliche Ergebnis-Token; die $6 sind nicht schon deshalb ein Duplikat der $4, weil sie in einer Taker-Zusammenfassung stehen.

```text
Direkter Trade: Maker-Details 2.40 + 3.60 = Taker-Summe 6.00
MINT:          Yes-Zahlung 4.00 + No-Zahlung 6.00 = Sicherheiten 10.00
```

Im vereinfachten reinen MINT mit gleichen Einheiten, ohne Gebühren und Restbeträge entspricht die Taker-Zahlung den gesamten Paarsicherheiten abzüglich der Maker-Zahlung. Die empfangenen Ergebnisanteile werden auf der anderen Seite zusammengefasst. Produktiv müssen außerdem `side`, `tokenId`, die Dezimalstellen der Sicherheiten, Gebühren und Restbeträge bekannt sein. Der Enum-Name `COMPLEMENTARY` bezeichnet im gepinnten Code direktes BUY/SELL-Matching desselben Tokens. Zwei Käufe oder zwei Verkäufe komplementärer Ergebnis-Token laufen dagegen über `MINT` beziehungsweise `MERGE`—der Name kann in die Irre führen.

**Maker-Details und Taker-Zusammenfassungen bleiben in getrennten Tabellen.** Die Log-Klassifizierung bestimmt die Rolle eines Datensatzes. Die Definition des wirtschaftlichen Umsatzes folgt erst nach Identifikation des Matching-Typs. Deshalb kann die Ereigniszählung stimmen und der Geldalgorithmus trotzdem falsch sein.

### Ein Summenereignis identifizieren

Doppelte Abfragen roher Ereignisse werden anhand von `(chain_id, transaction_hash, log_index)` dedupliziert. Gemeint ist hier der **blockweite `logIndex` der RPC-Antwort**. Dafür einen Lookup aufbauen, den Wert nicht als direkten Array-Index für `receipt.logs` benutzen. Verwendet eine Indexer-Datenbank transaktionslokale Indizes, muss diese Zählweise durchgängig gelten und Reihenfolge sowie unmittelbare Nachbarschaft erhalten.

Der folgende Pseudocode gilt für ein vollständiges erfolgreiches Receipt eines Emitters, der nachweislich den gepinnten V2-Pfad verwendet. ABI-Decodierung sowie einheitliche Adress- und Integerdarstellung sind Voraussetzung. Scheitert ein `require`, endet die automatische Aggregation dieses Receipts.

```python
for m in events_of(receipt, 'OrdersMatched'):
    f = log_at(receipt, m.log_index - 1)
    require(f.event == 'OrderFilled' and f.address == m.address)
    require((f.orderHash, f.maker, f.side, f.tokenId,
             f.makerAmountFilled, f.takerAmountFilled) ==
            (m.takerOrderHash, m.takerOrderMaker, m.side, m.tokenId,
             m.makerAmountFilled, m.takerAmountFilled))
    taker_summary_ids.add(raw_id(f))
maker_fills = [f for f in order_fills if raw_id(f) not in taker_summary_ids]
```

`events_of` und `order_fills` enthalten nur den bestätigten Vertrag. Vor der letzten Zeile müssen alle relevanten `OrderFilled` zu vollständigen, erklärbaren Match-Gruppen gehören. Fehlende Logs, widersprüchliche Felder oder nicht geschlossene Gruppen werden zur Untersuchung zurückgestellt. Eine Transaktion kann mehrere Match-Aufrufe enthalten; jede Gruppe wird separat zugeordnet. Bricht eine spätere Version die Nachbarschaft, ist die Aggregation dieses Receipts anzuhalten und die Version neu zu prüfen. Nicht einfach den letzten Eintrag mit passendem `orderHash` in der gesamten Transaktion nehmen: Dieselbe Order kann über mehrere Aufrufe teilweise ausgeführt werden. Beide Tabellen behalten ihre Beträge, die anschließend je nach direktem Handel, MINT oder MERGE interpretiert werden.

## Schnelle Richtungswechsel: Der Schwellenwert verändert das Ergebnis

Für beide Fenster verwendete ich dieselbe Regel: nach Proxy-Wallet-Kennung und Ergebnis-Token gruppieren, dann gegenläufige Trades mit höchstens 2 Prozent Mengenabweichung paaren. Jeder Datensatz darf nur einmal verwendet werden. Allein der erlaubte Zeitabstand verändert die Treffer stark.

| Maximaler Paarabstand | Adanech: 83 Fensterdatensätze | Ciucă: 128 Fensterdatensätze |
| --- | ---: | ---: |
| 30 Sekunden | 1 Paar | 2 Paare |
| 60 Sekunden | 2 Paare | 37 Paare |
| 5 Minuten | 2 Paare | 39 Paare |
| 15 Minuten | 19 Paare | 39 Paare |
| 60 Minuten | 20 Paare | 44 Paare |

Die von der 15-Minuten-Regel ausgewählten Datensätze entsprechen **8.39% und 2.50%** des jeweiligen Fensterumsatzes. Das sind Prüfungskandidaten, keine festgestellten Wash Trades.

Ein konstruiertes Gegenbeispiel reicht: Ein Market-Making-Konto kauft 100 No-Anteile zu $0.985 und verkauft neun Sekunden später dieselbe Menge zu $0.986. Die Differenz vor Gebühren beträgt $0.10. Normale Bestandsanpassung und absichtliche Volumenkreisläufe können beide die Regel „kurze Zeit, ähnliche Menge, entgegengesetzte Richtung“ erfüllen.

Als Nächstes wären Gegenparteibeziehungen, walletübergreifende Geldflüsse, Gebühren und Bestandsänderungen zu untersuchen. Dieser Richtungswechsel-Filter innerhalb eines Wallets hat andere Erkennungskriterien als die von WIRED zitierten gepaarten Konten, die wiederholt miteinander handeln.

## Fünf-Minuten-Preise korrigierten den ersten Eindruck

Beim Adanech-Yes-Kontrakt lagen am 22. September 2026 mehrere benachbarte Zwölf-Stunden-Beobachtungen bei `0.0030`. Zunächst war also nur zu sehen: Der Handel geht weiter, an den groben Messpunkten bleibt der Preis gleich. Fünf-Minuten-Daten zeigten, dass diese Interpretation unvollständig war.

Die vollständige Reihe umfasst 288 normale Fünf-Minuten-Intervalle. Der Vergleich aufeinanderfolgender Punkte findet drei Preisänderungen. Die Tabelle zeigt nur **den Ausgangspunkt vor der ersten Änderung und den Beginn der drei Änderungen**. Unveränderte Intervalle sind ausgelassen; die vier Zeilen sind keine fortlaufenden Stichproben.

| UTC-Zeit | Historischer Yes-Intervallpreis |
| --- | ---: |
| 20:25 | 0.0030 |
| 20:30 | 0.0020 |
| 20:35 | 0.0025 |
| 21:00 | 0.0030 |

Die feinere Reihe zeigt den Rückgang und die Erholung, die im groben Raster verschwinden. Es bleiben Intervallpreise; kausaler Preiseinfluss erfordert zusätzlich damalige Quotes, Tiefe und Nachrichtenzeitpunkte.

Die Historienabfrage für den abgerechneten Kontrakt fügte außerdem am Ende einen Auszahlungspunkt mit `resolution_seconds=0, price=0` an. Laut [API-Konvention](https://data-api.polymarket.com/v2/docs) gehört er getrennt von normalen Fünf-Minuten-Beobachtungen; den Abrechnungsstatus prüft man über [resolutions](https://data-api.polymarket.com/v2/resolutions?condition=0x7d0aaf81bbd3fd73b6a1651cce08a452c0cbf9c0cbb4520ce0f981065b639d88). In einer Kursreihe würde er einen fiktiven Absturz auf null zeichnen.

Für Kurskurven oder Signalanalyse kann man mit historischen Preisen beginnen, ohne jedes `OrderFilled` onchain zu rekonstruieren. Der entfernte Auszahlungspunkt ist aber nur ein Reinigungsschritt. Zeitabdeckung, Lücken und Abrechnung müssen ebenfalls stimmen; für erzielbare Strategierenditen kommen Ausführungsannahmen, Gebühren und Slippage hinzu. Historische Intervallpreise sind keine ausführbaren Quotes.

Wiederverwendbar ist die Reihenfolge: **Verträge, Zeit und Nenner abstimmen; Yes/No von BUY/SELL trennen; Logs identifizieren; anschließend Beträge nach Match-Typ definieren.** Auch bei externen Studien sind Tabellenrechnung und Rekonstruktion des Klassifikators zwei verschiedene Aufgaben. Die Belege pro Ebene festzuhalten hilft mehr, als zuerst über einen Prozentsatz zu streiten. Ein ähnliches Nennerproblem bei Aktien-Backtests zeigt [Wie aussagekräftig ist die Trefferquote, wenn dieselbe Aktie immer wieder ausgewählt wird?](/de/posts/quant-winrate-repeated-stock-picks/) mit vollständigem Python und SQL für drei statistische Definitionen.

*Dieser Beitrag behandelt Datenabgrenzung und Implementierung, nicht Handelsempfehlungen. Gamma-Beträge und lokale Fenster stammen aus Snapshots vom 2026-10-20 UTC; MSR-Tabelle, API-Vertrag und gepinnter Quellcode wurden am 2026-10-21 UTC geprüft.*
