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.
Nov 17, 2007
Converting data to NF1 format
If you have a list of date/data pairs, you can convert it to NF1 format using something like the following:
SternSetCharacteristics[datalist_, formatcode_, name_, timeunit_,
type_] :=
Module[{prob}, prob = 0;
prob = prob +
If[MemberQ[
StringMatchQ[{"year", "quarter", "monthly", "week", "day",
"hour", "minute", "second"}, timeunit], True], 0, 1];
prob = prob +
If[MemberQ[StringMatchQ[{"pctchanges", "delta", "value"}, type],
True], 0, 10];
If[Mod[prob, 10] > 0,
"SSC: Trouble with inputs. Prob = " <> prob <> ".",
If[Length[datalist[[1]]] == 2,
Prepend[{datalist}, {formatcode, name, timeunit, type}],
Prepend[Drop[datalist, 1], {formatcode, name, timeunit, type}]]]]
(note that the code above dates to a time before I had figured out how to create proper Mathematica error messages).
SternSetCharacteristics::usage = "SternSetCharacteristics[datalist_,formatcode_,name_,timeunit_,type_ ]: Given a financial data object and a list of characteristics, assigns the characteristics to the object. Timeunit must be year, quarter, monthly, week, day, minute, or second. Type must be pctchanges, delta, or value. Works on data/data lists, or on existing NF1 objects. With NF1 objects, though, SternChangeCharNF1 will often be more useful.";If you have an NF1 object and want to change its characteristics, the following is useful.
Options[SternChangeCharNF1] = {formatcode -> "df", name -> "df",
timeunit -> "df", type -> "df"};
SternChangeCharNF1[nf1list_, OptionsPattern[]] :=
If[(ToString[Head[OptionValue[formatcode]]] != "String" ||
ToString[Head[OptionValue[name]]] != "String" ||
ToString[Head[OptionValue[timeunit]]] != "String" ||
ToString[Head[OptionValue[type]]] != "String") ,
Message[SternChangeCharNF1::"notstring"],
SternSetCharacteristics[nf1list[[2]],
If[OptionValue[formatcode] == "df",
SternGetChar[nf1list, "formatcode"], OptionValue[formatcode]],
If[OptionValue[name] == "df", SternGetChar[nf1list, "name"],
OptionValue[name]],
If[OptionValue[timeunit] == "df",
SternGetChar[nf1list, "timeunit"], OptionValue[timeunit]],
If[OptionValue[type] == "df", SternGetChar[nf1list, "type"],
OptionValue[type]]]]
SternChangeCharNF1::notstring =
"Something other than a string was passed where a string was
expected.";
SternChangeCharNF1::usage =
"SternChangeCharNF1[nf1list_,opts___]: Given an NF1 list and some
subset of formatcode->(whatever, but typically nf1),
name->(whatever), timeunit->(year, quarter, monthly, week, day,
minute, or second) and type->(pctchanges, delta, or value), sets the
properties of the nf1 object accordingly. Typically easier to use
than SternSetCharacteristics, as the arguments are optional.";
Closely related, the following function can be used to extract a given characteristic. I use this commonly when using Mathematica to determine which of many data sets fits a specified characteristic (highest risk-adjusted returns, for example). This function makes it easy to report the name of the selected series.
SternGetChar[datalist_, whichchar_] :=
Module[{prob, fieldnum},
If[StringMatchQ[datalist[[1]][[1]], "nf1"],(* have nf1 format *)
fieldnum =
Switch[whichchar, "formatcode", 1, "name", 2, "timeunit", 3,
"type", 4] (* else we have a problem *)]; prob = 0;
prob = prob +
If[MemberQ[
StringMatchQ[{"formatcode", "name", "timeunit", "type"},
whichchar], True], 0, 1];
If[prob > 0, "SGC: Trouble with inputs. Prob=" <> prob <> ".",
datalist[[1]][[ToExpression[fieldnum]]]]]
SternGetChar::usage =
"SternGetChar[datalist_,whichchar_]: Given a financial data object
and an identifying field (formatcode,name,timeunit, or type), returns
the value of that field.";
Financial data can come from many sources, and it is best to convert it to NF1 format before moving it into Mathematica. I have code in MySQL that does this, and in Microsoft Access, as well as VBA code that does this in Excel.
At some point, if anybody wants it, I may post my current production code, which can handle data formatted in many ways and correctly tags the NF1 object in almost every case, even processing multiple series properly and copying them all into the clipboard for ease in moving them to Mathematica.
For now, here is a very simple version which assumes you have two columns -- one with dates, and the other with data. Create an empty cell with the name "output", then select the date and data cells and run the following VBA:
Sub selectconversion()
' if it tries to output excel-style scientific notation, change the "General Number" formats to "#,##0.0000"
' needs a cell call "output"
Dim i As Integer
Dim outstring As String
Dim fmtcode1 As String
Dim fmtcode2 As String
Dim col1isString As Boolean
Dim col2isString As Boolean
col1isString = IsDate(Selection.Cells(1, 1))
fmtcode1 = IIf(col1isString, "Short Date", "0.#################")
outstring = "{"
If Selection.Columns.Count = 1 Then
outstring = outstring & IIf(col1isString, Chr(34), "") &
IIf(Selection.Rows.Count > 0, Format(Selection.Rows(1), fmtcode1), "")
& IIf(col1isString, Chr(34), "") ' for avoiding an extra comma
If Selection.Rows.Count > 1 Then
For i = 2 To Selection.Rows.Count
outstring = outstring & ", " & IIf(col1isString, Chr(34), "") &
Format(Selection.Rows(i), fmtcode1) & IIf(col1isString, Chr(34), "")
Next i
End If
Else ' more than one column
col2isString = IsDate(Selection.Cells(1, 2))
fmtcode2 = IIf(col2isString, "Short Date", "0.#################")
outstring = outstring & IIf(Selection.Rows.Count > 0, "{" &
IIf(col1isString, Chr(34), "") & Format(Selection.Cells(1, 1), fmtcode1) &
IIf(col1isString, Chr(34), "") & "," & IIf(col2isString, Chr(34), "") &
Format(Selection.Cells(1, 2), fmtcode2) & IIf(col2isString, Chr(34), "") &
"}", "") ' for avoiding an extra comma
If Selection.Rows.Count > 1 Then
For i = 2 To Selection.Rows.Count
outstring = outstring & ", {" & IIf(col1isString, Chr(34), "") &
Format(Selection.Cells(i, 1), fmtcode1) & IIf(col1isString, Chr(34),
"") & "," & IIf(col2isString, Chr(34), "") & Format(Selection.Cells(i, 2),
fmtcode2) & IIf(col2isString, Chr(34), "") & "}"
Next i
End If
End If
outstring = outstring & "}"
Range("output").Value = outstring
End Sub
This is enough of utility functions for now. Starting with my next entry, we will have code that can be used in the analysis of data.
Nov 15, 2007
Handling dates
Mathematica has three date formats. The first represents the date and time as a single integer, the total number of seconds since the beginning of January 1, 1900. The second represents the same information as a list of integers, {year, month, day, hour, minute, second}. The third is a string, such as "12/01/2006". As of version 6, Mathematica can convert freely between these formats and can automatically create graphs of dated data. Previously, this could be done only with custom Tick functions (not terribly difficult, but the solution built into Mathematica 6 is cleaner).
I have created functions that allow the easy conversion of NF1 lists using any of these date formats to any of the others. I call the first format "Mathematica Integer," the second is just "Mathematica," and the third is called "Conventional." In the conversion functions, these are flagged as "mi," "m," and "c," respectively.
Note the availability of the wtime flag in most of these functions. If this is set to False, these functions return date only, omitting the time.
First, a function that takes an nf1 object and identifies what datetype it uses. This presumes the same date format is used consistently through the nf1 object.
SternDateTypeNF1[nf1obj_] :=
Module[{cf, testingpiece}, testingpiece = nf1obj[[2]][[1]][[1]];
cf = "sorry, no match."; If[VectorQ[testingpiece], cf = "M"];
If[IntegerQ[testingpiece], cf = "MI"];
If[Head[testingpiece] === String, cf = "C"]; cf]
SternDateTypeNF1::usage =
"SternDateTypeNF1[nf1obj_]. This function takes an nf1 object with
dates in any format, and reports M, MI, or C, for Mathematica
{y,m,d,h,m,s}, Mathematica Integer (expressing seconds since January
1,1900), or Conventional (" <> FromCharacterCode[34] <> "1/31/2001" <>
FromCharacterCode[34] <> ").";
or like this, for non-NF1 dates
SternDateType[date_] :=
Module[{cf}, cf = "X"; If[VectorQ[date] , cf = "M"];
If[IntegerQ[date], cf = "MI"]; If[Head[date] === String, cf = "C"];
If[Not[cf == "M" || cf == "MI" || cf == "C"],
Message[SternDateType::"noformat"], cf]]
SternDateType::usage =
"SternDateType[date_]. This function takes a date in any format,
and reports what format it's in (C, M, or MI for conventional,
Mathematica, or Mathematica Integer).";
SternDateType::noformat =
"Can't figure out what format this date is in.";
Then a utility function that converts a date in any format to any specified other format.
Options[SternDateConversion] = {wtime -> True,
cFormat -> {"Month", "Day", "Year"}};
SternDateConversion[date_, targetformat_, OptionsPattern[]] :=
Module[{cf, conclusion, w}, cf = SternDateType[date];
w = OptionValue[wtime];
If[(targetformat == "M" || targetformat == "MI" ||
targetformat == "C"),
If[cf == "M" && targetformat == "MI",
conclusion = AbsoluteTime[date]];
If[cf == "C" && targetformat == "MI",
conclusion = AbsoluteTime[{date, OptionValue[cFormat]}]];
If[(cf == "M" || cf == "MI") && targetformat == "C",
conclusion =
If[w == False,
DateString[date, {"MonthShort", "/", "DayShort", "/", "Year"}],
DateString[
date, {"MonthShort", "/", "DayShort", "/", "Year", " - ",
"Hour12Short", ":", "Minute", " ", "AMPMLowerCase"}]]];
If[cf == "MI" && targetformat == "M",
conclusion = If[w, DateList[date], Take[DateList[date], 3]]];
If[cf == "C" && targetformat == "M",
conclusion =
If[w, DateList[{date, OptionValue[cFormat]}],
Take[DateList[{date, {"Month", "Day", "Year"}}], 3]]];
If[(cf == "M" && targetformat == "M") || (cf == "C" &&
targetformat == "C") || (cf == "MI" && targetformat == "MI"),
conclusion = date], Message[SternDateConversion::"badformat"]];
conclusion]
SternDateConversion::usage =
"SternDateConversion[date_,targetformat_,opts___]. This function
takes a date in any format, and an argument telling it what date
format to convert to, and converts the list of input dates to a list
of dates in the desired format. The formatting string must be M, MI,
or C, for Mathematica {y,m,d,h,m,s}, Mathematica Integer (expressing
seconds since January 1,1900), or Conventional (" <>
FromCharacterCode[34] <> "1/31/2001" <> FromCharacterCode[34] <>
"). Can set wtime flag to False to shut off time in conventional
dates. Use DateListConversion or DateDataPairConversionNF1 for
lists.";
SternDateConversion::badformat = "Target format must be M, MI, or C.";
And this does the same for NF1 objects.
Options[DateDataPairConversionNF1] = {wtime -> True,
cFormat -> {"Month", "Day", "Year"}};
DateDataPairConversionNF1[nf1obj_, targetformat_, OptionsPattern[]] :=
Module[{meat, head}, head = nf1obj[[1]];
meat = nf1obj[[2]]; {head,
Transpose[{Map[
SternDateConversion[#, targetformat,
wtime -> OptionValue[wtime],
cFormat -> OptionValue[cFormat]] &, Transpose[meat][[1]]],
Transpose[meat][[2]]}]}]
SternDateConversion::usage =
"SternDateConversion[date_,targetformat_,opts___]. This function
takes a date in any format, and an argument telling it what date
format to convert to, and converts the list of input dates to a list
of dates in the desired format. The formatting string must be M, MI,
or C, for Mathematica {y,m,d,h,m,s}, Mathematica Integer (expressing
seconds since January 1,1900), or Conventional (" <>
FromCharacterCode[34] <> "1/31/2001" <> FromCharacterCode[34] <>
"). Can set wtime flag to False to shut off time in conventional
dates. Use DateListConversion or DateDataPairConversionNF1 for
lists.";
Finally, a little function for selecting date ranges out of an NF1 object.
SternDateRangeNF1[nf1object_, earliestdate_,
lastdate_: "12/31/2099"] :=
If[(SternDateConversion[lastdate, "MI"]) > (SternDateConversion[
earliestdate, "MI"]), {nf1object[[1]],
Select[DateDataPairConversionNF1[nf1object, "MI"][[2]],
SternDateConversion[earliestdate, "MI"] <= #[[1]] <=
SternDateConversion[lastdate, "MI"] &]},(*
else give an error message *)
Message[SternDateRangeNF1::"badorder"]]
SternDateRangeNF1::usage =
"SternDateRangeNF1[nf1object_,earliestdate_,lastdate_]. This
function trims an NF1 object and returns the same object for a
specified date range. You provide the name of the object, the
earliest date to allow and, optionally, the last date to allow. The
latter will be set to 12/31/2099 if you don't provide anything
sooner. If dates are out of order when submitted, they will be out of
order when returned. There is no guarantee as to the date format the
object will be left in. Use DateDataPairConversionNF1[] if that
matters to you.";
SternDateRangeNF1::badorder = "The lastdate entered must come after the
earliestdate entered.";
For example, take the following data object
sampleNF1 = {{"nf1", "sample", "day", "value"}, {{"1/1/2007", 3},
{"1/2/2007", 3.1}, {"1/3/2007", 3.2}, {"1/4/2007", 3.3},
{"1/5/2007", 3.4}}};
We can convert it to other date formats now quite easily.
In[]:= DateDataPairConversionNF1[sampleNF1, "MI"]
Out[]= {{"nf1", "sample", "day", "value"}, {{3376598400, 3},
{3376684800, 3.1}, {3376771200, 3.2}, {3376857600, 3.3},
{3376944000, 3.4}}}
In[]:= DateDataPairConversionNF1[sampleNF1, "M", wtime -> False]
Out[]= {{"nf1", "sample", "day",
"value"}, {{{2007, 1, 1}, 3}, {{2007, 1, 2}, 3.1}, {{2007, 1, 3},
3.2}, {{2007, 1, 4}, 3.3}, {{2007, 1, 5}, 3.4}}}
A few more building blocks in coming days, then we will be able to begin useful work.
Nov 11, 2007
NF1 Objects
The best place to start is with data structures — what is the best way to represent dynamic data, including financial data, in Mathematica? Most people would probably start with a list of date/data pairs, like this
data1 = {{"1/1/2000", 37}, {"1/2/2000", 39}, {"1/3/2000", 33.2}};
This data can be graphed, curves can be fit against it, it can be compared to, or manipulated in light of, other datasets. Experience shows, however, that this simple data structure soon grows complicated. We quickly have data1, data2, data3, and various manipulated versions of them, and it can be a mess keeping track of which of these represent what. And, especially when generating tables of computed characteristics, or labelling graphs, it becomes onerous to attach the right name to the right data series.
Imagine, for example, that you have 20 series representing the prices of stocks over time, and a function that checks the growth rate of each. You want to run this function on each series and report back a table listing the name of each stock and its growth rate, in declining order of growth. There is no easy way to do this with the simple data structure shown above; we have the information needed to compute the growth rate, but we don't have the name of the stock.
The best solution is to eschew the simple list of data/data pairs, and to standardize on a slightly more complex data structure that includes the name of the data series and other information of use. The particular structure I use is called "nf1", and looks like this:
{{formatcode, name, timeunit, type}, {{date, data}, {date, data}, etc.}}
where formatcode, in this case -> "nf1", name -> "IBM US Equity", for example, timeunit -> "monthly", and type -> "pctchanges" (or "delta," or "value"). I have written code that converts data from all my usual data sources to this format, and some VBA that is useful for converting random spreadsheets sent to me by others.
Most of the sample code that will follow on these pages will assume that the data is in nf1 format.
Over the next few days, I will post some useful functions for working with nf1 objects, starting with nf1OK, which does a quick check of any variable passed to it, to see if it is a properly formatted nf1 object.
nf1OK[object_] :=
Module[{result = True},
If[Length[object] != 2, Message[nf1OK::"twoparts", Length[object]];
False, If[Length[object[[1]]] != 4,
Message[nf1OK::"headlengths", Length[object[[1]]]]; False,
If[object[[1]][[1]] != "nf1",
Message[nf1OK::"typecode", object[[1]][[1]]]; False,
If[! StringQ[object[[1]][[2]]],
Message[nf1OK::"nametype", object[[1]][[2]]]; False,
If[! MemberQ[StringMatchQ[{"year", "quarter", "monthly", "week", "day",
"hour", "minute", "second"}, object[[1]][[3]]], True],
Message[nf1OK::"timeunit", object[[1]][[3]]]; False,
If[! MemberQ[StringMatchQ[{"pctchanges", "delta", "value"}, object[[1]][[4]]], True],
Message[nf1OK::"timeunit", object[[1]][[4]]]; False,
If[MemberQ[(If[! (VectorQ[#1] && Length[#1] == 2), True, False] &) /@ object[[2]], True],
Message[nf1OK::"datalist", object[[1]][[2]]]; False,
If[Length[object[[2]]] < 1, Message[nf1OK::"datalength", object[[1]][[2]]]; False,
If[MemberQ[(Module[{std}, std = SternDateTypeNF1[object];
std?"M" && std?"MI" && std?"C"] &) /@ object, True],
Message[nf1OK::"dateformat", object[[1]][[2]]]; False,
If[MemberQ[Module[{std, datelist}, std = SternDateTypeNF1[object];
datelist = Transpose[object[[2]]][[1]];
If[std == "M", (If[Total[#1] == 0, True, False] &) /@ datelist];
If[std == "MI", (If[#1 < 1, True, False] &) /@ datelist];
If[std == "C", (If[StringLength[#1] < 1, True, False] &) /@ datelist]], True],
Message[nf1OK::"datecheck", object[[1]][[2]]]; False,
If[MemberQ[Module[{std, datelist}, std = SternDateTypeNF1[object];
datelist = Transpose[object[[2]]][[1]];
If[std == "M", (If[! VectorQ[#1], True, False] &) /@ datelist];
If[std == "MI", (If[! IntegerQ[#1], True, False] &) /@ datelist];
If[std == "C", (If[! Head[#1] == String, True, False] &) /@ datelist]], True],
Message[nf1OK::"nulldates", object[[1]][[2]]]; False,
If[MemberQ[(If[! NumericQ[#1], True, False] &) /@
Transpose[object[[2]]][[2]], True],
Message[nf1OK::"nulldata", object[[1]][[2]]], True]; True]]]]]]]]]]]]
It is always good form to include a functional definition.
nf1OK::usage = "nf1OK[object_] checks an object to see if it looks like a good NF1 object. Returns an error if it finds one, or True if all is well.";
And error messages appear below.
nf1OK::twoparts = "an NF1 object must have two parts. You have `1`.";
nf1OK::typecode = "The typecode of this object reads `1` rather than nf1.";
nf1OK::headlengths = "the header to an NF1 object must have four parts. You have `1`.";
nf1OK::nametype = "The name of this object needs to be a string. You have `1`.";
nf1OK::timeunit = "The timeunit needs to be year, quarter, monthly, week, day, hour, minute, or second. You have `1`.";
nf1OK::seriestype = "The type needs to be pctchanges, delta, or value. You have `1`.";
nf1OK::datalength = "You need at least one date/data pair in object `1`.";
nf1OK::datecheck = "You can not have any dates of 0 in object `1`.";
nf1OK::nulldates = "You have malformed or null dates in object `1`.";
nf1OK::nulldata = "You can not have null or non-numeric data in object `1`.";
nf1OK::datalist = "There is problem with the list of date/data objects in object `1`.";
nf1OK::dateformat = "Can not figure out date format in object `1`.";
Advanced placement observation 1 — the "timeunit" code in the nf1 header has never proven to be useful, as every datapoint has a timestamp anyway. I can imagine scenarios in which it would be useful, but in practice I have never used it.
Advanced placement observation 2 — the code above depends on a function called "SternDateTypeNF1," which I have not yet explained. That will come soon.
Nov 09, 2007
Thus begins the first post:
“The combination of precise formulas with highly imprecise assumptions can be used to establish, or rather to justify, practically any value one wishes Calculus [gives] speculation the deceptive guise of investment.”
—Benjamin Graham, 1949

