很多人学量化时,会把注意力放在“策略”上。这当然重要。但如果你真的想长期做研究,只靠零散脚本是不够的。今天一个 test.py,明天一个 ma_final_v3.py,后天一个 真的最终版.ipynb,时间久了你会发现,自己根本不知道哪个结果可信。

一个更好的做法是:从一开始就给自己搭一个轻量的量化研究工作台。

它不需要很复杂,也不需要上来就做成专业系统。你只需要把数据获取、策略代码、回测结果、图表、配置文件放到固定位置,让每一次研究都能复现。

这篇文章将详细拆解一个适合个人使用的高效工作台结构:QuantDash 负责提供和缓存多市场行情数据,Codex(或 Cursor/DeepSeek 等 AI 编程助手)帮你写策略、调试、重构和补全测试代码。


1. 为什么需要工作台

零散脚本的最大问题不是丑,而是不可复现。你可能会遇到以下典型痛点:

  • 【痛点 Before】

    1. 策略回测结果不错,但隔了几天忘了当时用的具体参数。

    2. 净值图表保存在本地,但不知道对应哪一个版本的策略代码。

    3. 临时换了一组股票池,旧的研究结果和新的回测数据直接混在一起。

    4. 某天修改了均线计算逻辑或数据复权方式,历史回测绩效全部失效。

    5. AI 编程助手帮你重构了代码,但你没有记录任何改动的缘由。

  • 【优雅实现 After】
    量化研究要长期迭代,必须让每次实验留下确定性的痕迹。一个标准的个人轻量工作台可以按以下结构组织:

quant-workbench/
├── data/
│   ├── raw/         # 存放原始下载的 CSV 缓存
│   └── processed/   # 存放清洗后的因子特征数据
├── strategies/
│   ├── ma_cross.py      # 双均线信号生成
│   └── rsi_reversal.py  # RSI 逆势信号
├── reports/
│   ├── charts/
│   └── runs/        # 每次实验运行的配置与指标快照
├── configs/
│   ├── ma_cross.json
│   └── rsi_reversal.json
├── notebooks/       # 仅用于临时灵感探索,不放核心业务代码
├── data_loader.py   # 数据加载与缓存
├── backtest.py      # 通用回测计算模块
├── main.py          # 主运行入口
└── requirements.txt

这不是唯一的标准,但它能帮你把“数据、信号、回测、配置、报告”这五个层面划分得清清楚楚。


2. 数据层:统一从 QuantDash 取数

先写一个高内聚的数据模块,比如 data_loader.py。

在量化研究中,我们最忌讳每次跑回测都去重复请求远端 API,不仅耗时,还容易在批量回测时被限流。利用 QuantDash 的高稳定 API 结合本地 CSV 缓存,可以完美解决这一问题:

from pathlib import Path
import pandas as pd
from quantdash import QuantDash

class QuantDashDataLoader:
    def __init__(self, cache_dir: str = "data/raw"):
        # 初始化客户端,会自动读取环境变量 QUANTDASH_API_KEY
        self.qd = QuantDash()
        self.cache_dir = Path(cache_dir)
        self.cache_dir.mkdir(parents=True, exist_ok=True)

    def load_daily_bars(
        self,
        symbol: str,
        count: int = 800,
        adjust: str = "forward",
        use_cache: bool = True,
    ) -> pd.DataFrame:
        """加载日线K线,优先读取本地缓存"""
        cache_file = self.cache_dir / f"{symbol}_{count}_{adjust}.csv"
        
        if use_cache and cache_file.exists():
            return pd.read_csv(cache_file)
        
        # 统一的 K 线获取接口(前复权)
        df = self.qd.klines.get(
            symbol=symbol,
            period="1d",
            count=count,
            adjust=adjust,
            to_dataframe=True,
        )
        # 标准化排序并重置索引
        df = df.sort_values("trade_date").reset_index(drop=True)
        # 写入本地缓存,避免重复请求
        df.to_csv(cache_file, index=False)
        return df

    def load_quotes(self, symbols: list[str]) -> pd.DataFrame:
        """获取实时行情"""
        return self.qd.quotes.get(symbols=symbols, to_dataframe=True)

这样做有几个核心好处:

  1. 策略代码不直接关心底层 API 细节。

  2. 缓存文件极大减少了重复网络请求,回测调参快如闪电。

  3. 复权方式(adjust)、数据长度都写在函数参数中,防呆防错。

  4. 以后如果要扩展或更换数据源,只需要修改这个 data_loader.py 即可,其他策略无需改动任何一行代码。


3. 策略层:每个策略写成可复用函数

比如经典的双均线策略 strategies/ma_cross.py:

import pandas as pd

def ma_cross_signal(
    df: pd.DataFrame,
    short_window: int = 20,
    long_window: int = 60,
) -> pd.DataFrame:
    """生成双均线交叉交易信号"""
    df = df.copy()
    df["ma_short"] = df["close"].rolling(short_window).mean()
    df["ma_long"] = df["close"].rolling(long_window).mean()
    # 均线多头排列时信号为 1,否则为 0
    df["signal"] = (df["ma_short"] > df["ma_long"]).astype(int)
    return df

注意,这里策略函数只负责生成 signal(信号),绝对不参与计算收益。
把“信号生成”和“回测计算”彻底解耦,是一个至关重要的专业习惯。否则,你每个策略文件里都会充斥着大量的收益率计算、滑点扣除、绩效画图代码,越写越臃肿,极难维护。


4. 回测层:统一处理收益、成本和指标

在 backtest.py 中写一个通用的回测引擎:

import numpy as np
import pandas as pd

def calc_metrics(equity: pd.Series, returns: pd.Series) -> dict:
    """计算核心量化绩效指标"""
    total_return = equity.iloc[-1] / equity.iloc[0] - 1
    annual_return = (1 + total_return) ** (252 / len(equity)) - 1
    drawdown = equity / equity.cummax() - 1
    sharpe = 0 if returns.std() == 0 else returns.mean() / returns.std() * np.sqrt(252)
    return {
        "total_return": total_return,
        "annual_return": annual_return,
        "max_drawdown": drawdown.min(),
        "sharpe": sharpe,
    }

def backtest_single_asset(
    df: pd.DataFrame,
    fee: float = 0.0003,
    slippage: float = 0.0002,
) -> tuple[pd.DataFrame, dict]:
    """对单只标的运行标准回测(含手续费与滑点)"""
    df = df.copy().dropna(subset=["signal"])
    
    # 【避坑红线】:信号后移一天,保证收盘后次日开盘买入,规避未来函数
    df["position"] = df["signal"].shift(1).fillna(0)
    
    df["ret"] = df["close"].pct_change().fillna(0)
    df["strategy_ret"] = df["position"] * df["ret"]
    
    # 计算交易摩擦
    df["turnover"] = df["position"].diff().abs().fillna(0)
    df["cost"] = df["turnover"] * (fee + slippage)
    df["strategy_ret"] = df["strategy_ret"] - df["cost"]
    
    # 净值曲线
    df["equity"] = (1 + df["strategy_ret"]).cumprod()
    
    metrics = calc_metrics(df["equity"], df["strategy_ret"])
    return df, metrics

有了这个模块,以后你写任何单标的策略(如突破、MACD、RSI 等),只要在策略函数里生成标准的 signal 序列,就能直接扔进这个回测引擎里。这就是工作台的意义:把重复劳动变少,把研究动作变标准


5. 配置层:每次实验都保存参数

不要把任何选股参数、均线窗口死写在 Python 代码里。我们统一使用外部 JSON 文件(例如 configs/ma_cross.json)来进行实验配置:

{
  "name": "ma_cross_600519",
  "symbol": "600519.SH",
  "count": 800,
  "adjust": "forward",
  "short_window": 20,
  "long_window": 60,
  "fee": 0.0003,
  "slippage": 0.0002
}

主运行程序 main.py 只需要读取这个配置,并依次调用我们的工作台各层组件:

import json
from data_loader import QuantDashDataLoader
from strategies.ma_cross import ma_cross_signal
from backtest import backtest_single_asset

# 1. 加载本次实验的配置文件
with open("configs/ma_cross.json", "r", encoding="utf-8") as f:
    config = json.load(f)

# 2. 从数据层取数(自动命中本地缓存)
loader = QuantDashDataLoader()
df = loader.load_daily_bars(
    config["symbol"],
    count=config["count"],
    adjust=config["adjust"],
)

# 3. 策略信号生成
df = ma_cross_signal(
    df,
    short_window=config["short_window"],
    long_window=config["long_window"],
)

# 4. 执行标准化回测
result, metrics = backtest_single_asset(
    df,
    fee=config["fee"],
    slippage=config["slippage"],
)

print("=== 回测绩效 ===")
print(metrics)

这样你每次实验的输入都被清晰、固化地记录了下来。当你以后回看结果时,再也不会面对一堆不知道用什么参数跑出来的“孤儿”图表而抓狂。


6. 报告层:保存结果,而不是只打印

每次回测运行结束后,将这三样东西保存到同一个实验文件夹中:

  1. 配置快照(config.json)

  2. 回测指标(metrics.json)

  3. 收益曲线及明细(result.csv + equity.png)

from pathlib import Path
import json
import matplotlib.pyplot as plt

# 根据实验名建立专属报告夹
run_dir = Path("reports/runs/ma_cross_600519")
run_dir.mkdir(parents=True, exist_ok=True)

with open(run_dir / "config.json", "w", encoding="utf-8") as f:
    json.dump(config, f, ensure_ascii=False, indent=2)

with open(run_dir / "metrics.json", "w", encoding="utf-8") as f:
    json.dump(metrics, f, ensure_ascii=False, indent=2)

result.to_csv(run_dir / "result.csv", index=False)

# 绘制净值图并保存
result.set_index("trade_date")["equity"].plot(figsize=(10, 5))
plt.tight_layout()
plt.savefig(run_dir / "equity.png", dpi=160)
plt.close()

运行后,一个确定性的实验目录就诞生了:

reports/runs/ma_cross_600519/
├── config.json
├── metrics.json
├── result.csv
└── equity.png

这对于长期维护和复盘非常舒服。你可以随时翻阅、随时对比,也可以直接打包发给 AI 帮助你做更深一步的绩效归因和漏洞分析。


7. AI 在工作台里能做什么

当你的项目结构(目录树、文件调用职责、参数标准)稳定下来之后,大语言模型(如 Codex 等)的作用会被无限放大。你可以非常轻松地对 AI 发出以下指令:

  • 【新增策略】

    "请在 strategies/ 下新增一个 rsi_reversal.py。要求生成 signal 字段:RSI 小于 30 时买入信号设为 1,大于 50 时卖出信号设为 0。不要在策略函数里计算收益,保持和 ma_cross.py 完全一致的函数和入参风格。"

  • 【扩展回测引擎特征】

    "请给 backtest.py 中的 backtest_single_asset 函数增加一个可选的 benchmark 参数。如果传入基准价格序列,请自动计算超额收益(Alpha)和信息比率(Information Ratio),并并入 metrics 返回。"

  • 【全面防作弊自查】

    "请检查我整个 quant-workbench/ 目录下的回测代码,重点帮我检查信号(signal)、持仓(position)和收益率(ret)的对齐关系,看看有没有任何未来函数或偷看收盘价的可能。如果有,请立刻指出并给出修改方案。"

最关键的是,你不再让 AI 去面对一团无章可循的散乱脚本,而是在一个具有严谨标准的高内聚项目里工作。 这样 AI 能够以极高的成功率理解你的开发意图,并产生极高质量的代码改动。


8. 一个适合发给 AI 的项目提示词

当你新开一个 AI 会话时,建议把这段作为工作台的全局说明发给它:

这是我的个人量化研究工作台。
数据源使用 QuantDash:
- QuantDash 官网:https://quantdash.net/
- Python SDK 文档:https://docs.quantdash.net/

常用接口:
- 日K线:qd.klines.get(symbol, period="1d", count=800, adjust="forward", to_dataframe=True)
- 批量 K 线:qd.klines.batch(symbols, period="1d", count=800, adjust="forward", to_dataframe=True)
- 实时行情:qd.quotes.get(symbols=symbols, to_dataframe=True)
- 全量 A 股:qd.quotes.get(universes="CN_Stock", to_dataframe=True)
- 全量 ETF:qd.quotes.get(universes="CN_ETF", to_dataframe=True)
- 五档盘口:qd.depth.get(symbol)

项目约定:
1. data_loader.py 只负责从 QuantDash 取数和本地 CSV 缓存。
2. strategies/ 里的各个策略函数只负责生成 signal 字段,绝对不计算收益。
3. backtest.py 统一负责处理 position、returns、cost、equity、metrics 的核心回测逻辑。
4. 所有的 signal 都必须 shift(1) 后才能转化为下一天的持仓 position,以彻底避免未来函数。
5. 每次运行完,都要在 reports/runs/[实验名]/ 下保存 config.json、metrics.json、result.csv、equity.png。
6. 不要把 API key 暴露在明文代码里,使用 QUANTDASH_API_KEY 环境变量自动读取。

有了这段上下文,你后续让 AI 新增策略、写多标的回测、甚至参数扫描时,它都会表现得像一个深谙你习惯的熟手助理。


9. 工作台的下一步:从脚本到小应用

当你这个命令行的工作台稳定运行、积累了几十次策略回测之后,你可以顺理成章地借助 Streamlit 做一个简单的本地可视化面板:

  • 参数面板:用 Streamlit 拖拽滑动条选择均线窗口、止损比例,自动重写 config.json。

  • 图表展示:用 Streamlit 直接绘制动态交互的净值曲线、回撤曲线、持仓占比图。

  • 实验历史列表:读取 reports/runs,做一个历史回测夏普比率排行榜。

  • 盘中行情监控:接入 QuantDash 的实时行情,计算当前自选股的多周期指标,自动生成预警提示。

量化研究是一门长期的工程实践。先从一个最简单的、数据与逻辑解耦的轻量级工作台开始。不要一开始就追求庞大完美,追求**“让每次实验都有据可查,让每次回测都真实可信”**,你的研发和迭代效率就会获得质的飞跃。


结语与“三步走”落地指引

  • 第一步:获取完整源码。请访问官方开源托管仓库获取本文完整 Demo 代码架构及进阶配置:https://github.com/quantdash-net/QuantDash(请认准官方 quantdash-net 组织,欢迎 Star 支持)。

  • 第二步:申请专属密钥。注册获取您的个人免费/生产级 API Key:https://quantdash.net/

  • 第三步:查阅开发细节。更多高频行情、多市场 Tick 及实时深度五档盘口接口参数请参考:https://docs.quantdash.net/

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐