Quantify Turtle Trading Rules with Python!

Keywords: Python less

1

Introduction

For the directional strategy of pure bull or short, only when the stock price is a mean regression or trend, can the trading strategy be profitable. Otherwise, if the price is random walk, the transaction will be unprofitable (Fama efficient market hypothesis). In other words, most of the so-called complex quantification strategies can be attributed to mean regression or trend tracking strategies. Trend tracking strategy considers that prices will continue along a certain trend, also known as "inertia" or "momentum" strategy, many technical indicators are based on the idea of momentum. Today, I will introduce the famous trend trading strategy, the "Turtle Trading Rule", focusing on how to use Python to quantify the turtle trading rules, especially the comprehensive application of Pandas. Detailed introduction of Turtle Principle and related anecdotes are interesting to read the original book and related information on the Internet. Reply to "Turtle Trading" in the background of Wechat Public Number can download "Turtle Trading Rules" HD Chinese PDF.

2

Brief Introduction to Turtle Trading Rules

Python Resource Sharing Group: 626017123

Turtle trading law can be considered as a complete trading system with all the components of a complete trading system, including market, entry, position size, stop/stop, exit, trading strategy, etc.
Strategy: How to buy and sell?
Trend Tracking: Tang Qian'an Passage 
Turtle trading rules use the breakthrough point of Tang Qian'an channel as a trading signal to guide trading. Simply speaking, Tang Qian'an channel is composed of an upper rail line, a middle rail line and a lower rail line. The upper rail line is composed of the highest price in N1 days, and the lower rail line is calculated by the lowest price in N2 days. When the price breaks through the upper rail, it is a possible buy signal, otherwise, it breaks through. It's a possible sell signal when you go off the rails.  
Turtle trading system is essentially a trend-following system, but the most worth learning is the management of funds, especially the part of batch warehousing and dynamic stopping. The book mentions the N-value warehouse management method, in which the N-value is similar to the ATR calculation of the average true amplitude of technical indicators. ATR is the 20-day average of true amplitude TR, and TR is the maximum of the difference between the highest price and the lowest price on the current trading day, the difference between the closing price on the previous trading day and the highest price on the current trading day, and the difference between the closing price on the previous trading day and the lowest price on the current trading day.
TR=Max(High_Low,abs(High_PreClose),abs(PreClose_Low), technical indicators library TA-Lib provides a direct calculation function of ATR.
 
The original Turtle trade used Tang Qian'an channel to track the trend. It performed well in the more obvious trend, but it did not work well in the volatile market. Of course, this is the common fault of all trend-based strategies. The following focuses on the use of Python to visualize the Tang Qi'an channel, and the use of a simplified version of the Turtle Trading Law for a simple historical test.

2

3

Python Implementation of Turtle Trading Rules

#Introduce package s that may be used later.
import pandas as pd  
import numpy as np
import talib as ta
from datetime import datetime,timedelta
import matplotlib.pyplot as plt
%matplotlib inline   
#Chinese and minus sign in normal drawing
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False
#Using tushare to obtain transaction data
#Setting token
import tushare as ts 
#Note that token was replaced by what you got on tushare
token='Enter your token'
pro=ts.pro_api(token)
index={'Shanghai Composite Index': '000001.SH',
        'Shenzhen Syndrome Cheng Zhi': '399001.SZ',
        'Shanghai and Shenzhen 300': '000300.SH',
        'GEM Index': '399006.SZ',
        'Shanghai 50': '000016.SH',
        'CS 500': '000905.SH',
        'Medium and small plate finger': '399005.SZ',
        'Shanghai 180': '000010.SH'}
#Get the stock code and name of the current transaction
def get_code():
    df = pro.stock_basic(exchange='', list_status='L')
    codes=df.ts_code.values
    names=df.name.values
    stock=dict(zip(names,codes))
    #Merge indices and stocks into a dictionary
    stocks=dict(stock,**index)
    return stocks    
#Get market data
def get_daily_data(stock,start,end):
    #If the code is in the dictionary index, the index data is taken
    code=get_code()[stock]
    if code in index.values():
        df=pro.index_daily(ts_code=code,start_date=start, end_date=end)
    #Otherwise, it's a stock data.
    else:
        df=pro.daily(ts_code=code, adj='qfq',start_date=start, end_date=end)
    #Set transaction date to index value
    df.index=pd.to_datetime(df.trade_date)
    df=df.sort_index()
    #Calculating Return Rate
    df['ret']=df.close/df.close.shift(1)-1
    return df

Taking the Shanghai-Shenzhen 300 Index as an example, this paper visualizes the Tang Qian'an Channel and the breakthrough signal.

hs=get_daily_data('Shanghai and Shenzhen 300','20180101','')[['close','open','high','low','vol']]
#The highest price in the latest N1 trading day
hs['up']=ta.MAX(hs.high,timeperiod=20).shift(1)
#The lowest price in the last two trading days
hs['down']=ta.MIN(hs.low,timeperiod=10).shift(1)
#Real daily fluctuation range
hs['ATR']=ta.ATR(hs.high,hs.low,hs.close,timeperiod=20)
hs.tail()

 

 

 


 

 

Here we use a simplified version of the Turtle Trading Rule for historical retest, i.e. without considering warehouse management and dynamic stops./Stop earnings conditions, Tang Qian'an channel breakthrough as a buy and sell signal.
//The trading rules are as follows:
(1)When today's closing price is higher than the highest price in the past 20 trading days, buy at the closing price.  
(2)After buying, when the closing price is less than the lowest price in the past 10 trading days, sell at the closing price.
def my_strategy(data):
    x1=data.close>data.up
    x2=data.close.shift(1)<data.up.shift(1)
    x=x1&x2
    y1=data.close<data.down
    y2=data.close.shift(1)>data.down.shift(1)
    y=y1&y2
    data.loc[x,'signal']='buy'
    data.loc[y,'signal']='sell'
    buy_date=(data[data.signal=='buy'].index).strftime('%Y%m%d')
    sell_date=(data[data.signal=='sell'].index).strftime('%Y%m%d')
    buy_close=data[data.signal=='buy'].close.round(2).tolist()
    sell_close=data[data.signal=='sell'].close.round(2).tolist()
    return (buy_date,buy_close,sell_date,sell_close)
#Visualization of K-line and Tang Qian'an Channel
from pyecharts import *
grid = Grid()
attr=[str(t) for t in hs.index.strftime('%Y%m%d')]
v1=np.array(hs.loc[:,['open','close','low','high']])
v2=np.array(hs.up)
v3=np.array(hs.down)
kline = Kline("Shanghai-Shenzhen 300 Tang Qian Passage",title_text_size=15)
kline.add("K Line graph", attr, v1.round(1),is_datazoom_show=True,)
# volume
bar = Bar()
bar.add("volume", attr, hs['vol'],tooltip_tragger="axis", is_legend_show=False, 
        is_yaxis_show=False, yaxis_max=5*max(hs["vol"]))
line = Line()
line.add("Upper track", attr, v2.round(1),is_datazoom_show=True,
         is_smooth=True,is_symbol_show=False,line_width=1.5)
line.add("Lower track", attr, v3.round(1),is_datazoom_show=True,
         is_smooth=True,is_symbol_show=False,line_width=1.5)
#Adding Trading Signals
bd,bc,sd,sc=my_strategy(hs)
es = EffectScatter("buy")
es.add( "sell", sd, sc, )
es.add("buy", bd, bc,symbol="triangle",)
overlap = Overlap(width=2000, height=600)
overlap.add(kline)
overlap.add(line)
overlap.add(bar,yaxis_index=1, is_add_yaxis=True)
overlap.add(es)
grid.add(overlap, grid_right="10%")
grid

 

 

 


 

 

(Note: Running the above code yields a dynamic interaction graph with adjustable time intervals)

#Turn off pandas warnings
pd.options.mode.chained_assignment = None
def strategy(stock,start,end,N1=20,N2=10):
    df=get_daily_data(stock,start,end)
    #The highest price in the latest N1 trading day
    df['H_N1']=ta.MAX(df.high,timeperiod=N1)
    #The lowest price in the last two trading days
    df['L_N2']=ta.MIN(df.low,timeperiod=N2)
    #The closing price of the day was set to 1 when the closing price was higher than the peak of the latest N1 trading day yesterday.
    buy_index=df[df.close>df['H_N1'].shift(1)].index
    df.loc[buy_index,'Closing Signal']=1
    #Set the closing signal to 0 when the closing price of the day is below the lowest level of the last N2 trading days yesterday
    sell_index=df[df.close<df['L_N2'].shift(1)].index
    df.loc[sell_index,'Closing Signal']=0
    df['Day position']=df['Closing Signal'].shift(1)
    df['Day position'].fillna(method='ffill',inplace=True)
    d=df[df['Day position']==1].index[0]-timedelta(days=1)
    df1=df.loc[d:].copy()
    df1['ret'][0]=0
    df1['Day position'][0]=0
    #When the position is 1, buy the position, when the position is 0, empty the position, calculate the net value of funds.
    df1['Net Policy Value']=(df1.ret.values*df1['Day position'].values+1.0).cumprod()
    df1['Net Exponential Value']=(df1.ret.values+1.0).cumprod()
    df1['Strategic Return Rate']=df1['Net Policy Value']/df1['Net Policy Value'].shift(1)-1
    df1['Index Return Rate']=df1.ret
    total_ret=df1[['Net Policy Value','Net Exponential Value']].iloc[-1]-1
    annual_ret=pow(1+total_ret,250/len(df1))-1
    dd=(df1[['Net Policy Value','Net Exponential Value']].cummax()-df1[['Net Policy Value','Net Exponential Value']])/df1[['Net Policy Value','Net Exponential Value']].cummax()
    d=dd.max()
    beta=df1[['Strategic Return Rate','Index Return Rate']].cov().iat[0,1]/df1['Index Return Rate'].var()
    alpha=(annual_ret['Net Policy Value']-annual_ret['Net Exponential Value']*beta)
    exReturn=df1['Strategic Return Rate']-0.03/250
    sharper_atio=np.sqrt(len(exReturn))*exReturn.mean()/exReturn.std()
    TA1=round(total_ret['Net Policy Value']*100,2)
    TA2=round(total_ret['Net Exponential Value']*100,2)
    AR1=round(annual_ret['Net Policy Value']*100,2)
    AR2=round(annual_ret['Net Exponential Value']*100,2)
    MD1=round(d['Net Policy Value']*100,2)
    MD2=round(d['Net Exponential Value']*100,2)
    S=round(sharper_atio,2)
    df1[['Net Policy Value','Net Exponential Value']].plot(figsize=(15,7))
    plt.title('A Simple Retrospection of Turtle Trading Strategies',size=15)
    bbox = dict(boxstyle="round", fc="w", ec="0.5", alpha=0.9)
    plt.text(df1.index[int(len(df1)/5)], df1['Net Exponential Value'].max()/1.5, f'Accumulated rate of return:\
//Policy {TA1}%, index {TA2}%; \ Annual return: strategy {AR1}%, index {AR2}%;  n maximum withdrawal: strategy {MD1}%, index {MD2}%; n\
//Strategy alpha: {round(alpha,2)}, strategy beta: {round (beta, 2)};  n Sharp ratio: {S}',size=13,bbox=bbox)  
    plt.xlabel('')
    ax=plt.gca()
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    plt.show()
    #Return df1.loc [: ['close','ret','H_N1','L_N2','day's position','net strategy','net index']]

The following is a simple retest of Shanghai Composite Index, Shanghai and Shenzhen 300, GEM Index, Ping An of China, Dongfang Communications and Maotai of Guizhou Province to see how effective Tang Qi'an's trading rules are, and the specific indicators are shown in the chart.

strategy('Shanghai Composite Index','20050101','')

 

 

 


 

 

strategy('Shanghai and Shenzhen 300','','')

 

 

 


 

 

strategy('GEM Index','','')

 

 

 


 

 

strategy('Shanghai and Shenzhen 300','20180101','')

 

 

 


 

 

strategy('China's Peace and Security','20050101','',N1=20,N2=10)

 

 

 


 

 

strategy('Oriental Communications','20130101','',N1=20,N2=10)

 

 

 


 

 

strategy('Maotai, Guizhou','20050101','',N1=20,N2=10)

 

 

 


 

 

The above-mentioned retest does not consider the use of N-value storage management and dynamic stop loss. The following is a retest with the addition of storage management on the Wankuang mining platform. Compared with the retest framework using Pandas simply above (graphics are ugly), the retest indicators in Maotai, Guizhou Province, seem to be more ideal, and the maximum withdrawal is only 21%. Specific implementation code can refer to the sharing on WanmingPlatform Community. In addition, the quantitative platforms such as Jukuan and Youkuang also provide corresponding template for strategy back-testing, so that the codes are much the same and the interested ones can be further understood.

 

 


 

 

4

epilogue

 

 


 

 

This paper briefly introduces the basic principle of turtle trading rules, uses Python to visualize its trading signals, and uses Pandas to test the relevant indexes and individual shares using the simplified version of turtle trading rules. It can be seen from the test results that the simplified trend tracking strategy performs well for some targets in some intervals, but not for some targets or for some periods. Of course, the purpose of this paper is to review the classical strategies and show the comprehensive application of Pandas in financial quantitative analysis, so as to play a valuable role in the application of Python in financial quantitative analysis without making any stock selection or strategy recommendation. It is worth noting that any strategy has certain limitations, especially the number of traders who know and use the strategy is much less effective than the concept just appeared. As with technical analysis indicators, they are very effective when they first appear, but when they are well known or applied, their natural utility is greatly reduced (compared with Alpha in the multi-factor model, which has gradually become a risk factor after being mined by everyone). However, the so-called new ideas and strategies must stand on the shoulders of the predecessors, so we can not totally deny the poor results of the classical strategies. How to improve, refine and upgrade them to make them more suitable for the current market is the problem we are facing.  

Posted by welsh_sponger on Fri, 23 Aug 2019 23:45:47 -0700