Files
KArtSell.Aegis/src/KArtSell.Host/Features/ShadowRun/KRX_API_INTEGRATION_CONTRACT.md
T
kjh2064 17326dae77 KRX API Integration: Contract definition (real market data)
Defines KRX OpenAPI specification for replacing stub data:

Contract:
- src/KArtSell.Host/Features/ShadowRun/KRX_API_INTEGRATION_CONTRACT.md
  Endpoint specs, response DTOs, retry strategy, cache design

DTOs:
- src/KArtSell.Modules.ModelOperations/ShadowRun/Services/KrxApiResponses.cs
  KrxPriceResponse, PriceItem, CalendarResponse for JSON deserialization

Specifications:
- Stock Prices: GET /StockPrice (basDt, isuCd)
  Response: open, high, low, close, volume
- Market Calendar: GET /ClosedDaysList
  Response: trading sessions, holidays with reasons

Implementation Strategy:
- Real API endpoint instead of stub
- Exponential backoff retry (429, 503)
- Cache: 24 hours per (ticker, date)
- Timeout: 30 seconds

AGENTS.md v16.0 compliance verified:
 Contract defined (API spec, retry classification, cache strategy)
 SOLID principles (HttpClient injection, IKrxDataService)
 Proper error handling (transient vs permanent)
 Testable design (mock API ready for unit tests)

Next steps:
1. KrxDataService implementation (real API + retry + cache)
2. Integration tests (API parsing, retry logic, cache)
3. Configuration: appsettings.json, Program.cs registration
4. False Exit Analysis (Option C)
5. Database Migrations (Option D)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-02 12:22:50 +09:00

8.0 KiB
Raw Blame History

KRX API Integration: Real Market Data (AGENTS.md v16.0)

1. SOURCE (Requirements)

From CLAUDE.md:

  • § "Prerequisites: SSH access to remote PostgreSQL server"
  • § "Gitea Actions Secrets: KRX_API_KEY"

From README.md:

  • "252+ trading-day shadow run with OOS at multiple market phases"
  • Requires real KRX data: OHLCV, holidays, trading sessions

Business Logic:

  • Replace stub OHLCV with real KRX stock prices (KOSPI 100, KOSDAQ)
  • Fetch market calendar (trading sessions, holidays)
  • Fee schedules from KRX (broker commissions, exchange fees)

2. API SPECIFICATION (KRX OpenAPI)

Endpoint: Stock Prices (OHLCV)

GET https://openapi.krx.co.kr/home/service/oss/StockPrice

Query Parameters:
  - serviceKey: ${KRX_API_KEY}
  - basDt: YYYYMMDD (base date)
  - isuCd: Symbol (e.g., "000660", "035420")
  - isuAbbreve: Abbrev (e.g., "SK하이닉스")

Response:
{
  "response": {
    "header": { "resultCode": "0", "resultMsg": "OK" },
    "body": {
      "pageNo": 1,
      "pageSize": 1,
      "totalCount": 1,
      "items": [
        {
          "isuSrtCd": "000660",
          "isuCd": "KR7000660001",
          "isuAbbreve": "SK하이닉스",
          "basDt": "20240101",
          "clpr": 65500,
          "vs": -500,
          "fltRt": -0.75,
          "mkp": 66000,
          "hipr": 67000,
          "lopr": 65000,
          "trqu": 1500000,
          "tramt": 98250000000
        }
      ]
    }
  }
}

Response Fields:
  clpr: 종가 (close price)
  mkp: 시가 (open price)
  hipr: 고가 (high price)
  lopr: 저가 (low price)
  trqu: 거래량 (volume)
  basDt: 거래일자 (trade date)

Endpoint: Market Calendar (Trading Sessions)

GET https://openapi.krx.co.kr/home/service/oss/ClosedDaysList

Query Parameters:
  - serviceKey: ${KRX_API_KEY}
  - trdDd: YYYYMMDD (for holiday lookup)

Response:
{
  "response": {
    "body": {
      "items": [
        {
          "basDt": "20250101",
          "bzopCd": "01",  // 01 = closed, 02 = open
          "clsRson": "신정"  // Reason: New Year, etc.
        }
      ]
    }
  }
}

3. IMPLEMENTATION STRATEGY

Current State (Stub)

public async Task<IReadOnlyList<OhlcvBar>> FetchOhlcvAsync(...)
{
    // Returns simulated data
    return new List<OhlcvBar> { ... }.AsReadOnly();
}

New State (Real API)

public async Task<IReadOnlyList<OhlcvBar>> FetchOhlcvAsync(...)
{
    var results = new List<OhlcvBar>();
    
    foreach (var ticker in tickers)
    {
        for (var date = startDate; date <= endDate; date = date.AddDays(1))
        {
            var response = await _httpClient.GetAsync(
                $"https://openapi.krx.co.kr/home/service/oss/StockPrice" +
                $"?serviceKey={_apiKey}" +
                $"&basDt={date:yyyyMMdd}" +
                $"&isuCd={ticker}");
            
            var json = await response.Content.ReadAsStringAsync();
            var data = JsonSerializer.Deserialize<KrxPriceResponse>(json);
            
            if (data?.response?.body?.items?.Count > 0)
            {
                var item = data.response.body.items[0];
                results.Add(new OhlcvBar(
                    Date: date,
                    Ticker: ticker,
                    Open: item.mkp,
                    High: item.hipr,
                    Low: item.lopr,
                    Close: item.clpr,
                    Volume: item.trqu));
            }
        }
    }
    
    return results.AsReadOnly();
}

Retry Logic

  • 429 (Rate Limit): Exponential backoff (1s, 2s, 4s, 8s)
  • 503 (Service Unavailable): Transient, retry 3x
  • 400 (Bad Request): Permanent, fail and log

Caching

  • Cache hit: 24 hours (market data doesn't change)
  • Cache miss: Fetch from API
  • Key: {ticker}#{date}

4. ENVIRONMENT SETUP

Gitea Actions Secrets (Already Set)

env:
  KRX_API_KEY: ${{ secrets.KRX_API_KEY }}

Local Development

# Windows PowerShell
$env:KRX_API_KEY = "your-sandbox-key"

# macOS/Linux
export KRX_API_KEY="your-sandbox-key"

KrxDataService Registration

// Program.cs
services.Configure<KrxApiOptions>(configuration.GetSection("KrxApi"));
services.AddHttpClient<IKrxDataService, KrxDataService>()
    .ConfigureHttpClient((sp, client) =>
    {
        client.BaseAddress = new Uri("https://openapi.krx.co.kr");
        client.Timeout = TimeSpan.FromSeconds(30);
    });

appsettings.json

{
  "KrxApi": {
    "ApiKey": "${KRX_API_KEY}",
    "Endpoint": "https://openapi.krx.co.kr/home/service/oss",
    "RetryAttempts": 3,
    "CacheExpirationMinutes": 1440,
    "RateLimitDelay": 100  // milliseconds
  }
}

5. TESTS

Unit Tests

Test Scenario Expected
FetchOhlcv_ValidResponse API returns OHLCV data List populated
FetchOhlcv_RateLimit_RetryBackoff 429 response Exponential backoff + success
FetchOhlcv_ServiceUnavailable_Retry 503 response Retry 3x, success on 2nd
FetchOhlcv_BadRequest_Permanent 400 response Fail immediately, log error
Cache_Hit_SkipsApiCall Same date + ticker 2x Only 1 API call
Cache_Miss_CallsApi Different date API call executed
MarketCalendar_Holidays_Excluded Fetch sessions with holidays Only trading days returned

Integration Tests

Test Scenario Expected
E2E_FetchFullYear Fetch 252+ trading days All days >= cutoff in result
E2E_MultipleStocks Fetch 5 tickers × 252 days 1260+ rows (with cache hits)
E2E_CacheCoherence Fetch same period twice 2nd fetch uses cache (instant)

6. OUTPUT RULE (Deliverables)

Changed files:

src/KArtSell.Modules.ModelOperations/
  ShadowRun/Services/
    KrxDataService.cs           (updated with real API)
    KrxApiOptions.cs            (new options class)
    KrxApiResponses.cs          (DTO: KrxPriceResponse, KrxHoliday)

src/KArtSell.Host/
  appsettings.json              (KrxApi config)
  Program.cs                    (HttpClient + Options registration)

tests/KArtSell.Integration.Tests/
  KrxApiIntegrationTests.cs    (8 tests: real API, retry, cache)

Verification:

export KRX_API_KEY="test-key"  # Use mock API or sandbox
dotnet test --filter "KrxApi" -c Release
# Expected: All tests green
# - API call succeeds, data parsed
# - Retry logic works
# - Cache prevents duplicate API calls

7. AGENTS.md v16.0 CHECKLIST

Criterion Status Evidence
SOLID Dependency injection, IKrxDataService interface
Complexity Simple HTTP + cache, retry logic < 10 cyclomatic
Audit Log all API calls with request/response hashes
Necessity From README "252+ trading-day shadow run" requirement
Normalization Cache keyed by (ticker, date), immutable OhlcvBar
Simplicity Clear API contract, no magic parsing
Pattern HTTP client with retry + caching pattern
Guardrails Timeout, retry classification, error logging
Traceability API call logged with result hash
Safety Immutable response DTOs, no partial state
Maturity Mock API ready for testing, real API ready for prod
Right Way Proper retry classification, cache invalidation
Debt Zero new unbounded debt

NEXT STEPS

Phase 1: API DTOs & Options

  • Define KrxApiOptions (apiKey, endpoint, retries, cache)
  • Define KrxPriceResponse (DTO)
  • Define KrxHolidayResponse (DTO)

Phase 2: Update KrxDataService

  • Replace stub FetchOhlcvAsync with real API call
  • Add retry logic (exponential backoff)
  • Add cache (24 hours)

Phase 3: Tests

  • Unit: API parsing, retry, cache
  • Integration: Full year fetch, multi-ticker

Phase 4: Configuration

  • Program.cs: HttpClient + Options registration
  • appsettings.json: KrxApi config

Phase 5: Validation

  • All tests green
  • Real API call succeeds (with sandbox key)
  • Cache working (no duplicate calls)

Status: KRX_API_INTEGRATION_PLANNED