...
This commit is contained in:
@@ -87,7 +87,7 @@ object AutoTradingManager {
|
||||
val nowDate = LocalDate.now(seoulZone)
|
||||
var checkTime = 60_000 * 3L
|
||||
val isTradingDay = nowDate.dayOfWeek.value in 1..5
|
||||
if (isTradingDay && now.isAfter(H07M50) && now.isBefore(H18) && !shouldShowFullWindow) {
|
||||
if (isTradingDay && now.isAfter(KisSession.startTime()) && now.isBefore(KisSession.endTime()) && !shouldShowFullWindow) {
|
||||
shouldShowFullWindow = true
|
||||
SystemSleepPreventer.wakeDisplay()
|
||||
} else if (now.isAfter(LocalTime.of(23, 50)) && now.isBefore(LocalTime.of(8, 0))) {
|
||||
@@ -104,7 +104,7 @@ object AutoTradingManager {
|
||||
val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean ->
|
||||
val seoulZone = ZoneId.of("Asia/Seoul")
|
||||
val now = LocalTime.now(ZoneId.of("Asia/Seoul"))
|
||||
if (now.isBefore(H15M30) && now.isAfter(H08M45) && isSuccess && completeTradingDecision != null) {
|
||||
if (KisSession.isAvailBuyTime(now) && isSuccess && completeTradingDecision != null) {
|
||||
val decision = completeTradingDecision
|
||||
|
||||
// 1. 이미 AI가 결정한 decision과 confidence를 신뢰함
|
||||
@@ -520,7 +520,7 @@ object AutoTradingManager {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (KisSession.config.getValues(ConfigIndex.STOP_LOSS) > 0.0
|
||||
if (KisSession.config.stop_Loss
|
||||
&& holding != null && holding.quantity.toInt() > 0
|
||||
&& holding.availOrderCount.toInt() > 0
|
||||
&& holding.profitRate.toDouble() <= KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)
|
||||
@@ -528,10 +528,19 @@ object AutoTradingManager {
|
||||
&& holding.valuationProfitAmount.toDouble() >= KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)) {
|
||||
println("${holding.name} ${holding.profitRate.toDouble()} ${holding.valuationProfitAmount.toDouble()} ${KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)} , ${KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)} , ${KisSession.config.getValues(ConfigIndex.STOP_LOSS)}")
|
||||
val profit = holding.profitRate.toDouble()
|
||||
tradeService.postOrder(
|
||||
stockCode = holding.code,
|
||||
qty = holding.availOrderCount,
|
||||
price = "0",
|
||||
isBuy = false,
|
||||
).onSuccess { newOrderNo ->
|
||||
println("✅ [보유 주식 손절 처리] 수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함 시장가 매도.")
|
||||
}.onFailure {
|
||||
}
|
||||
TradingLogStore.addNotice(
|
||||
"보유주식[${holding.name}]",
|
||||
holding.code,
|
||||
"수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함."
|
||||
"수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함 시장가 매도."
|
||||
)
|
||||
}
|
||||
analyzeDeepLossHoldingsAfterMarket(holding , true)
|
||||
@@ -545,13 +554,12 @@ object AutoTradingManager {
|
||||
private suspend fun analyzeDeepLossHoldingsAfterMarket(holding: UnifiedStockHolding, isForce : Boolean = false) { // 💡 [신규 추가] 수익률이 크게 마이너스인 종목(-5.0% 이하) 심층 가이드 분석
|
||||
val now = LocalTime.now()
|
||||
val currentMinute = now.minute
|
||||
if ((!isForce && (now.hour == 8 || now.hour == 16 || now.hour == 17)) || (isForce && (currentMinute == 0 ))) {
|
||||
if ((!isForce && (now.hour == 8 || now.hour == 16 || now.hour == 17)) || (isForce && (currentMinute % 5 == 0))) {
|
||||
val profit = holding.profitRate.toDouble()
|
||||
val lossThreshold = -5.0 // 가이드를 작동시킬 손실 기준선 (필요시 ConfigIndex 로 빼셔도 좋습니다)
|
||||
if (profit <= lossThreshold) {
|
||||
println("🔍 [손실 종목 분석] ${holding.name} (수익률: $profit%) - 가이드 산출 중...")
|
||||
|
||||
// 차트 데이터 빠르게 가져오기 (일봉 위주로 큰 추세만 확인)
|
||||
val dailyData = KisTradeService.fetchPeriodChartData(holding.code, "D", true).getOrNull()
|
||||
|
||||
if (!dailyData.isNullOrEmpty()) {
|
||||
@@ -583,6 +591,7 @@ object AutoTradingManager {
|
||||
"보유주식[${holding.name}]",
|
||||
holding.code,
|
||||
"수익률 심각($profit%) -> $advice",
|
||||
holding.quantity.toInt()
|
||||
)
|
||||
}
|
||||
// 🟡 [관망] 어정쩡하게 물려있는 상태
|
||||
@@ -594,9 +603,6 @@ object AutoTradingManager {
|
||||
"수익률($profit%) -> $advice", false
|
||||
)
|
||||
}
|
||||
|
||||
// 분석 결과를 UI 로그에 띄워 대표님이 확인할 수 있게 함
|
||||
|
||||
}
|
||||
} else {
|
||||
// -5% 이내의 자잘한 손실은 별도 분석 없이 조용히 넘기거나 약식 로그만 남김
|
||||
@@ -658,12 +664,8 @@ object AutoTradingManager {
|
||||
var currentTimeMillis = System.currentTimeMillis()
|
||||
var waitTime = 0.2
|
||||
val H15M30 = LocalTime.of(15, 30)
|
||||
val H16 = LocalTime.of(16, 0)
|
||||
val H18 = LocalTime.of(18, 0)
|
||||
val H20 = LocalTime.of(20, 0)
|
||||
val H08M00 = LocalTime.of(8, 0)
|
||||
val H08M45 = LocalTime.of(8, 45)
|
||||
val H07M50 = LocalTime.of(7, 50)
|
||||
|
||||
private fun runDiscoveryLoop(callback: TradingDecisionCallback) {
|
||||
discoveryJob = scope.launch {
|
||||
println("🚀 [AutoTrading] 발굴 루프 시작: ${LocalDateTime.now()}")
|
||||
@@ -673,10 +675,10 @@ object AutoTradingManager {
|
||||
currentTimeMillis = System.currentTimeMillis()
|
||||
lastTickTime.set(System.currentTimeMillis()) // 생존 신고
|
||||
when {
|
||||
now.isAfter(H20) || now.isBefore(H07M50) -> {
|
||||
now.isAfter(KisSession.endTime()) || now.isBefore(KisSession.startTime()) -> {
|
||||
prepareMarketOpen(now)
|
||||
}
|
||||
now.isBefore(H20) && now.isAfter(H08M00) -> {
|
||||
now.isBefore(KisSession.endTime()) && now.isAfter(KisSession.startTime()) -> {
|
||||
waitTime = 0.2
|
||||
if (now.isAfter(LocalTime.of(8, 0)) && now.isBefore(LocalTime.of(15, 30))) {
|
||||
if (!KisSession.isMarketTokenValid() || !KisSession.isTradeTokenValid()) {
|
||||
@@ -690,7 +692,7 @@ object AutoTradingManager {
|
||||
}
|
||||
withTimeout(CYCLE_TIMEOUT) {
|
||||
println("⏱️ [Cycle Start] ${LocalTime.now()}")
|
||||
if (now.isAfter(H20)) {
|
||||
if (now.isAfter(KisSession.endTime())) {
|
||||
executeClosingLiquidation(KisTradeService)
|
||||
} else {
|
||||
executeMarketLoop()
|
||||
@@ -713,7 +715,7 @@ object AutoTradingManager {
|
||||
}
|
||||
|
||||
suspend fun prepareMarketOpen(now : LocalTime) {
|
||||
if (now.isAfter(H20) || now.isBefore(H07M50)) {
|
||||
if (now.isAfter(KisSession.endTime()) || now.isBefore(KisSession.startTime())) {
|
||||
println("🌙 [System] 마감 시간 도달. 자원 정리 후 대기 모드(설정 화면)로 전환합니다.")
|
||||
onMarketClosed?.invoke()
|
||||
RagService.clearDailyCache()
|
||||
@@ -724,7 +726,7 @@ object AutoTradingManager {
|
||||
isSystemReadyToday = false
|
||||
shouldShowFullWindow = false
|
||||
stopDiscovery() // 발굴 루프 완전 폭파 (내일 8시 30분에 다시 켜짐)
|
||||
} else if (now.isAfter(H07M50) && now.isBefore(H08M00) && !isSystemReadyToday) {
|
||||
} else if (now.isAfter(KisSession.startTime().minusMinutes(10)) && now.isBefore(KisSession.startTime()) && !isSystemReadyToday) {
|
||||
if (MarketUtil.canTradeToday()) {
|
||||
SystemSleepPreventer.wakeDisplay()
|
||||
shouldShowFullWindow = true
|
||||
@@ -756,19 +758,72 @@ object AutoTradingManager {
|
||||
if (isMorning) {
|
||||
currentBalance = KisTradeService.fetchIntegratedBalance().getOrNull()
|
||||
currentBalance?.let { currentBalance ->
|
||||
if (LocalTime.now().isBefore(LocalTime.of(16,2))) {
|
||||
if (LocalTime.now().isBefore(LocalTime.of(18,1))) {
|
||||
TradingReportManager.recordAssetSnapshot(
|
||||
if (LocalTime.now().isAfter(LocalTime.of(17, 59))
|
||||
if (LocalTime.now().isAfter(LocalTime.of(18, 0))
|
||||
) SnapshotType.END else SnapshotType.MIDDLE, currentBalance, ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (KisSession.config.take_profit) currentBalance?.let { resumePendingSellOrders(KisTradeService, it) }
|
||||
if (KisSession.tradeConfig.auto_cancel_pending_buy) {
|
||||
checkAndCancelPendingBuyOrders()
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkAndCancelPendingBuyOrders(
|
||||
) {
|
||||
// 1. 미체결 내역 조회
|
||||
val unfilledResult = KisTradeService.fetchUnfilledOrders()
|
||||
unfilledResult.onSuccess { response ->
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
// 매수 주문('02')만 필터링
|
||||
response.filter { it.sll_buy_dvsn_cd == "02" }.forEach { order ->
|
||||
|
||||
val orderTimeMillis = parseOrderTime(order.ord_tmd)
|
||||
val elapsedMillis = currentTime - orderTimeMillis
|
||||
|
||||
// 조건 A: 설정된 대기 시간 경과 여부
|
||||
if (elapsedMillis >= KisSession.tradeConfig.auto_cancel_pending_time) {
|
||||
|
||||
// 2. 현재가 조회 (가격을 비교하기 위해)
|
||||
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}% 차이")
|
||||
if (priceGap >= KisSession.tradeConfig.auto_cancel_pending_rate) {
|
||||
TradingLogStore.addNotice(order.prdt_name,order.pdno,"[주문 취소] ${order.prdt_name} (${order.pdno}) - 시간경과 및 가격괴리(${String.format("%.2f", priceGap * 100)}%)로 취소 시도")
|
||||
KisTradeService.cancelOrder(
|
||||
order.ord_no, // 원주문번호
|
||||
order.pdno
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 주문 시간 문자열을 Millis로 변환하는 유틸리티 (당일 주문 기준)
|
||||
fun parseOrderTime(ordTmd: String): Long {
|
||||
return try {
|
||||
val now = java.time.LocalDateTime.now()
|
||||
val hour = ordTmd.substring(0, 2).toInt()
|
||||
val min = ordTmd.substring(2, 4).toInt()
|
||||
val sec = ordTmd.substring(4, 6).toInt()
|
||||
val orderDateTime = now.withHour(hour).withMinute(min).withSecond(sec)
|
||||
orderDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli()
|
||||
} catch (e: Exception) {
|
||||
System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun executeMarketLoop() {
|
||||
myOredsAndBalanceCodes.clear()
|
||||
checkBalance()
|
||||
@@ -822,7 +877,7 @@ object AutoTradingManager {
|
||||
while (iterator.hasNext()) {
|
||||
totalCount--
|
||||
val stock = iterator.next()
|
||||
if (now.isBefore(H15M30) && now.isAfter(H08M45)) {
|
||||
if (KisSession.isAvailBuyTime(now)) {
|
||||
if (BLACKLISTEDSTOCKCODES.contains(stock.code)) {
|
||||
println("❌ 차단 처리된 주식 : ${stock.name}")
|
||||
} else {
|
||||
@@ -860,7 +915,7 @@ object AutoTradingManager {
|
||||
checkBalance()
|
||||
lastForceCheckMinute = currentMinute // 실행 완료 기록
|
||||
}
|
||||
} else if ((now.hour == 8 || (now.hour >= 16 && now.hour < 20)) && (currentMinute % 2 == 1)) {
|
||||
} else if (((now.hour == 8 && KisSession.tradeConfig.before_nxt) || (now.hour >= 16 && now.hour < 20 && KisSession.tradeConfig.after_nxt)) && (currentMinute % 2 == 1)) {
|
||||
if (lastForceCheckMinute != currentMinute) {
|
||||
TradingLogStore.addAnalyzer(
|
||||
" - ",
|
||||
|
||||
Reference in New Issue
Block a user