'Peak and Trough of Numeric Data

Can anyone help me please. I need to find the peak and troughs in a data series but with 2 conditions which are:

  1. Peak or trough is peak for minimum of n(days)
  2. Last value must be above a percentage from the last peak or trough with this formula:
  • Uptrend Retracement = High Swing – ((High Swing – Low Swing) × percentage)
  • Downtrend Retracement = Low Swing + ((High Swing – Low Swing) × percentage)

If two conditions are true then the peak or trough is the new one. Can anyone help me please I really need help.



Solution 1:[1]

Data:

import pandas as pd
import pandas_datareader

# data
data = pandas_datareader.get_data_yahoo("MSFT", start="2000-01-01")["Adj Close"]

The defined variables (n and percentage). As can be seen in the loop, n is single-directional here, so selecting n = 10 means that it must be a local minima/maxima within the past 10 days and following 10 days.

# variables from question
n = 10 # single direction, so if a range in either direction, divide by 2.
percentage = 20 # 20 is 20%

Define the lists for the peaks and troughs:

# first high will be the start
highs = [data.iloc[0]]
high_dates = [data.index[0]]
# first low also the start date
lows = [data.iloc[0]]
low_dates = [data.index[0]]

Main body of code:

# for each row
for i in range(0, len(data)):
    # calculate the parameters
    upper_rule = highs[-1] - ((highs[-1] - lows[-1])*(percentage/100))
    lower_rule = lows[-1] + ((highs[-1] - lows[-1])*(percentage/100))
    # peak
    # if maximum of the rolling period and meets rule requirement
    if (data.rolling(window=n).max().iloc[i] == data.iloc[i]) \
        & (data.iloc[::-1].rolling(window=n).max().iloc[len(data)-i-1] == data.iloc[i]) \
        & (data.iloc[i] > upper_rule):
        highs.append(data.iloc[i])
        high_dates.append(data.index[i])
    # if minimum of the rolling period and meets rule requirement
    elif (data.rolling(window=n).min().iloc[i] == data.iloc[i]) \
        & (data.iloc[::-1].rolling(window=n).min().iloc[len(data)-i-1] == data.iloc[i]) \
        & (data.iloc[i] < lower_rule):
        lows.append(data.iloc[i])
        low_dates.append(data.index[i])

Add peaks and troughs to dataframe:

# dataframes of peaks and troughs
peaks = pd.DataFrame({"Close": highs}, index=high_dates)
troughs = pd.DataFrame({"Close": lows}, index=low_dates)
data = data.to_frame()
# append these to the dataframe
data = data.merge(peaks, left_index=True, right_index=True, how="left").rename(columns={"Close": "Peaks"})
data = data.merge(troughs, left_index=True, right_index=True, how="left").rename(columns={"Close": "Troughs"})

I have plotted this here:

import matplotlib.pyplot as plt

fig, ax1 = plt.subplots()
data["Adj Close"].plot(ax=ax1)
data["Peaks"].dropna().plot(ax=ax1, marker="^", markersize=5, color="green", linestyle='')
data["Troughs"].dropna().plot(ax=ax1, marker="v", markersize=5, color="red", linestyle='')

Using Microsoft as an example ("MSFT" used for data), I have found your secondary requirement to be quite restrictive (there is a trough in March 2020 which isn't included because the percentage is too low). The percentage would need to be very high to include this trough, because the price has climbed so much since the previous trough that met the requirement.

Note that I have taken just the "Adj Close" data, which means that data is a Series (which I why I use to_frame() before merging). If you have multiple columns as a dataframe, you will of course need to change the code slightly to specifically take the price value (you can't compare the previous price with a row in this code, obviously).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rawson