diff --git a/YahooFinanceApi.Tests/UnitTest1.cs b/YahooFinanceApi.Tests/UnitTest1.cs index 413a2ba..69faac6 100644 --- a/YahooFinanceApi.Tests/UnitTest1.cs +++ b/YahooFinanceApi.Tests/UnitTest1.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Linq; using System.Threading.Tasks; using Xunit; - +using YahooFinanceApi.Lookup; + namespace YahooFinanceApi.Tests { public class UnitTest1 @@ -16,6 +18,14 @@ public UnitTest1() CultureInfo.CurrentCulture = new CultureInfo("nl-nl"); } + [Fact] + public void SymbolListTest() + { + const string Symbol = "aap"; + var symbols = YahooLookup.GetLookupSymbolsAsync(Symbol, LookupType.Stocks, MarketType.US_Canada).Result; + Assert.True(symbols.Any(s => s.CompanyName == "Advance Auto Parts, Inc.")); + } + [Fact] public void HistoricalExceptionTest() { diff --git a/YahooFinanceApi/Helper.cs b/YahooFinanceApi/Helper.cs index 8483178..6c17e73 100644 --- a/YahooFinanceApi/Helper.cs +++ b/YahooFinanceApi/Helper.cs @@ -1,8 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; - + namespace YahooFinanceApi { static class Helper @@ -35,5 +36,7 @@ public static string Name(this T @enum) public static string GetRandomString(int length) => Guid.NewGuid().ToString().Substring(0, length); + public static IEnumerable AddSuffixes(this string prefix) + => "abcdefghijklmnopqrstuvwxyz".Select(a => prefix + a); } } diff --git a/YahooFinanceApi/Lookup/LookupSymbol.cs b/YahooFinanceApi/Lookup/LookupSymbol.cs new file mode 100644 index 0000000..a6607bd --- /dev/null +++ b/YahooFinanceApi/Lookup/LookupSymbol.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace YahooFinanceApi.Lookup +{ + public class LookupSymbol + { + public string Symbol { get; } + public string CompanyName { get; } + //public decimal LastValue { get; } + //public decimal Change { get; } + //public decimal ChangePercent { get; } + public string IndustryName { get; } + public string IndustryLink { get; } + public string Type { get; } + public string Exchange { get; } + + public LookupSymbol(string symbol, string companyName, /*decimal lastValue, decimal change, decimal changePercent, */ + string industryName, string industryLink, string type, string exchange) + { + Exchange = exchange; + Type = type; + IndustryLink = industryLink; + IndustryName = industryName; + //ChangePercent = changePercent; + //Change = change; + //LastValue = lastValue; + CompanyName = companyName; + Symbol = symbol; + } + } +} diff --git a/YahooFinanceApi/Lookup/LookupType.cs b/YahooFinanceApi/Lookup/LookupType.cs new file mode 100644 index 0000000..3cc3279 --- /dev/null +++ b/YahooFinanceApi/Lookup/LookupType.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.Serialization; + +namespace YahooFinanceApi.Lookup +{ + public enum LookupType + { + [EnumMember(Value = "A")] + All, + [EnumMember(Value = "S")] + Stocks, + [EnumMember(Value = "M")] + MutualFunds, + [EnumMember(Value = "E")] + ETFs, + [EnumMember(Value = "I")] + Indices, + [EnumMember(Value = "F")] + Futures, + [EnumMember(Value = "C")] + Currencies + } +} diff --git a/YahooFinanceApi/Lookup/MarketType.cs b/YahooFinanceApi/Lookup/MarketType.cs new file mode 100644 index 0000000..ee1f824 --- /dev/null +++ b/YahooFinanceApi/Lookup/MarketType.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.Serialization; + +namespace YahooFinanceApi.Lookup +{ + public enum MarketType + { + [EnumMember(Value = "ALL")] + All, + [EnumMember(Value = "US")] + US_Canada + } +} diff --git a/YahooFinanceApi/Lookup/YahooLookup.cs b/YahooFinanceApi/Lookup/YahooLookup.cs new file mode 100644 index 0000000..7e469d7 --- /dev/null +++ b/YahooFinanceApi/Lookup/YahooLookup.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Flurl.Http; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace YahooFinanceApi.Lookup +{ + public static class YahooLookup + { + const string LookupUrl = "https://finance.yahoo.com/_finance_doubledown/api/resource/finance.yfinlist.symbol_lookup"; + const string SearchTag = "s"; + const string LookupTypeTag = "t"; + const string MarketTag = "m"; + const string OffsetTag = "b"; + const string IsIncludeSimilarTag = "p"; + + public static async Task> GetLookupSymbolsAsync(string search, LookupType lookupType, MarketType marketType, CancellationToken token = default(CancellationToken)) + { + Func> responseFunc = (s, i) => GetSymbolListResponseStreamAsync(s, lookupType, marketType, i, token); + var childSearches = await GetLookupSearchesAsync(search, lookupType, marketType, token); + + var list = new List(); + foreach (var childSearch in childSearches) + { + int offset = 0; + while (true) + { + using (var s = await responseFunc(childSearch, offset)) + using (var sr = new StreamReader(s)) + { + var o = JObject.Parse(sr.ReadToEnd()); + var symbols = o["result"].ToArray(); + list.AddRange(symbols.Select(sym => JsonConvert.DeserializeObject(sym.ToString()))); + + var nav = o["navLink"]; + var navAsDict = (IDictionary)nav; + if (!navAsDict.ContainsKey("nextPage") || !navAsDict.ContainsKey("lastPage")) + break; + + offset = nav["nextPage"]["start"].Value(); + } + } + } + + return list; + } + + public static async Task> GetLookupSearchesAsync(string search, LookupType lookupType, MarketType marketType, CancellationToken token = default(CancellationToken)) + { + Func> responseFunc = i => GetSymbolListResponseStreamAsync(search, lookupType, marketType, i, token); + var list = new List(); + + if (await IsLookupSizeTooLargeAsync()) + { + foreach (var subSearch in search.AddSuffixes()) + list.AddRange(await GetLookupSearchesAsync(subSearch, lookupType, marketType, token)); + return list; + } + + return new string[] { search }; + + async Task IsLookupSizeTooLargeAsync() + { + const int LookupSizeLimit = 2000; + + using (var s = await responseFunc(0)) + using (var sr = new StreamReader(s)) + { + var o = JObject.Parse(sr.ReadToEnd()); + var nav = o["navLink"]; + var navAsDict = (IDictionary)nav; + if (!navAsDict.ContainsKey("lastPage")) + return false; + + return nav["lastPage"]["start"].Value() >= LookupSizeLimit; + } + } + } + + public static async Task GetSymbolListResponseStreamAsync(string search, LookupType lookupType, MarketType marketType, int offset, CancellationToken token = default(CancellationToken)) + => await new StringBuilder() + .Append(LookupUrl) + .Append($";{SearchTag}={search}") + .Append($";{LookupTypeTag}={lookupType.Name()}") + .Append($";{MarketTag}={marketType.Name()}") + .Append($";{OffsetTag}={offset}") + .Append($";{IsIncludeSimilarTag}=1") + .ToString() + .GetStreamAsync(token); + } +} diff --git a/YahooFinanceApi/YahooFinanceApi.csproj b/YahooFinanceApi/YahooFinanceApi.csproj index 7f63223..5a33587 100644 --- a/YahooFinanceApi/YahooFinanceApi.csproj +++ b/YahooFinanceApi/YahooFinanceApi.csproj @@ -40,4 +40,7 @@ + + + \ No newline at end of file