이하영

open_api 관련함수 추가

...@@ -21,4 +21,6 @@ real_num=1 ...@@ -21,4 +21,6 @@ real_num=1
21 # 데이터베이스 이름 21 # 데이터베이스 이름
22 real_bot_name="AutoBot"+str(real_num) 22 real_bot_name="AutoBot"+str(real_num)
23 real_stockInfo_name="stock_info" 23 real_stockInfo_name="stock_info"
24 -real_dailyInfo_name="daily_info"
...\ No newline at end of file ...\ No newline at end of file
24 +real_dailyInfo_name="daily_info"
25 +
26 +max_api_call=98
...\ No newline at end of file ...\ No newline at end of file
......
1 from PyQt5.QtCore import * 1 from PyQt5.QtCore import *
2 from PyQt5.QAxContainer import * 2 from PyQt5.QAxContainer import *
3 from PyQt5.QtWidgets import * 3 from PyQt5.QtWidgets import *
4 -import pymysql
5 import datetime 4 import datetime
6 -from sqlalchemy import *
7 from collections import defaultdict 5 from collections import defaultdict
8 from pandas import DataFrame 6 from pandas import DataFrame
9 import re 7 import re
8 +import time
9 +
10 +import warnings
11 +warnings.simplefilter(action='ignore',category=UserWarning)
12 +
13 +from sqlalchemy import create_engine,event,Text,Float
14 +from sqlalchemy.pool import Pool
15 +import pymysql
16 +pymysql.install_as_MySQLdb()
10 17
11 from Logger import * 18 from Logger import *
12 import cf 19 import cf
13 from Simulator_Api import * 20 from Simulator_Api import *
14 21
15 -pymysql.install_as_MySQLdb() 22 +# open_api에 tr요청을 보낼 때, 연속해서 보내면 오류가 발생하기 때문에 중간에 interval을 줘야 한다.
23 +TR_REQ_TIME_INTERVAL=0.5 # interval값을 저장하는 변수
24 +code_pattern=re.compile(r'\d{6}') # 코드 패턴을 저장. 코드는 연속된 6자리의 숫자
25 +
26 +# query문의 %를 %%로 변환하는 함수
27 +def escape_percentage(conn,clauseelement,multiparmas,parmas):
28 + if isinstance(clauseelement,str) and '%' in clauseelement and multiparmas is not None:
29 + while True:
30 + replaced=re.sub(r'([^%])%([^%])', r'\1%%\2', clauseelement)
31 + if replaced==clauseelement:
32 + break
33 + clauseelement=replaced
34 + return clauseelement,multiparmas,parmas
35 +
36 +# MySQL의 default sql_mode 설정
37 +def setup_sql_mod(dbapi_connection):
38 + cursor=dbapi_connection.cursor()
39 + cursor.execute("SET sql_mode=''")
40 +
41 +event.listen(Pool,'connect',setup_sql_mod)
42 +event.listen(Pool,'first_connect',setup_sql_mod)
16 43
17 class Open_Api(QAxWidget): 44 class Open_Api(QAxWidget):
18 def __init__(self): 45 def __init__(self):
...@@ -191,13 +218,10 @@ class Open_Api(QAxWidget): ...@@ -191,13 +218,10 @@ class Open_Api(QAxWidget):
191 def _receive_chejan_data(self,sGubun,nItemCnt,sFIdList): 218 def _receive_chejan_data(self,sGubun,nItemCnt,sFIdList):
192 # 체결구분. 접수와 체결 219 # 체결구분. 접수와 체결
193 if sGubun=="0": 220 if sGubun=="0":
194 - code=self.get_chejan_data(9001) # 현재 체결 진행 중인 코드 221 + code = code_pattern.search(self.get_chejan_data(9001)).group(0) # 주식 코드가 숫자만오지 않아서 정규식으로 필터링
195 - code=re.compile(r'\d{6}') # 종목코드는 연속된 숫자 6자리 222 + order_num = self.get_chejan_data(9203) # 주문번호
196 - order_num=self.get_chejan_data(9203) # 주문번호
197 -
198 - # 주문번호가 존재하지 않는다면 주문실패
199 if not order_num: 223 if not order_num:
200 - logger.debug(code,"주문 실패") 224 + logger.debug(f'{code} 주문 실패')
201 return 225 return
202 226
203 chegyul_fail_amount=self.get_chejan_data(902) # 미체결 수량 227 chegyul_fail_amount=self.get_chejan_data(902) # 미체결 수량
...@@ -208,17 +232,17 @@ class Open_Api(QAxWidget): ...@@ -208,17 +232,17 @@ class Open_Api(QAxWidget):
208 if code: 232 if code:
209 if chegyul_fail_amount!="": 233 if chegyul_fail_amount!="":
210 # 해당 종목을 보유하고 있지 않은 경우 234 # 해당 종목을 보유하고 있지 않은 경우
211 - if not self.is_exist_possess_item_in_transaction(code): 235 + if not self.is_all_stock_check(code):
212 # 해당 종목의 체결 실패 내역이 없다면 236 # 해당 종목의 체결 실패 내역이 없다면
213 # transaction 테이블에 업데이트. 정상 체결 시 chegyul_check=0 237 # transaction 테이블에 업데이트. 정상 체결 시 chegyul_check=0
214 if chegyul_fail_amount=="0": 238 if chegyul_fail_amount=="0":
215 logger.debug(code, "체결 완료") 239 logger.debug(code, "체결 완료")
216 - self.db_to_transaction(order_num,code,0,purchase_price,0) 240 + self.db_to_all_stocks(order_num,code,0,purchase_price,0)
217 # 체결 실패 내역이 존재한다면 241 # 체결 실패 내역이 존재한다면
218 # transaction 테이블에 업데이트. 미체결 시 chegyul_check=1 242 # transaction 테이블에 업데이트. 미체결 시 chegyul_check=1
219 else: 243 else:
220 logger.debug(code,"미체결") 244 logger.debug(code,"미체결")
221 - self.db_to_transaction(order_num,code,1,purchase_price,0) 245 + self.db_to_all_stocks(order_num,code,1,purchase_price,0)
222 246
223 # 매수하는 경우 247 # 매수하는 경우
224 elif order_gubun=="+매수": 248 elif order_gubun=="+매수":
...@@ -227,7 +251,7 @@ class Open_Api(QAxWidget): ...@@ -227,7 +251,7 @@ class Open_Api(QAxWidget):
227 pass 251 pass
228 elif chegyul_fail_amount=="0" and self.check_stock_chegyul(code): 252 elif chegyul_fail_amount=="0" and self.check_stock_chegyul(code):
229 logger.debug("매수 완료") 253 logger.debug("매수 완료")
230 - self.check_end_invest(code) 254 + self.end_invest_count_check(code)
231 else: 255 else:
232 pass 256 pass
233 257
...@@ -235,7 +259,7 @@ class Open_Api(QAxWidget): ...@@ -235,7 +259,7 @@ class Open_Api(QAxWidget):
235 elif order_gubun=="-매도": 259 elif order_gubun=="-매도":
236 if chegyul_fail_amount=="0": 260 if chegyul_fail_amount=="0":
237 logger.debug("전량 매도") 261 logger.debug("전량 매도")
238 - self.check_end_sell(code) 262 + self.sell_final_check(code)
239 else: 263 else:
240 logger.debug("부분 매도") 264 logger.debug("부분 매도")
241 self.check_sell_chegyul_fail(code) 265 self.check_sell_chegyul_fail(code)
...@@ -255,33 +279,16 @@ class Open_Api(QAxWidget): ...@@ -255,33 +279,16 @@ class Open_Api(QAxWidget):
255 else: 279 else:
256 logger.debug("Invlid _receive_chejan_data") 280 logger.debug("Invlid _receive_chejan_data")
257 281
258 - # 특정 종목을 보유하고 있는지 확인하는 함수
259 - def is_exist_possess_item_in_transaction(self,code):
260 - query = "select code from transaction " \
261 - "where code='%s' and (sell_date ='%s' or sell_date='%s') ORDER BY buy_date desc LIMIT 1"
262 - result = self.engine_bot.execute(query % (code, 0, "")).fetchall()
263 - if len(result) != 0:
264 - return True
265 - else:
266 - return False
267 -
268 # 해당 종목이 체결되었는지 확인하는 함수 282 # 해당 종목이 체결되었는지 확인하는 함수
269 def check_stock_chegyul(self,code): 283 def check_stock_chegyul(self,code):
270 - query = "SELECT chegyul_check FROM transaction " \ 284 + query = f"SELECT chegyul_check FROM all_stocks " \
271 - "where code='%s' and sell_date = '%s' ORDER BY buy_date desc LIMIT 1" 285 + f"where code='{code}' and sell_date = '0' ORDER BY buy_date desc LIMIT 1"
272 - result = self.engine_bot.execute(query % (code, 0)).fetchall() 286 + result = self.engine_bot.execute(query).fetchall()
273 if result[0][0] == 1: 287 if result[0][0] == 1:
274 return True 288 return True
275 else: 289 else:
276 return False 290 return False
277 291
278 - # 체결 완료 시 transaction 테이블의 chegyul_check항목을 업데이트
279 - def check_end_invest(self,code):
280 - query = "UPDATE transaction " \
281 - "SET " \
282 - "chegyul_check='%s' WHERE code='%s' and sell_date = '%s' ORDER BY buy_date desc LIMIT 1"
283 - self.engine_bot.execute(query % (0, code, 0))
284 -
285 # 매도 완료 후 DB 업데이트트 292 # 매도 완료 후 DB 업데이트트
286 def check_sell_end(self,code): 293 def check_sell_end(self,code):
287 query="select valuation_profit,rate,item_total_urchase,present_price" \ 294 query="select valuation_profit,rate,item_total_urchase,present_price" \
...@@ -307,9 +314,9 @@ class Open_Api(QAxWidget): ...@@ -307,9 +314,9 @@ class Open_Api(QAxWidget):
307 314
308 # 매도 체결 실패시 DB 업데이트 315 # 매도 체결 실패시 DB 업데이트
309 def check_sell_chegyul_fail(self,code): 316 def check_sell_chegyul_fail(self,code):
310 - query = "UPDATE transaction SET chegyul_check='%s' " \ 317 + query = f"UPDATE all_stocks SET chegyul_check='1' WHERE code='{code}' and sell_date = '0' " \
311 - "WHERE code='%s' and sell_date = '%s' ORDER BY buy_date desc LIMIT 1" 318 + f"ORDER BY buy_date desc LIMIT 1"
312 - self.engine_bot.execute(query % (1, code, 0)) 319 + self.engine_JB.execute(query)
313 320
314 # OnReceiveChejan()이벤트가 호출될때 체결정보나 잔고정보를 얻어오는 함수 321 # OnReceiveChejan()이벤트가 호출될때 체결정보나 잔고정보를 얻어오는 함수
315 # param : nFid - 실시간 타입에 포함된 FID 322 # param : nFid - 실시간 타입에 포함된 FID
...@@ -340,8 +347,7 @@ class Open_Api(QAxWidget): ...@@ -340,8 +347,7 @@ class Open_Api(QAxWidget):
340 try: 347 try:
341 self.dynamicCall("SetInputValue(QString, QString)", sId, sValue) 348 self.dynamicCall("SetInputValue(QString, QString)", sId, sValue)
342 except Exception as e: 349 except Exception as e:
343 - print(e) 350 + logger.critical(e)
344 - sys.exit()
345 351
346 # 조회요청함수 352 # 조회요청함수
347 # param : sRQName - 사용자 구분명 353 # param : sRQName - 사용자 구분명
...@@ -349,8 +355,17 @@ class Open_Api(QAxWidget): ...@@ -349,8 +355,17 @@ class Open_Api(QAxWidget):
349 # nPrevNext - 연속조회여부 355 # nPrevNext - 연속조회여부
350 # sScreenNo - 화면번호 356 # sScreenNo - 화면번호
351 def comm_rq_data(self,sRQName,sTrData,nPrevNext,sScrNo): 357 def comm_rq_data(self,sRQName,sTrData,nPrevNext,sScrNo):
352 - self.dynamicCall("CommRqData(QString, QString, int, QString", sRQName, sTrData, nPrevNext, sScrNo) 358 + self.exit_check()
353 - self.tr_event_loop.exec_() 359 + ret=self.dynamicCall("CommRqData(QString, QString, int, QString", sRQName, sTrData, nPrevNext, sScrNo)
360 +
361 + if ret==-200:
362 + logger.critical("요청 제한 횟수 초과")
363 +
364 + self.call_time=datetime.datetime.now()
365 +
366 + if ret==0:
367 + self.tr_loop_count+=1
368 + self.tr_event_loop.exec_()
354 369
355 # OnReceiveTRData()이벤트가 호출될때 조회데이터를 얻어오는 함수 370 # OnReceiveTRData()이벤트가 호출될때 조회데이터를 얻어오는 함수
356 # param : sTrCode - TR 이름 371 # param : sTrCode - TR 이름
...@@ -369,8 +384,7 @@ class Open_Api(QAxWidget): ...@@ -369,8 +384,7 @@ class Open_Api(QAxWidget):
369 ret=self.dynamicCall("GetRepeatCnt(QString, QString)",sTrCode,sRecordName) 384 ret=self.dynamicCall("GetRepeatCnt(QString, QString)",sTrCode,sRecordName)
370 return ret 385 return ret
371 except Exception as e: 386 except Exception as e:
372 - print(e) 387 + logger.critical(e)
373 - sys.exit()
374 388
375 # 변수 설정 389 # 변수 설정
376 # 실전투자인지 모의투자인지 여부를 확인하고 그에 해당하는 데이터베이스를 생성하는 함수 390 # 실전투자인지 모의투자인지 여부를 확인하고 그에 해당하는 데이터베이스를 생성하는 함수
...@@ -396,7 +410,7 @@ class Open_Api(QAxWidget): ...@@ -396,7 +410,7 @@ class Open_Api(QAxWidget):
396 logger.debug("Invalid Account Number. Check the Config.py") 410 logger.debug("Invalid Account Number. Check the Config.py")
397 exit(1) 411 exit(1)
398 412
399 - self.is_balance_null=True 413 + self.jango_is_null=True
400 self.py_gubun=False 414 self.py_gubun=False
401 415
402 # 데이터베이스 생성 및 엔진 설정 416 # 데이터베이스 생성 및 엔진 설정
...@@ -411,16 +425,14 @@ class Open_Api(QAxWidget): ...@@ -411,16 +425,14 @@ class Open_Api(QAxWidget):
411 charset='utf8mb4', 425 charset='utf8mb4',
412 cursorclass=pymysql.cursors.DictCursor 426 cursorclass=pymysql.cursors.DictCursor
413 ) 427 )
414 - 428 + with conn.cursor() as cursor:
415 - cursor=conn.cursor() 429 + if not self.is_database_exist(cursor):
416 - if not self.is_database_exist(cursor): 430 + self.create_database(cursor)
417 - self.create_database(cursor) 431 + self.engine_bot = create_engine("mysql+pymysql://" + self.cf.db_id + ":" + self.cf.db_pw + "@" +
418 - self.engine_bot=create_engine("mysql+pymysql://"+self.cf.db_id+":"+self.cf.db_pw+"@"+ 432 + self.cf.db_ip + ":" + self.cf.db_port + "/" + db_name, encoding='utf-8')
419 - self.cf.db_ip + ":" + self.cf.db_port+"/"+db_name,encoding='utf-8') 433 + self.create_basic_database(cursor)
420 - self.create_basic_database(cursor)
421 434
422 conn.commit() 435 conn.commit()
423 - cursor.close()
424 conn.close() 436 conn.close()
425 437
426 self.engine_craw = create_engine("mysql+pymysql://" + cf.db_id + ":" + cf.db_pw + "@" + cf.db_ip + ":" + 438 self.engine_craw = create_engine("mysql+pymysql://" + cf.db_id + ":" + cf.db_pw + "@" + cf.db_ip + ":" +
...@@ -430,6 +442,10 @@ class Open_Api(QAxWidget): ...@@ -430,6 +442,10 @@ class Open_Api(QAxWidget):
430 self.engine_daily_buy_list = create_engine("mysql+pymysql://" + cf.db_id + ":" + cf.db_pw + "@" + cf.db_ip + ":" 442 self.engine_daily_buy_list = create_engine("mysql+pymysql://" + cf.db_id + ":" + cf.db_pw + "@" + cf.db_ip + ":"
431 + cf.db_port + "/daily_buy_list",encoding='utf-8') 443 + cf.db_port + "/daily_buy_list",encoding='utf-8')
432 444
445 + event.listen(self.engine_craw,'before_execute',escape_percentage,retval=True)
446 + event.listen(self.engine_daily_craw,'before_execute',escape_percentage,retval=True)
447 + event.listen(self.engine_daily_buy_list,'before_execute',escape_percentage,retval=True)
448 +
433 # bot database가 존재하는지 확인하는 함수 449 # bot database가 존재하는지 확인하는 함수
434 def is_database_exist(self,cursor): 450 def is_database_exist(self,cursor):
435 query=f"select 1 from information_schema.schemata where schema_name='{self.db_name}'" 451 query=f"select 1 from information_schema.schemata where schema_name='{self.db_name}'"
...@@ -505,23 +521,23 @@ class Open_Api(QAxWidget): ...@@ -505,23 +521,23 @@ class Open_Api(QAxWidget):
505 521
506 # simulator_fun 에서 설정한 변수를 가져오는 함수 522 # simulator_fun 에서 설정한 변수를 가져오는 함수
507 def set_simul_variable(self): 523 def set_simul_variable(self):
508 - logger.debug("-* set simul variable 함수 *-") 524 + logger.debug("-* set simul variable function *-")
509 # daily_buy_list에 저장된 가장 최신 날짜 525 # daily_buy_list에 저장된 가장 최신 날짜
510 self.date_rows_yesterday=self.sf.get_recent_daily_buy_list_date() 526 self.date_rows_yesterday=self.sf.get_recent_daily_buy_list_date()
511 527
512 # AutoBot 데이터베이스에 all_stock 테이블이 존재하지 않을 경우 테이블 생성 및 초기화 528 # AutoBot 데이터베이스에 all_stock 테이블이 존재하지 않을 경우 테이블 생성 및 초기화
513 - # all_stock : 모든 주식 거래 내역을 저장하는 테이블 529 + # all_stocks : 모든 주식 거래 내역을 저장하는 테이블
514 if not self.sf.is_simul_table_exist(self.db_name,"all_stocks"): 530 if not self.sf.is_simul_table_exist(self.db_name,"all_stocks"):
515 logger.debug("all_stocks 테이블을 생성합니다") 531 logger.debug("all_stocks 테이블을 생성합니다")
516 # 테이블 생성 후 초기화 532 # 테이블 생성 후 초기화
517 self.invest_unit=0 533 self.invest_unit=0
518 self.db_to_transaction(0,0,0,0,0) 534 self.db_to_transaction(0,0,0,0,0)
519 - self.delete_transaction_item("0") 535 + self.delete_all_stock("0")
520 536
521 # setting_data에 invest_unit값이 없다면 설정 537 # setting_data에 invest_unit값이 없다면 설정
522 if not self.check_set_invest_unit(): 538 if not self.check_set_invest_unit():
523 self.set_invest_unit() 539 self.set_invest_unit()
524 - # 존재한다면 해당 값을 simulator_func에 저장 540 + # 존재한다면 해당 값을 simulator_api에 저장
525 else: 541 else:
526 self.invest_unit=self.get_invest_unit() 542 self.invest_unit=self.get_invest_unit()
527 self.sf.invest_unit=self.invest_unit 543 self.sf.invest_unit=self.invest_unit
...@@ -529,6 +545,7 @@ class Open_Api(QAxWidget): ...@@ -529,6 +545,7 @@ class Open_Api(QAxWidget):
529 # all_stock(거래 내역) 테이블 생성 545 # all_stock(거래 내역) 테이블 생성
530 def db_to_all_stocks(self,order_num,code,chegyul_check,purchase_price,rate): 546 def db_to_all_stocks(self,order_num,code,chegyul_check,purchase_price,rate):
531 logger.debug("-* db_to_all_stocks function *-") 547 logger.debug("-* db_to_all_stocks function *-")
548 +
532 self.date_setting() 549 self.date_setting()
533 self.sf.init_df_all_stocks() # all_stocks 테이블 데이터프레임 생성 550 self.sf.init_df_all_stocks() # all_stocks 테이블 데이터프레임 생성
534 551
...@@ -544,6 +561,7 @@ class Open_Api(QAxWidget): ...@@ -544,6 +561,7 @@ class Open_Api(QAxWidget):
544 561
545 if order_num != 0: 562 if order_num != 0:
546 recent_daily_buy_list_date=self.sf.get_recent_daily_buy_list_date() 563 recent_daily_buy_list_date=self.sf.get_recent_daily_buy_list_date()
564 + # 구매 내역이 존재하는 경우 해당 데이터를 추가
547 if recent_daily_buy_list_date: 565 if recent_daily_buy_list_date:
548 # 특정 날짜, 특정 종목의 주가 데이터 566 # 특정 날짜, 특정 종목의 주가 데이터
549 df=self.sf.get_daily_buy_list_by_code(code,recent_daily_buy_list_date) 567 df=self.sf.get_daily_buy_list_by_code(code,recent_daily_buy_list_date)
...@@ -593,14 +611,14 @@ class Open_Api(QAxWidget): ...@@ -593,14 +611,14 @@ class Open_Api(QAxWidget):
593 'clo120_diff_rate': Float, 611 'clo120_diff_rate': Float,
594 }) 612 })
595 613
596 - # 거래내역(transaction) 테이블에서 특정 종목을 삭제하는 함수 614 + # all_stocks(거래내역) 테이블에서 특정 종목을 삭제하는 함수
597 def delete_transaction_item(self,code): 615 def delete_transaction_item(self,code):
598 - query=f"delete from transaction where code={code}" 616 + query=f"delete from all_stocks where code={code}"
599 self.engine_bot.execute(query) 617 self.engine_bot.execute(query)
600 618
601 # setting_data 테이블에 invest_unit이 오늘 업데이트 되었는지 확인 619 # setting_data 테이블에 invest_unit이 오늘 업데이트 되었는지 확인
602 def check_set_invest_unit(self): 620 def check_set_invest_unit(self):
603 - query="select invest_unit, set_invest_unit from setting_data" 621 + query="select invest_unit, set_invest_unit from setting_data limit 1"
604 result=self.engine_bot.execute(query).fetchall() 622 result=self.engine_bot.execute(query).fetchall()
605 if result[0][1]==self.today: 623 if result[0][1]==self.today:
606 self.invest_unit=result[0][0] 624 self.invest_unit=result[0][0]
...@@ -610,6 +628,7 @@ class Open_Api(QAxWidget): ...@@ -610,6 +628,7 @@ class Open_Api(QAxWidget):
610 628
611 # 데이터베이스에서 invest_unit값을 가져오는 함수 629 # 데이터베이스에서 invest_unit값을 가져오는 함수
612 def get_invest_unit(self): 630 def get_invest_unit(self):
631 + logger.debug("-* get_invest_unit function *-")
613 query="select invest_unit from setting_data" 632 query="select invest_unit from setting_data"
614 result=self.engine_bot.execute(query).fetchall() 633 result=self.engine_bot.execute(query).fetchall()
615 return result[0][0] 634 return result[0][0]
...@@ -619,7 +638,7 @@ class Open_Api(QAxWidget): ...@@ -619,7 +638,7 @@ class Open_Api(QAxWidget):
619 def set_invest_unit(self): 638 def set_invest_unit(self):
620 self.get_deposit() 639 self.get_deposit()
621 self.get_balance() 640 self.get_balance()
622 - self.total_invest=self.deposit+self.total_purchase_price 641 + self.total_invest=self.change_format(str(int(self.deposit)+int(self.total_purchase_price)))
623 642
624 self.invest_unit=self.sf.invest_unit 643 self.invest_unit=self.sf.invest_unit
625 query=f"update setting_data set invest_unit='{self.invest_unit}', set_invest_unit='{self.today}'" 644 query=f"update setting_data set invest_unit='{self.invest_unit}', set_invest_unit='{self.today}'"
...@@ -643,36 +662,38 @@ class Open_Api(QAxWidget): ...@@ -643,36 +662,38 @@ class Open_Api(QAxWidget):
643 self.comm_rq_data("opw00018_req","opw00018",2,"2000") 662 self.comm_rq_data("opw00018_req","opw00018",2,"2000")
644 663
645 # open_api를 통해 보유한 종목을 가져오는 함수 664 # open_api를 통해 보유한 종목을 가져오는 함수
646 - # 가져온 정보를 possesd_item이라는 테이블에 저장 665 + # 가져온 정보를 possessed_item이라는 테이블에 저장
647 - def db_to_possessd_item(self): 666 + def db_to_possessed_item(self):
648 item_count=len(self.opw00018_output['multi']) 667 item_count=len(self.opw00018_output['multi'])
649 - possesd_item_data={'date':[],'code':[],'code_name':[],'holding_amount':[],'purchase_price':[], 668 + possessed_item_data={'date':[],'code':[],'code_name':[],'holding_amount':[],'purchase_price':[],
650 - 'present_price':[],'valuation_profit':[],'rate':[],'item_total_purchase':[]} 669 + 'present_price':[],'valuation_profit':[],'rate':[],'item_total_purchase':[]}
651 670
652 - possesd_item=DataFrame(possesd_item_data, 671 + possessed_item=DataFrame(possessed_item_data,
653 columns=['date','code','code_name','holding_amount','purchase_price', 672 columns=['date','code','code_name','holding_amount','purchase_price',
654 'present_price','valuation_profit','rate','item_total_purchase']) 673 'present_price','valuation_profit','rate','item_total_purchase'])
655 674
656 for i in range(item_count): 675 for i in range(item_count):
657 item=self.opw00018_output['multi'][i] 676 item=self.opw00018_output['multi'][i]
658 677
659 - possesd_item.loc[i,'date']=self.today 678 + possessed_item.loc[i,'date']=self.today
660 - possesd_item.loc[i,'code']=item[7] 679 + possessed_item.loc[i,'code']=item[7]
661 - possesd_item.loc[i,'code_name']=item[0] 680 + possessed_item.loc[i,'code_name']=item[0]
662 - possesd_item.loc[i,'holding_amount']=int(item[1]) 681 + possessed_item.loc[i,'holding_amount']=int(item[1]) # 보유량
663 - possesd_item.loc[i,'purchase_price']=int(item[2]) 682 + possessed_item.loc[i,'purchase_price']=int(item[2]) # 매수가
664 - possesd_item.loc[i,'present_price']=int(item[3]) 683 + possessed_item.loc[i,'present_price']=int(item[3]) # 현재가
665 - possesd_item.loc[i,'valuation_profit']=int(item[4]) 684 + possessed_item.loc[i,'valuation_profit']=int(item[4]) # 평가수익률
666 - possesd_item.loc[i,'rate']=float(item[5]) 685 + possessed_item.loc[i,'rate']=float(item[5]) # 수익률
667 - possesd_item.loc[i,'item_total_purchase']=int(item[6]) 686 + possessed_item.loc[i,'item_total_purchase']=int(item[6]) # 총매수금액
668 687
669 - possesd_item.to_sql("possesd_item",self.engine_bot,if_exists='replace') 688 + possessed_item.to_sql("possessed_item",self.engine_bot,if_exists='replace')
670 - self.contract_sync() 689 + self.chegyul_sync()
671 690
672 - # 현재 소유하고 있는 종목에 대해 transaction 테이블을 업데이트 691 + # 매수 완료한 항목을 all_stocks에 기록하는 함수
673 def chegyul_sync(self): 692 def chegyul_sync(self):
674 - query="select code,code_name,rate from possessed_item " \ 693 + # 신규 매수한 항목을 가져온다
675 - "where code not in (select code from transaction where sell_date='0' group by code) group by code" 694 + query = "select code, code_name, rate from possessed_item p " \
695 + "where p.code not in (select a.code from all_stocks a where a.sell_date = '0' group by a.code) " \
696 + "group by p.code"
676 result=self.engine_bot.execute(query).fetchall() 697 result=self.engine_bot.execute(query).fetchall()
677 698
678 for item in result: 699 for item in result:
...@@ -692,7 +713,7 @@ class Open_Api(QAxWidget): ...@@ -692,7 +713,7 @@ class Open_Api(QAxWidget):
692 else: 713 else:
693 continue 714 continue
694 715
695 - self.db_to_transaction(self.not_contract['주문번호'],item.code,contract_check,self.not_contract['체결가'],item.rate) 716 + self.db_to_all_stocks(self.not_contract['주문번호'],item.code,chegyul_check,self.not_contract['체결가'],item.rate)
696 717
697 # posses_item 테이블을 업데이트 했을 경우 setting data 테이블에 업데이트 한 날짜를 표시 718 # posses_item 테이블을 업데이트 했을 경우 setting data 테이블에 업데이트 한 날짜를 표시
698 def setting_data_posses_stock(self): 719 def setting_data_posses_stock(self):
...@@ -730,7 +751,7 @@ class Open_Api(QAxWidget): ...@@ -730,7 +751,7 @@ class Open_Api(QAxWidget):
730 def is_stock_table_exist(self,code_name): 751 def is_stock_table_exist(self,code_name):
731 query = "select 1 from information_schema.tables where table_schema ='stock_info' and table_name = '{}'" 752 query = "select 1 from information_schema.tables where table_schema ='stock_info' and table_name = '{}'"
732 result=self.engine_stock.execute(query.format(code_name)).fetchall() 753 result=self.engine_stock.execute(query.format(code_name)).fetchall()
733 - if result: 754 + if result:
734 return True 755 return True
735 else: 756 else:
736 return False 757 return False
...@@ -745,20 +766,20 @@ class Open_Api(QAxWidget): ...@@ -745,20 +766,20 @@ class Open_Api(QAxWidget):
745 else: 766 else:
746 return str(0) 767 return str(0)
747 768
748 - # 체결이 완료되었는지 확인하고 transaction(거래내역) 테이블을 업데이트하는 함수 769 + # 체결이 완료되었는지 확인하고 all_stocks(거래내역)테이블을 업데이트
749 def check_chegyul(self): 770 def check_chegyul(self):
750 - query="select code from transaction where chegyul_check='1'" 771 + query="select code from all_stocks where chegyul_check='1' and (sell_date='0' or sell_date=''"
751 - rows=self.engine_bot.execute(query).fetchall() 772 + result=self.engine_bot.execute(query).fetchall()
752 773
753 - for r in rows: 774 + for r in result:
754 self.set_input_value("종목코드",r.code) 775 self.set_input_value("종목코드",r.code)
755 self.set_input_value("조회구분",1) 776 self.set_input_value("조회구분",1)
756 self.set_input_value("계좌번호",self.account_no) 777 self.set_input_value("계좌번호",self.account_no)
757 self.comm_rq_data("opt10076_req","opt10076",0,"0350") 778 self.comm_rq_data("opt10076_req","opt10076",0,"0350")
758 779
759 - query=f"update transaction set chegyul_check='0' where code='{r.code}' and sell_data='0' " \ 780 + query=f"update all_stocks set chegyul_check='0' where code='{r.code}' and sell_data='0' " \
760 f"order by buy_date desc limit 1" 781 f"order by buy_date desc limit 1"
761 - # 거래가 완료된 항목은 주문번호가 존재하지 않음 782 + # 거래가 완료된 항목은 주문번호 등 데이터가 존재하지 않음
762 # 거래가 완료된 항목에 대해서 contract_check항목을 '0'으로 업데이트 783 # 거래가 완료된 항목에 대해서 contract_check항목을 '0'으로 업데이트
763 if not self.not_contract['주문번호']: 784 if not self.not_contract['주문번호']:
764 self.engine_bot.execute(query) 785 self.engine_bot.execute(query)
...@@ -769,28 +790,45 @@ class Open_Api(QAxWidget): ...@@ -769,28 +790,45 @@ class Open_Api(QAxWidget):
769 else: 790 else:
770 logger.debug("미체결 종목이 존재합니다") 791 logger.debug("미체결 종목이 존재합니다")
771 792
793 +
772 # 매도했을 경우 possessed_item(보유종목) 테이블에서 항목을 삭제 794 # 매도했을 경우 possessed_item(보유종목) 테이블에서 항목을 삭제
773 def delete_possessed_item(self,code): 795 def delete_possessed_item(self,code):
774 query=f"delete from possessed_item where code={code}" 796 query=f"delete from possessed_item where code={code}"
775 self.engine_bot.execute(query) 797 self.engine_bot.execute(query)
776 798
777 - # 매도한 후 transaction 테이블 업데이트 799 + # 매도한 후 all_stocks 테이블 업데이트
778 - def check_sell_final(self,code): 800 + def sell_final_check(self,code):
779 - query=f"update transaction chegyul_check='0', sell_date='{self.today_time}' " \ 801 + query=f"SELECT valuation_profit, rate, item_total_purchase, present_price FROM possessed_item " \
780 - f"where code='{code}' and sell_date='0' order by buy_date desc" 802 + f"WHERE code='{code}' LIMIT 1"
781 - self.engine_bot.execute(query) 803 + result=self.engine_bot.execute(query).fetchall()
782 804
783 - # posses_item테이블에는 존재하지만 transaction_history테이블에 존재하지 않는 항목에 대해 업데이트 805 + if result:
784 - def final_check_contract(self): 806 + item = result[0]
785 - query="select code from transaction_history" \ 807 + query=f"update all_stocks set " \
786 - "where" \ 808 + f"item_total_purchase={item.item_total_purchase}, chegyul_check=0, " \
787 - "sell_date='%s' or sell_date='%s' and code not in (select code from posses_item) and contract_check!='%s'" 809 + f"sell_date={self.today_time}, valuation_profit={item.valuation_profit}," \
788 - result=self.engine_bot.execute(query%(0,"",1)).fetchall() 810 + f"sell_rate={item.rate}, sell_price={item.present_price} " \
789 - num=len(result) 811 + f"where code='{code}' and sell_date='0' order by buy_date desc limit 1"
790 - for i in range(num): 812 + self.engine_bot.execute(query)
791 - self.sell_check(result[i][0]) 813 +
792 - query="update setting_data set final_contract_check='%s'" 814 + # 매도한 항목을 possessed_item(보유종목) 테이블에서 삭제
793 - self.engine_bot.execute(query%(self.today)) 815 + self.engine_bot.execute(f"DELETE FROM possessed_item WHERE code = '{code}'")
816 +
817 + # 매도했을 때, possessed_item에서 삭제되었지만 all_stocks에 sell_date 칼럼이 업데이트 되지 않은 항목들을 처리하는 함수
818 + def final_chegyul_check(self):
819 + query = "select code from all_stocks a " \
820 + "where (a.sell_date = '0' or a.sell_date ='') and a.code not in ( select code from possessed_item) "\
821 + "and a.chegyul_check != '1'"
822 +
823 + result = self.engine_bot.execute(query).fetchall()
824 + num = len(result)
825 +
826 + for t in range(num):
827 + self.sell_final_check2(result[t][0])
828 +
829 + # 모든 항목을 처리했으면 setting_data테이블의 final_chegyul_check 항목에 오늘 날짜를 저장
830 + query = f"UPDATE setting_data SET final_chegyul_check='{self.today}' limit 1"
831 + self.engine_bot.execute(query)
794 832
795 # 가지고 있던 종목을 판매하여 posses_item테이블에 내역이 존재하지 않지만 transaction_history에 매도처리가 되지 않은 항목 업데이트 833 # 가지고 있던 종목을 판매하여 posses_item테이블에 내역이 존재하지 않지만 transaction_history에 매도처리가 되지 않은 항목 업데이트
796 def sell_check(self,code): 834 def sell_check(self,code):
...@@ -808,9 +846,10 @@ class Open_Api(QAxWidget): ...@@ -808,9 +846,10 @@ class Open_Api(QAxWidget):
808 846
809 # setting_data에 possessed_item 항목을 업데이트 847 # setting_data에 possessed_item 항목을 업데이트
810 def set_setting_data_possessed_item(self): 848 def set_setting_data_possessed_item(self):
811 - query=f"update setting_data set possessed_item={self.today}" 849 + query=f"update setting_data set possessed_item={self.today} limit 1"
812 self.engine_bot.execute(query) 850 self.engine_bot.execute(query)
813 851
852 + # 특정 종목의 틱(1분별) 데이터 조회 함수
814 def get_total_data_min(self,code,code_name,start): 853 def get_total_data_min(self,code,code_name,start):
815 self.ohlcv=defaultdict(list) 854 self.ohlcv=defaultdict(list)
816 855
...@@ -819,10 +858,10 @@ class Open_Api(QAxWidget): ...@@ -819,10 +858,10 @@ class Open_Api(QAxWidget):
819 self.set_input_value("수정주가구분",1) 858 self.set_input_value("수정주가구분",1)
820 self.comm_rq_data("opt10080_req","opt10080",0,"1999") 859 self.comm_rq_data("opt10080_req","opt10080",0,"1999")
821 860
822 - self.is_craw_table_exist=False 861 + self.craw_table_exist=False
823 862
824 if self.is_min_craw_table_exist(code_name): 863 if self.is_min_craw_table_exist(code_name):
825 - self.is_craw_table_exist=True 864 + self.craw_table_exist=True
826 self.craw_db_last_min=self.get_craw_db_last_min(code_name) 865 self.craw_db_last_min=self.get_craw_db_last_min(code_name)
827 self.craw_db_last_min_sum_volume=self.get_craw_db_last_min_sum_volume(code_name) 866 self.craw_db_last_min_sum_volume=self.get_craw_db_last_min_sum_volume(code_name)
828 867
...@@ -840,11 +879,10 @@ class Open_Api(QAxWidget): ...@@ -840,11 +879,10 @@ class Open_Api(QAxWidget):
840 if self.ohlcv['date'][-1]<self.craw_db_last_min: 879 if self.ohlcv['date'][-1]<self.craw_db_last_min:
841 break 880 break
842 881
843 - time.sleep(TR_REQ_TIME_INTERBVAL) 882 + time.sleep(TR_REQ_TIME_INTERVAL)
844 883
845 if len(self.ohlcv['date']==0 or self.ohlcv['date'][0]==''): 884 if len(self.ohlcv['date']==0 or self.ohlcv['date'][0]==''):
846 return [] 885 return []
847 -
848 if self.ohlcv['date']=='': 886 if self.ohlcv['date']=='':
849 return [] 887 return []
850 888
...@@ -852,6 +890,305 @@ class Open_Api(QAxWidget): ...@@ -852,6 +890,305 @@ class Open_Api(QAxWidget):
852 890
853 return df 891 return df
854 892
893 + # 특정 종목의 일자별 거래 데이터 조회 함수
894 + def get_total_data(self,code,code_name,date):
895 + logger.debug("-* get_total_data function *-")
896 +
897 + self.ohlcv=defaultdict(list)
898 + self.set_input_value("종목코드",code)
899 + self.set_input_value("기준일자",date)
900 + self.set_input_value("수정주가구분",1)
901 +
902 + self.comm_rq_data("opt10081_req","opt10081",0,"0101")
903 +
904 + if not self.is_craw_table_exist(code_name):
905 + while self.remained_data:
906 + self.set_input_value("종목코드", code)
907 + self.set_input_value("기준일자", date)
908 + self.set_input_value("수정주가구분", 1)
909 + self.comm_rq_data("opt10081_req", "opt10081", 2, "0101")
910 +
911 + if len(self.ohlcv)==0:
912 + return []
913 + if self.ohlcv['date']=='':
914 + return []
915 +
916 + df=DataFrame(self.ohlcv,columns=['date','open','high','low','close','volume'])
917 + return df
918 +
919 + # daily_craw 데이터베이스에 {code_name} 테이블이 존재하는지 확인하는 함수
920 + def is_craw_table_exist(self,code_name):
921 + query="select 1 from information_schema.tables where table_schema='daily_craw' and table_name='{}'"
922 + result=self.engine_daily_craw.execute(query.format(code_name))
923 + if result:
924 + return True
925 + else:
926 + return False
927 +
928 + # min_craw 데이터베이스에 {code_name} 테이블이 존재하는지 확인하는 함수
929 + def is_min_craw_table_exist(self,code_name):
930 + query = "select 1 from information_schema.tables where table_schema ='min_craw' and table_name = '{}'"
931 + result = self.engine_craw.execute(query.format(code_name)).fetchall()
932 + if result:
933 + return True
934 + else:
935 + return False
936 +
937 + # min_craw 테이블에서 마지막에 저장한 행의 sum_volume값을 가져오는 함수
938 + def get_craw_db_last_min_sum_volume(self,code_name):
939 + query = f"SELECT sum_volume from `{code_name}` order by date desc limit 1"
940 + result = self.engine_craw.execute(query).fetchall()
941 + if len(result):
942 + return result[0][0]
943 + else:
944 + return str(0)
945 +
946 + # min_craw 테이블에서 마지막에 저장한 행의 시간(분) 정보를 가져오는 함수
947 + def get_craw_db_last_min(self,code_name):
948 + query = f"SELECT date from `{code_name}` order by date desc limit 1"
949 + result = self.engine_craw.execute(query).fetchall()
950 + if len(result):
951 + return result[0][0]
952 + # 신생
953 + else:
954 + return str(0)
955 +
956 + # min_craw 테이블에서 마지막에 저장한 행의 날짜 정보를 가져오는 함수
957 + def get_craw_db_last_date(self,code_name):
958 + query = f"SELECT date from `{code_name}` order by date desc limit 1"
959 + result = self.engine_craw.execute(query).fetchall()
960 + if len(result):
961 + return result[0][0]
962 + else:
963 + return str(0)
964 +
965 + # 특정 종목의 특정 날짜의 특정 데이터(시가/종가/고가/저가/거래량)를 반환하는 함수
966 + def get_one_day_option_data(self,code,start,option):
967 + self.ohlcv=defaultdict(list)
968 +
969 + self.set_input_value("종목코드",code)
970 + self.set_input_value("기준일자",start)
971 + self.set_input_value("수정주가구분",1)
972 + self.comm_rq_data("opt10081_req","opt10081",0,"0101")
973 +
974 + if self.ohlcv['date']=='':
975 + return False
976 +
977 + df=DataFrame(self.ohlcv,columns=['open','high','low','close','volume'],index=self.ohlcv['date'])
978 +
979 + if df.empty:
980 + return False
981 +
982 + if option=="open":
983 + return df.iloc[0,0]
984 + elif option=='high':
985 + return df.iloc[0,1]
986 + elif option=='low':
987 + return df.iloc[0,2]
988 + elif option=='close':
989 + return df.iloc[0,3]
990 + elif option=='volume':
991 + return df.iloc[0,4]
992 + else:
993 + return False
994 +
995 + # open_api에 주문요청을 보내는 함수. 성공할 경우 return 0
996 + # param - sRQName : 사용자 구분명
997 + # sScrNo : 화면번호
998 + # sAccNo : 계좌번호
999 + # nOrderType : 주문유형 (1.신규매수/2.신규매도/3.매수취소/4.매도취소/5.매수정정/6.매도정정
1000 + # code : 종목코드
1001 + # nQty : 주문수량
1002 + # nPrice : 주문가격
1003 + # sHogaGb : 거래구분 (00.지정가/03.시장가/05.조건부지정가/06.최유리지정가/07.최우선지정가/10.지정가IOC/
1004 + # 13.시장가IOC/16.최유리IOC/20.지정가FOK/23.시장가FOK/26.최유리FOK/61.장전시간외종가
1005 + # 62.시간외단일가매매/81.장후시간외종가)
1006 + # sOrgOrderNo : 원주문번호. 신규주문은 공백, 정정(취소) 주문할 원주문번호 입력
1007 + # return - 0 : 성공
1008 + # - -308 : 주문가능횟수 초과. 1초에 5회만 주문 가능
1009 + # - 이외 : 주문실패
1010 + def send_order(self,sRQName,sScrNo,sAccNo,nOrderType,code,nQty,nPrice,sHogaGb,sOrgOrderNo):
1011 + logger.debug("send order")
1012 + try:
1013 + self.exit_check()
1014 + self.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
1015 + [sRQName,sScrNo,sAccNo,nOrderType,code,nQty,nPrice,sHogaGb,sOrgOrderNo])
1016 + except Exception as e:
1017 + logger.critical(e)
1018 +
1019 + # 코드명에 해당하는 종목코드를 반환하는 함수
1020 + def codename_to_code(self,codename):
1021 + # 데이터베이스에 종목명과 같은 값을 가지는 행의 code를 반환
1022 + query = f"select code from stock_item_all where code_name={codename}"
1023 + result = self.engine_daily_buy_list.execute(query).fetchall()
1024 + if len(result)!=0:
1025 + return result[0][0]
1026 +
1027 + # 종목명의 길이가 길 경우
1028 + query = f"select code from stock_item_all where code_name like '{codename}%'"
1029 + result = self.engine_daily_buy_list.execute(query).fetchall()
1030 + if len(result)!=0:
1031 + return result[0][0]
1032 +
1033 + # 종목명에 해당하는 값이 없을 경우 return False
1034 + return False
1035 +
1036 + # 매수 완료 후 데이터베이스를 업데이트 하는 함수
1037 + def end_invest_count_check(self,code):
1038 + query = f"update all_stocks set chegyul_check='0' WHERE code='{code}' and sell_date = '0' " \
1039 + f"ORDER BY buy_date desc LIMIT 1"
1040 + self.engine_bot.execute(query)
1041 +
1042 + # possessed_item 테이블에 중복으로 데이터가 반영되는 것을 막기 위해 possessed_item에서 해당 종목 삭제
1043 + query = f"delete from possessed_item where code ={code}"
1044 + self.engine_bot.execute(query)
1045 +
1046 + # 잔액이 생겨서 다시 매수할 수 있는 상황이 되었을 경우, setting_data의 today_buy_stop 옵션을 0으로 변경경
1047 + def reset_buy_check(self):
1048 + query = "UPDATE setting_data SET today_buy_stop='0' WHERE id='1'"
1049 + self.engine_bot.execute(query)
1050 +
1051 + # 투자 가능한 잔액이 부족하거나, 매수할 종목이 더 이상 없는 경우 setting_data의 today_buy_stop옵션을 0으로 변경
1052 + # 더이상 매수하지 않음
1053 + def buy_check_stop(self):
1054 + query = f"UPDATE setting_data SET today_buy_stop='{self.today}' limit 1"
1055 + self.engine_bot.execute(query)
1056 +
1057 + # 잔액을 확인하는 함수
1058 + # 잔액이 부족하여 더이상 투자를 진행할 수 없는 경우, janfo_is_null을 True로 설정한 후 False 반환
1059 + def check_jango(self):
1060 + self.get_deposit()
1061 + try:
1062 + if int(self.d2_deposit_before_format) > (int(self.sf.limit_money)):
1063 + self.jango_is_null = False # trade 루프를 돌다가 잔액이 부족해졌을 때 루프를 빠져나오기 위한 변수
1064 + return True
1065 + else:
1066 + self.jango_is_null = True
1067 + return False
1068 + except Exception as e:
1069 + logger.critical(e)
1070 +
1071 + # today_buy_stop 칼럼을 확인하는 함수
1072 + # setting_data테이블의 today_buy_stop 칼럼에 오늘 날짜가 적혀있는 경우 매수 중지
1073 + def buy_check(self):
1074 + query = "select today_buy_stop from setting_data limit 1"
1075 + result = self.engine_JB.execute(sql).fetchall()[0][0]
1076 +
1077 + if result != self.today:
1078 + return True
1079 + else:
1080 + return False
1081 +
1082 + # 구매할 수량을 계산하는 함수
1083 + def buy_num_count(self,invest_unit,present_price):
1084 + return int(invest_unit/present_price)
1085 +
1086 + # 거래 함수
1087 + def trade(self):
1088 + # 실시간 현재가(close)를 저장
1089 + # 현재시점의 종가(close)는 현재가와 같다
1090 + current_price = self.get_one_day_option_data(self.get_today_buy_list_code, self.today, 'close')
1091 +
1092 + if current_price == False:
1093 + return False
1094 +
1095 + min_buy_limit = int(self.get_today_buy_list_close) * self.sf.invest_min_limit_rate # 매수가격 최저 범위
1096 + max_buy_limit = int(self.get_today_buy_list_close) * self.sf.invest_limit_rate # 매수가격 최고 범위
1097 +
1098 + # 현재가가 매수 가격 최저 범위와 매수 가격 최고 범위 안에 들어와 있다면 매수
1099 + if min_buy_limit < current_price < max_buy_limit:
1100 + buy_num = self.buy_num_count(self.invest_unit, int(current_price))
1101 + logger.debug(
1102 + "매수+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-",
1103 + self.get_today_buy_list_code, self.get_today_buy_list_close, current_price, buy_num, min_buy_limit,
1104 + max_buy_limit, self.sf.invest_limit_rate, self.d2_deposit_before_format, self.today, self.today_detail,
1105 + self.date_rows_yesterday, self.invest_unit, int(current_price) * int(buy_num))
1106 +
1107 + # 03 시장가 매수
1108 + # 4번째 parameter: 1: 신규매수 / 2: 신규매도 / 3:매수취소 / 4:매도취소 / 5: 매수정정 / 6:매도정정
1109 + self.send_order("send_order_req", "0101", self.account_number, 1, self.get_today_buy_list_code, buy_num, 0,
1110 + "03", "")
1111 +
1112 + # 만약 sf.only_nine_buy가 False 이면, 매도 후에 잔액이 생기면 다시 매수를 시작
1113 + # sf.only_nine_buy가 True이면 1회만 매수, 1회 매수 시 잔액이 부족해지면 바로 매수 중단
1114 + if not self.jango_check() and self.sf.use_nine:
1115 + # setting_data에 today_buy_stop을 1 로 설정
1116 + self.buy_check_stop()
1117 +
1118 + def get_today_buy_list(self):
1119 + # 구매할 목록을 저장하는 테이블(realtime_daily_buy_list)이 존재하지 않거나, 테이블 내에 항목이 존재하지 않는다면 return
1120 + if self.sf.is_simul_table_exist(self.db_name, "realtime_daily_buy_list"):
1121 + self.sf.get_realtime_daily_buy_list()
1122 + if self.sf.len_df_realtime_daily_buy_list == 0:
1123 + return
1124 + else:
1125 + return
1126 +
1127 + # 만약에 realtime_daily_buy_list 의 종목 수가 1개 이상이면
1128 + for i in range(self.sf.len_df_realtime_daily_buy_list):
1129 + code = self.sf.df_realtime_daily_buy_list.loc[i, 'code']
1130 + close = self.sf.df_realtime_daily_buy_list.loc[i, 'close']
1131 + check_item = self.sf.df_realtime_daily_buy_list.loc[i, 'check_item'] # 매수확인. 오늘 매수한 종목이면 1, 아니면 0
1132 +
1133 + # 잔액이 부족한 경우 break
1134 + if self.jango_is_null:
1135 + break
1136 + # 이미 매수한 종목은 건너뛰고 다음종목 거래래
1137 + if check_item == True:
1138 + continue
1139 + else:
1140 + self.get_today_buy_list_code = code
1141 + self.get_today_buy_list_close = close
1142 + # 매수 하기 전에 해당 종목의 check_item을 1로 변경
1143 + query = f"UPDATE realtime_daily_buy_list SET check_item='1' WHERE code='{self.get_today_buy_list_code}'"
1144 + self.engine_bot.execute(query)
1145 + self.trade()
1146 +
1147 + # 모든 매수를 마쳤으면 더이상 매수 하지 않도록 설정
1148 + if self.sf.use_nine:
1149 + self.buy_check_stop()
1150 +
1151 + # 보유하고 있는 종목에 대해 all_stocks(거래내역) 테이블을 업데이트
1152 + def rate_check(self):
1153 + query = "select code,holding_amount,purchase_price,present_price,valuation_profit,rate,item_total_purchase "\
1154 + "from possessed_item group by code"
1155 + result=self.engine_bot.execute(query).fetchall()
1156 + num = len(result)
1157 +
1158 + for k in range(num):
1159 + code = result[k][0]
1160 + holding_amount = result[k][1]
1161 + purchase_price = result[k][2]
1162 + present_price =result[k][3]
1163 + valuation_profit=result[k][4]
1164 + rate = result[k][5]
1165 + item_total_purchase = result[k][6]
1166 +
1167 + query = f"update all_stocks set " \
1168 + f"holding_amount ='{holding_amount}', purchase_price ='{purchase_price}', " \
1169 + f"present_price='{present_price}',valuation_profit='{valuation_profit}',rate='{rate}'," \
1170 + f"item_total_purchase='{item_total_purchase}' " \
1171 + f"where code='{code}' and sell_date = '0'"
1172 + self.engine_bot.execute(query)
1173 +
1174 + # 매도 후 possessed_item(보유종목) 테이블에는 없지만,
1175 + # all_stocks(거래내역) 테이블에 sell_date가 업데이트되지 않은 항목을 처리하는 함수
1176 + def sell_final_check2(self,code):
1177 + query = f"UPDATE all_stocks SET chegyul_check='0', sell_date ='{self.today_time}' " \
1178 + f"WHERE code='{code}' and sell_date ='0' ORDER BY buy_date desc LIMIT 1"
1179 + self.engine_bot.execute(query)
1180 +
1181 + # all_stocks(거래내역) 테이블에서 현재 보유중인 종목이 존재하는지 확인하는 함수
1182 + def is_all_stock_check(self,code):
1183 + query = f"select code from all_stocks where code='{code}' and (sell_date ='0' or sell_date='') " \
1184 + f"ORDER BY buy_date desc LIMIT 1"
1185 + result=self.engine_bot.execute(query).fetchall()
1186 + if len(result) != 0:
1187 + return True
1188 + else:
1189 + return False
1190 +
1191 +
855 # 주식일봉차트조회 요청 함수 - 하나의 데이터만 저장 1192 # 주식일봉차트조회 요청 함수 - 하나의 데이터만 저장
856 def _opt10081(self, sRQName, sTrCode): 1193 def _opt10081(self, sRQName, sTrCode):
857 code = self._get_comm_data(sTrCode, sRQName, 0, "종목코드") 1194 code = self._get_comm_data(sTrCode, sRQName, 0, "종목코드")
...@@ -895,8 +1232,11 @@ class Open_Api(QAxWidget): ...@@ -895,8 +1232,11 @@ class Open_Api(QAxWidget):
895 1232
896 # 예수금 상세현황요청 함수 1233 # 예수금 상세현황요청 함수
897 def _opw00001(self,sRQName,sTrCode): 1234 def _opw00001(self,sRQName,sTrCode):
898 - self.deposit=self._get_comm_data(sTrCode,sRQName,0,"d+2출금가능금액") 1235 + try:
899 - self.deposit=int(self.deposit) 1236 + self.d2_deposit_before_format = self._get_comm_data(sTrCode, sRQName, 0, "d+2출금가능금액")
1237 + self.d2_deposit = self.change_format(self.d2_deposit_before_format)
1238 + except Exception as e:
1239 + logger.critical(e)
900 1240
901 # 계좌평가 잔고내역을 저장하는 변수 초기화 1241 # 계좌평가 잔고내역을 저장하는 변수 초기화
902 def reset_opw00018_output(self): 1242 def reset_opw00018_output(self):
...@@ -913,23 +1253,23 @@ class Open_Api(QAxWidget): ...@@ -913,23 +1253,23 @@ class Open_Api(QAxWidget):
913 self.total_earning_rate=self._get_comm_data(sTrCode,sRQName,0,"총수익률(%)") 1253 self.total_earning_rate=self._get_comm_data(sTrCode,sRQName,0,"총수익률(%)")
914 self.estimated_deposit=self._get_comm_data(sTrCode,sRQName,0,"추정예탁자산") 1254 self.estimated_deposit=self._get_comm_data(sTrCode,sRQName,0,"추정예탁자산")
915 1255
916 - self.total_purchase_price=int(self.total_purchase_price) 1256 + self.change_total_purchase_price = self.change_format(self.total_purchase_price)
917 - self.total_eval_price=int(self.total_eval_price) 1257 + self.change_total_eval_price = self.change_format(self.total_eval_price)
918 - self.total_eval_profit_loss_price=int(self.total_eval_profit_loss_price) 1258 + self.change_total_eval_profit_loss_price = self.change_format(self.total_eval_profit_loss_price)
919 - self.total_earning_rate=float(self.total_earning_rate) 1259 + self.change_total_earning_rate = self.change_format2(self.total_earning_rate)
920 - self.estimated_deposit=int(self.estimated_deposit) 1260 + self.change_estimated_deposit = self.change_format(self.estimated_deposit)
921 1261
922 - self.opw00018_output['single'].append(self.total_purchase_price) 1262 + self.opw00018_output['single'].append(self.change_total_purchase_price)
923 - self.opw00018_output['single'].append(self.total_eval_price) 1263 + self.opw00018_output['single'].append(self.change_total_eval_price)
924 - self.opw00018_output['single'].append(self.total_eval_profit_loss_price) 1264 + self.opw00018_output['single'].append(self.change_total_eval_profit_loss_price)
925 - self.opw00018_output['single'].append(self.total_earning_rate) 1265 + self.opw00018_output['single'].append(self.change_total_earning_rate)
926 - self.opw00018_output['single'].append(self.estimated_deposit) 1266 + self.opw00018_output['single'].append(self.change_estimated_deposit)
927 1267
928 # 종목별 평가 잔고 데이터 - 멀티데이터 저장 1268 # 종목별 평가 잔고 데이터 - 멀티데이터 저장
929 rows=self._get_repeat_cnt(sTrCode,sRQName) 1269 rows=self._get_repeat_cnt(sTrCode,sRQName)
930 1270
931 for i in range(rows): 1271 for i in range(rows):
932 - code=self._get_comm_data(sTrCode,sRQName,i,"종목번호") 1272 + code=code_pattern.search(self._get_comm_data(sTrCode,sRQName,i,"종목번호")).group(0)
933 name=self._get_comm_data(sTrCode,sRQName,i,"종목명") 1273 name=self._get_comm_data(sTrCode,sRQName,i,"종목명")
934 quantity=self._get_comm_data(sTrCode,sRQName,i,"보유수량") 1274 quantity=self._get_comm_data(sTrCode,sRQName,i,"보유수량")
935 purchase_price=self._get_comm_data(sTrCode,sRQName,i,"매입가") 1275 purchase_price=self._get_comm_data(sTrCode,sRQName,i,"매입가")
...@@ -938,13 +1278,12 @@ class Open_Api(QAxWidget): ...@@ -938,13 +1278,12 @@ class Open_Api(QAxWidget):
938 earning_rate=self._get_comm_data(sTrCode,sRQName,i,"수익률(%)") 1278 earning_rate=self._get_comm_data(sTrCode,sRQName,i,"수익률(%)")
939 item_total_purchase=self._get_comm_data(sTrCode,sRQName,i,"매입금액") 1279 item_total_purchase=self._get_comm_data(sTrCode,sRQName,i,"매입금액")
940 1280
941 - code=code[1:] 1281 + quantity = self.change_format(quantity)
942 - quantity=int(quantity) 1282 + purchase_price = self.change_format(purchase_price)
943 - purchase_price=int(purchase_price) 1283 + current_price = self.change_format(current_price)
944 - current_price=int(current_price) 1284 + eval_profit_loss_price = self.change_format(eval_profit_loss_price)
945 - eval_profit_loss_price=int(eval_profit_loss_price) 1285 + earning_rate = self.change_format2(earning_rate)
946 - earning_rate=float(earning_rate) 1286 + item_total_purchase = self.change_format(item_total_purchase)
947 - item_total_purchase=int(item_total_purchase)
948 1287
949 self.opw00018_output['multi'].append( 1288 self.opw00018_output['multi'].append(
950 [name,quantity,purchase_price,current_price,eval_profit_loss_price,earning_rate,item_total_purchase,code] 1289 [name,quantity,purchase_price,current_price,eval_profit_loss_price,earning_rate,item_total_purchase,code]
...@@ -952,8 +1291,12 @@ class Open_Api(QAxWidget): ...@@ -952,8 +1291,12 @@ class Open_Api(QAxWidget):
952 1291
953 # 일자별 실현 손익 요청 1292 # 일자별 실현 손익 요청
954 def _opt10074(self,sRQName,sTrCode): 1293 def _opt10074(self,sRQName,sTrCode):
955 - self.total_profit=self._get_comm_data(sTrCode,sRQName,0,"실현손익") 1294 + try:
956 - self.today_profit=self._get_comm_data(sTrCode,sRQName,0,"당일매도손익") 1295 + rows = self._get_repeat_cnt(sTrCode, sRQName)
1296 + self.total_profit = self._get_comm_data(sTrCode,sRQName, 0, "실현손익")
1297 + self.today_profit = self._get_comm_data(sTrCode,sRQName, 0, "당일매도손익")
1298 + except Exception as e:
1299 + logger.critical(e)
957 1300
958 # 위탁종합거래내역 요청 함수 1301 # 위탁종합거래내역 요청 함수
959 def _opw00015(self,sRQName,sTrCode): 1302 def _opw00015(self,sRQName,sTrCode):
...@@ -966,7 +1309,7 @@ class Open_Api(QAxWidget): ...@@ -966,7 +1309,7 @@ class Open_Api(QAxWidget):
966 def _opt10076(self,sRQName,sTrCode): 1309 def _opt10076(self,sRQName,sTrCode):
967 outputs=['주문번호','종목명','주문구분','주문가격','주문수량','체결가','체결량','미체결수량', 1310 outputs=['주문번호','종목명','주문구분','주문가격','주문수량','체결가','체결량','미체결수량',
968 '당일매매수수료','당일매매세금','주문상태','매매구분','원주문번호','주문시간','종목코드'] 1311 '당일매매수수료','당일매매세금','주문상태','매매구분','원주문번호','주문시간','종목코드']
969 - self._data={} 1312 + self._data={} # 미체결 항목을 저장하는 변수
970 1313
971 for key in outputs: 1314 for key in outputs:
972 if key not in ['주문번호','원주문번호','주문시간','종목코드']: 1315 if key not in ['주문번호','원주문번호','주문시간','종목코드']:
...@@ -990,7 +1333,7 @@ class Open_Api(QAxWidget): ...@@ -990,7 +1333,7 @@ class Open_Api(QAxWidget):
990 today_profit=self._get_comm_data(sTrCode,sRQName,i,"당일매도손익") 1333 today_profit=self._get_comm_data(sTrCode,sRQName,i,"당일매도손익")
991 earning_rate=self._get_comm_data(sTrCode,sRQName,i,"손익율") 1334 earning_rate=self._get_comm_data(sTrCode,sRQName,i,"손익율")
992 1335
993 - code=code.lstrip('A') 1336 + code=self.change_format4(code)
994 1337
995 self.opt10073_output['multi'].append([date,code,code_name,amount,today_profit,earning_rate]) 1338 self.opt10073_output['multi'].append([date,code,code_name,amount,today_profit,earning_rate])
996 1339
...@@ -1013,6 +1356,78 @@ class Open_Api(QAxWidget): ...@@ -1013,6 +1356,78 @@ class Open_Api(QAxWidget):
1013 self.ohlcv['volume'].append(int(volume)) 1356 self.ohlcv['volume'].append(int(volume))
1014 self.ohlcv['sum_volume'].append(int(0)) 1357 self.ohlcv['sum_volume'].append(int(0))
1015 1358
1359 + # 데이터베이스로부터 보유하고 있는 종목의 보유량(holding amount)을 가져오는 함수
1360 + def get_holding_amount(self,code):
1361 + logger.debug("-* get_holding_amount function *-")
1362 + query=f"select holding_amount from possessed_item where code={code}"
1363 + result=self.engine_bot.execute(query).fetchall()
1364 + if len(result):
1365 + return result[0][0]
1366 + else:
1367 + logger.debug("Holding amount is empty.")
1368 + return False
1369 +
1370 + # open_api로부터 받아온 데이터의 형식을 변형하는 함수
1371 + # 데이터 값 앞의 의미없는 0을 제거
1372 + def change_format(self,data):
1373 + try:
1374 + strip_data=data.lstrip('0')
1375 + # 유효한 값이 존재하지 않을 경우 0으로 대체
1376 + if strip_data=="":
1377 + strip_data='0'
1378 + return int(strip_data)
1379 + except Exception as e:
1380 + logger.critical(e)
1381 +
1382 + # 수익률을 소수 형태로 변환하는 함수
1383 + def change_format2(self,data):
1384 + try:
1385 + strip_data=data.lstrip('-0')
1386 + # 유효한 값이 없을 경우 0으로 대체
1387 + if strip_data=="":
1388 + strip_data='0'
1389 + else:
1390 + strip_data=str(float(strip_data)/self.mod_gubun)
1391 + # 수익률< 1 일 경우
1392 + if strip_data.startswith('.'):
1393 + strip_data='0'+strip_data
1394 + # 수익률 < 0 일 경우
1395 + if data.startswith('-'):
1396 + strip_data='-'+strip_data
1397 + return strip_data
1398 + except Exception as e:
1399 + logger.critical(e)
1400 +
1401 + # 특수문자(%), 앞뒤 공백 제거 함수
1402 + def change_format3(self,data):
1403 + try:
1404 + strip_data=data.stript('%')
1405 + strip_data=strip_data.strip()
1406 + return strip_data
1407 + except Exception as e:
1408 + logger.critical(e)
1409 +
1410 + # 코드 앞의 문자를 제거하는 함수수
1411 + def change_format4(sel,data):
1412 + try:
1413 + strip_data=data.lstrip('A')
1414 + return strip_data
1415 + except Exception as e:
1416 + logger.critical(e)
1417 +
1418 + # open api 조회 요청 수를 체크하는 함수
1419 + # 최대 조회 요청 가능 횟수를 넘어서면, 프로그램을 종료
1420 + def exit_check(self):
1421 + rq_delay=datetime.timedelta(seconds=0.6)
1422 + time_diff=datetime.datetime.now()-self.call_time
1423 + if rq_delay>time_diff:
1424 + time.sleep((rq_delay-time_diff).total_seconds())
1425 +
1426 + self.rq_count+=1
1427 +
1428 + logger.debug(self.rq_count)
1429 + if self.rq_count==cf.max_api_call:
1430 + sys.exit(1)
1016 1431
1017 if __name__=="__main__": 1432 if __name__=="__main__":
1018 app=QApplication(sys.argv) 1433 app=QApplication(sys.argv)
......