This commit is contained in:
2026-09-22 17:18:21 +09:00
parent 40c70577ca
commit daf74fbe83
2 changed files with 48 additions and 20 deletions
+1
View File
@@ -204,6 +204,7 @@ data class UnfilledOrder(
val ord_dvsn_name: String,
val rvse_cncl_dvsn_name: String,
val ord_tmd : String,
val ord_dvsn_cd : String
) {
fun isBuyOrder() : Boolean {
return true
+47 -20
View File
@@ -1187,24 +1187,29 @@ object AutoTradingManager {
suspend fun checkAndCancelPendingBuyOrders(
) {
println("checkAndCancelPendingBuyOrders 주문 조회 및 가격 괴리 매수 체크")
// 1. 미체결 내역 조회
val unfilledResult = KisTradeService.fetchUnfilledOrders()
unfilledResult.onSuccess { response ->
val currentTime = System.currentTimeMillis()
// 매수 주문('02')만 필터링
response.filter { it.sll_buy_dvsn_cd == "02" }.forEach { order ->
response.filter {
println("${it.prdt_name}[${it.pdno}] | 가격 ${it.ord_unpr.toInt()} ${it.ord_dvsn_cd}")
it.sll_buy_dvsn_cd == "02" && it.ord_dvsn_cd == "00" }.forEach { order ->
val orderTimeMillis = parseOrderTime(order.ord_tmd)
val elapsedMillis = currentTime - orderTimeMillis
if (elapsedMillis >= KisSession.tradeConfig.auto_cancel_pending_time) {
// 2. 현재가 조회 (가격을 비교하기 위해)
println("checkAndCancelPendingBuyOrders ${elapsedMillis / 1000L}초 경과 | ${order.prdt_name}[${order.pdno}] | 가격 ${order.ord_unpr.toInt()} , ${order.rmnd_qty.toInt()}주 매수 대기중" )
delay(150)
val currentPrice = KisTradeService.fetchCurrentPrice(order.pdno)
.getOrNull()?.stck_prpr?.toDouble() ?: 0.0
val orderedPrice = order.ord_unpr.toDoubleOrNull() ?: 0.0
// 조건 B: 현재가와 주문가의 괴리율 체크 (현재가가 너무 올라갔거나 내려갔을 때)
val priceGap = Math.abs(currentPrice - orderedPrice) / orderedPrice
println("checkAndCancelPendingBuyOrders order $order ${elapsedMillis / 1000L}초 ${priceGap}% 차이")
println("checkAndCancelPendingBuyOrders order 주문가 :$order | 현제가 :$currentPrice ${elapsedMillis / 1000L}초 ${priceGap}% 차이 || 괴리 기준 ${KisSession.tradeConfig.auto_cancel_pending_rate}%")
if (priceGap >= KisSession.tradeConfig.auto_cancel_pending_rate) {
TradingLogStore.addNotice(
order.prdt_name,
@@ -1505,6 +1510,9 @@ object AutoTradingManager {
if (isWatering) {
println("💧 [물타기 분석 모드 가동] ${stock.name} (현재 수익률: ${targetHolding?.profitRate}%)")
} else if (targetHolding != null ){
println("💧 [분석 제외 대상] ${stock.name} 보유주식 이나 물타기 대상은 아님 (현재 수익률: ${targetHolding?.profitRate}%)")
return@withTimeout
}
// 2. 재무 안정성 체크 (물타기라도 상폐/자본잠식 위험주는 추가 매수 금지)
@@ -1530,6 +1538,10 @@ object AutoTradingManager {
if (dailyData.size < 30) return@withTimeout
delay(40) // API 속도 제한 방어
val weeklyData = tradeService.fetchPeriodChartData(stock.code, "W", true).getOrNull() ?: return@withTimeout
if (weeklyData.size < 10) return@withTimeout
val today = dailyData.lastOrNull() ?: return@withTimeout
val rate = today.getFluctuationRate()
@@ -1561,20 +1573,25 @@ object AutoTradingManager {
return@withTimeout
}
val tempAnalyzer = TechnicalAnalyzer().apply { this.daily = dailyData }
val tempAnalyzer = TechnicalAnalyzer().apply {
this.daily = dailyData
this.weekly = weeklyData
}
val volatility = tempAnalyzer.calculateVolatilityForecast(dailyData, 20)
val expectedProfitRate = ((volatility.realisticHigh - currentPrice) / currentPrice) * 100.0
val dailyStats = tempAnalyzer.calculateDynamicReboundStats(dailyData, 3.0)
// 🌟 [완화 3] 기대수익률 및 진입 타이밍 이원화
// 1. 일봉 단기 타점 (오늘이 미세 반등 타이밍인가?)
val dailyStats = tempAnalyzer.calculateDynamicReboundStats(dailyData, 3.0)
// 🌟 2. 주봉 스윙 타점 (이 주식이 5% 이상 빠졌을 때, 평균 몇 주 만에 회복하는가?)
val weeklyStats = tempAnalyzer.calculateDynamicReboundStats(weeklyData, 5.0)
val isProfitable: Boolean
val isValidEntryTiming: Boolean
val isSteadyUptrend = tempAnalyzer.checkSteadyUptrend(dailyData)
var isSteadyUptrend = false
if (isWatering) {
// 물타기: 평단 낮추기 목적이므로 기대수익률 1.0% 이상이면 충분
// 물타기 로직 (기존과 동일하게 유지)
isProfitable = expectedProfitRate >= 1.0 || dailyStats.avgReboundAmplitude >= 1.5
// 물타기 진입 타이밍: 반등 주기 도달 OR (일봉 RSI 35 이하 과매도) OR (볼린저 하단 터치)
val rsi = tempAnalyzer.calculateRSI(dailyData)
val lowerBand = AdvancedTradeAssistant.calculateBollingerLowerBand(dailyData)
val isOversoldZone = rsi <= 38.0 || (lowerBand > 0 && currentPrice <= lowerBand * 1.03)
@@ -1583,22 +1600,33 @@ object AutoTradingManager {
dailyData, dailyStats.avgReboundPeriod, dailyStats.avgDropRate, dailyStats.timeTolerance
))
} else {
// 신규 진입: 기존 엄격 기준 유지
isProfitable = expectedProfitRate >= KisSession.tradeConfig.minExpectedProfitRate ||
(dailyStats.avgReboundAmplitude.times(0.8)) >= KisSession.tradeConfig.minExpectedProfitRate
// 신규 진입 로직
val isApproaching = tempAnalyzer.checkReboundApproaching(
dailyData, dailyStats.avgReboundPeriod, dailyStats.avgDropRate, dailyStats.timeTolerance
)
isValidEntryTiming = (dailyStats.isValid && isApproaching &&
val baseProfitable = expectedProfitRate >= KisSession.tradeConfig.minExpectedProfitRate ||
(dailyStats.avgReboundAmplitude * 0.8) >= KisSession.tradeConfig.minExpectedProfitRate
isSteadyUptrend = tempAnalyzer.checkSteadyUptrend(dailyData)
// 우상향 종목은 프리패스
isProfitable = baseProfitable || isSteadyUptrend
// 🌟 [핵심 필터] 주간 반등 주기가 2.5(약 2~3주) 이하인지 확인 (죽은 주식 장기투자 방지)
val isFastSwing = weeklyStats.isValid && weeklyStats.avgReboundPeriod <= 2.5
// 일봉 타점이 맞는지 확인
val isTimingValid = (dailyStats.isValid && isApproaching &&
dailyStats.avgReboundPeriod <= KisSession.tradeConfig.maxExpectedReboundDays &&
dailyStats.avgReboundPeriod >= KisSession.tradeConfig.minExpectedReboundDays) ||
isSteadyUptrend
dailyStats.avgReboundPeriod >= KisSession.tradeConfig.minExpectedReboundDays)
// 일봉 타점이 맞으면서 && 주봉상 2주 내 탈출 가능한 종목만 승인 (단, 완벽한 우상향 중이면 통과)
isValidEntryTiming = (isTimingValid && isFastSwing) || isSteadyUptrend
}
val currentAtr = tempAnalyzer.calculateATR(dailyData)
if (!isProfitable || !isValidEntryTiming) {
println("-> [${stock.name}] 목표 수익 조건 미달 (물타기:$isWatering, 수익:$isProfitable, 타이밍:$isValidEntryTiming, 우상향:$isSteadyUptrend) | 기대 수익율 : ${expectedProfitRate} || ${dailyStats.avgReboundAmplitude}")
println("-> [${stock.name}] 목표 수익 조건 미달 (물타기:$isWatering, 수익:$isProfitable, 타이밍:$isValidEntryTiming, 우상향:$isSteadyUptrend) | 기대 수익율 : ${expectedProfitRate} || ${dailyStats.avgReboundAmplitude} | 평균 반등일 : ${dailyStats.avgReboundPeriod}")
return@withTimeout
}
@@ -1623,18 +1651,17 @@ object AutoTradingManager {
}
}
println("\n💧 [검문소 통과 -> ${if (isWatering) "물타기 탈출 분석" else "신규 매수 분석"}] ${stock.name} (물타기:$isWatering, 수익:$isProfitable, 타이밍:$isValidEntryTiming) | 기대 수익율 : ${expectedProfitRate} || ${dailyStats.avgReboundAmplitude}")
println("\n💧 [검문소 통과 -> ${if (isWatering) "물타기 탈출 분석" else "신규 매수 분석"}] ${stock.name} (물타기:$isWatering, 수익:$isProfitable, 타이밍:$isValidEntryTiming, 우상향:$isSteadyUptrend) | 기대 수익율 : ${expectedProfitRate} || ${dailyStats.avgReboundAmplitude}| 평균 반등일 : ${dailyStats.avgReboundPeriod}")
// 멀티 타임프레임 및 AI 분석 진입
val analyzer = coroutineScope {
val min30 = async { tradeService.fetchChartData(stock.code, true).getOrDefault(emptyList()) }
val weekly = async { tradeService.fetchPeriodChartData(stock.code, "W", true).getOrDefault(emptyList()) }
val monthly = async { tradeService.fetchPeriodChartData(stock.code, "M", true).getOrDefault(emptyList()) }
TechnicalAnalyzer().apply {
this.daily = dailyData
this.weekly = weeklyData
this.min30 = min30.await()
this.weekly = weekly.await()
this.monthly = monthly.await()
}
}