INVESTAR / StockAnalysisInPython

456 stars 413 forks source link

변동성돌파전략 자동매매 실행 도중 크레온 재접속 시 중복주문 방지 방법이 궁금합니다. #86

Open 31indiana opened 3 years ago

31indiana commented 3 years ago

안녕하세요. 책을 보며 잘 따라하고 있습니다. 감사합니다. 그런데 정상적인 실행 도중에 네트워크 오류 등의 이슈로 크레온 재접속 및 자동매매 재실행 시 이미 목표가에 주문 및 체결되었던 주문이 다시 현재 예수금 기준으로 재주문 및 체결이 되고 있습니다. 혹시 이를 방지할 수 있는 방법이 있을까요? 그리고 매수를 희망하는 종목별로 k값을 좀 다르게 적용하고 싶은데 방법이 궁금합니다. 답변 기다리겠습니다.

INVESTAR commented 3 years ago
  1. 네트워크 오류나 크레온 시스템에 문제가 발생해서 자동매매 프로그램을 재실행하게 되면, 매수했던 종목을 다시 매수하게 되는 경우가 발생할 수 있습니다. 이를 방지하려면 매수할 때마다 매수 종목 리스트를 파일로 저장을 해놓는 수 밖에 없을 것 같습니다. 자동매매 프로그램이 최초 실행될 때 매수 종목 리스트 파일을 읽어서, target_buy_count나 buy_percent 같은 값을 조정하시고, 실제 매수 처리 로직에서는 매수 종목 리스트 파일에 존재하는 종목은 매수하지 않도록 수정해 주셔야 합니다. 그리고, 장마감 이후에 자동매매 프로그램이 자동 종료되는 시점에서 매수 종목 리스트 파일을 삭제하는 방식으로 처리를 해야 할 것 같습니다.

  2. 그리고, 매수 희망 종목 별로 k값을 다르게 하고 싶으시다면 기존의 리스트 대신 아래처럼 딕셔너리를 사용하시는 게 좋을 것 같습니다. symbol_list = {'A028300': 0.5, 'A068270': 0.7, ...}

31indiana commented 3 years ago

네. 아직 코딩이 익숙치않아 할 수 있을지 걱정이지만 답변 참고해서 해보겠습니다. 감사합니다.

2021년 6월 20일 (일) 오전 8:45, HwangHoo Kim @.***>님이 작성:

1.

네트워크 오류나 크레온 시스템에 문제가 발생해서 자동매매 프로그램을 재실행하게 되면, 매수했던 종목을 다시 매수하게 되는 경우가 발생할 수 있습니다. 이를 방지하려면 매수할 때마다 매수 종목 리스트를 파일로 저장을 해놓는 수 밖에 없을 것 같습니다. 자동매매 프로그램이 최초 실행될 때 매수 종목 리스트 파일을 읽어서, target_buy_count나 buy_percent 같은 값을 조정하시고, 실제 매수 처리 로직에서는 매수 종목 리스트 파일에 존재하는 종목은 매수하지 않도록 수정해 주셔야 합니다. 그리고, 장마감 이후에 자동매매 프로그램이 자동 종료되는 시점에서 매수 종목 리스트 파일을 삭제하는 방식으로 처리를 해야 할 것 같습니다. 2.

그리고, 매수 희망 종목 별로 k값을 다르게 하고 싶으시다면 기존의 리스트 대신 아래처럼 딕셔너리를 사용하시는 게 좋을 것 같습니다. symbol_list = {'A028300': 0.5, 'A068270': 0.7, ...}

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/INVESTAR/StockAnalysisInPython/issues/86#issuecomment-864476780, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQMLDC3P5MP742VKTP5QLQDTTUT2VANCNFSM46477TRQ .

31indiana commented 3 years ago

안녕하세요. 저자님. 답변 주신 내용을 참고해서 첨부해드리는 파일과 같이 시도해보았는데 다음과 같은 부분이 해결이 되지 않아 이렇게 메일로 질문 드립니다. 제가 시도한 부분을 ###################으로 표시해드렸습니다.

  1. 매수한 종목을 파일에 업데이트시 엑셀에 [] 와 같이 대괄호 2개가 A1셀에 입력되고 B1 , C1에 각각 종목코드가 입력되네요. 대괄호 두 개를 생기지 않게 할 수는 없을까요?

  2. bought_ticker_list 파일에 매수된 종목 코드가 있어도 재접속 또는 재실행 시 같은 종목이 계속 재매수가 됩니다.

  3. 그리고 종목별고 k값을 다르게 적용하기 위해선 target_price를 k값 별로 계산해서 딕셔너리를 이용해야 할까요? 재주문은 일단 시도라도 해봤는데 종목별로 k값을 다르게 하는 것은 감이 잘 안오네요 ㅜ

답변 부탁드리겠습니다. 감사합니다.

2021년 6월 20일 (일) 오후 1:47, JAE YOUNG PARK @.***>님이 작성:

네. 아직 코딩이 익숙치않아 할 수 있을지 걱정이지만 답변 참고해서 해보겠습니다. 감사합니다.

2021년 6월 20일 (일) 오전 8:45, HwangHoo Kim @.***>님이 작성:

1.

네트워크 오류나 크레온 시스템에 문제가 발생해서 자동매매 프로그램을 재실행하게 되면, 매수했던 종목을 다시 매수하게 되는 경우가 발생할 수 있습니다. 이를 방지하려면 매수할 때마다 매수 종목 리스트를 파일로 저장을 해놓는 수 밖에 없을 것 같습니다. 자동매매 프로그램이 최초 실행될 때 매수 종목 리스트 파일을 읽어서, target_buy_count나 buy_percent 같은 값을 조정하시고, 실제 매수 처리 로직에서는 매수 종목 리스트 파일에 존재하는 종목은 매수하지 않도록 수정해 주셔야 합니다. 그리고, 장마감 이후에 자동매매 프로그램이 자동 종료되는 시점에서 매수 종목 리스트 파일을 삭제하는 방식으로 처리를 해야 할 것 같습니다. 2.

그리고, 매수 희망 종목 별로 k값을 다르게 하고 싶으시다면 기존의 리스트 대신 아래처럼 딕셔너리를 사용하시는 게 좋을 것 같습니다. symbol_list = {'A028300': 0.5, 'A068270': 0.7, ...}

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/INVESTAR/StockAnalysisInPython/issues/86#issuecomment-864476780, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQMLDC3P5MP742VKTP5QLQDTTUT2VANCNFSM46477TRQ .

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

######################################################################################## ######################################################################################## import csv ######################################################################################## ########################################################################################

def post_message(token, channel, text): response = requests.post("https://slack.com/api/chat.postMessage", headers={"Authorization": "Bearer "+token}, data={"channel": channel,"text": text} )

myToken = "" def dbgout(message): """인자로 받은 문자열을 파이썬 셸과 슬랙으로 동시에 출력한다.""" print(datetime.now().strftime('[%m/%d %H:%M:%S]'), message) strbuf = datetime.now().strftime('[%m/%d %H:%M:%S] ') + message post_message(myToken,"#autotrading", strbuf)

def printlog(message, args): """인자로 받은 문자열을 파이썬 셸에 출력한다.""" print(datetime.now().strftime('[%m/%d %H:%M:%S]'), message, args)

########################################################################################### ###########################################################################################

파일 읽어오기

with open('bought_ticker_list.csv', 'r', newline='') as f: reader = csv.reader(f) bought_ticker_list = list(reader) ########################################################################################### ###########################################################################################

크레온 플러스 공통 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']

def get_ohlc(code, qty): """인자로 받은 종목의 OHLC 가격 정보를 qty 개수만큼 반환한다.""" cpOhlc.SetInputValue(0, code) # 종목코드 cpOhlc.SetInputValue(1, ord('2')) # 1:기간, 2:개수 cpOhlc.SetInputValue(4, qty) # 요청개수 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)) 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': dbgout('계좌명: ' + str(cpBalance.GetHeaderValue(0))) dbgout('결제잔고수량 : ' + str(cpBalance.GetHeaderValue(1))) dbgout('평가금액: ' + str(cpBalance.GetHeaderValue(3))) dbgout('평가손익: ' + str(cpBalance.GetHeaderValue(4))) dbgout('종목수: ' + 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': dbgout(str(i+1) + ' ' + stock_code + '(' + stock_name + ')'

def get_current_cash(): """증거금 100% 주문 가능 금액을 반환한다.""" 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% 주문 가능 금액

################변동성 돌파 전략 구현 start############## def get_target_price(code): """매수 목표가를 반환한다.""" try: time_now = datetime.now() str_today = time_now.strftime('%Y%m%d') ohlc = get_ohlc(code, 10) if str_today == str(ohlc.iloc[0].name): today_open = ohlc.iloc[0].open lastday = ohlc.iloc[1] else: lastday = ohlc.iloc[0]
today_open = lastday[3] lastday_high = lastday[1] lastday_low = lastday[2] target_price = today_open + (lastday_high - lastday_low) * 0.1 #k:계수 0.5 return target_price except Exception as ex: dbgout("get_target_price() -> exception! " + str(ex) + "") return None

################변동성 돌파 전략 구현 finish##############

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: dbgout('get_movingaverage(' + str(window) + ') -> exception! ' + str(ex)) return None

def buy_etf(code): """인자로 받은 종목을 최유리 지정가 FOK 조건으로 매수한다.""" try: global bought_list # 함수 내에서 값 변경을 하기 위해 global로 지정 if code in bought_list: # 매수 완료 종목이면 더 이상 안 사도록 함수 종료

printlog('code:', code, 'in', bought_list)

        return False

########################################################################################### ###########################################################################################

앞서 할당된 변수(bought_ticker_list)에 code 있으면 함수종료

    global bought_ticker_list
    if code in bought_ticker_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)     
    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:선물/옵션                
        # 최유리 FOK 매수 주문 설정
        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(7, "2")        # 주문조건 0:기본, 1:IOC, 2:FOK
        cpOrder.SetInputValue(8, "12")       # 주문호가 1:보통, 3:시장가
                                             # 5:조건부, 12:최유리, 13:최우선 
        # 매수 주문 요청
        ret = cpOrder.BlockRequest() 
        printlog('최유리 FoK 매수 ->', 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:                      #수량이 0보다 크면 
            bought_list.append(code)            #보유수량리스트에 업뎃.
            dbgout("`buy_etf("+ str(stock_name) + ' : ' + str(code) + 
                ") -> " + str(bought_qty) + "EA bought!" + "`")   #현재 어떤 종목을 얼마나 샀는지 슬랙메시지로 전송

############################################################################################ ############################################################################################

매수한 종목 리스트를 파일로 저장하기

            buy_ticker = [code]
            bought_ticker_list.extend(buy_ticker)
            with open('bought_ticker_list.csv', 'w', newline='') as f:
                writer = csv.writer(f)
                writer.writerow(bought_ticker_list)

############################################################################################ ############################################################################################

except Exception as ex:
    dbgout("`buy_etf("+ str(code) + ") -> exception! " + str(ex) + "`")

def sell_all(): """보유한 모든 종목을 최유리 지정가 IOC 조건으로 매도한다.""" 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, "1") # 조건 0:기본, 1:IOC, 2:FOK cpOrder.SetInputValue(8, "12") # 호가 12:최유리, 13:최우선

최유리 IOC 매도 주문 요청

                ret = cpOrder.BlockRequest()
                printlog('최유리 IOC 매도', 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:
    dbgout("sell_all() -> exception! " + str(ex))

if name == 'main': try: symbol_list = ['A233740', 'A226490'] #자동매매 원하는 종목티커

    #A233740:코스닥레버리지(kodex)
    #A226490:코스피(kodex) 

    bought_list = []     # 매수 완료된 종목 리스트

############################################################################################ ############################################################################################ target_buy_count = 2 - len(bought_ticker_list) # 매수할 최대 종목 수 buy_percent = 1/target_buy_count #종목별 분산비율 ############################################################################################ ############################################################################################

    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)   #ETF LP(유동성공급자)활동시간 기준09:00~15:15
        t_sell = t_now.replace(hour=15, minute=15, second=0, microsecond=0)  #ETF LP(유동성공급자)활동시간 기준09:00~15:15
        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:
                dbgout('`sell_all() returned True -> self-destructed!`')

############################################################################################ ############################################################################################

매도후 변수 및 파일내용삭제처리

                del bought_ticker_list[:]
                with open('bought_ticker_list.csv', 'w', newline='') as f:
                    writer = csv.writer(f)
                    writer.writerow(bought_ticker_list)

############################################################################################ ############################################################################################

                sys.exit(0)
        if t_exit < t_now:  # PM 03:20 ~ :프로그램 종료
            dbgout('`self-destructed!`')
            sys.exit(0)
        time.sleep(3)
except Exception as ex:
    dbgout('`main -> exception! ' + str(ex) + '`')
INVESTAR commented 3 years ago

본 깃허브는 책으로 전달할 수 없는 사항을 공유하기 위한 취지로 만들었습니다. 독자님의 코드 리뷰를 해드릴 경우, 형평성을 맞추려면 다른 독자님들의 코드도 다 리뷰를 해야하기 때문에 코드 리뷰는 현실적으로 어려운 점을 양해해 주시기 바랍니다.

31indiana commented 3 years ago

네. 그러시겠네요. 그럼 재접속시 재매수 방지 및 종목별 다른 k값 적용을 위해 힌트를 좀 더 주실 수 있으실까요?

2021년 6월 27일 (일) 오후 8:55, HwangHoo Kim @.***>님이 작성:

본 깃허브는 책으로 전달할 수 없는 사항을 공유하기 위한 취지로 만들었습니다. 독자님의 코드 리뷰를 해드릴 경우, 형평성을 맞추려면 다른 독자님들의 코드도 다 리뷰를 해야하기 때문에 코드 리뷰는 현실적으로 어려운 점을 양해해 주시기 바랍니다.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/INVESTAR/StockAnalysisInPython/issues/86#issuecomment-869149360, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQMLDCZM3LMKHQNSLQS53LTTU4GSFANCNFSM46477TRQ .