하아찡

[C#/WPF] Upbit프로젝트 WebSocketTicker 본문

C#/코인프로그램 - 코드

[C#/WPF] Upbit프로젝트 WebSocketTicker

하아찡 2023. 11. 28. 15:17

코인데이터를 실시간으로 받아오기위해선 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; }
}
반응형