private async Task DoTimer(ITimerTrigger timer, TimerTriggeredHandlerArgs args, CancellationToken ct)
        {
            if (!StartWork())
            {
                return;
            }

            try
            {
                using (var context = CreateContext())
                {
                    var tradeRequest = new TradesHistoryRequest {
                        Limit = 10
                    };
                    var data = await _b2C2RestClient.GetTradeHistoryAsync(tradeRequest, ct);

                    var added = 0;
                    do
                    {
                        added = 0;
                        foreach (var log in data.Data)
                        {
                            foreach (var assetMapping in _assetMappings)
                            {
                                log.AssetPair = log.AssetPair.Replace(assetMapping.Key, assetMapping.Value);
                            }

                            var item = await context.Trades.FirstOrDefaultAsync(e => e.TradeId == log.TradeId, ct);

                            if (item != null)
                            {
                                continue;
                            }

                            item = new TradeEntity(log);
                            context.Trades.Add(item);
                            added++;
                        }

                        await context.SaveChangesAsync(ct);

                        tradeRequest.Cursor = data.Next;
                        data = await _b2C2RestClient.GetTradeHistoryAsync(tradeRequest, ct);
                    } while (added > 0);
                }
            }
            finally
            {
                StopWork();
            }
        }
        public async Task <int> ReloadTradeHistoryAsync()
        {
            while (!StartWork())
            {
                await Task.Delay(1000);
            }

            try
            {
                using (var context = CreateContext())
                {
                    var tradeRequest = new TradesHistoryRequest {
                        Limit = 100
                    };
                    string tradeId = null;

                    if (_enableAutoUpdate)
                    {
                        var last = context.Trades.OrderByDescending(x => x.Created).FirstOrDefault();
                        if (last != null)
                        {
                            tradeRequest.CreatedAfter = last.Created.AddHours(-1);
                            tradeId = last.TradeId;
                        }
                    }

                    var data = await _b2C2RestClient.GetTradeHistoryAsync(tradeRequest);

                    _log.Debug($"Current cursor = null; get data after tradeId = {(tradeId ?? "null")}; load more {data.Data.Count}");

                    int  totalCount = 0;
                    bool finish     = false;

                    while (!finish || data.Data.Count > 0)
                    {
                        var items = new List <TradeEntity>();

                        foreach (var item in data.Data)
                        {
                            foreach (var assetMapping in _assetMappings)
                            {
                                item.AssetPair = item.AssetPair.Replace(assetMapping.Key, assetMapping.Value);
                            }

                            items.Add(new TradeEntity(item));
                        }

                        foreach (var item in items)
                        {
                            if (!string.IsNullOrEmpty(tradeId) && item.TradeId == tradeId)
                            {
                                finish = true;
                                break;
                            }

                            totalCount++;
                            context.Trades.Add(item);
                        }

                        await context.SaveChangesAsync();

                        if (finish)
                        {
                            _log.Debug($"Finish loading to tradeId = {tradeId}. Loaded {totalCount} records");
                            break;
                        }

                        tradeRequest.Cursor = data.Next;
                        data = await _b2C2RestClient.GetTradeHistoryAsync(tradeRequest);

                        if (string.IsNullOrEmpty(data.Next))
                        {
                            finish = true;
                        }

                        _log.Debug($"Current cursor = {tradeRequest.Cursor}; next cursor: {tradeRequest.Cursor}; load more {data.Data.Count}");
                    }

                    return(totalCount);
                }
            }
            finally
            {
                StopWork();
            }
        }
        public async Task <PaginationResponse <List <TradeLog> > > GetTradeHistoryAsync(TradesHistoryRequest request, CancellationToken ct = default(CancellationToken))
        {
            var requestId = Guid.NewGuid();

            _log.Debug("trade history - request", context: $"requestId: {requestId}, request: {request?.ToJson()}");

            var responseStr = string.Empty;

            try
            {
                var param = new Dictionary <string, string>();

                if (request != null)
                {
                    if (request.CreatedAfter.HasValue)
                    {
                        param.Add("created__gte", request.CreatedAfter.Value.ToString("yyyy-MM-ddThh:mm:ss"));
                    }

                    if (request.CreatedBefore.HasValue)
                    {
                        param.Add("created__lt", request.CreatedBefore.Value.ToString("yyyy-MM-ddThh:mm:ss"));
                    }

                    if (!string.IsNullOrEmpty(request.Instrument))
                    {
                        param.Add("instrument", request.Instrument);
                    }

                    if (request.Since.HasValue)
                    {
                        param.Add("since", request.Since.Value.ToString("yyyy-MM-ddThh:mm:ss"));
                    }

                    if (!string.IsNullOrEmpty(request.Cursor))
                    {
                        param.Add("cursor", request.Cursor);
                    }
                }

                param.Add("limit", Math.Max(100, request?.Limit ?? 50).ToString());

                var tradeUrl = new Uri(QueryHelpers.AddQueryString("trade/", param), UriKind.Relative).ToString();

                using var response = await _httpClient.GetAsync(tradeUrl, ct);

                var status = response.StatusCode;

                responseStr = await response.Content.ReadAsStringAsync();

                _log.Debug("trade history - response", new { RequestId = requestId, Response = responseStr });

                CheckForError(responseStr, status, requestId);

                var data = JsonConvert.DeserializeObject <List <TradeLog> >(responseStr);

                var result = new PaginationResponse <List <TradeLog> > {
                    Data = data
                };

                if (response.Headers.TryGetValues("link", out var links))
                {
                    (result.Next, result.Previous) = GetCursors(links);
                }

                return(result);
            }
            catch (Exception e)
            {
                _log.Error(e, "trade history - response exception", new {
                    RequestId = requestId,
                    Response  = responseStr
                });

                throw;
            }
        }