diff --git a/src/main/kotlin/analyzer/TechnicalAnalyzer.kt b/src/main/kotlin/analyzer/TechnicalAnalyzer.kt index 6cd5774..f5def79 100644 --- a/src/main/kotlin/analyzer/TechnicalAnalyzer.kt +++ b/src/main/kotlin/analyzer/TechnicalAnalyzer.kt @@ -354,7 +354,7 @@ class TechnicalAnalyzer { // ๐ŸŒŸ 3. 3๊ฐœ ์ค‘ 2๊ฐœ ์ด์ƒ ๋งŒ์กฑ ์‹œ ๋ฐ˜๋“ฑ ์ž„๋ฐ•(Approaching)์œผ๋กœ ํŒ๋‹จ val passedConditions = listOf(isPriceDropped, isPastMinTime, isWithinMaxTime).count { it } //A. ์ง„์ž… ํ•˜๋ฝ๋ฅ  ๊ธฐ์ค€checkReboundApproaching 3๊ฐœ ์ค‘ 2๊ฐœ ๋งŒ์กฑ ์‹œ ํ†ต๊ณผ ๊ฐ€๊ฒฉ ํ•˜๋ฝ ์กฐ๊ฑด(isPriceDropped)์„ ํ•„์ˆ˜ ์กฐ๊ฑด(AND)์œผ๋กœ ๊ณ ์ • - return passedConditions >= 2 + return passedConditions > 2 } fun calculateMFI(candles: List, period: Int = 14): Double { diff --git a/src/main/kotlin/service/AutoTradingManager.kt b/src/main/kotlin/service/AutoTradingManager.kt index 425abec..3e3a39b 100644 --- a/src/main/kotlin/service/AutoTradingManager.kt +++ b/src/main/kotlin/service/AutoTradingManager.kt @@ -590,7 +590,7 @@ object AutoTradingManager { qty = holding.availOrderCount, price = targetPrice.toInt().toString(), isBuy = false, - orderDivision = if (marketCode.equals("Y")) "07" else "", + orderDivision = if (marketCode.equals("Y")) "41" else "", marketCode = if (marketCode.equals("Y")) "KRX" else "NXT" ).onSuccess { newOrderNo -> println("โœ… [${if (marketCode.equals("Y")) "์‹œ๊ฐ„์™ธ ๋‹จ์ผ๊ฐ€" else "๋Œ€์ฒด๊ฑฐ๋ž˜์†Œ"} ์ฃผ๋ฌธ ์™„๋ฃŒ] ${holding.name}: $newOrderNo") @@ -743,7 +743,7 @@ object AutoTradingManager { ) { var targetPrice = holding.avgPrice.toDouble() targetPrice = MarketUtil.roundToTickSize( - targetPrice + MarketUtil.getTickSize(targetPrice) * KisSession.tradeConfig.autoSellOrderAppend + targetPrice + (MarketUtil.getTickSize(targetPrice) * KisSession.tradeConfig.autoSellOrderAppend) ) tradeService.postOrder( stockCode = holding.code, @@ -761,7 +761,7 @@ object AutoTradingManager { TradingLogStore.addNotice( "๋ณด์œ ์ฃผ์‹[${holding.name}]", holding.code, - "๋งค์ˆ˜๊ฐ€ ๊ธฐ์ค€ (${holding.avgPrice.toDouble()} 3ํ˜ธ๊ฐ€ ์œ„[${targetPrice}] ๋งค๋„ ์ฃผ๋ฌธ ${if (isSuccess) "์„ฑ๊ณต" else "์‹คํŒจ[${errMsg}]"}" + "๋งค์ˆ˜๊ฐ€ ๊ธฐ์ค€ (${holding.avgPrice.toDouble()} ${KisSession.tradeConfig.autoSellOrderAppend}ํ˜ธ๊ฐ€ ์œ„[${targetPrice}] ๋งค๋„ ์ฃผ๋ฌธ ${if (isSuccess) "์„ฑ๊ณต" else "์‹คํŒจ[${errMsg}]"}" ) } else if (KisSession.config.stop_Loss && holding != null && holding.quantity.toInt() > 0 @@ -1323,7 +1323,7 @@ object AutoTradingManager { true ) var list = mutableListOf("X") - if (now.hour != 8 && now.hour < 18) { + if (now.hour != 8 && now.hour < 20) { list.add("Y") } list.forEach { code -> @@ -1363,69 +1363,98 @@ object AutoTradingManager { val maxBudget = KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX) val maxPrice = KisSession.config.getValues(ConfigIndex.MAX_PRICE_INDEX) val minPrice = KisSession.config.getValues(ConfigIndex.MIN_PRICE_INDEX) + // ๊ฐœ๋ณ„ ์ข…๋ชฉ ๋ถ„์„์€ ์ตœ๋Œ€ 2๋ถ„์œผ๋กœ ์ œํ•œ withTimeout(ONE_STOCK_ALYSIS_TIME) { + // ------------------------------------------------------------- + // [1์ฐจ ํ•„ํ„ฐ: ๊ฐ€๋ฒผ์šด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ & ์ •์ฑ… ๊ฒ€์ฆ (๋ถˆํ•„์š”ํ•œ API ํ˜ธ์ถœ ๋ฐฉ์ง€)] + // ------------------------------------------------------------- + // 1. ์ข…๋ชฉ๋ช… ๋ฐ ๊ธฐ์—… ๊ณ ์œ ๋ฒˆํ˜ธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ val corpInfo = DartCodeManager.getCorpCode(stock.code) if (corpInfo?.cName.isNullOrEmpty()) { print("-> ๊ธฐ์—…๋ช…์„ ๋ชป์ฐพ์•„์„œ ์ œ์™ธ | ") return@withTimeout } - if (currentBalance?.getHoldings() - ?.any { it.code.equals(stock.code) && it.quantity.toInt() > 2 } == true - ) { - println("๋ฌผํƒ€๊ธฐ ๋Œ€์ƒ ๋ถ„์„") + + // 2. [๋ณด์™„] ์žฌ๋ฌด ๊ฑด์ „์„ฑ ๋ฏธ๋‹ฌ ์ข…๋ชฉ ์‚ฌ์ „ ์ฐจ๋‹จ (์ตœ์ƒ๋‹จ์œผ๋กœ ์ด๋™ํ•˜์—ฌ ์ฐจํŠธ API ์†Œ๋ชจ ๋ฐฉ์ง€) + if (isSafetyBeltStockCodes.contains(stock.code)) { + print("-> [${stock.name}] ์žฌ๋ฌด ๊ฑด์ „์„ฑ ๋ฏธ๋‹ฌ(์•ˆ์ „๋ฒจํŠธ ํƒˆ๋ฝ ์ข…๋ชฉ) ์ œ์™ธ | ") + return@withTimeout } + + // 3. ๋ฌผํƒ€๊ธฐ ๋Œ€์ƒ ๋กœ๊น… + if (currentBalance?.getHoldings()?.any { it.code == stock.code && it.quantity.toInt() > 2 } == true) { + println("๋ฌผํƒ€๊ธฐ ๋Œ€์ƒ ๋ถ„์„: ${stock.name}") + } + + // ์ดˆ๊ธฐ ์ฝœ๋ฐฑ ์•Œ๋ฆผ (๋ถ„์„ ์ง„ํ–‰ ํ‘œ์‹œ์šฉ) callback(TradingDecision().apply { this.stockCode = stock.code this.confidence = -1.0 this.stockName = stock.name }, false) - val dailyData = - tradeService.fetchPeriodChartData(stock.code, "D", true).getOrNull() - ?: return@withTimeout - val today = dailyData.lastOrNull() ?: null - var rate = today?.getFluctuationRate() ?: 0.0 - val isOk = - ((rate < KisSession.tradeConfig.plusFilter) && (rate > (abs(KisSession.tradeConfig.minusFilter) * -1))) + // 4. [๋ณด์™„] ๋ฐฐ๋‹น ์ „๋žต ์‚ฌ์šฉ ์‹œ ์‚ฌ์ „ ํ•„ํ„ฐ๋ง (๋ณต์žกํ•œ ๊ธฐ์ˆ  ๋ถ„์„ ์ „ ์กฐ๊ธฐ ํƒˆ๋ฝ) + if (KisSession.tradeConfig.isUpcomingDividend) { + val dividend = KisTradeService.fetchUpcomingDividend(stock.code).getOrNull() + if (dividend?.hasDividend == true) { + println("[${stock.name}] ๋ฐฐ๋‹น๋ฝ์ผ ${dividend.exDividendDate} : ${dividend.dividendAmount}") + } else { + print("-> [${stock.name}] ๋ฐฐ๋‹น ์ •๋ณด ์—†์–ด ๋ถ„์„ ์ข…๋ฃŒ | ") + return@withTimeout + } + } + + // ------------------------------------------------------------- + // [2์ฐจ ํ•„ํ„ฐ: ์ผ๋ด‰ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ฐ ํ˜„์žฌ๊ฐ€/ํ˜ธ๊ฐ€/๋ฆฌ์Šคํฌ ๊ฒ€์ฆ] + // ------------------------------------------------------------- + val dailyData = tradeService.fetchPeriodChartData(stock.code, "D", true).getOrNull() + ?: return@withTimeout + + // 5. [๋ณด์™„] ๋ฐ์ดํ„ฐ ๋ถ€์กฑ ์ข…๋ชฉ ๋ฐฉ์–ด (์‹ ๊ทœ ์ƒ์žฅ์ฃผ, ํ†ต๊ณ„ ์ตœ์†Œ ํ‘œ๋ณธ 30๋ด‰ ๋ฏธ๋งŒ ์ฐจ๋‹จ) + if (dailyData.size < 30) { + print("-> [${stock.name}] ์บ”๋“ค ๋ฐ์ดํ„ฐ ๋ถ€์กฑ(30๋ด‰ ๋ฏธ๋งŒ) ์ œ์™ธ | ") + return@withTimeout + } + + val today = dailyData.lastOrNull() + val rate = today?.getFluctuationRate() ?: 0.0 + val isOk = (rate < KisSession.tradeConfig.plusFilter) && (rate > (abs(KisSession.tradeConfig.minusFilter) * -1)) + delay(50) -// 1. var ๋Œ€์‹  val์„ ์‚ฌ์šฉํ•ด์•ผ ์•„๋ž˜์—์„œ ์Šค๋งˆํŠธ ์บ์ŠคํŠธ๊ฐ€ ์ž‘๋™ํ•˜์—ฌ !!๋ฅผ ์•ˆ ์จ๋„ ๋ฉ๋‹ˆ๋‹ค. val currentStock = KisTradeService.fetchCurrentPrice(stock.code).getOrNull() if (today == null || currentStock == null) { -// failList.add(stock.code) - print("-> ๊ธˆ์ผ ๊ธˆ์•ก ์กฐํšŒ ์‹คํŒจ | ${isOk}") + print("-> ๊ธˆ์ผ ๊ธˆ์•ก ์กฐํšŒ ์‹คํŒจ | isOk: ${isOk} | ") return@withTimeout } - // 3. ์œ„์—์„œ ํ™•์‹คํ•˜๊ฒŒ null ์ฒดํฌ๋ฅผ ํ–ˆ์œผ๋ฏ€๋กœ, ์ด์ œ currentStock์€ ์ ˆ๋Œ€ null์ด ์•„๋‹™๋‹ˆ๋‹ค. -// ์•ˆ์ „ํ•˜๊ฒŒ(Safe call ? ์—†์ด) ๋ฐ”๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. - val currentPrice = currentStock.stck_prpr.toDouble() - println("${stock.name}[${stock.code}] ํ˜„์žฌ๊ฐ€ : ${currentPrice} , ๋ณ€๋™๋ฅ  : ${rate} , ๊ฑฐ๋ž˜ ๊ธฐ์ค€ : ${isOk}") -// 4. ์œ„ํ—˜ํ•œ !! ๋‹จ์–ธ ๊ธฐํ˜ธ ์—†์ด ๊น”๋”ํ•˜๊ฒŒ ํ˜ธ์ถœ + val currentPrice = currentStock.stck_prpr.toDouble() + println("${stock.name}[${stock.code}] ํ˜„์žฌ๊ฐ€: ${currentPrice}, ๋ณ€๋™๋ฅ : ${rate}%, ๊ฑฐ๋ž˜๊ธฐ์ค€(isOk): ${isOk}") + + // 6. ๋ฆฌ์Šคํฌ ๋งค๋‹ˆ์ € ๊ฒ€๋ฌธ์†Œ ํ†ต๊ณผ ์—ฌ๋ถ€ ํ™•์ธ (๊ด€๋ฆฌ์ข…๋ชฉ, ํ™˜๊ธฐ์ข…๋ชฉ ๋“ฑ) val riskResult = RiskManager.evaluateRisk(currentStock) if (!riskResult.isSafe) { - print("-> ${stock.name}[${stock.code}] ๊ฒ€๋ฌธ์†Œ ํƒˆ๋ฝ: ${riskResult.rejectReason}") + print("-> ${stock.name}[${stock.code}] ๋ฆฌ์Šคํฌ ๊ฒ€๋ฌธ์†Œ ํƒˆ๋ฝ: ${riskResult.rejectReason} | ") return@withTimeout } - + // 7. ๊ณ„์ขŒ ์˜ˆ์‚ฐ ๋ฐ ๋‹จ๊ฐ€ ์ •์ฑ… ํ•„ํ„ฐ๋ง if (!isOk || (myCash > 10L && currentPrice > myCash) || currentPrice > maxBudget || currentPrice > maxPrice || currentPrice < minPrice) { - print("-> [${stock.name}] ๊ฐ€๊ฒฉ ์ •์ฑ…์œผ๋กœ ์ œ์™ธ [1์ฃผ:${currentPrice}, ์ž์‚ฐ:${myCash}, ์ตœ์†Œ ๊ธฐ์ค€:${minPrice}, ์ตœ๋Œ€ ๊ธฐ์ค€:${maxPrice}] | ") + print("-> [${stock.name}] ๊ฐ€๊ฒฉ ์ •์ฑ…์œผ๋กœ ์ œ์™ธ [1์ฃผ:${currentPrice}, ์ž์‚ฐ:${myCash}, ์ตœ์†Œ:${minPrice}, ์ตœ๋Œ€:${maxPrice}] | ") return@withTimeout } - - // ๐ŸŒŸ [์ถ”๊ฐ€] ๊ณ ๋„ํ™”๋œ ์‚ฌ์ „ ํ•„ํ„ฐ๋ง (๊ฒ€๋ฌธ์†Œ) + // ------------------------------------------------------------- + // [3์ฐจ ํ•„ํ„ฐ: ๊ธฐ์ˆ ์  ํ†ต๊ณ„ ๋ถ„์„ (๋ณ€๋™์„ฑ, ๋ฐ˜๋“ฑ ์ฃผ๊ธฐ, ์ง€ํ•˜์‹ค ํšŒํ”ผ)] + // ------------------------------------------------------------- val tempAnalyzer = TechnicalAnalyzer().apply { this.daily = dailyData } - // 1. ๋ณ€๋™์„ฑ ๊ธฐ๋ฐ˜ ์ˆ˜์ต๋ฅ  ๊ฒ€์ฆ (2% ์ด์ƒ ์—ด๋ ค์žˆ๋Š”๊ฐ€?) - println("(dailyData.size * 0.8).toInt() ${(dailyData.size * 0.3).toInt()}") + // 1. ๋ณ€๋™์„ฑ ๊ธฐ๋ฐ˜ ํ˜„์‹ค์  ๊ธฐ๋Œ€์ˆ˜์ต๋ฅ  ์˜ˆ์ธก val volatility = tempAnalyzer.calculateVolatilityForecast(dailyData, 20) - val expectedProfitRate = - ((volatility.realisticHigh - currentPrice) / currentPrice) * 100.0 + val expectedProfitRate = ((volatility.realisticHigh - currentPrice) / currentPrice) * 100.0 - // 2. ์ผ๋ด‰ ๊ธฐ์ค€ ๋ฐ˜๋“ฑ ์ฃผ๊ธฐ ํ†ต๊ณ„ ์ถ”์ถœ (์ผ์ฃผ์ผ ๋‚ด ์Šน๋ถ€ ๊ฐ€๋Šฅํ•œ๊ฐ€?) + // 2. ๋™์  ๋ฐ˜๋“ฑ ํ†ต๊ณ„ ์‚ฐ์ถœ val dailyStats = tempAnalyzer.calculateDynamicReboundStats(dailyData, 5.0) val isApproaching = tempAnalyzer.checkReboundApproaching( candles = dailyData, @@ -1433,117 +1462,89 @@ object AutoTradingManager { dropThreshold = dailyStats.avgDropRate, timeTolerance = dailyStats.timeTolerance ) - print("-> [${stock.name}] ํ•„ํ„ฐ๋ง ${dailyStats.avgReboundPeriod} ${dailyStats.avgDropRate} ${dailyStats.timeTolerance}") - val isSteadyUptrend = false //tempAnalyzer.checkSteadyUptrend(dailyData) + print("-> [${stock.name}] ๋ฐ˜๋“ฑํ†ต๊ณ„(์ฃผ๊ธฐ:${"%.1f".format(dailyStats.avgReboundPeriod)}์ผ, ํ‰๊ท ํ•˜๋ฝํญ:${"%.1f".format(dailyStats.avgDropRate)}%, ์˜ค์ฐจ:${"%.1f".format(dailyStats.timeTolerance)})") + // 3. ์šฐ์ƒํ–ฅ ์ถ”์„ธ(Trend Following) ํŒ๋ณ„ - ํ•„์š” ์‹œ ํ™œ์„ฑํ™” + val isSteadyUptrend = tempAnalyzer.checkSteadyUptrend(dailyData) - // ๐ŸŒŸ [์ˆ˜์ •] ์กฐ๊ฑด ํ†ตํ•ฉ (OR ์กฐ๊ฑด) - val isProfitable = - expectedProfitRate >= KisSession.tradeConfig.minExpectedProfitRate || dailyStats.avgReboundAmplitude >= KisSession.tradeConfig.minExpectedProfitRate + // 4. ๊ธฐ๋Œ€์ˆ˜์ต๋ฅ  ์กฐ๊ฑด (์˜ˆ์ธก ์ˆ˜์ต ๋˜๋Š” ๊ณผ๊ฑฐ ํ‰๊ท  ๋ฐ˜๋“ฑํญ์ด ๊ธฐ์ค€ ์ด์ƒ์ธ๊ฐ€?) + val isProfitable = expectedProfitRate >= KisSession.tradeConfig.minExpectedProfitRate || + dailyStats.avgReboundAmplitude >= KisSession.tradeConfig.minExpectedProfitRate - // ๋ฐ˜๋“ฑ ์ฃผ๊ธฐ์— ๋„๋‹ฌํ–ˆ๊ฑฐ๋‚˜(Mean Reversion), ์•ˆ์ •์ ์œผ๋กœ ๋ป—์–ด๋‚˜๊ฐ€๋Š” ์šฐ์ƒํ–ฅ ์ข…๋ชฉ(Trend Following)์ด๋ฉด ํ†ต๊ณผ - val isValidEntryTiming = - (dailyStats.isValid && isApproaching && dailyStats.avgReboundPeriod <= KisSession.tradeConfig.maxExpectedReboundDays && dailyStats.avgReboundPeriod >= KisSession.tradeConfig.minExpectedReboundDays) || isSteadyUptrend + // 5. ์ง„์ž… ํƒ€์ด๋ฐ ์กฐ๊ฑด (ํ†ต๊ณ„์  ๋ฐ˜๋“ฑ ์ฃผ๊ธฐ์— ๋„๋‹ฌํ–ˆ๊ฑฐ๋‚˜, ์•ˆ์ •์  ์šฐ์ƒํ–ฅ ์ถ”์„ธ์ธ๊ฐ€?) + val isValidEntryTiming = (dailyStats.isValid && isApproaching && + dailyStats.avgReboundPeriod <= KisSession.tradeConfig.maxExpectedReboundDays && + dailyStats.avgReboundPeriod >= KisSession.tradeConfig.minExpectedReboundDays) || isSteadyUptrend val currentAtr = tempAnalyzer.calculateATR(dailyData) if (!isProfitable || !isValidEntryTiming) { - print( - "-> [${stock.name}] ์กฐ๊ฑด ๋ฏธ๋‹ฌ ํ•„ํ„ฐ๋ง (์˜ˆ์ธก์ˆ˜์ต: ${"%.1f".format(expectedProfitRate)}%, ์ฃผ๊ธฐ: ${ - "%.1f".format( - dailyStats.avgReboundPeriod - ) - }์ผ, ์ง„์ž…๊ถŒ: $isValidEntryTiming) | " - ) - return@withTimeout // ์กฐ๊ฑด์— ๋งž์ง€ ์•Š์œผ๋ฉด ์ฃผ๋ด‰/์›”๋ด‰ API ํ˜ธ์ถœ ๋ฐ LLM ๋ถ„์„ ์—†์ด ์ฆ‰์‹œ ๋‹ค์Œ ์ข…๋ชฉ์œผ๋กœ ๋„˜์–ด๊ฐ + print("-> [${stock.name}] ์กฐ๊ฑด ๋ฏธ๋‹ฌ ํ•„ํ„ฐ๋ง (์˜ˆ์ธก์ˆ˜์ต:${"%.1f".format(expectedProfitRate)}%, ์ฃผ๊ธฐ:${"%.1f".format(dailyStats.avgReboundPeriod)}์ผ, ์ง„์ž…๊ถŒ:$isValidEntryTiming) | ") + return@withTimeout } - val dropPrediction = - tempAnalyzer.predictDropBottom(dailyData, dailyStats, volatility, currentAtr) + // 6. [๋ณด์™„] ๋ฐ”๋‹ฅ ์˜ˆ์ธก ๋ฐ ์ง€ํ•˜์‹ค(์ถ”๊ฐ€ ๋‚™ํญ) ํšŒํ”ผ ๋กœ์ง + val dropPrediction = tempAnalyzer.predictDropBottom(dailyData, dailyStats, volatility, currentAtr) if (dropPrediction != null) { - // ๐Ÿ’ก [๋ฐฉ์–ด ๋กœ์ง 1] ์•„์ง ๋ฐ”๋‹ฅ๊นŒ์ง€ ํ•œ์ฐธ ๋‚จ์•˜๋‹ค๋ฉด ์ง€ํ•˜์‹ค ๋ฐฉ์ง€ - if (dropPrediction.remainingDropRate < -1.0 && !dropPrediction.isBottomZone) { - print("-> [${stock.name}] ์ง€ํ•˜์‹ค ์ฃผ์˜ (ํ˜„์žฌ ${"%.1f".format(dropPrediction.currentDropRate)}% ํ•˜๋ฝ, ์ถ”๊ฐ€ ํ•˜๋ฝ ์œ„ํ—˜) | ") + // (1) ๋ถ€ํ˜ธ ํ˜ผ์„  ๋ฐฉ์ง€: ํ˜„์žฌ๊ฐ€์™€ ์˜ˆ์ƒ ๋ฐ”๋‹ฅ๊ฐ€ ๊ฐ„์˜ ์‹ค์ œ ์ถ”๊ฐ€ ํ•˜๋ฝ ์—ฌ๋ ฅ(%) ์ง๊ด€์  ๊ณ„์‚ฐ + val distanceToBottomPct = ((currentPrice - dropPrediction.expectedBottomPrice) / currentPrice) * 100.0 + + // ๋ฐ”๋‹ฅ์กด์— ๋“ค์–ด์˜ค์ง€ ์•Š์•˜๋Š”๋ฐ ๋ฐ”๋‹ฅ๊นŒ์ง€ 1.5% ์ด์ƒ ์ถ”๊ฐ€ ํ•˜๋ฝ ์—ฌ๋ ฅ์ด ๋‚จ์•„์žˆ๋‹ค๋ฉด ์ง„์ž… ์ฐจ๋‹จ + if (distanceToBottomPct > 2.0 && !dropPrediction.isBottomZone) { + print("-> [${stock.name}] ์ง€ํ•˜์‹ค ์ฃผ์˜ (์˜ˆ์ƒ ๋ฐ”๋‹ฅ๊นŒ์ง€ -${"%.1f".format(distanceToBottomPct)}% ์ถ”๊ฐ€ ํ•˜๋ฝ ์—ฌ๋ ฅ) | ") return@withTimeout } - // ๐Ÿ’ก [๋ฐฉ์–ด ๋กœ์ง 2 - ์‹ ๊ทœ] ๊ฐ€๊ฒฉ์€ ๋ฐ”๋‹ฅ๊ถŒ์— ์™”์ง€๋งŒ, ์บ”๋“ค์— ๋ธŒ๋ ˆ์ดํฌ(์ง€์ง€)๊ฐ€ ๊ฑธ๋ ธ๋Š”๊ฐ€? + // (2) ๋ฐ”๋‹ฅ๊ถŒ ๋„๋‹ฌ ์‹œ ํ•˜๋ฝ์„ ๋ฉˆ์ถ”๋Š” ๋ธŒ๋ ˆ์ดํฌ(์ง€์ง€ ์บ”๋“ค/๋ฐ˜๋“ฑ ์‹œ๊ทธ๋„) ํ™•์ธ if (dropPrediction.isBottomZone) { val hasBrake = tempAnalyzer.checkBrakeAndReversal(dailyData) if (!hasBrake) { - print("-> [${stock.name}] ๋ฐ”๋‹ฅ ๊ฐ€๊ฒฉ ๋„๋‹ฌํ–ˆ์œผ๋‚˜, ๋ธŒ๋ ˆ์ดํฌ(์ง€์ง€/๊ฑฐ๋ž˜๋Ÿ‰ ์ง„์ •) ๋ฏธํ™•์ธ. ๋–จ์–ด์ง€๋Š” ์นผ๋‚  ํšŒํ”ผ | ") - return@withTimeout // ๋ธŒ๋ ˆ์ดํฌ๊ฐ€ ์—†์œผ๋ฉด ๋งค์ˆ˜ ์•ˆ ํ•จ! + print("-> [${stock.name}] ๋ฐ”๋‹ฅ ๊ฐ€๊ฒฉ ๋„๋‹ฌํ–ˆ์œผ๋‚˜ ์ง€์ง€/๋ธŒ๋ ˆ์ดํฌ ๋ฏธํ™•์ธ (๋–จ์–ด์ง€๋Š” ์นผ๋‚  ํšŒํ”ผ) | ") + return@withTimeout } } } - if (KisSession.tradeConfig.isUpcomingDividend) { - var dividend = KisTradeService.fetchUpcomingDividend(stock.code).getOrNull() - if (dividend?.hasDividend == true) { - println("[${stock.name}] ๋ฐฐ๋‹น๋ฝ์ผ ${dividend.exDividendDate} : ${dividend.dividendAmount}") - } else { - println("[${stock.name}] ๋ฐฐ๋‹น ์ •๋ณด ์—†์–ด์„œ ๋ถ„์„ ์ข…๋ฃŒ") - return@withTimeout + println("\n๐Ÿ” [์‚ฌ์ „ ํ•„ํ„ฐ ํ†ต๊ณผ -> ์ •๋ฐ€ ๋ถ„์„ ์ง„์ž…] ${stock.name} (${LocalTime.now()})") + + // ------------------------------------------------------------- + // [4์ฐจ: ๋ฉ€ํ‹ฐ ํƒ€์ž„ํ”„๋ ˆ์ž„(30๋ถ„/์ฃผ๋ด‰/์›”๋ด‰) ๋น„๋™๊ธฐ ํ˜ธ์ถœ & AI(RAG) ๋ถ„์„] + // ------------------------------------------------------------- + val analyzer = coroutineScope { + val min30 = async { tradeService.fetchChartData(stock.code, true).getOrDefault(emptyList()) } + delay(20) + val weekly = async { tradeService.fetchPeriodChartData(stock.code, "W", true).getOrDefault(emptyList()) } + delay(20) + val monthly = async { tradeService.fetchPeriodChartData(stock.code, "M", true).getOrDefault(emptyList()) } + delay(20) + + TechnicalAnalyzer().apply { + this.daily = dailyData + delay(50) + this.min30 = min30.await() + delay(50) + this.weekly = weekly.await() + delay(50) + this.monthly = monthly.await() } - } else { - println("[${stock.name}] ๋ฐฐ๋‹น ์ •๋ณด ๋ฌด๊ด€ ํ•จ.") } - println( - "๐Ÿ” [๋ถ„์„ ์ง„์ž…] ${stock.name} (${LocalTime.now()}) (์˜ˆ์ธก์ˆ˜์ต: ${ - "%.1f".format( - expectedProfitRate + if (analyzer.isValid()) { + println("โœ… [๋ถ„์„ ์‹œ์ž‘] ${stock.name} (${LocalTime.now()} - ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ํ†ต๊ณผ)") + RagService.processStock( + currentPrice, + analyzer, + stock.name, + stock.code + ) { decision, isSuccess -> + callback( + decision?.apply { this.currentPrice = currentPrice }, + isSuccess ) - }%, ์ฃผ๊ธฐ: ${"%.1f".format(dailyStats.avgReboundPeriod)}์ผ, ์ง„์ž…๊ถŒ: $isValidEntryTiming)" - ) - if (!isSafetyBeltStockCodes.contains(stock.code)) { - val analyzer = coroutineScope { - val min30 = async { - tradeService.fetchChartData(stock.code, true).getOrDefault(emptyList()) - } - delay(20) - val weekly = - async { - tradeService.fetchPeriodChartData(stock.code, "W", true) - .getOrDefault(emptyList()) - } - delay(20) - val monthly = - async { - tradeService.fetchPeriodChartData(stock.code, "M", true) - .getOrDefault(emptyList()) - } - delay(20) - TechnicalAnalyzer().apply { - this.daily = dailyData - delay(50) - this.min30 = min30.await() - delay(50) - this.weekly = weekly.await() - delay(50) - this.monthly = monthly.await() - } - } - if (analyzer.isValid()) { - - println("โœ… [๋ถ„์„ ์‹œ์ž‘] ${stock.name} (${LocalTime.now()} ๋ถ„์„ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ -> ${analyzer.isValid()})") - RagService.processStock( - currentPrice, - analyzer, - stock.name, - stock.code - ) { decision, isSuccess -> - callback( - decision?.apply { this.currentPrice = currentPrice }, - isSuccess - ) - } - } else { - println("โœ… [๋ถ„์„ ์‹คํŒจ] ${stock.name} (${LocalTime.now()} ๋ถ„์„ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ -> ${analyzer.isValid()})") } } else { - println("์žฌ๋ฌด ์•ˆ์ •์„ฑ ๋ถ€์กฑ (์บ์‹œ)") + println("โŒ [๋ถ„์„ ์‹คํŒจ] ${stock.name} (${LocalTime.now()} - ํ•„์ˆ˜ ์บ”๋“ค ๋ฐ์ดํ„ฐ ๋ˆ„๋ฝ)") } - println("โœ… [๋ถ„์„ ์ข…๋ฃŒ] ${stock.name} (${LocalTime.now()})") + println("๐Ÿ [๋ถ„์„ ์ข…๋ฃŒ] ${stock.name} (${LocalTime.now()})") } } catch (e: Exception) { e.printStackTrace()