I am trying to come up with an alternative to using the "Quirky Update" using windowing and CTE for a somewhat complex calculation of "running performance". The quick math for running performance is ((1 + Running) * (1 + Daily)) - 1. This running needs to be updated for the current row and then used in the next rows calculation. It resets to 0 when the AssetID changes. The only alternative I have found is using exp(sum(log)) (shown in code sample) and though numbers are correct in the real world it is too slow to use, where using Quirky Update is near instantaneous. In my sample I have windowing replacing the Daily calculation, but because the running needs to update the current row and have that reflected in the next rows calculation, I couldn't think of a way using windowing and CTE to handle the running calculation. So my attempt is with Variables in the update statement similar to Quirky Update and they are wrong. Maybe there is a way similar to how I am calculating daily? Thanks in advance, Robb
if OBJECT_ID('tempdb..#DMVRunningPerformance') is not null
drop table #DMVRunningPerformance
create table #DMVRunningPerformance (
LongPosition_bt bit null,
AssetID_in int null,
Symbol_vc varchar(255),
DayID_in int,
Status_vc varchar(255) null,
MV float,
DailyPerf float null,
RunningPerf float null)
CREATE CLUSTERED INDEX [indexdown] ON #DMVRunningPerformance
(
[LongPosition_bt] ASC,
[AssetID_in] ASC,
[DayID_in] DESC
)
WITH (SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
insert into #DMVRunningPerformance
values (1,100,'IBM',75006,NULL,201048.8987,NULL,NULL)
insert into #DMVRunningPerformance
values (1,100,'IBM',75005,NULL,200841.5658,NULL,NULL)
insert into #DMVRunningPerformance
values (1,100,'IBM',75004,NULL,200321.7043,NULL,NULL)
insert into #DMVRunningPerformance
values (1,100,'IBM',75003,NULL,201120.0467,NULL,NULL)
insert into #DMVRunningPerformance
values (1,100,'IBM',75002,NULL,201779.8805,NULL,NULL)
insert into #DMVRunningPerformance
values (1,100,'IBM',75001,NULL,201651.3917,NULL,NULL)
insert into #DMVRunningPerformance
values (1,100,'IBM',75000,NULL,201101.0320,NULL,NULL)
insert into #DMVRunningPerformance
values (1,200,'MSFT',75006,NULL,805048.8987,NULL,NULL)
insert into #DMVRunningPerformance
values (1,200,'MSFT',75005,NULL,801841.5658,NULL,NULL)
insert into #DMVRunningPerformance
values (1,200,'MSFT',75004,NULL,804321.7043,NULL,NULL)
insert into #DMVRunningPerformance
values (1,200,'MSFT',75003,NULL,809120.0467,NULL,NULL)
insert into #DMVRunningPerformance
values (1,200,'MSFT',75002,NULL,801779.8805,NULL,NULL)
insert into #DMVRunningPerformance
values (1,200,'MSFT',75001,NULL,801151.3917,NULL,NULL)
insert into #DMVRunningPerformance
values (1,200,'MSFT',75000,NULL,801901.0320,NULL,NULL)
------------------- BEGIN Quirky Update -------------------------------------
declare @RunningPerformance as float=0
declare @AssetID_in int=-1,
@DayID_in int=0,
@LongPosition_bt bit=0,
@Status_vc varchar(255)=NULL,
@NewGroup_bt bit=1,
@DailyPerformance float,
@NDMarketValue float,
@PDMarketValue float=0,
@NDSymbol_vc varchar(255)='XXXXXXXXXX',
@PDSymbol_vc varchar(255)='XXXXXXXXXX',
@NDDayID_in int,
@PDDayID_in int,
@NDAssetID_in int=-1,
@PDAssetID_in int=-1
update #DMVRunningPerformance
set
@LongPosition_bt=LongPosition_bt,
@NewGroup_bt=CASE WHEN @AssetID_in<>AssetID_in or @LongPosition_bt<>LongPosition_bt THEN 1 ELSE 0 END,
@DailyPerformance=CASE WHEN @NewGroup_bt=1 THEN 0 ELSE
(@NDMarketValue-MV)/MV END,
@RunningPerformance=CASE WHEN @NewGroup_bt=1 THEN 0 ELSE
((1 + @RunningPerformance) * (1 + @DailyPerformance)) - 1 END
,DailyPerf=@DailyPerformance
,RunningPerf=@RunningPerformance
,@AssetID_in=AssetID_in
,@NDAssetID_in=AssetID_in
,@NDSymbol_vc=Symbol_vc
,@NDDayID_in=DayID_in
,@NDMarketValue=MV
FROM #DMVRunningPerformance OPTION (MAXDOP 1)
select 'this data is correct'
select * from #DMVRunningPerformance
------------------- END Quirky Update -------------------------------------
-- reset performance
update #DMVRunningPerformance set DailyPerf=null, RunningPerf=NULL
------------------- BEGIN windowing and CTE -------------------------------------
-- update daily perf with CTE
;WITH dailyCTE as
(
select LongPosition_bt,AssetID_in,Symbol_vc,DayID_in,PreviousAssetID,
MV,
DailyPerformance = CASE WHEN YesterdayMV IS NULL THEN 0
ELSE (YesterdayMV - MV) / MV END
FROM
(
select p.LongPosition_bt,p.AssetID_in,p.Symbol_vc,p.DayID_in,
p.MV,
lag(p.MV,1) over (Partition by LongPosition_bt,AssetID_in
order by DayID_in desc) as YesterdayMV,
lag(p.AssetID_in,1) over (Partition by LongPosition_bt,AssetID_in
order by DayID_in desc) as PreviousAssetID
from #DMVRunningPerformance p
) p1
)
Update #DMVRunningPerformance
set DailyPerf=dailyCTE.DailyPerformance,
@NewGroup_bt=CASE WHEN PreviousAssetID is null THEN 1 ELSE 0 END,
@RunningPerformance=CASE WHEN PreviousAssetID is null THEN DailyPerformance ELSE
((1 + @RunningPerformance) * (1 + DailyPerformance)) - 1 END,
RunningPerf=@RunningPerformance
from dailyCTE join #DMVRunningPerformance d
on dailyCTE.LongPosition_bt=d.LongPosition_bt and dailyCTE.AssetID_in=d.AssetID_in and dailyCTE.DayID_in=d.DayID_in
select 'this data daily is correct, running is incorrect'
select * from #DMVRunningPerformance
-- alternate method for running perf
-- works but for realworld data takes orders of magnitude longer than quirky update
if 1=2
select
dr.AssetID_in,dr.LongPosition_bt,dr.DayID_in,dr.MV,dr.DailyPerf-1 as DailyPerformance,
RunningPerformance_fl = exp((
select
sum(log(DailyPerf))
from
#DMVRunningPerformance
where
AssetID_in=dr.AssetID_in and LongPosition_bt=dr.LongPosition_bt and
DayID_in >= DR.DayID_in)) - 1
from #DMVRunningPerformance dr
order by
dr.assetid_in,DR.DayID_in desc
------------------- END windowing and CTE -------------------------------------
drop table #DMVRunningPerformance