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; using DateTime = System.DateTime; using DevExpress.Xpo.Logger; using DevExpress.XtraTreeList.Filtering.Provider; using System.Runtime.InteropServices; using DevExpress.XtraEditors.Filtering; using DevExpress.XtraExport.Xls; using DevExpress.Utils.Filtering.Internal; namespace BitgetAPI.WebService { public class WebConnection { public Thread th_PingTimer { get; set; } = null; // 웹소켓 스레드 시작/종료 플래그 public bool Is_th_webSocketThread { get; set; } = false; public ClientWebSocket webSocket { get; set; } = null; private object obj = new object(); public List> webSocketDataList = new List>(); CommonProperty commonProperty = CommonProperty.Instance; 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>(resResult); var marketInfo = JsonConvert.DeserializeObject>>(datalist["data"]?.ToString()); var data = marketInfo.FindAll(x => x["coin"].ToString().Contains("USDT")); } break; case CommonProperty.ExchangeWebReqType.BitgetMarket_Future: { var datalist = JsonConvert.DeserializeObject>(resResult); var marketInfos = JsonConvert.DeserializeObject>>(datalist["data"]?.ToString()); commonProperty.marketInfoList = new List(); foreach (var item in marketInfos) { MarketInfo marketInfo = new MarketInfo(); marketInfo.MarketName = item["symbol"].ToString(); marketInfo.ExchangePlace = Enum.GetName(typeof(CommonProperty.ExchangePlace), commonProperty.selectedChangePlace); marketInfo.lastPr = Convert.ToDouble(item["lastPr"]); marketInfo.change24h = Convert.ToDouble(item["change24h"]); marketInfo.baseVolume = Convert.ToDouble(item["baseVolume"]); marketInfo.quoteVolume = Convert.ToDouble(item["quoteVolume"]); marketInfo.high24h = Convert.ToDouble(item["high24h"]); marketInfo.low24h = Convert.ToDouble(item["low24h"]); marketInfo.open24h = Convert.ToDouble(item["open24h"]); commonProperty.marketInfoList.Add(marketInfo); } return commonProperty.marketInfoList; } break; case CommonProperty.ExchangeWebReqType.BitgetCandleStick_Coin: { } break; case CommonProperty.ExchangeWebReqType.BitgetCandleStick_Future: { var datalist = JsonConvert.DeserializeObject>(resResult); var datas = JsonConvert.DeserializeObject>>(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개밖에 없음 // 마지막 시간 데이터는 불완전 > 웹소켓 데이터와 합체 if (item == datas.Last()) { var timespan_add = new System.TimeSpan(0, 0, 0); switch (interval) { case CommonProperty.MCandleTimeType.M1: // 같은 분이면 무조건 업데이트 timespan_add = new System.TimeSpan(0, 1, 0); break; case CommonProperty.MCandleTimeType.M3: // 같은 분이면 무조건 업데이트 timespan_add = new System.TimeSpan(0, 3, 0); break; case CommonProperty.MCandleTimeType.M5: timespan_add = new System.TimeSpan(0, 5, 0); break; case CommonProperty.MCandleTimeType.M15: timespan_add = new System.TimeSpan(0, 15, 0); break; case CommonProperty.MCandleTimeType.M30: timespan_add = new System.TimeSpan(0, 30, 0); break; case CommonProperty.MCandleTimeType.H1: timespan_add = new System.TimeSpan(1, 0, 0); break; case CommonProperty.MCandleTimeType.H4: timespan_add = new System.TimeSpan(4, 0, 0); break; case CommonProperty.MCandleTimeType.H6: timespan_add = new System.TimeSpan(6, 0, 0); break; case CommonProperty.MCandleTimeType.H12: timespan_add = new System.TimeSpan(12, 0, 0); break; } var sameCandleData_WebService = candleDataList.Find(i => candleData.Argument <= i.Argument && i.Argument < (candleData.Argument + timespan_add) && i.WebSocketUpdate == true); if (sameCandleData_WebService != null) { // 종가 => 웹소켓 데이터 사용 (웹 서비스 호출 데이터 중 마지막 데이터는 캔들이 완성되지 않음) // 시가 => 웹 서비스 데이터 사용 sameCandleData_WebService.Open = candleData.Open; // 저가, 고가 => 비교해서 사용 sameCandleData_WebService.Low = Math.Min(sameCandleData_WebService.Low, candleData.Low); sameCandleData_WebService.High = Math.Max(sameCandleData_WebService.High, candleData.High); sameCandleData_WebService.WebServiceUpdate = true; } else { candleDataList.Add(candleData); candleData.WebServiceUpdate = true; } } /* //var sameCandleData_WebService = candleDataList.Find(i => i.Argument.Minute == candleData.Argument.Minute && //i.Argument.Hour == candleData.Argument.Hour && i.WebSocketUpdate == true); // 기존 웹소켓으로 부터 받은 데이터에 수정 등록 if (sameCandleData_WebService != null) { }*/ // 웹 서비스로 받은 데이터를 새로 추가 else { candleDataList.Add(candleData); candleData.WebServiceUpdate = true; } } } 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, Dictionary dic) { 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 */ 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 연결 성공"); //var jsonDataText = "{\"op\":\"subscribe\",\"args\":[{\"instType\":\"SPOT\",\"channel\":\"ticker\",\"instId\":\"ETHUSDT\"}, {\"instType\":\"SPOT\",\"channel\":\"ticker\",\"instId\":\"BTCUSDT\"}]}"; var jsonData = JsonConvert.SerializeObject(dic); // Ping 유지용 (25초) th_PingTimer = new Thread(() => ContinuePing(webSocket)); th_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(buffer), CancellationToken.None); message = ""; // 수신한 데이터 처리 message = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count); if (message.Contains("pong")) { string[] splitStr = { "pong" }; var temp = message.Split(splitStr, StringSplitOptions.RemoveEmptyEntries); message = ""; foreach (var item in temp) { message += item; } } if (tempStrData == "") { if (!ValidateJSON(message)) { tempStrData = message; continue; } } else { tempStrData += message; if (!ValidateJSON(tempStrData)) { continue; } else { message = tempStrData; tempStrData = ""; } } if (message.Contains("{\"action")) { string[] separatingStrings = { "{\"action" }; var splitData = message.Split(separatingStrings, System.StringSplitOptions.RemoveEmptyEntries); foreach (var item in splitData) { var temp = "{\"action" + item; try { var datalist = JsonConvert.DeserializeObject>(temp); if (datalist["action"].ToString() == "update") { lock (obj) { webSocketDataList.Add(datalist); } } } catch (JsonReaderException) { } } } #region 접어 둘거 //if (datalist.ContainsKey("data")) //{ // switch (bitgetWebSocketType) // { // case CommonProperty.BitgetWebSocketType.현황정보_전체종목: // { // var candleInfo = JsonConvert.DeserializeObject>(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_Edit = candleDataList.ToList(); // // 마지막 시간 데이터 가져오기 // var lastCandleData = lastCandleData_Edit.Find(j => j.Argument == lastCandleData_Edit.Max(i => i.Argument)); // if (lastCandleData == null) return; // // 업데이트 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; // lastCandleData.WebSocketUpdate = true; // } // // 없는 경우 추가 // else // { // if (candleData.Open == 0) { } // // 1분 이상 Ticker가 없는 겨우는 Open 가만 가격이 등록됨 // candleData.Open = candleData.Close; // candleData.WebSocketUpdate = true; // // 새 데이터 등록 // candleDataList.Add(candleData); // // 기존 데이터 삭제 // 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>(datalist["arg"]?.ToString()); // var candleInfos = JsonConvert.DeserializeObject>>(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 != 0 || (candleData.Argument.Minute % 3 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M5: // if (candleData.Argument.Minute % 5 != 0 || (candleData.Argument.Minute % 5 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M15: // if (candleData.Argument.Minute % 15 != 0 || (candleData.Argument.Minute % 15 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M30: // if (candleData.Argument.Minute % 30 != 0 || (candleData.Argument.Minute % 30 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // 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 != 0 || (candleData.Argument.Hour % 4 == 0 && candleData.Argument.Hour == lastCandleData.Argument.Hour)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.H6: // if (candleData.Argument.Hour % 6 != 0 || (candleData.Argument.Hour % 6 == 0 && candleData.Argument.Hour == lastCandleData.Argument.Hour)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.H12: // if (candleData.Argument.Hour % 12 != 0 || (candleData.Argument.Hour % 12 == 0 && candleData.Argument.Hour == lastCandleData.Argument.Hour)) // updateFlag = true; // break; // } // // 이미 등록된 시간 캔들이 있는 경우 변경 // if (updateFlag) // { // lastCandleData = candleData; // } // // 없는 경우 추가 // else // { // lastCandleData.Close = candleData.Open; // // 새 데이터 등록 // candleDataList.Add(candleData); // // 기존 데이터 삭제 // if (candleDataList.Count > 250) // { // var minData = candleDataList.Min(t => t.Argument); // var minCandle = candleDataList.FirstOrDefault(t => t.Argument == minData); // if (minCandle != null) // { // candleDataList.Remove(minCandle); // } // } // } // } // } // #endregion // break; // } //} #endregion } } catch (Exception ex) { Console.WriteLine("WebSocket 연결 중 오류 발생: " + ex.Message); } finally { closeWebSocket(); // 웹소켓 재귀 if (Is_th_webSocketThread) { BitGetWebSocektStart(bitgetWebSocketType, dic); } } } // 웹소켓 종료 public async void closeWebSocket() { if (webSocket != null) { if (webSocket.State == WebSocketState.Open) { if (th_PingTimer != null) { th_PingTimer.Abort(); th_PingTimer = null; } await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "WebSocket 연결 종료", CancellationToken.None); } webSocket.Dispose(); } } // Ping 테스트 (25초), 30초에 한번 응답 확인 필요 public static async void ContinuePing(ClientWebSocket webSocket) { while (true) { Thread.Sleep(25000); await SendMessage(webSocket, "ping"); } } private static async Task SendMessage(ClientWebSocket webSocket, string message) { try { byte[] buffer = Encoding.UTF8.GetBytes(message); await webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); Console.WriteLine("메시지 전송: " + message); } catch (Exception ex) { Console.WriteLine(ex.Message); } } public static bool ValidateJSON(string s) { try { if (s.Contains("{\"action")) { string[] separatingStrings = { "{\"action" }; var splitData = s.Split(separatingStrings, System.StringSplitOptions.RemoveEmptyEntries); bool flag = true; foreach (var item in splitData) { var temp = "{\"action" + item; try { JToken.Parse(temp); } catch (JsonReaderException) { flag = false; } } return flag; } else { JToken.Parse(s); } return true; } catch (JsonReaderException) { return false; } } public void CalculateDataList(BitgetWebSocketType bitgetWebSocketType, List intervalList) { try { var cloneList = new List>(); lock (obj) { try { foreach (var item in webSocketDataList) { var dic = new Dictionary(); foreach (var key in item.Keys) { dic[key] = item[key]; } cloneList.Add(dic); } webSocketDataList.Clear(); } catch (Exception e) { } } switch (bitgetWebSocketType) { case CommonProperty.BitgetWebSocketType.현황정보_전체종목: { var dataList = cloneList.Where(t => t.ContainsKey("data")).Select(t => t["data"]); var list = JsonConvert.DeserializeObject>>(JsonConvert.SerializeObject(dataList)); var calList = new List(); foreach (var item in list) { calList.Add(item[0]); } var stringList = list.Select(t => t[0].instId).Distinct().ToList(); list = list.OrderBy(t => t[0].ts).ToList(); var minList = new Dictionary>(); foreach (var item in stringList) { var temp = calList.Where(t => item == t.instId); if (temp.Count() > 0) { minList.Add(item, new List()); foreach (var t in temp) { var tickerList = t; DateTime dt = new DateTime(1970, 01, 01); tickerList.reciveTime = dt.AddMilliseconds(Convert.ToDouble(tickerList.ts)).ToLocalTime(); } var timeList = temp.Select(t => t.reciveTime.Minute).OrderBy(t => t).ToList(); foreach (var min in timeList) { var minDataList = temp.Where(t => t.reciveTime.Minute == min).OrderBy(t => t.reciveTime).ToList(); var candleData = new CandleData(); var dt = minDataList[minDataList.Count - 1].reciveTime; candleData.Argument = dt; candleData.Close = minDataList.Last().lastPr; candleData.Open = minDataList.First().lastPr; candleData.Low = minDataList.Min(t => t.lastPr); candleData.High = minDataList.Max(t => t.lastPr); minList[item].Add(candleData); } } } //minList = new Dictionary>(); //List tempList = new List(); //foreach (var datalist in list) //{ // var tickerList = datalist[0]; // DateTime dt = new DateTime(1970, 01, 01); // tickerList.reciveTime = dt.AddMilliseconds(Convert.ToDouble(tickerList.ts)).ToLocalTime(); // tempList.Add(tickerList); // if (minList.ContainsKey(tickerList.instId)) // { // if (!minList[tickerList.instId].Any(m => m.Argument.Minute == tickerList.reciveTime.Minute)) // { // var candleData = new CandleData(); // candleData.Argument = tickerList.reciveTime; // candleData.Close = tickerList.lastPr; // candleData.Open = tickerList.lastPr; // candleData.Low = tickerList.lastPr; // candleData.High = tickerList.lastPr; // minList[tickerList.instId].Add(candleData); // } // else // { // var tempItem = minList[tickerList.instId].First(t => t.Argument.Minute == tickerList.reciveTime.Minute); // tempItem.Argument = tickerList.reciveTime; // tempItem.Close = tickerList.lastPr; // tempItem.Low = tempItem.Low > tickerList.lastPr ? tickerList.lastPr : tempItem.Low; // tempItem.High = tempItem.High < tickerList.lastPr ? tickerList.lastPr : tempItem.High; // } // } // else // { // minList.Add(tickerList.instId, new List()); // var candleData = new CandleData(); // candleData.Argument = tickerList.reciveTime; // candleData.Close = tickerList.lastPr; // candleData.Open = tickerList.lastPr; // candleData.Low = tickerList.lastPr; // candleData.High = tickerList.lastPr; // minList[tickerList.instId].Add(candleData); // } //} foreach (var item in minList.Keys) { var temp = minList[item]; foreach (var candleData in temp) { // 캔들 리스트에서 해당 데이터 찾기 var searchIndex_c = commonProperty.candleDataLists.IndexOf(i => i.ConGoodsData.MarketName == item); // 캔들 리스트에 업데이트 (전체 시간별) foreach (CommonProperty.MCandleTimeType interval in Enum.GetValues(typeof(CommonProperty.MCandleTimeType))) { var strInterval = Utils.Convertor.GetEnumDescription((Enum)interval); if (!intervalList.Contains(strInterval)) continue; var candleDataList = commonProperty.candleDataLists[searchIndex_c].candleDataList[interval]; // 캔들 리스트가 없으면 추가 if (candleDataList.Count == 0) { candleDataList.Add(candleData); } else { var lastCandleData_Edit = candleDataList; // 마지막 시간 데이터 가져오기 var lastCandleData = lastCandleData_Edit.Find(j => j.Argument == lastCandleData_Edit.Max(i => i.Argument)); if (lastCandleData == null || lastCandleData.Argument > candleData.Argument) { if (lastCandleData != null) { var beforeList = candleDataList.OrderByDescending(t => t.Argument).ToList(); var before = beforeList.FirstOrDefault(t => t.Argument < candleData.Argument); if (before != null) { lastCandleData = lastCandleData_Edit.Find(t => t.Argument == before.Argument); } else continue; } else continue; } if (lastCandleData.Argument.Second != 0) { var beforeList = lastCandleData_Edit.OrderByDescending(t => t.Argument).ToList(); var beforeArg = beforeList[1].Argument; lastCandleData.Argument = beforeList[1].Argument.AddMinutes((int)interval); } // 업데이트 or 추가 bool updateFlag = false; if (candleData.Argument > lastCandleData.Argument && candleData.Argument < lastCandleData.Argument.AddMinutes((int)interval)) updateFlag = true; //switch (interval) //{ // case CommonProperty.MCandleTimeType.M1: // if (lastCandleData.Argument.Minute == candleData.Argument.Minute) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M3: // if (candleData.Argument > lastCandleData.Argument && candleData.Argument < lastCandleData.Argument.AddMinutes((int)interval)) // updateFlag = true; // //if (candleData.Argument.Minute % 3 != 0 || (candleData.Argument.Minute % 3 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M5: // //if (candleData.Argument.Minute % 5 != 0 || (candleData.Argument.Minute % 5 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M15: // if (candleData.Argument.Minute % 15 != 0 || (candleData.Argument.Minute % 15 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M30: // if (candleData.Argument.Minute % 30 != 0 || (candleData.Argument.Minute % 30 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // 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 != 0 || (candleData.Argument.Hour % 4 == 0 && candleData.Argument.Hour == lastCandleData.Argument.Hour)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.H6: // if (candleData.Argument.Hour % 6 != 0 || (candleData.Argument.Hour % 6 == 0 && candleData.Argument.Hour == lastCandleData.Argument.Hour)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.H12: // if (candleData.Argument.Hour % 12 != 0 || (candleData.Argument.Hour % 12 == 0 && candleData.Argument.Hour == lastCandleData.Argument.Hour)) // updateFlag = true; // break; //} // 이미 등록된 시간 캔들이 있는 경우 변경 if (updateFlag) { // 시간 업데이트 //lastCandleData.Argument = candleData.Argument; // 종가 수정 lastCandleData.Close = candleData.Close; lastCandleData.Low = lastCandleData.Low > candleData.Low ? candleData.Low : lastCandleData.Low; lastCandleData.High = lastCandleData.High < candleData.High ? candleData.High : lastCandleData.High; lastCandleData.WebSocketUpdate = true; } // 없는 경우 추가 else { candleData.WebSocketUpdate = true; var dt = candleData.Argument; dt = dt.AddSeconds(dt.Second * -1); dt = dt.AddMilliseconds(dt.Millisecond * -1); candleData.Open = lastCandleData.Close; // 새 데이터 등록 candleDataList.Add(candleData); // 기존 데이터 삭제 if (candleDataList.Count > 250) { // 가장 오래된 데이터 삭제 candleDataList.Remove(j => j.Argument == candleDataList.Min(i => i.Argument)); } } } } } } } break; case CommonProperty.BitgetWebSocketType.캔들정보: #region 캔들 웹소켓 : 종목 50, 캔들 1 개만 해도 뒤지려고 함 !! (일딴 패스) { var list = new List(); cloneList = cloneList.Where(t => t["action"].ToString() == "update").ToList(); foreach (var item in cloneList) { var resCandleStick = new ResponseCandleStick(); resCandleStick.arg = new ResponseCandleStickArg(); double.TryParse(item["ts"].ToString(), out var ts); var arg = (JObject)item["arg"]; resCandleStick.arg.instType = arg.GetValue("instType").ToString(); resCandleStick.arg.channel = arg.GetValue("channel").ToString(); resCandleStick.arg.instId = arg.GetValue("instId").ToString(); resCandleStick.action = item["action"]?.ToString(); resCandleStick.ts = ts; var dataList = JsonConvert.DeserializeObject>>(JsonConvert.SerializeObject(item["data"])); resCandleStick.data = dataList[0]; list.Add(resCandleStick); } var minList = new Dictionary>(); var stringList = list.Select(t => t.arg.instId).ToList(); list = list.OrderBy(t => t.ts).ToList(); foreach (var item in stringList) { var temp = list.Where(t => item == t.arg.instId); if (temp.Count() > 0) { minList.Add(item, new List()); foreach (var t in temp) { DateTime dt = new DateTime(1970, 01, 01); t.reciveTime = dt.AddMilliseconds(Convert.ToDouble(t.data[0])).ToLocalTime(); } var timeList = temp.Select(t => t.reciveTime.Minute).OrderBy(t => t).ToList(); foreach (var min in timeList) { var minDataList = temp.Where(t => t.reciveTime.Minute == min).OrderBy(t => t.reciveTime).ToList(); var lastData = minDataList[minDataList.Count - 1]; var candleData = new CandleData(); double.TryParse(lastData.data[1].ToString(), out var openData); double.TryParse(lastData.data[2].ToString(), out var highData); double.TryParse(lastData.data[3].ToString(), out var lowData); double.TryParse(lastData.data[4].ToString(), out var closeData); candleData.Argument = lastData.reciveTime; candleData.Close = closeData; candleData.Open = openData; candleData.Low = lowData; candleData.High = highData; minList[item].Add(candleData); } } } foreach (var item in minList.Keys) { var temp = minList[item]; foreach (var candleData in temp) { foreach (CommonProperty.MCandleTimeType mCandleTimeType in Enum.GetValues(typeof(CommonProperty.MCandleTimeType))) { var strInterval = Utils.Convertor.GetEnumDescription((Enum)mCandleTimeType); if (!intervalList.Contains(strInterval)) continue; // 캔들 리스트에서 해당 데이터 찾기 var searchIndex = commonProperty.candleDataLists.IndexOf(i => i.ConGoodsData.MarketName == item); var candleDataList = commonProperty.candleDataLists[searchIndex].candleDataList[mCandleTimeType]; // 마지막 데이터 (최신데이터) 가져오기 CandleData lastCandleData = null; if (candleDataList.Count == 0) candleDataList.Add(candleData); else { var max = candleDataList.Max(t => t.Argument); lastCandleData = candleDataList.Find(t => t.Argument == max); // 최신데이터와 비교하여 교체/등록하기 if (lastCandleData.Argument.Second != 0) { var beforeList = candleDataList.OrderByDescending(t => t.Argument).ToList(); var beforeArg = beforeList[1].Argument; lastCandleData.Argument = beforeList[1].Argument.AddMinutes((int)mCandleTimeType); } // 업데이트 or 추가 bool updateFlag = false; if (candleData.Argument >= lastCandleData.Argument && candleData.Argument < lastCandleData.Argument.AddMinutes((int)mCandleTimeType)) updateFlag = true; #region 접어 두기 //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 != 0 || (candleData.Argument.Minute % 3 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M5: // if (candleData.Argument.Minute % 5 != 0 || (candleData.Argument.Minute % 5 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M15: // if (candleData.Argument.Minute % 15 != 0 || (candleData.Argument.Minute % 15 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.M30: // if (candleData.Argument.Minute % 30 != 0 || (candleData.Argument.Minute % 30 == 0 && candleData.Argument.Minute == lastCandleData.Argument.Minute)) // 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 != 0 || (candleData.Argument.Hour % 4 == 0 && candleData.Argument.Hour == lastCandleData.Argument.Hour)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.H6: // if (candleData.Argument.Hour % 6 != 0 || (candleData.Argument.Hour % 6 == 0 && candleData.Argument.Hour == lastCandleData.Argument.Hour)) // updateFlag = true; // break; // case CommonProperty.MCandleTimeType.H12: // if (candleData.Argument.Hour % 12 != 0 || (candleData.Argument.Hour % 12 == 0 && candleData.Argument.Hour == lastCandleData.Argument.Hour)) // updateFlag = true; // break; //} #endregion // 이미 등록된 시간 캔들이 있는 경우 변경 if (updateFlag) { // 시간 업데이트 //lastCandleData.Argument = candleData.Argument; // 종가 수정 lastCandleData.Close = candleData.Close; lastCandleData.Low = lastCandleData.Low > candleData.Low ? candleData.Low : lastCandleData.Low; lastCandleData.High = lastCandleData.High < candleData.High ? candleData.High : lastCandleData.High; lastCandleData.WebSocketUpdate = true; } // 없는 경우 추가 else { candleData.WebSocketUpdate = true; lastCandleData.Close = candleData.Open; if(lastCandleData.High < lastCandleData.Close) { lastCandleData.High = lastCandleData.Close; } if (lastCandleData.Low > lastCandleData.Close) { lastCandleData.Low = lastCandleData.Close; } var dt = candleData.Argument; dt = dt.AddSeconds(dt.Second * -1); dt = dt.AddMilliseconds(dt.Millisecond * -1); // 새 데이터 등록 candleDataList.Add(candleData); // 기존 데이터 삭제 if (candleDataList.Count > 250) { // 가장 오래된 데이터 삭제 candleDataList.Remove(j => j.Argument == candleDataList.Min(i => i.Argument)); } } } } } } } #endregion break; } } catch (Exception e) { } } } }