This commit is contained in:
lunaticbum 2026-02-03 18:07:18 +09:00
parent d4926646f9
commit a51ffb6193
9 changed files with 251 additions and 61 deletions

View File

@ -28,7 +28,8 @@ data class BalanceSummary(
val tot_evlu_amt: String = "0", // 총 평가금액 val tot_evlu_amt: String = "0", // 총 평가금액
val evlu_pfls_rt: String = "0.0", // 총 수익률 (에러 발생 지점: 기본값 추가로 해결) val evlu_pfls_rt: String = "0.0", // 총 수익률 (에러 발생 지점: 기본값 추가로 해결)
val asst_icrt: String = "0.0", // 일부 환경에서 수익률 필드명 val asst_icrt: String = "0.0", // 일부 환경에서 수익률 필드명
val nass_amt: String = "0" // 순자산 금액 val nass_amt: String = "0" , // 순자산 금액
val dnca_tot_amt: String = "0"
) )
@Serializable @Serializable
data class RankingResponse( data class RankingResponse(
@ -116,6 +117,7 @@ data class UnifiedStockHolding(
data class UnifiedBalance( data class UnifiedBalance(
val totalAsset: String, // 총 평가자산 val totalAsset: String, // 총 평가자산
val totalProfitRate: String, // 총 수익률 val totalProfitRate: String, // 총 수익률
val deposit: String,
val holdings: List<UnifiedStockHolding> // 통합 보유 종목 리스트 val holdings: List<UnifiedStockHolding> // 통합 보유 종목 리스트
) )

View File

@ -41,7 +41,7 @@ object KisTradeService {
// [수정] 모든 로그(Headers + Body)를 찍도록 설정 // [수정] 모든 로그(Headers + Body)를 찍도록 설정
install(Logging) { install(Logging) {
logger = Logger.DEFAULT logger = Logger.DEFAULT
level = LogLevel.BODY level = LogLevel.NONE
} }
} }
@ -84,10 +84,12 @@ object KisTradeService {
val totalAmt = (domRes?.output2?.firstOrNull()?.tot_evlu_amt?.toLongOrNull() ?: 0L) + val totalAmt = (domRes?.output2?.firstOrNull()?.tot_evlu_amt?.toLongOrNull() ?: 0L) +
(ovsRes?.output2?.firstOrNull()?.tot_evlu_amt?.toLongOrNull() ?: 0L) (ovsRes?.output2?.firstOrNull()?.tot_evlu_amt?.toLongOrNull() ?: 0L)
val depositAmt = domRes?.output2?.firstOrNull()?.dnca_tot_amt?.toLongOrNull() ?: 0L
Result.success(UnifiedBalance( Result.success(UnifiedBalance(
totalAsset = String.format("%,d", totalAmt), totalAsset = String.format("%,d", totalAmt),
totalProfitRate = domRes?.output2?.firstOrNull()?.evlu_pfls_rt ?: "0.0", totalProfitRate = domRes?.output2?.firstOrNull()?.evlu_pfls_rt ?: "0.0",
deposit = String.format("%,d", depositAmt),
holdings = combinedHoldings holdings = combinedHoldings
)) ))
} catch (e: Exception) { Result.failure(e) } } catch (e: Exception) { Result.failure(e) }
@ -220,7 +222,7 @@ object KisTradeService {
val body = response.body<JsonObject>() val body = response.body<JsonObject>()
val output2 = body["output2"]?.jsonArray val output2 = body["output2"]?.jsonArray
println("output2 ${output2}") // println("output2 ${output2}")
val candles = output2?.map { element -> val candles = output2?.map { element ->
val obj = element.jsonObject val obj = element.jsonObject
CandleData( CandleData(
@ -394,7 +396,7 @@ object KisTradeService {
val cano = pureAccount.take(8) val cano = pureAccount.take(8)
val acntPrdtCd = pureAccount.takeLast(2) val acntPrdtCd = pureAccount.takeLast(2)
return try { return try {
println("orgNo") // println("orgNo")
val response = client.post("$baseUrl/uapi/domestic-stock/v1/trading/order-rvsecncl") { val response = client.post("$baseUrl/uapi/domestic-stock/v1/trading/order-rvsecncl") {
header("authorization", "Bearer ${config.tradeToken}") header("authorization", "Bearer ${config.tradeToken}")
header("appkey", if (config.isSimulation) config.vtsAppKey else config.realAppKey) header("appkey", if (config.isSimulation) config.vtsAppKey else config.realAppKey)

View File

@ -29,7 +29,7 @@ object NewsService {
} }
install(Logging) { install(Logging) {
logger = Logger.DEFAULT logger = Logger.DEFAULT
level = LogLevel.ALL level = LogLevel.NONE
} }
} }

View File

@ -52,9 +52,9 @@ object RagService {
fun active() { fun active() {
println("[Cache] Active") // println("[Cache] Active")
if (UrlCacheManager.isInitialized()) return if (UrlCacheManager.isInitialized()) return
println("[Cache] initialize") // println("[Cache] initialize")
UrlCacheManager.initialize(embeddingStore, embeddingModel) UrlCacheManager.initialize(embeddingStore, embeddingModel)
} }
@ -130,32 +130,39 @@ object RagService {
suspend fun processStock(stockName: String,stockCode: String,result : TradingDecisionCallback) { suspend fun processStock(stockName: String,stockCode: String,result : TradingDecisionCallback) {
// 1. 10분간의 데이터 가져오기 (API 호출) // 1. 10분간의 데이터 가져오기 (API 호출)
coroutineScope { coroutineScope {
var tradingDecision : TradingDecision = TradingDecision() try {
tradingDecision.stockCode = stockCode
var corpInfo = DartCodeManager.getCorpCode(stockCode)
corpInfo?.stockName = stockName
corpInfo?.let { NewsService.fetchAndIngestNews(it) }
val financialDataDeferred = async { NewsService.fetchFinancialGrowth(corpInfo?.cCode ?: "") }
tradingDecision.financialData = financialDataDeferred.await() var tradingDecision: TradingDecision = TradingDecision()
result(tradingDecision,false) tradingDecision.stockCode = stockCode
var corpInfo = DartCodeManager.getCorpCode(stockCode)
corpInfo?.stockName = stockName
tradingDecision.stockName = stockName
tradingDecision.corpName = corpInfo?.cName ?: ""
corpInfo?.let { NewsService.fetchAndIngestNews(it) }
tradingDecision.techSummary = TechnicalAnalyzer.generateComprehensiveReport() val financialDataDeferred = async { NewsService.fetchFinancialGrowth(corpInfo?.cCode ?: "") }
result(tradingDecision,false)
val question = "${corpInfo?.cName} $stockName[$stockCode]의 향후 실적 전망과 관련된 핵심 뉴스" tradingDecision.financialData = financialDataDeferred.await()
val questionEmbedding = embeddingModel.embed(question).content() result(tradingDecision, false)
val searchResult = embeddingStore.search(
EmbeddingSearchRequest.builder()
.queryEmbedding(questionEmbedding)
.maxResults(3)
.build()
)
tradingDecision.newsContext = searchResult.matches().joinToString("\n") { it.embedded().text() }
result(tradingDecision,false)
result(decideTrading(stockCode, tradingDecision),true)
tradingDecision.techSummary = TechnicalAnalyzer.generateComprehensiveReport()
result(tradingDecision, false)
val question = "${corpInfo?.cName} $stockName[$stockCode]의 향후 실적 전망과 관련된 핵심 뉴스"
val questionEmbedding = embeddingModel.embed(question).content()
val searchResult = embeddingStore.search(
EmbeddingSearchRequest.builder()
.queryEmbedding(questionEmbedding)
.maxResults(3)
.build()
)
tradingDecision.newsContext = searchResult.matches().joinToString("\n") { it.embedded().text() }
result(tradingDecision, false)
result(decideTrading(stockCode, tradingDecision), true)
}catch (e: Exception) {
e.printStackTrace()
}
} }
} }
@ -220,7 +227,7 @@ object RagService {
""".trimIndent() """.trimIndent()
val response = chatModel.chat(UserMessage.from(finalPrompt)) val response = chatModel.chat(UserMessage.from(finalPrompt))
println(response) // println(response)
return response.aiMessage().text() return response.aiMessage().text()
} }
@ -271,7 +278,7 @@ object RagService {
val response = chatModel.chat(UserMessage.from(prompt)) val response = chatModel.chat(UserMessage.from(prompt))
val rawResponse = response.aiMessage().text() val rawResponse = response.aiMessage().text()
val jsonResponse = JsonSanitizer.formatJson(rawResponse) val jsonResponse = JsonSanitizer.formatJson(rawResponse)
println("📥 [AI Raw JSON]:\n$jsonResponse") // println("📥 [AI Raw JSON]:\n$jsonResponse")
// 2. 유연한 파서 설정 (소수점 및 예외 상황 대응) // 2. 유연한 파서 설정 (소수점 및 예외 상황 대응)
val lenientJson = Json { val lenientJson = Json {
@ -283,12 +290,14 @@ object RagService {
// JSON 파싱 (Kotlinx Serialization 활용) // JSON 파싱 (Kotlinx Serialization 활용)
return try { return try {
println(jsonResponse) // println(jsonResponse)
val decision = lenientJson.decodeFromString<TradingDecision>(jsonResponse) val decision = lenientJson.decodeFromString<TradingDecision>(jsonResponse)
decision.financialData = tempDecision.financialData decision.financialData = tempDecision.financialData
decision.newsContext = tempDecision.newsContext decision.newsContext = tempDecision.newsContext
decision.techSummary = tempDecision.techSummary decision.techSummary = tempDecision.techSummary
decision.stockCode = tempDecision.stockCode decision.stockCode = tempDecision.stockCode
decision.corpName = tempDecision.corpName
decision.stockName = tempDecision.stockName
decision decision
} catch (e: dev.langchain4j.exception.InternalServerException) { } catch (e: dev.langchain4j.exception.InternalServerException) {
// 서버 에러 (컨텍스트 초과 등) 발생 시 로그 남기고 null 반환 혹은 커스텀 에러 처리 // 서버 에러 (컨텍스트 초과 등) 발생 시 로그 남기고 null 반환 혹은 커스텀 에러 처리
@ -308,11 +317,14 @@ object RagService {
} }
@Serializable @Serializable
class TradingDecision { class TradingDecision {
var corpName : String = ""
var stockName : String = ""
val ultraShortScore: Double = 0.0 // 초단기 (분봉/에너지) val ultraShortScore: Double = 0.0 // 초단기 (분봉/에너지)
val shortTermScore: Double = 0.0 // 단기 (일봉/뉴스) val shortTermScore: Double = 0.0 // 단기 (일봉/뉴스)
val midTermScore: Double = 0.0 // 중기 (주봉/재무) val midTermScore: Double = 0.0 // 중기 (주봉/재무)
val longTermScore: Double = 0.0 val longTermScore: Double = 0.0
// [추가] 화면 전환용 종목명
var currentPrice: Double = 0.0
var stockCode: String = "" var stockCode: String = ""
var decision: String? = null var decision: String? = null
var reason: String? = null var reason: String? = null
@ -323,12 +335,18 @@ class TradingDecision {
fun profitPossible() = fun profitPossible() =
listOf<Double>(ultraShortScore, listOf<Double>(ultraShortScore,
shortTermScore, shortTermScore,
midTermScore, midTermScore,
longTermScore).average() longTermScore).average()
fun safePossible() =
listOf<Double>(
midTermScore,
longTermScore).average()
override fun toString(): String { override fun toString(): String {
return """ return """
$corpName($stockName)
수익실현 가능성 : ${profitPossible()} 수익실현 가능성 : ${profitPossible()}
ultraShortScore :$ultraShortScore ultraShortScore :$ultraShortScore
shortTermScore :$shortTermScore shortTermScore :$shortTermScore

View File

@ -3,8 +3,13 @@ package service
import TradingDecision import TradingDecision
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import model.CandleData import model.CandleData
import model.RankingType
import network.KisTradeService
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
import java.time.ZoneId import java.time.ZoneId
@ -17,30 +22,139 @@ typealias TradingDecisionCallback = (TradingDecision?, Boolean)->Unit
object AutoTradingManager { object AutoTradingManager {
private val scope = CoroutineScope(Dispatchers.Default) private val scope = CoroutineScope(Dispatchers.Default)
val targetStocks = mutableListOf<Pair<String, String>>() val targetStocks = mutableListOf<Pair<String, String>>()
// 자동 발굴 루프 제어용 Job
private var discoveryJob: Job? = null
fun addStock(stockName : String,stockCode : String, result :TradingDecisionCallback) {
targetStocks.add(Pair(stockName, stockCode))
startTradingLoop(stockName,stockCode,result) fun startAutoDiscoveryLoop(
tradeService: KisTradeService,
callback: TradingDecisionCallback
) {
if (discoveryJob?.isActive == true) return
discoveryJob = scope.launch {
println("🚀 [AutoTrading] 5분 주기 자동 발굴 시작")
while (discoveryJob?.isActive == true) {
try {
// 1. [체크] 현재 잔고 및 보유 종목 조회
val balanceResult = tradeService.fetchIntegratedBalance().getOrNull()
val myHoldings = balanceResult?.holdings?.map { it.code }?.toSet() ?: emptySet()
val myCash = balanceResult?.deposit?.replace(",", "")?.toLongOrNull() ?: 0L
println("💰 보유 현금: ${String.format("%,d", myCash)}원 | 보유 종목 수: ${myHoldings.size}")
// 2. 랭킹 데이터 가져오기
// 1. 랭킹 데이터 가져오기 (비동기)
val volRankDeferred = async { tradeService.fetchMarketRanking(RankingType.VOLUME, true).getOrDefault(emptyList()) }
val riseRankDeferred = async { tradeService.fetchMarketRanking(RankingType.RISE, true).getOrDefault(emptyList()) }
val volList = volRankDeferred.await()
val riseList = riseRankDeferred.await()
// [수정] 2. 의미 있는 후보군 선정 (단순 상위 15개가 아님)
// (A) 거래량 상위 종목 중: 현재가 기준 등락률이 0% ~ 20% 사이인 것만 필터링 -> 상위 10개
val volCandidates = volList
.filter { stock ->
val rate = stock.prdy_ctrt.toDoubleOrNull() ?: 0.0
rate in 0.0..20.0 // 0% 초과 20% 이하
}
.take(10)
// (B) 상승률 상위 종목 중: 너무 급등한(20% 초과) 종목은 제외하고, 적당히 오르고 있는 종목만 필터링 -> 상위 10개
// 보통 상승률 랭킹은 상한가(30%)부터 내려오므로, 앞쪽의 급등주를 건너뛰어야 함
val riseCandidates = riseList
.filter { stock ->
val rate = stock.prdy_ctrt.toDoubleOrNull() ?: 0.0
rate in 3.0..20.0 // 최소 3% 이상은 올라야 의미 있음, 20% 이하는 안전 구간
}
.take(10)
// 3. 두 리스트 합치기 (중복 제거)
val candidates = (volCandidates + riseCandidates).distinctBy { it.code }
println("🔎 1차 필터링 후보 ${candidates.size}개 (급등주 제외) 검증 시작...")
candidates.forEach { stock ->
// [조건 1] 이미 보유한 종목 제외
if (myHoldings.contains(stock.code)) return@forEach
val currentPrice = stock.stck_prpr.replace(",", "").toDoubleOrNull() ?: 0.0
// [조건 2] 최소 1주 매수 가능 여부
if (currentPrice > myCash) return@forEach
// 3. 일봉 데이터 조회 (필터링 용도 + TechnicalAnalyzer 입력용)
val dailyResult = tradeService.fetchPeriodChartData(stock.code, "D", true)
val dailyData = dailyResult.getOrNull()
val todayCandle = dailyData?.lastOrNull()
if (dailyData != null && todayCandle != null) {
val open = todayCandle.stck_oprc.toDoubleOrNull() ?: 0.0
val current = todayCandle.stck_prpr.toDoubleOrNull() ?: 0.0
if (open > 0) {
val riseRate = (current - open) / open * 100
// [조건 3] 상승 중(양봉)이면서 20% 이하 상승
if (riseRate > 0 && riseRate <= 20.0) {
println("✨ [발굴] ${stock.name} (+${String.format("%.1f", riseRate)}%) -> 데이터 수집 및 분석")
// [핵심 수정] AI 분석 전 필요한 차트 데이터(30분, 주봉, 월봉)를 모두 가져와 TechnicalAnalyzer에 주입
// 비동기로 동시에 요청하여 속도 향상
val min30Def = async { tradeService.fetchChartData(stock.code, true).getOrDefault(emptyList()) }
val weekDef = async { tradeService.fetchPeriodChartData(stock.code, "W", true).getOrDefault(emptyList()) }
val monthDef = async { tradeService.fetchPeriodChartData(stock.code, "M", true).getOrDefault(emptyList()) }
val min30Data = min30Def.await()
val weeklyData = weekDef.await()
val monthlyData = monthDef.await()
// TechnicalAnalyzer 상태 업데이트 (싱글톤이므로 순차 처리 필수)
TechnicalAnalyzer.clear()
TechnicalAnalyzer.daily = dailyData
TechnicalAnalyzer.weekly = weeklyData
TechnicalAnalyzer.monthly = monthlyData
TechnicalAnalyzer.min30 = min30Data
// 데이터 준비 완료 후 AI 분석 요청 (suspend 함수이므로 완료될 때까지 대기 -> 데이터 섞임 방지)
RagService.processStock(stock.name, stock.code) { decision, isSuccess ->
if (decision != null) {
decision.stockName = stock.name
decision.currentPrice = current // 차트에서 확인한 최신 현재가 주입
}
callback(decision, isSuccess) // DashboardScreen으로 전달
}
// 분석 후 잠시 대기 (서버 부하 조절)
delay(2000)
}
}
}
delay(100) // 종목 간 API 호출 간격
}
println("💤 사이클 종료. 5분 대기...")
} catch (e: Exception) {
println("⚠️ 루프 오류: ${e.message}")
}
delay(5 * 60 * 1000) // 5분
}
}
} }
fun startTradingLoop(stockName : String, stockCode : String, result :TradingDecisionCallback) { // 루프 중단 함수
fun stopDiscovery() {
discoveryJob?.cancel()
discoveryJob = null
println("🛑 [AutoTrading] 자동 발굴 중단됨")
}
// 기존 단일 종목 추가 로직 (유지)
fun addStock(stockName: String, stockCode: String, result: TradingDecisionCallback) {
scope.launch { scope.launch {
println("🚀 10분 주기 자동 분석 및 매매 시작: ${LocalTime.now()}") RagService.processStock(stockName, stockCode, result)
// targetStocks.forEach { stockCode ->
launch { // 종목별 병렬 분석 (M3 Pro 파워 활용)
RagService.processStock(stockName, stockCode,result)
// {decision,b ->
//// when (decision?.decision) {
//// "BUY" -> if (decision.confidence > 70) executeOrder(stockCode, "매수")
//// "SELL" -> executeOrder(stockCode, "매도")
//// else -> println("[$stockCode] 관망 유지: ${decision?.reason}")
//// }
// result(decision,b)
// }
}
// }
// targetStocks.re
// delay(10 * 60 * 1000) // 10분 대기
} }
} }

View File

@ -70,6 +70,7 @@ fun AiAnalysisView(stockCode:String,stockName: String, currentPrice: String, tra
AutoTradingManager.addStock(stockName,stockCode) { decision,success -> AutoTradingManager.addStock(stockName,stockCode) { decision,success ->
aiOpinion = decision.toString() aiOpinion = decision.toString()
isAnalyzing = !success isAnalyzing = !success
tradingDecisionCallback.invoke(decision,success)
} }
} catch (e: Exception) { } catch (e: Exception) {
aiOpinion = "분석 중 오류 발생: ${e.message}" aiOpinion = "분석 중 오류 발생: ${e.message}"

View File

@ -17,6 +17,7 @@ import model.KisSession
import model.StockBasicInfo import model.StockBasicInfo
import network.KisTradeService import network.KisTradeService
import network.KisWebSocketManager import network.KisWebSocketManager
import service.AutoTradingManager
@Composable @Composable
fun DashboardScreen() { fun DashboardScreen() {
@ -32,6 +33,27 @@ fun DashboardScreen() {
var selectedStockInfo by remember { mutableStateOf<StockBasicInfo?>(null) } // 단순 종목 선택 시 var selectedStockInfo by remember { mutableStateOf<StockBasicInfo?>(null) } // 단순 종목 선택 시
var completeTradingDecision by remember { mutableStateOf<TradingDecision?>(null) } // 단순 종목 선택 시 var completeTradingDecision by remember { mutableStateOf<TradingDecision?>(null) } // 단순 종목 선택 시
DisposableEffect(Unit) {
// 1. 화면 진입 시: 자동 발굴 루프 시작
// AI 분석 결과(decision)가 나오면 completeTradingDecision 상태를 업데이트하여
// IntegratedOrderSection에서 자동으로 매수 로직이 실행되도록 연결합니다.
AutoTradingManager.startAutoDiscoveryLoop(tradeService) { decision, isSuccess ->
if (isSuccess && decision != null) {
selectedStockCode = decision.stockCode
selectedStockName = decision.stockName
isDomestic = true // 발굴 로직은 국내주식 기준이므로 true 고정
// 2. 결정 객체 업데이트 -> IntegratedOrderSection의 LaunchedEffect 트리거
completeTradingDecision = decision
}
}
// 2. 화면 이탈 시(앱 종료 등): 루프 중단 (리소스 정리)
onDispose {
AutoTradingManager.stopDiscovery()
}
}
// 중앙 관리용 상태들 // 중앙 관리용 상태들
var refreshTrigger by remember { mutableStateOf(0) } var refreshTrigger by remember { mutableStateOf(0) }

View File

@ -70,7 +70,7 @@ fun IntegratedOrderSection(
} }
var profitRate by remember(monitoringItem) { var profitRate by remember(monitoringItem) {
mutableStateOf(monitoringItem?.profitRate?.toString() ?: "3.0") mutableStateOf(monitoringItem?.profitRate?.toString() ?: "2.0")
} }
var stopLossRate by remember(monitoringItem) { var stopLossRate by remember(monitoringItem) {
mutableStateOf(monitoringItem?.stopLossRate?.toString() ?: "-2.0") mutableStateOf(monitoringItem?.stopLossRate?.toString() ?: "-2.0")
@ -114,12 +114,43 @@ fun IntegratedOrderSection(
} }
} }
LaunchedEffect(completeTradingDecision) { LaunchedEffect(completeTradingDecision) {
val MIN_CONFIDENCE = 70.0 // 최소 신뢰도
val MIN_MID_SCORE = 65.0 // 최소 중기 점수 (주봉/재무)
println("completeTradingDecision = $completeTradingDecision")
if (completeTradingDecision != null && if (completeTradingDecision != null &&
completeTradingDecision.stockCode.equals(stockCode)) { completeTradingDecision.stockCode.equals(stockCode)) {
println(completeTradingDecision?.decision)
fun resultCheck(completeTradingDecision :TradingDecision) {
println("""
${completeTradingDecision.corpName}
${completeTradingDecision.confidence}
${completeTradingDecision.profitPossible()}
${completeTradingDecision.safePossible()}
""".trimIndent())
// 2. 조건 검사: 신뢰도 80 이상 AND 중기 점수 70 이상
if (completeTradingDecision.confidence >= MIN_CONFIDENCE &&
completeTradingDecision.profitPossible() >= MIN_MID_SCORE &&
completeTradingDecision.safePossible() > MIN_MID_SCORE) {
println("🚀 [조건 만족] 강력 매수 시그널 포착 -> 자동 매수 진행 (1주) ${completeTradingDecision.stockCode}")
// 3. 매수 실행 (자동 감시 켜기: true, 수량: 1주)
// 수량은 필요에 따라 로직으로 계산하여 변경 가능 (예: 자산의 10% 등)
excuteTrade(willEnableAutoSell = true, orderQty = "1")
} else {
println("✋ [조건 미달] 매수 의견이나 점수 부족으로 관망")
}
}
when (completeTradingDecision?.decision) { when (completeTradingDecision?.decision) {
"BUY" -> if (completeTradingDecision.confidence > 70) excuteTrade(true, "1") "BUY" -> {
println("[$stockCode] 매수 추천 resultCheck: ${completeTradingDecision?.reason}")
resultCheck(completeTradingDecision)
}
"SELL" -> println("[$stockCode] 매도: ${completeTradingDecision?.reason}") "SELL" -> println("[$stockCode] 매도: ${completeTradingDecision?.reason}")
else -> println("[$stockCode] 관망 유지: ${completeTradingDecision?.reason}") else -> {
resultCheck(completeTradingDecision)
println("[$stockCode] 관망 유지 resultCheck: ${completeTradingDecision?.reason}")
}
} }
} }
} }

View File

@ -94,20 +94,20 @@ fun StockDetailSection(
launch { tradeService.fetchPeriodChartData(stockCode, "D").onSuccess { launch { tradeService.fetchPeriodChartData(stockCode, "D").onSuccess {
daySummary = it.takeLast(7) daySummary = it.takeLast(7)
TechnicalAnalyzer.daily = it TechnicalAnalyzer.daily = it
println("daySummary ${daySummary.size} total: ${it.size} ${it.firstOrNull()?.toString()}") // println("daySummary ${daySummary.size} total: ${it.size} ${it.firstOrNull()?.toString()}")
} }
} // 최근 7일 } // 최근 7일
launch { tradeService.fetchPeriodChartData(stockCode, "W").onSuccess { launch { tradeService.fetchPeriodChartData(stockCode, "W").onSuccess {
weekSummary = it.takeLast(4) weekSummary = it.takeLast(4)
TechnicalAnalyzer.weekly = it TechnicalAnalyzer.weekly = it
println("weekSummary ${weekSummary.size} total: ${it.size} ${it.firstOrNull()?.toString()}") // println("weekSummary ${weekSummary.size} total: ${it.size} ${it.firstOrNull()?.toString()}")
} }
} // 최근 4주 } // 최근 4주
launch { tradeService.fetchPeriodChartData(stockCode, "M").onSuccess { launch { tradeService.fetchPeriodChartData(stockCode, "M").onSuccess {
monthSummary = it.takeLast(6) // 최근 6개월 monthSummary = it.takeLast(6) // 최근 6개월
yearSummary = it.takeLast(36) // 최근 3년 yearSummary = it.takeLast(36) // 최근 3년
TechnicalAnalyzer.monthly = it TechnicalAnalyzer.monthly = it
println("monthSummary ${monthSummary.size} yearSummary ${yearSummary.size} total: ${it.size} ${it.firstOrNull()?.toString()}") // println("monthSummary ${monthSummary.size} yearSummary ${yearSummary.size} total: ${it.size} ${it.firstOrNull()?.toString()}")
} }
} }
launch { launch {