jbn / ZigZag

Python library for identifying the peaks and valleys of a time series.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Calculate threshold as multiples of ATR

HumanRupert opened this issue · comments

commented

Something like this feature in NinjaTrader or this script in TradingView.

I've got time to implement it. But I'm not sure how active this repo is. In any case, HMU if you need a maintainer.

Did you get a response @akhtariali ? If not, would you consider forking the repo and adding your High/Low and ATR updates? I would be very interested in the functionality you mention especially if you can code a functional version that uses the candle High/Low.

commented

@flowtrader2016 I ended up implementing this code in Python, using ta-lib to calculate ATR. I'll send @jbn an email to see if he looks for maintainers/collaborators.

@flowtrader2016 I ended up implementing this code in Python, using ta-lib to calculate ATR. I'll send @jbn an email to see if he looks for maintainers/collaborators.

I would love to see your code @akhtariali Is it something you could share please? Thanks

commented

@flowtrader2016 Sure thing.

zigzags = []

def calc_change_since_pivot(row, key):
    current = row[key]
    last_pivot = zigzags[-1]["Value"]
    if(last_pivot == 0): last_pivot = 1 ** (-100) # avoid division by 0
    perc_change_since_pivot = (current - last_pivot) / abs(last_pivot)
    return perc_change_since_pivot

def get_zigzag(row, taip=None):
    if(taip == "Peak"): key = "High"
    elif(taip == "Trough"): key = "Low"
    else: key = "Close"

    return {
        "Date": row["Date"],
        "Value": row[key],
        "Type": taip 
    }

for ix, row in hist.iterrows():
    threshold = row["ATR"] / row["Open"] * ATR_MULTIPILIER

    # handle first point
    is_starting = ix == 0
    if(is_starting):
        zigzags.append(get_zigzag(row))
        continue

    # handle first line
    is_first_line = len(zigzags) == 1
    if(is_first_line):
        perc_change_since_pivot = calc_change_since_pivot(row, "Close")

        if(abs(perc_change_since_pivot) >= threshold):
            if(perc_change_since_pivot > 0):
                zigzags.append(get_zigzag(row, "Peak"))
                zigzags[0]["Type"] = "Trough"
            else: 
                zigzags.append(get_zigzag(row, "Trough"))
                zigzags[0]["Type"] = "Peak"
        continue
    
    # handle other lines
    is_trough = zigzags[-2]["Value"] > zigzags[-1]["Value"]
    is_ending = ix == len(hist.index) - 1
    last_pivot = float(zigzags[-1]["Value"])

    # based on last pivot type, look for reversal or continuation
    if(is_trough):
        perc_change_since_pivot = calc_change_since_pivot(row, "High")
        is_reversing = (perc_change_since_pivot >= threshold) or is_ending
        is_continuing = row["Low"] <= last_pivot
        if (is_continuing): zigzags[-1] = get_zigzag(row, "Trough")
        elif (is_reversing): zigzags.append(get_zigzag(row, "Peak"))
    else:
        perc_change_since_pivot = calc_change_since_pivot(row, "Low")
        is_reversing = (perc_change_since_pivot <= -threshold) or is_ending
        is_continuing = row["High"] >= last_pivot
        if(is_continuing): zigzags[-1] = get_zigzag(row, "Peak")
        elif (is_reversing): zigzags.append(get_zigzag(row, "Trough"))
    
zigzags = pd.DataFrame(zigzags)
zigzags["PrevExt"] = zigzags.Value.shift(2)
print(zigzags.head())
print("\n")

# Note: 2nd index extremum is always compared with the 0th index, which is not actually an extremum, so ignore them.
higher_highs = zigzags.dropna()
higher_highs = higher_highs.loc[(higher_highs["Value"] > higher_highs["PrevExt"]) & (higher_highs["Type"] == "Peak") & (higher_highs.index != 2)]
print(higher_highs.head())
print("\n")

lower_lows = zigzags.dropna()
lower_lows = lower_lows.loc[(lower_lows["Value"] < lower_lows["PrevExt"]) & (lower_lows["Type"] == "Trough") & (lower_lows.index != 2)]
print(lower_lows.head())

With a l'l bit of matplotlib magic, you'll end up with:

fig, ax = plt.subplots()

candlestick_ohlc(ax, hist.values, width=0.6, colorup='green', colordown='red', alpha=0.8)
ax.set_xlabel('Date')
ax.set_ylabel('Price')
date_format = mpl_dates.DateFormatter('%d-%m-%Y')
ax.xaxis.set_major_formatter(date_format)

fig.autofmt_xdate()

zigzags.plot(ax=ax, x="Date", y="Value", legend=False)

for ix, row in lower_lows.iterrows():
    plt.plot(row["Date"], row["Value"], "rv")

for ix, row in higher_highs.iterrows():
    plt.plot(row["Date"], row["Value"], "g^")

plt.show()

Screen Shot 2021-05-08 at 16 06 31

@flowtrader2016 Sure thing.

Hi @akhtariali ,

I tried to use your code with this test dataframe:

def genMockDataFrame(days,start_price,colName,startDate,seed=None): 
   
    periods = days*24
    np.random.seed(seed)
    steps = np.random.normal(loc=0, scale=0.0018, size=periods)
    steps[0]=0
    P = start_price+np.cumsum(steps)
    P = [round(i,4) for i in P]

    fxDF = pd.DataFrame({ 
        'ticker':np.repeat( [colName], periods ),
        'date':np.tile( pd.date_range(startDate, periods=periods, freq='H'), 1 ),
        'price':(P)})
    fxDF.index = pd.to_datetime(fxDF.date)
    fxDF = fxDF.price.resample('D').ohlc()
    return fxDF

hist = genMockDataFrame(100,1.1904,'eurusd','19/3/2020',seed=1)

I'm not sure what value to use for the MULTIPLIER and receive list index out of range error when trying different values. Would be great to see it working with this/any test data.

Thanks

commented

@flowtrader2016 I use yfinance to fetch data. ATR_MULTIPLIER is used to calculate the % threshold of divergence since the last pivot to calculate reversals–threshold = ATR / open * MULTIPLIER.

@apologeticallypervy did you receive a response? If not it would be great to see you create a new repo with your zigzag code.

commented

No luck. I'm having crazy times right now; will take a look at it ASAP.

@HumanRupert thanks for you share,It's beautiful