October 19, 2010 at 7:17 am
Hi,
I'm looking for a query on some financial data to show me the largest drawdown that has occurred on an account. Drawdown is defined as the largest percent loss in equity that has happened over a series of trades.
I do not have the account balances, but I do have the percent change on the account per trade. From this the largest drawdown in percentage terms should be able to be calculated. The data I have is tradeId, market, tradeDateTime, pctAccountChange. In the sample data below, you could seed the account with any number and at the end get a final account balance. So, if we start with $1,000 we could see the final balance being $1,607.16. I need a query that returns the largest drawdown in terms of percentage of the account. You could think of it as the largest percentage decrease in the account equity from a peak to a trough. I hope this makes sense.
Here's the sample data and table definition. You can ignore the market as that's not relevant, really.
-- schema
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[accountPerformance]') AND type in (N'U'))
DROP TABLE [dbo].[accountPerformance]
GO
CREATE TABLE [dbo].[accountPerformance](
[tradeId] [int] NOT NULL,
[market] [char](6) NOT NULL,
[tradeDateTime] [datetime] NOT NULL,
[pctAccountChange] [decimal](38, 6) NOT NULL,
CONSTRAINT [PK_accountPerformance] PRIMARY KEY NONCLUSTERED
([tradeId] ASC)
)
GO
CREATE CLUSTERED INDEX [CIX_accountPerformance] ON [dbo].[accountPerformance]
(
[tradeDateTime] ASC
)
GO
-- sample data
set nocount on
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(17,'GBPUSD','Jan 7 2010 11:00:00:000AM',-2.000000)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(18,'GBPUSD','Jan 18 2010 3:00:00:000AM',0.275862)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(1,'EURUSD','Jan 19 2010 11:00:00:000AM',1.375000)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(39,'USDJPY','Jan 27 2010 3:00:00:000AM',-0.987500)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(40,'USDJPY','Jan 28 2010 3:00:00:000PM',-1.154638)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(2,'EURUSD','Jan 28 2010 3:00:00:000PM',0.697986)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(19,'GBPUSD','Jan 29 2010 11:00:00:000AM',1.379746)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(3,'EURUSD','Feb 3 2010 7:00:00:000PM',3.764226)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(20,'GBPUSD','Feb 4 2010 7:00:00:000AM',2.225988)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(41,'USDJPY','Feb 10 2010 11:00:00:000AM',-1.588234)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(4,'EURUSD','Feb 15 2010 7:00:00:000AM',-0.326796)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(42,'USDJPY','Feb 17 2010 7:00:00:000AM',1.422534)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(21,'GBPUSD','Feb 18 2010 7:00:00:000AM',2.426966)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(23,'GBPUSD','Feb 23 2010 7:00:00:000AM',1.074074)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(5,'EURUSD','Feb 23 2010 3:00:00:000PM',1.168674)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(43,'USDJPY','Feb 25 2010 3:00:00:000AM',0.797752)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(44,'USDJPY','Mar 12 2010 3:00:00:000AM',-0.333332)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(6,'EURUSD','Mar 19 2010 3:00:00:000PM',0.418438)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(24,'GBPUSD','Mar 23 2010 7:00:00:000AM',2.473684)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(45,'USDJPY','Mar 24 2010 11:00:00:000AM',2.290000)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(7,'EURUSD','Apr 6 2010 4:00:00:000AM',2.153846)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(25,'GBPUSD','Apr 13 2010 7:00:00:000AM',0.100718)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(26,'GBPUSD','Apr 20 2010 7:00:00:000AM',-0.804122)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(8,'EURUSD','Apr 20 2010 3:00:00:000PM',3.047058)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(27,'GBPUSD','Apr 26 2010 7:00:00:000AM',-0.639784)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(9,'EURUSD','Apr 27 2010 3:00:00:000PM',1.128000)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(28,'GBPUSD','May 3 2010 3:00:00:000AM',8.554544)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(10,'EURUSD','May 3 2010 7:00:00:000AM',8.530000)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(11,'EURUSD','May 11 2010 11:00:00:000PM',2.570652)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(29,'GBPUSD','Jun 8 2010 11:00:00:000AM',-0.901162)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(30,'GBPUSD','Jun 18 2010 7:00:00:000AM',-0.681318)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(31,'GBPUSD','Jun 25 2010 3:00:00:000PM',0.642156)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(12,'EURUSD','Jul 5 2010 7:00:00:000AM',-0.145250)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(13,'EURUSD','Jul 7 2010 11:00:00:000PM',-1.012578)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(14,'EURUSD','Jul 14 2010 7:00:00:000PM',2.636362)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(32,'GBPUSD','Jul 23 2010 3:00:00:000AM',5.094488)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(15,'EURUSD','Aug 20 2010 7:00:00:000AM',-0.919354)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(33,'GBPUSD','Aug 31 2010 11:00:00:000AM',-0.914634)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(35,'GBPUSD','Sep 7 2010 11:00:00:000AM',-0.657894)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(16,'EURUSD','Sep 8 2010 7:00:00:000AM',-0.711538)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(36,'GBPUSD','Sep 15 2010 11:00:00:000AM',0.563492)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(38,'EURUSD','Sep 15 2010 3:00:00:000PM',4.675976)
INSERT INTO [accountPerformance] ([tradeId],[market],[tradeDateTime],[pctAccountChange])VALUES(37,'GBPUSD','Sep 23 2010 3:00:00:000PM',1.112582)
Thanks very much for looking at this. It is greatly appreciated.
October 19, 2010 at 7:27 am
Hi keymoo,
Am I right in assuming that the drawdown has to be continuous series of down movements? E.g if the value went:
10,9,8,7,8,7,6,5,4,5
Then I have two drawdowns, one from 10 to 7 and one from 8 to 4?
Also, what about simultaneous trades? Do these net off? E.g. TradeId 40 & 2
Regards, Iain
October 19, 2010 at 8:22 am
Hi Iain,
I guess to work out the drawdown you could calculate highs in equity - lows in equity and then look for the largest one and that would be the largest drawdown. For example in this image
Editor: Image from http://www.myforexresults.com/article.php?product_id=2
the max drawdown is illustrated. Does that help explain it? Another way is to take the sample data I provided and put it into excel and create a new column called balance and then calculate the balance after each trade. Then create a chart of datetime against balance and you can see the ups and downs in the account very clearly. I want to show, using T-SQL, the biggest downward trend in the account expressed in terms of percent of account equity.
I know it's a bit complicated, hence my troubles with it. Here's the equity curve for the sample data I provided with a start balance of $1,000.
October 19, 2010 at 8:59 am
Hi,
The following should help, I think. That said, the final query to return the result looks quite inefficient (triangular join?) - there's probably a smarter way. I'll keep playing around to see if I can find something better...
-- aggregate the simultaneous tx
-- not sure if this is required but seems logical
select row_number() over (order by tradedatetime) as Row_Order
, tradedatetime, sum(pctaccountchange) as net_pctChange
into dbo.grouped_Performance
from dbo.accountPerformance
group by tradedatetime
-- cluster on row order
create clustered index ix_row_order on dbo.grouped_Performance(Row_Order)
-- add column for running sum
-- not sure about required level of precision
-- but pct change is 6dp so use that
alter table dbo.grouped_Performance
add Running_Sum decimal(38,6)
-- do the quirky update as per Lutz
-- define some variable to help with the quirky update
DECLARE
@runningValue [DECIMAL](38, 6), -- to hold the intermediate values
@Sequence INT -- verify that the Sequence of processing is the same as the reqired Sequence
SET @Sequence=0
SET @runningValue=1000
;
WITH SafeTable AS
(
SELECT net_pctChange
, Row_Order AS Sequence -- define the sequence for data processing
, Running_Sum
FROM dbo.grouped_Performance
)
UPDATE SafeTable
SET
@Sequence = CASE WHEN Sequence = @Sequence + 1 THEN Sequence ELSE 1/0 END, -- double check to be still in sync
@runningValue = (@runningValue*(1+net_pctChange/100)), -- calculate the new value
Running_Sum = @runningValue -- store the value in the requested format
FROM SafeTable
WITH (TABLOCKX) -- IMPORTANT: lock the table to avoid data changes in between
OPTION (MAXDOP 1); -- avoid parallel processing to ensure proper sequence
-- calculate the max drop for each data point
-- by looking for the lowest value in running sum
-- that is dated after the current row's date
select *
, (select min(Running_Sum) from dbo.grouped_Performance
where Row_Order > gp.Row_Order
and Running_Sum < gp.Running_Sum) as min_future_low
, Running_Sum - (select min(Running_Sum) from dbo.grouped_Performance
where Row_Order > gp.Row_Order
and Running_Sum < gp.Running_Sum) as total_drop
October 19, 2010 at 9:11 am
This looks a bit better on plan and will also let you return the dates:
select gpa.*, gpb.*
, gpa.Running_Sum - gpb.Running_Sum as diff
from dbo.grouped_Performance gpa
join dbo.grouped_Performance gpb
on gpa.Row_Order < gpb.Row_Order
and gpa.Running_Sum > gpb.Running_Sum
order by gpa.Row_Order
October 20, 2010 at 2:17 am
Hi Iain,
Thanks for looking at this but it's not returning what I'm after. I want to find the largest peak to valley drop in percentage value of the account, so it should return just one value. I'm not really sure what your query is returning to me. Would you mind having another look?
For example in this image the yellow boxes highlight the drawdown areas. I am looking for the largest drawdown area, but done in T-SQL. 😉
Thanks
October 20, 2010 at 4:24 am
Hi keymoo,
The query posted gives all drops over the period. You should read each result as:
Between the tradedatetime in column 6 and the tradedatetime in column 2, the value of the account dropped by the amount in the diff column.
So, to get the maximum, all you need to do is select the maximum value of [diff]:
;with cte as (
select gpa.Running_Sum - gpb.Running_Sum as diff
from dbo.grouped_Performance gpa
join dbo.grouped_Performance gpb
on gpa.Row_Order < gpb.Row_Order
and gpa.Running_Sum > gpb.Running_Sum
)
select max(diff) from cte
If you also want to return the dates for the period of the drop, you can use:
;with cte as (
select gpa.tradedatetime as start_tradedatetime
, gpb.tradedatetime as end_tradedatetime
, gpa.Running_Sum - gpb.Running_Sum as diff
from dbo.grouped_Performance gpa
join dbo.grouped_Performance gpb
on gpa.Row_Order < gpb.Row_Order
and gpa.Running_Sum > gpb.Running_Sum
)
select top 1 * from cte
order by diff desc
Regards, Iain
October 20, 2010 at 4:39 am
Actually, having re-read your original post, you're looking for the biggest percentage drop, not absolute value drop. To get that, use the following:
;with cte as (
select gpa.tradedatetime as start_tradedatetime
, gpb.tradedatetime as end_tradedatetime
, gpa.Running_Sum - gpb.Running_Sum as diff
, (gpa.Running_Sum - gpb.Running_Sum) / gpa.Running_Sum * 100 as pct_drop
from dbo.grouped_Performance gpa
join dbo.grouped_Performance gpb
on gpa.Row_Order < gpb.Row_Order
and gpa.Running_Sum > gpb.Running_Sum
)
select top 1 * from cte
order by pct_drop desc
October 20, 2010 at 7:02 am
Thanks very much Iain, absolutely perfect! 🙂
Viewing 9 posts - 1 through 8 (of 8 total)
You must be logged in to reply to this topic. Login to reply