49 lines
1.9 KiB
Python
49 lines
1.9 KiB
Python
"""量化练习公共库:数据获取(带缓存+重试) + 常用指标 + 回测指标。"""
|
|
import os, time, pandas as pd, numpy as np
|
|
|
|
CACHE = os.path.join(os.path.dirname(__file__), "..", "data", "cache")
|
|
os.makedirs(CACHE, exist_ok=True)
|
|
|
|
def _fetch(symbol, start, end, adjust, retries=6, wait=2.0):
|
|
import akshare as ak
|
|
last = None
|
|
for i in range(retries):
|
|
try:
|
|
return ak.stock_zh_a_hist(symbol=symbol, period="daily",
|
|
start_date=start, end_date=end, adjust=adjust)
|
|
except Exception as e: # 网络间歇性抽风,重试
|
|
last = e
|
|
time.sleep(wait * (i + 1))
|
|
raise last
|
|
|
|
def get_daily(symbol="000001", start="20200101", end="20241231", adjust="qfq"):
|
|
"""拉单只A股日线(前复权),带本地缓存与自动重试。"""
|
|
f = os.path.join(CACHE, f"{symbol}_{start}_{end}_{adjust}.csv")
|
|
if os.path.exists(f):
|
|
return pd.read_csv(f, parse_dates=["日期"])
|
|
df = _fetch(symbol, start, end, adjust)
|
|
df.to_csv(f, index=False, encoding="utf-8-sig")
|
|
return df
|
|
|
|
def add_returns(df, price_col="收盘"):
|
|
df = df.copy()
|
|
df["ret"] = df[price_col].pct_change().fillna(0.0)
|
|
return df
|
|
|
|
def net_value(ret):
|
|
return (1 + pd.Series(ret).fillna(0.0)).cumprod()
|
|
|
|
def stats(ret, periods=252):
|
|
ret = pd.Series(ret).fillna(0.0)
|
|
nv = net_value(ret); total = nv.iloc[-1] - 1
|
|
ann = (1 + total) ** (periods / max(len(ret), 1)) - 1
|
|
vol = ret.std() * np.sqrt(periods)
|
|
sharpe = (ret.mean() * periods) / vol if vol > 0 else np.nan
|
|
dd = (nv / nv.cummax() - 1).min()
|
|
calmar = ann / abs(dd) if dd < 0 else np.nan
|
|
return {"总收益": f"{total:.2%}", "年化": f"{ann:.2%}", "年化波动": f"{vol:.2%}",
|
|
"夏普": f"{sharpe:.2f}", "最大回撤": f"{dd:.2%}", "卡玛": f"{calmar:.2f}"}
|
|
|
|
def max_drawdown(nv):
|
|
return (nv / nv.cummax() - 1).min()
|