精华帖子

KDJ指标

由small_q创建,最终由small_q 被浏览 3 用户

因子原理

今天我们来聊聊 KDJ 指标,这个在股市中常被提及的技术分析工具,它可以帮助你更好地理解市场趋势。

KDJ 是由 K 线、D 线和 J 线组成的指标,主要用于判断市场的超买超卖情况。简单来说,KDJ指标可以评估股票是被高估还是被低估,从而做出更明智的投资选择。

我们先来看看KDJ指标是如何计算出来的:

首先,计算第N日的RSV值

其中C是N日收盘价,L是N日最低价,H是N日最高价。

再次,计算K值、D值、J值。

K线:反映了短期价格变化的敏感度。等式右边的K为前一日的K值, 初始K通常设置为50。

D线:是 K 线的平滑移动平均,反映了中期趋势。等式右边的D为前一日的D值, 与K一样,初始值通常设定为50。

J线:是 K 线与 D 线的差值,常用来识别买入和卖出信号。

KDJ的用法决定了它的研究方法并不契合因子投资理论。我们无法单独拿一个K、D或者J值按照往常一样使用因子投资的分析体系,所以我们对该因子的研究需要使用新方法。

因子研究

用法1:看似金叉多死叉空,实则背离

“金叉死叉”是技术分析中一个耳熟能详的用法。金叉指K值上穿D值,被视为多头信号,预示上涨;死叉则是K值下穿D值,为空头信号,预示下跌。

我们借助“金叉死叉”来完善KDJ的投研架构:

  1. 在bigquant上调用股票行情数据,计算个股的KDJ指标值与未来一天的收益率;

    import dai
    import talib
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    
    sd = '2018-01-01'
    ed = '2024-12-31'
    sql = """
    SELECT date, instrument, close, 
    open, high, low, volume, m_lead(open, 2) / m_lead(open, 1) - 1 AS future_return
    FROM cn_stock_prefactors
    WHERE is_zz500 = 1
    AND is_risk_warning = 0
    AND st_status = 0
    AND suspended = 0
    QUALIFY COLUMNS (*) IS NOT NULL
    ORDER BY date
    """
    data = dai.query(sql, filters={'date': [sd, ed]}).df()
    
  2. 标记每天市场中发生“金叉”和“死叉”的股票;

    def KDJ(df):
        df.sort_values('date', inplace=True)
        df['k'], df['d'] = talib.STOCH(
            df['high'].values, 
            df['low'].values, 
            df['close'].values, 
            fastk_period=9,
            slowk_period=3,
            slowk_matype=0,
            slowd_period=3,
            slowd_matype=0
        )
        df['j'] = 3 * df['k'] - 2 * df['d']
    
        df['signal'] = np.where(
            (df['k']>df['d']) & (df['k'].shift(1) < df['d'].shift(1)), 
            1,                                                                 # 金叉
            np.where(
                (df['k']<df['d']) & (df['k'].shift(1) > df['d'].shift(1)),     # 死叉
                -1, 
                0
            )
        )
        return df
    
    df = data.groupby('instrument').apply(KDJ).reset_index(drop=True).dropna()
    df
    
  3. 将所有金叉个股的未来一天收益率取平均作为当期金叉的预期收益(若当天不存在金叉,则定义为0),将所有死叉个股的未来一天收益率取平均作为当期死叉预期收益(若当天没有个股发生死叉,则定义为0);分别计算累计收益。

    # 统计每天金叉死叉的累计收益曲线
    def calc_return(df):
        """
        |date|gold_cross_return|dead_cross_return|
        |... |        ...      |      ...        |
        """
        # 计算金叉的平均收益
        gold = df[df['signal']==1]
        if len(gold)==0:
            gold_return = 0
        else:
            gold_return = gold['future_return'].mean()
        
        # 计算死叉的平均收益
        death = df[df['signal']==-1]
        if len(death)==0:
            death_return = 0
        else:
            death_return = death['future_return'].mean()
        
        return pd.DataFrame(
            {
                "date": [df['date'].iloc[0]], 
                "gold_cross_return" : [gold_return], 
                "dead_cross_return": [death_return]
            }
        )
    
    cross_return = df.groupby('date').apply(calc_return).reset_index(drop=True)
    cross_return
    

如图所示,为2018年到2024年金叉和死叉股票的累计收益趋势:

我们发现,金叉的累计收益从18年以来一路向下,相较之下,死叉的累计收益表现较为平稳,这与传统的“金叉死叉”法则是背离的。所以简单地使用KDJ的金叉死叉开仓平仓是不现实的。

用法2:超买状态空头、超卖多头

为了挖掘KDJ的潜在价值,我们转而关注超买超卖区域的表现。

首先,我们依旧需要根据公式计算KDJ指标与个股未来一天收益率;

fig = plt.figure()
plt.plot(cross_return['date'], cross_return['gold_cross_return'].cumsum(), label='gold_cross_return')
plt.plot(cross_return['date'], cross_return['dead_cross_return'].cumsum(), label='death_cross_return')
plt.legend()
plt.show()

接着,我们定义超买和超卖状态,当K值<10,D值<20,J值<0时,为超卖状态;当K值>90,D值>80,J值>100时,为超买状态;

最后,分别统计超买、超卖状态下未来一天的收益均值和累计收益。

def calc_return_2(df):
    # 计算超卖组合的预期收益率
    over_sell = df[(df['k']<10) & (df['d']<20) & (df['j']<0)]
    if len(over_sell)==0:
        over_sell_return = 0
    else:
        over_sell_return = over_sell['future_return'].mean()
    
    # 计算超买组合的预期收益率
    over_buy = df[(df['k']>90) & (df['d']>80) & (df['j']>100)]
    if len(over_buy)==0:
        over_buy_return = 0
    else:
        over_buy_return = over_buy['future_return'].mean()
    
    return pd.DataFrame(
        {
            'date': [df['date'].iloc[0]], 
            'over_sell_return': [over_sell_return], 
            'over_buy_return': [over_buy_return]
        }
    )

over_return = df.groupby('date').apply(calc_return_2).reset_index(drop=True)
over_return

从图中可以发现,用法2更适合做空机制,即看涨“超卖”,看跌“超买”。

图中”超卖“的累计收益在19年9月之后位于”超买“上方,说明用法2基本正确,但是超卖区域的累计收益依旧为负,所以我们需要在此基础上优化这个指标的用法。

用法3:低位金叉多头,高位死叉空头

我们以传统金叉进、死叉出的策略,将前两种用法结合,在低位统计金叉信号的表现,在高位统计死叉信号的表现。

首先,我们定义低位超卖、高位超买,计算每只股票的金叉死叉信号;

再次,我们统计低位金叉、高位死叉的累积收益。

def calc_return_3(df):
    # 高位死叉
    temp_1 = df[(df['signal']==1) & ((df['k']>90) & (df['d']>80) & (df['j']>100))]
    if len(temp_1)==0:
        over_buy_death_return = 0
    else:
        over_buy_death_return = temp_1['future_return'].mean()
    
    # 低位金叉
    temp_2 = df[(df['signal']==-1) & ((df['k']<10) & (df['d']<20) & (df['j']<0))]
    if len(temp_2)==0:
        over_sell_gold_return = 0
    else:
        over_sell_gold_return = temp_2['future_return'].mean()

    return pd.DataFrame(
        {
            "date": [df['date'].iloc[0]], 
            "over_buy_death_return": [over_buy_death_return], 
            "over_sell_gold_return": [over_sell_gold_return]
        }
    )

melt = df.groupby('date').apply(calc_return_3).reset_index(drop=True)

可以发现,低位金叉表现出明显的多头信号,而高位死叉则强化了下跌信号。

用法3不仅符合我们对KDJ指标的预期认知,提升了策略的可操作性,同时累计收益曲线也得到了明显改善。

因子分析源码

完整因子分析源码,请查看:

https://bigquant.com/codesharev3/7296e6e2-26be-4333-9445-05ab28aff97b

\

标签

投资决策
{link}