Я использую CoinbasePro API для извлечения исторических рыночных данных по акциям.
Примечание: API может возвращать не более 300 свечей на запрос.
В приведенном ниже коде создается список задач, которые должны выполняться простым пакетным процессором с ограниченной скоростью В целом этот код не имеет отношения к проблеме, но здесь для получения полной картины.:
var startTime = new DateTimeOffset(DateTime.Today.AddYears(-2)).UtcDateTime;
var totalEndDate = new DateTimeOffset(DateTime.Today).UtcDateTime;
var historicalDataStream = this.GetHistoricalDataStream(
"BTC-USD", Granularity.One_Hour, startTime, totalEndDate, 250, stoppingToken).ToArray();
while (index < historicalDataStream.Length)
{
var timer = Task.Delay(TimeSpan.FromSeconds(DELAY_IN_SECONDS)); // ".2" to make sure
var tasks = historicalDataStream
.Skip(index)
.Take(MAX_ITEMS_PER_SECOND)
.Select(producer => producer(coinbaseClient));
var tasksAndTimer = tasks.Concat(new[] { timer });
await Task.WhenAll(tasksAndTimer);
var product = this.GetProductFromId(cryptoContext, productId);
//we must await the save since the same ef context cannot be used in parallel
await this.SaveCandleData(cryptoContext, product, tasks);
index += MAX_ITEMS_PER_SECOND;
}
Составляю список задач:
public IEnumerable<Func<CoinbaseProClient, Task<IEnumerable<HistoricalCandle>>>> GetHistoricalDataStream(string productId, Granularity granularity, DateTimeOffset totalStartDate, DateTimeOffset totalEndDate, int maxResults, CancellationToken stoppingToken)
{
var startDate = totalStartDate;
var maxRangeSeconds = maxResults * (int)granularity;
while (startDate < totalEndDate)
{
var tmpEndDate = startDate.AddSeconds(maxRangeSeconds);
var endDate = ((tmpEndDate < totalEndDate) ? tmpEndDate : totalEndDate);
//Create closures so the variables do not change
var s = startDate;
var e = endDate;
var g = granularity;
yield return (client) =>
{
return this.ProcessHistoricalDataAsync(client, productId, s, e, g, stoppingToken);
};
startDate = endDate;
}
}
protected Task<IEnumerable<HistoricalCandle>> ProcessHistoricalDataAsync(CoinbaseProClient client, string productId, DateTimeOffset start, DateTimeOffset end, Granularity granularity, CancellationToken stoppingToken)
{
var startDateoffset = start.UtcDateTime;
var endDateoffset = end.UtcDateTime;
return client.MarketData.GetHistoricRatesAsync(productId, startDateoffset, endDateoffset, (int)granularity, stoppingToken)
.ContinueWith(task => task.Result.Select(x => new HistoricalCandle {
StartDate = x.Time,
GranularitySeconds = (int)granularity,
Open = x.Open ?? 0,
High = x.High ?? 0,
Low = x.Low ?? 0,
Close = x.Close ?? 0,
Volume = x.Volume ?? 0
})
);
}
Рядом с конечными точками каждого пакета есть повторяющиеся записи, я предполагаю, что это связано с переходом на летнее время
Id StartDate GranularitySeconds Open High Low Close Volume ProductId 117842 2019-08-22 00:00:00.0000000 +00:00 3600 10133.34000000 10150.00000000 10034.79000000 10074.51000000 272.61765990 1
117848 2019-08-22 00:00:00.0000000 +00:00 3600 10133.34000000 10150.00000000 10034.79000000 10074.51000000 272.61765990 1
В то время как в другие дни меньше записей
Date Count
2020-10-20, 23
2019-10-31, 23
Как видите, я попытался нормализовать все, чтобы использовать дату в формате UTC, но все равно получаю неверные данные. Я не вижу, что я здесь делаю не так, как я могу получить правильный набор результатов?
Кроме того, вы неправильно используете DateTimeOffset. Вы вызываете свойство UtcDate, которое нет возвращает DateTimeOffset. Он возвращает DateTime. Затем вы вставляете этот DateTime в свой собственный метод, который ожидает DateTimeOffset, предположительно выполняя некоторое преобразование. Вы должны просто передать DateTimeOffset в свой метод. При вызове API нет необходимости вызывать UtcDateTime до самого конца. Вся суть DateTimeOffset в том, чтобы сделать преобразование всего в UTC без излишнего труда. Он обрабатывает все это изнутри.
Обязательный комментарий, когда OP борется с проблемами DateTime: посмотрите NodaTime (nodatime.org), это может помочь
Вы передаете объекты datetime в GetHistoricRatesAsync, но неясно, ожидает ли он UTC. Вы должны проверить документацию этого API, чтобы узнать, что он ожидает от этих параметров.