가격괴리 취소 버그 수정
This commit is contained in:
@@ -279,7 +279,7 @@ object AutoTradingManager {
|
||||
|
||||
// 시스템 가이드에 따른 1차 기준 단가 계산
|
||||
val oneTickLowerPrice = initialBasePrice - (tickSize * KisSession.config.getValues(investmentGrade.buyGuide).toInt())
|
||||
val finalPrice = MarketUtil.roundToTickSize(oneTickLowerPrice)
|
||||
val finalPrice = MarketUtil.roundToTickSize(oneTickLowerPrice.toDouble())
|
||||
|
||||
var stockCode = decision.stockCode
|
||||
var stockName = decision.stockName
|
||||
@@ -298,24 +298,45 @@ object AutoTradingManager {
|
||||
val totalQty = orderQty.replace(",", "").toIntOrNull() ?: 0
|
||||
if (totalQty <= 0) return@launch
|
||||
|
||||
// 🌟 [핵심] 매수 2분할 로직 (1주는 1차에 전량, 그 이상은 절반씩)
|
||||
// 🌟 1. 매도 기준(목표가/손절가)을 루프 밖에서 '최초 1회'만 고정으로 계산
|
||||
val sRate = -1.5
|
||||
val tax = KisSession.config.getValues(ConfigIndex.TAX_INDEX)
|
||||
val effectiveProfitRate = (profitRate1 ?: KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)) + tax
|
||||
|
||||
var targetBasePrice = initialBasePrice // AI가 분석한 최초 기준가
|
||||
|
||||
// 물타기 시 평단가 보정 로직
|
||||
if (KisSession.tradeConfig.lowerAveragePrice && hasCode) {
|
||||
try {
|
||||
val oldTarget = currentBalance?.getHoldings()?.firstOrNull { it.availOrderCount.toInt() > 0 && it.code == decision.stockCode }
|
||||
if (oldTarget != null) {
|
||||
targetBasePrice = oldTarget.avgPrice.toDouble() * 1.5 // (기존 보정 로직 유지)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
// 🌟 통일된 최종 매도/손절 목표가 (1차든 2차든 이 가격으로 매도됨)
|
||||
val unifiedTargetPrice = MarketUtil.roundToTickSize(targetBasePrice * (1 + effectiveProfitRate / 100.0))
|
||||
val unifiedStopPrice = MarketUtil.roundToTickSize(targetBasePrice * (1 + sRate / 100.0))
|
||||
|
||||
// 🌟 2. 분할 매수 수량 및 가격 설정
|
||||
val qty1 = (totalQty + 1) / 2
|
||||
val qty2 = totalQty - qty1
|
||||
|
||||
val price1 = finalPrice
|
||||
// 2차 매수는 1차 매수가보다 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) -> 🎯통일된 목표가: $unifiedTargetPrice")
|
||||
|
||||
// 분할 주문 리스트 생성
|
||||
val orderList = mutableListOf<Pair<Int, Double>>()
|
||||
if (qty1 > 0) orderList.add(qty1 to price1)
|
||||
if (qty2 > 0) orderList.add(qty2 to price2)
|
||||
|
||||
// 순차적으로 주문 전송
|
||||
// 🌟 3. 순차적 주문 전송 (DB 저장 시 unifiedTargetPrice 공통 사용)
|
||||
orderList.forEachIndexed { index, (qty, price) ->
|
||||
val orderStep = index + 1 // 1차, 2차 표시용
|
||||
val orderStep = index + 1
|
||||
|
||||
KisTradeService.postOrder(
|
||||
stockCode,
|
||||
@@ -324,30 +345,6 @@ object AutoTradingManager {
|
||||
isBuy = true
|
||||
).onSuccess { realOrderNo ->
|
||||
println("[${investmentGrade.displayName}] ${orderStep}차 주문 성공: $realOrderNo $stockCode ${qty}주 $price")
|
||||
TradingLogStore.addLog(
|
||||
decision,
|
||||
"BUY",
|
||||
"[${investmentGrade.displayName}] ${orderStep}차 분할 주문 성공: $realOrderNo"
|
||||
)
|
||||
|
||||
val sRate = -1.5
|
||||
val tax = KisSession.config.getValues(ConfigIndex.TAX_INDEX)
|
||||
val effectiveProfitRate = (profitRate1 ?: KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)) + tax
|
||||
|
||||
var targetBasePrice = initialBasePrice
|
||||
try {
|
||||
val oldTarget = currentBalance?.getHoldings()?.firstOrNull { it.availOrderCount.toInt() > 0 && it.code == decision.stockCode }
|
||||
if (KisSession.tradeConfig.lowerAveragePrice && hasCode && oldTarget != null) {
|
||||
val avgPrive = oldTarget.avgPrice.toDouble()
|
||||
// 대표님이 작성하신 기존 물타기 basePrice 보정 로직 유지
|
||||
targetBasePrice = avgPrive * 1.5
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
val calculatedTarget = MarketUtil.roundToTickSize(targetBasePrice * (1 + effectiveProfitRate / 100.0))
|
||||
val calculatedStop = MarketUtil.roundToTickSize(targetBasePrice * (1 + sRate / 100.0))
|
||||
|
||||
if (!hasCode) {
|
||||
DatabaseFactory.saveAutoTrade(
|
||||
@@ -358,8 +355,8 @@ object AutoTradingManager {
|
||||
quantity = qty,
|
||||
profitRate = effectiveProfitRate,
|
||||
stopLossRate = sRate,
|
||||
targetPrice = calculatedTarget,
|
||||
stopLossPrice = calculatedStop,
|
||||
targetPrice = unifiedTargetPrice, // 👈 통일된 목표가 주입
|
||||
stopLossPrice = unifiedStopPrice, // 👈 통일된 손절가 주입
|
||||
status = "PENDING_BUY",
|
||||
isDomestic = true
|
||||
)
|
||||
@@ -379,12 +376,10 @@ object AutoTradingManager {
|
||||
TradingLogStore.addLog(
|
||||
decision,
|
||||
"BUY",
|
||||
"[${investmentGrade.displayName}] ${orderStep}차 감시 설정 완료 (예상 목표가: $calculatedTarget): $realOrderNo"
|
||||
"[${investmentGrade.displayName}] ${orderStep}차 감시 설정 완료 (예상 목표가: $unifiedTargetPrice): $realOrderNo"
|
||||
)
|
||||
}.onFailure {
|
||||
println("매수 실패: ${it.message} $stockCode $qty $price")
|
||||
|
||||
// 1차 주문에서 이미 예수금이 부족하다면 2차는 무의미하므로 재분석 대기열 처리
|
||||
if (it.message?.contains("주문가능금액을 초과") == true && index == 0) {
|
||||
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode, hts_kor_isnm = stockName))
|
||||
TradingLogStore.addWatchLog(decision, "WATCH", "${it.message ?: "매수 실패"} => 재분석 대기열에 추가")
|
||||
@@ -393,7 +388,6 @@ object AutoTradingManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 🌟 [안전장치] KIS API 초당 호출 제한 방어
|
||||
delay(250)
|
||||
}
|
||||
} else if (!hasCode && KisSession.isAvailBuyTime(LocalTime.now()) == false &&
|
||||
@@ -438,22 +432,17 @@ object AutoTradingManager {
|
||||
if (dbItem.status == TradeStatus.PENDING_BUY) {
|
||||
// ✅ 1. 진짜 사온 가격 (실제 매수 체결가)
|
||||
var actualBuyPrice = execData.price.toDoubleOrNull() ?: dbItem.targetPrice
|
||||
|
||||
// 💡 [수정] 매수 주문(orderNo)에 대해 '진짜 산 가격'을 기록해야 합니다.
|
||||
// 기존에는 여기에 finalTargetPrice를 넣으셨는데, 그러면 매수 단가가 오염됩니다.
|
||||
TradingReportManager.updateExecution(orderNo, actualBuyPrice, dbItem.quantity)
|
||||
var hasCodes =
|
||||
KisSession.tradeConfig.lowerAveragePrice && currentBalance?.getHoldings()
|
||||
?.any { it.code.equals(dbItem.code) && it.quantity.toInt() > 2 && dbItem.quantity == KisSession.tradeConfig.lowerAverageStockCount } ?: false
|
||||
if (hasCodes) {
|
||||
actualBuyPrice = actualBuyPrice * 1.1
|
||||
}
|
||||
val absoluteMinRate = KisSession.config.getValues(ConfigIndex.TAX_INDEX)
|
||||
val finalProfitRate = maxOf(dbItem.profitRate, absoluteMinRate)
|
||||
val finalTargetPrice =
|
||||
MarketUtil.roundToTickSize(actualBuyPrice * (1 + finalProfitRate / 100.0))
|
||||
|
||||
println("🎯 [매수 확정] ${dbItem.name} | 매수가: ${actualBuyPrice.toInt()} -> 목표가 설정: ${finalTargetPrice.toInt()}")
|
||||
// 물타기 체크 로직 유지
|
||||
var hasCodes = KisSession.tradeConfig.lowerAveragePrice && currentBalance?.getHoldings()
|
||||
?.any { it.code == dbItem.code && it.quantity.toInt() > 2 && dbItem.quantity == KisSession.tradeConfig.lowerAverageStockCount } == true
|
||||
if (hasCodes) { actualBuyPrice = actualBuyPrice * 1.1 }
|
||||
|
||||
// 🌟 [수정] actualBuyPrice로 재계산하지 않고, excuteTrade에서 최초로 결정해 DB에 넣은 목표가를 그대로 가져옵니다.
|
||||
val finalTargetPrice = dbItem.targetPrice
|
||||
|
||||
println("🎯 [매수 확정] ${dbItem.name} | 매수가: ${actualBuyPrice.toInt()} -> 통일된 목표가 세팅: ${finalTargetPrice.toInt()}")
|
||||
|
||||
KisTradeService.postOrder(
|
||||
stockCode = dbItem.code,
|
||||
@@ -468,12 +457,7 @@ object AutoTradingManager {
|
||||
stockName = dbItem.name,
|
||||
isBuy = false,
|
||||
orderQty = dbItem.quantity,
|
||||
reason = "🎯 목표 수익률 ${
|
||||
String.format(
|
||||
"%.2f",
|
||||
finalProfitRate
|
||||
)
|
||||
}% 도달을 위한 익절 주문",
|
||||
reason = "🎯 목표 매도가 ${finalTargetPrice} 도달을 위한 익절 주문",
|
||||
holdingAvgPrice = actualBuyPrice, // 👈 여기서 매수단가를 넘겨줘야 매도 리포트가 정확해집니다!
|
||||
decision = null
|
||||
)
|
||||
@@ -1207,7 +1191,7 @@ object AutoTradingManager {
|
||||
.getOrNull()?.stck_prpr?.toDouble() ?: 0.0
|
||||
val orderedPrice = order.ord_unpr.toDoubleOrNull() ?: 0.0
|
||||
// 조건 B: 현재가와 주문가의 괴리율 체크 (현재가가 너무 올라갔거나 내려갔을 때)
|
||||
val priceGap = Math.abs(currentPrice - orderedPrice) / orderedPrice
|
||||
val priceGap = (Math.abs(currentPrice - orderedPrice) / orderedPrice) * 100
|
||||
println("checkAndCancelPendingBuyOrders order 주문가 :$order | 현제가 :$currentPrice ${elapsedMillis / 1000L}초 ${priceGap}% 차이 || 괴리 기준 ${KisSession.tradeConfig.auto_cancel_pending_rate}%")
|
||||
|
||||
if (priceGap >= KisSession.tradeConfig.auto_cancel_pending_rate) {
|
||||
|
||||
Reference in New Issue
Block a user