Skip to main content

Using SQL To Calculate XIRR (Internal Rate of Return)

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

Comments

Anonymous said…
Hello,

This was very important to my life.

Congratulations. This Script is the best.
Anonymous said…
tks,
your sql is very well.
Anonymous said…
I've been using your script with much pleasure. I just experienced an issue where the first IRR iteration returns a large negative value. Adding this line solves the problem.

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
Anonymous said…
Excellent post! With Jacques correction I can confirm that the results are as accurate or better than the Excel XIRR function.
Anonymous said…
just to summarize the above

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 %'
Blogger said…
Thanks Jacques and Anon for the corrections!
Anonymous said…
I am getting an error divide by zero during the calculation. ie
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?
Blogger said…
Anon, most likely @PresentValue and @PresentValuePrev have same value (as if you didnt know!).. i would just add a case statement around @PresentValue-@PresentValuePrev to check for zero value and return some very small value - so atleast it does not error out. Your final value will still be inaccurate though.
Anonymous said…
Thanks but then i seem to get stuck in an infinite loop. code below

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?
Anonymous said…
Hi

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.
Anonymous said…
My XIRR Function gets stuck with infinite while loop with some values.I am using same function . can anybody help me? thanks in avance..
Unknown said…
Hi i found this error in your function if you have any solution for same then reply .

Arithmetic overflow error converting expression to data type float.
Anonymous said…
This method is pretty much using the secant method right? How do you ensure that your solution always converges?

Thanks!
Anonymous said…
big thanks for Ankeet Shah!
Thank You So Much It Is Working
Unknown said…
very useful. Thank you for sharing. Waiting for other posts Microstrategy Online Training
Anonymous said…
Thansk a lot for fastest method!
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

Anonymous said…
Friend! It's me again (Anonymous ;-)) That's my updated function text:
-------------------
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!
Anonymous said…
Hi Anon

do you mind sharing the alternative slow biPart method XIRR1?
Devi said…
Thanks for sharing such a wonderful blog. oracle training in chennai
hema said…
Nice post. Thanks for sharing! I want people to know just how good this information is in your article. It’s interesting content and Great work.


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
Reach to the best Python Training institute in Chennai for skyrocketing your career, Infycle Technologies. It is the best Software Training & Placement institute in and around Chennai, that also gives the best placement training for personality tests, interview preparation, and mock interviews for leveling up the candidate's grades to a professional level.
Lavanya said…
with us to get some idea about that content.Thanks for sharing
Microstrategy Training
Microstrategy Online Training

The Popular Ones

Alternating Row Background Color For SSRS Matrix (Pivot Table)

I had a tough time to apply alternate row colors to a SSRS Matrix and finally figured out! Without further ado, here it is... Assume you have a matrix with more than 1, lets say 2 row groupings; RG1 and RG2. 1. Right-click on RG2 (innermost row group), and select "Insert Group"; for simplicity call it "RowColorGroup" 2. In the "Group On" section, add some constant value - for example ="" or ="ankeet" etc... you get the idea! 3. Select the newly created group "RowColorGroup" and enter the following in its "Value" property: =iif(RunningValue(Fields!RG1.Value & Fields!RG2.Value,CountDistinct,Nothing) Mod 2, "LightSteelBlue", "White") 4. Select the "BackgroundColor" property of "RowColorGroup" and enter "=Value" 5. Set the width of "RowColorGroup" to 0pt and "CanGrow" to false 6. Select the data cell(s) and set their "BackgroundColor" pro

cannot create a column accessor for OLE DB provider "ibmdasql" for linked server

I have a linked server from Microsoft SQL 2008 to a DB2 server. Today when I tried to run a SELECT statement based on the linked server, I hit this error, "cannot create a column accessor for OLE DB provider "ibmdasql" for linked server". Earlier in the day, we had restarted the SQL Server Service. Running the following script on the 'affected' sql server should fix the issue. USE [master] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE PROC [dbo].[usp_enum_oledb_providers] AS exec sp_enum_oledb_providers GO sp_procoption 'usp_enum_oledb_providers', 'startup', 1 Restart the sql server service after running above script.