问题描述
解决方案
这是一个基于 Awesome Oscillator (AO) 指标的交易策略。
策略原理
Awesome Oscillator (AO) 是一个动量指标,用于衡量近期动量与长期动量的差异。
- 计算公式:
- 中点价格 (Median Price) = (最高价 + 最低价) / 2
- AO = SMA(中点价格, 5) - SMA(中点价格, 34)
- 其中 SMA 为简单移动平均线。
- 交易逻辑(零轴穿越策略):
- 买入信号:当 AO 线从下向上穿越零轴时(即上一个周期的 AO < 0 且 当前周期的 AO > 0),视为看涨信号,全仓买入。
- 卖出信号:当 AO 线从上向下穿越零轴时(即上一个周期的 AO > 0 且 当前周期的 AO < 0),视为看跌信号,清仓卖出。
策略代码
以下是完整的 JoinQuant 策略代码:
# -*- coding: utf-8 -*-
import jqdata
import pandas as pd
import numpy as np
def initialize(context):
"""
初始化函数,设定基准、手续费、滑点等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定要操作的股票,这里以平安银行(000001.XSHE)为例
# 您可以修改为一个列表,例如 g.security_list = get_index_stocks('000300.XSHG')
g.security_list = ['000001.XSHE']
# 设定AO指标的参数
g.short_period = 5
g.long_period = 34
# 设定交易费用
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 每天开盘时运行策略
run_daily(trade_logic, 'every_bar')
def trade_logic(context):
"""
每日交易逻辑
"""
for security in g.security_list:
# 获取足够的历史数据来计算SMA34
# 我们需要计算当前的AO和前一日的AO,所以至少需要 long_period + 2 天的数据
# 为了保险起见,获取50天的数据
data = attribute_history(security, 50, '1d', ['high', 'low', 'close'])
# 如果数据不足,跳过
if len(data) < g.long_period + 2:
continue
# 1. 计算中点价格 (Median Price)
# Median Price = (High + Low) / 2
median_price = (data['high'] + data['low']) / 2
# 2. 计算 SMA5 和 SMA34
sma_5 = median_price.rolling(window=g.short_period).mean()
sma_34 = median_price.rolling(window=g.long_period).mean()
# 3. 计算 AO 指标
ao = sma_5 - sma_34
# 获取当前和前一天的AO值
# iloc[-1] 是当前(最新)的AO值(基于昨天收盘数据,因为是在开盘运行)
# iloc[-2] 是前一天的AO值
current_ao = ao.iloc[-1]
previous_ao = ao.iloc[-2]
# 检查是否包含NaN值(刚开始计算时会有NaN)
if np.isnan(current_ao) or np.isnan(previous_ao):
continue
# 获取当前持仓
position = context.portfolio.positions[security]
# 4. 交易信号判断
# 买入信号:AO 上穿零轴 (前值 < 0, 当前值 > 0)
if previous_ao < 0 and current_ao > 0:
# 如果没有持仓,则买入
if position.closeable_amount == 0:
# 使用可用资金平均分配买入(如果是多只股票)
cash = context.portfolio.available_cash / len(g.security_list)
order_value(security, cash)
log.info("买入信号触发: %s, Prev_AO: %.4f, Curr_AO: %.4f" % (security, previous_ao, current_ao))
# 卖出信号:AO 下穿零轴 (前值 > 0, 当前值 < 0)
elif previous_ao > 0 and current_ao < 0:
# 如果有持仓,则卖出
if position.closeable_amount > 0:
order_target(security, 0)
log.info("卖出信号触发: %s, Prev_AO: %.4f, Curr_AO: %.4f" % (security, previous_ao, current_ao))
常见问题 (Q&A)
Q: 为什么使用 attribute_history 获取数据?
A: AO 指标需要基于过去一段时间的最高价和最低价来计算中点价格。attribute_history 是 JoinQuant 中获取历史行情数据最常用的 API,能够方便地获取 DataFrame 格式的数据进行向量化计算。
Q: 代码中的 run_daily(trade_logic, 'every_bar') 是什么意思?
A: 这表示策略按天频率运行。trade_logic 函数会在每个交易日开盘时被调用一次。此时获取的历史数据(attribute_history)默认是不包含当天的(即截止到昨天收盘),这符合回测逻辑,避免了未来函数。
Q: 如何修改策略以操作多只股票?
A: 在 initialize 函数中,修改 g.security_list 即可。例如,若要操作沪深300成分股,可以将代码改为:g.security_list = get_index_stocks('000300.XSHG')。代码中的资金分配逻辑已经做了简单的平均分配处理。
Q: AO 指标除了零轴穿越还有其他用法吗?
A: 是的,AO 还有“碟形信号”(Saucer)和“双峰背离”(Twin Peaks)等用法。本策略为了保持简洁和稳健,仅实现了最核心的零轴穿越(动量反转)逻辑。