주식투자 자동화 프로그램 개발 -1 파이썬 개발환경 구축하기
주식에 관심이 많은 지인과 함께 아이디어를 제공받아 주식투자 자동화 프로그램 개발을 시작했다.
파이썬으로 개발하였고 유튜브를 참고하여 기본토대를 제작하였다.
먼저 파이썬 개발환경 구축하기
1. vscode 설치
vscode는 가장 많이사용되기도하고 예전에 작업해본적이 있기때문에 편리하였다.
Visual Studio Code - Code Editing. Redefined
Visual Studio Code - Code Editing. Redefined
Visual Studio Code is a code editor redefined and optimized for building and debugging modern web and cloud applications. Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows.
code.visualstudio.com
매우 빠르게 설치가능하다.
2. 파이썬 다운로드
파이썬은 32비트로 진행해야한다. 그중에 나는 3.8.6 버전으로 다운하였고 vscode에 적용시켰다.
이과정에서 정말..고생을 많이했다.
파이썬 32비트로 설치해야하는데 64비트로 설치해서 원하는 코드가 작동하지않아 애먹었다.
꼭 잘확인하여 설치할것.
여기서 Windows x86 executable installer 을 설치할것!
Python Releases for Windows | Python.org
Python Releases for Windows
The official home of the Python Programming Language
www.python.org
설치하고 난후에 관리자권한으로 실행되도록 설정한다.
여기서 python.exe와 pythonw.exe. 에서 관리자 권한으로 이 프로그램 실행을 설정해준다.
3. 환경변수 설정하기
이것은 자동으로 설정이 되었다. 하지만 여러 언어가 설치되어있다면 필수적으로 해야하는작업
설치했던 경로로 설정해준다. 파이썬은 다운로드할때 애초에 C에 내가 폴더를 만들고 거기에 설정하는게 편하다. 자동으로 해주는것은 너무 경로가 복잡해서 지울때 애먹었따...^^..
"C:\Users\user\.vscode\extensions\.a3bd843f-ff28-4dee-a0cc-97600e418b58\python_files\lib\jedilsp\jedi\third_party\django-stubs\django-stubs\core\wsgi.pyi"
이런.... 처음에 파이썬 설치잘못하고 지우고 다시까는과정에서 뒤엉키고.......삭제시스템을 돌려도 안되서 노트북을 그냥 포맷해버렸다.
4.증권사 api설치
나는 영상에따라서 대신증권 크레온으로 다운로드 하였다. 크레온에서 소스코드도 제공한다.
다운로드 센터 - 크레온 (creontrade.com)
크레온
대신증권 온라인 주식 거래 서비스. 비대면계좌개설, 수수료 혜택, 주식시세, 종목추천, 투자정보 제공
www.creontrade.com
여기서 증권사를 만들고 크레온을 다운로드 해준다.
다운로드 해주고
크레온플러스가 다운로드되면 주문 오브젝트 사용동의를 해주고 주문내역 확인 설정을 해준다.
나중에는 일일이 로그인안해도 파이썬코드가 자동으로 로그인 된다.
5.터미널 설정하기
vscode에 powershell을 cmd로 설정해줘야한다.
이건 영상이랑 방법이 달라서 한참 애먹었는데..
Ctrl+Shift+P를 누르고 select Default Profile를 누르면 cmd를 설정할수 있다.
6. 파이썬 설정하기
파이썬이 잘설치가 되었다면 위와 같은 방법으로 누르고 python:Select Interpreter을
눌러 설치한 버전을 적용시켜준다.
하단에 적용된 버전을 확인할수 있다.
7. pip 사용하기
pip install pywinauto 설치
8. 크레온 api 가지고오기
크레온플러스자료실 - 크레온 (creontrade.com)
크레온플러스자료실 - 크레온
첨부파일을 PDF뷰어로 확인 하실수 있으며, PDF뷰어 미설치 고객께서는 우측 다운로드를 통해 설치 후 이용 가능 합니다. PDF뷰어 다운로드
money2.creontrade.com
종목구하는 코드를 test.py에 적용해주고 python.py를 cmd창에 입력하여 실행시켜준다.
그러면 종목을 확인할수 있다.
파이썬이 잘 구동되어있지않으면 여기서 오류가 발생한다.
9. slack과 연동하기
slack을 회원가입해주고 slack.api와 연동하여 OAuth 토큰을 발급해준다.
Unlock your productivity potential with Slack Platform | Slack
Slack은 생산성 플랫폼입니다
Slack은 팀과 커뮤니케이션할 수 있는 새로운 방법입니다. 이메일보다 빠르고, 더 조직적이며, 훨씬 안전합니다.
slack.com
자세한방법은 유튜브 참고
https://youtu.be/s24dxIp-Cp0?si=ahxQRT545Gb5-3WC
10. 알림확인하기
slack에 알림을 확인하기위하여 test.py에 토근을 넣어준다.
영상과는 코드가 조금 달라졌따.
import win32com.client
# 연결 여부 체크
objCpCybos = win32com.client.Dispatch("CpUtil.CpCybos")
bConnect = objCpCybos.IsConnect
if (bConnect == 0):
print("PLUS가 정상적으로 연결되지 않음. ")
exit()
# 현재가 객체 구하기
objStockMst = win32com.client.Dispatch("DsCbo1.StockMst")
objStockMst.SetInputValue(0, 'A005930') #종목 코드 - 삼성전자
objStockMst.BlockRequest()
# 현재가 정보 조회
offer = objStockMst.GetHeaderValue(16) #매도호가
import requests
def post_message(token, channel, text):
response = requests.post("https://slack.com/api/chat.postMessage",
headers={"Authorization": "Bearer "+token},
data={"channel": channel,"text": text}
)
print(response)
myToken = "xoxb-토큰"
# 현재가 정보 조회
offer = objStockMst.GetHeaderValue(16) #매도호가
post_message(myToken,"#stock",'삼성전자 현재가:'+ str(offer))
삼성전자 현재가가 얼마인지 들고오는 코드이다.
토큰과 연결이잘되었으면 slack 에 봇이 삼성전자 현재가를 알려줄것이다.
11. 변동성 돌파 전략 -by 래리 윌리암스
변동성 돌파전략을 적용하기위해 파이썬 증권 데이터 분석에서 제공하는 코드를 적용시킬것이다.
3번을 적용하면 돌파전략을 적용시킬수 있다.
https://github.com/INVESTAR/StockAnalysisInPython
import os, sys, ctypes
import win32com.client
import pandas as pd
from datetime import datetime
from slacker import Slacker
import time, calendar
import requests
def post_message(token, channel, text):
response = requests.post("https://slack.com/api/chat.postMessage",
headers={"Authorization": "Bearer "+token},
data={"channel": channel,"text": text}
)
print(response)
myToken = "xoxb-토큰"
def dbgout(message):
"""인자로 받은 문자열을 파이썬 셸과 슬랙으로 동시에 출력한다."""
print(datetime.now().strftime('[%m/%d %H:%M:%S]'), message)
strbuf = datetime.now().strftime('[%m/%d %H:%M:%S] ') + message
#Slack.chat.post_message('#stock', strbuf)
post_message(myToken,"#stock",strbuf)
def printlog(message, *args):
"""인자로 받은 문자열을 파이썬 셸에 출력한다."""
print(datetime.now().strftime('[%m/%d %H:%M:%S]'), message, *args)
# 크레온 플러스 공통 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 + ')'
+ ':' + 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
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% 주문 가능 금액
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.5
return target_price
except Exception as ex:
dbgout("`get_target_price() -> exception! " + str(ex) + "`")
return None
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_movingavrg(' + 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
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:
bought_list.append(code)
dbgout("`buy_etf("+ str(stock_name) + ' : ' + str(code) +
") -> " + str(bought_qty) + "EA bought!" + "`")
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 = [종목 번호]
bought_list = [] # 매수 완료된 종목 리스트
target_buy_count = 4 # 매수할 종목 수
buy_percent = 0.25
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:
dbgout('`sell_all() returned True -> 프로그램 종료`')
sys.exit(0)
if t_exit < t_now: # PM 03:20 ~ :프로그램 종료
dbgout('`프로그램 종료`')
sys.exit(0)
time.sleep(3)
except Exception as ex:
dbgout('`main -> exception! ' + str(ex) + '`')
[종목번호] 에는 구매하고싶은 주식의 종목수를 4개 정도 적어 넣는다.
12. 크레온 자동로그인
크레온 프로그램을 자동으로 로그인하는 편한 방법이있다. 아래의 코드에 아이디, 비밀번호, 인증서 비밀번호를 넣으면 파일을 실행하면 알아서 된다.
위에 올려뒀던 깃허브에 들어가서 1번을 다운로드 해준다.
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:userid /pwd:pa$$word /pwdcert:certPa$$word /autostart')
time.sleep(60)
app.start('C:\CREON\STARTER\coStarter.exe /prj:cp /id:userid /pwd:pa$$word /pwdcert:certPa$$word /autostart')
여기에 자신에게 맞는 정보를 넣어주면 된다.
이제 cmd창에서 python test.py (내가 설정한 파일이름)
으로 실행해주면 크레온이 알아서 로그인된다.
오늘 아침에 프로그램을 실행해보니 잘 돌아가는것 같다.
슬랙에 알아서 안내가 뜬다! ㅎㅎ 과연..한달 실행후 수익률은...?
