atrade/src/main/kotlin/network/DartCodeManager.kt

137 lines
5.2 KiB
Kotlin
Raw Normal View History

2026-01-22 16:21:18 +09:00
package network
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
2026-03-16 17:07:25 +09:00
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
2026-03-13 11:06:20 +09:00
import model.KisSession
2026-02-24 13:14:11 +09:00
import model.RankingStock
import service.AutoTradingManager
2026-01-22 16:21:18 +09:00
import java.io.ByteArrayInputStream
2026-01-22 17:56:31 +09:00
import java.io.File
2026-01-22 16:21:18 +09:00
import java.util.zip.ZipInputStream
import javax.xml.parsers.DocumentBuilderFactory
2026-03-16 17:07:25 +09:00
@Serializable
data class StockItem(
val code: String,
val name: String
){}
object StockUniverseLoader {
private val json = Json { ignoreUnknownKeys = true }
fun loadUniverse(filePath: String = "stocks_universe.json"): List<Pair<String, String>> {
return try {
val file = File(filePath)
if (!file.exists()) {
println("⚠️ 파일을 찾을 수 없습니다: ${file.absolutePath}")
return emptyList()
}
// 1. JSON 파일 읽기 및 역직렬화
val rawJson = file.readText()
val stockItems = json.decodeFromString<List<StockItem>>(rawJson)
// 2. Pair(코드, 이름) 튜플 리스트로 변환
stockItems.map { it.code to it.name }
} catch (e: Exception) {
println("❌ 유니버스 로드 실패: ${e.message}")
emptyList()
}
}
}
2026-01-23 17:05:09 +09:00
data class CorpInfo(
var cCode : String = "",
var cName : String = "",
var stockCode : String = "",
var stockName : String = "",
)
2026-01-22 16:21:18 +09:00
object DartCodeManager {
2026-01-23 17:05:09 +09:00
private val corpCodeMap = mutableMapOf<String, CorpInfo>()
2026-03-13 11:06:20 +09:00
private var DART_API_KEY = KisSession.config.dAppKey // 지범님의 API 키 입력
2026-01-22 16:21:18 +09:00
2026-01-22 17:56:31 +09:00
private fun saveXmlDebugFile(xmlBytes: ByteArray) {
try {
val debugFile = java.io.File("debug_CORPCODE.xml")
debugFile.writeBytes(xmlBytes)
println("💾 [디버그] XML 파일 저장 완료: ${debugFile.absolutePath}")
// M3 Pro 환경에서는 파일 쓰기가 거의 즉시 완료됩니다.
} catch (e: Exception) {
println("⚠️ [디버그] 파일 저장 실패: ${e.message}")
}
}
2026-01-22 16:21:18 +09:00
/**
* 실행 호출하여 매핑 테이블 업데이트
*/
suspend fun updateCorpCodes(client: HttpClient) {
println("📂 [DART] 법인코드 매핑 데이터 업데이트 시작...")
try {
val url = "https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key=$DART_API_KEY"
val response: HttpResponse = client.get(url)
val zipBytes = response.readBytes()
2026-02-10 15:08:52 +09:00
// val zipFile = File("dart_corp_codes.zip")
// zipFile.writeBytes(zipBytes)
// println("💾 [디버그] 원본 ZIP 저장 완료: ${zipFile.absolutePath} (${zipBytes.size} bytes)")
2026-01-22 16:21:18 +09:00
ZipInputStream(ByteArrayInputStream(zipBytes)).use { zis ->
var entry = zis.nextEntry
while (entry != null) {
if (entry.name == "CORPCODE.xml") {
2026-02-10 15:08:52 +09:00
// saveXmlDebugFile(zipBytes)
2026-01-22 16:21:18 +09:00
parseXml(zis.readAllBytes())
break
}
entry = zis.nextEntry
}
}
println("✅ [DART] 매핑 완료: ${corpCodeMap.size}개의 상장사 로드됨")
} catch (e: Exception) {
println("❌ [DART] 법인코드 업데이트 실패: ${e.message}")
}
}
private fun parseXml(xmlBytes: ByteArray) {
val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()
val doc = builder.parse(ByteArrayInputStream(xmlBytes))
val nodeList = doc.getElementsByTagName("list")
for (i in 0 until nodeList.length) {
val element = nodeList.item(i) as org.w3c.dom.Element
val stockCode = element.getElementsByTagName("stock_code").item(0)?.textContent?.trim() ?: ""
val corpCode = element.getElementsByTagName("corp_code").item(0)?.textContent ?: ""
2026-01-23 17:05:09 +09:00
val corpName = element.getElementsByTagName("corp_name").item(0)?.textContent ?: ""
// println("[$corpName]stockCode: $stockCode , corpCode: $corpCode")
2026-01-22 16:21:18 +09:00
// 종목코드(stock_code)가 있는 상장사만 매핑에 추가
if (stockCode.isNotEmpty()) {
2026-01-23 17:05:09 +09:00
corpCodeMap[stockCode] = CorpInfo(corpCode, corpName, stockCode)
2026-02-24 13:14:11 +09:00
// AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode, hts_kor_isnm = corpName))
2026-01-22 16:21:18 +09:00
}
}
}
2026-03-16 17:07:25 +09:00
fun getStockCodez() : Array<String> = corpCodeMap.keys.toTypedArray()
2026-01-22 16:21:18 +09:00
/**
* 6자리 종목코드로 8자리 법인코드 반환
*/
2026-01-23 17:05:09 +09:00
fun getCorpCode(stockCode: String): CorpInfo? {
2026-01-22 17:56:31 +09:00
// 1. 직접 매칭 시도
corpCodeMap[stockCode]?.let { return it }
// 2. 우선주 규칙 적용 (마지막 자리가 5, 7, 9인 경우 0으로 변경)
if (stockCode.length == 6 && stockCode.last() in listOf('5', '7', '9')) {
val commonStockCode = stockCode.substring(0, 5) + "0"
corpCodeMap[commonStockCode]?.let {
println(" [DART] 우선주($stockCode)를 보통주($commonStockCode) 코드로 매핑 성공")
return it
}
}
return null
2026-01-22 16:21:18 +09:00
}
}