API Docs

Manual, tutorials and complete function reference

Holding Period Risk Analysis
of Intraday Trading Strategies


In the previous tutorial you have seen how high/low frequency trading strategies could be compared to each other. This tutorial would refine your understanding of risk and performance for strategies with different frequency of trades and different position duration. Could a trading strategy that holds an open position for 5 minutes during the day be more risky then a strategy which has the same position open for 3 hours in a row? You would learn that the answer to this question will change depending on how you define what "market exposure" is for each strategy.

R Code Matlab Code
Defining Trading Signal

In the spirit of a previous tutorial, two strategies are defined using an N-second moving average. The high frequency (HF) trading strategy would have a window length of 100 seconds, and the low frequency (LF) strategy uses 1000 second window. When the stock price exceeds the N-second moving average, strategy would buy 100 shares of the stock. If moving average goes above the current price while we are still in position, the strategy would issue a sell signal.

The diagram below shows that LF strategy keeps its positions open for around 227.3 minutes while the HF strategy would only hold stocks for a total of 106.7 minutes.

require(PortfolioEffectHFT)

# Moving average
MA=function(x,order){
  result = x
  x1 = c(0,x)
  result[(order):NROW(x)] = (cumsum(x1)[-(1:(order))]-cumsum(x1)[-((NROW(x1)-order+1):NROW(x1))])/order
  result[1:(order-1)] = cumsum(x[1:(order-1)])/(1:(order-1))
  return(result-0.0000000001)
}

# Lag function padded with zeroes
lagpad=function(x, k=1) {
  i = is.vector(x)
  if(is.vector(x)) x = matrix(x) else x = matrix(x,nrow(x))
  if(k>0) {
    x = rbind(matrix(rep(0, k*ncol(x)),ncol=ncol(x)), matrix(x[1:(nrow(x)-k),], ncol=ncol(x)))
  }
  else {
    x = rbind(matrix(x[(-k+1):(nrow(x)),], ncol=ncol(x)),matrix(rep(0, -k*ncol(x)),ncol=ncol(x)))
  }
  if(i) x[1:length(x)] else x
}


symbol = "GOOG"
dateStart = "2014-10-13 09:30:00"
dateEnd = "2014-10-14 16:00:00"
portfolio = portfolio_create(dateStart,dateEnd)

position=position_add(portfolio,symbol,1)
price = compute(price(position))[[1]]
printTime = price[,1]

# Create two strategy by moving average differents length
highFrequencyStrategy = array(0,dim=NROW(price))
highFrequencyStrategy[price[,"value"]<MA(price[,"value"],200)]=100
highFrequencyStrategy[(NROW(price)/2):NROW(price)]=0
highFrequencyStrategy=lagpad(highFrequencyStrategy)

lowFrequencyStrategy=array(0,dim=NROW(price))
lowFrequencyStrategy[price[,"value"]<MA(price[,"value"],800)]=100
lowFrequencyStrategy=lagpad(lowFrequencyStrategy)

highFrequencyStrategyPlot = NULL
highFrequencyStrategyPlot = rbind(highFrequencyStrategyPlot,data.frame(positions=sum(highFrequencyStrategy>0)/60,Legends="Has position"))
highFrequencyStrategyPlot = rbind(highFrequencyStrategyPlot,data.frame(positions=sum(highFrequencyStrategy==0)/60,Legends="No position"))
lowFrequencyStrategyPlot = NULL
lowFrequencyStrategyPlot = rbind(lowFrequencyStrategyPlot,data.frame(positions=sum(lowFrequencyStrategy>0)/60,Legends="Has position"))
lowFrequencyStrategyPlot = rbind(lowFrequencyStrategyPlot,data.frame(positions=sum(lowFrequencyStrategy==0)/60,Legends="No position"))

xlabel = ""
ylabel = "In minutes"
p1 = ggplot(highFrequencyStrategyPlot, aes(x = "", y = positions, fill = Legends))+xlab(xlabel)+ylab(ylabel) +  geom_bar(stat = "identity")+
  geom_text(aes(x= 1,y = positions/2 + c(0, cumsum(positions)[-length(positions)]), label = paste(round(positions,digits =1)," minutes",sep="")), size=7,col="#d5e4eb")
p1 = p1+coord_polar("y")+ ggtitle("Intraday holding period for\n high-frequency strategy")+
  util_plotTheme()+util_fillScheme()
p2 = ggplot(lowFrequencyStrategyPlot, aes(x = "", y = positions, fill = Legends))+xlab(xlabel)+ylab(ylabel) +  geom_bar(stat = "identity")+
  geom_text(aes(x= 1,y = positions/2 + c(0, cumsum(positions)[-length(positions)]), label = paste(round(positions,digits =1)," minutes",sep="")), size=7,col="#d5e4eb")
p2 = p2+coord_polar("y")+ ggtitle("Intraday holding period for\n low-frequency strategy")+
  util_plotTheme()+util_fillScheme()
util_multiplot(p1,p2,cols=2)
Position Holding Time
portfolio=portfolio_create('fromTime','2014-10-10 09:30:00','toTime','2014-10-10 16:00:00');
portfolio_addPosition(portfolio,'GOOG',1);

goog=position_price(portfolio,'GOOG');
printTime=goog(:,1);
googPrice=goog(:,2);

MA=150;
googHFMA=tsmovavg(googPrice','S',MA);
googHFMA(1:(MA-1))=rdivide(cumsum(googPrice(1:(MA-1)))',1:(MA-1));
MA=800;
googLFMA=tsmovavg(googPrice','S',MA);
googLFMA(1:(MA-1))=rdivide(cumsum(googPrice(1:(MA-1)))',1:(MA-1));
 
highFrequencyStrategy=zeros(length(googPrice),1);
highFrequencyStrategy(googPrice>=googHFMA')=100;
highFrequencyStrategy(11700:23400)=0;
lowFrequencyStrategy=zeros(length(googPrice),1);
lowFrequencyStrategy(googPrice>=googLFMA')=100;

figure('position',[800 200 1000 700])
subplot(1,2,1);
highFrequencyStrategyPlot=[sum(highFrequencyStrategy>50),sum(highFrequencyStrategy<=50)];
h=pie(highFrequencyStrategyPlot);
th = findobj(h, 'Type', 'text');
set(th, 'FontSize', 16);
A=legend(h(1:2:end),'Has position','No position');
set(A, 'FontSize', 14);
title(sprintf('Intraday holding period for\n high-frequency strategy'), 'FontSize', 18);

subplot(1,2,2);
lowFrequencyStrategyPlot=[sum(lowFrequencyStrategy>50),sum(lowFrequencyStrategy<=50)];
h=pie(lowFrequencyStrategyPlot);
th = findobj(h, 'Type', 'text');
set(th, 'FontSize', 16);
A=legend(h(1:2:end),'Has position','No position');
set(A, 'FontSize', 14);
title(sprintf('Intraday holding period for\n low-frequency strategy'), 'FontSize',18);
Position Holding Time

Constructing Trading Portfolios

We construct 2 types of trading portfolios for each strategy (HF and LF) that would have different metrics aggregation mode. This is regulated by the holdingPeriodsOnly parameter, so when:

  • holdingPeriodsOnly = false, we are using all available time to accumulate portfolio metrics. So, for instance, when there are no positions open in the portfolio, our trading strategy risk will be slowly decreasing with time. The intuition behind this is very simple - the shorter trading positions we have, the less risky our strategy becomes. This approach uses calendar time as a bases of risk calculation and is indeed a classic method found in financial literature.
  • holdingPeriodsOnly = true, only total holding time spent in the position is used to compute trading portfolio metrics. In this mode, when all positions are closed, risk of a trading strategy would stay unchanged, as we are computing risk per unit of holding time. This allow us to make a fair comparison of strategies with different length of their market exposure.
We also keep portfolioMetricsMode set to portfolio to reflect changing stock volumes.

highFrequencyPortfolioHoldOnly = portfolio_create(dateStart,dateEnd)
portfolio_settings(highFrequencyPortfolioHoldOnly,holdingPeriodsOnly=TRUE,resultsNAFilter=F)
positionHFHO=position_add(highFrequencyPortfolioHoldOnly,symbol,quantity=as.numeric(highFrequencyStrategy),time=printTime)
highFrequencyPortfolioHoldOnly

highFrequencyPortfolioAllDay = portfolio_create(dateStart,dateEnd)
portfolio_settings(highFrequencyPortfolioAllDay,holdingPeriodsOnly=FALSE,resultsNAFilter=F)
positionHFAD=position_add(highFrequencyPortfolioAllDay,symbol,quantity=as.numeric(highFrequencyStrategy),time=printTime)
highFrequencyPortfolioAllDay

lowFrequencyPortfolioHoldOnly = portfolio_create(dateStart,dateEnd)
portfolio_settings(lowFrequencyPortfolioHoldOnly,holdingPeriodsOnly=TRUE,resultsNAFilter=F)
positionLFHO=position_add(lowFrequencyPortfolioHoldOnly,symbol,quantity=as.numeric(lowFrequencyStrategy),time=printTime)
lowFrequencyPortfolioHoldOnly

lowFrequencyPortfolioAllDay = portfolio_create(dateStart,dateEnd)
portfolio_settings(lowFrequencyPortfolioAllDay,holdingPeriodsOnly=FALSE,resultsNAFilter=F)
positionLFAD=position_add(lowFrequencyPortfolioAllDay,symbol,quantity=as.numeric(lowFrequencyStrategy),time=printTime)
lowFrequencyPortfolioAllDay

plot1=util_ggplot(plot(quantity(positionHFHO),title="High Frequency Portfolio Strategy",line_size=0.6))
plot2=util_ggplot(plot(quantity(positionLFHO),title="Low Frequency Portfolio Strategy",line_size=0.6))
util_multiplot(plot1,plot2,cols=1)
Holding Periods of Two Strategies
highFrequencyPortfolioHoldOnly=portfolio_create('fromTime','2014-10-10 09:30:00','toTime','2014-10-10 16:00:00');
portfolio_settings(highFrequencyPortfolioHoldOnly,'holdingPeriodsOnly','true');
portfolio_addPosition(highFrequencyPortfolioHoldOnly,'GOOG',highFrequencyStrategy,'time',printTime);
highFrequencyPortfolioHoldOnly

highFrequencyPortfolioAllDay=portfolio_create('fromTime','2014-10-10 09:30:00','toTime','2014-10-10 16:00:00');
portfolio_settings(highFrequencyPortfolioAllDay,'holdingPeriodsOnly','false');
portfolio_addPosition(highFrequencyPortfolioAllDay,'GOOG',highFrequencyStrategy,'time',printTime);
highFrequencyPortfolioAllDay

lowFrequencyPortfolioHoldOnly=portfolio_create('fromTime','2014-10-10 09:30:00','toTime','2014-10-10 16:00:00');
portfolio_settings(lowFrequencyPortfolioHoldOnly,'holdingPeriodsOnly','true');
portfolio_addPosition(lowFrequencyPortfolioHoldOnly,'GOOG',lowFrequencyStrategy,'time',printTime);
lowFrequencyPortfolioHoldOnly

lowFrequencyPortfolioAllDay=portfolio_create('fromTime','2014-10-10 09:30:00','toTime','2014-10-10 16:00:00');
portfolio_settings(lowFrequencyPortfolioAllDay,'holdingPeriodsOnly','false');
portfolio_addPosition(lowFrequencyPortfolioAllDay,'GOOG',lowFrequencyStrategy,'time',printTime);
lowFrequencyPortfolioAllDay


close
figure('position',[800 200 1000 700])
subplot(2,1,1);
util_plot2d([printTime,highFrequencyStrategy],'HF Quantity','Title','High Frequency Portfolio Strategy')
subplot(2,1,2);
util_plot2d([printTime,lowFrequencyStrategy],'LF Quantity','Title','Low Frequency Portfolio Strategy')
Holding Periods of Two Strategies

Strategy Variance

Let's explore the our strategy portfolios in terms of their return variance. The following notation was used for the charts:

  • HF HoldOnly - HF strategy with holdingPeriodsOnly = true
  • HF AllDay - HF strategy with holdingPeriodsOnly = false
  • LF HoldOnly - LF strategy with holdingPeriodsOnly = true
  • LF AllDay - LF strategy with holdingPeriodsOnly = false

You may notice that variance of HF HoldOnly was lower then LF HoldOnly in the first part of the trading day, but in the second part, it became higher for the HF HoldOnly strategy. Therefore, we could observe that in the second part of the day, variance per unit of holding time was clearly higher for a HF strategy. At the same time, variance of HF AllDay was always lower then that of LF AllDay, which is clearly the result of a HF strategy having a twice shorter market exposure in terms of calendar time.

plot(variance(highFrequencyPortfolioHoldOnly),variance(highFrequencyPortfolioAllDay),
     variance(lowFrequencyPortfolioHoldOnly),variance(lowFrequencyPortfolioAllDay),
     title="Variance, daily",legend=c("HF HoldOnly","HF AllDay","LF HoldOnly","LF AllDay"))
Trading Strategy Variance
close
figure('position',[800 200 1000 700])
util_plot2d(portfolio_variance(highFrequencyPortfolioHoldOnly),'HF HoldOnly','Title','Variance, daily')+...
util_line2d(portfolio_variance(highFrequencyPortfolioAllDay),'HF AllDay')+...
util_line2d(portfolio_variance(lowFrequencyPortfolioHoldOnly),'LF HoldOnly')+...
util_line2d(portfolio_variance(lowFrequencyPortfolioAllDay),'LF AllDay')
Trading Strategy Variance

Strategy Value-at-Risk

A similar relationship that we observed for the return variance holds for the Value-at-Risk too, when high distribution moments are taken into account.

plot(value_at_risk(highFrequencyPortfolioHoldOnly,0.95),value_at_risk(highFrequencyPortfolioAllDay,0.95),
     value_at_risk(lowFrequencyPortfolioHoldOnly,0.95),value_at_risk(lowFrequencyPortfolioAllDay,0.95),
     title="VaR, daily",legend=c("HF HoldOnly","HF AllDay","LF HoldOnly","LF AllDay"))
Trading Strategy Value-at-Risk
util_plot2d(portfolio_VaR(highFrequencyPortfolioHoldOnly,0.05),'HF HoldOnly','Title','VaR, daily')+...
util_line2d(portfolio_VaR(highFrequencyPortfolioAllDay,0.05),'HF AllDay')+...
util_line2d(portfolio_VaR(lowFrequencyPortfolioHoldOnly,0.05),'LF HoldOnly')+...
util_line2d(portfolio_VaR(lowFrequencyPortfolioAllDay,0.05),'LF AllDay')
Trading Strategy Value-at-Risk

Strategy Return

A diagram of intraday expected returns shows that regardless of the holdingPeriodsOnly parameter, expected returns of a LF strategy portfolio are always higher then those of HF strategy.

plot(expected_return(highFrequencyPortfolioHoldOnly),expected_return(highFrequencyPortfolioAllDay),
     expected_return(lowFrequencyPortfolioHoldOnly),expected_return(lowFrequencyPortfolioAllDay),
     title="Expected Return, daily",legend=c("HF HoldOnly","HF AllDay","LF HoldOnly","LF AllDay"))
Trading Strategy Expected Return
util_plot2d(portfolio_expectedReturn(highFrequencyPortfolioHoldOnly),'HF HoldOnly','Title','Return, daily')+...
util_line2d(portfolio_expectedReturn(highFrequencyPortfolioAllDay),'HF AllDay')+...
util_line2d(portfolio_expectedReturn(lowFrequencyPortfolioHoldOnly),'LF HoldOnly')+...
util_line2d(portfolio_expectedReturn(lowFrequencyPortfolioAllDay),'LF AllDay')
Trading Strategy Expected Return

Strategy Sharpe Ratio

Similarly with the returns diagram, the Sharpe Ratio performance metric displays clear prevalence of LF trading strategy during the whole trading day. Apparently, inverting relationship in the risk component was not strong enough to flip the Sharpe Ratio in the middle of the day.

When it comes to benchmarking your own trading strategies, you could now apply a risk per unit of holding time concept along with classic "calendar time" approach to computing portfolio metrics.

plot(sharpe_ratio(highFrequencyPortfolioHoldOnly),sharpe_ratio(highFrequencyPortfolioAllDay),
     sharpe_ratio(lowFrequencyPortfolioHoldOnly),sharpe_ratio(lowFrequencyPortfolioAllDay),
     title="Sharpe Ratio, daily",legend=c("HF HoldOnly","HF AllDay","LF HoldOnly","LF AllDay"))
Trading Portfolio Summary
util_plot2d(portfolio_sharpeRatio(highFrequencyPortfolioHoldOnly),'HF HoldOnly','Title','Sharpe Ratio, daily')+...
util_line2d(portfolio_sharpeRatio(highFrequencyPortfolioAllDay),'HF AllDay')+...
util_line2d(portfolio_sharpeRatio(lowFrequencyPortfolioHoldOnly),'LF HoldOnly')+...
util_line2d(portfolio_sharpeRatio(lowFrequencyPortfolioAllDay),'LF AllDay')
Trading Portfolio Summary