[TradingBot] Stock Trading Bot


개요

주식 시장 자동매매 봇을 만들어봅니다.

대신증권 Creonplus API를 이용한 주식 시장(ETF) 자동매매 봇입니다. 우선 자동 로그인을 하도록 해 봅니다. 필요한 라이브러리를 가져오고, API를 쓸 수 있도록 실행 후 로그인하게 합니다.

from pywinauto import application
import time
import os
os.system('taskkill /IM coStarter* /F /T')
os.system('taskkill /IM CpStart* /F /T')
os.system('taskkill /IM DibServer* /F /T')
os.system('wmic process where "name like \'%coStarter%\'" call terminate')
os.system('wmic process where "name like \'%CpStart%\'" call terminate')
os.system('wmic process where "name like \'%DibServer%\'" call terminate')
time.sleep(5)
app = application.Application()
app.start('C:\CREON\STARTER\coStarter.exe /prj:cp /id:your id /pwd:your pw /pwdcert:your cert pw /autostart')
time.sleep(60)

로그인 되었습니다. 원래 다른 파일에 저장되어 있는데 편의상 합쳤습니다. 필요한 라이브러리를 가져옵니다. 이번에도 거래 로그는 Telegram으로 받아보겠습니다.

import os, sys, ctypes
import win32com.client
import pandas as pd
from datetime import datetime
import telegram
import time, calendar

Telegram Bot 사용을 위한 정보와 거래 로그를 메시지로 보내는 함수입니다.

# 텔레그램 token (use your token)
token = 'use your token'
bot = telegram.Bot(token=token)
def telegramlog(message):
    # 함수로 받은 문자열을 파이썬에서 출력 + 텔레그램 메시지로 전송
    print(datetime.now().strftime('[%m/%d %H:%M:%S]'), message)
    strbuf = datetime.now().strftime('[%m/%d %H:%M:%S] ') + message
    
    # Use your telegram chat_id
    bot.sendMessage(chat_id = 'use your id', text = strbuf)

def printlog(message, *args):
    # 함수로 받은 문자열을 파이썬에서 출력
    print(datetime.now().strftime('[%m/%d %H:%M:%S]'), message, *args)

잘 알려진 트레이딩 전략인 변동성 돌파 전략에 상승장일 때만 진입하도록 5일과 10일 이동 평균 필터를 추가한 전략입니다. 거래 대상은 국내 상장 ETF 중 거래량이 많은 편인 KODEX 인버스, KODEX 코스닥150 선물 인버스, KODEX 200, KODEX 코스닥150, KODEX WTI 원유선물, KODEX 은선물입니다. 과거에 선물 시장에서 훌륭한 성과를 냈다고 알려진 전략이지만, 시장 자체의 변동성이 커야 한다는 약점이 있습니다.

# 크레온 플러스 공통 OBJECT
cpCodeMgr = win32com.client.Dispatch('CpUtil.CpStockCode')
cpStatus = win32com.client.Dispatch('CpUtil.CpCybos')
cpTradeUtil = win32com.client.Dispatch('CpTrade.CpTdUtil')
cpStock = win32com.client.Dispatch('DsCbo1.StockMst')
cpOhlc = win32com.client.Dispatch('CpSysDib.StockChart')
cpBalance = win32com.client.Dispatch('CpTrade.CpTd6033')
cpCash = win32com.client.Dispatch('CpTrade.CpTdNew5331A')
cpOrder = win32com.client.Dispatch('CpTrade.CpTd0311')  

# 크레온 플러스 연결 상태 점검
def check_creon_system():
    
    # 관리자 권한으로 프로세스 실행 여부
    if not ctypes.windll.shell32.IsUserAnAdmin():
        printlog('check_creon_system() : admin user -> FAILED')
        return False
 
    # 연결 여부 체크
    if (cpStatus.IsConnect == 0):
        printlog('check_creon_system() : connect to server -> FAILED')
        return False
 
    # 주문 관련 초기화 - 계좌 관련 코드가 있을 때만 사용
    if (cpTradeUtil.TradeInit(0) != 0):
        printlog('check_creon_system() : init trade -> FAILED')
        return False
    return True

# 받은 종목 현재가, 매수/매도 호가 체크
def get_current_price(code):
    
    cpStock.SetInputValue(0, code)  # 종목코드에 대한 가격 정보
    cpStock.BlockRequest()
    item = {}
    item['cur_price'] = cpStock.GetHeaderValue(11)   # 현재가(종가)
    item['ask'] =  cpStock.GetHeaderValue(16)        # 매도호가
    item['bid'] =  cpStock.GetHeaderValue(17)        # 매수호가    
    return item['cur_price'], item['ask'], item['bid']

# 받은 종목 OHLC(open high low close) 가격 정보 qty 개수만큼(최근 qty일치) 반환
def get_ohlc(code, qty):

    cpOhlc.SetInputValue(0, code)           # 종목코드
    cpOhlc.SetInputValue(1, ord('2'))        # ord가 1이면 기간, ord가 2이면 개수
    cpOhlc.SetInputValue(4, qty)             # 요청개수 (100이면 최근 100일치)
    cpOhlc.SetInputValue(5, [0, 2, 3, 4, 5]) # 0:날짜, 2~5:OHLC
    cpOhlc.SetInputValue(6, ord('D'))        # D:일단위
    cpOhlc.SetInputValue(9, ord('1'))        # 0:무수정주가, 1:수정주가
    cpOhlc.BlockRequest()
    count = cpOhlc.GetHeaderValue(3)   # 3:수신개수
    columns = ['open', 'high', 'low', 'close']
    index = []
    rows = []
    for i in range(count): 
        # 날짜
        index.append(cpOhlc.GetDataValue(0, i)) 
        # OHLC
        rows.append([cpOhlc.GetDataValue(1, i), cpOhlc.GetDataValue(2, i),
            cpOhlc.GetDataValue(3, i), cpOhlc.GetDataValue(4, i)]) 
    df = pd.DataFrame(rows, columns=columns, index=index) 
    return df

# 받은 종목 종목명, 들고 있는 수량 반환
def get_stock_balance(code):

    cpTradeUtil.TradeInit()
    acc = cpTradeUtil.AccountNumber[0]      # 계좌번호
    accFlag = cpTradeUtil.GoodsList(acc, 1) # -1:전체, 1:주식, 2:선물/옵션
    cpBalance.SetInputValue(0, acc)         # 계좌번호
    cpBalance.SetInputValue(1, accFlag[0])  # 상품구분 - 주식 상품 중 첫번째
    cpBalance.SetInputValue(2, 50)          # 요청 건수(최대 50)
    cpBalance.BlockRequest()     
    if code == 'ALL':
        telegramlog('계좌명: ' + str(cpBalance.GetHeaderValue(0)))
        telegramlog('결제잔고수량 : ' + str(cpBalance.GetHeaderValue(1)))
        telegramlog('평가금액: ' + str(cpBalance.GetHeaderValue(3)))
        telegramlog('평가손익: ' + str(cpBalance.GetHeaderValue(4)))
        telegramlog('종목수: ' + str(cpBalance.GetHeaderValue(7)))
    stocks = []
    for i in range(cpBalance.GetHeaderValue(7)):
        stock_code = cpBalance.GetDataValue(12, i)  # 종목코드
        stock_name = cpBalance.GetDataValue(0, i)   # 종목명
        stock_qty = cpBalance.GetDataValue(15, i)   # 수량
        if code == 'ALL':
            telegramlog(str(i+1) + ' ' + stock_code + '(' + stock_name + ')' 
                + ':' + str(stock_qty))
            stocks.append({'code': stock_code, 'name': stock_name, 
                'qty': stock_qty})
        if stock_code == code:  
            return stock_name, stock_qty
    if code == 'ALL':
        return stocks
    else:
        stock_name = cpCodeMgr.CodeToName(code)
        return stock_name, 0

# 증거금 100% 기준 주문 가능 금액
def get_current_cash():

    cpTradeUtil.TradeInit()
    acc = cpTradeUtil.AccountNumber[0]    # 계좌번호
    accFlag = cpTradeUtil.GoodsList(acc, 1) # -1:전체, 1:주식, 2:선물/옵션
    cpCash.SetInputValue(0, acc)              # 계좌번호
    cpCash.SetInputValue(1, accFlag[0])      # 상품구분 - 주식 상품 중 첫번째
    cpCash.BlockRequest() 
    return cpCash.GetHeaderValue(9) # 증거금 100% 주문 가능 금액

# 매수 목표가
def get_target_price(code):

    try:
        time_now = datetime.now()
        str_today = time_now.strftime('%Y%m%d')
        ohlc = get_ohlc(code, 10)
        
        # 당일 날짜 == ohlc 불러온 것의 최근 날짜
        if str_today == str(ohlc.iloc[0].name):
            today_open = ohlc.iloc[0].open 
            lastday = ohlc.iloc[1]  # 전일
        
        # 당일 날짜 != ohlc 불러온 것의 최근 날짜
        else:
            lastday = ohlc.iloc[0]    
            
            # 직전 거래일 종가
            today_open = lastday[3]
        lastday_high = lastday[1]
        lastday_low = lastday[2]
        
        # 시가에서 고가-저가 range의 0.5배 이상 오르면 매수할 것 (0.5 대신 최근 n일의 noise 평균을 사용한다거나 할 수 있음)
        # noise = 1 - abs(시가-종가)/(고가-저가)
        target_price = today_open + (lastday_high - lastday_low) * 0.5
        return target_price
    except Exception as ex:
        telegramlog("`get_target_price() -> exception! " + str(ex) + "`")
        return None
    
# 받은 종목 window일 이동평균가격 반환 
def get_movingaverage(code, window):
    
    try:
        time_now = datetime.now()
        str_today = time_now.strftime('%Y%m%d')
        ohlc = get_ohlc(code, 20)
        if str_today == str(ohlc.iloc[0].name):
            lastday = ohlc.iloc[1].name
        else:
            lastday = ohlc.iloc[0].name
        closes = ohlc['close'].sort_index()         
        ma = closes.rolling(window=window).mean()
        return ma.loc[lastday]
    except Exception as ex:
        telegramlog('get_movingavrg(' + str(window) + ') -> exception! ' + str(ex))
        return None    

# 받은 종목을 지정가로 기본 조건 매수
def buy_etf(code):

    try:
        global bought_list      # 함수 내에서 값 변경을 하기 위해 global로 지정
        if code in bought_list: # 매수 완료 종목이면 더 이상 안 사도록 함수 종료
            #printlog('code:', code, 'in', bought_list)
            return False
        time_now = datetime.now()
        current_price, ask_price, bid_price = get_current_price(code) 
        target_price = get_target_price(code)    # 매수 목표가
        ma5_price = get_movingaverage(code, 5)   # 5일 이동평균가
        ma10_price = get_movingaverage(code, 10) # 10일 이동평균가
        buy_qty = 0        # 매수할 수량 초기화
        if ask_price > 0:  # 매수호가가 존재하면   
            buy_qty = buy_amount // ask_price  
        stock_name, stock_qty = get_stock_balance(code)  # 종목명과 보유수량 조회
        #printlog('bought_list:', bought_list, 'len(bought_list):',
        #    len(bought_list), 'target_buy_count:', target_buy_count)
        
        # 목표가 돌파 + 5일 이평과 10일 이평 위에 있어서 단기 상승 추세
        if current_price > target_price and current_price > ma5_price \
            and current_price > ma10_price:  
            printlog(stock_name + '(' + str(code) + ') ' + str(buy_qty) +
                'EA : ' + str(current_price) + ' meets the buy condition!`')            
            cpTradeUtil.TradeInit()
            acc = cpTradeUtil.AccountNumber[0]      # 계좌번호
            accFlag = cpTradeUtil.GoodsList(acc, 1) # -1:전체,1:주식,2:선물/옵션                
            
            # 지정가 기본 조건 주문
            cpOrder.SetInputValue(0, "2")        # 2: 매수
            cpOrder.SetInputValue(1, acc)        # 계좌번호
            cpOrder.SetInputValue(2, accFlag[0]) # 상품구분 - 주식 상품 중 첫번째
            cpOrder.SetInputValue(3, code)       # 종목코드
            cpOrder.SetInputValue(4, buy_qty)    # 매수할 수량
            cpOrder.SetInputValue(5, current_price) # 주문 단가 (지정가 주문이라 필요)
            cpOrder.SetInputValue(7, "0")        # 주문조건 0:기본, 1:IOC, 2:FOK
            cpOrder.SetInputValue(8, "01")        # 주문호가 1:보통, 3:시장가
                                                 # 5:조건부, 12:최유리, 13:최우선 
            # 매수 주문 요청
            ret = cpOrder.BlockRequest() 
            printlog('지정가 일반 매수 ->', stock_name, code, buy_qty, '->', ret)
            if ret == 4:
                remain_time = cpStatus.LimitRequestRemainTime
                printlog('주의: 연속 주문 제한에 걸림. 대기 시간:', remain_time/1000)
                time.sleep(remain_time/1000) 
                return False
            time.sleep(2)
            printlog('현금주문 가능금액 :', buy_amount)
            
            # 이미 산 것인지, 몇 주나 들고 있는지 체크
            stock_name, bought_qty = get_stock_balance(code)
            printlog('get_stock_balance :', stock_name, stock_qty)
            
            if bought_qty > 0:
                bought_list.append(code)
                telegramlog("`buy_etf("+ str(stock_name) + ' : ' + str(code) + 
                    ") -> " + str(bought_qty) + "EA bought!" + "`")
    except Exception as ex:
        telegramlog("`buy_etf("+ str(code) + ") -> exception! " + str(ex) + "`")

# 오버나잇 없이 당일 마무리 위해 시장가로 기본 조건 매도
def sell_all():

    try:
        cpTradeUtil.TradeInit()
        acc = cpTradeUtil.AccountNumber[0]       # 계좌번호
        accFlag = cpTradeUtil.GoodsList(acc, 1)  # -1:전체, 1:주식, 2:선물/옵션   
        while True:    
            stocks = get_stock_balance('ALL') 
            total_qty = 0 
            for s in stocks:
                total_qty += s['qty'] 
            if total_qty == 0:
                return True
            for s in stocks:
                if s['qty'] != 0:                  
                    cpOrder.SetInputValue(0, "1")         # 1:매도, 2:매수
                    cpOrder.SetInputValue(1, acc)         # 계좌번호
                    cpOrder.SetInputValue(2, accFlag[0])  # 주식상품 중 첫번째
                    cpOrder.SetInputValue(3, s['code'])   # 종목코드
                    cpOrder.SetInputValue(4, s['qty'])    # 매도수량
                    cpOrder.SetInputValue(7, "0")   # 조건 0:기본, 1:IOC, 2:FOK
                    cpOrder.SetInputValue(8, "03")  # 호가 1:보통, 3:시장가, 5:조건부, 12:최유리, 13:최우선 
                    # 시장가 기본 조건 매도 주문 요청
                    ret = cpOrder.BlockRequest()
                    printlog('시장가 기본 조건 매도', s['code'], s['name'], s['qty'], 
                        '-> cpOrder.BlockRequest() -> returned', ret)
                    if ret == 4:
                        remain_time = cpStatus.LimitRequestRemainTime
                        printlog('주의: 연속 주문 제한, 대기시간:', remain_time/1000)
                time.sleep(1)
            time.sleep(30)
    except Exception as ex:
        telegramlog("sell_all() -> exception! " + str(ex))
if __name__ == '__main__': 
    try:
        symbol_list = ['A114800','A251340','A069500','A229200','A261220','A144600']
        bought_list = []     # 매수 완료된 종목 리스트
        target_buy_count = 2 # 최대 매수 종목 수 
        buy_percent = 0.3    # 목표치 다 사더라도 현금 남을 것. 2종목에 각각 잔고*0.3에 해당하는 금액까지 주문 가능함
        printlog('check_creon_system() :', check_creon_system())  # 크레온 접속 점검
        stocks = get_stock_balance('ALL')      # 보유한 모든 종목 조회
        total_cash = int(get_current_cash())   # 100% 증거금 주문 가능 금액 조회
        buy_amount = total_cash * buy_percent  # 종목별 주문 금액 계산
        printlog('100% 증거금 주문 가능 금액 :', total_cash)
        printlog('종목별 주문 비율 :', buy_percent)
        printlog('종목별 주문 금액 :', buy_amount)
        printlog('시작 시간 :', datetime.now().strftime('%m/%d %H:%M:%S'))
        soldout = False;

        while True:
            t_now = datetime.now()
            t_9 = t_now.replace(hour=9, minute=0, second=0, microsecond=0)
            t_start = t_now.replace(hour=9, minute=5, second=0, microsecond=0)
            t_sell = t_now.replace(hour=15, minute=15, second=0, microsecond=0)
            t_exit = t_now.replace(hour=15, minute=20, second=0,microsecond=0)
            today = datetime.today().weekday()
            if today == 5 or today == 6:  # 토요일이나 일요일이면 자동 종료
                printlog('Today is', 'Saturday.' if today == 5 else 'Sunday.')
                sys.exit(0)
            if t_9 < t_now < t_start and soldout == False:
                soldout = True
                sell_all()
            if t_start < t_now < t_sell :  # AM 09:05 ~ PM 03:15 : 매수
                for sym in symbol_list:
                    if len(bought_list) < target_buy_count:
                        buy_etf(sym)
                        time.sleep(1)
                if t_now.minute == 30 and 0 <= t_now.second <= 5: 
                    get_stock_balance('ALL')
                    time.sleep(5)
            if t_sell < t_now < t_exit:  # PM 03:15 ~ PM 03:20 : 일괄 매도
                if sell_all() == True:
                    telegramlog('`sell_all() returned True -> self-destructed!`')
                    sys.exit(0)
            if t_exit < t_now:  # PM 03:20 ~ :프로그램 종료
                telegramlog('`self-destructed!`')
                sys.exit(0)
            time.sleep(3)
    except Exception as ex:
        telegramlog('`main -> exception! ' + str(ex) + '`')





© 2021.03. by JacobJinwonLee

Powered by theorydb