앞으로 여유될 때 업비트 OpenAPI를 사용하여 프로그램을 만들어 볼 예정입니다. 아직 디자인이나 어떻게 구상할지까지는 작업 중 차차 반영할 예정입니다.
기본적으로 ACCESS_KEY, SECRET_KEY를 발급받고 아래와 같이 진행했습니다.
먼저 진행하게된 작업으로는
- Rest API를 사용하여 통신 구조 설계
- Json으로 받아와서 각 형식에 맞게 변환
- 간단하게 로그 찍는 클레스 작성
Rest API 및 통신 구조 설계
Rest API는 들어보기만 했지 사용해본 적이 없었는데, HTTP통신을 좀 더 수월하게 해주는 클레스더군요. 사실 어디까지 어떻게 되나 깊게 보지는 않았습니다. 오롯이 Reqeust -> Response로 원하는 정보가 정확히 오가는지 테스트가 필요했습니다.
데이터가 정상적으로 오가는 것을 확인하고, 이후에는 제 입맛에 맞게 구조 설계 작업을 진행했습니다. 정확한 정보가 오가고 하니 다음에는 쓰레드를 사용하여 어떤 방식으로 사용할지 고민을 해봐야겠습니다. (뮤텍스 관련 알아볼 예정)
ProtocolHandler.cs
using Newtonsoft.Json.Linq;
using RestSharp;
using System;
using System.Reflection;
namespace Network
{
public abstract class ProtocolHandler
{
public delegate void RestRequestDelegate(RestSharp.RestRequest request, Action<RestResponse> onResponse);
public RestRequestDelegate restRequest = null;
protected Uri URI { get; set; }
protected Method Method { get; set; }
/// <summary>
/// 통신 요청 정보 캐싱
/// </summary>
private Action<bool> onFinished = null;
protected abstract void Response(RestRequest req, RestResponse res);
/// <summary>
/// Rest 요청 프로세스
/// </summary>
/// <param name="onFinished"></param>
protected void RequestProcess(RestRequest req, Action<bool> onFinished)
{
this.onFinished = onFinished;
restRequest?.Invoke(req, (res) =>
{
if (res.IsSuccessful)
{
// 표준 수신 처리
Response(req, res);
// 수신 처리 완료 콜백
onFinished?.Invoke(true);
onFinished = null;
}
else
{
onFinished?.Invoke(false);
onFinished = null;
}
});
}
/// <summary>
/// Json 포맷을 Response 클래스로 변환
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="type"></param>
/// <param name="jsonContent"></param>
/// <returns></returns>
protected T JsonParser<T>(string jsonContent) where T : iRsponse, new()
{
T convertRes = new T();
try
{
var array = JArray.Parse(jsonContent);
FieldInfo[] fieldInfos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
foreach (var item in array)
{
var jObject = JObject.Parse(item.ToString());
for (int i = 0; i < fieldInfos.Length; i++)
{
FieldInfo fieldInfo = fieldInfos[i];
string name = fieldInfo.Name;
if (jObject.TryGetValue(name, out var jToken))
{
switch (fieldInfo.FieldType.Name)
{
case "Byte":
fieldInfo.SetValue(convertRes, jToken.ToObject<Byte>());
break;
case "SByte":
fieldInfo.SetValue(convertRes, jToken.ToObject<SByte>());
break;
case "Int16":
fieldInfo.SetValue(convertRes, jToken.ToObject<Int16>());
break;
case "Int32":
fieldInfo.SetValue(convertRes, jToken.ToObject<Int32>());
break;
case "Int64":
fieldInfo.SetValue(convertRes, jToken.ToObject<Int64>());
break;
case "Decimal":
fieldInfo.SetValue(convertRes, jToken.ToObject<Decimal>());
break;
case "UInt16":
fieldInfo.SetValue(convertRes, jToken.ToObject<UInt16>());
break;
case "UInt32":
fieldInfo.SetValue(convertRes, jToken.ToObject<UInt32>());
break;
case "UInt64":
fieldInfo.SetValue(convertRes, jToken.ToObject<UInt64>());
break;
case "Single":
fieldInfo.SetValue(convertRes, jToken.ToObject<Single>());
break;
case "Double":
fieldInfo.SetValue(convertRes, jToken.ToObject<Double>());
break;
case "Boolean":
fieldInfo.SetValue(convertRes, jToken.ToObject<Boolean>());
break;
case "DateTime":
fieldInfo.SetValue(convertRes, jToken.ToObject<DateTime>());
break;
case "String":
fieldInfo.SetValue(convertRes, jToken.ToString());
break;
default:
break;
}
}
}
}
}
catch (Exception e)
{
Logger.Error(e.Message);
}
return convertRes;
}
}
}
ProtocolHandler를 상속 받은 클레스를 각각 사용할 예정입니다.
Json 변환
json 형식으로 통신 하기에 사용하는 클레스가 적절히 알아서 변하는 구조를 설계했습니다. 아직 조금 찝찝한 부분들이 있는데 제 공부가 부족한지는 차차 알아봐야할 부분들이 있었습니다. (ex. 각 자료형마다 알아서 바꿔주는 기능이 조금 미흡하다.)
로그 찍는 클래스
깊게 생각하지 않고 간단하게 텍스트 남기는 용도로 만들었으며, 이벤트 처리로 Console뿐만이 아닌 UI에서도 보이도록 할 예정입니다.
Logger.cs
using System;
using System.Collections.Generic;
using System.Text;
public static class Logger
{
public delegate void OnAddLog(string text);
private static event OnAddLog onLog;
public static event OnAddLog OnLog
{
add
{
onLog -= value;
onLog += value;
}
remove
{
onLog -= value;
}
}
private const int LOG_COUNT_MAX = 20;
public static Queue<object> Logs { get; private set; } = new Queue<object>();
private static StringBuilder sb = new StringBuilder();
public static void Log(object obj)
{
sb.Length = 0;
Add(sb.Append("[Log] ").Append(obj).ToString());
}
public static void Warning(object obj)
{
sb.Length = 0;
Add(sb.Append("[LogWarning] ").Append(obj).ToString());
}
public static void Error(object obj)
{
sb.Length = 0;
Add(sb.Append("[LogError] ").Append(obj).ToString());
}
public static string GetLogs()
{
sb.Length = 0;
var enumerator = Logs.GetEnumerator();
while (enumerator.MoveNext())
{
sb.Append(enumerator.Current).Append('\n');
}
return sb.ToString();
}
private static void Add(string text)
{
// 추가
Logs.Enqueue(text);
// 정리
if (Logs.Count > LOG_COUNT_MAX)
Logs.Dequeue();
// Console 출력
Console.WriteLine(text);
// 로그 추가 이벤트 발생
onLog?.Invoke(text);
}
}
이제 막 시작하는데도, 역시 백지에서 시작은 꽤나 고되네요. 어려운 부분보다는 언제 다 하지~ 라는 막연함 때문에 꿈 꾸며 작업하고 있습니다.
프레임 워크 작업은 조금 더 고민해보고 어느정도 마무리되면 실제 주문을 시도해볼 예정입니다.
작년에 했던 주식 매매 프로그램 때, 키움 API랑과는 다르게 안내도 잘되어 있고, 깔끔하게 오가니 편하고 좋네요. 특히 통신 요청 카운트를 직접 찍어서 알 수 있다니 너무 다릅니다. 키움은 이런 부분은 또 비공개해서 통신 제한 걸리면 답도 없었거든요🤣
댓글