diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 98d38f578..86c5ab897 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -8,11 +8,12 @@ https://www.boost.org/LICENSE_1_0.txt = Examples :idprefix: examples_ -All examples can be found in the library `examples/` folder as well. +The following examples will help you get up and running with many of the major parts of the library. +All of these examples can be found in the library `examples/` folder as well. [#examples_construction] == Basic Construction -.This example demonstrates the basic use of the various constructors offered by the decimal types +.This https://github.com/cppalliance/decimal/blob/develop/examples/basic_construction.cpp[example] demonstrates the basic use of the various constructors offered by the decimal types ==== [source, c++] ---- @@ -35,7 +36,7 @@ Can not construct from invalid string [#examples_promotion] == Promotion and Mixed Decimal Arithmetic -.This example demonstrates the behaviors of promotion between types, and mixed decimal type arithmetic +.This https://github.com/cppalliance/decimal/blob/develop/examples/promotion.cpp[example] demonstrates the behaviors of promotion between types, and mixed decimal type arithmetic ==== [source, c++] ---- @@ -56,7 +57,7 @@ The result of a + b is a decimal64_t: 9.1 [#examples_charconv] == `` -.This example demonstrates the fundamentals of the `` like functions provided by the library +.This https://github.com/cppalliance/decimal/blob/develop/examples/charconv.cpp[example] demonstrates the fundamentals of the `` like functions provided by the library ==== [source, c++] ---- @@ -74,7 +75,7 @@ Value in scientific format with precision 20: -7.12345000000000000000e+06 [#examples_generic_programming] == Generic Programming -.This example demonstrates how to write generic code that accepts both built-in floating point types, and decimal floating point values from this library +.This https://github.com/cppalliance/decimal/blob/develop/examples/adl.cpp[example] demonstrates how to write generic code that accepts both built-in floating point types, and decimal floating point values from this library ==== [source, c++] ---- @@ -111,7 +112,7 @@ sin(-0.5) = -0.479426 [#examples_literals_constants] == Literals and Constants -.This example demonstrates how to construct values using literals, and the usage of numerical constants that are provided by the library +.This https://github.com/cppalliance/decimal/blob/develop/examples/literals.cpp[example] demonstrates how to construct values using literals, and the usage of numerical constants that are provided by the library ==== [source, c++] ---- @@ -138,7 +139,7 @@ pass:[{fmt}] support is available starting with pass:[C++14] so long as you have [#examples_fmt_format] === `` -.This example demonstrates the various formatting options provided by the library to support usage of pass:[{fmt}] +.This https://github.com/cppalliance/decimal/blob/develop/examples/fmt_format.cpp[example] demonstrates the various formatting options provided by the library to support usage of pass:[{fmt}] ==== [source, c++] ---- @@ -212,7 +213,7 @@ You must include it yourself. Taking the above example of pass:[{fmt}] and replacing all instances of `namespace fmt` with `namespace std` gives us another working example. -.This example demonstrates how to use `` with the library in-place or in addition to pass:[{fmt}] +.This https://github.com/cppalliance/decimal/blob/develop/examples/format.cpp[example] demonstrates how to use `` with the library in-place or in addition to pass:[{fmt}] ==== [source, c++] ---- @@ -242,7 +243,7 @@ Scientific Format with Specified Precision and Padding: [#examples_print] === `` -.This example demonstrates how to use `` with the library +.This https://github.com/cppalliance/decimal/blob/develop/examples/print.cpp[example] demonstrates how to use `` with the library ==== [source, c++] ---- diff --git a/doc/modules/ROOT/pages/financial_examples.adoc b/doc/modules/ROOT/pages/financial_examples.adoc index 1e3218d1b..872f82abd 100644 --- a/doc/modules/ROOT/pages/financial_examples.adoc +++ b/doc/modules/ROOT/pages/financial_examples.adoc @@ -8,23 +8,42 @@ https://www.boost.org/LICENSE_1_0.txt = Financial Examples :idprefix: financial_examples_ -== Financial Applications - -=== Simple Moving Average - -In the examples folder there is a file named `moving_average.cpp`. -This example shows how to parse historical stock data from a file and use it. -This serves as a framework for other calculations for securities. - -=== Currency Conversion -In the examples folder there is a file named `currency_conversion.cpp`. -This example shows how to simply convert currencies based off a given exchange rate. +Below are a few additional examples as to how the Decimal library can be used in the context of financial applications. +All of these examples can be found in the library `examples/` folder as well. + +== Parsing Pricing Data from File +[#examples_money_parsing] +.This https://github.com/cppalliance/decimal/blob/develop/examples/numerical_parsing.cpp[example] demonstrates the numerical differences between parsing of monetary values between using `decimal32_t` and `float` +==== +[source, c++] +---- +include::example$numerical_parsing.cpp[] +---- + +.Expected Output +.... +Number of data points: 252 + Sum from MS Excel: 52151.99 +Sum using decimal32_t: 52151.99 + Sum using float: 52151.96 +.... +==== [#examples_boost_math] -== Boost.Math Integration - -=== Bollinger Bands - -In the examples folder there is a file named `statistics.cpp`. -This example demonstrates how to parse a file, and then leverage Boost.Math to compute statistics of that data set culminating with the values of the Bollinger Bands. -This example could be extended with the simple moving average to create full bands based on the period of the moving average you would like. +== Boost.Math to Calculate Bollinger Bands +.This https://github.com/cppalliance/decimal/blob/develop/examples/statistics.cpp[example] demonstrates how we can use the decimal library with existing Boost.Math facilities to perform statistical analysis +==== +[source, c++] +---- +include::example$statistics.cpp[] +---- + +.Expected Output +.... + Mean Closing Price: $207.20 +Median Closing Price: $214.26 + Standard Deviation: $25.45 +Upper Bollinger Band: $258.11 +Lower Bollinger Band: $156.30 +.... +==== diff --git a/examples/currency_conversion.cpp b/examples/currency_conversion.cpp deleted file mode 100644 index e791c8993..000000000 --- a/examples/currency_conversion.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2025 Matt Borland -// Distributed under the Boost Software License, Version 1.0. -// https://www.boost.org/LICENSE_1_0.txt - -#include -#include -#include - -using namespace boost::decimal; - -auto convert_currency(decimal64_t amount, decimal64_t exchange_rate) -> decimal64_t -{ - return amount * exchange_rate; -} - -int main() -{ - const auto usd_amount = strtod64("1000.50", nullptr); - const auto usd_to_eur_rate = strtod64("0.92", nullptr); - - const decimal64_t eur_amount = convert_currency(usd_amount, usd_to_eur_rate); - constexpr decimal64_t exact_eur_amount(92046, -2); - - std::cout << "USD: " << std::fixed << std::setprecision(2) << usd_amount << "\n"; - std::cout << "EUR: " << std::fixed << std::setprecision(2) << eur_amount << "\n"; - - return !(eur_amount == exact_eur_amount); -} diff --git a/examples/moving_average.cpp b/examples/moving_average.cpp deleted file mode 100644 index 594069fa6..000000000 --- a/examples/moving_average.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2025 Matt Borland -// Distributed under the Boost Software License, Version 1.0. -// https://www.boost.org/LICENSE_1_0.txt - -#include "where_file.hpp" -#include -#include -#include -#include -#include -#include -#include - -using namespace boost::decimal; - -struct daily_data -{ - std::string date; - decimal64_t open; - decimal64_t high; - decimal64_t low; - decimal64_t close; - decimal64_t volume; -}; - -// Function to split a CSV line into daily_data -auto parse_csv_line(const std::string& line) -> daily_data -{ - std::stringstream ss(line); - std::string token; - daily_data data; - - // Parse each column - std::getline(ss, data.date, ','); - std::getline(ss, token, ','); - from_chars(token.c_str(), token.c_str() + token.size(), data.open); - - std::getline(ss, token, ','); - from_chars(token.c_str(), token.c_str() + token.size(), data.high); - - std::getline(ss, token, ','); - from_chars(token.c_str(), token.c_str() + token.size(), data.low); - - std::getline(ss, token, ','); - from_chars(token.c_str(), token.c_str() + token.size(), data.close); - - std::getline(ss, token, ','); - from_chars(token.c_str(), token.c_str() + token.size(), data.volume); - - return data; -} - -int main() -{ - std::vector stock_data; - const int window_size = 30; - - // Open and read the CSV file - std::ifstream file(where_file("AAPL.csv")); - std::string line; - - // Skip header line - std::getline(file, line); - - // Read data - while (std::getline(file, line)) - { - stock_data.push_back(parse_csv_line(line)); - } - - // Calculate and print 30-day moving averages - std::cout << "Date,30-Day Moving Average\n"; - - size_t loop_count = 0; // Trivial counter to ensure this ran in the CI - for (size_t i = window_size - 1; i < stock_data.size(); ++i) - { - decimal64_t sum(0); - - // Calculate sum for the window - for (size_t j = 0; j < window_size; ++j) - { - sum += stock_data[i - j].close; - } - - // Calculate average - decimal64_t moving_avg = sum / decimal64_t(window_size); - - // Print result - std::cout << stock_data[i].date << "," - << std::fixed << std::setprecision(2) << moving_avg << "\n"; - - ++loop_count; - } - - return loop_count == 0U; -} diff --git a/examples/numerical_parsing.cpp b/examples/numerical_parsing.cpp new file mode 100644 index 000000000..7842856c6 --- /dev/null +++ b/examples/numerical_parsing.cpp @@ -0,0 +1,106 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// This file briefly demonstrates the difference in results when parsing monetary values between float and decimal32_t + +#include "where_file.hpp" +#include // For type decimal32_t +#include // For support to +#include // For decimal support to +#include +#include +#include +#include +#include +#include +#include +#include + +using boost::decimal::decimal32_t; + +template +T parse_opening_price(const std::string& line); + +template <> +float parse_opening_price(const std::string& line) +{ + const auto result {std::stof(line)}; + return result; +} + +template <> +decimal32_t parse_opening_price(const std::string& line) +{ + decimal32_t result; + const auto r = from_chars(line, result); + + // If we have a parse failure throw std::invalid_argument if the environment supports it, + // using std::invalid_argument which is the same thing thrown by std::stof in the float case + // + // If we are in a no throw environment returning a qNaN will poison our results as well + if (!r) + { + // LCOV_EXCL_START + result = std::numeric_limits::quiet_NaN(); + BOOST_DECIMAL_THROW_EXCEPTION(std::invalid_argument("Parsing has failed")); + // LCOV_EXCL_STOP + } + + return result; +} + +template +T parse_csv_line(const std::string& line) +{ + std::stringstream ss(line); + std::string token; + std::string date; + + std::getline(ss, date, ','); + std::getline(ss, token, ','); + + return parse_opening_price(token); +} + +int main() +{ + // We have a CSV file containing one years worth of daily stock data for AAPL + // Here we will show the differences that arise (however small) + // between parsing with float and decimal32_t + + // Open and read the CSV file + std::ifstream file(boost::decimal::where_file("AAPL.csv")); + std::string line; + + // Skip header line + std::getline(file, line); + + std::vector decimal_opening_prices; + std::vector float_opening_prices; + + // Parse each line once into decimal32_t and once into a float + while (std::getline(file, line)) + { + decimal_opening_prices.emplace_back(parse_csv_line(line)); + float_opening_prices.emplace_back(parse_csv_line(line)); + } + + // Use std::accumulate to get the sum of all the pricing information in the array + // This will be used to compare the total value parsed + const auto decimal_sum {std::accumulate(decimal_opening_prices.begin(), + decimal_opening_prices.end(), decimal32_t{0})}; + + const auto float_sum {std::accumulate(float_opening_prices.begin(), + float_opening_prices.end(), float{0})}; + + // This is the reference value that was found using the sum command of the CSV + // inside Microsoft Excel + const std::string ms_excel_result {"52151.99"}; + + std::cout << std::setprecision(std::numeric_limits::digits10 + 1) + << "Number of data points: " << decimal_opening_prices.size() << '\n' + << " Sum from MS Excel: " << ms_excel_result << '\n' + << "Sum using decimal32_t: " << decimal_sum << '\n' + << " Sum using float: " << float_sum << std::endl; +} diff --git a/examples/statistics.cpp b/examples/statistics.cpp index d197ce84a..9a325e441 100644 --- a/examples/statistics.cpp +++ b/examples/statistics.cpp @@ -1,12 +1,17 @@ // Copyright 2025 Matt Borland // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt +// +// This example demonstrates how to perform statistics using boost.math // Needed for operations with boost math #define BOOST_DECIMAL_ALLOW_IMPLICIT_INTEGER_CONVERSIONS #include "where_file.hpp" -#include +#include // For type decimal64_t +#include // For from_chars +#include // Decimal support to and +#include // For sqrt of decimal types #include #include #include @@ -15,6 +20,7 @@ #include // Warning suppression for boost.math +// Boost.decimal is tested with -Werror -Wall -Wextra and a few other additional flags #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wfloat-equal" @@ -35,19 +41,18 @@ # pragma GCC diagnostic pop #endif -using namespace boost::decimal; - +// This struct holds all the information that is provided +// for a single trading day struct daily_data { std::string date; - decimal64_t open; - decimal64_t high; - decimal64_t low; - decimal64_t close; - decimal64_t volume; + boost::decimal::decimal64_t open; + boost::decimal::decimal64_t high; + boost::decimal::decimal64_t low; + boost::decimal::decimal64_t close; + boost::decimal::decimal64_t volume; }; -// Function to split a CSV line into daily_data auto parse_csv_line(const std::string& line) -> daily_data { std::stringstream ss(line); @@ -76,10 +81,13 @@ auto parse_csv_line(const std::string& line) -> daily_data int main() { + // The first few lines of this file are similar to the previous example + // in that we parse a single year of AAPL stock data before we can do anything useful with + std::vector stock_data; // Open and read the CSV file - std::ifstream file(where_file("AAPL.csv")); + std::ifstream file(boost::decimal::where_file("AAPL.csv")); std::string line; // Skip header line @@ -98,23 +106,23 @@ int main() closing_prices.emplace_back(day.close); } - const auto mean_closing_price = boost::math::statistics::mean(closing_prices); - const auto median_closing_price = boost::math::statistics::median(closing_prices); - const auto variance_closing_price = boost::math::statistics::variance(closing_prices); - const auto std_dev_closing_price = sqrt(variance_closing_price); + // Here we use Boost.Math's statistics facilities + // As shown at the top of the file you will need to define BOOST_DECIMAL_ALLOW_IMPLICIT_INTEGER_CONVERSIONS, + // and suppress a few warnings to make this build cleanly + const decimal64_t mean_closing_price = boost::math::statistics::mean(closing_prices); + const decimal64_t median_closing_price = boost::math::statistics::median(closing_prices); + const decimal64_t variance_closing_price = boost::math::statistics::variance(closing_prices); + const decimal64_t std_dev_closing_price = boost::decimal::sqrt(variance_closing_price); // 2-Sigma Bollinger Bands - const auto upper_band = mean_closing_price + 2 * std_dev_closing_price; - const auto lower_band = mean_closing_price - 2 * std_dev_closing_price; + // These are of a single point in time rather than making a plot over time for simplicity + const decimal64_t upper_band = mean_closing_price + 2 * std_dev_closing_price; + const decimal64_t lower_band = mean_closing_price - 2 * std_dev_closing_price; std::cout << std::fixed << std::setprecision(2) - << " Mean Closing Price: " << mean_closing_price << '\n' - << " Standard Deviation: " << std_dev_closing_price << '\n' - << "Upper Bollinger Band: " << upper_band << '\n' - << "Lower Bollinger Band: " << lower_band << std::endl; - - // Mean = 207.21 - // Median = 214.27 - return mean_closing_price > median_closing_price; + << " Mean Closing Price: $" << mean_closing_price << '\n' + << "Median Closing Price: $" << median_closing_price << '\n' + << " Standard Deviation: $" << std_dev_closing_price << '\n' + << "Upper Bollinger Band: $" << upper_band << '\n' + << "Lower Bollinger Band: $" << lower_band << std::endl; } - diff --git a/test/Jamfile b/test/Jamfile index 8462b2d14..9627c25b6 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -199,13 +199,12 @@ run ../examples/charconv_cohort_preservation.cpp ; run ../examples/literals.cpp ; run ../examples/rounding_mode.cpp ; run ../examples/rounding_mode_compile_time.cpp ; -run ../examples/moving_average.cpp ; -run ../examples/currency_conversion.cpp ; run ../examples/statistics.cpp ; run ../examples/format.cpp ; run ../examples/fmt_format.cpp ; run ../examples/print.cpp ; run ../examples/promotion.cpp ; +run ../examples/numerical_parsing.cpp ; # Test compilation of separate headers compile compile_tests/bid_conversion.cpp ;