from math import ceil,exp,floor,log,log10,modf,pow
from orangescape.model.engine.exception.Error import FormulaArgumentError
from orangescape.model.engine.exception.ErrorMessage import *
from orangescape.model.engine.util.DateUtil import xldate_as_tuple,XLDateNegative,XLDateTooLarge,XLDateAmbiguous
from orangescape.model.engine.execution.DateandTime import DATE,DAYS360,ISLEAPYEAR,YEARFRAC

def FV(argList):
    rate = argList[0]
    nper = argList[1]
    pmt = argList[2]
    if len(argList) > 3:
        pv = argList[3]
    else:
        pv = 0
    if len(argList) == 5:
        if argList[4] != 0:
            type = 1
        else:
            type = 0
    else:
        type = 0
    result = 0
    if (rate != 0):
        result = pmt*(1+rate*type)*((pow((1+rate),nper)-1)/rate);
        result = -( result + pv * (pow((1+rate),nper)) );
    else :
        result = -((pmt*nper) + pv);
    return result

def NPV(argList):
    rate=argList[0]
    i = len(argList)
    result = 0
    while i > 1:
         temp=argList[i-1]
         if (temp=='null'):
             temp=0.0
         result=result+ temp/(pow((1+rate),i-1));
         i-=1
    return result

def PMT(argList):
    rate = argList[0]
    nper = argList[1]
    if nper == 0:
        raise FormulaArgumentError,Engine2017 % (2,'PMT',0)
    pv = argList[2]
    if len(argList) > 3:
        fv = argList[3]
    else:
        fv = 0

    if len(argList) == 5:
        if argList[4] != 0:
            type = 1
        else:
            type = 0
    else:
       type = 0
    result = 0
    if (rate != 0):
        result = (pv*(pow((1+rate),nper)))+fv
        result = (-result)/((1+(rate*type))*((pow((1+rate),nper)-1)/rate))
    else:
        result=-(pv+fv)/nper;
    return result

def PV(argList):
    rate = argList[0]
    nper = argList[1]
    pmt = argList[2]
    if len(argList) > 3:
        fv = argList[3]
    else:
        fv = 0
    type=0
    if len(argList) == 5:
        if argList[4] != 0:
            type = 1
        else:
            type = 0
    else:
       type = 0

    if (rate!=0) :
        result=pmt*(1+(rate*type))*((pow((1+rate),nper)-1)/rate)
        result=(-(result+fv))/(pow((1+rate),nper))
    else:
        result=-((pmt*nper)+fv)
    return result

# Feb 13, 2009
# ------------
def log1p(x):
    """ Since log1p(x) = log(1+x) """
    return log(1+x)

#def pow1p(x,y):
#    """ (1+x)^y """
#    if (abs(x) > 0.5):
#        return pow(1+x,y)
#    else:
#        return exp(y*log1p(x))
#
#def pow1pm1(x,y):
#    """ ((1+x)^y)-1 """
#    if (x <= -1):
#        return pow(1+x,y)-1
#    else:
#        return expm1(y*log1p(x))

def pow1p(x, y):
    return pow(1+x, y)

def pow1pm1(x, y):
    """ Calculate ((1+x)^r)-1 accurately. """
    return pow(1+x, y)-1

def daysBetweenDates(dateserial1, dateserial2, basis):
    """ Source: http://koffice2.sourcearchive.com/documentation/1.9.98.5/helper_8cpp-source.html """
    isLeapYear = False
    dtTuple = xldate_as_tuple(dateserial1, 0)
    year1 = dtTuple[0]
    month1 = dtTuple[1]
    day1 = dtTuple[2]

    dtTuple = xldate_as_tuple(dateserial2, 0)
    year2 = dtTuple[0]
    month2 = dtTuple[1]
    day2 = dtTuple[2]

    years  = year2  - year1
    months = month2 - month1 + years * 12
    days   = day2 - day1

    isLeapYear = ISLEAPYEAR([year1])

    # Named Constants
    US_NASD30_360,ACTUAL_ACTUAL,ACTUAL_360,ACTUAL_365,EUROPEAN30_360=xrange(5)

    if basis==US_NASD30_360:
#        if ( month1 == 2 and month2 != 2 and year1 == year2 ):
#           if ( isLeapYear ):
#               print "Leap",months * 30 + days - 2
#               return months * 30 + days - 1
#           else:
#               print "Not Leap",months * 30 + days - 2
#               return months * 30 + days - 2
#        print "months * 30 + days",months * 30 + days
        return months * 30 + days
    elif basis==ACTUAL_ACTUAL:
        return dateserial2-dateserial1
        # TODO: real days for difference between months!
        # return ( month2 - month1 ) * 30 + years * 360 + days;
    elif basis==ACTUAL_360:
        return dateserial2-dateserial1
        # TODO: real days for difference between months!
        #return ( month2 - month1 ) * 30 + years * 365 + days;
    elif basis==ACTUAL_365:
       return dateserial2-dateserial1
    elif basis==EUROPEAN30_360:
        return months * 30 + days

def daysInYear(serialnumber,basis):
    year = xldate_as_tuple(serialnumber, 0)[0]    # Get YEar
    if ISLEAPYEAR([year]) and basis==1:
        return 366
    YearDays = [360,365,360,365,360]
    return YearDays[basis]

def daysInMonth(year,month):
    if month == 2 and ISLEAPYEAR(year):
        return 29
    monthdays=[0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    return monthdays[month]

def calcDuration(settlement, maturity, coup_, Yield_, freq, basis, numOfCoups):
    Yield = Yield_
    coup = coup_

    res = 0.0
    f100 = 100.0
    coup *= f100/freq
    Yield /= freq
    Yield += 1.0

    yearfrac = YEARFRAC([settlement, maturity, basis])
    diff = yearfrac*freq-numOfCoups
    diff=0.0

    for t in xrange(1, numOfCoups):
        res += (t+diff)*coup/pow(Yield,t+diff)

    res += (numOfCoups+diff)*(coup+f100)/pow(Yield,numOfCoups+diff)

    p = 0.0
    for t in xrange(1,numOfCoups):
        p += coup/pow(Yield,t+diff)

    p += (coup+f100)/pow(Yield,numOfCoups+diff)
    res /= p
    res /= float(freq)
    return res

"""
Function: ACCRINT(issue,first_interest,settlement,rate,[par],frequency,[basis])
"""
def ACCRINT(argList):
    try:
        """ Source: http://koffice2.sourcearchive.com/documentation/1.9.98.5/financial_8cpp-source.html """
        # If Dates are not valid return ERROR
        # If rate<=0 or par<=0 return #NUM Error
        # If basis < 0 or if basis > 4, ACCRINT returns the #NUM! error value.
        # If issue >= settlement, ACCRINT returns the #NUM! error value.
        issue = int(argList[0])
        firstInterest = int(argList[1])
        settlement = int(argList[2])
        if argList[0] >= argList[2]:  # (issue >= settlement)
            raise FormulaArgumentError,Engine2010 % ("issue>=settlement","ACCRINT")

        rate = argList[3]
        if rate<=0:    # Rate should be > 0
            raise FormulaArgumentError,Engine2009 % ("rate","ACCRINT")

        par = argList[4]
        if par==None: par=1000.0
        if par<=0:
            raise FormulaArgumentError,Engine2009 % ("par","ACCRINT")

        frequency = int(argList[5])
        if frequency not in (1,2,4):
            raise FormulaArgumentError,Engine2009 % ("frequency","ACCRINT")

        basis = 0        # Default is 0, if Basis omitted.
        if (len(argList) == 7):
            if argList[6]!=None:
                basis = int(argList[6])
                if (argList[6]<0 or basis>4):    # (basis<0 or basis>4)
                    raise FormulaArgumentError,Engine2009 % ("basis","ACCRINT")

        d = daysBetweenDates(issue, settlement, basis)
        y = daysInYear(issue, basis)
        if (d<0 or y<=0):
            raise FormulaArgumentError,Engine2009 % ("(issue, settlement, basis)","ACCRINT")

        return ((par*rate)/frequency)*(float(d)/y)*frequency

    except XLDateNegative, e:
        raise FormulaArgumentError, Engine2011 % ("ACCRINT")
    except XLDateTooLarge, e:
        raise FormulaArgumentError, Engine2008 % ("ACCRINT")
    except XLDateAmbiguous, e:
        raise FormulaArgumentError, Engine2021 % (e.args[0],"ACCRINT")
    except Exception, e:
        raise FormulaArgumentError, e

"""
Function: ACCRINTM(issue,settlement,rate,[par],[basis])
"""
def ACCRINTM(argList):
    try:
        """ Source: http://koffice2.sourcearchive.com/documentation/1.9.98.5/financial_8cpp-source.html """
        # If Dates are not valid return ERROR
        # If rate<=0 or par<=0 return #NUM Error
        # If basis < 0 or if basis > 4, ACCRINT returns the #NUM! error value.
        # If issue >= settlement, ACCRINT returns the #NUM! error value.

        issue = int(argList[0])
        settlement = int(argList[1])
        if issue > settlement:    # (issue > settlement)
            raise FormulaArgumentError,Engine2010 % ("issue>=settlement","ACCRINTM")

        rate = argList[2]
        if rate<=0:    # Rate should be > 0
            raise FormulaArgumentError,Engine2009 % ("rate","ACCRINTM")

        argsCount=len(argList)
        par = 1000.0                # Default is 1000, if Par omitted.
        if argsCount>3:
            if argList[3]!=None:
                par = argList[3]
                if par<=0:
                    raise FormulaArgumentError,Engine2009 % ("par","ACCRINTM")

        basis = 0                 # Default is 0, if Basis omitted.
        if (argsCount>4):
            if argList[4] != None :
                basis = int(argList[4])
                if (argList[4]<0 or basis>4):    # (basis<0 or basis>4)
                    raise FormulaArgumentError,Engine2009 % ("basis","ACCRINTM")

        d = daysBetweenDates(issue, settlement, basis)
        y = daysInYear(issue, basis)
        if (d<0 or y<=0):
            raise FormulaArgumentError,Engine2009 % ("(issue, settlement, basis)","ACCRINTM")

        return (par*rate*d)/y

    except XLDateNegative, e:
        raise FormulaArgumentError, Engine2011 % ("ACCRINTM")
    except XLDateTooLarge, e:
        raise FormulaArgumentError, Engine2008 % ("ACCRINTM")
    except XLDateAmbiguous, e:
        raise FormulaArgumentError, Engine2021 % (e.args[0],"ACCRINTM")
    except Exception, e:
        raise FormulaArgumentError, e

"""
AMORDEGRC( Cost; purchaseDate; firstPeriodEndDate; salvage; period; [basis])
"""
def AMORDEGRC(argList):
    try:
        # Source: http://api.kde.org/koffice-api/koffice-apidocs/kspread/html/financial_8cpp-source.html
        ROUND = lambda x,y: floor((x) + 0.5)

        cost = argList[0]
        if cost<0:
            raise FormulaArgumentError,Engine2009 % ("cost","AMORDEGRC")

        purchaseDate = argList[1]
        firstPeriodEndDate = argList[2]
        if firstPeriodEndDate<purchaseDate:
            raise FormulaArgumentError,Engine2010 % ("First_period<Date_Purchased","AMORDEGRC")

        salvage = argList[3]
        if salvage<0:
            raise FormulaArgumentError,Engine2009 % ("salvage","AMORDEGRC")
        if cost<salvage:
            raise FormulaArgumentError,Engine2010 % ("cost<salvage","AMORDEGRC")

        period = int(argList[4])
        if argList[4]<0:    # (period < 0)
            raise FormulaArgumentError,Engine2009 % ("salvage","AMORDEGRC")

        rate = argList[5]
        if rate<=0:
            raise FormulaArgumentError,Engine2009 % ("salvage","AMORDEGRC")

        basis = 0
        if len(argList)>6:
            basis = int(argList[6])
            if (argList[6]<0) or basis not in (0,1,3,4):
                raise FormulaArgumentError,Engine2009 % ("basis","AMORDEGRC")

        lifeOfAssets = 1.0/rate

        if (3.0<=lifeOfAssets<=4.0):    # (in YEARS i.e (3-4 Years))
            amorCoeff = 1.5
        elif (5.0<=lifeOfAssets<=6.0):
            amorCoeff = 2.0
        elif (lifeOfAssets>6.0):
            amorCoeff = 2.5
        elif (0.0<=lifeOfAssets<=3.0 or 4.0<=lifeOfAssets<=5.0):
            raise FormulaArgumentError,Engine2009 % ("rate","AMORDEGRC")

        rate *= amorCoeff
        nRate = ROUND(YEARFRAC([purchaseDate, firstPeriodEndDate, basis])*rate*cost, 0)
        cost -= nRate
        rest = cost - salvage

        for n in xrange(0, period):
            nRate = ROUND(rate * cost, 0)
            rest -= nRate

            if (rest < 0.0):
                if (period-n) in (0,1):
                    return ROUND(cost * 0.5, 0)
                else:
                    return 0.0

            cost -= nRate

        return nRate

    except XLDateNegative, e:
        raise FormulaArgumentError, Engine2011 % ("AMORDEGRC")
    except XLDateTooLarge, e:
        raise FormulaArgumentError, Engine2008 % ("AMORDEGRC")
    except XLDateAmbiguous, e:
        raise FormulaArgumentError, Engine2021 % (e.args[0],"AMORDEGRC")
    except Exception, e:
        raise FormulaArgumentError, e

"""
AMORLINC(cost,date_purchased,first_period,salvage,period,rate,[basis])
"""
def AMORLINC(argList):
    try:
        # Source: http://api.    kde.org/koffice-api/koffice-apidocs/kspread/html/financial_8cpp-source.html
        # Source: www.bettersolutions.com/excel/EDH113/LI849213514.htm
        cost = argList[0]
        if cost<0:
            raise FormulaArgumentError,Engine2009 % ("cost","AMORLINC")

        purchaseDate = argList[1]
        firstPeriodEndDate = argList[2]

        salvage = argList[3]
        if salvage<0:
            raise FormulaArgumentError,Engine2009 % ("salvage","AMORLINC")

        if cost < salvage:
            raise FormulaArgumentError,Engine2010 % ("cost<salvage","AMORLINC")

        period = int(argList[4])
        if argList[4] < 0:    # (period < 0)
            raise FormulaArgumentError,Engine2009 % ("period","AMORLINC")

        rate = argList[5]
        if rate<=0:
            raise FormulaArgumentError,Engine2009 % ("rate","AMORLINC")

        basis = 0;
        if (len(argList)>6):
            basis = int(argList[6])
            if (argList[6]<0) or basis not in (0,1,3,4):
                raise FormulaArgumentError,Engine2009 % ("basis","AMORLINC")

        oneRate = cost * rate;
        costDelta = cost - salvage;
        nullRate = YEARFRAC([purchaseDate, firstPeriodEndDate, basis]) * rate * cost
        numOfFullPeriods = (cost-salvage-nullRate)/oneRate

        if (period == 0):
            res = nullRate
        elif (period <= numOfFullPeriods):
            res = oneRate
        elif (period == numOfFullPeriods+1):
            res = costDelta - oneRate * numOfFullPeriods - nullRate
        else:
            res = 0.0

        return res

    except XLDateNegative, e:
        raise FormulaArgumentError, Engine2011 % ("AMORLINC")
    except XLDateTooLarge, e:
        raise FormulaArgumentError, Engine2008 % ("AMORLINC")
    except XLDateAmbiguous, e:
        raise FormulaArgumentError, Engine2021 % (e.args[0],"AMORLINC")
    except Exception, e:
        raise FormulaArgumentError, e

"""
COUPNUM(settlement,maturity,frequency,[basis])
"""
def COUPNUM(argList):
    try:
        # dates and integers only - don't need high-precision for this.
        settlement = argList[0]
        maturity = argList[1]
        if settlement>=maturity:
            raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","COUPNUM")

        frequency = int(argList[2])
        if frequency not in (1,2,4):
            raise FormulaArgumentError,Engine2009 % ("frequency","COUPNUM")

        # Default Values
        basis = 0
        eom = True
        if (len(argList)>3):
            basis = int(argList[3])
            if (argList[3]<0 or basis>4):    # basis<0 or basis>4
                raise FormulaArgumentError,Engine2009 % ("basis","COUPNUM")

            if len(argList) == 5:
                eom = argList[4]

        # Get Year, Month, Day from Datetime Serial Number.
        settlementYear,settlementMonth,settlementDay = xldate_as_tuple(settlement,0)[0:3]
        maturityYear,maturityMonth,maturityDay = xldate_as_tuple(maturity,0)[0:3]

        months = maturityMonth-settlementMonth+12*(maturityYear-settlementYear)

        theDate = DATE([maturityYear,maturityMonth-months,maturityDay])
        theYear,theMonth,theDay = xldate_as_tuple(theDate,0)[0:3]

        if (eom and daysInMonth(maturityYear,maturityMonth) == maturityDay):
            while(daysInMonth(theYear,theMonth) != theDay):
                theDay+=1
                dateserial = DATE([theYear, theMonth, theDay])
                theYear,theMonth,theDay = xldate_as_tuple(dateserial,0)[0:3]

        if (settlementDay >= theDay):
            months-=1

        return (1+months/(12/frequency))

    except XLDateNegative, e:
        raise FormulaArgumentError, Engine2011 % ("COUPNUM")
    except XLDateTooLarge, e:
        raise FormulaArgumentError, Engine2008 % ("COUPNUM")
    except XLDateAmbiguous, e:
        raise FormulaArgumentError, Engine2021 % (e.args[0],"COUPNUM")
    except Exception, e:
        raise FormulaArgumentError, e

"""
Function: DB(cost,salvage,life,period,[month])
Fixed-declining balance method for depreciation computation.
This function doesn't support extended datatypes, it simply converts everything to double - because it does quite a bit of computing.
DB = (cost - total depreciation from prior periods) * rate,
where: rate = 1-((salvage/cost)^(1/life)), rounded to three decimal places
"""
def DB(argList):
    cost = argList[0]
    if (cost == 0):
        raise FormulaArgumentError,Engine2009 % ("cost","DB")

    salvage = argList[1]
    if (salvage/cost) < 0:
        raise FormulaArgumentError,Engine2009 % ("cost/salvage","DB")

    life = argList[2]
    if (life <= 0.0):
        raise FormulaArgumentError,Engine2009 % ("life","DB")

    period = int(argList[3])
    if (0<argList[3]<1): period = 1       # if 0<period<1, then period = 1 (Excel takes in this way)
    if (argList[3] <= 0 or period>life):  # (period <= 0 or period>life)
        raise FormulaArgumentError,Engine2009 % ("period","DB")

    month = 12
    if len(argList) > 4:
        month = int(argList[4])
        if (month<1 or month>12):
            raise FormulaArgumentError,Engine2009 % ("month","DB")

    """ Rate Should be Rounded to 3 Dec. Places. """
    rate = 1000*(1-pow((salvage/cost),(1.0/life)))
    rate = floor(rate+0.5)/1000

    total = (cost*rate*month)/12

    if (period == 1):
        return total

    for i in xrange(1,int(life)):
        if (i == period-1):
            return rate*(cost-total)
        else:
            total += rate*(cost-total)

    return (cost-total)*rate*(12-month)/12.0

"""
# Function: DDB(cost,salvage,life,period,[factor])
# depreciation per period
DDB = Min( (cost - total depreciation from prior periods) * (factor/life), (cost - salvage - total depreciation from prior periods) )
"""
def DDB(argList):
    cost = argList[0]
    if (cost < 0.0):
        raise FormulaArgumentError,Engine2009 % ("cost","DDB")

    salvage = argList[1]
    if (salvage < 0.0):
        raise FormulaArgumentError,Engine2009 % ("salvage","DDB")

    life = argList[2]
    if (life <= 0.0):
        raise FormulaArgumentError,Engine2009 % ("life","DDB")

    period = argList[3]
    if (0<argList[3]<1): period = 1       # if 0<period<1, then period = 1 (Excel takes in this way)
    if (argList[3] <= 0.0 or period>life):  # (period <= 0.0 or period>life)
        raise FormulaArgumentError,Engine2009 % ("period","DDB")

    factor = 2.0        # Default
    if (len(argList) == 5):
        factor = argList[4]
        if (factor <= 0.0):
            raise FormulaArgumentError,Engine2009 % ("factor","DDB")

    result = 0.0

    if (factor >= life):
        # special case: amazingly gigantic depreciating rate
        if period > 1:
            result = 0
        elif cost < salvage:
            result = 0
        else:
            result = cost - salvage
    else:
        # depreciation is the value between two periods
        invrate = (life-factor)/life
        if period==1:
            current = invrate
        else:
            current = pow(invrate, period)
        previous = current/invrate
        current *= cost
        previous *= cost
        result = previous-current

        # should not be more than the salvage
        if (current < salvage):
            result = previous-salvage

    # can't be negative
    if (result < 0.0):
        result = 0.0

    return result

"""
Function: DISC(settlement,maturity,pr,redemption,[basis])
"""
def DISC(argList):
    settlement = argList[0]
    maturity = argList[1]
    if (settlement >= maturity):    # check dates
        raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","DISC")

    price = argList[2]
    if (price <= 0.0):
        raise FormulaArgumentError,Engine2009 % ("price","DISC")

    redemption = argList[3]
    if (redemption <= 0.0):
        raise FormulaArgumentError,Engine2009 % ("redemption","DISC")

    basis = 0;
    if len(argList) == 5:
        basis = int(argList[4])
        if (argList[4] < 0 or basis > 4):  # (basis < 0 or basis > 4)
            raise FormulaArgumentError,Engine2009 % ("basis","DISC")

    yearFrac = YEARFRAC([int(settlement),int(maturity),basis])
    return (1.0 - price/redemption)/yearFrac

"""
Function: DOLLARDE(fractional_dollar,fraction)
"""
def DOLLARDE(argList):
    dollarFrac = argList[0]
    frac = int(argList[1])

    if (frac <= 0.0):
        raise FormulaArgumentError,Engine2009 % ("fractional_dollar","DOLLARDE")

    fracPart,intPart = modf(dollarFrac)

    fracPart =fracPart/frac
    fracPart *= pow (10.0, ceil(log10(frac)))

    return (fracPart+intPart)

"""
Function: DOLLARFR(decimal_dollar,fraction)
"""
def DOLLARFR(argList):
    dollarFrac = argList[0]
    frac = int(argList[1])

    if (frac <= 0):
        raise FormulaArgumentError,Engine2009 % ("decimal_dollar","DOLLARFR")

    fracPart,intPart = modf(dollarFrac)
    fracPart *= frac
    fracPart *= pow (10.0, -ceil(log10(frac)))
    return (fracPart+intPart)

"""
Function: DURATION
# DURATION(settlement,maturity,coupon,yld,frequency,[basis])
"""
def DURATION(argList):
    try:
        settlement = argList[0]
        maturity = argList[1]
        if (settlement >= maturity):
            raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","DURATION")

        coup = argList[2]
        if (coup<0.0):
            raise FormulaArgumentError,Engine2009 % ("coupon","DURATION")

        Yield = argList[3]    # "[y]ield" is a Keyword in Python
        if (Yield<0.0):
            raise FormulaArgumentError,Engine2009 % ("yield","DURATION")

        freq = int(argList[4])
        if freq not in (1,2,4):
            raise FormulaArgumentError,Engine2009 % ("frequency","DURATION")

        basis = 0;
        if (len(argList)>5):
            basis = int(argList[5])
            if (argList[5] < 0 or basis > 4):  # (basis < 0 or basis > 4)
                raise FormulaArgumentError,Engine2009 % ("basis","DURATION")

        listParams=list()
        listParams.append(argList[0])
        listParams.append(argList[1])
        listParams.append(argList[4])
        listParams.append(basis)
        numOfCoups = int(COUPNUM(listParams))

        return calcDuration(settlement, maturity, coup, Yield, freq, basis, numOfCoups)
    except Exception, e:
        raise FormulaArgumentError, e

"""
Function: EFFECT
EFFECT(nominal_rate,npery)
"""
def EFFECT(argList):
    """
    Returns effective interest rate given nominal rate and periods per year
    EFFECT = (1+(nominal/periods)^periods) - 1
    """
    nominal_rate = argList[0]
    if (nominal_rate <= 0):
        raise FormulaArgumentError,Engine2009 % ("nominal rate","EFFECT")

    periods = int(argList[1])
    if (argList[1] < 1):
        raise FormulaArgumentError,Engine2009 % ("nof. compounding periods","EFFECT")

    return pow(1+(nominal_rate/periods),periods)-1

"""
Function: FVSCHEDULE(principal,schedule)
Returns future value
"""
def FVSCHEDULE(argList):
    principal = argList[0]
    schedule = argList[1:]
    if len(argList[1:])==1:
        schedule = [argList[1]]

    res = principal

    for i in xrange(0,len(schedule)):
        val = schedule[i]
        if not val is None:
            res = res*(1+val)

    return res

"""
Function: INTRATE(settlement,maturity,investment,redemption,[basis])
"""
def INTRATE(argList):
    try:
        #If settlement or maturity is not a valid date, INTRATE returns the #VALUE! error value.
        #If investment <= 0 or if redemption <= 0, INTRATE returns the #NUM! error value.
        #If basis < 0 or if basis > 4, INTRATE returns the #NUM! error value.
        #If settlement >= maturity, INTRATE returns the #NUM! error value.
        settlement = argList[0]
        maturity = argList[1]
        if settlement >= maturity:
            raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","INTRATE")

        investment = argList[2]
        if (investment <= 0):
            raise FormulaArgumentError,Engine2009 % ("investment","INTRATE")

        redemption = argList[3]
        if (redemption <= 0):
            raise FormulaArgumentError,Engine2009 % ("redemption","INTRATE")

        basis = 0
        if (len(argList) == 5):
            basis = int(argList[4])
            if (argList[4]<0 or basis>4):    # (basis<0 or basis>4)
                raise FormulaArgumentError,Engine2009 % ("basis","INTRATE")

        d = daysBetweenDates(settlement, maturity, basis)
        y = daysInYear(settlement, basis)
        if (d<=0 or y<=0):
            raise FormulaArgumentError,Engine2009 % ("(settlement, maturity, basis)","INTRATE")

        return ((redemption-investment)/investment)*(float(y)/d)

    except XLDateNegative, e:
        raise FormulaArgumentError, Engine2011 % ("INTRATE")
    except XLDateTooLarge, e:
        raise FormulaArgumentError, Engine2008 % ("INTRATE")
    except XLDateAmbiguous, e:
        raise FormulaArgumentError, Engine2021 % (e.args[0],"INTRATE")

""" Newly Added Functions """

"""
Function: ISPMT(rate,per,nper,pv)
"""
def ISPMT(argList):
    rate = argList[0]
    per  = argList[1]
    nper = argList[2]
    pv   = argList[3]

    """
    As per Documantation, Period must be between 1 and nper.
    @Ref: http://office.microsoft.com/en-us/excel/HP052508401033.aspx
    (i.e) if (per<1) or (per>nper): return errorVALUE()
    But originally Excel throws error only if nper=0.
    """
    if (nper==0.0):
        raise FormulaArgumentError,Engine2017 % (3,"ISPMT",0)

    d = -pv*rate
    return d-(d/(nper*per))

"""
Function: MDURATION(settlement,maturity,coupon,yld,frequency,[basis])
MDURATION = DURATION/(1+(market yield/coupon payments per year))
"""
def MDURATION(argList):
    try:
        settlement = argList[0]
        maturity = argList[1]
        if (settlement >= maturity):
            raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","MDURATION")

        coup = argList[2]
        if (coup<0.0):
            raise FormulaArgumentError,Engine2009 % ("coupon","MDURATION")

        Yield = argList[3]    # "[y]ield" is a Keyword in Python
        if (Yield<0.0):
            raise FormulaArgumentError,Engine2009 % ("yield","MDURATION")

        freq = int(argList[4])
        if freq not in (1,2,4):
            raise FormulaArgumentError,Engine2009 % ("frequency","MDURATION")

        basis = 0;
        if (len(argList)>5):
            basis = int(argList[5])
            if (argList[5] < 0 or basis > 4):  # (basis < 0 or basis > 4)
                raise FormulaArgumentError,Engine2009 % ("basis","MDURATION")

        listParams=list()
        listParams.append(argList[0])
        listParams.append(argList[1])
        listParams.append(argList[4])
        listParams.append(basis)
        numOfCoups = int(COUPNUM(listParams))

        res = calcDuration(settlement, maturity, coup, Yield, freq, basis, numOfCoups)
        res /= 1.0+(Yield/freq)
        return res
    except Exception, e:
        raise FormulaArgumentError, e

"""
Function: NOMINAL(effect_rate,npery)
"""
def NOMINAL(argList):
    effective = argList[0]
    periods = int(argList[1])

    if (effective<=0.0):
        raise FormulaArgumentError,Engine2009 % ("effect_rate","NOMINAL")
    if (periods<=0.0):
        raise FormulaArgumentError,Engine2012 % ("npery","NOMINAL")

    return periods*((effective+1.0)**(1.0/periods)-1)

"""
Function: NPER(rate, pmt, pv, [fv], [type])
"""
def NPER(argList):
    rate = argList[0]
    pmt = argList[1]
    pv = argList[2]

    # Defaults
    fv = 0.0
    type = 0

    # Optional Parameters
    argsCount = len(argList)
    if (argsCount > 3):  fv = argList[3]
    if (argsCount == 5):
        type = argList[4]
        if argList[4]!=0.0: type=1

    if (rate == 0.0):
        if pmt == 0.0:
            raise FormulaArgumentError,Engine2009 % ("pmt","NPER")    # Verify "arguments"??
        return -(pv+fv)/pmt    # From, PV = -FV-(Payment*NPER)

    if (type == 0):
        divisor = (rate*pv+pmt)
        if divisor == 0.0:
            raise FormulaArgumentError,Engine2009 % ("arguments","NPER")

        val = -(rate*fv-pmt)/divisor
        if val <= 0.0:
            raise FormulaArgumentError,Engine2009 % ("arguments","NPER")    # Verify "arguments"??

        divisor = log(1.0+rate)
        if divisor == 0.0:
            raise FormulaArgumentError,Engine2009 % ("arguments","NPER")
        return log(val)/divisor
    else:
        divisor = (rate*pv+pmt*(1.0+rate))
        if divisor == 0.0:
            raise FormulaArgumentError,Engine2009 % ("arguments","NPER")
        val = -(rate*fv-pmt*(1.0+rate))/divisor
        divisor = log(1.0+rate)
        if val<=0 or divisor==0.0:
            raise FormulaArgumentError,Engine2009 % ("arguments","NPER")    # Verify "arguments"??
        return log(val)/divisor

"""
ODDLPRICE(settlement,maturity,last_interest,rate,yld,redemption,frequency,[basis])
"""
def ODDLPRICE(argList):
    settlement = argList[0]
    maturity = argList[1]
    last = argList[2]
    if not (maturity>settlement>last):
        raise FormulaArgumentError,Engine2009 % ("maturity/settlement/last","ODDLPRICE")

    rate = argList[3]
    if (rate<0.0):
        raise FormulaArgumentError,Engine2009 % ("rate","ODDLPRICE")

    Yield = argList[4]    # [y]ield is Keyword
    if (Yield<0.0):
        raise FormulaArgumentError,Engine2009 % ("yield","ODDLPRICE")

    redemp = argList[5]
    if (redemp<=0.0):
        raise FormulaArgumentError,Engine2009 % ("redemption","ODDLPRICE")

    freq = int(argList[6])
    if freq not in (1,2,4):
        raise FormulaArgumentError,Engine2009 % ("frequency","ODDLPRICE")

    # Optional basis
    basis = 0;
    if (len(argList)>7):
        basis = int(argList[7])
        if (argList[7]<0) or basis>4:
            raise FormulaArgumentError,Engine2009 % ("basis","ODDLPRICE")

    dci  = YEARFRAC([last, maturity, basis])*freq
    dsci = YEARFRAC([settlement, maturity, basis])*freq
    ai = YEARFRAC([last, settlement, basis])*freq

    res = redemp+dci*100.0*rate/freq
    res /= dsci*Yield/freq+1.0
    res -= ai*100.0*rate/freq
    return res

"""
ODDLYIELD(settlement,maturity,last_interest,rate,pr,redemption,frequency,[basis])
"""
def ODDLYIELD(argList):
    settlement = argList[0]
    maturity = argList[1]
    last = argList[2]
    if not (maturity>settlement>last):
        raise FormulaArgumentError,Engine2009 % ("maturity or settlement or last","ODDLYIELD")
    if (maturity <= settlement):
        raise FormulaArgumentError,Engine2010 % ("maturity <= settlement","ODDLYIELD")
    if (settlement <= last):
        raise FormulaArgumentError,Engine2010 % ("settlement <= last","ODDLYIELD")

    rate = argList[3]
    if (rate < 0.0):
        raise FormulaArgumentError,Engine2009 % ("rate","ODDLYIELD")

    price = argList[4]
    if (price <= 0.0):
        raise FormulaArgumentError,Engine2009 % ("price","ODDLYIELD")

    redemp = argList[5]
    if (redemp <= 0.0):
        raise FormulaArgumentError,Engine2009 % ("redemption","ODDLYIELD")

    freq = float(int(argList[6]))
    if freq not in (1,2,4):
        raise FormulaArgumentError,Engine2009 % ("frequency","ODDLYIELD")

    # Optional basis
    basis = 0
    if (len(argList)>7):
        basis = int(argList[7])
        if (argList[7]<0 or basis>4):
            raise FormulaArgumentError,Engine2009 % ("basis","ODDLYIELD")

    dci  = YEARFRAC([last, maturity, basis])*freq
    dsci = YEARFRAC([settlement, maturity, basis])*freq
    ai = YEARFRAC([last, settlement, basis])*freq

    res = redemp + dci * 100.0 * rate / freq
    res /= (price + ai * 100.0 * rate / freq)
    res-=1.0
    res *= freq / dsci
    return res    # *100.0

"""
PRICEMAT(settlement,maturity,issue,rate,yld,basis)
Status: Result Failed for Some Testcases. [Need to Check]
"""
def PRICEMAT(argList):
    settlement = argList[0]
    maturity = argList[1]
    if (settlement >= maturity):
        raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","PRICEMAT")

    issue = argList[2]

    rate = argList[3]
    if (rate < 0.0):
        raise FormulaArgumentError,Engine2009 % ("rate","PRICEMAT")

    Yield = argList[4]
    if (Yield < 0.0):
        raise FormulaArgumentError,Engine2009 % ("yield","PRICEMAT")

    # Optional basis
    basis = 0;
    if (len(argList)>5):
        basis = int(argList[5])
        if (argList[5]<0) or basis>4:
            raise FormulaArgumentError,Engine2009 % ("basis","PRICEMAT")

    issMat = YEARFRAC([issue, maturity, basis])
    issSet = YEARFRAC([issue, settlement, basis])
    setMat = YEARFRAC([settlement, maturity, basis])

    res = 1.0 + issMat * rate
    res /= 1.0 + setMat * Yield
    res -= issSet * rate

    return res*100

"""
Function: RECEIVED(settlement,maturity,investment,discount,[basis])
RECEIVED = Investment / (1 - (discount * DIM/B))
where:
    B = number of days in a year, depending on the year basis.
    DIM = number of days from issue to maturity.
"""
def RECEIVED(argList):
    settlement = argList[0]
    maturity = argList[1]
    if (settlement >= maturity):
        raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","RECEIVED")

    investment = argList[2]
    if (investment<=0):
        raise FormulaArgumentError,Engine2009 % ("investment","RECEIVED")

    discount = argList[3]
    if (discount<=0):
        raise FormulaArgumentError,Engine2009 % ("discount","RECEIVED")

    # Optional basis
    basis = 0
    if (len(argList)>4):
        basis = int(argList[4])
        if (argList[4]<0 or basis>4):
            raise FormulaArgumentError,Engine2009 % ("basis","RECEIVED")

    d = daysBetweenDates(settlement, maturity, basis)
    y = daysInYear(settlement, basis)
    x = 1.0-(discount*d)/float(y)

    if (d <= 0 or y <= 0 or x<=0.0):
        raise FormulaArgumentError,Engine2009 % ("settlement or maturity","RECEIVED")

    return investment/x

"""
Function: SLN(cost,salvage,life)
straight-line depreciation for a single period
"""
def SLN(argList):
    cost = argList[0]
    salvage = argList[1]
    life = argList[2]

    if (life==0.0):
        raise FormulaArgumentError,Engine2009 % ("life","SLN")

    return (cost-salvage)/life

"""
Function: SYD(cost,salvage,life,per)
sum-of-years digits depreciation
SYD = (cost-salvage)*(life-per+1)*2/(life*(life+1))
"""
def SYD(argList):
    cost = argList[0]
    salvage = argList[1]
    if (salvage<0.0):
        raise FormulaArgumentError,Engine2009 % ("salvage","SYD")

    life = argList[2]
    if (life<=0.0):
        raise FormulaArgumentError,Engine2009 % ("life","SYD")

    period = argList[3]
    if (period<=0.0):
        raise FormulaArgumentError,Engine2009 % ("period","SYD")

    if (life<period):
        raise FormulaArgumentError,Engine2010 % ("life < period","SYD")

    v1 = cost - salvage
    v2 = life - period + 1
    v3 = life * (life + 1.0)
    return (v1 * v2 * 2) / v3

"""
Function: TBILLEQ(settlement,maturity,discount)
TBILLEQ = (365 x rate)/(360-(rate x DSM))
"""
def TBILLEQ(argList):
    settlement = argList[0]
    maturity = argList[1]
    if (settlement >= maturity):
        raise FormulaArgumentError,Engine2010 % ("settlement >= maturity","TBILLEQ")

    discount = argList[2]
    if (discount<=0):
        raise FormulaArgumentError,Engine2009 % ("discount","TBILLEQ")

    # ========== METHOD-I ==========
    #maturity = maturity+1
    #days = DAYS360([settlement, maturity, False])     # False -> US Method
    #if (days>360):
    #    return errorVALUE()
    #return (365.0*discount)/(360.0-(discount*days))

    # ========== METHOD-II ==========
    # @Ref: http://svn.gnome.org/viewvc/gnumeric/trunk/plugins/fn-financial/functions.c?view=markup
    DSM = maturity - settlement
    if (DSM>365.0):
        raise FormulaArgumentError,Engine2009 % ("settlement or maturity","TBILLEQ")

    divisor = 360.0-(discount*DSM)
    if (divisor<=0.0):        # #DIV/0!
        raise FormulaArgumentError,Engine2012 % ("settlement or maturity or discount","TBILLEQ")
    return (365.0*discount)/divisor

"""
@Function: TBILLPRICE(settlement,maturity,discount)
@formula: TBILLPRICE = 100 * (1-(discount * DSM)/360)
@Comment: Code is written as per gnumeric & also openoffice.
@Ref: http://svn.gnome.org/viewvc/gnumeric/trunk/plugins/fn-financial/functions.c?view=markup
@Ref: http://svn.services.openoffice.org/opengrok/xref/Current%20(trunk)/scaddins/source/analysis/financial.cxx
"""
def TBILLPRICE(argList):
    settlement = argList[0]
    maturity = argList[1]
    discount = argList[2]

    if (settlement >= maturity):
        raise FormulaArgumentError,Engine2010 % ("settlement > maturity","TBILLPRICE")
    if (discount<=0.0):
        raise FormulaArgumentError,Engine2009 % ("discount","TBILLPRICE")

    """ Based on OpenOffice. """
    """ TBILLPRICE([36892.0,37245.0,2.0]) Result is -94.4444444444 """
    #maturity+=1
    #YearFrac = YEARFRAC([settlement,maturity,0])
    #return 100.0*(1.0-discount*YearFrac)

    """ Based on GNumeric. """
    """ TBILLPRICE([36892.0,37245.0,2.0]) Result is -96.1111111111 """
    DSM = maturity-settlement
    return 100.0*(1.0-discount*DSM/360.0)

"""
@Function: TBILLYIELD(settlement,maturity,pr)
@Formula: TBILLYIELD = (100-par)/par * (360/DSM)
"""
def TBILLYIELD(argList):
    settlement = argList[0]
    maturity = argList[1]
    price = argList[2]

    DSM = int(maturity) - int(settlement)

    if (settlement >= maturity):
        raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","TBILLYIELD")
    if (price<=0.0):
        raise FormulaArgumentError,Engine2009 % ("price","TBILLYIELD")

    return (100.0-price)/price * (360.0/DSM)


"""
@Function: helper vdbGetGDA
"""
def vdbGetGDA(cost, salvage, life, period, depreciationFactor):
    rate = depreciationFactor / life
    if ( rate >= 1.0 ):
        rate = 1.0;
        if (period == 1.0):
            oldCost = cost
        else:
            oldCost = 0.0
    else:
        oldCost = cost * pow(1.0-rate, period-1.0)

    newCost = cost * pow(1.0-rate, period)

    if (newCost < salvage):
        res = oldCost - salvage
    else:
        res = oldCost - newCost

    if ( res < 0.0 ):
        res = 0.0
    return res

"""
Function: helper vdbInterVDB
"""
def vdbInterVDB(cost, salvage, life, life1, period, depreciationFactor):
    res = 0.0
    intEnd = ceil(period)
    loopEnd = int(intEnd)

    lia=0.0
    balance = cost - salvage
    nowLia = False

    for i in xrange(1,loopEnd+1):
        if (not nowLia):
            gda = vdbGetGDA( cost, salvage, life, i, depreciationFactor)
            lia = balance / (life1 - (i-1))

            if (lia > gda):
                term = lia
                nowLia = True
            else:
                term = gda
                balance -= gda
        else:
            term = lia

        if (i == loopEnd):
            term *= (period + 1.0 - intEnd)

        res += term

    return res

"""
Function: Variable Declining Balance(VDB)
VDB( cost; salvage; life; startPeriod; endPeriod [; depreciation-factor = 2 [; straight-line depreciation = TRUE ] )
"""
def VDB(argList):
    cost = argList[0]
    if (cost < 0.0):
        raise FormulaArgumentError,Engine2009 % ("cost","VDB")

    salvage = argList[1]
    if (salvage < 0.0):
        raise FormulaArgumentError,Engine2009 % ("salvage","VDB")

    life = argList[2]
    if (life < 0.0):
        raise FormulaArgumentError,Engine2009 % ("life","VDB")

    startPeriod = argList[3]
    if (startPeriod < 0.0):
        raise FormulaArgumentError,Engine2009 % ("start period","VDB")

    endPeriod = argList[4]
    if (endPeriod < startPeriod):
        raise FormulaArgumentError,Engine2010 % ("salvage > cost","VDB")
    if (endPeriod > life):
        raise FormulaArgumentError,Engine2010 % ("endPeriod > life","VDB")

    # Defaults
    depreciationFactor = 2
    flag = False

    # Optional Parameters
    argsCount = len(argList)
    if (argsCount >= 6):
        depreciationFactor = argList[5]
        if (depreciationFactor < 0.0):
            raise FormulaArgumentError,Engine2009 % ("depreciation factor","VDB")

    if (argsCount > 7):
        flag = argList[6]

    # calc loop start and end
    intStart = int(floor(startPeriod))
    intEnd   = int(ceil(endPeriod))
    loopStart = abs(intStart)
    loopEnd   = abs(intEnd)

    res = 0.0

    if (flag):
        # no straight-line depreciation
        for i in xrange(loopStart + 1, loopEnd+1):
            term = vdbGetGDA(cost, salvage, life, i, depreciationFactor)
            if (i == loopStart+1 ):
                term *= (fmin( endPeriod, intStart + 1.0 ) - startPeriod)
            elif (i == loopEnd ):
                term *= (endPeriod + 1.0 - intEnd)

            res += term
    else:
        life1=life

        if (startPeriod != floor(startPeriod)):
            if (depreciationFactor > 1):
                if (startPeriod >= life/2.0 or startPeriod == life/2.0 ):
                    part        = startPeriod - life/2.0
                    startPeriod = life/2.0
                    endPeriod  -= part
                    life1      += 1.0

    cost -= vdbInterVDB(cost, salvage, life, life1, startPeriod, depreciationFactor)
    res   = vdbInterVDB(cost, salvage, life, life-startPeriod, endPeriod-startPeriod, depreciationFactor)
    return res

# on Mar 16, 2009
# ---------------
"""
@Function: helper: xirrResult
args[0] = values
args[1] = dates
"""
def xirrResult(argList,rate):
    res = argList[0][0]
    date0 = argList[1][0]
    r = rate + 1.0

    count = len(argList[0])
    for i in xrange(1, count):
        theDate = argList[1][i]
        e_i = abs(theDate-date0)/365.0
        val = argList[0][i]
        res += val/pow(r, e_i)

    return res

"""
@Function: helper: xirrResultDerive
args[0] = values
args[1] = dates
"""
def xirrResultDerive(argList,rate):
    date0 = argList[1][0]

    r = rate + 1.0
    res = 0.0

    for i in xrange(1, len(argList[0])):
        theDate = argList[1][i]
        e_i = abs(theDate-date0)/365.0
        val = argList[0][i]
        res -= e_i*val/pow(r, e_i+1.0)

    return res

"""
Function: XIRR(values,dates,[guess]); where, guess=0.1
Compute the internal rate of return for a non-periodic series of cash flows.
Status: [PASSED-OK]
"""
def XIRR(argList):
    resultRate = 0.1    # Default guess is 10% (0.1)
    if (len(argList) > 2):
        resultRate = argList[2]

    numOfValues = len(argList[0])
    numOfDates = len(argList[1])

    if (numOfValues != numOfDates or numOfDates < 2):
        raise FormulaArgumentError,Engine2009 % ("values and dates","XIRR")

    if (resultRate <= 0.0):
        raise FormulaArgumentError,Engine2009 % ("guess","XIRR")

    # define max epsilon
    maxEpsilon = 1e-10

    # max number of iterations
    maxIter = 100    # MS Excel Based.

    # Newton's method - try to find a result, with a accuracy of maxEpsilon
    i = 0
    while(True):
        resultValue = xirrResult(argList, resultRate)
        newRate =  resultRate - resultValue / (xirrResultDerive(argList,resultRate))
        rateEpsilon = fabs(newRate - resultRate)
        resultRate = newRate
        contLoop = (rateEpsilon > maxEpsilon) and (fabs(resultValue) > maxEpsilon)
        i +=1
        if not(contLoop and (i < maxIter)):
            break

    if (contLoop):
        raise FormulaArgumentError,Engine2010 % ("Result can't determined","XIRR")

    return resultRate

def isValidDate(DateSerialNumber):
    if (0<=DateSerialNumber<=2958465):
        return True
    return False

"""
@Function: XNPV(rate,values,dates)
Compute the net present value of a series of cash flows.
"""
def XNPV(argList):
    rate = argList[0]
    rate += 1

    numOfValues = len(argList[1])
    numOfDates  = len(argList[2])

    res = 0

    if (numOfValues != numOfDates or numOfDates < 2):
        raise FormulaArgumentError,Engine2009 % ("values and dates","XNPV")

    if (rate <= 0.0):
        raise FormulaArgumentError,Engine2009 % ("rate","XNPV")

    date0 = argList[2][0]

    for i in xrange(0, numOfValues):
        val  = argList[1][i]
        thedate = argList[2][i]

        # Check is the Date Error or Not
        #if (tmpDate.isError()):
        #    return tmpDate
        #thedate = tmpDate

        # check if date is valid
        if not isValidDate(thedate):
            raise FormulaArgumentError,Engine2009 % ("dates","XNPV")

        days = thedate - date0
        res += val / pow( rate, days / 365.0 )

    return res

"""
Dave Bunker (debunk@borg.com)
@Ref: http://www.linuxjournal.com/article/2545
-------------------------------------------------------------------------
"""
def helperIRR(positives, negatives, maxIterations, epsilon, du):

    for iter in xrange(maxIterations, 0, -1):

        pos = d_pos = 0.0
        for time,amount in positives.iteritems():
            tmp  = amount * exp(du * time)
            pos += tmp
            d_pos += tmp * time

        neg = d_neg = 0.0
        for time,amount in negatives.iteritems():
            tmp  = amount * exp(du * time)
            neg += tmp
            d_neg += tmp * time

        delta = log(neg/pos)/(d_neg/neg-d_pos/pos)
        if(abs(delta) < epsilon):
            break
        du -= delta

    ResultObtained = True
    if (iter == 0):
        ResultObtained = False

    return ResultObtained,du

"""
IRR(values,guess)
@Note: In Excel, if IRR can't find a result after 20 tries, #NUM! error is returned.
@Note: Guess, Not implemented in this function. But need to do.
"""
def IRR(argList):
    cashflows = argList[0]
    estimatedresult = 0.10    # guess is 10% [Default].
    if len(argList)>1: estimatedresult = argList[1]
    positives = dict()
    negatives = dict()

    for time,amount in enumerate(cashflows):
        if(amount > 0.0):
            positives[time]=amount
        elif(amount < 0.0):
            negatives[time]=-amount

    if(len(positives) == 0 or len(negatives) == 0):
        raise FormulaArgumentError,Engine2009 % ("Income and Expenditures","IRR")
        #print "ERROR: Cannot calculate IRR. Need INCOME and EXPENDITURES!..."

    maxIterations = 100
    epsilon = 1.0e-10
    du = 0.0

    RESULT_OBTAINED,du = helperIRR(positives, negatives, maxIterations, epsilon, du)
    if RESULT_OBTAINED==False:
        raise FormulaArgumentError,Engine2010 % ("Result can't determined","IRR")
        #print "ERROR: irr() does not converge in " + `maxIterations` + " iterations\n"

    return (exp(-du)-1.0) * 100.0

"""
YIELDDISC(settlement,maturity,pr,redemption,[basis])
"""
def YIELDDISC(argList):
    settlement = argList[0]
    maturity = argList[1]
    if(settlement >= maturity):
        raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","YIELDDISC")

    price = argList[2]
    if (price <= 0.0):
        raise FormulaArgumentError,Engine2010 % ("price <= 0.0","YIELDDISC")

    redemp = argList[3]
    if (redemp <= 0.0):
        raise FormulaArgumentError,Engine2010 % ("redemption <= 0.0","YIELDDISC")

    # Basis - Optional
    basis=0
    if (len(argList) > 4):
        basis = int(argList[4])
        if (argList[4]<0 or int(basis)>4):
            raise FormulaArgumentError,Engine2009 % ("basis","YIELDDISC")

    result = (redemp/price) - 1.0
    result /= YEARFRAC([settlement, maturity, basis])

    return result

"""
YIELDMAT(settlement,maturity,issue,rate,pr,[basis])
"""
def YIELDMAT(argList):
    settlement = argList[0]
    maturity = argList[1]
    if (settlement >= maturity):
        raise FormulaArgumentError,Engine2010 % ("settlement>=maturity","YIELDMAT")

    issue = argList[2]

    rate = argList[3]
    if(rate < 0.0):
        raise FormulaArgumentError,Engine2010 % ("rate < 0.0","YIELDMAT")

    price = argList[4]
    if(price <= 0.0):
        raise FormulaArgumentError,Engine2010 % ("price <= 0.0","YIELDMAT")

    # Basis [Optional]
    basis=0
    if (len(argList) > 5):
        basis = int(argList[5])
        if (argList[5]<0 or basis>4):
            raise FormulaArgumentError,Engine2009 % ("basis","YIELDMAT")

    issMat = YEARFRAC([issue, maturity, basis])
    issSet = YEARFRAC([issue, settlement, basis])
    setMat = YEARFRAC([settlement, maturity, basis])

    res = 1.0+issMat*rate
    res /= price/100.0+issSet*rate
    res -= 1
    res /= setMat

    return res

def calculate_pvif(rate, nper):
    return pow1p(rate, nper)

"""
helper: calc_fvifa
"""
def calc_fvifa(rate, nper):
    # Removable singularity at rate == 0.
    if rate == 0.0:
        return nper
    return pow1pm1(rate, nper)/rate

"""
helper: getPay
"""
def getPay(rate, nper, pv, fv, type):
    if (rate==0.0):
        return (pv+fv)/nper*(-1.0)

    pvif = pow1p(rate, nper)
    fvifa = calc_fvifa(rate, nper)
    return (-pv * pvif - fv)/((1.0 + rate * type) * fvifa)

"""
helper for IPMT and CUMIPMT
"""
def helperIPMT(rate, per, nper, pv, fv, type):

    PAYMENT_AT_END_OF_PERIOD, PAYMENT_AT_BEGINING_OF_PERIOD = (0,1)

    pmt = getPay(rate, nper, pv, fv, 0) #type=0
    val1 = pow1p(rate, per-1)
    val2 = pow1pm1(rate, per-1)
    ipmt = (-1.0)*(pv * pow1p(rate, per-1)*rate + pmt*pow1pm1(rate, per-1))

    if type==PAYMENT_AT_END_OF_PERIOD:
        return ipmt
    elif type==PAYMENT_AT_BEGINING_OF_PERIOD:
        return ipmt/(1+rate)

def IPMT(argList):
    rate = argList[0]
    per  = argList[1]
    nper = argList[2]
    if nper == 0.0:
        raise FormulaArgumentError,Engine2009 % ('nper','IPMT')

    pv   = argList[3]

    fv = 0.0
    if (len(argList)>4):
        fv = argList[4]

    type = 0    # Default: Payments due at the end of the period is 0.
    if (len(argList) == 6):
        type = argList[5]
        if type!=0:    # if type is non-zero, then type is 1.
            type=1

    return helperIPMT(rate, per, nper, pv, fv, type)

"""
Function: CUMIPMT
"""
def CUMIPMT(argList):
    rate = argList[0]
    if(rate <= 0.0):
        raise FormulaArgumentError,Engine2010 % ("rate <= 0.0","CUMIPMT")

    nper = argList[1]
    periods = int(nper)
    if(periods <= 0):
        raise FormulaArgumentError,Engine2010 % ("periods <= 0","CUMIPMT")

    pv = argList[2]
    if(pv <= 0.0):
        raise FormulaArgumentError,Engine2010 % ("pv <= 0.0","CUMIPMT")

    start=int(argList[3])
    if(start <= 0 or start > periods):
        raise FormulaArgumentError,Engine2009 % ("start","CUMIPMT")

    end = int(argList[4])
    if(end < start or end > periods):
        raise FormulaArgumentError,Engine2009 % ("end","CUMIPMT")

    type = int(argList[5])
    if argList[5] not in (0,1):
        raise FormulaArgumentError,Engine2009 % ("type","CUMIPMT")

    result=0.0
    for per in xrange(start,end+1):
        hipmt=helperIPMT(rate, per, nper, pv, 0.0, type)
        result = result+hipmt

    return result

"""
# Function: CUMPRINC
"""
def CUMPRINC(argList):
    rate = argList[0]
    if(rate <= 0.0):
        raise FormulaArgumentError,Engine2010 % ("rate <= 0.0","CUMPRINC")

    nper = argList[1]
    if(nper <= 0):
        raise FormulaArgumentError,Engine2010 % ("periods <= 0","CUMPRINC")

    pv = argList[2]
    if(pv <= 0.0):
        raise FormulaArgumentError,Engine2010 % ("pv <= 0.0","CUMPRINC")

    start = argList[3]
    if(start <= 0 or start > nper):
        raise FormulaArgumentError,Engine2009 % ("start","CUMPRINC")

    end = argList[4]
    if(end <= 0 or end < start or end > nper):
        raise FormulaArgumentError,Engine2009 % ("end","CUMPRINC")

    type = int(argList[5])
    if type not in (0,1):
        raise FormulaArgumentError,Engine2009 % ("type","CUMPRINC")

    nper = int(nper)
    start = int(start)
    end = int(end)
    pay = getPay(rate, nper, pv, 0.0, type)
    cumipmt = CUMIPMT(argList)

    cumipmt = pay*(end-start+1)-cumipmt
    #if cumipmt<0:
    #    raise FormulaArgumentError,Engine2009 % ("type","CUMPRINC")
    return cumipmt
