Understanding HSI - III - The Best EWMA Pair

Posted by Chris IO on August 26, 2017

Inspired by Brian, who is specialised in corporate finance, I would like to test the optimal EWMA pairs that generate the best short signal. In general, It is said that momentum is a good type of signals to capture upward movement, while EWMA crossing is the one for downward movement.

Here I would like to test which two EWMA crossings would generate the best performance for Hang Seng Index.

Just to describe the method briefly, when the more sensitive (EWMA of 10 days relative to EWMA of 20 days) EWMA plunges the less sensitive EWMA, we open a short contract, and hold the contract until the more sensitive EWMA breaks through the less sensitive one. The difference in index is our profit and loss.

The test data used here springs from 2009 to 2017. I skipped financial crisis because the big downward movement at that time would create bias for the more sensitive pairs. I want to test out the best pair of EWMA under most of the circumstance, but not assuming we have to undergo another financial crisis. Some of you may disagree with this since including data from 2007 to 2008 seems to make the test more robust. However, robustness comes with tradeoff in performance. I aim to discover an optimised strategy that can be run most of the time, but not a general one that would be run and left alone. This is in fact, more efficient and practical since we apply some judgment or out-of-model analysis on whether or not we should adopt this strategy in the first place, making the model more nitche and fit for the current case. In this case, since we are talking about short-sell, skipping financial crisis would make our signal more profitable under general situations.

I tested the profit of two ranges of EWMA signal, 2-60 days for the more sensitive average and 100 to 250 days for the less sensitive one. I would post the code at the end of this article(Python) so you can test any custom range of signals.

The Most Profitable Pairs

Basically the results echo with our general understanding. For the most profitable(in terms of total profit), the use of 20/103 pair tops the list. Pairs around this range have similar performance so it is pretty stable.

In terms of the most efficient trade, amazingly, the performance of 59/237 and pairs around this range have super high gain per trade, smaller maximum loss and standard deviation.

If you have some interesting trading ideas that you want validation, free feel to contact me through twitter or email.

The Most Efficient Pairs

The Code for the testing:

Download Index_09-17.csv on here

import pandas as pd
import numpy as np

class backend:
   def __init__(self):
       #load data
       self.data = pd.read_csv('Index_09-17.csv',error_bad_lines=False)
       #set index for rolling window later
       self.data.index = pd.to_datetime(self.data.pop('Date'))
       #replace null with previous values
       self.data['Adj Close'].replace(to_replace='null',method='ffill',inplace=True)
       self.data['Open'].replace(to_replace='null',method='ffill',inplace=True)
       # convert to numeric
       self.close = pd.to_numeric(self.data['Adj Close'])
       self.open = pd.to_numeric(self.data['Open'])
       
   #output df of EWMA transformation    
   def EWMA(self,num):
       return pd.DataFrame(data={'{}'.format(num):self.close.ewm(span=num).mean()})
       
   #create signal that compare two EWMA columns
   def signal(self,fast,slow):
       signal = pd.concat([self.EWMA(fast),self.EWMA(slow)],axis=1)
       return signal['{}'.format(fast)]<signal['{}'.format(slow)]
       
   #compute PnL of custom EWMA pairs
   def PnL(self,fast,slow):
       x = self.signal(fast,slow)
       holding = False
       pnL_histroy = []
       entry = 0
       exit = 0
       for i in range(x.shape[0]):
           if x.iloc[i] == True:
               if holding == False:
                   entry = self.open.iloc[i+1]
                   holding = True
               else:
                   pass
           if x.iloc[i] == False:
               if holding == False:
                   pass
               else:
                   exit = self.open.iloc[i+1]
                   pnL_histroy.append(exit-entry)
                   holding = False
       #profit, number of trade, profit per trade,SD, maxprofit, max loss
       return sum(pnL_histroy),len(pnL_histroy),sum(pnL_histroy)/len(pnL_histroy), np.std(pnL_histroy),max(pnL_histroy),min(pnL_histroy)
       
   # make it nicer for one-line output to csv
   def output(self,fastl=2,fastu=100,slowl=101,slowu=250):
       index,profit,number,GPT,SD,MP,ML = [],[],[],[],[],[],[]
       for i in range(fastl,fastu):
           for j in range(slowl,slowu):
               p,num,gpt,sd,mp,ml = self.PnL(i,j)
               index.append((i,j))
               profit.append(p)
               number.append(num)
               GPT.append(gpt)
               SD.append(sd)
               MP.append(mp)
               ML.append(ml)
       df = pd.DataFrame(data={'index':index,'profit':profit,'number of trade':number,
       'Average Gain':GPT,'SD':SD,'Maximum Gain in One Trade':MP,'Maximum Loss in One Trade':ML})
       return df[['index','profit','number of trade','Average Gain','SD','Maximum Gain in One Trade','Maximum Loss in One Trade']]
       
       ###change the output(a,b,c,d) for your own range 
if __name__ == '__main__':
   output = backend().output(2,60,100,250)
   output.to_csv('result.csv',index=False)