자동매매 프로그램을 만들어 봅시다. - slog

자동매매 프로그램을 시간 날때마다 만들어 보려고 합니다.

본 프로그램은 완성도 있는 최종 구현이 목적이 아니라 .NET 5및 C# 9의 기능과 문법을 최대한 이용하여 자동매매 프로그램에 필요한 시세조회, 매매계좌관리, 시그널 처리에 대한 기본적인 기능구현을 목적으로 합니다.

C# 기반 자동매매 프로그램에 관심 있는 분들에게 도움이 되었으면 합니다.

좋아요 5

키움증권 함수 인자를 넘기기위해 매번 Dictionary 개체를 생성합니다. 다른 방법이 있을까요?

            return RequestMultiData<주식일봉정보>("opt10081",
                new()
                {
                    ["종목코드"] = 종목코드,
                    ["기준일자"] = 기준일자.ToCombineDate(),
                    ["수정주가구분"] = "1"
                });

            return RequestMultiData<주식일봉정보>("opt10081",
                ("종목코드", 종목코드),
                ("기준일자", 기준일자.ToCombineDate()),
                ("수정주가구분", "1")
            );

로 리펙토링 했습니다. ValueTuple은 struct이므로 스택으로 전달 될 것입니다.

사실 키움 Provider에서 Api_OnReceiveTrData의 속성별 설정은 리플렉터를 이용해 단순화 할 수 있습니다. 약간의 성능 하락이 있을 수 있는데 무시할 수 있을 수준입니다. 더 나아가 질의 별 메타정보를 읽어서 소스코드를 자동생성할 수도 있을 것입니다만… 이런 정리는 간단한 매매프로그램을 만든뒤에 해보도록 합시다

키움증권 OpenAPI의 경우 Window Handle을 사용하므로 서버에서 운영하기가 어려울 수 있습니다.

주문의 경우 다음과 같은 흐름으로 비동기로 정보를 수신 받습니다.

Api_OnReceiveMsg: 101, Req1, KOA_NORMAL_BUY_KP_ORD, [00Z112] 모의투자 정상처리 되었습니다
Api_OnReceiveTrData: 101, Req1, KOA_NORMAL_BUY_KP_ORD, ,
Api_OnReceiveChejanData:
Api_OnReceiveChejanData:

정상적인 흐름의 경우, Api_OnReceiveTrData에서 주문번호를 수집해 주문 메소드의 반환값으로 취할 수 있으나, 증권 서버의 부하로 인해 Api_OnReceiveChejanData가 먼저 수신될 수 있다고 문서에는 기록되어 있습니다. 그러므로, 주문번호를 수신하기 전, Api_OnReceiveChejanData로 수집된 정보를 어디엔가 담아둬야만 합니다. (주문번호가 있으면 매칭, 주문번호가 없으면 정보를 보관했다가, Api_OnReceiveTrData에서 매칭시키고 소멸)

인터넷에서 쉽게 확인할 수 있는 키움 OpenAPI의 구현체 소스코드를 보면 조회 및 주문시 RQName을 정적인 값으로 사용하는 것을 쉽게 볼 수 있습니다. 하지만 이렇게 하면, 여러개의 조회나 주문을 동시에 내릴 수 없습니다. 1초당 5회의 제한때문에 그 차이가 크지는 않지만, RQName은 요청 건건이 고유한 값으로 네이밍 하는게 좀 더 호율적입니다.

        private string GenerateReqName()
        {
            reqNum++;
            return $"Req{reqNum}";
        }

주문 및 실시간 주문은 주식거래시간에 구현 테스트를 해야 해서 어려움이 있네요.

주문, 주문정정, 주문취소를 구현하였습니다.

주식_주문, 주식_주문정정, 주식_주문취소인데요, 내부적으로는 RequestOrder를 이용해 처리합니다.

조회 제한시간과 별도로 주문 제한시간이 적용됩니다. 즉, 1초에 5회 초과 주문할 수 없어서 관련 처리가 RequestOrder에 있습니다.

좋아요 1

실시간 시세를 구현하였습니다.

실시간 시세는 호가잔량 및 체결 실시간 정보입니다.
실시간 시세가 필요한 종목을 주식_실시간시세_등록()으로 등록하고, 주식_실시간시세_해제()으로 해제할 수 있으며, 주식_실시간시세_전체해제()으로 실시간 수실을 전체해제할 수 있습니다.

실시간시세를 수신받으려면, 주식_실시간시세_호출Action<주식실시간시세> 형태의 콜벡을 등록해야 합니다. 수신된 정보는 호가정보와 체결정보가 함께 있습니다.

그리고 매수호가 체결인지, 매도호가 체결인지에 따라 체결이 상승인지 하락인지를 체결방향을 통해 알 수 있습니다. 이는 나중에 틱거래의 경우 유용할 것이라 생각합니다.

계좌 및 보유종목 관리를 구현하려고 합니다. 증권사 마다 기능이 다 다르므로 AutoTrader는 기본적인 기능만을 일단 고려할 것입니다.

현재 일반적으로 증권사의 계좌는 복수개가 됩니다. 사용자정보를 통해 계좌번호 목록을 가져올 수 있습니다.
주문을 통해 거래를 하게 되면, 거래 대상에 따라 계좌번호의 현금(또는 신용)으로 거래를 하게 됩니다. 체결이 되면 종목별 보유수량이 생기는데, 이 때 호가주문의 경우 미체결 상태로 시작하고, 시장가로 주문하더라도 호가잔량에 따라 여러단계로 체결이 될 수 있습니다. 이런 상태를 AutoTrader는 관리해야 합니다.

- 계좌번호 - 계좌현재정보
           - 체결목록  (종목 별 체결목록)
           - 미체결목록 (종목 별 미체결목록)
또는
           - 종목목록 (체결 / 미체결목록)

기본 기능이 마무리 되면, 진입/추가매수/추가매도/청산 단계의 시그널전략을 구현할 예정입니다.
하나의 전략은 -진입/추가매수/추가매도/청산 시그널을 갖게 되는 것이고, 시그널의 결합, 시그널인지를 판단하기 위한 상태가 있어야 합니다.

그리고 전략에 의해서 시그널이 발생했을 때 - 실 주문 히스토리가 연결되어 최종적으로 전략 별 승률 및 실적을 리포트 할 수 있어야 합니다.

매번 계좌 잔고 정보를 갱신하는게 아니라 주문이 발생했을 때 받아오는 실시간 정보를 이용해 계좌 잔고 정보를 갱신하는게 좋습니다. 이렇게 하려면, 처음 로딩 할 때 한번 계좌 잔고 정보를 갱신해주고, 이후에는 주문있을 때마다 OnReceiveChejan()를 통해 잔고를 계속 갱신해주는 것이지요.

이렇게 해야 증권사 정보와 프로그램간 차이가 발생하지 않습니다.

물론, 증권사 서버의 과부하로 실시간 정보를 누락하는 경우도 발생하니 나중에는 특정 인터벌 간격으로 조회를 통해서 비교해 다른점이 있을 경우 갱신하는 코드가 추가되어야 합니다.

계좌정보를 조회하는 기능을 구현하였습니다.

Task<주식계좌정보> 주식_계좌정보_조회(string 계좌번호);

주식계좌정보는 다음과 같습니다.

    public record 주식계좌정보
    {
        public string 계좌번호 { get; init; }

        public decimal 예수금 { get; init; }
        public decimal 유가잔고평가액 { get; init; }
        public decimal 예탁자산평가액 { get; init; }
        public decimal 총매입금액 { get; init; }
        public decimal 추정예탁자산 { get; init; }

        public IReadOnlyList<주식보유종목정보> 보유종목_목록 { get; init; }
        public IReadOnlyList<주식주문정보> 미보유주문_목록 { get; init; }
    }

    public record 주식보유종목정보
    {
        public string 종목코드 { get; init; }
        public string 종목명 { get; init; }

        public int 보유수량 { get; init; }
        public float 평균단가 { get; init; }
        public float 현재가 { get; init; }
        public decimal 평가금액 => (decimal)현재가 * 보유수량;
        public decimal 손익금액 => 평가금액 - 매입금액;
        public float 손익율 => Calc.PerRate(현재가, 평균단가);
        public decimal 매입금액 { get; init; }

        public IReadOnlyList<주식주문정보> 주문상세_목록 { get; init; }
    }

보유종목과 미보유주문 목록으로 관리합니다.
보유종목의 경우, 종목 별 주문상세 목록을 가지게 됩니다.

이제 실시간 체결 정보를 연동한 이후, 간단한 UI 프로그램을 만들어 테스트를 진행하려고 합니다.

프로젝트 리유니언 | Microsoft Docs

UI를 WinUI 3로 해보려 합니다. 하던거만 하면 재미없으니까요

좋아요 1