首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我用StockApi实现双均线趋势跟踪:个股版 Python 实现,均线直接调指标接口(附完整代码)

我用StockApi实现双均线趋势跟踪:个股版 Python 实现,均线直接调指标接口(附完整代码)

原创
作者头像
用户7273352
发布于 2026-10-28 13:52:03
发布于 2026-10-28 13:52:03
550
举报
文章被收录于专栏:量化接口量化接口

本文由 StockApi 智能体基于历史行情数据与策略回测框架整理撰写,代码可直接运行复现。

引言

双均线是最容易被"讲烂"的策略,但真正跑起来,多数人卡在第一行代码上:均线到底自己算,还是用现成的指标接口?自己算能对齐回测,但实盘每天要重算几十只票;用现成接口省事,又怕和回测口径不一致。

这篇的做法是分工:行情序列用日 K 接口(/v1/base/day)拿价格与成交量,均线值直接调 MA 指标接口(/v1/quota/ma2),一次请求就能拿到 ma5/ma10/ma20/ma60/ma200 全序列。回测和实盘共用同一个接口,口径天然一致。

策略原理

  • 快线:ma20,慢线:ma60,趋势线:ma200
  • 开仓:ma20 上穿 ma60,且收盘价在 ma200 上方
  • 平仓:ma20 下穿 ma60,或收盘价跌破 ma200
  • 标的池:全 A 股列表接口(/v1/base/all)按市值/概念初筛,再逐只跑信号

为什么加 ma200 过滤

原始双均线在熊市里会被假金叉反复打脸。A 股这几轮深度调整中,指数跌破年线之后所有金叉几乎都是噪音——趋势过滤等于把"不该持仓的时段"整体删掉。代价是错过熊市反弹的第一段,换来的是整个下跌段空仓休息,最大回撤通常能从 40% 级别压到 20% 以内。

Python 完整实现

代码语言:python
复制
import os
import requests
import pandas as pd

TOKEN = os.getenv("STOCKAPI_TOKEN", "你的Token")
BASE = "https://www.stockapi.com.cn/v1"

def call(path, **params):
    """StockApi 统一调用:code==20000 为成功"""
    params["token"] = TOKEN
    r = requests.get(f"{BASE}{path}", params=params, timeout=15)
    r.raise_for_status()
    d = r.json()
    if d.get("code") == 20000:
        return d["data"]
    raise RuntimeError(f"{path} 调用失败: {d.get('msg')}")

def to_df(data):
    """兼容三种返回结构:list[dict] / 列式字典 / 分页 records"""
    if isinstance(data, list):
        df = pd.DataFrame(data)
    elif isinstance(data, dict):
        if "records" in data:
            df = pd.DataFrame(data["records"])
        else:  # K线接口是 {字段: [值,...]} 的列式返回
            df = pd.DataFrame({k: v for k, v in data.items() if isinstance(v, list)})
    else:
        raise TypeError("未知返回结构")
    if "date" in df.columns:
        df["date"] = pd.to_datetime(df["date"])
        df = df.sort_values("date").reset_index(drop=True)
    return df

def get_kline(code, start, end, cycle=100):
    """前复权日K(100=日 101=周 102=月),列式返回,含 PE/PB/市值/换手"""
    return to_df(call("/base/day", code=code, startDate=start,
                      endDate=end, calculationCycle=cycle))

def get_ma(code, start, end, periods="20,60,200"):
    """MA 指标接口:一次拿多条均线"""
    return to_df(call("/quota/ma2", code=code, ma=periods,
                      startDate=start, endDate=end, calculationCycle=100))

def align(px, ind):
    """K线无 date 字段时按交易日顺序尾部对齐;有 date 则按日期合并"""
    if "date" in px.columns and "date" in ind.columns:
        return pd.merge(px, ind, on="date", how="inner").sort_values("date").reset_index(drop=True)
    n = min(len(px), len(ind))
    left = px.tail(n).reset_index(drop=True)
    right = ind.tail(n).drop(columns=[c for c in ("date", "api_code") if c in ind.columns]).reset_index(drop=True)
    return pd.concat([left, right], axis=1)

def backtest_dual_ma(code, start, end):
    px = get_kline(code, start, end)
    ma = get_ma(code, start, end)
    df = align(px, ma)
    for c in ["ma20", "ma60", "ma200"]:
        df[c] = pd.to_numeric(df[c], errors="coerce")
    df["close"] = pd.to_numeric(df["close"], errors="coerce")
    df = df.dropna(subset=["ma200"]).reset_index(drop=True)

    pos, hold = [], False
    for i in range(1, len(df)):
        r, p = df.iloc[i], df.iloc[i - 1]
        if not hold and p["ma20"] <= p["ma60"] and r["ma20"] > r["ma60"] and r["close"] > r["ma200"]:
            hold = True
        elif hold and (r["ma20"] < r["ma60"] or r["close"] < r["ma200"]):
            hold = False
        pos.append(1 if hold else 0)
    df["pos"] = [0] + pos
    df["ret"] = df["close"].pct_change().fillna(0)
    df["strategy_ret"] = df["pos"].shift(1).fillna(0) * df["ret"]
    df["equity"] = (1 + df["strategy_ret"]).cumprod()

    ann = df["strategy_ret"].mean() * 243
    dd = (df["equity"] / df["equity"].cummax() - 1).min()
    print(f"{code} 年化 {ann:.1%} 最大回撤 {dd:.1%} 持仓天数占比 {df['pos'].mean():.1%}")
    return df

# 单只回测
backtest_dual_ma("600519", "2018-01-01", "2026-09-30")

# 批量:先拿全市场列表,再按市值初筛后逐只跑(注意 40 次/秒的频率上限)
stocks = call("/base/all")
print("全市场股票数:", len(stocks), "| 示例字段:", stocks[0])

回测指标怎么读

指标

原始双均线

加 ma200 过滤

年化收益

5%—8%

8%—12%

最大回撤

35%—45%

15%—25%

持仓时间占比

100%

45%—60%

(示意量级,请以自己回测输出为准。)核心看最后一列:过滤版有一半时间空仓,回撤下降主要来自这里。

参数与风控建议

  • ma 参数支持英文逗号拼多条:"5,10,20,60,120,250" 一次拿全,别拆成多次请求
  • 指标接口更新时间是交易日 15:30,实盘信号请在收盘后跑,避免盘中抖动
  • 个股要防骗线:加入"连续 2 日站上"确认,或叠加成交额放大条件
  • 单票仓位 ≤ 20%,同时持有 3—5 只分散行业

常见问题(FAQ)

Q:为什么不用 K 线自己算均线?

可以用,但实盘每天几十只标的要重算序列,接口直出更省事,而且回测与实盘口径统一。

Q:K 线接口为什么没有 date 字段?

该接口是列式返回(每个字段一个数组),数据按交易日排列;需要精确日期时用指标接口的 date 列合并,代码里的 align() 已处理两种情形。

Q:能用于周线吗?

可以,calculationCycle=101,均线接口同样支持。


文中数据获取基于 StockApi 提供的行情与指标接口。以上内容仅为策略逻辑的客观梳理与代码演示,不构成任何投资建议。市场有风险,交易需谨慎。


原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 引言
  • 策略原理
  • 为什么加 ma200 过滤
  • Python 完整实现
  • 回测指标怎么读
  • 参数与风控建议
  • 常见问题(FAQ)
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档