Skip to content

7K星的开源选股系统真正省掉的,是你每天翻涨幅榜的那半小时

作 者:老余捞鱼

原创不易,转载请标明出处及原作者。

文中示例仅用于技术讨论,不构成任何操作建议。
量化策略开发应以学习和技术交流为目的。
本号不荐股、不卖课、不承诺收益。
市场有风险,请合法合规投资。

本文约 4000 字 | 预计阅读 7-10 分钟,前半是介绍和安装,后半有完整代码,建议先收藏。

· · ·

一、这个东西是干啥用的?

GitHub 上一个叫 Sequoia-X 的开源项目,7000 星,1401 次复刻,2018 年立项,今年推倒重写成 V2。

它干的活一句话能说完。A 股收盘后,自动把全市场 5000 多只股票扫一遍,谁走出了指定技术形态,挑出来,推送到你的飞书群。海龟突破、均线放量、高窄旗形、涨停回踩、趋势跌停、RPS 突破,六个策略内置,外加一个定增公告监控。

你不用装行情软件,不用趴在电脑前翻涨幅榜。每天晚上七点多,飞书机器人把当天符合条件的代码清单发到你手机上。

这类项目 GitHub 上不少,多数是半成品。这个能攒到 6954 星,我读完源码后找到三个原因。

第一,数据免费。行情用 baostock 拉,免费接口,不用注册。数据落在本地一个 SQLite 文件里,整个库能拷进 U 盘。第一次回填约 12 分钟,之后每个交易日收盘后增量更新,全市场两三分钟跑完。

第二,策略是插件。六个策略继承同一个基类,每个只实现一个 run() 方法。想加自己的策略,写一个类挂进去,主流程一行不改。

第三,推送分流。每个策略可以绑独立的飞书机器人,海龟的信号发这个群,RPS 的发那个群,互不干扰。

系统四层架构,数据进来,信号出去

至于它选出来的股票到底灵不灵,这是所有人第一个问题,我把答案放在下一节。

二、我测出来的结果,先给答案

先交代一句背景。项目本身没有回测模块,六个策略只判断”今天触没触发”,昨天、去年表现如何,系统不知道。所以我补了这块,把六个策略改成滚动版,在 5558 只股票、2024 年 1 月到今年 9 月的全历史后复权日 K 线上逐日重放,等于把这套系统在历史上完整跑了一遍。

两年多,一共 22 万个信号。统计每个信号之后 5 天、10 天、20 天的涨跌,和同期全市场中位数比。为什么要跟中位数比,因为市场本身有波动,水涨船高的时候随便买什么都涨,得把市场的份扣掉,剩下的才是这个形态自己的贡献。

结果一张表看清(持有 10 天口径)。

策略10日超额20日超额结论
RPS突破+1.69%+2.65%最强,动量有效
高窄旗形+0.73%+1.73%越拿越好
均线放量+0.75%+1.30%稳定正超额
海龟突破+0.72%+1.24%稳定正超额
趋势跌停-0.67%+1.75%前十天要熬
涨停回踩-1.67%-1.98%唯一全线负超额

信号后 10 天,相对同期全市场中位数的超额收益

结论分三层。

第一层,动量类有效。四个正超额策略统计上都站得住,样本量最小的旗形也有 6400 个信号,RPS 的 t 值冲到 26.9,这个置信水平在量化研究里非常少见。而且有个共同规律,持有越久超额越高,RPS 从 5 日的 +1.03% 涨到 20 日的 +2.65%。兑现需要时间,今天出信号明天就卖,赚不到大头。

持有 5/10/20 天,超额收益怎么变

二层,有一个要熬。趋势跌停前 10 天全是负超额,第 20 天翻正到 +1.75%。前面那十天大部分持有者已经拿不住离场了,吃得到尾部的人极少。

第三层,有一个是坏的。涨停回踩,昨天涨停今天放量收阴守住昨收盘,短线圈子里流传很广的”回踩确认”逻辑,两年 1418 个样本,5 天、10 天、20 天全线负超额,胜率 35%。这个形态在这套数据里更像上涨动能的终点。样本区间就两年多,风格切换一次结论可能变,但至少在这里,它是六个策略里唯一连正超额都摸不到边的。项目把它跟其他五个并列推送,用户拿到手无法区分。这就是没有回测模块的代价,策略的死亡率,系统自己不知道。

六个策略两年扫出的信号总数,对数轴

信号规模也值得看一眼。海龟日均选出 196.6 只,等于每天推一个 200 只股票的列表,只能当粗筛池。涨停回踩日均 2.9 只。信号数量决定了你拿它当初筛还是当盯盘。

三、怎么装,十分钟的事

环境要求就一条,Python 3.10 以上。Mac、Linux、Windows 都行,有一台不关机的机器更好。整个过程五步。

第一步拿代码。git clone https://github.com/sngyai/Sequoia-X.git,或者直接在页面上下 zip 解压。

第二步装依赖。推荐用 uv,一个 Python 包管理器,比 pip 快得多,uv sync 一条命令装完。没有 uv 就用老办法,pip install .

第三步配飞书。复制 .env.example 成 .env,里面只需要填一行,飞书机器人的 Webhook 地址。机器人不用审批,飞书群里点设置、添加自定义机器人,建好就能拿到地址。想给不同策略分群,照着 .env.example 里的注释加 STRATEGY_WEBHOOK_ 前缀的变量就行。

第四步首次回填。python main.py --backfill,约 12 分钟拉完全市场历史 K 线,落在 data/sequoia_v2.db。跑一次以后就不用再跑。

第五步日常运行。python main.py,两三分钟跑完,哪个策略选出股票,就推到对应机器人。

想全自动,加一条 crontab,每个交易日 19:15 自动执行。

15 19 * * 1-5 cd /root/Sequoia-X && .venv/bin/python main.py >> log.txt 2>&1

一句提醒。免费版飞书就有自定义机器人,个人用不需要企业版。全套跑起来内存占用不大,最低配的云服务器或家里旧笔记本都够。

四、装好之后怎么用

我的用法是把它放在流程的第一格。

以最近一个完整交易日的真实输出为例。收盘后系统跑完,海龟群 228 只,RPS 群 101 只,旗形群 25 只,均线群 19 只,涨停回踩群 6 只,去重后一共 358 只。这 358 只就是当天的初筛池,对应第二节数据里的含义,动量类为主。

然后是它做不了的部分。二次验证,基本面、行业位置、仓位约束,都过一遍才进自选。回测里那个全线负超额的涨停回踩群,我直接静音了。

一句话定位。它替你省掉的是每天翻涨幅榜的那半小时,省不掉的,是判断本身。

以上是普通读者的部分,装上就能用。往下是动手部分,写给想自己跑回测、想改策略的人。

五、进阶,数据这关我是怎么过的

要复现回测,得先备齐数据,全市场 5558 只、2024 年至今的后复权日 K。这一步我原以为最简单,结果花的时间比回测本身还长,三连坑实录。

第一坑,baostock 黑名单。按原项目路子 8 进程并行拉取,头几分钟很顺,一个半小时后卡死。重新登录,服务器回了八个字,黑名单用户,请与管理员联系。README 写的”无限流”,实测含义是不限次数、限并发强度。

第二坑,东财拒连。换 akshare,底层是东方财富接口。这台机器的网络环境里,东财 CDN 直接拒绝连接,curl 和 Python 全挂。

第三坑,腾讯限流。换腾讯接口,前 700 只飞快,四秒一百只,然后开始返回空内容。

最后跑通的组合是新浪的两个接口,一个给不复权日 K,一个给复权因子,两个一乘,后复权数据就有了。5558 只、344 万行 K 线,九分钟拉完,全程零断流。

不过数据过关不等于数据干净。我后来拿通达信和腾讯两个独立源对拍,发现一个藏得深的坑:新浪的复权因子停在 2022 年底就不更新了。这意味着 2023 年以后分过红、送过转的老牌股,后复权价在除权日附近会有一两个点的假跳空。全市场受影响的大概 20 只,占比不到 0.4%,对 22 万个信号的大盘统计冲不散,但你自己搭管线时要留意。免费源的复权因子不是实时维护的。

这段对你搭自己的数据管线有直接参考价值。免费数据源的真实边界,文档里从来不写。

六、动手区,核心代码

① 数据回填(新浪K线×复权因子,多线程)

import requests
import re
import json
import time
import bisect
from concurrent.futures import ThreadPoolExecutor, as_completed

HDR = {"User-Agent": "Mozilla/5.0", "Referer": "https://finance.sina.com.cn"}
PXY = {"http": None, "https": None}   # 关键:绕开系统代理

KLINE = ("https://quotes.sina.cn/cn/api/jsonp_v2.php"
         "/var%20d=/CN_MarketDataService.getKLineData")


def fetch_pair(sym, start="2024-01-01", end="2026-09-09"):
    # 1) 不复权日K
    r = requests.get(KLINE, params={
        "symbol": sym, "scale": "240", "ma": "no", "datalen": "640"},
        headers=HDR, proxies=PXY, timeout=15)
    raw = json.loads(re.search(r"\((\[.*\])\)", r.text, re.S).group(1))
    raw = [k for k in raw if start <= k["day"] <= end]

    # 2) 后复权因子
    r2 = requests.get(
        f"https://finance.sina.com.cn/realstock/company/{sym}/hfq.js",
        headers=HDR, proxies=PXY, timeout=12)
    fd = json.loads(re.search(r"=(\{.*\})", r2.text, re.S).group(1))
    fac = sorted((x["d"], float(x["f"])) for x in fd["data"])

    # 3) 合成后复权:价格 × 当日生效因子
    dates = [d for d, _ in fac]
    vals = [f for _, f in fac]
    out = []
    for k in raw:
        i = bisect.bisect_right(dates, k["day"]) - 1
        f = vals[i] if i >= 0 else vals[0]
        o, h, l, c = (float(k["open"]), float(k["high"]),
                      float(k["low"]), float(k["close"]))
        v = float(k["volume"])
        out.append((sym, k["day"], o*f, h*f, l*f, c*f, v,
                    v * (o+h+l+c)/4))   # 成交额估算
    return out

② 六策略信号(照抄项目口径,向量化)

import pandas as pd


def indicators(g):
    """单只股票全部指标,一次算完。"""
    g = g.sort_values("date").reset_index(drop=True)
    c, h, l, o, v = g["close"], g["high"], g["low"], g["open"], g["volume"]
    
    g["bars"] = range(1, len(g) + 1)
    g["ma5"], g["ma20"] = c.rolling(5).mean(), c.rolling(20).mean()
    g["ma60"] = c.rolling(60).mean()
    g["vol_ma20"] = v.rolling(20).mean()
    g["fwd10"] = c.shift(-10) / c - 1          # 信号后10日收益
    g["high20_prev"] = h.shift(1).rolling(20).max()   # 海龟:前20日最高
    g["prev_close"] = c.shift(1)
    g["prev_ma5"] = g["ma5"].shift(1)
    g["prev_ma20"] = g["ma20"].shift(1)
    g["prev_ma60"] = g["ma60"].shift(1)
    g["high40"], g["low40"] = h.rolling(40).max(), l.rolling(40).min()
    g["high10"], g["low10"] = h.rolling(10).max(), l.rolling(10).min()
    g["vol20_prev"] = v.shift(1).rolling(20).mean()
    g["prev_close2"] = c.shift(2)
    g["prev_volume"] = v.shift(1)
    g["ret120"] = c / c.shift(120) - 1
    g["roll_high120"] = h.rolling(120, min_periods=60).max()
    return g


def six_signals(df):
    """六个策略的布尔信号,逐条对齐项目源码。"""
    b = df["bars"]
    return {
        "turtle": (b >= 21)
                  & (df.close > df.high20_prev)
                  & (df.turnover > 1e8)
                  & (df.close > df.open)
                  & (df.close > df.prev_close),
        "ma_volume": (b >= 20)
                     & (df.prev_ma5 < df.prev_ma20)
                     & (df.ma5 > df.ma20)
                     & (df.volume > df.vol_ma20 * 1.5),
        "high_tight_flag": (b >= 40)
                           & (df.high40 / df.low40 > 1.6)
                           & (df.high10 / df.low10 < 1.15)
                           & (df.low10 >= df.high40 * 0.8)
                           & (df.volume < df.vol20_prev * 0.6),
        "limit_up_shakeout": (b >= 3)
                             & (df.prev_close >= df.prev_close2 * 1.095)
                             & (df.close < df.open)
                             & (df.volume > df.prev_volume * 2.0)
                             & (df.low >= df.prev_close),
        "uptrend_limit_down": (b >= 60)
                              & (df.prev_ma20 > df.prev_ma60)
                              & (df.close <= df.prev_close * 0.905)
                              & (df.volume > df.vol_ma20 * 2.0),
    }

③ 超额收益体检(对比同期市场中位数)

def excess_report(df, sig):
    """信号后10日收益 vs 同日全市场中位数。"""
    bench = (df.dropna(subset=["fwd10"])
               .groupby("date")["fwd10"].median())   # 当日基准
    sub = df[sig].dropna(subset=["fwd10"])
    excess = sub["fwd10"] - sub["date"].map(bench)
    n = len(excess)
    t = excess.mean() / excess.std(ddof=1) * (n ** 0.5)
    print(f"信号 {n} 个 | 超额均值 {excess.mean():.2%} "
          f"| 胜率 {(excess>0).mean():.1%} | t={t:.1f}")
    # RPS 信号(横截面排位,单独算)
    # df["rps"] = df.groupby("date")["ret120"].rank(pct=True) * 100

完整可运行版本(含股票列表拉取、SQLite 落库、断点续传、RPS 横截面计算)到 http://www.laoyulaoyu.com/code/sequoia-repro.zip 获取。

观点总结

把 22 万个信号翻译成人话。
这套系统选股票,五个策略选出来的,持有两周平均能比市场多赚一个百分点上下,RPS 类最强。一个策略选出来的,持有越久越亏。它自己不知道这件事,因为它的世界里只有今天。所以正确的打开方式,让它当你的粗筛工人,别让它当你的决策者。三分钟扫完全市场省下来的时间,正是你用来做二次验证的时间。工具的价值从来不是替你判断,是把判断需要的数据,在你下班之前摆上桌。

你的二次验证里最重的那条是什么,是估值、行业,还是仓位规则?评论区聊聊,下一篇我把”二次验证”拆成可执行的清单。

觉得有用的话,把这篇转给同样在搞量化的朋友。


#量化选股 #开源项目 #Python量化 #SequoiaX #A股 #技术形态 #程序化选股 #量化复盘 #GitHub开源 #选股系统 #量化工具 #动量策略

免责声明:本文所有数据均为历史回测结果,不构成任何投资建议。历史表现不代表未来收益。量化策略存在失效风险。市场有风险,投资需谨慎。

Published inAI&Invest专栏

Be First to Comment

    发表回复