하아찡
C# 업비트 Websocket 연결 본문
API 호출 목록 북마크사이트
https://mysitecollection.com/guest/1/19/coinapi
내사이트모음
자신만의 사이트를 저장해두고 사용합니다.
mysitecollection.com
이전에는 RestAPI호출하는 방법을 알아봤습니다.
일단 이전글에서 APIKey값을 사용한 RestAPI호출은 헤더에서 JWT를 같이 보내면 된다고 설명했지만 코드로 보여드리겠습니다.
public async Task<Dictionary<string,Accounts>> GetAccountsAsync()
{
UpbitURLs url = new UpbitURLs();
Dictionary<string,Accounts> ListResult = new();
if (_authManager.AuthActive == false)
{
// 키가 활성화 상태가아님.
return ListResult;
}
try
{
var client = new HttpClient();
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(url.Account),
Headers =
{
{ "accept", "application/json" },
{ "Authorization", _authManager.GetJWT() }, // <-- 이부분이 추가됨
},
};
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
JArray jsonArray = JArray.Parse(body);
foreach (var item in jsonArray.Children())
{
Accounts ac = new Accounts();
ac.UnitCurruncy = item["unit_currency"]?.ToString();
ac.Currency = item["currency"]?.ToString();
ac.CoinName = $"{ac.UnitCurruncy}-{ac.Currency}"; // "구매화폐-코인 -> (KRW-BTC)"
ac.Balance = Convert.ToDouble(item["balance"]?.ToString());
ac.Locked = Convert.ToDouble(item["locked"]?.ToString());
ac.AvgBuyPrice = Convert.ToDouble(item["avg_buy_price"]?.ToString());
ac.AvgBuyPriceModified = (bool)item["avg_buy_price_modified"];
ListResult[ac.CoinName] = ac;
}
return ListResult;
}
}
catch (Exception e)
{
// TODO 일단 값을 리턴해주긴하는데 수정 해야함.
// Null처리를 해줘야할듯?
return ListResult;
}
}
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(url.Account),
Headers =
{
{ "accept", "application/json" },
{ "Authorization", _authManager.GetJWT() },
},
};
해당부분만 JWT를 헤더에 같이 보내주시면 자신의 잔고 및 주문기능을 사용가능해집니다.
현재 코드는 C# MAUI를 사용하여 작업하고있습니다.
해당 웹소켓 연결부분은 싱글톤을 사용해서 하나의 인스턴스만 생성하여 사용합니다. 소켓통신에서 데이터를 받으면 원하는 이벤트를 호출하는 방식으로 처리했습니다.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CoinManager.Auth;
using CoinManager.Upbit;
using CoinManager.Utils;
using Newtonsoft.Json.Linq;
namespace CoinManager.Upbit.WebSocket
{
public enum WebSocketType
{
Public,
Private
}
public class UpbitWebSocket : IWebSocket
{
protected readonly AuthManager? _AuthManager;
#region Model
private ClientWebSocket? WS;
Thread? thread;
protected bool bActiveWebSocket = false;
protected UpbitURLs upbitURL = new();
protected string RequestMessage = "";
protected Uri serverUri;
protected string AuthToken = "";
protected WebSocketType _type;
#endregion Model
public UpbitWebSocket(AuthManager _authManager)
{
_AuthManager = _authManager;
}
public UpbitWebSocket()
{
}
#region Functions
/// <summary>
/// 소켓 생성 및 실행
/// </summary>
/// <returns></returns>
public async Task SetWebSocket()
{
if(RequestMessage == "")
{
Logger.Log("Error SetWebSocket RequestMessage = '' ");
return;
}
try
{
WS = new ClientWebSocket();
if(_type == WebSocketType.Private)
WS.Options.SetRequestHeader("Authorization", AuthToken);
await WS.ConnectAsync(serverUri, CancellationToken.None);
bActiveWebSocket = true; // 소켓 작동
await SendMessage(RequestMessage);
while (WS.State == System.Net.WebSockets.WebSocketState.Open)
{
await ReceiveMessage();
}
}
catch (Exception ex)
{
Logger.Log("Error UpbitKey Error");
if(_AuthManager != null)
_AuthManager.AuthActive = false;
bActiveWebSocket = false;
}
}
/// <summary>
/// 전달받은 데이터 처리
/// </summary>
private async Task ReceiveMessage()
{
if (WS == null) {
return;
}
var buffer = new ArraySegment<byte>(new byte[4096]);
//var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
var messageBuffer = new List<byte>();
try
{
WebSocketReceiveResult result;
do
{
result = await WS.ReceiveAsync(buffer, CancellationToken.None);
messageBuffer.AddRange(buffer.Take(result.Count));
}
while (!result.EndOfMessage); // 메시지가 다 끝날 때까지 받기
if (result.MessageType == WebSocketMessageType.Close)
{
bActiveWebSocket = false;
await WS.CloseAsync(WebSocketCloseStatus.NormalClosure, "닫기 요청됨", CancellationToken.None);
return;
}
string message = Encoding.UTF8.GetString(messageBuffer.ToArray());
JObject jsonObject = JObject.Parse(message);
if (jsonObject.ContainsKey("error"))
{
Logger.Log($"Error message: {message}");
}
else
{
// 응답 데이터는 각각 Private, Public에서 처리
DoRequestProcess(jsonObject);
//Debug.WriteLine($"Received message: {message}"); // 값 확인용
}
}
catch (Exception ex)
{
Logger.Log($"수신 중 예외 발생: {ex.Message}");
}
}
protected virtual void DoRequestProcess(JObject jsonObject)
{
}
private async Task SendMessage(string message)
{
if(WS == null)
{
Logger.Log("UpbitWebsocket Null");
return;
}
byte[] messageBytes = System.Text.Encoding.UTF8.GetBytes(message);
await WS.SendAsync(new ArraySegment<byte>(messageBytes), WebSocketMessageType.Text, true, CancellationToken.None);
}
/// <summary>
/// 쓰레드를 생성해서 따로 처리함.
/// </summary>
public void SocketStart()
{
thread = new Thread(async () => await SetWebSocket());
thread.Start();
}
#endregion Functions
}
}
제가 사용한 방식은 2가지 웹소켓을 연결하기위해 해당 소켓 뼈대를 만들어두고 상속시켜 사용했습니다.
Private기능을 처리하는 웹소켓, Public기능을 처리하는 웹소켓 두가지를 싱글톤으로 생성합니다.
Public기능 웹소켓
using CoinManager.Utils;
using Newtonsoft.Json.Linq;
using static CoinManager.Upbit.DataFrame;
namespace CoinManager.Upbit.WebSocket
{
public class PublicWebSocket : UpbitWebSocket
{
public PublicWebSocket()
{
serverUri = new Uri(upbitURL.PublicWebSocket);
_type = WebSocketType.Public;
}
protected override void DoRequestProcess(JObject jsonObject)
{
string getType = (string?)jsonObject.GetValue("ty") ?? "";
if (getType == "ticker")
{
// TODO ticker 관한 처리
ProccessTick(jsonObject);
}
else if (getType == "trade")
{
// TODO trade 관한처리
ProcessTrade(jsonObject);
}
else if (getType == "orderbook")
{
// TODO orderbook 관한처리
ProcessOrderbook(jsonObject);
}
else if (getType == "candle.1s")
{
// TODO candle.1s 관한처리
// UpbitAPI 아직 테스트용인듯?
// 나중에 캔들 불러올때 쓰면 될거같긴한데 굳이 써야하나 싶기도하고?
// 어차피 들어온데이터까지 캔들데이터로 처리하면되는데...?
// 의도를 파악못함.
}
else
{
Logger.Log("Error Not Found getType");
}
}
private void ProccessTick(JObject jsonObject)
{
... //스트림타입
}
private void ProcessTrade(JObject jsonObject)
{
Trade obj = new Trade();
obj.ty = JsonGetValue.GetValueOrDefault<string>(jsonObject, "ty");
obj.cd = JsonGetValue.GetValueOrDefault<string>(jsonObject, "cd");
obj.tp = JsonGetValue.GetValueOrDefault<double>(jsonObject, "tp");
obj.tv = JsonGetValue.GetValueOrDefault<double>(jsonObject, "tv");
obj.ab = JsonGetValue.GetValueOrDefault<string>(jsonObject, "ab");
obj.pcp = jsonObject.GetValue("pcp").Type == JTokenType.Null ? 0 : JsonGetValue.GetValueOrDefault<double>(jsonObject, "pcp");
obj.c = jsonObject.GetValue("c").Type == JTokenType.Null ? "" : JsonGetValue.GetValueOrDefault<string>(jsonObject, "c");
obj.cp = jsonObject.GetValue("cp").Type == JTokenType.Null ? 0 : JsonGetValue.GetValueOrDefault<double>(jsonObject, "cp");
obj.td = JsonGetValue.GetValueOrDefault<string>(jsonObject, "td");
obj.ttm = JsonGetValue.GetValueOrDefault<string>(jsonObject, "ttm");
obj.ttms = JsonGetValue.GetValueOrDefault<long>(jsonObject, "ttms");
obj.tms = JsonGetValue.GetValueOrDefault<long>(jsonObject, "tms");
obj.sid = JsonGetValue.GetValueOrDefault<long>(jsonObject, "sid");
obj.st = JsonGetValue.GetValueOrDefault<string>(jsonObject, "st");
// 트레이드 정보가 필요한쪽으로 넘겨줌
NotifyPublicTrade(obj);
}
private void ProcessOrderbook(JObject jsonObject)
{
Orderbook obj = new Orderbook();
obj.ty = JsonGetValue.GetValueOrDefault<string>(jsonObject,"ty");
obj.cd = JsonGetValue.GetValueOrDefault<string>(jsonObject, "cd");
obj.tas = JsonGetValue.GetValueOrDefault<double>(jsonObject, "tas");
obj.tbs = JsonGetValue.GetValueOrDefault<double>(jsonObject, "tbs");
obj.obu = new List<OrderbookUnit>();
obj.tms = JsonGetValue.GetValueOrDefault<long>(jsonObject, "tms");
foreach (var items in jsonObject.Children())
{
//호가 데이터 영역
if (items.Path == @"obu")
{
JArray jsonArray_child = JArray.Parse(items.First.ToString());
foreach (var item in jsonArray_child.Children())
{
OrderbookUnit obj_unit = new OrderbookUnit();
obj_unit.ap = Convert.ToDouble(item["ap"]?.ToString() ?? "0" );
obj_unit._as = Convert.ToDouble(item["as"]?.ToString() ?? "0");
obj_unit.bp = Convert.ToDouble(item["bp"]?.ToString() ?? "0");
obj_unit.bs = Convert.ToDouble(item["bs"]?.ToString() ?? "0");
obj.obu.Add(obj_unit);
}
}
}
}
/// <summary>
/// 웹소켓을 통해 코인데이터 전부다 받아옴 현재는 KRW로 거래 가능한 코인만 .
/// </summary>
/// <param name="markets">업비트 제공 전체코인목록</param>
public void SetMarketList(List<MarketCodes> markets)
{
JArray CoinList = new JArray();
JArray OrderBookCoinList = new JArray();
JObject ticket = new JObject();
JObject ticker = new JObject();
JObject trade = new JObject();
JObject orderbook = new JObject();
JObject candle1s = new JObject();
JObject format = new JObject();
// 마켓정보를 전달해주는데 KRW만 사용 할 예정
foreach (var coin in markets)
{
if (coin.Market.Contains("KRW"))
{
CoinList.Add(coin.Market);
OrderBookCoinList.Add(coin.Market + ".10");
}
}
ticket["ticket"] = Guid.NewGuid().ToString();//UUID
ticker["type"] = "ticker";
ticker["codes"] = CoinList;
ticker["isOnlyRealtime"] = "true"; //실시간 시세만 요청
trade["type"] = "trade";
trade["codes"] = CoinList;
trade["isOnlyRealtime"] = "true"; //실시간 시세만 요청
orderbook["type"] = "orderbook";
orderbook["codes"] = OrderBookCoinList;
orderbook["isOnlyRealtime"] = "true"; //실시간 시세만 요청
/*
candle1s["type"] = "candle.1s";
candle1s["codes"] = CoinList;
candle1s["isOnlyRealtime"] = "true"; //실시간 시세만 요청
*/
format["format"] = "SIMPLE";
RequestMessage = string.Format("[{0},{1},{2},{3},{4}]", ticket.ToString(), ticker.ToString(), trade.ToString(), orderbook.ToString(), format.ToString());
// 웹소켓 시작
SocketStart();
}
#region Event
public event Action<Trade>? OnPublicTrade;
private void NotifyPublicTrade(Trade obj)
{
OnPublicTrade?.Invoke(obj);
}
#endregion Event
}
}
ProccessTick 함수 내용은 지웠습니다... 데이터 받는 양이 정말 많아서 코드가 많이 길어져서 지웠습니다.
사실 코드를 봐도 뭐 작동은 하니 가져다 쓰시면 되긴하는데 왜 되는질 알아야겠죠?
일단 실시간으로 받고싶은 데이터 타입필드값을 정해줘야합니다.
Public웹소켓쪽에서는 ticker, trade, orderbook, candle.1s 으로 해당 값들을 Type이라고 하겠습니다.
해당 요청을 필요로 하는 데이터를 웹소켓을 통해 메세지를 보내줘야합니다. 업비트쪽에서 원하는 메세지 형태는 아래 이미지와 같습니다.
여기서 Ticket은 유니크한 고유값으로 사용해주시면됩니다.
그다음 Type Field는 내가 실시간으로 체결되는 값을 받고싶으면 trade를 Type Field에 넣으시면 됩니다.
그래서 해당 메세지를 만들어보면 아래와 같이 만들어지는데
[{유니크한값~},{trade},{DEFAULT}]
해당값을 보내기만하면 안됩니다... 조금더 살펴봐야해요.
각각의 Type별로 메세지에 추가되는 정보들이 있습니다. Trade를 기준으로 보시면 type은 "trade"를 보내주시면 되고 codes를 보시면 꼭 필요하다고 동그라미 쳐져있네요. 생략이 불가능합니다. 그러면 저 codes에는 어떤 데이터 형태인가?
위와같은 형식의 메세지를 만들어서 웹소켓을 통해 보내시면 해당 코인에 대한 실시간 체결데이터가 웹소켓을 통해 들어오게 됩니다.
그래서 실시간으로 정보가 필요한 코인을 적어서 보내시면 됩니다. 저는 모든 코인의 데이터가 다 필요해서 모든코인을 다 실시간으로 받게 처리했습니다.
해당 코드가 아래함수입니다.
public void SetMarketList(List<MarketCodes> markets)
{
JArray CoinList = new JArray();
JArray OrderBookCoinList = new JArray();
JObject ticket = new JObject();
JObject ticker = new JObject();
JObject trade = new JObject();
JObject orderbook = new JObject();
JObject candle1s = new JObject();
JObject format = new JObject();
// 마켓정보를 전달해주는데 KRW만 사용 할 예정
foreach (var coin in markets)
{
if (coin.Market.Contains("KRW"))
{
CoinList.Add(coin.Market);
OrderBookCoinList.Add(coin.Market + ".10");
}
}
ticket["ticket"] = Guid.NewGuid().ToString();//UUID
ticker["type"] = "ticker";
ticker["codes"] = CoinList;
ticker["isOnlyRealtime"] = "true"; //실시간 시세만 요청
trade["type"] = "trade";
trade["codes"] = CoinList;
trade["isOnlyRealtime"] = "true"; //실시간 시세만 요청
orderbook["type"] = "orderbook";
orderbook["codes"] = OrderBookCoinList;
orderbook["isOnlyRealtime"] = "true"; //실시간 시세만 요청
/*
candle1s["type"] = "candle.1s";
candle1s["codes"] = CoinList;
candle1s["isOnlyRealtime"] = "true"; //실시간 시세만 요청
*/
format["format"] = "SIMPLE";
RequestMessage = string.Format("[{0},{1},{2},{3},{4}]", ticket.ToString(), ticker.ToString(), trade.ToString(), orderbook.ToString(), format.ToString());
// 웹소켓 시작
SocketStart();
}
RestAPI통해 마켓정보를 전부다 받아와서 반복문 처리해서 메세지를 만들어서 보냈습니다.
프로그램에서 처리된 메세지 정보입니다.
[{
"ticket": "83206412-b87a-408a-b69a-a9a7ac461aac"
},{
"type": "ticker",
"codes": [
"KRW-BTC",
"KRW-ETH",
"KRW-NEO",
"KRW-MTL",
"KRW-XRP",
"KRW-ETC",
"KRW-SNT",
"KRW-WAVES",
"KRW-XEM",
"KRW-QTUM",
"KRW-LSK",
"KRW-STEEM",
"KRW-XLM",
"KRW-ARDR",
"KRW-ARK",
"KRW-STORJ",
"KRW-GRS",
"KRW-ADA",
"KRW-POWR",
"KRW-ICX",
"KRW-EOS",
"KRW-TRX",
"KRW-SC",
"KRW-ONT",
"KRW-ZIL",
"KRW-POLYX",
"KRW-ZRX",
"KRW-BCH",
"KRW-BAT",
"KRW-IOST",
"KRW-CVC",
"KRW-IQ",
"KRW-IOTA",
"KRW-ONG",
"KRW-GAS",
"KRW-BOUNTY",
"KRW-ELF",
"KRW-KNC",
"KRW-BSV",
"KRW-THETA",
"KRW-QKC",
"KRW-BTT",
"KRW-MOC",
"KRW-TFUEL",
"KRW-MANA",
"KRW-ANKR",
"KRW-AERGO",
"KRW-ATOM",
"KRW-TT",
"KRW-GAME2",
"KRW-MBL",
"KRW-WAXP",
"KRW-HBAR",
"KRW-MED",
"KRW-MLK",
"KRW-STPT",
"KRW-ORBS",
"KRW-VET",
"KRW-CHZ",
"KRW-STMX",
"KRW-DKA",
"KRW-HIVE",
"KRW-KAVA",
"KRW-AHT",
"KRW-LINK",
"KRW-XTZ",
"KRW-BORA",
"KRW-JST",
"KRW-CRO",
"KRW-TOKAMAK",
"KRW-SXP",
"KRW-HUNT",
"KRW-DOT",
"KRW-MVL",
"KRW-STRAX",
"KRW-AQT",
"KRW-GLM",
"KRW-META",
"KRW-FCT2",
"KRW-CBK",
"KRW-SAND",
"KRW-HP",
"KRW-DOGE",
"KRW-STRIKE",
"KRW-PUNDIX",
"KRW-FLOW",
"KRW-AXS",
"KRW-STX",
"KRW-XEC",
"KRW-SOL",
"KRW-POL",
"KRW-AAVE",
"KRW-1INCH",
"KRW-ALGO",
"KRW-NEAR",
"KRW-AVAX",
"KRW-T",
"KRW-CELO",
"KRW-GMT",
"KRW-APT",
"KRW-SHIB",
"KRW-MASK",
"KRW-ARB",
"KRW-EGLD",
"KRW-SUI",
"KRW-GRT",
"KRW-BLUR",
"KRW-IMX",
"KRW-SEI",
"KRW-MINA",
"KRW-CTC",
"KRW-ASTR",
"KRW-ID",
"KRW-PYTH",
"KRW-MNT",
"KRW-AKT",
"KRW-ZETA",
"KRW-AUCTION",
"KRW-STG",
"KRW-BEAM",
"KRW-TAIKO",
"KRW-USDT",
"KRW-ONDO",
"KRW-ZRO",
"KRW-BLAST",
"KRW-JUP",
"KRW-ENS",
"KRW-G",
"KRW-PENDLE",
"KRW-ATH",
"KRW-USDC",
"KRW-UXLINK",
"KRW-BIGTIME",
"KRW-CKB",
"KRW-W",
"KRW-CARV",
"KRW-INJ",
"KRW-MEW",
"KRW-UNI",
"KRW-SAFE",
"KRW-DRIFT",
"KRW-AGLD",
"KRW-PEPE",
"KRW-BONK",
"KRW-RENDER",
"KRW-MOVE",
"KRW-ME",
"KRW-MOCA",
"KRW-VANA",
"KRW-SONIC",
"KRW-VTHO",
"KRW-ANIME",
"KRW-VIRTUAL",
"KRW-BERA",
"KRW-LAYER",
"KRW-TRUMP",
"KRW-JTO",
"KRW-COW",
"KRW-KAITO",
"KRW-ARKM",
"KRW-ORCA",
"KRW-WAL",
"KRW-COMP",
"KRW-FIL",
"KRW-WCT",
"KRW-DEEP",
"KRW-SIGN",
"KRW-TIA",
"KRW-PENGU",
"KRW-NXPC"
],
"isOnlyRealtime": "true"
},{
"type": "trade",
"codes": [
"KRW-BTC",
"KRW-ETH",
"KRW-NEO",
"KRW-MTL",
"KRW-XRP",
"KRW-ETC",
"KRW-SNT",
"KRW-WAVES",
"KRW-XEM",
"KRW-QTUM",
"KRW-LSK",
"KRW-STEEM",
"KRW-XLM",
"KRW-ARDR",
"KRW-ARK",
"KRW-STORJ",
"KRW-GRS",
"KRW-ADA",
"KRW-POWR",
"KRW-ICX",
"KRW-EOS",
"KRW-TRX",
"KRW-SC",
"KRW-ONT",
"KRW-ZIL",
"KRW-POLYX",
"KRW-ZRX",
"KRW-BCH",
"KRW-BAT",
"KRW-IOST",
"KRW-CVC",
"KRW-IQ",
"KRW-IOTA",
"KRW-ONG",
"KRW-GAS",
"KRW-BOUNTY",
"KRW-ELF",
"KRW-KNC",
"KRW-BSV",
"KRW-THETA",
"KRW-QKC",
"KRW-BTT",
"KRW-MOC",
"KRW-TFUEL",
"KRW-MANA",
"KRW-ANKR",
"KRW-AERGO",
"KRW-ATOM",
"KRW-TT",
"KRW-GAME2",
"KRW-MBL",
"KRW-WAXP",
"KRW-HBAR",
"KRW-MED",
"KRW-MLK",
"KRW-STPT",
"KRW-ORBS",
"KRW-VET",
"KRW-CHZ",
"KRW-STMX",
"KRW-DKA",
"KRW-HIVE",
"KRW-KAVA",
"KRW-AHT",
"KRW-LINK",
"KRW-XTZ",
"KRW-BORA",
"KRW-JST",
"KRW-CRO",
"KRW-TOKAMAK",
"KRW-SXP",
"KRW-HUNT",
"KRW-DOT",
"KRW-MVL",
"KRW-STRAX",
"KRW-AQT",
"KRW-GLM",
"KRW-META",
"KRW-FCT2",
"KRW-CBK",
"KRW-SAND",
"KRW-HP",
"KRW-DOGE",
"KRW-STRIKE",
"KRW-PUNDIX",
"KRW-FLOW",
"KRW-AXS",
"KRW-STX",
"KRW-XEC",
"KRW-SOL",
"KRW-POL",
"KRW-AAVE",
"KRW-1INCH",
"KRW-ALGO",
"KRW-NEAR",
"KRW-AVAX",
"KRW-T",
"KRW-CELO",
"KRW-GMT",
"KRW-APT",
"KRW-SHIB",
"KRW-MASK",
"KRW-ARB",
"KRW-EGLD",
"KRW-SUI",
"KRW-GRT",
"KRW-BLUR",
"KRW-IMX",
"KRW-SEI",
"KRW-MINA",
"KRW-CTC",
"KRW-ASTR",
"KRW-ID",
"KRW-PYTH",
"KRW-MNT",
"KRW-AKT",
"KRW-ZETA",
"KRW-AUCTION",
"KRW-STG",
"KRW-BEAM",
"KRW-TAIKO",
"KRW-USDT",
"KRW-ONDO",
"KRW-ZRO",
"KRW-BLAST",
"KRW-JUP",
"KRW-ENS",
"KRW-G",
"KRW-PENDLE",
"KRW-ATH",
"KRW-USDC",
"KRW-UXLINK",
"KRW-BIGTIME",
"KRW-CKB",
"KRW-W",
"KRW-CARV",
"KRW-INJ",
"KRW-MEW",
"KRW-UNI",
"KRW-SAFE",
"KRW-DRIFT",
"KRW-AGLD",
"KRW-PEPE",
"KRW-BONK",
"KRW-RENDER",
"KRW-MOVE",
"KRW-ME",
"KRW-MOCA",
"KRW-VANA",
"KRW-SONIC",
"KRW-VTHO",
"KRW-ANIME",
"KRW-VIRTUAL",
"KRW-BERA",
"KRW-LAYER",
"KRW-TRUMP",
"KRW-JTO",
"KRW-COW",
"KRW-KAITO",
"KRW-ARKM",
"KRW-ORCA",
"KRW-WAL",
"KRW-COMP",
"KRW-FIL",
"KRW-WCT",
"KRW-DEEP",
"KRW-SIGN",
"KRW-TIA",
"KRW-PENGU",
"KRW-NXPC"
],
"isOnlyRealtime": "true"
},{
"type": "orderbook",
"codes": [
"KRW-BTC.10",
"KRW-ETH.10",
"KRW-NEO.10",
"KRW-MTL.10",
"KRW-XRP.10",
"KRW-ETC.10",
"KRW-SNT.10",
"KRW-WAVES.10",
"KRW-XEM.10",
"KRW-QTUM.10",
"KRW-LSK.10",
"KRW-STEEM.10",
"KRW-XLM.10",
"KRW-ARDR.10",
"KRW-ARK.10",
"KRW-STORJ.10",
"KRW-GRS.10",
"KRW-ADA.10",
"KRW-POWR.10",
"KRW-ICX.10",
"KRW-EOS.10",
"KRW-TRX.10",
"KRW-SC.10",
"KRW-ONT.10",
"KRW-ZIL.10",
"KRW-POLYX.10",
"KRW-ZRX.10",
"KRW-BCH.10",
"KRW-BAT.10",
"KRW-IOST.10",
"KRW-CVC.10",
"KRW-IQ.10",
"KRW-IOTA.10",
"KRW-ONG.10",
"KRW-GAS.10",
"KRW-BOUNTY.10",
"KRW-ELF.10",
"KRW-KNC.10",
"KRW-BSV.10",
"KRW-THETA.10",
"KRW-QKC.10",
"KRW-BTT.10",
"KRW-MOC.10",
"KRW-TFUEL.10",
"KRW-MANA.10",
"KRW-ANKR.10",
"KRW-AERGO.10",
"KRW-ATOM.10",
"KRW-TT.10",
"KRW-GAME2.10",
"KRW-MBL.10",
"KRW-WAXP.10",
"KRW-HBAR.10",
"KRW-MED.10",
"KRW-MLK.10",
"KRW-STPT.10",
"KRW-ORBS.10",
"KRW-VET.10",
"KRW-CHZ.10",
"KRW-STMX.10",
"KRW-DKA.10",
"KRW-HIVE.10",
"KRW-KAVA.10",
"KRW-AHT.10",
"KRW-LINK.10",
"KRW-XTZ.10",
"KRW-BORA.10",
"KRW-JST.10",
"KRW-CRO.10",
"KRW-TOKAMAK.10",
"KRW-SXP.10",
"KRW-HUNT.10",
"KRW-DOT.10",
"KRW-MVL.10",
"KRW-STRAX.10",
"KRW-AQT.10",
"KRW-GLM.10",
"KRW-META.10",
"KRW-FCT2.10",
"KRW-CBK.10",
"KRW-SAND.10",
"KRW-HP.10",
"KRW-DOGE.10",
"KRW-STRIKE.10",
"KRW-PUNDIX.10",
"KRW-FLOW.10",
"KRW-AXS.10",
"KRW-STX.10",
"KRW-XEC.10",
"KRW-SOL.10",
"KRW-POL.10",
"KRW-AAVE.10",
"KRW-1INCH.10",
"KRW-ALGO.10",
"KRW-NEAR.10",
"KRW-AVAX.10",
"KRW-T.10",
"KRW-CELO.10",
"KRW-GMT.10",
"KRW-APT.10",
"KRW-SHIB.10",
"KRW-MASK.10",
"KRW-ARB.10",
"KRW-EGLD.10",
"KRW-SUI.10",
"KRW-GRT.10",
"KRW-BLUR.10",
"KRW-IMX.10",
"KRW-SEI.10",
"KRW-MINA.10",
"KRW-CTC.10",
"KRW-ASTR.10",
"KRW-ID.10",
"KRW-PYTH.10",
"KRW-MNT.10",
"KRW-AKT.10",
"KRW-ZETA.10",
"KRW-AUCTION.10",
"KRW-STG.10",
"KRW-BEAM.10",
"KRW-TAIKO.10",
"KRW-USDT.10",
"KRW-ONDO.10",
"KRW-ZRO.10",
"KRW-BLAST.10",
"KRW-JUP.10",
"KRW-ENS.10",
"KRW-G.10",
"KRW-PENDLE.10",
"KRW-ATH.10",
"KRW-USDC.10",
"KRW-UXLINK.10",
"KRW-BIGTIME.10",
"KRW-CKB.10",
"KRW-W.10",
"KRW-CARV.10",
"KRW-INJ.10",
"KRW-MEW.10",
"KRW-UNI.10",
"KRW-SAFE.10",
"KRW-DRIFT.10",
"KRW-AGLD.10",
"KRW-PEPE.10",
"KRW-BONK.10",
"KRW-RENDER.10",
"KRW-MOVE.10",
"KRW-ME.10",
"KRW-MOCA.10",
"KRW-VANA.10",
"KRW-SONIC.10",
"KRW-VTHO.10",
"KRW-ANIME.10",
"KRW-VIRTUAL.10",
"KRW-BERA.10",
"KRW-LAYER.10",
"KRW-TRUMP.10",
"KRW-JTO.10",
"KRW-COW.10",
"KRW-KAITO.10",
"KRW-ARKM.10",
"KRW-ORCA.10",
"KRW-WAL.10",
"KRW-COMP.10",
"KRW-FIL.10",
"KRW-WCT.10",
"KRW-DEEP.10",
"KRW-SIGN.10",
"KRW-TIA.10",
"KRW-PENGU.10",
"KRW-NXPC.10"
],
"isOnlyRealtime": "true"
},{
"format": "SIMPLE"
}]
많이 길죠...? 한번에 다 처리해서 보내가지고요
그래서 웹소켓을 통해 데이터가 어떤 종류가 있고 어떻게 오냐?
이제 데이터를 실시간으로 받아오실수 만들고싶은 기능을 만들수 있습니다.
예를들어 내 잔고를 보기위한 기능을 만들라면,
자금은 얼마나있는지?
구매한 코인은 어떤게 있는지?
해당코인이 매수중인게 얼마나있는지? 매도중인 코인이 얼마나있는지?
아마 다음글은 올라오는데 시간이 좀 걸릴예정입니다.
MAUI로 처음 코드를 작업해보기도해서 삐걱거리는 부분이 있어가지고요. 그래도 Prism프레임워크 사용해서 했을때랑 좀 많이 비슷하게 편리해진거같긴해요.
그래도 처음 작업하는 거다보닌깐 찾아봐야하는 정보도 많고 머리도 박아가면서 왜안되지 이래봐야죠.
'C# > 업비트' 카테고리의 다른 글
C# 업비트 Rest API 호출 (2) | 2025.05.19 |
---|