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, ImputeERROR: 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 volatility31×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 months4×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 vols141×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) endERROR: 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