From 792febe8fb5a8840fd4454b3c60890224bd5204e Mon Sep 17 00:00:00 2001 From: ooples Date: Fri, 13 Jan 2023 17:14:01 -0500 Subject: [PATCH] Added new method to get stock recommendations for any symbol Other misc fixes --- OoplesFinance.YahooFinanceAPI.sln | 4 +- README.md | 5 ++- src/Helpers/DownloadHelper.cs | 39 ++++++++++++++++++- src/Helpers/RecommendationHelper.cs | 11 ++++++ src/Helpers/TrendingHelper.cs | 4 +- src/Helpers/UrlHelper.cs | 10 ++++- src/Interfaces/YahooJsonBase.cs | 2 +- src/Models/RecommendData.cs | 34 ++++++++++++++++ src/Models/TrendingData.cs | 6 +-- src/OoplesFinance.YahooFinanceAPI.csproj | 4 +- src/YahooClient.cs | 14 ++++++- .../TestConsoleApp}/Program.cs | 1 + .../TestConsoleApp}/TestConsoleApp.csproj | 2 +- ...sFinance.YahooFinanceAPI.Tests.Unit.csproj | 2 +- tests/{ => UnitTests}/Usings.cs | 3 +- tests/{ => UnitTests}/YahooClientTests.cs | 38 ++++++++++++++---- 16 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 src/Helpers/RecommendationHelper.cs create mode 100644 src/Models/RecommendData.cs rename {TestConsoleApp => tests/TestConsoleApp}/Program.cs (90%) rename {TestConsoleApp => tests/TestConsoleApp}/TestConsoleApp.csproj (81%) rename tests/{ => UnitTests}/OoplesFinance.YahooFinanceAPI.Tests.Unit.csproj (93%) rename tests/{ => UnitTests}/Usings.cs (50%) rename tests/{ => UnitTests}/YahooClientTests.cs (81%) diff --git a/OoplesFinance.YahooFinanceAPI.sln b/OoplesFinance.YahooFinanceAPI.sln index a0b29d4..cbad1e2 100644 --- a/OoplesFinance.YahooFinanceAPI.sln +++ b/OoplesFinance.YahooFinanceAPI.sln @@ -5,9 +5,9 @@ VisualStudioVersion = 17.5.33209.295 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OoplesFinance.YahooFinanceAPI", "src\OoplesFinance.YahooFinanceAPI.csproj", "{F783EAAC-E1BF-4AA9-B9C6-BC0F1519613F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OoplesFinance.YahooFinanceAPI.Tests.Unit", "tests\OoplesFinance.YahooFinanceAPI.Tests.Unit.csproj", "{CECB02A3-7D31-4AE6-AB3E-52E46B62BF4B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OoplesFinance.YahooFinanceAPI.Tests.Unit", "tests\unittests\OoplesFinance.YahooFinanceAPI.Tests.Unit.csproj", "{CECB02A3-7D31-4AE6-AB3E-52E46B62BF4B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsoleApp", "TestConsoleApp\TestConsoleApp.csproj", "{8FC9B783-0941-4623-BC98-AA31A8678808}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsoleApp", "tests\TestConsoleApp\TestConsoleApp.csproj", "{8FC9B783-0941-4623-BC98-AA31A8678808}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/README.md b/README.md index f7ec173..d98bf51 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ ## .Net Yahoo Finance API Library This is a library for downloading free data using Yahoo Finance that is completely open source (Apache 2.0 license) and very easy to use. -This library currently supports downloading 5 different types of stock market data at the time of this writing: -historical/daily prices, stock splits, dividends, capital gains, and top trending stock information. +This library currently supports downloading 6 different types of stock market data at the time of this writing: +historical/daily prices, stock splits, dividends, capital gains, stock recommendations, and top trending stock information. We support getting daily, weekly, or monthly data as well as many other options. @@ -27,6 +27,7 @@ var capitalGainList = await GetCapitalGainDataAsync(symbol, DataFrequency.Monthl var dividendList = await GetDividendDataAsync(symbol, DataFrequency.Weekly, startDate); var stockSplitList = await GetStockSplitDataAsync(symbol, DataFrequency.Monthly, startDate); var topTrendingList = await GetTopTrendingStocksAsync(Country.UnitedStates, 10); +var recommendedList = await GetStockRecommendationsAsync(symbol); ``` diff --git a/src/Helpers/DownloadHelper.cs b/src/Helpers/DownloadHelper.cs index 3266c4b..ded0b3c 100644 --- a/src/Helpers/DownloadHelper.cs +++ b/src/Helpers/DownloadHelper.cs @@ -40,6 +40,7 @@ internal static async Task> DownloadRawCsvDataAsync(string } else { + // Handle failure if (response.StatusCode == HttpStatusCode.NotFound) { throw new InvalidOperationException($"'{symbol}' Symbol Not Available On Yahoo Finance"); @@ -70,7 +71,7 @@ internal static async Task DownloadRawJsonDataAsync(Country country, int { if (count <= 0) { - throw new ArgumentException("Count Must Be At Least 1 To Return Any Data", nameof(count)); + throw new ArgumentException("Count Must Be At Least 1 To Return Any Data"); } else { @@ -98,6 +99,42 @@ internal static async Task DownloadRawJsonDataAsync(Country country, int } } + internal static async Task DownloadRawJsonDataAsync(string symbol) + { + if (string.IsNullOrWhiteSpace(symbol)) + { + throw new ArgumentException("Symbol Parameter Can't Be Empty Or Null"); + } + else + { + using var client = new HttpClient(); + using var request = new HttpRequestMessage(HttpMethod.Get, BuildYahooRecommendUrl(symbol)); + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + // Handle success + return await response.Content.ReadAsStringAsync(); + } + else + { + // Handle failure + if (response.StatusCode == HttpStatusCode.NotFound) + { + throw new InvalidOperationException($"'{symbol}' Symbol Not Available On Yahoo Finance"); + } + else if (response.StatusCode == HttpStatusCode.Unauthorized) + { + throw new InvalidOperationException("Yahoo Finance Authentication Error"); + } + else + { + throw new InvalidOperationException("Unspecified Error Occurred"); + } + } + } + } + /// /// Gets the base csv data that is used by all csv helper classes /// diff --git a/src/Helpers/RecommendationHelper.cs b/src/Helpers/RecommendationHelper.cs new file mode 100644 index 0000000..44595c0 --- /dev/null +++ b/src/Helpers/RecommendationHelper.cs @@ -0,0 +1,11 @@ +namespace OoplesFinance.YahooFinanceAPI.Helpers; + +internal class RecommendationHelper : YahooJsonBase +{ + internal override IEnumerable ParseYahooJsonData(string jsonData) + { + var rawRecommendData = JsonSerializer.Deserialize(jsonData); + + return rawRecommendData != null ? (IEnumerable)rawRecommendData.Finance.Results.First().RecommendedSymbols : Enumerable.Empty(); + } +} diff --git a/src/Helpers/TrendingHelper.cs b/src/Helpers/TrendingHelper.cs index 5b70735..d663ff2 100644 --- a/src/Helpers/TrendingHelper.cs +++ b/src/Helpers/TrendingHelper.cs @@ -8,10 +8,10 @@ internal class TrendingHelper : YahooJsonBase /// /// /// - internal override IEnumerable ParseYahooJsonData(string jsonData) + internal override IEnumerable ParseYahooJsonData(string jsonData) { var rawTrendingData = JsonSerializer.Deserialize(jsonData); - return rawTrendingData != null ? rawTrendingData.Finance.Result.First().Quotes.Select(x => x.Symbol) : Enumerable.Empty(); + return rawTrendingData != null ? (IEnumerable)rawTrendingData.Finance.Results.First().Quotes.Select(x => x.Symbol) : Enumerable.Empty(); } } diff --git a/src/Helpers/UrlHelper.cs b/src/Helpers/UrlHelper.cs index 592a305..cac8875 100644 --- a/src/Helpers/UrlHelper.cs +++ b/src/Helpers/UrlHelper.cs @@ -18,7 +18,7 @@ internal static Uri BuildYahooCsvUrl(string symbol, DataType dataType, DataFrequ $"&period2={(endDate ?? DateTime.Now).ToUnixTimestamp()}&interval={GetIntervalString(dataFrequency)}&events={GetEventsString(dataType)}&includeAdjustedClose={includeAdjClose}")); /// - /// Creates a url that will be used to compile the chosen parameter options into a json file. + /// Creates a url that will be used to get the top trending stocks using the chosen parameters /// /// /// @@ -26,6 +26,14 @@ internal static Uri BuildYahooCsvUrl(string symbol, DataType dataType, DataFrequ internal static Uri BuildYahooTrendingUrl(Country country, int count) => new(string.Format(CultureInfo.InvariantCulture, $"https://query2.finance.yahoo.com/v1/finance/trending/{GetCountryString(country)}?count={count}")); + /// + /// Creates a url that will be used to get recommendations for a selected symbol + /// + /// + /// + internal static Uri BuildYahooRecommendUrl(string symbol) => + new(string.Format(CultureInfo.InvariantCulture, $"https://query2.finance.yahoo.com/v6/finance/recommendationsbysymbol/{symbol}")); + /// /// Returns a custom string for the Country option. /// diff --git a/src/Interfaces/YahooJsonBase.cs b/src/Interfaces/YahooJsonBase.cs index 80c3c40..f5f0a70 100644 --- a/src/Interfaces/YahooJsonBase.cs +++ b/src/Interfaces/YahooJsonBase.cs @@ -2,5 +2,5 @@ internal abstract class YahooJsonBase { - internal abstract IEnumerable ParseYahooJsonData(string jsonData); + internal abstract IEnumerable ParseYahooJsonData(string jsonData); } diff --git a/src/Models/RecommendData.cs b/src/Models/RecommendData.cs new file mode 100644 index 0000000..8dc0424 --- /dev/null +++ b/src/Models/RecommendData.cs @@ -0,0 +1,34 @@ +namespace OoplesFinance.YahooFinanceAPI.Models; + +public class RecommendFinance +{ + [JsonPropertyName("result")] + public List Results { get; set; } = new(); + + [JsonPropertyName("error")] + public object Error { get; set; } = new(); +} + +public class RecommendedSymbol +{ + [JsonPropertyName("symbol")] + public string Symbol { get; set; } = string.Empty; + + [JsonPropertyName("score")] + public double Score { get; set; } +} + +public class RecommendResult +{ + [JsonPropertyName("symbol")] + public string Symbol { get; set; } = string.Empty; + + [JsonPropertyName("recommendedSymbols")] + public List RecommendedSymbols { get; set; } = new(); +} + +public class RecommendData +{ + [JsonPropertyName("finance")] + public RecommendFinance Finance { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Models/TrendingData.cs b/src/Models/TrendingData.cs index 71fe34c..c5fb183 100644 --- a/src/Models/TrendingData.cs +++ b/src/Models/TrendingData.cs @@ -1,9 +1,9 @@ namespace OoplesFinance.YahooFinanceAPI.Models; -internal class Finance +internal class TrendingFinance { [JsonPropertyName("result")] - public List Result { get; set; } = new(); + public List Results { get; set; } = new(); [JsonPropertyName("error")] public object Error { get; set; } = new(); @@ -33,5 +33,5 @@ internal class Result internal class TrendingData { [JsonPropertyName("finance")] - public Finance Finance { get; set; } = new(); + public TrendingFinance Finance { get; set; } = new(); } \ No newline at end of file diff --git a/src/OoplesFinance.YahooFinanceAPI.csproj b/src/OoplesFinance.YahooFinanceAPI.csproj index 1399167..f834ac6 100644 --- a/src/OoplesFinance.YahooFinanceAPI.csproj +++ b/src/OoplesFinance.YahooFinanceAPI.csproj @@ -8,11 +8,11 @@ True True Ooples Finance Yahoo Finance API - 1.0.3 + 1.0.4 ooples Ooples Finance Ooples Finance LLC 2022-2023 - A library to be able to scrape free stock market data from Yahoo Finance + A C# library to be able to scrape free stock market data from Yahoo Finance. Can get historical data, top trending stocks, capital gains, dividends, stock splits, stock recommendations and so much more! git README.md Apache-2.0 diff --git a/src/YahooClient.cs b/src/YahooClient.cs index a869ffc..bcbb7d5 100644 --- a/src/YahooClient.cs +++ b/src/YahooClient.cs @@ -179,13 +179,23 @@ public static async Task> GetCapitalGainDataAsync(s } /// - /// Gets a list of the Top Trending Stocks using the selected parameter options. + /// Gets a list of the Top Trending Stocks using the selected parameter options /// /// /// /// public static async Task> GetTopTrendingStocksAsync(Country country, int count) { - return new TrendingHelper().ParseYahooJsonData(await DownloadRawJsonDataAsync(country, count)); + return new TrendingHelper().ParseYahooJsonData(await DownloadRawJsonDataAsync(country, count)); + } + + /// + /// Gets a list of the Top Recommendations using the selected stock symbol + /// + /// + /// + public static async Task> GetStockRecommendationsAsync(string symbol) + { + return new RecommendationHelper().ParseYahooJsonData(await DownloadRawJsonDataAsync(symbol)); } } \ No newline at end of file diff --git a/TestConsoleApp/Program.cs b/tests/TestConsoleApp/Program.cs similarity index 90% rename from TestConsoleApp/Program.cs rename to tests/TestConsoleApp/Program.cs index 6073856..a170347 100644 --- a/TestConsoleApp/Program.cs +++ b/tests/TestConsoleApp/Program.cs @@ -9,5 +9,6 @@ var dividendList = await GetDividendDataAsync(symbol, DataFrequency.Weekly, startDate); var stockSplitList = await GetStockSplitDataAsync(symbol, DataFrequency.Monthly, startDate); var topTrendingList = await GetTopTrendingStocksAsync(Country.UnitedStates, 10); +var recommendedList = await GetStockRecommendationsAsync(symbol); Console.WriteLine(); \ No newline at end of file diff --git a/TestConsoleApp/TestConsoleApp.csproj b/tests/TestConsoleApp/TestConsoleApp.csproj similarity index 81% rename from TestConsoleApp/TestConsoleApp.csproj rename to tests/TestConsoleApp/TestConsoleApp.csproj index d83d29c..adad192 100644 --- a/TestConsoleApp/TestConsoleApp.csproj +++ b/tests/TestConsoleApp/TestConsoleApp.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/OoplesFinance.YahooFinanceAPI.Tests.Unit.csproj b/tests/UnitTests/OoplesFinance.YahooFinanceAPI.Tests.Unit.csproj similarity index 93% rename from tests/OoplesFinance.YahooFinanceAPI.Tests.Unit.csproj rename to tests/UnitTests/OoplesFinance.YahooFinanceAPI.Tests.Unit.csproj index a6353c5..38542b1 100644 --- a/tests/OoplesFinance.YahooFinanceAPI.Tests.Unit.csproj +++ b/tests/UnitTests/OoplesFinance.YahooFinanceAPI.Tests.Unit.csproj @@ -28,7 +28,7 @@ - + diff --git a/tests/Usings.cs b/tests/UnitTests/Usings.cs similarity index 50% rename from tests/Usings.cs rename to tests/UnitTests/Usings.cs index d0e695f..9703262 100644 --- a/tests/Usings.cs +++ b/tests/UnitTests/Usings.cs @@ -1,3 +1,4 @@ global using Xunit; global using static OoplesFinance.YahooFinanceAPI.YahooClient; -global using OoplesFinance.YahooFinanceAPI.Enums; \ No newline at end of file +global using OoplesFinance.YahooFinanceAPI.Enums; +global using FluentAssertions; \ No newline at end of file diff --git a/tests/YahooClientTests.cs b/tests/UnitTests/YahooClientTests.cs similarity index 81% rename from tests/YahooClientTests.cs rename to tests/UnitTests/YahooClientTests.cs index 1aa2dc0..5df3210 100644 --- a/tests/YahooClientTests.cs +++ b/tests/UnitTests/YahooClientTests.cs @@ -1,7 +1,3 @@ -using FluentAssertions; -using NSubstitute; -using NSubstitute.ExceptionExtensions; - namespace OoplesFinance.YahooFinanceAPI.Tests.Unit; public sealed class YahooClientTests @@ -10,7 +6,7 @@ public sealed class YahooClientTests public async Task GetHistoricalData_ThrowsException_WhenNoSymbolIsFound() { // Arrange - var symbol = "GOOGLECOINS"; + var symbol = "OOPLES"; var startDate = DateTime.Now.AddYears(-1); // Act @@ -24,7 +20,7 @@ public async Task GetHistoricalData_ThrowsException_WhenNoSymbolIsFound() public async Task GetStockSplitData_ThrowsException_WhenNoSymbolIsFound() { // Arrange - var symbol = "GOOGLECOINS"; + var symbol = "OOPLES"; var startDate = DateTime.Now.AddYears(-1); // Act @@ -38,7 +34,7 @@ public async Task GetStockSplitData_ThrowsException_WhenNoSymbolIsFound() public async Task GetDividendData_ThrowsException_WhenNoSymbolIsFound() { // Arrange - var symbol = "GOOGLECOINS"; + var symbol = "OOPLES"; var startDate = DateTime.Now.AddYears(-1); // Act @@ -52,7 +48,7 @@ public async Task GetDividendData_ThrowsException_WhenNoSymbolIsFound() public async Task GetCapitalGainData_ThrowsException_WhenNoSymbolIsFound() { // Arrange - var symbol = "GOOGLECOINS"; + var symbol = "OOPLES"; var startDate = DateTime.Now.AddYears(-1); // Act @@ -129,4 +125,30 @@ public async Task GetTopTrendingStocks_ThrowsException_WhenCountIsInvalid() // Assert await result.Should().ThrowAsync().WithMessage("Count Must Be At Least 1 To Return Any Data"); } + + [Fact] + public async Task GetStockRecommendations_ThrowsException_WhenNoSymbolIsFound() + { + // Arrange + var symbol = "OOPLES"; + + // Act + var result = async () => await GetStockRecommendationsAsync(symbol); + + // Assert + await result.Should().ThrowAsync().WithMessage($"'{symbol}' Symbol Not Available On Yahoo Finance"); + } + + [Fact] + public async Task GetStockRecommendations_ThrowsException_WhenEmptySymbolIsUsed() + { + // Arrange + var symbol = ""; + + // Act + var result = async () => await GetStockRecommendationsAsync(symbol); + + // Assert + await result.Should().ThrowAsync().WithMessage("Symbol Parameter Can't Be Empty Or Null"); + } } \ No newline at end of file