The creator of the oscillator had the goal of detecting outstretched momentum movements across different lookback periods. This is in turn lessens the excess volatility caused by the lower lookback periods. To construct the Ultimate Oscillator, we will follow these steps before presenting them in more detail right below:
- Select the lookback periods of the three moving averages we will use. The default periods set by the creator of the Oscillator are 7, 14, and 28. As a fan of the Fibonacci sequence, I will use 5, 13, and 21.
- Calculate the Buying Pressure for each period.
- Calculate the True Range for each period.
- Divide the Buying Pressure by the True Range for each period.
- Calculate the exponential moving average of the previous result three times using the three different periods.
- Calculate a weighted average of the three exponential moving averages and multiply by 100.
Let us get started firs with the definition of Buyin Pressure. It is a simple formula as shown below:
Which can be written down in Python language as:
# Buying pressure
for i in range(len(Data)):Data[i, where] = Data[i, close] - min(Data[i, low], Data[i - 1, close])
Next step is to calculate the True Range, a measure for historical volatility. It uses the below formula:
Which can be written down in Python language as:
# True range
for i in range(len(Data)):Data[i, where + 1] = max(Data[i, high], Data[i - 1, close]) - min(Data[i, low], Data[i - 1, close])
if Data[i, where + 1] == 0:
Data[i, where + 1] = 0.01
The condition in the last lines of the above code snippet is because in the next step we will divide the Buying Pressure by the True Range and in case True Range equals zero, we will get an undefined value and will ruin the next calculations. The condition is simply a fix saying that if we get a zero value, we will consider it to be 0.01.
Now, we can proceed with the division using the below code:
# BP / TR
Data[:, where + 2] = Data[:, where] / Data[:, where + 1]
The moving averages used will be of the exponential type. In the default version of the Ultimate Oscillator, simple moving averages are used but since I prefer tweaks, I will use exponential moving averages. Here is what we will do:
- Calculate the 5-period exponential moving average of BP/TR.
- Calculate the 13-period exponential moving average of BP/TR.
- Calculate the 21-period exponential moving average of BP/TR.
Which can be written down in Python language as:
# A5
Data = ema(Data, 2, 5, where + 2, where + 3)# A13
Data = ema(Data, 2, 13, where + 2, where + 4)
# A21
Data = ema(Data, 2, 21, where + 2, where + 5)
Remember to have an OHLC data array with a few columns to spare. You can use the adder function below to add as many columns as you need.
# Adding a few columns using the adder function
def adder(Data, times):for i in range(1, times + 1):
z = np.zeros((len(Data), 1), dtype = float)
return Data# Using the adder function
Data = np.append(Data, z, axis = 1)
my_data = adder(my_data, 20)
And finally, we are ready to apply the below formula to calculate the Ultimate Oscillator:
Giving us the below full Python code for the Ultimate Oscillator:
def ultimate_oscillator(Data, high, low, close, where): # Buying pressure
for i in range(len(Data)):Data[i, where] = Data[i, close] - min(Data[i, low], Data[i - 1, close])
# True range
for i in range(len(Data)):Data[i, where + 1] = max(Data[i, high], Data[i - 1, close]) - min(Data[i, low], Data[i - 1, close])
if Data[i, where + 1] == 0:
Data[i, where + 1] = 0.01# BP / TR
Data[:, where + 2] = Data[:, where] / Data[:, where + 1]# A5
Data = ema(Data, 2, 5, where + 2, where + 3)# A13
Data = ema(Data, 2, 13, where + 2, where + 4)# A21
Data = ema(Data, 2, 21, where + 2, where + 5)# Ultimate Oscillator
for i in range(len(Data)):Data[i, where + 6] = (Data[i, where + 3] * 4) + (Data[i, where + 4] * 2) + (Data[i, where + 5])
Data[i, where + 6] = (Data[i, where + 6] / 7) * 100Data = deleter(Data, where, 6)
return Data
The deleter function seen in the above code can be defined as follows:
def deleter(Data, index, times):for i in range(1, times + 1):
Data = np.delete(Data, index, axis = 1)
return Data