This commit is contained in:
2026-06-26 10:17:03 +09:00
parent 81ce9b1539
commit 7474558136
2 changed files with 87 additions and 12 deletions
+26 -11
View File
@@ -462,7 +462,7 @@ object AutoTradingManager {
} else {
val now = LocalTime.now()
val targetProfitLimit = if (holding.isTodayEntry && now.isBefore(LocalTime.of(18, 0))) {
val targetProfitLimit = if (holding.isTodayEntry && now.isBefore(LocalTime.of(16, 0))) {
// 당일 매수 종목: 짧은 익절 (예: 1.0% 이상이면 즉시 매도)
KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)+ KisSession.config.getValues(ConfigIndex.TAX_INDEX)
} else {
@@ -973,10 +973,10 @@ object AutoTradingManager {
if (corpInfo?.cName.isNullOrEmpty()) {
false
} else if (it.code !in myHoldings &&
it.code !in pendingStocks &&
it.code !in executionCache.values.map { it.code } &&
it.code !in failList &&
it.code !in isSafetyBeltStockCodes){
it.code !in pendingStocks &&
it.code !in executionCache.values.map { it.code } &&
it.code !in failList &&
it.code !in isSafetyBeltStockCodes){
isOk
} else {
false
@@ -1060,7 +1060,7 @@ object AutoTradingManager {
if (now.isBefore(LocalTime.of(8,50)) && now.isAfter(LocalTime.of(8,45))) {
cancelAllPendingSellOrders()
isExecuted = true
} else if ( (now.isBefore(LocalTime.of(16,0)) && now.isAfter(KisSession.endBuyTime())) ) {
} else if ( (now.isBefore(LocalTime.of(15,40)) && now.isAfter(KisSession.endBuyTime())) ) {
val unfilledResult = KisTradeService.fetchUnfilledOrders()
unfilledResult.onSuccess { response ->
response.filter { it.sll_buy_dvsn_cd == "02" }.forEach { order ->
@@ -1082,8 +1082,11 @@ object AutoTradingManager {
println("⏰ [강제 스케줄 실행] 오전 9시 ${currentMinute}분 - 보유주식 매도 체크를 시작합니다.")
checkBalance()
isExecuted = true
} else if (((now.hour == 8 && KisSession.tradeConfig.before_nxt && currentMinute < 45) ||
(now.hour >= 16 && now.hour < 20 && KisSession.tradeConfig.after_nxt)) && (currentMinute % 2 == 0)) {
} else if (
(
(now.hour == 8 && KisSession.tradeConfig.before_nxt && currentMinute < 45) ||
(now.isAfter(LocalTime.of(15,40)) && now.isBefore(LocalTime.of(20,0)) && KisSession.tradeConfig.after_nxt)
) && (currentMinute % 2 == 0)) {
TradingLogStore.addAnalyzer(
" - ",
" - ",
@@ -1158,17 +1161,18 @@ object AutoTradingManager {
val tempAnalyzer = TechnicalAnalyzer().apply { this.daily = dailyData }
// 1. 변동성 기반 수익률 검증 (2% 이상 열려있는가?)
val volatility = tempAnalyzer.calculateVolatilityForecast(dailyData, 20)
val volatility = tempAnalyzer.calculateVolatilityForecast(dailyData, 40)
val expectedProfitRate = ((volatility.realisticHigh - currentPrice) / currentPrice) * 100.0
// 2. 일봉 기준 반등 주기 통계 추출 (일주일 내 승부 가능한가?)
val dailyStats = tempAnalyzer.calculateDynamicReboundStats(dailyData)
val dailyStats = tempAnalyzer.calculateDynamicReboundStats(dailyData, 5.0)
val isApproaching = tempAnalyzer.checkReboundApproaching(
candles = dailyData,
avgReboundTerm = dailyStats.avgReboundPeriod,
dropThreshold = dailyStats.avgDropRate * 0.8,
dropThreshold = dailyStats.avgDropRate,
timeTolerance = dailyStats.timeTolerance
)
print("-> [${stock.name}] 필터링 ${dailyStats.avgReboundPeriod} ${dailyStats.avgDropRate} ${dailyStats.timeTolerance}")
val isSteadyUptrend = tempAnalyzer.checkSteadyUptrend(dailyData)
@@ -1183,7 +1187,18 @@ object AutoTradingManager {
print("-> [${stock.name}] 조건 미달 필터링 (예측수익: ${"%.1f".format(expectedProfitRate)}%, 주기: ${"%.1f".format(dailyStats.avgReboundPeriod)}일, 진입권: $isValidEntryTiming) | ")
return@withTimeout // 조건에 맞지 않으면 주봉/월봉 API 호출 및 LLM 분석 없이 즉시 다음 종목으로 넘어감
}
val dropPrediction = tempAnalyzer.predictDropBottom(dailyData, dailyStats, volatility)
if (dropPrediction != null) {
// 💡 [방어 로직] 아직 바닥까지 한참 남았는데 섣불리 들어가는 것을 방지!
// 과거 평균 10% 빠지는 종목인데, 지금 겨우 -3% 빠진 상태라면 (남은 하락폭 -7%)
if (dropPrediction.remainingDropRate < -2.0 && !dropPrediction.isBottomZone) {
print("-> [${stock.name}] 지하실 주의 (현재 ${"%.1f".format(dropPrediction.currentDropRate)}% 하락, 바닥까지 ${"%.1f".format(dropPrediction.remainingDropRate)}% 추가 하락 위험) | ")
return@withTimeout // 매수 후보에서 과감히 제외!
}
// 반대로 완벽한 바닥권(isBottomZone = true)에 들어왔다면 매수 타점으로 인정하여 다음 단계로 넘김
}
println("🔍 [분석 진입] ${stock.name} (${LocalTime.now()}) (예측수익: ${"%.1f".format(expectedProfitRate)}%, 주기: ${"%.1f".format(dailyStats.avgReboundPeriod)}일, 진입권: $isValidEntryTiming)")
if (!isSafetyBeltStockCodes.contains(stock.code)) {
val analyzer = coroutineScope {