..
This commit is contained in:
@@ -4,6 +4,7 @@ import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.util.zip.ZipInputStream
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
@@ -11,6 +12,17 @@ object DartCodeManager {
|
||||
private val corpCodeMap = mutableMapOf<String, String>()
|
||||
private const val DART_API_KEY = "61143d2af0759f6c28ce372d9e339d1e01687abc" // 지범님의 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}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 앱 실행 시 호출하여 매핑 테이블 업데이트
|
||||
*/
|
||||
@@ -21,11 +33,14 @@ object DartCodeManager {
|
||||
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
|
||||
}
|
||||
@@ -48,7 +63,7 @@ object DartCodeManager {
|
||||
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 ?: ""
|
||||
|
||||
println("stockCode: $stockCode , corpCode: $corpCode")
|
||||
// 종목코드(stock_code)가 있는 상장사만 매핑에 추가
|
||||
if (stockCode.isNotEmpty()) {
|
||||
corpCodeMap[stockCode] = corpCode
|
||||
@@ -60,6 +75,18 @@ object DartCodeManager {
|
||||
* 6자리 종목코드로 8자리 법인코드 반환
|
||||
*/
|
||||
fun getCorpCode(stockCode: String): String? {
|
||||
return corpCodeMap[stockCode]
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -452,7 +452,7 @@ object KisTradeService {
|
||||
else "/uapi/overseas-stock/v1/quotations/inquire-time-itemchartprice"
|
||||
val now = LocalTime.now().minusMinutes(30)
|
||||
val searchTime = if (now.isAfter(LocalTime.of(15, 30))) {
|
||||
"153000"
|
||||
"150000"
|
||||
} else {
|
||||
now.format(DateTimeFormatter.ofPattern("HHmmss"))
|
||||
}
|
||||
|
||||
@@ -69,7 +69,8 @@ object RagService {
|
||||
// 1. 10분간의 데이터 가져오기 (API 호출)
|
||||
coroutineScope {
|
||||
var tradingDecision : TradingDecision = TradingDecision()
|
||||
val financialDataDeferred = async { NewsService.fetchFinancialGrowth(DartCodeManager.getCorpCode(stockCode)) }
|
||||
val corpCode = DartCodeManager.getCorpCode(stockCode)
|
||||
val financialDataDeferred = async { NewsService.fetchFinancialGrowth(corpCode) }
|
||||
|
||||
tradingDecision.financialData = financialDataDeferred.await()
|
||||
result(tradingDecision.toString(),false)
|
||||
@@ -148,19 +149,38 @@ object RagService {
|
||||
financialData: String
|
||||
): TradingDecision? {
|
||||
val prompt = """
|
||||
당신은 단기 데이트레이딩 전문가입니다. 아래 데이터를 분석하여 '매수', '매도', '관망' 중 하나를 결정하세요.
|
||||
<|begin_of_text|><|start_header_id|>system<|end_header_id|>
|
||||
당신은 수치 기반의 '정량 분석(Quantitative Analysis)' 단기 데이트레이딩 전문가이자 전문 애널리스트입니다.
|
||||
제공된 데이터를 바탕으로 투자 기간별 스코어를 산출하고 최종 매매 결정을 내리십시오.
|
||||
아래 데이터를 분석하여 '매수', '매도', '관망' 중 하나를 결정하세요.
|
||||
|
||||
[종목]: $stockName
|
||||
[데이터 요약]
|
||||
- 종목: $stockName
|
||||
$techSummary
|
||||
[관련 뉴스]: $newsContext
|
||||
[재무 기초]: $financialData
|
||||
- 기업/재무: $financialData
|
||||
- 시장 심리: $newsContext
|
||||
|
||||
반드시 아래 JSON 형식으로만 답변하세요:
|
||||
[스코어 산출 가이드 (0-100)]
|
||||
1. 초단기: 30분봉 추세, MFI, OBV 에너지가 일치하면 80점 이상.
|
||||
2. 단기: 일봉 이평선 정배열 및 3일 변동률 양수일 때 70점 이상.
|
||||
3. 중기: 주봉 추세와 재무 성장성(매출/영익)이 동반 상승 시 75점 이상.
|
||||
4. 장기: 월봉 위치와 기업의 근본적인 시장 지배력 기반 판단.
|
||||
|
||||
[응답 형식]
|
||||
반드시 아래 JSON 형식으로만 답변하십시오:
|
||||
{
|
||||
"ultraShortScore": (숫자),
|
||||
"shortTermScore": (숫자),
|
||||
"midTermScore": (숫자),
|
||||
"longTermScore": (숫자),
|
||||
"decision": "BUY" | "SELL" | "HOLD",
|
||||
"reason": "결정적 근거 한 줄",
|
||||
"confidence": 0~100
|
||||
}
|
||||
<|eot_id|>
|
||||
<|start_header_id|>user<|end_header_id|>
|
||||
모든 데이터를 종합하여 스코어링 리포트를 작성하십시오.
|
||||
<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||
""".trimIndent()
|
||||
|
||||
val response = chatModel.chat(UserMessage.from(prompt))
|
||||
@@ -183,6 +203,10 @@ object RagService {
|
||||
}
|
||||
@Serializable
|
||||
class TradingDecision {
|
||||
val ultraShortScore: Int = 0 // 초단기 (분봉/에너지)
|
||||
val shortTermScore: Int = 0 // 단기 (일봉/뉴스)
|
||||
val midTermScore: Int = 0 // 중기 (주봉/재무)
|
||||
val longTermScore: Int = 0
|
||||
var decision: String? = null
|
||||
var reason: String? = null
|
||||
var confidence: Int = 0
|
||||
@@ -191,6 +215,10 @@ class TradingDecision {
|
||||
var financialData : String? = null
|
||||
override fun toString(): String {
|
||||
return """
|
||||
ultraShortScore :$ultraShortScore
|
||||
shortTermScore :$shortTermScore
|
||||
midTermScore :$midTermScore
|
||||
longTermScore :$longTermScore
|
||||
decision: $decision
|
||||
reason: $reason
|
||||
confidence: $confidence
|
||||
|
||||
Reference in New Issue
Block a user