본문 바로가기
Python

SMA와 MACD 이용 Python 가상화폐 자동매매(1/2)

by 아짱이아빠 2021. 8. 15. 11:46
반응형

이번에는 Python을 이용한 가상화폐의 자동매매에 대해 알아본다.

최근 2개월간 자동매매 프로그램을 짜고 실제로 투자를 해보았으며 이를 통해 알게 된 것들을 포함하여 정리해보고자 한다.
처음 자동매매 프로그램을 시작하게된 계기는 밤늦게까지 코인 시장을 보고 있는 게 너무 힘들어서 밤사이 자동매매를 걸어놓고 편히 잠을 자려고 시작했는데, 어처구니없게도 자동매매를 걸어놓은 뒤부터 더 잠을 못 자고 있다.

자동매매 프로그램에 대해 현재까지의 결론부터 간략히 얘기하자면 수익을 낼 수 없었다.
그러나 아직 진행 중이고 계속 매매 알고리즘을 바꿔가면서 시도해보고 있다.
벌써 프로그램 버전만 0 부터 26까지 올라올 만큼 다양한 시도를 하고 있는데 이렇다 할 수익을 볼 수는 없었다.
여기서 그동안의 자동매매 프로그램이 왜 수익이 나지 않는지에 대해 나름 깨닳은 것들도 정리하였다.

우선 내가 만든 자동매매 프로그램은 다른 사람들이 만든 프로그램처럼 복잡하거나 다양한 기능이 자동화되어 있거나 하지 않다.
(사실 프로그램 전공자도 아니고 시간 날때마다 Python을 취미 삼아 개인적으로 공부하고 있는 중이라 고급 스킬을 이용할 능력도 시간도 되지 않는다.)

프로그램은 아주 단순하여 직접 원하는 코인을 넣어주고 시작하여 특정 수익이 나면 종료한다.
다만, 매매 알고리즘은 다른 사람들이 만든 것과는 조금 다르게 RSI나 MACD, EMA 등의 보조 지표들을 이용한다.

먼저 자동매매프로그램 구성에 대해 정리하고 적용된 매매 알고리즘에 대한 backtesting 결과, 그리고 마지막으로 자동매매를 하면서 느낀 점들을 정리하고자 한다.
참고로, 이 포스트는 개인적으로 Python 프로그램에 대한 공부를 한 결과를 정리하는 것이 목적이므로 프로그램이 단순 무식하게 짜여 있어도 이해해 주길 바랍니다.


[Python 가상화폐 자동매매 프로그램]

자동매매 프로그램은 아래의 구조처럼 단순한 구성으로 되어있으며 당연히 다른 사람들이 만든 프로그램과 다를 것은 없다.

자동매매 프로그램 구성
(자동매매 프로그램 구성)


(1) 프로그램 준비
프로그램 준비는 프로그램에 사용하는 모듈에 대한 import 및 코인 거래소에서 할당받은 접속코드를 넣는 부분, 그리고 자동매매에 사용되는 보조 지표 계산과 현재가 조회나 잔고를 조회하는 것들을 정의한 부분으로 구성된다.

아래는 프로그램에 필요한 모듈을 import하고 각자 받은 계정에 접속하는 코드이다.

import time
import pyupbit
import datetime
import numpy as np

access = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"


자동매매는 upbit API를 사용하였으므로 파이썬에서 이 API를 사용할 수 있게 해주는 pyupbit를 불러와야 한다.
그리고 그 전에 upbit 거래소에 접속하여 각자 access 코드와 secrete 코드를 할당받아야 하며 받은 코드를 넣어준다.
코드를 받는 부분은 다른 블로그 글들에 더 자세히 설명하고 있으니 여기서는 넘어가도록 하겠다.

다음은 자동매매에 적용할 보조 지표 계산을 정의하는 부분이며 코드는 아래와 같다.

def get_SMA_MACD(ticker) :
    df = pyupbit.get_ohlcv(ticker, interval='minutes10', count=116)
    SMA01 = df['close'].rolling(window=3).mean()
    SMA02 = df['close'].rolling(window=10).mean()
    SMA03 = df['close'].rolling(window=50).mean()
    
    ShortEMA = df.close.ewm(span=12, adjust=False).mean()
    LongEMA = df.close.ewm(span=26, adjust=False).mean()
    MACD = ShortEMA-LongEMA
    Signal = MACD.ewm(span=9, adjust=False).mean()
       
    return (SMA01, SMA02, SMA03, MACD, Signal)

여기가 자동매매에서 어떤 보조 지표를 적용할 것인지를 결정하는 부분이라 할 수 있다.
보다시피 3종류의 단순이동평균과 이동평균 수렴 확산 지수인 MACD를 조합하여 매수와 매도를 하도록 하였으므로 이 두 가지 보조 지표를 계산하는 내용을 정의하고 있다.

맨 처음에 시작했을 때 즉, 버전 0 프로그램에는 단순히 상대 강도 지수인 RSI만을 적용하였었다.
계속해서 손해가 발생하여 RSI와 MACD의 조합부터 고정된 이득을 보면 매도를 하는 로직 등 다양하게 시도했었고 현재 시험하고 있는 최신 버전이 이 SMA와 MACD를 조합하는 방법이다.

SMA와 MACD를 계산하기 위해서는 이전의 종가(close) 데이터들이 필요하다.
이전의 종가 데이터는 “pyupbit.get_ohlcv”를 통해 불러오며 몇 분봉을 몇 개 불러올 것인지 같이 정의해 준다.
“ohlcv”는 코인의 Open/High/Low/Close/Volume의 데이터를 의미하며 한번에 이 모든 것을 불러오므로 그중에서 필요한 종가인 close column에 있는 데이터만을 사용한다.

위의 코드에서는 10분봉을 기준으로 매매를 하므로 10분 봉 데이터를 가지고 온다.
이전까지 자동매매 시도는 1분봉을 기준으로 하였으며 1분 봉으로는 코인 가격의 변동성이 너무 심하여 절대 수익을 낼 수 없다는 결론에 도달했다.
그래서 지금은 좀 지루하지만 10분봉을 기준으로 한다.

10분봉 데이터 116개를 가져오는 코드로 되어 있다.
116이란 숫자는 노력으로 알아낸 숫자인데 116개 정도를 불러와서 MACD를 개산해야 실시간으로 거래소에서 보여주는 MACD 지수값과 가장 유사했다.
이 말의 의미는 만약 MACD가 아닌 RSI를 적용한다고 하면 이전 데이터를 몇 개를 가져와야 할지는 달라진다는 얘기이다.
어찌 되었든 116개를 불러와서 계산하면 소수점 첫째 자리까지는 일치하는 것을 확인하여 적용하고 있다.

다음에 있는 코드는 3가지 SMA와 MACD, Signal을 계산하는 부분이며 특별히 설명할 것은 없으므로 넘어가겠다.

아래는 현재 내 통장에 있는 잔액을 조회하고 코인의 현재 가격을 조회하는 부분을 정의한 코드이다.

def get_balance(ticker):
    balances = upbit.get_balances()
    for b in balances:
        if b['currency'] == ticker:
            if b['balance'] is not None:
                return float(b['balance'])
            else:
                return 0
    return 0
    
def get_current_price(ticker):
    return pyupbit.get_orderbook(tickers=ticker)[0]["orderbook_units"][0]["ask_price"]

코드는 pyupbit에서 제공하는 코드로 어떤 동작인지 이해하지 못하며 굳이 이 내용을 이해할 필요까지는 없다고 생각한다.
그러나 자동매매에서 매우 중요한 부분이긴 하다.
다음에 나오겠지만 자동매매를 수행하는 부분에서 내가 만든 조건에 부합하여 매수를 할 때와 매도를 할 때에 꼭 필요하다.

아래는 앞에서의 접속코드로 upbit에 로그인을 하는 부분이고 로그인을 하고 나면 자동매매가 시작되었다는 문구를 찍어주는 코드이다.

upbit = pyupbit.Upbit(access, secret)
print("autotrade start")


다음은 꼭 필요한 코드는 아니고 좀 편하게 하기 위해 넣은 부분이다.
코인을 바꾸어 자동매매를 하려할 때마다 수행 코드 부분에 매매하려는 코인의 코드를 넣어주는 것이 불편하여 프로그램을 시작하면 매매하는 코드를 두 번에 걸쳐 넣기만 하면 되도록 하였다.

coin = input("화폐 및 코인 코드 입력 :")
coin_ = input("코인 코드 입력 :")

coin 변수에는 “KRW-BTC”와 같이 화폐단위를 같이 넣어주고 coin_ 변수에는 “BTC”와 같이 코인 코드만 넣는다.


(2)자동매매 수행

자동매매를 수행하는 부분이다.

while True:
	try:
    	now = datetime.datetime.now()
		start_time = now - datetime.timedelta(hours=1)
		end_time = now + datetime.timedelta(hours=10)

		if start_time < now < end_time:
			current_price = get_current_price(coin)
    		SMA01 = get_SMA_MACD(coin)[0]
    		SMA02 = get_SMA_MACD(coin)[1]
    		SMA03 = get_SMA_MACD(coin)[2]
    		MACD = get_SMA_MACD(coin)[3]
    		Signal = get_SMA_MACD(coin)[4]
    		print(SMA01[-1])
    		print(SMA02[-1])
    		print(SMA03[-1])
    		print(MACD[-1])
    
    		if (SMA03[-1] < SMA02[-1]) and (SMA03[-1] < SMA01[-1]) and ((SMA01[-2] < SMA02[-2]) and (SMA01[-1] > SMA02[-1])) and (MACD[-1] > Signal[-1]) :
    			krw = upbit.get_balance("KRW")
        		upbit.buy_market_order(coin, krw*0.9995)
        		buy_price = current_price print(buy_price)
    
    		if SMA01[-1] < SMA02[-1] :
    			bal = upbit.get_balance(coin_)
        		upbit.sell_market_order(coin, bal*0.9995)
        		sell_price = current_price print(sell_price)
        
    	earned_price = upbit.get_balance("KRW")
        if earned_price >= (start_price*0.07)+start_price:
        	break
        
        time.sleep(200)
     except Exception as e:
     	print(e)
        time.sleep(1)


전체적으로 자동매매 수행은 while 문을 통해 계속 수행되며 내부에 특정 종료 조건이 만족하면 종료된다.
아니면 "ctrl+c" 를 눌러 종료할 수 있다.

코드의 처음부분은 자동매매를 얼마의 시간 동안 수행할지 정의해 주기 위해 시간 데이터를 가져오고 조건을 만들어 준다.
“now”는 현재 시간이고 “start_time”은 현재 시간에서 1시간을 뺀 것이고 “end_time”은 10시간을 더한 것이다.
그러나 여기서 이 시간 정의는 의미가 없으며 “now”가 계속 업데이트되므로 if 문에 종결 조건은 없는 것이나 마찬가지이고 따라서 프로그램은 계속 수행된다.
정해진 시간을 이용해서 프로그램을 종료시키려면 여기를 수정하면 될 것이다.

프로그램이 시작되면 제일 먼저 하는 것은 현재 코인의 값을 가져오고 앞에서 정의한 3가지 SMA와 MACD, Signal 지수를 계산한다.

 

반응형


그 다음이 이 자동매매 프로그램의 핵심이 되는 부분이다.
프로그램이 시작된 이후 첫 번째 if 문은 매수를 위한 조건이고 두 번째 if 문은 매도를 위한 조건이다.
매수는 3일 기준 SMA와 10일 기준 SMA가 50일 기준의 SMA보다 커야 하고 3일 기준 SMA가 10일 기준 SMA를 넘는 순간이며 또한 이때에 MACD는 Signal보다 커야 하는 조건이다.
이 조건이 모두 만족할 때에 매수를 하며 매수는 0.005%의 수수료를 제외하고 통장에 있는 전액을 매수에 사용한다.
이를 위해 원화 기준의 통장 잔액을 조회하고 매수 명령을 보낸다.
매수가 이루어지면 매수 당시의 현재가를 “buy_price” 변수에 저장하고 print 문으로 한번 찍어준다.
사실 여기의 프로그램에서는 현재 매수 가격을 별도로 알고 있을 필요가 없으나 이전 버전의 매매 프로그램에서는 매수 가격을 기준으로 매도 조건을 결정했었으므로 매수했을 때의 가격이 필요했었다.
매도의 조건을 매수가격을 기준으로 판단하려면 필요한 코드이다.

매도 조건은 3일 기준 SMA가 10일 기준 SMA 보다 밑으로 가는 순간이 될 때이다.
자동매매 프로그램을 해보면서 중요한 부분 중 또 한 가지는 이 매도 조건을 어떻게 설정하느냐 인 것 같다.
정말 다양한 매도조건을 만들 수 있고 여러 가지를 해봤는데 역시 수익을 낼 수는 없었다.
여기서도 3일 기준의 SMA가 아니고 현재가가 10일 기준의 SMA 보다 작을 때 매도하는 조건으로 할 수 도 있으며 어느 것이 더 적합한지는 아직 검증되지 않았다.
다음에 얘기할 backtesting에서 알아볼 계획이다.

다음은 잔고 조회를 해서 자동매매를 시작했을 때의 가격보다 7% 이득을 봤다면 프로그램을 종료하는 코드이다.
이 코드를 추가한 이후 이 코드가 실행될 만큼 수익이 난 적은 한 번도 없었다.
특정 수익이 났을 때에 종료하려면 사용하고 그렇지 않고 시간으로 프로그램의 시작과 종료를 제어하려면 앞에서의 "start_time"과 "end_time"을 수정하면 된다.

마지막은 프로그램의 실행 주기를 설정하는 부분이다.
개인적으로 이 부분이 실제 직접 거래를 하는 것과 프로그램을 통해 자동매매를 하는 것 간에 수익 차이를 발생시키는 가장 큰 부분이라고 생각한다.
2개월간 자동매매를 돌리고 관찰해본 결과 실제 거래소의 계산 시간과 프로그램에서 조회하고 판단하고 거래요청을 수행하는 시간 간의 차이가 발생하고 이를 정확히 일치시킬 수가 없기 때문에 이 부분에서 수익이 날 것도 손해가 발생한다던지 손해를 안 봐도 되는 부분에서 손해가 발생하게 된다.
예를 들어 1분 봉을 기준으로 자동매매를 할 때를 보면, 현재 거래소에서 1분 단위 시세가 오르락내리락하고 있다.
즉, 프로그램상으로는 정해진 매수 조건을 만족시켰다가 아니었다가를 반복하고 있는 상황이다.
이때에 운이 없게도 프로그램 동작 순간에 가격은 순간 최고가가 되어 매수가 일어나는데 실제 1분이 지나고 보면 가격이 다시 떨어져 매수 조건을 만족하지 않는 경우였던 것이다.
이렇게 되면 매수를 안 해야 하지만 실제로는 매수가 이루어졌고 이후 코인의 가격은 계속 하락을 하여 물린 상황이 되거나 손절 조건에서 매도하여 손해를 보게 되는 것이다.
이러한 상황이 계속 발생하여 이익을 보는 거래를 했다가도 다시 더 큰 손해를 보게 된다.
또한 이러한 시간적인 문제에 의해 거래가 발생되는 부분은 backtesting의 결과와도 차이를 발생시킨다.
Backtesting은 과거의 상황이 종료된 데이터를 가지고 점검을 하기 때문에 위에서의 실제 거래되는 상황이 모의가 되지 않는다는 것을 인지하고 있어야 한다.
이 “time.sleep”을 1분이 정확히 되도록 거래소 업데이트 시간과 동시에 프로그램 수행을 여러 번 해보았지만 인터넷 때문인지 아니면 프로그램 로직이 돌면서 걸리는 시간 때문인지, 데이터 요청과 회신에 걸리는 시간때문인지 점점 느려져 시간을 일정하게 맞출 수 없었다.

그렇기 때문에 1분 봉을 기준으로는 아무리 프로그램을 잘 짜도, 아니 오히려 너무 세밀하게 만들수록 원하는 결과를 얻지 못한다는 결론에 왔고 현재는 10분 봉을 기준으로 바꾸어 시도해 보고 있는 것이다.

10분봉 기준이기 때문에 여기서는 10분 안에 3번의 프로그램 수행을 하려고 200초마다 한 번씩 프로그램을 수행하도록 하였다.
사실 1분 봉에서의 문제가 여기서도 발생할 것이다.
10분이 끝나는 시점에는 매수 조건을 만족시키지 못하지만 그전에 매수 조건이 되어 매수를 해버리는 경우는 반드시 생길 것이다.
그러나 1분 봉보다는 그 경우가 훨씬 적을 것이라 기대하고 있다.
그럼 왜 15분 봉이 아닌 10분 봉인지는 backtesting 부분에서 얘기하도록 하겠다.

Backtesting 코드와 이를 통한 자동매매 프로그램 분석은 다음 포스트에서 얘기하겠다.

 

반응형

댓글