R for Investing
Learn R from the beginning using financial examples. Every lesson works on prices, returns, positions or portfolios, and every code block on the page has been run.
31 lessons, about five hours in total. Start at Lesson 1 and work down. If you already write R, start at the level that matches you.
- Beginner, 10 lessons, about 1.5 hours. Objects, arithmetic, vectors, subsetting, loops, conditions, functions and lists. Lesson 8 turns prices into returns.
- Intermediate, 10 lessons, about 1.5 hours. Data frames and dplyr: loading a CSV, picking rows and columns, returns in a table, moving averages, lags, equity curves, per-ticker work and joins.
- Advanced, 11 lessons, about 2 hours. Moving averages and RSI, turning an indicator into a position, lagging the signal so it cannot see the future, trading costs, drawdown, performance metrics, a backtest in one function, the bootstrap, and
data.tablewithfread()for speed.
By the end you can load a price file, build a signal from it, run a backtest that does not look ahead, and report what it earned after costs.
To check what stuck, the quiz draws ten questions at random from any level.
What is an object in R?
Name a value with <-, check its type with class(), and multiply whole vectors of prices and share counts at once.
How do I do maths and print a result?
Work out the profit and return on a trade with R's arithmetic, then round and format the answer so a person can read it.
What is TRUE and FALSE?
Compare prices to get TRUE and FALSE, combine conditions with and, or and not, and count how many closes sat above a level.
How do I name and grow a vector?
Label the elements of a vector, look one up by its ticker, add another on the end, and see why a vector holds only one type.
How do I take part of a vector?
Pick elements out of a vector by position, by dropping, and by a condition, then average the last five closes.
What is a for loop?
Repeat a block of code once per element, fill a vector you made in advance, and total a portfolio the long way and the short way.
What is if and else?
Label a return with if, else if and else, then label a whole vector at once with ifelse.
How do I turn prices into returns?
Turn a vector of closing prices into daily returns, first with a loop and then in one line, and compound those returns into a single figure.
What is a function?
Give a calculation a name, call it with your own numbers, set a default argument, and call one function from inside another.
What is a list?
Hold text and numbers together in one object, read parts of it with [[ ]] and $, and loop over a list of positions to value a book.
What is a data frame?
Build a table from equal-length vectors, pull rows, columns and blocks out of it, add a computed column, sort it, and turn position values into weights.
How do I load a CSV file?
Read a price file into a data frame with read.csv, check its shape and types, turn the date column into real dates, and pull one ticker out of it.
How do I pick rows and columns?
Cut a price table down to the rows and columns you want, first with base R square brackets and then with the dplyr verbs filter, select and arrange.
How do I add a column?
Add a column to a table in base R and with dplyr's mutate, build one column from another in the same call, and keep only the columns you need.
How do I compute returns in a table?
Add a daily return column to a price table with dplyr::lag(), and see what goes wrong when one table holds several tickers.
What is a moving average?
Compute the mean of the last n values at every row with zoo::rollmean, add five and ten day averages to a price table, and match them in base R.
How do I use yesterday's value?
Move a column down one row with lag so the current row can read the previous one, and up one row with lead.
How do I build an equity curve?
Turn a column of returns into the growth of one unit of money with cumprod(1 + r), and check the last value against the prices.
How do I do the same thing per ticker?
Split a table by ticker with group_by, collapse each group to one row with summarise, and run a return calculation inside each ticker so no return crosses from one ticker into the next.
How do I join two tables?
Line two tables up on a shared column with merge and with the dplyr join verbs, and control what happens to the rows that have no match.
What is the difference between an SMA and an EMA?
Compute a simple moving average with zoo::rollmean and an exponential one as a short loop, and see how each reacts when a spike leaves the window.
How do I compute RSI?
Build the relative strength index from scratch: split price changes into up moves and down moves, smooth each with Wilder's method, and put the ratio on a 0 to 100 scale.
How do I turn an indicator into a position?
Read an indicator with a rule to get a signal, then lag the signal by one row to get the position you hold.
Why do I lag the signal?
Shift a signal down one row so it sits in front of the return it earns, and see how far the total moves.
How do I charge trading costs?
Count trades with abs(diff(position)), charge a fee on each one, and compare the gross and net totals of a moving average rule.
What is a drawdown?
Measure how far an equity curve sits below its own running peak with cummax, take the worst one, and date the fall on real prices.
How do I measure return and risk?
Compute CAGR, annualised volatility, Sharpe and Sortino from a return series, then run all four on every ticker.
How do I put a backtest in one function?
Wrap returns, the lagged position, turnover, costs and the equity curve in one function, run it on a moving-average rule, and sweep the window.
How certain is a Sharpe ratio?
Resample a return series with replacement two thousand times to see the range of Sharpe ratios the same data could have produced.
What is data.table and fread?
Read the price file with fread, which hands back a data.table with the dates already parsed, and write row, column and group work in the DT[i, j, by] form.
How do I compute returns per ticker with data.table?
Compute a daily return inside each ticker with a single data.table assignment, check the ticker boundary, and see what := does to the table it is called on.