calculating "Max Draw Down" in SQL
edit: it's worth reviewing the comments section of the first answer to get a clearer idea of the problem.
edit: I'm using SQLServer 2005
something similar to this was posted before but I don't think enough information was given by the poster to truly explain what max draw down is. All my definitions of max draw down come from (the first two pages of) this paper: http://www.stat.columbia.edu/~vecer/maxdrawdown3.pdf
effectively, you have a few terms defined mathematically:
Running Maximum, Mt
开发者_如何学PythonMt = maxu in [0,t] (Su)
where St is the price of a Stock, S, at time, t.
Drawdown, Dt
Dt = Mt - St
Max Draw Down, MDDt
MDDt = maxu in [0,t] (Du)
so, effectively what needs to be determined is the local maximums and minimums from a set of hi and low prices for a given stock over a period of time. I have a historical quote table with the following (relevant) columns:
stockid int day date hi int --this is in pennies low int --also in pennies
so for a given date range, you'll see the same stockid every day for that date range.
EDIT:
hi and low are high for the day and low for each day.once the local max's and min's are determined, you can pair every max with every min that comes after it and calculate the difference. From that set, the maximum difference would be the "Max Draw Down".
The hard part though, is finding those max's and min's.
edit: it should be noted: 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. A stock can't be sold at a minval that came before a maxval. so, if the global minval comes before the global maxval, those two values do not provide enough information to determine the max-drawdown.
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.
Some things we need to consider in the problem domain:
- Stocks have a range of prices every day, often viewed in candlestick charts
- lets call the highest price of a day HI
- lets call the lowest price of a day LOW
- the problem is constrained by time, even if the time constraints are the IPO date and Delisting Dates
- the maximum drawdown is the most you could possibly lose on a stock over that timeframe
- 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
- Sometimes the difference will result in a negative number, however that is not a drawdown
- 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
- the maxes need to come from the HI and
- 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.
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'
I have encounter this problem recently, My solution is like this: let data: 3,5,7,3,-1,3,-8,-3,0,10 add the sum one by one, if the sum is great than 0, set it 0, else get the sum, the result would be like this 0,0,0,0,-1,0,-8,-11,-11,-1 The Maximum draw down is the lowest value in the data, -11.
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.
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.
精彩评论