Thanks to binaryworld.net, I was finally able to get a sql way to calculate XIRR. After 2 long hours of search I found this site and the logic as well as the code works perfectly well!
XIRR is a function in excel that calculates Internal Rate of Return based on payments/income over a period of time.
Without further ado, here is the code (a slightly modified version from
BinaryWorld.net. Happy XIRRing!
-- First, CREATE XIRR Table to store values
CREATE TABLE XIRRTempData(
amt float,
dt datetime,
guid varchar(128)
)
go
create function dbo.XIRR(
@d datetime,
@GUID varchar(128)
) returns decimal(18,10)
as
begin
/*
USAGE: select @IRR = dbo.xirr(null, guid)
select @IRR IRR, @IRR * 100 'IRR %'
Note: Leave the first parameter (date) null if you wish to see the XIRR calculated as
of the maximum date in the dataset provided else provide a specific date to see
the XIRR calculated as the given date.
Created By: Ankeet Shah
Created On: 7/16/2008
*/
IF @d is null
SELECT @d = max(d) from IncomeTable
declare @irrPrev float set @irrPrev = 0
declare @irr float set @irr = -0.1
declare @PresentValuePrev float
declare @PresentValue float
set @PresentValuePrev = ( select sum(amt) from XIRRTempData where guid = @GUID )
set @PresentValue = (select sum(amt/power(1e0+@irr,cast(dt-@d as float)/360)) from XIRRTempData where guid = @GUID )
while abs(@PresentValue) >= 0.0001
begin
declare @t float
set @t = @irrPrev
set @irrPrev = @irr
set @irr = @irr + (@t-@irr)*@PresentValue/(@PresentValue-@PresentValuePrev)
set @PresentValuePrev = @PresentValue
set @PresentValue = (select sum(amt/power(1e0+@irr,cast(dt-@d as float)/365)) from XIRRTempData where guid = @GUID )
end
return @irr
end
go
-- usage
-- generate a GUID
DECLARE @GUID varchar(128)
SELECT @GUID = NewID()
-- INSERT test data INTO XIRRTempData table
insert into XIRRTempData values(-50000, '1/1/2007', @GUID)
insert into XIRRTempData values(500, '1/10/2007', @GUID)
insert into XIRRTempData values(500, '6/1/2007', @GUID)
insert into XIRRTempData values(500, '10/25/2007', @GUID)
insert into XIRRTempData values(500, '12/31/2007', @GUID)
insert into XIRRTempData values(500, '3/1/2008', @GUID)
insert into XIRRTempData values(51000, '6/15/2008', @GUID)
declare @IRR numeric(18,10)
select @IRR = dbo.xirr(getdate(), @GUID)
select @IRR IRR, @IRR * 100 'IRR %', getdate() DateParam
select @IRR = dbo.xirr(null, @GUID)
select @IRR IRR, @IRR * 100 'IRR %', NULL DateParam
go
drop function xirr
go
select * from XIRRTempData
go
drop table XIRRTempData
go
XIRR is a function in excel that calculates Internal Rate of Return based on payments/income over a period of time.
Without further ado, here is the code (a slightly modified version from
BinaryWorld.net. Happy XIRRing!
-- First, CREATE XIRR Table to store values
CREATE TABLE XIRRTempData(
amt float,
dt datetime,
guid varchar(128)
)
go
create function dbo.XIRR(
@d datetime,
@GUID varchar(128)
) returns decimal(18,10)
as
begin
/*
USAGE: select @IRR = dbo.xirr(null, guid)
select @IRR IRR, @IRR * 100 'IRR %'
Note: Leave the first parameter (date) null if you wish to see the XIRR calculated as
of the maximum date in the dataset provided else provide a specific date to see
the XIRR calculated as the given date.
Created By: Ankeet Shah
Created On: 7/16/2008
*/
IF @d is null
SELECT @d = max(d) from IncomeTable
declare @irrPrev float set @irrPrev = 0
declare @irr float set @irr = -0.1
declare @PresentValuePrev float
declare @PresentValue float
set @PresentValuePrev = ( select sum(amt) from XIRRTempData where guid = @GUID )
set @PresentValue = (select sum(amt/power(1e0+@irr,cast(dt-@d as float)/360)) from XIRRTempData where guid = @GUID )
while abs(@PresentValue) >= 0.0001
begin
declare @t float
set @t = @irrPrev
set @irrPrev = @irr
set @irr = @irr + (@t-@irr)*@PresentValue/(@PresentValue-@PresentValuePrev)
set @PresentValuePrev = @PresentValue
set @PresentValue = (select sum(amt/power(1e0+@irr,cast(dt-@d as float)/365)) from XIRRTempData where guid = @GUID )
end
return @irr
end
go
-- usage
-- generate a GUID
DECLARE @GUID varchar(128)
SELECT @GUID = NewID()
-- INSERT test data INTO XIRRTempData table
insert into XIRRTempData values(-50000, '1/1/2007', @GUID)
insert into XIRRTempData values(500, '1/10/2007', @GUID)
insert into XIRRTempData values(500, '6/1/2007', @GUID)
insert into XIRRTempData values(500, '10/25/2007', @GUID)
insert into XIRRTempData values(500, '12/31/2007', @GUID)
insert into XIRRTempData values(500, '3/1/2008', @GUID)
insert into XIRRTempData values(51000, '6/15/2008', @GUID)
declare @IRR numeric(18,10)
select @IRR = dbo.xirr(getdate(), @GUID)
select @IRR IRR, @IRR * 100 'IRR %', getdate() DateParam
select @IRR = dbo.xirr(null, @GUID)
select @IRR IRR, @IRR * 100 'IRR %', NULL DateParam
go
drop function xirr
go
select * from XIRRTempData
go
drop table XIRRTempData
go
Comments
This was very important to my life.
Congratulations. This Script is the best.
your sql is very well.
if @irr <=-1 set @irr = -0.99
You can reproduce this problem with those cash flows
-26,883,795.46 30/11/2008
27,940.57 01/12/2008
-25,473.85 11/12/2008
2,633,082.89 18/12/2008
2,732,488.24 19/12/2008
2,000,832.57 22/12/2008
674,658.57 23/12/2008
12,753,228.03 31/12/2008
Cheers,
Jacques
if Object_ID('tempdb..#IncomeTable') is not null drop table #IncomeTable
CREATE TABLE #IncomeTable(
CashFlows float,
DataDate smalldatetime
)
truncate table #IncomeTable
insert into #IncomeTable values(-26883795.46, '30 Nov 2008')
insert into #IncomeTable values(27940.57, '01 Dec 2008')
insert into #IncomeTable values(-25473.85, '11 Dec 2008')
insert into #IncomeTable values(2633082.89, '18 Dec 2008')
insert into #IncomeTable values(2732488.24, '19 Dec 2008')
insert into #IncomeTable values(2000832.57, '22 Dec 2008')
insert into #IncomeTable values(674658.57, '23 Dec 2008')
insert into #IncomeTable values(12753228.03, '31 Dec 2008')
declare @DataDate smalldatetime
select @DataDate = min(DataDate) from #IncomeTable
declare @irrPrev float set @irrPrev = 0
declare @irr float set @irr = -0.1
declare @PresentValuePrev float
declare @PresentValue float
set @PresentValuePrev = ( select sum(CashFlows) from #IncomeTable)
set @PresentValue = (select sum(CashFlows/power(1e0+@irr,cast(DataDate-@DataDate as float)/360)) from #IncomeTable)
while abs(@PresentValue) >= 0.0001
begin
declare @t float
set @t = @irrPrev
set @irrPrev = @irr
set @irr = @irr + (@t-@irr) * @PresentValue / (@PresentValue-@PresentValuePrev)
if @irr <=-1 set @irr = -0.99999
set @PresentValuePrev = @PresentValue
set @PresentValue = (select sum(CashFlows/power(1+@irr,cast(DataDate-@DataDate as float)/365)) from #IncomeTable)
end
print 'Result:'
select @IRR IRR, cast(ROUND(@IRR,6) * 100 as decimal(18,4)) 'IRR %'
my previousvalue and current previous value are the same making the
set @irr = @irr + (@t-@irr) * @PresentValue / (@PresentValue-@PresentValuePrev) throw the divide by zero error. Any extra fix anyone can suggest?
WHILE abs(@PresentValue) >= 0.01
begin
declare @t float
set @t = @irrPrev
set @irrPrev = @irr
set @irr = @irr + (@t-@irr) * @PresentValue / case when @PresentValue-@PresentValuePrev = 0 then 0.0025 else @PresentValue-@PresentValuePrev end
SELECT @irr
if @irr <=-1 set @irr = -0.99999
set @PresentValuePrev = @PresentValue
set @PresentValue = (select sum(Value/power(1+@irr,cast(Date-@DataDate as float)/365)) from #XIRR)
end
Can you post the suggested fix?
If the xirr calculation definition is (valuerange,daterange, error)
what part of the sql algorithm defines the error value. I need to be able to specify the error as a +- value.
Arithmetic overflow error converting expression to data type float.
Thanks!
PS my advise: if you see an overflow, try to change a sing near var @irr to the string:
was:
set @irr = -0.1
need:
set @irr = 0.1
-------------------
Create FUNCTION [dbo].[XIRR]
(@CF [CashFlowTable] READONLY)
RETURNS decimal(18,10)
AS
BEGIN
declare @d date
SELECT @d = max(PayDate) from @CF
declare @irrPrev float set @irrPrev = 0
declare @irr float set @irr = 0.1
declare @PresentValuePrev float
declare @PresentValue float
declare @Cnt int = 0
declare @t float
declare @delta float
if (select count(*) FROM @CF) < 2 return NULL --Bad (too short) cashflow
set @PresentValuePrev = ( select sum(Payment) from @CF)
set @PresentValue = (select sum(Payment/power(1e0+@irr,DateDiff(day,@d,PayDate)/365.0)) from @CF)
while abs(@PresentValue) >= 0.0001 and @Cnt < 100
begin
set @t = @irrPrev
set @irrPrev = @irr
select @Delta = @PresentValue-@PresentValuePrev
-- If this method does not converge, we use ALTERNATHIVE slow biPart method (named XIRR1)
if @Delta = 0 set @irr = dbo.XIRR1(@CF)
else set @irr = @irr + (@t-@irr)*@PresentValue/@delta
--we limit the allowable calculated rate from -1 to 3
if @irr <=-1 set @irr = -0.99999 --To avoin ERROR in power function
if @irr >=3 set @irr = 3 --To avoin OVERFLOW in power function
set @PresentValuePrev = @PresentValue
set @PresentValue = (select sum(Payment/power(1e0+@irr,DateDiff(day,@d,PayDate)/365.0)) from @CF)
set @Cnt += 1
end
if @Cnt = 100 select @irr = null --If Cnt=100 we do not trust the result
return @irr
----------
Thanks the Blogger again!
do you mind sharing the alternative slow biPart method XIRR1?
Web design Training in Chennai
Web design Training in Velachery
Web design Training in Tambaram
Web design Training in Porur
Web design Training in Omr
Web design Training in Annanagar
java online training hyderabad
java online training hyderabad
Microstrategy Training
Microstrategy Online Training
micro strategy certification training
msbi course training
spring boot certification course training