Category Archives: Trading Strategies

Parameter Optimization for Strategy 2

Now, let’s try some parameter optimisation for the SMA strategy! There probably are functions out there on R which I can use to do this, but I figured it would take me as long to actually code it as it would to find something usable on the internet, and I enjoy coding much more than looking stuff up on the internet.

My aim is to find out which SMA is the best to use for going long, and which SMA is the best to use for going short on the S&P 500. Ideally, I should optimize the short SMA for each long SMA (or vice-versa) to find the best combination, but I don’t think optimizing them independently (as I did here) would make much of a difference in this case. This is the code I wrote:

optimizeSMA=function(mainVector,returnsVector,smaInit=3,smaEnd=200,long=TRUE){

   bestSMA=0
   bestSharpe=0

   for( i in smaInit:smaEnd){
      smaVec=SMA(mainVector,i)
      if(long==T){

	binVec=lag(ifelse(mainVector>smaVec,1,0),1)
	binVec[is.na(binVec)]=0
	stratRets=binVec*returnsVector
	sharpe=SharpeRatio.annualized(stratRets, scale=252)
	if(sharpe>bestSharpe){
	  bestSMA=i
	  bestSharpe=sharpe
	}

      }else{

	binVec=lag(ifelse(mainVector<smaVec,-1,0),1)
	binVec[is.na(binVec)]=0
	stratRets=binVec*returnsVector
	sharpe=SharpeRatio.annualized(stratRets, scale=252)
	if(sharpe>bestSharpe){
	  bestSMA=i
	  bestSharpe=sharpe
	}
      }
   }

   print(cbind(bestSMA, bestSharpe))
}

Created by Pretty R at inside-R.org

It is pretty straight-forward and self-explanatory. It initiates a loop which goes through each SMA from smaInit to smaEnd and stores the one with the highest Sharpe ratio. For more complicated strategies, we will need to do a little bit more heavy-lifting when it comes to parameter optimization. This code maximizes the Sharpe ratio, but you can easily modify it to maximize returns, minimize volatility, etc. The highest Sharpe ratio SMA to use for the long position is the 70-Day SMA and for the short position is the 84-day SMA.

After running the strategy with the optimized parameters, these are the performance results:

Optimized:

Cumulative Return: 0.31104898   Annual Return: 0.04286661

Annualized Sharpe Ratio: 0.18405777   Win %: 0.53078556

Annualized Volatility: 0.23289757    Maximum Drawdown: -0.28943309

Max Length Drawdown: 1078.00000000

Not a huge difference from what we had before. And the little bit of performance improvement that we achieved is probably more a result of curve-fitting than anything else. If your initial parameter values conform with some market intuition -and thus capture most of the obtainable market return- parameter optimization will not be that helpful in a paper-trading implementation of your strategy, as the improvements will mostly be due to curve-fitting the historical data.

Advertisements

Leave a comment

Filed under Finance, Trading Strategies

Strategy 2: Riding the SMA Curve

This is the least complicated trend strategy in existance. You buy and hold the security as long as the security price is above a XXX-Day Simple Moving Average (SMA), and you can short it if it is below the SMA curve. The important question with this strategy is what the length in days of the SMA should be. We can run a test of different SMAs to see which one is most profitable/ least risky, and then choose accordingly. The intuition in choosing this should be based on an understanding of how long price trends usually last for a given security. This will obviously vary for different markets, different securities and across time periods.

A more technical approach to estimating the optimal variant of the SMA to use could be derived from one of the many parameter optimisation techniques avaiable through R, or through coding it yourself. These run the risk of curve-fitting, but as long as you are aware of the dangers associated with that, this could be one thing for you to try. I’ve found through experience that the 200-Day SMA works best for the S&P 500 as a whole, so I will be running this backtest using that.

Let’s say that if the market closes above its 200-Day Daily High SMA on any given day, we go long the next day and if it closes below it 200-Day Daily Low, we go short.

1-Get the data:
getSymbols(‘SPY’)

2-Calculate the 200-Day SMAs:
smaHi200=SMA(Hi(SPY),200)
smaLo200=SMA(Lo(SPY),200)

3-Calculate the lagged trading signal vector:
binVec=lag(ifelse(Cl(SPY)>smaHi200,1,0)+ifelse(Cl(SPY)<smaLo200,-1,0),1)

4-Get rid of the NAs:
binVec[is.na(binVec)]=0

5-Calculate returns vector and multiply out the trading vector with the returns vector to get the strategy return:
rets=diff(log(Ad(SPY)))
stratRets=binVec*rets

6-Run performance analytics:
charts.PerformanceSummary(cbind(rets,stratRets))
Performance(stratRets)

sma200ls

Note: The Performance function I got from somewhere on the internet. I can’t remember where exactly so unfortunately I can’t directly credit the source. Regardless, I’m thankful to whoever wrote it. Here is the code:

Performance <- function(x) {

	cumRetx = Return.cumulative(x)
	annRetx = Return.annualized(x, scale=252)
	sharpex = SharpeRatio.annualized(x, scale=252)
	winpctx = length(x[x > 0])/length(x[x != 0])
	annSDx = sd.annualized(x, scale=252)

	DDs <- findDrawdowns(x)
	maxDDx = min(DDs$return)
	maxLx = max(DDs$length)

	Perf = c(cumRetx, annRetx, sharpex, winpctx, annSDx, maxDDx, maxLx)
	names(Perf) = c("Cumulative Return", "Annual Return","Annualized Sharpe Ratio",
		"Win %", "Annualized Volatility", "Maximum Drawdown", "Max Length Drawdown")
	return(Perf)
}

Created by Pretty R at inside-R.org

We get the following results (our sample period is 2007-01-01 to 2013-06-19):

Market
Cumulative Return: 0.10875991   Annual Return: 0.01613934

Annualized Sharpe Ratio: 0.06700363   Win %: 0.55157505

Annualized Volatility: 0.24087258   Maximum Drawdown: -0.59577736

Max Length Drawdown: 1411.00000000

Strategy
Cumulative Return: 0.30075987  Annual Return: 0.04159395

Annualized Sharpe Ratio: 0.17975121 Win %: 0.53215078   

Annualized Volatility: 0.23139732   Maximum Drawdown: -0.35921405  

Max Length Drawdown: 1078.00000000 

So this strategy performed remarkably well compared to the market. 1-0 for Trend strategies! Looking at the graphs it’s easy to see the points where the strategy mirrors the market returns (and hence is short) and where the strategy follows the market returns (and hence is long). So does that mean we can start trading using this algorithm and make money? Not really- one important point to keep in mind is that backtesting can only be used to reject strategies, not to accept them. It is possible that this strategy can make money going forward, but who really know what the market will do? At least we can’t outright reject this strategy as useless.

What we are essentially saying is that under the conditions we used, our strategy was profitable. If those conditions were to continue, or repeat themselves, than we would have a profitable strategy in our hands. Are those conditions likely to repeat themselves? If market reactions to different stimuli are consistent across time, and if those stimuli re-occur then yes, perhaps. There’s also something to be said here about interactions between the different reactions or stimuli and on their subsequent effects on market outcomes. Is this something we can test for? Maybe, but its not something I’ll be getting into for now.

Leave a comment

Filed under Finance, Trading Strategies

Strategy 1 Extended (Part 2)

We can extend our strategy and make it more profitable by incorporating short selling. Our annualized volatility will go up, but it will be interesting to see what happens to the annualized return. This is a very simple modification to make.

1-First we create a short selling vector:

shortVec=ifelse(((Cl(SPY)<Op(SPY))),-1,0) * ifelse(lag(Cl(SPY),1)<lag(Op(SPY),1),-1,0) * ifelse(lag(Cl(SPY),2)<lag(Op(SPY),2),-1,0) #This is saying that if the stock closes down for three consecutive days, short it.

2-As before, we lag it and get rid of the NAs:

shortVec=lag(binVec3Day,1)
shortVec[is.na(binVec3Day)]=0

3-Now we add the short-signal vector to the lagged and NA-removed long-signal vector we had before:
longShortVec=binVec3Day+shortVec

4-And as before, multiply the trading vector with the S&P return vector to get daily strategy returns, then run performance analytics.

Image

So with this modification, the annualized volatility rises to 9.40% approximately, and the annualized return falls to -7.10%. Not too good.

The strategy above, and all the subsequent modifications, were momentum based strategies. They rely on large, directed, short-term price movements to be profitable and don’t do too well when the price movements are small and directionless, but the market itself is following an overall trend. The strategies which do well in a trending market are called (!!!) trend strategies. We will look at one in the next post.

Another very important thing I ignored in computing the returns is adjustments for splits and dividends. This can be done using the adjusted price information provided for most equities, and that is what I will be using to calculate returns from now on. By using the adjusted price information however, we are not able to simulate when exactly we enter and exit the market (Open, Close), and that is a tradeoff we’ll have to make for greater convenience. However, I will still be using opening and closing price information to compute the trading signals.

Leave a comment

Filed under Finance, Trading Strategies

Strategy 1 Extended (Part 1)

Like I said in my previous post, there are two ways I could think of, off the top of my head, to implement a 2-day or 5-day extension to the previous strategy. One way would be just a simple extension of the approach above, using multiple conditions in the if-else statement with lagged vectors. This is how I will solve it here.

The second method, and one that I would be more comfortable with, is just coding it using a for loop and if statements. Its pretty straightforward if you know even the very basics of coding, and if you do, you can probably figure it out on your own.

I tried stringing together a bunch of ‘and’ conditions but it didn’t seem to work. This is what I had typed in:
binVec3Day=ifelse(((Cl(SPY)>Op(SPY)) && (lag(Cl(SPY),1)>lag(Cl(SPY),1)) && (lag(Cl(SPY),2)>lag(Op(SPY),2))),1,0)

So I just replace the ‘&&’ operator with the multiplication operator, and strung the whole thing together with multiple ifelse statements. Here is what I had:
binVec3Day=ifelse(((Cl(SPY)>Op(SPY))),1,0) * ifelse(lag(Cl(SPY),1)>lag(Op(SPY),1),1,0) * ifelse(lag(Cl(SPY),2)>lag(Op(SPY),2),1,0)

Now we need to lag the trading signal vector by a day and get rid of the two NAs which have shown up for our first two days (as it is impossible to know how to trade on the first three days if you need three days of historical data for a trading signal). This is easily done by:

binVec3Day=lag(binVec3Day,1)
binVec3Day[is.na(binVec3Day)]=0

Out of curiosity, I ran sum(binVec3Day) to see how many trades in total there would be from Jan 1st 2007 to now, June 18 2013, using this strategy. The total came out to 230, which brings me to another very important point. The tests I am conducting now do not include transaction costs or an allowance for slippage. Slippage refers to not getting exactly the price required for the trade. These costs can have a significant impact on strategy returns and I will incorporate them into my strategies later on.

Multiplying by the returns vector and plotting the performance summary, again we see that this strategy did not do too well. This is most likely because our dataset starts from 2007, and thus our strategy has to suffer through the financial crash of 2008. We get a cumulative return from 2007 of approximately -18.00% and an annualized return of approximately -3.00%.

Image

Lets look at a subset of our data, to see if the cumulative returns improve. We can assume an investor decides to enter the market in 2010 using this strategy. To subset our data, we can do this:

rets2010=strat3Day[‘2010-01-01/2013-06-01’]

Now we can plot it using performance summary.

Image

We can combine our strategy’s returns with benchmark returns on the same graph for comparison purposes. This is done by:

combinedReturns=cbind(retVec,Strat3Day)

(cbind actually stands for column bind, and isn’t short for ‘combined’ which you could mistake from my use of variable names above)

Now when we plot combinedReturns using Performance Summary, we can see that it has underperformed the benchmark so far, in terms of cumulative return. Where our strategy stands out though is the reduced drawdown we suffer in down markets. Annualized volatility is reduced from approximately 18.00% to 4.00%. We can’t really use the Sharpe ratio for comparison here as the excess returns are negative.

Image

You can get the annualized Sharpe ratio and volatility for the S&P by entering:
SharpeRatio.annualized(retVec, scale=252)

sd.annualized(retVec, scale=252)

So why are we getting negative cumulative returns? Because the strategy is not in the market enough due to market conditions. Hence the low volatility and negative return. If we can make use of any available big downward moves, we may be able to turn a profit by shorting. Onwards to the next post!

Leave a comment

Filed under Finance, Trading Strategies

Trading Strategy 1: What goes up, goes up…

As I said earlier, my main task at my internship is to hunt for profitable strategies. As you can imagine, strategies can range from the exceedingly simple and easy to implement, to the crazily complex. Let’s start out with one of the simplest trading strategies out there.

The gist of this strategy is you buy the security tomorrow if the closing price today was higher than the opening price today. You hold the security until the end of the day and sell it at close.

This strategy requires buying at open and selling at close so we will calculate Open to Close returns.

1-Get data:
getSymbols(‘SPY’)

2-Calculate Open to Close returns:
retVec=Delt(Op(SPY),Cl(SPY))

3-Create a trading signal vector:
binaryVec=lag(ifelse(Cl(SPY)>Op(SPY),1,0),1) #This is saying that for any given index in the Opening and Closing price vectors, when the closing price is greater than the closing price, enter it as a ‘1’ in the binaryVec vector, otherwise enter a ‘0’. Then lag it by one day as you see the signal today but are only able to act on it tomorrow.

4-Finally, multiply the trading signal vector with the return vector and run performance analytics on it:
stratVec=retVec*binaryVec

5-charts.PerformanceSummary(stratVec)

Image

Note: Lagging creates NA values in the vector, which you can set to 0 by saying laggedVector[is.na(laggedVector)]=0.

This was an easy to run strategy and, not surprisingly, it didn’t do too well (although 2007 has to be one of the worst times historically to enter the market with long-only positions). Our intuition for this strategy was that if a stock ends higher today, it will carry that momentum forward to tomorrow. Several modifications can be made, such as tightening our criteria for what we label as momentum. To avoid capturing just random price fluctuations (to a marginal extent), we can raise the bar for what we judge as momentum by saying that instead of trading tomorrow if today’s price return was positive, we will only trade tomorrow if the last 2 days’ price return was positive as well, or the last 5 days’ price return was positive. There are two ways (that I can think of) something like this can be done in R, which I will cover in the next post.

Leave a comment

Filed under Finance, Trading Strategies