diff --git a/src/main/kotlin/model/AppConfig.kt b/src/main/kotlin/model/AppConfig.kt index b5b49e6..61bc142 100644 --- a/src/main/kotlin/model/AppConfig.kt +++ b/src/main/kotlin/model/AppConfig.kt @@ -247,8 +247,8 @@ class TradeConfig { var ONE_STOCK_ALYSIS_TIME = 180000L var isLowPerformanceMonitoring: Boolean = false var useGradeShare : List = listOf("LEVEL_4","LEVEL_5") - var useTagsShare : List = listOf("NOTICE", "WATCH","매도 완료") - var useLogKeywordsShare : List = listOf("재분석") + var useTagsShare : List = listOf("NOTICE", "WATCH") + var useLogKeywordsShare : List = listOf("재분석","매도 완료") var useAutoRepost : Boolean = false var minusFilter : Double = 15.0 var plusFilter : Double = 15.0 @@ -268,6 +268,8 @@ class TradeConfig { var maxExpectedReboundDays: Double = 10.0 // 필터링 기준 최대 허용 반등 주기 (일) var minExpectedReboundDays: Double = 1.5 var isUpcomingDividend : Boolean = false + var buyTickOffset : Int = 4 + var sellTickOffset : Int = 4 } diff --git a/src/main/kotlin/service/AutoTradingManager.kt b/src/main/kotlin/service/AutoTradingManager.kt index 37628da..a65dbfe 100644 --- a/src/main/kotlin/service/AutoTradingManager.kt +++ b/src/main/kotlin/service/AutoTradingManager.kt @@ -304,7 +304,7 @@ object AutoTradingManager { val price1 = finalPrice // 2차 매수는 1차 매수가보다 3호가 아래에 대기 (더 싼 가격에 줍기) - val price2 = MarketUtil.roundToTickSize(price1 - (tickSize * 3)) + val price2 = MarketUtil.roundToTickSize(price1 - (tickSize * KisSession.tradeConfig.buyTickOffset)) println("🔄 [분할 매수 진입] $stockName 1차(${qty1}주 / $price1) | 2차(${qty2}주 / $price2)") @@ -574,7 +574,7 @@ object AutoTradingManager { val qty2 = totalQty - qty1 // 나머지 (4호가 위) val price1 = currentPrice - val price2 = MarketUtil.roundToTickSize(currentPrice + (MarketUtil.getTickSize(currentPrice) * 4)) + val price2 = MarketUtil.roundToTickSize(currentPrice + (MarketUtil.getTickSize(currentPrice) * KisSession.tradeConfig.sellTickOffset)) TradingLogStore.addAfterMarketLog( holding.name, @@ -639,7 +639,7 @@ object AutoTradingManager { "${holding.name}[${holding.code}]", price2.toString(), "SELL", - "🎊 ${if (marketCode == "Y") "시간외 단일가" else "대체거래소"} 2차 분할매도(4호가 위) ${qty2}주 매도 완료" + "🎊 ${if (marketCode == "Y") "시간외 단일가" else "대체거래소"} 2차 분할매도(${KisSession.tradeConfig.sellTickOffset}호가 위) ${qty2}주 매도 완료" ) DatabaseFactory.saveAutoTrade( AutoTradeItem( @@ -766,9 +766,10 @@ object AutoTradingManager { val qty2 = totalQty - qty1 val price1 = currentPrice - val price2 = MarketUtil.roundToTickSize(currentPrice + (MarketUtil.getTickSize(currentPrice) * 4)) + val price2 = MarketUtil.roundToTickSize(currentPrice + (MarketUtil.getTickSize(currentPrice) * KisSession.tradeConfig.sellTickOffset)) - println("🔄 [보유 주식 주문] ${holding.name} (${holding.code}) 분할 매도: 현재가($price1) ${qty1}주, 4호가 위($price2) ${qty2}주") + + println("🔄 [보유 주식 주문] ${holding.name} (${holding.code}) 분할 매도: 현재가($price1) ${qty1}주, ${KisSession.tradeConfig.sellTickOffset}호가 위($price2) ${qty2}주") // 1차 매도: 현재가 if (qty1 > 0) { @@ -1499,9 +1500,7 @@ object AutoTradingManager { if (corpInfo?.cName.isNullOrEmpty()) return@withTimeout // 🌟 [핵심] 물타기 대상 여부 플래그 식별 - val targetHolding = currentBalance?.getHoldings()?.firstOrNull { - it.code == stock.code && it.quantity.toInt() > KisSession.tradeConfig.lowerAverageTargetCount - } + val targetHolding = currentBalance?.getHoldings()?.firstOrNull { it.code == stock.code && it.quantity.toInt() > 0 } val isWatering = targetHolding != null && KisSession.tradeConfig.lowerAveragePrice if (isWatering) { @@ -1570,7 +1569,7 @@ object AutoTradingManager { // 🌟 [완화 3] 기대수익률 및 진입 타이밍 이원화 val isProfitable: Boolean val isValidEntryTiming: Boolean - + val isSteadyUptrend = tempAnalyzer.checkSteadyUptrend(dailyData) if (isWatering) { // 물타기: 평단 낮추기 목적이므로 기대수익률 1.0% 이상이면 충분 isProfitable = expectedProfitRate >= 1.0 || dailyStats.avgReboundAmplitude >= 1.5 @@ -1586,7 +1585,7 @@ object AutoTradingManager { } else { // 신규 진입: 기존 엄격 기준 유지 isProfitable = expectedProfitRate >= KisSession.tradeConfig.minExpectedProfitRate || - dailyStats.avgReboundAmplitude >= KisSession.tradeConfig.minExpectedProfitRate + (dailyStats.avgReboundAmplitude.times(0.8)) >= KisSession.tradeConfig.minExpectedProfitRate val isApproaching = tempAnalyzer.checkReboundApproaching( dailyData, dailyStats.avgReboundPeriod, dailyStats.avgDropRate, dailyStats.timeTolerance @@ -1594,12 +1593,12 @@ object AutoTradingManager { isValidEntryTiming = (dailyStats.isValid && isApproaching && dailyStats.avgReboundPeriod <= KisSession.tradeConfig.maxExpectedReboundDays && dailyStats.avgReboundPeriod >= KisSession.tradeConfig.minExpectedReboundDays) || - tempAnalyzer.checkSteadyUptrend(dailyData) + isSteadyUptrend } val currentAtr = tempAnalyzer.calculateATR(dailyData) if (!isProfitable || !isValidEntryTiming) { - println("-> [${stock.name}] 목표 수익 조건 미달 (물타기:$isWatering, 수익:$isProfitable, 타이밍:$isValidEntryTiming) | 기대 수익율 : ${expectedProfitRate} || ${dailyStats.avgReboundAmplitude}") + println("-> [${stock.name}] 목표 수익 조건 미달 (물타기:$isWatering, 수익:$isProfitable, 타이밍:$isValidEntryTiming, 우상향:$isSteadyUptrend) | 기대 수익율 : ${expectedProfitRate} || ${dailyStats.avgReboundAmplitude}") return@withTimeout } diff --git a/src/main/kotlin/ui/TradingDecisionLog.kt b/src/main/kotlin/ui/TradingDecisionLog.kt index 1710c2d..1db6d9c 100644 --- a/src/main/kotlin/ui/TradingDecisionLog.kt +++ b/src/main/kotlin/ui/TradingDecisionLog.kt @@ -729,6 +729,29 @@ fun TradingDecisionLog() { ) } + Row(horizontalArrangement = Arrangement.SpaceEvenly) { + SettingInputField( + modifier = Modifier.weight(1.0f, true), + label = "분할 매수시", + initialValue = (tradeConfig.buyTickOffset).toString(), + onSave = { + tradeConfig.buyTickOffset = it.toInt() + KisSession.saveTradeConfig() + }, + helperText = "매수가 기준 아래 호가로 분할 매수" + ) + SettingInputField( + modifier = Modifier.weight(1.0f, true), + label = "분할 매도시", + initialValue = (tradeConfig.sellTickOffset).toString(), + onSave = { + tradeConfig.sellTickOffset = it.toInt() + KisSession.saveTradeConfig() + }, + helperText = "매수가 기준 위 호가로 분할 매수" + ) + } + } } }