Monday, January 22, 2018

BackTest on Dual Thrust

Dual Thrust is basically one of the first investment strategies that I have developed and tested.

It was developed by Michael Chalek in 1980s, and it was one of the most profitable strategies at that time.

Its general idea is to capture an upward or downward momentum signaled by a suddenly increased or decreased price.

This strategy is determined by two lines: one buy-line, the other one sell-line. If the close price / current price is higher than the buy-line, then buy one; else if lower than sell-line, then sell one.

Buy/Sell-line is based on the volatility of current price. Specifically, it is the open price of a certain date plus or minus the parameter 'k' times an another parameter 'range'. Range is composed of max(Highest_High - Lowest_Close, Highest_Close - Lowest_Low), which can be shown more clearly in the followings.

In expression:

  • HH = highest highest price in n days 
  • HC = highest close price in n days
  • LC = lowest close price in n days
  • LL = lowest lowest price in n days
  • range = max(HH-LC, HC-LL)
  • buyline = open + k1*range
  • sellline = open - k2*range

Here we assume that
  • Parameter date is n
  • k1 = k2 = K

The performance of this strategy is doubted. But, why still using such an old, out of date strategy?

For one, I am a Chinese market potential investor. Though it has been generally accepted that US markets are already weakly efficient, Chinese financial markets are still far from that.

For another, Dual Thrust is easy to understand and simple to test.

The financial market I will be testing is Shanghai Futures Exchange, China, and the financial instrument is Futures contracts of Aluminum.

All the data were selected from CSMAR on a daily basis (国泰安金融数据库).


The result is:


Continue with sensitive analysis: change parameters K and date, and we have:
Sharpe       K = 0.5     K = 0.7
date = 8       1.36          1.40
date = 12     1.63         -0.09
date = 16     1.60          1.45
To conclude, we have:

Advantages:
  • Easy to understand and implement

Disadvantages:
  • Static strategies, needs dynamical parameter adaptation
  • Parameters like K and date are sensitive to changes
  • No underlying theory about the authenticity of each parameter

Potential improvements:
  • Smaller trends (e.g. 5-minute Candlestick chart) may stabilize profits in a shorter-run
  • Adjust parameters based on volatility

Conclusion:
  • Static traditional Dual Thrust is valid but not very strong. Its performance is sensitive to many factors, and to improve the performance many improvements need to be done.

Source Code File: To be updated
Raw Data: To be updated

# -*- coding: utf-8 -*-
"""
Created on Thu Jan 22 11:56:49 2018
Copyright belongs to the author only.@author: CPZ
"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Read Files
dr = pd.read_csv('Al_2000_2017_daily.csv',encoding = "ISO-8859-1")
dt = pd.to_datetime(dr['trading_day'])

# Select Main contracts
# only invest in main contracts
filters = [False for i in range(len(dr))]
for i in range(len(dr)):
    if dt[i].month <= 10:
        if int(dr['contract_id'][i][4:]) == dt[i].month+2:
            filters[i] = True
    elif dt[i].month == 11:
        if int(dr['contract_id'][i][4:]) == 1 and int(dr['contract_id'][i][2:4]) == dt[i].year - 2000 +1:
            filters[i] = True
    elif dt[i].month == 12:
        if int(dr['contract_id'][i][4:]) == 2 and int(dr['contract_id'][i][2:4]) == dt[i].year - 2000 +1:
            filters[i] = True
df = dr[filters]

# Main Body
# Some one also set k1 and k2 to be 0.7
k1 = k2 = 0.5
# Date can also be set as other numbers
date = 12
range0 = [np.max([ np.max(df.high[i:i+date]) - np.min(df.close[i:i+date]),
                  np.max(df.close[i:i+date]) - np.min(df.low[i:i+date])]) for i in range(len(df)-date)]
df2 = df[date:]
df2.insert(df2.shape[1],'range',range0)
df2.insert(df2.shape[1],'buyline',df2.open + k1*df2.range)
df2.insert(df2.shape[1],'sellline',df2.open - k2*df2.range)
df3 = df2[['buyline','close','sellline','trading_day']]

# Performance
# test1 shows signals triggered in the past
# account reflects profits upon daily basis
cash = 0
account = [0]
flag = 0
test1 = []
for i in range(len(df3)-1):
    if df3['close'].iloc[i] > df3['buyline'].iloc[i]:
        flag = 1
    elif df3['close'].iloc[i] < df3['sellline'].iloc[i]:
        flag = -1
    test1.append([flag,df3['close'].iloc[i],df3['buyline'].iloc[i],df3['sellline'].iloc[i],df3['trading_day'].iloc[i]])
    cash += flag * (df3['close'].iloc[i+1] - df3['close'].iloc[i])
    account.append(cash)
test1 = pd.DataFrame(test1,columns=['flag','last_price','buyline','sellline','trading_day'])
sharpe = np.mean(account)/np.std(account)

dt2 = pd.to_datetime(df3['trading_day'])
plt.plot(dt2,account)
plt.xlabel('Year')
plt.ylabel('profits')
plt.title('Al, K='+str(k1)+', date='+str(date)+', sharpe='+str(round(float(sharpe),2)))
plt.savefig('Al_k'+str(k1)+'_date'+str(date)+'_CSMAR_profits_0122.png',dpi=750)
plt.show()

plt.plot(dt2,df3.buyline,label='buyline',lw=.5)
plt.plot(dt2,df3.close,label='close',lw=.5)
plt.plot(dt2,df3.sellline,label='sellline',lw=.5)
plt.xlabel('Year')
plt.ylabel('price')
plt.legend(loc='upper right')
plt.title('Al, K='+str(k1)+', date='+str(date)+' CSMAR')
plt.savefig('Al_k'+str(k1)+'_date'+str(date)+'_CSMAR_prices_0122.png',dpi=750)
plt.show()

* Assuming that non-risk interest rate is 0 in Sharpe calculation.

No comments:

Post a Comment