public async Task <SymbolData> GetSymbol(string stockSymbol, int cooldownTimeMs)
        {
            if (!TokenExists())
            {
                Logger.Instance.LogMessage(TracingLevel.WARN, $"{this.GetType()} GetSymbol was called without a valid token");
                return(null);
            }

            try
            {
                // Fetch precached version if relevant
                if (dictSymbolCache.ContainsKey(stockSymbol))
                {
                    var symbolCache = dictSymbolCache[stockSymbol];
                    if (symbolCache != null && (DateTime.Now - symbolCache.LastRefresh).TotalMilliseconds <= cooldownTimeMs)
                    {
                        return(symbolCache.SymbolData);
                    }
                }

                var kvp = new List <KeyValuePair <string, string> >
                {
                    new KeyValuePair <string, string>("token", TokenManager.Instance.Token.StockToken),
                    new KeyValuePair <string, string>("symbols", stockSymbol),
                    new KeyValuePair <string, string>("types", "quote"),
                    new KeyValuePair <string, string>("range", "dynamic")
                };
                //kvp.Add(new KeyValuePair<string, string>("chartLast", DEFAULT_CHART_POINTS.ToString()));
                HttpResponseMessage response = await StockQuery(STOCK_BATCH_CHART_QUOTE, kvp);

                if (response.IsSuccessStatusCode)
                {
                    string body = await response.Content.ReadAsStringAsync();

                    var obj = JObject.Parse(body);

                    // Invalid Stock Symbol
                    if (obj.Count == 0)
                    {
                        Logger.Instance.LogMessage(TracingLevel.ERROR, $"{this.GetType()} GetSymbol invalid symbol: {stockSymbol}");
                        return(null);
                    }

                    var        jp    = obj.Properties().First();
                    StockQuote quote = jp.Value["quote"].ToObject <StockQuote>();
                    if (quote.ChangePercent.HasValue)
                    {
                        quote.ChangePercent *= 100;
                    }

                    var symbolData = new SymbolData(quote?.Symbol, quote, null);
                    dictSymbolCache[stockSymbol] = new SymbolCache(DateTime.Now, symbolData);
                    Logger.Instance.LogMessage(TracingLevel.INFO, $"{this.GetType()} GetSymbol retrieved Symbol: {stockSymbol}");
                    return(symbolData);
                }
                else
                {
                    Logger.Instance.LogMessage(TracingLevel.ERROR, $"{this.GetType()} GetSymbol invalid response: {response.StatusCode}");

                    if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
                    {
                        TokenManager.Instance.SetTokenFailed();
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Instance.LogMessage(TracingLevel.ERROR, $"{this.GetType()} GetSymbol Exception for symbol {stockSymbol}: {ex}");
            }
            return(null);
        }
        public async Task <SymbolData> GetSymbol(string stockSymbol, int cooldownTimeMs)
        {
            if (!TokenExists())
            {
                Logger.Instance.LogMessage(TracingLevel.WARN, $"{this.GetType()} GetSymbol was called without a valid token");
                return(null);
            }

            try
            {
                // Fetch precached version if relevant
                if (dictSymbolCache.ContainsKey(stockSymbol))
                {
                    var symbolCache = dictSymbolCache[stockSymbol];
                    if (symbolCache != null && (DateTime.Now - symbolCache.LastRefresh).TotalMilliseconds <= cooldownTimeMs)
                    {
                        return(symbolCache.SymbolData);
                    }
                }

                string queryUrl = String.Format(STOCK_URI, stockSymbol);
                HttpResponseMessage response = await StockQuery(queryUrl, null);

                if (response.IsSuccessStatusCode)
                {
                    string body = await response.Content.ReadAsStringAsync();

                    var obj = JObject.Parse(body);

                    // Invalid Stock Symbol
                    if (obj.Count == 0)
                    {
                        Logger.Instance.LogMessage(TracingLevel.ERROR, $"{this.GetType()} GetSymbol invalid symbol: {stockSymbol}");
                        return(null);
                    }

                    JToken res = obj["quoteResponse"]["result"].First;

                    StockQuote quote = CreateStockQuote(res);
                    if (quote != null)
                    {
                        var symbolData = new SymbolData(quote?.Symbol, quote, null);

                        Logger.Instance.LogMessage(TracingLevel.DEBUG, $"DEBUG: Symbol {stockSymbol} Dict: {dictSymbolCache.Count} Quote: {quote.Symbol} SymbolData: {symbolData.SymbolName}");

                        dictSymbolCache[stockSymbol] = new SymbolCache(DateTime.Now, symbolData);
                        Logger.Instance.LogMessage(TracingLevel.INFO, $"{this.GetType()} GetSymbol retrieved Symbol: {stockSymbol}");
                        return(symbolData);
                    }
                }
                else
                {
                    Logger.Instance.LogMessage(TracingLevel.ERROR, $"{this.GetType()} GetSymbol obj invalid response: {response.StatusCode}");

                    if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
                    {
                        TokenManager.Instance.SetTokenFailed();
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Instance.LogMessage(TracingLevel.ERROR, $"{this.GetType()} GetSymbol Exception for symbol {stockSymbol}: {ex}");
            }
            return(null);
        }