하아찡
[C#/WPF] Upbit프로젝트 WebSocketTicker 본문
코인데이터를 실시간으로 받아오기위해선 Upbit Websocket을 사용하라해서 WebSocket을 사용해서 실시간 데이터를 받아옴. 아래는 Upbit API Reference주소 입니다.
https://docs.upbit.com/reference/websocket-ticker
Open API | 업비트 개발자 센터
docs.upbit.com
홈페이지에선 C#으로 제공된 코드가 없어가지고 그냥 C#방식으로 사용했음.
WebSocketTicker.cs
using FileIO;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text;
using WebSocketSharp;
using WebSocket = WebSocketSharp.WebSocket;
namespace Upbit.Functions
{
public class WebSocketTicker
{
public delegate void TickEventHandler(Structs.Ticker connect);
public static event TickEventHandler TickerEvent;
public delegate void OrderEventHandler(Structs.Orderbook connect);
public static event OrderEventHandler OrderEvent;
public delegate void TradeEventHandler(Structs.Trade connect);
public static event TradeEventHandler TradeEvent;
private string CoinName = "";
private WebSocket? ws = null;
private List<string>? Coin;
object bid;
object ask;
URLs url = new URLs();
//실시간 차트를 하기위해 차트 오브젝트 생성
object MarketView;
bool Connect = false;
public WebSocketTicker(List<string> coin = null)
{
//코인정보를 넘겨받음
Coin = coin;
if(coin is null)
{
Logs.Loginstance.WebSocketLog("생성될때 전달받은 코인값이 없습니다.");
}
else
{
SetWebSocket();
}
}
private void SetWebSocket()
{
if(ws is null)
{
ws = new WebSocket(url.WebSocketTick);
ws.OnOpen += ws_open;
ws.OnClose += ws_close;
ws.OnMessage += ws_message;
ws.Connect();
}
else
{
Logs.Loginstance.WebSocketLog("웹 소켓이 생성되어있습니다.");
}
}
public void SetChangeCoin(List<string> coin)
{
//다른 코인을 불러올때 사용.
//미리 생성했던 소켓을 종료하지않고 다시 생성해서
//이전 데이터랑 같이 불러ㅎ와지는 문제가 생겨서 추가함
ws.Close();
Coin = coin;
SetWebSocket();
}
private void ws_connect()
{
}
private void ws_open(object sender, EventArgs e)
{
Logs.Loginstance.WebSocketLog("업비트 실시간 소켓 연결");
JArray array = new JArray();
JArray array1 = new JArray();
JObject ticket = new JObject();
JObject trade = new JObject();
JObject orderbook = new JObject();
JObject format = new JObject();
JObject ticker = new JObject();
string str;
foreach (var coin in Coin)
{
array.Add(coin + ".10");
array1.Add(coin);
}
Logs.Loginstance.WebSocketLog($"{CoinName}코인 실시간 소켓 연결");
ticket["ticket"] = Guid.NewGuid().ToString();//UUID
orderbook["type"] = "orderbook";
orderbook["codes"] = array;
orderbook["isOnlyRealtime"] = "true"; //실시간 시세만 요청
trade["type"] = "trade";
trade["codes"] = array1;
trade["isOnlyRealtime"] = "true"; //실시간 시세만 요청
ticker["type"] = "ticker";
ticker["codes"] = array1;
//obj5["isOnlyRealtime"] = "true";
format["format"] = "SIMPLE";
str = string.Format("[{0},{1},{2},{3},{4}]", ticket.ToString(), trade.ToString(), orderbook.ToString(), ticker.ToString(), format.ToString());
//str = string.Format("[{0},{1},{2},{3},{4},{5}]", obj1.ToString(), obj2.ToString(), obj3.ToString(), obj5.ToString(), obj6.ToString(), obj4.ToString());
ws.Send(str);
//tradeserver.Enabled = true;
}
private void ws_close(object sender, EventArgs e)
{
Connect = false;
//소켓 연결종료 로그 남김
Logs.Loginstance.WebSocketLog("업비트 실시간 소켓 종료");
}
private void ws_message(object sender, MessageEventArgs e)
{
Connect = true;
bool bTrade = false;
bool bOrderbook = false;
JObject jsonObject = JObject.Parse(Encoding.UTF8.GetString(e.RawData));
string types = (string)jsonObject.GetValue("ty");
if (types == "trade")
{
Structs.Trade obj = new Structs.Trade();
obj.ty = (string)jsonObject.GetValue("ty");
obj.cd = (string)jsonObject.GetValue("cd");
obj.tp = (double)jsonObject.GetValue("tp");
obj.tv = (double)jsonObject.GetValue("tv");
obj.ab = (string)jsonObject.GetValue("ab");
obj.pcp = jsonObject.GetValue("pcp").Type == JTokenType.Null ? 0 : (double)jsonObject.GetValue("pcp");
obj.c = jsonObject.GetValue("c").Type == JTokenType.Null ? "" : (string)jsonObject.GetValue("c");
obj.cp = jsonObject.GetValue("cp").Type == JTokenType.Null ? 0 : (double)jsonObject.GetValue("cp");
obj.td = (string)jsonObject.GetValue("td");
obj.ttm = (string)jsonObject.GetValue("ttm");
obj.ttms = (long)jsonObject.GetValue("ttms");
obj.tms = (long)jsonObject.GetValue("tms");
obj.sid = (long)jsonObject.GetValue("sid");
obj.st = (string)jsonObject.GetValue("st");
if(TradeEvent != null && obj.st == "SNAPSHOT")
TradeEvent(obj);
}
else if (types == "orderbook")
{
Structs.Orderbook obj = new Structs.Orderbook();
obj.ty = (string)jsonObject.GetValue("ty");
obj.cd = (string)jsonObject.GetValue("cd");
obj.tas = (double)jsonObject.GetValue("tas");
obj.tbs = (double)jsonObject.GetValue("tbs");
obj.obu = new List<Structs.OrderbookUnit>();
obj.tms = (long)jsonObject.GetValue("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())
{
Structs.OrderbookUnit obj_unit = new Structs.OrderbookUnit();
obj_unit.ap = Convert.ToDouble(item["ap"].ToString());
obj_unit._as = Convert.ToDouble(item["as"].ToString());
obj_unit.bp = Convert.ToDouble(item["bp"].ToString());
obj_unit.bs = Convert.ToDouble(item["bs"].ToString());
obj.obu.Add(obj_unit);
}
}
}
if (OrderEvent != null)
OrderEvent(obj);
}
else if (types == "ticker")
{
Structs.Ticker obj = new Structs.Ticker();
obj.ty = (string)jsonObject.GetValue("ty"); //타입
obj.cd = (string)jsonObject.GetValue("cd"); //마켓코드
obj.op = (double)jsonObject.GetValue("op"); //시가
obj.hp = (double)jsonObject.GetValue("hp"); //고가
obj.lp = (double)jsonObject.GetValue("lp"); //저가
obj.tp = (double)jsonObject.GetValue("tp"); //현재가
obj.pcp = (double)jsonObject.GetValue("pcp"); //전일 종가
obj.c = (string)jsonObject.GetValue("c"); //전일대비 올랐는지 안올랐는지
obj.cp = (double)jsonObject.GetValue("cp"); //부호없는 전일대비값
obj.scp = (double)jsonObject.GetValue("scp"); //전일대비 값
obj.cr = (double)jsonObject.GetValue("cr"); //부호없는 전일대비 등락율
obj.scr = (double)jsonObject.GetValue("scr"); //전일대비 등략율
obj.tv = (double)jsonObject.GetValue("tv"); //최근거래량
obj.atv = (double)jsonObject.GetValue("atv"); //누적거래량
obj.atv24h = (double)jsonObject.GetValue("atv24h"); //24시간 누적거래량
obj.atp = (double)jsonObject.GetValue("atp"); //누적거래대금
obj.atp24h = (double)jsonObject.GetValue("atp24h"); //24시간 누적 거래대금
obj.tdt = (string)jsonObject.GetValue("tdt"); //최근거래일자
obj.ttm = (string)jsonObject.GetValue("ttm"); //최근거래시각
obj.ttms = (long)jsonObject.GetValue("ttms"); //체결타임 스탬프
obj.ab = (string)jsonObject.GetValue("ab"); //매수/매도 구분
obj.aav = (double)jsonObject.GetValue("aav"); //누적매도량
obj.abv = (double)jsonObject.GetValue("abv"); //누적매수량
obj.h52wp = (double)jsonObject.GetValue("h52wp"); //52주 최저가
obj.h52wdt = (string)jsonObject.GetValue("h52wdt"); //52주 최고가 달성일
obj.l52wp = (double)jsonObject.GetValue("l52wp"); //52주 최저가
obj.l52wdt = (string)jsonObject.GetValue("l52wdt"); //52주 최저가 달성일
obj.ts = (string)jsonObject.GetValue("ts"); //거래상태
obj.ms = (string)jsonObject.GetValue("ms"); //거래상태
obj.msfi = (string)jsonObject.GetValue("msfi"); //거래상태
obj.its = (bool)jsonObject.GetValue("its"); //거래 정지 여부
//obj.dd = (string)jsonObject.GetValue("dd"); //상장폐지일
obj.mw = (string)jsonObject.GetValue("mw"); //유의종목
obj.tms = (long)jsonObject.GetValue("tms"); //타임스탬프
obj.st = (string)jsonObject.GetValue("st"); //스트림타입
if(TickerEvent != null)
TickerEvent(obj);
}
}
}
}
이벤트
public delegate void TickEventHandler(Structs.Ticker connect);
public static event TickEventHandler TickerEvent;
public delegate void OrderEventHandler(Structs.Orderbook connect);
public static event OrderEventHandler OrderEvent;
public delegate void TradeEventHandler(Structs.Trade connect);
public static event TradeEventHandler TradeEvent;
각종 이벤트를 추가해줄건데
현재가 -> Tick
호가 -> Orderbook
체결 -> Trade
까지 존재했는데
내 체결이벤트를 받아오는 myTrade가 새롭게 추가됐어가지고 myTrade는 다른작업으로 코드를 작성해야해서 추후에 올려드리겠습니다.
소켓 생성 및 이벤트 등록
private void SetWebSocket()
{
if(ws is null)
{
ws = new WebSocket(url.WebSocketTick);
ws.OnOpen += ws_open;
ws.OnClose += ws_close;
ws.OnMessage += ws_message;
ws.Connect();
}
else
{
Logs.Loginstance.WebSocketLog("웹 소켓이 생성되어있습니다.");
}
}
ws_open쪽과 ws_message쪽만 설명해드리겠습니다.
ws_open
private void ws_open(object sender, EventArgs e)
{
Logs.Loginstance.WebSocketLog("업비트 실시간 소켓 연결");
JArray array = new JArray();
JArray array1 = new JArray();
JObject ticket = new JObject();
JObject trade = new JObject();
JObject orderbook = new JObject();
JObject format = new JObject();
JObject ticker = new JObject();
string str;
foreach (var coin in Coin)
{
array.Add(coin + ".10");
array1.Add(coin);
}
Logs.Loginstance.WebSocketLog($"{CoinName}코인 실시간 소켓 연결");
ticket["ticket"] = Guid.NewGuid().ToString();//UUID
orderbook["type"] = "orderbook";
orderbook["codes"] = array;
orderbook["isOnlyRealtime"] = "true"; //실시간 시세만 요청
trade["type"] = "trade";
trade["codes"] = array1;
trade["isOnlyRealtime"] = "true"; //실시간 시세만 요청
ticker["type"] = "ticker";
ticker["codes"] = array1;
//obj5["isOnlyRealtime"] = "true";
format["format"] = "SIMPLE";
str = string.Format("[{0},{1},{2},{3},{4}]", ticket.ToString(), trade.ToString(), orderbook.ToString(), ticker.ToString(), format.ToString());
ws.Send(str);
}
요청 포멧은 [{Ticket Field},{Type Field},....,{Type Field},{Format Field}] 이와같이 보냅니다
실제 데이터 포멧 예시
[{"ticket": "b974cfc4-9d95-41b0-8625-c73aaf147365"},{"type": "trade", "codes": ["KRW-BTC","KRW-ETH"],"isOnlyRealtime": "true"},
{"type": "orderbook", "codes": ["KRW-BTC.10","KRW-ETH.10"], "isOnlyRealtime": "true"},
{"type": "ticker", "codes": ["KRW-BTC","KRW-ETH"]}, {"format": "SIMPLE"}]"
이와같이 메세지를 정상적으로 전송 할 경우 데이터를 받을 수 있습니다.
여기서 앞으로 작업에서 알아둬야할점 JSON방식으로 데이터를 처리하다보니 어떻게 열려있는게 뭔지는 알아야합니다.
기본적으로 데이터는 이름과 값으로 쌍으로 이루어져있습니다.
데이터는 쉼표로 구분을해주며, 객체는 {}(중괄호)로 둘러쌓여있습니다.
배열은 [](대괄호)로 둘러쌓여있습니다.
이정도만 알고있으면 데이터를 받아왔을때 C#에서 JObejct를 사용할껀지, JArray를 사용해서 분해할건지 어느정도 감이 잡히실겁니다.
{"type": "orderbook", "codes": ["KRW-BTC.10","KRW-ETH.10"], "isOnlyRealtime": "true"},
호가를 불러올때는 코인명 뒤에 .10을 붙이면 10개의 호가를 불러올 수 가있어서 필자는 10개를 불러오기위해 .10을 붙여 array변수에 넣어뒀음. 호가 개수는 최대 15개 까지 가능합니다.
{"format": "SIMPLE"}
데이터를 받아올때 축약어로 받아오게 설정함.
ws_message
private void ws_message(object sender, MessageEventArgs e)
{
Connect = true;
bool bTrade = false;
bool bOrderbook = false;
JObject jsonObject = JObject.Parse(Encoding.UTF8.GetString(e.RawData));
string types = (string)jsonObject.GetValue("ty");
if (types == "trade")
{
Structs.Trade obj = new Structs.Trade();
obj.ty = (string)jsonObject.GetValue("ty");
obj.cd = (string)jsonObject.GetValue("cd");
obj.tp = (double)jsonObject.GetValue("tp");
obj.tv = (double)jsonObject.GetValue("tv");
obj.ab = (string)jsonObject.GetValue("ab");
obj.pcp = jsonObject.GetValue("pcp").Type == JTokenType.Null ? 0 : (double)jsonObject.GetValue("pcp");
obj.c = jsonObject.GetValue("c").Type == JTokenType.Null ? "" : (string)jsonObject.GetValue("c");
obj.cp = jsonObject.GetValue("cp").Type == JTokenType.Null ? 0 : (double)jsonObject.GetValue("cp");
obj.td = (string)jsonObject.GetValue("td");
obj.ttm = (string)jsonObject.GetValue("ttm");
obj.ttms = (long)jsonObject.GetValue("ttms");
obj.tms = (long)jsonObject.GetValue("tms");
obj.sid = (long)jsonObject.GetValue("sid");
obj.st = (string)jsonObject.GetValue("st");
if(TradeEvent != null && obj.st == "SNAPSHOT")
TradeEvent(obj);
}
else if (types == "orderbook")
{
Structs.Orderbook obj = new Structs.Orderbook();
obj.ty = (string)jsonObject.GetValue("ty");
obj.cd = (string)jsonObject.GetValue("cd");
obj.tas = (double)jsonObject.GetValue("tas");
obj.tbs = (double)jsonObject.GetValue("tbs");
obj.obu = new List<Structs.OrderbookUnit>();
obj.tms = (long)jsonObject.GetValue("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())
{
Structs.OrderbookUnit obj_unit = new Structs.OrderbookUnit();
obj_unit.ap = Convert.ToDouble(item["ap"].ToString());
obj_unit._as = Convert.ToDouble(item["as"].ToString());
obj_unit.bp = Convert.ToDouble(item["bp"].ToString());
obj_unit.bs = Convert.ToDouble(item["bs"].ToString());
obj.obu.Add(obj_unit);
}
}
}
if (OrderEvent != null)
OrderEvent(obj);
}
else if (types == "ticker")
{
Structs.Ticker obj = new Structs.Ticker();
obj.ty = (string)jsonObject.GetValue("ty"); //타입
obj.cd = (string)jsonObject.GetValue("cd"); //마켓코드
obj.op = (double)jsonObject.GetValue("op"); //시가
obj.hp = (double)jsonObject.GetValue("hp"); //고가
obj.lp = (double)jsonObject.GetValue("lp"); //저가
obj.tp = (double)jsonObject.GetValue("tp"); //현재가
obj.pcp = (double)jsonObject.GetValue("pcp"); //전일 종가
obj.c = (string)jsonObject.GetValue("c"); //전일대비 올랐는지 안올랐는지
obj.cp = (double)jsonObject.GetValue("cp"); //부호없는 전일대비값
obj.scp = (double)jsonObject.GetValue("scp"); //전일대비 값
obj.cr = (double)jsonObject.GetValue("cr"); //부호없는 전일대비 등락율
obj.scr = (double)jsonObject.GetValue("scr"); //전일대비 등략율
obj.tv = (double)jsonObject.GetValue("tv"); //최근거래량
obj.atv = (double)jsonObject.GetValue("atv"); //누적거래량
obj.atv24h = (double)jsonObject.GetValue("atv24h"); //24시간 누적거래량
obj.atp = (double)jsonObject.GetValue("atp"); //누적거래대금
obj.atp24h = (double)jsonObject.GetValue("atp24h"); //24시간 누적 거래대금
obj.tdt = (string)jsonObject.GetValue("tdt"); //최근거래일자
obj.ttm = (string)jsonObject.GetValue("ttm"); //최근거래시각
obj.ttms = (long)jsonObject.GetValue("ttms"); //체결타임 스탬프
obj.ab = (string)jsonObject.GetValue("ab"); //매수/매도 구분
obj.aav = (double)jsonObject.GetValue("aav"); //누적매도량
obj.abv = (double)jsonObject.GetValue("abv"); //누적매수량
obj.h52wp = (double)jsonObject.GetValue("h52wp"); //52주 최저가
obj.h52wdt = (string)jsonObject.GetValue("h52wdt"); //52주 최고가 달성일
obj.l52wp = (double)jsonObject.GetValue("l52wp"); //52주 최저가
obj.l52wdt = (string)jsonObject.GetValue("l52wdt"); //52주 최저가 달성일
obj.ts = (string)jsonObject.GetValue("ts"); //거래상태
obj.ms = (string)jsonObject.GetValue("ms"); //거래상태
obj.msfi = (string)jsonObject.GetValue("msfi"); //거래상태
obj.its = (bool)jsonObject.GetValue("its"); //거래 정지 여부
//obj.dd = (string)jsonObject.GetValue("dd"); //상장폐지일
obj.mw = (string)jsonObject.GetValue("mw"); //유의종목
obj.tms = (long)jsonObject.GetValue("tms"); //타임스탬프
obj.st = (string)jsonObject.GetValue("st"); //스트림타입
if(TickerEvent != null)
TickerEvent(obj);
}
}
각 필드명 참고
https://docs.upbit.com/reference/websocket-ticker
Open API | 업비트 개발자 센터
docs.upbit.com
JObject jsonObject = JObject.Parse(Encoding.UTF8.GetString(e.RawData));
string types = (string)jsonObject.GetValue("ty");
위 코드는 데이터를 정상적으로 받아왔을때 현재 전달받은 데이터가 trade,orderbook,tick 인지 구분해주기 위해 Response의 Type값인 ty데이터를 읽어와서 구분해줍니다.
구분해주는 이유는 데이터마다 처리방식이 달라가지고 구분해줘야 합니다.
그 외 코드는 데이터를 처리해주는 부분이라 위 링크랑 비교해가시면서 확인해보시면 됩니다. 어려운내용은없어가지고 패스하겠습니다.
현재 코드에서 사용된 구조체 및 클래스
public struct Orderbook
{
public string ty { get; set; }
public string cd { get; set; }
public double tas { get; set; }
public double tbs { get; set; }
public List<OrderbookUnit> obu { get; set; }
public long tms { get; set; }
}
public struct OrderbookUnit
{
public double ap { get; set; }
public double _as { get; set; }
public double bp { get; set; }
public double bs { get; set; }
}
public struct Ticker
{
public string ty { get; set; }
public string cd { get; set; }
public double op { get; set; }
public double hp { get; set; }
public double lp { get; set; }
public double tp { get; set; }
public double pcp { get; set; }
public string c { get; set; }
public double cp { get; set; }
public double scp { get; set; }
public double cr { get; set; }
public double scr { get; set; }
public double tv { get; set; }
public double atv { get; set; }
public double atv24h { get; set; }
public double atp { get; set; }
public double atp24h { get; set; }
public string tdt { get; set; }
public string ttm { get; set; }
public long ttms { get; set; }
public string ab { get; set; }
public double aav { get; set; }
public double abv { get; set; }
public double h52wp { get; set; }
public string h52wdt { get; set; }
public double l52wp { get; set; }
public string l52wdt { get; set; }
public string ts { get; set; }
public string ms { get; set; }
public string msfi { get; set; }
public bool its { get; set; }
public string dd { get; set; }
public string mw { get; set; }
public long tms { get; set; }
public string st { get; set; }
}
public struct Trade
{
public string ty { get; set; }
public string cd { get; set; }
public double tp { get; set; }
public double tv { get; set; }
public string ab { get; set; }
public double pcp { get; set; }
public string c { get; set; }
public double cp { get; set; }
public string td { get; set; }
public string ttm { get; set; }
public long ttms { get; set; }
public long tms { get; set; }
public long sid { get; set; }
public string st { get; set; }
}
'C# > 코인프로그램 - 코드' 카테고리의 다른 글
[C#/WPF] Upbit프로젝트 Balance (1) | 2023.11.29 |
---|---|
[C#/WPF] Upbit프로젝트 MyTradeClientWebSocket (0) | 2023.11.28 |
[C#/WPF] Upbit프로젝트 DialogAccess (0) | 2023.11.27 |
[C#/WPF] Upbit프로젝트 Access (0) | 2023.11.27 |
C# Network 프로젝트 (0) | 2023.11.27 |