Time-Series-dygraph

suppressPackageStartupMessages(require(dplyr))
suppressPackageStartupMessages(require(tidyquant))
suppressPackageStartupMessages(require(FinanceGraphs))

Interactive Time Series Visualization with Dygraphs

Visualizing financial time series is critical to communicating ideas and deciding how to trade or invest. While ggplot2 is the gold standard for static graphs, Dygraphs is an excellent package to mix interactivity with flexibility and visual aesthetics, and is well suited to Financial time series in particular.

That flexibility is great, but can take some time (and code) to set up and customize well. This package is designed to reduce the time spent going from data to finished and enhanced visualization. Rather than emphasize pipes and complicated coding, this package offers just a few flexible functions and a simplified parameter-based approach to graph customization. Aesthetic details have sensible defaults, but are almost always customizable. The package is designed to be as agnostic as possible about input data, and also seeks to manage other common data which may add useful information to the graph.

Data vs Time plots with fgts_dygraph

fgts_dygraph() is a flexible wrapper around dygraphs building blocks. Internally, it makes extensive use of data.table functionality, both for flexibility and speed. Key concepts useful to use this function are

Simple Examples

Most financial time series are just prices of various assets. Most of the examples will use a simple prepared data set of prices. Simply plotting these prices can be done with just a few parameters.

head(eqtypx,1)
#>          date      EEM      IBM      QQQ      TLT
#> 1: 2018-01-02 39.98331 103.4128 150.4116 99.85725
fgts_dygraph(eqtypx, title="Stock Prices", ylab="Adjusted Close")

Note two important features of this graph. First, the series is smoothed by taking moving averages specified by the number in the lower left hand column. These values are determined by the length of the input series, can be changed using the roller parameter. Second, there is a nice date range selector below the graph. This can be interactively used to focus on parts of the graph, and double clicking within the graph always resets the view to the broadest possible one.

Let’s enhance the graph with a few more parameters that can be added to the fgts_dygraph function call:

fgts_dygraph(eqtypx, title="Stock Prices, Date focused", roller=3,dtstartfrac=0.6,splitcols=TRUE)
Parameter call Feature
roller=3 Lessen the smoothing by only averaging every 3 prices
dtstartfrac=0.6 Start the graph 60 percent of the way from input series beginning to the end
splitcols=TRUE Split the first series found (leftmost if wide, first if narrow) onto a second axis. A list of series names can also be given

To highlight or hide series and focus in on a particular date range, use:

fgts_dygraph(eqtypx, title="Stock Prices, highlighted", roller=1,
             dtwindow="-3y::",hidecols="TLT",hilightcols="IBM",hilightwidth=4, hilightstyle="dashed")
Parameter call Feature
dtwindow="-3y::" focus the date range selector to 1 year prior to Sys.Date()
hidecols="TLT" Hide one or more series. Can either separate by ; or give a list.
hilightcols="IBM Highlight a series by thickening its line or changing the line style
hilightwidth=4 New width for columns named inhilightcols
hilightstyle="dashed" New line style for columns named inhilightcols

Horizontal annotations and rebasing

Many times, you may want to show relative changes of series with widely varying values. Dygraphs has a nice function dyRebase for doing so, but there are times you would like to rebase to given date. For example to make all the series start at 120 as of the beginning of 2025, use the rebase parameter.

There are also ways to add horizontal lines to highlight current (or last in the dataset) values or labels. These can be useful when you prefer more static graphs than interactive ones, especially when legends are turned off.

Parameter call Feature
rebase="2025-01-01,120" Divide all series by their values a of 1/1/2025 and scale to 120.
annotations="last,linelabel" Put vertical lines highlighting last values in each series
annotations="range,100,120" Put a highlighted range betwewen 100 and 120 percent of value on 1/1/2025
events="pt,2025-02-01,QQQ,Feb1" Add a note a particular date and series
dylegend="never" Don’t replicate information in the legend
bg_opts="grid,none;norange" Hide the grid and the date range selector
fgts_dygraph(eqtypx, title="Stock Prices, rebased",hidecols="TLT;EEM", dtstartfrac=0.7,
             rebase="2025-01-01,120",events="pt,2025-02-01,QQQ,Feb3",
             annotations="last,linelabel;range,100,120",
             dylegend = "never", bg_opts="grid,none")

Grouping series together

Many time series are closely related, as in confidence intervals around a forecast or ranges for a given time periods. Dygraphs allows you to plot series with areas shaded between lower and upper bounds. The ability to do so can be used to enhance graphs in many ways. Some I’ve used in addition to adding confidence bounds include

fgts_dygraph() plots together series which have suffixes in (.lo,.hi) with their undecorated counterparts, keeping their assigned colors. An example showing Colombia’s currency value relative to peers is:

toplot <- reerdta[REGION=="LATAM",.(cop=sum(value*(variable=="COL")),
               peers=mean(value),peers.lo=min(value),peers.hi=max(value)),by=.(date)]
head(toplot,2)
#>          date      cop    peers peers.lo peers.hi
#> 1: 2005-01-01 82.23199 87.45444 64.42042 105.4008
#> 2: 2005-02-01 82.86437 88.32892 66.80717 107.9005

Those series (peers, peers.lo, and peers.hi) are combined to get a graph which shows how one series has moved with a geographically similar set of peers.

fgts_dygraph(toplot,title="COP REER vs Latam peers",ylab="Price",
             roller=1,hilightcols="cop",hilightwidth=4,annotations="last,linevalue")

Another example is showing periods where a series is significantly correleted to another (e.g. the broader market). The series eqtyrtn in the package has the rolling 66 day p.value of regressing returns of TLT against QQQ:

toplot <- eqtypx |> left_join(select(eqtyrtn,date,p_TLT_QQQ), by="date") |>
                    filter(!is.na(p_TLT_QQQ)) |>
                    transmute(date,TLT, TLT.lo=TLT * case_when( p_TLT_QQQ<0.04 ~ 0.9,.default=1) )
fgts_dygraph(toplot,title="TLT with significant relationship to QQQ",ylab="Price",xlab="shaded areas significant",roller=1)

Events and Event handlers

fgts_dygraph() has many ways of integrating date-based information such as events or regimes with the original data. Both “pre-canned” events and events which draw in other information (via event_ds) can be displayed. Events are show in two ways:

One liners using events parameter

Events can be determined from several sources, and can be combined together.

  • Internal data: The package maintains contains a prepackaged data set of historical market regimes or other events of interest (called “dates of interest” or just “doi”). Those can be added to (with new categories) or amended as needed, and then added to the graph using (e.g.) events="doi,fedmoves". Two categories included with the package are fedmoves with individual dates and regm, which has both beginning and end dates.
smalldta <- eqtypx |> filter(date>=as.Date("2023-01-01")) |> select(date,EEM,TLT)
fgts_dygraph(smalldta,title="With Precanned Events",ylab="Price",rebase=",100",roller=1,
                    events="doi,regm;doi,fedmoves")

Internally, a persistent data set is kept that includes dates (beginning and/or end), text labels (as eventid) and formatting information. In some cases the formatting is inferred from the label (e.g. ending in “+” makes it green), but each point in customizable.

fg_get_dates_of_interest("fedmoves|regm") |> group_by(category) |> slice_tail(n=2)
#> # A tibble: 4 × 8
#> # Groups:   category [2]
#>   category eventid  eventid2 DT_ENTRY   END_DT_ENTRY color strokePattern loc  
#>   <chr>    <chr>    <chr>    <date>     <date>       <chr> <chr>         <chr>
#> 1 fedmoves F:-25    rt:4     2025-10-29 2025-10-29   <NA>  <NA>          <NA> 
#> 2 fedmoves F:-25    rt:3.75  2025-12-10 2025-12-10   <NA>  <NA>          <NA> 
#> 3 regm     GoodX25+ NA       2025-04-22 2026-01-01   <NA>  <NA>          <NA> 
#> 4 regm     IranWar- NA       2026-02-28 2026-03-09   <NA>  <NA>          <NA>

New data can be added easily. We can add new events (e.g a FOMC move in 2026) by using the fg_update_dates_of_interest() function.

newdoi <-data.frame(category="fedmoves",eventid="F:-50",DT_ENTRY=as.Date("6/16/2026",format="%m/%d/%Y"))
fg_update_dates_of_interest(newdoi)
#> Saved dates of interest file to C:\Users\DFH\AppData\Local/R/cache/R/FinanceGraphs/fg_doi.RD
#> NULL
fg_get_dates_of_interest("fedmoves") |> dplyr::slice_tail(n=2) |> as.data.frame()
#>   category eventid eventid2   DT_ENTRY END_DT_ENTRY color strokePattern  loc
#> 1 fedmoves   F:-25     rt:4 2025-10-29   2025-10-29  <NA>          <NA> <NA>
#> 2 fedmoves   F:-25  rt:3.75 2025-12-10   2025-12-10  <NA>          <NA> <NA>
  • Seasonal factors : Regularly recurring dates can also be added. They can either be absolute, (e.g. equity option expirations or IMM CDS roll dates) or relative to the end of the series, (e.g. dates with same day of the quarter or business day of the year.) See documentation for details.

  • Single dates: Coercible dates can also be added with a simple string of the form "dt,<text>,yyyy-mm-dd".

For example, to add monthly option expiration dates and a note for Christmas, add

fgts_dygraph(smalldta,title="With Seasonals",rebase=",100",roller=1,dylegend="never",dtstartfrac=0.7,
             events="seasonal,optexp,mo|qtr;dt,XMAS,2025-12-25")
  • Statistically determined dates can be added. There are few available with one-line specification, but most can be added with “helpers” discussed in the next section. Breakouts, extrema, and turning points are currently implemented. When called through the events line, they operate on the first series found. For example, 7 turning points on QQQ are plotted with
fgts_dygraph(smalldta,title="With Turning Points",rebase=",100",roller=1,events="tp,7")

Events, Annotations, and forecasts can all be added with separate data frames.

Events can also be added with data.frames that include all the details of the annotation, such as the text, dates, and colors. Those can be created by the user, but the package includes several “helpers” to take (more) raw data and add relevant formatting details.

Event helpers

Event helpers are small functions which convert any time based data into the correct annotations. The currently implemented helpers are

Function Description
fg_addbreakouts() Statistically identify breakout points (also available via events)
fg_findTurningPoints() Statistically identify turning points (also available via events)
fg_cut_to_events() “Cut” a univariate series into colored bands, with two different colors for positive and negative values
fg_signal_to_events() Map a long/short signal to events
fg_tq_divs() Add dividend events from tidyquant dividend data
fg_av_earnings() Add earnings events from alphavantagepf earnings data
fg_ratingsEvents() Add colored ranges based on analyst credit ratings

The first two are described in the function documentation. The next two are designed to map exogenous univariate series to colored regions.

  • fg_cut_to_events() creates event series whose colors (1) vary with the “strength” of the signal, and (2) use two different colors for positive and negative values. Suppose we want to overlay a sense of consumer sentiment data over equity prices. The following code combines both QQQ and sentiment one graph (so you can see what’s happening) and the coloring that results from the sentiment data. Note that we use a long/melted format for the data.
toplot <- rbind(eqtypx_melt |> filter(variable=="QQQ"), 
                consumer_sent |> transmute(date,variable=symbol,value=price))
fgts_dygraph(toplot,title="With consumer sentiment",splitcols="UMCSENT",stepcols="UMCSENT",roller=5,
             event_ds = fg_cut_to_events(consumer_sent,center="zscore"))
  • fg_signal_to_events() uses run-length encoding to map a discrete signal series to a set of colors. This would be helpful with a long/short signal overlaid on an asset price. The following example shows a simple moving average strategy (with periods of no positioning) overlaid on EEM (Emerging Markets Equity ETF). Note how easy it is to make these with data.table. First we create the signal, and then get some colors to map to the labels. (For more on fg_get_aes() see below)
suppressPackageStartupMessages(require(data.table))
ma_signal<-eqtypx[,.(date,sig=cut(frollmean(EEM,5)-frollmean(EEM,20),
                     c(-10,-0.5,0.5,10),labels=c("long","flat","short")),EEM)]
tail(ma_signal,3)
#>          date  sig   EEM
#> 1: 2026-03-18 long 57.56
#> 2: 2026-03-19 long 57.62
#> 3: 2026-03-20 long 55.64
colormap <- fg_get_aes("tradesignal")[,.(sig=variable,value)]
colormap
#>      sig   value
#> 1:  flat   white
#> 2:  long #6161ff
#> 3: short #f56462
fgts_dygraph(eqtypx[,.(date,EEM)],dtstartfrac=0.6,roller=1,title="5/20 MA positions",
             event_ds=fg_signal_to_events(ma_signal,colormap))
  • fg_tq_divs() and fg_av_earnings() create event datasets of dividend and earnings information. The dividends require tidyquant. Since there are many ways to get earnings data, the earnings helper needs the data to be downloaded before invocation. A sample set of IBM earnings is included in the package, but you can also use the commented code to get the same thing.
suppressPackageStartupMessages(require(tidyquant))
all_events <- rbindlist(list(fg_tq_divs(c("IBM")),earnings_ibm |> fg_av_earnings() ))
fgts_dygraph(eqtypx[,.(date,QQQ,IBM)],title="With earnings and divs",dtstartfrac=0.8,event_ds=all_events)

Note that mutiple event sets can be used with rbind, and the color of the event annotations matches that of the original series.

  • fg_ratingsEvents() maps a data.frame of ratings changes to colored bars which get darker as the ratings move nearer to the High-Yield/Investment grade divide. The package has a very abbreviated set of ratings changes (ratings_db) which, when overlaid with a currency (or spread), gives
head(ratings_db,3)
#>    CREDIT AGENCY RATING WATCH   DT_ENTRY
#> 1:  COLOM  FITCH     BB  <NA> 2025-12-16
#> 2:  COLOM  MOODY   Baa3  <NA> 2025-06-26
#> 3:  COLOM    S.P     BB    *- 2025-06-26
fgts_dygraph(nomfxdta |> filter(variable=="COP"),title="COP with Ratings",
         event_ds=fg_ratingsEvents("COLOM",ratings_db,agency="S.P"))

Forecasts

Time series plotted in fgts_dygraph can also be extended beyond the current day. Forecasts come in many output forms, but fortunately, there are broom like objects now which can standardize the outputs of many forecasting models. forecast in particular can produce standardized forecasts from multiple models, including ets(), auto.arima(), tbats and a host of others.

The forecasts from forecast require quite a bit of post-processing to recover dates, but fortunately the function sw_sweep() from Sweep produced an actual data.frame. This can be forwaded to another (included) helper fg_sweep(). An example to predict QQQ with ets() is shown below. First, we show the format we expect the forecasts to be in.

suppressPackageStartupMessages(require(timetk))
suppressPackageStartupMessages(require(forecast))
#> Warning: package 'forecast' was built under R version 4.5.3
suppressPackageStartupMessages(require(sweep))
fcst_eqtypx <- tk_ts(eqtypx[,.(date,QQQ)]) |> ets() |> forecast::forecast(h=60) |> sweep::sw_sweep(timetk_idx=TRUE)
#> Warning: Non-numeric columns being dropped: date
#> Warning in .check_tzones(e1, e2): 'tzone' attributes are inconsistent
head( fcst_in <- fg_sweep(fcst_eqtypx) ,3)
#>          date    QQQ.f  QQQ.flo  QQQ.fhi
#> 1: 2026-03-21 583.6937 572.5354 594.8520
#> 2: 2026-03-22 583.8967 569.0573 598.7361
#> 3: 2026-03-23 584.0997 566.3230 601.8764
fgts_dygraph(eqtypx[,.(date,IBM,QQQ)],title="Rebased With Forecasts",roller=1,dtstartfrac=0.6,rebase="2024-01-01,100",forecast_ds=fcst_in)

Note that

Customization: Colors

The visual aesthetics of fgts_dygraphs are designed to have sensible defaults, but still somewhat customizable, and more importantly mostly out of the way of the function call. Colors in particular are stored internally in persistent data.frames To see the default line colors and shading, use fg_get_aes(<category>) as seen below, or run fg_get_aes() to get a chart with the actual colors used. Some colors have variables associated with them, which are arbitrary ordering values or (in some cases) values mapped to greps on the text labels. [^1]

mycolors <-fg_get_aes("lines",n_max=4)
mycolors
#>    category variable  type     value const used                     helpstr
#> 1:    lines      D01 color     black        all Low cardinality line colors
#> 2:    lines      D02 color       red        all Low cardinality line colors
#> 3:    lines      D03 color      blue        all Low cardinality line colors
#> 4:    lines      D04 color darkgreen        all Low cardinality line colors

[^1] In particular, the included events in the set regm have text labels that look like (e.g) "TariffT-". The “-” at the end of the text is mapped to the marketregines,- color.

There are two ways to change colors. To persistently change them use fg_update_aes() which requires a data.frame in the same format as that obtained with fg_get_aes(). To use graduated colors instead of the defaults chosen by the package:

suppressPackageStartupMessages(require(RColorBrewer))
newcolors <- mycolors |> mutate(value = rev(RColorBrewer::brewer.pal(8,"GnBu"))[1:4])
fg_update_aes(newcolors, persist=TRUE )
#> Saved aesthetic updates to C:\Users\DFH\AppData\Local/R/cache/R/FinanceGraphs/fg_aes.RD
fg_get_aes("lines",n_max=4)
#>    category variable  type   value const used                     helpstr
#> 1:    lines      D01 color #08589E        all Low cardinality line colors
#> 2:    lines      D02 color #2B8CBE        all Low cardinality line colors
#> 3:    lines      D03 color #4EB3D3        all Low cardinality line colors
#> 4:    lines      D04 color #7BCCC4        all Low cardinality line colors

which gives, with annotations

fgts_dygraph(eqtypx, title="Stock Prices, new colors", annotations="last,label",rebase=",100",bg_opts="grid,none")

To just change series colors temporarily in a session (and not save them for future use, you can use fg_update_line_colors() as in the next example

fg_update_line_colors(c("gray30","gray30","red","gray30"))
#> Saved aesthetic updates to C:\Users\DFH\AppData\Local/R/cache/R/FinanceGraphs/fg_aes.RD
fgts_dygraph(eqtypx, title="Stock Prices, line colors", annotations="last,label",rebase=",100",bg_opts="grid,none")

To reset the colors back to their defaults, run

fg_reset_to_default_state("color")
#> Removing Aesthetics file and reverting to defaults of package
#> fg_reset_to_default_state(color) completed
fg_get_aesstring("lines",n_max=2)
#> [1] "black" "red"

Customization: Adding Dates of Interest

Events added with the events="doi,<category>" parameter can managed persistently across invocations of the package by using fg_update_dates_of_interest(). For example, suppose the FOMC cuts rates 50bps in the future. The event can be added with

newdoi <-data.frame(category="fedmoves",eventid="F:-50",DT_ENTRY=as.Date("6/16/2026",format="%m/%d/%Y"))
fg_update_dates_of_interest(newdoi)
#> Saved dates of interest file to C:\Users\DFH\AppData\Local/R/cache/R/FinanceGraphs/fg_doi.RD
#> NULL
tail(fg_get_dates_of_interest("fedmoves"),2)  |> as.data.frame()
#>   category eventid eventid2   DT_ENTRY END_DT_ENTRY color strokePattern  loc
#> 1 fedmoves   F:-25     rt:4 2025-10-29   2025-10-29  <NA>          <NA> <NA>
#> 2 fedmoves   F:-25  rt:3.75 2025-12-10   2025-12-10  <NA>          <NA> <NA>

Entire new categories can be added. Here are a few examples:

Recession Indicators

Adding recession indicators from FRED. FRED has a monthly probability of recession indicator. Mapping that indicator to a yes/no series using a 7pct threhold, we can add recession events using the event helper fg_signal_to_events():

recindic <- recession_indic |> transmute(date,isrec=(price>7))
colormap <- data.frame(isrec=c(FALSE,TRUE),color=c("white","pink"),eventid=c("","Recession"))
newevents <- fg_signal_to_events( recindic, colormap |> mutate(category="FREDPREC"))
fg_update_dates_of_interest(newevents)
#> Saved dates of interest file to C:\Users\DFH\AppData\Local/R/cache/R/FinanceGraphs/fg_doi.RD
#> NULL
fgts_dygraph(eqtypx, title="Stock Prices, w/ Recession Events", events="doi,FREDPREC",rebase=",100",bg_opts="grid,none")

Central Bank events

We can add other central bank moves, e.g. Brazil’s COPOM decisions BCB, by downloaded the data and using

filepath <- system.file("extdata", "selic_historical_rates.csv", package = "FinanceGraphs")
selic<-data.table::fread(filepath,skip=1,select=c(2,5),col.names=c("date","tgt"))
selic<-selic[,let(DT_ENTRY=as.Date(date,format="%m/%d/%Y"))][order(DT_ENTRY)][,move:=c(0,diff(tgt,1))]
newevents <- selic[,.(category="COPOM",eventid=format(tgt,digits=2), DT_ENTRY,color=c("blue","grey","red")[sign(move)+2])]
fg_update_dates_of_interest(newevents[!color=="grey"]) # Only add real moves
#> Saved dates of interest file to C:\Users\DFH\AppData\Local/R/cache/R/FinanceGraphs/fg_doi.RD
#> NULL
tail(newevents,2)
#>    category eventid   DT_ENTRY color
#> 1:    COPOM    15.0 2025-12-10  grey
#> 2:    COPOM    15.0 2026-01-28  grey

and use it easily with just a text argument to events.

fgts_dygraph(filter(nomfxdta,variable=="BRL"),title="BRL w/ COPOM",dtstartfrac=0.6,events="doi,COPOM")

Cleaning up any persistent event additions can be accomplished with

fg_reset_to_default_state("all")
#> Removing dates file and reverting to defaults of package
#> Removing Aesthetics file and reverting to defaults of package
#> Removing User-made Themes and reverting to defaults of package
#> Removing cache Directory
#> fg_reset_to_default_state(all) completed