This introduces finding the lower peaks of a financial time series to interpolate these values across the data points of the lower peaks to generate the lower envelope of this time series.
There are different interpolation methods introduced here. Piecewise Cubic Hermite Interpolating Polynomial (PCHIP), Natural Cubic Spline Interpolation, and AKIMA Spline Interpolations are the effective tool for the enveloping. The choice will differ depending on the purpose of finding the envelope.
# importing necessary tools for math and plot
# For mathematics
import math
import numpy as np
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.find_peaks.html
# https://stackoverflow.com/questions/1713335/peak-finding-algorithm-for-python-scipy
from scipy.signal import find_peaks
# Piecewise Cubic Hermite Interpolating Polynomial (PCHIP)
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.pchip_interpolate.html
# https://pythonnumericalmethods.studentorg.berkeley.edu/notebooks/chapter17.03-Cubic-Spline-Interpolation.html
from scipy.interpolate import pchip_interpolate
# Cubic Spline Interpolation
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicSpline.html
from scipy.interpolate import CubicSpline
# AKIMA Spling Interpolation
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.Akima1DInterpolator.html
from scipy.interpolate import Akima1DInterpolator
# Inline animations in Jupyter
# https://stackoverflow.com/questions/43445103/inline-animations-in-jupyter
from matplotlib.animation import FuncAnimation
import matplotlib.animation as animation
import itertools # for joining lists inside a list
from itertools import count
# For graphing
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import pandas as pd
from pandas.tseries.offsets import *
# Setting Plot Size
plt.rcParams["figure.figsize"] = (12,7)
# Calling stock values from Yahoo finance API
# Ref. https://www.kaggle.com/code/alessandrozanette/s-p500-analysis-using-yfinance-data
## Calling API from Yahoo Finance: Uncomment the line "!pip install yfinance" if not done
# !pip install yfinance # Uncomment it to run if not installed yet in a different cell separated from this one.
import yfinance as yf
%config InlineBackend.figure_format='retina'
import warnings
warnings.filterwarnings("ignore")
' ######### Calling Yahoo Finance dataset with API ######### '
def Dataset(Ticker_stock, No_Years_Data):
# Parameter Adjustment
# Set Ticker Symbol
# e.g. Ticker="^GSPC" for S&P 500, Ticker="NDAQ" for NASDAQ, Ticker="TOPX" for TOPIX
# Ticker="AMZN" for Amazon.com Inc., "AAPL" for Apple Inc., Ticker="NTDOY" for NINTENDO
Ticker_stock=Ticker_stock # Ticker symbok for a stock
# Set the number of years to download data
No_Years_Data=No_Years_Data;
# Now, let's retrieve the data of the past x years using yfinance
Years="".join([str(No_Years_Data),'y']) # Set the number of years ! Adjust
y_t = yf.Ticker(Ticker_stock).history(period=Years) # Ticker Symbokl ! Adjust !
# Another way to dl data
# y_t = yf.download(Ticker_stock, start="2015-01-01", end="2023-12-31")
# Let's take a look at the called data
#display(y_t.tail())
# Change to list
# https://stackoverflow.com/questions/39597553/from-datetimeindex-to-list-of-times
t_j=y_t.index.tolist()
t_j=[t_j[j].strftime("%Y-%m-%d") for j in range(len(t_j))]
# change from numpy array to list
y_t=y_t["Close"].tolist()
# # Plotting the data y_t with its date t
# for k in range(No_Years_Data*4):
# Window=int(math.floor(len(t)/No_Years_Data)/4)
# St=k*Window; Ed=St+Window; print(St,Ed)
# plt.figure(); plt.plot(t[St:Ed],y_t[St:Ed]); plt.xticks(rotation=90); plt.show()
## Linear Interpolation of data
# https://stackoverflow.com/questions/2315032/how-do-i-find-missing-dates-in-a-list-of-sorted-dates
# https://stackoverflow.com/questions/13019719/get-business-days-between-start-and-end-date-using-pandas
t_raw=t_j.copy()
t_intpl = pd.date_range(start=t_raw[0], end=t_raw[-1], freq=BDay()) # Interpolating only for business days
y_raw=y_t.copy()
y_intpl = np.interp(pd.to_datetime(t_intpl).astype(int), pd.to_datetime(t_raw).astype(int), y_raw)
t_j=t_intpl.copy(); y_t=y_intpl.copy()
t_j=t_j.tolist()
t_j=[t_j[j].strftime("%Y-%m-%d") for j in range(len(t_j))]
t_j=np.array(t_j)
return t_j, y_t
' ######### Calling dataset to analyse ######### '
# Example of Barrick Gold Corp
# Parameter Adjustment
# Set Ticker Symbol
# e.g. Ticker="^GSPC" for S&P 500, Ticker="NDAQ" for NASDAQ, Ticker="^N225" for Nikkei
# Ticker="AMZN" for Amazon.com Inc., "AAPL" for Apple Inc., Ticker="NTDOY" for NINTENDO
Ticker_stock="GOLD" # Ticker symbok for a stock
# Set the number of years to download data
No_Years_Data=5;
# Output
t_j, y_t = Dataset(Ticker_stock, 2)
N=len(y_t); j=list(range(0,N)) # Generating the index label
y_j=y_t.copy() # Copying the main variable with the different subscript name
plt.figure();plt.plot(t_j, y_t, color='lightsteelblue'); plt.show(block=False)
' ######### Defining the function for displaying animation ######### '
def MoveStepByStep(f_j,Title):
N_=len(f_j)
plt.figure()
plt.rcParams["animation.html"] = "jshtml"
plt.rcParams['figure.dpi'] = 150
plt.ioff()
fig, ax = plt.subplots()
count_ = count(); NoFrames=30; StepSize=math.floor(N_/NoFrames);
plt.figure();
def animate(a):
counts=next(count_); Show=counts*StepSize
ax.cla()
ax.set_title(Title);
ax.plot(j[0:Show],f_j[0:Show], label=Title, color='darkseagreen')
ax.plot(p,f_p, "*", color='Magenta', label="Lower Peaks", markersize=9);
ax.set_xlim(0,N_)
ax.set_ylim(min(f_j)*0.9,max(f_j)*1.1)
Animation=animation.FuncAnimation(fig, animate, frames=NoFrames)
display(Animation)
# Separating figures
plt.show(block=False)
' ######### Enveloping the lower peaks with the different interpolation methods ######### '
# Finding the lower peaks
p, _ = find_peaks(-y_j, prominence=1)
t_p=t_j[p]; y_p=y_j[p] # Saving values at the peak points
# Adding the first point
p=np.append(0,p); y_p=np.append(y_j[0],y_p); t_p=np.append(t_j[0],t_p);
plt.figure();
plt.plot(y_j, color='lightsteelblue', label="Original");
plt.plot(p, y_p, "*", color='Magenta', label="Lower Peaks", markersize=9);
plt.legend(loc="best")
plt.show(); plt.show(block=False)
# Adding the variable name for the lower envelope
f_p=y_p.copy()
f_j_list=[] # Storing the interpolation showcase.
Title_list=[] # Storing the interpolation methods' name
# Defininig function to output the lower envelope
def PlotLowerEnvelope(Title,p,y_j,f_j):
plt.figure();
plt.title(Title)
plt.plot(y_j, color='lightsteelblue', label="Original");
plt.plot(p, y_p, "*", color='Magenta', label="Lower Peaks", markersize=9);
plt.plot(f_j, color='darkgreen', label=Title);
plt.legend(loc="best")
plt.show(); plt.show(block=False)
# Linear interpolation
f_j_Linear = np.interp(pd.to_datetime(t_j).astype(int), pd.to_datetime(t_p).astype(int), f_p)
Title='Linear Interpolation'
f_j_list.append(f_j_Linear); Title_list.append(Title)
PlotLowerEnvelope(Title,p,y_j,f_j_Linear)
MoveStepByStep(f_j_Linear,Title)
# Piecewise Cubic Hermite Interpolating Polynomial (PCHIP)
f_j_PCHIP = pchip_interpolate(p, f_p,j)
Title='PCHIP'
f_j_list.append(f_j_PCHIP); Title_list.append(Title)
PlotLowerEnvelope(Title,p,y_j,f_j_PCHIP)
MoveStepByStep(f_j_PCHIP,Title)
# Cubic Spline Interpolation
f_j_CSP = CubicSpline(p, f_p, bc_type='natural'); f_j_CSP=f_j_CSP(j)
Title='Cubic Spline'
f_j_list.append(f_j_CSP); Title_list.append(Title)
PlotLowerEnvelope(Title,p,y_j,f_j_CSP)
MoveStepByStep(f_j_CSP,Title)
# Akima Spline Interpolation
f_j_Akima = Akima1DInterpolator(p, f_p)(j)
Title='Akima Spline'
f_j_list.append(f_j_Akima); Title_list.append(Title)
PlotLowerEnvelope(Title,p,y_j,f_j_Akima)
MoveStepByStep(f_j_Akima,Title)
# Comparing these interpolation methods
N_cut=math.floor(N*0.95)
plt.rcParams["figure.figsize"] = (18,9)
plt.figure();
plt.title("Comparing these interpolation methods")
plt.plot(y_j[0:N_cut], color='lightsteelblue', label="Original");
plt.plot(p, y_p[0:N_cut], "*", color='Magenta', label="Lower Peaks", markersize=9);
for k in range(len(f_j_list)):
plt.plot(f_j_list[k][0:N_cut], "--", label=Title_list[k]);
plt.legend(loc="best")
plt.show(); plt.show(block=False)
# Showing derivatives of these functions
NoDerivs=2
plt.figure()
fig, axs = plt.subplots(len(f_j_list), NoDerivs+1)
plt.tight_layout()
for k in range(len(f_j_list)):
f_j=f_j_list[k];d_f_j=f_j;
for l in range(NoDerivs+1):
SubPlotTitle = "".join([Title_list[k]," Diff(",str(l),")"])
axs[k, l].plot(d_f_j,".",color='darkgreen');
axs[k, l].set_title(label=SubPlotTitle)
d_f_j=np.diff(d_f_j)
plt.show(block=False)