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