This commit is contained in:
2026-02-19 15:47:31 +09:00
parent df124612ab
commit c4f58f159a
11 changed files with 609 additions and 113 deletions
@@ -43,7 +43,6 @@ class KisAuthService {
*/
suspend fun refreshAllTokens(): Boolean = coroutineScope {
val config = KisSession.config
println("refreshAllTokens")
// 1. 실전 시세용 토큰 발급 (Market Token)
val marketTokenJob = async { fetchAccessToken(config.realAppKey, config.realSecretKey, false) }
@@ -75,7 +74,6 @@ class KisAuthService {
private suspend fun fetchAccessToken(appKey: String, secretKey: String, isSim: Boolean): Result<TokenResponse> {
return try {
println("fetchAccessToken")
val response = client.post("${getBaseUrl(isSim)}/oauth2/tokenP") {
contentType(ContentType.Application.Json)
setBody(TokenRequest("client_credentials", appKey, secretKey))
+64 -29
View File
@@ -21,6 +21,7 @@ import model.StockBalanceResponse
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
@@ -68,7 +69,11 @@ object KisTradeService {
code = it.pdno, name = it.prdt_name, quantity = it.hldg_qty,
avgPrice = it.pchs_avg_pric, currentPrice = it.prpr,
profitRate = it.evlu_pfls_rt, evalAmount = it.evlu_amt, isDomestic = true
))
).apply {
if (it.hldg_qty.toLong() > 0) {
println("보유 종목 : ${it.prdt_name} , 수량 : ${it.hldg_qty}")
}
})
}
// 해외 종목 매핑 (해외 API 응답 모델 구조에 따라 필드 매핑)
@@ -437,7 +442,7 @@ object KisTradeService {
if (response.status == HttpStatusCode.OK) {
val approvalKey = response.body<Map<String, String>>()["approval_key"]
if (approvalKey != null) {
KisSession.config = KisSession.config.copy(websocketToken = approvalKey)
KisSession.config = KisSession.config.copy(websocketToken = approvalKey,)
true
} else false
} else false
@@ -500,35 +505,65 @@ object KisTradeService {
// --- 내부 Raw 호출용 (통합 잔고에서 사용) ---
private suspend fun fetchDomesticRawBalance(): Result<StockBalanceResponse> {
val config = KisSession.config
val baseUrl = prodUrl
val trId = "TTTC8434R"
var pureAccount = config.accountNo.replace("-", "").trim()
if (pureAccount.length == 8) pureAccount += "01"
val config = KisSession.config
val baseUrl = prodUrl
val trId = "TTTC8434R"
val cano = pureAccount.take(8)
val acntPrdtCd = pureAccount.takeLast(2)
return try {
val response = client.get("$baseUrl/uapi/domestic-stock/v1/trading/inquire-balance") {
header("authorization", "Bearer ${config.tradeToken}")
header("appkey", if (config.isSimulation) config.vtsAppKey else config.realAppKey)
header("appsecret", if (config.isSimulation) config.vtsSecretKey else config.realSecretKey)
header("tr_id", trId)
parameter("CANO", cano)
parameter("ACNT_PRDT_CD", acntPrdtCd)
parameter("AFHR_FLPR_YN", "N")
parameter("OFL_YN", "N")
parameter("INQR_DVSN", "0")
parameter("UNPR_DVSN", "01")
parameter("FUND_STTL_ICLD_YN", "N")
parameter("FNCG_AMT_AUTO_RDPT_YN", "N")
parameter("PRCS_DVSN", "00")
parameter("CTX_AREA_FK100", "")
parameter("CTX_AREA_NK100", "")
val allHoldings = mutableListOf<StockHolding>()
var totalBalance: StockBalanceResponse? = null
// 연속 조회를 위한 변수
var ctxAreaFk = ""
var ctxAreaNk = ""
var trCont = "N" // 'N': 최초 조회, 'F': 다음 조회, 'M': 연속 조회
try {
do {
val response = client.get("$baseUrl/uapi/domestic-stock/v1/trading/inquire-balance") {
header("authorization", "Bearer ${config.tradeToken}")
header("appkey", if (config.isSimulation) config.vtsAppKey else config.realAppKey)
header("appsecret", if (config.isSimulation) config.vtsSecretKey else config.realSecretKey)
header("tr_id", trId)
header("tr_cont", trCont) // 연속 조회 키 설정
val pureAccount = config.realAccountNo.replace("-", "").trim()
parameter("CANO", pureAccount.take(8))
parameter("ACNT_PRDT_CD", pureAccount.takeLast(2))
parameter("AFHR_FLPR_YN", "N")
parameter("OFL_YN", "N")
parameter("INQR_DVSN", "0")
parameter("UNPR_DVSN", "01")
parameter("FUND_STTL_ICLD_YN", "N")
parameter("FNCG_AMT_AUTO_RDPT_YN", "N")
parameter("PRCS_DVSN", "00")
// 연속 조회 파라미터 전달
parameter("CTX_AREA_FK100", ctxAreaFk)
parameter("CTX_AREA_NK100", ctxAreaNk)
}
val body = response.body<StockBalanceResponse>()
// 데이터 합치기
allHoldings.addAll(body.output1)
if (totalBalance == null) totalBalance = body
// 헤더에서 다음 조회를 위한 키값 추출
trCont = response.headers["tr_cont"] ?: "D" // 'D' 또는 'E'는 끝을 의미
ctxAreaFk = response.headers["ctx_area_fk100"] ?: ""
ctxAreaNk = response.headers["ctx_area_nk100"] ?: ""
delay(250)
} while (trCont == "F" || trCont == "M") // 연속 데이터가 있는 동안 반복
// 모든 데이터를 합친 최종 객체 반환
return if (totalBalance != null) {
Result.success(totalBalance.copy(output1 = allHoldings))
} else {
println(totalBalance.toString())
Result.failure(Exception("No data found"))
}
} catch (e: Exception) {
e.printStackTrace()
return Result.failure(e)
}
val body = response.body<StockBalanceResponse>()
Result.success(body)
} catch (e: Exception) { Result.failure(e) }
}
private suspend fun fetchOverseasRawBalance(): Result<StockBalanceResponse> {
+2 -1
View File
@@ -137,6 +137,7 @@ object RagService {
try {
var tradingDecision: TradingDecision = TradingDecision()
tradingDecision.stockCode = stockCode
tradingDecision.analyzer = technicalAnalyzer
tradingDecision.currentPrice = currentPrice
var corpInfo = DartCodeManager.getCorpCode(stockCode)
corpInfo?.stockName = stockName
@@ -358,7 +359,7 @@ class TradingDecision {
var techSummary : String? = null
var newsContext : String? = null
var financialData : String? = null
var analyzer : TechnicalAnalyzer? = null
fun shortPossible() =
listOf<Double>(ultraShortScore,