UpbitRestClient.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. using Application.Abstractions.Crypto;
  2. using System.Net.Http.Json;
  3. using System.Text.Json;
  4. using System.Text.Json.Serialization;
  5. namespace Infrastructure.Crypto;
  6. public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
  7. {
  8. private static readonly JsonSerializerOptions _jsonOptions = new()
  9. {
  10. PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
  11. NumberHandling = JsonNumberHandling.AllowReadingFromString
  12. };
  13. // ─── Candle ───────────────────────────────────────────────
  14. public async Task<IReadOnlyList<UpbitCandle>> GetSecondCandlesAsync(string market, int count, CancellationToken ct = default)
  15. {
  16. return await FetchCandlesAsync($"candles/seconds?market={market}&count={count}", ct);
  17. }
  18. public async Task<IReadOnlyList<UpbitCandle>> GetMinuteCandlesAsync(string market, int unit, int count, CancellationToken ct = default)
  19. {
  20. return await FetchCandlesAsync($"candles/minutes/{unit}?market={market}&count={count}", ct);
  21. }
  22. public async Task<IReadOnlyList<UpbitCandle>> GetDayCandlesAsync(string market, int count, CancellationToken ct = default)
  23. {
  24. return await FetchCandlesAsync($"candles/days?market={market}&count={count}", ct);
  25. }
  26. public async Task<IReadOnlyList<UpbitCandle>> GetWeekCandlesAsync(string market, int count, CancellationToken ct = default)
  27. {
  28. return await FetchCandlesAsync($"candles/weeks?market={market}&count={count}", ct);
  29. }
  30. public async Task<IReadOnlyList<UpbitCandle>> GetMonthCandlesAsync(string market, int count, CancellationToken ct = default)
  31. {
  32. return await FetchCandlesAsync($"candles/months?market={market}&count={count}", ct);
  33. }
  34. public async Task<IReadOnlyList<UpbitCandle>> GetYearCandlesAsync(string market, int count, CancellationToken ct = default)
  35. {
  36. return await FetchCandlesAsync($"candles/years?market={market}&count={count}", ct);
  37. }
  38. // ─── Market ───────────────────────────────────────────────
  39. public async Task<IReadOnlyList<UpbitMarket>> GetMarketsAsync(CancellationToken ct = default)
  40. {
  41. var items = await http.GetFromJsonAsync<List<UpbitMarketDto>>("market/all?is_details=true", _jsonOptions, ct) ?? [];
  42. return [..items.Select(m => new UpbitMarket(
  43. m.Market,
  44. m.KoreanName,
  45. m.EnglishName,
  46. m.MarketWarning
  47. ))];
  48. }
  49. // ─── Ticker ───────────────────────────────────────────────
  50. public async Task<IReadOnlyList<UpbitTickerDetail>> GetTickersAsync(string[] markets, CancellationToken ct = default)
  51. {
  52. var query = string.Join(",", markets);
  53. var items = await http.GetFromJsonAsync<List<UpbitTickerDetailDto>>($"ticker?markets={query}", _jsonOptions, ct) ?? [];
  54. return [..items.Select(t => new UpbitTickerDetail(
  55. t.Market,
  56. t.TradePrice,
  57. t.Change,
  58. t.SignedChangePrice,
  59. t.SignedChangeRate,
  60. t.OpeningPrice,
  61. t.HighPrice,
  62. t.LowPrice,
  63. t.PrevClosingPrice,
  64. t.AccTradePrice,
  65. t.AccTradePrice24h,
  66. t.AccTradeVolume,
  67. t.AccTradeVolume24h,
  68. t.Highest52WeekPrice,
  69. t.Highest52WeekDate,
  70. t.Lowest52WeekPrice,
  71. t.Lowest52WeekDate,
  72. t.Timestamp
  73. ))];
  74. }
  75. // ─── Trade ────────────────────────────────────────────────
  76. public async Task<IReadOnlyList<UpbitTrade>> GetTradesAsync(string market, int count, CancellationToken ct = default)
  77. {
  78. var items = await http.GetFromJsonAsync<List<UpbitTradeDto>>($"trades/ticks?market={market}&count={count}", _jsonOptions, ct) ?? [];
  79. return [..items.Select(t => new UpbitTrade(
  80. t.Market,
  81. t.TradeDate,
  82. t.TradeTime,
  83. t.Timestamp,
  84. t.TradePrice,
  85. t.TradeVolume,
  86. t.PrevClosingPrice,
  87. t.AskBid,
  88. t.SequentialId
  89. ))];
  90. }
  91. // ─── Orderbook ────────────────────────────────────────────
  92. public async Task<IReadOnlyList<UpbitOrderbook>> GetOrderbookAsync(string[] markets, CancellationToken ct = default)
  93. {
  94. var query = string.Join(",", markets);
  95. var items = await http.GetFromJsonAsync<List<UpbitOrderbookDto>>($"orderbook?markets={query}", _jsonOptions, ct) ?? [];
  96. return [..items.Select(o => new UpbitOrderbook(
  97. o.Market,
  98. o.TotalAskSize,
  99. o.TotalBidSize,
  100. [..o.OrderbookUnits.Select(u => new UpbitOrderbookUnit(
  101. u.AskPrice,
  102. u.BidPrice,
  103. u.AskSize,
  104. u.BidSize
  105. ))],
  106. o.Timestamp
  107. ))];
  108. }
  109. // ─── Private helpers ──────────────────────────────────────
  110. private async Task<IReadOnlyList<UpbitCandle>> FetchCandlesAsync(string url, CancellationToken ct)
  111. {
  112. var items = await http.GetFromJsonAsync<List<UpbitCandleDto>>(url, _jsonOptions, ct) ?? [];
  113. return [..items.Select(c => new UpbitCandle(
  114. c.Market,
  115. c.CandleDateTimeUtc,
  116. c.OpeningPrice,
  117. c.HighPrice,
  118. c.LowPrice,
  119. c.TradePrice,
  120. c.Timestamp,
  121. c.CandleAccTradePrice,
  122. c.CandleAccTradeVolume,
  123. c.FirstDayOfPeriod
  124. ))];
  125. }
  126. // ─── Internal DTOs ────────────────────────────────────────
  127. private sealed class UpbitCandleDto
  128. {
  129. public string Market { get; set; } = default!;
  130. public DateTime CandleDateTimeUtc { get; set; }
  131. public decimal OpeningPrice { get; set; }
  132. public decimal HighPrice { get; set; }
  133. public decimal LowPrice { get; set; }
  134. public decimal TradePrice { get; set; }
  135. public long Timestamp { get; set; }
  136. public decimal CandleAccTradePrice { get; set; }
  137. public decimal CandleAccTradeVolume { get; set; }
  138. public string? FirstDayOfPeriod { get; set; }
  139. }
  140. private sealed class UpbitMarketDto
  141. {
  142. public string Market { get; set; } = default!;
  143. public string KoreanName { get; set; } = default!;
  144. public string EnglishName { get; set; } = default!;
  145. public string? MarketWarning { get; set; }
  146. }
  147. private sealed class UpbitTickerDetailDto
  148. {
  149. public string Market { get; set; } = default!;
  150. public decimal TradePrice { get; set; }
  151. public string Change { get; set; } = default!;
  152. public decimal SignedChangePrice { get; set; }
  153. public decimal SignedChangeRate { get; set; }
  154. public decimal OpeningPrice { get; set; }
  155. public decimal HighPrice { get; set; }
  156. public decimal LowPrice { get; set; }
  157. public decimal PrevClosingPrice { get; set; }
  158. public decimal AccTradePrice { get; set; }
  159. public decimal AccTradePrice24h { get; set; }
  160. public decimal AccTradeVolume { get; set; }
  161. public decimal AccTradeVolume24h { get; set; }
  162. public decimal Highest52WeekPrice { get; set; }
  163. public string Highest52WeekDate { get; set; } = default!;
  164. public decimal Lowest52WeekPrice { get; set; }
  165. public string Lowest52WeekDate { get; set; } = default!;
  166. public long Timestamp { get; set; }
  167. }
  168. private sealed class UpbitTradeDto
  169. {
  170. public string Market { get; set; } = default!;
  171. public string TradeDate { get; set; } = default!;
  172. public string TradeTime { get; set; } = default!;
  173. public long Timestamp { get; set; }
  174. public decimal TradePrice { get; set; }
  175. public decimal TradeVolume { get; set; }
  176. public decimal PrevClosingPrice { get; set; }
  177. public string AskBid { get; set; } = default!;
  178. public long SequentialId { get; set; }
  179. }
  180. private sealed class UpbitOrderbookDto
  181. {
  182. public string Market { get; set; } = default!;
  183. public decimal TotalAskSize { get; set; }
  184. public decimal TotalBidSize { get; set; }
  185. public List<UpbitOrderbookUnitDto> OrderbookUnits { get; set; } = [];
  186. public long Timestamp { get; set; }
  187. }
  188. private sealed class UpbitOrderbookUnitDto
  189. {
  190. public decimal AskPrice { get; set; }
  191. public decimal BidPrice { get; set; }
  192. public decimal AskSize { get; set; }
  193. public decimal BidSize { get; set; }
  194. }
  195. }