merge pricedisc, pricemat, yielddisc, yieldmat, disc, received, intrate #57
This commit is contained in:
committed by
Nicolás Hatcher
parent
04e012b518
commit
050677f905
@@ -41,6 +41,94 @@ fn is_less_than_one_year(start_date: i64, end_date: i64) -> Result<bool, String>
|
||||
Ok(end_day <= start_day)
|
||||
}
|
||||
|
||||
fn is_leap_year(year: i32) -> bool {
|
||||
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
|
||||
}
|
||||
|
||||
fn days360_us(start: chrono::NaiveDate, end: chrono::NaiveDate) -> i32 {
|
||||
let mut d1 = start.day() as i32;
|
||||
let m1 = start.month() as i32;
|
||||
let y1 = start.year();
|
||||
let mut d2 = end.day() as i32;
|
||||
let mut m2 = end.month() as i32;
|
||||
let mut y2 = end.year();
|
||||
|
||||
if d1 == 31 || (m1 == 2 && (d1 == 29 || (d1 == 28 && !is_leap_year(y1)))) {
|
||||
d1 = 30;
|
||||
}
|
||||
|
||||
if d2 == 31 {
|
||||
if d1 != 30 {
|
||||
d2 = 1;
|
||||
if m2 == 12 {
|
||||
y2 += 1;
|
||||
m2 = 1;
|
||||
} else {
|
||||
m2 += 1;
|
||||
}
|
||||
} else {
|
||||
d2 = 30;
|
||||
}
|
||||
}
|
||||
|
||||
d2 + m2 * 30 + y2 * 360 - d1 - m1 * 30 - y1 * 360
|
||||
}
|
||||
|
||||
fn days360_eu(start: chrono::NaiveDate, end: chrono::NaiveDate) -> i32 {
|
||||
let mut d1 = start.day() as i32;
|
||||
let mut d2 = end.day() as i32;
|
||||
let m1 = start.month() as i32;
|
||||
let m2 = end.month() as i32;
|
||||
let y1 = start.year();
|
||||
let y2 = end.year();
|
||||
|
||||
if d1 == 31 {
|
||||
d1 = 30;
|
||||
}
|
||||
if d2 == 31 {
|
||||
d2 = 30;
|
||||
}
|
||||
|
||||
d2 + m2 * 30 + y2 * 360 - d1 - m1 * 30 - y1 * 360
|
||||
}
|
||||
|
||||
fn days_between(start: i64, end: i64, basis: i32) -> Result<i32, String> {
|
||||
let start_date = from_excel_date(start)?;
|
||||
let end_date = from_excel_date(end)?;
|
||||
Ok(match basis {
|
||||
0 => days360_us(start_date, end_date),
|
||||
1..=3 => (end - start) as i32,
|
||||
4 => days360_eu(start_date, end_date),
|
||||
_ => return Err("invalid basis".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn days_in_year(date: chrono::NaiveDate, basis: i32) -> Result<i32, String> {
|
||||
Ok(match basis {
|
||||
0 | 2 | 4 => 360,
|
||||
1 => {
|
||||
if is_leap_year(date.year()) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
3 => 365,
|
||||
_ => return Err("invalid basis".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn year_frac(start: i64, end: i64, basis: i32) -> Result<f64, String> {
|
||||
let start_date = from_excel_date(start)?;
|
||||
let days = days_between(start, end, basis)? as f64;
|
||||
let year_days = days_in_year(start_date, basis)? as f64;
|
||||
Ok(days / year_days)
|
||||
}
|
||||
|
||||
fn year_diff(start: i64, end: i64, basis: i32) -> Result<f64, String> {
|
||||
year_frac(start, end, basis)
|
||||
}
|
||||
|
||||
fn compute_payment(
|
||||
rate: f64,
|
||||
nper: f64,
|
||||
@@ -1652,7 +1740,6 @@ impl Model {
|
||||
CalcResult::Number(result)
|
||||
}
|
||||
|
||||
// PRICE(settlement, maturity, rate, yld, redemption, frequency, [basis])
|
||||
pub(crate) fn fn_price(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if !(6..=7).contains(&args.len()) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
@@ -1720,7 +1807,117 @@ impl Model {
|
||||
CalcResult::Number(price)
|
||||
}
|
||||
|
||||
// YIELD(settlement, maturity, rate, pr, redemption, frequency, [basis])
|
||||
pub(crate) fn fn_pricedisc(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
let arg_count = args.len();
|
||||
if !(4..=5).contains(&arg_count) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let settlement = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let maturity = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let discount = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let redemption = match self.get_number_no_bools(&args[3], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let basis = if arg_count == 5 {
|
||||
match self.get_number_no_bools(&args[4], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
if settlement >= maturity || discount <= 0.0 || redemption <= 0.0 {
|
||||
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
|
||||
}
|
||||
if settlement < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| settlement > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
{
|
||||
return CalcResult::new_error(Error::NUM, cell, "Invalid number for date".to_string());
|
||||
}
|
||||
let yd = match year_diff(settlement as i64, maturity as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let result = redemption * (1.0 - discount * yd);
|
||||
CalcResult::Number(result)
|
||||
}
|
||||
|
||||
pub(crate) fn fn_pricemat(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
let arg_count = args.len();
|
||||
if !(5..=6).contains(&arg_count) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let settlement = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let maturity = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let issue = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let rate = match self.get_number_no_bools(&args[3], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let yld = match self.get_number_no_bools(&args[4], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let basis = if arg_count == 6 {
|
||||
match self.get_number_no_bools(&args[5], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
if rate < 0.0 || yld < 0.0 || settlement >= maturity {
|
||||
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
|
||||
}
|
||||
if settlement < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| settlement > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| issue < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| issue > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
{
|
||||
return CalcResult::new_error(Error::NUM, cell, "Invalid number for date".to_string());
|
||||
}
|
||||
let f_iss_mat = match year_frac(issue as i64, maturity as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let f_iss_set = match year_frac(issue as i64, settlement as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let f_set_mat = match year_frac(settlement as i64, maturity as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let mut result = 1.0 + f_iss_mat * rate;
|
||||
result /= 1.0 + f_set_mat * yld;
|
||||
result -= f_iss_set * rate;
|
||||
result *= 100.0;
|
||||
CalcResult::Number(result)
|
||||
}
|
||||
|
||||
pub(crate) fn fn_yield(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if !(6..=7).contains(&args.len()) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
@@ -1786,6 +1983,261 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fn_yielddisc(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
let arg_count = args.len();
|
||||
if !(4..=5).contains(&arg_count) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let settlement = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let maturity = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let pr = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let redemption = match self.get_number_no_bools(&args[3], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let basis = if arg_count == 5 {
|
||||
match self.get_number_no_bools(&args[4], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
if settlement >= maturity || pr <= 0.0 || redemption <= 0.0 {
|
||||
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
|
||||
}
|
||||
if settlement < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| settlement > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
{
|
||||
return CalcResult::new_error(Error::NUM, cell, "Invalid number for date".to_string());
|
||||
}
|
||||
let yf = match year_frac(settlement as i64, maturity as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let result = (redemption / pr - 1.0) / yf;
|
||||
CalcResult::Number(result)
|
||||
}
|
||||
|
||||
pub(crate) fn fn_yieldmat(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
let arg_count = args.len();
|
||||
if !(5..=6).contains(&arg_count) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let settlement = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let maturity = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let issue = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let rate = match self.get_number_no_bools(&args[3], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let price = match self.get_number_no_bools(&args[4], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let basis = if arg_count == 6 {
|
||||
match self.get_number_no_bools(&args[5], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
if price <= 0.0 || rate < 0.0 || settlement >= maturity || settlement < issue {
|
||||
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
|
||||
}
|
||||
if settlement < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| settlement > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| issue < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| issue > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
{
|
||||
return CalcResult::new_error(Error::NUM, cell, "Invalid number for date".to_string());
|
||||
}
|
||||
let f_iss_mat = match year_frac(issue as i64, maturity as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let f_iss_set = match year_frac(issue as i64, settlement as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let f_set_mat = match year_frac(settlement as i64, maturity as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let mut y = 1.0 + f_iss_mat * rate;
|
||||
y /= price / 100.0 + f_iss_set * rate;
|
||||
y -= 1.0;
|
||||
y /= f_set_mat;
|
||||
CalcResult::Number(y)
|
||||
}
|
||||
|
||||
// DISC(settlement, maturity, pr, redemption, [basis])
|
||||
pub(crate) fn fn_disc(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
let arg_count = args.len();
|
||||
if !(4..=5).contains(&arg_count) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let settlement = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let maturity = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let pr = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let redemption = match self.get_number_no_bools(&args[3], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let basis = if arg_count == 5 {
|
||||
match self.get_number_no_bools(&args[4], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
if pr <= 0.0 || redemption <= 0.0 || settlement >= maturity {
|
||||
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
|
||||
}
|
||||
if settlement < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| settlement > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
{
|
||||
return CalcResult::new_error(Error::NUM, cell, "Invalid number for date".to_string());
|
||||
}
|
||||
let yf = match year_frac(settlement as i64, maturity as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let result = (1.0 - pr / redemption) / yf;
|
||||
CalcResult::Number(result)
|
||||
}
|
||||
|
||||
// RECEIVED(settlement, maturity, investment, discount, [basis])
|
||||
pub(crate) fn fn_received(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
let arg_count = args.len();
|
||||
if !(4..=5).contains(&arg_count) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let settlement = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let maturity = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let investment = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let discount = match self.get_number_no_bools(&args[3], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let basis = if arg_count == 5 {
|
||||
match self.get_number_no_bools(&args[4], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
if investment <= 0.0 || discount <= 0.0 || settlement >= maturity {
|
||||
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
|
||||
}
|
||||
if settlement < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| settlement > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
{
|
||||
return CalcResult::new_error(Error::NUM, cell, "Invalid number for date".to_string());
|
||||
}
|
||||
let yd = match year_diff(settlement as i64, maturity as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let result = investment / (1.0 - discount * yd);
|
||||
CalcResult::Number(result)
|
||||
}
|
||||
|
||||
// INTRATE(settlement, maturity, investment, redemption, [basis])
|
||||
pub(crate) fn fn_intrate(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
let arg_count = args.len();
|
||||
if !(4..=5).contains(&arg_count) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let settlement = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let maturity = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let investment = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let redemption = match self.get_number_no_bools(&args[3], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let basis = if arg_count == 5 {
|
||||
match self.get_number_no_bools(&args[4], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
if investment <= 0.0 || redemption <= 0.0 || settlement >= maturity {
|
||||
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
|
||||
}
|
||||
if settlement < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| settlement > MAXIMUM_DATE_SERIAL_NUMBER as f64
|
||||
|| maturity < MINIMUM_DATE_SERIAL_NUMBER as f64
|
||||
{
|
||||
return CalcResult::new_error(Error::NUM, cell, "Invalid number for date".to_string());
|
||||
}
|
||||
let yd = match year_diff(settlement as i64, maturity as i64, basis as i32) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
|
||||
};
|
||||
let result = ((redemption / investment) - 1.0) / yd;
|
||||
CalcResult::Number(result)
|
||||
}
|
||||
|
||||
// DOLLARDE(fractional_dollar, fraction)
|
||||
pub(crate) fn fn_dollarde(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.len() != 2 {
|
||||
|
||||
Reference in New Issue
Block a user