initial commit

This commit is contained in:
2026-02-04 00:16:34 +09:00
commit ae11528dd9
867 changed files with 209640 additions and 0 deletions

View 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.

File diff suppressed because it is too large Load Diff

View 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

View 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.