手把手教程:编写你的第一个量化策略¶
本教程将带你一步步编写一个经典的双均线策略 (Dual Moving Average)。这通常是量化交易员的"Hello World"。
我们将涵盖从数据准备、策略编写到回测分析的全过程。
1. 策略思路¶
双均线策略的逻辑非常直观:
- 金叉 (Golden Cross): 当短期均线(如 10日线)由下向上穿过长期均线(如 30日线)时,认为趋势向上,买入。
- 死叉 (Death Cross): 当短期均线由上向下穿过长期均线时,认为趋势向下,卖出。
2. 完整代码¶
为了方便你直接运行,我们将所有代码放在一个文件中。你可以复制以下代码到 my_first_strategy.py 并运行。
import pandas as pd
import numpy as np
from akquant import Strategy, run_backtest
# --------------------------
# 第一步:准备数据
# --------------------------
# 在真实场景中,你会使用 pd.read_csv("stock_data.csv")
# 这里为了演示方便,我们生成一些模拟数据
def generate_mock_data():
dates = pd.date_range(start="2023-01-01", end="2023-12-31")
n = len(dates)
# 生成一条随机走势
np.random.seed(42) # 固定随机种子,保证每次运行结果一致
returns = np.random.normal(0.0005, 0.02, n)
price = 100 * np.cumprod(1 + returns)
# 构造 DataFrame
df = pd.DataFrame({
"date": dates,
"open": price,
"high": price * 1.01,
"low": price * 0.99,
"close": price,
"volume": 10000,
"symbol": "AAPL" # 假设这是苹果公司的股票
})
return df
# --------------------------
# 第二步:编写策略
# --------------------------
class DualMAStrategy(Strategy):
"""
继承 akquant.Strategy 类,这是所有策略的基类。
"""
# 新增 (推荐): 声明式设置历史数据预热期
# 这样框架会自动为你处理 set_history_depth,无需在 on_start 中手动调用
# 优先级: 动态属性 (self.warmup_period) > 类属性 (warmup_period) > AST 自动推断
warmup_period = 40 # 30日均线 + 10 安全余量
def __init__(self, fast_window=10, slow_window=30):
# 定义策略参数:快线周期和慢线周期
self.fast_window = fast_window
self.slow_window = slow_window
# 进阶技巧:动态设置预热期
# 如果均线周期是动态传入的,建议在这里根据参数覆盖 warmup_period
self.warmup_period = slow_window + 10
def on_start(self):
"""
策略启动时执行一次。
在这里告诉系统我们要关注哪些股票。
"""
print("策略启动...")
self.subscribe("AAPL")
# 方式 1 (推荐):使用 warmup_period (支持 类属性 / 实例属性 / AST自动推断)
# 方式 2 (旧版):手动调用 self.set_history_depth(self.slow_window + 10)
# 由于我们已经配置了 warmup_period,这里无需做任何操作
# 框架会自动取 max(warmup_period, ast_inferred_value, run_backtest_history_depth)
def on_bar(self, bar):
"""
核心逻辑:每一根 K 线走完时,都会触发一次这个函数。
bar 参数包含了当前的行情数据 (bar.close, bar.high 等)。
"""
# 1. 获取历史收盘价
# get_history 返回的是一个 numpy 数组,包含最近 N 天的数据
closes = self.get_history(count=self.slow_window, symbol=bar.symbol, field="close")
# 如果数据还不够计算长均线(比如刚开始回测的前几天),就直接返回,不操作
if len(closes) < self.slow_window:
return
# 2. 计算均线
# 使用 numpy 计算平均值
fast_ma = np.mean(closes[-self.fast_window:]) # 取最后 fast_window 个数据求平均
slow_ma = np.mean(closes[-self.slow_window:]) # 取最后 slow_window 个数据求平均
# 3. 获取当前持仓
# 如果没持仓返回 0,持有 1000 股返回 1000
position = self.get_position(bar.symbol)
# 4. 交易信号判断
# 金叉:短线 > 长线,且当前空仓 -> 买入
if fast_ma > slow_ma and position == 0:
print(f"[{bar.timestamp_str}] 金叉买入! 价格: {bar.close:.2f}")
self.buy(symbol=bar.symbol, quantity=1000)
# 死叉:短线 < 长线,且当前持仓 -> 卖出
elif fast_ma < slow_ma and position > 0:
print(f"[{bar.timestamp_str}] 死叉卖出! 价格: {bar.close:.2f}")
self.sell(symbol=bar.symbol, quantity=position) # 卖出所有持仓
# --------------------------
# 第三步:运行回测
# --------------------------
if __name__ == "__main__":
# 1. 获取数据
df = generate_mock_data()
# 2. 运行回测
print("开始回测...")
result = run_backtest(
data=df,
strategy=DualMAStrategy, # 传入我们的策略类
strategy_params={"fast_window": 10, "slow_window": 30}, # 调整参数
cash=100_000.0, # 初始资金 10万
commission=0.0003 # 佣金万分之三
)
# 3. 打印结果
print("\n" + "="*30)
print(result) # 打印详细的绩效报表
print("="*30)
3. 代码详细解析¶
3.1 策略结构¶
每一个 AKQuant 策略都是一个 Python 类,继承自 Strategy。你需要关注三个主要方法:
__init__: 设置策略的参数(如均线周期)。on_start: 初始化工作。- 数据预热 (新版推荐):设置
warmup_period。- 静态设置: 类属性
warmup_period = 40。 - 动态设置: 在
__init__中self.warmup_period = slow_window + 10。 - 自动推断: 如果代码中有
SMA(30),框架也会尝试自动推断 (AST)。
- 静态设置: 类属性
- 数据预热 (旧版兼容):调用
self.set_history_depth(40)。如果不设置,get_history会报错。
- 数据预热 (新版推荐):设置
on_bar: 这是一个死循环,系统会按时间顺序把每一根 K 线传给你。你在这里做决策:买还是卖?
3.2 获取数据¶
bar.close: 当前这根 K 线的收盘价。self.get_history(N, symbol, "close"): 获取过去 N 天的收盘价数组。这是最高效的数据访问方式。
3.3 下单交易¶
self.buy(symbol, quantity): 发出买单。默认是市价单(按当前价格成交)。self.sell(symbol, quantity): 发出卖单。
4. 常见问题 (FAQ)¶
Q: 为什么程序报错 RuntimeError: History tracking is not enabled?
A: 这是因为你调用了 get_history 但未设置历史数据长度。请确保在策略类中设置了 warmup_period 属性(推荐),或者在 on_start 中调用 self.set_history_depth()。系统默认为了性能不开启历史缓存,必须显式开启。
Q: 为什么 get_history 返回了很多 NaN?
A: 这通常发生在回测刚开始阶段。比如你设置了 depth=30,但在第 5 天就调用了 get_history(30)。此时数据不足,系统会自动在前面填充 NaN 以保证返回数组长度一致。建议在 on_bar 开头判断 if len(closes) < N: return。
Q: 回测结果里的 sharpe_ratio 是什么?
A: 夏普比率,衡量策略性价比的指标。大于 1 通常被认为是还可以的策略,大于 2 是非常优秀的策略。
Q: 如何换成我自己的数据?
A: 只要将 data=df 替换为你自己的 DataFrame 即可。确保你的 DataFrame 包含 date, open, high, low, close, volume, symbol 这几列。
5. 进阶:参数优化¶
在实际开发中,我们往往需要寻找策略的最优参数(例如:均线周期到底选 10/30 好,还是 5/20 好?)。AKQuant 提供了内置的网格搜索工具。
from akquant import run_optimization
# 定义参数网格
param_grid = {
"fast_window": range(5, 30, 5), # [5, 10, 15, 20, 25]
"slow_window": range(20, 60, 10), # [20, 30, 40, 50]
}
# 运行优化
results_df = run_optimization(
strategy=DualMAStrategy,
param_grid=param_grid,
data=df,
cash=100_000.0,
commission=0.0003,
sort_by="total_return", # 按收益率排序
ascending=False
)
# 打印前 5 名
print(results_df.head(5))
run_optimization 会自动利用多核 CPU 并行加速回测,并返回一个包含所有参数组合及对应绩效指标的 DataFrame。