137 lines
5.2 KiB
Kotlin
137 lines
5.2 KiB
Kotlin
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
|
||
}
|
||
} |