From 904e3011c94c36e59f43b88bcf1312aa485a6b3d Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Fri, 13 Feb 2026 13:49:40 +0900 Subject: [PATCH] ... --- src/main/kotlin/model/StockModels.kt | 7 ++++- src/main/kotlin/network/RagService.kt | 3 ++- src/main/kotlin/service/AutoTradingManager.kt | 26 ++++++++++--------- src/main/kotlin/ui/AiAnalysisView.kt | 2 +- src/main/kotlin/ui/IntegratedOrderSection.kt | 18 ++++++------- src/main/kotlin/ui/StockDetailArea.kt | 14 +++++----- 6 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/model/StockModels.kt b/src/main/kotlin/model/StockModels.kt index 2363bab..eb14495 100644 --- a/src/main/kotlin/model/StockModels.kt +++ b/src/main/kotlin/model/StockModels.kt @@ -66,7 +66,12 @@ enum class RankingType( mapOf("FID_RANK_SORT_CLS_CODE" to "2", "FID_TRGT_CLS_CODE" to "11111111")), VALUE("거래대금순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", mapOf("FID_RANK_SORT_CLS_CODE" to "3", "FID_TRGT_CLS_CODE" to "11111111")), - + VOLUME0("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", + mapOf("FID_RANK_SORT_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "11111111")), + VOLUME1("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", + mapOf("FID_RANK_SORT_CLS_CODE" to "1", "FID_TRGT_CLS_CODE" to "11111111")), + VOLUME4("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", + mapOf("FID_RANK_SORT_CLS_CODE" to "4", "FID_TRGT_CLS_CODE" to "11111111")), // [4] 기타 랭킹 (예상체결만 FID_MK_OP_CLS_CODE 필요) EXPECTED_RISE("예상상승", "FHPST01820000", "20182", "/uapi/domestic-stock/v1/ranking/exp-trans-updown", mapOf("FID_MK_OP_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "11111111")), diff --git a/src/main/kotlin/network/RagService.kt b/src/main/kotlin/network/RagService.kt index 83b704d..dc6a3c8 100644 --- a/src/main/kotlin/network/RagService.kt +++ b/src/main/kotlin/network/RagService.kt @@ -131,12 +131,13 @@ object RagService { } } - suspend fun processStock(technicalAnalyzer: TechnicalAnalyzer,stockName: String,stockCode: String,result : TradingDecisionCallback) { + suspend fun processStock(currentPrice : Double , technicalAnalyzer: TechnicalAnalyzer,stockName: String,stockCode: String,result : TradingDecisionCallback) { // 1. 10분간의 데이터 가져오기 (API 호출) coroutineScope { try { var tradingDecision: TradingDecision = TradingDecision() tradingDecision.stockCode = stockCode + tradingDecision.currentPrice = currentPrice var corpInfo = DartCodeManager.getCorpCode(stockCode) corpInfo?.stockName = stockName tradingDecision.stockName = stockName diff --git a/src/main/kotlin/service/AutoTradingManager.kt b/src/main/kotlin/service/AutoTradingManager.kt index 03eb2d8..fb550aa 100644 --- a/src/main/kotlin/service/AutoTradingManager.kt +++ b/src/main/kotlin/service/AutoTradingManager.kt @@ -49,7 +49,7 @@ object AutoTradingManager { fun isRunning(): Boolean = discoveryJob?.isActive == true private var remainingCandidates = mutableListOf() -// private val processedCodes = mutableSetOf() // 중복 처리 방지용 (선택 사항) + // private val processedCodes = mutableSetOf() // 중복 처리 방지용 (선택 사항) private val reanalysisList = mutableListOf() private val retryCountMap = mutableMapOf() /** @@ -102,16 +102,14 @@ object AutoTradingManager { if (remainingCandidates.isEmpty()) { val candidates: MutableList = fetchCandidates(tradeService).apply { println("후보군 총 개수 : $size") - } - .filter { (it.prdy_ctrt.toDoubleOrNull() ?: 0.0) in MIN_RISE_RATE..MAX_RISE_RATE } - .filter { it.code !in myHoldings && it.code !in pendingStocks } - .filter { !it.name.contains("호스팩", true) } + }.filter { !it.name.contains("호스팩", true) } .sortedBy { (it.prdy_ctrt.toDoubleOrNull() ?: 0.0) } .toMutableList() + if (reanalysisList.isNotEmpty()) { candidates.addAll(reanalysisList) } - remainingCandidates.addAll(candidates.distinctBy { it.code }) + remainingCandidates.addAll(candidates.filter { it.code !in myHoldings && it.code !in pendingStocks }.distinctBy { it.code }) } else { println("미확인 데이터 ${remainingCandidates.size}") } @@ -148,7 +146,7 @@ object AutoTradingManager { delay(5000) } - waitForNextCycle(1) + waitForNextCycle(0.5) } } } @@ -200,7 +198,7 @@ object AutoTradingManager { } - RagService.processStock(analyzer, stock.name, stock.code) { decision, isSuccess -> + RagService.processStock(currentPrice,analyzer, stock.name, stock.code) { decision, isSuccess -> callback(decision?.apply { this.currentPrice = currentPrice }, isSuccess) } println("✅ [분석 종료] ${stock.name} (${LocalTime.now()})") @@ -217,6 +215,9 @@ object AutoTradingManager { // async { tradeService.fetchMarketRanking(RankingType.VOLUME1, true).getOrDefault(emptyList()) }, // async { tradeService.fetchMarketRanking(RankingType.VOLUME0, true).getOrDefault(emptyList()) }, async { tradeService.fetchMarketRanking(RankingType.VOLUME, true).getOrDefault(emptyList()) }, + async { tradeService.fetchMarketRanking(RankingType.VOLUME0, true).getOrDefault(emptyList()) }, + async { tradeService.fetchMarketRanking(RankingType.VOLUME1, true).getOrDefault(emptyList()) }, + async { tradeService.fetchMarketRanking(RankingType.VOLUME4, true).getOrDefault(emptyList()) }, async { tradeService.fetchMarketRanking(RankingType.RISE, true).getOrDefault(emptyList()) }, async { tradeService.fetchMarketRanking(RankingType.FALL, true).getOrDefault(emptyList()) }, // async { tradeService.fetchMarketRanking(RankingType.RISE2, true).getOrDefault(emptyList()) }, @@ -236,12 +237,13 @@ object AutoTradingManager { startAutoDiscoveryLoop(tradeService, callback) } - private suspend fun waitForNextCycle(minutes: Int) { + private suspend fun waitForNextCycle(minutes: Double) { println("💤 대기 모드 진입...") val endWait = System.currentTimeMillis() + (minutes * 60 * 1000L) while (System.currentTimeMillis() < endWait && isRunning()) { lastTickTime.set(System.currentTimeMillis()) // 대기 중에도 Watchdog에 생존 신고 - delay(10000) + println("💤 대기 모드 상태 확인...") + delay(5000) } } @@ -273,9 +275,9 @@ object AutoTradingManager { println("🛑 [AutoTrading] 자동 발굴 중단됨") } - fun addStock(technicalAnalyzer : TechnicalAnalyzer,stockName: String, stockCode: String, result: TradingDecisionCallback) { + fun addStock(currentPrice : Double , technicalAnalyzer : TechnicalAnalyzer,stockName: String, stockCode: String, result: TradingDecisionCallback) { scope.launch { - RagService.processStock(technicalAnalyzer,stockName, stockCode, result) + RagService.processStock(currentPrice,technicalAnalyzer,stockName, stockCode, result) } } diff --git a/src/main/kotlin/ui/AiAnalysisView.kt b/src/main/kotlin/ui/AiAnalysisView.kt index 45de08a..f3d475e 100644 --- a/src/main/kotlin/ui/AiAnalysisView.kt +++ b/src/main/kotlin/ui/AiAnalysisView.kt @@ -68,7 +68,7 @@ fun AiAnalysisView(technicalAnalyzer: TechnicalAnalyzer,stockCode:String,stockNa scope.launch { isAnalyzing = true try { - AutoTradingManager.addStock(technicalAnalyzer,stockName,stockCode) { decision,success -> + AutoTradingManager.addStock(currentPrice.replace(",","").toDouble(),technicalAnalyzer,stockName,stockCode) { decision,success -> aiOpinion = decision.toString() isAnalyzing = !success tradingDecisionCallback.invoke(decision,success) diff --git a/src/main/kotlin/ui/IntegratedOrderSection.kt b/src/main/kotlin/ui/IntegratedOrderSection.kt index 6f08298..ac83fe3 100644 --- a/src/main/kotlin/ui/IntegratedOrderSection.kt +++ b/src/main/kotlin/ui/IntegratedOrderSection.kt @@ -192,12 +192,12 @@ fun IntegratedOrderSection( scope.launch { val tickSize = MarketUtil.getTickSize(basePrice) val oneTickLowerPrice = basePrice - (tickSize * when(investmentGrade) { - InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> 1 + InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> 0 InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND -> 1 InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND -> 2 InvestmentGrade.LEVEL_2_HIGH_RISK -> 2 - InvestmentGrade.LEVEL_1_SPECULATIVE -> 4 - else -> 4 + InvestmentGrade.LEVEL_1_SPECULATIVE -> 3 + else -> 3 }) // 2. 주문 가격 설정 (직접 입력값이 없으면 한 틱 낮은 가격 사용) @@ -255,6 +255,7 @@ fun IntegratedOrderSection( var append = 0.0 if (completeTradingDecision != null && completeTradingDecision.stockCode.equals(stockCode)) { + basePrice = completeTradingDecision.currentPrice println("basePrice $basePrice") fun resultCheck(completeTradingDecision :TradingDecision) { val weights = mapOf( @@ -264,14 +265,13 @@ fun IntegratedOrderSection( ) val totalScore = - (completeTradingDecision.shortPossible() * weights["short"]!!) + - (completeTradingDecision.profitPossible() * weights["profit"]!!) + - (completeTradingDecision.safePossible() * weights["safe"]!!) + ((completeTradingDecision.shortPossible() + append) * weights["short"]!!) + + ((completeTradingDecision.profitPossible() + append) * weights["profit"]!!) + + ((completeTradingDecision.safePossible() + append) * weights["safe"]!!) if (totalScore >= MIN_PURCHASE_SCORE && completeTradingDecision.confidence >= MIN_CONFIDENCE) { var investmentGrade : InvestmentGrade = getInvestmentGrade(completeTradingDecision,totalScore, completeTradingDecision.confidence) - // 4. 점수에 따른 가변 마진 적용 - // 토탈 스코어가 85점 이상이면 마진을 3.0으로 고정하거나 추가 가산(append) 적용 + val finalMargin = minimumNetProfit * investmentGrade.profitGuide println(""" 사명 : ${completeTradingDecision.corpName} @@ -299,7 +299,7 @@ fun IntegratedOrderSection( investmentGrade = investmentGrade, ) - } else if(totalScore >= (MIN_PURCHASE_SCORE * 0.85) && completeTradingDecision.confidence >= (MIN_CONFIDENCE * 0.85)) { + } else if(totalScore >= (MIN_PURCHASE_SCORE * 0.85) && completeTradingDecision.confidence + append >= (MIN_CONFIDENCE * 0.85)) { AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName)) println("✋ [관망] 토탈 스코어 또는 신뢰도 미달 이나 약간의 오차로 재분석 대기열에 추가") } else { diff --git a/src/main/kotlin/ui/StockDetailArea.kt b/src/main/kotlin/ui/StockDetailArea.kt index 2d47f51..a6725f2 100644 --- a/src/main/kotlin/ui/StockDetailArea.kt +++ b/src/main/kotlin/ui/StockDetailArea.kt @@ -60,7 +60,7 @@ fun StockDetailSection( // 이전 종목 코드를 기억하기 위한 상태 var previousCode by remember { mutableStateOf("") } - var latestPrice by remember { mutableStateOf("0") } + var lastPrice by remember { mutableStateOf("0") } // 종목 변경 시 데이터 로드 및 웹소켓 구독 관리 @@ -90,7 +90,7 @@ fun StockDetailSection( val price = tradeLog.price wsManager.tradeLogs.add(tradeLog) if (wsManager.tradeLogs.size > 50) wsManager.tradeLogs.removeLast() - println("code $code ,price $price") +// println("code $code ,price $price") val currentPrice = price if (chartData.isNotEmpty() && currentPrice != "0") { val priceDouble = currentPrice.replace(",", "").toDoubleOrNull() ?: 0.0 @@ -123,7 +123,7 @@ fun StockDetailSection( chartData = chartData.dropLast(1) + updatedCandle } } - latestPrice = currentPrice + lastPrice = currentPrice } } @@ -218,7 +218,7 @@ fun StockDetailSection( code = stockCode, isDomestic = isDomestic, previousClose = previousClose, - openPrice = latestPrice, + openPrice = lastPrice, resultMessage = resultMessage, resultMessageClear = {resultMessage = ""}, isSuccess = isSuccess @@ -227,10 +227,10 @@ fun StockDetailSection( // 실시간 가격 표시 (WebSocket 데이터) Column(horizontalAlignment = Alignment.End) { Text( - text = "${latestPrice} 원", + text = "${lastPrice} 원", style = MaterialTheme.typography.h4, fontWeight = FontWeight.Bold, - color = if (latestPrice?.contains("-") ?: false) Color.Blue else Color.Red + color = if (lastPrice?.contains("-") ?: false) Color.Blue else Color.Red ) Text("실시간 체결가", style = MaterialTheme.typography.caption, color = Color.Gray) } @@ -276,7 +276,7 @@ fun StockDetailSection( stockCode = stockCode, stockName = stockName, isDomestic = isDomestic, - currentPrice = latestPrice, + currentPrice = lastPrice, holdingQuantity = holdingQuantity, tradeService = tradeService, onOrderSaved = onOrderSaved,