Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
So the last time that a FRAMA strategy was tried with price crossovers, the problem was that due to counter-trending failures, the filter that was added missed a lot of good trades, and wound up losing a lot of money during flat markets that passed the arbitrary filter.
This trading system tries to rectify those issues by trading a rising FRAMA filtered on a 5-day standard deviation ratio.
The hypothesis is this: the FRAMA rises in legitimately trending markets, and stays flat in choppy markets. Therefore, the ratio of standard deviations (that is, a running standard deviation of the FRAMA over the standard deviation of the market close) should be higher during trending markets, and lower during choppy markets. Additionally, as this ratio bottoms out at zero and usually tops out at 1 (rarely gets higher), it can be used as an indicator across instruments of vastly different properties.
The data that will be used will be the quandl futures data file (without federal funds, coffee, or sugar, because of data issues).
Here’s the data file:
require(IKTrading) currency('USD') Sys.setenv(TZ="UTC") t1 <- Sys.time() if(!"CME_CL" %in% ls()) { #Energies CME_CL <- quandClean("CHRIS/CME_CL", start_date=from, end_date=to, verbose=verbose) #Crude CME_NG <- quandClean("CHRIS/CME_NG", start_date=from, end_date=to, verbose=verbose) #NatGas CME_HO <- quandClean("CHRIS/CME_HO", start_date=from, end_date=to, verbose=verbose) #HeatingOil CME_RB <- quandClean("CHRIS/CME_RB", start_date=from, end_date=to, verbose=verbose) #Gasoline ICE_B <- quandClean("CHRIS/ICE_B", start_date=from, end_date=to, verbose=verbose) #Brent ICE_G <- quandClean("CHRIS/ICE_G", start_date=from, end_date=to, verbose=verbose) #Gasoil #Grains CME_C <- quandClean("CHRIS/CME_C", start_date=from, end_date=to, verbose=verbose) #Chicago Corn CME_S <- quandClean("CHRIS/CME_S", start_date=from, end_date=to, verbose=verbose) #Chicago Soybeans CME_W <- quandClean("CHRIS/CME_W", start_date=from, end_date=to, verbose=verbose) #Chicago Wheat CME_SM <- quandClean("CHRIS/CME_SM", start_date=from, end_date=to, verbose=verbose) #Chicago Soybean Meal CME_KW <- quandClean("CHRIS/CME_KW", start_date=from, end_date=to, verbose=verbose) #Kansas City Wheat CME_BO <- quandClean("CHRIS/CME_BO", start_date=from, end_date=to, verbose=verbose) #Chicago Soybean Oil #Softs #ICE_SB <- quandClean("CHRIS/ICE_SB", start_date=from, end_date=to, verbose=verbose) #Sugar #Sugar 2007-03-26 is wrong #ICE_KC <- quandClean("CHRIS/ICE_KC", start_date=from, end_date=to, verbose=verbose) #Coffee #Coffee January of 08 is FUBAR'd ICE_CC <- quandClean("CHRIS/ICE_CC", start_date=from, end_date=to, verbose=verbose) #Cocoa ICE_CT <- quandClean("CHRIS/ICE_CT", start_date=from, end_date=to, verbose=verbose) #Cotton #Other Ags CME_LC <- quandClean("CHRIS/CME_LC", start_date=from, end_date=to, verbose=verbose) #Live Cattle CME_LN <- quandClean("CHRIS/CME_LN", start_date=from, end_date=to, verbose=verbose) #Lean Hogs #Precious Metals CME_GC <- quandClean("CHRIS/CME_GC", start_date=from, end_date=to, verbose=verbose) #Gold CME_SI <- quandClean("CHRIS/CME_SI", start_date=from, end_date=to, verbose=verbose) #Silver CME_PL <- quandClean("CHRIS/CME_PL", start_date=from, end_date=to, verbose=verbose) #Platinum CME_PA <- quandClean("CHRIS/CME_PA", start_date=from, end_date=to, verbose=verbose) #Palladium #Base CME_HG <- quandClean("CHRIS/CME_HG", start_date=from, end_date=to, verbose=verbose) #Copper #Currencies CME_AD <- quandClean("CHRIS/CME_AD", start_date=from, end_date=to, verbose=verbose) #Ozzie CME_CD <- quandClean("CHRIS/CME_CD", start_date=from, end_date=to, verbose=verbose) #Loonie CME_SF <- quandClean("CHRIS/CME_SF", start_date=from, end_date=to, verbose=verbose) #Franc CME_EC <- quandClean("CHRIS/CME_EC", start_date=from, end_date=to, verbose=verbose) #Euro CME_BP <- quandClean("CHRIS/CME_BP", start_date=from, end_date=to, verbose=verbose) #Cable CME_JY <- quandClean("CHRIS/CME_JY", start_date=from, end_date=to, verbose=verbose) #Yen CME_NE <- quandClean("CHRIS/CME_NE", start_date=from, end_date=to, verbose=verbose) #Kiwi #Equities CME_ES <- quandClean("CHRIS/CME_ES", start_date=from, end_date=to, verbose=verbose) #Emini CME_MD <- quandClean("CHRIS/CME_MD", start_date=from, end_date=to, verbose=verbose) #Midcap 400 CME_NQ <- quandClean("CHRIS/CME_NQ", start_date=from, end_date=to, verbose=verbose) #Nasdaq 100 CME_TF <- quandClean("CHRIS/CME_TF", start_date=from, end_date=to, verbose=verbose) #Russell Smallcap CME_NK <- quandClean("CHRIS/CME_NK", start_date=from, end_date=to, verbose=verbose) #Nikkei #Dollar Index and Bonds/Rates ICE_DX <- quandClean("CHRIS/CME_DX", start_date=from, end_date=to, verbose=verbose) #Dixie #CME_FF <- quandClean("CHRIS/CME_FF", start_date=from, end_date=to, verbose=verbose) #30-day fed funds CME_ED <- quandClean("CHRIS/CME_ED", start_date=from, end_date=to, verbose=verbose) #3 Mo. Eurodollar/TED Spread CME_FV <- quandClean("CHRIS/CME_FV", start_date=from, end_date=to, verbose=verbose) #Five Year TNote CME_TY <- quandClean("CHRIS/CME_TY", start_date=from, end_date=to, verbose=verbose) #Ten Year Note CME_US <- quandClean("CHRIS/CME_US", start_date=from, end_date=to, verbose=verbose) #30 year bond } CMEinsts <- c("CL", "NG", "HO", "RB", "C", "S", "W", "SM", "KW", "BO", "LC", "LN", "GC", "SI", "PL", "PA", "HG", "AD", "CD", "SF", "EC", "BP", "JY", "NE", "ES", "MD", "NQ", "TF", "NK", #"FF", "ED", "FV", "TY", "US") ICEinsts <- c("B", "G", #"SB", #"KC", "CC", "CT", "DX") CME <- paste("CME", CMEinsts, sep="_") ICE <- paste("ICE", ICEinsts, sep="_") symbols <- c(CME, ICE) stock(symbols, currency="USD", multiplier=1) t2 <- Sys.time() print(t2-t1)
Here’s the strategy:
require(DSTrading) require(IKTrading) require(quantstrat) require(PerformanceAnalytics) initDate="1990-01-01" from="2000-03-01" to="2011-12-31" options(width=70) verose=TRUE FRAMAsdr <- function(HLC, n, FC, SC, nSD, ...) { frama <- FRAMA(HLC, n=n, FC=FC, SC=SC, ...) sdr <- runSD(frama$FRAMA, n=nSD)/runSD(Cl(HLC), n=nSD) sdr[sdr > 2] <- 2 out <- cbind(FRAMA=frama$FRAMA, trigger=frama$trigger, sdr=sdr) out } source("futuresData.R") #trade sizing and initial equity settings tradeSize <- 100000 initEq <- tradeSize*length(symbols) strategy.st <- portfolio.st <- account.st <- "FRAMA_SDR_I" rm.strat(portfolio.st) rm.strat(strategy.st) initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD') initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD',initEq=initEq) initOrders(portfolio.st, initDate=initDate) strategy(strategy.st, store=TRUE) #parameters FC = 1 SC = 300 n = 126 triggerLag = 1 nSD = 5 sdThresh <- .3 period=10 pctATR=.02 #indicators add.indicator(strategy.st, name="FRAMAsdr", arguments=list(HLC=quote(HLC(mktdata)), FC=FC, SC=SC, n=n, triggerLag=triggerLag, nSD=nSD), label="SDR") add.indicator(strategy.st, name="lagATR", arguments=list(HLC=quote(HLC(mktdata)), n=period), label="atrX") #signals add.signal(strategy.st, name="sigComparison", arguments=list(columns=c("FRAMA.SDR", "trigger.SDR"), relationship="gt"), label="FRAMAup") add.signal(strategy.st, name="sigThreshold", arguments=list(column="sdr.SDR", threshold=sdThresh, relationship="gt",cross=FALSE), label="SDRgtThresh") add.signal(strategy.st, name="sigAND", arguments=list(columns=c("FRAMAup", "SDRgtThresh"), cross=TRUE), label="longEntry") add.signal(strategy.st, name="sigCrossover", arguments=list(columns=c("FRAMA.SDR", "trigger.SDR"), relationship="lt"), label="FRAMAdnExit") #add.signal(strategy.st, name="sigThreshold", # arguments=list(column="sdr.SDR", threshold=sdThresh, relationship="lt", cross=TRUE), # label="SDRexit") #rules add.rule(strategy.st, name="ruleSignal", arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", orderside="long", replace=FALSE, prefer="Open", osFUN=osDollarATR, tradeSize=tradeSize, pctATR=pctATR, atrMod="X"), type="enter", path.dep=TRUE) add.rule(strategy.st, name="ruleSignal", arguments=list(sigcol="FRAMAdnExit", sigval=TRUE, orderqty="all", ordertype="market", orderside="long", replace=FALSE, prefer="Open"), type="exit", path.dep=TRUE) #add.rule(strategy.st, name="ruleSignal", # arguments=list(sigcol="SDRexit", sigval=TRUE, orderqty="all", ordertype="market", # orderside="long", replace=FALSE, prefer="Open"), # type="exit", path.dep=TRUE) #apply strategy t1 <- Sys.time() out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st) t2 <- Sys.time() print(t2-t1) #set up analytics updatePortf(portfolio.st) dateRange <- time(getPortfolio(portfolio.st)$summary)[-1] updateAcct(portfolio.st,dateRange) updateEndEq(account.st)
Notice that the exit due to the volatility filter had to have been commented out (as it caused the strategy to lose all its edge). In any case, the FRAMA is the usual 126 day FRAMA, and the running standard deviation is 5 days, in order to try and reduce lag. The standard deviation ratio threshold will be .2 or higher. Here are the results:
> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses)) [1] 1.297276 > (aggCorrect <- mean(tStats$Percent.Positive)) [1] 39.08526 > (numTrades <- sum(tStats$Num.Trades)) [1] 5186 > (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE)) [1] 2.065526
In other words, typical trend follower results. 40/60 wrong to right, with a 2:1 win to loss ratio. Far from spectacular.
Duration statistics:
print(t(durStats)) [,1] Min 1 Q1 2 Med 5 Mean 11 Q3 11 Max 158 WMin 1 WQ1 5 WMed 10 WMean 18 WQ3 21 WMax 158 LMin 1 LQ1 1 LMed 3 LMean 6 LQ3 6 LMax 93
In short, winners last longer than losers, which makes sense given that there are a lot of whipsaws, and that this is a trend-following strategy.
Market exposure:
> print(mean(as.numeric(as.character(mktExposure$MktExposure)))) [1] 0.3820789
38%. So how did it perform?
Like this. Not particularly great, considering it’s a 60% gain over 11 years. Here are the particular statistics:
> SharpeRatio.annualized(portfRets) [,1] Annualized Sharpe Ratio (Rf=0%) 0.8124137 > Return.annualized(portfRets) [,1] Annualized Return 0.04229355 > maxDrawdown(portfRets) [1] 0.07784351
In other words, about 10 basis points of returns per percent of market exposure, or a 10% annualized return. The problem being? The drawdown is much higher than the annualized return, meaning that leverage will only make things worse. Basically, for the low return on exposure and high drawdown to annualized return, this strategy is a failure. While the steadily ascending equity curve is good, it is meaningless when the worst losses take more than a year to recover from.
In any case, here’s a look at some individual instruments.
Here’s the equity curve for the E-minis.
So first off, we can see one little feature of this strategy–due to the entry and exit not being symmetric (that is, it takes two conditions to enter–a rising FRAMA and a standard deviation ratio above .2–and only exits on one of them (falling FRAMA), price action that exhibits a steady grind upwards, due to the rapid change in ATR (it’s a 10-day figure) can actually slightly pyramid from time to time. This is a good feature in my opinion, since it can add onto a winning position. However, in times of extreme volatility, when even an adaptive indicator can get bounced around chasing “mini-trends”, we can see losses pile on.
Next, let’s look at a much worse situation. Here’s the equity curve for the Eurodollar/TED spread.
In this case, it’s clearly visible that the strategy has countertrend issues, as well as the fact that the 5-day standard deviation ratio can be relatively myopic when it comes to instruments that have protracted periods of complete inactivity–that is, the market is not even choppy so much as just still.
I’ll leave this here, and move onto other attempts at getting around this sort of roadblock next.
Thanks for reading.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.