This article looks at calculating the global minimum variance portfolio as an illustration of the interactive way of developing solutions with kdb+.
We will calculate the portfolio using an arbitrary collection of assets, in this case a basket of five equities: BARC.L, HSBA.L, LLOY.L, RBS.L and VOD.L. Before calculating the portfolio we need some recent time series of prices for the assets. A sample of bid prices for the assets, at 15 minute intervals, is in this csv file. We load this into q and parse it into a table.
q)bids: ("USF"; enlist csv) 0: `:bids.csv q)show bids minute sym bid -------------------- 08:00 BARC.L 720 08:00 HSBA.L 922 08:00 LLOY.L 556.5 08:00 RBS.L 1992 08:00 VOD.L 144.25 08:05 BARC.L 721 08:05 HSBA.L 921 08:05 LLOY.L 556 08:05 RBS.L 1996 08:05 VOD.L 144.5 08:10 BARC.L 721 08:10 HSBA.L 920.5 08:10 LLOY.L 556.5 08:10 RBS.L 1996 08:10 VOD.L 144.75 08:15 BARC.L 723 08:15 HSBA.L 920.5 08:15 LLOY.L 557 08:15 RBS.L 1996 08:15 VOD.L 144.5 ..
From the bids table we calculate the returns for each symbol.
q)returns: 0 ! select r: log bid % bid ^ prev bid by sym from bids q)show returns sym r .. -----------------------------------------------------------------------------.. BARC.L 0 0.001387925 0 0.002770085 -0.00415801.. HSBA.L 0 -0.001085187 -0.0005430356 -1.110223e-016 -1.110223e-.. LLOY.L 0 -0.0008988765 0.0008988765 0.0008980692 0.000897263.. RBS.L -1.110223e-016 0.002006019 -1.110223e-016 -1.110223e-016 0.003500879.. VOD.L 0 0.001731602 0.001728609 -0.001728609 0 ..
By 'filling' the previous bid with bid itself we remove the null return on the first bid: the return becomes zero. As 'select ... by ...' adds keys to the resultant table, we can keep it flat through the '0 !' operation. Note in the resulting returns table that, unlike SQL databases, kdb+ allows a column to contain a list of values.
To reduce calculations we will make a sparse covariance matrix, one with only a single entry per unique pair of symbols. The unique pairs are reduced from the simple permutation of all pairs of symbols.
q)pairs: distinct asc each returns.sym cross returns.sym q)show pairs BARC.L BARC.L BARC.L HSBA.L BARC.L LLOY.L BARC.L RBS.L BARC.L VOD.L HSBA.L HSBA.L HSBA.L LLOY.L HSBA.L RBS.L HSBA.L VOD.L LLOY.L LLOY.L LLOY.L RBS.L LLOY.L VOD.L RBS.L RBS.L RBS.L VOD.L VOD.L VOD.L
The reduction is made through sorting each pair of symbols from the 'cross' function, then selecting only the unique values. In calculating the covariance matrix we will need to repeatedly access the returns for a symbol, so we create a function to do this.
q)r4s: {raze exec r from returns where sym = x}
Then we calculate the sparse covariance matrix for the pairs into a table.
q)matrix: (`sym1`sym2`cv !) each {x, cov [r4s first x; r4s last x]} each pairs q)show matrix sym1 sym2 cv ---------------------------- BARC.L BARC.L 1.326453e-006 BARC.L HSBA.L 2.076055e-007 BARC.L LLOY.L 2.626368e-007 BARC.L RBS.L 1.806784e-007 BARC.L VOD.L 7.138501e-008 HSBA.L HSBA.L 5.957067e-007 HSBA.L LLOY.L 2.502168e-007 HSBA.L RBS.L 1.699649e-008 HSBA.L VOD.L 1.199947e-007 LLOY.L LLOY.L 8.537148e-007 LLOY.L RBS.L 9.391369e-008 LLOY.L VOD.L 1.673899e-007 RBS.L RBS.L 1.14479e-006 RBS.L VOD.L -5.121614e-008 VOD.L VOD.L 1.419477e-006
We define a function to extract all the covariances from a sparse matrix for a given symbol; then calculate the minimum variance weightings themselves, as normalised inverse covariance sums, into a table.
q)c4s: {exec cv from matrix where (sym1 = x) or (sym2 = x)} q)weights: ([] sym: returns.sym; .quant.w {reciprocal sum c4s x} each returns.sym) q)show weights sym w ---------------- BARC.L 0.1504934 HSBA.L 0.2589832 LLOY.L 0.1894035 RBS.L 0.222591 VOD.L 0.1785288
A collection of quantitative functions in q can be downloaded. See also the postscript to this article.
1. | www.kx.com |