suppressPackageStartupMessages(require(dplyr))
suppressPackageStartupMessages(require(tidyquant))
suppressPackageStartupMessages(require(FinanceGraphs))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.
fgts_dygraphfgts_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
Input data is always a data.frame
or data.table with a Date coercible column. It
can either be narrow/long, with a character variable such as
variable (needed as a parameter if different) for series
names and a single numeric column, or wide, with a Date
column and multiple numeric columns whose names are used as
series.
Events are annotations added to the graph with a date or date range associated with it. There are several “event helpers” provided which help to find and map various types of annotations to a common format used internally within the function. The most common examples are dates of interest where market moving events or regime periods may be added.
Annotations are notes or annotations on axes other than the date axis. Examples (shown below) include lines which show the last values or series names at their endpoints, or ranges highlighting (e.g.) buy or sell targets.
Range highlighting are options to focus the graph on subsets of the plotted data.
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:
| 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 |
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")hidecols="TLT;EEM" or hidecols=c("TLT","EEM")
can also be used, or annotations strung together with semicolons.last,linelabel
annotation.hidecols. To draw with the colors
typically used for the first two series (see Customization below), take
the series out before sending to fgts_dygraph() by (e.g.)
using instead
fgts_dygraph(eqtypx |> dplyr::select(date,IBM,QQQ),...)
to put those two series first."pt" (and "dt") are
rolled to next dates if not in the underlying data.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.9005Those 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)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:
date and
date_end) are shown as shaded bands between two dates. The
colors of the bands are designed to be consistent with their meaning,
i.e. green for positive or red for negative. Those colors are
customizable.events parameterEvents can be determined from several sources, and can be combined together.
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")events line, they operate on the first series found. For
example, 7 turning points on QQQ are plotted withEvents 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 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.64colormap <- fg_get_aes("tradesignal")[,.(sig=variable,value)]
colormap
#> sig value
#> 1: flat white
#> 2: long #6161ff
#> 3: short #f56462fgts_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), giveshead(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"))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.8764fgts_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
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 colorswhich 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
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:
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")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 greyand 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