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!