[파이썬] 코인 자동매매 프로그램 만들기 #4 - 자동 매매 프로그램
개발/Python

[파이썬] 코인 자동매매 프로그램 만들기 #4 - 자동 매매 프로그램

반응형


이번에는 이전까지 테스트한 데이터와 전략들을 토대로 실제로 목표가에 맞게 매수하고 매도하는 프로그램을 작성해보겠습니다

변수 선언

먼저 자동매매 클래스를 만들고 init 함수로 필요한 변수들을 선언해 줍니다

class autoTrade :
    def __init__(self, start_cash, ticker) :
        self.fee = 0.05 # 수수료
        self.target_price # 목표 매수가
        self.bull = False # 상승장 여부
        self.ticker = ticker # 티커
        self.buy_yn = false # 매수 여부
       
        self.start_cash = start_cash # 시작 자산

현재가 조회

먼저 자동 매매를 진행하기 위해서는 주기적으로 현재가를 불러와 목표 매수가와 비교해야 하기 때문에 while문을 이용해 1초마다 현재가를 반복적으로 구해오는 코드를 작성하겠습니다

def start(self) :
    while True :
        current_price = pyupbit.get_current_price(self.ticker)
        print(current_price)
        time.sleep(1)

 

오늘의 데이터

노이즈 분석

상승장 여부와 변동성 돌파 전략에서 목표가는 장이 시작될 때 마다 갱신되어야 하기에 그때 사용할 변동성 돌파 전략에 따른 목표가와 상승장 여부를 계산하여 변수값을 갱신해주는 함수를 만들어 주겠습니다

먼저 목표 매수가를 구할 때 고정된 K값이 아닌 노이즈에 따라 적절하게 변동시킨 K값을 이용하기 위해 노이즈를 계산하고 20일간의 노이즈 평균값을 구해줍니다

def get_today_data(self) :
    daily_data = pyupbit.get_ohlcv("KRW-BTC", count=41)
    # 노이즈 계산 ( 1- 절대값(시가 - 종가) / (고가 - 저가) )
    daily_data['noise'] = 1 - abs(daily_data['open'] - daily_data['close']) / (daily_data['high'] - daily_data['low'])
    # 노이즈 20일 평균
    daily_data['noise_ma20'] = daily_data['noise'].rolling(window=20).mean().shift(1)
                           open        high         low       close       volume         value     noise  noise_ma20
2022-01-07 09:00:00  53240000.0  53358000.0  50900000.0  51449000.0  7509.108093  3.895280e+11  0.271359    0.628241
2022-01-08 09:00:00  51449000.0  52709000.0  50829000.0  52142000.0  4171.096458  2.154237e+11  0.631383    0.600604
2022-01-09 09:00:00  52142000.0  52600000.0  51144000.0  51678000.0  3369.095090  1.748699e+11  0.681319    0.585213
2022-01-10 09:00:00  51672000.0  51999000.0  48897000.0  51246000.0  7955.460111  4.025708e+11  0.862669    0.578637
2022-01-11 09:00:00  51255000.0  51699000.0  50680000.0  50939000.0  3629.760407  1.857707e+11  0.689892    0.604199

 

목표가 계산

비트코인의 일봉 데이터를 불러와 전일의 변동폭을 구하고 돌파계수 K자리에 20일 노이즈 평균을 곱해 오늘의 시가와 더해줍니다

daily_data = pyupbit.get_ohlcv(self.ticker, count=21)
# 변동폭 ( 고가 - 저가 )
daily_data['range'] = daily_data['high'] - daily_data['low']
# 목표매수가 ( 시가 + 변동폭 * K )
daily_data['targetPrice'] = daily_data['open'] + daily_data['range'].shift(1) * self.K
                           open        high         low       close       volume         value     noise  noise_ma20      range   targetPrice
2022-01-07 09:00:00  53240000.0  53358000.0  50900000.0  51449000.0  7509.108093  3.895280e+11  0.271359    0.628241  2458000.0  5.444120e+07
2022-01-08 09:00:00  51449000.0  52709000.0  50829000.0  52142000.0  4171.096458  2.154237e+11  0.631383    0.600604  1880000.0  5.292529e+07
2022-01-09 09:00:00  52142000.0  52600000.0  51144000.0  51678000.0  3369.095090  1.748699e+11  0.681319    0.585213  1456000.0  5.324220e+07
2022-01-10 09:00:00  51672000.0  51999000.0  48897000.0  51246000.0  7955.460111  4.025708e+11  0.862669    0.578637  3102000.0  5.251450e+07
2022-01-11 09:00:00  51255000.0  51699000.0  50680000.0  50940000.0  3637.360330  1.861579e+11  0.690873    0.604199  1019000.0  5.312923e+07

 

상승장 판단

그리고 매수여부를 판단할 때 사용할 5일 이동평균선을 이용한 상승장 여부를 계산해줍니다

# 5일 이동평균선
daily_data['ma5'] = daily_data['close'].rolling(window=5, min_periods=1).mean().shift(1)
# 상승장 여부
daily_data['bull'] = daily_data['open'] > daily_data['ma5']
                           open        high         low       close       volume         value     noise  noise_ma20      range   targetPrice         ma5   bull
2022-01-07 09:00:00  53240000.0  53358000.0  50900000.0  51449000.0  7509.108093  3.895280e+11  0.271359    0.628241  2458000.0  5.444120e+07  55457600.0  False
2022-01-08 09:00:00  51449000.0  52709000.0  50829000.0  52142000.0  4171.096458  2.154237e+11  0.631383    0.600604  1880000.0  5.292529e+07  54241200.0  False
2022-01-09 09:00:00  52142000.0  52600000.0  51144000.0  51678000.0  3369.095090  1.748699e+11  0.681319    0.585213  1456000.0  5.324220e+07  53341400.0  False
2022-01-10 09:00:00  51672000.0  51999000.0  48897000.0  51246000.0  7955.460111  4.025708e+11  0.862669    0.578637  3102000.0  5.251450e+07  52472400.0  False
2022-01-11 09:00:00  51255000.0  51699000.0  50680000.0  50770000.0  3650.417073  1.868219e+11  0.524043    0.604199  1019000.0  5.312923e+07  51950800.0  False

 

이렇게 계산된 데이터 중 마지막 데이터의 목표매수가와 상승장 여부 값으로 갱신해줍니다

today = daily_data.iloc[-1]

self.target_price = today.targetPrice
self.bull = today.bull

전략 및 목표가 재설정

이제 오늘의 데이터를 구하는 함수를 작성했으니 매일 장이 시작할 때 함수를 호출하여 목표매수가, K, 상승장 여부 등을 갱신할 수 있도록 하겠습니다

현재시간을 계속 받아오면서 지금이 장 시작시간인지를 확인해야합니다. 암호화폐 시장은 24시간 돌아가는 시장이므로 매일 자정을 장 시작시간으로 가정하겠습니다

우선 datetime 모듈을 이용하여 현재시간을 받아옵니다

import datetime

while True :
    now = datetime.datetime.now()
    
    print(now)
    time.sleep(1)
2022-01-12 22:45:23.763549
2022-01-12 22:45:24.764976
2022-01-12 22:45:25.765403

 

다음으로 장 시작 시간인 자정을 계산합니다

openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1)
2022-01-12 09:00:00

 

이제 두 시간을 비교하여 현재시간이 장 시작 시간인지를 판단하여야 하는데 위에서 보이는 것 처럼 현재 시간을 정확하게 0.00초로 구해지지 않으며 컴퓨터 상태나 인터넷 상황에 따라 조금의 오차가 발생할 수 있으므로 약 5초 정도의 여유를 두고 현재 시간이 9:00:00 ~ 9:00:05 사이에 있는지를 비교하겠습니다

이때 조건을 만족한다면 get_today_data함수를 호출하여 목표 변수값을 갱신해 줍니다

now = datetime.datetime.now()
        openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1)

        while True :
            now = datetime.datetime.now()

            if openTime < now < openTime + datetime.timedelta(seconds=5) :
                openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1)
                self.get_today_data() # 데이터 갱신
            time.sleep(1)

 

또한 프로그램 시작시 그 시점의 데이터를 받아올 수 있도록 클래스의 __init__ 함수에서 호출해줍니다

 

매수하기

다음으로 실제로 목표 매수가를 만족할 때 매수를 시도하는 함수를 작성하겠습니다

업비트에서 발급받은 API Key를 통해 계정에 연결하고 현재 잔고를 조회합니다

그리고 pyupbit 모듈의 buy_market_order 함수에 티커와 매수가를 입력하여 시장가 매수를 하도록 합니다

acc_key = "ACCESS KEY"
sec_key = "SECRET KEY"

upbit = pyupbit.Upbit(acc_key, sec_key)
def buy_coin(self) :
    balance = upbit.get_balance()
    upbit.buy_market_order(self.ticker, balance)

 

이 때 시장가 주문에 사용되는 금액은 수수료를 고려하지 않은 금액이므로 업비트의 수수료 0.05%를 고려하여 전체 자산에서 수수료를 제외한 금액으로 주문하겠습니다

마지막으로 최소 주문 가능 금액이 존재하는데

원화 마켓의 경우 5,000원 이상부터 주문이 가능하므로 현재 남아있는 자산이 5,000원 이상일 때만 매수를 진행하도록 조건을 달아주겠습니다

그리고 매수와 매도는 하루에 한번씩만 진행할 것이기 때문에 클래스 처음에 선언한 매수 여부를 True로 저장하여 매수 여부를 판단합니다

def buy_coin(self) :
    balance = upbit.get_balance() # 잔고 조회
        
    if balance > 5000 : # 잔고 5000원 이상일 때
        upbit.buy_market_order(self.ticker, balance * 0.9995)
        self.buy_yn = True

 

while문에서 현재가가 목표 매수가 이상일 때 그리고 상승장일 때 작성한 매수 함수를 호출합니다

if((current_price >= self.target_price) and self.bull and not self.buy_yn) : # 매수 시도
    self.buy_coin()

 

매도하기

다음날 장 시작에 매수했던 코인을 매도하기 위한 함수도 작성해 줍니다

def sell_coin(self) :
    balance = upbit.get_balance("KRW_BTC") # 잔고 조회

    upbit.sell_market_order(ticker, balance)
    self.buy_yn = False

 

매도 함수는 매일 장 시작시간으로 가정한 자정에 호출합니다

if openTime < now < openTime + datetime.timedelta(seconds=5) :
    openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1)
    self.sell_coin() # 매도 시도
    self.get_today_data() # 데이터 갱신

※ 2022.01.19 매도함수 호출 시 매수 여부 조건 확인 추가

if openTime < now < openTime + datetime.timedelta(seconds=5) :
    openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1)
    if(self.buy_yn) :
        self.sell_coin() # 매도 시도
    self.get_today_data() # 데이터 갱신

[파이썬] 코인 자동매매 프로그램 만들기 #6 - 오류 수정

 

보안 및 예외처리

이번 챕터의 마지막으로 보안 및 예외처리를 해주도록 하겠습니다

매수와 매도 시도시 사용하는 Access Key와 Secret Key를 코드에 작성해 놓는 경우 저처럼 코드를 공개할 때 API Key까지 같이 노출이 되게 됩니다

그렇기 때문에 key값을 따로 저장해두고 프로그램 시작시 파일에서 읽어오는 형태로 변경하겠습니다

with open("key_info.txt") as f :
    lines = f.readlines()
    acc_key = lines[0].strip()
    sec_key = lines[1].strip()
    
upbit = pyupbit.Upbit(acc_key, sec_key)

 

 

다음은 예외처리 입니다

코드 실행 중간에 예기치 못한 오류로 프로그램이 진행되지 않고 멈춰버린다면 추가적인 손실을 초래하거나 추가 수익의 기회를 놓치는 경우가 발생할 수 있습니다

따라서 오류가 발생하더라도 프로그램이 계속 돌아갈 수 있도록 try ~ except 구문을 추가하여 예외처리를 해주겠습니다

while True :
    try :
        now = datetime.datetime.now()

        if openTime < now < openTime + datetime.timedelta(seconds=5) :
            openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1)
            self.sell_coin() # 매도 시도
            self.get_today_data() # 데이터 갱신

        current_price = pyupbit.get_current_price(self.ticker)
        if((current_price >= self.target_price) and self.bull and not self.buy_yn) : # 매수 시도
            self.buy_coin()
    except :
        print("error!")

    time.sleep(1)

 

최종코드

import pyupbit
import time
import datetime
import math

with open("key_info.txt") as f :
    lines = f.readlines()
    acc_key = lines[0].strip()
    sec_key = lines[1].strip()

upbit = pyupbit.Upbit(acc_key, sec_key)

class autoTrade :
    def __init__(self, start_cash, ticker) :
        self.fee = 0.05 # 수수료
        self.target_price = 0 # 목표 매수가
        self.bull = False # 상승장 여부
        self.ticker = ticker # 티커
        self.buy_yn = False # 매수 여부

        self.start_cash = start_cash # 시작 자산

        self.get_today_data()

    def start(self) :
        now = datetime.datetime.now() # 현재 시간
        openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1) # 자정

        while True :
            try :
                now = datetime.datetime.now()

                if openTime < now < openTime + datetime.timedelta(seconds=5) :
                    openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1)
                    self.sell_coin() # 매도 시도
                    self.get_today_data() # 데이터 갱신

                current_price = pyupbit.get_current_price(self.ticker)
                if((current_price >= self.target_price) and self.bull and not self.buy_yn) : # 매수 시도
                    self.buy_coin()
            except :
                print("error!")

            time.sleep(1)

    def get_today_data(self) :
        daily_data = pyupbit.get_ohlcv(self.ticker, count=41)
        # 노이즈 계산 ( 1- 절대값(시가 - 종가) / (고가 - 저가) )
        daily_data['noise'] = 1 - abs(daily_data['open'] - daily_data['close']) / (daily_data['high'] - daily_data['low'])
        # 노이즈 20일 평균
        daily_data['noise_ma20'] = daily_data['noise'].rolling(window=20).mean().shift(1)
       
        # 변동폭 ( 고가 - 저가 )
        daily_data['range'] = daily_data['high'] - daily_data['low']
        # 목표매수가 ( 시가 + 변동폭 * K )
        daily_data['targetPrice'] = daily_data['open'] + daily_data['range'].shift(1) * daily_data['noise_ma20']

        # 5일 이동평균선
        daily_data['ma5'] = daily_data['close'].rolling(window=5, min_periods=1).mean().shift(1)
        # 상승장 여부
        daily_data['bull'] = daily_data['open'] > daily_data['ma5']

        today = daily_data.iloc[-1]

        self.target_price = today.targetPrice
        self.bull = today.bull

    def buy_coin(self) :
        balance = upbit.get_balance() # 잔고 조회
        
        if balance > 5000 : # 잔고 5000원 이상일 때
            upbit.buy_market_order(self.ticker, balance * 0.9995)
            self.buy_yn = True

    def sell_coin(self) :
        balance = upbit.get_balance("KRW_BTC") # 잔고 조회

        upbit.sell_market_order(ticker, balance)
        self.buy_yn = False

ticker = "KRW-BTC"
tradingBot = autoTrade(1000000, ticker)
today_data = tradingBot.start()