diff --git a/src/main/kotlin/analyzer/TechnicalAnalyzer.kt b/src/main/kotlin/analyzer/TechnicalAnalyzer.kt index 6a40189..934cccc 100644 --- a/src/main/kotlin/analyzer/TechnicalAnalyzer.kt +++ b/src/main/kotlin/analyzer/TechnicalAnalyzer.kt @@ -373,18 +373,15 @@ class TechnicalAnalyzer { // ๐ŸŒŸ 2. 3๊ฐ€์ง€ ํ•ต์‹ฌ ์กฐ๊ฑด ๋ถ„๋ฆฌ val isPriceDropped = currentDropRate <= -dropThreshold - // ์กฐ๊ฑด A: ๊ฐ€๊ฒฉ์ด ํ†ต๊ณ„์  ํ•˜๋ฝํญ๋งŒํผ ์ถฉ๋ถ„ํžˆ ๋น ์กŒ๋Š”๊ฐ€? - val isPastMinTime = daysSincePeak >= (avgReboundTerm - timeTolerance) - // ์กฐ๊ฑด B: ๋ฐ˜๋“ฑ '์ตœ์†Œ' ๊ธฐ๊ฐ„์„ ์ฑ„์› ๋Š”๊ฐ€? (๋–จ์–ด์ง€๋Š” ์นผ๋‚ ์„ ๋„ˆ๋ฌด ์ผ์ฐ ์žก๋Š” ๊ฒƒ ๋ฐฉ์ง€) - val isWithinMaxTime = daysSincePeak <= (avgReboundTerm + (timeTolerance * 2)) - // ์กฐ๊ฑด C: ๋ฐ˜๋“ฑ '์ตœ๋Œ€' ๊ธฐ๊ฐ„์„ ๋„˜๊ธฐ์ง€ ์•Š์•˜๋Š”๊ฐ€? (์ฃฝ์€ ์ฃผ์‹์ฒ˜๋Ÿผ ๋„ˆ๋ฌด ์˜ค๋ž˜ ํšก๋ณดํ•˜๋Š” ๊ฒƒ ๋ฐฉ์ง€) + // ์ตœ๋Œ€ ๊ธฐ๊ฐ„ ์กฐ๊ฑด์€ ์ฐธ๊ณ ์šฉ์œผ๋กœ ๋‚จ๊ฒจ๋‘๋˜ ๋งค์ˆ˜ ์ฐจ๋‹จ ๋กœ์ง์—์„œ๋Š” ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค. + // val isWithinMaxTime = daysSincePeak <= (avgReboundTerm + (timeTolerance * 2)) - // ๐ŸŒŸ 3. 3๊ฐœ ์ค‘ 2๊ฐœ ์ด์ƒ ๋งŒ์กฑ ์‹œ ๋ฐ˜๋“ฑ ์ž„๋ฐ•(Approaching)์œผ๋กœ ํŒ๋‹จ - val passedConditions = listOf(isPriceDropped, isPastMinTime, isWithinMaxTime).count { it } - //A. ์ง„์ž… ํ•˜๋ฝ๋ฅ  ๊ธฐ์ค€checkReboundApproaching 3๊ฐœ ์ค‘ 2๊ฐœ ๋งŒ์กฑ ์‹œ ํ†ต๊ณผ ๊ฐ€๊ฒฉ ํ•˜๋ฝ ์กฐ๊ฑด(isPriceDropped)์„ ํ•„์ˆ˜ ์กฐ๊ฑด(AND)์œผ๋กœ ๊ณ ์ • - return passedConditions > 2 + // ๐ŸŒŸ 3. ํ˜„์‹ค์ ์ธ ํƒ€์  ํŒ๋ณ„ (ํ•„์ˆ˜ 2๊ฐ€์ง€๋งŒ ๊ฐ•๋ ฅํ•˜๊ฒŒ ์š”๊ตฌ) + // ํ•„์ˆ˜ 1: ๊ฐ€๊ฒฉ์ด ํ†ต๊ณ„์  ํ•˜๋ฝํญ๋งŒํผ ์ถฉ๋ถ„ํžˆ ๋น ์กŒ๋Š”๊ฐ€? (๋ˆŒ๋ฆผ๋ชฉ ๋Œ€์ „์ œ) + // ํ•„์ˆ˜ 2: ์ตœ์†Œํ•œ์˜ ๋ฐ˜๋“ฑ ์ค€๋น„ ๊ธฐ๊ฐ„(ํ‰๊ท  ๊ธฐ๊ฐ„ - ์˜ค์ฐจ)์€ ์ง€๋‚ฌ๋Š”๊ฐ€? (๋–จ์–ด์ง€๋Š” ์นผ๋‚  ๋ฐฉ์ง€) + return isPriceDropped && isPastMinTime } fun calculateMFI(candles: List, period: Int = 14): Double { diff --git a/src/main/kotlin/network/KisTradeService.kt b/src/main/kotlin/network/KisTradeService.kt index 0101f69..248fbcb 100644 --- a/src/main/kotlin/network/KisTradeService.kt +++ b/src/main/kotlin/network/KisTradeService.kt @@ -570,7 +570,7 @@ object KisTradeService { if (response.status.isSuccess()) { val body = response.body() if (body.rt_cd == "0") { - println("${body.output}") +// println("${body.output}") Result.success(body.output) } else { println("API ์—๋Ÿฌ: ${body.msg1}") diff --git a/src/main/kotlin/service/AutoTradingManager.kt b/src/main/kotlin/service/AutoTradingManager.kt index ff401ab..9bfe295 100644 --- a/src/main/kotlin/service/AutoTradingManager.kt +++ b/src/main/kotlin/service/AutoTradingManager.kt @@ -116,7 +116,7 @@ object AutoTradingManager { val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean -> val seoulZone = ZoneId.of("Asia/Seoul") val now = LocalTime.now(ZoneId.of("Asia/Seoul")) - if (KisSession.isAvailBuyTime(now) && isSuccess && completeTradingDecision != null) { + if (KisSession.isMarketOpenTime(now) && isSuccess && completeTradingDecision != null) { val decision = completeTradingDecision println("${decision.stockName} ${decision.decision}") @@ -409,7 +409,7 @@ object AutoTradingManager { "WATCH", "๋งค์ˆ˜ ์‹œ๊ฐ„ ์™ธ ๋ถ„์„ => ์žฌ๋ถ„์„ ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€" ) - } else { + } else if (KisSession.isMarketOpenTime(LocalTime.now()) == false){ val unfilledResult = KisTradeService.fetchUnfilledOrders() unfilledResult.onSuccess { response -> response.filter { it.sll_buy_dvsn_cd == "02" }.forEach { order -> @@ -464,7 +464,7 @@ object AutoTradingManager { if (hasCodes) { actualBuyPrice = actualBuyPrice * 1.1 } - val absoluteMinRate = KisSession.config.getValues(ConfigIndex.TAX_INDEX) + 0.05 + val absoluteMinRate = KisSession.config.getValues(ConfigIndex.TAX_INDEX) val finalProfitRate = maxOf(dbItem.profitRate, absoluteMinRate) val finalTargetPrice = MarketUtil.roundToTickSize(actualBuyPrice * (1 + finalProfitRate / 100.0)) @@ -1201,34 +1201,38 @@ object AutoTradingManager { candidates.addAll(reanalysisList) } reanalysisList.clear() + val wateringCodes = mutableSetOf() + if (KisSession.tradeConfig.lowerAveragePrice) { - currentBalance?.getHoldings()?.map { + // map ๋Œ€์‹  forEach๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ๋‚ญ๋น„ ๋ฐฉ์ง€ + currentBalance?.getHoldings()?.forEach { if ( it.quantity.toInt() > KisSession.tradeConfig.lowerAverageTargetCount && it.profitRate.toDouble() < 0.0 && it.profitRate.toDouble() < (abs(KisSession.tradeConfig.lowerAverageMaxRate) * -1) && it.profitRate.toDouble() > (abs(KisSession.tradeConfig.lowerAverageMinRate) * -1) ) { + // ๋ฌผํƒ€๊ธฐ ์ „์šฉ Set์— ์ฝ”๋“œ ๋“ฑ๋ก + wateringCodes.add(it.code) + candidates.add( RankingStock( mksc_shrn_iscd = it.code, hts_kor_isnm = it.name ) ) - println("๋ฌผํƒ€๊ธฐ ๋Œ€์ƒ ์ถ”๊ฐ€ ${it.name}[${it.code}]") - var oldTarget = it - if (oldTarget != null) { - var avgPrive = oldTarget.avgPrice.toDouble() + println("๐Ÿ’ง [๋ฌผํƒ€๊ธฐ ์šฐ์„ ์ˆœ์œ„ ํ• ๋‹น] ${it.name}[${it.code}]") - var qty = oldTarget.quantity.toDouble() - var basePrice = - ((avgPrive * qty) + it.currentPrice.toDouble()).div(qty!!.toInt() + 1) - println("๋ฌผํƒ€๊ธฐ ${avgPrive}, ${qty} ${basePrice}") - } + val avgPrive = it.avgPrice.toDouble() + val qty = it.quantity.toDouble() + val basePrice = ((avgPrive * qty) + it.currentPrice.toDouble()).div(qty.toInt() + 1) + println(" -> ๋‹จ๊ฐ€ ํ˜„ํ™ฉ: ํ‰๋‹จ๊ฐ€ $avgPrive, ๋ณด์œ  ${qty}์ฃผ, ์˜ˆ์ƒ๋‹จ๊ฐ€ $basePrice") } } } - remainingCandidates.addAll(candidates.filter { + + // 1. ๊ธฐ์กด ํ•„ํ„ฐ๋ง ๋ฐ ์ค‘๋ณต ์ œ๊ฑฐ ์ ์šฉ + val filteredCandidates = candidates.filter { (if (KisSession.tradeConfig.lowerAveragePrice) { true } else { @@ -1238,8 +1242,16 @@ object AutoTradingManager { it.code !in executionCache.values.map { it.code } && it.code !in failList && it.code !in isSafetyBeltStockCodes - }.distinctBy { it.code }) - remainingCandidates.shuffle() + }.distinctBy { it.code } + + // ๐ŸŒŸ 2. [ํ•ต์‹ฌ] ๋ฆฌ์ŠคํŠธ๋ฅผ ๋‘ ๊ทธ๋ฃน์œผ๋กœ ํŒŒํ‹ฐ์…”๋‹ (๋ฌผํƒ€๊ธฐ ๋Œ€์ƒ vs ์‹ ๊ทœ ๋ฐœ๊ตด) + val (wateringList, newList) = filteredCandidates.partition { it.code in wateringCodes } + + remainingCandidates.clear() + + // ๐ŸŒŸ 3. ๋ถ„์„ ๋Œ€๊ธฐ์—ด ์žฌ์กฐ๋ฆฝ (๋ฌผํƒ€๊ธฐ ๋Œ€์ƒ 1์ˆœ์œ„ ๋ฐฐ์น˜) + remainingCandidates.addAll(wateringList) // ๋ฌผํƒ€๊ธฐ ๋Œ€์ƒ์„ ๋ฌด์กฐ๊ฑด ๋ฆฌ์ŠคํŠธ ๋งจ ์•ž์œผ๋กœ (์…”ํ”Œ ์•ˆ ํ•จ) + remainingCandidates.addAll(newList.shuffled()) } else { println("๋ฏธํ™•์ธ ๋ฐ์ดํ„ฐ ${remainingCandidates.size}") } @@ -1373,7 +1385,7 @@ object AutoTradingManager { // ๐ŸŒŸ [ํ•ต์‹ฌ] ๋ฌผํƒ€๊ธฐ ๋Œ€์ƒ ์—ฌ๋ถ€ ํ”Œ๋ž˜๊ทธ ์‹๋ณ„ val targetHolding = currentBalance?.getHoldings()?.firstOrNull { - it.code == stock.code && it.quantity.toInt() > 2 + it.code == stock.code && it.quantity.toInt() > 0 } val isWatering = targetHolding != null && KisSession.tradeConfig.lowerAveragePrice @@ -1472,7 +1484,7 @@ object AutoTradingManager { val currentAtr = tempAnalyzer.calculateATR(dailyData) if (!isProfitable || !isValidEntryTiming) { - print("-> [${stock.name}] ์กฐ๊ฑด ๋ฏธ๋‹ฌ (๋ฌผํƒ€๊ธฐ:$isWatering, ์ˆ˜์ต:$isProfitable, ํƒ€์ด๋ฐ:$isValidEntryTiming) | ") + print("-> [${stock.name}] ๋ชฉํ‘œ ์ˆ˜์ต ์กฐ๊ฑด ๋ฏธ๋‹ฌ (๋ฌผํƒ€๊ธฐ:$isWatering, ์ˆ˜์ต:$isProfitable, ํƒ€์ด๋ฐ:$isValidEntryTiming) | ๊ธฐ๋Œ€ ์ˆ˜์ต์œจ : ${expectedProfitRate}") return@withTimeout } @@ -1497,7 +1509,7 @@ object AutoTradingManager { } } - println("\n๐Ÿ’ง [๊ฒ€๋ฌธ์†Œ ํ†ต๊ณผ -> ${if (isWatering) "๋ฌผํƒ€๊ธฐ ํƒˆ์ถœ ๋ถ„์„" else "์‹ ๊ทœ ๋งค์ˆ˜ ๋ถ„์„"}] ${stock.name} (๋ฌผํƒ€๊ธฐ:$isWatering, ์ˆ˜์ต:$isProfitable, ํƒ€์ด๋ฐ:$isValidEntryTiming)") + println("\n๐Ÿ’ง [๊ฒ€๋ฌธ์†Œ ํ†ต๊ณผ -> ${if (isWatering) "๋ฌผํƒ€๊ธฐ ํƒˆ์ถœ ๋ถ„์„" else "์‹ ๊ทœ ๋งค์ˆ˜ ๋ถ„์„"}] ${stock.name} (๋ฌผํƒ€๊ธฐ:$isWatering, ์ˆ˜์ต:$isProfitable, ํƒ€์ด๋ฐ:$isValidEntryTiming) | ๊ธฐ๋Œ€ ์ˆ˜์ต์œจ : ${expectedProfitRate}") // ๋ฉ€ํ‹ฐ ํƒ€์ž„ํ”„๋ ˆ์ž„ ๋ฐ AI ๋ถ„์„ ์ง„์ž… val analyzer = coroutineScope {