Files
KisStock0/한국투자증권(API)/legacy/websocket/delphi/MainForm.pas
2026-02-04 00:16:34 +09:00

306 lines
9.2 KiB
ObjectPascal

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.