정기적으로 웹에서 실시간 주식 정보를 가져와 로컬에 SQLite 데이터베이스에 기록합니다. 실행 시 자동으로 데이터베이스가 생성되며, 메뉴를 통해 테이블을 생성하고, 10초 간격으로 데이터를 저장할 수 있습니다. 저장된 데이터는 쿼리 기능을 통해 확인 가능합니다. 문자 인코딩은 유니코드보다 ASCII를 사용하여 처리가 간단해졌습니다.
다음은 주식 데이터를 수집하고 저장하는 구현 예제입니다.
// StockDataCollector.cpp: 주식 정보를 데이터베이스에 저장
// sqlite-amalgamation의 sqlite3.c 및 sqlite3.h 파일을 프로젝트에 포함
// 전처리 헤더 사용 제외, 문자 집합은 ASCII로 설정
#include "stdafx.h"
#include "StockDataCollector.h"
#include <windows.h>
#include <wininet.h>
#include <stdio.h>
#include "sqlite3.h"
#pragma comment(lib, "wininet.lib")
#define MAX_BUFFER_SIZE 1024
#define DATABASE_NAME "stock_data.db"
// 전역 변수
HINSTANCE hAppInstance;
TCHAR appTitle[MAX_LOADSTRING];
TCHAR windowClass[MAX_LOADSTRING];
// 함수 선언
ATOM RegisterWindowClass(HINSTANCE);
BOOL InitializeApplication(HINSTANCE, int);
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
// 주식 정보 구조체
struct StockRecord {
TCHAR symbol[16]; // 종목 코드 (예: sh000001)
TCHAR timestamp[20]; // 시간 포맷: YYYY-MM-DD HH:MM:SS
double currentPrice; // 현재 가격
double previousClose; // 전일 종가
double priceChange; // 변동률 (%)
};
// GUI 요소
HWND editBox;
TCHAR outputBuffer[MAX_BUFFER_SIZE];
TCHAR statusMessage[256];
StockRecord stockData;
// SQLite 관련
sqlite3* dbHandle;
char* errorMsg;
int result;
char sqlQuery[MAX_BUFFER_SIZE];
// 출력 함수
int PrintLog(TCHAR* format, ...);
static int QueryCallback(void*, int, char**, char**);
bool FetchStockData(StockRecord&);
int OpenDatabase(HWND);
int SaveToDatabase(const StockRecord&);
// 메시지 핸들러
int OnWindowCreate(HWND, UINT, WPARAM, LPARAM);
int OnWindowSize(HWND, UINT, WPARAM, LPARAM);
int OnPaintWindow(HWND, UINT, WPARAM, LPARAM);
int OnTimerEvent(HWND, UINT, WPARAM, LPARAM);
int OnCreateTableMenu(HWND, UINT, WPARAM, LPARAM);
int OnDropTableMenu(HWND, UINT, WPARAM, LPARAM);
int OnTestInsertMenu(HWND, UINT, WPARAM, LPARAM);
int OnQueryDataMenu(HWND, UINT, WPARAM, LPARAM);
// 메인 함수
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
MSG message;
HACCEL acceleratorTable;
LoadString(hInstance, IDS_APP_TITLE, appTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_STOCKCOLLECTOR, windowClass, MAX_LOADSTRING);
RegisterWindowClass(hInstance);
if (!InitializeApplication(hInstance, nCmdShow))
return FALSE;
acceleratorTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_STOCKCOLLECTOR));
while (GetMessage(&message, NULL, 0, 0)) {
if (!TranslateAccelerator(message.hwnd, acceleratorTable, &message)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
return (int)message.wParam;
}
// 윈도우 클래스 등록
ATOM RegisterWindowClass(HINSTANCE hInstance) {
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WindowProc;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_STOCKCOLLECTOR));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_STOCKCOLLECTOR);
wcex.lpszClassName = windowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
// 애플리케이션 초기화
BOOL InitializeApplication(HINSTANCE hInstance, int nCmdShow) {
HWND hWnd = CreateWindow(windowClass, appTitle,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
NULL, NULL, hInstance, NULL);
if (!hWnd) return FALSE;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
hAppInstance = hInstance;
return TRUE;
}
// 메시지 루프 처리
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
int commandId, event;
switch (message) {
case WM_CREATE:
OnWindowCreate(hWnd, message, wParam, lParam);
break;
case WM_SIZE:
OnWindowSize(hWnd, message, wParam, lParam);
break;
case WM_PAINT:
OnPaintWindow(hWnd, message, wParam, lParam);
break;
case WM_TIMER:
OnTimerEvent(hWnd, message, wParam, lParam);
break;
case WM_COMMAND:
commandId = LOWORD(wParam);
event = HIWORD(wParam);
switch (commandId) {
case IDM_CREATETABLE:
OnCreateTableMenu(hWnd, message, wParam, lParam);
break;
case IDM_DELETETABLE:
OnDropTableMenu(hWnd, message, wParam, lParam);
break;
case IDM_INSERTDATA:
OnTestInsertMenu(hWnd, message, wParam, lParam);
break;
case IDM_SQLDATA:
OnQueryDataMenu(hWnd, message, wParam, lParam);
break;
case IDM_ABOUT:
MessageBox(hWnd, _T("SQLite 기반 주식 데이터 수집기 - 2022"), _T("정보"), MB_OK);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_DESTROY:
sqlite3_close(dbHandle);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// SQL 쿼리 결과 콜백
int QueryCallback(void* notUsed, int argc, char** values, char** columns) {
for (int i = 0; i < argc; ++i) {
PrintLog(_T("%s = %s\n"), columns[i], values[i] ? values[i] : _T("NULL"));
}
PrintLog(_T("\r\n"));
return 0;
}
// 데이터베이스 열기
int OpenDatabase(HWND hWnd) {
result = sqlite3_open(DATABASE_NAME, &dbHandle);
if (result != SQLITE_OK) {
PrintLog(_T("<ERR> 데이터베이스 열기 실패: %s\n"), sqlite3_errmsg(dbHandle));
sqlite3_close(dbHandle);
return -1;
}
wsprintf(statusMessage, _T("데이터베이스 준비 완료: %s"), DATABASE_NAME);
return 0;
}
// 창 생성 시 초기화
int OnWindowCreate(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
editBox = CreateWindow(TEXT("edit"), NULL,
WS_CHILD | WS_BORDER | WS_VISIBLE | ES_MULTILINE | WS_VSCROLL,
0, 0, 0, 0, hWnd, (HMENU)IDC_EDIT, hAppInstance, NULL);
OpenDatabase(hWnd);
SetTimer(hWnd, 1, 10000, NULL); // 10초 간격
return 1;
}
// 창 크기 조절 시 컨트롤 위치 조정
int OnWindowSize(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
RECT clientRect;
GetClientRect(hWnd, &clientRect);
MoveWindow(editBox, 0, 0, clientRect.right, clientRect.bottom - 20, TRUE);
return DefWindowProc(hWnd, message, wParam, lParam);
}
// 상태 표시줄 업데이트
int OnPaintWindow(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
hdc = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &rect);
DrawText(hdc, statusMessage, lstrlen(statusMessage), &rect,
DT_LEFT | DT_BOTTOM | DT_SINGLELINE);
EndPaint(hWnd, &ps);
return 1;
}
// 타이머 이벤트: 주식 데이터 수집 및 저장
int OnTimerEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
SYSTEMTIME now;
GetSystemTime(&now);
PrintLog(_T("\r\n[%04d-%02d-%02d %02d:%02d:%02d] "),
now.wYear, now.wMonth, now.wDay,
now.wHour, now.wMinute, now.wSecond);
wsprintf(stockData.timestamp, _T("%04d-%02d-%02d %02d:%02d:%02d"),
now.wYear, now.wMonth, now.wDay,
now.wHour, now.wMinute, now.wSecond);
const TCHAR* symbols[] = {
_T("sh000001"), _T("sz399001"),
_T("sh601398"), _T("sz000063")
};
for (int i = 0; i < 4; ++i) {
wcscpy(stockData.symbol, symbols[i]);
if (FetchStockData(stockData)) {
PrintLog(_T(" |%s %8.2f %8.4f%% "),
stockData.symbol, stockData.currentPrice,
stockData.priceChange * 100.0);
SaveToDatabase(stockData);
}
}
return 1;
}
// 테이블 생성
int OnCreateTableMenu(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
strcpy(sqlQuery, "CREATE TABLE IF NOT EXISTS StockRecords (" \
"Symbol TEXT, Timestamp TEXT, CurrentPrice REAL);");
result = sqlite3_exec(dbHandle, sqlQuery, QueryCallback, nullptr, &errorMsg);
if (result != SQLITE_OK) {
PrintLog(_T("<ERR> 테이블 생성 실패: %s\n"), errorMsg);
sqlite3_free(errorMsg);
return -1;
}
PrintLog(_T("테이블 생성 성공"));
return 1;
}
// 테이블 삭제
int OnDropTableMenu(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
strcpy(sqlQuery, "DROP TABLE IF EXISTS StockRecords;");
result = sqlite3_exec(dbHandle, sqlQuery, QueryCallback, nullptr, &errorMsg);
if (result != SQLITE_OK) {
PrintLog(_T("<ERR> 테이블 삭제 실패: %s\n"), errorMsg);
sqlite3_free(errorMsg);
return -1;
}
PrintLog(_T("테이블 삭제 완료"));
return 1;
}
// 테스트 데이터 삽입
int OnTestInsertMenu(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
strcpy(sqlQuery, "INSERT INTO StockRecords VALUES('sh000001', '2022-01-01 12:10', 3011.23);");
result = sqlite3_exec(dbHandle, sqlQuery, QueryCallback, nullptr, &errorMsg);
if (result != SQLITE_OK) {
PrintLog(_T("<ERR> 삽입 실패: %s\n"), errorMsg);
sqlite3_free(errorMsg);
} else {
PrintLog(_T("테스트 데이터 삽입 완료"));
}
return 1;
}
// 전체 데이터 조회
int OnQueryDataMenu(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
strcpy(sqlQuery, "SELECT * FROM StockRecords;");
char** resultData;
int rowCount = 0, colCount = 0;
result = sqlite3_get_table(dbHandle, sqlQuery, &resultData, &rowCount, &colCount, &errorMsg);
if (result != SQLITE_OK) {
PrintLog(_T("<ERR> 쿼리 실패: %s\n"), errorMsg);
sqlite3_free(errorMsg);
return -1;
}
PrintLog(_T("행 수: %d, 열 수: %d\n"), rowCount, colCount);
for (int i = 0; i <= rowCount; ++i) {
PrintLog(_T("\r\n"));
for (int j = 0; j < colCount; ++j) {
PrintLog(_T("\t%s"), resultData[i * colCount + j]);
}
}
sqlite3_free_table(resultData);
return 1;
}
// 로그 출력
int PrintLog(TCHAR* format, ...) {
va_list args;
int length;
if (!editBox) return 0;
va_start(args, format);
length = _vsntprintf(outputBuffer, MAX_BUFFER_SIZE, format, args);
va_end(args);
int textLength = GetWindowTextLength(editBox);
if (textLength + length > 30000) {
SendMessage(editBox, EM_SETSEL, 0, 10000);
SendMessage(editBox, WM_CLEAR, 0, 0);
textLength -= 10000;
}
SendMessage(editBox, EM_SETSEL, textLength, textLength);
SendMessage(editBox, EM_REPLACESEL, 0, (LPARAM)outputBuffer);
return length;
}
// 실시간 주식 데이터 읽기
bool FetchStockData(StockRecord& record) {
char buffer[1024] = "";
char* token;
char* nextToken = nullptr;
DWORD bytesRead = 1;
sprintf(record.symbol, "http://qt.gtimg.cn/q=%s", record.symbol);
HINTERNET session = InternetOpen(_T("StockFetcher"), INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);
if (!session) return false;
HINTERNET request = InternetOpenUrl(session, record.symbol, nullptr, 0,
INTERNET_FLAG_DONT_CACHE, 0);
if (!request) {
InternetCloseHandle(session);
return false;
}
InternetReadFile(request, buffer, 1023, &bytesRead);
buffer[bytesRead] = '\0';
token = strtok_s(buffer, "~", &nextToken);
token = strtok_s(nullptr, "~", &nextToken);
token = strtok_s(nullptr, "~", &nextToken);
token = strtok_s(nullptr, "~", &nextToken);
record.currentPrice = atof(token);
token = strtok_s(nullptr, "~", &nextToken);
record.previousClose = atof(token);
record.priceChange = (record.currentPrice - record.previousClose) / record.previousClose;
InternetCloseHandle(request);
InternetCloseHandle(session);
return true;
}
메뉴 리소스 정의:
#define IDM_CREATETABLE 32773
#define IDM_DELETETABLE 32774
#define IDM_INSERTDATA 32775
#define IDM_SQLDATA 32776
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
IDC_STOCKCOLLECTOR MENU
BEGIN
POPUP "&파일"
BEGIN
MENUITEM "테이블 생성", IDM_CREATETABLE
MENUITEM "테이블 삭제", IDM_DELETETABLE
MENUITEM "테스트 삽입", IDM_INSERTDATA
MENUITEM "데이터 조회", IDM_SQLDATA
MENUITEM "종료", IDM_EXIT
END
POPUP "&도움말"
BEGIN
MENUITEM "&정보...", IDM_ABOUT
END
END