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

量化交易系统指标集成后信号始终为0,无法触发交易

量化交易系统Signal字段始终为0的排查与解决

我的量化交易系统已完成指标集成,能正常运行,但Signal字段始终为0,无法生成买入(1)或卖出(-1)信号,导致没有交易订单执行。但相同标的在TradingView平台上能正常给出对应信号。

核心代码

indicators.py

import pandas as pd
import numpy as np
import ta

def lsma(data, period=14, regression=True):
    size = len(data)
    out = np.full(size, np.nan)
    w = np.arange(1, period + 1, dtype=np.float64)
    if regression:
        for i in range(period - 1, size):
            e = i + 1
            s = e - period
            intercept, slope = np.dot(np.linalg.pinv(np.vstack((np.ones(period), w)).T), data[s:e])
            out[i] = slope * period + intercept
    else:
        for i in range(period - 1, size):
            e = i + 1
            s = e - period
            out[i] = np.dot(data[s:e], w) / np.sum(w)
    return out

def zlsma(data, period=14, regression=True):
    size = len(data)
    sum_w = np.sum(np.arange(1, period + 1, dtype=np.float64))
    lsma_v = lsma(data, period, regression)
    out = np.full(size, np.nan)
    w = sum_w / (2 * np.sum(np.arange(1, period)))
    for i in range(period - 1, size):
        out[i] = lsma_v[i] + (data[i] - lsma_v[i]) * w
    return out

def add_indicators(df):
    df['FastEMA'] = ta.trend.EMAIndicator(df['Close'], window=5).ema_indicator()
    df['SlowEMA'] = ta.trend.EMAIndicator(df['Close'], window=15).ema_indicator()
    df['LongEMA'] = ta.trend.EMAIndicator(df['Close'], window=50).ema_indicator()
    df['VeryLongEMA'] = ta.trend.EMAIndicator(df['Close'], window=200).ema_indicator()
    df['RSI'] = ta.momentum.RSIIndicator(df['Close'], window=5).rsi()
    bb_bands = ta.volatility.BollingerBands(df['Close'], window=20, window_dev=2)
    df['BB_Lower'] = bb_bands.bollinger_lband()
    df['BB_Upper'] = bb_bands.bollinger_hband()
    df['VolumeAvg'] = ta.trend.SMAIndicator(df['Volume'], window=20).sma_indicator()
    df['ZLSMA'] = zlsma(df['Close'].values, 32)
    df['ATR'] = ta.volatility.AverageTrueRange(df['High'], df['Low'], df['Close'], window=1).average_true_range()
    df['LongExitCE'] = df['High'].rolling(window=1).max() - df['ATR'] * 2
    df['ShortExitCE'] = df['Low'].rolling(window=1).min() + df['ATR'] * 2
    return df

def heikin_ashi(df):
    df['HA_Close'] = (df['Open'] + df['High'] + df['Low'] + df['Close']) / 4

    idx = df.index.name
    df.reset_index(inplace=True)

    for i in range(0, len(df)):
        if i == 0:
            df.at[i, 'HA_Open'] = (df.at[i, 'Open'] + df.at[i, 'Close']) / 2
        else:
            df.at[i, 'HA_Open'] = (df.at[i - 1, 'HA_Open'] + df.at[i - 1, 'HA_Close']) / 2

    if idx:
        df.set_index(idx, inplace=True)

    df['HA_High'] = df[['HA_Open', 'HA_Close', 'High']].max(axis=1)
    df['HA_Low'] = df[['HA_Open', 'HA_Close', 'Low']].min(axis=1)
    return df

def generate_signals(df, predicted_prices):
    df['Signal'] = 0
    df['Predicted_Close'] = predicted_prices[:len(df)]
    df.loc[(df['Close'] > df['FastEMA']) & (df['FastEMA'] > df['ZLSMA']) &
           (df['Close'] > df['BB_Upper']) & (df['Volume'] > df['VolumeAvg'] * 1.5) &
           (df['RSI'] > 55) & (df['Predicted_Close'] > df['Close']), 'Signal'] = 1
    df.loc[(df['Close'] < df['FastEMA']) & (df['FastEMA'] < df['ZLSMA']) &
           (df['Close'] < df['BB_Lower']) & (df['Volume'] > df['VolumeAvg'] * 1.5) &
           (df['RSI'] < 45) & (df['Predicted_Close'] < df['Close']), 'Signal'] = -1
    return df

main.py 调用代码

if __name__ == "__main__":
    exchange = BybitExchange()
    max_pos = 50  # 最大持仓数
    symbols = exchange.get_tickers()  # 获取Bybit衍生品所有标的

    # 无限循环
    while True:
        balance = exchange.get_balance()
        if balance is None:
            print('无法连接到API')
        else:
            balance = float(balance)
            print(f'账户余额: {balance}')
            pos = exchange.get_positions()
            print(f'当前持仓数: {len(pos)},持仓列表: {pos}')

            if len(pos) < max_pos:
                # 遍历所有标的
                for elem in symbols:
                    pos = exchange.get_positions()
                    if len(pos) >= max_pos:
                        break
                    # 获取信号
                    trades = exchange.get_trade(elem)
                    if trades['Signal'].iloc[-1] == 1:
                        print(f'发现{elem}的买入信号')
                        exchange.set_mode(elem)
                        time.sleep(2)
                        exchange.set_leverage(elem, leverage, leverage)
                        sl_atr = sl_coef * trades.ATR.iloc[-1]
                        tp_sl_ratio = tp_sl_ratio
                        sl = trades['Close'].iloc[-1] - sl_atr
                        tp = trades['Close'].iloc[-1] + sl_atr * tp_sl_ratio
                        exchange.place_order_market('Buy', balance*0.99*leverage*0.97, tp, sl)
                        time.sleep(5)
                    elif trades['Signal'].iloc[-1] == -1:
                        print(f'发现{elem}的卖出信号')
                        exchange.set_mode(elem)
                        time.sleep(2)
                        exchange.set_leverage(elem, leverage, leverage)
                        sl_atr = sl_coef * trades.ATR.iloc[-1]
                        tp_sl_ratio = tp_sl_ratio
                        sl = trades['Close'].iloc[-1] + sl_atr
                        tp = trades['Close'].iloc[-1] - sl_atr * tp_sl_ratio
                        exchange.place_order_market('Sell', balance*0.99*leverage*0.97, tp, sl)
                        time.sleep(5)
        print('等待2分钟')
        time.sleep(120)

排查方向与解决方法

1. 信号触发条件过于严苛

generate_signals中,买入/卖出信号需要同时满足6个条件,只要有一个不满足就无法生成信号。比如:

  • 收盘价同时站上FastEMA、ZLSMA和布林带上轨
  • 成交量超过20日均值1.5倍
  • RSI处于55以上(买入)或45以下(卖出)
  • 预测收盘价还要高于/低于当前收盘价

解决方法:

  • 先单独验证每个条件的触发情况,在generate_signals中添加调试代码,打印每个条件的布尔值统计:
    # 调试代码示例
    print("Close > FastEMA 触发次数:", (df['Close'] > df['FastEMA']).sum())
    print("FastEMA > ZLSMA 触发次数:", (df['FastEMA'] > df['ZLSMA']).sum())
    print("Close > BB_Upper 触发次数:", (df['Close'] > df['BB_Upper']).sum())
    # 其他条件同理
    
  • 适当放宽条件,比如将成交量阈值从1.5倍调整为1.2倍,或者扩大RSI的阈值范围(比如买入RSI>50,卖出RSI<50)。

2. ZLSMA指标实现与TradingView不一致

自己实现的zlsma函数可能和TradingView的算法有差异:

  • 检查权重计算逻辑:w = sum_w / (2 * np.sum(np.arange(1, period))),这里np.arange(1, period)不包含period,而TradingView的ZLSMA可能使用全周期权重。
  • 对比TradingView上的ZLSMA输出和本地计算的df['ZLSMA']值,验证是否存在偏差。

3. 数据长度不足导致指标值为NaN

所有指标都需要足够的历史数据才能计算有效值:

  • ZLSMA用了32周期,最长的EMA是200周期,所以get_trade(elem)返回的K线数据至少需要200条以上,否则指标会出现NaN,导致条件判断失败。
  • 检查predicted_prices是否有效:如果预测值是NaN或者不符合预期,最后一个条件会不成立。

4. 未使用Heikin-Ashi数据

代码中实现了heikin_ashi函数,但指标计算和信号生成都用的是原始K线数据,如果TradingView是基于Heikin-Ashi K线计算的指标,会导致指标值差异。解决方法:将add_indicators和generate_signals中的Close等字段替换为HA字段,比如df['HA_Close']。

5. main.py变量未定义问题

main.py中使用了leverage、sl_coef、tp_sl_ratio三个变量但未定义,虽然不是Signal为0的直接原因,但会导致下单逻辑报错,建议补充定义:

# 在main.py开头添加
leverage = 10  # 杠杆倍数
sl_coef = 2    # 止损ATR系数
tp_sl_ratio = 1.5  # 止盈止损比例

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 15:27:03