问题描述
解决方案
这是一个基于聚宽(JoinQuant)API 编写的完整交易策略示例。
为了演示止损和止盈的功能,我构建了一个基础的双均线策略(金叉买入,死叉卖出)作为入场和出场的基础,并在此之上增加了固定比例的止损和止盈逻辑。
策略逻辑说明
- 入场信号:当短期均线(5日)上穿长期均线(10日)时,全仓买入。
- 出场信号(基础):当短期均线(5日)下穿长期均线(10日)时,清仓卖出。
- 止损逻辑:当当前价格低于持仓成本的 5% 时,触发止损,强制卖出。
- 止盈逻辑:当当前价格高于持仓成本的 10% 时,触发止盈,强制卖出。
- 优先级:止损/止盈的判断优先级高于均线信号。
策略代码
# -*- coding: utf-8 -*-
import jqdata
def initialize(context):
"""
初始化函数,设定基准、股票池、参数等
"""
# 设定沪深300作为基准
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')
# --- 策略参数设置 ---
# 要操作的股票:平安银行
g.security = '000001.XSHE'
# 短期均线窗口
g.short_window = 5
# 长期均线窗口
g.long_window = 10
# 止损比例 (例如 -0.05 代表亏损5%止损)
g.stop_loss_pct = -0.05
# 止盈比例 (例如 0.10 代表盈利10%止盈)
g.take_profit_pct = 0.10
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
"""
每日交易逻辑
"""
security = g.security
# 获取历史收盘价数据,长度取长均线+1天,为了计算均线
# include_now=True 表示包含当前分钟/日的数据,但在回测daily频率下,开盘时取不到当日收盘价,
# 这里我们取过去的数据来计算昨日的均线情况,避免未来函数
hist_data = attribute_history(security, g.long_window + 1, '1d', ['close'], skip_paused=True)
# 如果数据不足(例如新股),则跳过
if len(hist_data) < g.long_window + 1:
return
# 获取当前持仓信息
position = context.portfolio.positions[security]
# 获取当前最新价格 (回测中daily频率在09:30运行时,current_price通常为开盘价)
current_data = get_current_data()
current_price = current_data[security].last_price
# --- 1. 优先执行止损止盈检查 ---
if position.total_amount > 0:
# 计算收益率:(当前价格 - 持仓成本) / 持仓成本
# avg_cost 是建仓成本,包含手续费
cost = position.avg_cost
if cost > 0:
returns = (current_price - cost) / cost
# 检查止损
if returns <= g.stop_loss_pct:
log.info("触发止损: 标的=%s, 成本=%.2f, 现价=%.2f, 收益率=%.2f%%" % (security, cost, current_price, returns * 100))
order_target(security, 0)
return # 止损后直接结束本日策略逻辑,不再判断均线
# 检查止盈
if returns >= g.take_profit_pct:
log.info("触发止盈: 标的=%s, 成本=%.2f, 现价=%.2f, 收益率=%.2f%%" % (security, cost, current_price, returns * 100))
order_target(security, 0)
return # 止盈后直接结束本日策略逻辑
# --- 2. 均线策略逻辑 ---
# 计算短期均线 (取最近 short_window 天的均值)
# values[:-1] 是为了取昨日及之前的数据,模拟昨日收盘后的信号
ma_short = hist_data['close'][-g.short_window:].mean()
# 计算长期均线
ma_long = hist_data['close'][-g.long_window:].mean()
# 获取上一日的短期和长期均线值,用于判断交叉
prev_ma_short = hist_data['close'][-(g.short_window+1):-1].mean()
prev_ma_long = hist_data['close'][-(g.long_window+1):-1].mean()
# 获取可用资金
cash = context.portfolio.available_cash
# 信号判断
# 金叉:短期均线 上穿 长期均线
# 逻辑:昨天短均线 < 昨天长均线 且 今天短均线 > 今天长均线
# 注意:这里的“今天”指的是基于历史数据计算出的最新均线值
# 买入逻辑:金叉 且 当前无持仓
if ma_short > ma_long and prev_ma_short <= prev_ma_long and position.total_amount == 0:
log.info("均线金叉买入: 标的=%s, MA5=%.2f, MA10=%.2f" % (security, ma_short, ma_long))
order_value(security, cash)
# 卖出逻辑:死叉 且 有持仓
elif ma_short < ma_long and prev_ma_short >= prev_ma_long and position.total_amount > 0:
log.info("均线死叉卖出: 标的=%s, MA5=%.2f, MA10=%.2f" % (security, ma_short, ma_long))
order_target(security, 0)
代码关键点解析
-
position.avg_cost:- 这是聚宽提供的持仓对象属性,代表持仓平均成本。
- 止盈止损的核心就是比较
current_price(当前价)和avg_cost(成本价)的偏离程度。
-
优先级控制:
- 代码中先进行了止损止盈的判断。如果触发了止损或止盈,执行
order_target(security, 0)卖出后,直接return。 - 这样做是为了防止同一天内既触发止损,又因为均线信号产生其他操作(虽然在日线级别通常不会冲突,但在逻辑上应保持清晰的优先级)。
- 代码中先进行了止损止盈的判断。如果触发了止损或止盈,执行
-
run_daily:- 设置在
09:30运行。在回测模式下,这使用的是当天的开盘价进行撮合。 - 如果您希望盘中实时监控止损(例如分钟级止损),需要将回测频率调整为“分钟”,并将逻辑放入
handle_data或设置run_daily(func, 'every_bar')。
- 设置在
-
order_target(security, 0):- 这是一个非常方便的函数,意为“将目标股票的仓位调整为0”,即清仓卖出。
常见问题 (Q&A)
Q: 如何实现移动止损(Trailing Stop)?
A: 移动止损不是基于成本价,而是基于持仓期间的最高价回撤。你需要使用全局变量(如 g.highest_price)记录持仓以来的最高价。
- 买入时初始化
g.highest_price = cost。 - 每天更新:
if current_price > g.highest_price: g.highest_price = current_price。 - 止损判断:
if (current_price - g.highest_price) / g.highest_price < -0.10:(回撤10%止损)。
Q: 这个策略可以用于多只股票吗?
A: 可以。你需要将 g.security 改为一个列表(例如 g.stocks = ['000001.XSHE', '600519.XSHG']),然后在 market_open 函数中使用 for security in g.stocks: 循环遍历每一只股票进行相同的逻辑判断。
Q: 为什么回测时止损价格和设定的不一样?
A: 这里的代码是按日回测(Daily)。如果某一天开盘直接跌停,或者开盘价已经远低于止损线,系统只能以开盘价卖出,这会导致实际亏损超过设定的5%。这是市场风险(Gap Risk),在实盘中同样存在。如果需要更精确的止损,建议使用分钟级回测。