private async Task <List <object>?> GetTickResponseAsync(string symbol, string param, string frequency, CancellationToken ct)
        {
            var task = GetTimeZone(symbol, ct); // start task

            using var stream = await GetResponseStreamAsync(symbol, param, frequency, ct).ConfigureAwait(false);

            using var sr        = new StreamReader(stream);
            using var csvReader = new CsvReader(sr, CultureInfo.InvariantCulture);

            var closeTime = Exchanges.GetCloseTimeFromSymbol(symbol);
            var tz        = await task.ConfigureAwait(false);

            if (tz == null)
            {
                return(null);      // invalid symbol
            }
            if (!csvReader.Read()) // skip header
            {
                throw new Exception("Did not read headers.");
            }
            var ticks = new List <object>();

            while (csvReader.Read())
            {
                var tick = TickParser.Parse(param, csvReader.Context.Record, closeTime, tz);
                if (tick != null)
                {
                    ticks.Add(tick);
                }
            }
            return(ticks);
        }
        private async Task <List <T>?> GetTicksAsync <T>(string symbol, CancellationToken ct)
        {
            if (string.IsNullOrWhiteSpace(symbol))
            {
                throw new ArgumentNullException(nameof(symbol));
            }

            var key    = $"{symbol},{TickParser.GetParamFromType<T>()},{Freq.Name()}";
            var result = await HistoryCache.Get(key, ct).ConfigureAwait(false);

            if (result == null) // tricky
            {
                return(null);
            }
            return(result.Cast <T>().ToList()); // tricky
        }