프로젝트/비트코인 자동매매

급등주 찾기 - 업비트 파이썬 비트코인 자동매매 프로그램

Tech&Fin 2022. 1. 26. 15:13
반응형

업비트의 경우 일명 9시 경주마라고 불리고 있는 변동률이 초기화 되는 오전 9시에 이유없이 갑자기 급상승하는 종목들이 있는데요. 이런 종목 외에도 호재 공지가 뜨거나 해외 거래소의 급상승으로 인해 갑자기 급상승하는 종목들을 발견할 수 있습니다.

 

업비트 API를 이용하여 급등하는 종목을 찾으려면 분봉 데이터를 종목별로 가져와야 하기 때문에 100여개가 넘는 종목에 대해서 캔들 데이터를 조회하여 급등 종목을 찾으려면 빨라야 20~30초 정도가 소요 됩니다. 급등하는 종목에서 20~30초는 많게는 50~100%까지의 상승도 이루어 낼수 있는 시간이 될 수 있기 때문에 20~30초의 딜레이는 급등주 종목을 찾아서 매수하기에는 다소 애매한 시간이 될 수 있습니다.

 

하지만 얼마전에 살펴본 웹소켓을 이용하여 현재가 데이터를 실시간으로 오라클 DB에 저장하는 프로그램을 이용하면 1초에 300~1000번까지 전 종목을 반복해서 조회 할 수 있습니다.

 

이번 시간에는 DB에 저장된 데이터를 쿼리를 이용하여 실시간으로 급등하는 종목을 찾는 프로그램을 만들어 보려고 합니다.

 

 

목차 - 클릭하면 이동합니다.

     

    급등주 찾기

    시작하기 전에

    어떤 종목이 갑자기 급등한다고 해서 전부 계속해서 상승하는 것은 아닙니다. 어떤 종목은 급상승이 대상승으로 이루어지는 경우도 있지만 어떤 경우에는 잠깐 반짝하고 다시 제자리로 돌아가는 경우도 있습니다.

     

    따라서 급등주를 찾았다고 무조건 매수하거나 큰 금액을 매수하는 것은 매우 위험합니다. 물론 위험이 큰 만큼 잘 잡으면 높은 수익을 낼 수도 있습니다.

     

    시장의 흐름을 잘 살펴보다보면 어떤 기간에는 급등하는 종목이 계속해서 상승하는 시기를 발견할 수 있는데요. 이런 테마가 보일때 한정적으로 소액을 시도해 보시는 것이 좋을 것 같습니다. 또한 반짝 급등했다 돌아오는 종목은 물리게 되면 시간적인 기회비용을 잃기 때문에 항상 물타기를 통해 본전이라도 빨리 빠져나올 수 있는 금액만 시도해 보시는 것이 좋을 것 같습니다.

     

    VIEW 만들기

    오라클에서는 자주 사용하는 정형화 된 쿼리를 VIEW라는 것으로 만들어 마치 테이블처럼 활용할 수 있는데요. VIEW를 활용하여 급등하는 종목을 찾는 쿼리를 작성해 보도록 하겠습니다.

     

    CREATE OR REPLACE VIEW ADMIN.FIND_SHOOT_1MIN
    (CODE, DT, ST_PRICE, EN_PRICE, MIN_PRICE,
     MAX_PRICE, TRADE_VOLUME, TRADE_CNT, PCNT_BY_ST, PCNT_BY_MAX,
     PCNT_BY_ST_MAX)
    AS
    SELECT A1.CODE CODE,       
           A1.DATETIME DT,
           A1.ST_PRICE ST_PRICE,
           A1.EN_PRICE EN_PRICE,
           A1.MIN_PRICE MIN_PRICE,
           A1.MAX_PRICE MAX_PRICE,
           A1.TRADE_VOLUME TRADE_VOLUME,
           A1.TRADE_CNT TRADE_CNT,
           ROUND ( ( (A1.EN_PRICE - A1.ST_PRICE) / A1.EN_PRICE) * 100, 2) AS PCNT_BY_ST,
           ROUND ( ( (A1.EN_PRICE - A1.MAX_PRICE) / A1.EN_PRICE) * 100, 2) AS PCNT_BY_MAX,
           ROUND ( ( (A1.MAX_PRICE - A1.ST_PRICE) / A1.MAX_PRICE) * 100, 2) AS PCNT_BY_ST_MAX
      FROM (  SELECT A.CODE,
                     TO_CHAR (MAX (A.DATETIME), 'YYYYMMDDHH24MISS') DATETIME,
                     MIN (TRADE_PRICE) KEEP (DENSE_RANK FIRST ORDER BY SEQ ASC) AS ST_PRICE,
                     MAX (TRADE_PRICE) KEEP (DENSE_RANK FIRST ORDER BY SEQ DESC) AS EN_PRICE,
                     MIN (TRADE_PRICE) MIN_PRICE,
                     MAX (TRADE_PRICE) MAX_PRICE,
                     SUM (TRADE_VOLUME) TRADE_VOLUME,
                     COUNT (TRADE_PRICE) TRADE_CNT
                FROM TICKER_DATA A
               WHERE A.DATETIME >= TO_DATE ( TO_CHAR (CURRENT_DATE - 0 / 24 / 60, 'YYYYMMDDHH24MI') || '00', 'YYYYMMDDHH24MISS')
                 AND A.DATETIME < TO_DATE ( TO_CHAR (CURRENT_DATE + 1 / 24 / 60, 'YYYYMMDDHH24MI') || '00', 'YYYYMMDDHH24MISS')
            GROUP BY A.CODE) A1
    ;

    위의 쿼리를 DBWeaver를 이용해 실행하면 FIND_SHOOT_1MIN이라는 VIEW가 생성되게 되고 해당 뷰를 조회하면 여러가지 정보를 얻을 수 있습니다.

     

    ① CODE : 종목코드

    ② DT : 최종 데이터의 시간

    ③ ST_PRICE : 시작가

    ④ EN_PRICE : 종가(현재가)

    ⑤ MIN_PRICE : 최저가

    ⑥ MAX_PRICE : 최고가

    ⑦ TRADE_VOLUME : 거래량

    ⑧ TRADE_CNT : 거래건수

    ⑨ PCNT_BY_ST : 시작가 대비 상승률

    ⑩ PCNT_BY_MAX : 최고가 대비 상승률

    ⑪ PCNT_BY_ST_MAX : 시작가 대비 최고가의 상승률

     

    이 중에서 급등주를 찾기위해서 사용할 컬럼은 '⑨ PCNT_BY_ST : 시작가 대비 상승률' 입니다. 1분봉 기준으로 시작가 대비 급등한 종목을 찾는데 사용할 수 있습니다.

     

    급등주 찾기 프로그램(find_shoot.py)

    반응형
    import os
    import sys
    import time
    import logging
    import traceback
    import cx_Oracle
    
    # 공통 모듈 Import
    sys.path.append(os.path.dirname(os.path.dirname(__file__)))
    from module import upbit
    
    
    # -----------------------------------------------------------------------------
    # - Name : start_find_shoot
    # - Desc : 급등주 찾기 로직
    # -----------------------------------------------------------------------------
    def start_find_shoot():
        try:
    
            data_cnt = 0
    
            # DB 연결
            conn = cx_Oracle.connect(upbit.get_env_keyvalue('p_username'), upbit.get_env_keyvalue('p_password'), upbit.get_env_keyvalue('p_service'))
    
            # 커서 획득
            c = conn.cursor()
    
            """         
             WHERE A.PCNT_BY_ST > 10.0                      -- 시작가 현재 상승률 > 10.0%
               AND A.TRADE_CNT > 500                        -- 거래건수 > 500
             ORDER BY A.PCNT_BY_ST DESC                       -- 시작가 대비 현재 상승률 상위순으로 정렬
    
            """
    
            sql = "SELECT A.CODE \
                  ,A.DT \
                  ,A.ST_PRICE \
                  ,A.EN_PRICE \
                  ,A.MIN_PRICE \
                  ,A.MAX_PRICE \
                  ,A.TRADE_VOLUME \
                  ,A.TRADE_CNT \
                  ,A.PCNT_BY_ST \
                  ,A.PCNT_BY_MAX \
                  ,A.PCNT_BY_ST_MAX \
              FROM FIND_SHOOT_1MIN A \
             WHERE A.PCNT_BY_ST > 10.0 \
               AND A.TRADE_CNT > 500 \
             ORDER BY A.PCNT_BY_ST DESC"
    
            # ----------------------------------------------------------------------
            # 반복 수행
            # ----------------------------------------------------------------------
            while True:
    
                # SQL 수행
                c.execute(sql)
    
                """
                0  ,A.CODE \
                1  ,A.DT \
                2  ,A.ST_PRICE \
                3  ,A.EN_PRICE \
                4  ,A.MIN_PRICE \
                5  ,A.MAX_PRICE \
                6  ,A.TRADE_VOLUME \
                7  ,A.TRADE_CNT \
                8  ,A.PCNT_BY_ST \
                9  ,A.PCNT_BY_MAX \
                10 ,A.PCNT_BY_ST_MAX \    
                """
    
                rows = c.fetchall()
    
                # 대상조건 발견 시
                if len(rows) > 0:
    
                    # 발견된 로우만큼 처리
                    # 급등주 발견 텔레그렘 메세지 발송
                    for row in rows:
                        logging.info(row)
    
                        message = '[급등주 발견]'
                        message = message + '\n\n종목코드:' + str(row[0])
                        message = message + '\n상승률:' + str(row[8])
                        message = message + '\n거래건수:' + str(row[7])
    
                        upbit.send_telegram_message(message)
    
                        logging.info('메세지 발송 완료!')
                        logging.info(message)
    
                    # 중복 메세지 발송하지 않기 위해 60초간 Sleep
                    time.sleep(60)
    
                # 조회 건수 체크
                data_cnt = data_cnt + 1
    
                if data_cnt % 1000 == 0:
                    logging.info("Checking...[" + str(data_cnt) + "][" + str(len(rows)) + "]")
    
        # ---------------------------------------
        # 모든 함수의 공통 부분(Exception 처리)
        # ----------------------------------------
        except Exception:
            raise
    
    
    # -----------------------------------------------------------------------------
    # - Name : main
    # - Desc : 메인
    # -----------------------------------------------------------------------------
    if __name__ == '__main__':
    
        # noinspection PyBroadException
        try:
    
            print("***** USAGE ******")
            print("[1] 로그레벨(D:DEBUG, E:ERROR, 그외:INFO)")
    
            # 로그레벨(D:DEBUG, E:ERROR, 그외:INFO)
            log_level = sys.argv[1].upper()
            upbit.set_loglevel(log_level)
    
            if log_level == '':
                logging.error("입력값 오류!")
                sys.exit(-1)
    
            logging.info("***** INPUT ******")
            logging.info("[1] 로그레벨(D:DEBUG, E:ERROR, 그외:INFO):" + str(log_level))
    
            # ---------------------------------------------------------------------
            # Logic Start!
            # ---------------------------------------------------------------------
            start_find_shoot()
    
        except KeyboardInterrupt:
            logging.error("KeyboardInterrupt Exception 발생!")
            logging.error(traceback.format_exc())
            sys.exit(-100)
    
        except Exception:
            logging.error("Exception 발생!")
            logging.error(traceback.format_exc())
            sys.exit(-200)

     

    WHERE A.PCNT_BY_ST > 10.0                      -- 시작가 현재 상승률 > 10.0%
      AND A.TRADE_CNT > 500                        -- 거래건수 > 500
    ORDER BY A.PCNT_BY_ST DESC                     -- 시작가 대비 현재 상승률 상위순으로 정렬

    SQL쿼리의 WHERE절의 시작가 대배 현재 상승률이 10% 초과 이고 거래 건수가 500건을 초과하는 종목을 찾습니다.

     

    # 대상조건 발견 시
    if len(rows) > 0:
    
        # 발견된 로우만큼 처리
        # 급등주 발견 텔레그렘 메세지 발송
        for row in rows:
            logging.info(row)
    
            message = '[급등주 발견]'
            message = message + '\n\n종목코드:' + str(row[0])
            message = message + '\n상승률:' + str(row[8])
            message = message + '\n거래건수:' + str(row[7])
    
            upbit.send_telegram_message(message)
    
            logging.info('메세지 발송 완료!')
            logging.info(message)
    
        # 중복 메세지 발송하지 않기 위해 60초간 Sleep
        time.sleep(60)

    대상 종목을 찾으면 여러 건일 수 있으므로 찾은 종목수 만큼 텔레그램을 통해 메세지를 보내도록 했습니다. 이 부분에 매수 로직을 넣으면 자동으로 매수까지 할 수 있습니다. 하지만 해당 부분은 신중히 모니터링을 해 보신 후 적용 하시는 것을 권장 드립니다.

     

    메세지를 한번 보내면 계속해서 보낼 수 있기 때문에 일단 60초간 멈추도록 했습니다. 이 부분도 추후 기존에 살펴보았던 메세지 보내는 기능을 조금 업그레이드 하여 개선을 해 볼 수 있도록 하겠습니다.

     

    # 조회 건수 체크
    data_cnt = data_cnt + 1
    
    if data_cnt % 1000 == 0:
        logging.info("Checking...[" + str(data_cnt) + "][" + str(len(rows)) + "]")

    한번 쿼리에 전 종목을 조회하고 이렇게 전 종목을 1000번 조회할 때마다 로그를 찍어 봤습니다. 대략적인 시간을 계산하기 위함인데요.

     

    1000번을 조회하는데 2~3초 정도가 걸리니 1초에 전 종목을 200~300회 이상 조회하게 됩니다. 전 종목을 한번 조회하는데 20~30초가 걸리는 것과는 속도의 차이가 많이 발생 합니다. 물론 데이터가 많이 쌓이거나 거래가 폭발하는 시점에는 조금씩 느려지기는 하지만 적어도 현재로써는 1초에 1번 이상은 조회가 가능한 것 같습니다.

     

    실행 결과

    갑자기 10%가 상승하는 종목은 자주 나오는 것이 아니기 때문에 로직의 조건을 조금 낮추어 0.9% 이상 상승 및 거래건수 10건 이상으로 바꾸어 보니 위와 같이 대상이 발견되고 텔레그램으로 메세지를 수신 받을 수 있었습니다.

     

     

    공통코드

    공통 코드의 일부가 조금 수정되어 업로드 하오니 참고 부탁 드립니다.

     

    upbit.py
    0.07MB

     

    마치며

    웹소켓 데이터만을 이용해서도 9시에 급등하는 종목을 찾을 수는 있습니다. 9시에 상승률이 리셋되기 때문인데요. 9시 이후에 갑자기 상승하는 종목을 찾는 건 DB를 이용하지 않거나 메모리에 데이터를 적재하고 사용하지 않으면 찾기가 어려울 수 있습니다.

     

    오늘 살펴본 급등주 찾는 로직은 여러가지 방면으로 사용될 수 있습니다. 달리는 말에 올라타서 3~5%만 수익을 내고 나오는 로직이나 바이낸스에 같은 로직을 적용하면 급등했다 되돌아오는 종목들이 많기 때문에 급등시 숏 포지션을 매수하여 수익을 내는 방법에 사용할 수도 있습니다.

     

    하지만 경험상 지속되는 로직은 없는 것 같으니 시장을 항상 면밀히 살펴보신 후 테마가 어떻게 흐르는지를 판단하여 해당 테마에 맞는 로직으로 대응하는 것이 현명할 것이라 생각 됩니다.

     

    블로그를 구독하시면 소식을 조금 더 빨리 받아 보실 수 있습니다. 감사합니다.

     

    반응형