views:

105

answers:

5
A: 

For SQL Server and for one stock at a time, try this:

Create Procedure 'MDDCalc'(
    @StartDate date,
    @EndDate date,
    @Stock int)

AS

DECLARE @MinVal Int
DECLARE @MaxVal Int
DECLARE @MaxDate date

SET @MaxVal = (
    SELECT MAX(hi)
    FROM Table
    WHERE Stockid = @Stock 
    AND Day BETWEEN (@Startdate-1) AND (@EndDate+1))

SET @MaxDate=(
    SELECT Min(Date)
    FROM Table
    WHERE Stockid = @Stock
    AND hi = @MaxVal)

SET @MinVal = (
    SELECT MIN(low)
    FROM Table
    WHERE Stockid = @Stock 
    AND Day BETWEEN (@MaxDate-1) AND (@EndDate+1))

SELECT  (@MaxVal-@MinVal) AS 'MDD'
JNK
this is a good start, but the problem is that the maxval could occur after minval. This wouldn't be considered a max drawdown because max drawdown is defined as the value of the hypothetical loss if the stock is bought at it's highest buy point and sold at it's lows sell point. So, a stock can't be sold at a minval that came before a maxval.
Ramy
OK let me modify, be a minute or 5.
JNK
@Ramy - OK, edited to only use data AFTER the FIRST occurence of the max value.
JNK
This is getting closer I think. But the problem is that the global max isn't necessarily needed for the calculation of MDD. Let me try and explain with a verbal example. Say the price's global max is 100 and say it occurs at time t-5. now, say the price goes up to price 99 at time t-10. If the price at t-7 drops to say, 5, but only drops to say, 98, a t, the this would return the max draw down as 2, whereas the max drawdown is actually 94 - because it dropped so much further from the second highest price in the example.
Ramy
@Ramy - I understand what you are saying and that does greatly complicate things :) I'm not sure if SQL is the place to do this calculation. It almost seems like it would be easier to store all your max and min values in an array and then perform some analysis on the array.
JNK
@JNK, I agree. It DOES greatly complicate things. I think the whole problem turns on a way to find all those min's and max's....
Ramy
+2  A: 

Brutally inefficient, but very simple version using a view is below:

WITH DDView
AS (SELECT      pd_curr.StockID,
                pd_curr.Date,
                pd_curr.Low_Price  AS CurrPrice,
                pd_prev.High_Price AS PrevPrice,
                pd_curr.Low_Price / pd_prev.High_Price - 1.0 AS DD

    FROM        PriceData pd_curr
    INNER JOIN  PriceData pd_prev
            ON  pd_curr.StockID = pd_prev.StockID
            AND pd_curr.Date >= pd_prev.Date
            AND pd_curr.Low_Price <= pd_prev.High_Price
            AND pd_prev.Date >= '2001-12-31' -- @param: min_date of analyzed period
    WHERE       pd_curr.Date <= '2010-09-31' -- @param: max_date of analyzed period
)
SELECT      dd.StockID,
            MIN(COALESCE(dd.DD, 0)) AS MaxDrawDown
FROM        DDView dd
GROUP BY    dd.StockID

As usually you would perform the analysis on specific time period, it would make sense to wrap the query in a stored procedure with the parameters @StartDate, @EndDate and possibly @StockID. Again, this is quite inefficient by design - O(N^2), but if you have good indices and not huge amount of data, SQL Server will handle it pretty good.

van
@van, not sure why you compare the current low to the previous high. Think of the lows as being one graph over time and the highs being another graph over time. The max's would be the peaks of the graph of highs while the min's would be the valleys of the graph of the lows. I'm not sure that this is quite accurate.
Ramy
@Remy. If you have an investment vehicle, where the client is able to invest at end-of-day NAV (net asset value), then you do not have LOW/HIGH prices at all, but rather one CLOSE price, in which case you would use it to compute MDD. If you provide LOW and HIGH prices, I assume those are the min and max prices for the day, in which case the MDD is the "most unlucky timing" for the investment, and in this case it is "intraday", so if very unlucky, you might have invested at HIGH price.
van
A: 

Is this what you're after?

select StockID,max(drawdown) maxdrawdown
from (
select h.StockID,h.day highdate,l.day lowdate,h.hi - l.lo drawdown
from mdd h
  inner join mdd l on h.StockID = l.StockID
    and h.day<l.day) x
group by StockID;

It's a SQL based brute force approach. It compares every low price after today's hi price within the same stock and finds the greatest difference between the two prices. This will be the Maximum Draw Down.

It doesn't compare consider the same day as possible for maximum draw down as we don't have enough info in the table to determine if the Hi price happened before the Lo price on the day.

Craig
A: 

Some things we need to consider in the problem domain:

  1. Stocks have a range of prices every day, often viewed in candlestick charts
  2. lets call the highest price of a day HI
  3. lets call the lowest price of a day LOW
  4. the problem is constrained by time, even if the time constraints are the IPO date and Delisting Dates
  5. the maximum drawdown is the most you could possibly lose on a stock over that timeframe
  6. assuming a LONG strategy: logically if we are able to determine all local maxes (MAXES) and all local mins (MINS) we could define a set of where we pair each MAX with each subsequent MIN and calculate the difference DIFFS
  7. Sometimes the difference will result in a negative number, however that is not a drawdown
  8. therefore, we need to select append 0 in the set of diffs and select the max

The problem lies in defining the MAXES and the MINS, with the function of the curve we could apply calculus, bummer we can't. Obviously

  1. the maxes need to come from the HI and
  2. the MINS need to come from the LOW

One way to solve this is to define a cursor and brute force it. Functional languages have nice toolsets for solving this as well.

akaphenom
A: 

Here is a SQL Server 2005 user-defined function that should return the correct answer for a single stockid very efficiently

CREATE FUNCTION dbo.StockMaxDD(@StockID int, @day datetime) RETURNS int  AS
 BEGIN
    Declare @MaxVal int;    Set @MaxVal = 0;
    Declare @MaxDD int;     Set @MaxDD = 0;

    SELECT TOP(99999)
        @MaxDD  = CASE WHEN @MaxDD < (@MaxVal-low) THEN (@MaxVal-low)  ELSE @MaxDD END,
        @MaxVal = CASE WHEN hi  > @MaxVal THEN hi   ELSE @MaxVal END
    FROM    StockHiLo
    WHERE   stockid = @Stockid
      AND   [day]  <= @day
    ORDER BY [day] ASC

    RETURN @MaxDD;
 END

This would not, however, be very efficient for doing a number of stockids at the same time. If you need to do many/all of the stockids at once, then there is a similar, but substantially more difficult approach that can do that very efficiently.

RBarryYoung