Data posisi COT memberi tahu Anda apa yang dilakukan peserta spekulatif terbesar di pasar berjangka mata uang dengan uang riil setiap minggu, tanpa interpretasi.
Panduan ini berjalan melalui proses lengkap: menarik data COT dari FXMacroData API, menghitung metrik yang berasal kunci, membangun filter posisi, dan menerapkannya ke aliran kerja entri Anda.
Apa yang Akan Anda Bangun
- Fungsi Python yang mengambil data COT mingguan untuk salah satu dari delapan mata uang yang didukung
- Metrik normalisasi posisi bersih (net sebagai % dari bunga terbuka)
- Filter penentuan posisi multi-kondisi dengan ambang batas yang dapat dikonfigurasi
- Sebuah gerbang entri perdagangan yang mengembalikan sinyal arah:
longAku akan pergi.short, atauneutral - Sebuah panduan praktis menggunakan EUR/USD sebagai contoh kerja
Persyaratan
- Kunci API FXMacroData tersedia di /langganan. titik akhir COT termasuk dalam semua rencana berbayar.
- Python 3.9+ dengan
requestsperpustakaan yang terinstal (pip install requests)). - Keterampilan dasar dalam terminologi laporan COT (non-komersial long, short, open interest). Panduan Laporan COT untuk Pedagang FX mencakup dasar-dasarnya.
- Opsional,
pandasuntuk langkah-langkah manipulasi data (pip install pandas)).
Langkah 1 Mengambil Data COT dari API
FXMacroData COT Endpoint mengembalikan posisi non-komersial dan komersial mingguan untuk futures mata uang. Mata uang yang didukung adalah AUD, CAD, CHF, EUR, GBP, JPY, NZD, dan USD. Setiap catatan berisi jumlah kontrak panjang, pendek, dan bersih untuk peserta non-kommersial dan komersil, ditambah total bunga terbuka.
curl "https://fxmacrodata.com/api/v1/cot/eur?api_key=YOUR_API_KEY&start=2023-01-01"
Jawaban JSON memiliki struktur ini:
{
"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
}
]
}
Di Python, bungkus panggilan ini dalam pembantu yang mengembalikan daftar data yang disortir secara kronologis:
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])
Mengapa 12 Bulan Sejarah?
The filter thresholds in Step 3 are expressed as percentile ranks over the trailing year. One year is long enough to capture a full positioning cycle for most major pairs without including regime changes that are too old to be relevant. You can widen the window to 2–3 years for currencies with slower-moving positioning cycles like JPY or CHF.
Langkah 2 Menghitung Metrik Posisi Turunan
Jumlah kontrak mentah sulit dibandingkan di berbagai mata uang dan sepanjang waktu. panjang bersih 80.000 kontrak berarti sesuatu yang sangat berbeda dalam EUR futures (pasar besar, likuid) versus CHF (keuntungan terbuka yang lebih kecil).
2a. Posisi bersih sebagai persentase dari bunga terbuka
Membagi posisi bersih nonkomersial dengan total bunga terbuka menghasilkan rasio normalisasi antara -1 dan +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. Posisi Percentile Rank
Untuk mengetahui apakah posisi saat ini ekstrem, Anda perlu konteks historis. net_pct within the trailing window converts an absolute number into a percentile (0 = most bearish on record, 100 = most bullish). A percentile above 75 indicates a crowded long; below 25 indicates a crowded short.
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")
Menjelaskan Percentile Rank
- 75th100th percentile: Tidak komersial yang penuh sesak panjang. Memihak entri panjang sementara tren memegang; menambahkan risiko pembalikan jika fundamental berubah.
- 25th75th percentile: Zona netral. tidak kuat posisi angin belakang atau angin ke depan sinyal lainnya harus memimpin.
- 0th25th percentile: Tidak komersial yang penuh dengan pendek. mendukung entri pendek sementara tren bertahan; menambahkan risiko memeras pada setiap kejutan bullish.
2c. Momentum Posisi
Arah tren penting seperti tingkat saat ini. Net long yang tumbuh adalah sinyal yang berbeda dari net long yang telah stabil atau mulai menyusut. Hitung perubahan 4 minggu di net_pct untuk menangkap momentum:
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}")
Langkah 3 Buat Filter Masuk
Dengan tiga metrik di tangan, Anda dapat membangun fungsi filter yang mengembalikan sinyal arah untuk mata uang apa pun. Logika menggabungkan tingkat saat ini, konteks persentil, dan arah momentum ke dalam satu gerbang.
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)
Output sampel ketika EUR non-komersial berjalan panjang penuh dan menambahkan ke itu:
{
"currency" : "EUR",
"signal" : "long",
"net_pct" : 0.231,
"percentile": 82.4,
"momentum" : 0.018,
"date" : "2025-03-25"
}
Langkah 4 Terapkan Filter untuk entri perdagangan
Fungsi filter mengembalikan salah satu dari tiga sinyal longAku akan pergi. short, atau neutral. Penggunaan yang dimaksudkan adalah sebagai gerbang di depan sinyal masuk utama Anda: hanya mengambil pengaturan panjang ketika filter COT mengatakan long (atau neutral Jika Anda lebih agresif), dan hanya mengambil pengaturan singkat ketika filter COT mengatakan shortAku tidak tahu.
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()}")
Catatan Pelaksanaan: COT adalah sinyal mingguan
Data COT dirilis setiap hari Jumat untuk posisi mulai hari Selasa sebelumnya. Itu membuatnya menjadi sinyal frekuensi rendah cocok untuk menyaring bias mingguan atau harian, bukan entri intraday. Jalankan kembali filter sekali seminggu setelah rilis ET jam 3:30 malam Jumat, cache hasilnya, dan gunakan sebagai gerbang bias statis untuk semua entri pada minggu berikutnya. Dokumen titik akhir COT untuk memverifikasi waktu rilis.
Langkah 5 Perluas ke Dashboard Multi-Valuasi
Mengoperasikan filter di semua delapan mata uang yang didukung sekaligus memberi Anda dasbor posisi mingguan. Ini berguna untuk mengidentifikasi pasangan FX mana yang memiliki angin belakang yang paling jelas didorong oleh spekulator dan mana yang harus dihindari karena posisi bekerja melawan arah Anda.
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}")
Hasil sampel mingguan:
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
Membaca gambar ini: non-komersial adalah posisi EUR, AUD, CHF, dan NZD berjangka panjang; JPY dan USD pendek; dan netral pada CAD dan GBP. Seorang pedagang yang mempertimbangkan EUR/JPY panjang akan menemukan kedua kaki dikonfirmasi oleh arus spekulator. Seorang trader yang mempertimbangkan USD/CAD panjang akan menghadapi COT headwind pada USD dan latar belakang netral di CAD pengaturan yang lebih lemah dari perspektif posisi.
Langkah 6 Gabungkan COT dengan lapisan konfirmasi makro
Jika spekulan EUR panjang dan diferensial suku bunga ECB-Fed meluas menguntungkan EUR, posisi dan kasus fundamental sejajar.
Gunakan titik akhir suku bunga kebijakan untuk menarik nilai tukar terbaru untuk setiap mata uang dan menghitung diferensial:
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"])
Ketika COT dan Makro Tidak Setuju
Ketika posisi sangat padat di satu sisi tetapi fundamental makro bergeser menentangnya (misalnya, berjangka JPY pendek besar tetapi Bank of Japan mulai mengetatkan), rezim mungkin sedang transisi. Ini adalah pengaturan yang menghasilkan pergerakan tercepat dan terbesar seringkali ke arah yang memaksa penutup pendek atau likuidasi panjang. Dalam kasus seperti itu, filter COT saja tidak cukup; pantau Sejarah suku bunga kebijakan bank sentral dekat untuk setiap pergeseran yang bisa memicu posisi unwind.
Ringkasan
Anda sekarang memiliki filter entri berbasis COT lengkap yang dibangun pada data FXMacroData API langsung.
- Dapatkan catatan COT mingguan untuk mata uang atau mata uang yang Anda perdagangan.
- Menghitung posisi bersih sebagai persentase bunga terbuka untuk normalisasi di seluruh mata uang.
- Peringkat posisi saat ini dalam tahun terakhir untuk mengidentifikasi kondisi ekstrem atau netral.
- Hitung momentum 4 minggu untuk memastikan apakah posisi sedang berlari menguntungkanmu.
- Gerbang entri perdagangan Anda terhadap sinyal filter hanya melanjutkan ketika keselarasan COT sesuai arah Anda.
- Opsional menggabungkan dengan pemeriksaan tingkat-perbedaan untuk konfirmasi dua faktor.
Dashboard multi-mata uang penuh memberi Anda gambaran mingguan di mana uang spekulan diposisikan, sehingga Anda memasuki perdagangan dengan aliran institusional daripada melawan itu. inflasi atau pekerjaan titik akhir untuk membangun model skor makro yang mempertimbangkan ketiga sinyal bersama-sama posisi, perbedaan suku bunga, dan momentum pertumbuhan/inflasi untuk gambaran yang lebih lengkap.