COT-Positionierungsdaten zeigen Ihnen, was die größten spekulativen Teilnehmer an den Devisen-Futures-Märkten jede Woche mit echtem Geld tun, ohne Interpretation.
Dieser Leitfaden führt Sie durch den gesamten Prozess: COT-Daten aus der FXMacroData API ziehen, die abgeleiteten Schlüsselmetriken berechnen, einen Positionierungsfilter erstellen und ihn auf Ihren Eingabe-Workflow anwenden. Am Ende haben Sie einen funktionierenden Python-basierten Filter, den Sie in jede beliebige FX-Strategie einfügen können.
Was du bauen wirst
- Eine Python-Funktion, die wöchentliche COT-Daten für eine der acht unterstützten Währungen abruft
- Eine Normungskennzahl für die Nettoposition (netto als % der offenen Beteiligung)
- Ein mehrkonditionierter Positionierungsfilter mit konfigurierbaren Schwellenwerten
- Ein Handels-Eingangstor, das ein Richtungssignal zurückgibt:
long- Ich weiß .shortOder ...neutral - Ein praktischer Durchgang mit EUR/USD als Beispiel
Voraussetzungen
- FXMacroData-API-Schlüssel verfügbar unter /abonnierenDer COT-Endpunkt ist in allen bezahlten Plänen enthalten.
- Python 3.9+ Mit dem ...
requestsBibliothek installiert (pip install requests) - Grundlegende Kenntnisse der COT-Berichtterminologie (nichtkommerzielle Longs, Shorts, Open Interest). COT-Berichtführer für Devisenhändler Die Kommission hat die Kommission aufgefordert,
- Optional,
pandasfür die Datenmanipulation (pip install pandas)
Schritt 1 COT-Daten aus der API abrufen
Der FXMacroData COT Endpunkt gibt wöchentliche nicht-kommerzielle und kommerzielle Positionierung für Währungstermine zurück. Unterstützte Währungen sind AUD, CAD, CHF, EUR, GBP, JPY, NZD und USD. Jeder Datensatz enthält die lange, kurze und Netto-Kontraktzahl für nicht-kommerzielle Teilnehmer und kommerzische Teilnehmer sowie das Gesamt-offene Interesse.
curl "https://fxmacrodata.com/api/v1/cot/eur?api_key=YOUR_API_KEY&start=2023-01-01"
Die Antwort JSON hat folgende Struktur:
{
"currency": "eur",
"data": [
{
"date": "2025-03-25",
"noncommercial_long": 198432,
"noncommercial_short": 61840,
"noncommercial_net": 136592,
"commercial_long": 68230,
"commercial_short": 201860,
"open_interest": 591400
},
{
"date": "2025-03-18",
"noncommercial_long": 185710,
"noncommercial_short": 66320,
"noncommercial_net": 119390,
"commercial_long": 72140,
"commercial_short": 189430,
"open_interest": 578200
}
]
}
In Python wickeln Sie diesen Aufruf in einen Helfer ein, der die chronologisch sortierte Datenliste zurückgibt:
import requests
from datetime import date, timedelta
BASE_URL = "https://fxmacrodata.com/api/v1"
API_KEY = "YOUR_API_KEY"
def fetch_cot(currency: str, lookback_days: int = 365) -> list[dict]:
"""Return COT weekly records for *currency* over the last *lookback_days* days."""
start = (date.today() - timedelta(days=lookback_days)).isoformat()
resp = requests.get(
f"{BASE_URL}/cot/{currency.lower()}",
params={"api_key": API_KEY, "start": start},
timeout=15,
)
resp.raise_for_status()
payload = resp.json()
return sorted(payload["data"], key=lambda r: r["date"])
records = fetch_cot("eur")
print(f"Loaded {len(records)} COT records for EUR")
print("Latest:", records[-1])
Warum 12 Monate Geschichte?
Die Filterschwellen in Schritt 3 werden als Perzentilrangen über das letzte Jahr ausgedrückt. Ein Jahr ist lang genug, um einen vollständigen Positionierungszyklus für die meisten wichtigen Paare zu erfassen, ohne Regimeänderungen einzubeziehen, die zu alt sind, um relevant zu sein. Sie können das Fenster auf 23 Jahre für Währungen mit langsameren Positionisationszyklen wie JPY oder CHF erweitern.
Schritt 2 Berechnung abgeleiteter Positionierungsmetriken
Die Anzahl der Rohkontrakte ist schwierig, über Währungen und Zeit hinweg zu vergleichen. Eine Nettolänge von 80.000 Kontrakte bedeutet etwas ganz anderes in EUR-Futures (großer, liquider Markt) gegenüber CHF (kleinerer offener Zinssatz).
2a. Nettoposition als Prozentsatz der offenen Beteiligung
Die Dividierung der Nettoposition nichtkommerzieller Positionen durch die Gesamtzahl der offenen Zinsen ergibt ein normalisiertes Verhältnis zwischen -1 und +1.
def net_pct_oi(records: list[dict]) -> list[dict]:
"""Add 'net_pct' field = noncommercial_net / open_interest to each record."""
enriched = []
for r in records:
oi = r.get("open_interest") or 1 # guard against zero
enriched.append({**r, "net_pct": r["noncommercial_net"] / oi})
return enriched
records = net_pct_oi(records)
latest = records[-1]
print(f"EUR net % OI: {latest['net_pct']:.3f} ({latest['date']})")
2b. Positionierung des Perzentilranges
Um zu wissen, ob die aktuelle Positionierung extrem ist, braucht man einen historischen Kontext. net_pct Ein Perzentil über 75 zeigt einen vollen Long an; unter 25 zeigt einen vollen Short an.
def percentile_rank(series: list[float], value: float) -> float:
"""Return the percentile rank of *value* within *series* (0–100)."""
below = sum(1 for x in series if x < value)
return below / len(series) * 100
net_series = [r["net_pct"] for r in records]
current_net = records[-1]["net_pct"]
pct_rank = percentile_rank(net_series, current_net)
print(f"EUR positioning percentile: {pct_rank:.1f}th")
Interpretation der Perzentilrangen
- 75° 100° Perzentil: Nichtkommerzielle sind lang überfüllt, bevorzugen lange Eintritte, während der Trend anhält, erhöhen das Umkehrrisiko, wenn sich die Fundamentaldaten ändern.
- 25. 75. Perzentil: Keine starken Positionswinde oder Gegenwindssignale sollten führen.
- 0° 25° Perzentil: Nicht-kommerzielle Aktien sind kurz gefüllt, bevorzugen kurzfristige Eintritte, während der Trend anhält, und erhöhen das Risiko einer Überraschung durch Aufwärtsbewegung.
2c. Positionierungsmomentum
Die Trendrichtung ist genauso wichtig wie das aktuelle Niveau. Eine wachsende Netto-Lange ist ein anderes Signal als eine Netto lange, die sich stabilisiert oder zu schrumpfen begann. Berechnen Sie die 4-wöchige Veränderung in net_pct, um die Dynamik zu erfassen:
def positioning_momentum(records: list[dict], periods: int = 4) -> float:
"""Return the change in net_pct over the last *periods* weeks."""
if len(records) < periods + 1:
return 0.0
return records[-1]["net_pct"] - records[-(periods + 1)]["net_pct"]
momentum = positioning_momentum(records)
print(f"EUR 4-week positioning change: {momentum:+.3f}")
Schritt 3 Erstellen Sie den Eingabefilter
Mit den drei Metriken in der Hand, können Sie eine Filterfunktion konstruieren, die ein Richtungssignal für jede Währung zurückgibt.
def cot_entry_filter(
currency: str,
lookback_days: int = 365,
long_pct_threshold: float = 55.0,
short_pct_threshold: float = 45.0,
momentum_min: float = 0.005,
) -> dict:
"""
Return a COT positioning signal for *currency*.
Parameters
----------
currency : ISO currency code (AUD, CAD, CHF, EUR, GBP, JPY, NZD, USD)
lookback_days : history window for percentile calculation
long_pct_threshold : minimum percentile to confirm a long bias
short_pct_threshold : maximum percentile to confirm a short bias
momentum_min : minimum absolute 4-week change to confirm momentum
Returns
-------
dict with keys: currency, signal, net_pct, percentile, momentum, date
"""
records = fetch_cot(currency, lookback_days)
records = net_pct_oi(records)
latest = records[-1]
net_series = [r["net_pct"] for r in records]
pct_rank = percentile_rank(net_series, latest["net_pct"])
momentum = positioning_momentum(records)
if pct_rank >= long_pct_threshold and momentum >= momentum_min:
signal = "long"
elif pct_rank <= short_pct_threshold and momentum <= -momentum_min:
signal = "short"
else:
signal = "neutral"
return {
"currency" : currency.upper(),
"signal" : signal,
"net_pct" : round(latest["net_pct"], 4),
"percentile" : round(pct_rank, 1),
"momentum" : round(momentum, 4),
"date" : latest["date"],
}
result = cot_entry_filter("eur")
print(result)
Die Stichprobenproduktion, wenn EUR-Nicht-Geschäftsaufträge eine überfüllte Zeit verfolgen und dazu hinzufügen:
{
"currency" : "EUR",
"signal" : "long",
"net_pct" : 0.231,
"percentile": 82.4,
"momentum" : 0.018,
"date" : "2025-03-25"
}
Schritt 4 Filter auf Transaktionspositionen anwenden
Die Filterfunktion gibt eines von drei Signalen zurück long- Ich weiß . shortOder ... neutralDie Verwendung ist als Tor vor dem primären Eingangssignal vorgesehen: nur lange Einstellungen, wenn der COT-Filter sagt long (oder neutral Wenn Sie aggressiver sind, und nur kurze Einstellungen machen, wenn der COT-Filter sagt short- Ich weiß .
def should_enter_trade(
currency: str,
proposed_direction: str,
allow_neutral: bool = False,
) -> bool:
"""
Return True if COT positioning supports *proposed_direction* for *currency*.
Parameters
----------
currency : ISO currency code
proposed_direction : 'long' or 'short'
allow_neutral : if True, a 'neutral' COT signal does not block entry
"""
cot = cot_entry_filter(currency)
if cot["signal"] == proposed_direction:
return True
if allow_neutral and cot["signal"] == "neutral":
return True
return False
# Example: checking whether to enter a EUR/USD long
currency = "eur" # base currency of the pair
direction = "long"
if should_enter_trade(currency, direction):
print(f"COT confirms {direction} bias for {currency.upper()} — proceed to entry check")
else:
print(f"COT filter blocked {direction} entry for {currency.upper()}")
Anmerkung zur Durchführung: COT ist ein wöchentliches Signal
Die COT-Daten werden jeden Freitag für Positionen ab dem vorherigen Dienstag veröffentlicht. Dies macht es zu einem Niederfrequenzsignal , das für die Filterung wöchentlicher oder täglicher Verzerrungen geeignet ist, nicht für Intraday-Einträge. Führen Sie den Filter einmal pro Woche nach Freitag 15:30 Uhr ET-Veröffentlichung erneut durch, speichern Sie das Ergebnis und verwenden Sie es als statisches Bias-Gate für alle Einträge in der folgenden Woche. COT-Endpunktdokumente zur Überprüfung des Zeitplans der Freigabe.
Schritt 5 Erweitern Sie auf ein Multi-Währungs-Dashboard
Wenn Sie den Filter auf allen acht unterstützten Währungen gleichzeitig ausführen, erhalten Sie ein wöchentliches Positions-Dashboard. Dies ist nützlich, um zu identifizieren, welche Devisenpaare die klarsten spekulativ getriebenen Nachwinde haben und welche zu vermeiden sind, da die Positionierung gegen Ihre Richtung wirkt.
CURRENCIES = ["aud", "cad", "chf", "eur", "gbp", "jpy", "nzd", "usd"]
def cot_dashboard() -> list[dict]:
"""Return COT positioning signals for all supported currencies."""
results = []
for ccy in CURRENCIES:
try:
result = cot_entry_filter(ccy)
results.append(result)
except Exception as exc:
print(f"Warning: could not load {ccy.upper()} COT data — {exc}")
return results
dashboard = cot_dashboard()
for row in dashboard:
bar = "▲" if row["signal"] == "long" else ("▼" if row["signal"] == "short" else "–")
print(f"{row['currency']:4s} {bar} {row['signal']:8s} pct={row['percentile']:5.1f} mom={row['momentum']:+.3f}")
Wöchentliche Stichprobenproduktion:
AUD ▲ long pct= 71.2 mom=+0.012
CAD – neutral pct= 53.8 mom=-0.004
CHF ▲ long pct= 68.4 mom=+0.008
EUR ▲ long pct= 82.4 mom=+0.018
GBP – neutral pct= 48.1 mom=-0.002
JPY ▼ short pct= 19.6 mom=-0.022
NZD ▲ long pct= 63.0 mom=+0.007
USD ▼ short pct= 22.1 mom=-0.015
Die Position der nicht-kommerziellen Futures ist lang EUR, AUD, CHF und NZD; kurz JPY und USD; und neutral gegenüber CAD und GBP. Ein Händler, der EUR/JPY-Längen in Betracht zieht, würde beide Beine durch Spekulantenströme bestätigt finden. Ein Trader, der USD/CAD-Länger in Betreff zieht würde einen COT-Hirnwind auf USD und einen neutralen Hintergrund auf CAD ein schwächeres Setup aus Positionierungsperspektive sehen.
Schritt 6 COT mit einer Makrokonfirmierungsschicht kombinieren
Die meisten robusten Konzepte kombinieren die COT-Positionierung mit mindestens einem Makro-Fundamentalsatz, der die gleiche Richtungstheorie unterstützt.
- Verwenden Sie die ... Endpunkt für die Leitzinssätze um den jüngsten Kurs für jede Währung zu ermitteln und die Differenz zu berechnen:
def fetch_latest_rate(currency: str) -> float | None:
"""Return the most recent policy rate for *currency* as a float."""
resp = requests.get(
f"{BASE_URL}/announcements/{currency.lower()}/policy_rate",
params={"api_key": API_KEY, "limit": 1},
timeout=15,
)
if resp.status_code != 200:
return None
data = resp.json().get("data", [])
return data[0]["val"] if data else None
def rate_differential(base_ccy: str, quote_ccy: str) -> float | None:
"""Return base_rate − quote_rate, or None if either rate is unavailable."""
base_rate = fetch_latest_rate(base_ccy)
quote_rate = fetch_latest_rate(quote_ccy)
if base_rate is None or quote_rate is None:
return None
return base_rate - quote_rate
def combined_filter(base_ccy: str, quote_ccy: str, direction: str) -> dict:
"""
Return a combined COT + rate-differential signal for a currency pair.
direction : 'long' (buy base) or 'short' (sell base)
"""
cot_base = cot_entry_filter(base_ccy)
cot_quote = cot_entry_filter(quote_ccy)
diff = rate_differential(base_ccy, quote_ccy)
# For a long (buy base): we want long bias on base AND short/neutral on quote
if direction == "long":
cot_ok = cot_base["signal"] == "long" and cot_quote["signal"] != "long"
macro_ok = diff is not None and diff > 0
else:
cot_ok = cot_base["signal"] == "short" and cot_quote["signal"] != "short"
macro_ok = diff is not None and diff < 0
return {
"pair" : f"{base_ccy.upper()}/{quote_ccy.upper()}",
"direction" : direction,
"cot_ok" : cot_ok,
"macro_ok" : macro_ok,
"confirmed" : cot_ok and macro_ok,
"cot_base" : cot_base,
"cot_quote" : cot_quote,
"rate_diff" : round(diff, 4) if diff is not None else None,
}
signal = combined_filter("eur", "jpy", "long")
print("Confirmed:", signal["confirmed"])
print("COT base :", signal["cot_base"]["signal"], "| COT quote:", signal["cot_quote"]["signal"])
print("Rate diff :", signal["rate_diff"])
Wenn COT und Makro nicht übereinstimmen
Wenn die Positionierung extrem auf einer Seite überfüllt ist, aber das Makro-Fundamental sich dagegen verschiebt (z. B. große kurze JPY-Futures, aber die Bank von Japan beginnt zu straffen), kann das Regime in einer Übergangsphase sein. Dies sind die Setups, die die schnellsten und größten Bewegungen oft in die Richtung erzeugen, die Short-Cover oder Long-Liquidation zwingt. In solchen Fällen ist der COT-Filter allein nicht ausreichend; überwachen Sie die Zinspolitik der Zentralbanken Die Position wird von der Positionsausrichtung abgeschaltet.
Zusammenfassung
Sie haben nun einen vollständigen COT-basierten Eingabefilter, der auf Live-FXMacroData API-Daten basiert.
- Holen Sie sich wöchentliche COT-Daten für die Währung oder Währungen, mit denen Sie handeln.
- Berechnung der Nettoposition als Prozentsatz der offenen Zinsen zur Normalisierung über Währungen hinweg.
- Die aktuelle Position innerhalb des letzten Jahres zu klassifizieren, um extreme oder neutrale Bedingungen zu identifizieren.
- Berechnen Sie die 4-wöchige Dynamik, um zu bestätigen, ob die Positionierung zu Ihren Gunsten läuft.
- Sie müssen Ihre Handelsmeldungen nur dann gegen das Filtersignal schalten, wenn die COT-Ausrichtung Ihrer Richtung entspricht.
- Optional mit einer Rate-Differential-Prüfung für eine Zweifaktorbestätigung kombiniert.
Das vollständige Multi-Währungs-Dashboard gibt Ihnen eine wöchentliche Momentaufnahme, wo Spekulantengeld positioniert ist, so dass Sie Trades mit institutionellen Fluss statt gegen es eingeben. Inflation Oder ... Beschäftigung Die Kommission hat die Kommission aufgefordert, die Ergebnisse der Analyse zu überprüfen, um ein Makroscoring-Modell zu erstellen, das alle drei Signale zusammen abwägt Positionierung, Zinsdifferenzen und Wachstums-/Inflationdynamik für ein vollständigeres Bild des Regimes.