Files
BitGet_API/BitgetApi/WebService/WebConnection.cs
USER002 10cdbc9e4b 데이터 세트 구성 및 조합
1. 웹소켓 실시간 캔들
2. WebService  캔들
2024-05-28 16:56:19 +09:00

579 lines
30 KiB
C#

using BitgetAPI;
using BitgetAPI.Utils;
using BitgetAPI.Data;
using BitgetAPI.Utils;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BitgetApi.Data;
using Newtonsoft.Json.Linq;
using DevExpress.Utils.Extensions;
using static BitgetAPI.CommonProperty;
namespace BitgetAPI.WebService
{
public class WebConnection
{
public static object SendWebServer(CommonProperty.ExchangeWebReqType exchangeWebReqType, string url = "",
MarketCandleDataList marketCandleDataList = null, CommonProperty.MCandleTimeType interval = MCandleTimeType.M1,
string postData = "", string subUri = "")
{
object returnJsonData = null;
CommonProperty commonProperty = CommonProperty.Instance;
try
{
HttpWebRequest wReq = null;
// Post 방식인 경우만 사용
var rgbyData = Encoding.ASCII.GetBytes(postData);
// 웹 전송 타입 지정
var webConType = CommonProperty.WebConnectionType.GET;
// 1. WebRequest 객체 형성 및 HttpWebRequest 로 형변환
// 2. 거래소에 따른 헤더 파일 처리(POST 일 경우만 지정함)
if(string.IsNullOrEmpty(url))
{
switch (exchangeWebReqType)
{
#region Upbit
case CommonProperty.ExchangeWebReqType.UpbitMarket:
wReq = (HttpWebRequest)WebRequest.Create(new Uri(CommonProperty.UpbitURL_MarketInfo));
break;
#endregion
#region Bitget
case CommonProperty.ExchangeWebReqType.BitgetMarket_Spot:
wReq = (HttpWebRequest)WebRequest.Create(new Uri(CommonProperty.BitgetURL_MarketInfo_COIN));
break;
case CommonProperty.ExchangeWebReqType.BitgetMarket_Future:
wReq = (HttpWebRequest)WebRequest.Create(new Uri(CommonProperty.BitgetURL_MarketInfo_FUTURE));
break;
case CommonProperty.ExchangeWebReqType.BitgetCandleStick_Coin:
wReq = (HttpWebRequest)WebRequest.Create(new Uri(CommonProperty.BitgetURL_MarketCandle_Coin));
break;
case CommonProperty.ExchangeWebReqType.BitgetCandleStick_Future:
wReq = (HttpWebRequest)WebRequest.Create(new Uri(CommonProperty.BitgetURL_MarketCandle_Future));
break;
#endregion
}
}
else wReq= (HttpWebRequest)WebRequest.Create(new Uri(url));
// GET, POST 구분
switch (webConType)
{
case CommonProperty.WebConnectionType.GET:
break;
case CommonProperty.WebConnectionType.POST:
wReq.Method = "POST"; // 전송 방법 "GET" or "POST"
using (var stream = wReq.GetRequestStream())
{
stream.Write(rgbyData, 0, rgbyData.Length);
}
wReq.ContentType = "application/x-www-form-urlencoded";
wReq.ContentLength = rgbyData.Length;
break;
}
using (var wRes = (HttpWebResponse)wReq.GetResponse())
{
var respPostStream = wRes.GetResponseStream();
var readerPost = new StreamReader(respPostStream, Encoding.UTF8, true);
var resResult = readerPost.ReadToEnd();
switch (exchangeWebReqType)
{
#region Bitget
case CommonProperty.ExchangeWebReqType.BitgetMarket_Spot:
{
var datalist = JsonConvert.DeserializeObject<Dictionary<string, object>>(resResult);
var marketInfo = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(datalist["data"]?.ToString());
var data = marketInfo.FindAll(x => x["coin"].ToString().Contains("USDT"));
}
break;
case CommonProperty.ExchangeWebReqType.BitgetMarket_Future:
{
var datalist = JsonConvert.DeserializeObject<Dictionary<string, object>>(resResult);
var marketInfos = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(datalist["data"]?.ToString());
commonProperty.marketInfoList = new List<MarketInfo>();
foreach (var item in marketInfos)
{
MarketInfo marketInfo = new MarketInfo();
marketInfo.MarketName = item["symbol"].ToString();
marketInfo.ExchangePlace = Enum.GetName(typeof(CommonProperty.ExchangePlace), commonProperty.selectedChangePlace);
commonProperty.marketInfoList.Add(marketInfo);
}
return commonProperty.marketInfoList;
}
break;
case CommonProperty.ExchangeWebReqType.BitgetCandleStick_Coin:
{
}
break;
case CommonProperty.ExchangeWebReqType.BitgetCandleStick_Future:
{
var datalist = JsonConvert.DeserializeObject<Dictionary<string, object>>(resResult);
var datas = JsonConvert.DeserializeObject<List<List<object>>>(datalist["data"]?.ToString());
if (marketCandleDataList == null) return null;
var candleDataList = marketCandleDataList.candleDataList[interval];
foreach(var item in datas)
{
CandleData candleData = new CandleData();
candleData.Argument = Convertor.GetUnixtimeStampToDateTime(long.Parse(item[0].ToString()));
candleData.Open = Convert.ToDouble(item[1]);
candleData.High = Convert.ToDouble(item[2]);
candleData.Low = Convert.ToDouble(item[3]);
candleData.Close = Convert.ToDouble(item[4]);
candleData.Vol_C = Convert.ToDouble(item[5]);
candleData.Vol_USD = Convert.ToDouble(item[6]);
// 같은 시간이 존재하면 업데이트 : 1개밖에 없음
var sameCandleData = candleDataList.Find(i => i.Argument.Minute == candleData.Argument.Minute &&
i.Argument.Hour == candleData.Argument.Hour);
if (sameCandleData != null)
{
// 종가 => 웹소켓 데이터 사용 (웹 서비스 호출 데이터 중 마지막 데이터는 캔들이 완성되지 않음)
// 시가 => 웹 서비스 데이터 사용
sameCandleData.Open = candleData.Open;
// 저가, 고가 => 비교해서 사용
sameCandleData.Low = Math.Min(sameCandleData.Low, candleData.Low);
sameCandleData.High = Math.Max(sameCandleData.High, candleData.High);
}
}
return candleDatas;
}
break;
#endregion
}
}
}
catch (WebException webEx)
{
using (var Response = (HttpWebResponse)webEx.Response)
{
var nCode = Response.StatusCode;
using (var reader = new StreamReader(Response.GetResponseStream()))
{
var sRespBodyData = reader.ReadToEnd();
//return (JObject.Parse(sRespBodyData));
}
}
}
return returnJsonData;
}
public async void BitGetWebSocektStart(CommonProperty.BitgetWebSocketType bitgetWebSocketType, string chnnelType)
{
string apiUrl = "https://api.upbit.com/v1/market/all?isDetails=true";
// IP당 100개 연결
// 시간당 240회 !!!
// 타이머 30초 유지 > 30초 이내의 응답 메시지 수신 필요 > 아니면 오류 발생 !! > 재접속 필요 !!
// 초당 10개 메시지 허용
// 단일 연결로 최대 1000개의 스트림 구독
// 단인 IP 최대의 100개 연결
// *
/* timestamp : Unix Epoch 시간
* passphrase : APIKey 비밀번호
* timestamp : Unix Epoch 시간, 단위는 초(--restAPI의 서명 타임스탬프와 다름--)
* secretKey : 사용자가 APIKey를 신청할 때 생성되는 보안 키입니다. 예: 22582BD0CFF14C41EDBF1AB98506286D
*/
CommonProperty commonProperty = CommonProperty.Instance;
ClientWebSocket webSocket = new ClientWebSocket();
string WebSocketUrl = "wss://ws.bitget.com/v2/ws/public";
string message = "";
var tempStrData = "";
try
{
await webSocket.ConnectAsync(new Uri(WebSocketUrl), CancellationToken.None);
// 연결이 성공적으로 이루어지면 처리할 코드 작성
Console.WriteLine("WebSocket 연결 성공");
// Json String 만들기
// 종목별 Ticker 구독하기
var dic = new Dictionary<string, object>();
dic.Add("op", "subscribe");
var argsList = new List<Dictionary<string, object>>();
foreach (var marketInfo in commonProperty.marketInfoList)
{
var args = new Dictionary<string, object>();
args.Add("instType", "USDT-FUTURES");
args.Add("channel", chnnelType);
args.Add("instId", marketInfo.MarketName);
argsList.Add(args);
}
dic.Add("args", argsList)
;
//var jsonDataText = "{\"op\":\"subscribe\",\"args\":[{\"instType\":\"SPOT\",\"channel\":\"ticker\",\"instId\":\"ETHUSDT\"}, {\"instType\":\"SPOT\",\"channel\":\"ticker\",\"instId\":\"BTCUSDT\"}]}";
var jsonData = JsonConvert.SerializeObject(dic);
//await SendMessage(webSocket, jsonData);
// Ping 유지용 (25초)
var pingTimer = new Thread(() => ContinuePing(webSocket));
pingTimer.Start();
await SendMessage(webSocket, jsonData);
// 데이터 수신을 위한 반복문 시작
while (webSocket.State == WebSocketState.Open)
{
//Thread.Sleep(100);
byte[] buffer = new byte[1024];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
message = "";
// 수신한 데이터 처리
message = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
if (!ValidateJSON(message))
{
tempStrData += message;
if (!ValidateJSON(message))
{
continue;
}
else
{
message = tempStrData;
}
}
var datalist = JsonConvert.DeserializeObject<Dictionary<string, object>>(message);
if (datalist.ContainsKey("data"))
{
switch(bitgetWebSocketType)
{
case CommonProperty.BitgetWebSocketType.현황정보_전체종목:
{
var candleInfo = JsonConvert.DeserializeObject<List<WebSocketTickerInfo>>(datalist["data"]?.ToString());
var tickerList = candleInfo[0];
DateTime dt = new DateTime(1970, 01, 01);
tickerList.reciveTime = dt.AddMilliseconds(Convert.ToDouble(tickerList.ts)).ToLocalTime();
// 웹소켓 티커 리스트에 추가
var searchIndex = commonProperty.webSocketCandles.IndexOf(i => i.instId == tickerList.instId);
if (searchIndex < 0)
commonProperty.webSocketCandles.Add(tickerList);
else
{
commonProperty.webSocketCandles[searchIndex] = tickerList;
}
// 캔들 리스트에서 해당 데이터 찾기
var searchIndex_c = commonProperty.candleDataLists.IndexOf(i => i.ConGoodsData.MarketName == tickerList.instId);
// 캔들 리스트에 업데이트 (전체 시간별)
foreach (CommonProperty.MCandleTimeType interval in Enum.GetValues(typeof(CommonProperty.MCandleTimeType)))
{
var candleDataList = commonProperty.candleDataLists[searchIndex_c].candleDataList[interval];
// 캔들 리스트에 추가
var candleData = new CandleData();
candleData.Argument = tickerList.reciveTime;
candleData.Close = tickerList.lastPr;
// 캔들 리스트가 없으면 추가
if (candleDataList.Count == 0)
candleDataList.Add(candleData);
else
{
// 마지막 시간 데이터 가져오기
var lastCandleData = candleDataList.Find(j=>j.Argument == candleDataList.Max(i => i.Argument));
// 업데이트 or 추가
bool updateFlag = true;
switch (interval)
{
case CommonProperty.MCandleTimeType.M1:
// 같은 분이면 무조건 업데이트
if (lastCandleData.Argument.Minute != candleData.Argument.Minute)
updateFlag = false;
break;
case CommonProperty.MCandleTimeType.M3:
// 같은 분이면 무조건 업데이트
if (lastCandleData.Argument.Minute != candleData.Argument.Minute &&
candleData.Argument.Minute % 3 == 1) updateFlag = false;
break;
case CommonProperty.MCandleTimeType.M5:
if (lastCandleData.Argument.Minute != candleData.Argument.Minute &&
candleData.Argument.Minute % 5 == 1) updateFlag = false;
break;
case CommonProperty.MCandleTimeType.M15:
if (lastCandleData.Argument.Minute != candleData.Argument.Minute &&
candleData.Argument.Minute % 15 == 1) updateFlag = false;
break;
case CommonProperty.MCandleTimeType.M30:
if (lastCandleData.Argument.Minute != candleData.Argument.Minute &&
candleData.Argument.Minute % 30 == 1) updateFlag = false;
break;
case CommonProperty.MCandleTimeType.H1:
if (lastCandleData.Argument.Hour != candleData.Argument.Hour)
updateFlag = false;
break;
case CommonProperty.MCandleTimeType.H4:
if (lastCandleData.Argument.Hour != candleData.Argument.Hour &&
candleData.Argument.Hour % 4 == 1) updateFlag = false;
break;
case CommonProperty.MCandleTimeType.H6:
if (lastCandleData.Argument.Hour != candleData.Argument.Hour &&
candleData.Argument.Hour % 6 == 1) updateFlag = false;
break;
case CommonProperty.MCandleTimeType.H12:
if (lastCandleData.Argument.Hour != candleData.Argument.Hour &&
candleData.Argument.Hour % 12 == 1) updateFlag = false;
break;
}
// 이미 등록된 시간 캔들이 있는 경우 변경
if (updateFlag)
{
// 시간 업데이트
lastCandleData.Argument = candleData.Argument;
// 종가 수정
lastCandleData.Close = candleData.Close;
// 저가 등록
if(lastCandleData.Low == 0 || lastCandleData.Low > candleData.Close) lastCandleData.Low = candleData.Close;
// 고가 등록
if (lastCandleData.High == 0 || lastCandleData.High < candleData.Close) lastCandleData.High = candleData.Close;
}
// 없는 경우 추가
else
{
// 새 데이터 등록
candleDataList.Add(candleData);
// 시가 등록
candleData.Open = candleData.Close;
// 기존 데이터 삭제
if (candleDataList.Count > 250)
{
// 가장 오래된 데이터 삭제
candleDataList.Remove(j => j.Argument == candleDataList.Min(i => i.Argument));
}
}
}
}
}
break;
case CommonProperty.BitgetWebSocketType.캔들정보:
#region 캔들 웹소켓 : 종목 50, 캔들 1 개만 해도 뒤지려고 함 !! (일딴 패스)
{
var argList = JsonConvert.DeserializeObject<Dictionary<string,object>>(datalist["arg"]?.ToString());
var candleInfos = JsonConvert.DeserializeObject<List<List<object>>>(datalist["data"]?.ToString());
var candleInfo = candleInfos[0];
DateTime dt = new DateTime(1970, 01, 01);
var candleData = new CandleData(
dt.AddMilliseconds(Convert.ToDouble(candleInfo[0])).ToLocalTime(),
Convert.ToDouble(candleInfo[1]),
Convert.ToDouble(candleInfo[2]),
Convert.ToDouble(candleInfo[3]),
Convert.ToDouble(candleInfo[4]),
Convert.ToDouble(candleInfo[5]),
Convert.ToDouble(candleInfo[6]),
Convert.ToDouble(candleInfo[7])
);
// Websocket 캔들 정보 구분 하기 (1분, 1시간 등)
CommonProperty.MCandleTimeType mCandleTimeType = CommonProperty.MCandleTimeType.H1;
foreach (var interval in Enum.GetValues(typeof(CommonProperty.MCandleTimeType)))
{
// Description
var description = Utils.Convertor.GetEnumDescription((Enum)interval);
if (argList["channel"].ToString().Contains(description))
mCandleTimeType = (CommonProperty.MCandleTimeType) interval;
break;
}
// 캔들 리스트에서 해당 데이터 찾기
var searchIndex = commonProperty.candleDataLists.IndexOf(i => i.ConGoodsData.MarketName == argList["instId"].ToString());
var candleDataList = commonProperty.candleDataLists[searchIndex].candleDataList[mCandleTimeType];
// 마지막 데이터 (최신데이터) 가져오기
CandleData lastCandleData = null;
if (candleDataList.Count == 0)
candleDataList.Add(candleData);
else
{
lastCandleData = commonProperty.candleDataLists[searchIndex].candleDataList[mCandleTimeType].Last();
// 최신데이터와 비교하여 교체/등록하기
// 업데이트 or 추가
bool updateFlag = false;
switch (mCandleTimeType)
{
case CommonProperty.MCandleTimeType.M1:
if (lastCandleData.Argument.Minute == candleData.Argument.Minute)
updateFlag = true;
break;
case CommonProperty.MCandleTimeType.M3:
if (candleData.Argument.Minute % 3 != 1)
updateFlag = true;
break;
case CommonProperty.MCandleTimeType.M5:
if (candleData.Argument.Minute % 5 != 1)
updateFlag = true;
break;
case CommonProperty.MCandleTimeType.M15:
if (candleData.Argument.Minute % 15 != 1)
updateFlag = true;
break;
case CommonProperty.MCandleTimeType.H1:
if (candleData.Argument.Hour == candleData.Argument.Hour)
updateFlag = true;
break;
case CommonProperty.MCandleTimeType.H4:
if (candleData.Argument.Hour % 4 != 1)
updateFlag = true;
break;
case CommonProperty.MCandleTimeType.H6:
if (candleData.Argument.Hour % 6 != 1)
updateFlag = true;
break;
case CommonProperty.MCandleTimeType.H12:
if (candleData.Argument.Hour % 12 != 1)
updateFlag = true;
break;
}
// 이미 등록된 시간 캔들이 있는 경우 변경
if (updateFlag)
{
lastCandleData = candleData;
}
// 없는 경우 추가
else
{
// 새 데이터 등록
candleDataList.Add(candleData);
// 기존 데이터 삭제
if(candleDataList.Count > 250)
candleDataList.RemoveAt(0);
}
}
}
#endregion
break;
}
//var sendTime = dt.AddMilliseconds(Convert.ToDouble(datalist["ts"])).ToLocalTime();
// Candle 리스트에 데이터 쌓기
//var marketCandleList = commonProperty.candleDataLists.Find(i => i.ConGoodsData.MarketName == candleInfo[0]["instId"].ToString());
// 분 업데이트 (1,3,5,10,15,30)
//if(recieveTime.Second == 0)
{
}
// 시 업데이트 (1,4,6)
//if(recieveTime.Minute == 0)
{
}
//else
{
}
}
// 요청시간
// Candle 시간
}
}
catch (Exception ex)
{
Console.WriteLine("WebSocket 연결 중 오류 발생: " + ex.Message);
}
finally
{
if (webSocket.State == WebSocketState.Open)
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "WebSocket 연결 종료", CancellationToken.None);
webSocket.Dispose();
}
}
// Ping 테스트 (25초), 30초에 한번 응답 확인 필요
private async void ContinuePing(ClientWebSocket webSocket)
{
while(true)
{
Thread.Sleep(25000);
await SendMessage(webSocket, "ping");
}
}
private static async Task SendMessage(ClientWebSocket webSocket, string message)
{
byte[] buffer = Encoding.UTF8.GetBytes(message);
await webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
Console.WriteLine("메시지 전송: " + message);
}
public static bool ValidateJSON(string s)
{
try
{
JToken.Parse(s);
return true;
}
catch (JsonReaderException)
{
return false;
}
}
}
}