问题描述
解决方案
在PTrade中实现百分比移动止损(Trailing Stop),核心逻辑是记录持仓以来的最高价格,当当前价格从最高点回撤达到设定的百分比(例如回撤5%或10%)时,触发卖出信号。
这种策略可以让利润奔跑(价格上涨时止损线跟随上移),同时控制回撤风险。
策略逻辑步骤
- 初始化:在
initialize中定义止损百分比(如 5%)和一个全局字典g.high_prices用来存储每只持仓股票的历史最高价。 - 更新最高价:在
handle_data中,遍历当前持仓。如果当前价格高于记录的最高价,则更新最高价。 - 检查止损:计算
止损价 = 最高价 * (1 - 止损百分比)。如果当前价格 <= 止损价,则执行清仓操作。 - 清理数据:卖出后,从字典中删除该股票的记录。
PTrade 策略代码示例
以下是一个完整的策略示例。为了演示效果,策略包含了一个简单的买入逻辑(示例中为买入平安银行),重点在于移动止损的实现。
def initialize(context):
"""
初始化函数
"""
# 1. 设置要操作的股票(示例:平安银行)
g.security = '000001.SZ'
set_universe([g.security])
# 2. 设置移动止损参数
# 止损百分比,例如 0.05 代表从最高点回撤 5% 止损
g.stop_loss_pct = 0.05
# 3. 用于记录持仓股票的最高价
# 格式: {'股票代码': 最高价格}
g.high_prices = {}
# 4. 设置买入标志(仅用于演示买入)
g.bought = False
def handle_data(context, data):
"""
盘中运行函数
"""
# ----------------------
# 1. 简单的买入逻辑 (仅作演示,用于建立底仓)
# ----------------------
if not g.bought:
# 全仓买入
order_value(g.security, context.portfolio.cash)
log.info("【买入】建立底仓: %s" % g.security)
g.bought = True
# 买入时,初始化最高价为当前价格
g.high_prices[g.security] = data[g.security]['close']
# ----------------------
# 2. 百分比移动止损逻辑 (核心部分)
# ----------------------
# 获取当前所有持仓的股票代码
positions = context.portfolio.positions.keys()
# 遍历每一只持仓股票
for stock in positions:
# 获取该股票的持仓对象
position = context.portfolio.positions[stock]
# 如果持仓量为0,跳过(可能是刚卖出但列表未刷新的情况)
if position.amount == 0:
continue
# 获取当前价格
current_price = data[stock]['close']
# --- A. 维护最高价逻辑 ---
# 如果该股票不在记录中(可能是非策略买入或初始化遗漏),则以当前价作为初始最高价
if stock not in g.high_prices:
g.high_prices[stock] = current_price
# 如果当前价格创出新高,更新最高价
if current_price > g.high_prices[stock]:
g.high_prices[stock] = current_price
# log.info("【新高】%s 价格创新高: %.2f,止损线提升" % (stock, current_price))
# --- B. 计算止损并执行 ---
# 获取记录的历史最高价
max_price = g.high_prices[stock]
# 计算触发止损的价格阈值
stop_price = max_price * (1 - g.stop_loss_pct)
# 检查是否触发止损
if current_price <= stop_price:
log.info("【止损触发】%s 当前价: %.2f, 历史最高: %.2f, 触发价: %.2f (回撤 %.1f%%)" %
(stock, current_price, max_price, stop_price, g.stop_loss_pct * 100))
# 卖出该股票所有持仓
order_target(stock, 0)
# --- C. 清理数据 ---
# 卖出后,删除该股票的最高价记录,防止影响下次交易
if stock in g.high_prices:
del g.high_prices[stock]
# 重置演示用的买入标志(实际策略中不需要这行)
if stock == g.security:
g.bought = False
代码详解
-
g.high_prices字典:- 这是实现移动止损的关键。我们不能只看成本价,必须记录持仓期间出现的最高价格。
- 使用字典结构
{'600570.SS': 80.5, '000001.SZ': 15.2}可以同时管理多只股票。
-
更新机制:
- 在每个
handle_data周期(分钟或日线),我们检查current_price > g.high_prices[stock]。如果是,说明价格上涨了,我们将止损线向上拖动(更新最高价)。
- 在每个
-
触发机制:
- 止损线是动态计算的:
stop_price = max_price * (1 - g.stop_loss_pct)。 - 一旦
current_price跌破这个动态止损线,立即执行order_target(stock, 0)清仓。
- 止损线是动态计算的:
-
数据清理:
- 非常重要的一步是
del g.high_prices[stock]。如果不删除,下次买入该股票时,可能会错误地使用很久以前的最高价来计算止损,导致刚买入就立刻卖出。
- 非常重要的一步是
注意事项
- 回测频率:如果是日线回测,止损判断是在收盘时进行的(或者基于收盘价)。如果是分钟回测,则是在每分钟结束时判断,分钟级能更灵敏地捕捉盘中跳水。
- Python版本兼容性:代码使用了
%格式化字符串(如"%s" % var),这是为了兼容 Python 3.5 及更早版本,确保在所有 PTrade 环境中均可运行。 - 持久化:如果是实盘交易,建议使用
pickle模块在after_trading_end中保存g.high_prices,并在initialize中读取。防止服务器重启导致最高价记录丢失(回测中不需要此步骤)。
Q&A
Q: 如何将移动止损改为固定成本止损?
A: 不需要维护 g.high_prices。直接使用 context.portfolio.positions[stock].cost_basis 获取持仓成本,然后判断 current_price <= cost_basis * (1 - 止损比例) 即可。
Q: 可以在盘中实时(Tick级别)止损吗?
A: 可以。将上述逻辑放入 tick_data(context, data) 函数中,并使用 order_tick 接口下单。但要注意 Tick 数据量大,逻辑需尽量精简。
Q: 为什么卖出后要删除 g.high_prices 中的记录?
A: 为了重置状态。如果不删除,当你再次买入同一只股票时,程序会误用上一次持仓时的最高价来计算止损,这通常会导致错误的立即止损。