4. 环境与数据接口:数据源接入、回测引擎搭建与实时行情模拟

做量化交易,说白了就三件事:数据、策略、执行。而数据,是所有策略的起点。我见过太多人花几个月写策略,结果一跑回测,发现数据源没接对,全白干了。今天咱们就把这块硬骨头啃下来。

4.1 数据源接入:Tushare 与 JoinQuant

国内做量化,绕不开两个数据源:Tushare 和 JoinQuant(聚宽)。我个人习惯是两者搭配用——Tushare 拿历史数据,JoinQuant 做在线回测。

4.1.1 Tushare 接入实战

Tushare 是个老牌数据平台,免费版够用,但 token 要自己去官网申请。嗯,这里有个坑:免费版有频率限制,每分钟最多 200 次请求。我曾经在回测脚本里忘了加 sleep,直接触发了封禁,数据停了半小时。

import tushare as ts
import time

# 初始化,token 换成你自己的
pro = ts.pro_api('你的token')

# 获取日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20230101', end_date='20231231')
print(df.head())

# 注意:批量获取时加延时
for code in ['000001.SZ', '600036.SH']:
    df = pro.daily(ts_code=code, start_date='20230101', end_date='20231231')
    time.sleep(0.5)  # 防止被封
我的小技巧: 把 token 写在环境变量里,别硬编码到代码中。我吃过亏,代码传到 GitHub 上,token 被人拿去刷数据,直接欠费。

4.1.2 JoinQuant 在线数据

JoinQuant 的好处是数据直接集成在回测环境里,不用自己管存储。你写策略时,直接调 get_price() 就行。但要注意,聚宽的数据是分钟级对齐的,如果你做高频策略,得用它的 tick 数据接口。

# JoinQuant 回测环境中的代码
def initialize(context):
    g.security = '000001.XSHE'
    set_benchmark('000300.XSHG')

def handle_data(context, data):
    # 获取过去 20 天的收盘价
    close_prices = attribute_history(g.security, 20, '1d', ['close'])
    ma20 = close_prices['close'].mean()
    current_price = data[g.security].close
    
    if current_price > ma20:
        order_target_percent(g.security, 0.8)
    else:
        order_target_percent(g.security, 0.2)
注意: JoinQuant 的本地模拟环境与线上环境有差异。我遇到过本地跑得好好的,上线后数据格式不一样,报错查了半天。建议先在线上小资金跑几天。

4.2 回测引擎搭建:从零开始写一个轻量级引擎

回测引擎,说白了就是模拟历史行情,让策略跑一遍。大厂用的事件驱动引擎,咱们自己写个简单的就够了。我当年第一个回测引擎只有 200 行代码,但跑通了多策略组合。

4.2.1 核心架构设计

一个回测引擎至少需要三个模块:数据加载器、策略执行器、绩效计算器。我习惯用面向对象的方式写,方便扩展。

class BacktestEngine:
    def __init__(self, data, initial_cash=1000000):
        self.data = data  # DataFrame,包含 open/high/low/close/volume
        self.cash = initial_cash
        self.positions = {}  # 持仓字典
        self.trades = []     # 交易记录
        
    def run(self, strategy_func):
        for i in range(len(self.data)):
            # 获取当前时刻的数据
            current_bar = self.data.iloc[i]
            # 调用策略函数
            signals = strategy_func(current_bar, self.positions)
            # 执行交易
            self.execute_trades(signals, current_bar)
            # 更新持仓市值
            self.update_portfolio(current_bar)
            
    def execute_trades(self, signals, bar):
        # 这里实现具体的买卖逻辑
        for signal in signals:
            if signal['action'] == 'buy':
                cost = signal['shares'] * bar['close']
                if cost <= self.cash:
                    self.cash -= cost
                    self.positions[signal['code']] = signal['shares']
                    self.trades.append({
                        'time': bar.name,
                        'action': 'buy',
                        'price': bar['close'],
                        'shares': signal['shares']
                    })
核心要点: 回测引擎的精度取决于数据粒度。日线回测忽略盘中波动,分钟线回测更接近实盘。我建议新手从日线开始,跑通了再升级到分钟线。

4.2.2 滑点与手续费模拟

很多新手回测收益很高,一实盘就亏,原因就是没考虑滑点和手续费。我吃过这个亏,回测年化 30%,实盘只有 8%。

参数 建议值 说明
滑点 0.1% - 0.3% 流动性差的股票取上限
佣金 万分之 2.5 最低 5 元
印花税 千分之 1 卖出时收取
def apply_slippage(price, direction, slippage=0.001):
    """模拟滑点,direction: 1 买入, -1 卖出"""
    return price * (1 + direction * slippage)

def calculate_commission(trade_value):
    """计算佣金,最低 5 元"""
    commission = trade_value * 0.00025
    return max(commission, 5.0)

4.3 实时行情模拟:让策略跑起来

回测跑通了,下一步就是模拟实盘。实时行情模拟,说白了就是让策略在「假的时间流」里跑真数据。我常用的方法有两种:回放模式和加速模式。

4.3.1 回放模式

把历史数据按时间顺序一条条喂给策略,模拟实时行情。这种方式最真实,但速度慢。

import time
from datetime import datetime

class ReplayEngine:
    def __init__(self, data):
        self.data = data
        self.current_index = 0
        
    def next_bar(self):
        """模拟下一个 tick 到来"""
        if self.current_index < len(self.data):
            bar = self.data.iloc[self.current_index]
            self.current_index += 1
            # 模拟实时延迟
            time.sleep(0.1)  # 100ms 模拟网络延迟
            return bar
        return None
        
    def run_realtime(self, strategy_func):
        while True:
            bar = self.next_bar()
            if bar is None:
                break
            strategy_func(bar)

4.3.2 加速模式

如果你要测试策略在极端行情下的表现,加速模式更合适。比如把 1 分钟的数据压缩到 0.1 秒处理完。

class AcceleratedEngine:
    def __init__(self, data, speed=10):
        self.data = data
        self.speed = speed  # 加速倍数
        
    def run(self, strategy_func):
        for i in range(len(self.data)):
            bar = self.data.iloc[i]
            strategy_func(bar)
            # 按加速倍数缩短等待时间
            time.sleep(0.01 / self.speed)
避坑指南: 我曾经用加速模式跑了一个月的数据,策略表现很好。但换成回放模式后,发现策略在开盘前 5 分钟频繁交易——因为加速模式忽略了集合竞价的时间窗口。所以,最终验证一定要用回放模式。

4.4 知识体系总览

下面这张图,是我对本章内容的总结。数据源是地基,回测引擎是骨架,实时模拟是血液。三者缺一不可。

环境与数据接口知识体系 数据源接入 回测引擎搭建 实时行情模拟 Tushare 接入 JoinQuant 接入 核心架构设计 滑点与手续费 回放模式 加速模式 三者联动:数据 → 回测 → 模拟 → 实盘

嗯,数据接口这块,说白了就是「拿来主义」——把别人的数据拿过来,用自己的引擎跑一遍,再用模拟环境验证。我做了这么多年,发现最花时间的不是写策略,而是把这三个环节串起来。你想想看,数据格式不统一、回测结果和模拟对不上、模拟环境又和实盘有差异...每一步都是坑。

但别怕,把这些坑一个个填平了,你的策略就稳了。我个人习惯是先跑通最简单的日线回测,再加分钟线,最后上实时模拟。一步一步来,别想一口吃成胖子。

专注资料整理