Take the 2-minute tour ×
Quantitative Finance Stack Exchange is a question and answer site for finance professionals and academics. It's 100% free, no registration required.

You need quantmod & tseries in R to run this:

library(quantmod)
library(tseries)

pairs <- c(
    "EUR/USD",
    "GBP/USD",
    "AUD/USD",
    "USD/CAD",
    "USD/CHF",
    "NZD/USD"
    )

name <- function(n) {
    gsub("/","",n,fixed=TRUE)
}

getFrame <- function(p) {
    result <- NULL
    as.data.frame(lapply(p, function(x) {
        if(!exists(name(x))) {
            getSymbols(x, src="oanda") 
        }
        if(is.null(result)) {
            result <- get(name(x))
        } else {
            result <- merge(result, get(name(x))) 
        }
    }))
}

isStationary <- function(frame) {
    model <- lm(frame[,1] ~ as.matrix(frame[,-1]) + 0)
    spread <- frame[,1] - rowSums(coef(model) * frame[, -1])
    results <- adf.test(spread, alternative="stationary", k=10)
    if(results$p.value < 0.05) {
        coefficients <- coef(model)
        names(coefficients) <- gsub("as.matrix.frame.......", "", names(coefficients))
        plot(spread[1:100], type = "b")
        cat("Minimum spread: ", min(spread), "\n")
        cat("Maximum spread: ", max(spread), "\n")
        cat("P-Value: ", results$p.value, "\n")
        cat("Coeficients: \n")
        print(coefficients)
    }
}

frame <- getFrame(pairs)
isStationary(frame)

I get FX daily data from Oanda, do a simple linear regression to find the hedging ratios, and then use the Augmented DF test to test for the P-value of mean reversion in the spread.

When I run it I get this:

Minimum spread:  -1.894506 
Maximum spread:  2.176735 
P-Value:  0.03781909 
Coeficients: 
        GBP.USD     AUD.USD     USD.CAD     USD.CHF     NZD.USD 
 0.59862816  0.48810239 -0.12900886  0.04337268  0.02713479

EUR.USD coefficient is 1.

When I plot the spread the first 100 days look like this:

enter image description here

Surely something must be wrong. The holy grail shouldn't be so easy to find.

Can someone help me find what is wrong?

I tried backtesting on Dukascopy with the above coefficients as lot sizes of a basket, but I run into loses. And the spread has a different order of magnitude in dukascopy. Why is that?

share|improve this question
1  
Since you are modeling currency pairs across the globe there might be time effects in the model. For example, one currency pair may contain information about another currency pair because of timings of when markets open and close but the marks all take place on the same date. –  Quant Guy Aug 17 '11 at 23:56
    
"the marks all take place on the same date", what does that mean? –  louzer Aug 18 '11 at 0:16
    
By marks I mean when the currency pairs are quoted. Since there is a USD on each contract, if these currency pairs trade co-terminously then my hypothesis is false. On the other hand if one currency pair's quote for the day closes ahead of another currency pair but they are both marked on the same date then you might have a spurious result that is simply based on calendar/time effects. –  Quant Guy Aug 18 '11 at 3:06

2 Answers 2

@Sergey correctly identified the problem. The explanation is that coef(model) is a vector, frame is a data.frame, and element-by-element multiplication takes place in column-major order. The shorter vector (coef(model)) is recycled along the longer vector (each column in frame). For example:

frame <- data.frame(V1=1:5)
frame$V2 <- 2
frame$V3 <- 5
coef.model <- 1:3
frame * coef.model
#   V1 V2 V3
# 1  1  6 10
# 2  4  2 15
# 3  9  4  5
# 4  4  6 10
# 5 10  2 15

What you intended was something like this:

sweep(frame,2,coef.model,"*")
#   V1 V2 V3
# 1  1  4 15
# 2  2  4 15
# 3  3  4 15
# 4  4  4 15
# 5  5  4 15
share|improve this answer

The main problem in your code is this line:

rowSums(coef(model) * frame[, -1])

I'm not sure exactly what is does, perhaps some matrix multiplication, but definitely not what you expect it to do. Try to replace it with manual multiplication

spread <- frame[,1] - (coef(model)[1]*frame[,2] + coef(model)[2]*frame[,3] + coef(model)[3]*frame[,4] + coef(model)[4]*frame[,5] + coef(model)[5]*frame[,6])

And holy grail will disappear

no more holy grail

I can see a couple of other errors as well:

  1. You cant sell on ASK price. With getSymbols.oanda you always get ASK
  2. You'd better separate testing and training data sets
share|improve this answer
3  
Wow. Thanks! Thanks a lot! I know I shouldn't be glad that you've taken the grail from me. But your logic feels so good. –  louzer Aug 18 '11 at 19:26

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.