← 返回博客列表

量化投资从入门到精通

【量化投资从入门到精通 #03】一个策略好不好看哪几个数收益回撤夏普在说什么

2026年08月22日 17:12 · 麦蕊智数 MyData

一、这个方法解决什么投资问题

上一篇你把"活跃股"写成了一条规则、并用真实行情跑了出来。下一步自然要问:这条规则(或者说,任何策略)到底行不行?

这里最容易踩的坑,是只看一个数字——"赚了多少"。但"赚 20%"可能是靠满仓押一只、途中回撤 40% 熬回来的;也可能是稳稳涨上来的。只看收益,等于只看体检报告里的体重。

判断一个策略好不好,得给它做一套"体检":赚了多少、最疼的一次亏多少、波动大不大、每冒一份险换回几份收益、赢的频率高不高。这五个角度合起来,才看得清一个策略的真面目。本篇就用麦蕊的真实历史 K 线,把这套体检亲手算一遍。

二、核心思想:五个指标,回答五个不同的问题

指标 它在回答什么投资问题 直觉
累计收益率 这段时间里,到底净赚了多少? 体重
最大回撤 最惨的一次,从山顶摔到谷底亏了多少? 最痛的一刀
年化波动率 上下乱跳的幅度有多大? 抖不抖
夏普比率 每多冒一份险,多赚了几份钱?值不值? 性价比
胜率 涨的日子占多少? 频率

关键认知:这五个指标是互补的,不是互相替代。收益高但回撤也狠,不代表"好";夏普高但胜率低,也可能是"偶尔大赚、经常小亏"。看策略,要像看人一样——多维度,不能一票否决也不能一美遮百丑。

三、实操演示:用麦蕊历史 K 线做体检

我们取平安银行(000001) 近 120 个自然日的日 K 线(不复权),手写这五个指标。注意:这里不调任何"神器"库,全部手算——因为只有手算过,你才真正懂每个数字怎么来的。

import os, datetime, math
from mairui import Client

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 clean

def max_drawdown(close):
    peak, mdd = close[0], 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("<你的证书>")   # 证书从环境变量读取,不写死在代码里
rows = get_close_series(mc, "000001", days=120)
cum, mdd, ann_vol, sharpe, win = stats([r["c"] for r in rows])
print(f"累计收益率 {cum*100:.2f}%  最大回撤 {mdd*100:.2f}%")
print(f"年化波动率 {ann_vol*100:.2f}%  夏普 {sharpe:.2f}  胜率 {win*100:.2f}%")

四、结果解读:每个数字在说什么

实跑(2026-08-06)结果,平安银行近 82 个交易日:

累计收益率   :   0.27 %    年化波动率 :  18.04 %
最大回撤     : -12.76 %    夏普(简化) :   0.14
胜率         :  46.91 %  (38/81 个交易日上涨)

这组数字在讲一个什么故事?

  • 累计 +0.27%:四个月几乎没涨。单看这一个数字,你会觉得"这票废了"。
  • 但胜率只有 46.91%(涨的日子比跌的少),累计却没亏——说明涨的日子平均涨得比跌的日子狠。这就是"胜率低 ≠ 亏钱"的反直觉例子:频率低,但幅度占优。
  • 最大回撤 -12.76%:这四个月里,最惨的一段从高点摔到谷底亏了 12.76%。如果你满仓,这是你要硬扛的心理底线。
  • 年化波动 18.04%:日常上下跳得不算凶,中等偏稳。
  • 夏普 0.14:简化版(无风险利率取 0)偏低——意味着"冒这点险换来的额外收益很有限"。真实夏普要减掉国债收益,会更低。

合起来看:这是一个"不赚不亏、但过程颠簸、性价比一般"的标的/策略画像。如果这是你写的某个策略的历史成绩,体检报告已经告诉你:别被'没亏'骗了,它的风险调整后收益其实很薄。

一句记忆:最大回撤量"最痛",夏普量"值不值",胜率量"频率",累计收益量"结果"——四个维度拼起来,才看懂一个策略。

五、常见陷阱

  1. 复权参数填错stock_historydividend'n'(不复权)或 'q'(前复权),填 '1' 会直接返回 {'error':'数据不存在'}。算收益前先想清楚用哪种口径,前后要一致。
  2. 空行:历史接口偶尔返回 cNone 的行,计算前务必过滤,否则除零崩溃。
  3. 夏普别神化:简化版没减无风险利率,且对"用哪段窗口算"极度敏感——换个时间段,数字能差很远。看到"夏普 2.3"的宣传,先问:这段 K 线从哪天到哪天?
  4. 指标互相打架时不硬评:收益高 + 回撤狠,是"赚得猛"还是"赌得大"?要结合夏普一起看,别单拎一个漂亮数字下结论。

六、小结 + 下篇预告

本篇你带走了给策略"做体检"的一套基本功:五个指标,各自回答一个投资问题,合起来才客观。下一篇 #04 别被回测曲线骗了,我们聊一个更扎心的事——为什么你算出来的漂亮成绩,可能从头就是假的:前视偏差(用了未来才知道的信息)、幸存者偏差(只看了活下来的票)、过拟合(规则是为了拟合历史而写的)。这三条,是新手入门量化最先要立的"防骗护栏"。

免责声明

本文所有代码与数据仅用于量化方法演示,文中涉及的股票代码为示例、不构成任何投资建议,亦不构成收益承诺。历史指标不代表未来表现,投资有风险,决策需谨慎。

标签: 股票行情API
QQ 客服 3826425416 咨询时请提供证书号或订单号,便于快速处理
咨询