diff --git a/doc/decimal/examples.adoc b/doc/decimal/examples.adoc index 6c8b9b4de..a224018bd 100644 --- a/doc/decimal/examples.adoc +++ b/doc/decimal/examples.adoc @@ -228,3 +228,11 @@ 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. + +== 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. diff --git a/examples/moving_average.cpp b/examples/moving_average.cpp index 3ac45ca85..4e9a6d972 100644 --- a/examples/moving_average.cpp +++ b/examples/moving_average.cpp @@ -2,7 +2,7 @@ // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt - +#include "where_file.hpp" #include #include #include @@ -13,100 +13,6 @@ using namespace boost::decimal; -auto where_file(const std::string& test_vectors_filename) -> std::string -{ - // Try to open the file in each of the known relative paths - // in order to find out where it is located. - - // Boost-root - std::string test_vectors_filename_relative = "libs/decimal/examples/" + test_vectors_filename; - - std::ifstream in_01(test_vectors_filename_relative.c_str()); - - const bool file_01_is_open { in_01.is_open() }; - - - if(file_01_is_open) - { - in_01.close(); - } - else - { - // Local test directory or IDE - test_vectors_filename_relative = "../examples/" + test_vectors_filename; - - std::ifstream in_02(test_vectors_filename_relative.c_str()); - - const bool file_02_is_open { in_02.is_open() }; - - if(file_02_is_open) - { - in_02.close(); - } - else - { - // test/cover - test_vectors_filename_relative = "../../examples/" + test_vectors_filename; - - std::ifstream in_03(test_vectors_filename_relative.c_str()); - - const bool file_03_is_open { in_03.is_open() }; - - if(file_03_is_open) - { - in_03.close(); - } - else - { - // CMake builds - test_vectors_filename_relative = "../../../../libs/decimal/examples/" + test_vectors_filename; - - std::ifstream in_04(test_vectors_filename_relative.c_str()); - - const bool file_04_is_open { in_04.is_open() }; - - if(file_04_is_open) - { - in_04.close(); - } - else - { - // Try to open the file from the absolute path. - test_vectors_filename_relative = test_vectors_filename; - - std::ifstream in_05(test_vectors_filename_relative.c_str()); - - const bool file_05_is_open { in_05.is_open() }; - - if(file_05_is_open) - { - in_05.close(); - } - else - { - // Clion Cmake builds - test_vectors_filename_relative = "../../../libs/decimal/examples/" + test_vectors_filename; - - std::ifstream in_06(test_vectors_filename_relative.c_str()); - - const bool file_06_is_open { in_06.is_open() }; - if (file_06_is_open) - { - in_06.close(); - } - else - { - test_vectors_filename_relative = ""; - } - } - } - } - } - } - - return test_vectors_filename_relative; -} - struct daily_data { std::string date; @@ -157,7 +63,8 @@ int main() std::getline(file, line); // Read data - while (std::getline(file, line)) { + while (std::getline(file, line)) + { stock_data.push_back(parse_csv_line(line)); } @@ -170,7 +77,8 @@ int main() decimal64 sum(0); // Calculate sum for the window - for (size_t j = 0; j < window_size; ++j) { + for (size_t j = 0; j < window_size; ++j) + { sum += stock_data[i - j].close; } diff --git a/examples/statistics.cpp b/examples/statistics.cpp new file mode 100644 index 000000000..2239cb295 --- /dev/null +++ b/examples/statistics.cpp @@ -0,0 +1,114 @@ +// 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 + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-conversion" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +using namespace boost::decimal; + +struct daily_data +{ + std::string date; + decimal64 open; + decimal64 high; + decimal64 low; + decimal64 close; + decimal64 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, data.open); + + std::getline(ss, token, ','); + from_chars(token, data.high); + + std::getline(ss, token, ','); + from_chars(token, data.low); + + std::getline(ss, token, ','); + from_chars(token, data.close); + + std::getline(ss, token, ','); + from_chars(token, data.volume); + + return data; +} + +int main() +{ + std::vector stock_data; + + // 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)); + } + + // Get the closing prices for the entire year + std::vector closing_prices; + for (const auto& day : stock_data) + { + 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); + + // 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; + + 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; +} + diff --git a/examples/where_file.hpp b/examples/where_file.hpp new file mode 100644 index 000000000..60ab551a1 --- /dev/null +++ b/examples/where_file.hpp @@ -0,0 +1,113 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_DECIMAL_EXAMPLE_WHERE_FILE_HPP +#define BOOST_DECIMAL_EXAMPLE_WHERE_FILE_HPP + +#include +#include +#include +#include + +namespace boost { +namespace decimal { + +auto where_file(const std::string& test_vectors_filename) -> std::string +{ + // Try to open the file in each of the known relative paths + // in order to find out where it is located. + + // Boost-root + std::string test_vectors_filename_relative = "libs/decimal/examples/" + test_vectors_filename; + + std::ifstream in_01(test_vectors_filename_relative.c_str()); + + const bool file_01_is_open { in_01.is_open() }; + + + if(file_01_is_open) + { + in_01.close(); + } + else + { + // Local test directory or IDE + test_vectors_filename_relative = "../examples/" + test_vectors_filename; + + std::ifstream in_02(test_vectors_filename_relative.c_str()); + + const bool file_02_is_open { in_02.is_open() }; + + if(file_02_is_open) + { + in_02.close(); + } + else + { + // test/cover + test_vectors_filename_relative = "../../examples/" + test_vectors_filename; + + std::ifstream in_03(test_vectors_filename_relative.c_str()); + + const bool file_03_is_open { in_03.is_open() }; + + if(file_03_is_open) + { + in_03.close(); + } + else + { + // CMake builds + test_vectors_filename_relative = "../../../../libs/decimal/examples/" + test_vectors_filename; + + std::ifstream in_04(test_vectors_filename_relative.c_str()); + + const bool file_04_is_open { in_04.is_open() }; + + if(file_04_is_open) + { + in_04.close(); + } + else + { + // Try to open the file from the absolute path. + test_vectors_filename_relative = test_vectors_filename; + + std::ifstream in_05(test_vectors_filename_relative.c_str()); + + const bool file_05_is_open { in_05.is_open() }; + + if(file_05_is_open) + { + in_05.close(); + } + else + { + // Clion Cmake builds + test_vectors_filename_relative = "../../../libs/decimal/examples/" + test_vectors_filename; + + std::ifstream in_06(test_vectors_filename_relative.c_str()); + + const bool file_06_is_open { in_06.is_open() }; + if (file_06_is_open) + { + in_06.close(); + } + else + { + test_vectors_filename_relative = ""; + } + } + } + } + } + } + + return test_vectors_filename_relative; +} + +} // namespace decimal +} // namespace boost + +#endif //BOOST_DECIMAL_EXAMPLE_WHERE_FILE_HPP diff --git a/test/Jamfile b/test/Jamfile index bddeec3c7..c7b1bd08a 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -152,3 +152,4 @@ run ../examples/literals.cpp ; run ../examples/rounding_mode.cpp ; run ../examples/moving_average.cpp ; run ../examples/currency_conversion.cpp ; +run ../examples/statistics.cpp ;