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.

permalink | | 2007.11.20-23:36.00

hum