| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- using Application.Abstractions.Crypto;
- using System.Net.Http.Json;
- using System.Text.Json;
- using System.Text.Json.Serialization;
- namespace Infrastructure.Crypto;
- public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
- {
- private static readonly JsonSerializerOptions _jsonOptions = new()
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- NumberHandling = JsonNumberHandling.AllowReadingFromString
- };
- // ─── Candle ───────────────────────────────────────────────
- public async Task<IReadOnlyList<UpbitCandle>> GetSecondCandlesAsync(string market, int count, CancellationToken ct = default)
- {
- return await FetchCandlesAsync($"candles/seconds?market={market}&count={count}", ct);
- }
- public async Task<IReadOnlyList<UpbitCandle>> GetMinuteCandlesAsync(string market, int unit, int count, CancellationToken ct = default)
- {
- return await FetchCandlesAsync($"candles/minutes/{unit}?market={market}&count={count}", ct);
- }
- public async Task<IReadOnlyList<UpbitCandle>> GetDayCandlesAsync(string market, int count, CancellationToken ct = default)
- {
- return await FetchCandlesAsync($"candles/days?market={market}&count={count}", ct);
- }
- public async Task<IReadOnlyList<UpbitCandle>> GetWeekCandlesAsync(string market, int count, CancellationToken ct = default)
- {
- return await FetchCandlesAsync($"candles/weeks?market={market}&count={count}", ct);
- }
- public async Task<IReadOnlyList<UpbitCandle>> GetMonthCandlesAsync(string market, int count, CancellationToken ct = default)
- {
- return await FetchCandlesAsync($"candles/months?market={market}&count={count}", ct);
- }
- public async Task<IReadOnlyList<UpbitCandle>> GetYearCandlesAsync(string market, int count, CancellationToken ct = default)
- {
- return await FetchCandlesAsync($"candles/years?market={market}&count={count}", ct);
- }
- // ─── Market ───────────────────────────────────────────────
- public async Task<IReadOnlyList<UpbitMarket>> GetMarketsAsync(CancellationToken ct = default)
- {
- var items = await http.GetFromJsonAsync<List<UpbitMarketDto>>("market/all?is_details=true", _jsonOptions, ct) ?? [];
- return [..items.Select(m => new UpbitMarket(
- m.Market,
- m.KoreanName,
- m.EnglishName,
- m.MarketWarning
- ))];
- }
- // ─── Ticker ───────────────────────────────────────────────
- public async Task<IReadOnlyList<UpbitTickerDetail>> GetTickersAsync(string[] markets, CancellationToken ct = default)
- {
- var query = string.Join(",", markets);
- var items = await http.GetFromJsonAsync<List<UpbitTickerDetailDto>>($"ticker?markets={query}", _jsonOptions, ct) ?? [];
- return [..items.Select(t => new UpbitTickerDetail(
- t.Market,
- t.TradePrice,
- t.Change,
- t.SignedChangePrice,
- t.SignedChangeRate,
- t.OpeningPrice,
- t.HighPrice,
- t.LowPrice,
- t.PrevClosingPrice,
- t.AccTradePrice,
- t.AccTradePrice24h,
- t.AccTradeVolume,
- t.AccTradeVolume24h,
- t.Highest52WeekPrice,
- t.Highest52WeekDate,
- t.Lowest52WeekPrice,
- t.Lowest52WeekDate,
- t.Timestamp
- ))];
- }
- // ─── Trade ────────────────────────────────────────────────
- public async Task<IReadOnlyList<UpbitTrade>> GetTradesAsync(string market, int count, CancellationToken ct = default)
- {
- var items = await http.GetFromJsonAsync<List<UpbitTradeDto>>($"trades/ticks?market={market}&count={count}", _jsonOptions, ct) ?? [];
- return [..items.Select(t => new UpbitTrade(
- t.Market,
- t.TradeDate,
- t.TradeTime,
- t.Timestamp,
- t.TradePrice,
- t.TradeVolume,
- t.PrevClosingPrice,
- t.AskBid,
- t.SequentialId
- ))];
- }
- // ─── Orderbook ────────────────────────────────────────────
- public async Task<IReadOnlyList<UpbitOrderbook>> GetOrderbookAsync(string[] markets, CancellationToken ct = default)
- {
- var query = string.Join(",", markets);
- var items = await http.GetFromJsonAsync<List<UpbitOrderbookDto>>($"orderbook?markets={query}", _jsonOptions, ct) ?? [];
- return [..items.Select(o => new UpbitOrderbook(
- o.Market,
- o.TotalAskSize,
- o.TotalBidSize,
- [..o.OrderbookUnits.Select(u => new UpbitOrderbookUnit(
- u.AskPrice,
- u.BidPrice,
- u.AskSize,
- u.BidSize
- ))],
- o.Timestamp
- ))];
- }
- // ─── Private helpers ──────────────────────────────────────
- private async Task<IReadOnlyList<UpbitCandle>> FetchCandlesAsync(string url, CancellationToken ct)
- {
- var items = await http.GetFromJsonAsync<List<UpbitCandleDto>>(url, _jsonOptions, ct) ?? [];
- return [..items.Select(c => new UpbitCandle(
- c.Market,
- c.CandleDateTimeUtc,
- c.OpeningPrice,
- c.HighPrice,
- c.LowPrice,
- c.TradePrice,
- c.Timestamp,
- c.CandleAccTradePrice,
- c.CandleAccTradeVolume,
- c.FirstDayOfPeriod
- ))];
- }
- // ─── Internal DTOs ────────────────────────────────────────
- private sealed class UpbitCandleDto
- {
- public string Market { get; set; } = default!;
- public DateTime CandleDateTimeUtc { get; set; }
- public decimal OpeningPrice { get; set; }
- public decimal HighPrice { get; set; }
- public decimal LowPrice { get; set; }
- public decimal TradePrice { get; set; }
- public long Timestamp { get; set; }
- public decimal CandleAccTradePrice { get; set; }
- public decimal CandleAccTradeVolume { get; set; }
- public string? FirstDayOfPeriod { get; set; }
- }
- private sealed class UpbitMarketDto
- {
- public string Market { get; set; } = default!;
- public string KoreanName { get; set; } = default!;
- public string EnglishName { get; set; } = default!;
- public string? MarketWarning { get; set; }
- }
- private sealed class UpbitTickerDetailDto
- {
- public string Market { get; set; } = default!;
- public decimal TradePrice { get; set; }
- public string Change { get; set; } = default!;
- public decimal SignedChangePrice { get; set; }
- public decimal SignedChangeRate { get; set; }
- public decimal OpeningPrice { get; set; }
- public decimal HighPrice { get; set; }
- public decimal LowPrice { get; set; }
- public decimal PrevClosingPrice { get; set; }
- public decimal AccTradePrice { get; set; }
- public decimal AccTradePrice24h { get; set; }
- public decimal AccTradeVolume { get; set; }
- public decimal AccTradeVolume24h { get; set; }
- public decimal Highest52WeekPrice { get; set; }
- public string Highest52WeekDate { get; set; } = default!;
- public decimal Lowest52WeekPrice { get; set; }
- public string Lowest52WeekDate { get; set; } = default!;
- public long Timestamp { get; set; }
- }
- private sealed class UpbitTradeDto
- {
- public string Market { get; set; } = default!;
- public string TradeDate { get; set; } = default!;
- public string TradeTime { get; set; } = default!;
- public long Timestamp { get; set; }
- public decimal TradePrice { get; set; }
- public decimal TradeVolume { get; set; }
- public decimal PrevClosingPrice { get; set; }
- public string AskBid { get; set; } = default!;
- public long SequentialId { get; set; }
- }
- private sealed class UpbitOrderbookDto
- {
- public string Market { get; set; } = default!;
- public decimal TotalAskSize { get; set; }
- public decimal TotalBidSize { get; set; }
- public List<UpbitOrderbookUnitDto> OrderbookUnits { get; set; } = [];
- public long Timestamp { get; set; }
- }
- private sealed class UpbitOrderbookUnitDto
- {
- public decimal AskPrice { get; set; }
- public decimal BidPrice { get; set; }
- public decimal AskSize { get; set; }
- public decimal BidSize { get; set; }
- }
- }
|