build(infra): KIS API 클라이언트 시그니처 일원화 및 빌드 수정
- 수정: KisApiClient 메서드 반환 타입 Task<string> → Task<Dictionary<string, object>>로 통일 - 수정: GetOrRefreshTokenAsync ReadAsAsync → ReadFromJsonAsync로 변경 - 수정: PlaceholderKisApiClient IKisApiClient 인터페이스 완전 구현 - 수정: CollectionEndpoints 모든 WithOpenApi() 호출 제거 - 수정: Program.cs using 지시문 순서 재정렬 - 추가: Client/_Imports.razor Fluent UI 컴포넌트 네임스페이스 정의 이제 빌드 성공: QuantEngine.Web 프로젝트 컴파일 완료 (경고 0, 에러 0) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+20
-15
@@ -171,10 +171,10 @@ namespace QuantEngine.Infrastructure.External
|
|||||||
return await response.Content.ReadAsStringAsync();
|
return await response.Content.ReadAsStringAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GetCurrentPriceAsync(string code)
|
public async Task<Dictionary<string, object>> GetCurrentPriceAsync(string code, string account = "mock")
|
||||||
{
|
{
|
||||||
return SendRequestAsync(
|
var json = await SendRequestAsync(
|
||||||
"/uapi/domestic-stock/v1/quotations/inquire-price",
|
"/uapi/domestic-stock/v1/quotations/inquire-price",
|
||||||
"FHKST01010100",
|
"FHKST01010100",
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
@@ -182,12 +182,13 @@ namespace QuantEngine.Infrastructure.External
|
|||||||
{ "FID_INPUT_ISCD", code }
|
{ "FID_INPUT_ISCD", code }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GetAskingPrice10LevelAsync(string code)
|
public async Task<Dictionary<string, object>> GetAskingPrice10LevelAsync(string code, string account = "mock")
|
||||||
{
|
{
|
||||||
return SendRequestAsync(
|
var json = await SendRequestAsync(
|
||||||
"/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn",
|
"/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn",
|
||||||
"FHKST01010200",
|
"FHKST01010200",
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
@@ -195,12 +196,13 @@ namespace QuantEngine.Infrastructure.External
|
|||||||
{ "FID_INPUT_ISCD", code }
|
{ "FID_INPUT_ISCD", code }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GetDailyShortSaleAsync(string code, string startDate, string endDate)
|
public async Task<Dictionary<string, object>> GetDailyShortSaleAsync(string code, string startDate, string endDate, string account = "mock")
|
||||||
{
|
{
|
||||||
return SendRequestAsync(
|
var json = await SendRequestAsync(
|
||||||
"/uapi/domestic-stock/v1/quotations/daily-short-sale",
|
"/uapi/domestic-stock/v1/quotations/daily-short-sale",
|
||||||
"FHPST04830000",
|
"FHPST04830000",
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
@@ -210,12 +212,13 @@ namespace QuantEngine.Infrastructure.External
|
|||||||
{ "FID_INPUT_DATE_2", endDate }
|
{ "FID_INPUT_DATE_2", endDate }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GetDailyItemChartPriceAsync(string code, string startDate, string endDate, string period = "D")
|
public async Task<Dictionary<string, object>> GetDailyItemChartPriceAsync(string code, string startDate, string endDate, string period = "D", string account = "mock")
|
||||||
{
|
{
|
||||||
return SendRequestAsync(
|
var json = await SendRequestAsync(
|
||||||
"/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice",
|
"/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice",
|
||||||
"FHKST03010100",
|
"FHKST03010100",
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
@@ -227,12 +230,13 @@ namespace QuantEngine.Infrastructure.External
|
|||||||
{ "FID_ORG_ADJ_PRC", "0" }
|
{ "FID_ORG_ADJ_PRC", "0" }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GetInvestorTrendAsync(string code)
|
public async Task<Dictionary<string, object>> GetInvestorTrendAsync(string code, string account = "mock")
|
||||||
{
|
{
|
||||||
return SendRequestAsync(
|
var json = await SendRequestAsync(
|
||||||
"/uapi/domestic-stock/v1/quotations/inquire-investor",
|
"/uapi/domestic-stock/v1/quotations/inquire-investor",
|
||||||
"FHKST01010900",
|
"FHKST01010900",
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
@@ -240,6 +244,7 @@ namespace QuantEngine.Infrastructure.External
|
|||||||
{ "FID_INPUT_ISCD", code }
|
{ "FID_INPUT_ISCD", code }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
return JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ public class KisApiClient : IKisApiClient
|
|||||||
);
|
);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
var tokenData = await response.Content.ReadAsAsync<Dictionary<string, object>>();
|
var tokenData = await response.Content.ReadFromJsonAsync<Dictionary<string, object>>();
|
||||||
var accessToken = tokenData["access_token"]?.ToString() ?? throw new InvalidOperationException("No access_token in response");
|
var accessToken = tokenData["access_token"]?.ToString() ?? throw new InvalidOperationException("No access_token in response");
|
||||||
var expiresInStr = tokenData.ContainsKey("expires_in") ? tokenData["expires_in"]?.ToString() : "86400";
|
var expiresInStr = tokenData.ContainsKey("expires_in") ? tokenData["expires_in"]?.ToString() : "86400";
|
||||||
var expiresInSec = int.TryParse(expiresInStr, out var seconds) ? seconds : 86400;
|
var expiresInSec = int.TryParse(expiresInStr, out var seconds) ? seconds : 86400;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
@using System.Net.Http
|
||||||
|
@using System.Net.Http.Json
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
|
@using Microsoft.JSInterop
|
||||||
|
@using Microsoft.FluentUI.AspNetCore.Components
|
||||||
|
@using Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||||
|
@using QuantEngine.Web
|
||||||
|
@using QuantEngine.Web.Components
|
||||||
|
@using QuantEngine.Web.Components.Layout
|
||||||
|
@using QuantEngine.Web.Services
|
||||||
|
@using QuantEngine.Web.Services
|
||||||
@@ -8,44 +8,37 @@ public static class CollectionEndpoints
|
|||||||
public static void MapCollectionEndpoints(this WebApplication app)
|
public static void MapCollectionEndpoints(this WebApplication app)
|
||||||
{
|
{
|
||||||
var group = app.MapGroup("/api/collection")
|
var group = app.MapGroup("/api/collection")
|
||||||
.WithName("Collection")
|
.WithName("Collection");
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
group.MapGet("/state", GetCollectionState)
|
group.MapGet("/state", GetCollectionState)
|
||||||
.WithName("GetCollectionState")
|
.WithName("GetCollectionState")
|
||||||
.WithOpenApi()
|
|
||||||
.Produces(200)
|
.Produces(200)
|
||||||
.Produces(500);
|
.Produces(500);
|
||||||
|
|
||||||
group.MapGet("/runs", GetRecentRuns)
|
group.MapGet("/runs", GetRecentRuns)
|
||||||
.WithName("GetRecentRuns")
|
.WithName("GetRecentRuns")
|
||||||
.WithOpenApi()
|
|
||||||
.Produces(200)
|
.Produces(200)
|
||||||
.Produces(500);
|
.Produces(500);
|
||||||
|
|
||||||
group.MapGet("/runs/{runId}/snapshots", GetRunSnapshots)
|
group.MapGet("/runs/{runId}/snapshots", GetRunSnapshots)
|
||||||
.WithName("GetRunSnapshots")
|
.WithName("GetRunSnapshots")
|
||||||
.WithOpenApi()
|
|
||||||
.Produces(200)
|
.Produces(200)
|
||||||
.Produces(404)
|
.Produces(404)
|
||||||
.Produces(500);
|
.Produces(500);
|
||||||
|
|
||||||
group.MapGet("/runs/{runId}/errors", GetRunErrors)
|
group.MapGet("/runs/{runId}/errors", GetRunErrors)
|
||||||
.WithName("GetRunErrors")
|
.WithName("GetRunErrors")
|
||||||
.WithOpenApi()
|
|
||||||
.Produces(200)
|
.Produces(200)
|
||||||
.Produces(404)
|
.Produces(404)
|
||||||
.Produces(500);
|
.Produces(500);
|
||||||
|
|
||||||
group.MapGet("/latest/{ticker}", GetLatestSnapshotsForTicker)
|
group.MapGet("/latest/{ticker}", GetLatestSnapshotsForTicker)
|
||||||
.WithName("GetLatestSnapshotsForTicker")
|
.WithName("GetLatestSnapshotsForTicker")
|
||||||
.WithOpenApi()
|
|
||||||
.Produces(200)
|
.Produces(200)
|
||||||
.Produces(500);
|
.Produces(500);
|
||||||
|
|
||||||
group.MapPost("/run", StartCollectionRun)
|
group.MapPost("/run", StartCollectionRun)
|
||||||
.WithName("StartCollectionRun")
|
.WithName("StartCollectionRun")
|
||||||
.WithOpenApi()
|
|
||||||
.Produces(202)
|
.Produces(202)
|
||||||
.Produces(500);
|
.Produces(500);
|
||||||
}
|
}
|
||||||
@@ -57,7 +50,7 @@ public static class CollectionEndpoints
|
|||||||
var state = await repo.GetDashboardStateAsync();
|
var state = await repo.GetDashboardStateAsync();
|
||||||
return Results.Ok(state);
|
return Results.Ok(state);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
return Results.StatusCode(500);
|
return Results.StatusCode(500);
|
||||||
}
|
}
|
||||||
@@ -70,7 +63,7 @@ public static class CollectionEndpoints
|
|||||||
var runs = await repo.GetRecentRunsAsync(limit);
|
var runs = await repo.GetRecentRunsAsync(limit);
|
||||||
return Results.Ok(new { runs, count = runs.Count });
|
return Results.Ok(new { runs, count = runs.Count });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
return Results.StatusCode(500);
|
return Results.StatusCode(500);
|
||||||
}
|
}
|
||||||
@@ -83,7 +76,7 @@ public static class CollectionEndpoints
|
|||||||
var snapshots = await repo.GetRunSnapshotsAsync(runId);
|
var snapshots = await repo.GetRunSnapshotsAsync(runId);
|
||||||
return Results.Ok(new { runId, snapshots, count = snapshots.Count });
|
return Results.Ok(new { runId, snapshots, count = snapshots.Count });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
return Results.StatusCode(500);
|
return Results.StatusCode(500);
|
||||||
}
|
}
|
||||||
@@ -96,7 +89,7 @@ public static class CollectionEndpoints
|
|||||||
var errors = await repo.GetRunErrorsAsync(runId, limit);
|
var errors = await repo.GetRunErrorsAsync(runId, limit);
|
||||||
return Results.Ok(new { runId, errors, count = errors.Count });
|
return Results.Ok(new { runId, errors, count = errors.Count });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
return Results.StatusCode(500);
|
return Results.StatusCode(500);
|
||||||
}
|
}
|
||||||
@@ -109,7 +102,7 @@ public static class CollectionEndpoints
|
|||||||
var snapshots = await repo.GetLatestSnapshotsForTickerAsync(ticker, limit);
|
var snapshots = await repo.GetLatestSnapshotsForTickerAsync(ticker, limit);
|
||||||
return Results.Ok(new { ticker, snapshots, count = snapshots.Count });
|
return Results.Ok(new { ticker, snapshots, count = snapshots.Count });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
return Results.StatusCode(500);
|
return Results.StatusCode(500);
|
||||||
}
|
}
|
||||||
@@ -170,7 +163,7 @@ public static class CollectionEndpoints
|
|||||||
startedAt = now
|
startedAt = now
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
return Results.StatusCode(500);
|
return Results.StatusCode(500);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,19 +107,58 @@ public class PlaceholderTokenCache : ITokenCache
|
|||||||
|
|
||||||
public class PlaceholderKisApiClient : IKisApiClient
|
public class PlaceholderKisApiClient : IKisApiClient
|
||||||
{
|
{
|
||||||
// Placeholder: To be implemented with actual KIS API calls
|
public Task<Dictionary<string, object>> GetCurrentPriceAsync(string code, string account = "mock")
|
||||||
public Task<string?> GetAccessTokenAsync(string account = "mock")
|
|
||||||
{
|
{
|
||||||
return Task.FromResult<string?>("placeholder_token");
|
return Task.FromResult(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "code", code },
|
||||||
|
{ "price", 0 },
|
||||||
|
{ "change", 0 },
|
||||||
|
{ "changeRate", 0 }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<dynamic?> GetQuotationAsync(string ticker, string account = "mock")
|
public Task<Dictionary<string, object>> GetAskingPrice10LevelAsync(string code, string account = "mock")
|
||||||
{
|
{
|
||||||
return Task.FromResult<dynamic?>(null);
|
return Task.FromResult(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "code", code },
|
||||||
|
{ "askLevels", new List<object>() },
|
||||||
|
{ "bidLevels", new List<object>() }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<dynamic?> GetRankingAsync(string sort = "price", int limit = 10, string account = "mock")
|
public Task<Dictionary<string, object>> GetDailyShortSaleAsync(string code, string startDate, string endDate, string account = "mock")
|
||||||
{
|
{
|
||||||
return Task.FromResult<dynamic?>(null);
|
return Task.FromResult(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "code", code },
|
||||||
|
{ "startDate", startDate },
|
||||||
|
{ "endDate", endDate },
|
||||||
|
{ "data", new List<object>() }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Dictionary<string, object>> GetDailyItemChartPriceAsync(string code, string startDate, string endDate, string period = "D", string account = "mock")
|
||||||
|
{
|
||||||
|
return Task.FromResult(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "code", code },
|
||||||
|
{ "startDate", startDate },
|
||||||
|
{ "endDate", endDate },
|
||||||
|
{ "period", period },
|
||||||
|
{ "candles", new List<object>() }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Dictionary<string, object>> GetInvestorTrendAsync(string code, string account = "mock")
|
||||||
|
{
|
||||||
|
return Task.FromResult(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "code", code },
|
||||||
|
{ "individual", 0 },
|
||||||
|
{ "foreign", 0 },
|
||||||
|
{ "institution", 0 }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Text.Json;
|
|||||||
using Microsoft.FluentUI.AspNetCore.Components;
|
using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using QuantEngine.Web.Infrastructure;
|
using QuantEngine.Web.Infrastructure;
|
||||||
|
using QuantEngine.Web.Endpoints;
|
||||||
|
|
||||||
// Serilog Configuration with Telegram Sink
|
// Serilog Configuration with Telegram Sink
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
@@ -63,7 +64,6 @@ app.MapRazorComponents<App>()
|
|||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
// Collection API Endpoints
|
// Collection API Endpoints
|
||||||
using QuantEngine.Web.Endpoints;
|
|
||||||
app.MapCollectionEndpoints();
|
app.MapCollectionEndpoints();
|
||||||
|
|
||||||
app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) =>
|
app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user