본문 바로가기
Projects/History

코인 자동매매 프로그램, 프레임 워크 작업

by Client. DJ 2022. 2. 1.
반응형

 

업비트의 Open API를 사용하기로 결정

앞으로 여유될 때 업비트 OpenAPI를 사용하여 프로그램을 만들어 볼 예정입니다. 아직 디자인이나 어떻게 구상할지까지는 작업 중 차차 반영할 예정입니다.

 

기본적으로 ACCESS_KEY, SECRET_KEY를 발급받고 아래와 같이 진행했습니다.

 

먼저 진행하게된 작업으로는

  1. Rest API를 사용하여 통신 구조 설계
  2. Json으로 받아와서 각 형식에 맞게 변환
  3. 간단하게 로그 찍는 클레스 작성

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 변환 받아오기 (아무렇게나 적은 가짜 정보😉)

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랑과는 다르게 안내도 잘되어 있고, 깔끔하게 오가니 편하고 좋네요. 특히 통신 요청 카운트를 직접 찍어서 알 수 있다니 너무 다릅니다. 키움은 이런 부분은 또 비공개해서 통신 제한 걸리면 답도 없었거든요🤣

반응형

댓글