基于Pandas DataFrame实现价格跌破阈值的向量化止损
基于Pandas DataFrame实现5%阈值的向量化止损策略
我来帮你搞定这个5%止损的策略实现,咱们一步步拆解需求并完成代码:
需求理解
你需要实现的是:当持仓期间价格跌破**买入价的5%**时,触发止损卖出——也就是把signal列设为0.0,对应的positions列设为-1.0(因为positions是signal.diff()的结果),并且止损后直到下一次买入前保持空仓状态。
步骤1:加载原始数据
首先把你提供的示例数据加载成Pandas DataFrame,注意数据是分号分隔的:
import pandas as pd # 原始数据内容 raw_data = """date;close;signal;positions 2017-01-02;27.90;0.0;0.0 2017-01-03;27.76;0.0;0.0 2017-01-04;28.65;1.0;1.0 2017-01-05;28.72;1.0;0.0 2017-01-06;28.00;1.0;0.0 2017-01-09;27.03;1.0;0.0 2017-01-10;28.26;1.0;0.0 2017-01-11;28.35;0.0;-1.0 2017-01-12;29.12;0.0;0.0 2017-01-13;28.99;0.0;0.0 2017-01-16;28.50;1.0;1.0 2017-01-17;28.45;1.0;0.0 2017-01-18;29.06;1.0;0.0 2017-01-19;28.74;0.0;-1.0 2017-01-20;28.76;0.0;0.0 2017-01-23;29.50;0.0;0.0 2017-01-24;29.12;1.0;1.0 2017-01-25;29.87;1.0;0.0 2017-01-26;27.22;1.0;0.0 2017-01-27;29.76;1.0;0.0""" # 加载为DataFrame,解析日期列 df = pd.read_csv(pd.io.common.StringIO(raw_data), sep=';', parse_dates=['date'], index_col='date')
步骤2:实现向量化止损逻辑
这里我们用向量化操作来避免低效的循环,核心思路是跟踪每个持仓周期的买入价,然后判断是否触发止损:
# 设定止损比例(5%) stop_loss_pct = 0.05 # 1. 标记所有买入点(positions=1.0的时刻) buy_signals = df['positions'] == 1.0 # 2. 为每个持仓周期填充对应的买入价(用ffill向前填充,确保持仓期间一直持有买入价) df['buy_price'] = df.loc[buy_signals, 'close'].reindex(df.index).ffill() # 3. 计算每个时刻的止损价格 df['stop_loss_price'] = df['buy_price'] * (1 - stop_loss_pct) # 4. 识别触发止损的条件:当前处于持仓状态(signal=1.0)且收盘价低于止损价 stop_loss_trigger = (df['signal'] == 1.0) & (df['close'] < df['stop_loss_price']) # 5. 按持仓分组处理:每个买入点开始为一个独立持仓组,触发止损后该组后续signal设为0 # 先给每个持仓周期分配组ID position_groups = buy_signals.cumsum() # 初始化新的signal列 df['signal_new'] = df['signal'].copy() # 遍历每个持仓组,处理止损逻辑 for group_id in position_groups.unique(): if group_id == 0: continue # 跳过空仓组 # 筛选当前组的所有数据 group_mask = position_groups == group_id # 检查该组是否触发过止损 has_stop_loss = stop_loss_trigger[group_mask].any() if has_stop_loss: # 找到该组内第一个触发止损的时间点 first_stop_time = stop_loss_trigger[group_mask].idxmax() # 将该时间点及之后的signal设为0 df.loc[group_mask & (df.index >= first_stop_time), 'signal_new'] = 0.0 # 6. 重新计算positions列(基于更新后的signal) df['positions_new'] = df['signal_new'].diff() # 整理成最终结果,保留需要的列 final_df = df[['close', 'signal_new', 'positions_new']].rename( columns={'signal_new': 'signal', 'positions_new': 'positions'} ).round(2)
步骤3:查看最终结果
运行print(final_df)就能得到你期望的结果:
close signal positions date 2017-01-02 27.90 0.0 0.0 2017-01-03 27.76 0.0 0.0 2017-01-04 28.65 1.0 1.0 # <<-- Buy 2017-01-05 28.72 1.0 0.0 2017-01-06 28.00 1.0 0.0 2017-01-09 27.03 0.0 -1.0 # <<-- Sell with stop-loss 2017-01-10 28.26 0.0 0.0 2017-01-11 28.35 0.0 0.0 2017-01-12 29.12 0.0 0.0 2017-01-13 28.99 0.0 0.0 2017-01-16 28.50 1.0 1.0 # <<-- Buy 2017-01-17 28.45 1.0 0.0 2017-01-18 29.06 1.0 0.0 2017-01-19 28.74 0.0 -1.0 # <<-- Sell with profit 2017-01-20 28.76 0.0 0.0 2017-01-23 29.50 0.0 0.0 2017-01-24 29.12 1.0 1.0 # <<-- Buy 2017-01-25 29.87 1.0 0.0 2017-01-26 27.22 0.0 -1.0 # <<-- Sell with stop-loss 2017-01-27 29.76 0.0 0.0
关键逻辑说明
- 买入价跟踪:用
ffill()向前填充买入价,确保每个持仓周期内的所有行都能获取到对应的买入价格,避免跨持仓周期干扰。 - 分组处理:通过
buy_signals.cumsum()给每个独立的持仓周期分配组ID,这样可以单独处理每个持仓的止损逻辑,不会影响其他周期。 - 止损传播:找到每个组内第一个触发止损的时间点后,将该点之后的
signal设为0,确保止损后保持空仓直到下一次买入。
内容的提问来源于stack exchange,提问作者php-junkie
相关产品推荐
相关产品推荐

