| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- 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);
- }
- // ─── Paires/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, // 가상자산의 영문명
- new UpbitMarketEvent( // 종목 경보 정보
- m.MarketEvent?.Warning ?? false, // 유의 종목 여부
- new UpbitMarketCaution( // 주의 종목 여부
- m.MarketEvent?.Caution?.PriceFluctuations ?? false, // 가격 급등락 경보
- m.MarketEvent?.Caution?.TradingVolumeSoaring ?? false, // 거래량 급증 경보
- m.MarketEvent?.Caution?.DepositAmountSoaring ?? false, // 입금량 급증 경보
- m.MarketEvent?.Caution?.GlobalPriceDifferences ?? false, // 국내외 가격 차이 경보
- m.MarketEvent?.Caution?.ConcentrationOfSmallAccounts ?? false // 소수 계정 집중 거래 경보
- )
- )
- ))];
- }
- // ─── 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.TradeDate, // 체근 체결 일자(UTC)
- t.TradeTime, // 체근 체결 시작 (UTC)
- t.TradeDateKst, // 체근 체결 일자(KST)
- t.TradeTimeKst, // 체근 체결 시각(KST)
- t.TradeTimestamp, // 체결 시각의 밀리초단위 타임스탬프
- t.OpeningPrice, // 해당 페어의 첫 거래 가격
- t.HighPrice, // 해당 페어의 최고 거래 가격
- t.LowPrice, // 해덩 페어의 최저 거래 가격
- t.TradePrice, // 해당 페어의 현재 가격
- t.PrevClosingPrice, // 해당 페어의 전일 종가
- t.Change, // 가격 변동 상태(EVEN: 보합, RISE: 항승, FALL: 하락)
- t.ChangePrice, // 전일 종가 대비 가격 변화
- t.ChangeRate, // 전일 종가 대비 가격 변화율
- t.SignedChangePrice, // 전일 종가 대비 가격 변화 (부호 있는)(RISE: +, FALL: -)
- t.SignedChangeRate, // 전일 종가 대비 가격 변화율
- t.TradeVolume, // 최근 거래 수량
- t.AccTradePrice, // 누적 거래 금액(UTC)
- t.AccTradePrice24h, // 24시간 누적 거래 금액
- t.AccTradeVolume, // 누적 거래량(UTC)
- t.AccTradeVolume24h, // 24시간 누적 거래량
- t.Highest52WeekPrice, // 52주 신고가
- t.Highest52WeekDate, // 52주 신고가 달성일
- t.Lowest52WeekPrice, // 52주 신저가
- t.Lowest52WeekDate, // 52주 신저가 달성일
- t.Timestamp // 현재가 정보가 반영된 시각의 시간(mm)
- ))];
- }
- // ─── 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.TradeDateUtc, // 체결 일자(UTC)
- t.TradeTimeUtc, // 체결 시각(UTC)
- t.Timestamp, // 체결 시간(mm)
- t.TradePrice, // 최근 체결 가격
- t.TradeVolume, // 최근 체결량
- t.PrevClosingPrice, // 전일 종가
- t.ChangePrice, // 전일 종가 대비 가격 변화
- t.AskBid, // 매수/매도 구분(BID: 매수, ASK: 매도)
- t.SequentialId // 체결 번호(Unique)
- ))];
- }
- // ─── 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( // 호가 정보가 담긴 목록 (총 30호)
- u.AskPrice, // 매도 호가
- u.BidPrice, // 매수 호가
- u.AskSize, // 매도 잔량
- u.BidSize // 매수 잔량
- ))],
- o.Timestamp, // 조회 요청 시간(ms)
- o.Level // 호가가 적용된 가격 단위
- ))];
- }
- // ─── 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, // 캔들 기준 시각 (UTC)
- c.CandleDateTimeKst, // 캔들 기준 시각 (KST)
- c.OpeningPrice, // 시가
- c.HighPrice, // 고가
- c.LowPrice, // 저가
- c.TradePrice, // 종가 (현재가)
- c.Timestamp, // 마지막 틱이 저장된 시각 (ms)
- c.CandleAccTradePrice, // 누적 거래 금액
- c.CandleAccTradeVolume, // 누적 거래량
- c.Unit, // 캔들 집계 시간 단위 (분)
- c.PrevClosingPrice, // 전일 종가 (UTC 0시 기준)
- c.ChangePrice, // 전일 종가 대비 가격 변화
- c.ChangeRate, // 전일 종가 대비 가격 변화율
- c.ConvertedTradePrice, // 종가 환산 화폐 단위로 환산된 가격
- c.FirstDayOfPeriod // 캔들 집계 시작일자
- ))];
- }
- // ─── Internal DTOs ────────────────────────────────────────
- private sealed class UpbitCandleDto
- {
- public string Market { get; set; } = default!;
- public string CandleDateTimeUtc { get; set; } = default!;
- public string CandleDateTimeKst { get; set; } = default!;
- 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 int? Unit { get; set; }
- public decimal? PrevClosingPrice { get; set; }
- public decimal? ChangePrice { get; set; }
- public decimal? ChangeRate { get; set; }
- public decimal? ConvertedTradePrice { 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 UpbitMarketEventDto? MarketEvent { get; set; }
- }
- private sealed class UpbitMarketEventDto
- {
- public bool Warning { get; set; }
- public UpbitMarketCautionDto? Caution { get; set; }
- }
- private sealed class UpbitMarketCautionDto
- {
- public bool PriceFluctuations { get; set; }
- public bool TradingVolumeSoaring { get; set; }
- public bool DepositAmountSoaring { get; set; }
- public bool GlobalPriceDifferences { get; set; }
- public bool ConcentrationOfSmallAccounts { get; set; }
- }
- private sealed class UpbitTickerDetailDto
- {
- public string Market { get; set; } = default!;
- public string TradeDate { get; set; } = default!;
- public string TradeTime { get; set; } = default!;
- public string TradeDateKst { get; set; } = default!;
- public string TradeTimeKst { get; set; } = default!;
- public long TradeTimestamp { get; set; }
- public decimal OpeningPrice { get; set; }
- public decimal HighPrice { get; set; }
- public decimal LowPrice { get; set; }
- public decimal TradePrice { get; set; }
- public decimal PrevClosingPrice { get; set; }
- public string Change { get; set; } = default!;
- public decimal ChangePrice { get; set; }
- public decimal ChangeRate { get; set; }
- public decimal SignedChangePrice { get; set; }
- public decimal SignedChangeRate { get; set; }
- public decimal TradeVolume { 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 TradeDateUtc { get; set; } = default!;
- public string TradeTimeUtc { get; set; } = default!;
- public long Timestamp { get; set; }
- public decimal TradePrice { get; set; }
- public decimal TradeVolume { get; set; }
- public decimal PrevClosingPrice { get; set; }
- public decimal ChangePrice { 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; }
- public decimal Level { 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; }
- }
- }
|