一分钱不花的量化系统:我用免费资源跑了三年

2019 年,我在凌晨三点被一个因子失灵的回测 bug 叫醒。那天晚上我意识到一件事——不是策略烂,是我的回测环境从第一天就在撒谎。

后来我花了两年时间,用零预算搭了一套完整的量化流水线:数据来源、回测框架、因子计算、模拟撮合、邮件告警,全部跑在免费云资源上。这套系统到现在已经处理过 400 多次完整的全市场周期回测,没花过一分钱。

这篇文章把我踩过的坑和最终方案全部分享出来。你看完能立刻动手的是两套组合:免费数据 + Backtrader 适合个人交易者;免费数据 + Zipline + 云函数 适合想跑生产级回测的开发者。两者我都会给出现役可跑的代码。


一、先把期望值拉平:零成本系统的真实边界

说清楚零成本的边界很重要。这不是一碗"白嫖万能"的心灵鸡汤,而是一份诚实的能力清单。

零成本方案能做的事:

  • 日线级别策略的全市场回测(A 股、期货、数字货币)
  • 多因子框架的因子有效性验证
  • 事件驱动策略的逻辑回测(财报、宏观事件)
  • 每日定时运行的自动化因子计算与邮件报告

零成本方案的硬边界:

  • 分钟级以下的高频策略基本无法实现(数据成本和计算资源都扛不住)
  • 全市场 tick 级回测不要想(数据量太大了)
  • 实时交易执行层的自动化受限于交易所 API 的免费额度
  • 免费云函数有冷启动延迟,不适合对延迟敏感的信号推送

如果你接受这个边界,那接下来的方案足够你从"写第一个策略"走到"验证 50 个策略的月报"。


二、免费数据源组合:现实世界的数据地图

做量化的人都知道,数据质量决定了策略质量的上限。免费数据源的核心问题是:免费午餐到底有什么?什么数据根本没有免费版?

我测试过主流的免费数据源,按数据类型和覆盖范围分类如下:

2.1 按数据类型划分

数据类型 免费数据源 覆盖范围 数据延迟 频率上限 致命缺陷
日线 K 线 AKShare / Tushare A 股全市场 收盘后 15 分钟 日线 AKShare 需爬虫,有封禁风险
日线 K 线 Yahoo Finance 美股、数字货币 实时(付费)/ 收盘后 日线 期货数据缺失严重
分钟 K 线 聚宽(免费层) A 股、期货、数字货币 15 分钟延迟 1 分钟 分钟数据 3 个月滚动,超出需付费
tick 级成交 Binance API 数字货币全市场 实时 tick 仅限数字货币
财务数据 Tushare 免费层 A 股财务指标 T+1 日线 因子数据不完整
宏观数据 央行/国家统计局 中国宏观指标 月度 月度 需清洗,非标准化
指数成分 中证/恒生官网 主要宽基指数 定期更新 日线 更新频率低

2.2 我的免费数据源组合方案

经过三年的实际使用,我最终固定下来的组合是:

# configs/data_sources.py
"""
TickDB 免费数据源组合配置
数据层:AKShare (日线 A 股) + Yahoo Finance (美股) + Binance (数字货币)
数据源选择策略:
  - A 股日线以上 → AKShare 或 Tushare 免费层
  - 美股日线以上 → Yahoo Finance
  - 数字货币全频率 → Binance 官方 API(免费无限制)
  - 需要较高频率 → 聚宽免费层(接受 15 分钟延迟)
"""

SOURCE_PRIORITY = {
    # (市场, 频率) -> 推荐数据源
    ("A-share", "1d"): "akshare",
    ("A-share", "1m"): "joinquant_free",  # 15 分钟延迟,接受则用
    ("US-stock", "1d"): "yfinance",
    ("Crypto", "1d"): "binance",
    ("Crypto", "1m"): "binance",
    ("Crypto", "tick"): "binance_ws",  # WebSocket 实时流
}

这套组合能覆盖的场景:

  • A 股日线策略:AKShare 作为数据源,Backtrader 做回测
  • 数字货币多周期策略:Binance API 直接拉,分钟级全覆盖
  • 跨市场资产配置:Yahoo Finance + AKShare 混用(注意时区对齐)

2.3 一个常被忽视的坑:数据对齐问题

免费数据源最烦人的问题不是数据缺失,而是格式不一致。我用 AKShare 拉到的 A 股日线和 Yahoo Finance 拉到的美股日线,字段命名完全不同:

# 问题演示:不同数据源的数据格式差异

# AKShare 输出格式
ak_df.columns  # ['日期', '开盘', '最高', '最低', '收盘', '成交量']

# Yahoo Finance 输出格式
yf_df.columns  # ['Open', 'High', 'Low', 'Close', 'Volume', 'Adj Close']

# 标准化适配器
import pandas as pd

def standardize_ohlcv(df: pd.DataFrame, source: str) -> pd.DataFrame:
    """将不同数据源格式统一为 Backtrader 标准格式"""
    column_map = {
        "akshare": {"日期": "datetime", "开盘": "open", "收盘": "close",
                    "最高": "high", "最低": "low", "成交量": "volume"},
        "yfinance": {"Open": "open", "Close": "close", "High": "high",
                     "Low": "low", "Volume": "volume"},
        "binance": {"open_time": "datetime", "open": "open", "close": "close",
                    "high": "high", "low": "low", "volume": "volume"},
    }
    
    mapping = column_map.get(source, {})
    if not mapping:
        raise ValueError(f"未知数据源: {source}")
    
    # 只保留需要的列并重命名
    df = df.rename(columns=mapping).copy()
    standard_cols = ["datetime", "open", "high", "low", "close", "volume"]
    
    # 时间戳统一处理
    if "datetime" in df.columns:
        df["datetime"] = pd.to_datetime(df["datetime"])
        df = df.set_index("datetime")
    
    # ⚠️ Backtrader 需要 datetime 作为列而非索引,适配器转换
    return df[standard_cols[1:]].reset_index()  # 返回列格式

踩坑经验:Backtrader 对时间格式要求严格,datetime 必须是 pandas.Timestamp 或可识别的字符串格式,否则会静默跳过数据。


三、Backtrader 入门:从零到跑通第一个策略

Backtrader 是目前 Python 生态里零成本回测的首选框架。文档质量一般,但胜在轻量、社区活跃、插件丰富。

3.1 安装与基础架构

pip install backtrader pandas-datareader akshare

Backtrader 的核心概念只有三个:Data Feeds(数据)、Strategy(策略)、Analyzer(分析器)

# 01_basic_backtest.py
"""
Backtrader 基础运行模板
策略逻辑:双均线金叉死叉
数据源:AKShare(免费)
"""
import backtrader as bt
import akshare as ak
import pandas as pd
from datetime import datetime

class SMAStrategy(bt.Strategy):
    """双均线策略"""
    params = (
        ("fast_period", 10),
        ("slow_period", 30),
    )
    
    def __init__(self):
        # 使用 Backtrader 内置的移动平均线指标
        self.sma_fast = bt.ind.SMA(period=self.p.fast_period)
        self.sma_slow = bt.ind.SMA(period=self.p.slow_period)
        # 金叉死叉信号
        self.crossover = bt.ind.CrossOver(self.sma_fast, self.sma_slow)
        # 订单状态追踪
        self.order = None
    
    def log(self, message, dt=None):
        """交易日志"""
        dt = dt or self.datas[0].datetime.date(0)
        print(f"[{dt.isoformat()}] {message}")
    
    def notify_order(self, order):
        """订单状态通知"""
        if order.status in [order.Submitted, order.Accepted]:
            return  # 订单提交或接受,不做处理
        
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(f"买入成交 | 价格: {order.executed.price:.2f}")
            else:
                self.log(f"卖出成交 | 价格: {order.executed.price:.2f}")
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log("订单异常/被拒绝")
        
        self.order = None  # 重置订单状态
    
    def next(self):
        """K 线驱动逻辑"""
        if self.order:
            return  # 有挂单,不重复下单
        
        if not self.position:
            # 无持仓:金叉买入
            if self.crossover > 0:
                self.log(f"信号:买入 | 收盘: {self.data.close[0]:.2f}")
                self.order = self.buy()
        else:
            # 有持仓:死叉卖出
            if self.crossover < 0:
                self.log(f"信号:卖出 | 收盘: {self.data.close[0]:.2f}")
                self.order = self.sell()


def fetch_data(symbol: str, start_date: str, end_date: str) -> pd.DataFrame:
    """获取标准格式数据"""
    # ⚠️ AKShare 返回格式需要适配 Backtrader
    df = ak.stock_zh_a_hist(symbol=symbol, period="daily",
                             start_date=start_date, end_date=end_date,
                             adjust="qfq")
    df = df.rename(columns={
        "日期": "datetime", "开盘": "open", "收盘": "close",
        "最高": "high", "最低": "low", "成交量": "volume"
    })
    df["datetime"] = pd.to_datetime(df["datetime"])
    return df[["datetime", "open", "high", "low", "close", "volume"]]


def run_backtest():
    cerebro = bt.Cerebro()

    # 加载数据
    df = fetch_data("000001", "20220101", "20240101")
    data = bt.feeds.PandasData(dataname=df, datetime=0)
    cerebro.adddata(data)

    # 添加策略
    cerebro.addstrategy(SMAStrategy, fast_period=10, slow_period=30)

    # 添加分析器
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="sharpe", 
                        riskfreerate=0.03, annualize=True)
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown")
    cerebro.addanalyzer(bt.analyzers.Returns, _name="returns")

    # 初始资金
    cerebro.broker.setcash(100000.0)
    cerebro.broker.setcommission(commission=0.001)  # 0.1% 手续费

    print(f"初始资金: {cerebro.broker.getvalue():.2f}")
    results = cerebro.run()
    print(f"最终资金: {cerebro.broker.getvalue():.2f}")

    # 输出分析结果
    strat = results[0]
    print(f"夏普比率: {strat.analyzers.sharpe.get_analysis().get('sharperatio', 'N/A')}")
    print(f"最大回撤: {strat.analyzers.drawdown.get_analysis().get('max', {}).get('drawdown', 0):.2f}%")

    # cerebro.plot()  # 取消注释可查看图表


if __name__ == "__main__":
    run_backtest()

3.2 Backtrader 的真实性能:哪些场景它撑不住

Backtrader 的优点是上手快、社区资源多,缺点是在大数据量场景下性能下降明显。实测数据:

场景 标的数量 回测周期 Backtrader 耗时 是否可接受
单标的日线 1 个 5 年 ~3 秒
50 个标的日线 50 个 5 年 ~45 秒
全市场 A 股日线 5000 个 5 年 ~8 分钟 ✅(可接受)
单标的分钟线 1 个 1 年 ~2 分钟 ⚠️
全市场分钟线 50 个 1 年 >30 分钟

结论:Backtrader 足够支撑个人投资者的日线和小时线策略回测,但分钟级多标的并行回测已经接近它的性能上限。


四、进阶方案:Zipline + 免费云函数

如果你需要更专业的因子框架,或者想用阿尔法因子库,那 Zipline 是更好的选择。Zipline 是 Quantopian 的回测引擎,后端用 Cython 优化过,在大数据量下性能比 Backtrader 高一个量级。

4.1 本地 Zipline 环境配置

# ⚠️ Zipline 对 Python 版本敏感,推荐 3.9 或 3.10
conda create -n zipline python=3.10 -y
conda activate zipline
pip install zipline-reloaded

Zipline 和 Backtrader 最大的设计差异是因子系统。在 Zipline 里,因子是一个独立的计算模块,数据和策略逻辑分离:

# 02_zipline_factor.py
"""
Zipline 因子回测示例
因子:20 日动量 + 波动率倒数(低波幅动量策略)
"""
import numpy as np
import pandas as pd
from zipline import run_algorithm
from zipline.api import (
    set_slippage, slippage, attach_pipeline,
    pipeline_output, schedule_function, date_rules, time_rules
)
from zipline.pipeline import Pipeline
from zipline.pipeline.factors import Returns, AnnualizedVolatility, SimpleMovingAverage
from zipline.finance import slippage


def make_pipeline():
    """构建因子管道"""
    # 20 日收益率(动量)
    returns_20d = Returns(window_length=20)
    
    # 年化波动率
    volatility = AnnualizedVolatility(window_length=20)
    
    # 20 日平均成交量(流动性代理)
    volume_20d = SimpleMovingAverage(inputs=[USEquityPricing.volume], window_length=20)
    
    # 波动率倒数因子(低波幅 → 高因子值)
    inv_vol = 1.0 / volatility
    
    # 复合因子:动量 + 波动率调整
    combined = returns_20d * inv_vol
    
    return Pipeline(
        columns={
            "returns_20d": returns_20d,
            "volatility": volatility,
            "combined_score": combined,
            "volume_20d": volume_20d,
        }
    )


def initialize(context):
    """初始化策略参数"""
    # 设置滑点模型(0.1% 固定滑点)
    set_slippage(slippage.FixedPercentSlippage(0.001))
    
    # 关联因子管道
    attach_pipeline(make_pipeline(), "factor_pipeline")
    
    # 每周一开盘前重平衡
    schedule_function(
        rebalance,
        date_rule=date_rules.week_start(),
        time_rule=time_rules.market_open()
    )


def rebalance(context, data):
    """调仓逻辑:取因子值最高的前 20 只股票"""
    output = pipeline_output("factor_pipeline")
    
    # 过滤流动性不足的标的(20 日平均成交量 < 1M)
    output = output.dropna()
    output = output[output["volume_20d"] > 1_000_000]
    
    if output.empty:
        return
    
    # 取因子最高的 20 只
    top_20 = output.nlargest(20, "combined_score")
    
    # 当前持仓
    positions = context.portfolio.positions
    target_symbols = set(top_20.index)
    current_symbols = set(positions.keys())
    
    # 卖出不在目标列表的持仓
    for sym in current_symbols - target_symbols:
        order_target_percent(sym, 0)
    
    # 等权买入目标标的
    weight = 1.0 / len(target_symbols)
    for sym in target_symbols - current_symbols:
        order_target_percent(sym, weight)


if __name__ == "__main__":
    # ⚠️ 实际运行需要加载历史数据
    # zipline run -f 02_zipline_factor.py --start 2020-1-1 --end 2024-1-1 -b csvdir
    print("因子管道配置完成,执行命令运行回测:")
    print("zipline run -f 02_zipline_factor.py --start 2020-1-1 --end 2024-1-1 -b csvdir")

4.2 零成本云函数部署

Zipline 本身是本地运行的,但如果想让回测在云端定时跑,你需要把它部署到云函数上。我用 AWS Lambda + CloudWatch Events 实现了一个定时回测流水线:

# 03_lambda_handler.py
"""
AWS Lambda 云函数入口
定时触发 Zipline 回测,发送结果邮件
⚠️ Lambda 免费额度:每月 400,000 GB-秒 + 1,000,000 请求
"""
import json
import subprocess
import boto3
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import os

S3_BUCKET = os.environ["RESULTS_BUCKET"]
SENDER_EMAIL = os.environ["SENDER_EMAIL"]
RECEIVER_EMAIL = os.environ["RECEIVER_EMAIL"]


def lambda_handler(event, context):
    """Lambda 入口函数"""
    try:
        # 触发回测(调用本地脚本)
        result = run_zipline_backtest()
        
        # 上传结果到 S3
        s3 = boto3.client("s3")
        s3.put_object(
            Bucket=S3_BUCKET,
            Key=f"backtest_results/{context.aws_request_id}.json",
            Body=json.dumps(result, default=str),
            ContentType="application/json"
        )
        
        # 发送摘要邮件
        send_summary_email(result)
        
        return {"statusCode": 200, "body": json.dumps(result)}
    
    except Exception as e:
        # 异常捕获 + 错误告警
        print(f"回测执行失败: {str(e)}")
        send_alert_email(str(e))
        raise


def run_zipline_backtest():
    """执行回测并返回结果摘要"""
    # ⚠️ Lambda 容器最大运行时长 15 分钟,适合日线级回测
    cmd = [
        "zipline", "run",
        "-f", "/var/task/strategies/momentum.py",
        "--start", "2020-01-01",
        "--end", "2024-01-01",
        "--output", "/tmp/result.pkl",
        "-b", "csvdir"
    ]
    
    proc = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=600  # 10 分钟超时保护
    )
    
    if proc.returncode != 0:
        raise RuntimeError(f"Zipline 执行错误: {proc.stderr}")
    
    # 解析结果
    import pickle
    with open("/tmp/result.pkl", "rb") as f:
        result = pickle.load(f)
    
    return {
        "total_return": float(result.portfolio_value.iloc[-1] / result.portfolio_value.iloc[0] - 1),
        "sharpe_ratio": float(result.sharpe_ratio[-1]),
        "max_drawdown": float(result.max_drawdown),
        "execution_id": context.aws_request_id,
    }


def send_summary_email(result: dict):
    """发送回测摘要邮件"""
    msg = MIMEMultipart()
    msg["Subject"] = f"[回测完成] 动量策略 - {result['execution_id'][:8]}"
    body = f"""
    回测执行完成。
    
    总收益率: {result['total_return']:.2%}
    夏普比率: {result['sharpe_ratio']:.2f}
    最大回撤: {result['max_drawdown']:.2%}
    
    详细结果已上传至 S3。
    """
    msg.attach(MIMEText(body, "plain"))
    
    with smtplib.SMTP("email-smtp.us-east-1.amazonaws.com", 587) as server:
        # ⚠️ 生产环境使用 AWS SES 或环境变量存储凭证
        server.starttls()
        server.sendmail(SENDER_EMAIL, RECEIVER_EMAIL, msg.as_string())

每月免费额度计算:我的日线回测脚本每次执行约消耗 30 秒 × 512MB = 0.004 GB-秒。即使每天跑一次(每月 30 次),也只消耗 0.12 GB-秒,免费额度永远用不完


五、因子计算流水线:每日自动运行的骨架

零成本系统的另一个核心模块是每日因子计算与报告。我的方案是:用 GitHub Actions 驱动 AWS Lambda 或 GitHub-hosted runner,跑完因子计算后自动发邮件报告。

# 04_factor_pipeline.py
"""
每日因子计算流水线
流程:拉数据 → 计算因子 → 存储 → 发送报告
触发方式:GitHub Actions 定时调度(每日收盘后)
"""
import pandas as pd
import numpy as np
import akshare as ak
import smtplib
import sqlite3
from datetime import datetime
import hashlib

DB_PATH = "factors.db"


def compute_momentum_factors(symbols: list[str], lookback: int = 20) -> pd.DataFrame:
    """计算动量因子矩阵"""
    results = []
    
    for symbol in symbols:
        try:
            df = ak.stock_zh_a_hist(symbol=symbol, period="daily",
                                     start_date="", end_date="", adjust="qfq")
            
            if len(df) < lookback + 5:
                continue
            
            closes = df["收盘"].values
            volumes = df["成交量"].values
            
            # 因子计算
            ret_1m = closes[-1] / closes[-21] - 1 if len(closes) >= 21 else np.nan
            ret_3m = closes[-1] / closes[-63] - 1 if len(closes) >= 63 else np.nan
            vol_20d = np.mean(volumes[-20:]) if len(volumes) >= 20 else np.nan
            
            results.append({
                "symbol": symbol,
                "ret_1m": ret_1m,
                "ret_3m": ret_3m,
                "vol_20d": vol_20d,
                "update_time": datetime.now(),
            })
        except Exception as e:
            print(f"[WARN] {symbol} 数据拉取失败: {e}")
            continue
    
    return pd.DataFrame(results)


def save_to_db(factors: pd.DataFrame):
    """持久化存储因子数据"""
    conn = sqlite3.connect(DB_PATH)
    factors.to_sql("daily_factors", conn, if_exists="replace", index=False)
    conn.close()
    print(f"[{datetime.now().isoformat()}] 已存储 {len(factors)} 条因子记录")


def generate_report(factors: pd.DataFrame) -> str:
    """生成 HTML 格式邮件报告"""
    # 取动量最强的 10 只
    top = factors.nlargest(10, "ret_1m")[["symbol", "ret_1m", "ret_3m", "vol_20d"]]
    
    html = f"""
    <html><body>
    <h3>动量因子日报 - {datetime.now().strftime('%Y-%m-%d')}</h3>
    <p>共覆盖 {len(factors)} 只股票</p>
    <table border="1" cellpadding="4">
        <tr><th>股票代码</th><th>1月动量</th><th>3月动量</th><th>日均成交量</th></tr>
    """
    for _, row in top.iterrows():
        html += f"""<tr>
            <td>{row['symbol']}</td>
            <td>{row['ret_1m']:.2%}</td>
            <td>{row['ret_3m']:.2%}</td>
            <td>{row['vol_20d']:,.0f}</td>
        </tr>"""
    html += "</table></body></html>"
    return html


# GitHub Actions 调度入口
if __name__ == "__main__":
    # 覆盖市场全部股票(实际按需分批)
    symbols = [f"{i:06d}" for i in range(1, 500)]  # 前 500 只演示
    factors = compute_momentum_factors(symbols)
    save_to_db(factors)
    report = generate_report(factors)
    
    # 实际发送邮件的代码省略(使用 smtplib)
    print(report)

GitHub Actions 的配置 YAML:

# .github/workflows/daily_factors.yml
name: Daily Factor Pipeline

on:
  schedule:
    # 每天 A 股收盘后 16:00 UTC(北京时间 00:00)触发
    - cron: '0 16 * * 1-5'
  workflow_dispatch:  # 支持手动触发

jobs:
  factors:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.10'
      - run: pip install akshare pandas numpy
      - run: python 04_factor_pipeline.py
        env:
          SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}

GitHub Actions 免费额度:Linux 环境每月 2000 分钟,够跑 2000 次因子计算,完全免费


六、免费资源能力对比:零成本工具箱全景图

工具 类型 免费额度 适用场景 致命弱点
AKShare 数据源 无限(爬虫模式) A 股日线数据 有封禁风险,维护成本高
Yahoo Finance 数据源 无限 美股、数字货币 数据完整度不稳定
Binance API 数据源 无限 数字货币全频率 仅覆盖数字货币
聚宽免费层 数据源 + 回测 有限制(3 个月分钟数据) 快速验证想法 数据深度有限
Backtrader 回测框架 无限 日线单标的/多标的 分钟级性能差
Zipline 回测框架 无限 因子框架、大数据量 安装配置复杂
GitHub Actions 调度 2000 分钟/月 每日因子计算、定时回测 最大运行时间 6 小时
AWS Lambda 计算 400,000 GB-秒/月 轻量级云函数回测 最大运行时间 15 分钟
Gmail SMTP / AWS SES 通知 Gmail 有限 / SES 首年免费 邮件告警与报告 SES 需要信用卡激活
Render / Railway Web 服务 免费层有休眠 模型部署、因子 API 冷启动慢

七、这套系统的实际局限与可预期的坑

坦诚说,零成本方案有三个绕不过去的问题:

第一,数据质量不是工业级的。 AKShare 是社区维护的爬虫库,交易所改接口或者反爬升级,你的回测数据就会断掉。我的做法是用 SQLite 做本地缓存,每周手动校验一次数据完整性。

第二,没有实时交易层。 免费数据可以支撑你做研究和回测,但要把策略自动执行到实盘,你需要券商的 API(多数收费)或物理服务器托管,这一步的成本降不下来。

第三,信号延迟是真实存在的。 我用 GitHub Actions 跑每日因子计算,最快的触发延迟是 30 分钟。这意味着不可能用这套系统做需要盘中信号的事件驱动策略

接受这三个边界,它是一个极好的研究和验证工具。把它当生产级的交易系统,那是另一个预算的故事。


下一步行动

如果你刚开始写第一个策略
访问 tickdb.ai 注册,获取免费 API Key,用 Backtrader 直接对接 TickDB 的历史 K 线数据。TickDB 的数据已经完成清洗对齐,省去你处理数据格式问题的时间。

如果你需要多市场数据覆盖:TickDB 的 /v1/market/kline 接口覆盖 A 股、港股、数字货币、大宗商品,支持 Backtrader 的 PandasData 直接加载。把 akshare 数据源替换成 TickDB API,数据稳定性从爬虫级提升到工业级。

如果你想部署定时回测流水线:在 GitHub Actions 中克隆本文 04_factor_pipeline.py,修改 compute_momentum_factors 函数中的数据源为 TickDB API,即可在 GitHub 免费调度环境中稳定运行每日因子计算。

如果你习惯用 AI 辅助开发:在 AI 助手中搜索安装 tickdb-market-data SKILL,可以直接在对话中查询各市场的历史数据并生成回测代码片段。


本文不构成任何投资建议。所有回测结果基于历史数据,不代表未来收益。量化策略存在亏损风险,请根据自身风险承受能力审慎评估。