Using average trend index to assist MACD strategy

Keywords: Blockchain less

Preface

"Trend is your friend" is a proverb familiar to every trader. But friends who have done deals may realize that trends always start without warning and end abruptly. So in CTA strategy, how to catch the trend and filter the volatile market is the tireless pursuit of many subjective and quantitative traders. In this course, we will use the average trend index (ADX) as a filter to analyze the application in its quantitative trading.

What is the average trend index

ADX(average directional indicator) is a technical tool to measure the trend. It was put forward by wells Wilder in 1978. Unlike other technical analysis tools, ADX can not judge the multi empty direction, nor prompt the precise trading point. It just measures the strength of the current trend.

The default period parameter of ADX is 14, which is usually displayed in the sub graph of K-line graph. Its value is between 0 and 100. The larger the value is, the stronger the upward or downward trend is. Generally, when the value of ADX is greater than 40, the stronger the trend is, and then the trend trading has the greatest return potential. When the value of ADX is less than 20, the weaker the trend is, and the trader is warned not to use the trend tracking trading strategy.

Calculation method of ADX

The calculation method of ADX is relatively complex, which involves many intermediate variables such as: price moving distance in the positive direction (+ DM), price moving distance in the negative direction (- DM), true volatility (TR), positive directivity index (+ DI), negative directivity index (- DI), etc

Calculation trend change

  • up: today's top price - yesterday's top price
  • down: yesterday's lowest price - today's lowest price
  • +DM: if up is greater than max(down, 0), then + DM is equal to up, otherwise it is equal to zero
  • -DM: if down is greater than max(up, 0), then - DM is equal to down, otherwise it is equal to zero

Calculate true amplitude

  • TR: Max (the difference between today's highest price and today's lowest price, the absolute value of the difference between today's highest price and yesterday's closing price, and the absolute value of the difference between today's lowest price and yesterday's closing price)

Calculate trend index

  • +DI(14): +DM(14)/TR(14)*100
  • -DI(14): -DM(14)/TR(14)*100

Calculate ADX

  • DX: ((+DI14)- (-DI14)/(+DI14)+(-DI14))*100
  • ADX: MA(DX,14)

Although the calculation of ADX is relatively complex, its logic is relatively clear: up and down represent the positive and negative moving distance of price respectively; + DI and - DI represent the rising and falling trend after correction with volatility respectively. No matter whether the trend is up or down, as long as there is an obvious trend market, there will always be one of + DI and - DI, so the value of DX will be between 0 and 100 with the strength of the trend; finally, ADX is the 14 balance average of DX.

When + DI is higher than - DI, it indicates that the price is in an upward trend. On the contrary, when - DI is higher than + DI, the price is in a downward trend. Traders can determine the strength of an uptrend or downtrend by examining the ADX value at the same point in time.

Policy logic

In the previous sections, we used MACD index to create a simple strategy. Although the strategy can perform well in the trend market, it can not make ends meet in the volatile market, and even in the long-term volatile market, the fund withdrawal is relatively large. So we will add the previous MACD strategy to the ADX filter in this section. Let's see how it works?

Original strategy logic

  • Multi head opening: DIF greater than zero axis
  • Short opening: DIF less than zero axis
  • Long position closing: DIF breaks down DEA
  • Short position closing: DIF breakthrough DEA

Improved strategy logic

  • Multi head opening: DIF is greater than zero axis, and ADX is greater than 20
  • Short opening: DIF is less than zero axis, and ADX is greater than 20
  • Long position close out: DIF breaks down DEA, or ADX drops
  • Short position closing: DIF breaks DEA upward, or ADX drops

On the basis of the original strategy logic, we add ADX filter to open and close positions respectively to control the number of open positions in the period of market shocks. The value of ADX must be greater than the specified value when the position is opened; once the ADX drops after the position is opened, the position will be closed out. The whole strategy logic is designed as a strict in and out mode, so as to control the retreat range in the period of shock.

Strategy compilation

Original strategy

# Back test configuration
'''backtest
start: 2015-02-22 00:00:00
end: 2019-10-17 00:00:00
period: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
'''


mp = 0  # Define a global variable to control the virtual position


# Judge whether the array rises
def is_up(arr):
    arr_len = len(arr)
    if arr[arr_len - 1] > arr[arr_len - 2] and arr[arr_len - 2] > arr[arr_len - 3]:
        return True     

    
# Judge whether the array drops
def is_down(arr):
    arr_len = len(arr)
    if arr[arr_len - 1] < arr[arr_len - 2] and arr[arr_len - 2] < arr[arr_len - 3]:
        return True

    
# Judge whether two arrays are golden forks
def is_up_cross(arr1, arr2):
    if arr1[len(arr1) - 2] < arr2[len(arr2) - 2] and arr1[len(arr1) - 1] > arr2[len(arr2) - 1]:
        return True

# Judge whether two arrays are dead cross
def is_down_cross(arr1, arr2):
    if arr1[len(arr1) - 2] > arr2[len(arr2) - 2] and arr1[len(arr1) - 1] < arr2[len(arr2) - 1]:
        return True    

    
# Program main function
def onTick():
    exchange.SetContractType("rb000")  # Subscription futures
    bar_arr = exchange.GetRecords()  # Get K-line array
    if len(bar_arr) < long + m + 1:  # If the length of K-line array is too small, MACD cannot be calculated, so directly return to skip
        return
    all_macd = TA.MACD(bar_arr, short, long, m)  # Calculate MACD value, return a two-dimensional array
    dif = all_macd[0]  # Get the value of DIF and return an array
    dif.pop()  # Delete last element of DIF array
    dea = all_macd[1]  # Get the value of DEA and return an array
    dea.pop()  # Delete last element of DEA array
    last_close = bar_arr[len(bar_arr) - 1]['Close']  # Get the latest price (selling price) for opening and closing positions
    global mp  # Global variable to control virtual position
    
    # Open multiple
    if mp == 0 and dif[len(dif) - 1] > 0:
        exchange.SetDirection("buy")  # Set transaction direction and type
        exchange.Buy(last_close, 1)  # Open multiple
        mp = 1  # Set the value of virtual position, i.e. how many orders there are
    
    # Empty list
    if mp == 0 and dif[len(dif) - 1] < 0:
        exchange.SetDirection("sell")  # Set transaction direction and type
        exchange.Sell(last_close - 1, 1)  # Empty list
        mp = -1  # Set the value of virtual position, i.e. free order
        
    # Ping duo Shan
    if mp == 1 and is_down_cross(dif, dea):
        exchange.SetDirection("closebuy")  # Set transaction direction and type
        exchange.Sell(last_close - 1, 1)  # Ping duo Shan
        mp = 0  # Set the value of virtual position, i.e. empty position
    
    # Flat bill
    if mp == -1 and is_up_cross(dif, dea):
        exchange.SetDirection("closesell")  # Set transaction direction and type
        exchange.Buy(last_close, 1)  # Flat bill
        mp = 0  # Set the value of virtual position, i.e. empty position

        
def main():
    while True:
        onTick()
        Sleep(1000)

According to the strategy logic changed above, we can add ADX filter directly on the basis of the original strategy. Although the calculation method of ADX is relatively complex, we can calculate the value of ADX with the help of the talib library with only a few lines of code. Because calculating ADX requires talib, and calculating talib library requires numpy.array data type, we need to import talib library and numpy library at the beginning of the code.

import talib
import numpy as np

When using the talib library to calculate ADX, a total of four parameters are needed: the highest price, the lowest price, the closing price, and the cycle parameters. So we also need to write a get u data function. The purpose of this function is to extract the highest price, lowest price and closing price from the K-line array.

# Convert the K-line array to the highest price, lowest price and closing price array, which is used to convert data of numpy.array type
def get_data(bars):
    arr = [[], [], []]
    for i in bars:
        arr[0].append(i['High'])
        arr[1].append(i['Low'])
        arr[2].append(i['Close'])
    return arr

Then we use the numpy library to convert ordinary arrays into numpy.array type data. Finally, we can use the talib library to calculate the value of ADX. For the specific writing method, see the comments in the following code:

np_arr = np.array(get_data(bar_arr)) # Convert the list to data of type numpy.array to calculate the value of ADX
adx = talib.ADX(np_arr[0], np_arr[1], np_arr[2], 20);  # Calculate the value of ADX

In the strategy logic, we need to judge whether the size of ADX is up or down. It's very simple to judge the size, just extract the ADX value of a specific day. Just like judging MACD, we only take the ADX value of the penultimate K-line; but when judging the rise and fall, we only need to take the ADX value of the penultimate K-line and the third K-line.

adx1 = adx_arr[len(adx_arr) - 2]  # ADX value of the penultimate K line
adx2 = adx_arr[len(adx_arr) - 3]  # ADX value of the third K line from the bottom

Finally, modify the order logic:

# Open multiple
if mp == 0 and dif[len(dif) - 1] > 0 and adx1 > 40:
    exchange.SetDirection("buy")  # Set transaction direction and type
    exchange.Buy(last_close, 1)  # Open multiple
    mp = 1  # Set the value of virtual position, i.e. how many orders there are

# Empty list
if mp == 0 and dif[len(dif) - 1] < 0 and adx1 > 40:
    exchange.SetDirection("sell")  # Set transaction direction and type
    exchange.Sell(last_close - 1, 1)  # Empty list
    mp = -1  # Set the value of virtual position, i.e. free order
    
# Ping duo Shan
if mp == 1 and (is_down_cross(dif, dea) or adx1 < adx2):
    exchange.SetDirection("closebuy")  # Set transaction direction and type
    exchange.Sell(last_close - 1, 1)  # Ping duo Shan
    mp = 0  # Set the value of virtual position, i.e. empty position

# Flat bill
if mp == -1 and (is_up_cross(dif, dea) or adx1 < adx2):
    exchange.SetDirection("closesell")  # Set transaction direction and type
    exchange.Buy(last_close, 1)  # Flat bill
    mp = 0  # Set the value of virtual position, i.e. empty position

Full policy code

# Back test configuration
'''backtest
start: 2015-02-22 00:00:00
end: 2019-10-17 00:00:00
period: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
'''

# Import library
import talib
import numpy as np


mp = 0  # Define a global variable to control the virtual position


# Convert the K-line array to the highest price, lowest price and closing price array, which is used to convert data of numpy.array type
def get_data(bars):
    arr = [[], [], []]
    for i in bars:
        arr[0].append(i['High'])
        arr[1].append(i['Low'])
        arr[2].append(i['Close'])
    return arr


# Judge whether two arrays are golden forks
def is_up_cross(arr1, arr2):
    if arr1[len(arr1) - 2] < arr2[len(arr2) - 2] and arr1[len(arr1) - 1] > arr2[len(arr2) - 1]:
        return True

# Judge whether two arrays are dead cross
def is_down_cross(arr1, arr2):
    if arr1[len(arr1) - 2] > arr2[len(arr2) - 2] and arr1[len(arr1) - 1] < arr2[len(arr2) - 1]:
        return True    

    
# Program main function
def onTick():
    exchange.SetContractType("rb000")  # Subscription futures
    bar_arr = exchange.GetRecords()  # Get K-line array
    if len(bar_arr) < long + m + 1:  # If the length of K-line array is too small, MACD cannot be calculated, so directly return to skip
        return
    all_macd = TA.MACD(bar_arr, short, long, m)  # Calculate MACD value, return a two-dimensional array
    dif = all_macd[0]  # Get the value of DIF and return an array
    dif.pop()  # Delete last element of DIF array
    dea = all_macd[1]  # Get the value of DEA and return an array
    dea.pop()  # Delete last element of DEA array
    
    np_arr = np.array(get_data(bar_arr)) # Convert the list to data of type numpy.array to calculate the value of ADX
    adx_arr = talib.ADX(np_arr[0], np_arr[1], np_arr[2], 20);  # Calculate the value of ADX
    adx1 = adx_arr[len(adx_arr) - 2]  # ADX value of the penultimate K line
    adx2 = adx_arr[len(adx_arr) - 3]  # ADX value of the third K line from the bottom
    
    last_close = bar_arr[len(bar_arr) - 1]['Close']  # Get the latest price (selling price) for opening and closing positions
    global mp  # Global variable to control virtual position
    
    # Open multiple
    if mp == 0 and dif[len(dif) - 1] > 0 and adx1 > 40:
        exchange.SetDirection("buy")  # Set transaction direction and type
        exchange.Buy(last_close, 1)  # Open multiple
        mp = 1  # Set the value of virtual position, i.e. how many orders there are
    
    # Empty list
    if mp == 0 and dif[len(dif) - 1] < 0 and adx1 > 40:
        exchange.SetDirection("sell")  # Set transaction direction and type
        exchange.Sell(last_close - 1, 1)  # Empty list
        mp = -1  # Set the value of virtual position, i.e. free order
        
    # Ping duo Shan
    if mp == 1 and (is_down_cross(dif, dea) or adx1 < adx2):
        exchange.SetDirection("closebuy")  # Set transaction direction and type
        exchange.Sell(last_close - 1, 1)  # Ping duo Shan
        mp = 0  # Set the value of virtual position, i.e. empty position
    
    # Flat bill
    if mp == -1 and (is_up_cross(dif, dea) or adx1 < adx2):
        exchange.SetDirection("closesell")  # Set transaction direction and type
        exchange.Buy(last_close, 1)  # Flat bill
        mp = 0  # Set the value of virtual position, i.e. empty position

        
def main():
    while True:
        onTick()
        Sleep(1000)

Post the complete source code of the improved strategy in this section, or you can go to https://www.fmz.com/strategy/174672 The inventor's strategy of quantifying the official website is to copy and download, without the need to configure direct online back testing.

Ending

Before a popular saying: standing in the tuyere, pigs can fly, so can we do business. In front of the big trend, no matter how stupid the strategy can be, so what we have to do is to grasp the big trend and control the retreat during the period of turbulence. Using ADX in combination with MACD can help traders to identify differences and improve the accuracy of transactions. You can reduce the risk in the volatile market and increase your profit potential in the trend market, so as to minimize the risk and maximize the profit. Bottom line: if you want to make a lot of money, you must not be against the trend!

Posted by donbueck on Wed, 26 Feb 2020 00:03:40 -0800