Anexen / pyxirr

Rust-powered collection of financial functions.
https://anexen.github.io/pyxirr/
The Unlicense
172 stars 16 forks source link

pass start_date (present "moment") and end_date (future "moment") to XFV #10

Closed yonil7 closed 3 years ago

yonil7 commented 3 years ago

on XFV instead of passing:

rate: Rate, # Rate of interest per period
nper: int, # Number of compounding periods

we can pass:

rate: Rate, # the annual interest rate
start_date: DateLike, # the start date (present "moment")
end_date: DateLike, # the end date (future "moment")

Another (less explicit) option instead of passing start_date and end_date is for the function to automatically take them from the series of dates passed (in amounts or dates) (start_date will be the minimum date of this series, end_date will be the maximum date of this series) In this case if the user will want to have a start_date / end_date that does not happen on the first/last payment, he/she can add these dates to the series with amount=0

Also, we can add clarifications to the docs on when rate is per period (e.g. on FV) and when rate is the annual interest rate (e.g. on XNPV)

Anexen commented 3 years ago

@yonil7, so, this is similar to the first link I suggested in #7. Please, see http://westclintech.com/SQL-Server-Financial-Functions/SQL-Server-XFV-function

I also believe it's better to have two distinct functions for regular XFV and irregular XFV.

yonil7 commented 3 years ago

XLeratorDB (the link you sent) has 2 relevant functions:

As all this library functions works on multiple cash flows (and not a single cash flow), XLeratorDB XNFV is what we need. (but on pyxirr I would name it XFV - see comment on pyxirr naming convention below)

As for the naming conventions on pyxirr, I would use:

Anexen commented 3 years ago

@yonil7, XLeratorDB documentation says that XNPV is the net future value of a series of irregular cash flows. This makes sense because we are using net present value for the calculation.

So far I have come up with the following functions:

from pyxirr import fv, xnpv, npv

# http://westclintech.com/SQL-Server-Financial-Functions/SQL-Server-XFV-function
def xfv(start_date, cash_flow_date, end_date, cash_flow_rate, end_rate, cash_flow):
    return (
        fv(end_rate, (end_date - start_date).days / 365, 0, -1)
        / fv(cash_flow_rate, (cash_flow_date - start_date).days / 365, 0, -1)
        * cash_flow
    )

# http://westclintech.com/SQL-Server-Financial-Functions/SQL-Server-XNFV-function
def xnfv(rate, dates, amounts):
    periods = (max(dates) - min(dates)).days / 365
    pv = xnpv(rate, dates, amounts)
    return fv(rate, periods, 0, -pv)

# http://westclintech.com/SQL-Server-Financial-Functions/SQL-Server-NFV-function
def nfv(rate, nper, amounts):
    pv = npv(rate, amounts, start_from_zero=False)
    return fv(rate, nper, 0, -pv)

Examples:

Uneven periodic cash flows:

# net future value of an investment based on a series of periodic cash flows and a rate.
# example from https://www.youtube.com/watch?v=775ljhriB8U
print("NFV:", nfv(0.03, 6, [1050.0, 1350.0, 1350.0, 1450.0]))  # 5750.16

Irregular cash flows:

from datetime import date

cash_flows = [
    (date(2011, 11, 30), -100000),
    (date(2012, 3, 15), -50000),
    (date(2012, 7, 18), -2500),
    (date(2012, 11, 30), 12500),
    (date(2013, 1, 23), 37500),
    (date(2013, 4, 30), 75000),
    (date(2014, 2, 6), 90000),
]
dates, amounts = list(zip(*cash_flows))
start_date = min(dates)
end_date = max(dates)
rate = 0.0250  # annual 

print("XNFV:", xnfv(rate, dates, amounts))  # 57238. must be the same as below
print(
    "SUM(XFV):",
    sum(
        xfv(start_date, cash_flow_date, end_date, rate, rate, amount)
        for cash_flow_date, amount in cash_flows
    ),
)

I'm going to include these functions in pyxirr shortly.