This commit is contained in:
lunaticbum 2026-02-13 13:49:40 +09:00
parent ad5e7e0ccb
commit 904e3011c9
6 changed files with 39 additions and 31 deletions

View File

@ -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")),

View File

@ -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

View File

@ -49,7 +49,7 @@ object AutoTradingManager {
fun isRunning(): Boolean = discoveryJob?.isActive == true
private var remainingCandidates = mutableListOf<RankingStock>()
// private val processedCodes = mutableSetOf<String>() // 중복 처리 방지용 (선택 사항)
// private val processedCodes = mutableSetOf<String>() // 중복 처리 방지용 (선택 사항)
private val reanalysisList = mutableListOf<RankingStock>()
private val retryCountMap = mutableMapOf<String, Int>()
/**
@ -102,16 +102,14 @@ object AutoTradingManager {
if (remainingCandidates.isEmpty()) {
val candidates: MutableList<RankingStock> = 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)
}
}

View File

@ -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)

View File

@ -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 {

View File

@ -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,