You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

量化策略代码信号生成异常:条件失效及无止损止盈信号排查

量化策略两大问题排查:持仓过滤无效、止损止盈无信号

问题概述

  1. 移动平均线策略中,移除买入信号的(self.df['Accu_Position'] == 0)持仓过滤条件后,回测结果完全一致,说明该条件未实际生效。
  2. 策略全程未生成任何止损(StopLoss)、止盈(TakeProfit)信号,触发逻辑失效。

完整策略代码:

from matplotlib import pylab
import numpy as np
import pandas as pd
from backtesting import Backtest , Strategy
import matplotlib.pyplot as plt

df = pd.read_excel("E:/usdcnh.xls")
print(df.columns)

class MovingAverageStrategy:
    def __init__(self, df, window_size, buy_threshold, sell_threshold, stop_loss, take_profit):
        self.df = df
        self.window_size = window_size
        self.buy_threshold = buy_threshold
        self.sell_threshold = sell_threshold

        self.stop_loss = stop_loss
        self.take_profit = take_profit

    def calculate_moving_average(self):
        self.df['MovingAverage'] = self.df['Close'].rolling(window=self.window_size).mean()

    def calculate_deviation(self):
        self.df['Deviation'] = self.df['Close'] - self.df['MovingAverage']

    def generate_signals(self):
        self.calculate_moving_average()
        self.calculate_deviation()

        self.df['Position'] = 0 
        self.df['Accu_Position'] = self.df['Position'] + self.df['Position'].shift(-1)
        self.df['Accu_Position'].iloc[-1] = self.df['Position'].iloc[-1]

        self.df['Buy_Condition'] = (self.df['Deviation'] <= self.buy_threshold) & (self.df['Accu_Position'] == 0)
        self.df['Buy_Signal'] = np.where(self.df['Buy_Condition'], 1, 0)
        self.df['Sell_Condition'] = (self.df['Deviation'] >= self.sell_threshold) & (self.df['Accu_Position'] == 0)
        self.df['Sell_Signal'] = np.where(self.df['Sell_Condition'] , -1, 0)

        self.df['EntryPrice'] = np.where((self.df['Buy_Signal']!=0) | (self.df['Sell_Signal']!=0), self.df['Close'], 0)
        self.df['EntryPriceRecord'] = np.where(self.df['EntryPrice']!=0, self.df['EntryPrice'], self.df['EntryPrice'].shift(-1)) 
        self.df['PriceChange'] = self.df['Close'] - self.df['EntryPriceRecord']

        self.df['StopLoss_Signal'] = 0  
        stop_loss_condition = ((self.df['Accu_Position'] == 1) & (self.df['PriceChange'] <= -self.stop_loss)) | ((self.df['Accu_Position'] == -1) & (self.df['PriceChange'] >= self.stop_loss))
        self.df.loc[stop_loss_condition, 'StopLoss_Signal'] = -self.df['Accu_Position']

        self.df['TakeProfit_Signal'] = 0
        take_profit_condition = ((self.df['Accu_Position'] == 1) & (self.df['PriceChange'] >= self.take_profit)) | ((self.df['Accu_Position'] == -1) & (self.df['PriceChange'] <= -self.take_profit))
        self.df.loc[take_profit_condition, 'TakeProfit_Signal'] = -self.df['Accu_Position']

        self.df['Signal'] = self.df[['Buy_Signal', 'Sell_Signal', 'StopLoss_Signal', 'TakeProfit_Signal']].sum(axis=1)
        self.df['Position'] += self.df['Signal']

        num_buy_signals = self.df['Buy_Signal'].sum()
        num_sell_signals = self.df['Sell_Signal'].sum()
        num_take_profit_signals = self.df['TakeProfit_Signal'].sum()
        num_stop_loss_signals = self.df['StopLoss_Signal'].sum()

        print('Number of Buy Signals:', num_buy_signals)
        print('Number of Sell Signals:', num_sell_signals)
        print('Number of Take Profit Signals:', num_take_profit_signals)
        print('Number of Stop Loss Signals:', num_stop_loss_signals)
        print('Number of Stop Loss Signals:', num_stop_loss_signals)
        print(self.df['Accu_Position'])

    def calculate_pnl(self):
        self.df['PnL'] = self.df['Close'] * self.df['Position']
        self.df['AccuPnl'] = self.df['PnL'] + self.df['PnL'].shift()
        print('Total PnL:', self.df['PnL'].sum())


    def apply_strategy(self):
        self.calculate_moving_average()
        self.calculate_deviation()
        self.generate_signals()
        self.calculate_pnl()
        return self.df

strategy = MovingAverageStrategy(df, window_size=200, buy_threshold=-0.0350, sell_threshold=0.0350, stop_loss=0.0020, take_profit=0.0050)
df = strategy.apply_strategy()

# # Plotting the equity
plt.figure(figsize=(10, 6))
plt.plot(df['Close'], label='Equity')
plt.plot(df['MovingAverage'], label='MovingAverage')

# # Plotting the buy and sell points
buy_points = df[df['Buy_Signal'] != 0]
sell_points = df[df['Sell_Signal'] != 0]
StopLoss_points = df[df['StopLoss_Signal'] != 0]
TakeProfit_points = df[df['TakeProfit_Signal'] != 0]

plt.scatter(buy_points.index, buy_points['Close'], color='green', label='Buy')
plt.scatter(sell_points.index, sell_points['Close'], color='red', label='Sell')
plt.scatter(StopLoss_points.index, StopLoss_points['Close'], color='blue', label='StopLoss')
plt.scatter(TakeProfit_points.index, TakeProfit_points['Close'], color='orange', label='TakeProfit')

plt.xlabel('Date')
plt.ylabel('Equity')
plt.title('Equity and Buy/Sell Points')
plt.legend()
plt.show()

问题根源及修正方案

1. 持仓过滤条件无效的原因

Accu_Position的初始化逻辑完全错误:

self.df['Position'] = 0 
self.df['Accu_Position'] = self.df['Position'] + self.df['Position'].shift(-1)

初始Position全为0,导致Accu_Position也全为0,(self.df['Accu_Position'] == 0)条件永远为真,移除与否自然不影响结果。

修正方式:
Accu_Position是持仓累计值,需基于生成的Signal逐步计算,放在信号生成之后:

# 先计算所有信号总和
self.df['Signal'] = self.df[['Buy_Signal', 'Sell_Signal', 'StopLoss_Signal', 'TakeProfit_Signal']].sum(axis=1)
# 累计得到持仓,限制单仓范围为-1/0/1
self.df['Position'] = self.df['Signal'].cumsum().clip(-1, 1)
# Accu_Position直接复用正确的持仓值
self.df['Accu_Position'] = self.df['Position']

2. 止损止盈无信号的双重原因

  • Accu_Position全为0,导致止损止盈的持仓判断条件永远不成立;
  • EntryPriceRecord用shift(-1)取后一行值,无法持续填充持仓期间的入场价,导致PriceChange计算错误。

修正EntryPriceRecord逻辑:

self.df['EntryPrice'] = np.where((self.df['Buy_Signal']!=0) | (self.df['Sell_Signal']!=0), self.df['Close'], np.nan)
# 用前向填充保留持仓期间的入场价
self.df['EntryPriceRecord'] = self.df['EntryPrice'].ffill().fillna(0)

修正后的完整generate_signals方法

def generate_signals(self):
    self.calculate_moving_average()
    self.calculate_deviation()

    # 初始化所有信号列
    self.df['Position'] = 0 
    self.df['Buy_Signal'] = 0
    self.df['Sell_Signal'] = 0
    self.df['StopLoss_Signal'] = 0
    self.df['TakeProfit_Signal'] = 0
    self.df['Accu_Position'] = 0

    # 生成买卖信号(基于初始空仓条件)
    buy_condition = (self.df['Deviation'] <= self.buy_threshold) & (self.df['Accu_Position'] == 0)
    self.df.loc[buy_condition, 'Buy_Signal'] = 1

    sell_condition = (self.df['Deviation'] >= self.sell_threshold) & (self.df['Accu_Position'] == 0)
    self.df.loc[sell_condition, 'Sell_Signal'] = -1

    # 计算入场价并填充持仓期间的值
    self.df['EntryPrice'] = np.where((self.df['Buy_Signal']!=0) | (self.df['Sell_Signal']!=0), self.df['Close'], np.nan)
    self.df['EntryPriceRecord'] = self.df['EntryPrice'].ffill().fillna(0)
    self.df['PriceChange'] = self.df['Close'] - self.df['EntryPriceRecord']

    # 计算初始持仓
    self.df['Signal'] = self.df[['Buy_Signal', 'Sell_Signal', 'StopLoss_Signal', 'TakeProfit_Signal']].sum(axis=1)
    self.df['Position'] = self.df['Signal'].cumsum().clip(-1, 1)
    self.df['Accu_Position'] = self.df['Position']

    # 基于正确持仓生成止损止盈信号
    # 多头止损
    long_stop = (self.df['Accu_Position'] == 1) & (self.df['PriceChange'] <= -self.stop_loss)
    self.df.loc[long_stop, 'StopLoss_Signal'] = -1
    # 空头止损
    short_stop = (self.df['Accu_Position'] == -1) & (self.df['PriceChange'] >= self.stop_loss)
    self.df.loc[short_stop, 'StopLoss_Signal'] = 1

    # 多头止盈
    long_tp = (self.df['Accu_Position'] == 1) & (self.df['PriceChange'] >= self.take_profit)
    self.df.loc[long_tp, 'TakeProfit_Signal'] = -1
    # 空头止盈
    short_tp = (self.df['Accu_Position'] == -1) & (self.df['PriceChange'] <= -self.take_profit)
    self.df.loc[short_tp, 'TakeProfit_Signal'] = 1

    # 重新计算总信号和最终持仓
    self.df['Signal'] = self.df[['Buy_Signal', 'Sell_Signal', 'StopLoss_Signal', 'TakeProfit_Signal']].sum(axis=1)
    self.df['Position'] = self.df['Signal'].cumsum().clip(-1, 1)
    self.df['Accu_Position'] = self.df['Position']

    # 统计信号数量
    num_buy_signals = self.df['Buy_Signal'].sum()
    num_sell_signals = self.df['Sell_Signal'].sum()
    num_take_profit_signals = self.df['TakeProfit_Signal'].sum()
    num_stop_loss_signals = self.df['StopLoss_Signal'].sum()

    print('Number of Buy Signals:', num_buy_signals)
    print('Number of Sell Signals:', num_sell_signals)
    print('Number of Take Profit Signals:', num_take_profit_signals)
    print('Number of Stop Loss Signals:', num_stop_loss_signals)
    print(self.df['Accu_Position'])

额外优化:盈亏计算修正

原calculate_pnl逻辑错误,正确的每日盈亏应基于持仓和价格变动:

def calculate_pnl(self):
    # 每日盈亏 = (当日收盘价 - 前日收盘价) * 当日持仓
    self.df['PnL'] = (self.df['Close'] - self.df['Close'].shift(1)) * self.df['Position']
    # 累计盈亏
    self.df['AccuPnl'] = self.df['PnL'].cumsum()
    print('Total PnL:', self.df['AccuPnl'].iloc[-1])

内容的提问来源于stack exchange,提问作者Postman1999

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 07:15:59