initial commit
This commit is contained in:
14
한국투자증권(API)/legacy/websocket/delphi/KISWebSocket.dpr
Normal file
14
한국투자증권(API)/legacy/websocket/delphi/KISWebSocket.dpr
Normal file
@@ -0,0 +1,14 @@
|
||||
program KISWebSocket;
|
||||
|
||||
uses
|
||||
Vcl.Forms,
|
||||
MainForm in 'MainForm.pas' {Form1};
|
||||
|
||||
{$R *.res}
|
||||
|
||||
begin
|
||||
Application.Initialize;
|
||||
Application.MainFormOnTaskbar := True;
|
||||
Application.CreateForm(TForm1, Form1);
|
||||
Application.Run;
|
||||
end.
|
||||
1119
한국투자증권(API)/legacy/websocket/delphi/KISWebSocket.dproj
Normal file
1119
한국투자증권(API)/legacy/websocket/delphi/KISWebSocket.dproj
Normal file
File diff suppressed because it is too large
Load Diff
183
한국투자증권(API)/legacy/websocket/delphi/MainForm.dfm
Normal file
183
한국투자증권(API)/legacy/websocket/delphi/MainForm.dfm
Normal file
@@ -0,0 +1,183 @@
|
||||
object Form1: TForm1
|
||||
Left = 0
|
||||
Top = 0
|
||||
Caption = #54620#44397#53804#51088#51613#44428' WebSocket '#53580#49828#53944' v1.0'
|
||||
ClientHeight = 600
|
||||
ClientWidth = 750
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -12
|
||||
Font.Name = 'Segoe UI'
|
||||
Font.Style = []
|
||||
Position = poScreenCenter
|
||||
OnCreate = FormCreate
|
||||
TextHeight = 15
|
||||
object Label1: TLabel
|
||||
Left = 24
|
||||
Top = 24
|
||||
Width = 73
|
||||
Height = 15
|
||||
Caption = 'Approval Key:'
|
||||
end
|
||||
object Label2: TLabel
|
||||
Left = 24
|
||||
Top = 58
|
||||
Width = 32
|
||||
Height = 15
|
||||
Caption = 'TR_ID:'
|
||||
end
|
||||
object Label3: TLabel
|
||||
Left = 24
|
||||
Top = 92
|
||||
Width = 89
|
||||
Height = 15
|
||||
Caption = 'TR_KEY:'#51333#47785#53076#46300
|
||||
end
|
||||
object edtApprovalKey: TEdit
|
||||
Left = 120
|
||||
Top = 21
|
||||
Width = 420
|
||||
Height = 23
|
||||
TabOrder = 0
|
||||
TextHint = 'Approval Key '#51077#47141' (e.g. e44ffe64-7a22-...)'
|
||||
end
|
||||
object edtTrId: TEdit
|
||||
Left = 120
|
||||
Top = 55
|
||||
Width = 150
|
||||
Height = 23
|
||||
TabOrder = 1
|
||||
Text = 'H0STCNT0'
|
||||
TextHint = 'TR_ID ('#50696': H0STCNT0)'
|
||||
OnChange = edtTrIdChange
|
||||
end
|
||||
object edtTrKey: TEdit
|
||||
Left = 120
|
||||
Top = 89
|
||||
Width = 150
|
||||
Height = 23
|
||||
TabOrder = 2
|
||||
Text = '005930'
|
||||
TextHint = #51333#47785#53076#46300' ('#50696': 005930)'
|
||||
end
|
||||
object btnConnect: TButton
|
||||
Left = 560
|
||||
Top = 19
|
||||
Width = 170
|
||||
Height = 30
|
||||
Caption = #50672#44208
|
||||
TabOrder = 3
|
||||
OnClick = btnConnectClick
|
||||
end
|
||||
object btnSubscribe: TButton
|
||||
Left = 560
|
||||
Top = 55
|
||||
Width = 80
|
||||
Height = 30
|
||||
Caption = #44396#46021
|
||||
TabOrder = 4
|
||||
OnClick = btnSubscribeClick
|
||||
end
|
||||
object btnUnsubscribe: TButton
|
||||
Left = 650
|
||||
Top = 55
|
||||
Width = 80
|
||||
Height = 30
|
||||
Caption = #44396#46021#54644#51228
|
||||
TabOrder = 5
|
||||
OnClick = btnUnsubscribeClick
|
||||
end
|
||||
object btnDisconnect: TButton
|
||||
Left = 560
|
||||
Top = 91
|
||||
Width = 170
|
||||
Height = 30
|
||||
Caption = #50672#44208' '#54644#51228
|
||||
TabOrder = 6
|
||||
OnClick = btnDisconnectClick
|
||||
end
|
||||
object Memo1: TMemo
|
||||
Left = 24
|
||||
Top = 136
|
||||
Width = 706
|
||||
Height = 441
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -12
|
||||
Font.Name = 'Consolas'
|
||||
Font.Style = []
|
||||
ParentFont = False
|
||||
ScrollBars = ssVertical
|
||||
TabOrder = 7
|
||||
end
|
||||
object sgcWebSocketClient1: TsgcWebSocketClient
|
||||
Port = 80
|
||||
ConnectTimeout = 0
|
||||
ReadTimeout = -1
|
||||
WriteTimeout = 0
|
||||
TLS = False
|
||||
Proxy.Enabled = False
|
||||
Proxy.Port = 8080
|
||||
Proxy.ProxyType = pxyHTTP
|
||||
HeartBeat.Enabled = True
|
||||
HeartBeat.HeartBeatType = hbtAlways
|
||||
HeartBeat.Interval = 30000
|
||||
HeartBeat.Timeout = 0
|
||||
IPVersion = Id_IPv4
|
||||
OnConnect = sgcWebSocketClient1Connect
|
||||
OnMessage = sgcWebSocketClient1Message
|
||||
OnDisconnect = sgcWebSocketClient1Disconnect
|
||||
OnError = sgcWebSocketClient1Error
|
||||
Authentication.Enabled = False
|
||||
Authentication.URL.Enabled = True
|
||||
Authentication.Session.Enabled = False
|
||||
Authentication.Basic.Enabled = False
|
||||
Authentication.Token.Enabled = False
|
||||
Authentication.Token.AuthName = 'Bearer'
|
||||
Extensions.DeflateFrame.Enabled = False
|
||||
Extensions.DeflateFrame.WindowBits = 15
|
||||
Extensions.PerMessage_Deflate.Enabled = False
|
||||
Extensions.PerMessage_Deflate.ClientMaxWindowBits = 15
|
||||
Extensions.PerMessage_Deflate.ClientNoContextTakeOver = False
|
||||
Extensions.PerMessage_Deflate.MemLevel = 9
|
||||
Extensions.PerMessage_Deflate.ServerMaxWindowBits = 15
|
||||
Extensions.PerMessage_Deflate.ServerNoContextTakeOver = False
|
||||
Options.CleanDisconnect = False
|
||||
Options.FragmentedMessages = frgOnlyBuffer
|
||||
Options.Parameters = '/'
|
||||
Options.RaiseDisconnectExceptions = True
|
||||
Options.ValidateUTF8 = False
|
||||
Specifications.Drafts.Hixie76 = False
|
||||
Specifications.RFC6455 = True
|
||||
NotifyEvents = neAsynchronous
|
||||
LogFile.Enabled = False
|
||||
QueueOptions.Binary.Level = qmNone
|
||||
QueueOptions.Ping.Level = qmNone
|
||||
QueueOptions.Text.Level = qmNone
|
||||
WatchDog.Attempts = 0
|
||||
WatchDog.Enabled = False
|
||||
WatchDog.Interval = 10
|
||||
Throttle.BitsPerSec = 0
|
||||
Throttle.Enabled = False
|
||||
LoadBalancer.Enabled = False
|
||||
LoadBalancer.Port = 0
|
||||
LoadBalancer.TLS = False
|
||||
TLSOptions.VerifyCertificate = False
|
||||
TLSOptions.VerifyDepth = 0
|
||||
TLSOptions.Version = tlsUndefined
|
||||
TLSOptions.IOHandler = iohOpenSSL
|
||||
TLSOptions.OpenSSL_Options.APIVersion = oslAPI_1_0
|
||||
TLSOptions.OpenSSL_Options.LegacyProvider.Enabled = False
|
||||
TLSOptions.OpenSSL_Options.LegacyProvider.LibPath = oslpNone
|
||||
TLSOptions.OpenSSL_Options.LibPath = oslpNone
|
||||
TLSOptions.OpenSSL_Options.UnixSymLinks = oslsSymLinksDefault
|
||||
TLSOptions.OpenSSL_Options.VersionMin = tlsUndefined
|
||||
TLSOptions.OpenSSL_Options.X509Checks.Mode = []
|
||||
TLSOptions.SChannel_Options.CertStoreName = scsnMY
|
||||
TLSOptions.SChannel_Options.CertStorePath = scspStoreCurrentUser
|
||||
TLSOptions.SChannel_Options.UseLegacyCredentials = False
|
||||
Left = 400
|
||||
Top = 64
|
||||
end
|
||||
end
|
||||
306
한국투자증권(API)/legacy/websocket/delphi/MainForm.pas
Normal file
306
한국투자증권(API)/legacy/websocket/delphi/MainForm.pas
Normal file
@@ -0,0 +1,306 @@
|
||||
unit MainForm;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, System.JSON,
|
||||
Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, Vcl.Dialogs,
|
||||
sgcWebSocket, sgcWebSocket_Classes, sgcWebSocket_Client, sgcBase_Classes,
|
||||
sgcSocket_Classes, sgcTCP_Classes, sgcWebSocket_Classes_Indy;
|
||||
|
||||
type
|
||||
TForm1 = class(TForm)
|
||||
sgcWebSocketClient1: TsgcWebSocketClient;
|
||||
btnConnect: TButton;
|
||||
btnSubscribe: TButton;
|
||||
btnUnsubscribe: TButton;
|
||||
btnDisconnect: TButton;
|
||||
Memo1: TMemo;
|
||||
edtApprovalKey: TEdit;
|
||||
edtTrId: TEdit;
|
||||
edtTrKey: TEdit;
|
||||
Label1: TLabel;
|
||||
Label2: TLabel;
|
||||
Label3: TLabel;
|
||||
procedure btnConnectClick(Sender: TObject);
|
||||
procedure btnSubscribeClick(Sender: TObject);
|
||||
procedure btnUnsubscribeClick(Sender: TObject);
|
||||
procedure btnDisconnectClick(Sender: TObject);
|
||||
procedure sgcWebSocketClient1Connect(Connection: TsgcWSConnection);
|
||||
procedure sgcWebSocketClient1Disconnect(Connection: TsgcWSConnection; Code: Integer);
|
||||
procedure sgcWebSocketClient1Message(Connection: TsgcWSConnection; const Text: string);
|
||||
procedure sgcWebSocketClient1Error(Connection: TsgcWSConnection; const Error: string);
|
||||
procedure FormCreate(Sender: TObject);
|
||||
procedure edtTrIdChange(Sender: TObject);
|
||||
private
|
||||
APPROVAL_KEY: string;
|
||||
procedure LogMessage(const Msg: string);
|
||||
procedure SendSubscription(const TrType: string);
|
||||
procedure UpdateWebSocketURL;
|
||||
public
|
||||
end;
|
||||
|
||||
var
|
||||
Form1: TForm1;
|
||||
|
||||
implementation
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
procedure TForm1.FormCreate(Sender: TObject);
|
||||
begin
|
||||
// 웹소켓 초기 설정
|
||||
sgcWebSocketClient1.Active := False;
|
||||
sgcWebSocketClient1.HeartBeat.Enabled := True;
|
||||
sgcWebSocketClient1.HeartBeat.Interval := 30000;
|
||||
|
||||
btnSubscribe.Enabled := False;
|
||||
btnUnsubscribe.Enabled := False;
|
||||
btnDisconnect.Enabled := False;
|
||||
|
||||
Memo1.Clear;
|
||||
|
||||
// 기본값 설정
|
||||
edtTrId.Text := 'H0STCNT0'; // 국내주식 체결통보
|
||||
edtTrKey.Text := '005930'; // 삼성전자
|
||||
|
||||
// URL 초기 설정
|
||||
UpdateWebSocketURL;
|
||||
|
||||
LogMessage('═══════════════════════════════════════════');
|
||||
LogMessage(' 한국투자증권 웹소켓 클라이언트 v1.0');
|
||||
LogMessage('═══════════════════════════════════════════');
|
||||
LogMessage('');
|
||||
LogMessage('[TR_ID 예시]');
|
||||
LogMessage(' H0STCNT0 : 국내주식 체결가');
|
||||
LogMessage(' H0STASP0 : 국내주식 호가');
|
||||
LogMessage(' HDFSCNT0 : 해외주식 지연체결가');
|
||||
LogMessage(' HDFSASP0 : 해외주식 호가');
|
||||
LogMessage('');
|
||||
LogMessage('[종목코드 예시]');
|
||||
LogMessage(' 국내: 005930 (삼성전자), 000660 (SK하이닉스)');
|
||||
LogMessage(' 해외: DNASTSLA (나스닥 테슬라), DNYSBABA (뉴욕 알리바바)');
|
||||
LogMessage('═══════════════════════════════════════════');
|
||||
end;
|
||||
|
||||
procedure TForm1.UpdateWebSocketURL;
|
||||
var
|
||||
trId: string;
|
||||
begin
|
||||
trId := Trim(edtTrId.Text);
|
||||
if trId <> '' then
|
||||
begin
|
||||
sgcWebSocketClient1.URL := 'ws://ops.koreainvestment.com:21000/tryitout/' + trId;
|
||||
LogMessage('URL 업데이트: ws://ops.koreainvestment.com:21000/tryitout/' + trId);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TForm1.edtTrIdChange(Sender: TObject);
|
||||
begin
|
||||
// TR_ID가 변경되면 URL도 자동 업데이트
|
||||
if not sgcWebSocketClient1.Active then
|
||||
UpdateWebSocketURL;
|
||||
end;
|
||||
|
||||
procedure TForm1.LogMessage(const Msg: string);
|
||||
begin
|
||||
Memo1.Lines.Add(FormatDateTime('[hh:nn:ss] ', Now) + Msg);
|
||||
if Memo1.Lines.Count > 0 then
|
||||
Memo1.Perform(EM_SCROLLCARET, 0, 0);
|
||||
end;
|
||||
|
||||
procedure TForm1.btnConnectClick(Sender: TObject);
|
||||
var
|
||||
trId: string;
|
||||
begin
|
||||
APPROVAL_KEY := Trim(edtApprovalKey.Text);
|
||||
trId := Trim(edtTrId.Text);
|
||||
|
||||
if APPROVAL_KEY = '' then
|
||||
begin
|
||||
ShowMessage('Approval Key를 입력해주세요.');
|
||||
edtApprovalKey.SetFocus;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if trId = '' then
|
||||
begin
|
||||
ShowMessage('TR_ID를 입력해주세요.');
|
||||
edtTrId.SetFocus;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
try
|
||||
// URL 최종 확인 및 업데이트
|
||||
UpdateWebSocketURL;
|
||||
|
||||
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
LogMessage('웹소켓 연결 시도...');
|
||||
LogMessage('TR_ID: ' + trId);
|
||||
sgcWebSocketClient1.Active := True;
|
||||
except
|
||||
on E: Exception do
|
||||
begin
|
||||
LogMessage('연결 실패: ' + E.Message);
|
||||
ShowMessage('연결 실패: ' + E.Message);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TForm1.btnDisconnectClick(Sender: TObject);
|
||||
begin
|
||||
if sgcWebSocketClient1.Active then
|
||||
begin
|
||||
LogMessage('연결 해제 중...');
|
||||
sgcWebSocketClient1.Active := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TForm1.SendSubscription(const TrType: string);
|
||||
var
|
||||
jHeader, jBody, jInput, jSend: TJSONObject;
|
||||
sJson: string;
|
||||
begin
|
||||
if not sgcWebSocketClient1.Active then
|
||||
begin
|
||||
ShowMessage('웹소켓이 연결되지 않았습니다.');
|
||||
Exit;
|
||||
end;
|
||||
|
||||
jHeader := TJSONObject.Create;
|
||||
jInput := TJSONObject.Create;
|
||||
jBody := TJSONObject.Create;
|
||||
jSend := TJSONObject.Create;
|
||||
|
||||
try
|
||||
// Header 구성
|
||||
jHeader.AddPair('approval_key', APPROVAL_KEY);
|
||||
jHeader.AddPair('custtype', 'P');
|
||||
jHeader.AddPair('tr_type', TrType);
|
||||
jHeader.AddPair('content-type', 'utf-8');
|
||||
|
||||
// Input 구성
|
||||
jInput.AddPair('tr_id', 'H0STCNT0');
|
||||
jInput.AddPair('tr_key', '000660');
|
||||
|
||||
// Body 구성
|
||||
jBody.AddPair('input', jInput); // jInput 소유권은 jBody가 가져감
|
||||
|
||||
// 전체 JSON 구성
|
||||
jSend.AddPair('header', jHeader); // jHeader 소유권은 jSend가 가져감
|
||||
jSend.AddPair('body', jBody); // jBody 소유권은 jSend가 가져감
|
||||
|
||||
sJson := jSend.ToJSON;
|
||||
|
||||
LogMessage('전송 데이터: ' + sJson);
|
||||
sgcWebSocketClient1.WriteData(sJson);
|
||||
|
||||
if TrType = '1' then
|
||||
LogMessage('구독 요청 전송 완료')
|
||||
else
|
||||
LogMessage('구독 해제 요청 전송 완료');
|
||||
|
||||
finally
|
||||
// ✅ 하위 객체는 해제하지 말고, 최상위 객체(jSend)만 해제
|
||||
jSend.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TForm1.btnSubscribeClick(Sender: TObject);
|
||||
begin
|
||||
SendSubscription('1');
|
||||
end;
|
||||
|
||||
procedure TForm1.btnUnsubscribeClick(Sender: TObject);
|
||||
begin
|
||||
SendSubscription('2');
|
||||
end;
|
||||
|
||||
procedure TForm1.sgcWebSocketClient1Connect(Connection: TsgcWSConnection);
|
||||
begin
|
||||
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
LogMessage('✓ 웹소켓 연결 성공!');
|
||||
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
btnConnect.Enabled := False;
|
||||
btnSubscribe.Enabled := True;
|
||||
btnUnsubscribe.Enabled := True;
|
||||
btnDisconnect.Enabled := True;
|
||||
edtApprovalKey.Enabled := False;
|
||||
|
||||
end;
|
||||
|
||||
procedure TForm1.sgcWebSocketClient1Disconnect(Connection: TsgcWSConnection; Code: Integer);
|
||||
begin
|
||||
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
LogMessage('✗ 웹소켓 연결 해제됨');
|
||||
LogMessage('종료 코드: ' + IntToStr(Code));
|
||||
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
btnConnect.Enabled := True;
|
||||
btnSubscribe.Enabled := False;
|
||||
btnUnsubscribe.Enabled := False;
|
||||
btnDisconnect.Enabled := False;
|
||||
edtApprovalKey.Enabled := True;
|
||||
|
||||
end;
|
||||
|
||||
procedure TForm1.sgcWebSocketClient1Message(Connection: TsgcWSConnection; const Text: string);
|
||||
var
|
||||
jResponse: TJSONObject;
|
||||
jHeader, jBody: TJSONObject;
|
||||
trId, rtCd, msgCd, msg1: string;
|
||||
begin
|
||||
LogMessage('수신: ' + Text);
|
||||
|
||||
try
|
||||
jResponse := TJSONObject.ParseJSONValue(Text) as TJSONObject;
|
||||
if Assigned(jResponse) then
|
||||
try
|
||||
// Header 파싱
|
||||
if jResponse.TryGetValue<TJSONObject>('header', jHeader) then
|
||||
begin
|
||||
jHeader.TryGetValue<string>('tr_id', trId);
|
||||
LogMessage('TR_ID: ' + trId);
|
||||
end;
|
||||
|
||||
// Body 파싱
|
||||
if jResponse.TryGetValue<TJSONObject>('body', jBody) then
|
||||
begin
|
||||
// 응답 코드 확인
|
||||
rtCd := jBody.GetValue<string>('rt_cd', '');
|
||||
msgCd := jBody.GetValue<string>('msg_cd', '');
|
||||
msg1 := jBody.GetValue<string>('msg1', '');
|
||||
|
||||
if rtCd <> '' then
|
||||
begin
|
||||
if rtCd = '0' then
|
||||
LogMessage('✓ 성공: ' + msg1 + ' [' + msgCd + ']')
|
||||
else
|
||||
LogMessage('✗ 실패: ' + msg1 + ' [' + msgCd + ']');
|
||||
end
|
||||
else
|
||||
begin
|
||||
// 실시간 체결 데이터
|
||||
LogMessage('실시간 데이터: ' + jBody.ToJSON);
|
||||
end;
|
||||
end;
|
||||
|
||||
finally
|
||||
jResponse.Free;
|
||||
end;
|
||||
except
|
||||
on E: Exception do
|
||||
LogMessage('메시지 파싱 오류: ' + E.Message);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TForm1.sgcWebSocketClient1Error(Connection: TsgcWSConnection; const Error: string);
|
||||
begin
|
||||
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
LogMessage('❌ 에러 발생: ' + Error);
|
||||
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
ShowMessage('에러: ' + Error);
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,560 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
import time
|
||||
from multiprocessing import Process, Queue, Manager
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import asyncio
|
||||
import websockets
|
||||
import urllib3
|
||||
import os
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
# 웹소켓 접속키 발급
|
||||
def get_approval(key, secret):
|
||||
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"secretkey": secret}
|
||||
PATH = "oauth2/Approval"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
approval_key = res.json()["approval_key"]
|
||||
return approval_key
|
||||
|
||||
|
||||
# 국내주식호가 출력라이브러리
|
||||
def stockhoka(data):
|
||||
""" 넘겨받는데이터가 정상인지 확인
|
||||
print("stockhoka[%s]"%(data))
|
||||
"""
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
"""
|
||||
print("유가증권 단축 종목코드 [" + recvvalue[0] + "]")
|
||||
print("영업시간 [" + recvvalue[1] + "]" + "시간구분코드 [" + recvvalue[2] + "]")
|
||||
print("======================================")
|
||||
print("매도호가10 [%s] 잔량10 [%s]" % (recvvalue[12], recvvalue[32]))
|
||||
print("매도호가09 [%s] 잔량09 [%s]" % (recvvalue[11], recvvalue[31]))
|
||||
print("매도호가08 [%s] 잔량08 [%s]" % (recvvalue[10], recvvalue[30]))
|
||||
print("매도호가07 [%s] 잔량07 [%s]" % (recvvalue[9], recvvalue[29]))
|
||||
print("매도호가06 [%s] 잔량06 [%s]" % (recvvalue[8], recvvalue[28]))
|
||||
print("매도호가05 [%s] 잔량05 [%s]" % (recvvalue[7], recvvalue[27]))
|
||||
print("매도호가04 [%s] 잔량04 [%s]" % (recvvalue[6], recvvalue[26]))
|
||||
print("매도호가03 [%s] 잔량03 [%s]" % (recvvalue[5], recvvalue[25]))
|
||||
print("매도호가02 [%s] 잔량02 [%s]" % (recvvalue[4], recvvalue[24]))
|
||||
print("매도호가01 [%s] 잔량01 [%s]" % (recvvalue[3], recvvalue[23]))
|
||||
print("--------------------------------------")
|
||||
print("매수호가01 [%s] 잔량01 [%s]" % (recvvalue[13], recvvalue[33]))
|
||||
print("매수호가02 [%s] 잔량02 [%s]" % (recvvalue[14], recvvalue[34]))
|
||||
print("매수호가03 [%s] 잔량03 [%s]" % (recvvalue[15], recvvalue[35]))
|
||||
print("매수호가04 [%s] 잔량04 [%s]" % (recvvalue[16], recvvalue[36]))
|
||||
print("매수호가05 [%s] 잔량05 [%s]" % (recvvalue[17], recvvalue[37]))
|
||||
print("매수호가06 [%s] 잔량06 [%s]" % (recvvalue[18], recvvalue[38]))
|
||||
print("매수호가07 [%s] 잔량07 [%s]" % (recvvalue[19], recvvalue[39]))
|
||||
print("매수호가08 [%s] 잔량08 [%s]" % (recvvalue[20], recvvalue[40]))
|
||||
print("매수호가09 [%s] 잔량09 [%s]" % (recvvalue[21], recvvalue[41]))
|
||||
print("매수호가10 [%s] 잔량10 [%s]" % (recvvalue[22], recvvalue[42]))
|
||||
print("======================================")
|
||||
print("총매도호가 잔량 [%s]" % (recvvalue[43]))
|
||||
print("총매도호가 잔량 증감 [%s]" % (recvvalue[54]))
|
||||
print("총매수호가 잔량 [%s]" % (recvvalue[44]))
|
||||
print("총매수호가 잔량 증감 [%s]" % (recvvalue[55]))
|
||||
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[45]))
|
||||
print("시간외 총매수호가 증감 [%s]" % (recvvalue[46]))
|
||||
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[56]))
|
||||
print("시간외 총매수호가 증감 [%s]" % (recvvalue[57]))
|
||||
print("예상 체결가 [%s]" % (recvvalue[47]))
|
||||
print("예상 체결량 [%s]" % (recvvalue[48]))
|
||||
print("예상 거래량 [%s]" % (recvvalue[49]))
|
||||
print("예상체결 대비 [%s]" % (recvvalue[50]))
|
||||
print("부호 [%s]" % (recvvalue[51]))
|
||||
print("예상체결 전일대비율 [%s]" % (recvvalue[52]))
|
||||
print("누적거래량 [%s]" % (recvvalue[53]))
|
||||
print("주식매매 구분코드 [%s]" % (recvvalue[58]))
|
||||
"""
|
||||
|
||||
|
||||
# 국내주식체결처리 출력라이브러리
|
||||
def stockspurchase(data_cnt, data):
|
||||
print("============================================")
|
||||
menulist = "유가증권단축종목코드|주식체결시간|주식현재가|전일대비부호|전일대비|전일대비율|가중평균주식가격|주식시가|주식최고가|주식최저가|매도호가1|매수호가1|체결거래량|누적거래량|누적거래대금|매도체결건수|매수체결건수|순매수체결건수|체결강도|총매도수량|총매수수량|체결구분|매수비율|전일거래량대비등락율|시가시간|시가대비구분|시가대비|최고가시간|고가대비구분|고가대비|최저가시간|저가대비구분|저가대비|영업일자|신장운영구분코드|거래정지여부|매도호가잔량|매수호가잔량|총매도호가잔량|총매수호가잔량|거래량회전율|전일동시간누적거래량|전일동시간누적거래량비율|시간구분코드|임의종료구분코드|정적VI발동기준가"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
## print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
## print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 국내주식체결통보 출력라이브러리
|
||||
def stocksigningnotice_domestic(data, key, iv):
|
||||
|
||||
# AES256 처리 단계
|
||||
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
|
||||
pValue = aes_dec_str.split('^')
|
||||
|
||||
if pValue[13] == '2': # 체결통보
|
||||
## print("#### 국내주식 체결 통보 ####")
|
||||
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|체결수량|체결단가|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|주문가격"
|
||||
menustr1 = menulist.split('|')
|
||||
else:
|
||||
## print("#### 국내주식 주문·정정·취소·거부 접수 통보 ####")
|
||||
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|주문수량|주문가격|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|체결단가"
|
||||
menustr1 = menulist.split('|')
|
||||
|
||||
i = 0
|
||||
for menu in menustr1:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
def data_processor_worker(worker_id, data_queue, stock_code):
|
||||
"""
|
||||
데이터 처리 전용 워커 프로세스
|
||||
|
||||
Args:
|
||||
worker_id: 워커 식별 번호
|
||||
data_queue: 메인 프로세스로부터 데이터를 받을 큐
|
||||
stock_code: 담당 종목코드
|
||||
"""
|
||||
import sys
|
||||
|
||||
process_id = os.getpid()
|
||||
print(f"\n{'#'*80}")
|
||||
print(f"[Processor-{worker_id}] 시작: PID={process_id}, 담당종목={stock_code}")
|
||||
print(f"{'#'*80}\n")
|
||||
|
||||
processed_count = 0
|
||||
message = None # 에러 추적용
|
||||
error_count = 0
|
||||
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
# 큐에서 데이터 가져오기 (타임아웃 1초)
|
||||
message = data_queue.get(timeout=1.0)
|
||||
|
||||
if message is None: # 종료 신호
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] 종료 신호 수신")
|
||||
break
|
||||
|
||||
# 메시지 유효성 검사
|
||||
if not isinstance(message, dict):
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] 잘못된 메시지 타입: {type(message)}, 내용: {message}")
|
||||
continue
|
||||
|
||||
msg_type = message.get('type')
|
||||
data = message.get('data')
|
||||
source_stock = message.get('stock_code')
|
||||
|
||||
if not msg_type:
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] 메시지 타입 없음: {message}")
|
||||
continue
|
||||
|
||||
if not source_stock:
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] 종목코드 없음: {message}")
|
||||
continue
|
||||
|
||||
# 자신이 담당하는 종목의 데이터만 처리
|
||||
if source_stock != stock_code:
|
||||
continue
|
||||
|
||||
processed_count += 1
|
||||
|
||||
if msg_type == 'hoka':
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] [종목:{stock_code}] "
|
||||
f"호가 데이터 처리 중... (처리건수: {processed_count})")
|
||||
stockhoka(data)
|
||||
|
||||
elif msg_type == 'chegyeol':
|
||||
data_cnt = message.get('data_cnt', 1)
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] [종목:{stock_code}] "
|
||||
f"체결 데이터 처리 중... (체결건수: {data_cnt}, 총처리: {processed_count})")
|
||||
stockspurchase(data_cnt, data)
|
||||
|
||||
elif msg_type == 'signing_notice':
|
||||
aes_key = message.get('aes_key')
|
||||
aes_iv = message.get('aes_iv')
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] [종목:{stock_code}] "
|
||||
f"체결통보 처리 중... (처리건수: {processed_count})")
|
||||
stocksigningnotice(data, aes_key, aes_iv)
|
||||
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
error_msg = str(e)
|
||||
|
||||
# Queue.Empty는 정상 동작
|
||||
if "Empty" in str(type(e).__name__):
|
||||
continue
|
||||
|
||||
# 기타 에러는 상세 출력
|
||||
import traceback
|
||||
error_details = traceback.format_exc()
|
||||
|
||||
print(f"\n{'!'*80}")
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] 🔴 처리 에러 발생 (에러 #{error_count})")
|
||||
print(f" 에러 타입: {type(e).__name__}")
|
||||
print(f" 에러 메시지: '{error_msg}' (길이: {len(error_msg)})")
|
||||
print(f" 에러 repr: {repr(e)}")
|
||||
|
||||
if message:
|
||||
print(f" 문제 메시지 타입: {type(message)}")
|
||||
try:
|
||||
print(f" 문제 메시지 내용: {message}")
|
||||
except:
|
||||
print(f" 문제 메시지 내용: [출력 불가]")
|
||||
else:
|
||||
print(f" 문제 메시지: None")
|
||||
|
||||
print(f"\n 상세 스택 트레이스:")
|
||||
print(error_details)
|
||||
print(f"{'!'*80}\n")
|
||||
|
||||
# 에러가 너무 많으면 종료
|
||||
if error_count > 100:
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] 에러 과다 발생으로 종료")
|
||||
break
|
||||
|
||||
continue
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] 인터럽트로 종료")
|
||||
except Exception as e:
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] 치명적 에러: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print(f"[Processor-{worker_id}] [PID:{process_id}] 종료. 총 처리건수: {processed_count}, 에러: {error_count}")
|
||||
|
||||
|
||||
def parse_stock_code_from_data(raw_data, trid):
|
||||
"""
|
||||
실시간 데이터에서 종목코드 추출
|
||||
|
||||
Args:
|
||||
raw_data: 파이프로 구분된 실시간 데이터
|
||||
trid: TR ID (H0STCNT0, H0STASP0 등)
|
||||
|
||||
Returns:
|
||||
종목코드 (6자리)
|
||||
"""
|
||||
try:
|
||||
# 디버깅: 처음 3번만 전체 데이터 출력
|
||||
if not hasattr(parse_stock_code_from_data, 'debug_count'):
|
||||
parse_stock_code_from_data.debug_count = 0
|
||||
|
||||
if parse_stock_code_from_data.debug_count < 3:
|
||||
print(f"\n[DEBUG parse_stock_code] TR_ID: {trid}")
|
||||
print(f"[DEBUG parse_stock_code] Raw data: {raw_data[:200]}")
|
||||
parse_stock_code_from_data.debug_count += 1
|
||||
|
||||
# 한국투자증권 실시간 데이터는 ^ 구분자로 필드가 나뉨
|
||||
fields = raw_data.split('^')
|
||||
|
||||
if parse_stock_code_from_data.debug_count <= 3:
|
||||
print(f"[DEBUG parse_stock_code] 필드 개수: {len(fields)}")
|
||||
print(f"[DEBUG parse_stock_code] 첫 5개 필드: {fields[:5]}\n")
|
||||
|
||||
if len(fields) > 0:
|
||||
# 첫 번째 필드가 보통 종목코드
|
||||
stock_code = fields[0].strip()
|
||||
|
||||
# 종목코드 검증 (6자리 숫자)
|
||||
if stock_code.isdigit() and len(stock_code) == 6:
|
||||
return stock_code
|
||||
|
||||
# 파싱 실패 시 None 반환
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[parse_stock_code] 에러: {e}, data: {raw_data[:100]}")
|
||||
return None
|
||||
|
||||
|
||||
async def websocket_receiver(url, approval_key, stock_codes, data_queues, custtype='P'):
|
||||
"""
|
||||
단일 웹소켓 세션으로 여러 종목 데이터 수신
|
||||
수신한 데이터를 각 프로세스의 큐에 분배
|
||||
|
||||
Args:
|
||||
url: 웹소켓 URL
|
||||
approval_key: 승인 키
|
||||
stock_codes: 구독할 종목 리스트
|
||||
data_queues: 각 워커의 데이터 큐 딕셔너리 {종목코드: Queue}
|
||||
custtype: 고객 타입
|
||||
"""
|
||||
main_pid = os.getpid()
|
||||
aes_key = None
|
||||
aes_iv = None
|
||||
|
||||
retry_count = 0
|
||||
max_retries = 5
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
print(f"\n{'='*80}")
|
||||
print(f"[WebSocket Main] PID: {main_pid}")
|
||||
print(f"[WebSocket Main] 연결 시도... URL: {url}")
|
||||
print(f"[WebSocket Main] 구독 종목: {', '.join(stock_codes)}")
|
||||
print(f"{'='*80}\n")
|
||||
|
||||
async with websockets.connect(url, ping_interval=None) as websocket:
|
||||
# 웹소켓 세션 정보
|
||||
local_address = websocket.local_address
|
||||
remote_address = websocket.remote_address
|
||||
|
||||
print(f"\n{'*'*80}")
|
||||
print(f"[WebSocket Main] 세션 정보")
|
||||
print(f" - 메인 프로세스 PID: {main_pid}")
|
||||
print(f" - 로컬 주소: {local_address[0]}:{local_address[1]}")
|
||||
print(f" - 원격 주소: {remote_address[0]}:{remote_address[1]}")
|
||||
print(f" - 웹소켓 객체 ID: {id(websocket)}")
|
||||
print(f" - 구독 종목 수: {len(stock_codes)}")
|
||||
print(f"{'*'*80}\n")
|
||||
|
||||
# 각 종목에 대해 구독 요청 전송
|
||||
for i, stock_code in enumerate(stock_codes):
|
||||
await asyncio.sleep(0.5) # 요청 간격
|
||||
|
||||
senddata = {
|
||||
"header": {
|
||||
"approval_key": approval_key,
|
||||
"custtype": custtype,
|
||||
"tr_type": "1",
|
||||
"content-type": "utf-8"
|
||||
},
|
||||
"body": {
|
||||
"input": {
|
||||
"tr_id": "H0STCNT0", # 주식체결
|
||||
"tr_key": stock_code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
senddata_str = json.dumps(senddata, ensure_ascii=False)
|
||||
print(f"[WebSocket Main] [{i+1}/{len(stock_codes)}] 구독 요청: {stock_code}")
|
||||
await websocket.send(senddata_str)
|
||||
|
||||
retry_count = 0 # 연결 성공 시 재시도 카운트 초기화
|
||||
subscribe_count = 0
|
||||
|
||||
# 데이터 수신 및 분배 루프
|
||||
print(f"\n[WebSocket Main] 데이터 수신 대기 중...\n")
|
||||
|
||||
while True:
|
||||
try:
|
||||
data = await asyncio.wait_for(websocket.recv(), timeout=30.0)
|
||||
except asyncio.TimeoutError:
|
||||
print(f"[WebSocket Main] 타임아웃 - 연결 유지 중...")
|
||||
continue
|
||||
|
||||
# 실시간 데이터 처리 (0 또는 1로 시작)
|
||||
if data[0] in ('0', '1'):
|
||||
recvstr = data.split('|')
|
||||
|
||||
if len(recvstr) < 4:
|
||||
print(f"[WebSocket Main] Invalid data: {data}")
|
||||
continue
|
||||
|
||||
trid0 = recvstr[1]
|
||||
raw_data = recvstr[3]
|
||||
|
||||
# 실제 데이터에서 종목코드 추출
|
||||
target_stock = parse_stock_code_from_data(raw_data, trid0)
|
||||
|
||||
# 종목코드 추출 실패 또는 구독하지 않은 종목
|
||||
if not target_stock:
|
||||
print(f"[WebSocket Main] ⚠️ 종목코드 추출 실패: {raw_data[:50]}...")
|
||||
continue
|
||||
|
||||
if target_stock not in data_queues:
|
||||
print(f"[WebSocket Main] ⚠️ 구독하지 않은 종목: {target_stock}")
|
||||
continue
|
||||
|
||||
if data[0] == '0':
|
||||
# 주식호가
|
||||
if trid0 == "H0STASP0":
|
||||
message = {
|
||||
'type': 'hoka',
|
||||
'data': raw_data,
|
||||
'stock_code': target_stock
|
||||
}
|
||||
data_queues[target_stock].put(message)
|
||||
print(f"[WebSocket Main] 호가 데이터 → Processor (종목: {target_stock})")
|
||||
|
||||
# 주식체결
|
||||
elif trid0 == "H0STCNT0":
|
||||
data_cnt = int(recvstr[2])
|
||||
message = {
|
||||
'type': 'chegyeol',
|
||||
'data': raw_data,
|
||||
'data_cnt': data_cnt,
|
||||
'stock_code': target_stock
|
||||
}
|
||||
data_queues[target_stock].put(message)
|
||||
print(f"[WebSocket Main] 체결 데이터 → Processor (종목: {target_stock}, 건수: {data_cnt})")
|
||||
|
||||
elif data[0] == '1':
|
||||
# 주식체결 통보
|
||||
if trid0 in ("K0STCNI0", "K0STCNI9", "H0STCNI0", "H0STCNI9"):
|
||||
if aes_key and aes_iv:
|
||||
message = {
|
||||
'type': 'signing_notice',
|
||||
'data': raw_data,
|
||||
'aes_key': aes_key,
|
||||
'aes_iv': aes_iv,
|
||||
'stock_code': target_stock
|
||||
}
|
||||
data_queues[target_stock].put(message)
|
||||
print(f"[WebSocket Main] 체결통보 → Processor (종목: {target_stock})")
|
||||
|
||||
# JSON 메시지 처리
|
||||
else:
|
||||
try:
|
||||
jsonObject = json.loads(data)
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
# PINGPONG 처리
|
||||
if trid == "PINGPONG":
|
||||
print(f"[WebSocket Main] RECV [PINGPONG]")
|
||||
await websocket.send(data)
|
||||
print(f"[WebSocket Main] SEND [PINGPONG]")
|
||||
|
||||
# 일반 응답 처리
|
||||
else:
|
||||
rt_cd = jsonObject["body"]["rt_cd"]
|
||||
tr_key = jsonObject["header"]["tr_key"]
|
||||
msg = jsonObject["body"].get("msg1", "")
|
||||
|
||||
if rt_cd == '1': # 에러
|
||||
print(f"[WebSocket Main] ❌ ERROR [종목:{tr_key}] MSG [{msg}]")
|
||||
|
||||
elif rt_cd == '0': # 정상
|
||||
print(f"[WebSocket Main] ✓ SUCCESS [종목:{tr_key}] MSG [{msg}]")
|
||||
|
||||
if "SUBSCRIBE SUCCESS" in msg or "SUCCESS" in msg:
|
||||
subscribe_count += 1
|
||||
print(f"[WebSocket Main] 🎉 구독 완료! ({subscribe_count}/{len(stock_codes)})")
|
||||
|
||||
# AES 키 저장
|
||||
if trid in ("H0STCNI0", "H0STCNI9"):
|
||||
aes_key = jsonObject["body"]["output"]["key"]
|
||||
aes_iv = jsonObject["body"]["output"]["iv"]
|
||||
print(f"[WebSocket Main] AES KEY 저장: {aes_key[:20]}...")
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[WebSocket Main] JSON error: {e}")
|
||||
except KeyError as e:
|
||||
print(f"[WebSocket Main] Key error: {e}")
|
||||
|
||||
except websockets.exceptions.ConnectionClosed as e:
|
||||
print(f"[WebSocket Main] 연결 종료: {e}")
|
||||
retry_count += 1
|
||||
await asyncio.sleep(2 ** retry_count)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[WebSocket Main] Exception: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
retry_count += 1
|
||||
await asyncio.sleep(2 ** retry_count)
|
||||
|
||||
print(f"[WebSocket Main] 최대 재시도 횟수 초과")
|
||||
|
||||
|
||||
def main():
|
||||
"""메인 함수"""
|
||||
|
||||
# API 키 설정
|
||||
g_appkey = '앱키 입력해주세요'
|
||||
g_appsecret = '앱시크릿키 입력해주세요'
|
||||
|
||||
custtype = 'P' # 개인
|
||||
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
|
||||
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
|
||||
|
||||
# 모니터링할 종목 리스트
|
||||
stock_codes = [
|
||||
'005930', # 삼성전자
|
||||
'000660', # SK하이닉스
|
||||
'035420', # NAVER
|
||||
'005380', # 현대차
|
||||
'051910', # LG화학
|
||||
]
|
||||
|
||||
print(f"\n{'='*80}")
|
||||
print(f"메인 프로세스 PID: {os.getpid()}")
|
||||
print(f"구독 종목: {', '.join(stock_codes)}")
|
||||
print(f"{'='*80}\n")
|
||||
|
||||
try:
|
||||
# 1. Approval key 발급
|
||||
print("=== Approval Key 발급 중 ===")
|
||||
approval_key = get_approval(g_appkey, g_appsecret)
|
||||
print(f"Approval Key: {approval_key}\n")
|
||||
|
||||
# 2. 각 종목별 데이터 큐 생성 (Manager 사용)
|
||||
manager = Manager()
|
||||
data_queues = {}
|
||||
|
||||
for stock_code in stock_codes:
|
||||
data_queues[stock_code] = manager.Queue(maxsize=1000)
|
||||
|
||||
# 3. 데이터 처리 워커 프로세스 생성
|
||||
processes = []
|
||||
|
||||
for i, stock_code in enumerate(stock_codes):
|
||||
worker_id = i + 1
|
||||
p = Process(
|
||||
target=data_processor_worker,
|
||||
args=(worker_id, data_queues[stock_code], stock_code)
|
||||
)
|
||||
p.start()
|
||||
processes.append(p)
|
||||
print(f"✓ Processor-{worker_id} 시작: PID={p.pid}, 종목={stock_code}")
|
||||
time.sleep(0.2)
|
||||
|
||||
print(f"\n=== 모든 프로세서 시작 완료 ({len(processes)}개) ===\n")
|
||||
|
||||
# 4. 웹소켓 수신 시작 (메인 프로세스에서 실행)
|
||||
print("=== 웹소켓 연결 시작 ===\n")
|
||||
asyncio.run(websocket_receiver(url, approval_key, stock_codes, data_queues, custtype))
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n=== 프로그램 종료 중 ===")
|
||||
|
||||
# 모든 워커에 종료 신호 전송
|
||||
for stock_code in stock_codes:
|
||||
try:
|
||||
data_queues[stock_code].put(None)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 프로세스 종료 대기
|
||||
for p in processes:
|
||||
p.join(timeout=2)
|
||||
if p.is_alive():
|
||||
p.terminate()
|
||||
|
||||
print("모든 프로세서 종료 완료")
|
||||
|
||||
except Exception as e:
|
||||
print(f"메인 프로세스 에러: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
for p in processes:
|
||||
p.terminate()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
299
한국투자증권(API)/legacy/websocket/python/ops_ws_sample.py
Normal file
299
한국투자증권(API)/legacy/websocket/python/ops_ws_sample.py
Normal file
@@ -0,0 +1,299 @@
|
||||
# 웹 소켓 모듈을 선언한다.
|
||||
import websockets
|
||||
import json
|
||||
import requests
|
||||
import os
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from base64 import b64decode
|
||||
|
||||
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
|
||||
|
||||
key_bytes = 32
|
||||
|
||||
# AES256 DECODE
|
||||
def aes_cbc_base64_dec(key, iv, cipher_text):
|
||||
"""
|
||||
:param key: str type AES256 secret key value
|
||||
:param iv: str type AES256 Initialize Vector
|
||||
:param cipher_text: Base64 encoded AES256 str
|
||||
:return: Base64-AES256 decodec str
|
||||
"""
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
|
||||
|
||||
# 웹소켓 접속키 발급
|
||||
def get_approval(key, secret):
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"secretkey": secret}
|
||||
PATH = "oauth2/Approval"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
approval_key = res.json()["approval_key"]
|
||||
return approval_key
|
||||
|
||||
# 주식체결 출력라이브러리
|
||||
def stockhoka(data):
|
||||
""" 넘겨받는데이터가 정상인지 확인
|
||||
print("stockhoka[%s]"%(data))
|
||||
"""
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("유가증권 단축 종목코드 [" + recvvalue[0] + "]")
|
||||
print("영업시간 [" + recvvalue[1] + "]" + "시간구분코드 [" + recvvalue[2] + "]")
|
||||
print("======================================")
|
||||
print("매도호가10 [%s] 잔량10 [%s]" % (recvvalue[12], recvvalue[32]))
|
||||
print("매도호가09 [%s] 잔량09 [%s]" % (recvvalue[11], recvvalue[31]))
|
||||
print("매도호가08 [%s] 잔량08 [%s]" % (recvvalue[10], recvvalue[30]))
|
||||
print("매도호가07 [%s] 잔량07 [%s]" % (recvvalue[9], recvvalue[29]))
|
||||
print("매도호가06 [%s] 잔량06 [%s]" % (recvvalue[8], recvvalue[28]))
|
||||
print("매도호가05 [%s] 잔량05 [%s]" % (recvvalue[7], recvvalue[27]))
|
||||
print("매도호가04 [%s] 잔량04 [%s]" % (recvvalue[6], recvvalue[26]))
|
||||
print("매도호가03 [%s] 잔량03 [%s]" % (recvvalue[5], recvvalue[25]))
|
||||
print("매도호가02 [%s] 잔량02 [%s]" % (recvvalue[4], recvvalue[24]))
|
||||
print("매도호가01 [%s] 잔량01 [%s]" % (recvvalue[3], recvvalue[23]))
|
||||
print("--------------------------------------")
|
||||
print("매수호가01 [%s] 잔량01 [%s]" % (recvvalue[13], recvvalue[33]))
|
||||
print("매수호가02 [%s] 잔량02 [%s]" % (recvvalue[14], recvvalue[34]))
|
||||
print("매수호가03 [%s] 잔량03 [%s]" % (recvvalue[15], recvvalue[35]))
|
||||
print("매수호가04 [%s] 잔량04 [%s]" % (recvvalue[16], recvvalue[36]))
|
||||
print("매수호가05 [%s] 잔량05 [%s]" % (recvvalue[17], recvvalue[37]))
|
||||
print("매수호가06 [%s] 잔량06 [%s]" % (recvvalue[18], recvvalue[38]))
|
||||
print("매수호가07 [%s] 잔량07 [%s]" % (recvvalue[19], recvvalue[39]))
|
||||
print("매수호가08 [%s] 잔량08 [%s]" % (recvvalue[20], recvvalue[40]))
|
||||
print("매수호가09 [%s] 잔량09 [%s]" % (recvvalue[21], recvvalue[41]))
|
||||
print("매수호가10 [%s] 잔량10 [%s]" % (recvvalue[22], recvvalue[42]))
|
||||
print("======================================")
|
||||
print("총매도호가 잔량 [%s]" % (recvvalue[43]))
|
||||
print("총매도호가 잔량 증감 [%s]" % (recvvalue[54]))
|
||||
print("총매수호가 잔량 [%s]" % (recvvalue[44]))
|
||||
print("총매수호가 잔량 증감 [%s]" % (recvvalue[55]))
|
||||
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[45]))
|
||||
print("시간외 총매수호가 증감 [%s]" % (recvvalue[46]))
|
||||
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[56]))
|
||||
print("시간외 총매수호가 증감 [%s]" % (recvvalue[57]))
|
||||
print("예상 체결가 [%s]" % (recvvalue[47]))
|
||||
print("예상 체결량 [%s]" % (recvvalue[48]))
|
||||
print("예상 거래량 [%s]" % (recvvalue[49]))
|
||||
print("예상체결 대비 [%s]" % (recvvalue[50]))
|
||||
print("부호 [%s]" % (recvvalue[51]))
|
||||
print("예상체결 전일대비율 [%s]" % (recvvalue[52]))
|
||||
print("누적거래량 [%s]" % (recvvalue[53]))
|
||||
print("주식매매 구분코드 [%s]" % (recvvalue[58]))
|
||||
|
||||
|
||||
# 주식체결처리 출력라이브러리
|
||||
def stockspurchase(data_cnt, data):
|
||||
print("============================================")
|
||||
menulist = "유가증권단축종목코드|주식체결시간|주식현재가|전일대비부호|전일대비|전일대비율|가중평균주식가격|주식시가|주식최고가|주식최저가|매도호가1|매수호가1|체결거래량|누적거래량|누적거래대금|매도체결건수|매수체결건수|순매수체결건수|체결강도|총매도수량|총매수수량|체결구분|매수비율|전일거래량대비등락율|시가시간|시가대비구분|시가대비|최고가시간|고가대비구분|고가대비|최저가시간|저가대비구분|저가대비|영업일자|신장운영구분코드|거래정지여부|매도호가잔량|매수호가잔량|총매도호가잔량|총매수호가잔량|거래량회전율|전일동시간누적거래량|전일동시간누적거래량비율|시간구분코드|임의종료구분코드|정적VI발동기준가"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]"%(cnt+1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 국내주식체결통보 출력라이브러리
|
||||
def stocksigningnotice(data, key, iv):
|
||||
|
||||
# AES256 처리 단계
|
||||
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
|
||||
pValue = aes_dec_str.split('^')
|
||||
|
||||
if pValue[13] == '2': # 체결통보
|
||||
print("#### 국내주식 체결 통보 ####")
|
||||
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|체결수량|체결단가|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|주문가격"
|
||||
menustr1 = menulist.split('|')
|
||||
else:
|
||||
print("#### 국내주식 주문·정정·취소·거부 접수 통보 ####")
|
||||
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|주문수량|주문가격|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|체결단가"
|
||||
menustr1 = menulist.split('|')
|
||||
|
||||
i = 0
|
||||
for menu in menustr1:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
async def connect():
|
||||
|
||||
try:
|
||||
# 웹 소켓에 접속.( 주석은 koreainvest test server for websocket)
|
||||
## 시세데이터를 받기위한 데이터를 미리 할당해서 사용한다.
|
||||
|
||||
g_appkey = '앱키를 입력하세요'
|
||||
g_appsecret = '앱 시크릿키를 입력하세요'
|
||||
|
||||
stockcode = '005930' # 테스트용 임시 종목 설정, 삼성전자
|
||||
htsid = 'HTS ID를 입력하세요' # 체결통보용 htsid 입력
|
||||
custtype = 'P' # customer type, 개인:'P' 법인 'B'
|
||||
|
||||
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
|
||||
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
|
||||
|
||||
g_approval_key = get_approval(g_appkey, g_appsecret)
|
||||
print("approval_key [%s]" % (g_approval_key))
|
||||
|
||||
async with websockets.connect(url, ping_interval=None) as websocket:
|
||||
|
||||
"""" 주석처리는 더블쿼트 3개로 처리
|
||||
"""
|
||||
print("1.주식호가, 2.주식호가해제, 3.주식체결, 4.주식체결해제, 5.주식체결통보(고객), 6.주식체결통보해제(고객), 7.주식체결통보(모의), 8.주식체결통보해제(모의)")
|
||||
print("Input Command :")
|
||||
cmd = input()
|
||||
|
||||
# 입력값 체크 step
|
||||
if cmd < '0' or cmd > '8':
|
||||
print("> Wrong Input Data", cmd)
|
||||
elif cmd == '0':
|
||||
print("Exit!!")
|
||||
|
||||
# 입력값에 따라 전송 데이터셋 구분 처리
|
||||
if cmd == '1': # 주식호가 등록
|
||||
tr_id = 'H0STASP0'
|
||||
tr_type = '1'
|
||||
elif cmd == '2': # 주식호가 등록해제
|
||||
tr_id = 'H0STASP0'
|
||||
tr_type = '2'
|
||||
elif cmd == '3': # 주식체결 등록
|
||||
tr_id = 'H0STCNT0'
|
||||
tr_type = '1'
|
||||
elif cmd == '4': # 주식체결 등록해제
|
||||
tr_id = 'H0STCNT0'
|
||||
tr_type = '2'
|
||||
elif cmd == '5': # 주식체결통보 등록(고객용)
|
||||
tr_id = 'H0STCNI0' # 고객체결통보
|
||||
tr_type = '1'
|
||||
elif cmd == '6': # 주식체결통보 등록해제(고객용)
|
||||
tr_id = 'H0STCNI0' # 고객체결통보
|
||||
tr_type = '2'
|
||||
elif cmd == '7': # 주식체결통보 등록(모의)
|
||||
tr_id = 'H0STCNI9' #테스트용 직원체결통보
|
||||
tr_type = '1'
|
||||
elif cmd == '8': # 주식체결통보 등록해제(모의)
|
||||
tr_id = 'H0STCNI9' # 테스트용 직원체결통보
|
||||
tr_type = '2'
|
||||
else:
|
||||
senddata = 'wrong inert data'
|
||||
|
||||
# send json, 체결통보는 tr_key 입력항목이 상이하므로 분리를 한다.
|
||||
if cmd == '5' or cmd == '6' or cmd == '7' or cmd == '8':
|
||||
senddata = '{"header":{"approval_key":"' + g_approval_key + '","custtype":"'+custtype+'","tr_type":"' + tr_type + '","content-type":"utf-8"},"body":{"input":{"tr_id":"' + tr_id + '","tr_key":"' + htsid + '"}}}'
|
||||
else :
|
||||
senddata = '{"header":{"approval_key":"' + g_approval_key + '","custtype":"'+custtype+'","tr_type":"' + tr_type + '","content-type":"utf-8"},"body":{"input":{"tr_id":"' + tr_id + '","tr_key":"' + stockcode + '"}}}'
|
||||
|
||||
print('Input Command is :', senddata)
|
||||
|
||||
await websocket.send(senddata)
|
||||
# 무한히 데이터가 오기만 기다린다.
|
||||
while True:
|
||||
data = await websocket.recv()
|
||||
# print("Recev Command is :", data)
|
||||
|
||||
if data[0] == '0' or data[0] == '1': # 실시간 데이터일 경우
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
if data[0] == '0':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
if trid0 == "H0STASP0": # 주식호가tr 일경우의 처리 단계
|
||||
print("#### 주식호가 ####")
|
||||
stockhoka(recvstr[3])
|
||||
await asyncio.sleep(1)
|
||||
|
||||
elif trid0 == "H0STCNT0": # 주식체결 데이터 처리
|
||||
print("#### 주식체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase(data_cnt, recvstr[3])
|
||||
|
||||
elif data[0] == '1':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
if trid0 == "H0STCNI0" or trid0 == "H0STCNI9": # 주실체결 통보 처리
|
||||
stocksigningnotice(recvstr[3], aes_key, aes_iv)
|
||||
|
||||
# clearConsole()
|
||||
# break;
|
||||
else:
|
||||
jsonObject = json.loads(data)
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
if trid != "PINGPONG":
|
||||
rt_cd = jsonObject["body"]["rt_cd"]
|
||||
if rt_cd == '1': # 에러일 경우 처리
|
||||
print("### ERROR RETURN CODE [ %s ] MSG [ %s ]" % (rt_cd, jsonObject["body"]["msg1"]))
|
||||
break
|
||||
elif rt_cd == '0': # 정상일 경우 처리
|
||||
print("### RETURN CODE [ %s ] MSG [ %s ]" % (rt_cd, jsonObject["body"]["msg1"]))
|
||||
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
|
||||
if trid == "H0STCNI0" or trid == "H0STCNI9":
|
||||
aes_key = jsonObject["body"]["output"]["key"]
|
||||
aes_iv = jsonObject["body"]["output"]["iv"]
|
||||
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
|
||||
|
||||
elif trid == "PINGPONG":
|
||||
print("### RECV [PINGPONG] [%s]" % (data))
|
||||
await websocket.pong(data)
|
||||
print("### SEND [PINGPONG] [%s]" % (data))
|
||||
|
||||
# ----------------------------------------
|
||||
# 모든 함수의 공통 부분(Exception 처리)
|
||||
# ----------------------------------------
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
print('Connect Again!')
|
||||
time.sleep(0.1)
|
||||
|
||||
# 웹소켓 다시 시작
|
||||
await connect()
|
||||
|
||||
|
||||
# # 비동기로 서버에 접속한다.
|
||||
# asyncio.get_event_loop().run_until_complete(connect())
|
||||
# asyncio.get_event_loop().close()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# - Name : main
|
||||
# - Desc : 메인
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main():
|
||||
try:
|
||||
# 웹소켓 시작
|
||||
await connect()
|
||||
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# ---------------------------------------------------------------------
|
||||
# Logic Start!
|
||||
# ---------------------------------------------------------------------
|
||||
# 웹소켓 시작
|
||||
asyncio.run(main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("KeyboardInterrupt Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-100)
|
||||
|
||||
except Exception:
|
||||
print("Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-200)
|
||||
283
한국투자증권(API)/legacy/websocket/python/ws_commodity_future.py
Normal file
283
한국투자증권(API)/legacy/websocket/python/ws_commodity_future.py
Normal file
@@ -0,0 +1,283 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
### 모듈 임포트 ###
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import asyncio
|
||||
import traceback
|
||||
import websockets
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from base64 import b64decode
|
||||
|
||||
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
|
||||
|
||||
key_bytes = 32
|
||||
|
||||
|
||||
### 함수 정의 ###
|
||||
|
||||
# AES256 DECODE
|
||||
def aes_cbc_base64_dec(key, iv, cipher_text):
|
||||
"""
|
||||
:param key: str type AES256 secret key value
|
||||
:param iv: str type AES256 Initialize Vector
|
||||
:param cipher_text: Base64 encoded AES256 str
|
||||
:return: Base64-AES256 decodec str
|
||||
"""
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
|
||||
|
||||
|
||||
# 웹소켓 접속키 발급
|
||||
def get_approval(key, secret):
|
||||
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"secretkey": secret}
|
||||
PATH = "oauth2/Approval"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
approval_key = res.json()["approval_key"]
|
||||
return approval_key
|
||||
|
||||
|
||||
# 상품선물호가 출력라이브러리
|
||||
def stockhoka_productfuts(data):
|
||||
|
||||
# print(data)
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("상품선물 ["+recvvalue[ 0]+"]")
|
||||
print("영업시간 ["+recvvalue[ 1]+"]")
|
||||
print("====================================")
|
||||
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
|
||||
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
|
||||
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
|
||||
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
|
||||
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
|
||||
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
|
||||
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
|
||||
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
|
||||
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
|
||||
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
|
||||
print("====================================")
|
||||
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
|
||||
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
|
||||
|
||||
|
||||
# 상품선물체결처리 출력라이브러리
|
||||
def stockspurchase_productfuts(data_cnt, data):
|
||||
print("============================================")
|
||||
# print(data)
|
||||
menulist = "선물단축종목코드|영업시간|선물전일대비|전일대비부호|선물전일대비율|선물현재가|선물시가|선물최고가|선물최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드|미결제약정수량|미결제약정수량증감|시가시간|시가대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|선물매도호가|선물매수호가|매도호가잔량|매수호가잔량|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|협의대량거래량|실시간상한가|실시간하한가|실시간가격제한구분"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
# 선물옵션 체결통보 출력라이브러리
|
||||
def stocksigningnotice_futsoptn(data, key, iv):
|
||||
|
||||
# AES256 처리 단계
|
||||
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
|
||||
print(aes_dec_str)
|
||||
pValue = aes_dec_str.split('^')
|
||||
print(pValue)
|
||||
|
||||
if pValue[6] == '0': # 체결통보
|
||||
print("#### 지수선물옵션 체결 통보 ####")
|
||||
menulist_sign = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|체결수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_sign.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
else: # pValue[6] == 'L', 주문·정정·취소·거부 접수 통보
|
||||
|
||||
if pValue[5] == '1': # 정정 접수 통보 (정정구분이 1일 경우)
|
||||
print("#### 지수선물옵션 정정 접수 통보 ####")
|
||||
menulist_revise = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|정정수량|정정단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_revise.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
elif pValue[5] == '2': # 취소 접수 통보 (정정구분이 2일 경우)
|
||||
print("#### 지수선물옵션 취소 접수 통보 ####")
|
||||
menulist_cancel = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|취소수량|주문단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_cancel.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
elif pValue[11] == '1': # 거부 접수 통보 (거부여부가 1일 경우)
|
||||
print("#### 지수선물옵션 거부 접수 통보 ####")
|
||||
menulist_refuse = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|주문수량|주문단가|주문시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_refuse.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
else: # 주문 접수 통보
|
||||
print("#### 지수선물옵션 주문접수 통보 ####")
|
||||
menulist_order = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|주문수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_order.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
### 앱키 정의 ###
|
||||
|
||||
async def connect():
|
||||
|
||||
try:
|
||||
g_appkey = "앱키를 입력하세요"
|
||||
g_appsecret = "앱 시크릿키를 입력하세요"
|
||||
|
||||
g_approval_key = get_approval(g_appkey, g_appsecret)
|
||||
print("approval_key [%s]" % (g_approval_key))
|
||||
|
||||
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
|
||||
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
|
||||
|
||||
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기
|
||||
code_list = [['1','H0CFASP0','175V08'],['1','H0CFCNT0','175T11'], # 상품선물호가, 체결가
|
||||
['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
|
||||
|
||||
senddata_list=[]
|
||||
|
||||
for i,j,k in code_list:
|
||||
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
|
||||
senddata_list.append(temp)
|
||||
|
||||
|
||||
async with websockets.connect(url, ping_interval=None) as websocket:
|
||||
|
||||
for senddata in senddata_list:
|
||||
await websocket.send(senddata)
|
||||
await asyncio.sleep(0.5)
|
||||
print(f"Input Command is :{senddata}")
|
||||
|
||||
while True:
|
||||
data = await websocket.recv()
|
||||
# await asyncio.sleep(0.5)
|
||||
# print(f"Recev Command is :{data}") # 정제되지 않은 Request / Response 출력
|
||||
|
||||
if data[0] == '0':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "H0CFASP0": # 상품선물호가 tr 일경우의 처리 단계
|
||||
print("#### 상품선물호가 ####")
|
||||
stockhoka_productfuts(recvstr[3])
|
||||
|
||||
elif trid0 == "H0CFCNT0": # 상품선물체결 데이터 처리
|
||||
print("#### 상품선물체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_productfuts(data_cnt, recvstr[3])
|
||||
|
||||
elif data[0] == '1':
|
||||
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "H0IFCNI0": # 선물옵션체결 통보 처리
|
||||
print("#### 선물옵션 통보 처리 ####")
|
||||
stocksigningnotice_futsoptn(recvstr[3], aes_key, aes_iv)
|
||||
|
||||
else:
|
||||
|
||||
jsonObject = json.loads(data)
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
if trid != "PINGPONG":
|
||||
rt_cd = jsonObject["body"]["rt_cd"]
|
||||
|
||||
if rt_cd == '1': # 에러일 경우 처리
|
||||
|
||||
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
|
||||
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
break
|
||||
|
||||
elif rt_cd == '0': # 정상일 경우 처리
|
||||
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
|
||||
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
|
||||
if trid == "H0IFCNI0":
|
||||
aes_key = jsonObject["body"]["output"]["key"]
|
||||
aes_iv = jsonObject["body"]["output"]["iv"]
|
||||
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
|
||||
|
||||
elif trid == "PINGPONG":
|
||||
print("### RECV [PINGPONG] [%s]" % (data))
|
||||
await websocket.pong(data)
|
||||
print("### SEND [PINGPONG] [%s]" % (data))
|
||||
|
||||
# ----------------------------------------
|
||||
# 모든 함수의 공통 부분(Exception 처리)
|
||||
# ----------------------------------------
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
print('Connect Again!')
|
||||
time.sleep(0.1)
|
||||
|
||||
# 웹소켓 다시 시작
|
||||
await connect()
|
||||
|
||||
|
||||
# # 비동기로 서버에 접속한다.
|
||||
# asyncio.get_event_loop().run_until_complete(connect())
|
||||
# asyncio.get_event_loop().close()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# - Name : main
|
||||
# - Desc : 메인
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main():
|
||||
try:
|
||||
# 웹소켓 시작
|
||||
await connect()
|
||||
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# ---------------------------------------------------------------------
|
||||
# Logic Start!
|
||||
# ---------------------------------------------------------------------
|
||||
# 웹소켓 시작
|
||||
asyncio.run(main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("KeyboardInterrupt Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-100)
|
||||
|
||||
except Exception:
|
||||
print("Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-200)
|
||||
@@ -0,0 +1,397 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
### 모듈 임포트 ###
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import asyncio
|
||||
import traceback
|
||||
import websockets
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from base64 import b64decode
|
||||
|
||||
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
|
||||
|
||||
key_bytes = 32
|
||||
|
||||
|
||||
### 함수 정의 ###
|
||||
|
||||
# AES256 DECODE
|
||||
def aes_cbc_base64_dec(key, iv, cipher_text):
|
||||
"""
|
||||
:param key: str type AES256 secret key value
|
||||
:param iv: str type AES256 Initialize Vector
|
||||
:param cipher_text: Base64 encoded AES256 str
|
||||
:return: Base64-AES256 decodec str
|
||||
"""
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
|
||||
|
||||
|
||||
# 웹소켓 접속키 발급
|
||||
def get_approval(key, secret):
|
||||
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"secretkey": secret}
|
||||
PATH = "oauth2/Approval"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
approval_key = res.json()["approval_key"]
|
||||
return approval_key
|
||||
|
||||
# [필수] 유료 시세 수신을 위한 access_token 발급 함수
|
||||
# 해외주식/해외선물 유료 시세 수신 전 반드시 이 함수를 호출해 access_token을 발급받아야 함
|
||||
#
|
||||
# === 해외 유료 시세 수신 안내 ===
|
||||
# ▒ 해외주식 (HDFSASP0, HDFSASP1, HDFSCNT0: 미국, 중국, 일본, 베트남, 홍콩)
|
||||
# - 무료 시세: 별도 신청 없이 수신 가능
|
||||
# - 유료 시세: HTS 또는 MTS에서 신청 후 access_token 발급 필요
|
||||
# > HTS(eFriend Plus/Force): [7781] 시세신청(실시간)
|
||||
# > MTS(한국투자 앱): 고객지원 > 거래서비스 신청 > 해외증권 > 해외 실시간 시세 신청
|
||||
#
|
||||
# ▒ 해외선물 (HDFFF020, HDFFF010: CME, SGX / 기타 거래소는 무료 시세 제공)
|
||||
# - CME, SGX: 무료 시세 없음 → 유료 시세 신청 필수
|
||||
# - 유료 시세: HTS에서 신청 후 access_token 발급 필요
|
||||
# > HTS(eFriend Plus/Force): [7936] 해외선물옵션 실시간 시세신청/조회
|
||||
#
|
||||
# ▒ 유료 시세 수신 절차
|
||||
# 1. HTS 또는 MTS에서 유료 시세 신청
|
||||
# 2. get_access_token()으로 access_token 발급 (※ 신청 후에 발급해야 유효)
|
||||
# 3. 토큰 발급 시점 기준 최대 2시간 이내에 유료 권한 자동 반영
|
||||
# 4. 이후 웹소켓 연결 → 유료 시세 수신 가능
|
||||
def get_access_token(key, secret):
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"appsecret": secret}
|
||||
PATH = "oauth2/tokenP"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
access_token = res.json()["access_token"]
|
||||
return access_token
|
||||
|
||||
### 1. 국내주식 ###
|
||||
|
||||
# 국내주식호가 출력라이브러리
|
||||
def stockhoka_domestic(data):
|
||||
""" 넘겨받는데이터가 정상인지 확인
|
||||
print("stockhoka[%s]"%(data))
|
||||
"""
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("유가증권 단축 종목코드 [" + recvvalue[0] + "]")
|
||||
print("영업시간 [" + recvvalue[1] + "]" + "시간구분코드 [" + recvvalue[2] + "]")
|
||||
print("======================================")
|
||||
print("매도호가10 [%s] 잔량10 [%s]" % (recvvalue[12], recvvalue[32]))
|
||||
print("매도호가09 [%s] 잔량09 [%s]" % (recvvalue[11], recvvalue[31]))
|
||||
print("매도호가08 [%s] 잔량08 [%s]" % (recvvalue[10], recvvalue[30]))
|
||||
print("매도호가07 [%s] 잔량07 [%s]" % (recvvalue[9], recvvalue[29]))
|
||||
print("매도호가06 [%s] 잔량06 [%s]" % (recvvalue[8], recvvalue[28]))
|
||||
print("매도호가05 [%s] 잔량05 [%s]" % (recvvalue[7], recvvalue[27]))
|
||||
print("매도호가04 [%s] 잔량04 [%s]" % (recvvalue[6], recvvalue[26]))
|
||||
print("매도호가03 [%s] 잔량03 [%s]" % (recvvalue[5], recvvalue[25]))
|
||||
print("매도호가02 [%s] 잔량02 [%s]" % (recvvalue[4], recvvalue[24]))
|
||||
print("매도호가01 [%s] 잔량01 [%s]" % (recvvalue[3], recvvalue[23]))
|
||||
print("--------------------------------------")
|
||||
print("매수호가01 [%s] 잔량01 [%s]" % (recvvalue[13], recvvalue[33]))
|
||||
print("매수호가02 [%s] 잔량02 [%s]" % (recvvalue[14], recvvalue[34]))
|
||||
print("매수호가03 [%s] 잔량03 [%s]" % (recvvalue[15], recvvalue[35]))
|
||||
print("매수호가04 [%s] 잔량04 [%s]" % (recvvalue[16], recvvalue[36]))
|
||||
print("매수호가05 [%s] 잔량05 [%s]" % (recvvalue[17], recvvalue[37]))
|
||||
print("매수호가06 [%s] 잔량06 [%s]" % (recvvalue[18], recvvalue[38]))
|
||||
print("매수호가07 [%s] 잔량07 [%s]" % (recvvalue[19], recvvalue[39]))
|
||||
print("매수호가08 [%s] 잔량08 [%s]" % (recvvalue[20], recvvalue[40]))
|
||||
print("매수호가09 [%s] 잔량09 [%s]" % (recvvalue[21], recvvalue[41]))
|
||||
print("매수호가10 [%s] 잔량10 [%s]" % (recvvalue[22], recvvalue[42]))
|
||||
print("======================================")
|
||||
print("총매도호가 잔량 [%s]" % (recvvalue[43]))
|
||||
print("총매도호가 잔량 증감 [%s]" % (recvvalue[54]))
|
||||
print("총매수호가 잔량 [%s]" % (recvvalue[44]))
|
||||
print("총매수호가 잔량 증감 [%s]" % (recvvalue[55]))
|
||||
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[45]))
|
||||
print("시간외 총매수호가 증감 [%s]" % (recvvalue[46]))
|
||||
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[56]))
|
||||
print("시간외 총매수호가 증감 [%s]" % (recvvalue[57]))
|
||||
print("예상 체결가 [%s]" % (recvvalue[47]))
|
||||
print("예상 체결량 [%s]" % (recvvalue[48]))
|
||||
print("예상 거래량 [%s]" % (recvvalue[49]))
|
||||
print("예상체결 대비 [%s]" % (recvvalue[50]))
|
||||
print("부호 [%s]" % (recvvalue[51]))
|
||||
print("예상체결 전일대비율 [%s]" % (recvvalue[52]))
|
||||
print("누적거래량 [%s]" % (recvvalue[53]))
|
||||
print("주식매매 구분코드 [%s]" % (recvvalue[58]))
|
||||
|
||||
|
||||
# 국내주식체결처리 출력라이브러리
|
||||
def stockspurchase_domestic(data_cnt, data):
|
||||
print("============================================")
|
||||
menulist = "유가증권단축종목코드|주식체결시간|주식현재가|전일대비부호|전일대비|전일대비율|가중평균주식가격|주식시가|주식최고가|주식최저가|매도호가1|매수호가1|체결거래량|누적거래량|누적거래대금|매도체결건수|매수체결건수|순매수체결건수|체결강도|총매도수량|총매수수량|체결구분|매수비율|전일거래량대비등락율|시가시간|시가대비구분|시가대비|최고가시간|고가대비구분|고가대비|최저가시간|저가대비구분|저가대비|영업일자|신장운영구분코드|거래정지여부|매도호가잔량|매수호가잔량|총매도호가잔량|총매수호가잔량|거래량회전율|전일동시간누적거래량|전일동시간누적거래량비율|시간구분코드|임의종료구분코드|정적VI발동기준가"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 국내주식체결통보 출력라이브러리
|
||||
def stocksigningnotice_domestic(data, key, iv):
|
||||
|
||||
# AES256 처리 단계
|
||||
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
|
||||
pValue = aes_dec_str.split('^')
|
||||
|
||||
if pValue[13] == '2': # 체결통보
|
||||
print("#### 국내주식 체결 통보 ####")
|
||||
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|체결수량|체결단가|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|주문가격"
|
||||
menustr1 = menulist.split('|')
|
||||
else:
|
||||
print("#### 국내주식 주문·정정·취소·거부 접수 통보 ####")
|
||||
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|주문수량|주문가격|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|체결단가"
|
||||
menustr1 = menulist.split('|')
|
||||
|
||||
i = 0
|
||||
for menu in menustr1:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
### 2. 해외주식 ###
|
||||
|
||||
# 해외주식호가 출력라이브러리
|
||||
def stockhoka_overseas(data):
|
||||
""" 넘겨받는데이터가 정상인지 확인
|
||||
print("stockhoka[%s]"%(data))
|
||||
"""
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("실시간종목코드 [" + recvvalue[0] + "]" + ", 종목코드 [" + recvvalue[1] + "]")
|
||||
print("소숫점자리수 [" + recvvalue[2] + "]")
|
||||
print("현지일자 [" + recvvalue[3] + "]" + ", 현지시간 [" + recvvalue[4] + "]")
|
||||
print("한국일자 [" + recvvalue[5] + "]" + ", 한국시간 [" + recvvalue[6] + "]")
|
||||
print("======================================")
|
||||
print("매수총 잔량 [%s]" % (recvvalue[7]))
|
||||
print("매수총잔량대비 [%s]" % (recvvalue[9]))
|
||||
print("매도총 잔량 [%s]" % (recvvalue[8]))
|
||||
print("매도총잔략대비 [%s]" % (recvvalue[10]))
|
||||
print("매수호가 [%s]" % (recvvalue[11]))
|
||||
print("매도호가 [%s]" % (recvvalue[12]))
|
||||
print("매수잔량 [%s]" % (recvvalue[13]))
|
||||
print("매도잔량 [%s]" % (recvvalue[14]))
|
||||
print("매수잔량대비 [%s]" % (recvvalue[15]))
|
||||
print("매도잔량대비 [%s]" % (recvvalue[16]))
|
||||
|
||||
|
||||
# 해외주식체결처리 출력라이브러리
|
||||
def stockspurchase_overseas(data_cnt, data):
|
||||
print("============================================")
|
||||
menulist = "실시간종목코드|종목코드|수수점자리수|현지영업일자|현지일자|현지시간|한국일자|한국시간|시가|고가|저가|현재가|대비구분|전일대비|등락율|매수호가|매도호가|매수잔량|매도잔량|체결량|거래량|거래대금|매도체결량|매수체결량|체결강도|시장구분"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
|
||||
# 해외주식체결통보 출력라이브러리
|
||||
def stocksigningnotice_overseas(data, key, iv):
|
||||
|
||||
# AES256 처리 단계
|
||||
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
|
||||
pValue = aes_dec_str.split('^')
|
||||
|
||||
if pValue[12] == '2': # 체결통보
|
||||
print("#### 해외주식 체결 통보 ####")
|
||||
menulist = "고객 ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류2|단축종목코드|체결수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|체결종목명|해외종목구분|담보유형코드|담보대출일자|분할매수매도시작시간|분할매수매도종료시간|시간분할타입유형|체결단가12"
|
||||
menustr1 = menulist.split('|')
|
||||
|
||||
else:
|
||||
print("#### 해외주식 주문·정정·취소·거부 접수 통보 ####")
|
||||
menulist = "고객 ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류2|단축종목코드|주문수량|주문단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량_미출력|계좌명|체결종목명|해외종목구분|담보유형코드|담보대출일자|분할매수매도시작시간|분할매수매도종료시간|시간분할타입유형|체결단가12"
|
||||
menustr1 = menulist.split('|')
|
||||
|
||||
i = 0
|
||||
for menu in menustr1:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
### 웹소켓 연결 ###
|
||||
|
||||
async def connect():
|
||||
try:
|
||||
|
||||
g_appkey = "앱키를 입력하세요"
|
||||
g_appsecret = "앱 시크릿키를 입력하세요"
|
||||
|
||||
# 해외주식/해외선물(CME, SGX) 유료시세 사용 시 필수(2시간 이내 유료신청정보 동기화)
|
||||
# access_token = get_access_token(appkey, appsecret)
|
||||
|
||||
g_approval_key = get_approval(g_appkey, g_appsecret)
|
||||
print("approval_key [%s]" % (g_approval_key))
|
||||
|
||||
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
|
||||
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
|
||||
|
||||
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기 # 모의투자 국내주식 체결통보: H0STCNI9
|
||||
code_list = [['1','H0STASP0','005930'],['1','H0STCNT0','005930'],['1','H0STCNI0','HTS ID를 입력하세요'],
|
||||
['1','H0STASP0','DNASAAPL'],['1','HDFSCNT0','DNASAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요']]
|
||||
code_list = [['1','H0STASP0','005930'],['1','H0STASP0','RBAQAAPL']]
|
||||
code_list = [['1','H0STASP0','005930'],['1','H0STCNT0','005930'],['1','H0STCNI0','HTS ID를 입력하세요'],
|
||||
['1','H0STASP0','DNASAAPL'],['1','HDFSCNT0','DNASAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요']]
|
||||
|
||||
senddata_list=[]
|
||||
|
||||
for i,j,k in code_list:
|
||||
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
|
||||
senddata_list.append(temp)
|
||||
|
||||
|
||||
async with websockets.connect(url, ping_interval=None) as websocket:
|
||||
|
||||
for senddata in senddata_list:
|
||||
await websocket.send(senddata)
|
||||
await asyncio.sleep(0.5)
|
||||
print(f"Input Command is :{senddata}")
|
||||
|
||||
while True:
|
||||
|
||||
data = await websocket.recv()
|
||||
# await asyncio.sleep(0.5)
|
||||
# print(f"Recev Command is :{data}")
|
||||
|
||||
if data[0] == '0':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "H0STASP0": # 주식호가tr 일경우의 처리 단계
|
||||
print("#### 주식호가 ####")
|
||||
stockhoka_domestic(recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "H0STCNT0": # 주식체결 데이터 처리
|
||||
print("#### 주식체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_domestic(data_cnt, recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "HDFSASP1": # 해외주식호가tr 일경우의 처리 단계
|
||||
print("#### 해외주식호가 ####")
|
||||
stockhoka_overseas(recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "HDFSCNT0": # 주식체결 데이터 처리
|
||||
print("#### 해외주식체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_overseas(data_cnt, recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif data[0] == '1':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "H0STCNI0" or trid0 == "H0STCNI9": # 주실체결 통보 처리
|
||||
stocksigningnotice_domestic(recvstr[3], aes_key, aes_iv)
|
||||
|
||||
elif trid0 == "H0GSCNI0" or trid0 == "H0GSCNI9": # 해외주실체결 통보 처리
|
||||
stocksigningnotice_overseas(recvstr[3], aes_key, aes_iv)
|
||||
|
||||
else:
|
||||
|
||||
jsonObject = json.loads(data)
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
if trid != "PINGPONG":
|
||||
rt_cd = jsonObject["body"]["rt_cd"]
|
||||
|
||||
if rt_cd == '1': # 에러일 경우 처리
|
||||
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
|
||||
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
break
|
||||
|
||||
elif rt_cd == '0': # 정상일 경우 처리
|
||||
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
|
||||
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
|
||||
if trid == "H0STCNI0" or trid == "H0STCNI9":
|
||||
aes_key = jsonObject["body"]["output"]["key"]
|
||||
aes_iv = jsonObject["body"]["output"]["iv"]
|
||||
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
|
||||
|
||||
elif trid == "H0GSCNI0" or trid == "H0GSCNI9":
|
||||
aes_key = jsonObject["body"]["output"]["key"]
|
||||
aes_iv = jsonObject["body"]["output"]["iv"]
|
||||
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
|
||||
|
||||
elif trid == "PINGPONG":
|
||||
print("### RECV [PINGPONG] [%s]" % (data))
|
||||
await websocket.pong(data)
|
||||
print("### SEND [PINGPONG] [%s]" % (data))
|
||||
|
||||
# ----------------------------------------
|
||||
# 모든 함수의 공통 부분(Exception 처리)
|
||||
# ----------------------------------------
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
print('Connect Again!')
|
||||
time.sleep(0.1)
|
||||
|
||||
# 웹소켓 다시 시작
|
||||
await connect()
|
||||
|
||||
|
||||
# # 비동기로 서버에 접속한다.
|
||||
# asyncio.get_event_loop().run_until_complete(connect())
|
||||
# asyncio.get_event_loop().close()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# - Name : main
|
||||
# - Desc : 메인
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main():
|
||||
try:
|
||||
# 웹소켓 시작
|
||||
await connect()
|
||||
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# ---------------------------------------------------------------------
|
||||
# Logic Start!
|
||||
# ---------------------------------------------------------------------
|
||||
# 웹소켓 시작
|
||||
asyncio.run(main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("KeyboardInterrupt Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-100)
|
||||
|
||||
except Exception:
|
||||
print("Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-200)
|
||||
633
한국투자증권(API)/legacy/websocket/python/ws_domestic_future.py
Normal file
633
한국투자증권(API)/legacy/websocket/python/ws_domestic_future.py
Normal file
@@ -0,0 +1,633 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
### 모듈 임포트 ###
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import asyncio
|
||||
import traceback
|
||||
import websockets
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from base64 import b64decode
|
||||
|
||||
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
|
||||
|
||||
key_bytes = 32
|
||||
|
||||
|
||||
### 함수 정의 ###
|
||||
|
||||
# AES256 DECODE
|
||||
def aes_cbc_base64_dec(key, iv, cipher_text):
|
||||
"""
|
||||
:param key: str type AES256 secret key value
|
||||
:param iv: str type AES256 Initialize Vector
|
||||
:param cipher_text: Base64 encoded AES256 str
|
||||
:return: Base64-AES256 decodec str
|
||||
"""
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
|
||||
|
||||
|
||||
# 웹소켓 접속키 발급
|
||||
def get_approval(key, secret):
|
||||
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"secretkey": secret}
|
||||
PATH = "oauth2/Approval"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
approval_key = res.json()["approval_key"]
|
||||
return approval_key
|
||||
|
||||
|
||||
# 지수선물호가 출력라이브러리
|
||||
def stockhoka_futs(data):
|
||||
|
||||
# print(data)
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("지수선물 ["+recvvalue[ 0]+"]")
|
||||
print("영업시간 ["+recvvalue[ 1]+"]")
|
||||
print("====================================")
|
||||
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
|
||||
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
|
||||
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
|
||||
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
|
||||
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
|
||||
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
|
||||
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
|
||||
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
|
||||
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
|
||||
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
|
||||
print("====================================")
|
||||
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
|
||||
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
|
||||
|
||||
|
||||
# 지수옵션호가 출력라이브러리
|
||||
def stockhoka_optn(data):
|
||||
# print(data)
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("지수옵션 ["+recvvalue[ 0]+"]")
|
||||
print("영업시간 ["+recvvalue[ 1]+"]")
|
||||
print("====================================")
|
||||
print("옵션매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
|
||||
print("옵션매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
|
||||
print("옵션매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
|
||||
print("옵션매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
|
||||
print("옵션매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
|
||||
print("옵션매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
|
||||
print("옵션매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
|
||||
print("옵션매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
|
||||
print("옵션매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
|
||||
print("옵션매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
|
||||
print("====================================")
|
||||
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
|
||||
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
|
||||
|
||||
|
||||
# 지수선물체결처리 출력라이브러리
|
||||
def stockspurchase_futs(data_cnt, data):
|
||||
print("============================================")
|
||||
# print(data)
|
||||
menulist = "선물단축종목코드|영업시간|선물전일대비|전일대비부호|선물전일대비율|선물현재가|선물시가|선물최고가|선물최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드|미결제약정수량|미결제약정수량증감|시가시간|시가대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|선물매도호가|선물매수호가|매도호가잔량|매수호가잔량|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|협의대량거래량|실시간상한가|실시간하한가|실시간가격제한구분"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 지수옵션체결처리 출력라이브러리
|
||||
def stockspurchase_optn(data_cnt, data):
|
||||
print("============================================")
|
||||
# print(data)
|
||||
menulist = "옵션단축종목코드|영업시간|옵션현재가|전일대비부호|옵션전일대비|전일대비율|옵션시가|옵션최고가|옵션최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|HTS미결제약정수량|미결제약정수량증감|시가시간|시가대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수2비율|프리미엄값|내재가치값|시간가치값|델타|감마|베가|세타|로우|HTS내재변동성|괴리도|미결제약정직전수량증감|이론베이시스|역사적변동성|체결강도|괴리율|시장베이시스|옵션매도호가1|옵션매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|평균변동성|협의대량누적거래량|실시간상한가|실시간하한가|실시간가격제한구분"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 상품선물호가 출력라이브러리
|
||||
def stockhoka_productfuts(data):
|
||||
|
||||
# print(data)
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("상품선물 ["+recvvalue[ 0]+"]")
|
||||
print("영업시간 ["+recvvalue[ 1]+"]")
|
||||
print("====================================")
|
||||
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
|
||||
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
|
||||
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
|
||||
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
|
||||
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
|
||||
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
|
||||
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
|
||||
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
|
||||
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
|
||||
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
|
||||
print("====================================")
|
||||
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
|
||||
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
|
||||
|
||||
|
||||
# 상품선물체결처리 출력라이브러리
|
||||
def stockspurchase_productfuts(data_cnt, data):
|
||||
print("============================================")
|
||||
# print(data)
|
||||
menulist = "선물단축종목코드|영업시간|선물전일대비|전일대비부호|선물전일대비율|선물현재가|선물시가|선물최고가|선물최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드|미결제약정수량|미결제약정수량증감|시가시간|시가대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|선물매도호가|선물매수호가|매도호가잔량|매수호가잔량|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|협의대량거래량|실시간상한가|실시간하한가|실시간가격제한구분"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 주식선물호가 출력라이브러리
|
||||
def stockhoka_stockfuts(data):
|
||||
|
||||
# print(data)
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("주식선물 ["+recvvalue[ 0]+"]")
|
||||
print("영업시간 ["+recvvalue[ 1]+"]")
|
||||
print("====================================")
|
||||
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
|
||||
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
|
||||
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
|
||||
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
|
||||
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
|
||||
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
|
||||
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
|
||||
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
|
||||
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
|
||||
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
|
||||
print("====================================")
|
||||
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
|
||||
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
|
||||
|
||||
|
||||
# 주식선물체결처리 출력라이브러리
|
||||
def stockspurchase_stockfuts(data_cnt, data):
|
||||
print("============================================")
|
||||
print(data)
|
||||
menulist = "선물단축종목코드|영업시간|주식현재가|전일대비부호|전일대비|선물전일대비율|주식시가2|주식최고가|주식최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드1|HTS미결제약정수량|미결제약정수량증감|시가시간|시가2대비현재가부호|시가2대비현재가|최고가시간|최고가대비현재가부호|최고가대비현재가|최저가시간|최저가대비현재가부호|최저가대비현재가|매수2비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|매도호가1|매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|실시간상한가|실시간하한가|실시간가격제한구분"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 주식옵션호가 출력라이브러리
|
||||
def stockhoka_stockoptn(data):
|
||||
|
||||
# print(data)
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("주식옵션 ["+recvvalue[ 0]+"]")
|
||||
print("영업시간 ["+recvvalue[ 1]+"]")
|
||||
print("====================================")
|
||||
print("옵션매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
|
||||
print("옵션매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
|
||||
print("옵션매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
|
||||
print("옵션매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
|
||||
print("옵션매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
|
||||
print("옵션매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
|
||||
print("옵션매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
|
||||
print("옵션매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
|
||||
print("옵션매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
|
||||
print("옵션매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
|
||||
print("====================================")
|
||||
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
|
||||
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
|
||||
|
||||
|
||||
# 주식옵션체결처리 출력라이브러리
|
||||
def stockspurchase_stockoptn(data_cnt, data):
|
||||
print("============================================")
|
||||
# print(data)
|
||||
menulist = "옵션단축종목코드|영업시간|옵션현재가|전일대비부호|옵션전일대비|전일대비율|옵션시가2|옵션최고가|옵션최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|HTS미결제약정수량|미결제약정수량증감|시가시간|시가2대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수2비율|프리미엄값|내재가치값|시간가치값|델타|감마|베가|세타|로우|HTS내재변동성|괴리도|미결제약정직전수량증감|이론베이시스|역사적변동성|체결강도|괴리율|시장베이시스|옵션매도호가1|옵션매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
# 야간선물(CME)체결처리 출력라이브러리
|
||||
def stockspurchase_cmefuts(data_cnt, data):
|
||||
print("============================================")
|
||||
print(data)
|
||||
menulist = "선물단축종목코드|영업시간|선물전일대비|전일대비부호|선물전일대비율|선물현재가|선물시가2|선물최고가|선물최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드1|HTS미결제약정수량|미결제약정수량증감|시가시간|시가2대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수2비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|선물매도호가1|선물매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
# 야간선물(CME)호가 출력라이브러리
|
||||
def stockhoka_cmefuts(data):
|
||||
# print(data)
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("야간선물(CME) ["+recvvalue[ 0]+"]")
|
||||
print("영업시간 ["+recvvalue[ 1]+"]")
|
||||
print("====================================")
|
||||
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
|
||||
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
|
||||
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
|
||||
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
|
||||
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
|
||||
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
|
||||
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
|
||||
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
|
||||
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
|
||||
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
|
||||
print("====================================")
|
||||
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
|
||||
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
|
||||
|
||||
|
||||
# 야간옵션(EUREX)체결처리 출력라이브러리
|
||||
def stockspurchase_eurexoptn(data_cnt, data):
|
||||
print("============================================")
|
||||
print(data)
|
||||
menulist = "옵션단축종목코드|영업시간|옵션현재가|전일대비부호|옵션전일대비|전일대비율|옵션시가2|옵션최고가|옵션최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|HTS미결제약정수량|미결제약정수량증감|시가시간|시가2대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수2비율|프리미엄값|내재가치값|시간가치값|델타|감마|베가|세타|로우|HTS내재변동성|괴리도|미결제약정직전수량증감|이론베이시스|역사적변동성|체결강도|괴리율|시장베이시스|옵션매도호가1|옵션매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
# 야간옵션(EUREX)호가 출력라이브러리
|
||||
def stockhoka_eurexoptn(data):
|
||||
# print(data)
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("야간옵션(EUREX) ["+recvvalue[ 0]+"]")
|
||||
print("영업시간 ["+recvvalue[ 1]+"]")
|
||||
print("====================================")
|
||||
print("옵션매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
|
||||
print("옵션매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
|
||||
print("옵션매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
|
||||
print("옵션매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
|
||||
print("옵션매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
|
||||
print("옵션매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
|
||||
print("옵션매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
|
||||
print("옵션매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
|
||||
print("옵션매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
|
||||
print("옵션매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
|
||||
print("====================================")
|
||||
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
|
||||
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
|
||||
|
||||
# 야간옵션(EUREX)예상체결처리 출력라이브러리
|
||||
def stocksexppurchase_eurexoptn(data_cnt, data):
|
||||
print("============================================")
|
||||
print(data)
|
||||
menulist = "옵션단축종목코드|영업시간|예상체결가|예상체결대비|예상체결대비부호|예상체결전일대비율|예상장운영구분코드"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 선물옵션 체결통보 출력라이브러리
|
||||
def stocksigningnotice_futsoptn(data, key, iv):
|
||||
|
||||
# AES256 처리 단계
|
||||
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
|
||||
print(aes_dec_str)
|
||||
pValue = aes_dec_str.split('^')
|
||||
print(pValue)
|
||||
|
||||
if pValue[6] == '0': # 체결통보
|
||||
print("#### 지수선물옵션 체결 통보 ####")
|
||||
menulist_sign = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|체결수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_sign.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
else: # pValue[6] == 'L', 주문·정정·취소·거부 접수 통보
|
||||
|
||||
if pValue[5] == '1': # 정정 접수 통보 (정정구분이 1일 경우)
|
||||
print("#### 선물옵션 정정 접수 통보 ####")
|
||||
menulist_revise = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|정정수량|정정단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_revise.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
elif pValue[5] == '2': # 취소 접수 통보 (정정구분이 2일 경우)
|
||||
print("#### 선물옵션 취소 접수 통보 ####")
|
||||
menulist_cancel = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|취소수량|주문단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_cancel.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
elif pValue[11] == '1': # 거부 접수 통보 (거부여부가 1일 경우)
|
||||
print("#### 선물옵션 거부 접수 통보 ####")
|
||||
menulist_refuse = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|주문수량|주문단가|주문시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_refuse.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
else: # 주문 접수 통보
|
||||
print("#### 선물옵션 주문접수 통보 ####")
|
||||
menulist_order = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|주문수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
|
||||
menustr = menulist_order.split('|')
|
||||
i = 0
|
||||
for menu in menustr:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
### 앱키 정의 ###
|
||||
|
||||
async def connect():
|
||||
try:
|
||||
g_appkey = "앱키를 입력하세요"
|
||||
g_appsecret = "앱 시크릿키를 입력하세요"
|
||||
|
||||
g_approval_key = get_approval(g_appkey, g_appsecret)
|
||||
print("approval_key [%s]" % (g_approval_key))
|
||||
|
||||
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
|
||||
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
|
||||
|
||||
### 3-1. 국내 지수선물옵션 호가, 체결가, 체결통보 ### # 모의투자 선물옵션 체결통보: H0IFCNI9
|
||||
# code_list = [['1','H0IFASP0','101T12'],['1','H0IFCNT0','101T12'], # 지수선물호가, 체결가
|
||||
# ['1','H0IOASP0','201T11317'],['1','H0IOCNT0','201T11317'], # 지수옵션호가, 체결가
|
||||
# ['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
|
||||
|
||||
### 3-2. 국내 상품선물 호가, 체결가, 체결통보 ###
|
||||
# code_list = [['1','H0CFASP0','175T11'],['1','H0CFCNT0','175T11'], # 상품선물호가, 체결가
|
||||
# ['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
|
||||
|
||||
### 3-3. 국내 주식선물옵션 호가, 체결가, 체결통보 ###
|
||||
# code_list = [['1', 'H0ZFCNT0', '111V06'], ['1', 'H0ZFASP0', '111V06'],['1', 'H0ZFANC0', '111V06'], # 주식선물호가, 체결가, 예상체결
|
||||
# ['1', 'H0ZOCNT0', '211V05059'], ['1', 'H0ZOASP0', '211V05059'], ['1', 'H0ZOANC0', '211V05059'], # 주식옵션호가, 체결가, 예상체결
|
||||
# ['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
|
||||
|
||||
### 3-4. 국내 야간옵션(EUREX) 호가, 체결가, 예상체결, 체결통보 ###
|
||||
# code_list = [['1', 'H0EUCNT0', '101V06'], ['1', 'H0EUASP0', '101V06'], ['1', 'H0EUANC0', '101V06'], ['1', 'H0EUCNI0', 'HTS ID를 입력하세요']]
|
||||
|
||||
### 3-5. 국내 야간선물(CME) 호가, 체결가, 체결통보 ###
|
||||
# code_list = [['1', 'H0MFCNT0', '101V06'], ['1', 'H0MFASP0', '101V06'], ['1', 'H0MFCNI0', 'HTS ID를 입력하세요']]
|
||||
|
||||
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기
|
||||
code_list = [['1','H0IFASP0','101T12'],['1','H0IFCNT0','101T12'], # 지수선물호가, 체결가
|
||||
['1','H0IOASP0','201T11317'],['1','H0IOCNT0','201T11317'], # 지수옵션호가, 체결가
|
||||
['1','H0CFASP0','175V06'],['1','H0CFCNT0','175V06'], # 상품선물호가, 체결가
|
||||
['1', 'H0ZFCNT0', '111V06'], ['1', 'H0ZFASP0', '111V06'], # 주식선물 체결, 호가
|
||||
['1', 'H0ZOCNT0', '211V05059'], ['1', 'H0ZOASP0', '211V05059'], # 주식옵션 체결, 호가
|
||||
['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
|
||||
|
||||
senddata_list=[]
|
||||
|
||||
for i,j,k in code_list:
|
||||
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
|
||||
senddata_list.append(temp)
|
||||
|
||||
async with websockets.connect(url, ping_interval=None) as websocket:
|
||||
|
||||
for senddata in senddata_list:
|
||||
await websocket.send(senddata)
|
||||
await asyncio.sleep(0.5)
|
||||
print(f"Input Command is :{senddata}")
|
||||
|
||||
while True:
|
||||
|
||||
data = await websocket.recv()
|
||||
# await asyncio.sleep(0.5)
|
||||
# print(f"Recev Command is :{data}") # 정제되지 않은 Request / Response 출력
|
||||
|
||||
if data[0] == '0':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "H0IFASP0": # 지수선물호가 tr 일경우의 처리 단계
|
||||
print("#### 지수선물 호가 ####")
|
||||
stockhoka_futs(recvstr[3])
|
||||
# await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "H0IFCNT0": # 지수선물체결 데이터 처리
|
||||
print("#### 지수선물 체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_futs(data_cnt, recvstr[3])
|
||||
# await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "H0IOASP0": # 지수옵션호가 tr 일경우의 처리 단계
|
||||
print("#### 지수옵션 호가 ####")
|
||||
stockhoka_optn(recvstr[3])
|
||||
# await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "H0IOCNT0": # 지수옵션체결 데이터 처리
|
||||
print("#### 지수옵션 체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_optn(data_cnt, recvstr[3])
|
||||
# await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "H0CFASP0": # 상품선물호가 tr 일경우의 처리 단계
|
||||
print("#### 상품선물 호가 ####")
|
||||
stockhoka_productfuts(recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0CFCNT0": # 상품선물체결 데이터 처리
|
||||
print("#### 상품선물 체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_productfuts(data_cnt, recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0ZFCNT0": # 주식선물 체결 데이터 처리
|
||||
print("#### 주식선물 체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_stockfuts(data_cnt, recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0ZFASP0": # 주식선물 호가 데이터 처리
|
||||
print("#### 주식선물 호가 ####")
|
||||
stockhoka_stockfuts(recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0ZFANC0": # 주식선물 예상체결 데이터 처리
|
||||
print("#### 주식선물 예상체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stocksexppurchase_stockfuts(data_cnt, recvstr[3])
|
||||
|
||||
elif trid0 == "H0ZOCNT0": # 주식옵션 체결 데이터 처리
|
||||
print("#### 주식옵션 체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_stockoptn(data_cnt, recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0ZOASP0": # 주식옵션 호가 데이터 처리
|
||||
print("#### 주식옵션 호가 ####")
|
||||
stockhoka_stockoptn(recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0ZOANC0": # 주식옵션 예상체결 데이터 처리
|
||||
print("#### 주식옵션 예상체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stocksexppurchase_stockoptn(data_cnt, recvstr[3])
|
||||
|
||||
|
||||
elif trid0 == "H0MFCNT0": # 야간선물(CME) 체결 데이터 처리
|
||||
print("#### 야간선물(CME) 체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_cmefuts(data_cnt, recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0MFASP0": # 야간선물(CME) 호가 데이터 처리
|
||||
print("#### 야간선물(CME) 호가 ####")
|
||||
stockhoka_cmefuts(recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0EUCNT0": # 야간옵션(EUREX) 체결 데이터 처리
|
||||
print("#### 야간옵션(EUREX) 체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_eurexoptn(data_cnt, recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0EUASP0": # 야간옵션(EUREX) 호가 데이터 처리
|
||||
print("#### 야간옵션(EUREX) 호가 ####")
|
||||
stockhoka_eurexoptn(recvstr[3])
|
||||
# await asyncio.sleep(0.2)
|
||||
|
||||
elif trid0 == "H0EUANC0": # 야간옵션(EUREX) 예상체결 데이터 처리
|
||||
print("#### 야간옵션(EUREX) 예상체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stocksexppurchase_eurexoptn(data_cnt, recvstr[3])
|
||||
|
||||
|
||||
elif data[0] == '1':
|
||||
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "H0IFCNI0": # 지수선물옵션체결 통보 처리
|
||||
stocksigningnotice_futsoptn(recvstr[3], aes_key, aes_iv)
|
||||
|
||||
else:
|
||||
|
||||
jsonObject = json.loads(data)
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
if trid != "PINGPONG":
|
||||
rt_cd = jsonObject["body"]["rt_cd"]
|
||||
|
||||
if rt_cd == '1': # 에러일 경우 처리
|
||||
|
||||
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
|
||||
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
break
|
||||
|
||||
elif rt_cd == '0': # 정상일 경우 처리
|
||||
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
|
||||
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
|
||||
if trid == "H0IFCNI0" or trid == "H0MFCNI0" or trid == "H0EUCNI0": # 지수/상품/주식 선물옵션 & 야간선물옵션
|
||||
aes_key = jsonObject["body"]["output"]["key"]
|
||||
aes_iv = jsonObject["body"]["output"]["iv"]
|
||||
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
|
||||
|
||||
elif trid == "PINGPONG":
|
||||
print("### RECV [PINGPONG] [%s]" % (data))
|
||||
await websocket.pong(data)
|
||||
print("### SEND [PINGPONG] [%s]" % (data))
|
||||
|
||||
# ----------------------------------------
|
||||
# 모든 함수의 공통 부분(Exception 처리)
|
||||
# ----------------------------------------
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
print('Connect Again!')
|
||||
time.sleep(0.1)
|
||||
|
||||
# 웹소켓 다시 시작
|
||||
await connect()
|
||||
|
||||
|
||||
# # 비동기로 서버에 접속한다.
|
||||
# asyncio.get_event_loop().run_until_complete(connect())
|
||||
# asyncio.get_event_loop().close()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# - Name : main
|
||||
# - Desc : 메인
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main():
|
||||
try:
|
||||
# 웹소켓 시작
|
||||
await connect()
|
||||
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# ---------------------------------------------------------------------
|
||||
# Logic Start!
|
||||
# ---------------------------------------------------------------------
|
||||
# 웹소켓 시작
|
||||
asyncio.run(main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("KeyboardInterrupt Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-100)
|
||||
|
||||
except Exception:
|
||||
print("Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-200)
|
||||
1508
한국투자증권(API)/legacy/websocket/python/ws_domestic_overseas_all.py
Normal file
1508
한국투자증권(API)/legacy/websocket/python/ws_domestic_overseas_all.py
Normal file
File diff suppressed because it is too large
Load Diff
312
한국투자증권(API)/legacy/websocket/python/ws_domestic_stock.py
Normal file
312
한국투자증권(API)/legacy/websocket/python/ws_domestic_stock.py
Normal file
@@ -0,0 +1,312 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
### 모듈 임포트 ###
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import asyncio
|
||||
import traceback
|
||||
import websockets
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from base64 import b64decode
|
||||
|
||||
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
|
||||
|
||||
key_bytes = 32
|
||||
|
||||
|
||||
# AES256 DECODE
|
||||
def aes_cbc_base64_dec(key, iv, cipher_text):
|
||||
"""
|
||||
:param key: str type AES256 secret key value
|
||||
:param iv: str type AES256 Initialize Vector
|
||||
:param cipher_text: Base64 encoded AES256 str
|
||||
:return: Base64-AES256 decodec str
|
||||
"""
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
|
||||
|
||||
|
||||
# 웹소켓 접속키 발급
|
||||
def get_approval(key, secret):
|
||||
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"secretkey": secret}
|
||||
PATH = "oauth2/Approval"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
approval_key = res.json()["approval_key"]
|
||||
return approval_key
|
||||
|
||||
|
||||
# 국내주식호가 출력라이브러리
|
||||
def stockhoka(data):
|
||||
""" 넘겨받는데이터가 정상인지 확인
|
||||
print("stockhoka[%s]"%(data))
|
||||
"""
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("유가증권 단축 종목코드 [" + recvvalue[0] + "]")
|
||||
print("영업시간 [" + recvvalue[1] + "]" + "시간구분코드 [" + recvvalue[2] + "]")
|
||||
print("======================================")
|
||||
print("매도호가10 [%s] 잔량10 [%s]" % (recvvalue[12], recvvalue[32]))
|
||||
print("매도호가09 [%s] 잔량09 [%s]" % (recvvalue[11], recvvalue[31]))
|
||||
print("매도호가08 [%s] 잔량08 [%s]" % (recvvalue[10], recvvalue[30]))
|
||||
print("매도호가07 [%s] 잔량07 [%s]" % (recvvalue[9], recvvalue[29]))
|
||||
print("매도호가06 [%s] 잔량06 [%s]" % (recvvalue[8], recvvalue[28]))
|
||||
print("매도호가05 [%s] 잔량05 [%s]" % (recvvalue[7], recvvalue[27]))
|
||||
print("매도호가04 [%s] 잔량04 [%s]" % (recvvalue[6], recvvalue[26]))
|
||||
print("매도호가03 [%s] 잔량03 [%s]" % (recvvalue[5], recvvalue[25]))
|
||||
print("매도호가02 [%s] 잔량02 [%s]" % (recvvalue[4], recvvalue[24]))
|
||||
print("매도호가01 [%s] 잔량01 [%s]" % (recvvalue[3], recvvalue[23]))
|
||||
print("--------------------------------------")
|
||||
print("매수호가01 [%s] 잔량01 [%s]" % (recvvalue[13], recvvalue[33]))
|
||||
print("매수호가02 [%s] 잔량02 [%s]" % (recvvalue[14], recvvalue[34]))
|
||||
print("매수호가03 [%s] 잔량03 [%s]" % (recvvalue[15], recvvalue[35]))
|
||||
print("매수호가04 [%s] 잔량04 [%s]" % (recvvalue[16], recvvalue[36]))
|
||||
print("매수호가05 [%s] 잔량05 [%s]" % (recvvalue[17], recvvalue[37]))
|
||||
print("매수호가06 [%s] 잔량06 [%s]" % (recvvalue[18], recvvalue[38]))
|
||||
print("매수호가07 [%s] 잔량07 [%s]" % (recvvalue[19], recvvalue[39]))
|
||||
print("매수호가08 [%s] 잔량08 [%s]" % (recvvalue[20], recvvalue[40]))
|
||||
print("매수호가09 [%s] 잔량09 [%s]" % (recvvalue[21], recvvalue[41]))
|
||||
print("매수호가10 [%s] 잔량10 [%s]" % (recvvalue[22], recvvalue[42]))
|
||||
print("======================================")
|
||||
print("총매도호가 잔량 [%s]" % (recvvalue[43]))
|
||||
print("총매도호가 잔량 증감 [%s]" % (recvvalue[54]))
|
||||
print("총매수호가 잔량 [%s]" % (recvvalue[44]))
|
||||
print("총매수호가 잔량 증감 [%s]" % (recvvalue[55]))
|
||||
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[45]))
|
||||
print("시간외 총매수호가 증감 [%s]" % (recvvalue[46]))
|
||||
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[56]))
|
||||
print("시간외 총매수호가 증감 [%s]" % (recvvalue[57]))
|
||||
print("예상 체결가 [%s]" % (recvvalue[47]))
|
||||
print("예상 체결량 [%s]" % (recvvalue[48]))
|
||||
print("예상 거래량 [%s]" % (recvvalue[49]))
|
||||
print("예상체결 대비 [%s]" % (recvvalue[50]))
|
||||
print("부호 [%s]" % (recvvalue[51]))
|
||||
print("예상체결 전일대비율 [%s]" % (recvvalue[52]))
|
||||
print("누적거래량 [%s]" % (recvvalue[53]))
|
||||
print("주식매매 구분코드 [%s]" % (recvvalue[58]))
|
||||
|
||||
|
||||
# 국내주식체결처리 출력라이브러리
|
||||
def stockspurchase(data_cnt, data):
|
||||
print("============================================")
|
||||
menulist = "유가증권단축종목코드|주식체결시간|주식현재가|전일대비부호|전일대비|전일대비율|가중평균주식가격|주식시가|주식최고가|주식최저가|매도호가1|매수호가1|체결거래량|누적거래량|누적거래대금|매도체결건수|매수체결건수|순매수체결건수|체결강도|총매도수량|총매수수량|체결구분|매수비율|전일거래량대비등락율|시가시간|시가대비구분|시가대비|최고가시간|고가대비구분|고가대비|최저가시간|저가대비구분|저가대비|영업일자|신장운영구분코드|거래정지여부|매도호가잔량|매수호가잔량|총매도호가잔량|총매수호가잔량|거래량회전율|전일동시간누적거래량|전일동시간누적거래량비율|시간구분코드|임의종료구분코드|정적VI발동기준가"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 국내주식체결통보 출력라이브러리
|
||||
def stocksigningnotice_domestic(data, key, iv):
|
||||
|
||||
# AES256 처리 단계
|
||||
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
|
||||
pValue = aes_dec_str.split('^')
|
||||
|
||||
if pValue[13] == '2': # 체결통보
|
||||
print("#### 국내주식 체결 통보 ####")
|
||||
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|체결수량|체결단가|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|주문가격"
|
||||
menustr1 = menulist.split('|')
|
||||
else:
|
||||
print("#### 국내주식 주문·정정·취소·거부 접수 통보 ####")
|
||||
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|주문수량|주문가격|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|체결단가"
|
||||
menustr1 = menulist.split('|')
|
||||
|
||||
i = 0
|
||||
for menu in menustr1:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
async def connect():
|
||||
# 웹 소켓에 접속.( 주석은 koreainvest test server for websocket)
|
||||
## 시세데이터를 받기위한 데이터를 미리 할당해서 사용한다.
|
||||
|
||||
try:
|
||||
g_appkey = '앱키를 입력하세요'
|
||||
g_appsecret = '앱 시크릿키를 입력하세요'
|
||||
|
||||
stockcode = '005930' # 테스트용 임시 종목 설정, 삼성전자
|
||||
htsid = 'HTS ID를 입력하세요' # 체결통보용 htsid 입력
|
||||
custtype = 'P' # customer type, 개인:'P' 법인 'B'
|
||||
|
||||
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
|
||||
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
|
||||
|
||||
g_approval_key = get_approval(g_appkey, g_appsecret)
|
||||
print("approval_key [%s]" % (g_approval_key))
|
||||
|
||||
async with websockets.connect(url, ping_interval=None) as websocket:
|
||||
|
||||
"""" 주석처리는 더블쿼트 3개로 처리
|
||||
"""
|
||||
print("1.주식호가, 2.주식호가해제, 3.주식체결, 4.주식체결해제, 5.주식체결통보(고객), 6.주식체결통보해제(고객), 7.주식체결통보(모의), 8.주식체결통보해제(모의)")
|
||||
print("Input Command :")
|
||||
cmd = input()
|
||||
|
||||
# 입력값 체크 step
|
||||
if cmd < '0' or cmd > '9':
|
||||
print("> Wrong Input Data", cmd)
|
||||
|
||||
elif cmd == '0':
|
||||
print("Exit!!")
|
||||
|
||||
# 입력값에 따라 전송 데이터셋 구분 처리
|
||||
if cmd == '1': # 주식호가 등록
|
||||
tr_id = 'H0STASP0'
|
||||
tr_type = '1'
|
||||
elif cmd == '2': # 주식호가 등록해제
|
||||
tr_id = 'H0STASP0'
|
||||
tr_type = '2'
|
||||
elif cmd == '3': # 주식체결 등록
|
||||
tr_id = 'H0STCNT0'
|
||||
tr_type = '1'
|
||||
elif cmd == '4': # 주식체결 등록해제
|
||||
tr_id = 'H0STCNT0'
|
||||
tr_type = '2'
|
||||
elif cmd == '5': # 주식체결통보 등록(고객용)
|
||||
tr_id = 'H0STCNI0' # 고객체결통보
|
||||
tr_type = '1'
|
||||
elif cmd == '6': # 주식체결통보 등록해제(고객용)
|
||||
tr_id = 'H0STCNI0' # 고객체결통보
|
||||
tr_type = '2'
|
||||
elif cmd == '7': # 주식체결통보 등록(모의)
|
||||
tr_id = 'H0STCNI9' # 테스트용 직원체결통보
|
||||
tr_type = '1'
|
||||
elif cmd == '8': # 주식체결통보 등록해제(모의)
|
||||
tr_id = 'H0STCNI9' # 테스트용 직원체결통보
|
||||
tr_type = '2'
|
||||
else:
|
||||
senddata = 'wrong inert data'
|
||||
|
||||
# send json, 체결통보는 tr_key 입력항목이 상이하므로 분리를 한다.
|
||||
if cmd == '5' or cmd == '6' or cmd == '7' or cmd == '8':
|
||||
senddata = '{"header":{"approval_key":"' + g_approval_key + '","custtype":"' + custtype + '","tr_type":"' + tr_type + '","content-type":"utf-8"},"body":{"input":{"tr_id":"' + tr_id + '","tr_key":"' + htsid + '"}}}'
|
||||
else:
|
||||
senddata = '{"header":{"approval_key":"' + g_approval_key + '","custtype":"' + custtype + '","tr_type":"' + tr_type + '","content-type":"utf-8"},"body":{"input":{"tr_id":"' + tr_id + '","tr_key":"' + stockcode + '"}}}'
|
||||
|
||||
print('Input Command is :', senddata)
|
||||
|
||||
await websocket.send(senddata)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# 데이터가 오기만 기다린다.
|
||||
while True:
|
||||
data = await websocket.recv()
|
||||
# await asyncio.sleep(0.5)
|
||||
# print("Recev Command is :", data)
|
||||
|
||||
if data[0] == '0' or data[0] == '1': # 실시간 데이터일 경우
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
if data[0] == '0':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
if trid0 == "H0STASP0": # 주식호가tr 일경우의 처리 단계
|
||||
print("#### 주식호가 ####")
|
||||
stockhoka(recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "H0STCNT0": # 주식체결 데이터 처리
|
||||
print("#### 주식체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase(data_cnt, recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif data[0] == '1':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
if trid0 == "K0STCNI0" or trid0 == "K0STCNI9" or trid0 == "H0STCNI0" or trid0 == "H0STCNI9": # 주실체결 통보 처리
|
||||
stocksigningnotice(recvstr[3], aes_key, aes_iv)
|
||||
|
||||
# clearConsole()
|
||||
# break;
|
||||
else:
|
||||
jsonObject = json.loads(data)
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
if trid != "PINGPONG":
|
||||
rt_cd = jsonObject["body"]["rt_cd"]
|
||||
if rt_cd == '1': # 에러일 경우 처리
|
||||
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
#break
|
||||
elif rt_cd == '0': # 정상일 경우 처리
|
||||
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
|
||||
if trid == "H0STCNI0" or trid == "H0STCNI9":
|
||||
aes_key = jsonObject["body"]["output"]["key"]
|
||||
aes_iv = jsonObject["body"]["output"]["iv"]
|
||||
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
|
||||
|
||||
elif trid == "PINGPONG":
|
||||
print("### RECV [PINGPONG] [%s]" % (data))
|
||||
await websocket.pong(data)
|
||||
print("### SEND [PINGPONG] [%s]" % (data))
|
||||
|
||||
|
||||
|
||||
# ----------------------------------------
|
||||
# 모든 함수의 공통 부분(Exception 처리)
|
||||
# ----------------------------------------
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
print('Connect Again!')
|
||||
time.sleep(0.1)
|
||||
|
||||
# 웹소켓 다시 시작
|
||||
await connect()
|
||||
|
||||
|
||||
# # 비동기로 서버에 접속한다.
|
||||
# asyncio.get_event_loop().run_until_complete(connect())
|
||||
# asyncio.get_event_loop().close()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# - Name : main
|
||||
# - Desc : 메인
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main():
|
||||
try:
|
||||
# 웹소켓 시작
|
||||
await connect()
|
||||
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# ---------------------------------------------------------------------
|
||||
# Logic Start!
|
||||
# ---------------------------------------------------------------------
|
||||
# 웹소켓 시작
|
||||
asyncio.run(main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("KeyboardInterrupt Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-100)
|
||||
|
||||
except Exception:
|
||||
print("Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-200)
|
||||
288
한국투자증권(API)/legacy/websocket/python/ws_overseas_future.py
Normal file
288
한국투자증권(API)/legacy/websocket/python/ws_overseas_future.py
Normal file
@@ -0,0 +1,288 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
### 모듈 임포트 ###
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import asyncio
|
||||
import traceback
|
||||
import websockets
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from base64 import b64decode
|
||||
|
||||
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
|
||||
|
||||
key_bytes = 32
|
||||
|
||||
|
||||
### 함수 정의 ###
|
||||
|
||||
# AES256 DECODE
|
||||
def aes_cbc_base64_dec(key, iv, cipher_text):
|
||||
"""
|
||||
:param key: str type AES256 secret key value
|
||||
:param iv: str type AES256 Initialize Vector
|
||||
:param cipher_text: Base64 encoded AES256 str
|
||||
:return: Base64-AES256 decodec str
|
||||
"""
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
|
||||
|
||||
|
||||
# 웹소켓 접속키 발급
|
||||
def get_approval(key, secret):
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"secretkey": secret}
|
||||
PATH = "oauth2/Approval"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
approval_key = res.json()["approval_key"]
|
||||
return approval_key
|
||||
|
||||
# [필수] 유료 시세 수신을 위한 access_token 발급 함수
|
||||
# 해외주식/해외선물 유료 시세 수신 전 반드시 이 함수를 호출해 access_token을 발급받아야 함
|
||||
#
|
||||
# === 해외 유료 시세 수신 안내 ===
|
||||
# ▒ 해외주식 (HDFSASP0, HDFSASP1, HDFSCNT0: 미국, 중국, 일본, 베트남, 홍콩)
|
||||
# - 무료 시세: 별도 신청 없이 수신 가능
|
||||
# - 유료 시세: HTS 또는 MTS에서 신청 후 access_token 발급 필요
|
||||
# > HTS(eFriend Plus/Force): [7781] 시세신청(실시간)
|
||||
# > MTS(한국투자 앱): 고객지원 > 거래서비스 신청 > 해외증권 > 해외 실시간 시세 신청
|
||||
#
|
||||
# ▒ 해외선물 (HDFFF020, HDFFF010: CME, SGX / 기타 거래소는 무료 시세 제공)
|
||||
# - CME, SGX: 무료 시세 없음 → 유료 시세 신청 필수
|
||||
# - 유료 시세: HTS에서 신청 후 access_token 발급 필요
|
||||
# > HTS(eFriend Plus/Force): [7936] 해외선물옵션 실시간 시세신청/조회
|
||||
#
|
||||
# ▒ 유료 시세 수신 절차
|
||||
# 1. HTS 또는 MTS에서 유료 시세 신청
|
||||
# 2. get_access_token()으로 access_token 발급 (※ 신청 후에 발급해야 유효)
|
||||
# 3. 토큰 발급 시점 기준 최대 2시간 이내에 유료 권한 자동 반영
|
||||
# 4. 이후 웹소켓 연결 → 유료 시세 수신 가능
|
||||
def get_access_token(key, secret):
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"appsecret": secret}
|
||||
PATH = "oauth2/tokenP"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
access_token = res.json()["access_token"]
|
||||
return access_token
|
||||
|
||||
### 4. 해외선물옵션 ###
|
||||
|
||||
# 해외선물옵션호가 출력라이브러리
|
||||
def stockhoka_overseafut(data):
|
||||
# print(data)
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("종목코드 ["+recvvalue[ 0]+"]")
|
||||
print("수신일자 ["+recvvalue[ 1]+"]")
|
||||
print("수신시각 ["+recvvalue[ 2]+"]")
|
||||
print("전일종가 ["+recvvalue[ 3]+"]")
|
||||
print("====================================")
|
||||
print("매수1수량 ["+recvvalue[ 4]+"]"+", 매수1번호 ["+recvvalue[ 5]+"]"+", 매수1호가 ["+recvvalue[ 6]+"]")
|
||||
print("매도1수량 ["+recvvalue[ 7]+"]"+", 매도1번호 ["+recvvalue[ 8]+"]"+", 매도1호가 ["+recvvalue[ 9]+"]")
|
||||
print("매수2수량 ["+recvvalue[10]+"]"+", 매수2번호 ["+recvvalue[11]+"]"+", 매수2호가 ["+recvvalue[12]+"]")
|
||||
print("매도2수량 ["+recvvalue[13]+"]"+", 매도2번호 ["+recvvalue[14]+"]"+", 매도2호가 ["+recvvalue[15]+"]")
|
||||
print("매수3수량 ["+recvvalue[16]+"]"+", 매수3번호 ["+recvvalue[17]+"]"+", 매수3호가 ["+recvvalue[18]+"]")
|
||||
print("매도3수량 ["+recvvalue[19]+"]"+", 매도3번호 ["+recvvalue[20]+"]"+", 매도3호가 ["+recvvalue[21]+"]")
|
||||
print("매수4수량 ["+recvvalue[22]+"]"+", 매수4번호 ["+recvvalue[23]+"]"+", 매수4호가 ["+recvvalue[24]+"]")
|
||||
print("매도4수량 ["+recvvalue[25]+"]"+", 매도4번호 ["+recvvalue[26]+"]"+", 매도4호가 ["+recvvalue[27]+"]")
|
||||
print("매수5수량 ["+recvvalue[28 ]+"]"+", 매수5번호 ["+recvvalue[29]+"]"+", 매수5호가 ["+recvvalue[30]+"]")
|
||||
print("매도5수량 ["+recvvalue[31]+"]"+", 매도5번호 ["+recvvalue[32]+"]"+", 매도5호가 ["+recvvalue[33]+"]")
|
||||
print("====================================")
|
||||
print("전일정산가 ["+recvvalue[32]+"]")
|
||||
|
||||
|
||||
# 해외선물옵션체결처리 출력라이브러리
|
||||
def stockspurchase_overseafut(data_cnt, data):
|
||||
print("============================================")
|
||||
menulist = "종목코드|영업일자|장개시일자|장개시시각|장종료일자|장종료시각|전일종가|수신일자|수신시각|본장_전산장구분|체결가격|체결수량|전일대비가|등락률|시가|고가|저가|누적거래량|전일대비부호|체결구분|수신시각2만분의일초|전일정산가|전일정산가대비|전일정산가대비가격|전일정산가대비율"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
# print(pValue)
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 해외선물옵션 체결통보 출력라이브러리
|
||||
def stocksigningnotice_overseafut(data, key, iv):
|
||||
menulist = "유저ID|계좌번호|주문일자|주문번호|원주문일자|원주문번호|종목명|정정취소구분코드|매도매수구분코드|복합주문구분코드|가격구분코드|FM거래소접수구분코드|주문수량|FMLIMIT가격|FMSTOP주문가격|총체결수량|총체결단가|잔량|FM주문그룹일자|주문그룹번호|주문상세일시|조작상세일시|주문자|체결일자|체결번호|API체결번호|체결수량|FM체결가격|통화코드|위탁수수료|주문매체온라인여부|FM체결금액|선물옵션종목구분코드"
|
||||
menustr1 = menulist.split('|')
|
||||
|
||||
# AES256 처리 단계
|
||||
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
|
||||
# print(aes_dec_str)
|
||||
pValue = aes_dec_str.split('^')
|
||||
# print(pValue)
|
||||
print("#### 해외선물옵션 체결통보 처리 ####")
|
||||
|
||||
i = 0
|
||||
for menu in menustr1:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
### 앱키 정의 ###
|
||||
|
||||
async def connect():
|
||||
try:
|
||||
|
||||
g_appkey = "앱키를 입력하세요"
|
||||
g_appsecret = "앱 시크릿키를 입력하세요"
|
||||
|
||||
# 해외주식/해외선물(CME, SGX) 유료시세 사용 시 필수(2시간 이내 유료신청정보 동기화)
|
||||
# access_token = get_access_token(appkey, appsecret)
|
||||
|
||||
g_approval_key = get_approval(g_appkey, g_appsecret)
|
||||
print("approval_key [%s]" % (g_approval_key))
|
||||
|
||||
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
|
||||
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
|
||||
|
||||
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기
|
||||
|
||||
### 4. 해외선물옵션 호가, 체결가, 체결통보 ###
|
||||
# code_list = [['1','HDFFF020','FCAZ22']] # 해외선물체결
|
||||
# code_list = [['1','HDFFF010','FCAZ22']] # 해외선물호가
|
||||
# code_list = [['1','HDFFF020','OESH23 C3900']] # 해외옵션체결
|
||||
# code_list = [['1','HDFFF010','OESH23 C3900']] # 해외옵션호가
|
||||
# code_list = [['1','HDFFF2C0','HTS ID를 입력하세요']] # 해외선물옵션체결통보
|
||||
code_list = [['1','HDFFF020','FCAZ22'],['1','HDFFF010','FCAZ22'],['1','HDFFF020','OESH23 C3900'],['1','HDFFF010','OESH23 C3900'],['1','HDFFF2C0','HTS ID를 입력하세요']]
|
||||
|
||||
senddata_list=[]
|
||||
|
||||
for i,j,k in code_list:
|
||||
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
|
||||
senddata_list.append(temp)
|
||||
|
||||
async with websockets.connect(url, ping_interval=None) as websocket:
|
||||
|
||||
for senddata in senddata_list:
|
||||
await websocket.send(senddata)
|
||||
await asyncio.sleep(0.5)
|
||||
print(f"Input Command is :{senddata}")
|
||||
|
||||
while True:
|
||||
data = await websocket.recv()
|
||||
# await asyncio.sleep(0.5)
|
||||
# print(f"Recev Command is :{data}") # 정제되지 않은 Request / Response 출력
|
||||
|
||||
if data[0] == '0':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "HDFFF010": # 해외선물옵션호가 tr 일경우의 처리 단계
|
||||
print("#### 해외선물옵션호가 ####")
|
||||
stockhoka_overseafut(recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "HDFFF020": # 해외선물옵션체결 데이터 처리
|
||||
print("#### 해외선물옵션체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_overseafut(data_cnt, recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif data[0] == '1':
|
||||
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "HDFFF2C0": # 해외선물옵션체결 통보 처리
|
||||
stocksigningnotice_overseafut(recvstr[3], aes_key, aes_iv)
|
||||
|
||||
else:
|
||||
|
||||
jsonObject = json.loads(data)
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
if trid != "PINGPONG":
|
||||
rt_cd = jsonObject["body"]["rt_cd"]
|
||||
|
||||
if rt_cd == '1': # 에러일 경우 처리
|
||||
|
||||
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
|
||||
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
break
|
||||
|
||||
elif rt_cd == '0': # 정상일 경우 처리
|
||||
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
|
||||
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
|
||||
if trid == "HDFFF2C0": # 해외선물옵션
|
||||
aes_key = jsonObject["body"]["output"]["key"]
|
||||
aes_iv = jsonObject["body"]["output"]["iv"]
|
||||
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
|
||||
|
||||
elif trid == "PINGPONG":
|
||||
print("### RECV [PINGPONG] [%s]" % (data))
|
||||
await websocket.pong(data)
|
||||
print("### SEND [PINGPONG] [%s]" % (data))
|
||||
|
||||
|
||||
# ----------------------------------------
|
||||
# 모든 함수의 공통 부분(Exception 처리)
|
||||
# ----------------------------------------
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
print('Connect Again!')
|
||||
time.sleep(0.1)
|
||||
|
||||
# 웹소켓 다시 시작
|
||||
await connect()
|
||||
|
||||
|
||||
# # 비동기로 서버에 접속한다.
|
||||
# asyncio.get_event_loop().run_until_complete(connect())
|
||||
# asyncio.get_event_loop().close()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# - Name : main
|
||||
# - Desc : 메인
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main():
|
||||
try:
|
||||
# 웹소켓 시작
|
||||
await connect()
|
||||
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# ---------------------------------------------------------------------
|
||||
# Logic Start!
|
||||
# ---------------------------------------------------------------------
|
||||
# 웹소켓 시작
|
||||
asyncio.run(main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("KeyboardInterrupt Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-100)
|
||||
|
||||
except Exception:
|
||||
print("Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-200)
|
||||
303
한국투자증권(API)/legacy/websocket/python/ws_overseas_stock.py
Normal file
303
한국투자증권(API)/legacy/websocket/python/ws_overseas_stock.py
Normal file
@@ -0,0 +1,303 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
### 모듈 임포트 ###
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import asyncio
|
||||
import traceback
|
||||
import websockets
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from base64 import b64decode
|
||||
|
||||
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
|
||||
|
||||
key_bytes = 32
|
||||
|
||||
|
||||
### 함수 정의 ###
|
||||
|
||||
# AES256 DECODE
|
||||
def aes_cbc_base64_dec(key, iv, cipher_text):
|
||||
"""
|
||||
:param key: str type AES256 secret key value
|
||||
:param iv: str type AES256 Initialize Vector
|
||||
:param cipher_text: Base64 encoded AES256 str
|
||||
:return: Base64-AES256 decodec str
|
||||
"""
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
|
||||
|
||||
|
||||
# 웹소켓 접속키 발급
|
||||
def get_approval(key, secret):
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"secretkey": secret}
|
||||
PATH = "oauth2/Approval"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
approval_key = res.json()["approval_key"]
|
||||
return approval_key
|
||||
|
||||
# [필수] 유료 시세 수신을 위한 access_token 발급 함수
|
||||
# 해외주식/해외선물 유료 시세 수신 전 반드시 이 함수를 호출해 access_token을 발급받아야 함
|
||||
#
|
||||
# === 해외 유료 시세 수신 안내 ===
|
||||
# ▒ 해외주식 (HDFSASP0, HDFSASP1, HDFSCNT0: 미국, 중국, 일본, 베트남, 홍콩)
|
||||
# - 무료 시세: 별도 신청 없이 수신 가능
|
||||
# - 유료 시세: HTS 또는 MTS에서 신청 후 access_token 발급 필요
|
||||
# > HTS(eFriend Plus/Force): [7781] 시세신청(실시간)
|
||||
# > MTS(한국투자 앱): 고객지원 > 거래서비스 신청 > 해외증권 > 해외 실시간 시세 신청
|
||||
#
|
||||
# ▒ 해외선물 (HDFFF020, HDFFF010: CME, SGX / 기타 거래소는 무료 시세 제공)
|
||||
# - CME, SGX: 무료 시세 없음 → 유료 시세 신청 필수
|
||||
# - 유료 시세: HTS에서 신청 후 access_token 발급 필요
|
||||
# > HTS(eFriend Plus/Force): [7936] 해외선물옵션 실시간 시세신청/조회
|
||||
#
|
||||
# ▒ 유료 시세 수신 절차
|
||||
# 1. HTS 또는 MTS에서 유료 시세 신청
|
||||
# 2. get_access_token()으로 access_token 발급 (※ 신청 후에 발급해야 유효)
|
||||
# 3. 토큰 발급 시점 기준 최대 2시간 이내에 유료 권한 자동 반영
|
||||
# 4. 이후 웹소켓 연결 → 유료 시세 수신 가능
|
||||
def get_access_token(key, secret):
|
||||
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
|
||||
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
|
||||
headers = {"content-type": "application/json"}
|
||||
body = {"grant_type": "client_credentials",
|
||||
"appkey": key,
|
||||
"appsecret": secret}
|
||||
PATH = "oauth2/tokenP"
|
||||
URL = f"{url}/{PATH}"
|
||||
time.sleep(0.05)
|
||||
res = requests.post(URL, headers=headers, data=json.dumps(body))
|
||||
access_token = res.json()["access_token"]
|
||||
return access_token
|
||||
|
||||
### 2. 해외주식 ###
|
||||
|
||||
# 해외주식호가 출력라이브러리
|
||||
def stockhoka_overseas(data):
|
||||
""" 넘겨받는데이터가 정상인지 확인
|
||||
print("stockhoka[%s]"%(data))
|
||||
"""
|
||||
recvvalue = data.split('^') # 수신데이터를 split '^'
|
||||
|
||||
print("실시간종목코드 [" + recvvalue[0] + "]" + ", 종목코드 [" + recvvalue[1] + "]")
|
||||
print("소숫점자리수 [" + recvvalue[2] + "]")
|
||||
print("현지일자 [" + recvvalue[3] + "]" + ", 현지시간 [" + recvvalue[4] + "]")
|
||||
print("한국일자 [" + recvvalue[5] + "]" + ", 한국시간 [" + recvvalue[6] + "]")
|
||||
print("======================================")
|
||||
print("매수총 잔량 [%s]" % (recvvalue[7]))
|
||||
print("매수총잔량대비 [%s]" % (recvvalue[9]))
|
||||
print("매도총 잔량 [%s]" % (recvvalue[8]))
|
||||
print("매도총잔략대비 [%s]" % (recvvalue[10]))
|
||||
print("매수호가 [%s]" % (recvvalue[11]))
|
||||
print("매도호가 [%s]" % (recvvalue[12]))
|
||||
print("매수잔량 [%s]" % (recvvalue[13]))
|
||||
print("매도잔량 [%s]" % (recvvalue[14]))
|
||||
print("매수잔량대비 [%s]" % (recvvalue[15]))
|
||||
print("매도잔량대비 [%s]" % (recvvalue[16]))
|
||||
|
||||
|
||||
# 해외주식체결처리 출력라이브러리
|
||||
def stockspurchase_overseas(data_cnt, data):
|
||||
print("============================================")
|
||||
menulist = "실시간종목코드|종목코드|수수점자리수|현지영업일자|현지일자|현지시간|한국일자|한국시간|시가|고가|저가|현재가|대비구분|전일대비|등락율|매수호가|매도호가|매수잔량|매도잔량|체결량|거래량|거래대금|매도체결량|매수체결량|체결강도|시장구분"
|
||||
menustr = menulist.split('|')
|
||||
pValue = data.split('^')
|
||||
i = 0
|
||||
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
|
||||
print("### [%d / %d]" % (cnt + 1, data_cnt))
|
||||
for menu in menustr:
|
||||
print("%-13s[%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
# 해외주식체결통보 출력라이브러리
|
||||
def stocksigningnotice_overseas(data, key, iv):
|
||||
|
||||
# AES256 처리 단계
|
||||
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
|
||||
pValue = aes_dec_str.split('^')
|
||||
|
||||
if pValue[12] == '2': # 체결통보
|
||||
print("#### 해외주식 체결 통보 ####")
|
||||
menulist = "고객 ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류2|단축종목코드|체결수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|체결종목명|해외종목구분|담보유형코드|담보대출일자|분할매수매도시작시간|분할매수매도종료시간|시간분할타입유형|체결단가12"
|
||||
menustr1 = menulist.split('|')
|
||||
|
||||
else:
|
||||
print("#### 해외주식 주문·정정·취소·거부 접수 통보 ####")
|
||||
menulist = "고객 ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류2|단축종목코드|주문수량|주문단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량_미출력|계좌명|체결종목명|해외종목구분|담보유형코드|담보대출일자|분할매수매도시작시간|분할매수매도종료시간|시간분할타입유형|체결단가12"
|
||||
menustr1 = menulist.split('|')
|
||||
|
||||
i = 0
|
||||
for menu in menustr1:
|
||||
print("%s [%s]" % (menu, pValue[i]))
|
||||
i += 1
|
||||
|
||||
|
||||
|
||||
async def connect():
|
||||
try:
|
||||
|
||||
g_appkey = '앱키를 입력하세요'
|
||||
g_appsecret = '앱 시크릿키를 입력하세요'
|
||||
|
||||
# 해외주식/해외선물(CME, SGX) 유료시세 사용 시 필수(2시간 이내 유료신청정보 동기화)
|
||||
# access_token = get_access_token(appkey, appsecret)
|
||||
|
||||
g_approval_key = get_approval(g_appkey, g_appsecret)
|
||||
print("approval_key [%s]" % (g_approval_key))
|
||||
|
||||
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
|
||||
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
|
||||
|
||||
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기
|
||||
|
||||
### 해외주식(미국) 호가, 체결가, 체결통보 ###
|
||||
# code_list = [['1','HDFSASP0','DNASAAPL'],['1','HDFSCNT0','DNASAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요']]
|
||||
|
||||
### 해외주식(미국-주간) 호가, 체결가, 체결통보 ###
|
||||
# code_list = [['1','HDFSASP0','RBAQAAPL'],['1','HDFSCNT0','RBAQAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요']]
|
||||
|
||||
### 해외주식(아시아) 호가, 체결가, 체결통보 ###
|
||||
# code_list = [['1','HDFSASP1','DHKS00003'],['1','HDFSCNT0','DHKS00003'],['1','H0GSCNI0','HTS ID를 입력하세요']]
|
||||
|
||||
### 해외주식(미국) 호가, 체결가, 체결통보, 해외주식(아시아) 호가, 체결가, 체결통보 ###
|
||||
code_list = [['1','HDFSASP0','DNASAAPL'],['1','HDFSCNT0','DNASAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요'],
|
||||
['1','HDFSASP1','DHKS00003'],['1','HDFSCNT0','DHKS00003'],['1','H0GSCNI0','HTS ID를 입력하세요']]
|
||||
|
||||
senddata_list=[]
|
||||
|
||||
for i,j,k in code_list:
|
||||
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
|
||||
senddata_list.append(temp)
|
||||
|
||||
|
||||
async with websockets.connect(url, ping_interval=None) as websocket:
|
||||
|
||||
for senddata in senddata_list:
|
||||
await websocket.send(senddata)
|
||||
await asyncio.sleep(0.5)
|
||||
print(f"Input Command is :{senddata}")
|
||||
|
||||
while True:
|
||||
|
||||
data = await websocket.recv()
|
||||
# await asyncio.sleep(0.5)
|
||||
# print(f"Recev Command is :{data}") # 정제되지 않은 Request / Response 출력
|
||||
|
||||
if data[0] == '0':
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "HDFSASP0": # 해외주식호가tr 일경우의 처리 단계
|
||||
print("#### 해외(미국)주식호가 ####")
|
||||
stockhoka_overseas(recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "HDFSASP1": # 해외주식호가tr 일경우의 처리 단계
|
||||
print("#### 해외(아시아)주식호가 ####")
|
||||
stockhoka_overseas(recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif trid0 == "HDFSCNT0": # 해외주식체결 데이터 처리
|
||||
print("#### 해외주식체결 ####")
|
||||
data_cnt = int(recvstr[2]) # 체결데이터 개수
|
||||
stockspurchase_overseas(data_cnt, recvstr[3])
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
elif data[0] == '1':
|
||||
|
||||
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
|
||||
trid0 = recvstr[1]
|
||||
|
||||
if trid0 == "H0GSCNI0" or trid0 == "H0GSCNI9": # 해외주식체결 통보 처리
|
||||
stocksigningnotice_overseas(recvstr[3], aes_key, aes_iv)
|
||||
|
||||
else:
|
||||
|
||||
jsonObject = json.loads(data)
|
||||
trid = jsonObject["header"]["tr_id"]
|
||||
|
||||
if trid != "PINGPONG":
|
||||
rt_cd = jsonObject["body"]["rt_cd"]
|
||||
|
||||
if rt_cd == '1': # 에러일 경우 처리
|
||||
|
||||
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
|
||||
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
break
|
||||
|
||||
elif rt_cd == '0': # 정상일 경우 처리
|
||||
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
|
||||
|
||||
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
|
||||
if trid == "H0GSCNI0": # 해외주식
|
||||
aes_key = jsonObject["body"]["output"]["key"]
|
||||
aes_iv = jsonObject["body"]["output"]["iv"]
|
||||
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
|
||||
|
||||
elif trid == "PINGPONG":
|
||||
print("### RECV [PINGPONG] [%s]" % (data))
|
||||
await websocket.pong(data)
|
||||
print("### SEND [PINGPONG] [%s]" % (data))
|
||||
|
||||
# ----------------------------------------
|
||||
# 모든 함수의 공통 부분(Exception 처리)
|
||||
# ----------------------------------------
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
print('Connect Again!')
|
||||
time.sleep(0.1)
|
||||
|
||||
# 웹소켓 다시 시작
|
||||
await connect()
|
||||
|
||||
|
||||
# # 비동기로 서버에 접속한다.
|
||||
# asyncio.get_event_loop().run_until_complete(connect())
|
||||
# asyncio.get_event_loop().close()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# - Name : main
|
||||
# - Desc : 메인
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main():
|
||||
try:
|
||||
# 웹소켓 시작
|
||||
await connect()
|
||||
|
||||
except Exception as e:
|
||||
print('Exception Raised!')
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# ---------------------------------------------------------------------
|
||||
# Logic Start!
|
||||
# ---------------------------------------------------------------------
|
||||
# 웹소켓 시작
|
||||
asyncio.run(main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("KeyboardInterrupt Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-100)
|
||||
|
||||
except Exception:
|
||||
print("Exception 발생!")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(-200)
|
||||
Reference in New Issue
Block a user