## Copyright (C) 2018 william wright <wrigh347 [at] umn [dot] edu>
## 
## This program is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## 
## This program is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see
## <https://www.gnu.org/licenses/>.

## -*- texinfo -*- 
## @deftypefn {Function File} {[@var{maxdd, maxdd_idx}] =} maxdrawdown (@var{data}) 
## @deftypefnx {Function File} {[@var{maxdd, maxdd_idx}] =} maxdrawdown (@var{data}, @var{formattype}) 
##
## Calculates the maximum drawdown for each column of price data (@var{data}).
## If given, @var{formattype} indicates which form of drawdown to use. 
## The default is "return" and is just the biggest percentage drawdown. 
## If "arithmetic" is given the function calculates the biggest drawdown 
## in terms of the units of the price data. The option "geometric" is the same
## as "arithmetic" except it is calculated on the log of the price data.
## @end deftypefn

function [maxdd, maxdd_idx] = maxdrawdown (data, formattype = "return")

## Check input and set the defaults 
if (nargin < 1 || nargin > 2) 
  print_usage (); 
elseif nargin < 2 
  formattype = "return"; 
endif

size_of_data = size (data);
if length (size_of_data) > 2
  error ("maxdrawdown: Invalid format for Data. Must be a vector or matrix");
elseif size_of_data(1) == 1
  if size_of_data(2) == 1
    error ("maxdrawdown: Invalid format for Data. Must be a vector or matrix");
  else  
    data = data';
  endif
endif 
 
switch (formattype)
  case "return", "geometric"
    if min (data) <= 0
      error ("maxdrawdown: data cannot have values <=0 for 'return' and 'geometric' formats");
    endif
  otherwise
## No problem  
endswitch
  
#if ((formattype == "return") || (formattype == "geometric")) && ( min (data) <= 0)
#    error (  "data cannot have values <=0 for 'return' and 'geometric' formats")
#endif

switch (formattype)
  case "return"
    [maxdd, D] = max (1 - (data ./ cummax (data))); # finds maxdd and index
  case "arithmetic"
    [maxdd, D] = max (cummax (data) - data);
  case "geometric"
## same as arithmetic except on log of the data
    data = log (data); 
    [maxdd, D] = max (cummax (data) - data);
  otherwise
    error ("maxdrawdown: invalid format");
endswitch

maxdd_idx = [maxdd; D];
for i = 1:length (D)
## check if there was no drawdown (index can't be 1)
  if (D(i) == 1) 
    maxdd_idx(1, i) = nan;
    maxdd_idx(2, i) = nan;
  else
    [E, maxdd_idx(1, i)] = max (data(1:D(i), i));   # find peak before maxdd
  endif
endfor

endfunction

## Tests 
%!shared d, e, f, g, h, i, a, b 
%! d = [1 1.25; 2 1.5; 1 1.75; 4 2; 8 2.25; 7 2.5; 6 2.75; 5 3]; 
%! e = [0.5 0];
%! f = [3 0];
%! g = [0.69315 0];
%! h = [2 NaN; 3 NaN]; 
%! i = [5 NaN; 8 NaN];
%! [a, b] = maxdrawdown (d); 
%!assert([a, b], [e, h], 0.0001) 
%! [a, b] = maxdrawdown (d, "arithmetic"); 
%!assert([a, b], [f, i], 0.0001) 
%! [a, b] = maxdrawdown (d, "geometric"); 
%!assert([a, b], [g, h], 0.0001) 
