This commit is contained in:
lunaticbum 2026-03-19 11:41:21 +09:00
parent eee4f1bab7
commit f830163940
3 changed files with 82 additions and 43 deletions

View File

@ -444,5 +444,10 @@ object TradingLogStore {
}
}
fun clear() {
synchronized(this) {
decisionLogs.clear()
}
}
}

View File

@ -3,6 +3,7 @@ package service
import AutoTradeItem
import network.TradingDecision
import TradingLogStore
import TradingLogStore.decisionLogs
import getLlamaBinPath
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -395,9 +396,13 @@ object AutoTradingManager {
} catch (e: Exception) {}
}
var onMarketClosed: (() -> Unit)? = null
var now = LocalTime.now(ZoneId.of("Asia/Seoul"))
var currentTimeMillis = System.currentTimeMillis()
var waitTime = 0.2
val H16 = LocalTime.of(16, 0)
val H08M50 = LocalTime.of(8, 50)
private fun runDiscoveryLoop(tradeService: KisTradeService, callback: TradingDecisionCallback) {
discoveryJob = scope.launch {
println("🚀 [AutoTrading] 발굴 루프 시작: ${LocalDateTime.now()}")
@ -406,9 +411,22 @@ object AutoTradingManager {
now = LocalTime.now(ZoneId.of("Asia/Seoul"))
currentTimeMillis = System.currentTimeMillis()
lastTickTime.set(System.currentTimeMillis()) // 생존 신고
// [수정] 16시 이후이거나 8시 30분 이전이면 모든 로직 중단 및 초기화
if (now.isAfter(LocalTime.of(16, 0)) || now.isBefore(LocalTime.of(8, 30))) {
println("🌙 [System] 마감 시간 도달. 자원 정리 후 대기 모드(설정 화면)로 전환합니다.")
SystemSleepPreventer.sleepDisplay() // 모니터 끄기
KisWebSocketManager.disconnect()
BrowserManager.closeIfIdle(0)
LlamaServerManager.stopAll() // AI 서버 완전 종료
TradingLogStore.clear()
onMarketClosed?.invoke() // Main.kt에 설정 화면으로 가라고 신호 전송
stopDiscovery() // 발굴 루프 완전 폭파 (내일 8시 30분에 다시 켜짐)
return@launch
}
when {
//장중
now.isBefore(LocalTime.of(16, 0)) && now.isAfter(LocalTime.of(8, 50)) -> {
now.isBefore(H16) && now.isAfter(H08M50) -> {
waitTime = 0.2
if (now.isAfter(LocalTime.of(8, 0)) && now.isBefore(LocalTime.of(15, 30))) {
if (!KisSession.isMarketTokenValid() || !KisSession.isTradeTokenValid()) {
@ -491,7 +509,7 @@ object AutoTradingManager {
}
//장외
now.isAfter(LocalTime.of(18, 0)) || now.isBefore(LocalTime.of(8, 50)) -> {
now.isAfter(H16) || now.isBefore(H08M50) -> {
when {
(now.hour == 0 && now.minute == 0 && (isSystemReadyToday || isSystemCleanedUpToday)) -> {
waitTime = 10.0

View File

@ -35,6 +35,7 @@ import network.KisTradeService
import org.jetbrains.exposed.sql.deleteAll
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.transactions.transaction
import service.SystemSleepPreventer
// src/main/kotlin/ui/SettingsScreen.kt
@ -45,6 +46,61 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
var config by remember { mutableStateOf(KisSession.config) }
var statusMessage by remember { mutableStateOf("정보를 입력하세요.") }
val authenticateAndStart: suspend () -> Unit = {
var retryCount = 0
val maxRetries = 3
val totalDelaySeconds = 90
var isAuthCompleted = false
while (retryCount <= maxRetries && !isAuthCompleted) {
if (retryCount > 0) {
for (secondsLeft in totalDelaySeconds downTo 1) {
statusMessage = "⚠️ 인증 실패. ${secondsLeft}초 후 자동으로 다시 시도합니다. (시도 ${retryCount}/${maxRetries})"
delay(1000L)
}
}
statusMessage = if (retryCount == 0) "⏳ 인증 시도 중..." else "${retryCount}차 재시도 중..."
KisSession.config = config
DatabaseFactory.saveConfig(config)
DartCodeManager.updateCorpCodes(HttpClient(CIO) {
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
})
val authSuccess = KisAuthService.refreshAllTokens()
val wsKeySuccess = KisTradeService.refreshWebsocketKey()
if (authSuccess && wsKeySuccess) {
statusMessage = "✅ 인증 성공! LLM 시작 중..."
isAuthCompleted = true
onAuthSuccess() // 여기서 대시보드로 넘어감
} else {
retryCount++
if (retryCount > maxRetries) {
statusMessage = "❌ 인증 실패. 3회 재시도 후 중단되었습니다."
}
}
}
}
LaunchedEffect(config) {
while (true) {
val now = java.time.LocalTime.now(java.time.ZoneId.of("Asia/Seoul"))
// 08:30 ~ 15:30 사이이고, 키값이 최소한 하나라도 존재할 때 자동 실행
if (now.isAfter(java.time.LocalTime.of(8, 30)) && now.isBefore(java.time.LocalTime.of(15, 30))) {
if (config.realAppKey.isNotEmpty() || config.vtsAppKey.isNotEmpty()) {
SystemSleepPreventer.wakeDisplay() // 모니터 켜기
statusMessage = "⏰ 자동 실행 시간(08:30)입니다. 시스템을 가동합니다."
authenticateAndStart()
break // 성공하면 루프 탈출
}
}
delay(60_000 * 2) // 1분마다 시간 체크
}
}
// 계좌번호 입력 시 데이터 자동 로드 함수
fun checkAndLoadConfig(accountNo: String, isReal: Boolean) {
val loaded = DatabaseFactory.findConfigByAccount(accountNo)
@ -190,47 +246,7 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
modifier = Modifier.fillMaxWidth().height(50.dp),
onClick = {
scope.launch {
var retryCount = 0
val maxRetries = 3
val totalDelaySeconds = 90 // 1분 30초 = 90초
var isAuthCompleted = false
while (retryCount <= maxRetries && !isAuthCompleted) {
// 재시도 시 대기 및 카운트다운 표시
if (retryCount > 0) {
for (secondsLeft in totalDelaySeconds downTo 1) {
statusMessage = "⚠️ 인증 실패. ${secondsLeft}초 후 자동으로 다시 시도합니다. (시도 ${retryCount}/${maxRetries})"
delay(1000L) // 1초 대기
}
}
statusMessage = if (retryCount == 0) "⏳ 인증 시도 중..." else "${retryCount}차 재시도 중..."
// 1. 설정값 저장
KisSession.config = config
DatabaseFactory.saveConfig(config)
// 2. 법인코드 업데이트
DartCodeManager.updateCorpCodes(HttpClient(CIO) {
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
})
// 3. 토큰 및 웹소켓 키 갱신
val authSuccess = KisAuthService.refreshAllTokens()
val wsKeySuccess = KisTradeService.refreshWebsocketKey()
if (authSuccess && wsKeySuccess) {
statusMessage = "✅ 인증 성공! LLM 시작 중..."
isAuthCompleted = true
onAuthSuccess()
} else {
retryCount++
if (retryCount > maxRetries) {
statusMessage = "❌ 인증 실패. 3회 재시도 후 중단되었습니다. 키 정보를 확인하세요."
}
// 다음 루프에서 카운트다운 진입
}
}
authenticateAndStart()
}
}
) { Text("설정 저장 및 실행") }