skills/windpy-sdk/scripts/wind_client.py

287 lines
10 KiB
Python
Raw Permalink Normal View History

"""
Wind 客户端 直连 WindPy自动检测登录状态返回 pandas DataFrame
用法:
from wind_client import wsd, wss, wset, wsq, edb, health
from wind_client import wsi, wst, wsee, wses, wsed
from wind_client import tdays, tdaysoffset, tdayscount
from wind_client import weqs, htocode, wai, wgel
df = wsd("000300.SH", "close,pct_chg", "-30D")
df = wss("600519.SH,000858.SZ", "sec_name,close,pe_ttm", "tradeDate=20241231")
df = wset("sectorconstituent", "date=20241231;windcode=000300.SH")
df = wsq("600519.SH", "rt_last,rt_pct_chg")
df = edb("M0001383", "2024-01-01", "2024-12-31")
dates = tdays("20260101", "20260226")
"""
import os
import re
import sys
import atexit
import pandas as pd
# ---------------------------------------------------------------------------
# WindPy 连接管理
# ---------------------------------------------------------------------------
_w = None
def _check_auto_login() -> bool:
"""检查 Wind 是否配置了自动登录。"""
config_path = os.path.expanduser("~/.Wind/WFT/users/Auth/user.config")
if not os.path.exists(config_path):
return False
try:
with open(config_path, "r", encoding="utf-8") as f:
content = f.read()
match = re.search(r'isAutoLogin="(\d+)"', content)
return match is not None and match.group(1) == "1"
except Exception:
return False
def _ensure_connected():
"""确保 WindPy 已连接,首次调用时自动 w.start()。"""
global _w
if _w is not None and _w.isconnected():
return _w
from WindPy import w as _wind
_w = _wind
if _w.isconnected():
return _w
if not _check_auto_login():
print("[wind_client] 警告: Wind 未开启自动登录,可能弹出登录窗口", file=sys.stderr)
print("[wind_client] 请在 Wind 终端设置中勾选「自动登录」", file=sys.stderr)
ret = _w.start(waitTime=30)
if not _w.isconnected():
raise RuntimeError(f"Wind 连接失败: {ret}")
atexit.register(_cleanup)
return _w
def _cleanup():
global _w
if _w is not None and _w.isconnected():
_w.stop()
_w = None
# ---------------------------------------------------------------------------
# WindData → DataFrame 转换
# ---------------------------------------------------------------------------
def _is_synthetic_codes(codes: list) -> bool:
"""判断 Codes 是否为 wset 返回的行号 ("1","2","3",...)。"""
if not codes:
return False
return all(isinstance(c, str) and c.isdigit() for c in codes[:10])
def _serialize_times(times) -> list:
"""将 WindData.Times 转为字符串列表。"""
if not times:
return []
return [str(t) for t in times]
def _to_dataframe(result, endpoint: str = "") -> pd.DataFrame:
"""将 WindData 对象转为 DataFrame。"""
if hasattr(result, 'ErrorCode') and result.ErrorCode != 0:
raise RuntimeError(f"Wind error {result.ErrorCode}: {result.Data}")
data = result.Data if hasattr(result, 'Data') and result.Data else []
fields = result.Fields if hasattr(result, 'Fields') and result.Fields else []
codes = [str(c) for c in result.Codes] if hasattr(result, 'Codes') and result.Codes else []
times = result.Times if hasattr(result, 'Times') and result.Times else []
if not data:
return pd.DataFrame()
# wset: Codes 为行号 ("1","2",...), 不是真实代码
if _is_synthetic_codes(codes) and fields:
return pd.DataFrame(dict(zip(fields, data)))
# 截面接口: 按代码索引
_cross_section = {"wss", "wsq", "wsee", "wsed"}
if endpoint in _cross_section and codes:
df = pd.DataFrame(dict(zip(fields, data)), index=codes)
df.index.name = "code"
return df
# 判断是否为时间序列: data[0] 长度匹配 times 长度
row_len = len(data[0]) if data else 0
is_time_series = times and row_len == len(times)
if is_time_series:
n_codes = len(codes) if codes else 0
if n_codes > 1 and len(data) == n_codes:
# 多代码单字段
df = pd.DataFrame(dict(zip(codes, data)), index=pd.to_datetime(times))
df.index.name = "date"
return df
# 单代码: 按字段分列
df = pd.DataFrame(dict(zip(fields, data)), index=pd.to_datetime(times))
df.index.name = "date"
return df
# 截面 fallback
if codes and row_len == len(codes):
df = pd.DataFrame(dict(zip(fields, data)), index=codes)
df.index.name = "code"
return df
# fallback
if fields:
return pd.DataFrame(dict(zip(fields, data)))
return pd.DataFrame(data)
# ---------------------------------------------------------------------------
# 公开 API — 对齐 WindPy 原生接口
# ---------------------------------------------------------------------------
def wsd(codes: str, fields: str, begin: str = "", end: str = "", options: str = "") -> pd.DataFrame:
"""日级时间序列,等价于 w.wsd()。"""
w = _ensure_connected()
result = w.wsd(codes, fields, begin, end, options)
return _to_dataframe(result, "wsd")
def wss(codes: str, fields: str, options: str = "") -> pd.DataFrame:
"""截面快照,等价于 w.wss()。"""
w = _ensure_connected()
result = w.wss(codes, fields, options)
return _to_dataframe(result, "wss")
def wset(table_name: str, options: str = "") -> pd.DataFrame:
"""报表数据集,等价于 w.wset()。"""
w = _ensure_connected()
result = w.wset(table_name, options)
return _to_dataframe(result, "wset")
def wsq(codes: str, fields: str, options: str = "") -> pd.DataFrame:
"""实时行情快照,等价于 w.wsq()。"""
w = _ensure_connected()
result = w.wsq(codes, fields, options)
return _to_dataframe(result, "wsq")
def edb(codes: str, begin: str = "", end: str = "", options: str = "") -> pd.DataFrame:
"""宏观经济指标,等价于 w.edb()。"""
w = _ensure_connected()
result = w.edb(codes, begin, end, options)
return _to_dataframe(result, "edb")
def wsi(codes: str, fields: str, begin: str = "", end: str = "", options: str = "") -> pd.DataFrame:
"""分钟K线序列等价于 w.wsi()。options 常用: BarSize=1/5/15/30/60"""
w = _ensure_connected()
result = w.wsi(codes, fields, begin, end, options)
return _to_dataframe(result, "wsi")
def wst(codes: str, fields: str, begin: str = "", end: str = "", options: str = "") -> pd.DataFrame:
"""日内Tick跳价等价于 w.wst()。"""
w = _ensure_connected()
result = w.wst(codes, fields, begin, end, options)
return _to_dataframe(result, "wst")
def wsee(codes: str, fields: str, options: str = "") -> pd.DataFrame:
"""板块多维数据,等价于 w.wsee()。codes 为板块/指数代码。"""
w = _ensure_connected()
result = w.wsee(codes, fields, options)
return _to_dataframe(result, "wsee")
def wses(codes: str, fields: str, begin: str = "", end: str = "", options: str = "") -> pd.DataFrame:
"""板块时间序列,等价于 w.wses()。"""
w = _ensure_connected()
result = w.wses(codes, fields, begin, end, options)
return _to_dataframe(result, "wses")
def wsed(codes: str, fields: str, options: str = "") -> pd.DataFrame:
"""板块查询,等价于 w.wsed()。codes 为 SectorID。"""
w = _ensure_connected()
result = w.wsed(codes, fields, options)
return _to_dataframe(result, "wsed")
def tdays(begin: str = "", end: str = "", options: str = "") -> list:
"""交易日列表,等价于 w.tdays()。返回日期字符串列表。"""
w = _ensure_connected()
result = w.tdays(begin, end, options)
if hasattr(result, 'ErrorCode') and result.ErrorCode != 0:
raise RuntimeError(f"Wind error {result.ErrorCode}: {result.Data}")
data = result.Data if hasattr(result, 'Data') and result.Data else []
return _serialize_times(data[0]) if data else []
def tdaysoffset(offset: int, begin: str = "", options: str = "") -> str:
"""交易日偏移,等价于 w.tdaysoffset()。返回偏移后的日期字符串。"""
if not begin:
from datetime import date
begin = date.today().strftime("%Y%m%d")
w = _ensure_connected()
result = w.tdaysoffset(offset, begin, options)
if hasattr(result, 'ErrorCode') and result.ErrorCode != 0:
raise RuntimeError(f"Wind error {result.ErrorCode}: {result.Data}")
data = result.Data if hasattr(result, 'Data') and result.Data else []
return str(data[0][0]) if data and data[0] else ""
def tdayscount(begin: str = "", end: str = "", options: str = "") -> int:
"""交易日计数,等价于 w.tdayscount()。返回交易日数量。"""
w = _ensure_connected()
result = w.tdayscount(begin, end, options)
if hasattr(result, 'ErrorCode') and result.ErrorCode != 0:
raise RuntimeError(f"Wind error {result.ErrorCode}: {result.Data}")
data = result.Data if hasattr(result, 'Data') and result.Data else []
return int(data[0][0]) if data and data[0] else 0
def weqs(filtername: str, options: str = "") -> pd.DataFrame:
"""条件选股,等价于 w.weqs()。"""
w = _ensure_connected()
result = w.weqs(filtername, options)
return _to_dataframe(result, "weqs")
def htocode(codes: str, sec_type: str, options: str = "") -> pd.DataFrame:
"""代码转换(名称/简称→Wind代码等价于 w.htocode()。"""
w = _ensure_connected()
result = w.htocode(codes, sec_type, options)
return _to_dataframe(result, "htocode")
def wai(func: str, input: str, options: str = "") -> pd.DataFrame:
"""智能API等价于 w.wai()。"""
w = _ensure_connected()
result = w.wai(func, input, options)
return _to_dataframe(result, "wai")
def wgel(funname: str, windid: str, options: str = "") -> pd.DataFrame:
"""企业库,等价于 w.wgel()。"""
w = _ensure_connected()
result = w.wgel(funname, windid, options)
return _to_dataframe(result, "wgel")
def health() -> dict:
"""检查 Wind 连接状态。"""
try:
w = _ensure_connected()
return {"status": "ok", "connected": w.isconnected()}
except Exception as e:
return {"status": "error", "message": str(e)}