Skip to content

Commit

Permalink
combined plot candle chart functions (to get rid of duplicate code)
Browse files Browse the repository at this point in the history
  • Loading branch information
EtoileScintillante committed Apr 6, 2024
1 parent 6bc26e9 commit acecd15
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 175 deletions.
6 changes: 3 additions & 3 deletions src/bot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 5 additions & 14 deletions src/include/visualize.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
201 changes: 43 additions & 158 deletions src/visualize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,142 +101,24 @@ 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<std::vector<std::string>> ohlcData = fetchOHLCData(symbol, duration);

if (ohlcData.empty())
{
std::cerr << "No OHLC data available." << std::endl;
return;
}

std::vector<std::string> dates, candleColor;
std::vector<double> openingPrices, closingPrices, highPrices, lowPrices;
std::vector<double> 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<double> xTicks;
std::vector<std::string> 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<int>(std::ceil(static_cast<double>(dates.size()) / desiredTicks));
for (int i = 0; i < dates.size(); i += step)
{
xTicks.push_back(static_cast<double>(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<std::vector<std::string>> ohlcvData = fetchOHLCData(symbol, duration);
if (ohlcvData.empty())
{
std::cout << "No OHLCV data available." << std::endl;
std::cout << "No OHLC data available." << std::endl;
return;
}

std::vector<std::string> dates, candleColor;
std::vector<double> openingPrices, closingPrices, highPrices, lowPrices, volumes;
std::vector<double> 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)
Expand All @@ -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");
Expand All @@ -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++;
}
Expand Down Expand Up @@ -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();
Expand All @@ -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();
}
}

0 comments on commit acecd15

Please sign in to comment.