1. 들어가기 전에
혹시 이전 회차 에 결과에 실망하신 분들이 있으신가요? main.py 중간 쯤에 있는 add_macd의 수치를 약간 바꾸어 보았습니다. 변경 부분 bcondition.add_macd(12, 30, 9) 그랬더니
12, 26, 9와는 완전 다른 결과를 출력 하고 있습니다. 제가 이전 회차에서 왜 f(n)에서 f(x)를 찾는 작업 이라고 말씀 드린 이유를 위 그림을 보시면 잘 알 수 있습니다. 다시 돌아가서 macd의 기본 값이 왜 12, 26, 9일까요? 예전에 거래일은 6거래일 즉 토요일도 장이 열리던 시절이 있었습니다. 그럼 12는 2주 26은 한달 그리고 9는 2번주의 중간 정도 된다는 것을 아실 겁니다. 하지만 잘 생각해 보시면 bitcoin거래는 365일 24시간 계속 되는 거래 입니다. 애초에 12, 26, 9 값이 맞을 리가 만무 합니다. 그렇다고 12, 30, 9가 꼭 정답이라고도 할 수 없습니다. 계속적으로 값을 대입하다보면 일일 거래 건수와 수익률이 최적인 어떤 상태가 나올 겁니다. 우리는 그기를 찾으면 됩니다. 지금은 12, 30, 9로 값을 셋팅 하고 한번 진행 해 보도록 하겠습니다.
2. Bithumb 거래소 연동 하기
제가 시작한지 얼마 안돼서 경험이 그렇게 많은 편은 아닙니다. 하지만 Bithumb의 API나 시스템은 실명스럽기 그지 없네요. 하긴 지금 저희가 사용하려는 pybithumb lib는 Bithumb의 정식 lib는 아닌듯 합니다. 홈페이지 어디에도 이 lib를 사용하라는 말은 없습니다. 대신 rest api에 대한 가이드와 설명이 있습니다. 지금은 시간이 안돼서 rest api로 다시 lib를 제작할 수 는 없지만 실제 rest api에 있는 내용중 pybithumb에는 없는 것들이 있습니다. 이런 문제를 제치고 일단 api오류가 생각보다 많이 발생 합니다. 특히 get_balance함수(잔고 조회)의 경우 3번중 1번은 오류입니다. 또 다른 문제는 구조적인 문제 입니다. 이 문제에 직면하고는 bitcoin 자동 매매에 상당한 실망을 한 상태 입니다. 주식의 경우 풍부한 유동성과 함께 여러가지 체결방법과 가격대별로 자동으로 호가에 대한 단위와 구간이 있습니다. 하지만 bitcoin의 경우 이런 구간에 대한 설정이 상당히 엉성하고 형편 없습니다. 이런 부분까지 고려한 프로그램이 나와야 할 것으로 보입니다.
예를 들면 주식의 경우 주가가 만원 미만인 경우 100원단위 호가로 호가를 호출 한다면 만원 이상이 되면 자동적으로 호가 단위가 500원 단위 그 이상이 되면 호가 단위는 1000원 이렇게 계속 호가 단위가 바뀌는 구간이 있습니다. 하지만 bitcoin은 그런 구간이 없어서 호가 사이 금액 차이가 엄청 납니다. 그래서 자동매매가 정상적으로 동작하려면 시장가 매매가 되어야 하는데 이 시장가 매매가 호가 사이 금액 차이 때문에 우리가 원하는 금액으로 매수가 안되는 단점을 보입니다. 이를 보완 할 방법은 차차 생각 해 보도록 하겠습니다.
소스 내용(BithumbTrade.py)
import pybithumb
import math
import time as datetime
import sys
import os
class BithumbTrade:
def __init__(self, bithumb, logger, ticker):
self.bithumb = bithumb
self.logger = logger
self.ticker = ticker
###잔고 조회
def get_balance(self):
for i in range(2):
balance = self.bithumb.get_balance('BTC')
self.logger.info("잔고조회:" + str(balance))
#수익률 구하기
if balance is not None:
return balance
else:
self.logger.warn("Bithumb API오류")
datetime.sleep(1)
def buy_crypto_currency(self, timing_price):
while True:
try:
balance = self.get_balance() #현금 보유 금액
krw = balance[2] - balance[3] #현금 보유 금액
possible_krw = krw * 0.9
possible_krw = possible_krw - (possible_krw % 1000) #천원 단위로 매매함.
if possible_krw < 500:
self.logger.info("보유 금액이 너무 작습니다.:%.2f" % krw)
return
while True:
orderbook = pybithumb.get_orderbook(self.ticker) #호가창 정보
self.logger.info("orderbook:%s", str(orderbook))
sell_price = orderbook['asks'][0]['price']
sell_volume = orderbook['asks'][0]['quantity'] #quantity
if sell_price > timing_price:
sell_price = timing_price
unit = possible_krw/float(sell_price) #호가창 가격에서 첫번재 가격으로 가능 금액을 나눈다.
unit = math.trunc(unit * 10000) / 10000 #소수점 네자리 이상 버림.
self.logger.info("%s --> unit : %.5f : sell_price: %.2f sell_volume: %.5f" % (self.ticker, unit, sell_price, sell_volume))
self.logger.info("%s --> unit : %.5f : sell_price: %.2f" % (self.ticker, unit, timing_price))
if(unit == 0):
self.logger.info("보유 금액이 너무 작습니다.:%.2f" % krw)
return
elif(unit < 0.001):
self.logger.info("unit이 최소단위보다 작습니다.: %.4f", unit)
return
sell_price = int(sell_price)
self.logger.info("timing_price:%d, unit:%.5f, possible_krw:%.2f" % (sell_price, unit, possible_krw))
order = self.bithumb.buy_limit_order(self.ticker, sell_price, unit, payment_currency="KRW") #매수 주문
if order is not None and type(order) is tuple:
self.logger.info("order success:%s, timing_price:%d, sell_price:%d" % (str(order), timing_price, sell_price))
return
elif order is not None:
self.logger.warn("order `fail:" + str(order))
if order.get('status') == '5500':
possible_krw = possible_krw - 10000 #만원씩 빼서 재 도전
unit = possible_krw/float(sell_price) #호가창 가격에서 첫번재 가격으로 가능 금액을 나눈다.
unit = math.trunc(unit * 10000) / 10000 #소수점 네자리 이상 버림.
self.logger.info("%s --> unit : %.4f" % (self.ticker, unit))
datetime.sleep(1)
continue
elif order.get('status') == '5600':
orderbook = pybithumb.get_orderbook(self.ticker) #호가창 정보
self.logger.info("orderbook:%s", str(orderbook))
sell_price = orderbook['asks'][0]['price']
unit = possible_krw/float(sell_price) #호가창 가격에서 첫번재 가격으로 가능 금액을 나눈다.
unit = math.trunc(unit * 10000) / 10000 #소수점 네자리 이상 버림.
self.logger.info("%s --> unit : %.4f" % (self.ticker, unit))
return
else:
datetime.sleep(1)
except Exception as ex:
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
self.logger.error("buy_crypto_currency -> exception! %s : %s %d" % (str(ex) , fname, exc_tb.tb_lineno))
datetime.sleep(1)
def sell_crypto_currency(self, timing_price):
while True:
try:
unit = self.bithumb.get_balance(self.ticker)[0]
if(unit == 0):
self.logger.info("보유 코인(%s)이 없습니다." % self.ticker)
return
orderbook = pybithumb.get_orderbook(self.ticker) #호가창 정보
self.logger.info("orderbook:%s", str(orderbook))
# order = bithumb.sell_market_order(ticker, unit, payment_currency="KRW")
sell_price = int(timing_price)
order = self.bithumb.sell_limit_order(self.ticker, sell_price, unit, payment_currency="KRW") #매수 주문
self.logger.info("sell_market: ticker: %s, unit:%.2f" % (self.ticker, unit))
if order is not None and type(order) is tuple:
self.logger.info("order success:%s, timing_price:%d" % (str(order), timing_price))
return
elif order is not None:
self.logger.warn("order fail:" + str(order))
return
else:
datetime.sleep(1)
except Exception as ex:
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
self.logger.error("sell_crypto_currency -> exception! %s : %s %d" % (str(ex) , fname, exc_tb.tb_lineno))
datetime.sleep(1)
위의 소스를 적용하기 전에 먼저 main.py가 있는 디렉토리에 private 라고 폴더를 하나 만듭니다.
소스 내용(BithumbKey.py)
con_key = 'bithumb에서 발급받은 api key'
secu_key = 'bithumb에서 발급받은 secu key'
혹시 git이나 외부 소스 관리 플랫폼에 소스를 올리더라도 이 private 폴더는 .gitignore에 추가 하여 BithumbKey.py파일이 안 올라 가게 조치 하시기 발랍니다.
그리고 main.py를 대폭 수정 했습니다.
소스 내용(main.py)
from getBithumbData import ScrapCurrBithum
from BithumbCondition import BithumbCondition
from BithumbTrade import BithumbTrade
import time as datetime
import sqlite3
import sys
import os
import logging
import logging.config
import json
import pybithumb
import private.BithumbKey as key
config = json.load(open('./logging.json'))
logging.config.dictConfig(config)
buy_func = lambda x, dataframe: x >= 2 and dataframe.loc[x, 'flag1'] > 0 and dataframe.loc[x-1, 'flag1'] <= 0 and dataframe.loc[x-2, 'flag1'] <= 0
sell_func = lambda x, dataframe: x >= 2 and dataframe.loc[x, 'flag1'] <= 0 and dataframe.loc[x-1, 'flag1'] > 0 and dataframe.loc[x-2, 'flag1'] > 0
BASE_BALANCE = 1000000 # 초기 투자금 100만원
# BASIC_FEES = 0.0005
if __name__ == '__main__':
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
dbname = "bithum5mMACD1.db"
sqlcon = sqlite3.connect(dbname)
bcondition = BithumbCondition(logger, BASE_BALANCE, buy_func, sell_func)
scb = ScrapCurrBithum()
bithumb = pybithumb.Bithumb(key.con_key, key.secu_key)
trade = BithumbTrade(bithumb, logger, 'BTC')
last_time = ""
krw = 0
while True:
try:
start_time = datetime.time()
###현재가 구하기
try:
price = pybithumb.get_current_price("BTC")
logger.info("현재가 : %.4f" % price)
except Exception as ex:
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
logger.error("get_current_proce -> exception! %s : %s %d" % (str(ex) , fname, exc_tb.tb_lineno))
###잔고 조회
balance = trade.get_balance()
logger.info("잔고조회:" + str(balance))
#수익률 구하기
if balance is not None:
rate = ((balance[0]*price + balance[2] - BASE_BALANCE)*100)/BASE_BALANCE
logger.info("수익률: %.2f %%" % rate)
#현재의 원화 잔고 얻기
krw = balance[2] - balance[3]
orderbook = pybithumb.get_orderbook("BTC")
logger.info("orderbook:%s", str(orderbook))
if orderbook is not None:
#최우선 매도 호가 구하기
asks = orderbook['asks']
sell_price = asks[0]['price'] #==> 최우선 매도 호가
sell_volume = asks[0]['quantity'] #quantity
unit = krw/float(sell_price) #==> 매도 수
logger.info("최우선 매도호가: %.2f 매도수량:%.5f 수량:%.4f" % (sell_price, sell_volume, unit))
df = scb.get_data()
bcondition.append_dataframe(df)
#전체 보조 지표를 포함하지 않고 개별 보조 지표를 사용하도록 하겠습니다.
bcondition.add_macd(12, 30, 9)
bcondition.add_moving_avg240()
df,timing = bcondition.sell_and_buy() #macd 계산
savedf = df.tail(1)
lastdata = savedf.loc[savedf.index.max()]
lasttime = lastdata['time']
close = lastdata['close']
sma240 = lastdata['sma240']
sma240_incli = lastdata['sma240_incli']
sma240vsclose = lastdata['sma240vsclose']
sma240clsmax = lastdata['sma240clsmax']
sma240clsmin = lastdata['sma240clsmin']
sma240clsminsig1 = lastdata['sma240clsminsig1']
sma240clsminsig2 = lastdata['sma240clsminsig2']
sma240clsmaxsig1 = lastdata['sma240clsmaxsig1']
sma240clsmaxsig2 = lastdata['sma240clsmaxsig2']
logger.info("마지막 데이터:" + str(lastdata))
logger.info("sma240:" + str([sma240, sma240_incli, sma240vsclose, sma240clsmax, sma240clsmin]))
logger.info("sma240:" + str([sma240clsminsig1, sma240clsminsig2, sma240clsmaxsig1, sma240clsmaxsig2]))
if sma240_incli >= 0 :
logger.info("sma240==> 상승 추세" )
else:
logger.info("sma240==> 하락 추세" )
if sma240vsclose >= 0:
logger.info("sma240 보다 상위 위치==> 상승 추세" )
else:
logger.info("sma240 보다 하위 위치==> 하락 추세" )
if lasttime == timing['time'] and timing['sellbuy'] == 'sell':
logger.info("매도 타이밍" + str(timing))
trade.sell_crypto_currency(timing['price'])
elif lasttime == timing['time'] and timing['sellbuy'] == 'buy':
logger.info("매수 타이밍" + str(timing))
trade.buy_crypto_currency(timing['price'])
get_time= datetime.strftime("%Y%m%d%H%M%S", datetime.localtime(lasttime/1000))
if last_time == "":
df.to_sql(name=dbname, con=sqlcon, if_exists='replace')
elif last_time != get_time:
last_time = get_time
savedf.to_sql(name=dbname, con=sqlcon, if_exists='append')
logger.info("MACD1 최종:")
logger.info(df)
strdt1 = datetime.strftime("%Y%m%d", datetime.localtime(df.loc[df.index.max()-2, 'time']/1000))
strdt2 = datetime.strftime("%Y%m%d", datetime.localtime(df.loc[df.index.max()-1, 'time']/1000))
logger.info("마지막1전: %s, 마지막:%s", strdt1, strdt2)
if(strdt1 != strdt2 ): #날짜가 변경되었다면
logger.info('자정이면 메모리를 일부 크리어한다.')
df = df.tail(df.index.max() - 12*24)
df.reset_index(drop=True, inplace=True)
end_time = datetime.time()
if (end_time - start_time) > 0 and (end_time - start_time) <= 5*60:
datetime.sleep(5*60 - (end_time - start_time))
except Exception as ex:
## NULL 데이터 인 경우 merge가 안된다.
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
logger.error("`add_closeminmax -> exception! %s ` : %s %d" % (str(ex) , fname, exc_tb.tb_lineno))
일단은 이 소스를 기반으로 운영하면서 수정이 필요해 보입니다. 지금 현재 Bithumb에서는 Trade에 두가지 매매 방법이 있습니다. 시장가 매매와 지정가 매매입니다. 주식의 경우 매매 방법은 총 12가지로 정부에서 매매방법에 대한 가이드를 하고 수정한 것들입니다. 같은 시장가 매매 라고 해도 지금의 coin 거래소의 방법들(Bithumb외의 다른 곳도 거의 비슷합니다.)과는 전혀 다르다고 보시면 됩니다. 우리가 트래킹한 수익률과 실제 매매의 수익률이 일치 할려면 현재의 시스템으로는 한계가 있어 보입니다. 현재는 지정가로 하면 매매가 안되고 시장가로 하면 손해가 발생합니다. 그래서 일단 240 이동 평균선(5분 데이터의 경우 10시간임)으로 추세를 예측하는 부분을 삽입했습니다. 이 후 데이터가 싸이면 이부분을 이용해서 언제 거래를 지속 할지 그리고 언제 지정가 매매를 할지 언제 시장가 매매를 할지를 판단해 보도록 하겟습니다.
3. 마치며
오늘 회차는 어떠셨나요? 제가 생각했던 것 보다 거래소의 수준이 한심한 수준이네요. 현재 1위 거래소는 upbit, 2위는 bithumb 3위는 코인빗 4위가 코인원 5위가 프로빗 익스체인지 라고 하네요. 제가 생각하는 주식과 코인의 가장 큰 차이는 주식의 경우 증권거래소를 비롯하여 중간에 많은 기관이 서로를 보조하는 기능이 있습니다. 코인의 경우는 그러한 기능은 간소하지만 그러다보니 문제점이 많이 보입니다. 어쨌든 그러한 문제는 우리 서서로 풀어야 한다는게 참 안타깝네요. 다음차에 데이터가 모이면 macd와 추세를 이용하여 현재의 문제점을 보안 해보도록 하겠습니다.
'python > 자동매매 프로그램' 카테고리의 다른 글
python Bitcoin 자동 매매 프로그램(6) - 나의 Bithum API 극복기 (1) | 2021.08.05 |
---|---|
python Bitcoin 자동 매매 프로그램(5) - 파도와 바람 (7) | 2021.07.25 |
python Bitcoin 자동 매매 프로그램(3)-5분 데이터와 보조지표의 연동 (0) | 2021.05.23 |
python Bitcoin 자동 매매 프로그램(2) - 보조 지표 구하기 (0) | 2021.05.23 |
python Bitcoin 자동 매매 프로그램(1)-데이터 얻기 (0) | 2021.05.23 |