期货持仓报告,简称COT(Commitment of Traders)报告,记录机构投资者包括商业公司和对冲基金的期货持仓数据。由美国期货交易委员会(CFTC)公布,公布时间是每周五下午2点30分(美东时间)。
我们关注的是传统格式(Legacy Format)的COT报告,汇总了期货和期权的持仓数据。
传统格式的COT报告包含以下数据:
非商业持仓代表了大玩家对未来价格的预期,如果它们押注价格上涨,大额买入推动价格上涨,反之亦然。但我们从均值回归的角度解读这个指标,当非商业净多头头寸创新高,后续将缺乏足够的买盘支持价格的进一步上涨,价格有可能处于周期性顶部;相反当净空头创历史新高,表明多数投资者都押注价格下跌,这时候价格可能筑底。
import os
import time
import requests
import quandl
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
plt.style.use("ggplot")
从Quandl下载COT报告。
Quandl是金融数据提供商,有大量的免费数据集可以使用,用户需要先申请API密钥。
为了方便用Python获取数据,先安装三方库’quandl’.
codes = pd.read_csv("./cot.csv")
codes.head()
category | symbol | name | cnname | code_future | code | |
---|---|---|---|---|---|---|
0 | currency | ICE_DX | U.S. Dollar Index | 美元指数 | CHRIS/ICE_DX1 | CFTC/098662_FO_L_ALL |
1 | currency | CME_BT | Bitcoin CME Futures | 比特币 | NaN | CFTC/133741_FO_L_ALL |
2 | currency | CME_BP | British Pound | 英镑 | CHRIS/CME_BP1 | CFTC/096742_FO_L_ALL |
3 | currency | CME_CD | Canadian Dollar | 加元 | CHRIS/CME_CD1 | CFTC/090741_FO_L_ALL |
4 | currency | CME_JY | Japanese Yen | 日元 | CHRIS/CME_JY1 | CFTC/097741_FO_L_ALL |
quandl.ApiConfig.api_key = "your api key"
quandl.ApiConfig.api_key = os.getenv("QUANDL_API")
# 下载货币,指数,能源和金融期货的的COT报告
categories = ["currency", "energy", "index", "metal"]
cot_list = []
t0 = time.time()
for _, row in codes.loc[codes.category.isin(categories)].iterrows():
try:
symbol, code = row["symbol"], row["code"]
df = quandl.get(code)
df["symbol"] = symbol
cot_list.append(df)
print(f"{symbol}: download data success")
except Exception as e:
print(f"{symbol}: download failed {e}")
time.sleep(1)
# 下载完毕后先合并数据,然后保存在本地csv,方便后续使用
cot_joined = pd.concat(cot_list)
cot_joined.to_csv("./cot_history.csv", index=True)
elapsed = (time.time() - t0) / 60.0
print(f"tasks completed in {elapsed:.2f} minutes")
ICE_DX: download data success
CME_BT: download data success
CME_BP: download data success
CME_CD: download data success
CME_JY: download data success
CME_SF: download data success
CME_EC: download data success
CME_AD: download data success
CME_MP: download data success
CME_NE: download data success
CME_RA: download data success
CME_BR: download data success
CME_RU: download data success
CME_CL: download data success
CME_RB: download data success
CME_QG: download data success
CME_BZ: download data success
CME_EH: download data success
CME_ES: download data success
CME_NQ: download data success
CME_YM: download data success
CBOE_VX: download data success
CME_GC: download data success
CME_SI: download data success
CME_HG: download data success
CME_PL: download data success
CME_PA: download data success
tasks completed in 1.13 minutes
usecols = [
"Date", "Open Interest", "Noncommercial Long", "Noncommercial Short",
"Commercial Long", "Commercial Short", "symbol"
]
cot = pd.read_csv("./cot_history.csv", usecols=usecols, index_col="Date", parse_dates=True)
cot.tail()
Open Interest | Noncommercial Long | Noncommercial Short | Commercial Long | Commercial Short | symbol | |
---|---|---|---|---|---|---|
Date | ||||||
2020-09-08 | 9607.0 | 5102.0 | 2253.0 | 2375.0 | 6067.0 | CME_PA |
2020-09-15 | 10005.0 | 5619.0 | 2453.0 | 2190.0 | 6253.0 | CME_PA |
2020-09-22 | 9605.0 | 5489.0 | 2433.0 | 2198.0 | 5737.0 | CME_PA |
2020-09-29 | 9148.0 | 4912.0 | 2263.0 | 2236.0 | 5512.0 | CME_PA |
2020-10-06 | 9630.0 | 5490.0 | 2283.0 | 2255.0 | 5880.0 | CME_PA |
查看单个期货产品的非商业多头,空头和净头寸。
非商业期货净头寸 = 非商业期货多头 - 非商业期货空头。
# 选择期货品种和样本
symbol = "ICE_DX"
date_from = "2000-01-01"
date_to = "2020-10-15"
# 获取英文名称
name = codes.loc[codes.symbol == symbol, "name"].iloc[0]
# 筛选指定期货的数据,计算净头寸
cot_symbol = (cot.loc[cot.symbol == symbol]
.assign(net_pos=lambda x: x["Noncommercial Long"] - x["Noncommercial Short"])
.loc[date_from:date_to])
# 创建曲线图(同一个坐标显示多条曲线),观察非商业期货多头,空头和净头寸的变化
# fig, ax = plt.subplots(figsize=(12, 7))
# ax.plot(cot_symbol.index, cot_symbol["Noncommercial Long"], label="long position")
# ax.plot(cot_symbol.index, cot_symbol["Noncommercial Short"], label="short position")
# ax.plot(cot_symbol.index, cot_symbol["net_pos"], label="net position")
# ax.set_title(f"Speculative Positions for {name}", fontsize=15)
# ax.legend()
# 创建曲线图(分层),当样本比较大时可视化效果更好
plot_columns = ["Noncommercial Long", "Noncommercial Short", "net_pos"]
fig, axes = plt.subplots(nrows=3, ncols=1, sharex=True, sharey=False, figsize=(12, 10))
for i, ax in enumerate(axes.flatten()):
ax.plot(cot_symbol.index, cot_symbol[plot_columns[i]], "-r")
ax.set_title(f"{name}: {plot_columns[i]}")
计算所有期货品种的投机性多头或空头的百分比增长,将最新一期的增长率做横向对比,观察短期市场情绪的变化。
# 非商业期货多头或空头
target = "Noncommercial Short"
# 计算头寸的百分比变化
names = codes.filter(["symbol", "name"]).set_index("symbol")["name"]
pos_chg = (cot.groupby("symbol")
.apply(lambda x: x[target].pct_change().iloc[-1])
.reset_index()
.rename(columns={0: "change"})
.assign(name=lambda x: x.symbol.map(names))
.sort_values("change", ascending=False))
# pos_chg.head()
# 自定义调色板,获得更好的可视化效果
cmap = mpl.cm.get_cmap("RdBu", len(pos_chg))
colors = cmap(np.linspace(0, 1, len(pos_chg)))
# 水平柱状图
fig, ax = plt.subplots(figsize=(10, 10))
ax.barh(pos_chg["name"], pos_chg["change"], color=colors)
ax.set_title(f"Last Weekly Percent Change of {target}", fontsize=15, loc="left")
一个常用的衍生指标是COT指数,基于非商业期货多头和空头头寸,用于衡量市场情绪。
计算公式: c i t = n e t p o s t − m i n ( n e t p o s ) m a x ( n e t p o s ) − m i n ( n e t p o s ) ci_t = \frac{netpos_t - min(netpos)}{max(netpos) - min(netpos)} cit=max(netpos)−min(netpos)netpost−min(netpos)
COT指数的取值范围在 [ 0 , 1 ] [0, 1] [0,1],越接近0,看空情绪越强烈,越接近1,看涨情绪越强烈。
def calculate_cot_index(cot: pd.DataFrame,
lookback: int = 52,
smooth: int = 0,
last: bool = False) -> pd.Series:
"""计算COT指数
Args:
cot(pd.DataFrame): COT报告,包含'Noncommercial Long','Noncommercial Short'两个字段
索引为时间序列索引(DatetimeIndex)
lookback(int): 回溯期
smooth(int): 是否将cot指数移动平滑,0表示不需要
last(bool): True返回最新一期的COT指数,False返回全部数据
"""
# 样本观测值必须大于(lookback + smooth)
if len(cot) <= (lookback + smooth):
raise Exception(f"not enough samples")
netpos = cot["Noncommercial Long"] - cot["Noncommercial Short"]
min_netpos = netpos.rolling(lookback).min()
max_netpos = netpos.rolling(lookback).max()
cot_index = (netpos - min_netpos) / (max_netpos - min_netpos)
if smooth > 0:
cot_index = cot_index.rolling(smooth).mean()
if last:
return cot_index.iloc[-1]
return cot_index
计算所有期货品种的COT指数,取最新一期的值做横向对比。
lookback = 52
smooth = 4
cot_index = (cot.groupby("symbol")
.apply(calculate_cot_index, lookback=lookback, smooth=smooth, last=True)
.reset_index()
.rename(columns={0: "cot_index"})
.assign(name=lambda x: x.symbol.map(names))
.sort_values("cot_index", ascending=False))
# cot_index.head()
cmap = mpl.cm.get_cmap("viridis", len(cot_index))
colors = cmap(np.linspace(0, 1, len(cot_index)))
fig, ax = plt.subplots(figsize=(10, 10))
ax.barh(cot_index["name"], cot_index["cot_index"], color=colors)
ax.set_title("COT index (Market Sentiment)", fontsize=15)
ax.set_xlabel("cot index")
查看单个期货品种的COT指数。
symbol = "CME_GC"
lookback = 156
smooth = 4
name = codes.loc[codes.symbol == symbol, "name"].iloc[0]
ci = calculate_cot_index(cot.loc[cot.symbol == symbol], lookback, smooth)
ci.dropna(inplace=True)
fig, ax = plt.subplots(figsize=(12, 7))
ax.plot(ci.index, ci, color="black")
ax.set_title(f"COT Index for {name}", fontsize=15)
ax.fill_between(ci.index, y1=0, y2=0.2, color="red", alpha=0.2)
ax.fill_between(ci.index, y1=0.8, y2=1.0, color="green", alpha=0.2)
你们的点赞和收藏是我们最大的创作动力,我们每天都会为大家带来数据科学和量化交易领域的精品内容。
蜂鸟数据:开源金融数据接口,一个API连接世界金融市场。
蜂鸟数据团队由业界顶尖的数据工程师,数据科学家和宽客组成,我们正努力构建一个开源的金融数据库,并提供API接口,目标是令金融数据开源化和平民化。
浏览并测试我们接口吧,目前覆盖股票,外汇,商品期货,数字货币和宏观经济领域,包括实时报价(tick)和历史数据(分钟),提供REST API和Websocket两种接入方式,能够满足金融分析师,量化交易和理财app的需求。
蜂鸟数据API接口文档
登录蜂鸟官网,注册免费获取API密钥