This is a pretty advanced data management issue. R has many powerful data manipulation tools, and you wonโt need to move away from R to prepare the (supposedly pretty dumb) dyncouponbonds object. In fact, you really should not do this, because taking a structure from another language and then turning into dyncouponbonds will just work more.
The first thing I would like to make sure is that you are familiar with the lapply function. You are going to use it a lot. You are going to use it to create a list of coupon bond objects, which is actually dyncouponbonds. However, creating coupon coupon objects is a bit more complicated, mainly because of the CASHFLOWS sublist that every cash flow associated with the ISIN bond wants and the cash flow date. For this you will use lapply and some pretty advanced signatures. The subset function is also useful.
This question also very much depends on where you will receive the data from, and getting it from Bloomberg is not trivial, mainly because you need to go back in history using the BDS function and the DES_CASH_FLOW field for each bond receiving its cash flows. I tell the story because if you use dyncouponbonds, I assume that you would like to do a historical analysis of the yield curve. You need to redefine the "SETTLE_DT" field of the BDS function to the value that you will receive for communication using the BDP function and the "FIRST_SETTLE_DT" field so that you receive all cash flows from the beginning of the bond's life (otherwise it will only return from today and will not bring benefits of historical analysis). But I was distracted. If you are not using bloomberg, I do not know where you will get this data from.
Then you will need to get static data for each bond, namely: maturity, ISIN and coupon rate and issue date. And you will need historical prices and accumulated interest data. Again, if you use bloomberg, you will use the BDP function for this with the fields you see in the code below and the historical BDH data function, which I wrapped as bbdh. Assuming you are a bloomberg user, here is the code:
bbGetCountry <- function(cCode, up = FALSE) { # this function is going to get all the data out of bloomberg that we need for a # country, and update it if ncessary if (up == TRUE) startDate <- as.Date("2012-01-01") else startDate <- histStartDate # first get all the curve members for history wdays <- wdaylist(startDate, Sys.Date()) # create the list of working days from startdate actives <- lapply(wdays, function(x) { bds(conn, BBcurveIDs[cCode], "CURVE_MEMBERS", override_fields = "CURVE_DATE", override_values = format(x, "%Y%m%d")) }) names(actives) <- wdays uniqueActives <- unique(unlist(actives)) # there will be puhlenty duplicates. Get rid of them # now get the unchanging bond data staticData <- bdp(conn, uniqueActives, bbStaticDataFields) # now get the cash flowdata cfData <- lapply(uniqueActives, function(x) { bds(conn, x, "DES_CASH_FLOW_ADJ", override_fields = "SETTLE_DT", override_values = format(as.Date(staticData[x, "FIRST_SETTLE_DT"]), "%Y%m%d")) }) names(cfData) <- uniqueActives # now for historic data historicData <- lapply(bbHistoricDataFields, function(x) bbdh(uniqueActives, flds = x, startDate = startDate)) names(historicData) <- bbHistoricDataFields # put the names in otherwise we get a numbered list allDates <- as.Date(index(historicData$LAST_PRICE)) # all the dates we will find settlement dates for for all bonds. No posix save(actives, file = paste("data/", cCode, "actives.dat", sep = "")) #save all the files now save(staticData, file = paste("data/", cCode, "staticData.dat", sep = "")) save(cfData, file = paste("data/", cCode, "cfData.dat", sep = "")) save(historicData, file = paste("data/", cCode, "historicData.dat", sep = "")) #save(settleDates, file = paste("data/", cCode, "settleDates.dat", sep = "")) assign(paste(cCode, "data", sep = ""), list(actives = actives, staticData = staticData, cfData = cfData, # historicData = historicData), pos = 1)
}
The bbdh function that I use above is a wrapper around the bbh function of the Rbbg library and looks like this:
bbdh <- function(secs, years = 1, flds = "last_price", startDate = NULL) { #this function gets secs over years from bloomberg daily data if(is.null(startDate)) startDate <- Sys.Date() - years * 365.25 if(class(startDate) == "Date") stardDate <- format(startDate, "%Y%m%d") #convert date classes to bb string if(nchar(startDate) > 8) startDate <- format(as.Date(startDate), "%Y%m%d") # if we've been passed wrong format character string rawd <- bdh(conn, secs, flds, startDate, always.display.tickers = TRUE, include.non.trading.days = TRUE, option_names = c("nonTradingDayFillOption", "nonTradingDayFillMethod"), option_values = c("NON_TRADING_WEEKDAYS", "PREVIOUS_VALUE")) rawd <- dcast(rawd, date ~ ticker) #put into columns colnames(rawd) <- sub(" .*", "", colnames(rawd)) #remove the govt, currncy bits from bb tickers return(xts(rawd[, -1], order.by = as.POSIXct(rawd[, 1]))) }
The country code is based on a structure that links two letter characters with a description of the bloomberg yield curve:
BBcurveIDs <- list(PO = "YCGT0084 Index", #Portugal DE = "YCGT0016 Index", FR = "YCGT0014 Index", SP = "YCGT0061 Index", IT = "YCGT0040 Index", AU = "YCGT0001 Index", #Australia AS = "YCGT0063 Index", #Austria JP = "YCGT0018 Index", GB = "YCGT0022 Index", HK = "YCGT0095 Index", CA = "YCGT0007 Index", CH = "YCGT0082 Index", NO = "YCGT0078 Index", SE = "YCGT0021 Index", IR = "YCGT0062 Index", BE = "YCGT0006 Index", NE = "YCGT0020 index", ZA = "YCGT0090 Index", PL = "YCGT0177 Index", #Poland MX = "YCGT0251 Index")
So, bbGetCountry will create 4 different data structures called assets, staticData, dynamicData and HistoricalData, all of the following bloomberg fields:
bbStaticDataFields <- c("ID_ISIN", "ISSUER", "COUPON", "CPN_FREQ", "MATURITY", "CALC_TYP_DES", # pricing calculation type "INFLATION_LINKED_INDICATOR", # N or Y, in R returned as TRUE or FALSE "ISSUE_DT", "FIRST_SETTLE_DT", "PX_METHOD", # PRC or YLD "PX_DIRTY_CLEAN", # market convention dirty or clean "DAYS_TO_SETTLE", "CALLABLE", "MARKET_SECTOR_DES", "INDUSTRY_SECTOR", "INDUSTRY_GROUP", "INDUSTRY_SUBGROUP") bbDynamicDataFields <- c("IS_STILL_CALLABLE", "RTG_MOODY", "RTG_MOODY_WATCH", "RTG_SP", "RTG_SP_WATCH", "RTG_FITCH", "RTG_FITCH_WATCH") bbHistoricDataFields <- c("PX_BID", "PX_ASK", #"PX_CLEAN_BID", #"PX_CLEAN_ASK", "PX_DIRTY_BID", "PX_DIRTY_ASK", #"ASSET_SWAP_SPD_BID", #"ASSET_SWAP_SPD_ASK", "LAST_PRICE", #"SETTLE_DT", "YLD_YTM_MID")
Now you are ready to create coupon code objects using all these data structures:
createCouponBonds <- function(cCode, dateString) { cdata <- get(paste(cCode, "data", sep = "")) # get the data set today <- as.Date(dateString) settleDate <- today daycount <- 0 while(daycount < 3) { settleDate <- settleDate + 1 if (!(weekdays(settleDate) %in% c("Saturday", "Sunday"))) daycount <- daycount + 1 } goodbonds <- subset(cdata$staticData, COUPON != 0 & INFLATION_LINKED_INDICATOR == FALSE) # clean out zeros and tbills goodbonds <- goodbonds[rownames(goodbonds) %in% cdata$actives[[dateString]][, 1], ] stripnames <- sapply(strsplit(rownames(goodbonds), " "), function(x) x[1]) pxbid <- cdata$historicData$PX_BID[today, stripnames] pxask <- cdata$historicData$PX_ASK[today, stripnames] pxdbid <- cdata$historicData$PX_DIRTY_BID[today, stripnames] pxdask <- cdata$historicData$PX_DIRTY_ASK[today, stripnames] price <- as.numeric((pxbid + pxask) / 2) accrued <- as.numeric(pxdbid - pxbid) cashflows <- lapply(rownames(goodbonds), function(x) { goodflows <- cdata$cfData[[x]][as.Date(cdata$cfData[[x]][, "Date"]) >= today, ] #gfstipnames <- sapply(strsplit(rownames(goodflows), " "), function(x) x[1]) dunno if I need this isin <- rep(cdata$staticData[x, "ID_ISIN"], nrow(goodflows)) cf <- apply(goodflows[, 2:3], 1, sum) / 10000 dt <- as.Date(goodflows[, 1]) return(list(isin = isin, cf = cf, dt = dt)) }) isinvec <- unlist(lapply(cashflows, function(x) x$isin)) cfvec <- as.numeric(unlist(lapply(cashflows, function(x) x$cf))) datevec <- unlist(lapply(cashflows, function(x) x$dt)) govbonds <- list(ISIN = goodbonds$ID_ISIN, MATURITYDATE = as.Date(goodbonds$MATURITY), ISSUEDATE = as.Date(goodbonds$FIRST_SETTLE_DT), COUPONRATE = as.numeric(goodbonds$COUPON) / 100, PRICE = price, ACCRUED = accrued, CASHFLOWS = list(ISIN = isinvec, CF = cfvec, DATE = as.Date(datevec)), TODAY = settleDate) govbonds <- list(govbonds) names(govbonds) <- cCode class(govbonds) <- "couponbonds" return(govbonds) }
Look at the cashflows <- lapply ... function, because it is here that you will create a sublist and the core of the answer to your question, although, of course, how this is done, a lot depends on how you decided to create intermediate data structures, and I gave you only one opportunity. I understand that my answer is complex, but the problem is very complex. All the code you need is also not included in this answer, some helper functions are missing, but I am happy to provide them if you contact me. Of course, the skeleton of the main functions is here, and in fact most of the problem lies in obtaining data in the first place and properly structuring it. You correctly assume that some data is static for each bond, some of them are dynamic, and some of them are historical. Thus, the sizes of intermediate data structures are different for different parts of coupon bond objects. How you represent this is up to you, although I used separate lists / data frames for each, linked using link identifiers, where necessary.
The above function will have a date string so that you can do this for each of your historical data points using the above lapply, and hey "presto", dyncouponds:
spl <<- lapply(dodates, function(x) createCouponBonds("SP", x)) names(spl) <<- lapply(spl, function(x) x$SP$TODAY) class(spl) <- "dyncouponbonds"
There you go. You asked for it.
If you are not using bloomberg, your input structures will be very different, but as I said, start by being familiar with the foot and the juiciness. Obviously, there are many other ways to solve this problem, but the above work is for Bloomberg. If you understand this code, you probably know what you are doing for other data sources.
Finally, note that the rbbg package from findata.org is used to interact with bloomberg.