问题描述
解决方案
这是一个基于 GDX(济安线) 指标的量化交易策略。
策略思路
GDX 指标包含三条线:济安线 (JAX)、压力线 (YLX) 和 支撑线 (ZCX)。
本策略采用趋势跟随的逻辑:
- 买入信号:当股票收盘价 上穿 济安线(JAX),且当前没有持仓时,视为趋势转强,买入。
- 卖出信号:当股票收盘价 下穿 济安线(JAX),且当前持有该股时,视为趋势转弱,卖出。
- 股票池:选取沪深300成分股,剔除停牌和ST股。
- 资金管理:等权重分配资金,最多持有 10 只股票。
策略代码
# -*- coding: utf-8 -*-
# 导入聚宽函数库
from jqdata import *
# 导入技术分析库,GDX指标在此库中
from jqlib.technical_analysis import *
def initialize(context):
"""
初始化函数,设定基准、手续费、全局变量等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 定义全局变量
g.stock_pool_index = '000300.XSHG' # 股票池:沪深300
g.max_hold_count = 10 # 最大持仓数量
# GDX 指标参数
g.N = 30 # 周期 N
g.M = 9 # 参数 M (用于计算压力和支撑的幅度)
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
"""
每日交易逻辑
"""
# 1. 获取股票池(剔除停牌、ST、退市)
check_out_list = get_feasible_stocks(context)
# 2. 获取 GDX 指标数据
# GDX 返回三个字典:济安线(JAX), 压力线(YLX), 支撑线(ZCX)
# 注意:check_date 使用前一个交易日,避免未来函数(虽然技术指标通常用收盘价,盘中可用前一日数据作为参考或使用分钟级合成)
# 这里我们使用 context.current_dt (回测框架会自动处理为当前可用的最新数据)
try:
gdx_jax, gdx_ylx, gdx_zcx = GDX(check_out_list, check_date=context.current_dt, N=g.N, M=g.M)
except Exception as e:
log.error("获取GDX指标失败: %s" % e)
return
# 3. 获取当前持仓
current_positions = context.portfolio.positions.keys()
# 4. 卖出逻辑:持仓股价格跌破济安线
for stock in current_positions:
# 获取该股票的最新价格
current_price = get_current_data()[stock].last_price
# 获取该股票的济安线数值
jax_value = gdx_jax.get(stock)
# 如果数据存在,且价格低于济安线,卖出
if jax_value is not None and current_price < jax_value:
order_target_value(stock, 0)
log.info("卖出 %s: 价格(%.2f) 下穿 济安线(%.2f)" % (stock, current_price, jax_value))
# 5. 买入逻辑:价格站上济安线
# 更新持仓列表(因为刚才可能卖出了一些)
current_positions = list(context.portfolio.positions.keys())
available_slots = g.max_hold_count - len(current_positions)
if available_slots > 0:
buy_list = []
for stock in check_out_list:
# 如果已经持仓,跳过
if stock in current_positions:
continue
current_price = get_current_data()[stock].last_price
jax_value = gdx_jax.get(stock)
# 简单的过滤:价格 > 0 且 JAX 有值
if jax_value is not None and current_price > 0:
# 策略逻辑:价格大于济安线 (也可以加入 current_price > gdx_ylx 压力线作为更强的突破信号)
if current_price > jax_value:
buy_list.append(stock)
# 如果符合条件的股票多于可用仓位,可以按某种规则排序(这里简单取前几个,也可以按市值或动量排序)
# 这里为了简单,直接取列表前几个
target_buy_list = buy_list[:available_slots]
# 执行买入
if len(target_buy_list) > 0:
# 计算每只股票分配的资金
cash_per_stock = context.portfolio.available_cash / len(target_buy_list)
for stock in target_buy_list:
current_price = get_current_data()[stock].last_price
jax_value = gdx_jax.get(stock)
order_value(stock, cash_per_stock)
log.info("买入 %s: 价格(%.2f) 上穿 济安线(%.2f)" % (stock, current_price, jax_value))
def get_feasible_stocks(context):
"""
筛选股票池:剔除停牌、ST、退市、涨跌停
"""
# 获取沪深300成分股
stock_list = get_index_stocks(g.stock_pool_index)
# 获取当前数据
current_data = get_current_data()
feasible_stocks = []
for stock in stock_list:
# 剔除停牌
if current_data[stock].paused:
continue
# 剔除ST
if current_data[stock].is_st:
continue
# 剔除涨跌停(无法买卖)
if current_data[stock].last_price >= current_data[stock].high_limit or \
current_data[stock].last_price <= current_data[stock].low_limit:
continue
feasible_stocks.append(stock)
return feasible_stocks
策略要点解析
-
指标获取:
使用了jqlib.technical_analysis库中的GDX函数。
gdx_jax, gdx_ylx, gdx_zcx = GDX(security_list, check_date, N=30, M=9)gdx_jax: 济安线(主要趋势线)gdx_ylx: 压力线gdx_zcx: 支撑线
-
交易逻辑:
- 本策略使用了最核心的 济安线 (JAX) 作为多空分界线。
- Price > JAX:认为处于多头市场,持有或买入。
- Price < JAX:认为处于空头市场,清仓。
-
扩展建议:
- 进阶用法:你可以修改代码,利用压力线和支撑线做波段。例如:价格跌至支撑线(ZCX)附近反弹时买入,价格涨至压力线(YLX)附近受阻时卖出。
- 止损:当前代码主要依赖指标反转止损,建议在实盘中加入固定比例止损(如亏损 10% 止损)。
Q&A
Q: GDX 指标的 N 和 M 参数一般设置多少?
A: 默认参数通常是 N=30, M=9。N 代表计算移动平均的周期,M 代表计算压力和支撑偏离济安线的百分比幅度。你可以根据回测结果调整 N 来改变对趋势的敏感度(N越小越敏感)。
Q: 为什么使用 jqlib 而不是自己写公式?
A: 聚宽的 jqlib 库内置了经过优化的 C++ 实现的技术指标,计算速度比在 Python 中自己写循环或 Pandas 计算要快得多,特别是在处理全市场或大量股票时。
Q: 这个策略适合什么行情?
A: 这是一个典型的趋势跟踪策略。它在单边上涨或下跌的趋势行情中表现较好,但在震荡市中可能会因为价格频繁穿梭于济安线上下而导致频繁止损(来回打脸)。建议结合其他过滤器(如大盘指数趋势)一起使用。