###############################################################################
# R (http://r-project.org/) Instrument Class Model
#
# Copyright (c) 2009-2012
# Peter Carl, Dirk Eddelbuettel, Jeffrey Ryan,
# Joshua Ulrich, Brian G. Peterson, and Garrett See
#
# This library is distributed under the terms of the GNU Public License (GPL)
# for full details see the file COPYING
#
# $Id$
#
###############################################################################
#' get an exchange rate series
#'
#' Try to find exchange rate data in an environment, inverting if necessary.
#'
#' @param ccy1 chr name of 1st currency
#' @param ccy2 chr name of 2nd currency
#' @param env environment in which to look for data.
#' @return xts object with as many columns as practicable.
#' @author Garrett See
#' @seealso
#' \code{\link{buildRatio}}
#' \code{\link{redenominate}}
#' @examples
#'
#' \dontrun{
#' EURUSD <- getSymbols("EURUSD=x",src='yahoo',auto.assign=FALSE)
#' USDEUR <- .get_rate("USD","EUR")
#' head(USDEUR)
#' head(EURUSD)
#' }
#' @rdname get_rate
.get_rate <- function(ccy1, ccy2, env=.GlobalEnv) {
rsym <- NA
invert <- FALSE
if (exists(paste(ccy1, ccy2, sep=""),where=env)) {
rsym <- paste(ccy1, ccy2, sep="")
} else if (exists(paste(ccy2, ccy1, sep=""),where=env)) {
rsym <- paste(ccy2, ccy1, sep="")
invert = TRUE
} else if (exists(paste(ccy1, ccy2, sep="."),where=env)) {
rsym <- paste(ccy1, ccy2, sep=".")
} else if (exists(paste(ccy2, ccy1, sep="."),where=env)) {
rsym <- paste(ccy2, ccy1, sep=".")
invert = TRUE
} else if (exists(paste(ccy1, ccy2, sep="/"),where=env)) {
rsym <- paste(ccy1, ccy2, sep="/")
} else if (exists(paste(ccy2, ccy1, sep="/"),where=env)) {
rsym <- paste(ccy2, ccy1, sep="/")
invert = TRUE
}
rate <- try(get(rsym,pos=env),silent=TRUE)
if (inherits(rate,'try-error'))
stop(paste('Could not find exchange rate for ', ccy1,
' and ', ccy2, ' in ', deparse(substitute(env)), sep=''))
rsym <- paste(substr(rsym,1,3), substr(rsym,nchar(rsym)-2,nchar(rsym)),sep="")
if (invert) {
rate <- 1/rate #inverting will reverse High/Low and Bid/Ask
rsym.inv <- paste(substr(rsym,4,6),substr(rsym,1,3),sep="")
if (is.OHLC(rate)) {
cn <- colnames(rate)
hc <- grep('High',cn,ignore.case=TRUE)
lc <- grep('Low',cn,ignore.case=TRUE)
cn[hc] <- gsub('High','Low',cn[hc])
cn[lc] <- gsub('Low','High',cn[lc])
colnames(rate) <- gsub(rsym,rsym.inv,cn)
rate <- OHLC(rate)
} else if (is.BBO(rate)) {
cn <- colnames(rate)
bc <- grep('Bid',cn,ignore.case=TRUE)
ac <- grep('Ask',cn,ignore.case=TRUE)
cn[bc] <- gsub('Bid','Ask',cn[bc])
cn[ac] <- gsub('Ask','Bid',cn[ac])
colnames(rate) <- gsub(rsym,rsym.inv,cn)
tmprate <- rate[, c(has.Bid(rate,1),has.Ask(rate,1))]
rate <- cbind(tmprate,rate[,-c(has.Bid(rate,1),has.Ask(rate,1))])
}
}
rate
}
#' Extract a single row from each day in an xts object
#' @param x xts object of sub-daily data.
#' @param EOD_time time of day to use.
#' @return xts object with daily scale.
#' @author Garrett See
#' @seealso quantmod:::to.daily, quantmod:::to.period
#' @export
#' @rdname to_daily
.to_daily <- function(x, EOD_time="15:00:00") {
x <- do.call(rbind, lapply(split(x[paste("T00:00:00/T",EOD_time,sep="")],'days'),'last'))
xts(x, order.by=as.Date(paste(index(x))))
}
#' construct price ratios of 2 instruments
#'
#' Calculates time series of ratio of 2 instruments using available data.
#' Returned object will be ratios calculated using Bids, Asks, and Mids, or Opens, Closes, and Adjusteds.
#'
#' \code{x} should be a vector of 2 instrument names. An attempt will be made to \code{get} the data
#' for both instruments. If there are no xts data stored under either of the names, it will try to
#' return prebuilt data with a call to \code{\link{.get_rate}}.
#'
#' If the data are not of the same frequency, or are not of the same type (OHLC, BBO, etc.)
#' An attempt will be made to make them compatible. Preference is given to the first leg.
#'
#' If the data in \code{x[1]} is daily or slower and the data in \code{x[2]} is intraday
# then the intraday data in \code{x[2]} will become univariate
#' (e.g. if you give it daily OHLC and intraday Bid Ask Mid, it will use all of
#' the OHLC columns of \code{x[1]} and only the the End of Day Mid price of the BAM object.
#'
#' If the data in \code{x[1]} is intraday, and the data in \code{x[2]} is daily or slower,
#' for each day, the previous closing value of \code{x[2]} will be filled forward with \code{na.locf}
#'
#' @param x vector of instrument names. e.g. c("SPY","DIA")
#' @param env environment where xts data is stored
#' @param silent silence warnings?
#' @return
#' An xts object with columns of
#' Bid, Ask, Mid
#' OR
#' Open, Close, Adjusted
#' OR
#' Price
#' @author Garrett See
#' @seealso
#' \code{\link{redenominate}}
#' \code{\link{buildSpread}}
#' \code{\link{fn_SpreadBuilder}}
#' @examples
#'
#' \dontrun{
#' syms <- c("SPY","DIA")
#' getSymbols(syms)
#' rat <- buildRatio(syms)
#' summary(rat)
#' }
#' @export
buildRatio <- function(x,env=.GlobalEnv, silent=FALSE) {
if (length(x) != 2 || !is.character(x)) {
stop('Please provide vector of 2 instruments names')
}
rat.sym <- paste(x[1],x[2],sep="")
x1 <- try(get(x[1],pos=env),silent=TRUE)
x2 <- try(get(x[2],pos=env),silent=TRUE)
if (inherits(x1,'try-error') || inherits(x2,'try-error')) {
#maybe we can get the ratio directly
if (!silent) warning(paste('Nothing to build. Returning data found in', deparse(substitute(env)),'if any.'))
return(.get_rate(x[1],x[2],env))
}
#!#---#!#
Bi <- #This, or Bid, should be exported from quantmod
function (x)
{
if (has.Bid(x))
return(x[,has.Bid(x,1)])
#return(x[, grep("Bid", colnames(x), ignore.case = TRUE)])
stop("subscript out of bounds: no column name containing \"Bid\"")
}
As <- #This, or Ask, should be exported from quantmod
function (x)
{
if (has.Ask(x))
return(x[,has.Ask(x,1)])
#return(x[, grep("Ask", colnames(x), ignore.case = TRUE)])
stop("subscript out of bounds: no column name containing \"Ask\"")
}
has.Mid <- quantmod:::has.Mid
Mid <- #This should be exported from quantmod
function (x)
{
if (has.Mid(x))
return(x[,has.Mid(x,1)])
#return(x[, grep("Mid", colnames(x), ignore.case = TRUE)])
stop("subscript out of bounds: no column name containing \"Mid\"")
}
#!#---#!#
instr2 <- NULL
instr1 <- try(getInstrument(x[1],silent=TRUE))
if (is.instrument(instr1)) instr2 <- try(getInstrument(x[2],silent=TRUE))
if (is.instrument(instr2)) {
mult1 <- as.numeric(instr1$multiplier)
mult2 <- as.numeric(instr2$multiplier)
} else mult1 <- mult2 <- 1
mrat <- mult1 / mult2
if (is.OHLC(x1) && is.OHLC(x2)) {
op <- Op(x1)[,1] / Op(x2)[,1] * mrat
cl <- Cl(x1)[,1] / Cl(x2)[,1] * mrat
if (!has.Ad(x1)) x1$Adjusted <- Cl(x1)[,1]
if (!has.Ad(x2)) x2$Adjusted <- Cl(x2)[,1]
ad <- Ad(x1)[,1] / Ad(x2)[,1] * mrat
rat <- cbind(op,cl,ad)
colnames(rat) <- paste(rat.sym, c("Open","Close","Adjusted"),sep='.')
} else if (is.BBO(x1) && is.BBO(x2)) {
bid <- Bi(x1)[,1]/As(x2)[,1] * mrat
ask <- As(x1)[,1]/Bi(x2)[,1] * mrat
if (has.Mid(x1) && has.Mid(x2)) {
mid <- Mid(x1)[,1] / Mid(x2)[,1] * mrat
} else {
mid <- ((Bi(x1)[,1]+As(x1)[,1])/2) / ((Bi(x2)[,1]+As(x2)[,1])/2) * mrat
}
rat <- cbind(bid,ask,mid)
colnames(rat) <- paste(rat.sym,c('Bid','Ask','Mid'),sep='.')
} else if (NCOL(x1) == 1 && NCOL(x2) == 1) {
rat <- x1 / x2 * mrat #coredata(x1) / coredata(x2)
} else if (periodicity(x1)$frequency >= 86400) {
#if daily or slower use OHLC and Mid
if (is.OHLC(x1)) { #If first leg is.OHLC, 2nd leg will be univariate
div <- if (NCOL(x2) == 1) {
x2
} else if (has.Mid(x2)) {
Mid(x2)[,1]
} else getPrice(x2)
rat <- mrat * x1[,1] / div
if (NCOL(x1) > 1) {
for (i in 2:NCOL(x1)) {
rat <- cbind(rat, mrat * x1[,i]/div)
}
}
} else if (is.OHLC(x2)) { #1st leg will be univariate
num <- if (NCOL(x1) == 1){
x1
} else if (has.Mid(x1)) {
Mid(x1)[,1]
} else getPrice(x1)
rat <- mrat * num / x2[,1]
if (NCOL(x2) > 1) {
for (i in 2:NCOL(x2)) {
rat <- cbind(rat, mrat * num/x2[,i])
}
colnames(rat) <- colnames(x2)
}
}
} else if (periodicity(x1)$frequency < 86400) {
#if intraday, use BAM and Cl
if (is.BBO(x1)) { #1st leg is.BBO, 2nd leg will be univariate
div <- if (NCOL(x2) == 1) {
x2
} else if (has.Cl(x2)) {
Cl(x2)[,1]
} else if (has.Ad(x2)) {
Ad(x2)[,1]
} else getPrice(x2)[,1]
rat <- mrat * x1[,1] / div
if (NCOL(x1) > 1) {
for (i in 2:NCOL(x1)) {
rat <- cbind(rat, mrat * x1[,i]/div)
}
}
} else if (is.BBO(x2)) { #1st leg will be univariate
num <- if (NCOL(x1) == 1) {
x1
} else if (has.Cl(x1)) {
Cl(x1)[,1]
} else if (has.Ad(x1)) {
Ad(x1)[,1]
} else getPrice(x1)[,1]
rat <- mrat * num / x2[,1]
if (NCOL(x2) > 1){
for (i in 2:NCOL(x2)) {
rat <- cbind(rat, mrat * num/x2[,i])
}
}
}
} else stop("I'm not programmed to handle this yet.")
if (NCOL(rat) == 1)
colnames(rat) <- paste(rat.sym,'price',sep='.')
rat
}
#' Redenominate (change the base of) an instrument
#'
#' Redenominate (change the base of) an instrument
#'
#' If \code{old_base} is not provided, \code{x} must be the name of an
#' instrument (or an object with the name of a defined instrument) so that the
#' currency attribute of the instrument can be used. Otherwise, \code{old_base}
#' must be provided.
#'
#' If you want to convert to JPY something that is denominated in EUR,
#' you must have data for the EURJPY (or JPYEUR) exchange rate. If you don't have
#' data for EURJPY, but you do have data for EURUSD and USDJPY,
#' you could \code{redenominate} to USD, then \code{redenominate} to EUR,
#' but this function is not yet smart enough to do that for you.
#'
#' See the help for buildRatio also.
#'
#' @param x can be either an xts object or the name of an instrument.
#' @param new_base change the denomination to this; usually a currency.
#' @param old_base what is the current denomination?
#' @param EOD_time If data need to be converted to daily, this is the time of day to take the observation.
#' @param env environment that holds the data
#' @param silent silence warnings?
#' @return xts object, with as many columns as practicable, that represents the value of an instrument in a different currency (base).
#' @author Garrett See
#' @note this does not yet define any instruments or assign anything.
#' @seealso
#' \code{\link{buildRatio}}
#' @examples
#'
#' \dontrun{
#' require(quantmod)
#' EURUSD <- getSymbols("EURUSD=x",src='yahoo',auto.assign=FALSE)
#' GLD <- getSymbols("GLD", src='yahoo', auto.assign=FALSE)
#' GLD.EUR <- redenominate(GLD,"EUR","USD") #can call with xts object
#'
#' currency("USD")
#' stock("GLD","USD")
#' GLD.EUR <- redenominate('GLD','EUR') #can also call with instrument name
#' }
#' @export
redenominate <- function(x, new_base='USD', old_base=NULL, EOD_time='15:00:00', env=.GlobalEnv, silent=FALSE) {
#TODO: create an instrument with currency=new_base.
if (is.xts(x)) {
Symbol <- deparse(substitute(x))
} else Symbol <- x
if (is.character(Symbol)) {
instr <- try(getInstrument(Symbol,silent=TRUE))
if (!is.instrument(instr)) {
if (is.null(old_base)) stop(paste("If old_base is not provided, ", Symbol, ' must be defined.', sep=""))
mult <- 1
} else {
if (is.null(old_base)) old_base <- instr$currency
mult <- as.numeric(instr$multiplier)
}
if (is.character(x)) x <- get(Symbol,pos=env)
}
idxx <- index(x)
#Now figure out the exchange rate
#First assume that both bases are currencies, and look for an exchange rate
if (!identical(new_base, old_base)) {
rate <- try(.get_rate(new_base,old_base,env),silent=TRUE) #try with formats like EURUSD, EUR.USD, EUR/USD, and their inverses
if (inherits(rate,'try-error')) {
rate <- buildRatio(c(old_base, new_base), env=env) #maybe it's not FX
}
} else rate <- xts(rep(1L, nrow(x)), index(x))
#!#---#!# Define function we'll need
has.Mid <- quantmod:::has.Mid
Mid <- #This should be exported from quantmod
function (x)
{
if (has.Mid(x))
return(x[,has.Mid(x,1)])
#return(x[, grep("Mid", colnames(x), ignore.case = TRUE)])
stop("subscript out of bounds: no column name containing \"Mid\"")
}
#!#---#!#
#Now we have data in x that needs to be multilied by data in rate.
#First make sure they are the same periodicity
#If you have daily data for x and intraday data for rate
#convert rate to periodicity of x
if (periodicity(x)$frequency >= 86400 && periodicity(rate)$frequency < 86400) { #x frequency is daily or lower, but rate freq is intraday
if (is.OHLC(rate) || NCOL(rate) == 1) {
rate <- to.period(rate, periodicity(x)$units)
} else if(is.BBO(rate)) {
if (periodicity(x)$scale == 'daily') {
rate <- .to_daily(rate, EOD_time) #This doesn't make OHLC, the rest do.
} else rate <- to.period(Mid(rate)[,1], periodicity(x)$units)
} else rate <- to.period(getPrice(rate)[,1], periodicity(x)$units)
}
# If you have intraday data for x and daily data for rate
# use the daily rate for all rows of each day.
if (periodicity(x)$frequency < 86400 && periodicity(rate)$frequency >= 86400) {
df <- cbind(x, rate, all=TRUE)
df <- na.locf(df,na.rm=TRUE)
x <- df[, 1:NCOL(x)]
rate <- df[, (NCOL(x)+1):NCOL(df)]
}
ff <- merge(rate,x,all=FALSE)
ff <- na.omit(ff)
ff <- ff[idxx]
rate <- ff[,1:NCOL(rate)]
x <- ff[,(NCOL(rate)+1):NCOL(ff)]
tmpenv <- new.env()
rsym <- new_base
assign(rsym,rate,pos=tmpenv)
assign(Symbol,x,pos=tmpenv)
buildRatio(c(Symbol,rsym),env=tmpenv, silent=TRUE) / mult
#TODO: colnames
#TODO: auto.assign
}
#dailyConvertFX <- function(xts_obj, rate, prefer=NULL, EOD_time="11:00:00", verbose=TRUE) {
# #to convert a EUR denominated asset from EUR to USD, rate=EURUSD
# #DAX closes at 11:45 EDT or 10:45 Chicago time
# #FRED data is noon EDT or 11:00:00 Chicago time.
# if (periodicity(xts_obj)$scale != "daily") stop('xts_obj must be daily')
# rate <- getPrice(rate, prefer=prefer)
# tmpdt <- as.Date(index(rate[1:2,]))
# if (tmpdt[1] == tmpdt[2]) { #intraday data
# if (verbose) warning('converting rate to daily')
# rate <- .to_daily(rate, EOD_time)
# rate <- rate[paste(start(xts_obj), end(xts_obj), sep="/")]
# }
# df <- cbind(rate, xts_obj, all=TRUE)
# df <- df[paste(max(start(rate),start(xts_obj)), '::', sep="")]
# if (verbose && (NROW(df) < NROW(xts_obj))) warning('Data removed where rate was missing')
# as.vector(df[,1]) * df[,2:(NCOL(df))]
#}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.