KDJ

References

Definition

  • KDJ, also known as random index, is a technical index widely used in short-term trend analysis of futures and stock markets.
  • KDJ is calculated on the basis of the highest price, the lowest price and the closing price. It can reflect the intensity of price fluctuations, overbought and oversold, and give trading signals before prices rise or fall.
  • KDJ is sensitive to price changes, which may generate wrong trading signals in very volatile markets, causing prices not to rise or fall with the signals, thus causing traders to make misjudgments.

Calculation


step 1: calculate the immature random value (RSV)

png

  • Hn denotes the highest price, Ln denotes the lowest price, C denotes the closing price

step 2: calculate the %K line:

png

step 3: calculate the %D line:

png

step 4: calculate the %J line:

png


Read the indicator

  • KDJ values range from 0 to 100 (J values sometimes exceed). Generally speaking, an overbought signal occurs when the D value is more than 70 and an oversell signal occurs when the D value is less than 30.

  • Gold fork
    • When the K line breaks through the D line on the graph, it is commonly known as the golden fork, which is a buy signal. In addition, when the K-line and D-line cross upward below 20, the short-term buy signal is more accurate; if the K value is below 50 and crosses twice above D value to form a higher golden fork “W” shape, then the stock price may rise considerably and the market prospect is promising.
  • Dead fork
    • When the K value gets smaller and smaller, and then falls below the D line from above, it is often called a dead fork and is regarded as a sell signal. In addition, when K-line and D-line cross downward at gate 80, the short-term sell signal is more accurate. If the K value is above 50, crossing below the D line twice in the trend, and from the low dead cross “M” shape, the market outlook may have a considerable decline in stock prices.
  • Bottom and top
    • J-line is a sensitive line of direction. When the J value is greater than 90, especially for more than 5 consecutive days, the stock price will form at least a short-term peak. On the contrary, when the J value is less than 10:00, especially for several consecutive days, the stock price will form at least a short-term bottom.
Load basic packages
import pandas as pd
import numpy as np
import os
import gc
import copy
from pathlib import Path
from datetime import datetime, timedelta, time, date
#this package is to download equity price data from yahoo finance
#the source code of this package can be found here: https://github.com/ranaroussi/yfinance/blob/main
import yfinance as yf
pd.options.display.max_rows = 100
pd.options.display.max_columns = 100

import warnings
warnings.filterwarnings("ignore")

import pytorch_lightning as pl
random_seed=1234
pl.seed_everything(random_seed)
Global seed set to 1234





1234
#S&P 500 (^GSPC),  Dow Jones Industrial Average (^DJI), NASDAQ Composite (^IXIC)
#Russell 2000 (^RUT), Crude Oil Nov 21 (CL=F), Gold Dec 21 (GC=F)
#Treasury Yield 10 Years (^TNX)

#benchmark_tickers = ['^GSPC', '^DJI', '^IXIC', '^RUT',  'CL=F', 'GC=F', '^TNX']

benchmark_tickers = ['^GSPC']
tickers = benchmark_tickers + ['GSK', 'NVO', 'AROC']
#https://github.com/ranaroussi/yfinance/blob/main/yfinance/base.py
#     def history(self, period="1mo", interval="1d",
#                 start=None, end=None, prepost=False, actions=True,
#                 auto_adjust=True, back_adjust=False,
#                 proxy=None, rounding=False, tz=None, timeout=None, **kwargs):

dfs = {}

for ticker in tickers:
    cur_data = yf.Ticker(ticker)
    hist = cur_data.history(period="max", start='2000-01-01')
    print(datetime.now(), ticker, hist.shape, hist.index.min(), hist.index.max())
    dfs[ticker] = hist
2022-09-10 21:20:33.417662 ^GSPC (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 21:20:33.761677 GSK (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 21:20:34.092952 NVO (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 21:20:34.340454 AROC (3791, 7) 2007-08-21 00:00:00 2022-09-09 00:00:00
ticker = 'GSK'
dfs[ticker].tail(5)
Open High Low Close Volume Dividends Stock Splits
Date
2022-09-02 31.600000 31.969999 31.469999 31.850000 8152600 0.0 0.0
2022-09-06 31.650000 31.760000 31.370001 31.469999 5613900 0.0 0.0
2022-09-07 31.209999 31.590000 31.160000 31.490000 4822000 0.0 0.0
2022-09-08 30.910000 31.540001 30.830000 31.510000 6620900 0.0 0.0
2022-09-09 31.950001 31.969999 31.730000 31.889999 3556800 0.0 0.0
Define KDJ calculation function
def cal_kdj(ohlc: pd.DataFrame, period: int = 14) -> pd.DataFrame:
    """
    KDJ
    reference:  https://www.futunn.com/en/learn/detail-what-is-kdj-64858-0

    """
    ohlc = ohlc.copy(deep=True)
    ohlc.columns = [c.lower() for c in ohlc.columns]

    highest_high = ohlc["high"].rolling(center=False, window=period).max()
    lowest_low = ohlc["low"].rolling(center=False, window=period).min()
    rsv = (ohlc["close"] - lowest_low) / (highest_high - lowest_low) * 100
    rsv = rsv.values

    k_ = np.zeros(len(ohlc))
    d_ = np.zeros(len(ohlc))

    for i in range(len(ohlc)):
        if i < period:
            k_[i] = 0
            d_[i] = 0
        else:
            k_[i] = (2/3)*k_[i-1] + (1/3)*rsv[i]
            d_[i] = (2/3)*d_[i-1] + (1/3)*k_[i]
    j_ = 3*k_ - 2*d_


    return pd.DataFrame(data={'K': k_, 'D': d_, 'J': j_}, index=ohlc.index)

def cal_kdj2(ohlc: pd.DataFrame, period: int = 14, m1: int = 3, m2: int = 3) -> pd.DataFrame:
    """
    KDJ
    reference:  
    - https://www.futunn.com/en/learn/detail-what-is-kdj-64858-0
    - https://github.com/mpquant/MyTT/blob/ea4f14857ecc46a3739a75ce2e6974b9057a6102/MyTT.py#L145

    """
    ohlc = ohlc.copy(deep=True)
    ohlc.columns = [c.lower() for c in ohlc.columns]

    highest_high = ohlc["high"].rolling(center=False, window=period).max()
    lowest_low = ohlc["low"].rolling(center=False, window=period).min()
    rsv = (ohlc["close"] - lowest_low) / (highest_high - lowest_low) * 100
    
    k_ = rsv.ewm(span=m1*2-1, adjust=False).mean()
    d_ = k_.ewm(span=m2*2-1, adjust=False).mean()

    j_ = 3*k_ - 2*d_


    return pd.DataFrame(data={'K': k_, 'D': d_, 'J': j_})

Calculate KDJ
df = dfs[ticker][['Open', 'High', 'Low', 'Close', 'Volume']]
df = df.round(2)

cal_kdjdf_ta = cal_kdj(df, period = 14) df = df.merge(df_ta, left_index = True, right_index = True, how=’inner’ )

del df_ta gc.collect()

cal_kdj2
<function __main__.cal_kdj2(ohlc: pandas.core.frame.DataFrame, period: int = 14, m1: int = 3, m2: int = 3) -> pandas.core.frame.DataFrame>
df_ta = cal_kdj2(df, period = 14, m1 = 3, m2 = 3)
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )

del df_ta
gc.collect()
143

df_ta = cal_kdj2(df, period = 9, m1 = 5, m2 = 3) df = df.merge(df_ta, left_index = True, right_index = True, how=’inner’ )

del df_ta gc.collect()

display(df.head(5))
display(df.tail(5))
Open High Low Close Volume K D J
Date
1999-12-31 19.60 19.67 19.52 19.56 139400 NaN NaN NaN
2000-01-03 19.58 19.71 19.25 19.45 556100 NaN NaN NaN
2000-01-04 19.45 19.45 18.90 18.95 367200 NaN NaN NaN
2000-01-05 19.21 19.58 19.08 19.58 481700 NaN NaN NaN
2000-01-06 19.38 19.43 18.90 19.30 853800 NaN NaN NaN
Open High Low Close Volume K D J
Date
2022-09-02 31.60 31.97 31.47 31.85 8152600 4.883618 3.605564 7.439726
2022-09-06 31.65 31.76 31.37 31.47 5613900 4.287737 3.832955 5.197301
2022-09-07 31.21 31.59 31.16 31.49 4822000 6.056166 4.574025 9.020447
2022-09-08 30.91 31.54 30.83 31.51 6620900 10.230522 6.459524 17.772518
2022-09-09 31.95 31.97 31.73 31.89 3556800 17.151732 10.023594 31.408009
df[['K', 'D', 'J']].hist(bins=50)
array([[<AxesSubplot:title={'center':'K'}>,
        <AxesSubplot:title={'center':'D'}>],
       [<AxesSubplot:title={'center':'J'}>, <AxesSubplot:>]], dtype=object)

png

#https://github.com/matplotlib/mplfinance
#this package help visualize financial data
import mplfinance as mpf
import matplotlib.colors as mcolors

# all_colors = list(mcolors.CSS4_COLORS.keys())#"CSS Colors"
all_colors = list(mcolors.TABLEAU_COLORS.keys()) # "Tableau Palette",
# all_colors = list(mcolors.BASE_COLORS.keys()) #"Base Colors",


#https://github.com/matplotlib/mplfinance/issues/181#issuecomment-667252575
#list of colors: https://matplotlib.org/stable/gallery/color/named_colors.html
#https://github.com/matplotlib/mplfinance/blob/master/examples/styles.ipynb

def make_3panels2(main_data, mid_panel, chart_type='candle', names=None, 
                  figratio=(14,9), fill_weights = (0, 0)):


    style = mpf.make_mpf_style(base_mpf_style='yahoo',  #charles
                               base_mpl_style = 'seaborn-whitegrid',
#                                marketcolors=mpf.make_marketcolors(up="r", down="#0000CC",inherit=True),
                               gridcolor="whitesmoke", 
                               gridstyle="--", #or None, or - for solid
                               gridaxis="both", 
                               edgecolor = 'whitesmoke',
                               facecolor = 'white', #background color within the graph edge
                               figcolor = 'white', #background color outside of the graph edge
                               y_on_right = False,
                               rc =  {'legend.fontsize': 'small',#or number
                                      #'figure.figsize': (14, 9),
                                     'axes.labelsize': 'small',
                                     'axes.titlesize':'small',
                                     'xtick.labelsize':'small',#'x-small', 'small','medium','large'
                                     'ytick.labelsize':'small'
                                     }, 
                              )   

    if (chart_type is None) or (chart_type not in ['ohlc', 'line', 'candle', 'hollow_and_filled']):
        chart_type = 'candle'
    len_dict = {'candle':2, 'ohlc':3, 'line':1, 'hollow_and_filled':2}    
        
    kwargs = dict(type=chart_type, figratio=figratio, volume=True, volume_panel=2, 
                  panel_ratios=(4,2,1), tight_layout=True, style=style, returnfig=True)
    
    if names is None:
        names = {'main_title': '', 'sub_tile': ''}
    



    added_plots = { 
        'K':  mpf.make_addplot(mid_panel['K'], panel=1, color='orange', width=1, secondary_y=False), 
        'D':  mpf.make_addplot(mid_panel['D'], panel=1, color='dodgerblue', width=1, secondary_y=False), 
        'J':  mpf.make_addplot(mid_panel['J'], panel=1, color='darkviolet', width=1, secondary_y=False), 
    }
    
    fb_bbands2_ = dict(y1=fill_weights[0]*np.ones(mid_panel.shape[0]),
                      y2=fill_weights[1]*np.ones(mid_panel.shape[0]),color="lightskyblue",alpha=0.1,interpolate=True)
    fb_bbands2_['panel'] = 1

    fb_bbands= [fb_bbands2_]
    

    fig, axes = mpf.plot(main_data,  **kwargs,
                         addplot=list(added_plots.values()),
                         fill_between=fb_bbands
                        )
    # add a new suptitle
    fig.suptitle(names['main_title'], y=1.05, fontsize=12, x=0.1375)

    axes[0].set_title(names['sub_tile'], fontsize=10, style='italic',  loc='left')
    axes[2].set_ylabel('KDJ')
    
    axes[2].legend([None]*3)
    handles = axes[2].get_legend().legendHandles
    axes[2].legend(handles=handles,labels=list(added_plots.keys()))
    

    return fig, axes
   

start = -200
end = df.shape[0]

names = {'main_title': f'{ticker}', 
         'sub_tile': 'KDJ: an overbought signal occurs when the D value is more than 70 and an oversell signal occurs when the D value is less than 30'}


aa_, bb_ = make_3panels2(df.iloc[start:end][['Open', 'High', 'Low', 'Close', 'Volume']], 
                         df.iloc[start:end][['K', 'D', 'J']], 
                         chart_type='hollow_and_filled',
                         names = names,
                         fill_weights = (30, 70)
                        
                        )

png