V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
matters
V2EX  ›  投资

[续] 把整条量化研究流程又重做了一遍

  •  
  •   matters · Apr 30 · 409 views

    前阵子发了个小频率动量策略,当时回测看着还行,但实盘一跑就开始露问题。

    最近刚好在看《打开量化投资的黑箱》第九章,且根据 v 友建议,顺手把自己那一套研究流程从头捋了一遍,相当于是给之前那个策略“补课”。

    1. 策略起点

    之前做动量,基于一个很直觉的想法:

    👉 涨得多的继续涨

    当时没太多犹豫,直接写代码+回测。

    现在回头看,更像是一个“没被明确表达的假设”。

    现在会刻意把这一步写清楚一点:

    • 是基于经济逻辑?
    • 还是纯数据挖出来的?

    简单给自己加了一条约束:

    if 没有清晰逻辑:
        回测再好也不直接用
    

    2. 数据

    之前策略有个问题其实一直没认真处理数据。

    现在回头看,这一块基本决定了上限。

    ( 1 )幸存者偏差

    用当前成分股回测历史,这个坑就不展开了。

    ( 2 )时间对齐

    之前直接拼不同频率数据,本质就是未来函数。

    现在我给自己加了个很简单的检查:

    👉 每一份数据都问一句:当时能不能拿到

    顺手也把数据源换成了直接拉 API ,至少链路是“接近实盘”的:

    import requests
    import pandas as pd
    
    API_KEY = "YOUR_ALLTICK_API_KEY"
    
    def get_price_data(symbol="AAPL"):
        url = "https://api.alltick.co/marketdata/stock/history"
        params = {
            "symbol": symbol,
            "start_date": "2020-01-01",
            "end_date": "2023-01-01",
            "interval": "1d",
            "apikey": API_KEY
        }
        res = requests.get(url, params=params)
        data = res.json()["data"]
        df = pd.DataFrame(data)
        df["date"] = pd.to_datetime(df["date"])
        return df
    
    df = get_price_data()
    
    # 基础清洗
    df = df.sort_values("date")
    df["close"] = df["close"].fillna(method="ffill")
    df = df[df["volume"] > 0]
    

    这块其实没什么技术含量,但好处是:

    👉 回测用的数据结构,跟以后实盘用的是同一套来源

    3. 回测:开始老老实实加“现实约束”

    之前那版动量策略的问题是:

    👉 默认理想成交

    现在基本都会强制加:

    • 成本
    • 简单滑点
    • 信号延迟

    举个最简单的均线例子:

    df['ma20'] = df['close'].rolling(20).mean()
    
    df['signal'] = (df['close'] > df['ma20']).astype(int)
    
    df['returns'] = df['close'].pct_change()
    df['strategy'] = df['signal'].shift(1) * df['returns']
    
    cost = 0.0005
    df['strategy_net'] = df['strategy'] - cost * df['signal'].diff().abs()
    

    一旦把成本加进去,很多策略当场变脸。

    现在对回测的理解更偏向:

    👉 这是一个“历史条件下的生存测试”

    4. 参数优化:开始刻意“反着来”

    之前是找最优参数,现在是故意破坏它:

    for p in [17, 20, 23]:
        跑一下
    

    不稳的策略,一动就崩。

    再加一个基本操作:

    train = df[df['date'] < '2021-01-01']
    test = df[df['date'] >= '2021-01-01']
    

    test 基本只看一次。

    慢慢变成一个很简单的判断:

    • 能扛参数扰动的 → 再看
    • 需要精调的 → 基本放弃

    这一轮下来,有个挺明显的变化:

    👉 不太会被“完美回测”骗了

    以前是:

    • 曲线好看 = 可以上

    现在更像是:

    if 解释不了 + 不抗扰动:
        默认不可用
    

    有点反直觉的是:

    能用的策略变少了, 但心里反而更踏实一点。

    connor123
        1
    connor123  
       Apr 30
    看不懂,不够还是想吐槽下 3 月的美伊战争,以及昨晚的鲍威尔讲话,我的量化系统纯纯变成了全自动亏钱系统,tmd
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2588 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 37ms · UTC 11:04 · PVG 19:04 · LAX 04:04 · JFK 07:04
    ♥ Do have faith in what you're doing.