Nov 20, 2007
Functions for Basic Analysis of Financial Times Series, part i
Here you will find Mathematica code for determining various metrics of a time series. It includes some that I find useful (information ratio, sharpe ratio), as well as some that I personally find useless but which other people seem to care about.
Alpha and Beta are computed against a market benchmark. Information Ratio and Tracking Error depend on a benchmark as well. Selecting the appropriate benchmark is vital and sometimes difficult. I have several dozen data series that I use as my normal pool of candidates, representing various passive indices of different types of stocks, bonds, commodities, and types of managers. If nothing correlates, then Information Ratio is pretty much meaningless. Alpha becomes close to worthless. Tracking Error really applies only to index funds, with which your benchmark is specified, so selection never becomes an issue.
In practice, I find these numbers on their own to be worth little. Comparing to managers on the basis of Sharpe Ratio, for example, is meaningful only if they are being compared over the same time period and if they are active in similar strategies. More typically, I will track, for example, the change in Beta over time, or the change in Information Ratio over time, seeing how risk-adjusted returns have changed as assets under management have risen (for example). I will provide sample code for that another day.
Anyway, here is some code.
First, a utility function I don't believe I have previously provided. This strips the header and date information away from an NF1 object, leaving only a list of datapoints.
SternJustDataNF1[nf1list_] := If[StringMatchQ[SternGetChar[nf1list, "formatcode"], "nf1"], Transpose[nf1list[[2]]][[2]], Message[SternJustDataNF1::"nf1needed"]]
SternJustDataNF1::nf1needed = "SternJustDataNF1 has not been given an nf1 list.";
SternJustDataNF1::usage = "SternJustDataNF1[nf1list_] takes an nf1 list, and returns only the list of data it contained; no header, no dates.";
Arithmetic Mean
SternArithMean[nf1list_]:=Mean[SternJustDataNF1[nf1list]]
Geometrical Mean
SternGeometricMeanReturnNF1[nf1list_] :=
Module[{data}, data = SternJustDataNF1[nf1list];
Power[Product[1 + data[[x]], {x, 1, Length[data]}], 1/
Length[data]] - 1]
SternGeometricMeanReturnNF1::usage = "SternGeometricMeanReturnNFI[changelist_] takes an nf1 list and returns the geometric mean. Same as Mathematica's GeometricMean[1+x]-1. This is not annualized.";
Gain Mean
SternArithmeticMeanGainNF1[nf1list_] :=
Module[{data}, data = SternJustDataNF1[nf1list];
Mean[Select[data, # > 0 &]]]
SternArithmeticMeanGainNF1::usage = "SternArithmeticMeanGain[changelist_] takes an NF1 list and returns the arithmetic mean gain. This is not annualized.";
Loss Mean
SternArithmeticMeanLossNF1[nf1list_] :=
Module[{data}, data = SternJustDataNF1[nf1list];
Mean[Select[data, # < 0 &]]]
SternArithmeticMeanLossNF1::usage = "SternArithmeticMeanLossNF1[changelist_] takes an NF1 list and returns the arithmetic mean loss. This is not annualized.";
Standard Deviation of Return (σ)
SternStandardDeviationNF1[nf1list_] := StandardDeviation[SternJustDataNF1[nf1list]]
Gain Standard Deviation
SternStdDevGainNF1[nf1list_] :=
Module[{data}, data = SternJustDataNF1[nf1list];
StandardDeviation[Select[data, # > 0 &]]]
SternStdDevGainNF1::usage = "SternStdDevGainNF1[changelist_] takes an NF1 list and returns the standard deviation of gaining periods. This is not annualized.";
Loss Standard Deviation
SternStdDevLossNF1[nf1list_] :=
Module[{data}, data = SternJustDataNF1[nf1list];
StandardDeviation[Select[data, # < 0 &]]]
SternStdDevLossNF1::usage = "SternStdDevLossNF1[nf1list_] takes a data list and returns the standard deviation of losing periods. This is not annualized.";
Downside Deviation (needed for computing Sortino Ratio)
SternDownsideDeviationNF1[nf1list_, minimumacceptablereturn_] :=
Module[{data, marlist}, data = SternJustDataNF1[nf1list];
marlist =
Join[Select[data, # < 0 &] - minimumacceptablereturn,
Table[0, {Length[Select[data, # >= 0 &]]}]]; Sqrt[
Total[marlist^2/(Length[data] - 1)]]]
SternDownsideDeviationNF1::usage = "SternDownsideDeviationNF1[nf1list_,minimumacceptablereturn_] StdDev of performance of losing months-minimally acceptable returns; needed for computing the Sortino Ratio";
Sharpe Ratio
SternSharpeRatioNF1[nf1list_, nf1rfr_] :=
Module[{aligned, data, rfr},
aligned =
SternPrepListsForComparisonNF1[{nf1list, nf1rfr}, True, False];
data = SternJustDataNF1[aligned[[1]]];
rfr = SternJustDataNF1[aligned[[2]]]; (
1/Length[data]*Total[data - rfr])/StandardDeviation[data - rfr]]
SternSharpeRatioNF1::usage = "SternSharpeRatioNF1[nf1list_,nf1rfr_] is Mean Excess Returns / Standard Deviation of excess returns, not annualized";
Sortino Ratio
SternSortinoRatioNF1[nf1list_, minimumacceptablereturn_] := ( Mean[SternJustDataNF1[nf1list]] - minimumacceptablereturn)/ SternDownsideDeviationNF1[nf1list, minimumacceptablereturn]
SternSortinoRatioNF1::usage = "SternSortinoRatioNF1[nf1list_,minimumacceptablereturn_] is (Mean returns over threshold of acceptability)/(Downside deviation of those 'acceptable' returns), not annualized";
Beta (β)
SternBetaNF1[fundnf1_, benchnf1_] :=
Module[{aligned, data, bench},
aligned =
SternPrepListsForComparisonNF1[{fundnf1, benchnf1}, True, False];
data = SternJustDataNF1[aligned[[1]]];
bench = SternJustDataNF1[aligned[[2]]];
Total[(bench - Mean[bench])*(data - Mean[data])]/
Total[(bench - Mean[bench])^2]]
SternBetaNF1::usage = "SternBetaNF1[fundnf1_,benchnf1_] slope of the linear regression (best fit) line.";
SternMedianBetaNF1[fundnf1_, benchnf1_] :=
Module[{aligned, data, bench},
aligned =
SternPrepListsForComparisonNF1[{fundnf1, benchnf1}, True, False];
data = SternJustDataNF1[aligned[[1]]];
bench = SternJustDataNF1[aligned[[2]]];
SternRobustFit[Transpose[{data, bench}]][[2]][[1]]]
SternMedianBetaNF1::usage = "SternMedianBetaNF1[fundnf1_,benchnf1_] slope of the least median linear regression line.";
Note that beta can also be pulled from the Fit function and from Regress. If you get it that way, be sure to get the direction correct — transposing with the signal before the benchmark gives a different answer.
Alpha (α)
SternAlphaNF1[fundnf1_, benchnf1_] :=
Module[{aligned, data, bench},
aligned =
SternPrepListsForComparisonNF1[{fundnf1, benchnf1}, True, False];
data = SternJustDataNF1[aligned[[1]]];
bench = SternJustDataNF1[aligned[[2]]];
Mean[data] - SternBetaNF1[aligned[[1]], aligned[[2]]]*Mean[bench]]
SternAlphaNF1::usage = "SternAlphaNF1[fundnf1_,benchnf1_] linear regression (best fit) line's offset from the X axis.";
SternMedianAlphaNF1[fundnf1_, benchnf1_] :=
Module[{aligned, data, bench},
aligned =
SternPrepListsForComparisonNF1[{fundnf1, benchnf1}, True, False];
data = SternJustDataNF1[aligned[[1]]];
bench = SternJustDataNF1[aligned[[2]]];
SternRobustFit[Transpose[{data, bench}]][[1]]]
SternMedianAlphaNF1::usage = "SternMedianAlphaNF1[fundnf1_,benchnf1_] median linear regression line's offset from the X axis.";
As with beta, this can also be taken from Fit or Regress.
More tomorrow.

