API回测一场梦,本地数据才是真:WorkBuddy如何帮我构建高效策略回测体系

写在前面

做量化的人,都有过"回测跑一天"的经历。

写好一个策略,想看看历史表现,结果从API拉数据就花了半天。如果要跑多个策略、多只股票、多个时间段,时间成本更高。更头疼的是,API有频率限制,拉数据拉太快会被限流,只能慢慢等。

后来我开始用本地数据落盘方案,把历史数据存储在本地文件中,才发现回测原来可以这么快——WorkBuddy直接读本地文件,分钟级完成回测,效率提升了几十倍

今天想分享的,就是WorkBuddy如何结合本地数据(ig50),构建一套高效的策略回测体系。


第一阶段:API回测的痛点

典型的回测流程

用API做回测,典型流程是这样的:

1. WorkBuddy调API,获取股票列表
2. WorkBuddy调API,获取第一只股票的历史K线
3. WorkBuddy运行策略,计算收益
4. WorkBuddy调API,获取第二只股票的历史K线
5. WorkBuddy运行策略,计算收益
6. ... 重复几百次 ...

每个API调用,都要经过网络请求、数据返回、数据解析,整个过程通常需要几百毫秒到几秒。如果要回测100只股票,光拉数据就要几分钟到十几分钟。

真实的时间消耗

我之前用API跑过一个均线策略的回测,回测50只股票、1年历史数据:

步骤 时间消耗
拉取50只股票的历史K线 约8分钟
运行策略计算收益 约2分钟
汇总分析结果 约1分钟
总计 约11分钟

而且这还是顺利的情况,中间如果遇到API限流,时间会更长。


第二阶段:本地数据让回测分钟级完成

WorkBuddy读本地文件的速度

换成本地数据方案之后,回测的速度提升了一个量级:读本地的速度取决于你的CPU、内存和硬盘速度,如果再用多线程并行读取,回测速度还能进一步提升。

import json
import pandas as pd
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor

DATA_DIR = Path('/data')

def load_kline_data(args):
    """WorkBuddy多线程读取单只股票历史K线"""
    code, start_date, end_date = args
    
    kline_path = DATA_DIR / 'stock-a' / '历史K线' / code
    
    try:
        with open(kline_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
    except:
        return None
    
    df = pd.DataFrame(data)
    df['日期'] = pd.to_datetime(df['日期'])
    df = df[(df['日期'] >= start_date) & (df['日期'] <= end_date)]
    
    return code, df

# WorkBuddy多线程批量读取历史K线
stock_codes = ['000001', '000002', '600036', '600519']
args = [(code, '2024-01-01', '2024-12-31') for code in stock_codes]

with ThreadPoolExecutor(max_workers=8) as executor:
    kline_results = list(executor.map(load_kline_data, args))

# 过滤空数据
kline_results = [r for r in kline_results if r is not None]

同样的回测任务,用本地数据:

步骤 API回测耗时 本地单线程回测耗时 本地多线程回测耗时
读取50只股票的历史K线 约8分钟 约5秒 约1秒
运行策略计算收益 约2分钟 约2分钟 约2分钟
汇总分析结果 约1分钟 约1分钟 约1分钟
总计 约11分钟 约3分钟 约2分钟

回测时间从11分钟缩短到2分钟,效率提升近6倍。在CPU、内存、硬盘足够的情况下,多线程读取数据的速度上限取决于你的硬件配置。

WorkBuddy可以批量读取数据

更关键的是,用本地数据之后,WorkBuddy可以批量读取多只股票的数据

def batch_backtest(stock_codes, strategy_func, start_date, end_date):
    """WorkBuddy批量回测"""
    
    results = []
    
    # WorkBuddy批量读取数据
    for code in stock_codes:
        # 读取历史K线(本地,毫秒级)
        kline_path = DATA_DIR / 'stock-a' / '历史K线' / code
        
        try:
            with open(kline_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
        except:
            continue
        
        df = pd.DataFrame(data)
        df['日期'] = pd.to_datetime(df['日期'])
        df = df[(df['日期'] >= start_date) & (df['日期'] <= end_date)]
        
        if df.empty:
            continue
        
        # WorkBuddy运行策略
        signals = strategy_func(df)
        
        # WorkBuddy计算收益
        profit = calculate_profit(signals, df)
        win_rate = calculate_win_rate(signals, df)
        max_drawdown = calculate_max_drawdown(signals, df)
        
        results.append({
            'code': code,
            'profit': profit,
            'win_rate': win_rate,
            'max_drawdown': max_drawdown
        })
    
    return pd.DataFrame(results)

# WorkBuddy回测50只股票
results = batch_backtest(stock_codes, ma_strategy, '2024-01-01', '2024-12-31')

WorkBuddy可以在几分钟内完成50只股票的回测,这在用API的时候是不可想象的。


第三阶段:多策略并行回测

WorkBuddy可以同时跑多个策略

用API的时候,跑多个策略很麻烦——每个策略要单独拉数据,重复的API调用浪费大量时间。

用本地数据之后,WorkBuddy可以一次读取数据,运行多个策略

def multi_strategy_backtest(stock_codes, strategies, start_date, end_date):
    """WorkBuddy多策略并行回测"""
    
    all_results = []
    
    for code in stock_codes:
        # WorkBuddy读取数据(只读一次)
        kline_path = DATA_DIR / 'stock-a' / '历史K线' / code
        
        try:
            with open(kline_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
        except:
            continue
        
        df = pd.DataFrame(data)
        df['日期'] = pd.to_datetime(df['日期'])
        df = df[(df['日期'] >= start_date) & (df['日期'] <= end_date)]
        
        if df.empty:
            continue
        
        # WorkBuddy运行多个策略
        for strategy_name, strategy_func in strategies.items():
            signals = strategy_func(df)
            
            profit = calculate_profit(signals, df)
            win_rate = calculate_win_rate(signals, df)
            max_drawdown = calculate_max_drawdown(signals, df)
            
            all_results.append({
                'code': code,
                'strategy': strategy_name,
                'profit': profit,
                'win_rate': win_rate,
                'max_drawdown': max_drawdown
            })
    
    return pd.DataFrame(all_results)

# WorkBuddy同时跑3个策略
strategies = {
    '均线策略': ma_strategy,
    '布林带策略': bollinger_strategy,
    '动量策略': momentum_strategy
}

results = multi_strategy_backtest(stock_codes, strategies, '2024-01-01', '2024-12-31')

# WorkBuddy对比不同策略的表现
comparison = results.groupby('strategy').agg({
    'profit': 'mean',
    'win_rate': 'mean',
    'max_drawdown': 'mean'
}).sort_values('profit', ascending=False)

print(comparison)

WorkBuddy可以在几分钟内跑完多个策略、多只股票的回测,然后对比不同策略的表现:

策略       平均收益   平均胜率   平均最大回撤
布林带策略   15.2%    55.3%    -8.2%
均线策略    12.5%    52.1%    -10.5%
动量策略    8.3%     48.7%    -15.2%

这让策略优化的效率大大提升。


第四阶段:分钟级回测

WorkBuddy可以回测分钟级策略

做日内交易的人,需要回测分钟级策略。但分钟级数据量很大——一只股票1年的分钟K线,就有几万条记录。用API拉这些数据,非常慢。

用本地数据之后,WorkBuddy可以快速读取分钟级数据

def minute_level_backtest(code, strategy_func, start_date, end_date):
    """WorkBuddy分钟级回测"""
    
    # WorkBuddy读取分钟级K线(本地,毫秒级)
    minute_path = DATA_DIR / 'stock-a' / '历史K线(分钟级)' / code
    
    with open(minute_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    
    df = pd.DataFrame(data)
    df['时间'] = pd.to_datetime(df['时间'])
    df = df[(df['时间'] >= start_date) & (df['时间'] <= end_date)]
    
    # WorkBuddy运行分钟级策略
    signals = strategy_func(df)
    
    # WorkBuddy计算分钟级收益
    profit = calculate_minute_profit(signals, df)
    
    return profit

# WorkBuddy回测1分钟策略
profit = minute_level_backtest('000001', minute_strategy, '2024-01-01', '2024-12-31')

分钟级回测的数据量是日线级的几十倍,用API几乎跑不动,但用本地数据,WorkBuddy可以轻松完成。

分钟级回测的价值

分钟级回测,解决了日内交易最大的痛点:策略验证难

很多日内策略看起来逻辑完美,但实际跑起来效果很差。因为日内交易的细节很多——滑点、流动性、盘口变化,这些都要在分钟级数据上验证。

WorkBuddy可以做分钟级回测,让策略在上实盘之前充分验证,大大降低风险。


第五阶段:融合多数据源的回测

WorkBuddy可以融合多个数据源

高级一点的策略,会结合多个数据源——比如结合主力资金流向、北向资金、龙虎榜等数据。用API做这种回测,要调多个API,非常慢。

用本地数据之后,WorkBuddy可以融合多个数据源做回测

def multi_source_backtest(code, start_date, end_date):
    """WorkBuddy融合多数据源回测"""
    
    # WorkBuddy读取历史K线
    kline_path = DATA_DIR / 'stock-a' / '历史K线' / code
    with open(kline_path, 'r', encoding='utf-8') as f:
        kline_data = json.load(f)
    df_kline = pd.DataFrame(kline_data)
    
    # WorkBuddy读取主力资金流向
    fund_path = DATA_DIR / 'stock-a' / '主力资金流向'
    with open(fund_path, 'r', encoding='utf-8') as f:
        fund_data = json.load(f)
    df_fund = pd.DataFrame([item for item in fund_data if item['股票代码'] == code])
    
    # WorkBuddy读取北向资金
    north_path = DATA_DIR / 'stock-center' / '北向资金'
    with open(north_path, 'r', encoding='utf-8') as f:
        north_data = json.load(f)
    df_north = pd.DataFrame([item for item in north_data if item['股票代码'] == code])
    
    # WorkBuddy融合数据
    df_merged = pd.merge(df_kline, df_fund, on='日期', how='left')
    df_merged = pd.merge(df_merged, df_north, on='日期', how='left')
    
    # WorkBuddy运行融合策略
    signals = fusion_strategy(df_merged)
    
    # WorkBuddy计算收益
    profit = calculate_profit(signals, df_merged)
    
    return profit

# WorkBuddy融合多数据源回测
profit = multi_source_backtest('000001', '2024-01-01', '2024-12-31')

WorkBuddy可以一次读取多个数据源,融合分析,这在用API的时候几乎做不到。


第六阶段:踩坑与优化

教训一:数据格式要统一

不同数据源的字段名称、日期格式可能不一样,WorkBuddy读取之后要做统一处理:

def standardize_data(df):
    """WorkBuddy统一数据格式"""
    
    # 统一日期格式
    df['日期'] = pd.to_datetime(df['日期'])
    
    # 统一数值类型
    for col in df.columns:
        if '涨幅' in col or '比例' in col:
            df[col] = pd.to_numeric(df[col], errors='coerce')
    
    return df
教训二:数据质量要检查

本地数据虽然快,但也要检查数据质量——比如是否有缺失、异常值等:

def check_data_quality(df):
    """WorkBuddy检查数据质量"""
    
    # 检查缺失值
    missing = df.isnull().sum()
    if missing.any():
        print(f"⚠️ 数据存在缺失:{missing[missing > 0]}")
    
    # 检查异常值
    for col in ['收盘价', '成交量']:
        if col in df.columns:
            z_score = (df[col] - df[col].mean()) / df[col].std()
            outliers = df[abs(z_score) > 3]
            if len(outliers) > 0:
                print(f"⚠️ {col}存在异常值:{len(outliers)}个")
教训三:回测不是实盘

回测的目的是验证策略逻辑,不是追求回测收益。过度优化回测参数,容易导致"过拟合"——回测表现很好,实盘表现很差。

我的原则是:回测验证逻辑,实盘验证策略


总结:从API回测到本地回测

用WorkBuddy结合本地数据做回测的这段时间,我的策略开发效率提升了一个量级:

之前:API回测
  • 拉数据慢,回测一场要等半天
  • 多策略回测要重复拉数据
  • 分钟级回测几乎做不了
  • 多数据源融合很麻烦
现在:本地回测
  • 读本地文件,回测分钟级完成
  • 一次读取,多策略运行
  • 分钟级回测轻松完成
  • 多数据源融合很简单

从"API回测一场梦"到"本地回测分钟完成",策略开发效率提升了10倍以上


给同路人的建议

如果你也想用WorkBuddy做回测,建议从这几步开始:

  1. 确定回测粒度:日线级还是分钟级?
  2. 确定数据源:单数据源还是多数据源融合?
  3. 确定策略类型:趋势跟踪、均值回归、还是动量策略?

把这些问题想清楚,再构建你的WorkBuddy回测体系。回测是验证工具,你的策略逻辑才是核心。


gitee开源地址
github开源地址

更多推荐