import ConfigTable.grade_1_buy import ConfigTable.grade_1_profit import ConfigTable.grade_2_buy import ConfigTable.grade_2_profit import ConfigTable.grade_3_buy import ConfigTable.grade_3_profit import ConfigTable.grade_4_buy import ConfigTable.grade_4_profit import ConfigTable.grade_5_buy import ConfigTable.grade_5_profit import ConfigTable.max_count import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.logging.DEFAULT import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction import model.AppConfig import model.KisSession import network.DartCodeManager import network.KisTradeService import network.KisWebSocketManager import service.LlamaServerManager import network.NewsService import org.jetbrains.exposed.sql.selectAll import service.AutoTradingManager import service.AutoTradingManager.isSystemCleanedUpToday import service.SystemSleepPreventer import service.TradingDecisionCallback import ui.DashboardScreen import ui.SettingsScreen import ui.TradingDecisionLog // 화면 상태 정의 enum class AppScreen { Settings, Dashboard, TradingDecision } fun getLlamaBinPath(): String { val os = System.getProperty("os.name").lowercase() val arch = System.getProperty("os.arch").lowercase() val basePath = "./src/main/resources/bin" return when { // Apple Silicon (M1/M2/M3) os.contains("mac") && (arch.contains("aarch64") || arch.contains("arm64")) -> { "$basePath/mac-arm64/llama-server" } // Intel Mac (2017) os.contains("mac") -> { "$basePath/mac-x64/llama-server" } // Windows NUC os.contains("win") -> { "$basePath/win-x64/llama-server.exe" } else -> "$basePath/llama-server" } } fun main() = application { SystemSleepPreventer.start() // 앱 실행 시 필요한 바이너리 경로 (실행 파일 위치) val binPath = getLlamaBinPath() val windowState = rememberWindowState( placement = WindowPlacement.Fullscreen ) Window(onCloseRequest = ::exitApplication, title = "KIS AI 자동매매", state = windowState) { var currentScreen by remember { mutableStateOf(AppScreen.Settings) } var isLoaded by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() // 1. 앱 시작 시 DB에서 마지막 설정 로드 (KisSession에 주입) LaunchedEffect(Unit) { DatabaseFactory.init() transaction { ConfigTable.selectAll().lastOrNull()?.let { KisSession.config = AppConfig( realAppKey = it[ConfigTable.realAppKey], realSecretKey = it[ConfigTable.realSecretKey], realAccountNo = it[ConfigTable.realAccountNo], vtsAppKey = it[ConfigTable.vtsAppKey], vtsSecretKey = it[ConfigTable.vtsSecretKey], vtsAccountNo = it[ConfigTable.vtsAccountNo], nAppKey = it[ConfigTable.nAppKey], nSecretKey = it[ConfigTable.nSecretKey], dAppKey = it[ConfigTable.dAppKey], isSimulation = it[ConfigTable.isSimulation], htsId = it[ConfigTable.htsId], modelPath = it[ConfigTable.modelPath], embedModelPath = it[ConfigTable.embedModelPath], FEES_AND_TAXRATE = it[ConfigTable.fees_and_taxrate], MINIMUM_NET_PROFIT = it[ConfigTable.minimum_net_profit], BUY_WEIGHT = it[ConfigTable.buy_weight], MAX_BUDGET = it[ConfigTable.max_budget], MAX_PRICE = it[ConfigTable.max_price], MIN_PRICE = it[ConfigTable.min_price], MIN_PURCHASE_SCORE = it[ConfigTable.min_purchase_score], SELL_PROFIT = it[ConfigTable.sell_profit], GRADE_5_BUY = it[grade_5_buy], GRADE_5_PROFIT = it[grade_5_profit], GRADE_4_BUY = it[grade_4_buy], GRADE_4_PROFIT = it[grade_4_profit], GRADE_3_BUY = it[grade_3_buy], GRADE_3_PROFIT = it[grade_3_profit], GRADE_2_BUY = it[grade_2_buy], GRADE_2_PROFIT = it[grade_2_profit], GRADE_1_BUY = it[grade_1_buy], GRADE_1_PROFIT = it[grade_1_profit], MAX_COUNT = it[max_count], ) } } isLoaded = true } if (!isLoaded) { // 로딩 중 표시 CircularProgressIndicator() } else { when (currentScreen) { AppScreen.Settings -> { SettingsScreen( onAuthSuccess = { // 2. 설정 및 인증 완료 시점의 처리 val config = KisSession.config AutoTradingManager.isSystemReadyToday = true AutoTradingManager.isSystemCleanedUpToday = false CoroutineScope(Dispatchers.Default).launch { AutoTradingManager.startAutoDiscoveryLoop() KisWebSocketManager.onExecutionReceived = AutoTradingManager.onExecutionReceived KisWebSocketManager.connect() } if (config.modelPath.isNotEmpty()) { LlamaServerManager.startServer(binPath, config.modelPath,port = 8080) } if (config.embedModelPath.isNotEmpty()) { LlamaServerManager.startServer(binPath, config.embedModelPath, port = 8081) } // 대시보드로 화면 전환 currentScreen = AppScreen.TradingDecision } ) } AppScreen.Dashboard -> { DashboardScreen() } AppScreen.TradingDecision -> { TradingDecisionLog() } } } } }