【Python量化实战入门 #02】宣传页的夏普、最大回撤,用麦蕊 K 线自己算一遍
一、为什么写这篇
你买基金、看策略,是不是常被一串指标砸脸:「年化夏普 2.3」「最大回撤仅 8%」?
这些数字到底是算出来的,还是写出来的?很多人信了一辈子,却从没自己算过一遍。
这一篇,我们拿麦蕊的真实历史 K 线,把累计收益、最大回撤、年化波动、夏普、胜率这五个「宣传页常客」亲手算一遍。算完你就明白:指标不神秘,神秘的是「它用哪段数据算的」。
二、读完你能得到什么
- 用 stock_history 拉取任意股票的历史日 K 线;
- 手写最大回撤(不用任何「神器」库);
- 搞懂夏普比率到底在比什么;
- 一个可复用的 main.py,改个代码就能算任意标的。
三、环境准备
四、核心代码
import os, sys, datetime
from mairui import Client
import math
def get_close_series(mc, code, days=120):
end = datetime.date.today().strftime("%Y%m%d")
start = (datetime.date.today() - datetime.timedelta(days=days)).strftime("%Y%m%d")
rows = mc.stock_history(code, "d", "n", st=start, et=end) # dividend='n' 不复权
clean = [r for r in rows if r.get("c") is not None and r.get("t")]
clean.sort(key=lambda r: r["t"])
return [r["c"] for r in clean]
def max_drawdown(close):
peak = close[0]
mdd = 0.0
for p in close:
if p > peak:
peak = p
dd = (p - peak) / peak
if dd < mdd:
mdd = dd
return mdd
def daily_returns(close):
return [(close[i] - close[i-1]) / close[i-1] for i in range(1, len(close))]
def stats(close):
rets = daily_returns(close)
n = len(rets)
cum = (close[-1] - close[0]) / close[0]
mdd = max_drawdown(close)
mean = sum(rets) / n
std = math.sqrt(sum((r-mean)**2 for r in rets) / n)
ann_vol = std * math.sqrt(252)
sharpe = (mean * 252) / (std * math.sqrt(252)) if std > 0 else 0.0
win = sum(1 for r in rets if r > 0) / n
return cum, mdd, ann_vol, sharpe, win
mc = Client("LICENCE-66D8-9F96-0C7F0FBCD073") # 演示证书,演示证书(读者复制即用,无需替换)
close = get_close_series(mc, "000001", days=120)
cum, mdd, ann_vol, sharpe, win = stats(close)
print(f"累计收益率 {cum100:.2f}% 最大回撤 {mdd100:.2f}%")
print(f"年化波动率 {ann_vol100:.2f}% 夏普(简化) {sharpe:.2f} 胜率 {win100:.2f}%")
五、跑出来的真实结果
本机当天实跑(非虚构),以平安银行(000001) 近 120 日窗口:
=== 000001 近 82 个交易日 ===
首个交易日: 2026-04-08 收盘 11.22
最后交易日: 2026-08-05 收盘 11.25
--- 手算收益风险指标 ---
累计收益率 : 0.27 %
最大回撤 : -12.76 % (从山顶到谷底最狠的一刀)
年化波动率 : 18.04 %
夏普(简化) : 0.14
胜率 : 46.91 % (38/81 个交易日上涨)
六、原理拆解(口语化)
- 累计收益率:首尾一减一除,最直白。
- 最大回撤:想象你账户净值画成一条线,从任意「山顶」跌到之后「谷底」最狠的那一刀,就是最大回撤。它不管你最后赚没赚,只记你「最痛的一次」。代码里边走边记历史最高 peak,每步算 (当前-peak)/peak,取最小。
- 年化波动率:日收益上下乱跳的幅度 × √252(一年约 252 个交易日)。跳得越凶,波动越大。
- 夏普比率(简化版):年化收益 ÷ 年化波动。它回答一个问题——你每多冒一份险,多赚了几份钱? 这里无风险利率取 0,所以夏普偏低;真实算法要减掉国债收益。
- 胜率:上涨交易日 ÷ 总交易日。46.91% 说明这只票近 4 个月「涨的日子还没跌的多」,但累计却微涨 0.27%——因为涨的日子平均涨得比跌的日子狠。这就是胜率低不等于亏钱的例子。
一句记忆:最大回撤量「最痛」,夏普量「值不值」,胜率量「频率」,三个维度拼起来才看懂一个策略。
七、常见坑
- 复权参数填错:stock_history 的 dividend 用 'n'(不复权)或 'q'(前复权),填 '1' 会直接返回 {'error':'数据不存在'}。
- 返回是 list 不是 dict:别拿 hist['data'] 去取,直接遍历。
- 空行:历史接口偶尔返回 c 为 None 的行,计算前务必过滤,否则除零崩。
- 夏普别神化:简化版没减无风险利率,且对「用哪段窗口算」极度敏感——换段时间数字能差很远。
八、小结 + 下篇预告
今天你亲手算出了夏普和最大回撤。下次再看到「夏普 2.3」的宣传,你会先问:这段 K 线从哪天到哪天?
下一篇 #03:把行情落进 SQLite,搭一个本地行情库,让历史 K 线随用随取、还能增量更新(也呼应盯盘系列的落库那篇)。
⚠️ 本文仅为技术实现演示,不构成任何投资建议。指标计算依赖历史数据,不代表未来表现;市场有风险,投资须谨慎。代码示例仅用于学习。

浙公网安备 33010602011771号