이하영

open_api 관련함수 추가

......@@ -22,3 +22,5 @@ real_num=1
real_bot_name="AutoBot"+str(real_num)
real_stockInfo_name="stock_info"
real_dailyInfo_name="daily_info"
max_api_call=98
\ No newline at end of file
......
from PyQt5.QtCore import *
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
import pymysql
import datetime
from sqlalchemy import *
from collections import defaultdict
from pandas import DataFrame
import re
import time
import warnings
warnings.simplefilter(action='ignore',category=UserWarning)
from sqlalchemy import create_engine,event,Text,Float
from sqlalchemy.pool import Pool
import pymysql
pymysql.install_as_MySQLdb()
from Logger import *
import cf
from Simulator_Api import *
pymysql.install_as_MySQLdb()
# open_api에 tr요청을 보낼 때, 연속해서 보내면 오류가 발생하기 때문에 중간에 interval을 줘야 한다.
TR_REQ_TIME_INTERVAL=0.5 # interval값을 저장하는 변수
code_pattern=re.compile(r'\d{6}') # 코드 패턴을 저장. 코드는 연속된 6자리의 숫자
# query문의 %를 %%로 변환하는 함수
def escape_percentage(conn,clauseelement,multiparmas,parmas):
if isinstance(clauseelement,str) and '%' in clauseelement and multiparmas is not None:
while True:
replaced=re.sub(r'([^%])%([^%])', r'\1%%\2', clauseelement)
if replaced==clauseelement:
break
clauseelement=replaced
return clauseelement,multiparmas,parmas
# MySQL의 default sql_mode 설정
def setup_sql_mod(dbapi_connection):
cursor=dbapi_connection.cursor()
cursor.execute("SET sql_mode=''")
event.listen(Pool,'connect',setup_sql_mod)
event.listen(Pool,'first_connect',setup_sql_mod)
class Open_Api(QAxWidget):
def __init__(self):
......@@ -191,13 +218,10 @@ class Open_Api(QAxWidget):
def _receive_chejan_data(self,sGubun,nItemCnt,sFIdList):
# 체결구분. 접수와 체결
if sGubun=="0":
code=self.get_chejan_data(9001) # 현재 체결 진행 중인 코드
code=re.compile(r'\d{6}') # 종목코드는 연속된 숫자 6자리
order_num=self.get_chejan_data(9203) # 주문번호
# 주문번호가 존재하지 않는다면 주문실패
code = code_pattern.search(self.get_chejan_data(9001)).group(0) # 주식 코드가 숫자만오지 않아서 정규식으로 필터링
order_num = self.get_chejan_data(9203) # 주문번호
if not order_num:
logger.debug(code,"주문 실패")
logger.debug(f'{code} 주문 실패')
return
chegyul_fail_amount=self.get_chejan_data(902) # 미체결 수량
......@@ -208,17 +232,17 @@ class Open_Api(QAxWidget):
if code:
if chegyul_fail_amount!="":
# 해당 종목을 보유하고 있지 않은 경우
if not self.is_exist_possess_item_in_transaction(code):
if not self.is_all_stock_check(code):
# 해당 종목의 체결 실패 내역이 없다면
# transaction 테이블에 업데이트. 정상 체결 시 chegyul_check=0
if chegyul_fail_amount=="0":
logger.debug(code, "체결 완료")
self.db_to_transaction(order_num,code,0,purchase_price,0)
self.db_to_all_stocks(order_num,code,0,purchase_price,0)
# 체결 실패 내역이 존재한다면
# transaction 테이블에 업데이트. 미체결 시 chegyul_check=1
else:
logger.debug(code,"미체결")
self.db_to_transaction(order_num,code,1,purchase_price,0)
self.db_to_all_stocks(order_num,code,1,purchase_price,0)
# 매수하는 경우
elif order_gubun=="+매수":
......@@ -227,7 +251,7 @@ class Open_Api(QAxWidget):
pass
elif chegyul_fail_amount=="0" and self.check_stock_chegyul(code):
logger.debug("매수 완료")
self.check_end_invest(code)
self.end_invest_count_check(code)
else:
pass
......@@ -235,7 +259,7 @@ class Open_Api(QAxWidget):
elif order_gubun=="-매도":
if chegyul_fail_amount=="0":
logger.debug("전량 매도")
self.check_end_sell(code)
self.sell_final_check(code)
else:
logger.debug("부분 매도")
self.check_sell_chegyul_fail(code)
......@@ -255,33 +279,16 @@ class Open_Api(QAxWidget):
else:
logger.debug("Invlid _receive_chejan_data")
# 특정 종목을 보유하고 있는지 확인하는 함수
def is_exist_possess_item_in_transaction(self,code):
query = "select code from transaction " \
"where code='%s' and (sell_date ='%s' or sell_date='%s') ORDER BY buy_date desc LIMIT 1"
result = self.engine_bot.execute(query % (code, 0, "")).fetchall()
if len(result) != 0:
return True
else:
return False
# 해당 종목이 체결되었는지 확인하는 함수
def check_stock_chegyul(self,code):
query = "SELECT chegyul_check FROM transaction " \
"where code='%s' and sell_date = '%s' ORDER BY buy_date desc LIMIT 1"
result = self.engine_bot.execute(query % (code, 0)).fetchall()
query = f"SELECT chegyul_check FROM all_stocks " \
f"where code='{code}' and sell_date = '0' ORDER BY buy_date desc LIMIT 1"
result = self.engine_bot.execute(query).fetchall()
if result[0][0] == 1:
return True
else:
return False
# 체결 완료 시 transaction 테이블의 chegyul_check항목을 업데이트
def check_end_invest(self,code):
query = "UPDATE transaction " \
"SET " \
"chegyul_check='%s' WHERE code='%s' and sell_date = '%s' ORDER BY buy_date desc LIMIT 1"
self.engine_bot.execute(query % (0, code, 0))
# 매도 완료 후 DB 업데이트트
def check_sell_end(self,code):
query="select valuation_profit,rate,item_total_urchase,present_price" \
......@@ -307,9 +314,9 @@ class Open_Api(QAxWidget):
# 매도 체결 실패시 DB 업데이트
def check_sell_chegyul_fail(self,code):
query = "UPDATE transaction SET chegyul_check='%s' " \
"WHERE code='%s' and sell_date = '%s' ORDER BY buy_date desc LIMIT 1"
self.engine_bot.execute(query % (1, code, 0))
query = f"UPDATE all_stocks SET chegyul_check='1' WHERE code='{code}' and sell_date = '0' " \
f"ORDER BY buy_date desc LIMIT 1"
self.engine_JB.execute(query)
# OnReceiveChejan()이벤트가 호출될때 체결정보나 잔고정보를 얻어오는 함수
# param : nFid - 실시간 타입에 포함된 FID
......@@ -340,8 +347,7 @@ class Open_Api(QAxWidget):
try:
self.dynamicCall("SetInputValue(QString, QString)", sId, sValue)
except Exception as e:
print(e)
sys.exit()
logger.critical(e)
# 조회요청함수
# param : sRQName - 사용자 구분명
......@@ -349,7 +355,16 @@ class Open_Api(QAxWidget):
# nPrevNext - 연속조회여부
# sScreenNo - 화면번호
def comm_rq_data(self,sRQName,sTrData,nPrevNext,sScrNo):
self.dynamicCall("CommRqData(QString, QString, int, QString", sRQName, sTrData, nPrevNext, sScrNo)
self.exit_check()
ret=self.dynamicCall("CommRqData(QString, QString, int, QString", sRQName, sTrData, nPrevNext, sScrNo)
if ret==-200:
logger.critical("요청 제한 횟수 초과")
self.call_time=datetime.datetime.now()
if ret==0:
self.tr_loop_count+=1
self.tr_event_loop.exec_()
# OnReceiveTRData()이벤트가 호출될때 조회데이터를 얻어오는 함수
......@@ -369,8 +384,7 @@ class Open_Api(QAxWidget):
ret=self.dynamicCall("GetRepeatCnt(QString, QString)",sTrCode,sRecordName)
return ret
except Exception as e:
print(e)
sys.exit()
logger.critical(e)
# 변수 설정
# 실전투자인지 모의투자인지 여부를 확인하고 그에 해당하는 데이터베이스를 생성하는 함수
......@@ -396,7 +410,7 @@ class Open_Api(QAxWidget):
logger.debug("Invalid Account Number. Check the Config.py")
exit(1)
self.is_balance_null=True
self.jango_is_null=True
self.py_gubun=False
# 데이터베이스 생성 및 엔진 설정
......@@ -411,16 +425,14 @@ class Open_Api(QAxWidget):
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
cursor=conn.cursor()
with conn.cursor() as cursor:
if not self.is_database_exist(cursor):
self.create_database(cursor)
self.engine_bot=create_engine("mysql+pymysql://"+self.cf.db_id+":"+self.cf.db_pw+"@"+
self.cf.db_ip + ":" + self.cf.db_port+"/"+db_name,encoding='utf-8')
self.engine_bot = create_engine("mysql+pymysql://" + self.cf.db_id + ":" + self.cf.db_pw + "@" +
self.cf.db_ip + ":" + self.cf.db_port + "/" + db_name, encoding='utf-8')
self.create_basic_database(cursor)
conn.commit()
cursor.close()
conn.close()
self.engine_craw = create_engine("mysql+pymysql://" + cf.db_id + ":" + cf.db_pw + "@" + cf.db_ip + ":" +
......@@ -430,6 +442,10 @@ class Open_Api(QAxWidget):
self.engine_daily_buy_list = create_engine("mysql+pymysql://" + cf.db_id + ":" + cf.db_pw + "@" + cf.db_ip + ":"
+ cf.db_port + "/daily_buy_list",encoding='utf-8')
event.listen(self.engine_craw,'before_execute',escape_percentage,retval=True)
event.listen(self.engine_daily_craw,'before_execute',escape_percentage,retval=True)
event.listen(self.engine_daily_buy_list,'before_execute',escape_percentage,retval=True)
# bot database가 존재하는지 확인하는 함수
def is_database_exist(self,cursor):
query=f"select 1 from information_schema.schemata where schema_name='{self.db_name}'"
......@@ -505,23 +521,23 @@ class Open_Api(QAxWidget):
# simulator_fun 에서 설정한 변수를 가져오는 함수
def set_simul_variable(self):
logger.debug("-* set simul variable 함수 *-")
logger.debug("-* set simul variable function *-")
# daily_buy_list에 저장된 가장 최신 날짜
self.date_rows_yesterday=self.sf.get_recent_daily_buy_list_date()
# AutoBot 데이터베이스에 all_stock 테이블이 존재하지 않을 경우 테이블 생성 및 초기화
# all_stock : 모든 주식 거래 내역을 저장하는 테이블
# all_stocks : 모든 주식 거래 내역을 저장하는 테이블
if not self.sf.is_simul_table_exist(self.db_name,"all_stocks"):
logger.debug("all_stocks 테이블을 생성합니다")
# 테이블 생성 후 초기화
self.invest_unit=0
self.db_to_transaction(0,0,0,0,0)
self.delete_transaction_item("0")
self.delete_all_stock("0")
# setting_data에 invest_unit값이 없다면 설정
if not self.check_set_invest_unit():
self.set_invest_unit()
# 존재한다면 해당 값을 simulator_func에 저장
# 존재한다면 해당 값을 simulator_api에 저장
else:
self.invest_unit=self.get_invest_unit()
self.sf.invest_unit=self.invest_unit
......@@ -529,6 +545,7 @@ class Open_Api(QAxWidget):
# all_stock(거래 내역) 테이블 생성
def db_to_all_stocks(self,order_num,code,chegyul_check,purchase_price,rate):
logger.debug("-* db_to_all_stocks function *-")
self.date_setting()
self.sf.init_df_all_stocks() # all_stocks 테이블 데이터프레임 생성
......@@ -544,6 +561,7 @@ class Open_Api(QAxWidget):
if order_num != 0:
recent_daily_buy_list_date=self.sf.get_recent_daily_buy_list_date()
# 구매 내역이 존재하는 경우 해당 데이터를 추가
if recent_daily_buy_list_date:
# 특정 날짜, 특정 종목의 주가 데이터
df=self.sf.get_daily_buy_list_by_code(code,recent_daily_buy_list_date)
......@@ -593,14 +611,14 @@ class Open_Api(QAxWidget):
'clo120_diff_rate': Float,
})
# 거래내역(transaction) 테이블에서 특정 종목을 삭제하는 함수
# all_stocks(거래내역) 테이블에서 특정 종목을 삭제하는 함수
def delete_transaction_item(self,code):
query=f"delete from transaction where code={code}"
query=f"delete from all_stocks where code={code}"
self.engine_bot.execute(query)
# setting_data 테이블에 invest_unit이 오늘 업데이트 되었는지 확인
def check_set_invest_unit(self):
query="select invest_unit, set_invest_unit from setting_data"
query="select invest_unit, set_invest_unit from setting_data limit 1"
result=self.engine_bot.execute(query).fetchall()
if result[0][1]==self.today:
self.invest_unit=result[0][0]
......@@ -610,6 +628,7 @@ class Open_Api(QAxWidget):
# 데이터베이스에서 invest_unit값을 가져오는 함수
def get_invest_unit(self):
logger.debug("-* get_invest_unit function *-")
query="select invest_unit from setting_data"
result=self.engine_bot.execute(query).fetchall()
return result[0][0]
......@@ -619,7 +638,7 @@ class Open_Api(QAxWidget):
def set_invest_unit(self):
self.get_deposit()
self.get_balance()
self.total_invest=self.deposit+self.total_purchase_price
self.total_invest=self.change_format(str(int(self.deposit)+int(self.total_purchase_price)))
self.invest_unit=self.sf.invest_unit
query=f"update setting_data set invest_unit='{self.invest_unit}', set_invest_unit='{self.today}'"
......@@ -643,36 +662,38 @@ class Open_Api(QAxWidget):
self.comm_rq_data("opw00018_req","opw00018",2,"2000")
# open_api를 통해 보유한 종목을 가져오는 함수
# 가져온 정보를 possesd_item이라는 테이블에 저장
def db_to_possessd_item(self):
# 가져온 정보를 possessed_item이라는 테이블에 저장
def db_to_possessed_item(self):
item_count=len(self.opw00018_output['multi'])
possesd_item_data={'date':[],'code':[],'code_name':[],'holding_amount':[],'purchase_price':[],
possessed_item_data={'date':[],'code':[],'code_name':[],'holding_amount':[],'purchase_price':[],
'present_price':[],'valuation_profit':[],'rate':[],'item_total_purchase':[]}
possesd_item=DataFrame(possesd_item_data,
possessed_item=DataFrame(possessed_item_data,
columns=['date','code','code_name','holding_amount','purchase_price',
'present_price','valuation_profit','rate','item_total_purchase'])
for i in range(item_count):
item=self.opw00018_output['multi'][i]
possesd_item.loc[i,'date']=self.today
possesd_item.loc[i,'code']=item[7]
possesd_item.loc[i,'code_name']=item[0]
possesd_item.loc[i,'holding_amount']=int(item[1])
possesd_item.loc[i,'purchase_price']=int(item[2])
possesd_item.loc[i,'present_price']=int(item[3])
possesd_item.loc[i,'valuation_profit']=int(item[4])
possesd_item.loc[i,'rate']=float(item[5])
possesd_item.loc[i,'item_total_purchase']=int(item[6])
possessed_item.loc[i,'date']=self.today
possessed_item.loc[i,'code']=item[7]
possessed_item.loc[i,'code_name']=item[0]
possessed_item.loc[i,'holding_amount']=int(item[1]) # 보유량
possessed_item.loc[i,'purchase_price']=int(item[2]) # 매수가
possessed_item.loc[i,'present_price']=int(item[3]) # 현재가
possessed_item.loc[i,'valuation_profit']=int(item[4]) # 평가수익률
possessed_item.loc[i,'rate']=float(item[5]) # 수익률
possessed_item.loc[i,'item_total_purchase']=int(item[6]) # 총매수금액
possesd_item.to_sql("possesd_item",self.engine_bot,if_exists='replace')
self.contract_sync()
possessed_item.to_sql("possessed_item",self.engine_bot,if_exists='replace')
self.chegyul_sync()
# 현재 소유하고 있는 종목에 대해 transaction 테이블을 업데이트
# 매수 완료한 항목을 all_stocks에 기록하는 함수
def chegyul_sync(self):
query="select code,code_name,rate from possessed_item " \
"where code not in (select code from transaction where sell_date='0' group by code) group by code"
# 신규 매수한 항목을 가져온다
query = "select code, code_name, rate from possessed_item p " \
"where p.code not in (select a.code from all_stocks a where a.sell_date = '0' group by a.code) " \
"group by p.code"
result=self.engine_bot.execute(query).fetchall()
for item in result:
......@@ -692,7 +713,7 @@ class Open_Api(QAxWidget):
else:
continue
self.db_to_transaction(self.not_contract['주문번호'],item.code,contract_check,self.not_contract['체결가'],item.rate)
self.db_to_all_stocks(self.not_contract['주문번호'],item.code,chegyul_check,self.not_contract['체결가'],item.rate)
# posses_item 테이블을 업데이트 했을 경우 setting data 테이블에 업데이트 한 날짜를 표시
def setting_data_posses_stock(self):
......@@ -730,7 +751,7 @@ class Open_Api(QAxWidget):
def is_stock_table_exist(self,code_name):
query = "select 1 from information_schema.tables where table_schema ='stock_info' and table_name = '{}'"
result=self.engine_stock.execute(query.format(code_name)).fetchall()
if result:
if result:
return True
else:
return False
......@@ -745,20 +766,20 @@ class Open_Api(QAxWidget):
else:
return str(0)
# 체결이 완료되었는지 확인하고 transaction(거래내역) 테이블을 업데이트하는 함수
# 체결이 완료되었는지 확인하고 all_stocks(거래내역)테이블을 업데이트
def check_chegyul(self):
query="select code from transaction where chegyul_check='1'"
rows=self.engine_bot.execute(query).fetchall()
query="select code from all_stocks where chegyul_check='1' and (sell_date='0' or sell_date=''"
result=self.engine_bot.execute(query).fetchall()
for r in rows:
for r in result:
self.set_input_value("종목코드",r.code)
self.set_input_value("조회구분",1)
self.set_input_value("계좌번호",self.account_no)
self.comm_rq_data("opt10076_req","opt10076",0,"0350")
query=f"update transaction set chegyul_check='0' where code='{r.code}' and sell_data='0' " \
query=f"update all_stocks set chegyul_check='0' where code='{r.code}' and sell_data='0' " \
f"order by buy_date desc limit 1"
# 거래가 완료된 항목은 주문번호가 존재하지 않음
# 거래가 완료된 항목은 주문번호 등 데이터가 존재하지 않음
# 거래가 완료된 항목에 대해서 contract_check항목을 '0'으로 업데이트
if not self.not_contract['주문번호']:
self.engine_bot.execute(query)
......@@ -769,28 +790,45 @@ class Open_Api(QAxWidget):
else:
logger.debug("미체결 종목이 존재합니다")
# 매도했을 경우 possessed_item(보유종목) 테이블에서 항목을 삭제
def delete_possessed_item(self,code):
query=f"delete from possessed_item where code={code}"
self.engine_bot.execute(query)
# 매도한 후 transaction 테이블 업데이트
def check_sell_final(self,code):
query=f"update transaction chegyul_check='0', sell_date='{self.today_time}' " \
f"where code='{code}' and sell_date='0' order by buy_date desc"
# 매도한 후 all_stocks 테이블 업데이트
def sell_final_check(self,code):
query=f"SELECT valuation_profit, rate, item_total_purchase, present_price FROM possessed_item " \
f"WHERE code='{code}' LIMIT 1"
result=self.engine_bot.execute(query).fetchall()
if result:
item = result[0]
query=f"update all_stocks set " \
f"item_total_purchase={item.item_total_purchase}, chegyul_check=0, " \
f"sell_date={self.today_time}, valuation_profit={item.valuation_profit}," \
f"sell_rate={item.rate}, sell_price={item.present_price} " \
f"where code='{code}' and sell_date='0' order by buy_date desc limit 1"
self.engine_bot.execute(query)
# posses_item테이블에는 존재하지만 transaction_history테이블에 존재하지 않는 항목에 대해 업데이트
def final_check_contract(self):
query="select code from transaction_history" \
"where" \
"sell_date='%s' or sell_date='%s' and code not in (select code from posses_item) and contract_check!='%s'"
result=self.engine_bot.execute(query%(0,"",1)).fetchall()
num=len(result)
for i in range(num):
self.sell_check(result[i][0])
query="update setting_data set final_contract_check='%s'"
self.engine_bot.execute(query%(self.today))
# 매도한 항목을 possessed_item(보유종목) 테이블에서 삭제
self.engine_bot.execute(f"DELETE FROM possessed_item WHERE code = '{code}'")
# 매도했을 때, possessed_item에서 삭제되었지만 all_stocks에 sell_date 칼럼이 업데이트 되지 않은 항목들을 처리하는 함수
def final_chegyul_check(self):
query = "select code from all_stocks a " \
"where (a.sell_date = '0' or a.sell_date ='') and a.code not in ( select code from possessed_item) "\
"and a.chegyul_check != '1'"
result = self.engine_bot.execute(query).fetchall()
num = len(result)
for t in range(num):
self.sell_final_check2(result[t][0])
# 모든 항목을 처리했으면 setting_data테이블의 final_chegyul_check 항목에 오늘 날짜를 저장
query = f"UPDATE setting_data SET final_chegyul_check='{self.today}' limit 1"
self.engine_bot.execute(query)
# 가지고 있던 종목을 판매하여 posses_item테이블에 내역이 존재하지 않지만 transaction_history에 매도처리가 되지 않은 항목 업데이트
def sell_check(self,code):
......@@ -808,9 +846,10 @@ class Open_Api(QAxWidget):
# setting_data에 possessed_item 항목을 업데이트
def set_setting_data_possessed_item(self):
query=f"update setting_data set possessed_item={self.today}"
query=f"update setting_data set possessed_item={self.today} limit 1"
self.engine_bot.execute(query)
# 특정 종목의 틱(1분별) 데이터 조회 함수
def get_total_data_min(self,code,code_name,start):
self.ohlcv=defaultdict(list)
......@@ -819,10 +858,10 @@ class Open_Api(QAxWidget):
self.set_input_value("수정주가구분",1)
self.comm_rq_data("opt10080_req","opt10080",0,"1999")
self.is_craw_table_exist=False
self.craw_table_exist=False
if self.is_min_craw_table_exist(code_name):
self.is_craw_table_exist=True
self.craw_table_exist=True
self.craw_db_last_min=self.get_craw_db_last_min(code_name)
self.craw_db_last_min_sum_volume=self.get_craw_db_last_min_sum_volume(code_name)
......@@ -840,11 +879,10 @@ class Open_Api(QAxWidget):
if self.ohlcv['date'][-1]<self.craw_db_last_min:
break
time.sleep(TR_REQ_TIME_INTERBVAL)
time.sleep(TR_REQ_TIME_INTERVAL)
if len(self.ohlcv['date']==0 or self.ohlcv['date'][0]==''):
return []
if self.ohlcv['date']=='':
return []
......@@ -852,6 +890,305 @@ class Open_Api(QAxWidget):
return df
# 특정 종목의 일자별 거래 데이터 조회 함수
def get_total_data(self,code,code_name,date):
logger.debug("-* get_total_data function *-")
self.ohlcv=defaultdict(list)
self.set_input_value("종목코드",code)
self.set_input_value("기준일자",date)
self.set_input_value("수정주가구분",1)
self.comm_rq_data("opt10081_req","opt10081",0,"0101")
if not self.is_craw_table_exist(code_name):
while self.remained_data:
self.set_input_value("종목코드", code)
self.set_input_value("기준일자", date)
self.set_input_value("수정주가구분", 1)
self.comm_rq_data("opt10081_req", "opt10081", 2, "0101")
if len(self.ohlcv)==0:
return []
if self.ohlcv['date']=='':
return []
df=DataFrame(self.ohlcv,columns=['date','open','high','low','close','volume'])
return df
# daily_craw 데이터베이스에 {code_name} 테이블이 존재하는지 확인하는 함수
def is_craw_table_exist(self,code_name):
query="select 1 from information_schema.tables where table_schema='daily_craw' and table_name='{}'"
result=self.engine_daily_craw.execute(query.format(code_name))
if result:
return True
else:
return False
# min_craw 데이터베이스에 {code_name} 테이블이 존재하는지 확인하는 함수
def is_min_craw_table_exist(self,code_name):
query = "select 1 from information_schema.tables where table_schema ='min_craw' and table_name = '{}'"
result = self.engine_craw.execute(query.format(code_name)).fetchall()
if result:
return True
else:
return False
# min_craw 테이블에서 마지막에 저장한 행의 sum_volume값을 가져오는 함수
def get_craw_db_last_min_sum_volume(self,code_name):
query = f"SELECT sum_volume from `{code_name}` order by date desc limit 1"
result = self.engine_craw.execute(query).fetchall()
if len(result):
return result[0][0]
else:
return str(0)
# min_craw 테이블에서 마지막에 저장한 행의 시간(분) 정보를 가져오는 함수
def get_craw_db_last_min(self,code_name):
query = f"SELECT date from `{code_name}` order by date desc limit 1"
result = self.engine_craw.execute(query).fetchall()
if len(result):
return result[0][0]
# 신생
else:
return str(0)
# min_craw 테이블에서 마지막에 저장한 행의 날짜 정보를 가져오는 함수
def get_craw_db_last_date(self,code_name):
query = f"SELECT date from `{code_name}` order by date desc limit 1"
result = self.engine_craw.execute(query).fetchall()
if len(result):
return result[0][0]
else:
return str(0)
# 특정 종목의 특정 날짜의 특정 데이터(시가/종가/고가/저가/거래량)를 반환하는 함수
def get_one_day_option_data(self,code,start,option):
self.ohlcv=defaultdict(list)
self.set_input_value("종목코드",code)
self.set_input_value("기준일자",start)
self.set_input_value("수정주가구분",1)
self.comm_rq_data("opt10081_req","opt10081",0,"0101")
if self.ohlcv['date']=='':
return False
df=DataFrame(self.ohlcv,columns=['open','high','low','close','volume'],index=self.ohlcv['date'])
if df.empty:
return False
if option=="open":
return df.iloc[0,0]
elif option=='high':
return df.iloc[0,1]
elif option=='low':
return df.iloc[0,2]
elif option=='close':
return df.iloc[0,3]
elif option=='volume':
return df.iloc[0,4]
else:
return False
# open_api에 주문요청을 보내는 함수. 성공할 경우 return 0
# param - sRQName : 사용자 구분명
# sScrNo : 화면번호
# sAccNo : 계좌번호
# nOrderType : 주문유형 (1.신규매수/2.신규매도/3.매수취소/4.매도취소/5.매수정정/6.매도정정
# code : 종목코드
# nQty : 주문수량
# nPrice : 주문가격
# sHogaGb : 거래구분 (00.지정가/03.시장가/05.조건부지정가/06.최유리지정가/07.최우선지정가/10.지정가IOC/
# 13.시장가IOC/16.최유리IOC/20.지정가FOK/23.시장가FOK/26.최유리FOK/61.장전시간외종가
# 62.시간외단일가매매/81.장후시간외종가)
# sOrgOrderNo : 원주문번호. 신규주문은 공백, 정정(취소) 주문할 원주문번호 입력
# return - 0 : 성공
# - -308 : 주문가능횟수 초과. 1초에 5회만 주문 가능
# - 이외 : 주문실패
def send_order(self,sRQName,sScrNo,sAccNo,nOrderType,code,nQty,nPrice,sHogaGb,sOrgOrderNo):
logger.debug("send order")
try:
self.exit_check()
self.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
[sRQName,sScrNo,sAccNo,nOrderType,code,nQty,nPrice,sHogaGb,sOrgOrderNo])
except Exception as e:
logger.critical(e)
# 코드명에 해당하는 종목코드를 반환하는 함수
def codename_to_code(self,codename):
# 데이터베이스에 종목명과 같은 값을 가지는 행의 code를 반환
query = f"select code from stock_item_all where code_name={codename}"
result = self.engine_daily_buy_list.execute(query).fetchall()
if len(result)!=0:
return result[0][0]
# 종목명의 길이가 길 경우
query = f"select code from stock_item_all where code_name like '{codename}%'"
result = self.engine_daily_buy_list.execute(query).fetchall()
if len(result)!=0:
return result[0][0]
# 종목명에 해당하는 값이 없을 경우 return False
return False
# 매수 완료 후 데이터베이스를 업데이트 하는 함수
def end_invest_count_check(self,code):
query = f"update all_stocks set chegyul_check='0' WHERE code='{code}' and sell_date = '0' " \
f"ORDER BY buy_date desc LIMIT 1"
self.engine_bot.execute(query)
# possessed_item 테이블에 중복으로 데이터가 반영되는 것을 막기 위해 possessed_item에서 해당 종목 삭제
query = f"delete from possessed_item where code ={code}"
self.engine_bot.execute(query)
# 잔액이 생겨서 다시 매수할 수 있는 상황이 되었을 경우, setting_data의 today_buy_stop 옵션을 0으로 변경경
def reset_buy_check(self):
query = "UPDATE setting_data SET today_buy_stop='0' WHERE id='1'"
self.engine_bot.execute(query)
# 투자 가능한 잔액이 부족하거나, 매수할 종목이 더 이상 없는 경우 setting_data의 today_buy_stop옵션을 0으로 변경
# 더이상 매수하지 않음
def buy_check_stop(self):
query = f"UPDATE setting_data SET today_buy_stop='{self.today}' limit 1"
self.engine_bot.execute(query)
# 잔액을 확인하는 함수
# 잔액이 부족하여 더이상 투자를 진행할 수 없는 경우, janfo_is_null을 True로 설정한 후 False 반환
def check_jango(self):
self.get_deposit()
try:
if int(self.d2_deposit_before_format) > (int(self.sf.limit_money)):
self.jango_is_null = False # trade 루프를 돌다가 잔액이 부족해졌을 때 루프를 빠져나오기 위한 변수
return True
else:
self.jango_is_null = True
return False
except Exception as e:
logger.critical(e)
# today_buy_stop 칼럼을 확인하는 함수
# setting_data테이블의 today_buy_stop 칼럼에 오늘 날짜가 적혀있는 경우 매수 중지
def buy_check(self):
query = "select today_buy_stop from setting_data limit 1"
result = self.engine_JB.execute(sql).fetchall()[0][0]
if result != self.today:
return True
else:
return False
# 구매할 수량을 계산하는 함수
def buy_num_count(self,invest_unit,present_price):
return int(invest_unit/present_price)
# 거래 함수
def trade(self):
# 실시간 현재가(close)를 저장
# 현재시점의 종가(close)는 현재가와 같다
current_price = self.get_one_day_option_data(self.get_today_buy_list_code, self.today, 'close')
if current_price == False:
return False
min_buy_limit = int(self.get_today_buy_list_close) * self.sf.invest_min_limit_rate # 매수가격 최저 범위
max_buy_limit = int(self.get_today_buy_list_close) * self.sf.invest_limit_rate # 매수가격 최고 범위
# 현재가가 매수 가격 최저 범위와 매수 가격 최고 범위 안에 들어와 있다면 매수
if min_buy_limit < current_price < max_buy_limit:
buy_num = self.buy_num_count(self.invest_unit, int(current_price))
logger.debug(
"매수+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- code :%s, 목표가: %s, 현재가: %s, 매수량: %s, min_buy_limit: %s, max_buy_limit: %s , invest_limit_rate: %s,예수금: %s , today : %s, today_min : %s, date_rows_yesterday : %s, invest_unit : %s, real_invest_unit : %s +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-",
self.get_today_buy_list_code, self.get_today_buy_list_close, current_price, buy_num, min_buy_limit,
max_buy_limit, self.sf.invest_limit_rate, self.d2_deposit_before_format, self.today, self.today_detail,
self.date_rows_yesterday, self.invest_unit, int(current_price) * int(buy_num))
# 03 시장가 매수
# 4번째 parameter: 1: 신규매수 / 2: 신규매도 / 3:매수취소 / 4:매도취소 / 5: 매수정정 / 6:매도정정
self.send_order("send_order_req", "0101", self.account_number, 1, self.get_today_buy_list_code, buy_num, 0,
"03", "")
# 만약 sf.only_nine_buy가 False 이면, 매도 후에 잔액이 생기면 다시 매수를 시작
# sf.only_nine_buy가 True이면 1회만 매수, 1회 매수 시 잔액이 부족해지면 바로 매수 중단
if not self.jango_check() and self.sf.use_nine:
# setting_data에 today_buy_stop을 1 로 설정
self.buy_check_stop()
def get_today_buy_list(self):
# 구매할 목록을 저장하는 테이블(realtime_daily_buy_list)이 존재하지 않거나, 테이블 내에 항목이 존재하지 않는다면 return
if self.sf.is_simul_table_exist(self.db_name, "realtime_daily_buy_list"):
self.sf.get_realtime_daily_buy_list()
if self.sf.len_df_realtime_daily_buy_list == 0:
return
else:
return
# 만약에 realtime_daily_buy_list 의 종목 수가 1개 이상이면
for i in range(self.sf.len_df_realtime_daily_buy_list):
code = self.sf.df_realtime_daily_buy_list.loc[i, 'code']
close = self.sf.df_realtime_daily_buy_list.loc[i, 'close']
check_item = self.sf.df_realtime_daily_buy_list.loc[i, 'check_item'] # 매수확인. 오늘 매수한 종목이면 1, 아니면 0
# 잔액이 부족한 경우 break
if self.jango_is_null:
break
# 이미 매수한 종목은 건너뛰고 다음종목 거래래
if check_item == True:
continue
else:
self.get_today_buy_list_code = code
self.get_today_buy_list_close = close
# 매수 하기 전에 해당 종목의 check_item을 1로 변경
query = f"UPDATE realtime_daily_buy_list SET check_item='1' WHERE code='{self.get_today_buy_list_code}'"
self.engine_bot.execute(query)
self.trade()
# 모든 매수를 마쳤으면 더이상 매수 하지 않도록 설정
if self.sf.use_nine:
self.buy_check_stop()
# 보유하고 있는 종목에 대해 all_stocks(거래내역) 테이블을 업데이트
def rate_check(self):
query = "select code,holding_amount,purchase_price,present_price,valuation_profit,rate,item_total_purchase "\
"from possessed_item group by code"
result=self.engine_bot.execute(query).fetchall()
num = len(result)
for k in range(num):
code = result[k][0]
holding_amount = result[k][1]
purchase_price = result[k][2]
present_price =result[k][3]
valuation_profit=result[k][4]
rate = result[k][5]
item_total_purchase = result[k][6]
query = f"update all_stocks set " \
f"holding_amount ='{holding_amount}', purchase_price ='{purchase_price}', " \
f"present_price='{present_price}',valuation_profit='{valuation_profit}',rate='{rate}'," \
f"item_total_purchase='{item_total_purchase}' " \
f"where code='{code}' and sell_date = '0'"
self.engine_bot.execute(query)
# 매도 후 possessed_item(보유종목) 테이블에는 없지만,
# all_stocks(거래내역) 테이블에 sell_date가 업데이트되지 않은 항목을 처리하는 함수
def sell_final_check2(self,code):
query = f"UPDATE all_stocks SET chegyul_check='0', sell_date ='{self.today_time}' " \
f"WHERE code='{code}' and sell_date ='0' ORDER BY buy_date desc LIMIT 1"
self.engine_bot.execute(query)
# all_stocks(거래내역) 테이블에서 현재 보유중인 종목이 존재하는지 확인하는 함수
def is_all_stock_check(self,code):
query = f"select code from all_stocks where code='{code}' and (sell_date ='0' or sell_date='') " \
f"ORDER BY buy_date desc LIMIT 1"
result=self.engine_bot.execute(query).fetchall()
if len(result) != 0:
return True
else:
return False
# 주식일봉차트조회 요청 함수 - 하나의 데이터만 저장
def _opt10081(self, sRQName, sTrCode):
code = self._get_comm_data(sTrCode, sRQName, 0, "종목코드")
......@@ -895,8 +1232,11 @@ class Open_Api(QAxWidget):
# 예수금 상세현황요청 함수
def _opw00001(self,sRQName,sTrCode):
self.deposit=self._get_comm_data(sTrCode,sRQName,0,"d+2출금가능금액")
self.deposit=int(self.deposit)
try:
self.d2_deposit_before_format = self._get_comm_data(sTrCode, sRQName, 0, "d+2출금가능금액")
self.d2_deposit = self.change_format(self.d2_deposit_before_format)
except Exception as e:
logger.critical(e)
# 계좌평가 잔고내역을 저장하는 변수 초기화
def reset_opw00018_output(self):
......@@ -913,23 +1253,23 @@ class Open_Api(QAxWidget):
self.total_earning_rate=self._get_comm_data(sTrCode,sRQName,0,"총수익률(%)")
self.estimated_deposit=self._get_comm_data(sTrCode,sRQName,0,"추정예탁자산")
self.total_purchase_price=int(self.total_purchase_price)
self.total_eval_price=int(self.total_eval_price)
self.total_eval_profit_loss_price=int(self.total_eval_profit_loss_price)
self.total_earning_rate=float(self.total_earning_rate)
self.estimated_deposit=int(self.estimated_deposit)
self.change_total_purchase_price = self.change_format(self.total_purchase_price)
self.change_total_eval_price = self.change_format(self.total_eval_price)
self.change_total_eval_profit_loss_price = self.change_format(self.total_eval_profit_loss_price)
self.change_total_earning_rate = self.change_format2(self.total_earning_rate)
self.change_estimated_deposit = self.change_format(self.estimated_deposit)
self.opw00018_output['single'].append(self.total_purchase_price)
self.opw00018_output['single'].append(self.total_eval_price)
self.opw00018_output['single'].append(self.total_eval_profit_loss_price)
self.opw00018_output['single'].append(self.total_earning_rate)
self.opw00018_output['single'].append(self.estimated_deposit)
self.opw00018_output['single'].append(self.change_total_purchase_price)
self.opw00018_output['single'].append(self.change_total_eval_price)
self.opw00018_output['single'].append(self.change_total_eval_profit_loss_price)
self.opw00018_output['single'].append(self.change_total_earning_rate)
self.opw00018_output['single'].append(self.change_estimated_deposit)
# 종목별 평가 잔고 데이터 - 멀티데이터 저장
rows=self._get_repeat_cnt(sTrCode,sRQName)
for i in range(rows):
code=self._get_comm_data(sTrCode,sRQName,i,"종목번호")
code=code_pattern.search(self._get_comm_data(sTrCode,sRQName,i,"종목번호")).group(0)
name=self._get_comm_data(sTrCode,sRQName,i,"종목명")
quantity=self._get_comm_data(sTrCode,sRQName,i,"보유수량")
purchase_price=self._get_comm_data(sTrCode,sRQName,i,"매입가")
......@@ -938,13 +1278,12 @@ class Open_Api(QAxWidget):
earning_rate=self._get_comm_data(sTrCode,sRQName,i,"수익률(%)")
item_total_purchase=self._get_comm_data(sTrCode,sRQName,i,"매입금액")
code=code[1:]
quantity=int(quantity)
purchase_price=int(purchase_price)
current_price=int(current_price)
eval_profit_loss_price=int(eval_profit_loss_price)
earning_rate=float(earning_rate)
item_total_purchase=int(item_total_purchase)
quantity = self.change_format(quantity)
purchase_price = self.change_format(purchase_price)
current_price = self.change_format(current_price)
eval_profit_loss_price = self.change_format(eval_profit_loss_price)
earning_rate = self.change_format2(earning_rate)
item_total_purchase = self.change_format(item_total_purchase)
self.opw00018_output['multi'].append(
[name,quantity,purchase_price,current_price,eval_profit_loss_price,earning_rate,item_total_purchase,code]
......@@ -952,8 +1291,12 @@ class Open_Api(QAxWidget):
# 일자별 실현 손익 요청
def _opt10074(self,sRQName,sTrCode):
self.total_profit=self._get_comm_data(sTrCode,sRQName,0,"실현손익")
self.today_profit=self._get_comm_data(sTrCode,sRQName,0,"당일매도손익")
try:
rows = self._get_repeat_cnt(sTrCode, sRQName)
self.total_profit = self._get_comm_data(sTrCode,sRQName, 0, "실현손익")
self.today_profit = self._get_comm_data(sTrCode,sRQName, 0, "당일매도손익")
except Exception as e:
logger.critical(e)
# 위탁종합거래내역 요청 함수
def _opw00015(self,sRQName,sTrCode):
......@@ -966,7 +1309,7 @@ class Open_Api(QAxWidget):
def _opt10076(self,sRQName,sTrCode):
outputs=['주문번호','종목명','주문구분','주문가격','주문수량','체결가','체결량','미체결수량',
'당일매매수수료','당일매매세금','주문상태','매매구분','원주문번호','주문시간','종목코드']
self._data={}
self._data={} # 미체결 항목을 저장하는 변수
for key in outputs:
if key not in ['주문번호','원주문번호','주문시간','종목코드']:
......@@ -990,7 +1333,7 @@ class Open_Api(QAxWidget):
today_profit=self._get_comm_data(sTrCode,sRQName,i,"당일매도손익")
earning_rate=self._get_comm_data(sTrCode,sRQName,i,"손익율")
code=code.lstrip('A')
code=self.change_format4(code)
self.opt10073_output['multi'].append([date,code,code_name,amount,today_profit,earning_rate])
......@@ -1013,6 +1356,78 @@ class Open_Api(QAxWidget):
self.ohlcv['volume'].append(int(volume))
self.ohlcv['sum_volume'].append(int(0))
# 데이터베이스로부터 보유하고 있는 종목의 보유량(holding amount)을 가져오는 함수
def get_holding_amount(self,code):
logger.debug("-* get_holding_amount function *-")
query=f"select holding_amount from possessed_item where code={code}"
result=self.engine_bot.execute(query).fetchall()
if len(result):
return result[0][0]
else:
logger.debug("Holding amount is empty.")
return False
# open_api로부터 받아온 데이터의 형식을 변형하는 함수
# 데이터 값 앞의 의미없는 0을 제거
def change_format(self,data):
try:
strip_data=data.lstrip('0')
# 유효한 값이 존재하지 않을 경우 0으로 대체
if strip_data=="":
strip_data='0'
return int(strip_data)
except Exception as e:
logger.critical(e)
# 수익률을 소수 형태로 변환하는 함수
def change_format2(self,data):
try:
strip_data=data.lstrip('-0')
# 유효한 값이 없을 경우 0으로 대체
if strip_data=="":
strip_data='0'
else:
strip_data=str(float(strip_data)/self.mod_gubun)
# 수익률< 1 일 경우
if strip_data.startswith('.'):
strip_data='0'+strip_data
# 수익률 < 0 일 경우
if data.startswith('-'):
strip_data='-'+strip_data
return strip_data
except Exception as e:
logger.critical(e)
# 특수문자(%), 앞뒤 공백 제거 함수
def change_format3(self,data):
try:
strip_data=data.stript('%')
strip_data=strip_data.strip()
return strip_data
except Exception as e:
logger.critical(e)
# 코드 앞의 문자를 제거하는 함수수
def change_format4(sel,data):
try:
strip_data=data.lstrip('A')
return strip_data
except Exception as e:
logger.critical(e)
# open api 조회 요청 수를 체크하는 함수
# 최대 조회 요청 가능 횟수를 넘어서면, 프로그램을 종료
def exit_check(self):
rq_delay=datetime.timedelta(seconds=0.6)
time_diff=datetime.datetime.now()-self.call_time
if rq_delay>time_diff:
time.sleep((rq_delay-time_diff).total_seconds())
self.rq_count+=1
logger.debug(self.rq_count)
if self.rq_count==cf.max_api_call:
sys.exit(1)
if __name__=="__main__":
app=QApplication(sys.argv)
......