Skip to content

Commit

Permalink
Added new method to get stock recommendations for any symbol
Browse files Browse the repository at this point in the history
Other misc fixes
  • Loading branch information
ooples committed Jan 13, 2023
1 parent 0d77b29 commit 792febe
Show file tree
Hide file tree
Showing 16 changed files with 152 additions and 27 deletions.
4 changes: 2 additions & 2 deletions OoplesFinance.YahooFinanceAPI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand All @@ -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);
```


Expand Down
39 changes: 38 additions & 1 deletion src/Helpers/DownloadHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ internal static async Task<IEnumerable<string[]>> DownloadRawCsvDataAsync(string
}
else
{
// Handle failure
if (response.StatusCode == HttpStatusCode.NotFound)
{
throw new InvalidOperationException($"'{symbol}' Symbol Not Available On Yahoo Finance");
Expand Down Expand Up @@ -70,7 +71,7 @@ internal static async Task<string> 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
{
Expand Down Expand Up @@ -98,6 +99,42 @@ internal static async Task<string> DownloadRawJsonDataAsync(Country country, int
}
}

internal static async Task<string> 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");
}
}
}
}

/// <summary>
/// Gets the base csv data that is used by all csv helper classes
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions src/Helpers/RecommendationHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace OoplesFinance.YahooFinanceAPI.Helpers;

internal class RecommendationHelper : YahooJsonBase
{
internal override IEnumerable<T> ParseYahooJsonData<T>(string jsonData)
{
var rawRecommendData = JsonSerializer.Deserialize<RecommendData>(jsonData);

return rawRecommendData != null ? (IEnumerable<T>)rawRecommendData.Finance.Results.First().RecommendedSymbols : Enumerable.Empty<T>();
}
}
4 changes: 2 additions & 2 deletions src/Helpers/TrendingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ internal class TrendingHelper : YahooJsonBase
/// </summary>
/// <param name="jsonData"></param>
/// <returns></returns>
internal override IEnumerable<string> ParseYahooJsonData(string jsonData)
internal override IEnumerable<T> ParseYahooJsonData<T>(string jsonData)
{
var rawTrendingData = JsonSerializer.Deserialize<TrendingData>(jsonData);

return rawTrendingData != null ? rawTrendingData.Finance.Result.First().Quotes.Select(x => x.Symbol) : Enumerable.Empty<string>();
return rawTrendingData != null ? (IEnumerable<T>)rawTrendingData.Finance.Results.First().Quotes.Select(x => x.Symbol) : Enumerable.Empty<T>();
}
}
10 changes: 9 additions & 1 deletion src/Helpers/UrlHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,22 @@ internal static Uri BuildYahooCsvUrl(string symbol, DataType dataType, DataFrequ
$"&period2={(endDate ?? DateTime.Now).ToUnixTimestamp()}&interval={GetIntervalString(dataFrequency)}&events={GetEventsString(dataType)}&includeAdjustedClose={includeAdjClose}"));

/// <summary>
/// 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
/// </summary>
/// <param name="country"></param>
/// <param name="count"></param>
/// <returns></returns>
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}"));

/// <summary>
/// Creates a url that will be used to get recommendations for a selected symbol
/// </summary>
/// <param name="symbol"></param>
/// <returns></returns>
internal static Uri BuildYahooRecommendUrl(string symbol) =>
new(string.Format(CultureInfo.InvariantCulture, $"https://query2.finance.yahoo.com/v6/finance/recommendationsbysymbol/{symbol}"));

/// <summary>
/// Returns a custom string for the Country option.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Interfaces/YahooJsonBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

internal abstract class YahooJsonBase
{
internal abstract IEnumerable<string> ParseYahooJsonData(string jsonData);
internal abstract IEnumerable<T> ParseYahooJsonData<T>(string jsonData);
}
34 changes: 34 additions & 0 deletions src/Models/RecommendData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace OoplesFinance.YahooFinanceAPI.Models;

public class RecommendFinance
{
[JsonPropertyName("result")]
public List<RecommendResult> 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<RecommendedSymbol> RecommendedSymbols { get; set; } = new();
}

public class RecommendData
{
[JsonPropertyName("finance")]
public RecommendFinance Finance { get; set; } = new();
}
6 changes: 3 additions & 3 deletions src/Models/TrendingData.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace OoplesFinance.YahooFinanceAPI.Models;

internal class Finance
internal class TrendingFinance
{
[JsonPropertyName("result")]
public List<Result> Result { get; set; } = new();
public List<Result> Results { get; set; } = new();

[JsonPropertyName("error")]
public object Error { get; set; } = new();
Expand Down Expand Up @@ -33,5 +33,5 @@ internal class Result
internal class TrendingData
{
[JsonPropertyName("finance")]
public Finance Finance { get; set; } = new();
public TrendingFinance Finance { get; set; } = new();
}
4 changes: 2 additions & 2 deletions src/OoplesFinance.YahooFinanceAPI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<AutoGenerateBindingRedirects>True</AutoGenerateBindingRedirects>
<Title>Ooples Finance Yahoo Finance API</Title>
<Version>1.0.3</Version>
<Version>1.0.4</Version>
<Authors>ooples</Authors>
<Company>Ooples Finance</Company>
<Copyright>Ooples Finance LLC 2022-2023</Copyright>
<Description>A library to be able to scrape free stock market data from Yahoo Finance</Description>
<Description>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! </Description>
<RepositoryType>git</RepositoryType>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
Expand Down
14 changes: 12 additions & 2 deletions src/YahooClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,23 @@ public static async Task<IEnumerable<CapitalGainData>> GetCapitalGainDataAsync(s
}

/// <summary>
/// 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
/// </summary>
/// <param name="country"></param>
/// <param name="count"></param>
/// <returns></returns>
public static async Task<IEnumerable<string>> GetTopTrendingStocksAsync(Country country, int count)
{
return new TrendingHelper().ParseYahooJsonData(await DownloadRawJsonDataAsync(country, count));
return new TrendingHelper().ParseYahooJsonData<string>(await DownloadRawJsonDataAsync(country, count));
}

/// <summary>
/// Gets a list of the Top Recommendations using the selected stock symbol
/// </summary>
/// <param name="symbol"></param>
/// <returns></returns>
public static async Task<IEnumerable<RecommendedSymbol>> GetStockRecommendationsAsync(string symbol)
{
return new RecommendationHelper().ParseYahooJsonData<RecommendedSymbol>(await DownloadRawJsonDataAsync(symbol));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\src\OoplesFinance.YahooFinanceAPI.csproj" />
<ProjectReference Include="..\..\src\OoplesFinance.YahooFinanceAPI.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\src\OoplesFinance.YahooFinanceAPI.csproj" />
<ProjectReference Include="..\..\src\OoplesFinance.YahooFinanceAPI.csproj" />
</ItemGroup>

</Project>
3 changes: 2 additions & 1 deletion tests/Usings.cs → tests/UnitTests/Usings.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
global using Xunit;
global using static OoplesFinance.YahooFinanceAPI.YahooClient;
global using OoplesFinance.YahooFinanceAPI.Enums;
global using OoplesFinance.YahooFinanceAPI.Enums;
global using FluentAssertions;
38 changes: 30 additions & 8 deletions tests/YahooClientTests.cs → tests/UnitTests/YahooClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
using FluentAssertions;
using NSubstitute;
using NSubstitute.ExceptionExtensions;

namespace OoplesFinance.YahooFinanceAPI.Tests.Unit;

public sealed class YahooClientTests
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -129,4 +125,30 @@ public async Task GetTopTrendingStocks_ThrowsException_WhenCountIsInvalid()
// Assert
await result.Should().ThrowAsync<ArgumentException>().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<InvalidOperationException>().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<ArgumentException>().WithMessage("Symbol Parameter Can't Be Empty Or Null");
}
}

0 comments on commit 792febe

Please sign in to comment.