From 2e7ac99e821b3417878c2f635def2943456333e8 Mon Sep 17 00:00:00 2001 From: Felipe Oriani Date: Mon, 3 Jun 2024 22:18:36 -0300 Subject: [PATCH] Add fixes for too many requests --- src/Fynance.Tests/TickerTests.cs | 12 +- src/Fynance/Fynance.csproj | 8 +- src/Fynance/Ticker.cs | 11 + src/Fynance/YahooTicker.cs | 397 ++++++++++++++++--------------- 4 files changed, 223 insertions(+), 205 deletions(-) diff --git a/src/Fynance.Tests/TickerTests.cs b/src/Fynance.Tests/TickerTests.cs index 349847b..209c638 100644 --- a/src/Fynance.Tests/TickerTests.cs +++ b/src/Fynance.Tests/TickerTests.cs @@ -91,12 +91,12 @@ public void Should_download_ticker_data_from_yahoo_finance_by_start_finish_date_ [TestMethod] public void Splits_Should_not_be_null_when_requested() { - const string symbol = "PSPC"; + const string symbol = "FESA4.SA"; var ticker = Ticker.Build() .SetSymbol(symbol) - .SetStartDate(new DateTime(2020, 1, 1)) - .SetFinishDate(new DateTime(2021, 12, 1)) + .SetStartDate(new DateTime(2024, 1, 1)) + .SetFinishDate(new DateTime(2024, 12, 1)) .SetInterval(Interval.OneDay) .SetSplits(true); @@ -107,12 +107,12 @@ public void Splits_Should_not_be_null_when_requested() [TestMethod] public void Dividends_Should_not_be_null_when_requested() { - const string symbol = "PSPC"; + const string symbol = "PETR4.SA"; var ticker = Ticker.Build() .SetSymbol(symbol) - .SetStartDate(new DateTime(2020, 1, 1)) - .SetFinishDate(new DateTime(2021, 12, 1)) + .SetStartDate(new DateTime(2024, 1, 1)) + .SetFinishDate(new DateTime(2024, 12, 1)) .SetInterval(Interval.OneDay) .SetDividends(true); diff --git a/src/Fynance/Fynance.csproj b/src/Fynance/Fynance.csproj index 12ba66c..a5fcb89 100644 --- a/src/Fynance/Fynance.csproj +++ b/src/Fynance/Fynance.csproj @@ -12,14 +12,14 @@ Apache-2.0 https://github.com/felipeoriani/Fynance - 1.1.0.0 - 1.1.0.0 - 1.1.0 + 1.2.0.0 + 1.2.0.0 + 1.2.0 true - + diff --git a/src/Fynance/Ticker.cs b/src/Fynance/Ticker.cs index 61ce7b1..95450ae 100644 --- a/src/Fynance/Ticker.cs +++ b/src/Fynance/Ticker.cs @@ -62,6 +62,11 @@ public abstract class Ticker : IDisposable /// public FyResult Result { get; protected set; } + /// + /// It contains the information about the user-agent http request header. The default value is fynance, but you can set your own application. + /// + public string UserAgent { get; protected set; } = "fynance"; + #endregion #region Construtors @@ -204,6 +209,12 @@ public virtual Ticker SetHttpClient(HttpClient client) return this; } + public virtual Ticker SetUserAgent(string userAgent) + { + this.UserAgent = userAgent; + return this; + } + /// /// Sync implementation to get the results from the predefined settings. /// diff --git a/src/Fynance/YahooTicker.cs b/src/Fynance/YahooTicker.cs index aa067bc..bae08b1 100644 --- a/src/Fynance/YahooTicker.cs +++ b/src/Fynance/YahooTicker.cs @@ -1,196 +1,203 @@ -namespace Fynance -{ - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Threading.Tasks; - using Yahoo; - using Result; - - /// - /// Implementation of Ticker for Yahoo Finance. - /// - public class YahooTicker : Ticker, IDisposable - { - #region [ctor] - - public YahooTicker() - { - } - - public YahooTicker(string symbol) - : base(symbol) - { - } - - public YahooTicker(HttpClient client) - { - Client = client; - } - - public YahooTicker(string symbol, HttpClient client) - : base(symbol) - { - Client = client; - } - - #endregion - - #region [Methods] - - /// - public override async Task GetAsync() - { - // Get the query string argumentrs for the yahoo finance route. - var queryString = GetQueryStringParameters(); - - // Build the full route. - var url = $"{YUtils.BaseUrl}/v8/finance/chart/{Symbol}?{queryString}"; - - // Get the http response from the http call on the given route. - HttpResponseMessage response = await GetResponse(url).ConfigureAwait(false); - - string responseBody = null; - YResponse yResponse = null; - - // Read all the content whe the request succeed. - if (response.IsSuccessStatusCode) - responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - // Deserialize all the content - if (!string.IsNullOrWhiteSpace(responseBody)) - yResponse = JsonConvert.DeserializeObject(responseBody); - - // When the http request does not succeed - var error = yResponse?.Chart?.Error; - if (!response.IsSuccessStatusCode || yResponse == null || error != null) - { - string code = "Fynance.Yahoo"; - string message = "This result was not possible to get from Yahoo Finance."; - - if (error != null) - { - code = error.Code; - message = error.Description; - } - - // Throw an exception for the current error - throw new FynanceException(code, message) - { - Symbol = this.Symbol, - Period = this.Period, - Interval = this.Interval, - StatusCode = response.StatusCode - }; - } - - try - { - // Once the request was performed fine, the results are prepared based on the TimeZone. - Result = yResponse.GetResult(TimeZone); - - // Define the splits. - if (Splits) - { - Result.Splits = Result.Splits ?? new FySplit[0]; - } - - // Define the dividends. - if (Dividends) - { - Result.Dividends = Result.Dividends ?? new FyDividend[0]; - } - } - catch (Exception ex) - { - throw new FynanceException("Fynance.Yahoo", "An error occurred while trying to fetch the results. Please, check the InnerException for more details.", ex); - } - - return Result; - } - - /// - /// Get a http response message from the client instance. - /// - /// Url to invoke. - /// An instance of HttpResponseMessage. - private async Task GetResponse(string url) - { - if (Client == null) - { - Client = new HttpClient(new HttpClientHandler { UseProxy = false }); - } - - return await Client.GetAsync(url).ConfigureAwait(false); - } - - /// - /// Prepare the query string arguments for Yahoo Finance request. - /// - /// An url with all containing arguments. - private string GetQueryStringParameters() - { - var queryStringParameters = new Dictionary(); - - // When there are definitions for 'StartDate' or 'FinishDate' then use it as arguments. - if (StartDate != null || FinishDate != null) - { - // Set default timestamp for 'StartDate' when it is not defined. - if (StartDate == null) - StartDate = YUtils.DefaultDateTime; - - // Set current datetime for' FinishDate' when it is not defined. - if (FinishDate == null) - FinishDate = DateTime.Now; - - // Validate the Start/Finish interval. - if (StartDate > FinishDate) - throw new ArgumentOutOfRangeException("StartDate", "The StartDate can not be greater than FinishDate."); - - var period1 = (long) YUtils.GetTimestampFromDateTime(StartDate.Value); - var period2 = (long) YUtils.GetTimestampFromDateTime(FinishDate.Value); - - // YahooFinance expect two parameters called 'period1' and 'period1' as timeStamps values. - queryStringParameters.Add(nameof(period1), period1); - queryStringParameters.Add(nameof(period2), period2); - } - else - { - // When there is no definition for Start/Finish dates. - // We can must use the Period property which is available on the enumerator YPeriod. - // Get the valida format for periods. - var range = YUtils.GetPeriod(Period); - - // Use the range parameter. - queryStringParameters.Add(nameof(range), range); - } - - // Add the interval based on Interval property. - var interval = YUtils.GetInterval(Interval == Interval.ThirtyMinutes ? Interval.FifteenMinutes : Interval); - - // Use the interval parameter. - queryStringParameters.Add(nameof(interval), interval); - - var events = new List(); - - // Define events for dividends and splits. - if (Dividends) events.Add("div"); - if (Splits) events.Add("splits"); - - // set the argumento for events when defined. - if (events.Any()) - { - queryStringParameters.Add("events", string.Join(",", events)); - } - - // build the queryString parameters. - var queryString = string.Join("&", queryStringParameters.Select(x => $"{x.Key}={x.Value}")); - - return queryString; - } - - #endregion - } +namespace Fynance +{ + using Newtonsoft.Json; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Yahoo; + using Result; + + /// + /// Implementation of Ticker for Yahoo Finance. + /// + public class YahooTicker : Ticker, IDisposable + { + #region [ctor] + + private const string UserAgentKey = "user-agent"; + + public YahooTicker() + { + } + + public YahooTicker(string symbol) + : base(symbol) + { + } + + public YahooTicker(HttpClient client) + { + Client = client; + } + + public YahooTicker(string symbol, HttpClient client) + : base(symbol) + { + Client = client; + } + + #endregion + + #region [Methods] + + /// + public override async Task GetAsync() + { + // Get the query string argumentrs for the yahoo finance route. + var queryString = GetQueryStringParameters(); + + // Build the full route. + var url = $"{YUtils.BaseUrl}/v8/finance/chart/{Symbol}?{queryString}"; + + // Get the http response from the http call on the given route. + HttpResponseMessage response = await GetResponse(url).ConfigureAwait(false); + + string responseBody = null; + YResponse yResponse = null; + + // Read all the content whe the request succeed. + if (response.IsSuccessStatusCode) + responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + // Deserialize all the content + if (!string.IsNullOrWhiteSpace(responseBody)) + yResponse = JsonConvert.DeserializeObject(responseBody); + + // When the http request does not succeed + var error = yResponse?.Chart?.Error; + if (!response.IsSuccessStatusCode || yResponse == null || error != null) + { + string code = "Fynance.Yahoo"; + string message = "This result was not possible to get from Yahoo Finance."; + + if (error != null) + { + code = error.Code; + message = error.Description; + } + + // Throw an exception for the current error + throw new FynanceException(code, message) + { + Symbol = this.Symbol, + Period = this.Period, + Interval = this.Interval, + StatusCode = response.StatusCode + }; + } + + try + { + // Once the request was performed fine, the results are prepared based on the TimeZone. + Result = yResponse.GetResult(TimeZone); + + // Define the splits. + if (Splits) + { + Result.Splits = Result.Splits ?? new FySplit[0]; + } + + // Define the dividends. + if (Dividends) + { + Result.Dividends = Result.Dividends ?? new FyDividend[0]; + } + } + catch (Exception ex) + { + throw new FynanceException("Fynance.Yahoo", "An error occurred while trying to fetch the results. Please, check the InnerException for more details.", ex); + } + + return Result; + } + + /// + /// Get a http response message from the client instance. + /// + /// Url to invoke. + /// An instance of HttpResponseMessage. + private async Task GetResponse(string url) + { + if (Client == null) + { + Client = new HttpClient(new HttpClientHandler { UseProxy = false }); + } + + if (Client.DefaultRequestHeaders.Contains(UserAgentKey)) + Client.DefaultRequestHeaders.Remove(UserAgentKey); + + Client.DefaultRequestHeaders.Add(UserAgentKey, this.UserAgent); + + return await Client.GetAsync(url).ConfigureAwait(false); + } + + /// + /// Prepare the query string arguments for Yahoo Finance request. + /// + /// An url with all containing arguments. + private string GetQueryStringParameters() + { + var queryStringParameters = new Dictionary(); + + // When there are definitions for 'StartDate' or 'FinishDate' then use it as arguments. + if (StartDate != null || FinishDate != null) + { + // Set default timestamp for 'StartDate' when it is not defined. + if (StartDate == null) + StartDate = YUtils.DefaultDateTime; + + // Set current datetime for' FinishDate' when it is not defined. + if (FinishDate == null) + FinishDate = DateTime.Now; + + // Validate the Start/Finish interval. + if (StartDate > FinishDate) + throw new ArgumentOutOfRangeException("StartDate", "The StartDate can not be greater than FinishDate."); + + var period1 = (long) YUtils.GetTimestampFromDateTime(StartDate.Value); + var period2 = (long) YUtils.GetTimestampFromDateTime(FinishDate.Value); + + // YahooFinance expect two parameters called 'period1' and 'period1' as timeStamps values. + queryStringParameters.Add(nameof(period1), period1); + queryStringParameters.Add(nameof(period2), period2); + } + else + { + // When there is no definition for Start/Finish dates. + // We can must use the Period property which is available on the enumerator YPeriod. + // Get the valida format for periods. + var range = YUtils.GetPeriod(Period); + + // Use the range parameter. + queryStringParameters.Add(nameof(range), range); + } + + // Add the interval based on Interval property. + var interval = YUtils.GetInterval(Interval == Interval.ThirtyMinutes ? Interval.FifteenMinutes : Interval); + + // Use the interval parameter. + queryStringParameters.Add(nameof(interval), interval); + + var events = new List(); + + // Define events for dividends and splits. + if (Dividends) events.Add("div"); + if (Splits) events.Add("splits"); + + // set the argumento for events when defined. + if (events.Any()) + { + queryStringParameters.Add("events", string.Join(",", events)); + } + + // build the queryString parameters. + var queryString = string.Join("&", queryStringParameters.Select(x => $"{x.Key}={x.Value}")); + + return queryString; + } + + #endregion + } } \ No newline at end of file