diff --git a/src/bot.cpp b/src/bot.cpp index 579340b..d8529ed 100644 --- a/src/bot.cpp +++ b/src/bot.cpp @@ -103,18 +103,18 @@ void Bot::commandHandler(const dpp::slashcommand_t &event) } // Create candlestick chart - (showV == "n") ? createCandle(symbol, period) : createCandleWithVolume(symbol, period); + (showV == "n") ? createCandleChart(symbol, period, false) : createCandleChart(symbol, period, true); // Additional delay to make sure the file is fully written to disk // Without this delay the bot sends an empty image file std::this_thread::sleep_for(std::chrono::milliseconds{1000}); // If the file exists, add it to the message - std::string imagePath = (showV == "n") ? "../images/candle_chart.png" : "../images/candle_volume.png"; + std::string imagePath = "../images/candle_chart.png"; if (std::filesystem::exists(imagePath)) { dpp::message msg{"### Candlestick chart for " + name + "\n" + note}; - msg.add_file(showV == "n" ? "candle_chart.png" : "candle_volume.png", dpp::utility::read_file(imagePath)); + msg.add_file("candle_chart.png", dpp::utility::read_file(imagePath)); event.reply(msg); // Delete the file after sending the message diff --git a/src/include/visualize.h b/src/include/visualize.h index 18344fb..4ab500b 100644 --- a/src/include/visualize.h +++ b/src/include/visualize.h @@ -21,25 +21,16 @@ /// Any other value will result in an error message and no plot. void priceGraph(std::string symbol, std::string duration, int mode); -/// Plots OHLC data and saves the candlestick chart as candle_chart.png in the folder 'images'. -/// This function fetches OHLC (Open-High-Low-Close) data and creates a candlestick chart. -/// The interval of the data is one day. -/// @param symbol The symbol of the stock/future/index/crypto. -/// @param duration The duration/period in the format: 1y, 6mo, 2w, 12d, etc. -/// @note Please note that using OHLC data of a period >6 months may lead -/// to a candlestick chart that is not clearly readable. In such cases, the candlesticks -/// may appear very thin or small, making it hard to discern the details. -void createCandle(std::string symbol, std::string duration); - -/// Plots OHLCV data and saves the candlestick chart as candle_volume.png in the folder 'images'. -/// This function fetches OHLCV (Open-High-Low-Close-Volume) data and creates a candlestick chart with the volume bars -/// plotted at the bottom of the chart. The interval of the data is one day. +/// Plots OHLC(V) data and saves the candlestick chart as candle_chart.png in the folder 'images'. +/// This function fetches OHLCV (Open-High-Low-Close-Volume) data and creates a candlestick chart, +/// optionally with the volume bars plotted at the bottom. The interval of the data is one day. /// @param symbol The symbol of the stock/future/index/crypto. /// @param duration The duration/period in the format: 1y, 6mo, 2w, 12d, etc. +/// @param withVolume Set to true if you want to plot volumes as well. /// @note Please note that using OHLCV data of a period >6 months may lead /// to a candlestick chart and volume graph that are not clearly readable. In such cases, the candlesticks /// and volume bars may appear very thin or small, making it hard to discern the details. -void createCandleWithVolume(std::string symbol, std::string duration); +void createCandleChart(std::string symbol, std::string duration, bool withVolume = false); #endif // VISUALIZE_H \ No newline at end of file diff --git a/src/visualize.cpp b/src/visualize.cpp index ff6d5b8..d7b4547 100644 --- a/src/visualize.cpp +++ b/src/visualize.cpp @@ -101,132 +101,14 @@ void priceGraph(std::string symbol, std::string duration, int mode) // matplot::show(); } -void createCandle(std::string symbol, std::string duration) +void createCandleChart(std::string symbol, std::string duration, bool withVolume) { // Fetch data Metrics data = fetchMetrics(symbol); std::vector> ohlcData = fetchOHLCData(symbol, duration); - if (ohlcData.empty()) { - std::cerr << "No OHLC data available." << std::endl; - return; - } - - std::vector dates, candleColor; - std::vector openingPrices, closingPrices, highPrices, lowPrices; - std::vector xAxis; // Needed for data plotting - - // Extract OHLC data and fill xAxis vector (prices will be plotted against these points) - double i = 0; - for (const auto &row : ohlcData) - { - xAxis.push_back(i); - if (row.size() >= 5) - { - try - { - dates.push_back(row[0]); // Date in format y/m/d (column 0 in OHLC data) - openingPrices.push_back(std::stod(row[1])); // Opening price (column 1 in OHLC data) - highPrices.push_back(std::stod(row[2])); // High price (column 2 in OHLC data) - lowPrices.push_back(std::stod(row[3])); // Low price (column 3 in OHLC data) - closingPrices.push_back(std::stod(row[4])); // Closing price (column 4 in OHLC data) - if (std::stod(row[4]) >= std::stod(row[1])) - { - candleColor.push_back("green"); - } - else - { - candleColor.push_back("red"); - } - } - catch(const std::exception& e) - { - std::cerr << e.what() << '\n'; - return; - } - - - } - i++; - } - - // Calculate lowest and highest prices (used for y-axis range) - double lowestPrice = *std::min_element(lowPrices.begin(), lowPrices.end()); - double highestPrice = *std::max_element(highPrices.begin(), highPrices.end()); - - std::vector xTicks; - std::vector xtickLabels; // New vector for x-tick labels - - if (dates.size() <= 20) - { - // Show all dates if the duration is less than or equal to 20 days - xTicks = matplot::linspace(0, openingPrices.size() - 1, dates.size()); - xtickLabels = dates; // Use all dates as x-tick labels - } - else - { - // Show fewer ticks for longer durations - int desiredTicks = 10; // You can adjust this value as needed - int step = static_cast(std::ceil(static_cast(dates.size()) / desiredTicks)); - for (int i = 0; i < dates.size(); i += step) - { - xTicks.push_back(static_cast(i)); - xtickLabels.push_back(dates[i]); // Use selected dates as x-tick labels - } - } - - // Create the candlestick chart - auto fig = matplot::figure(true); - fig->quiet_mode(true); - fig->size(900, 600); - matplot::hold(matplot::on); - matplot::ylim({+lowestPrice * 0.99, +highestPrice * 1.01}); - matplot::xlim({-1, xAxis[xAxis.size()-1]+1}); // Small offset to make sure the first and last candles are not drawn in the axis - - for (int i = 0; i < xAxis.size(); i++) - { - // Draw the vertical line (wick) of the candlestick - matplot::plot({xAxis[i], xAxis[i]}, {highPrices[i], lowPrices[i]})->line_width(1.2).color("black").marker(matplot::line_spec::marker_style::custom); - - // Calculate the top and bottom of the candlestick body - double top = std::max(openingPrices[i], closingPrices[i]); - double bottom = std::min(openingPrices[i], closingPrices[i]); - - // Draw the filled rectangle (candlestick body) - float offsetX = 0.2; - auto r = matplot::rectangle(xAxis[i]-offsetX, bottom, offsetX * 2, (top-bottom)); - r->fill(true); - r->color(candleColor[i]); - } - - // Set x-tick positions and labels - matplot::xticks(xTicks); - matplot::xticklabels(xtickLabels); - matplot::xtickangle(35); - matplot::ylabel("Price in " + data.currency); - - // Get the path to the "images" folder (assuming one level up from the executable) - std::string exePath = std::filesystem::current_path().string(); - std::string imagePath = exePath + "/../images/"; - - // Create the "images" folder if it doesn't exist - std::filesystem::create_directory(imagePath); - - // Save the plot in the "images" folder - std::string filename = imagePath + "candle_chart.png"; - matplot::save(filename); - //matplot::show(); -} - -void createCandleWithVolume(std::string symbol, std::string duration) -{ - // Fetch data - Metrics data = fetchMetrics(symbol); - std::vector> ohlcvData = fetchOHLCData(symbol, duration); - if (ohlcvData.empty()) - { - std::cout << "No OHLCV data available." << std::endl; + std::cout << "No OHLC data available." << std::endl; return; } @@ -234,9 +116,9 @@ void createCandleWithVolume(std::string symbol, std::string duration) std::vector openingPrices, closingPrices, highPrices, lowPrices, volumes; std::vector xAxis; // Needed for data plotting - // Extract OHLCV data and fill xAxis vector (prices and volumes will be plotted against these points) + // Extract OHLC data and fill xAxis vector (prices and volumes will be plotted against these points) double i = 0; - for (const auto &row : ohlcvData) + for (const auto &row : ohlcData) { xAxis.push_back(i); if (row.size() >= 5) @@ -248,7 +130,10 @@ void createCandleWithVolume(std::string symbol, std::string duration) highPrices.push_back(std::stod(row[2])); // High price (column 2 in OHLC data) lowPrices.push_back(std::stod(row[3])); // Low price (column 3 in OHLC data) closingPrices.push_back(std::stod(row[4])); // Closing price (column 4 in OHLC data) - volumes.push_back(std::stod(row[5])); // Volumes (column 5 in OHLC data) + if (withVolume && row.size() >= 6) + { + volumes.push_back(std::stod(row[5])); // Volumes (column 5 in OHLC data) + } if (std::stod(row[4]) >= std::stod(row[1])) { candleColor.push_back("green"); @@ -258,13 +143,11 @@ void createCandleWithVolume(std::string symbol, std::string duration) candleColor.push_back("red"); } } - catch(const std::exception& e) + catch (const std::exception &e) { std::cerr << e.what() << '\n'; return; } - - } i++; } @@ -323,42 +206,44 @@ void createCandleWithVolume(std::string symbol, std::string duration) r->color(candleColor[i]); } - // Divide volumes by 10,000,000 to bring them to a normal range - for (auto &volume : volumes) + if (withVolume) { - volume /= 10000000; - } - - // Calculate lowest and highest volumes (used for y-axis range) - double lowestVolume = *std::min_element(volumes.begin(), volumes.end()); - double highestVolume = *std::max_element(volumes.begin(), volumes.end()); - - // Draw volume bars using matplot::rectangle - for (int i = 0; i < xAxis.size(); i++) - { - float width = 0.2; - double volumeHeight = volumes[i]; - if (candleColor[i] == "red") + // Divide volumes by 10,000,000 to bring them to a normal range + for (auto &volume : volumes) { - auto r = matplot::rectangle(xAxis[i] - width, 0, 2 * width, volumeHeight)->use_y2(true). - fill(true). - color({0.7f, 1.f, 0.f, 0.f}); // Red with 70% opacity + volume /= 10000000; } - else + + // Calculate lowest and highest volumes (used for y-axis range) + double lowestVolume = *std::min_element(volumes.begin(), volumes.end()); + double highestVolume = *std::max_element(volumes.begin(), volumes.end()); + + // Draw volume bars using matplot::rectangle + for (int i = 0; i < xAxis.size(); i++) { - auto r = matplot::rectangle(xAxis[i] - width, 0, 2 * width, volumeHeight)->use_y2(true). - fill(true). - color({0.7f, 0.f, 1.f, 0.f}); // Green with 70% opacity + float width = 0.2; + double volumeHeight = volumes[i]; + if (candleColor[i] == "red") + { + auto r = matplot::rectangle(xAxis[i] - width, 0, 2 * width, volumeHeight)->use_y2(true). + fill(true). + color({0.7f, 1.f, 0.f, 0.f}); // Red with 70% opacity + } + else + { + auto r = matplot::rectangle(xAxis[i] - width, 0, 2 * width, volumeHeight)->use_y2(true). + fill(true). + color({0.7f, 0.f, 1.f, 0.f}); // Green with 70% opacity + } } - - } - // y2 setup - // Note that the upper y2 limit is set to 4 times the highest volume - // This is done to make sure that the volume bars are displayed at the bottom of the graph - // and do not overlay the candlesticks too much - matplot::y2lim({0, highestVolume * 4}); - matplot::y2label("Volume (in 10^7)"); + // y2 setup + // Note that the upper y2 limit is set to 4 times the highest volume + // This is done to make sure that the volume bars are displayed at the bottom of the graph + // and do not overlay the candlesticks too much + matplot::y2lim({0, highestVolume * 4}); + matplot::y2label("Volume (in 10^7)"); + } // Get the path to the "images" folder (assuming one level up from the executable) std::string exePath = std::filesystem::current_path().string(); @@ -368,7 +253,7 @@ void createCandleWithVolume(std::string symbol, std::string duration) std::filesystem::create_directory(imagePath); // Save the plot in the "images" folder - std::string filename = imagePath + "candle_volume.png"; + std::string filename = imagePath + "candle_chart.png"; matplot::save(filename); // matplot::show(); -} \ No newline at end of file +}