Re: Python to MT5
2The Continuation Index
Python: September 2025
Following is an implementation of the continuation index, as introduced John Ehlers’ article in this issue,
“The Continuation Index,” in the Python programming language.
The continuation index is based on Ehlers’ UltimateSmoother function and the Laguerre filter function.
The code given here imports the required Python libraries, imports data from Yahoo Finance,
converts the EasyLanguage code given in Ehlers’ article to Python for the continuation index, and compares parameters.
For TradeStation, please go here:
post1295578265.html#p1295578265
For MT5, please go here:
post1295577320.html#p1295577320
For NinjaTrader, please go here:
post1295577368.html#p1295577368
The chart below shows the continuation index on a daily chart of the emini S&P 500.
-----------------------------------------------------------------------------------------------------
DOWNLOAD:
The Continuation Index.pdf
Python: September 2025
Following is an implementation of the continuation index, as introduced John Ehlers’ article in this issue,
“The Continuation Index,” in the Python programming language.
The continuation index is based on Ehlers’ UltimateSmoother function and the Laguerre filter function.
The code given here imports the required Python libraries, imports data from Yahoo Finance,
converts the EasyLanguage code given in Ehlers’ article to Python for the continuation index, and compares parameters.
For TradeStation, please go here:
post1295578265.html#p1295578265
For MT5, please go here:
post1295577320.html#p1295577320
For NinjaTrader, please go here:
post1295577368.html#p1295577368
Code: Select all
"""
Written By: Rajeev Jain, 2025-07-23
Trader Tips python code for TAS&C Magazine article "The Continuation Index"
"""
# import required python libraries
%matplotlib inline
import pandas as pd
import numpy as np
import math
import datetime as dt
import yfinance as yf
print(yf.__version__)
0.2.58
# Use Yahoo Finance python package to obtain OHLCV data for the SP500 index
symbol = '^GSPC'
ohlcv = yf.download(
symbol,
start="2019-01-01",
end="2025-07-24",
#group_by="Ticker",
auto_adjust=True,
multi_level_index=False,
progress=False,
)
# Python functions to implement UltimateSmoother, Laguerre Filter and
# Continuation Index as defined in John Ehlers’ article.
def ultimate_smoother(price_series, period):
"""
Ultimate Smoother function converted from EasyLanguage to Python.
Parameters:
price_series (list or np.array): Time series of prices (e.g., closing
prices)
period (float): Smoothing period
Returns:
np.array: Smoothed price series
"""
price_series = np.asarray(price_series)
n = len(price_series)
US = np.zeros(n)
# Filter coefficients
Q = math.exp(-1.414 * math.pi / period)
c1 = 2 * Q * math.cos(1.414 * math.pi / period)
c2 = Q * Q
a0 = (1 + c1 + c2) / 4
for i in range(n):
if i < 3:
US[i] = price_series[i]
else:
US[i] = ((1 - a0) * price_series[i] +
(2 * a0 - c1) * price_series[i - 1] +
(c2 - a0) * price_series[i - 2] +
c1 * US[i - 1] -
c2 * US[i - 2])
return US
def laguerre_filter(price_series, gama, order, length):
"""
Laguerre Filter Function converted from EasyLanguage to Python.
Parameters:
price_series (list or np.array): Input price data
gama (float): Laguerre parameter, 0 <= gama < 1
order (int): Filter order (integer <= 10)
length (float): Length for Ultimate Smoother
Returns:
np.array: Laguerre filtered series
"""
assert 0 <= gama < 1, "gama must be in [0, 1)"
assert order <= 10 and order >= 1, "order must be integer between 1 and 10"
price_series = np.asarray(price_series)
n = len(price_series)
output = np.zeros(n)
# Initialize Laguerre matrix: shape (order+1, 2)
LG = np.zeros((order + 1, 2))
# Precompute ultimate smoother once
smoothed_price = ultimate_smoother(price_series, length)
for t in range(n):
# Shift previous values: LG[:, 2] = LG[:, 1]
LG[:, 1] = LG[:, 0]
# Update LG[1] using the smoothed price at current time
LG[1, 0] = smoothed_price[t]
# Compute rest of the Laguerre components recursively
for count in range(2, order + 1):
LG[count, 0] = (
-gama * LG[count - 1, 1] +
LG[count - 1, 1] +
gama * LG[count, 1]
)
# Sum current values of LG[1] to LG[order]
FIR = np.sum(LG[1:order + 1, 0])
output[t] = FIR / order
return output
def continuation_index(close_prices, gama=0.8, order=8, length=40):
"""
Continuation Index by John F. Ehlers (converted from EasyLanguage).
Parameters:
close_prices (list or np.array): Series of closing prices
gama (float): Laguerre gamma (0 <= gama < 1)
order (int): Order of Laguerre filter (<= 10)
length (int): Base smoothing period
Returns:
np.array: Continuation Index values
"""
close_prices = np.asarray(close_prices)
n = len(close_prices)
# Step 1: Ultimate Smoother with Length / 2
US = ultimate_smoother(close_prices, length / 2)
# Step 2: Laguerre Filter
LG = laguerre_filter(close_prices, gama, order, length)
# Step 3: Variance = avg(abs(US - LG)) over Length
diff = np.abs(US - LG)
variance = np.convolve(diff, np.ones(length)/length, mode='same')
# Step 4: Normalized difference, scaled by 2
with np.errstate(divide='ignore', invalid='ignore'):
ref = np.where(variance != 0, 2 * (US - LG) / variance, 0)
# Step 5: Inverse Fisher Transform compression
CI = (np.exp(2 * ref) - 1) / (np.exp(2 * ref) + 1)
return CI
# Compare Laguerre filter response for gamma setting of 0.4 vs gamma setting of 0.8
gama1 = 0.4
gama2 = 0.8
order = 8
length = 40
df = ohlcv.copy()
df['Laguerre_0.4'] = laguerre_filter(df['Close'], gama1, order, length)
df['Laguerre_0.8'] = laguerre_filter(df['Close'], gama2, order, length)
cols = ['Close', 'Laguerre_0.4', 'Laguerre_0.8']
ax = df[-255:][cols].plot(marker='.', grid=True, figsize=(9,6), title=f'Ticker={symbol}, Laguerre Filder Response, Gamma Parameter Comparison')
ax.set_xlabel('')
plt.show()
# Compare Laguerre filter response for order of 4 vs order of 8
order1 = 4
order2 = 8
length = 40
gama = 0.8
df = ohlcv.copy()
df['Laguerre_4th'] = laguerre_filter(df['Close'], gama, order1, length)
df['Laguerre_8th'] = laguerre_filter(df['Close'], gama, order2, length)
df
cols = ['Close', 'Laguerre_4th', 'Laguerre_8th']
ax = df[-255:][cols].plot(marker='.', grid=True, figsize=(9,6), title=f'Ticker={symbol}, Laguerre Filder Response, Order Parameter Comparison')
ax.set_xlabel('')
plt.show()
# Example of continuation index using default setting for
# daily close of S&P 500 index
def plot_ci(df):
ax = df[['Close', 'Laguerre']].plot(
marker='.',
grid=True,
figsize=(9,4),
title=f'Ticker={symbol}'
)
ax.set_xlabel('')
ax = df[['CI']].plot(
marker='.',
grid=True,
figsize=(9,2),
title=f'Continuation Index'
)
ax.set_xlabel('')
gama=0.8
order=8
length=40
df = ohlcv.copy()
df['Laguerre'] = laguerre_filter(df['Close'], gama, order, length)
df['CI'] = continuation_index(df['Close'], gama, order, length)
plot_ci(df[-252:])
DOWNLOAD:
The Continuation Index.pdf
Re: Python to MT5
3The Reversion Index
Python: January 2026
Following is Python code to implement concepts described in John Ehlers’ article in this issue, “The Reversion Index.”
The top plot in the picture shows an example of the SuperSmoother and its signals superimposed on price; the red and green vertical shading highlights when buy and sell signals are active. The bottom chart plots the reversion index along with its smoothed version.
------------------------------------------------------------------------------------------------
DOWNLOAD:
ReversionIndex.pdf
Python: January 2026
Following is Python code to implement concepts described in John Ehlers’ article in this issue, “The Reversion Index.”
Code: Select all
"""
Written By: Rajeev Jain, jainraje@yahoo.com
Python code to implement concepts in Technical Analysis of S&C Magazine
January 2026 article "The Reversion Index" by John F Ehlers. This python
code is provided for TraderTips section of the magazine.
2025-11-12 Initial implementation
2025-11-14 Uploaded to github
https://github.com/jainraje/TraderTipArticles/
"""
# Import required python libraries
%matplotlib inline
import pandas as pd
import numpy as np
import yfinance as yf
import math
import datetime as dt
import matplotlib.pyplot as plt
# Use Yahoo Finance python package to obtain OHLCV data for desired instrument.
symbol = '^GSPC'
start = "2023-11-13"
end = dt.datetime.now().strftime('%Y-%m-%d')
end = '2025-11-15'
ohlcv = yf.download(
symbol,
start,
end,
group_by="Ticker",
auto_adjust=True
)
ohlcv = ohlcv[symbol]
ohlcv
# three functions to implement concepts: 1) super_smoother 2) reversion_index
# and 3) plot_reversion_index to plot relevant signals
def super_smoother(price: pd.Series, period: float) -> pd.Series:
"""
Vectorized Ehlers SuperSmoother filter (© John F. Ehlers)
Parameters
----------
price : pd.Series
Input price series (e.g., closing prices)
period : float
Smoothing period
Returns
-------
pd.Series
Smoothed price series
"""
q = np.exp(-1.414 * np.pi / period)
c1 = 2 * q * np.cos(np.radians(1.414 * 180 / period))
c2 = q * q
a0 = (1 - c1 + c2) / 2
price_vals = price.to_numpy()
out_values = np.zeros_like(price_vals)
# Initialize first four values to the original price
out_values[:4] = price_vals[:4]
# Apply recursive filter from index 4 onward
for i in range(4, len(price_vals)):
out_values[i] = a0 * (price_vals[i] + price_vals[i-1]) + c1 * out_values[i-1] - c2 * out_values[i-2]
return pd.Series(out_values, index=price.index)
def reversion_index(close: pd.Series, length: int = 20) -> pd.DataFrame:
"""
Ehlers Reversion Index (© 2025 John F. Ehlers)
Correct and fully vectorized Python version.
"""
close = close.astype(float)
# Delta: change from previous bar
delta = close.diff().fillna(0)
# Rolling sums of delta and absolute delta
delta_sum = delta.rolling(window=length, min_periods=1).sum()
abs_delta_sum = delta.abs().rolling(window=length, min_periods=1).sum()
# Ratio: safely avoid division by zero
ratio = delta_sum / abs_delta_sum.replace(0, np.nan)
ratio = ratio.fillna(0)
# Smooth and trigger lines
smooth = super_smoother(ratio, period=8)
trigger = super_smoother(ratio, period=4)
return pd.DataFrame({
'ReversionSmooth': smooth,
'ReversionTrigger': trigger
}, index=close.index)
def plot_reversion_index(df):
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 9), sharex=True, gridspec_kw={'height_ratios':[2,2]})
# ----- Top: Price & SuperSmoother -----
ax1.plot(df.index, df['Close'], label='Close', linewidth=1)
ax1.plot(df.index, df['SuperSmoother'], label='SuperSmoother', linewidth=2, alpha=0.6, color='orange')
# Shading for bullish/bearish signals
ax1.fill_between(df.index, df['Close'].min(), df['Close'].max(),
where=df['Signal'] == 1, color='green', alpha=0.1)
ax1.fill_between(df.index, df['Close'].min(), df['Close'].max(),
where=df['Signal'] == -1, color='red', alpha=0.1)
ax1.set_title('Price & SuperSmoother with Signal')
ax1.grid(True)
ax1.legend(loc='center left', bbox_to_anchor=(1, 0.5))
# ----- Bottom: ReversionSmooth & ReversionTrigger -----
ax2.plot(df.index, df['ReversionSmooth'], label='ReversionSmooth', linewidth=1, color='red')
ax2.plot(df.index, df['ReversionTrigger'], label='ReversionTrigger', linewidth=1, color='darkblue')
ax2.axhline(0, color='gray', linestyle='--', linewidth=1)
# Overlay Signal as step plot
#ax2.step(df.index, df['Signal'], where='mid', label='Signal', color='purple', linewidth=1.5, alpha=0.5)
ax2.set_title('Reversion Index with Signal')
ax2.grid(True)
ax2.legend(loc='center left', bbox_to_anchor=(1, 0.5))
# ----- Improve date formatting on the bottom subplot -----
ax2.xaxis.set_major_locator(mdates.AutoDateLocator())
ax2.xaxis.set_major_formatter(mdates.ConciseDateFormatter(mdates.AutoDateLocator()))
plt.setp(ax2.get_xticklabels(), rotation=0, ha='center') # horizontal, centered
#plt.xlabel('Date')
plt.tight_layout()
plt.show()
# Call functions to run required calculations. Set buy when ReversionTrigger
# swings above ReversionSmooth and sell for when ReversionTrigger swings below.
# Different period and lengths can be tested to determine optimum settings.
# Top plot close vs the SuperSmoother and red and white background shading to
# show highlight when buy and sell signals are active. Using slicing to plot
# last 1 year (aka 252 trading days).
df = ohlcv.copy()
df['SuperSmoother'] = super_smoother(df['Close'], period=10)
df = df.join(reversion_index(df['Close'], length=20))
df['Signal'] = np.where(df['ReversionTrigger'] > df['ReversionSmooth'], 1, -1)
plot_reversion_index(df[-252:])
------------------------------------------------------------------------------------------------
DOWNLOAD:
ReversionIndex.pdf
Re: Python to MT5
4One Euro filter
For Python, please go here:
post1295578271.html#p1295578271
For TradeStation, please go here:
post1295578272.html#p1295578272
For NinjaTrader, please go here:
post1295577367.html#p1295577367
Python: December 2025
In his article in this issue, “The One Euro Filter,” John Ehlers discusses the one euro filter, which was originally developed and written about by Georges Casiez, Nicolas Roussel, and Daniel Vogel in 2012. In his article in this issue, Ehlers demonstrates how to plot this smoothing filter, discusses its effectiveness, and also discusses how this smoothing filter can be converted into an oscillator
Following is a Python code implementation based on the EasyLanguage code provided in Ehlers’ article for the one euro filter and oscillator.
For Python, please go here:
post1295578271.html#p1295578271
For TradeStation, please go here:
post1295578272.html#p1295578272
For NinjaTrader, please go here:
post1295577367.html#p1295577367
Python: December 2025
In his article in this issue, “The One Euro Filter,” John Ehlers discusses the one euro filter, which was originally developed and written about by Georges Casiez, Nicolas Roussel, and Daniel Vogel in 2012. In his article in this issue, Ehlers demonstrates how to plot this smoothing filter, discusses its effectiveness, and also discusses how this smoothing filter can be converted into an oscillator
Following is a Python code implementation based on the EasyLanguage code provided in Ehlers’ article for the one euro filter and oscillator.
Code: Select all
"""
# import required python libraries
import pandas as pd
import numpy as np
import yfinance as yf
import math
import datetime as dt
import matplotlib.pyplot as plt
import mplfinance as mpf
print(yf.__version__)
# three helper functions to 1) run one euro filter calculations 2) run
# oscillator calculations and to 3) plot the close vs one euro filter output
# on one subplot and the oscillator on a second subplot using built-in
# matplotlib capabilities.
def calc_one_euro_filter(close, period_min=10, beta=0.2):
"""
One Euro Filter Indicator
Adapted from Georges Casiez et al. (CHI 2012)
Converted from EasyLanguage to Python
Args:
close (array-like): List or NumPy array of closing prices
period_min (float): Minimum cutoff period (default=10)
beta (float): Responsiveness factor (default=0.2)
Returns:
np.ndarray: Smoothed signal
"""
close = np.asarray(close)
n = len(close)
# Initialize arrays
smoothed = np.zeros(n)
smoothed_dx = np.zeros(n)
period_dx = 10.0
alpha_dx = 2 * np.pi / (4 * np.pi + period_dx)
# Initialization
smoothed[0] = close[0]
smoothed_dx[0] = 0.0
for i in range(1, n):
# Delta smoothing (EMA of price change)
smoothed_dx[i] = alpha_dx * (close[i] - close[i - 1]) + (1 - alpha_dx) * smoothed_dx[i - 1]
# Adjust cutoff based on rate of change
cutoff = period_min + beta * abs(smoothed_dx[i])
# Compute adaptive alpha
alpha3 = 2 * np.pi / (4 * np.pi + cutoff)
# Adaptive smoothing
smoothed[i] = alpha3 * close[i] + (1 - alpha3) * smoothed[i - 1]
return smoothed
def calc_highpass(price, period):
"""
Two-pole high-pass filter (John Ehlers style)
Args:
price (array-like): Sequence of prices
period (float): Filter period
Returns:
np.ndarray: High-pass filtered values
"""
price = np.asarray(price, dtype=float)
n = len(price)
out = np.zeros(n)
# Filter coefficients
a1 = np.exp(-1.414 * np.pi / period)
b1 = 2 * a1 * np.cos(math.radians(1.414 * 180 / period))
c2 = b1
c3 = -a1 ** 2
c1 = (1 + c2 - c3)/4
# Main filter loop
for i in range(2, n):
out[i] = (
c1 * (price[i] - 2 * price[i - 1] + price[i - 2])
+ c2 * out[i - 1]
+ c3 * out[i - 2]
)
# Initialize first values (pass-through)
out[0:2] = price[0:2]
return out
def plot_one_euro_filter(df):
ax = df[['Close', 'OEF']].plot(grid=True, title=f"Ticker={symbol}, Close vs One Euro Filter", figsize=(9,4), marker='.')
ax.set_xlabel('')
ax = df[['Osc']].plot(grid=True, title=f"One Euro Filter Oscilator", figsize=(9,2))
ax.set_xlabel('')
# Use Yahoo Finance python package to obtain OHLCV data for desired symbol
symbol = '^GSPC'
start = "1990-01-01"
end = dt.datetime.now().strftime('%Y-%m-%d')
ohlcv = yf.download(
symbol,
start,
end,
group_by="Ticker",
auto_adjust=False
)
ohlcv = ohlcv[symbol]
# call helper functions to calculate the One Euro Filter output (named OEF) and the
# Oscillaor output (named Osc) and then plot close, OEF and Ocs values.
df['OEF'] = calc_one_euro_filter(df['Close'], period_min=10, beta=0.2)
df['Osc'] = calc_highpass(df['Close'], period=54)
plot_one_euro_filter(df['2025':])
Re: Python to MT5
5Cybernetic Oscillator
Python: June 2025
The Python code presented here is based on John Ehlers’ article in this issue, “Making A Better Oscillator,” which introduces his new cybernetic oscillator.
The routines given here perform tasks related to implementing the cybernetic oscillator using the Python language. This includes: importing the required python libraries; obtaining OHLCV data by using the Yahoo Finance Python package; using panda’s built-in plotting function to display a price chart; building blocks to build the cybernetic oscillator indicator; using MatPlotLib to plot close, CO1 and CO2 values; and finally, a callable routine to implement an example trading strategy based on rate-of-change, as described in Ehlers’ article.
An example chart plotting both a long-term and short-term cybernetic oscillator is shown in Figure 8. A chart illustrating the creation of an example rate-of-change strategy is shown in Figure 9. FIGURE 8: PYTHON. John Ehlers’ cybernetic oscillator is demonstrated here on a daily chart of the S&P 500. A short and longer-trend version are shown. FIGURE 9: PYTHON. An example trading strategy incorporating the cybernetic oscillator is created for demonstration purposes. The strategy is a simple dual ROC strategy. The indicators are visualized using the MatplotLib routine. Here, LP, BP1, and BP2 can be observed along with price.
Python: June 2025
The Python code presented here is based on John Ehlers’ article in this issue, “Making A Better Oscillator,” which introduces his new cybernetic oscillator.
The routines given here perform tasks related to implementing the cybernetic oscillator using the Python language. This includes: importing the required python libraries; obtaining OHLCV data by using the Yahoo Finance Python package; using panda’s built-in plotting function to display a price chart; building blocks to build the cybernetic oscillator indicator; using MatPlotLib to plot close, CO1 and CO2 values; and finally, a callable routine to implement an example trading strategy based on rate-of-change, as described in Ehlers’ article.
An example chart plotting both a long-term and short-term cybernetic oscillator is shown in Figure 8. A chart illustrating the creation of an example rate-of-change strategy is shown in Figure 9. FIGURE 8: PYTHON. John Ehlers’ cybernetic oscillator is demonstrated here on a daily chart of the S&P 500. A short and longer-trend version are shown. FIGURE 9: PYTHON. An example trading strategy incorporating the cybernetic oscillator is created for demonstration purposes. The strategy is a simple dual ROC strategy. The indicators are visualized using the MatplotLib routine. Here, LP, BP1, and BP2 can be observed along with price.
Code: Select all
#
# import required python libraries
#
%matplotlib inline
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
import math
import datetime as dt
print(yf.__version__)
#
# Use Yahoo Finance python package to obtain OHLCV data
#
symbol = '^GSPC'
symbol = 'SPY'
ohlcv = yf.download(symbol, start="1995-01-01", end="2025-04-18", group_by="Ticker", auto_adjust=True)
ohlcv = ohlcv[symbol]
#
# Use pandas built in plot function to see simple price chart
#
ax = ohlcv['Close'].plot(
figsize=(9,6),
grid=True,
title=f'{symbol}',
#marker='.'
)
#
# Building block / routines used to implement cybernetic oscillaor indicator
#
def calc_highpass(price, period):
a1 = np.exp(-1.414 * np.pi / period)
b1 = 2 * a1 * np.cos(math.radians(1.414 * 180 / period))
c2 = b1
c3 = -a1 * a1
c1 = (1 + c2 - c3)/4
out_values = []
for i in range(len(price)):
if i >= 4:
out_values.append(
c1*(price[i] - 2*price[i-1] + price[i-2]) + c2*out_values[i-1] + c3*out_values[i-2]
)
else:
out_values.append(price[i])
return out_values
def calc_super_smoother(price, period):
a1 = np.exp(-1.414 * np.pi / period)
b1 = 2 * a1 * np.cos(math.radians(1.414 * 180 / period))
c2 = b1
c3 = -a1 * a1
c1 = (1 - c2 - c3)
out_values = []
for i in range(len(price)):
if i >= 4:
out_values.append(c1*(price[i]+price[i-1])/2 + c2*out_values[i-1] + c3*out_values[i-2])
else:
out_values.append(price[i])
return out_values
def calc_rms(price):
length = len(price)
sum_sq = 0
for count in range(length):
sum_sq += price[count] * price[count]
return np.sqrt(sum_sq / length)
def calc_cybernetic_oscillator(close, params=(30, 20)):
hp_length = params[0]
lp_length = params[1]
df = pd.DataFrame(index=close.index, data=close)
df['HP'] = calc_highpass(close, hp_length)
df['LP'] = calc_super_smoother(df['HP'], lp_length)
df['RMS'] = df['LP'].rolling(100).apply(calc_rms)
df['CO'] = df['LP']/df['RMS']
return df['CO']
#
# Exampe python code to create two versions of the cybernetic oscillator
# as presented in June 2005 Trader Tip article
#
lp_length = 20
hp1_length = 30
hp2_length = 250
co1_params=(hp1_length, lp_length)
co2_params=(hp2_length, lp_length)
df = ohlcv.copy()
df['CO1'] = calc_cybernetic_oscillator(ohlcv['Close'], params=co1_params)
df['CO2'] = calc_cybernetic_oscillator(ohlcv['Close'], params=co2_params)
df
#
# MatPlotLib routine to plot Close, CO1 and CO2 values
#
def plot_indicators1(df):
# Create a figure with three subplots stacked vertically
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(9, 6), sharex=True)
# Plotting the first subplot (e.g., Price Data)
ax1.set_title(f"S&P 500")
ax1.plot(df.index, df['Close'], label='Close', color='blue', marker='.')
ax1.grid(True, linestyle='--', alpha=0.5)
ax1.set_ylabel('Price Plot')
ax1.legend(loc='upper left', bbox_to_anchor=(1, 1))
# Plotting the second subplot
ax2.set_title(f'Cybernetic Oscillator {co1_params}')
ax2.plot(df.index, df['CO1'], label='CO1', color='red')
ax2.axhline(y=0, color='black', linestyle='-', label='zero')
#ax2.set_ylabel('Linear Slope')
ax2.grid(True, linestyle='-', alpha=0.5)
ax2.legend(loc='upper left', bbox_to_anchor=(1, 1))
# Plotting the third subplot
ax3.set_title(f'Cybernetic Oscillator {co2_params}')
ax3.plot(df.index, df['CO2'], label='CO2', color='blue')
ax3.axhline(y=0, color='black', linestyle='-', label='zero')
ax3.grid(True, linestyle='-', alpha=0.5)
ax3.legend(loc='upper left', bbox_to_anchor=(1, 1))
# Rotate x-axis labels for better readability
plt.xticks(rotation=45)
# Improve overall layout
plt.tight_layout()
# Show the plot
plt.show()
plot_indicators1(df['2024-03':'2025'])
#
# Example python code to implement the simple dual ROC strategy showcased
# in trader tip article the python code is bundled in a *strategy* routine
# so it can easily be called, paramater as passed into the routine as a variable
#
def strategy(ohlcv, params):
df = ohlcv.copy()
df['LP'] = calc_super_smoother(df['Close'], params[0])
df['BP1'] = calc_highpass(df['LP'],params[1] )
df['ROC1'] = df['BP1'] - df['BP1'].shift(2)
df['BP2'] = calc_highpass(df['LP'],params[2])
df['ROC2'] = df['BP2'] - df['BP2'].shift(2)
df['Signal'] = np.where((df['ROC1'] > 0 ) & (df['ROC2'] >0 ), 1, np.nan)
df['Signal'] = np.where((df['ROC1'] < 0 ) & (df['ROC2'] <0 ), 0, df['Signal'])
df['Signal'] = df['Signal'].fillna(method='ffill')
return df
params=(20, 55, 156)
data = strategy(ohlcv, params)
data
#
# The Simple Dual ROC strategy indicators are visualized in the MatplotLib
# routine below. Here LP, BP1 and BP2 can be see observed along with price
#
def plot_indicators2(df):
# Create a figure with three subplots stacked vertically
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(9, 6), sharex=True)
# Plotting the first subplot (e.g., Price Data)
ax1.set_title(f"Ticker={symbol}")
ax1.plot(df.index, df['Close'], label='Close', color='blue',)
ax1.plot(df.index, df['LP'], label='LP', color='orange', )
ax1.grid(True, linestyle='--', alpha=0.5)
ax1.set_ylabel('Price Plot')
ax1.legend(loc='upper left', bbox_to_anchor=(1, 1))
# Plotting the second subplot
ax2.set_title(f'BP1')
ax2.plot(df.index, df['BP1'], label='BP1', color='red')
ax2.axhline(y=0, color='black', linestyle='--', label='zero')
#ax2.set_ylabel('Linear Slope')
ax2.grid(True, linestyle='-', alpha=0.5)
ax2.legend(loc='upper left', bbox_to_anchor=(1, 1))
# Plotting the third subplot
ax3.set_title(f'BP2')
ax3.plot(df.index, df['BP2'], label='BP2', color='blue')
ax3.axhline(y=0, color='black', linestyle='--', label='zero')
ax3.grid(True, linestyle='-', alpha=0.5)
ax3.legend(loc='upper left', bbox_to_anchor=(1, 1))
# Rotate x-axis labels for better readability
plt.xticks(rotation=45)
# Improve overall layout
plt.tight_layout()
# Show the plot
plt.show()
plot_indicators2(data['2024-03':'2025'])
#
# A simple backtest framework/routines are provided which allow changing
# parameters and visualize performance results. Performance can be compared
# against Buy&Hold of same ticker by setting. All buy and sell occur on the
# day of the buy/sell signal using market close values
#
def backtest(data):
df = data.copy()
df['Return Ratio'] = np.where(df['Signal'].shift()==1, 1+df['Close'].pct_change(),1 )
df['Strategy'] = df['Return Ratio'].cumprod()
df['BH'] = (1+df['Close'].pct_change()).cumprod()
df['Peak'] = df['Strategy'].cummax()
df['% DD'] = 100*(df['Strategy']/df['Peak']-1)
df['Peak'] = df['BH'].cummax()
df['BH % DD'] = 100*(df['BH']/df['Peak']-1)
df.at[df.index[0], 'Strategy'] = 1
df.at[df.index[0], 'BH'] = 1
return df
def plot_backtest_results(df, strategy_name="Simple Dual ROC Strategy", compare_bh_ena=False):
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True)
ax1.set_title(f"{strategy_name}, Ticker={symbol},")
ax1.plot(df.index, df['Strategy'], label='Strategy Equity Curve', color='blue',)
if compare_bh_ena:
ax1.plot(df.index, df['BH'], label='Buy & Hold Equity Curve', color='orange',)
ax1.grid(True, linestyle='--', alpha=0.5)
ax1.set_ylabel('Cumulative Return')
ax1.legend(loc='upper left', bbox_to_anchor=(1, 1))
ax2.set_title(f"% Drawdown")
ax2.plot(df.index, df['% DD'], label='Strategy Drawdown', color='blue',)
if compare_bh_ena:
ax2.plot(df.index, df['BH % DD'], label='Buy & Hold Drawdown', color='orange',)
ax2.grid(True, linestyle='--', alpha=0.5)
ax2.set_ylabel('% drawdown')
ax2.legend(loc='upper left', bbox_to_anchor=(1, 1))
#
# Putting the building blocks toegther, parameters can be changed and results viewed visually
# Here using default values presenyed in article
#
params=(20, 55, 156)
data = strategy(ohlcv, params)
df = backtest(data['2009':'2024'])
plot_backtest_results(df, strategy_name="Simple Dual ROC Strategy", compare_bh_ena=False)
#
# Here change parameter and see results compared to buy & hold of same ticker
#
params=(50, 55, 156)
data = strategy(ohlcv, params)
df = backtest(data['2009':'2024'])
plot_backtest_results(df, compare_bh_ena=True)