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