..
This commit is contained in:
@@ -10,6 +10,8 @@ import io.ktor.client.plugins.logging.LogLevel
|
||||
import io.ktor.client.plugins.logging.Logger
|
||||
import io.ktor.client.plugins.logging.Logging
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.client.statement.request
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -112,98 +114,73 @@ object KisTradeService {
|
||||
*/
|
||||
private suspend fun fetchDomesticRanking(type: RankingType): Result<List<RankingStock>> {
|
||||
val config = KisSession.config
|
||||
val jsonParser = Json {
|
||||
ignoreUnknownKeys = true
|
||||
coerceInputValues = true
|
||||
isLenient = true
|
||||
}
|
||||
|
||||
return try {
|
||||
val response = client.get("$prodUrl${type.path}") {
|
||||
val response = client.get("${prodUrl}${type.path}") {
|
||||
header("authorization", "Bearer ${config.marketToken}")
|
||||
header("appkey", config.realAppKey)
|
||||
header("appsecret", config.realSecretKey)
|
||||
header("tr_id", type.trId)
|
||||
header("custtype", "P")
|
||||
header("Accept", "application/json")
|
||||
|
||||
parameter("FID_COND_MRKT_DIV_CODE", "J")
|
||||
parameter("FID_COND_SCR_DIV_CODE", type.scrNo)
|
||||
parameter("FID_INPUT_ISCD", "0000") // 전체 시장
|
||||
parameter("FID_DIV_CLS_CODE", "0") // 전체
|
||||
// [Step 1] 파라미터 기본값 재설정 (성공했던 원본 코드 기준)
|
||||
val params = mutableMapOf(
|
||||
"FID_COND_MRKT_DIV_CODE" to "J",
|
||||
"FID_COND_SCR_DIV_CODE" to type.scrNo,
|
||||
"FID_INPUT_ISCD" to "0000",
|
||||
"FID_DIV_CLS_CODE" to "0",
|
||||
"FID_BLNG_CLS_CODE" to "0",
|
||||
"FID_RANK_SORT_CLS_CODE" to "0",
|
||||
// "FID_MK_OP_CLS_CODE" 삭제! (이게 있으면 RISE/FALL이 깨짐)
|
||||
"FID_PRC_CLS_CODE" to "0",
|
||||
"FID_INPUT_CNT_1" to "0", // 1이 아니라 0이어야 함
|
||||
"FID_INPUT_PRICE_1" to "",
|
||||
"FID_INPUT_PRICE_2" to "",
|
||||
"FID_VOL_CNT" to "",
|
||||
"FID_RSFL_RATE1" to "",
|
||||
"FID_RSFL_RATE2" to "", // 누락되었던 파라미터 추가
|
||||
"FID_INPUT_DATE_1" to "",
|
||||
"FID_INPUT_DATE_2" to "",
|
||||
"FID_APLY_RANG_VOL" to "",
|
||||
"FID_APLY_RANG_PRC_1" to "",
|
||||
"FID_APLY_RANG_PRC_2" to "",
|
||||
"FID_PERIOD_DIV_CODE" to "",
|
||||
"FID_SELECT_DIV_CODE" to "",
|
||||
"FID_INPUT_OPTION_1" to "",
|
||||
"FID_INPUT_OPTION_2" to "",
|
||||
"FID_TRGT_CLS_CODE" to "11111111",
|
||||
"FID_TRGT_EXLS_CLS_CODE" to "000000"
|
||||
)
|
||||
|
||||
parameter("FID_ETC_CLS_CODE", "0")
|
||||
parameter("FID_PRC_CLS_CODE", "0")
|
||||
when(type) {
|
||||
RankingType.VALUE -> {
|
||||
parameter("FID_BLNG_CLS_CODE", type.sortCode)
|
||||
}
|
||||
RankingType.VOLUME -> {
|
||||
parameter("FID_BLNG_CLS_CODE",type.sortCode)
|
||||
}
|
||||
RankingType.FALL -> {
|
||||
parameter("FID_RANK_SORT_CLS_CODE", type.sortCode)
|
||||
// [Step 2] Enum 특화 설정 덮어쓰기
|
||||
params.putAll(type.extraParams)
|
||||
|
||||
}
|
||||
RankingType.RISE -> {
|
||||
parameter("FID_RANK_SORT_CLS_CODE", type.sortCode)
|
||||
}
|
||||
// RankingType.AFTER -> {
|
||||
// parameter("FID_MKOP_CLS_CODE", type.sortCode)
|
||||
// }
|
||||
// RankingType.BEFORE -> {
|
||||
// parameter("FID_MKOP_CLS_CODE", type.sortCode)
|
||||
// }
|
||||
// RankingType.FOREIGNER_BUY, RankingType.INSTITUTION_BUY -> {
|
||||
// parameter("FID_BLNG_CLS_CODE", type.sortCode) // 순매수용
|
||||
// parameter("FID_INPUT_CNT_1", "5") // 5일 누적 등 조정 가능
|
||||
// }
|
||||
// RankingType.PER_RANK, RankingType.PBR_RANK -> {
|
||||
// parameter("FID_FINCL_CLS_CODE", type.sortCode) // 재무비율용
|
||||
// }
|
||||
RankingType.AFTER_HOURS_VOLUME -> {
|
||||
parameter("FID_TIME_OUT_CLS_CODE", "1") // 시간외 구분
|
||||
}
|
||||
RankingType.EXPECTED_RISE -> parameter("FID_MK_OP_CLS_CODE", type.sortCode)
|
||||
// 체결강도/호가 등은 FID_EXEC_CLS_CODE 추가
|
||||
else -> parameter("FID_BLNG_CLS_CODE", "0") // 기본
|
||||
// else -> {
|
||||
//
|
||||
// }
|
||||
}
|
||||
parameter("FID_PBMN", "")
|
||||
parameter("FID_APLY_RANG_PRC_1", "")
|
||||
parameter("FID_TRGT_CLS_CODE", "11111111")
|
||||
parameter("FID_TRGT_EXLS_CLS_CODE", "000000")
|
||||
parameter("FID_RSFL_RATE2", "")
|
||||
parameter("FID_RSFL_RATE1", "")
|
||||
parameter("FID_INPUT_CNT_1", "0")
|
||||
parameter("FID_INPUT_PRICE_1", "")
|
||||
parameter("FID_INPUT_PRICE_2", "")
|
||||
parameter("FID_VOL_CNT", "")
|
||||
parameter("FID_INPUT_DATE_1", "")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 상승/하락률 순위(HHPST01710000)일 경우 추가 파라미터
|
||||
if (type.trId == "HHPST01710000") {
|
||||
parameter("fid_diff_div_code", "00") // 00: 전일 대비
|
||||
}
|
||||
// [Step 3] 적용
|
||||
params.forEach { (key, value) -> parameter(key, value) }
|
||||
}
|
||||
val body = response.body<RankingResponse>()
|
||||
if (listOf(
|
||||
RankingType.VOLUME_POWER,
|
||||
RankingType.EXPECTED_RISE,
|
||||
RankingType.COMPANY_TRADE
|
||||
).contains(type)) {
|
||||
println("${type.name} , ${body}" )
|
||||
|
||||
val rawJson = response.bodyAsText()
|
||||
val body = try {
|
||||
jsonParser.decodeFromString<RankingResponse>(rawJson)
|
||||
} catch (e: Exception) {
|
||||
println("[ERROR] ${type.title} 파싱 실패. Raw: $rawJson")
|
||||
return Result.failure(e)
|
||||
}
|
||||
|
||||
if (body.rt_cd == "0") {
|
||||
Result.success(body.list ?: emptyList())
|
||||
} else {
|
||||
// rt_cd가 비어있다면 에러 메시지도 없을 수 있으므로 Raw Body 일부를 포함하여 예외 처리
|
||||
val errorMsg = if (body.msg1.isNotBlank()) body.msg1 else "응답 코드 없음 (Raw: $rawJson)"
|
||||
Result.failure(Exception(errorMsg))
|
||||
}
|
||||
if (body.rt_cd == "0") Result.success(body.list) else Result.failure(Exception(body.msg1))
|
||||
} catch (e: Exception) {
|
||||
if (listOf(
|
||||
RankingType.VOLUME_POWER,
|
||||
RankingType.EXPECTED_RISE,
|
||||
RankingType.COMPANY_TRADE
|
||||
).contains(type)) {
|
||||
println("${type.name} , ${e.message}" )
|
||||
}
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.http.ContentType.Application.Json
|
||||
import io.ktor.http.Url
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.serialization.json.Json
|
||||
import model.DartFinancialResponse
|
||||
@@ -36,17 +37,29 @@ object NewsService {
|
||||
suspend fun fetchAndIngestNews(corpInfo: CorpInfo) {
|
||||
val clientId = "CqXQXHO3h0kqtYsXkePY" // 설정에서 가져오도록 수정 필요
|
||||
val clientSecret = "DODCxb1M4Z"
|
||||
var qlist = listOf<String>("${corpInfo.stockName} 분석","${corpInfo.stockName}[${corpInfo.stockCode}]", "${corpInfo.cName} 최근 동향", "${corpInfo.cName}")
|
||||
qlist.forEach { query ->
|
||||
val qlistNews = listOf(
|
||||
"${corpInfo.stockName} 주가",
|
||||
"${corpInfo.stockName} 실적",
|
||||
"${corpInfo.stockName} 공시",
|
||||
"${corpInfo.stockName} 이벤트"
|
||||
)
|
||||
|
||||
val qlistCorpTrend = listOf(
|
||||
"${corpInfo.cName} 최근 동향",
|
||||
"${corpInfo.cName} 이슈",
|
||||
"${corpInfo.cName} 투자",
|
||||
"${corpInfo.cName} 실적"
|
||||
)
|
||||
(qlistNews + qlistCorpTrend).forEach { query ->
|
||||
try {
|
||||
val response: NaverNewsResponse = client.get("https://openapi.naver.com/v1/search/news.json") {
|
||||
parameter("query", query)
|
||||
parameter("display", 3) // 최근 10개 뉴스
|
||||
parameter("sort", "sim") // 유사도 순 (또는 date 발간순)
|
||||
parameter("display", 5) // 최근 10개 뉴스
|
||||
parameter("sort", "date") // 유사도 순 (또는 date 발간순)
|
||||
header("X-Naver-Client-Id", clientId)
|
||||
header("X-Naver-Client-Secret", clientSecret)
|
||||
}.body()
|
||||
SafeScraper.scrapeParallel(corpInfo,response.items)
|
||||
SafeScraper.scrapeParallel(corpInfo,response.items.sortedBy { it.pubDate }.distinctBy { Url(it.originallink).host }.take(5) )
|
||||
} catch (e: Exception) {
|
||||
println("❌ 뉴스 가져오기 실패: ${e.message}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user