Basic demo of TSFrames using financial data
Create a TSFrame object for IBM historical data
To load the IBM historical data, we will use the MarketData.yahoo
function from MarketData.jl, which returns the data in the form of a TimeArray
. We just simply pass this on to the TSFrame
constructor.
julia> using TSFrames, MarketData, Plots, Statistics, Impute
ERROR: ArgumentError: Package Impute not found in current path. - Run `import Pkg; Pkg.add("Impute")` to install the Impute package.
julia> ibm_ts = TSFrame(MarketData.yahoo(:IBM))
13419×6 TSFrame with Date Index Index Open High Low Close AdjClose Volume Date Float64 Float64 Float64 Float64 Float64 Float64 ────────────────────────────────────────────────────────────────────────────── 1971-02-08 16.0851 16.336 15.8939 16.2882 3.59015 719648.0 1971-02-09 16.2882 16.3121 16.1687 16.1807 3.56644 673624.0 1971-02-10 16.1568 16.1568 15.9775 16.1209 3.55327 648520.0 1971-02-11 16.1209 16.2285 16.097 16.1926 3.56907 579484.0 1971-02-12 16.1926 16.2285 16.1329 16.2285 3.57698 382836.0 1971-02-16 16.2285 16.4197 16.1926 16.3301 3.59937 684084.0 1971-02-17 16.2703 16.2703 16.0492 16.0851 3.54537 652704.0 1971-02-18 16.0851 16.1209 15.7385 15.7385 3.46898 822156.0 ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ 2024-04-16 185.59 185.71 182.86 183.75 183.75 4.4737e6 2024-04-17 184.16 184.67 181.78 183.1 183.1 3.003e6 2024-04-18 182.35 183.46 180.17 181.47 181.47 2.8867e6 2024-04-19 182.43 182.8 180.57 181.58 181.58 3.0376e6 2024-04-22 182.45 183.32 180.45 181.9 181.9 3.0765e6 2024-04-23 182.73 184.68 179.0 182.19 182.19 5.9502e6 2024-04-24 183.17 184.29 181.4 184.1 184.1 7.5576e6 13404 rows omitted
Create TSFrame object for AAPL
Similarly, we can create a TSFrame
object for the AAPL data.
julia> aapl_ts = TSFrame(MarketData.yahoo(:AAPL))
10932×6 TSFrame with Date Index Index Open High Low Close AdjClose Volum ⋯ Date Float64 Float64 Float64 Float64 Float64 Float ⋯ ──────────────────────────────────────────────────────────────────────────────── 1980-12-12 0.128348 0.128906 0.128348 0.128348 0.099192 4.690 ⋯ 1980-12-15 0.12221 0.12221 0.121652 0.121652 0.094017 1.758 1980-12-16 0.113281 0.113281 0.112723 0.112723 0.087117 1.057 1980-12-17 0.115513 0.116071 0.115513 0.115513 0.089273 8.644 1980-12-18 0.118862 0.11942 0.118862 0.118862 0.091861 7.344 ⋯ 1980-12-19 0.126116 0.126674 0.126116 0.126116 0.097467 4.863 1980-12-22 0.132254 0.132813 0.132254 0.132254 0.102211 3.736 1980-12-23 0.137835 0.138393 0.137835 0.137835 0.106524 4.695 ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋱ 2024-04-16 171.75 173.76 168.27 169.38 169.38 7.371 ⋯ 2024-04-17 169.61 170.65 168.0 168.0 168.0 5.090 2024-04-18 168.03 168.64 166.55 167.04 167.04 4.312 2024-04-19 166.21 166.4 164.08 165.0 165.0 6.777 2024-04-22 165.52 167.26 164.77 165.84 165.84 4.811 ⋯ 2024-04-23 165.35 167.05 164.92 166.9 166.9 4.953 2024-04-24 166.54 169.3 166.21 169.02 169.02 4.812 1 column and 10917 rows omitted
Create a 6-month subset of stock data
We would like to compare the stock returns for both the stocks for 6 months starting from June 1, 2021 till December 31, 2021. We use TSFrames.subset
method to create new objects which contain the specified duration of data.
julia> date_from = Date(2021, 06, 01);
julia> date_to = Date(2021, 12, 31);
julia> ibm = TSFrames.subset(ibm_ts, date_from, date_to)
150×6 TSFrame with Date Index Index Open High Low Close AdjClose Volume Date Float64 Float64 Float64 Float64 Float64 Float64 ───────────────────────────────────────────────────────────────────── 2021-06-01 138.623 139.417 137.428 137.849 120.965 2.5287e6 2021-06-02 138.26 139.34 137.772 139.312 122.249 2.9151e6 2021-06-03 138.537 139.465 137.706 139.149 122.106 4.32061e6 2021-06-04 139.579 141.061 139.35 140.937 123.675 3.26132e6 2021-06-07 141.061 142.199 140.698 141.511 124.179 3.62198e6 2021-06-08 141.606 143.595 141.606 142.514 125.059 5.31378e6 2021-06-09 142.476 144.426 142.275 144.044 126.402 5.54725e6 2021-06-10 144.809 146.119 143.174 143.92 126.293 4.97739e6 ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ 2021-12-22 129.06 129.79 127.6 129.75 116.706 3.9232e6 2021-12-23 130.0 130.96 129.52 130.63 117.498 3.649e6 2021-12-27 130.63 131.65 129.95 131.62 118.388 4.2939e6 2021-12-28 131.6 133.22 131.59 132.63 119.297 3.4452e6 2021-12-29 132.4 134.21 132.3 133.35 119.944 4.2399e6 2021-12-30 133.75 134.37 133.33 133.91 120.448 3.1581e6 2021-12-31 134.0 134.99 133.61 133.66 120.223 3.3621e6 135 rows omitted
julia> aapl = TSFrames.subset(aapl_ts, date_from, date_to)
150×6 TSFrame with Date Index Index Open High Low Close AdjClose Volume Date Float64 Float64 Float64 Float64 Float64 Float64 ───────────────────────────────────────────────────────────────────── 2021-06-01 125.08 125.35 123.94 124.28 122.357 6.76371e7 2021-06-02 124.28 125.24 124.05 125.06 123.125 5.92789e7 2021-06-03 124.68 124.85 123.13 123.54 121.629 7.62292e7 2021-06-04 124.07 126.16 123.85 125.89 123.942 7.51693e7 2021-06-07 126.17 126.32 124.83 125.9 123.952 7.10576e7 2021-06-08 126.6 128.46 126.21 126.74 124.779 7.44038e7 2021-06-09 127.21 127.75 126.52 127.13 125.163 5.68779e7 2021-06-10 127.02 128.19 125.94 126.11 124.159 7.11864e7 ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ 2021-12-22 173.04 175.86 172.15 175.64 173.435 9.21353e7 2021-12-23 175.85 176.85 175.27 176.28 174.067 6.83566e7 2021-12-27 177.09 180.42 177.07 180.33 178.066 7.49196e7 2021-12-28 180.16 181.33 178.53 179.29 177.039 7.91443e7 2021-12-29 179.33 180.63 178.14 179.38 177.128 6.23489e7 2021-12-30 179.47 180.57 178.09 178.2 175.962 5.9773e7 2021-12-31 178.09 179.23 177.26 177.57 175.34 6.40623e7 135 rows omitted
Combine adjusted closing prices of both stocks into one object
We now join (cbind) both the stocks' data into a single object for further analysis. We use TSFrames.join
to create two columns containing adjusted closing prices of both the stocks. The join happens by comparing the Index
values (dates) of the two objects. The resulting object contains two columns with exactly the same dates for which both the objects have data, all the other rows are omitted from the result.
julia> ibm_aapl = TSFrames.join(ibm[:, ["AdjClose"]], aapl[:, ["AdjClose"]]; jointype=:JoinBoth)
150×2 TSFrame with Date Index Index AdjClose AdjClose_1 Date Float64 Float64 ────────────────────────────────── 2021-06-01 120.965 122.357 2021-06-02 122.249 123.125 2021-06-03 122.106 121.629 2021-06-04 123.675 123.942 2021-06-07 124.179 123.952 2021-06-08 125.059 124.779 2021-06-09 126.402 125.163 2021-06-10 126.293 124.159 ⋮ ⋮ ⋮ 2021-12-22 116.706 173.435 2021-12-23 117.498 174.067 2021-12-27 118.388 178.066 2021-12-28 119.297 177.039 2021-12-29 119.944 177.128 2021-12-30 120.448 175.962 2021-12-31 120.223 175.34 135 rows omitted
julia> TSFrames.rename!(ibm_aapl, [:IBM, :AAPL])
150×2 TSFrame with Date Index Index IBM AAPL Date Float64 Float64 ────────────────────────────── 2021-06-01 120.965 122.357 2021-06-02 122.249 123.125 2021-06-03 122.106 121.629 2021-06-04 123.675 123.942 2021-06-07 124.179 123.952 2021-06-08 125.059 124.779 2021-06-09 126.402 125.163 2021-06-10 126.293 124.159 ⋮ ⋮ ⋮ 2021-12-22 116.706 173.435 2021-12-23 117.498 174.067 2021-12-27 118.388 178.066 2021-12-28 119.297 177.039 2021-12-29 119.944 177.128 2021-12-30 120.448 175.962 2021-12-31 120.223 175.34 135 rows omitted
After the join
operation the column names are modified because we merged two same-named columns (AdjClose
) so we use TSFrames.rename!()
method to rename the columns to easily remembered stock names.
Fill missing values
julia> ibm_aapl = ibm_aapl |> Impute.locf()
ERROR: UndefVarError: `Impute` not defined
Convert data into weekly frequency using last values
Here, we convert daily stock data into weekly frequency by taking the value with which the trading closed on the last day of the week as the week's price.
julia> ibm_aapl_weekly = to_weekly(ibm_aapl)
31×2 TSFrame with Date Index Index IBM AAPL Date Float64 Float64 ────────────────────────────── 2021-06-04 123.675 123.942 2021-06-11 126.913 125.38 2021-06-18 120.068 128.442 2021-06-25 123.189 131.051 2021-07-02 117.467 137.795 2021-07-09 118.725 142.865 2021-07-16 116.527 144.125 2021-07-23 118.574 146.262 ⋮ ⋮ ⋮ 2021-11-19 104.383 158.534 2021-11-26 104.167 154.841 2021-12-03 106.893 159.808 2021-12-10 111.615 177.197 2021-12-17 114.592 168.991 2021-12-23 117.498 174.067 2021-12-31 120.223 175.34 16 rows omitted
Compute weekly returns using the familiar log
and diff
functions
julia> ibm_aapl_weekly_returns = diff(log.(ibm_aapl_weekly))
31×2 TSFrame with Date Index Index IBM_log AAPL_log Date Float64? Float64? ──────────────────────────────────────────────── 2021-06-04 missing missing 2021-06-11 0.0258468 0.0115307 2021-06-18 -0.0554487 0.0241274 2021-06-25 0.0256601 0.0201091 2021-07-02 -0.0475583 0.0501811 2021-07-09 0.0106557 0.0361353 2021-07-16 -0.0186868 0.00878198 2021-07-23 0.0174141 0.0147148 ⋮ ⋮ ⋮ 2021-11-19 -0.0247662 0.0680366 2021-11-26 -0.0020703 -0.0235705 2021-12-03 0.0258271 0.0315734 2021-12-10 0.0432291 0.103288 2021-12-17 0.0263248 -0.0474145 2021-12-23 0.0250372 0.0295917 2021-12-31 0.0229303 0.00729118 16 rows omitted
julia> TSFrames.rename!(ibm_aapl_weekly_returns, [:IBM, :AAPL])
31×2 TSFrame with Date Index Index IBM AAPL Date Float64? Float64? ──────────────────────────────────────────────── 2021-06-04 missing missing 2021-06-11 0.0258468 0.0115307 2021-06-18 -0.0554487 0.0241274 2021-06-25 0.0256601 0.0201091 2021-07-02 -0.0475583 0.0501811 2021-07-09 0.0106557 0.0361353 2021-07-16 -0.0186868 0.00878198 2021-07-23 0.0174141 0.0147148 ⋮ ⋮ ⋮ 2021-11-19 -0.0247662 0.0680366 2021-11-26 -0.0020703 -0.0235705 2021-12-03 0.0258271 0.0315734 2021-12-10 0.0432291 0.103288 2021-12-17 0.0263248 -0.0474145 2021-12-23 0.0250372 0.0295917 2021-12-31 0.0229303 0.00729118 16 rows omitted
Compute standard deviation of weekly returns
Computing standard deviation is done using the std
function from Statistics
package. The skipmissing
is used to skip missing values which may have been generated while computing log returns or were already present in the data.
julia> ibm_std = std(skipmissing(ibm_aapl_weekly_returns[:, :IBM]))
0.03407929260372629
julia> aapl_std = std(skipmissing(ibm_aapl_weekly_returns[:, :AAPL]))
0.030582245722671125
Scatter plot of AAPL and IBM
Here, we use the Plots package to create a scatter plot with IBM weekly returns on the x-axis and Apple weekly returns on the y-axis.
plot(ibm_aapl_weekly_returns[:, :AAPL],
ibm_aapl_weekly_returns[:, :IBM],
seriestype = :scatter;
xlabel = "AAPL",
ylabel = "IBM",
legend = false)
Aggregation and rolling window operations
Here, we compute realised volatility of returns of both IBM and Apple stock weekly and bi-monthly. Then, we compute daily returns volatility on a rolling basis with a window size of 10.
julia> daily_returns = diff(log.(ibm_aapl))
150×2 TSFrame with Date Index Index IBM_log AAPL_log Date Float64? Float64? ────────────────────────────────────────────────── 2021-06-01 missing missing 2021-06-02 0.010555 0.00625651 2021-06-03 -0.00116716 -0.0122287 2021-06-04 0.0127657 0.0188436 2021-06-07 0.00406192 7.93402e-5 2021-06-08 0.00706846 0.00664986 2021-06-09 0.0106761 0.00307249 2021-06-10 -0.000863074 -0.00805569 ⋮ ⋮ ⋮ 2021-12-22 0.00602975 0.0152026 2021-12-23 0.00675953 0.00363742 2021-12-27 0.00754987 0.0227149 2021-12-28 0.00764456 -0.00578402 2021-12-29 0.0054138 0.000501838 2021-12-30 0.00419085 -0.00659982 2021-12-31 -0.00186881 -0.00354168 135 rows omitted
julia> rvol = apply(daily_returns, Week(1), std) # Compute the realised volatility
31×2 TSFrame with Date Index Index IBM_log_std AAPL_log_std Date Float64? Float64? ──────────────────────────────────────────────── 2021-06-01 missing missing 2021-06-07 0.00423138 0.00685038 2021-06-14 0.00515146 0.0140528 2021-06-21 0.0135565 0.00849504 2021-06-28 0.022242 0.00681131 2021-07-06 0.00774672 0.0123477 2021-07-12 0.00554712 0.0145983 2021-07-19 0.00913886 0.020118 ⋮ ⋮ ⋮ 2021-11-15 0.00421505 0.0107342 2021-11-22 0.00524036 0.0175359 2021-11-29 0.0142161 0.0188585 2021-12-06 0.00430295 0.0142467 2021-12-13 0.0139501 0.0249443 2021-12-20 0.00719005 0.0122394 2021-12-27 0.00389375 0.0121978 16 rows omitted
julia> rvol = apply(daily_returns, Month(2), std) # Every two months
4×2 TSFrame with Date Index Index IBM_log_std AAPL_log_std Date Float64? Float64? ─────────────────────────────────────────────── 2021-06-01 missing missing 2021-08-02 0.00863794 0.0124996 2021-10-01 0.0187056 0.0131257 2021-12-01 0.0079297 0.0184033
julia> rollapply(daily_returns, std, 10) # Compute rolling vols
141×2 TSFrame with Date Index Index rolling_IBM_log_std rolling_AAPL_log_std Date Float64? Float64? ─────────────────────────────────────────────────────── 2021-06-14 missing missing 2021-06-15 0.0070375 0.0116 2021-06-16 0.00777301 0.011578 2021-06-17 0.00939329 0.0103009 2021-06-18 0.00965323 0.0105067 2021-06-21 0.0128876 0.0109084 2021-06-22 0.0125775 0.0111714 2021-06-23 0.0121088 0.0114156 ⋮ ⋮ ⋮ 2021-12-22 0.0102034 0.022022 2021-12-23 0.0102076 0.0220114 2021-12-27 0.0102131 0.0213968 2021-12-28 0.0079822 0.0202205 2021-12-29 0.00799232 0.0199266 2021-12-30 0.00678746 0.0179653 2021-12-31 0.00534795 0.0116641 126 rows omitted
Rolling regression with a window of 10
One of the common finance problems is to run a rolling window regression of firm returns over market returns. For doing this, we will use the lm()
function from the GLM
package. We will create a separate function regress()
which would take in the data as an argument and use pre-defined strings to identify the returns columns, pass them to lm()
, and return the results.
We start by downloading the S&P500 daily data from Yahoo Finance, then performing the same steps as above to come to a joined TSFrame
object containing daily returns of S&P500 and IBM stock prices. Then, use rollapply()
with bycolumn=false
to tell rollapply()
to pass in the entire TSFrame
to the function in one go for each iteration within the window.
julia> sp500 = TSFrame(MarketData.yahoo("^GSPC"));
julia> sp500_adjclose = TSFrames.subset(sp500, date_from, date_to)[:, ["AdjClose"]]
150×1 TSFrame with Date Index Index AdjClose Date Float64 ────────────────────── 2021-06-01 4202.04 2021-06-02 4208.12 2021-06-03 4192.85 2021-06-04 4229.89 2021-06-07 4226.52 2021-06-08 4227.26 2021-06-09 4219.55 2021-06-10 4239.18 ⋮ ⋮ 2021-12-22 4696.56 2021-12-23 4725.79 2021-12-27 4791.19 2021-12-28 4786.35 2021-12-29 4793.06 2021-12-30 4778.73 2021-12-31 4766.18 135 rows omitted
julia> sp500_ibm = join(sp500_adjclose, ibm_adjclose, jointype=:JoinBoth)
ERROR: UndefVarError: `ibm_adjclose` not defined
julia> sp500_ibm_returns = diff(log.(sp500_ibm))
ERROR: UndefVarError: `sp500_ibm` not defined
julia> TSFrames.rename!(sp500_ibm_returns, ["SP500", "IBM"]);
ERROR: UndefVarError: `sp500_ibm_returns` not defined
julia> function regress(data) ll = lm(@formula(IBM ~ SP500), data) co::Real = coef(ll)[coefnames(ll) .== "IBM"][1] sd::Real = Statistics.std(residuals(ll)) return (co, sd) end
ERROR: LoadError: UndefVarError: `@formula` not defined in expression starting at REPL[6]:2
julia> rollapply(sp500_ibm_returns, regress, 10, bycolumn=false)
ERROR: UndefVarError: `sp500_ibm_returns` not defined