atrade/src/main/kotlin/network/DartCodeManager.kt
2026-03-16 17:07:25 +09:00

137 lines
5.2 KiB
Kotlin
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package network
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import model.KisSession
import model.RankingStock
import service.AutoTradingManager
import java.io.ByteArrayInputStream
import java.io.File
import java.util.zip.ZipInputStream
import javax.xml.parsers.DocumentBuilderFactory
@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()
}
}
}
data class CorpInfo(
var cCode : String = "",
var cName : String = "",
var stockCode : String = "",
var stockName : String = "",
)
object DartCodeManager {
private val corpCodeMap = mutableMapOf<String, CorpInfo>()
private var DART_API_KEY = KisSession.config.dAppKey // 지범님의 API 키 입력
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}")
}
}
/**
* 앱 실행 시 호출하여 매핑 테이블 업데이트
*/
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()
// val zipFile = File("dart_corp_codes.zip")
// zipFile.writeBytes(zipBytes)
// println("💾 [디버그] 원본 ZIP 저장 완료: ${zipFile.absolutePath} (${zipBytes.size} bytes)")
ZipInputStream(ByteArrayInputStream(zipBytes)).use { zis ->
var entry = zis.nextEntry
while (entry != null) {
if (entry.name == "CORPCODE.xml") {
// saveXmlDebugFile(zipBytes)
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 ?: ""
val corpName = element.getElementsByTagName("corp_name").item(0)?.textContent ?: ""
// println("[$corpName]stockCode: $stockCode , corpCode: $corpCode")
// 종목코드(stock_code)가 있는 상장사만 매핑에 추가
if (stockCode.isNotEmpty()) {
corpCodeMap[stockCode] = CorpInfo(corpCode, corpName, stockCode)
// AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode, hts_kor_isnm = corpName))
}
}
}
fun getStockCodez() : Array<String> = corpCodeMap.keys.toTypedArray()
/**
* 6자리 종목코드로 8자리 법인코드 반환
*/
fun getCorpCode(stockCode: String): CorpInfo? {
// 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
}
}