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