From 72b94ae1678f522e1ca5d1f50f6eb0e140f90e32 Mon Sep 17 00:00:00 2001 From: Karl Wan Date: Tue, 5 Sep 2017 20:56:58 +0800 Subject: [PATCH] Working on #9, completed query with search string --- YahooFinanceApi.Tests/UnitTest1.cs | 12 +++- YahooFinanceApi/Helper.cs | 4 ++ YahooFinanceApi/Lookup/LookupSymbol.cs | 32 +++++++++ YahooFinanceApi/Lookup/LookupType.cs | 23 ++++++ YahooFinanceApi/Lookup/MarketType.cs | 13 ++++ YahooFinanceApi/Lookup/YahooLookup.cs | 97 ++++++++++++++++++++++++++ YahooFinanceApi/YahooFinanceApi.csproj | 3 + 7 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 YahooFinanceApi/Lookup/LookupSymbol.cs create mode 100644 YahooFinanceApi/Lookup/LookupType.cs create mode 100644 YahooFinanceApi/Lookup/MarketType.cs create mode 100644 YahooFinanceApi/Lookup/YahooLookup.cs diff --git a/YahooFinanceApi.Tests/UnitTest1.cs b/YahooFinanceApi.Tests/UnitTest1.cs index e9ed180..2f5a22a 100644 --- a/YahooFinanceApi.Tests/UnitTest1.cs +++ b/YahooFinanceApi.Tests/UnitTest1.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Threading.Tasks; using Xunit; - +using YahooFinanceApi.Lookup; + namespace YahooFinanceApi.Tests { public class UnitTest1 @@ -15,6 +17,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 c289f6c..a1f1625 100644 --- a/YahooFinanceApi/Helper.cs +++ b/YahooFinanceApi/Helper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -23,5 +24,8 @@ public static string GetRandomString(int length) const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; return string.Join("", Enumerable.Range(0, length).Select(i => Chars[new Random().Next(Chars.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 2bd545a..91dffe6 100644 --- a/YahooFinanceApi/YahooFinanceApi.csproj +++ b/YahooFinanceApi/YahooFinanceApi.csproj @@ -47,4 +47,7 @@ + + + \ No newline at end of file