1#![allow(dead_code)]
88
89use itertools::Itertools;
90use num_format::{Locale, ToFormattedString};
91
92pub use float_cmp;
93pub use num_format;
94
95pub mod util;
100#[doc(inline)]
101pub use util::{
102 brent_root, FinanceError, FinanceResult, Money, PeriodLength, Periods, PositivePrice, Rate,
103};
104
105pub mod round;
106#[doc(inline)]
107pub use round::*;
108
109pub mod tvm;
114#[doc(inline)]
115pub use tvm::*;
116
117pub mod cashflow;
118#[doc(inline)]
119pub use cashflow::*;
120
121pub mod convert_rate;
125#[doc(inline)]
126pub use convert_rate::*;
127
128pub mod tvm_convert_rate;
129#[doc(inline)]
130pub use tvm_convert_rate::*;
131
132pub mod amortization;
137#[doc(inline)]
138pub use amortization::*;
139
140pub mod returns;
141#[doc(inline)]
142pub use returns::*;
143
144pub mod stocks;
145#[doc(inline)]
146pub use stocks::*;
147
148pub mod derivatives;
149#[doc(inline)]
150pub use derivatives::{
151 american_implied_vol, black76_greeks, black76_implied_vol, black76_parity_residual,
152 black76_price, black76_solution, black76_terms, bsm_cross_greeks, bsm_greeks, bsm_implied_vol,
153 bsm_price, bsm_solution, bsm_terms, crr_greeks, crr_price, crr_solution, forward_moneyness,
154 gk_cross_greeks, gk_greeks, gk_implied_vol, gk_parity_residual, gk_price, gk_solution,
155 intrinsic, put_call_parity_residual, spot_moneyness, time_value, tree_implied_vol,
156 Black76Greeks, Black76Params, Black76Solution, Black76State, Black76Terms, BsmCrossGreeks,
157 BsmGreeks, BsmParams, BsmSolution, BsmState, BsmTerms, CrrGreeks, CrrNode, CrrParams,
158 CrrSolution, ExerciseStyle, GkGreeks, GkParams, GkSolution, GkState, OptionType,
159 ValidatedBlack76, ValidatedBsm, ValidatedCrr, ValidatedGk,
160};
161
162use std::cmp::max;
163use std::fmt::{Debug, Error, Formatter};
164
165#[macro_export]
191macro_rules! is_approx_equal {
192 ( $x1:expr, $x2:expr ) => {
193 float_cmp::approx_eq!(f64, $x1, $x2, epsilon = 0.000001, ulps = 20)
194 };
195}
196
197#[macro_export]
198macro_rules! assert_approx_equal {
199 ( $x1:expr, $x2:expr ) => {
200 assert!(float_cmp::approx_eq!(
201 f64,
202 $x1,
203 $x2,
204 epsilon = 0.000001,
205 ulps = 20
206 ));
207 };
208}
209
210#[macro_export]
211macro_rules! assert_same_sign_or_zero {
212 ( $x1:expr, $x2:expr ) => {
213 assert!(
214 is_approx_equal!($x1, 0.0)
215 || is_approx_equal!($x2, 0.0)
216 || ($x1 > 0.0 && $x2 > 0.0)
217 || ($x1 < -0.0 && $x2 < -0.0)
218 );
219 };
220}
221
222#[macro_export]
223macro_rules! is_approx_equal_symmetry_test {
224 ( $x1:expr, $x2:expr ) => {
225 if (($x1 > 0.000001 && $x1 < 1_000_000.0) || ($x1 < -0.000001 && $x1 > -1_000_000.0))
226 && (($x2 > 0.000001 && $x2 < 1_000_000.0) || ($x2 < -0.000001 && $x2 > -1_000_000.0))
227 {
228 float_cmp::approx_eq!(f64, $x1, $x2, epsilon = 0.00000001, ulps = 2)
229 } else {
230 true
231 }
232 };
233}
234
235#[macro_export]
236macro_rules! assert_approx_equal_symmetry_test {
237 ( $x1:expr, $x2:expr ) => {
238 if (($x1 > 0.000001 && $x1 < 1_000_000.0) || ($x1 < -0.000001 && $x1 > -1_000_000.0))
239 && (($x2 > 0.000001 && $x2 < 1_000_000.0) || ($x2 < -0.000001 && $x2 > -1_000_000.0))
240 {
241 assert!(float_cmp::approx_eq!(
242 f64,
243 $x1,
244 $x2,
245 epsilon = 0.00000001,
246 ulps = 2
247 ));
248 }
249 };
250}
251
252#[macro_export]
253macro_rules! assert_rounded_2 {
254 ( $x1:expr, $x2:expr ) => {
255 assert_eq!(
256 ($x1 * 100.0f64).round() / 100.0,
257 ($x2 * 100.0f64).round() / 100.0
258 );
259 };
260}
261
262#[macro_export]
263macro_rules! assert_rounded_4 {
264 ( $x1:expr, $x2:expr ) => {
265 assert_eq!(
266 ($x1 * 10_000.0f64).round() / 10_000.0,
267 ($x2 * 10_000.0f64).round() / 10_000.0
268 );
269 };
270}
271
272#[macro_export]
273macro_rules! assert_rounded_6 {
274 ( $x1:expr, $x2:expr ) => {
275 assert_eq!(
276 ($x1 * 1_000_000.0f64).round() / 1_000_000.0,
277 ($x2 * 1_000_000f64).round() / 1_000_000.0
278 );
279 };
280}
281
282#[macro_export]
283macro_rules! assert_rounded_8 {
284 ( $x1:expr, $x2:expr ) => {
285 assert_eq!(
286 ($x1 * 100_000_000.0f64).round() / 100_000_000.0,
287 ($x2 * 100_000_000.0f64).round() / 100_000_000.0
288 );
289 };
290}
291
292#[macro_export]
293macro_rules! repeating_vec {
294 ( $x1:expr, $x2:expr ) => {{
295 let mut repeats = vec![];
296 for _i in 0..$x2 {
297 repeats.push($x1);
298 }
299 repeats
300 }};
301}
302
303fn decimal_separator_locale_opt(locale: Option<&Locale>) -> String {
304 match locale {
305 Some(locale) => locale.decimal().to_string(),
306 None => ".".to_string(),
307 }
308}
309
310fn minus_sign_locale_opt(val: f64, locale: Option<&Locale>) -> String {
311 if val.is_sign_negative() {
312 match locale {
313 Some(locale) => locale.minus_sign().to_string(),
314 None => "-".to_string(),
315 }
316 } else {
317 "".to_string()
318 }
319}
320
321pub(crate) fn parse_and_format_int(val: &str) -> String {
322 parse_and_format_int_locale_opt(val, None)
323}
324
325pub(crate) fn parse_and_format_int_locale_opt(val: &str, locale: Option<&Locale>) -> String {
326 let float_val: f64 = val.parse().unwrap();
327 if float_val.is_finite() {
328 let int_val: i128 = val.parse().unwrap();
329 format_int_locale_opt(int_val, locale)
330 } else {
331 val.to_string()
335 }
336}
337
338pub(crate) fn format_int<T>(val: T) -> String
339where
340 T: ToFormattedString,
341{
342 format_int_locale_opt(val, None)
343}
344
345pub(crate) fn format_int_locale_opt<T>(val: T, locale: Option<&Locale>) -> String
346where
347 T: ToFormattedString,
348{
349 match locale {
350 Some(locale) => val.to_formatted_string(locale),
351 None => val.to_formatted_string(&Locale::en).replace(",", "_"),
352 }
353}
354
355pub(crate) fn format_float<T>(val: T) -> String
356where
357 T: Into<f64>,
358{
359 format_float_locale_opt(val, None, None)
360}
361
362pub(crate) fn format_rate<T>(val: T) -> String
363where
364 T: Into<f64>,
365{
366 format_float_locale_opt(val, None, Some(6))
367}
368
369pub(crate) fn format_float_locale_opt<T>(
370 val: T,
371 locale: Option<&Locale>,
372 precision: Option<usize>,
373) -> String
374where
375 T: Into<f64>,
376{
377 let precision = precision.unwrap_or(4);
378 let val = val.into();
379 if val.is_finite() {
380 if precision == 0 {
384 format_int_locale_opt(val.round() as i128, locale)
385 } else {
386 let scale = 10_f64.powi(precision as i32);
389 let rounded_abs = (val.abs() * scale).round() / scale;
390 let left = format_int_locale_opt(rounded_abs.trunc() as i128, locale);
391 let frac_digits = (rounded_abs.fract() * scale).round() as u64;
392 let right = format!("{:0>width$}", frac_digits, width = precision);
393 let minus_sign = minus_sign_locale_opt(val, locale);
394 format!(
395 "{}{}{}{}",
396 minus_sign,
397 left,
398 decimal_separator_locale_opt(locale),
399 right
400 )
401 }
402 } else {
403 format!("{:?}", val)
404 }
405}
406
407pub(crate) fn print_table_locale_opt(
408 columns: &[(String, String, bool)],
409 mut data: Vec<Vec<String>>,
410 locale: Option<&num_format::Locale>,
411 precision: Option<usize>,
412) {
413 if columns.is_empty() || data.is_empty() {
414 return;
415 }
416
417 let column_separator = " ";
418
419 let column_count = data[0].len();
420
421 for row_index in 0..data.len() {
422 for col_index in 0..column_count {
423 let visible = columns[col_index].2;
424 if visible {
425 if !data[row_index][col_index].is_empty() {
428 let col_type = columns[col_index].1.to_lowercase();
429 if col_type != "s" {
431 if col_type == "f" || col_type == "r" {
432 let precision = if col_type == "f" {
433 precision
434 } else {
435 precision_opt_set_min(precision, 6)
436 };
437 if let Ok(n) = data[row_index][col_index].parse::<f64>() {
439 data[row_index][col_index] =
440 format_float_locale_opt(n, locale, precision);
441 }
442 } else if col_type == "i" {
443 data[row_index][col_index] = parse_and_format_int_locale_opt(
444 &data[row_index][col_index],
445 locale,
446 );
447 }
448 }
450 }
451 }
452 }
453 }
454
455 let mut column_widths = vec![];
456 for col_index in 0..column_count {
457 let visible = columns[col_index].2;
458 let width = if visible {
459 let mut width = columns[col_index].0.len();
460 for row in &data {
461 width = max(width, row[col_index].len());
462 }
463 width
464 } else {
465 0
466 };
467 column_widths.push(width);
468 }
469
470 let header_line = columns
471 .iter()
472 .enumerate()
473 .map(|(col_index, (header, _type, visible))| {
474 if *visible {
475 format!(
476 "{:>width$}{}",
477 header,
478 column_separator,
479 width = column_widths[col_index]
480 )
481 } else {
482 "".to_string()
483 }
484 })
485 .join("");
486 println!("\n{}", header_line.trim_end());
487
488 let dash_line = columns
489 .iter()
490 .enumerate()
491 .map(|(col_index, (_header, _type, visible))| {
492 if *visible {
493 format!(
494 "{}{}",
495 "-".repeat(column_widths[col_index]),
496 column_separator
497 )
498 } else {
499 "".to_string()
500 }
501 })
502 .join("");
503 println!("{}", dash_line.trim_end());
504
505 for row in data.iter() {
506 let value_line = row
507 .iter()
508 .enumerate()
509 .map(|(col_index, value)| {
510 let visible = columns[col_index].2;
511 if visible {
512 format!(
513 "{:>width$}{}",
514 value,
515 column_separator,
516 width = column_widths[col_index]
517 )
518 } else {
519 "".to_string()
520 }
521 })
522 .join("");
523 println!("{}", value_line.trim_end());
524 }
525}
526
527pub(crate) fn print_ab_comparison_values_string(field_name: &str, value_a: &str, value_b: &str) {
528 print_ab_comparison_values_internal(field_name, value_a, value_b, false);
529}
530
531pub(crate) fn print_ab_comparison_values_int(
532 field_name: &str,
533 value_a: i128,
534 value_b: i128,
535 locale: Option<&num_format::Locale>,
536) {
537 print_ab_comparison_values_internal(
538 field_name,
539 &format_int_locale_opt(value_a, locale),
540 &format_int_locale_opt(value_b, locale),
541 true,
542 );
543}
544
545pub(crate) fn print_ab_comparison_values_float(
546 field_name: &str,
547 value_a: f64,
548 value_b: f64,
549 locale: Option<&num_format::Locale>,
550 precision: Option<usize>,
551) {
552 print_ab_comparison_values_internal(
553 field_name,
554 &format_float_locale_opt(value_a, locale, precision),
555 &format_float_locale_opt(value_b, locale, precision),
556 true,
557 );
558}
559
560pub(crate) fn print_ab_comparison_values_rate(
561 field_name: &str,
562 value_a: f64,
563 value_b: f64,
564 locale: Option<&num_format::Locale>,
565 precision: Option<usize>,
566) {
567 let precision = precision_opt_set_min(precision, 6);
568 print_ab_comparison_values_float(field_name, value_a, value_b, locale, precision);
569}
570
571pub(crate) fn print_ab_comparison_values_bool(field_name: &str, value_a: bool, value_b: bool) {
572 print_ab_comparison_values_internal(
573 field_name,
574 &format!("{:?}", value_a),
575 &format!("{:?}", value_b),
576 false,
577 );
578}
579
580fn print_ab_comparison_values_internal(
581 field_name: &str,
582 value_a: &str,
583 value_b: &str,
584 right_align: bool,
585) {
586 if value_a == value_b {
587 println!("{}: {}", field_name, value_a);
588 } else if right_align {
589 let width = max(value_a.len(), value_b.len());
590 println!("{} a: {:>width$}", field_name, value_a, width = width);
591 println!("{} b: {:>width$}", field_name, value_b, width = width);
592 } else {
593 println!("{} a: {}", field_name, value_a);
594 println!("{} b: {}", field_name, value_b);
595 }
596}
597
598fn precision_opt_set_min(precision: Option<usize>, min: usize) -> Option<usize> {
599 Some(match precision {
600 Some(precision) => precision.max(min),
601 None => 6,
602 })
603}
604
605#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
607pub enum ValueType {
608 Payment,
609 Rate,
610}
611
612impl ValueType {
613 pub fn is_payment(&self) -> bool {
614 matches!(self, ValueType::Payment)
615 }
616
617 pub fn is_rate(&self) -> bool {
618 matches!(self, ValueType::Rate)
619 }
620}
621
622impl std::fmt::Display for ValueType {
623 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
624 match self {
625 ValueType::Payment => write!(f, "Payment"),
626 ValueType::Rate => write!(f, "Rate"),
627 }
628 }
629}
630
631#[derive(Clone, Debug)]
656pub enum Schedule {
657 Repeating {
658 value_type: ValueType,
659 value: f64,
660 periods: u32,
661 },
662 Custom {
663 value_type: ValueType,
664 values: Vec<f64>,
665 },
666}
667
668impl Schedule {
669 pub fn new_repeating(value_type: ValueType, value: f64, periods: u32) -> FinanceResult<Self> {
674 crate::util::error::require_finite("value", value)?;
675 Ok(Schedule::Repeating {
676 value_type,
677 value,
678 periods,
679 })
680 }
681
682 pub fn new_custom(value_type: ValueType, values: &[f64]) -> FinanceResult<Self> {
688 if values.is_empty() {
689 return Err(FinanceError::EmptyInput { what: "values" });
690 }
691 for (i, &value) in values.iter().enumerate() {
692 if !value.is_finite() {
693 return Err(FinanceError::NonFinite {
694 field: "values",
695 value,
696 });
697 }
698 let _ = i;
699 }
700 Ok(Schedule::Custom {
701 value_type,
702 values: values.to_vec(),
703 })
704 }
705
706 pub fn is_payment(&self) -> bool {
707 self.value_type().is_payment()
708 }
709
710 pub fn is_rate(&self) -> bool {
711 self.value_type().is_rate()
712 }
713
714 pub fn value_type(&self) -> &ValueType {
715 match self {
716 Schedule::Repeating { value_type, .. } => value_type,
717 Schedule::Custom { value_type, .. } => value_type,
718 }
719 }
720
721 pub fn value(&self) -> Option<f64> {
723 match self {
724 Schedule::Repeating { value, .. } => Some(*value),
725 Schedule::Custom { .. } => None,
726 }
727 }
728
729 pub fn get(&self, index: usize) -> Option<f64> {
731 match self {
732 Schedule::Repeating { value, periods, .. } => {
733 if index < *periods as usize {
734 Some(*value)
735 } else {
736 None
737 }
738 }
739 Schedule::Custom { values, .. } => values.get(index).copied(),
740 }
741 }
742
743 pub fn max(&self) -> Option<f64> {
745 match self {
746 Schedule::Repeating { value, .. } => Some(*value),
747 Schedule::Custom { values, .. } => {
748 if values.is_empty() {
749 None
750 } else {
751 Some(values.iter().cloned().fold(f64::NAN, f64::max))
752 }
753 }
754 }
755 }
756
757 pub fn len(&self) -> usize {
759 match self {
760 Schedule::Repeating { periods, .. } => *periods as usize,
761 Schedule::Custom { values, .. } => values.len(),
762 }
763 }
764
765 pub fn is_empty(&self) -> bool {
766 self.len() == 0
767 }
768}
769
770#[derive(Debug)]
771pub struct ScenarioList {
772 pub setup: String,
773 pub input_variable: TvmVariable,
774 pub output_variable: TvmVariable,
775 pub entries: Vec<ScenarioEntry>,
776}
777
778pub struct ScenarioEntry {
779 pub input: f64,
780 pub output: f64,
781 input_precision: usize,
782 output_precision: usize,
783}
784
785impl ScenarioList {
786 pub(crate) fn new(
787 setup: String,
788 input_variable: TvmVariable,
789 output_variable: TvmVariable,
790 entries: Vec<(f64, f64)>,
791 ) -> Self {
792 let input_precision = match input_variable {
793 TvmVariable::Periods => 0,
794 TvmVariable::Rate => 6,
795 _ => 4,
796 };
797 let output_precision = match output_variable {
798 TvmVariable::Periods => 0,
799 TvmVariable::Rate => 6,
800 _ => 4,
801 };
802 let entries = entries
803 .iter()
804 .map(|entry| ScenarioEntry::new(entry.0, entry.1, input_precision, output_precision))
805 .collect();
806 Self {
807 setup,
808 input_variable,
809 output_variable,
810 entries,
811 }
812 }
813
814 pub fn print_table(&self) {
815 self.print_table_locale_opt(None, None);
816 }
817
818 pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
819 self.print_table_locale_opt(Some(locale), Some(precision));
820 }
821
822 fn print_table_locale_opt(
823 &self,
824 locale: Option<&num_format::Locale>,
825 precision: Option<usize>,
826 ) {
827 let columns = vec![
828 self.input_variable.table_column_spec(true),
829 self.output_variable.table_column_spec(true),
830 ];
831 let data = self
833 .entries
834 .iter()
835 .map(|entry| vec![entry.input.to_string(), entry.output.to_string()])
836 .collect::<Vec<_>>();
837 print_table_locale_opt(&columns, data, locale, precision);
838 }
839}
840
841impl ScenarioEntry {
842 pub(crate) fn new(
843 input: f64,
844 output: f64,
845 input_precision: usize,
846 output_precision: usize,
847 ) -> Self {
848 Self {
849 input,
850 output,
851 input_precision,
852 output_precision,
853 }
854 }
855}
856
857impl Debug for ScenarioEntry {
858 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
859 let input = format_float_locale_opt(self.input, None, Some(self.input_precision));
860 let output = format_float_locale_opt(self.output, None, Some(self.output_precision));
861 write!(f, "{{ input: {}, output: {} }}", input, output)
862 }
863}
864
865pub(crate) fn columns_with_strings(columns: &[(&str, &str, bool)]) -> Vec<(String, String, bool)> {
866 columns
867 .iter()
868 .map(|(label, data_type, visible)| (label.to_string(), data_type.to_string(), *visible))
869 .collect()
870}
871
872pub(crate) fn initialized_vector<L, V>(length: L, value: V) -> Vec<V>
873where
874 L: Into<usize>,
875 V: Copy,
876{
877 let mut v = vec![];
878 for _ in 0..length.into() {
879 v.push(value);
880 }
881 v
882}
883
884#[cfg(test)]
885mod tests {
886 use super::*;
887
888 #[test]
889 fn test_schedule_new_and_get() {
890 let s = Schedule::new_repeating(ValueType::Rate, 0.05, 3).unwrap();
891 assert_eq!(s.len(), 3);
892 assert_eq!(s.get(0), Some(0.05));
893 assert_eq!(s.get(3), None);
894 assert!(Schedule::new_repeating(ValueType::Rate, f64::NAN, 1).is_err());
895
896 let c = Schedule::new_custom(ValueType::Payment, &[1.0, 2.0]).unwrap();
897 assert_eq!(c.get(1), Some(2.0));
898 assert_eq!(c.get(2), None);
899 assert!(Schedule::new_custom(ValueType::Payment, &[]).is_err());
900 assert!(Schedule::new_custom(ValueType::Payment, &[1.0, f64::INFINITY]).is_err());
901 }
902
903 #[test]
904 fn test_assert_same_sign_or_zero_nominal() {
905 assert_same_sign_or_zero!(0.0, 0.0);
906 assert_same_sign_or_zero!(0.0, -0.0);
907 assert_same_sign_or_zero!(-0.0, 0.0);
908 assert_same_sign_or_zero!(-0.0, -0.0);
909 assert_same_sign_or_zero!(0.023, 0.023);
910 assert_same_sign_or_zero!(10.0, 0.023);
911 assert_same_sign_or_zero!(-0.000045, -100.0);
912 assert_same_sign_or_zero!(0.023, 0.0);
913 assert_same_sign_or_zero!(0.0, 0.023);
914 assert_same_sign_or_zero!(0.023, -0.0);
915 assert_same_sign_or_zero!(-0.0, 0.023);
916 assert_same_sign_or_zero!(-0.000045, -100.0);
917 assert_same_sign_or_zero!(-0.000045, 0.0);
918 assert_same_sign_or_zero!(0.0, -100.0);
919 assert_same_sign_or_zero!(-0.000045, -0.0);
920 assert_same_sign_or_zero!(-0.0, -100.0);
921 assert_same_sign_or_zero!(100.0, -0.00000000001864464138634503);
922 }
923
924 #[should_panic]
925 #[test]
926 fn test_assert_same_sign_or_zero_fail_diff_sign() {
927 assert_same_sign_or_zero!(-0.000045, 100.0);
928 }
929}