3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个关于量化交易策略设计的问题。
腾落线 (Advance-Decline Line, ADL) 是一个衡量市场广度(Market Breadth)的累积指标。它通过计算每天上涨股票家数与下跌股票家数的差值,并将其累加到前一天的数值上,来判断大盘的内部强势或弱势。
计算公式:
$$今日ADL = 昨日ADL + (今日上涨家数 - 今日下跌家数)$$
指标含义:
在聚宽(JoinQuant)中实现ADL策略,我们需要以下步骤:
以下是一个完整的策略代码。该策略以沪深300成分股为样本计算ADL,并对**沪深300指数(或ETF)**进行交易。
# -*- coding: utf-8 -*-
import jqdata
import pandas as pd
import numpy as np
def initialize(context):
"""
初始化函数
"""
# 设定基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定手续费
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# --- 策略参数设置 ---
# 用于计算ADL的股票池范围:这里使用沪深300成分股代表大盘
# 如果想计算全A股,可以使用 get_all_securities(['stock']).index.tolist(),但回测速度会变慢
g.scope_index = '000300.XSHG'
# 要交易的标的(这里直接交易沪深300ETF作为示例,也可以交易股指期货或一篮子股票)
g.trade_target = '510300.XSHG'
# ADL指标的全局累积值,初始设为0
g.adl_value = 0
# 记录ADL历史值用于计算均线
g.adl_history = []
# ADL均线窗口,用于判断趋势
g.ma_window = 20
# 每日收盘前运行策略
run_daily(market_scan_and_trade, time='14:50')
def market_scan_and_trade(context):
"""
每日定时运行:计算ADL并交易
"""
# 1. 获取股票池(沪深300成分股)
stocks = get_index_stocks(g.scope_index)
# 2. 获取当前数据(最新价和昨收价)
# 使用 get_price 获取数据,因为 get_current_data 在回测中可能较慢且不易批量处理
# 获取当前分钟的快照数据是不够的,我们需要判断今日相对于昨日的涨跌
# 这里获取过去1天的数据是不够的,因为我们需要pre_close。
# 最简单的方法是获取当前快照
current_data = get_current_data()
advancers = 0 # 上涨家数
decliners = 0 # 下跌家数
unchanged = 0 # 平盘家数
valid_stocks = []
for stock in stocks:
# 过滤停牌股票
if current_data[stock].paused:
continue
# 获取价格
price = current_data[stock].last_price
pre_close = current_data[stock].day_open # 注意:get_current_data的day_open在回测中通常指今日开盘价,这里我们需要昨收
# 更准确的方式是使用 attribute_history 获取昨收,或者直接用 current_data[stock].high_limit / 1.1 (不推荐)
# 修正:使用 get_current_data 实际上包含 last_price 和 pre_close (但在某些回测模式下pre_close可能不准)
# 为了准确性,我们批量获取过去两个交易日的收盘价来比较
valid_stocks.append(stock)
if not valid_stocks:
return
# 批量获取历史收盘价来判断涨跌(获取过去1天的数据,包含今天尚未收盘的价格和昨天的收盘价是不行的,history取不到今天)
# 正确逻辑:获取当前价格,并获取昨天的收盘价
# 获取昨日收盘价
h_data = history(1, unit='1d', field='close', security_list=valid_stocks, df=True)
if h_data.empty:
return
# 遍历计算涨跌家数
for stock in valid_stocks:
if stock not in h_data.columns:
continue
current_price = current_data[stock].last_price
last_close = h_data[stock].iloc[-1] # 昨收
if current_price > last_close:
advancers += 1
elif current_price < last_close:
decliners += 1
else:
unchanged += 1
# 3. 计算今日净腾落数
net_advance = advancers - decliners
# 4. 更新累积ADL值
g.adl_value += net_advance
# 将今日ADL存入历史列表
g.adl_history.append(g.adl_value)
# 保持历史列表长度,避免无限增长占用内存,但要足够计算MA
if len(g.adl_history) > g.ma_window + 100:
g.adl_history = g.adl_history[-g.ma_window:]
# 5. 交易逻辑
# 如果历史数据不足以计算均线,则跳过
if len(g.adl_history) < g.ma_window:
return
# 计算ADL的均线
adl_series = pd.Series(g.adl_history)
adl_ma = adl_series.rolling(window=g.ma_window).mean().iloc[-1]
current_adl = g.adl_value
# 获取当前持仓
position = context.portfolio.positions[g.trade_target].total_amount
cash = context.portfolio.available_cash
# 打印日志方便调试
# log.info(f"Date: {context.current_dt.date()}, Adv: {advancers}, Dec: {decliners}, ADL: {current_adl}, MA_ADL: {adl_ma}")
# 信号判断:ADL 上穿 均线 -> 买入
if current_adl > adl_ma and position == 0:
log.info(f"ADL金叉均线 (ADL:{current_adl} > MA:{adl_ma}),市场广度转强,买入。")
order_value(g.trade_target, cash)
# 信号判断:ADL 下穿 均线 -> 卖出
elif current_adl < adl_ma and position > 0:
log.info(f"ADL死叉均线 (ADL:{current_adl} < MA:{adl_ma}),市场广度转弱,卖出。")
order_target(g.trade_target, 0)
# 6. 绘图记录
record(ADL=current_adl, ADL_MA=adl_ma)
数据获取 (market_scan_and_trade):
get_current_data() 获取当前的实时价格。history(1, ...) 获取昨天的收盘价。current_data[stock].day_open 当作昨收,因为那是今天的开盘价。比较 last_price 和 history 获取的昨日收盘价才是判断今日涨跌的正确方法。ADL 计算:
net_advance = 上涨家数 - 下跌家数。g.adl_value 是一个全局变量,它会随着回测时间的推移不断累加。初始值设为0并不影响策略逻辑,因为我们看的是相对趋势(和均线的关系),而不是绝对值。交易信号:
标的选择:
000300.XSHG (沪深300) 的成分股。510300.XSHG (沪深300ETF)。g.scope_index 改为中证800或全A股,更能反映整体市场情绪,但回测速度会变慢。current_index_price > 20_day_high)但ADL未创新高时,强制平仓。Q: 为什么初始ADL设为0,这会影响结果吗?
A: 不会。ADL是一个相对指标,它的绝对数值没有意义(取决于你从哪一天开始算)。我们关注的是它的斜率以及它与自身均线的相对位置。无论初始值是0还是10000,均线交叉的信号时间点是一样的。
Q: 可以在分钟级别回测中使用吗?
A: 可以,但ADL通常是日线级别的指标。如果在分钟级回测中,建议依然在每日收盘前(如14:50)计算一次并交易,或者使用 run_daily 配合分钟回测。如果在盘中实时计算ADL(例如每分钟计算),波动会非常剧烈,需要更平滑的处理逻辑。
Q: 如何查看ADL曲线?
A: 代码最后一行使用了 record(ADL=current_adl, ADL_MA=adl_ma)。回测结束后,在回测详情页面的收益图下方,会有一个“自定义数据”图表,勾选即可看到ADL和其均线的走势图。