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, GLM
julia> ibm_ts = TSFrame(MarketData.yahoo(:IBM))
13456×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.55486 719648.0 1971-02-09 16.2882 16.3121 16.1687 16.1807 3.53139 673624.0 1971-02-10 16.1568 16.1568 15.9775 16.1209 3.51835 648520.0 1971-02-11 16.1209 16.2285 16.097 16.1926 3.53399 579484.0 1971-02-12 16.1926 16.2285 16.1329 16.2285 3.54182 382836.0 1971-02-16 16.2285 16.4197 16.1926 16.3301 3.56399 684084.0 1971-02-17 16.2703 16.2703 16.0492 16.0851 3.51052 652704.0 1971-02-18 16.0851 16.1209 15.7385 15.7385 3.43488 822156.0 ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ 2024-06-07 168.18 171.31 168.06 170.01 170.01 3.4755e6 2024-06-10 169.55 170.76 168.88 170.38 170.38 3.4447e6 2024-06-11 169.98 170.0 166.81 169.32 169.32 2.9513e6 2024-06-12 171.35 172.47 168.1 169.0 169.0 3.5227e6 2024-06-13 169.01 169.59 168.34 169.12 169.12 3.5257e6 2024-06-14 168.29 169.47 167.23 169.21 169.21 2.7777e6 2024-06-17 168.76 169.72 167.5 169.5 169.5 3.2388e6 13441 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))
10969×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.087116 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-06-07 194.65 196.94 194.14 196.89 196.89 5.310 ⋯ 2024-06-10 196.9 197.3 192.15 193.12 193.12 9.726 2024-06-11 193.65 207.16 193.63 207.15 207.15 1.723 2024-06-12 207.37 220.2 206.9 213.07 213.07 1.981 2024-06-13 214.74 216.75 211.6 214.24 214.24 9.786 ⋯ 2024-06-14 213.85 215.17 211.3 212.49 212.49 7.012 2024-06-17 213.37 218.95 212.72 216.67 216.67 9.360 1 column and 10954 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 119.776 2.5287e6 2021-06-02 138.26 139.34 137.772 139.312 121.047 2.9151e6 2021-06-03 138.537 139.465 137.706 139.149 120.906 4.32061e6 2021-06-04 139.579 141.061 139.35 140.937 122.46 3.26132e6 2021-06-07 141.061 142.199 140.698 141.511 122.958 3.62198e6 2021-06-08 141.606 143.595 141.606 142.514 123.83 5.31378e6 2021-06-09 142.476 144.426 142.275 144.044 125.159 5.54725e6 2021-06-10 144.809 146.119 143.174 143.92 125.051 4.97739e6 ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ 2021-12-22 129.06 129.79 127.6 129.75 115.559 3.9232e6 2021-12-23 130.0 130.96 129.52 130.63 116.343 3.649e6 2021-12-27 130.63 131.65 129.95 131.62 117.224 4.2939e6 2021-12-28 131.6 133.22 131.59 132.63 118.124 3.4452e6 2021-12-29 132.4 134.21 132.3 133.35 118.765 4.2399e6 2021-12-30 133.75 134.37 133.33 133.91 119.264 3.1581e6 2021-12-31 134.0 134.99 133.61 133.66 119.041 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 119.776 122.357 2021-06-02 121.047 123.125 2021-06-03 120.906 121.629 2021-06-04 122.46 123.942 2021-06-07 122.958 123.952 2021-06-08 123.83 124.779 2021-06-09 125.159 125.163 2021-06-10 125.051 124.159 ⋮ ⋮ ⋮ 2021-12-22 115.559 173.435 2021-12-23 116.343 174.067 2021-12-27 117.224 178.066 2021-12-28 118.124 177.039 2021-12-29 118.765 177.128 2021-12-30 119.264 175.962 2021-12-31 119.041 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 119.776 122.357 2021-06-02 121.047 123.125 2021-06-03 120.906 121.629 2021-06-04 122.46 123.942 2021-06-07 122.958 123.952 2021-06-08 123.83 124.779 2021-06-09 125.159 125.163 2021-06-10 125.051 124.159 ⋮ ⋮ ⋮ 2021-12-22 115.559 173.435 2021-12-23 116.343 174.067 2021-12-27 117.224 178.066 2021-12-28 118.124 177.039 2021-12-29 118.765 177.128 2021-12-30 119.264 175.962 2021-12-31 119.041 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()
150×2 TSFrame with Date Index Index IBM AAPL Date Float64 Float64 ────────────────────────────── 2021-06-01 119.776 122.357 2021-06-02 121.047 123.125 2021-06-03 120.906 121.629 2021-06-04 122.46 123.942 2021-06-07 122.958 123.952 2021-06-08 123.83 124.779 2021-06-09 125.159 125.163 2021-06-10 125.051 124.159 ⋮ ⋮ ⋮ 2021-12-22 115.559 173.435 2021-12-23 116.343 174.067 2021-12-27 117.224 178.066 2021-12-28 118.124 177.039 2021-12-29 118.765 177.128 2021-12-30 119.264 175.962 2021-12-31 119.041 175.34 135 rows omitted
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 122.46 123.942 2021-06-11 125.666 125.38 2021-06-18 118.888 128.442 2021-06-25 121.978 131.051 2021-07-02 116.312 137.795 2021-07-09 117.558 142.865 2021-07-16 115.382 144.125 2021-07-23 117.409 146.262 ⋮ ⋮ ⋮ 2021-11-19 103.357 158.534 2021-11-26 103.144 154.841 2021-12-03 105.842 159.808 2021-12-10 110.518 177.197 2021-12-17 113.466 168.991 2021-12-23 116.343 174.067 2021-12-31 119.041 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.0258467 0.0115306 2021-06-18 -0.055449 0.0241276 2021-06-25 0.0256602 0.0201091 2021-07-02 -0.0475585 0.0501809 2021-07-09 0.0106559 0.0361352 2021-07-16 -0.0186871 0.0087824 2021-07-23 0.0174142 0.0147145 ⋮ ⋮ ⋮ 2021-11-19 -0.024766 0.0680368 2021-11-26 -0.00207031 -0.0235705 2021-12-03 0.025827 0.031573 2021-12-10 0.0432293 0.103288 2021-12-17 0.0263246 -0.0474147 2021-12-23 0.0250371 0.0295919 2021-12-31 0.0229304 0.00729135 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.0258467 0.0115306 2021-06-18 -0.055449 0.0241276 2021-06-25 0.0256602 0.0201091 2021-07-02 -0.0475585 0.0501809 2021-07-09 0.0106559 0.0361352 2021-07-16 -0.0186871 0.0087824 2021-07-23 0.0174142 0.0147145 ⋮ ⋮ ⋮ 2021-11-19 -0.024766 0.0680368 2021-11-26 -0.00207031 -0.0235705 2021-12-03 0.025827 0.031573 2021-12-10 0.0432293 0.103288 2021-12-17 0.0263246 -0.0474147 2021-12-23 0.0250371 0.0295919 2021-12-31 0.0229304 0.00729135 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.034079319088363175
julia> aapl_std = std(skipmissing(ibm_aapl_weekly_returns[:, :AAPL]))
0.030582272617964864
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.0105551 0.00625656 2021-06-03 -0.00116721 -0.0122286 2021-06-04 0.0127661 0.0188437 2021-06-07 0.00406152 7.94047e-5 2021-06-08 0.00706868 0.00664973 2021-06-09 0.010676 0.00307224 2021-06-10 -0.000863161 -0.00805551 ⋮ ⋮ ⋮ 2021-12-22 0.00602968 0.0152027 2021-12-23 0.00675931 0.00363725 2021-12-27 0.00755008 0.022715 2021-12-28 0.00764433 -0.00578393 2021-12-29 0.00541401 0.000501922 2021-12-30 0.00419059 -0.00660017 2021-12-31 -0.00186866 -0.00354142 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.00423143 0.00685028 2021-06-14 0.00515136 0.014053 2021-06-21 0.0135566 0.00849493 2021-06-28 0.0222422 0.00681131 2021-07-06 0.00774669 0.0123476 2021-07-12 0.0055471 0.0145983 2021-07-19 0.00913877 0.020118 ⋮ ⋮ ⋮ 2021-11-15 0.00421513 0.0107342 2021-11-22 0.00524029 0.0175358 2021-11-29 0.0142161 0.0188586 2021-12-06 0.00430287 0.0142467 2021-12-13 0.0139502 0.0249443 2021-12-20 0.00719008 0.0122393 2021-12-27 0.00389371 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.00863795 0.0124996 2021-10-01 0.0187055 0.0131257 2021-12-01 0.00792971 0.0184034
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.00703765 0.0116001 2021-06-16 0.00777315 0.011578 2021-06-17 0.00939336 0.0103009 2021-06-18 0.00965324 0.0105067 2021-06-21 0.0128876 0.0109085 2021-06-22 0.0125776 0.0111714 2021-06-23 0.0121088 0.0114157 ⋮ ⋮ ⋮ 2021-12-22 0.0102034 0.022022 2021-12-23 0.0102077 0.0220114 2021-12-27 0.0102132 0.0213968 2021-12-28 0.0079822 0.0202205 2021-12-29 0.00799231 0.0199266 2021-12-30 0.00678746 0.0179653 2021-12-31 0.00534795 0.0116642 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> ibm_adjclose = ibm[:, [:Index, :AdjClose]]
150×1 TSFrame with Date Index Index AdjClose Date Float64 ────────────────────── 2021-06-01 119.776 2021-06-02 121.047 2021-06-03 120.906 2021-06-04 122.46 2021-06-07 122.958 2021-06-08 123.83 2021-06-09 125.159 2021-06-10 125.051 ⋮ ⋮ 2021-12-22 115.559 2021-12-23 116.343 2021-12-27 117.224 2021-12-28 118.124 2021-12-29 118.765 2021-12-30 119.264 2021-12-31 119.041 135 rows omitted
julia> sp500_ibm = join(sp500_adjclose, ibm_adjclose, jointype=:JoinBoth)
150×2 TSFrame with Date Index Index AdjClose AdjClose_1 Date Float64 Float64 ────────────────────────────────── 2021-06-01 4202.04 119.776 2021-06-02 4208.12 121.047 2021-06-03 4192.85 120.906 2021-06-04 4229.89 122.46 2021-06-07 4226.52 122.958 2021-06-08 4227.26 123.83 2021-06-09 4219.55 125.159 2021-06-10 4239.18 125.051 ⋮ ⋮ ⋮ 2021-12-22 4696.56 115.559 2021-12-23 4725.79 116.343 2021-12-27 4791.19 117.224 2021-12-28 4786.35 118.124 2021-12-29 4793.06 118.765 2021-12-30 4778.73 119.264 2021-12-31 4766.18 119.041 135 rows omitted
julia> sp500_ibm_returns = diff(log.(sp500_ibm))
150×2 TSFrame with Date Index Index AdjClose_log AdjClose_1_log Date Float64? Float64? ────────────────────────────────────────────────── 2021-06-01 missing missing 2021-06-02 0.00144589 0.0105551 2021-06-03 -0.0036353 -0.00116721 2021-06-04 0.0087953 0.0127661 2021-06-07 -0.000797056 0.00406152 2021-06-08 0.00017501 0.00706868 2021-06-09 -0.00182553 0.010676 2021-06-10 0.00464145 -0.000863161 ⋮ ⋮ ⋮ 2021-12-22 0.0101287 0.00602968 2021-12-23 0.00620441 0.00675931 2021-12-27 0.0137441 0.00755008 2021-12-28 -0.00101067 0.00764433 2021-12-29 0.00140091 0.00541401 2021-12-30 -0.00299423 0.00419059 2021-12-31 -0.00262963 -0.00186866 135 rows omitted
julia> TSFrames.rename!(sp500_ibm_returns, ["SP500", "IBM"]);
julia> function regress(data) ll = lm(@formula(SP500 ~ IBM), data) co::Real = coef(ll)[coefnames(ll) .== "IBM"][1] sd::Real = Statistics.std(residuals(ll)) return (co, sd) end
regress (generic function with 1 method)
julia> rollapply(sp500_ibm_returns, regress, 10, bycolumn=false)
141×1 TSFrame with Date Index Index rolling_regress Date Tuple… ───────────────────────────────────── 2021-06-14 (0.121122, 0.00357524) 2021-06-15 (0.162983, 0.00343794) 2021-06-16 (0.266418, 0.00359775) 2021-06-17 (0.191828, 0.00348738) 2021-06-18 (0.265328, 0.00419971) 2021-06-21 (0.40735, 0.00453719) 2021-06-22 (0.425807, 0.00470503) 2021-06-23 (0.494157, 0.00383344) ⋮ ⋮ 2021-12-22 (0.00412656, 0.0118974) 2021-12-23 (0.00390952, 0.0117623) 2021-12-27 (0.0408862, 0.0121557) 2021-12-28 (-0.312518, 0.0113447) 2021-12-29 (-0.271112, 0.0108816) 2021-12-30 (0.112463, 0.0102273) 2021-12-31 (0.968816, 0.00825403) 126 rows omitted