├── .gitignore ├── LICENSE ├── README.md ├── algotrade ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── kiwoom ├── __init__.py ├── admin.py ├── apps.py ├── kiwoom.py ├── migrations │ └── __init__.py ├── models.py ├── static │ └── kiwoom │ │ ├── chart.css │ │ └── styles.css ├── templates │ └── kiwoom │ │ ├── account_info.html │ │ ├── details.html │ │ ├── header.html │ │ ├── home.html │ │ ├── index.html │ │ ├── manual_order.html │ │ ├── stock_detail_list.html │ │ └── stock_list.html ├── templatetags │ ├── __init__.py │ └── kiwoom_extras.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── requirements.txt └── templates ├── admin └── base_site.html └── common └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | _venv 3 | _wenv 4 | db.sqlite3 5 | 6 | polls 7 | 8 | .idea 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Wonju Jeon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algorithm Trading 2 | 3 | Algorithm Trading web application project with Python, Django, PyQt5 and Javascript 4 | 5 | 6 | ## Requirements 7 | 8 | * Windows 7/10 9 | * [Python 3.5.x 32bit](https://www.python.org/) 10 | * [Kiwoom Flash(번개) and Kiwoom Open API+ module](https://www2.kiwoom.com/nkw.templateFrameSet.do?m=m1408000000) 11 | 12 | ## Setup Instructions 13 | 14 | Clone the repo: `https://github.com/softage0/algorithm-trading-webapp.git` 15 | 16 | Run the following code on the cloned repo: 17 | ``` 18 | $ pip install -r requirements.txt 19 | ``` 20 | 21 | 22 | ## Quick Start 23 | 24 | Run dev server: 25 | ``` 26 | $ python manage.py runserver 27 | ``` 28 | 29 | Then you can access by the following URL: 30 | http://127.0.0.1:8000/ 31 | 32 | 33 | ## Setup Zipline (optional) 34 | 35 | *The following is the installation steps for Windows platform. The installations on the other platforms may be different.* 36 | 37 | [Zipline](http://www.zipline.io/) is a Pythonic algorithmic trading library. It is an event-driven system that supports both backtesting and live-trading. 38 | 39 | 40 | Install the following build tools first: 41 | 42 | * [Visual C++ Build Tools](http://landinghub.visualstudio.com/visual-cpp-build-tools) 43 | 44 | Download the following packages from [Gohlke's repository](http://www.lfd.uci.edu/~gohlke/pythonlibs/): 45 | 46 | * numpy-1.11.1+mkl-cp35-cp35m-win32.whl 47 | * scipy-0.17.1-cp35-cp35m-win32.whl 48 | * numexpr-2.6.0-cp35-cp35m-win32.whl 49 | 50 | And install them: 51 | ``` 52 | $ pip install {filename} 53 | ``` 54 | 55 | Install Zipline: 56 | ``` 57 | $ pip install zipline 58 | ``` 59 | 60 | 61 | ## References 62 | * 파이썬을 이용한 시스템 트레이딩 (기초편)(https://wikidocs.net/book/110) 63 | * https://github.com/sculove/QWebview-plus 64 | -------------------------------------------------------------------------------- /algotrade/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softage0/algorithm-trading-webapp/726baa24429c24623a5d1d2c57064dbfdf2bdd80/algotrade/__init__.py -------------------------------------------------------------------------------- /algotrade/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for algotrade project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '&+daw_y6k1=tlmhppizq54q3yxie%ld(4mbkx4v*z&ezcp@1sh' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'kiwoom.apps.KiwoomConfig', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ] 42 | 43 | MIDDLEWARE_CLASSES = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'algotrade.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'algotrade.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | -------------------------------------------------------------------------------- /algotrade/urls.py: -------------------------------------------------------------------------------- 1 | """algotrade URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import include, url 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^admin/', admin.site.urls), 21 | url(r'^', include('kiwoom.urls')), 22 | ] 23 | -------------------------------------------------------------------------------- /algotrade/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for algotrade project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "algotrade.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /kiwoom/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softage0/algorithm-trading-webapp/726baa24429c24623a5d1d2c57064dbfdf2bdd80/kiwoom/__init__.py -------------------------------------------------------------------------------- /kiwoom/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /kiwoom/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class KiwoomConfig(AppConfig): 5 | name = 'kiwoom' 6 | -------------------------------------------------------------------------------- /kiwoom/kiwoom.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | import atexit 4 | import json 5 | import threading 6 | import queue 7 | 8 | from PyQt5.QAxContainer import QAxWidget 9 | from PyQt5.QtWidgets import QApplication 10 | 11 | 12 | class Kiwoom(): 13 | def __init__(self, k_queue): 14 | super().__init__() 15 | self.S_SCREEN_NO = "0001" 16 | self.MARKET_LIST = { 17 | 0: '장내', 18 | 3: 'ELW', 19 | 4: '뮤추얼펀드', 20 | 5: '신주인수권', 21 | 6: '리츠', 22 | 8: 'ETF', 23 | 9: '하이일드펀드', 24 | 10: '코스닥', 25 | 30: '제3시장' 26 | } 27 | self.ORDER_TYPE = { 28 | 1: '신규매수', 29 | 2: '신규매도', 30 | 3: '매수취소', 31 | 4: '매도취소' 32 | } 33 | self.HOGA = { 34 | '00': '지정가', 35 | '03': '시장가' 36 | } 37 | 38 | self.q = k_queue 39 | self.qs = { 40 | 'OnReceiveTrData': queue.Queue(), 41 | 'OnReceiveRealData': queue.Queue(), 42 | 'OnReceiveMsg': queue.Queue(), 43 | 'OnReceiveChejanData': queue.Queue(), 44 | 'OnEventConnect': queue.Queue(), 45 | 'OnReceiveRealCondition': queue.Queue(), 46 | 'OnReceiveTrCondition': queue.Queue(), 47 | 'OnReceiveConditionVer': queue.Queue() 48 | } 49 | self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1") 50 | self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData) 51 | self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData) 52 | self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg) 53 | self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData) 54 | self.ocx.OnEventConnect[int].connect(self.OnEventConnect) 55 | self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition) 56 | self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition) 57 | self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer) 58 | atexit.register(self.quit) 59 | 60 | #################################################### 61 | # Interface Methods 62 | #################################################### 63 | def comm_connect(self): 64 | """ 65 | 로그인 윈도우를 실행한다. 66 | 로그인이 성공하거나 실패하는 경우 OnEventConnect 이벤트가 발생하고 이벤트의 인자 값으로 로그인 성공 여부를 알 수 있다. 67 | 68 | :return: 0 - 성공, 음수값은 실패 69 | """ 70 | return self.ocx.dynamicCall("CommConnect()") 71 | 72 | def comm_rq_data(self, sRQName, sTrCode, nPrevNext, sScreenNo): 73 | """ 74 | Tran을 서버로 송신한다. 75 | 76 | :param sRQName: 사용자구분 명 77 | :param sTrCode: Tran명 입력 78 | :param nPrevNext: 0:조회, 2:연속 79 | :param sScreenNo: 4자리의 화면번호 80 | Ex) openApi.CommRqData( “RQ_1”, “OPT00001”, 0, “0101”); 81 | :return: 82 | OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가 83 | OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패 84 | OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패 85 | OP_ERR_NONE(0) – 정상처리 86 | """ 87 | return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", sRQName, sTrCode, nPrevNext, sScreenNo) 88 | 89 | def get_login_info(self, sTag): 90 | """ 91 | 로그인한 사용자 정보를 반환한다. 92 | 93 | :param sTag: 사용자 정보 구분 TAG값 94 | “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다. 95 | "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다. 96 | “USER_ID” - 사용자 ID를 반환한다. 97 | “USER_NAME” – 사용자명을 반환한다. 98 | “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지 99 | “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지 100 | Ex) openApi.GetLoginInfo(“ACCOUNT_CNT”); 101 | :return: TAG값에 따른 데이터 반환 102 | """ 103 | return self.ocx.dynamicCall("GetLoginInfo(QString)", [sTag]) 104 | 105 | def send_order(self, sRQName, sScreenNo, sAccNo, nOrderType, sCode, nQty, nPrice, sHogaGb, sOrgOrderNo): 106 | """ 107 | 주식 주문을 서버로 전송한다. 108 | 109 | :param sRQName: 사용자 구분 요청 명 110 | :param sScreenNo: 화면번호[4] 111 | :param sAccNo: 계좌번호[10] 112 | :param nOrderType: 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매 도정정) 113 | :param sCode: 주식종목코드 114 | :param nQty: 주문수량 115 | :param nPrice: 주문단가 116 | :param sHogaGb: 거래구분 117 | 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10: 지정가IOC, 13:시장가IOC, 118 | 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61: 장전시간외종가, 62:시간외단일가, 81:장후시간외종가 119 | ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다. 120 | ex) 121 | 지정가 매수 - openApi.SendOrder(“RQ_1”, “0101”, “5015123410”, 1, “000660”, 10, 48500, “00”, “”); 122 | 시장가 매수 - openApi.SendOrder(“RQ_1”, “0101”, “5015123410”, 1, “000660”, 10, 0, “03”, “”); 123 | 매수 정정 - openApi.SendOrder(“RQ_1”,“0101”, “5015123410”, 5, “000660”, 10, 49500, “00”, “1”); 124 | 매수 취소 - openApi.SendOrder(“RQ_1”, “0101”, “5015123410”, 3, “000660”, 10, 0, “00”, “2”); 125 | :param sOrgOrderNo: 원주문번호 126 | :return: 에러코드 - parse_error_code 127 | """ 128 | return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)", 129 | [sRQName, sScreenNo, sAccNo, nOrderType, sCode, nQty, nPrice, sHogaGb, sOrgOrderNo]) 130 | 131 | def send_order_credit(self): 132 | pass 133 | 134 | def set_input_value(self, sID, sValue): 135 | """ 136 | Tran 입력 값을 서버통신 전에 입력한다. 137 | 138 | :param sID: 아이템명 139 | :param sValue: 입력 값 140 | Ex) openApi.SetInputValue(“종목코드”, “000660”); 141 | openApi.SetInputValue(“계좌번호”, “5015123401”); 142 | """ 143 | self.ocx.dynamicCall("SetInputValue(QString, QString)", sID, sValue) 144 | 145 | def set_output_fid(self): 146 | pass 147 | 148 | def comm_get_data(self, sJongmokCode, sRealType, sFieldName, nIndex, sInnerFieldName): 149 | """ 150 | Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다. 151 | 152 | 1. Tran 데이터 153 | :param sJongmokCode: Tran명 154 | :param sRealType: 사용안함 155 | :param sFieldName: 레코드명 156 | :param nIndex: 반복인덱스 157 | :param sInnerFieldName: 아이템명 158 | 159 | 2. 실시간 데이터 160 | :param sJongmokCode: Key Code 161 | :param sRealType: Real Type 162 | :param sFieldName: Item Index (FID) 163 | :param nIndex: 사용안함 164 | :param sInnerFieldName: 사용안함 165 | 166 | 3. 체결 데이터 167 | :param sJongmokCode: 체결구분 168 | :param sRealType: “-1” 169 | :param sFieldName: 사용안함 170 | :param nIndex: ItemIndex 171 | :param sInnerFieldName: 사용안함 172 | 173 | :return: 요청 데이터 174 | """ 175 | return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)", sJongmokCode, sRealType, 176 | sFieldName, nIndex, sInnerFieldName) 177 | 178 | def disconnect_real_data(self, sScnNo): 179 | """ 180 | 화면 내 모든 리얼데이터 요청을 제거한다. 181 | 화면을 종료할 때 반드시 위 함수를 호출해야 한다. 182 | 183 | :param sScnNo: 화면번호[4] 184 | Ex) openApi.DisconnectRealData(“0101”); 185 | """ 186 | self.ocx.dynamicCall("DisconnectRealData(QString)", sScnNo) 187 | 188 | def get_repeat_cnt(self, sTrCode, sRecordName): 189 | """ 190 | 수신 받은 데이터의 반복 개수를 반환한다. 191 | 레코드 반복횟수를 반환한다. 192 | 193 | :param sTrCode: Tran 명 194 | :param sRecordName: 레코드 명 195 | Ex) openApi.GetRepeatCnt(“OPT00001”, “주식기본정보”); 196 | :return: 레코드의 반복횟수 197 | """ 198 | return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRecordName) 199 | 200 | def comm_kw_rq_data(self, sArrCode, bNext, nCodeCount, nTypeFlag, sRQName, sScreenNo): 201 | """ 202 | 복수종목조회 Tran을 서버로 송신한다. 203 | 204 | :param sArrCode: 종목리스트 - 종목간 구분은 ‘;’이다. 205 | :param bNext: 연속조회요청 206 | :param nCodeCount: 종목개수 207 | :param nTypeFlag: 조회구분 - 0:주식관심종목정보, 3:선물옵션관심종목정보 208 | :param sRQName: 사용자구분 명 209 | :param sScreenNo: 화면번호[4] 210 | ex) openApi.CommKwRqData(“000660;005930”, 0, 2, 0, “RQ_1”, “0101”); 211 | :return: OP_ERR_RQ_STRING – 요청 전문 작성 실패 212 | OP_ERR_NONE - 정상처리 213 | """ 214 | return self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)", 215 | sArrCode, bNext, nCodeCount, nTypeFlag, sRQName, sScreenNo) 216 | 217 | def get_api_module_path(self): 218 | pass 219 | 220 | def get_code_list_by_market(self, sMarket): 221 | """ 222 | 시장구분에 따른 종목코드를 반환한다. 223 | 224 | :param sMarket: 시장구분 225 | 0:장내, 3:ELW, 4:뮤추얼펀드, 5:신주인수권, 6:리츠, 8:ETF, 9:하이일드펀드, 10:코스닥, 30:제3시장 226 | :return: 종목코드 리스트, 종목간 구분은 ’;’이다. 227 | """ 228 | return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [sMarket]) 229 | 230 | def get_connect_state(self): 231 | """ 232 | 현재접속상태를 반환한다. 233 | 234 | :return: 접속상태 - 0:미연결, 1:연결완료 235 | """ 236 | return self.ocx.dynamicCall("GetConnectState()") 237 | 238 | def get_master_code_name(self, strCode): 239 | """ 240 | 종목코드의 한글명을 반환한다. 241 | 장내외, 지수선옵, 주식선옵 검색 가능. 242 | 243 | :param strCode: 종목코드 244 | :return: 종목한글명 245 | """ 246 | return self.ocx.dynamicCall("GetMasterCodeName(QString)", strCode) 247 | 248 | def get_master_listed_stock_cnt(self): 249 | pass 250 | 251 | def get_master_construction(self): 252 | pass 253 | 254 | def get_master_listed_stock_date(self): 255 | pass 256 | 257 | def get_master_last_price(self): 258 | pass 259 | 260 | def get_master_stock_state(self): 261 | pass 262 | 263 | def get_data_count(self): 264 | pass 265 | 266 | def get_output_value(self): 267 | pass 268 | 269 | def get_comm_data(self, strTrCode, strRecordName, nIndex, strItemName): 270 | """ 271 | 수신 데이터를 반환한다. 272 | 273 | :param strTrCode: Tran 코드 274 | :param strRecordName: 레코드명 275 | :param nIndex: 복수데이터 인덱스 276 | :param strItemName: 아이템명 277 | Ex)현재가출력 - openApi.GetCommData(“OPT00001”, “주식기본정보”, 0, “현재가”); 278 | 279 | :return: 수신 데이터 280 | """ 281 | return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)", 282 | strTrCode, strRecordName, nIndex, strItemName) 283 | 284 | def get_comm_real_data(self, strRealType, nFid): 285 | """ 286 | 실시간데이터를 반환한다. 287 | 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능 288 | 289 | :param strRealType: 실시간 구분 290 | :param nFid: 실시간 아이템 291 | Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10); 292 | 293 | :return: 수신 데이터 294 | """ 295 | return self.ocx.dynamicCall("GetCommRealData(QString, int)", strRealType, nFid) 296 | 297 | def get_chejan_data(self, nFid): 298 | """ 299 | 체결잔고 데이터를 반환한다. 300 | 301 | :param nFid: 체결잔고 아이템 302 | Ex) 현재가출력 – openApi.GetChejanData(10); 303 | :return: 수신 데이터 304 | """ 305 | return self.ocx.dynamicCall("GetChejanData(int)", nFid) 306 | 307 | def set_real_reg(self, strScreenNo, strCodeList, strFidList, strRealType): 308 | """ 309 | 실시간 등록을 한다. 310 | ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다. 311 | 312 | :param strScreenNo: 실시간 등록할 화면 번호 313 | :param strCodeList: 실시간 등록할 종목코드(복수종목가능 – “종목1;종목2;종목3;....”) 314 | :param strFidList: 실시간 등록할 FID(“FID1;FID2;FID3;.....”) 315 | ex)9001;10;13;… 316 | 9001 – 종목코드 317 | 10 - 현재가 318 | 13 - 누적거래량 319 | :param strRealType: "0", "1" 타입 320 | strRealType이 “0” 으로 하면 같은화면에서 다른종목 코드로 실시간 등록을 하게 되면 321 | 마지막에 사용한 종목코드만 실시간 등록이 되고 기존에 있던 종목은 실시간이 자동 해지됨. 322 | “1”로 하면 같은화면에서 다른 종목들을 추가하게 되면 기존에 등록한 종목도 함께 실 시간 시세를 받을 수 있음. 323 | 꼭 같은 화면이여야 하고 최초 실시간 등록은 “0”으로 하고 이후부터 “1”로 등록해야 함. 324 | :return: 통신결과 325 | """ 326 | return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)", strScreenNo, strCodeList, strFidList, 327 | strRealType) 328 | 329 | def set_real_remove(self, strScrNo, strDelCode): 330 | """ 331 | 종목별 실시간 해제. 332 | SetRealReg() 함수로 실시간 등록한 종목만 실시간 해제 할 수 있다. 333 | 334 | -화면별 실시간해제 335 | 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다. 336 | SetRealRemove(“ALL”, “ALL”); 337 | 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할 338 | 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다. 339 | SetRealRemove(“0001”, “ALL”); 340 | -화면의 종목별 실시간해제 341 | 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할 342 | 종목코드를 입력하시면 됩니다. 343 | SetRealRemove(“0001”, “039490”); 344 | 345 | :param strScrNo: 실시간 해제할 화면 번호 346 | :param strDelCode: 실시간 해제할 종목. 347 | :return: 통신결과 348 | """ 349 | return self.ocx.dynamicCall("SetRealRemove(QString, QString)", strScrNo, strDelCode) 350 | 351 | def get_condition_load(self): 352 | """ 353 | 서버에 저장된 사용자 조건식을 조회해서 임시로 파일에 저장. 354 | 355 | System 폴더에 아이디_NewSaveIndex.dat파일로 저장된다. Ocx가 종료되면 삭제시킨다. 356 | 조건검색 사용시 이 함수를 최소 한번은 호출해야 조건검색을 할 수 있다. 357 | 영웅문에서 사용자 조건을 수정 및 추가하였을 경우에도 최신의 사용자 조건을 받고 싶으면 다시 조회해야한다. 358 | 359 | :return: 사용자 조건식을 파일로 임시 저장. 360 | """ 361 | return self.ocx.dynamicCall("GetConditionLoad()") 362 | 363 | def get_condition_name_list(self): 364 | """ 365 | 조건검색 조건명 리스트를 받아온다. 366 | 조건명 리스트를 구분(“;”)하여 받아온다. Ex) 인덱스1^조건명1;인덱스2^조건명2;인덱스3^조건명3;... 367 | 368 | :return: 조건명 리스트(인덱스^조건명) 369 | """ 370 | return self.ocx.dynamicCall("GetConditionNameList()") 371 | 372 | def send_condition(self, strScrNo, strConditionName, nIndex, nSearch): 373 | """ 374 | 조건검색 종목조회 TR송신한다. 375 | 376 | :param strScrNo: 화면번호 377 | :param strConditionName: 조건명 378 | :param nIndex: 조건명인덱스 379 | :param nSearch: 조회구분(0:일반조회, 1:실시간조회, 2:연속조회) - 1:실시간조회의 화면 개수의 최대는 10개 380 | 단순 조건식에 맞는 종목을 조회하기 위해서는 조회구분을 0으로 하고, 381 | 실시간 조건검색을 하기 위해서는 조회구분을 1로 한다. 382 | OnReceiveTrCondition으로 결과값이 온다. 383 | 연속조회가 필요한 경우에는 응답받는 곳에서 연속조회 여부에 따라 연속조회를 송신하면 된다. 384 | """ 385 | self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", strScrNo, strConditionName, nIndex, nSearch) 386 | 387 | def send_condition_stop(self, strScrNo, strConditionName, nIndex): 388 | """ 389 | 조건검색 실시간 중지TR을 송신한다. 390 | 해당 조건명의 실시간 조건검색을 중지하거나, 다른 조건명으로 바꿀 때 이전 조건명으로 실시간 조건검색을 반드시 중지해야한다. 391 | 화면 종료시에도 실시간 조건검색을 한 조건명으로 전부 중지해줘야 한다. 392 | ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다. 393 | 394 | :param strScrNo: 화면번호 395 | :param strConditionName: 조건명 396 | :param nIndex: 조건명인덱스 397 | """ 398 | self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", strScrNo, strConditionName, nIndex) 399 | 400 | def get_comm_data_ex(self, strTrCode, strRecordName): 401 | """ 402 | 차트 조회 데이터를 배열로 받아온다. 403 | 404 | ※ 항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다. 405 | 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다. 406 | 407 | :param strTrCode: 조회한 TR코드 408 | :param strRecordName: 조회한 TR명 409 | :return: 410 | 조회 데이터가 많은 차트 경우 GetCommData()로 항목당 하나씩 받아오는 것 보다 411 | 한번에 데이터 전부를 받아서 사용자가 처리할 수 있도록 배열로 받는다. 412 | """ 413 | return json.dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", strTrCode, strRecordName)) 414 | 415 | #################################################### 416 | # Control Event Handlers 417 | #################################################### 418 | def OnReceiveTrData(self, sScrNo, sRQName, sTrCode, sRecordName, sPreNext, nDataLength, sErrorCode, sMessage, sSplmMsg): 419 | """ 420 | Tran 수신시 이벤트 421 | 서버통신 후 데이터를 받은 시점을 알려준다. 422 | 423 | :param sScrNo: 화면번호 424 | :param sRQName: 사용자구분 명 425 | :param sTrCode: Tran 명 426 | :param sRecordName: Record 명 427 | :param sPreNext: 연속조회 유무 428 | :param nDataLength: 1.0.0.1 버전 이후 사용하지 않음. 429 | :param sErrorCode: 1.0.0.1 버전 이후 사용하지 않음. 430 | :param sMessage: 1.0.0.1 버전 이후 사용하지 않음. 431 | :param sSplmMsg: 1.0.0.1 버전 이후 사용하지 않음. 432 | """ 433 | self.qs['OnReceiveTrData'].put({ 434 | "sScrNo": sScrNo, 435 | "sRQName": sRQName, 436 | "sTrCode": sTrCode, 437 | "sRecordName": sRecordName, 438 | "sPreNext": sPreNext 439 | }) 440 | print("OnReceiveTrData received") 441 | 442 | def OnReceiveRealData(self, sJongmokCode, sRealType, sRealData): 443 | """ 444 | 실시간 시세 이벤트 445 | 실시간데이터를 받은 시점을 알려준다. 446 | 447 | :param sJongmokCode: 종목코드 448 | :param sRealType: 리얼타입 449 | :param sRealData: 실시간 데이터전문 450 | """ 451 | # self.qs['OnReceiveRealData'].put({ 452 | # "sJongmokCode": sJongmokCode, 453 | # "sRealType": sRealType, 454 | # "sRealData": sRealData 455 | # }) 456 | print("OnReceiveRealData received: ") 457 | print({ 458 | "sJongmokCode": sJongmokCode, 459 | "sRealType": sRealType, 460 | "sRealData": sRealData 461 | }) 462 | 463 | def OnReceiveMsg(self, sScrNo, sRQName, sTrCode, sMsg): 464 | """ 465 | 수신 메시지 이벤트 466 | 서버통신 후 메시지를 받은 시점을 알려준다. 467 | 468 | :param sScrNo: 화면번호 469 | :param sRQName: 사용자구분 명 470 | :param sTrCode: Tran 명 471 | :param sMsg: 서버메시지 472 | """ 473 | self.qs['OnReceiveMsg'].put("receiveMsg.kiwoom", { 474 | "sScrNo": sScrNo, 475 | "sRQName": sRQName, 476 | "sTrCode": sTrCode, 477 | "sMsg": sMsg 478 | }) 479 | print("OnReceiveMsg received") 480 | 481 | def OnReceiveChejanData(self, sGubun, nItemCnt, sFidList): 482 | """ 483 | 체결데이터를 받은 시점을 알려준다. 484 | 485 | :param sGubun: 체결구분 - 0:주문체결통보, 1:잔고통보, 3:특이신호 486 | :param nItemCnt: 아이템갯수 487 | :param sFidList: 데이터리스트 - 데이터 구분은 ‘;’ 이다. 488 | """ 489 | self.qs['OnReceiveChejanData'].put({ 490 | "sGubun": sGubun, 491 | "nItemCnt": nItemCnt, 492 | "sFidList": sFidList 493 | }) 494 | print("OnReceiveChejanData received") 495 | 496 | # Todo: API request implementation 497 | print("sGubun: ", sGubun) 498 | print(self.get_chejan_data(9203)) # 주문번호 499 | print(self.get_chejan_data(302)) # 종목명 500 | print(self.get_chejan_data(900)) # 주문수량 501 | print(self.get_chejan_data(901)) # 주문가격 502 | 503 | def OnEventConnect(self, nErrCode): 504 | """ 505 | 통신 연결 상태 변경시 이벤트 506 | 507 | :param nErrCode: 에러 코드 - 0이면 로그인 성공, 음수면 실패, 에러코드 참조 508 | """ 509 | self.qs['OnEventConnect'].put(nErrCode) 510 | print("OnEventConnect received") 511 | 512 | def OnReceiveRealCondition(self, strCode, strType, strConditionName, strConditionIndex): 513 | """ 514 | 조건검색 실시간 편입,이탈 종목을 받을 시점을 알려준다. 515 | 편입, 이탈 종목이 실시간으로 들어옵니다. 516 | strConditionName에 해당하는 종목이 실시간으로 들어옴. strType으로 편입된 종목인지 이탈된 종목인지 구분한다. 517 | 518 | :param strCode: 종목코드 519 | :param strType: 편입(“I”), 이탈(“D”) 520 | :param strConditionName: 조건명 521 | :param strConditionIndex: 조건명 인덱스 522 | """ 523 | self.qs['OnReceiveRealCondition'].put({ 524 | "strCode": strCode, 525 | "strType": strType, 526 | "strConditionName": strConditionName, 527 | "strConditionIndex": strConditionIndex 528 | }) 529 | print("OnReceiveRealCondition received") 530 | 531 | def OnReceiveTrCondition(self, sScrNo, strCodeList, strConditionName, nIndex, nNext): 532 | """ 533 | 조건검색 조회응답 이벤트 534 | 조건검색 조회응답으로 종목리스트를 구분자(“;”)로 붙어서 받는 시점. 535 | 536 | :param sScrNo: 종목코드 537 | :param strCodeList: 종목리스트(“;”로 구분) 538 | :param strConditionName: 조건명 539 | :param nIndex: 조건명 인덱스 540 | :param nNext: 연속조회(2:연속조회, 0:연속조회없음) 541 | :return: 542 | """ 543 | self.qs['OnReceiveTrCondition'].put({ 544 | "sScrNo": sScrNo, 545 | "strCodeList": strCodeList, 546 | "strConditionName": strConditionName, 547 | "nIndex": nIndex, 548 | "nNext": nNext, 549 | }) 550 | print("OnReceiveTrCondition received") 551 | 552 | def OnReceiveConditionVer(self, lRet, sMsg): 553 | """ 554 | 로컬에 사용자조건식 저장 성공여부 응답 이벤트 555 | 로컬에 사용자 조건식 저장 성공 여부를 확인하는 시점 556 | 557 | :param lRet: 사용자 조건식 저장 성공여부 (1: 성공, 나머지 실패) 558 | :param sMsg: 559 | """ 560 | self.qs['OnReceiveConditionVer'].put({ 561 | "lRet": lRet, 562 | "sMsg": sMsg 563 | }) 564 | print("OnReceiveConditionVer received") 565 | 566 | #################################################### 567 | # Custom Methods 568 | #################################################### 569 | def quit(self): 570 | """ Quit the server """ 571 | 572 | self.disconnect_real_data(self.S_SCREEN_NO) 573 | QApplication.quit() 574 | 575 | @staticmethod 576 | def parse_error_code(err_code): 577 | """ 578 | Return the message of error codes 579 | 580 | :param err_code: Error Code 581 | :type err_code: str 582 | :return: Error Message 583 | """ 584 | err_code = str(err_code) 585 | ht = { 586 | "0": "정상처리", 587 | "-100": "사용자정보교환에 실패하였습니다. 잠시후 다시 시작하여 주십시오.", 588 | "-101": "서버 접속 실패", 589 | "-102": "버전처리가 실패하였습니다.", 590 | "-200": "시세조회 과부하", 591 | "-201": "REQUEST_INPUT_st Failed", 592 | "-202": "요청 전문 작성 실패", 593 | "-300": "주문 입력값 오류", 594 | "-301": "계좌비밀번호를 입력하십시오.", 595 | "-302": "타인계좌는 사용할 수 없습니다.", 596 | "-303": "주문가격이 20억원을 초과합니다.", 597 | "-304": "주문가격은 50억원을 초과할 수 없습니다.", 598 | "-305": "주문수량이 총발행주수의 1%를 초과합니다.", 599 | "-306": "주문수량은 총발행주수의 3%를 초과할 수 없습니다." 600 | } 601 | return ht[err_code] + " (%s)" % err_code if err_code in ht else err_code 602 | 603 | 604 | class KThread(threading.Thread): 605 | """ 606 | Launch QT module as thread since QApplication.exec_ entering the event loop 607 | """ 608 | 609 | def __init__(self, q): 610 | super(KThread, self).__init__() 611 | self.q = q 612 | 613 | def run(self): 614 | app = QApplication(sys.argv) 615 | self.q.put(Kiwoom(self.q)) 616 | app.exec_() 617 | 618 | def get_all_queues(self, q): 619 | results = [] 620 | if not q: 621 | q = self.q 622 | try: 623 | while True: 624 | data = q.get(True) 625 | results.append(data) 626 | except queue.Empty: 627 | return results 628 | 629 | 630 | k_q = queue.Queue() 631 | k_thread = KThread(k_q) 632 | k_thread.start() 633 | 634 | k_module = k_q.get() 635 | -------------------------------------------------------------------------------- /kiwoom/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softage0/algorithm-trading-webapp/726baa24429c24623a5d1d2c57064dbfdf2bdd80/kiwoom/migrations/__init__.py -------------------------------------------------------------------------------- /kiwoom/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /kiwoom/static/kiwoom/chart.css: -------------------------------------------------------------------------------- 1 | .chart { 2 | font: 10px sans-serif; 3 | } 4 | 5 | .axis path, 6 | .axis line { 7 | fill: none; 8 | stroke: #000; 9 | shape-rendering: crispEdges; 10 | } 11 | 12 | .x.axis path { 13 | display: none; 14 | } 15 | 16 | .line { 17 | fill: none; 18 | stroke: steelblue; 19 | stroke-width: 1.5px; 20 | } 21 | 22 | .green-line { 23 | stroke: green; 24 | } 25 | 26 | svg text { 27 | fill: #000; 28 | } 29 | -------------------------------------------------------------------------------- /kiwoom/static/kiwoom/styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | body { 7 | padding-top: 50px; 8 | } 9 | 10 | /* 11 | * Global add-ons 12 | */ 13 | 14 | .sub-header { 15 | padding-bottom: 10px; 16 | border-bottom: 1px solid #eee; 17 | } 18 | 19 | /* 20 | * Top navigation 21 | * Hide default border to remove 1px line. 22 | */ 23 | .navbar-fixed-top { 24 | border: 0; 25 | } 26 | 27 | /* 28 | * Sidebar 29 | */ 30 | 31 | /* Hide for mobile, show later */ 32 | .sidebar { 33 | display: none; 34 | } 35 | 36 | @media (min-width: 768px) { 37 | .sidebar { 38 | position: fixed; 39 | top: 51px; 40 | bottom: 0; 41 | left: 0; 42 | z-index: 1000; 43 | display: block; 44 | padding: 20px; 45 | overflow-x: hidden; 46 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 47 | background-color: #f5f5f5; 48 | border-right: 1px solid #eee; 49 | } 50 | } 51 | 52 | /* Sidebar navigation */ 53 | .nav-sidebar { 54 | margin-right: -21px; /* 20px padding + 1px border */ 55 | margin-bottom: 20px; 56 | margin-left: -20px; 57 | } 58 | 59 | .nav-sidebar > li > a { 60 | padding-right: 20px; 61 | padding-left: 20px; 62 | } 63 | 64 | .nav-sidebar > .active > a, 65 | .nav-sidebar > .active > a:hover, 66 | .nav-sidebar > .active > a:focus { 67 | color: #fff; 68 | background-color: #428bca; 69 | } 70 | 71 | /* 72 | * Main content 73 | */ 74 | 75 | .main { 76 | padding: 20px; 77 | } 78 | 79 | @media (min-width: 768px) { 80 | .main { 81 | padding-right: 40px; 82 | padding-left: 40px; 83 | } 84 | } 85 | 86 | .main .page-header { 87 | margin-top: 0; 88 | } 89 | 90 | /* 91 | * Placeholder dashboard ideas 92 | */ 93 | 94 | .placeholders { 95 | margin-bottom: 30px; 96 | text-align: center; 97 | } 98 | 99 | .placeholders h4 { 100 | margin-bottom: 0; 101 | } 102 | 103 | .placeholder { 104 | margin-bottom: 20px; 105 | } 106 | 107 | .placeholder img { 108 | display: inline-block; 109 | border-radius: 50%; 110 | } 111 | 112 | /* Category dropdown menu */ 113 | @media (min-width: 768px) { 114 | .wide-hidden-toggle { 115 | display: none !important; 116 | } 117 | } 118 | 119 | .navbar-fixed-top .navbar-collapse { 120 | max-height: none; 121 | } 122 | 123 | /* form padding */ 124 | .form-padding { 125 | padding: 25px; 126 | } 127 | 128 | /* extras */ 129 | * { 130 | font-family: "Helvetica Neue Bold", Roboto, Helvetica, Arial, sans-serif; 131 | font-weight: 700; 132 | } 133 | 134 | h1 { 135 | font-family: "Helvetica Neue Light", Roboto, Helvetica, Arial, sans-serif; 136 | font-size: 50px; 137 | color: #51453B; 138 | line-height: 60px; 139 | font-weight: 300; 140 | } 141 | 142 | h2 { 143 | font-size: 24px; 144 | color: #42C3C9; 145 | line-height: 29px; 146 | } 147 | 148 | h3 { 149 | font-size: 18px; 150 | color: #4A4A4A; 151 | line-height: 23px; 152 | } 153 | 154 | strong { 155 | font-size: 16px; 156 | color: #7B7573; 157 | line-height: 24px; 158 | } 159 | 160 | a { 161 | font-size: 14px; 162 | color: #0093B9; 163 | line-height: 17px; 164 | } 165 | -------------------------------------------------------------------------------- /kiwoom/templates/kiwoom/account_info.html: -------------------------------------------------------------------------------- 1 | {% extends "kiwoom/index.html" %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Account Count{{account_count}}
Account No.{{account_no}}
User ID{{user_id}}
Name{{user_name}}
Key Input Security{{key_input_security}}
Firewall{{firewall}}
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /kiwoom/templates/kiwoom/details.html: -------------------------------------------------------------------------------- 1 | {% extends "kiwoom/index.html" %} 2 | 3 | {% block style %} 4 | {% load staticfiles %} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
Name{{ name }}
Quantity{{ quantity }}
Current Price{{ current_price }}
Current Total Price{{ total_price }}
27 |
28 |
29 |
30 |
31 | {% endblock %} 32 | 33 | {% block script %} 34 | 35 | 36 | 129 | {% endblock %} 130 | -------------------------------------------------------------------------------- /kiwoom/templates/kiwoom/header.html: -------------------------------------------------------------------------------- 1 | 41 | -------------------------------------------------------------------------------- /kiwoom/templates/kiwoom/home.html: -------------------------------------------------------------------------------- 1 | {% extends "kiwoom/index.html" %} 2 | 3 | {% block content %} 4 | Please choose the menu on the header bar. 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /kiwoom/templates/kiwoom/index.html: -------------------------------------------------------------------------------- 1 | {% extends "common/index.html" %} 2 | 3 | {% block head %} 4 | {% load staticfiles %} 5 | 6 | {% block style %} 7 | {% endblock %} 8 | {% endblock %} 9 | 10 | {% block body %} 11 | {% include "kiwoom/header.html" %} 12 |
13 | {% block content %} 14 | {% endblock %} 15 |
16 | {% block script %} 17 | {% endblock %} 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /kiwoom/templates/kiwoom/manual_order.html: -------------------------------------------------------------------------------- 1 | {% extends "kiwoom/index.html" %} 2 | {% load kiwoom_extras %} 3 | 4 | {% block content %} 5 |
6 | {% csrf_token %} 7 |
8 | Manual Order 9 |
10 | 11 |
12 | 17 |
18 |
19 |
20 | 21 |
22 | 27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 | 39 |
40 | 45 |
46 |
47 |
48 | 49 |
50 | 51 |
52 |
53 |
54 | 55 |
56 | 57 |
58 |
59 |
60 |
61 | 62 | 63 |
64 |
65 |
66 |
67 | {% endblock %} 68 | -------------------------------------------------------------------------------- /kiwoom/templates/kiwoom/stock_detail_list.html: -------------------------------------------------------------------------------- 1 | {% extends "kiwoom/index.html" %} 2 | {% load kiwoom_extras %} 3 | 4 | {% block content %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for code in stock_code_list %} 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% endfor %} 23 | 24 |
CodeNameDetailsManual Order
{{code}}{{code|get_master_code_name}}DetailsManual Order
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /kiwoom/templates/kiwoom/stock_list.html: -------------------------------------------------------------------------------- 1 | {% extends "kiwoom/index.html" %} 2 | 3 | {% block content %} 4 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /kiwoom/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softage0/algorithm-trading-webapp/726baa24429c24623a5d1d2c57064dbfdf2bdd80/kiwoom/templatetags/__init__.py -------------------------------------------------------------------------------- /kiwoom/templatetags/kiwoom_extras.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from ..kiwoom import k_module 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter 9 | def get_master_code_name(code): 10 | result = k_module.get_master_code_name(code) 11 | return result 12 | -------------------------------------------------------------------------------- /kiwoom/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /kiwoom/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | app_name = 'kiwoom' 6 | urlpatterns = [ 7 | url(r'^$', views.index, name='index'), 8 | url(r'^account_info/$', views.account_info, name='account_info'), 9 | url(r'^stock_list/$', views.stock_list, name='stock_list'), 10 | url(r'^stock_list/(?P[0-9]+)$', views.stock_detail_list, name='stock_detail_list'), 11 | url(r'^details/(?P\w+)$', views.details, name='details'), 12 | url(r'^manual_order/(?P\w+)$', views.manual_order, name='manual_order'), 13 | url(r'^api_docs/$', views.api_docs, name='api_docs'), 14 | ] 15 | -------------------------------------------------------------------------------- /kiwoom/views.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | 4 | from django.contrib import messages 5 | from django.shortcuts import render 6 | from django.http import HttpResponse, HttpResponseRedirect 7 | from django.core.urlresolvers import reverse 8 | from pandas import DataFrame 9 | 10 | from .kiwoom import k_module 11 | 12 | 13 | def index(request): 14 | if request.GET.get('login') and not k_module.get_connect_state(): 15 | k_module.comm_connect() 16 | k_module.qs['OnEventConnect'].get() # waiting for login 17 | if k_module.get_connect_state(): 18 | messages.success(request, 'Login Succeeded') 19 | else: 20 | messages.error(request, 'Login Failed') 21 | return HttpResponseRedirect(reverse('kiwoom:index')) 22 | elif request.GET.get('logout'): 23 | # Logout function does not exist. 24 | return HttpResponseRedirect(reverse('kiwoom:index')) 25 | 26 | return render(request, 'kiwoom/home.html', { 27 | 'login_state': k_module.get_connect_state() 28 | }) 29 | 30 | 31 | def api_docs(request): 32 | return HttpResponse(k_module.ocx.generateDocumentation()) 33 | 34 | 35 | def account_info(request): 36 | return render(request, 'kiwoom/account_info.html', { 37 | 'login_state': k_module.get_connect_state(), 38 | 'account_count': k_module.get_login_info("ACCOUNT_CNT"), 39 | 'account_no': k_module.get_login_info("ACCNO"), 40 | 'user_id': k_module.get_login_info("USER_ID"), 41 | 'user_name': k_module.get_login_info("USER_NAME"), 42 | 'key_input_security': k_module.get_login_info("KEY_BSECGB"), 43 | 'firewall': k_module.get_login_info("FIREW_SECGB") 44 | }) 45 | 46 | 47 | def stock_list(request): 48 | return render(request, 'kiwoom/stock_list.html', { 49 | 'login_state': k_module.get_connect_state(), 50 | 'market_list': k_module.MARKET_LIST 51 | }) 52 | 53 | 54 | def stock_detail_list(request, market_type): 55 | return render(request, 'kiwoom/stock_detail_list.html', { 56 | 'login_state': k_module.get_connect_state(), 57 | 'stock_code_list': k_module.get_code_list_by_market(market_type).strip(';').split(';'), 58 | 'k_module': k_module 59 | }) 60 | 61 | 62 | def details(request, code): 63 | k_module.set_input_value("종목코드", code) 64 | k_module.set_input_value("기준일자", datetime.datetime.strftime(datetime.datetime.now(), "%Y%m%d")) 65 | k_module.set_input_value("수정주가구분 ", 0) 66 | k_module.comm_rq_data("주식일봉차트조회요청", "opt10081", 0, k_module.S_SCREEN_NO) 67 | data = k_module.qs['OnReceiveTrData'].get() 68 | daily_stock_data = json.loads(k_module.get_comm_data_ex(data['sTrCode'], data['sRQName'])) 69 | daily_stock_data = DataFrame(daily_stock_data, dtype=int).iloc[:, 1:8] 70 | daily_stock_data.columns = ['current_price', 'tr_qty', 'tr_volume', 'date', 'start', 'high', 'close'] 71 | daily_stock_data = daily_stock_data.set_index('date') 72 | 73 | k_module.set_input_value('종목코드', code) 74 | k_module.comm_rq_data("주식기본정보", "opt10001", 0, k_module.S_SCREEN_NO) 75 | tr_data = k_module.qs['OnReceiveTrData'].get() 76 | 77 | return render(request, 'kiwoom/details.html', { 78 | 'login_state': k_module.get_connect_state(), 79 | 'name': k_module.comm_get_data(tr_data['sTrCode'], "", tr_data['sRQName'], 0, "종목명"), 80 | 'quantity': k_module.comm_get_data(tr_data['sTrCode'], "", tr_data['sRQName'], 0, "거래량"), 81 | 'current_price': k_module.comm_get_data(tr_data['sTrCode'], "", tr_data['sRQName'], 0, "현재가"), 82 | 'total_price': k_module.comm_get_data(tr_data['sTrCode'], "", tr_data['sRQName'], 0, "시가총액"), 83 | 'daily_stock_data': daily_stock_data.to_csv(line_terminator='\\n') 84 | }) 85 | 86 | 87 | def manual_order(request, code): 88 | if request.method == 'GET': 89 | return render(request, 'kiwoom/manual_order.html', { 90 | 'login_state': k_module.get_connect_state(), 91 | 'accounts': k_module.get_login_info("ACCNO").strip(';').split(';'), 92 | 'order_types': k_module.ORDER_TYPE, 93 | 'hoga': k_module.HOGA, 94 | 'code': code 95 | }) 96 | elif request.method == 'POST': 97 | query = request.POST 98 | k_module.send_order() 99 | return HttpResponseRedirect(reverse('kiwoom:account_info')) 100 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "algotrade.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.2.24 2 | numpy==1.13.1 3 | pandas==0.20.3 4 | PyQt5==5.9 5 | python-dateutil==2.6.1 6 | pytz==2017.2 7 | sip==4.19.3 8 | six==1.10.0 9 | -------------------------------------------------------------------------------- /templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | 3 | {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} 4 | 5 | {% block branding %} 6 |

Algorithm Trading administration

7 | {% endblock %} 8 | 9 | {% block nav-global %}{% endblock %} 10 | -------------------------------------------------------------------------------- /templates/common/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block head %} 9 | {% endblock %} 10 | 11 | 12 | {% block body %} 13 | {% endblock %} 14 | 15 | 16 | 17 | 18 | --------------------------------------------------------------------------------