diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index da284e5..fd932f9 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/src/FischBot.UnitTests/FischBot.UnitTests.csproj b/src/FischBot.UnitTests/FischBot.UnitTests.csproj index 8317358..feffd2d 100644 --- a/src/FischBot.UnitTests/FischBot.UnitTests.csproj +++ b/src/FischBot.UnitTests/FischBot.UnitTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net9.0 false diff --git a/src/FischBot.UnitTests/Modules/InfoModuleTests.cs b/src/FischBot.UnitTests/Modules/InfoModuleTests.cs index df40cc9..fdbe9b1 100644 --- a/src/FischBot.UnitTests/Modules/InfoModuleTests.cs +++ b/src/FischBot.UnitTests/Modules/InfoModuleTests.cs @@ -1,9 +1,7 @@ using System.Threading.Tasks; using Discord; -using Discord.Commands; using FischBot.Modules; using FischBot.Services.DiscordModuleService; -using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; diff --git a/src/FischBot/Api/CalendarificHolidaysApiClient/CalendarificHolidaysApiClient.cs b/src/FischBot/Api/CalendarificHolidaysApiClient/CalendarificHolidaysApiClient.cs index f5b08bd..926c577 100644 --- a/src/FischBot/Api/CalendarificHolidaysApiClient/CalendarificHolidaysApiClient.cs +++ b/src/FischBot/Api/CalendarificHolidaysApiClient/CalendarificHolidaysApiClient.cs @@ -1,4 +1,3 @@ -using System; using System.Net.Http; using System.Threading.Tasks; using FischBot.Api.CalendarificHolidaysApiClient.Dtos; diff --git a/src/FischBot/Api/DeepAiApiClient/IDeepAiApiClient.cs b/src/FischBot/Api/DeepAiApiClient/IDeepAiApiClient.cs index d965640..4859db4 100644 --- a/src/FischBot/Api/DeepAiApiClient/IDeepAiApiClient.cs +++ b/src/FischBot/Api/DeepAiApiClient/IDeepAiApiClient.cs @@ -1,5 +1,3 @@ -using System; -using System.IO; using System.Threading.Tasks; using FischBot.Api.DeepAiApiClient.Dtos; diff --git a/src/FischBot/Api/HalfStaffJsScraperClient/HalfStaffJsScraperClient.cs b/src/FischBot/Api/HalfStaffJsScraperClient/HalfStaffJsScraperClient.cs index 546fd23..1afe250 100644 --- a/src/FischBot/Api/HalfStaffJsScraperClient/HalfStaffJsScraperClient.cs +++ b/src/FischBot/Api/HalfStaffJsScraperClient/HalfStaffJsScraperClient.cs @@ -1,11 +1,7 @@ -using System; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Discord; using FischBot.Api.HalfStaffJsScraperClient.Dtos; -using FischBot.Api.NasaApiClient.Dtos; -using Microsoft.Extensions.Configuration; namespace FischBot.Api.HalfStaffJsScraperClient { diff --git a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Clouds.cs b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Clouds.cs index adec13a..17552c1 100644 --- a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Clouds.cs +++ b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Clouds.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace FischBot.Api.OpenWeatherMapApiClient.Dtos { diff --git a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Coordinates.cs b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Coordinates.cs index fe06917..5a0cc6b 100644 --- a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Coordinates.cs +++ b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Coordinates.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace FischBot.Api.OpenWeatherMapApiClient.Dtos { diff --git a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Main.cs b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Main.cs index 30de395..856ea06 100644 --- a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Main.cs +++ b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Main.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace FischBot.Api.OpenWeatherMapApiClient.Dtos { diff --git a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/OpenWeatherMapApiResponse.cs b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/OpenWeatherMapApiResponse.cs index cd1b7bb..34d7c3d 100644 --- a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/OpenWeatherMapApiResponse.cs +++ b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/OpenWeatherMapApiResponse.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; +using System.Text.Json.Serialization; namespace FischBot.Api.OpenWeatherMapApiClient.Dtos { diff --git a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Precipitation.cs b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Precipitation.cs index 1face02..437ae6c 100644 --- a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Precipitation.cs +++ b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Precipitation.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace FischBot.Api.OpenWeatherMapApiClient.Dtos { diff --git a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Sys.cs b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Sys.cs index e60216d..b5e3c96 100644 --- a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Sys.cs +++ b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Sys.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace FischBot.Api.OpenWeatherMapApiClient.Dtos { diff --git a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Weather.cs b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Weather.cs index fcb0943..d9d9dde 100644 --- a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Weather.cs +++ b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Weather.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace FischBot.Api.OpenWeatherMapApiClient.Dtos { diff --git a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Wind.cs b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Wind.cs index 74fac91..3bb738c 100644 --- a/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Wind.cs +++ b/src/FischBot/Api/OpenWeatherMapApiClient/Dtos/Wind.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace FischBot.Api.OpenWeatherMapApiClient.Dtos { diff --git a/src/FischBot/FischBot.csproj b/src/FischBot/FischBot.csproj index a80e528..88d9f31 100644 --- a/src/FischBot/FischBot.csproj +++ b/src/FischBot/FischBot.csproj @@ -1,24 +1,24 @@ Exe - net6.0 + net9.0 0.4.5 0.4-dragonfish-rev5 false - - - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FischBot/Handlers/CommandHandler.cs b/src/FischBot/Handlers/CommandHandler.cs index e194d5d..412d44a 100644 --- a/src/FischBot/Handlers/CommandHandler.cs +++ b/src/FischBot/Handlers/CommandHandler.cs @@ -5,7 +5,6 @@ using Discord.Commands; using Discord.WebSocket; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace FischBot.Handlers diff --git a/src/FischBot/Handlers/InteractionHandler.cs b/src/FischBot/Handlers/InteractionHandler.cs index 6039f4e..109dde5 100644 --- a/src/FischBot/Handlers/InteractionHandler.cs +++ b/src/FischBot/Handlers/InteractionHandler.cs @@ -19,7 +19,7 @@ public class InteractionHandler private readonly ILogger _logger; private readonly IConfiguration _configuration; - public InteractionHandler(IConfiguration configuration, DiscordSocketClient discordClient, InteractionService commands, IServiceProvider services, ILogger logger) + public InteractionHandler(IConfiguration configuration, DiscordSocketClient discordClient, InteractionService commands, IServiceProvider services, ILogger logger) { _discordClient = discordClient; _commands = commands; @@ -27,8 +27,8 @@ public InteractionHandler(IConfiguration configuration, DiscordSocketClient disc _logger = logger; _configuration = configuration; } - - public async Task InitializeAsync() + + public async Task InitializeAsync() { await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); @@ -41,7 +41,7 @@ public async Task InitializeAsync() _discordClient.Ready += async () => { // If running the bot with DEBUG flag, register all commands to guild specified in config - if (IsDebug()) + if (IsDebug()) { var testGuildId = _configuration.GetValue("FischBot:testGuildId"); @@ -51,7 +51,7 @@ public async Task InitializeAsync() Console.WriteLine($"Registered the following interaction commands: {string.Join(',', registeredCommands.Select(command => command.Name))}"); } - else + else { await _services .GetRequiredService() @@ -60,11 +60,11 @@ await _services }; } - private async Task HandleInteraction(SocketInteraction interaction) + private async Task HandleInteraction(SocketInteraction interaction) { var context = new SocketInteractionContext(_discordClient, interaction); - try + try { await _commands.ExecuteCommandAsync(context, _services); } @@ -112,7 +112,10 @@ private async Task SlashCommandExecuted(SlashCommandInfo command, Discord.IInter else { _logger.LogError($"Slash command failed to execute for [{context.User.Username}] <-> [{result}]!"); - await context.Interaction.RespondAsync($"Sorry, {context.User.Username}... something went wrong -> [{result}]!", ephemeral: true); + if (context.Interaction.HasResponded) + await context.Interaction.RespondAsync($"Sorry, {context.User.Username}... something went wrong -> [{result}]!", ephemeral: true); + else + await context.Interaction.FollowupAsync($"Sorry, {context.User.Username}... something went wrong -> [{result}]!", ephemeral: true); } } diff --git a/src/FischBot/Models/Weather/CurrentWeather.cs b/src/FischBot/Models/Weather/CurrentWeather.cs index 7d10e32..d8e966e 100644 --- a/src/FischBot/Models/Weather/CurrentWeather.cs +++ b/src/FischBot/Models/Weather/CurrentWeather.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace FischBot.Models.Weather { @@ -11,7 +7,7 @@ public class CurrentWeather public string CityName { get; set; } public string CountryCode { get; set; } public string Location => $"{CityName}, {CountryCode}"; - public string WeatherIconUrl { get; set; } + public string WeatherIconUrl { get; set; } public string Weather { get; set; } public string WeatherDescription { get; set; } public DateTimeOffset DateCalculated { get; set; } diff --git a/src/FischBot/Modules/CryptocurrencyModule.cs b/src/FischBot/Modules/CryptocurrencyModule.cs index c32c4b5..2f4df2d 100644 --- a/src/FischBot/Modules/CryptocurrencyModule.cs +++ b/src/FischBot/Modules/CryptocurrencyModule.cs @@ -33,13 +33,13 @@ public async Task DisplayRealtimeCryptoInfo([Summary(description: "Crypto symbol .AddField( "Price", - $"{price.ToString("C")} {(quote.Change > 0 ? "▲" : "▼")}{quote.Change.ToString("C")} ({quote.PercentChange.ToString("F2")}%)" + $"{price:C} {(quote.Change > 0 ? "▲" : "▼")}{quote.Change:C} ({quote.PercentChange:F2}%)" ) .AddField("Trading info for last trading day", quote.Datetime.ToString("d")) - .AddField("Open/Close", $"{quote.Open.ToString("C")}/{quote.Close.ToString("C")}") - .AddField("High/Low", $"{quote.High.ToString("C")}/{quote.Low.ToString("C")}") + .AddField("Open/Close", $"{quote.Open:C}/{quote.Close:C}") + .AddField("High/Low", $"{quote.High:C}/{quote.Low:C}") .WithFooter($"Source: twelvedata | Daily usage: {usageStats.daily_usage}/{usageStats.plan_daily_limit}") .Build(); diff --git a/src/FischBot/Modules/HalfMastModule.cs b/src/FischBot/Modules/HalfMastModule.cs index 7d3a500..e5b29d8 100644 --- a/src/FischBot/Modules/HalfMastModule.cs +++ b/src/FischBot/Modules/HalfMastModule.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; -using System.Threading; using System.Threading.Tasks; using Discord; using Discord.Interactions; diff --git a/src/FischBot/Modules/InfoModule.cs b/src/FischBot/Modules/InfoModule.cs index eb4ef13..db6adb8 100644 --- a/src/FischBot/Modules/InfoModule.cs +++ b/src/FischBot/Modules/InfoModule.cs @@ -1,11 +1,9 @@ using System; -using System.Linq; using System.Reflection; using System.Threading.Tasks; using Discord; using Discord.Interactions; using FischBot.Services.DiscordModuleService; -using Microsoft.Extensions.Configuration; namespace FischBot.Modules { @@ -16,14 +14,14 @@ public InfoModule(IDiscordModuleService moduleService) : base(moduleService) } [SlashCommand("say", "Echoes a message.")] - public async Task SayAsync([Summary(description: "The text to echo.")] string echo, [Summary(description: "Whether to show your identity. (optional)")]bool anonymous = false) + public async Task SayAsync([Summary(description: "The text to echo.")] string echo, [Summary(description: "Whether to show your identity. (optional)")] bool anonymous = false) { if (anonymous) { await ReplyAsync(echo); await RespondAsync("_Shhh... I've said your super secret message._", ephemeral: true); } - else + else { await RespondAsync(echo); } diff --git a/src/FischBot/Modules/StocksModule.cs b/src/FischBot/Modules/StocksModule.cs index 447226f..9a2af28 100644 --- a/src/FischBot/Modules/StocksModule.cs +++ b/src/FischBot/Modules/StocksModule.cs @@ -19,10 +19,10 @@ public enum TimePeriod { Week, Month, + SixMonths, Year } - public StocksModule(IDiscordModuleService moduleService, IFinanceService financeService, IImageChartService imageChartService) : base(moduleService) { _financeService = financeService; @@ -44,13 +44,13 @@ public async Task DisplayRealtimeStockInfo([Summary(description: "Stock to get i .AddField( "Price", - $"{price.ToString("C")} {(quote.Change > 0 ? "▲" : "▼")}{quote.Change.ToString("C")} ({quote.PercentChange.ToString("F2")}%)" + $"{price:C} {(quote.Change > 0 ? "▲" : "▼")}{quote.Change:C} ({quote.PercentChange:F2}%)" ) .AddField("Trading info for last trading day", quote.Datetime.ToString("d")) - .AddField("Open/Close", $"{quote.Open.ToString("C")}/{quote.Close.ToString("C")}") - .AddField("High/Low", $"{quote.High.ToString("C")}/{quote.Low.ToString("C")}") + .AddField("Open/Close", $"{quote.Open:C}/{quote.Close:C}") + .AddField("High/Low", $"{quote.High:C}/{quote.Low:C}") .AddField("Volume", quote.Volume, inline: false) @@ -66,59 +66,49 @@ public async Task DisplayRealtimeStockInfo([Summary(description: "Stock to get i } [SlashCommand("chart", "Displays a chart for the specified stock.")] - public async Task DisplayStockChart([Summary(description: "Stock symbol")] string symbol, [Summary(description: "Time period to display for (optional)")] TimePeriod period = TimePeriod.Week) + public async Task DisplayStockChart([Summary(description: "Stock symbol")] string symbol, + [Summary(description: "Time period to display for (optional)")] TimePeriod period = TimePeriod.Month) { - var dataset = await GetDataSet(symbol, period); - var lineColor = dataset.First() < dataset.Last() ? "2ECC71" : "E74C3C"; - - var chartImage = _imageChartService.CreateLineChart( - dataset, - lineColor, - 500, - 100); - - await RespondWithFileAsync(chartImage, "chart.png"); - } - - private async Task GetDataSet(string symbol, TimePeriod period) - { - if (period == TimePeriod.Week) + var interval = period switch { - var timeSeries = await _financeService.GetTimeSeries(symbol, "2h"); - - return timeSeries.Values - .Where(value => value.Datetime > DateTime.Now.AddDays(-7)) - .OrderBy(value => value.Datetime) - .Select(value => value.Open) - .Take(7) - .ToArray(); - } - - if (period == TimePeriod.Month) - { - var timeSeries = await _financeService.GetTimeSeries(symbol, "1day"); - - return timeSeries.Values - .Where(value => value.Datetime > DateTime.Now.AddMonths(-1)) - .OrderBy(value => value.Datetime) - .Select(value => value.Open) - .Take(7) - .ToArray(); - } - - if (period == TimePeriod.Year) + TimePeriod.Week => "1h", + TimePeriod.Month => "1day", + TimePeriod.SixMonths => "1week", + TimePeriod.Year => "1month", + _ => throw new NotImplementedException() + }; + + var span = period switch { - var timeSeries = await _financeService.GetTimeSeries(symbol, "1month"); - - return timeSeries.Values - .Where(value => value.Datetime > DateTime.Now.AddYears(-1)) - .OrderBy(value => value.Datetime) - .Select(value => value.Open) - .Take(7) - .ToArray(); - } - - throw new NotImplementedException(); + TimePeriod.Week => TimeSpan.FromDays(7), + TimePeriod.Month => TimeSpan.FromDays(30), + TimePeriod.SixMonths => TimeSpan.FromDays(180), + TimePeriod.Year => TimeSpan.FromDays(365), + _ => throw new NotImplementedException() + }; + + var timeSeries = await _financeService.GetTimeSeries(symbol, interval); + var timeSeriesValues = timeSeries.Values + .OrderBy(value => value.Datetime.DateTime) + .ToList(); + + var showYearInXAxis = period == TimePeriod.Year || period == TimePeriod.SixMonths; + + var chartImageStream = _imageChartService.CreateStockChart( + timeSeriesValues, + span, + showYearInXAxis); + + var usageStats = await _financeService.GetApiUsageStats(); + + var embed = new EmbedBuilder() + .WithTitle($"{timeSeries.Symbol} Stock Chart") + .WithFooter($"Source: twelvedata | Daily usage: {usageStats.daily_usage}/{usageStats.plan_daily_limit}") + .Build(); + + await RespondWithFileAsync(chartImageStream, + "chart.png", + embed: embed); } } } \ No newline at end of file diff --git a/src/FischBot/Program.cs b/src/FischBot/Program.cs index 20a6afc..f9872e6 100644 --- a/src/FischBot/Program.cs +++ b/src/FischBot/Program.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; diff --git a/src/FischBot/Services/ArtificialIntelligenceService/IArtificialIntelligenceService.cs b/src/FischBot/Services/ArtificialIntelligenceService/IArtificialIntelligenceService.cs index 79a89b9..14c9e29 100644 --- a/src/FischBot/Services/ArtificialIntelligenceService/IArtificialIntelligenceService.cs +++ b/src/FischBot/Services/ArtificialIntelligenceService/IArtificialIntelligenceService.cs @@ -1,5 +1,3 @@ -using System; -using System.IO; using System.Threading.Tasks; using FischBot.Models.ArtificialIntelligence; diff --git a/src/FischBot/Services/HalfMastService/HalfMastService.cs b/src/FischBot/Services/HalfMastService/HalfMastService.cs index f15d083..cd8d344 100644 --- a/src/FischBot/Services/HalfMastService/HalfMastService.cs +++ b/src/FischBot/Services/HalfMastService/HalfMastService.cs @@ -1,6 +1,3 @@ -using System.Linq; -using System.Net; -using System.Threading; using System.Threading.Tasks; using FischBot.Api.HalfStaffJsScraperClient; using FischBot.Models; diff --git a/src/FischBot/Services/ImageChartService/IImageChartService.cs b/src/FischBot/Services/ImageChartService/IImageChartService.cs index dcc2d6b..6b1a91a 100644 --- a/src/FischBot/Services/ImageChartService/IImageChartService.cs +++ b/src/FischBot/Services/ImageChartService/IImageChartService.cs @@ -1,9 +1,12 @@ +using System; +using System.Collections.Generic; using System.IO; +using FischBot.Models.Finance; namespace FischBot.Services.ImageChartService { public interface IImageChartService { - Stream CreateLineChart(decimal[] data, string lineColor, int width, int height); + MemoryStream CreateStockChart(IEnumerable timeSeriesValues, TimeSpan span, bool showYearInXAxis); } } \ No newline at end of file diff --git a/src/FischBot/Services/ImageChartService/ImageChartService.cs b/src/FischBot/Services/ImageChartService/ImageChartService.cs index 4183dce..ae2a0c9 100644 --- a/src/FischBot/Services/ImageChartService/ImageChartService.cs +++ b/src/FischBot/Services/ImageChartService/ImageChartService.cs @@ -1,32 +1,66 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; -using ImageChartsLib; +using FischBot.Models.Finance; +using ScottPlot; namespace FischBot.Services.ImageChartService { public class ImageChartService : IImageChartService { - public Stream CreateLineChart(decimal[] data, string lineColor, int width, int height) + public MemoryStream CreateStockChart(IEnumerable timeSeriesValues, TimeSpan span, bool showYearInXAxis) { - var serializedData = $"a:{string.Join(',', data)}"; - var dataRange = $"0,{Math.Floor(data.Min())},{Math.Ceiling(data.Max())}"; - - var lineChart = new ImageCharts() - .cht("ls") - .chco(lineColor) - .chls("2") - .chf("bg,s,00000000") - .chs($"{width}x{height}") - .chxt("y") - .chxs("0N*cUSD2sz*,666666") - .chxr(dataRange) - .chd(serializedData); - - var buffer = lineChart.toBuffer(); - var stream = new MemoryStream(buffer); - - return stream; + var ohlcs = timeSeriesValues + .Select(price => new OHLC( + (double)price.Open, + (double)price.High, + (double)price.Low, + (double)price.Close, + price.Datetime.DateTime, + span)) + .ToList(); + + var plot = new Plot(); + + var candlePlot = plot.Add.Candlestick(ohlcs); + + // enable sequential mode to place candles at X = 0, 1, 2, ... + candlePlot.Sequential = true; + + // Since we are using sequential mode, we have to set the X axis manually. + ConfigureXAxisLabels(plot, ohlcs, showYearInXAxis); + + // Putting the Y axis on the right side and formatting it as currency + candlePlot.Axes.YAxis = plot.Axes.Right; + plot.Axes.Right.TickGenerator = new ScottPlot.TickGenerators.NumericAutomatic() + { + LabelFormatter = (double value) => value.ToString("C") + }; + + var chartImage = plot.GetImageBytes(800, 400, ImageFormat.Png); + return new MemoryStream(chartImage); + } + + private static void ConfigureXAxisLabels(Plot plot, List ohlcs, bool showYearInXAxis) + { + // determine a few candles to display ticks for + int tickCount = 5; + int tickDelta = ohlcs.Count / tickCount; + DateTime[] tickDates = ohlcs + .Where((x, i) => i % tickDelta == 0) + .Select(x => x.DateTime) + .ToArray(); + + // By default, horizontal tick labels will be numbers (1, 2, 3...) + // We can use a manual tick generator to display dates on the horizontal axis + double[] tickPositions = Generate.Consecutive(tickDates.Length, tickDelta); + + var dateFormat = showYearInXAxis ? "MM/dd/yyyy" : "MM/dd"; + string[] tickLabels = tickDates.Select(x => x.ToString(dateFormat)).ToArray(); + + ScottPlot.TickGenerators.NumericManual tickGen = new(tickPositions, tickLabels); + plot.Axes.Bottom.TickGenerator = tickGen; } } } \ No newline at end of file diff --git a/src/FischBot/Services/LoggingService.cs b/src/FischBot/Services/LoggingService.cs index f3de2c5..20315ff 100644 --- a/src/FischBot/Services/LoggingService.cs +++ b/src/FischBot/Services/LoggingService.cs @@ -3,7 +3,6 @@ using Discord; using Discord.Commands; using Discord.WebSocket; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace FischBot.Services