├── .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 | Account Count |
7 | {{account_count}} |
8 |
9 |
10 | Account No. |
11 | {{account_no}} |
12 |
13 |
14 | User ID |
15 | {{user_id}} |
16 |
17 |
18 | Name |
19 | {{user_name}} |
20 |
21 |
22 | Key Input Security |
23 | {{key_input_security}} |
24 |
25 |
26 | Firewall |
27 | {{firewall}} |
28 |
29 |
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 | Name |
12 | {{ name }} |
13 |
14 |
15 | Quantity |
16 | {{ quantity }} |
17 |
18 |
19 | Current Price |
20 | {{ current_price }} |
21 |
22 |
23 | Current Total Price |
24 | {{ total_price }} |
25 |
26 |
27 |
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 |
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 | Code |
9 | Name |
10 | Details |
11 | Manual Order |
12 |
13 |
14 |
15 | {% for code in stock_code_list %}
16 |
17 | {{code}} |
18 | {{code|get_master_code_name}} |
19 | Details |
20 | Manual Order |
21 |
22 | {% endfor %}
23 |
24 |
25 | {% endblock %}
26 |
--------------------------------------------------------------------------------
/kiwoom/templates/kiwoom/stock_list.html:
--------------------------------------------------------------------------------
1 | {% extends "kiwoom/index.html" %}
2 |
3 | {% block content %}
4 |
5 | {% for code, name in market_list.items %}
6 | - {{name}}
7 | {% endfor %}
8 |
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 |
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 |
--------------------------------------------------------------------------------