feat: KIS Open API 연동 및 DataCollectionService 구현
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 7s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 8s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Snapshot Admin Deployment / build-and-deploy (push) Failing after 46s
Deploy to Production / Build & Deploy to Production (push) Failing after 1m5s

- C# 기반의 DataCollectionService 클래스 구현

- 기존의 파이썬 스크립트 실행 방식을 대체하고 KIS API 클라이언트를 직접 사용하여 주식 시세, 호가, 공매도 정보 수집

- CollectionEndpoints에 비동기 수집 요청 처리 통합 및 Program.cs에 서비스 DI 등록
This commit is contained in:
2026-06-29 23:39:21 +09:00
parent 2220f9f807
commit 5c5d9bfee7
3 changed files with 262 additions and 34 deletions
@@ -1,5 +1,5 @@
using QuantEngine.Core.Interfaces;
using System.Diagnostics;
using QuantEngine.Application.Services;
namespace QuantEngine.Web.Endpoints;
@@ -108,51 +108,30 @@ public static class CollectionEndpoints
}
}
private static async Task<IResult> StartCollectionRun(ICollectionRepository repo, ILogger<Program> logger)
private static async Task<IResult> StartCollectionRun(
DataCollectionService collectionService,
HttpRequest request,
ILogger<Program> logger)
{
try
{
var runId = Guid.NewGuid().ToString("N");
var now = DateTime.UtcNow.ToString("o");
var run = new CollectionRunRecord(
RunId: runId,
Status: "running",
StartedAt: now,
FinishedAt: null,
TotalSnapshots: null,
TotalErrors: null,
UpdatedAt: now
);
var body = await request.ReadAsAsync<CollectionRunRequest>();
var account = body?.Account ?? "real";
var tickers = body?.Tickers ?? new List<string> { "005930", "000660" };
await repo.SaveRunAsync(run);
// Temp: Invoke Python subprocess for actual collection
// Trigger async collection (fire-and-forget)
_ = Task.Run(async () =>
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "python",
Arguments = "tools/run_kis_data_collection_v1.py --input-json GatherTradingData.json --sqlite-db src/quant_engine/kis_data_collection.db --kis-account real",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
await process.WaitForExitAsync();
await repo.UpdateRunStatusAsync(runId, "completed", DateTime.UtcNow.ToString("o"), 0, 0);
await collectionService.RunCollectionAsync(runId, account, tickers);
}
catch (Exception ex)
{
logger.LogError(ex, $"Collection run {runId} failed");
await repo.UpdateRunStatusAsync(runId, "failed", DateTime.UtcNow.ToString("o"), null, null);
logger.LogError(ex, "Collection run {RunId} failed", runId);
}
});
@@ -160,12 +139,20 @@ public static class CollectionEndpoints
{
runId,
status = "running",
startedAt = now
startedAt = now,
tickerCount = tickers.Count
});
}
catch
catch (Exception ex)
{
logger.LogError(ex, "Failed to start collection run");
return Results.StatusCode(500);
}
}
private class CollectionRunRequest
{
public string? Account { get; set; }
public List<string>? Tickers { get; set; }
}
}