This commit is contained in:
2026-09-21 17:04:53 +09:00
parent bcdb3546fa
commit 40c70577ca
3 changed files with 38 additions and 14 deletions
+4 -2
View File
@@ -247,8 +247,8 @@ class TradeConfig {
var ONE_STOCK_ALYSIS_TIME = 180000L var ONE_STOCK_ALYSIS_TIME = 180000L
var isLowPerformanceMonitoring: Boolean = false var isLowPerformanceMonitoring: Boolean = false
var useGradeShare : List<String> = listOf("LEVEL_4","LEVEL_5") var useGradeShare : List<String> = listOf("LEVEL_4","LEVEL_5")
var useTagsShare : List<String> = listOf("NOTICE", "WATCH","매도 완료") var useTagsShare : List<String> = listOf("NOTICE", "WATCH")
var useLogKeywordsShare : List<String> = listOf("재분석") var useLogKeywordsShare : List<String> = listOf("재분석","매도 완료")
var useAutoRepost : Boolean = false var useAutoRepost : Boolean = false
var minusFilter : Double = 15.0 var minusFilter : Double = 15.0
var plusFilter : Double = 15.0 var plusFilter : Double = 15.0
@@ -268,6 +268,8 @@ class TradeConfig {
var maxExpectedReboundDays: Double = 10.0 // 필터링 기준 최대 허용 반등 주기 (일) var maxExpectedReboundDays: Double = 10.0 // 필터링 기준 최대 허용 반등 주기 (일)
var minExpectedReboundDays: Double = 1.5 var minExpectedReboundDays: Double = 1.5
var isUpcomingDividend : Boolean = false var isUpcomingDividend : Boolean = false
var buyTickOffset : Int = 4
var sellTickOffset : Int = 4
} }
+11 -12
View File
@@ -304,7 +304,7 @@ object AutoTradingManager {
val price1 = finalPrice val price1 = finalPrice
// 2차 매수는 1차 매수가보다 3호가 아래에 대기 (더 싼 가격에 줍기) // 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)") println("🔄 [분할 매수 진입] $stockName 1차(${qty1}주 / $price1) | 2차(${qty2}주 / $price2)")
@@ -574,7 +574,7 @@ object AutoTradingManager {
val qty2 = totalQty - qty1 // 나머지 (4호가 위) val qty2 = totalQty - qty1 // 나머지 (4호가 위)
val price1 = currentPrice 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( TradingLogStore.addAfterMarketLog(
holding.name, holding.name,
@@ -639,7 +639,7 @@ object AutoTradingManager {
"${holding.name}[${holding.code}]", "${holding.name}[${holding.code}]",
price2.toString(), price2.toString(),
"SELL", "SELL",
"🎊 ${if (marketCode == "Y") "시간외 단일가" else "대체거래소"} 2차 분할매도(4호가 위) ${qty2}주 매도 완료" "🎊 ${if (marketCode == "Y") "시간외 단일가" else "대체거래소"} 2차 분할매도(${KisSession.tradeConfig.sellTickOffset}호가 위) ${qty2}주 매도 완료"
) )
DatabaseFactory.saveAutoTrade( DatabaseFactory.saveAutoTrade(
AutoTradeItem( AutoTradeItem(
@@ -766,9 +766,10 @@ object AutoTradingManager {
val qty2 = totalQty - qty1 val qty2 = totalQty - qty1
val price1 = currentPrice 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차 매도: 현재가 // 1차 매도: 현재가
if (qty1 > 0) { if (qty1 > 0) {
@@ -1499,9 +1500,7 @@ object AutoTradingManager {
if (corpInfo?.cName.isNullOrEmpty()) return@withTimeout if (corpInfo?.cName.isNullOrEmpty()) return@withTimeout
// 🌟 [핵심] 물타기 대상 여부 플래그 식별 // 🌟 [핵심] 물타기 대상 여부 플래그 식별
val targetHolding = currentBalance?.getHoldings()?.firstOrNull { val targetHolding = currentBalance?.getHoldings()?.firstOrNull { it.code == stock.code && it.quantity.toInt() > 0 }
it.code == stock.code && it.quantity.toInt() > KisSession.tradeConfig.lowerAverageTargetCount
}
val isWatering = targetHolding != null && KisSession.tradeConfig.lowerAveragePrice val isWatering = targetHolding != null && KisSession.tradeConfig.lowerAveragePrice
if (isWatering) { if (isWatering) {
@@ -1570,7 +1569,7 @@ object AutoTradingManager {
// 🌟 [완화 3] 기대수익률 및 진입 타이밍 이원화 // 🌟 [완화 3] 기대수익률 및 진입 타이밍 이원화
val isProfitable: Boolean val isProfitable: Boolean
val isValidEntryTiming: Boolean val isValidEntryTiming: Boolean
val isSteadyUptrend = tempAnalyzer.checkSteadyUptrend(dailyData)
if (isWatering) { if (isWatering) {
// 물타기: 평단 낮추기 목적이므로 기대수익률 1.0% 이상이면 충분 // 물타기: 평단 낮추기 목적이므로 기대수익률 1.0% 이상이면 충분
isProfitable = expectedProfitRate >= 1.0 || dailyStats.avgReboundAmplitude >= 1.5 isProfitable = expectedProfitRate >= 1.0 || dailyStats.avgReboundAmplitude >= 1.5
@@ -1586,7 +1585,7 @@ object AutoTradingManager {
} else { } else {
// 신규 진입: 기존 엄격 기준 유지 // 신규 진입: 기존 엄격 기준 유지
isProfitable = expectedProfitRate >= KisSession.tradeConfig.minExpectedProfitRate || isProfitable = expectedProfitRate >= KisSession.tradeConfig.minExpectedProfitRate ||
dailyStats.avgReboundAmplitude >= KisSession.tradeConfig.minExpectedProfitRate (dailyStats.avgReboundAmplitude.times(0.8)) >= KisSession.tradeConfig.minExpectedProfitRate
val isApproaching = tempAnalyzer.checkReboundApproaching( val isApproaching = tempAnalyzer.checkReboundApproaching(
dailyData, dailyStats.avgReboundPeriod, dailyStats.avgDropRate, dailyStats.timeTolerance dailyData, dailyStats.avgReboundPeriod, dailyStats.avgDropRate, dailyStats.timeTolerance
@@ -1594,12 +1593,12 @@ object AutoTradingManager {
isValidEntryTiming = (dailyStats.isValid && isApproaching && isValidEntryTiming = (dailyStats.isValid && isApproaching &&
dailyStats.avgReboundPeriod <= KisSession.tradeConfig.maxExpectedReboundDays && dailyStats.avgReboundPeriod <= KisSession.tradeConfig.maxExpectedReboundDays &&
dailyStats.avgReboundPeriod >= KisSession.tradeConfig.minExpectedReboundDays) || dailyStats.avgReboundPeriod >= KisSession.tradeConfig.minExpectedReboundDays) ||
tempAnalyzer.checkSteadyUptrend(dailyData) isSteadyUptrend
} }
val currentAtr = tempAnalyzer.calculateATR(dailyData) val currentAtr = tempAnalyzer.calculateATR(dailyData)
if (!isProfitable || !isValidEntryTiming) { if (!isProfitable || !isValidEntryTiming) {
println("-> [${stock.name}] 목표 수익 조건 미달 (물타기:$isWatering, 수익:$isProfitable, 타이밍:$isValidEntryTiming) | 기대 수익율 : ${expectedProfitRate} || ${dailyStats.avgReboundAmplitude}") println("-> [${stock.name}] 목표 수익 조건 미달 (물타기:$isWatering, 수익:$isProfitable, 타이밍:$isValidEntryTiming, 우상향:$isSteadyUptrend) | 기대 수익율 : ${expectedProfitRate} || ${dailyStats.avgReboundAmplitude}")
return@withTimeout return@withTimeout
} }
+23
View File
@@ -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 = "매수가 기준 위 호가로 분할 매수"
)
}
} }
} }
} }