如何用Python下载并分析期货持仓数据

期货持仓报告

期货持仓报告,简称COT(Commitment of Traders)报告,记录机构投资者包括商业公司和对冲基金的期货持仓数据。由美国期货交易委员会(CFTC)公布,公布时间是每周五下午2点30分(美东时间)。

我们关注的是传统格式(Legacy Format)的COT报告,汇总了期货和期权的持仓数据。

传统格式的COT报告包含以下数据:

  • 商业持仓(Commercial): 产品制造商/销售商的期货持仓,划分为多头和空头,用期货来对冲价格波动的风险。
  • 非商业持仓(Noncommercial): 对冲基金,投行和大型个人玩家的期货持仓,划分为多头和空头,简称"投机性头寸"。
  • 多头持仓(Long): 多头合约的数量。
  • 空头持仓(Short): 空头合约的数量。
  • 未平仓合约(Open Interest): 流通在外未交割的合约数量。
  • 无需报备头寸(Non-reportable Position): 未达到CFTC要求的未平仓合约数量,指小玩家的持仓。

非商业持仓代表了大玩家对未来价格的预期,如果它们押注价格上涨,大额买入推动价格上涨,反之亦然。但我们从均值回归的角度解读这个指标,当非商业净多头头寸创新高,后续将缺乏足够的买盘支持价格的进一步上涨,价格有可能处于周期性顶部;相反当净空头创历史新高,表明多数投资者都押注价格下跌,这时候价格可能筑底。

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")

1. 准备数据

从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

2. 投机性头寸

查看单个期货产品的非商业多头,空头和净头寸。

非商业期货净头寸 = 非商业期货多头 - 非商业期货空头。

# 选择期货品种和样本
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]}")

如何用Python下载并分析期货持仓数据_第1张图片

3. 短期头寸变化

计算所有期货品种的投机性多头或空头的百分比增长,将最新一期的增长率做横向对比,观察短期市场情绪的变化。

# 非商业期货多头或空头
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")

如何用Python下载并分析期货持仓数据_第2张图片

4. COT指数

一个常用的衍生指标是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)netpostmin(netpos)

  • c i t ci_t cit是第t期的COT指数
  • n e t p o s t netpos_t netpost是第t期的非商业期货净头寸
  • m i n ( n e t p o s ) min(netpos) min(netpos)是过去K期的非商业期货净头寸的最小值
  • m a x ( n e t p o s ) max(netpos) max(netpos)是过去K期的非商业期货净头寸的最大值

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")

如何用Python下载并分析期货持仓数据_第3张图片

查看单个期货品种的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)

如何用Python下载并分析期货持仓数据_第4张图片你们的点赞和收藏是我们最大的创作动力,我们每天都会为大家带来数据科学和量化交易领域的精品内容。

蜂鸟数据:开源金融数据接口,一个API连接世界金融市场。

蜂鸟数据团队由业界顶尖的数据工程师,数据科学家和宽客组成,我们正努力构建一个开源的金融数据库,并提供API接口,目标是令金融数据开源化和平民化。

浏览并测试我们接口吧,目前覆盖股票,外汇,商品期货,数字货币和宏观经济领域,包括实时报价(tick)和历史数据(分钟),提供REST API和Websocket两种接入方式,能够满足金融分析师,量化交易和理财app的需求。

蜂鸟数据API接口文档

登录蜂鸟官网,注册免费获取API密钥

你可能感兴趣的:(python,数据分析,python,数据分析,大数据,机器学习,数据挖掘)