Compare commits

..

2 Commits

4 changed files with 500 additions and 0 deletions
+54
View File
@@ -14,6 +14,60 @@ AI 协助分析交易数据和回测的仓库。主要用来做策略回测、
- 无可用镜像时才允许拉取,默认拉取 `python:3.11-slim`
- 示例: `docker images | grep python` 查看已有镜像,再用已有镜像执行
## 环境搭建(首次使用)
### 1. 创建容器
检查是否已有 `finance-talk` 容器(名称固定,在终端中运行):
```bash
docker ps -a --filter "name=finance-talk" --format "{{.Names}}"
```
若无输出,则按以下步骤创建:
```bash
# 查看已有 Python 镜像,优先复用
docker images | grep python
# 无可用镜像时才拉取(默认 python:3.11-slim
docker pull python:3.11-slim
# 创建并启动容器(映射当前目录到 /workspace)
docker run -d \
--name finance-talk \
-v $(pwd):/workspace \
-w /workspace \
python:3.13.11-slim \
sleep infinity
```
### 2. 安装依赖包
```bash
docker exec finance-talk pip install tushare akshare
```
### 3. 验证
```bash
docker exec finance-talk python -c "import tushare; import akshare; print('OK')"
```
### 4. 日常使用
Python 脚本通过 `docker exec` 在容器中执行:
```bash
docker exec finance-talk python your_script.py
```
若容器已存在但未运行,先启动:
```bash
docker start finance-talk
```
## Data Sources
按优先级排序:
+54
View File
@@ -14,6 +14,60 @@ AI 协助分析交易数据和回测的仓库。主要用来做策略回测、
- 无可用镜像时才允许拉取,默认拉取 `python:3.11-slim`
- 示例: `docker images | grep python` 查看已有镜像,再用已有镜像执行
## 环境搭建(首次使用)
### 1. 创建容器
检查是否已有 `finance-talk` 容器(名称固定,在终端中运行):
```bash
docker ps -a --filter "name=finance-talk" --format "{{.Names}}"
```
若无输出,则按以下步骤创建:
```bash
# 查看已有 Python 镜像,优先复用
docker images | grep python
# 无可用镜像时才拉取(默认 python:3.11-slim
docker pull python:3.11-slim
# 创建并启动容器(映射当前目录到 /workspace)
docker run -d \
--name finance-talk \
-v $(pwd):/workspace \
-w /workspace \
python:3.13.11-slim \
sleep infinity
```
### 2. 安装依赖包
```bash
docker exec finance-talk pip install tushare akshare
```
### 3. 验证
```bash
docker exec finance-talk python -c "import tushare; import akshare; print('OK')"
```
### 4. 日常使用
Python 脚本通过 `docker exec` 在容器中执行:
```bash
docker exec finance-talk python your_script.py
```
若容器已存在但未运行,先启动:
```bash
docker start finance-talk
```
## Data Sources
按优先级排序:
View File
+392
View File
@@ -0,0 +1,392 @@
"""五大期货交易所前20会员成交持仓排名数据获取工具。
交易所对照:
- CZCE 郑商所 → get_rank_table_czce (玻璃FG、纯碱SA、甲醇MA等)
- SHFE 上期所 → get_shfe_rank_table (铜CU、铝AL、螺纹RB、原油SC等)
- DCE 大商所 → get_dce_rank_table (铁矿石I、豆粕M、棕榈P等)
- CFFEX 中金所 → get_cffex_rank_table (IF、IC、IH、T等)
- GFEX 广期所 → futures_gfex_position_rank (工业硅SI、碳酸锂LC等)
交易所公布规则:
- CZCE: 同时公布品种汇总 + 各合约明细
- SHFE/CFFEX: 只公布合约明细,品种汇总由函数自行加总
- DCE: 只公布品种排名
- GFEX: 公布合约明细
"""
import akshare as ak
import pandas as pd
from datetime import datetime, timedelta
EXCHANGE_CODE = {
"CZCE": "CZCE",
"SHFE": "SHFE",
"DCE": "DCE",
"CFFEX": "CFFEX",
"GFEX": "GFEX",
}
# 常见品种 -> 交易所映射
VARIETY_EXCHANGE = {
# 郑商所 CZCE
"FG": "CZCE", "SA": "CZCE", "MA": "CZCE", "TA": "CZCE",
"RM": "CZCE", "OI": "CZCE", "ZC": "CZCE", "SF": "CZCE",
"SM": "CZCE", "UR": "CZCE", "PF": "CZCE", "PX": "CZCE",
"SH": "CZCE", "PR": "CZCE", "PL": "CZCE", "AP": "CZCE",
"CJ": "CZCE", "CY": "CZCE", "PK": "CZCE", "CF": "CZCE",
"SR": "CZCE",
# 上期所 SHFE
"CU": "SHFE", "AL": "SHFE", "ZN": "SHFE", "PB": "SHFE",
"NI": "SHFE", "SN": "SHFE", "AU": "SHFE", "AG": "SHFE",
"RB": "SHFE", "HC": "SHFE", "WR": "SHFE", "FU": "SHFE",
"BU": "SHFE", "RU": "SHFE", "SC": "SHFE", "NR": "SHFE",
"SP": "SHFE", "SS": "SHFE", "LU": "SHFE", "BC": "SHFE",
"AO": "SHFE", "BR": "SHFE", "EC": "SHFE",
# 大商所 DCE
"C": "DCE", "CS": "DCE", "A": "DCE", "B": "DCE",
"M": "DCE", "Y": "DCE", "P": "DCE", "FB": "DCE",
"BB": "DCE", "JD": "DCE", "L": "DCE", "V": "DCE",
"PP": "DCE", "J": "DCE", "JM": "DCE", "I": "DCE",
"EG": "DCE", "RR": "DCE", "EB": "DCE", "PG": "DCE",
"LH": "DCE", "LG": "DCE", "BZ": "DCE",
# 中金所 CFFEX
"IF": "CFFEX", "IC": "CFFEX", "IH": "CFFEX", "IM": "CFFEX",
"T": "CFFEX", "TF": "CFFEX", "TS": "CFFEX", "TL": "CFFEX",
# 广期所 GFEX
"SI": "GFEX", "LC": "GFEX", "PS": "GFEX",
}
# 各交易所的品种代码列表(用于 get_rank_sum
ALL_VARS_CZCE = ["WH","PM","CF","SR","TA","OI","RI","MA","FG","RS","RM","ZC",
"JR","LR","SF","SM","WT","TC","GN","RO","ER","SRX","SRY",
"WSX","WSY","CY","AP","UR","CJ","SA","PK","PF","PX","SH","PR","PL"]
ALL_VARS_DCE = ["C","CS","A","B","M","Y","P","FB","BB","JD","L","V","PP",
"J","JM","I","EG","RR","EB","PG","LH","LG","BZ"]
ALL_VARS_SHFE = ["CU","AL","ZN","PB","NI","SN","AU","AG","RB","WR","HC",
"FU","BU","RU","SC","NR","SP","SS","LU","BC","AO","BR","EC"]
ALL_VARS_CFFEX = ["IF","IC","IM","IH","T","TF","TS","TL"]
ALL_VARS_GFEX = ["SI","LC","PS"]
# 没有统一字段名映射时用的 to_int 辅助
def _to_int(x):
if isinstance(x, str):
return int(x.replace(",", ""))
return int(x)
# ---------------------------------------------------------------------------
# 底层接口,按交易所获取当日前20排名
# ---------------------------------------------------------------------------
def fetch_top20(date: str = None, exchange: str = None, variety: str = None) -> pd.DataFrame:
"""获取指定交易日该品种的前20会员成交持仓排名。
主力合约检测调用 :func:`main_contract`,本函数自动定位。
Parameters
----------
date : str or None
交易日,格式 YYYY-MM-DD 或 YYYYMMDD。None 表示最新。
exchange : str or None
交易所代码(CZCE/SHFE/DCE/CFFEX/GFEX)。None 时从 variety 推算。
variety : str
品种代码(如 "FG", "RB", "I", "IF")。如果为 None 则返回全品种。
Returns
-------
pd.DataFrame
前20会员排名数据。
列:rank, party_name, vol, vol_chg,
long_party_name, long_open_interest, long_open_interest_chg,
short_party_name, short_open_interest, short_open_interest_chg,
symbol, exchange
"""
if exchange is None and variety is not None:
exchange = VARIETY_EXCHANGE.get(variety.upper())
if exchange is None:
raise ValueError(f"未知品种 {variety},请指定 exchange 参数")
if exchange == "CZCE":
return _fetch_czce(date, variety)
elif exchange == "SHFE":
return _fetch_shfe(date, variety)
elif exchange == "DCE":
return _fetch_dce(date, variety)
elif exchange == "CFFEX":
return _fetch_cffex(date, variety)
elif exchange == "GFEX":
return _fetch_gfex(date, variety)
else:
raise ValueError(f"不支持的交易所: {exchange}")
def _fetch_czce(date=None, variety=None):
rank = ak.get_rank_table_czce(date=date)
if variety:
variety = variety.upper()
if variety in rank:
# 品种级别汇总
return _normalize_czce(rank[variety], variety, "CZCE")
# 合约级别: 找到该品种的所有合约
contract_keys = [k for k in rank if k.startswith(variety) and k != variety]
if not contract_keys:
raise ValueError(f"未找到品种 {variety} 的数据")
# 返回汇总版
return _normalize_czce(rank.get(variety, pd.DataFrame()), variety, "CZCE")
else:
# 全部品种 - 返回汇总
return rank # dict, 调用方自己处理
def _fetch_shfe(date=None, variety=None):
vars_list = [variety.upper()] if variety else None
rank = ak.get_shfe_rank_table(date=date, vars_list=vars_list)
# SHFE 返回 dict: {合约代码: DataFrame}
rows = []
for symbol, df in rank.items():
for _, r in df.iterrows():
rows.append({
"rank": int(r["名次"]),
"party_name": r["会员简称"],
"vol": r["成交量"],
"vol_chg": r["成交量增减"],
"long_party_name": r["持买单量-会员简称"],
"long_open_interest": r["持买单量-合计"],
"long_open_interest_chg": r["持买单量-增减"],
"short_party_name": r["持卖单量-会员简称"],
"short_open_interest": r["持卖单量-合计"],
"short_open_interest_chg": r["持卖单量-增减"],
"symbol": symbol,
})
df = pd.DataFrame(rows)
if variety:
df = df[df["symbol"].str.startswith(variety.upper())]
return df
def _fetch_dce(date=None, variety=None):
vars_list = [variety.upper()] if variety else None
rank = ak.get_dce_rank_table(date=date, vars_list=vars_list)
rows = []
for symbol, df in rank.items():
for _, r in df.iterrows():
rows.append({
"rank": int(r["名次"]),
"party_name": r["会员简称"],
"vol": r["成交量"],
"vol_chg": r["成交量增减"],
"long_party_name": r["持买单-会员简称"],
"long_open_interest": r["持买单"],
"long_open_interest_chg": r["持买单增减"],
"short_party_name": r["持卖单-会员简称"],
"short_open_interest": r["持卖单"],
"short_open_interest_chg": r["持卖单增减"],
"symbol": symbol,
})
return pd.DataFrame(rows)
def _fetch_cffex(date=None, variety=None):
vars_list = [variety.upper()] if variety else None
rank = ak.get_cffex_rank_table(date=date, vars_list=vars_list)
rows = []
for symbol, df in rank.items():
for _, r in df.iterrows():
rows.append({
"rank": int(r["名次"]),
"party_name": r["会员简称"],
"vol": r["成交量"],
"vol_chg": r["成交量增减"],
"long_party_name": r["持买单-会员简称"],
"long_open_interest": r["持买单-合计"],
"long_open_interest_chg": r["持买单-增减"],
"short_party_name": r["持卖单-会员简称"],
"short_open_interest": r["持卖单-合计"],
"short_open_interest_chg": r["持卖单-增减"],
"symbol": symbol,
})
return pd.DataFrame(rows)
def _fetch_gfex(date=None, variety=None):
vars_list = [variety.upper()] if variety else None
rank = ak.futures_gfex_position_rank(date=date, vars_list=vars_list)
rows = []
if isinstance(rank, dict):
for symbol, df in rank.items():
for _, r in df.iterrows():
rows.append({
"rank": int(r["名次"]),
"party_name": r["会员简称"],
"vol": r["成交量"],
"vol_chg": r["成交量增减"],
"long_party_name": r["持买单-会员简称"],
"long_open_interest": r["持买单-合计"],
"long_open_interest_chg": r["持买单-增减"],
"short_party_name": r["持卖单-会员简称"],
"short_open_interest": r["持卖单-合计"],
"short_open_interest_chg": r["持卖单-增减"],
"symbol": symbol,
})
return pd.DataFrame(rows)
def _normalize_czce(df, variety, exchange):
"""标准化 CZCE 品种汇总 DataFrame 字段名"""
df = df.copy()
df["symbol"] = df.get("symbol", variety)
df["exchange"] = exchange
return df
# ---------------------------------------------------------------------------
# 主力合约检测
# ---------------------------------------------------------------------------
def main_contract(variety: str, date: str = None) -> str:
"""检测该品种在指定交易日的主力合约代码。
逻辑:成交量/持仓量最大的合约即为主力。
"""
variety = variety.upper()
exchange = VARIETY_EXCHANGE.get(variety)
if exchange is None:
raise ValueError(f"未知品种 {variety}")
rank = ak.get_rank_table_czce(date=date) if exchange == "CZCE" else {}
if exchange == "CZCE":
# CZCE: 合约级别的 key 是 品种+年月 (FG609), 品种级是 FG
contract_keys = [k for k in rank if k.startswith(variety) and k != variety]
if not contract_keys:
raise ValueError(f"未找到品种 {variety} 的合约数据")
vols = {}
for ck in contract_keys:
df = rank[ck]
vol_sum = df["vol"].apply(_to_int).sum()
vols[ck] = vol_sum
return max(vols, key=vols.get)
else:
# 其他交易所:通过成交额最大的标的判断
raise NotImplementedError(f"{exchange} 主力合约检测待实现")
# ---------------------------------------------------------------------------
# 品种汇总对比(多交易所多品种)
# ---------------------------------------------------------------------------
def rank_summary(date: str = None, varieties: list = None) -> pd.DataFrame:
"""多品种前N会员成交量/持仓汇总对比(仅支持 CZCE 品种)。
使用 get_rank_table_czce 的品种级别汇总数据,
返回各品种前5/10/15/20成交量和多空持仓总和。
Parameters
----------
date : str
交易日。
varieties : list
品种代码列表。默认 FG, SA, MA, CF, TA, UR, PF, SM。
Returns
-------
pd.DataFrame
每行一个品种,含品种级前N成交量和多空持仓总和。
"""
if varieties is None:
varieties = ["FG", "SA", "MA", "CF", "TA", "UR", "PF", "SM"]
rank = ak.get_rank_table_czce(date=date)
rows = []
for var in varieties:
var = var.upper()
if var not in rank:
continue
df = rank[var]
df["vol_int"] = df["vol"].apply(_to_int)
df["long_int"] = df["long_open_interest"].apply(_to_int)
df["short_int"] = df["short_open_interest"].apply(_to_int)
total_vol = df["vol_int"].sum()
vol_5 = df.head(5)["vol_int"].sum()
vol_10 = df.head(10)["vol_int"].sum()
long_sum = df["long_int"].sum()
short_sum = df["short_int"].sum()
rows.append({
"var": var,
"vol_total": total_vol,
"vol_top5": vol_5,
"vol_top10": vol_10,
"vol_top20": df["vol_int"].sum(),
"long_sum": long_sum,
"short_sum": short_sum,
"net": long_sum - short_sum,
})
return pd.DataFrame(rows).sort_values("vol_total", ascending=False)
# ---------------------------------------------------------------------------
# 主力合约前20成交量
# ---------------------------------------------------------------------------
def main_top20(variety: str, date: str = None) -> pd.DataFrame:
"""获取品种主力合约的前20会员成交量排名。
Parameters
----------
variety : str
品种代码,如 "FG"
date : str
交易日。None 表示最新。
Returns
-------
pd.DataFrame
主力合约前20会员成交量排名 + 多空持仓排名。
"""
exchange = VARIETY_EXCHANGE.get(variety.upper())
if exchange != "CZCE":
raise NotImplementedError(f"目前仅支持 CZCE 品种的主力合约前20查询")
rank = ak.get_rank_table_czce(date=date)
contract_keys = [k for k in rank if k.startswith(variety.upper()) and k != variety.upper()]
vols = {ck: rank[ck]["vol"].apply(_to_int).sum() for ck in contract_keys}
main_code = max(vols, key=vols.get)
df = rank[main_code].copy()
df["symbol"] = main_code
# 数值列处理(CZCE 返回字符串含逗号)
for col in ["vol", "vol_chg", "long_open_interest", "long_open_interest_chg",
"short_open_interest", "short_open_interest_chg"]:
df[col] = df[col].apply(_to_int)
return df.sort_values("rank")
# ---------------------------------------------------------------------------
# 输出格式化
# ---------------------------------------------------------------------------
def print_top20(df: pd.DataFrame, title: str = ""):
"""打印前20成交量排名 + 多空前10。"""
if title:
print(f"\n{'='*60}")
print(f" {title}")
print(f"{'='*60}")
print(f"\n【成交量前20】")
print(f"{'排名':>4} {'会员':<20} {'成交量':>10} {'变化':>10}")
print("-" * 50)
for _, r in df.iterrows():
print(f"{int(r['rank']):>4} {str(r['vol_party_name']):<20} "
f"{int(r['vol']):>10,} {int(r['vol_chg']):>10,}")
print(f"\n【持多单前10】")
print(f"{'排名':>4} {'会员':<20} {'多单':>10} {'变化':>10}")
print("-" * 50)
for _, r in df.head(10).iterrows():
print(f"{int(r['rank']):>4} {str(r['long_party_name']):<20} "
f"{int(r['long_open_interest']):>10,} {int(r['long_open_interest_chg']):>10,}")
print(f"\n【持空单前10】")
print(f"{'排名':>4} {'会员':<20} {'空单':>10} {'变化':>10}")
print("-" * 50)
for _, r in df.head(10).iterrows():
print(f"{int(r['rank']):>4} {str(r['short_party_name']):<20} "
f"{int(r['short_open_interest']):>10,} {int(r['short_open_interest_chg']):>10,}")