From 4b1805b9b8a834ef38e80c5d6a110da6c5797d17 Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Tue, 6 Oct 2026 14:47:26 +0900 Subject: [PATCH] ... --- src/main/kotlin/model/AppConfig.kt | 5 + src/main/kotlin/service/AutoTradingManager.kt | 15 +- src/main/kotlin/ui/TradingDecisionLog.kt | 189 ++++++++++++------ 3 files changed, 144 insertions(+), 65 deletions(-) diff --git a/src/main/kotlin/model/AppConfig.kt b/src/main/kotlin/model/AppConfig.kt index 61bc142..6f923fb 100644 --- a/src/main/kotlin/model/AppConfig.kt +++ b/src/main/kotlin/model/AppConfig.kt @@ -270,6 +270,11 @@ class TradeConfig { var isUpcomingDividend : Boolean = false var buyTickOffset : Int = 4 var sellTickOffset : Int = 4 + var useGrade5: Boolean = true + var useGrade4: Boolean = true + var useGrade3: Boolean = true + var useGrade2: Boolean = true + var useGrade1: Boolean = true } diff --git a/src/main/kotlin/service/AutoTradingManager.kt b/src/main/kotlin/service/AutoTradingManager.kt index 5d8c247..d09b229 100644 --- a/src/main/kotlin/service/AutoTradingManager.kt +++ b/src/main/kotlin/service/AutoTradingManager.kt @@ -254,7 +254,7 @@ object AutoTradingManager { } } - return if (isOverheated) { + val finalGrade = if (isOverheated) { when (rawGrade) { InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND -> InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND @@ -264,6 +264,19 @@ object AutoTradingManager { } else { rawGrade } + + // 🌟 [μΆ”κ°€] μ‚¬μš©μžκ°€ λΉ„ν™œμ„±ν™”ν•œ 등급인지 μ²΄ν¬ν•˜μ—¬ 맀수 μ›μ²œ 차단 + val isEnabled = when (finalGrade) { + InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> KisSession.tradeConfig.useGrade5 + InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND -> KisSession.tradeConfig.useGrade4 + InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND -> KisSession.tradeConfig.useGrade3 + InvestmentGrade.LEVEL_2_HIGH_RISK -> KisSession.tradeConfig.useGrade2 + InvestmentGrade.LEVEL_1_SPECULATIVE -> KisSession.tradeConfig.useGrade1 + else -> false + } + + // λΉ„ν™œμ„±ν™”λœ 등급이라면 LEVEL_0_SPECULATIVE(관망) λ°˜ν™˜ + return if (isEnabled) finalGrade else InvestmentGrade.LEVEL_0_SPECULATIVE } fun excuteTrade( diff --git a/src/main/kotlin/ui/TradingDecisionLog.kt b/src/main/kotlin/ui/TradingDecisionLog.kt index 1db6d9c..f3b1156 100644 --- a/src/main/kotlin/ui/TradingDecisionLog.kt +++ b/src/main/kotlin/ui/TradingDecisionLog.kt @@ -298,9 +298,9 @@ fun TradingDecisionLog() { modifier = Modifier.padding(top = 10.dp, bottom = 8.dp) ) } - +// ConfigIndex.BUY_WEIGHT_INDEX, var defaults = arrayOf( - ConfigIndex.TAX_INDEX, ConfigIndex.PROFIT_INDEX, ConfigIndex.BUY_WEIGHT_INDEX, + ConfigIndex.TAX_INDEX, ConfigIndex.PROFIT_INDEX, ConfigIndex.MAX_BUDGET_INDEX, ConfigIndex.MAX_PRICE_INDEX, ConfigIndex.MIN_PRICE_INDEX, ConfigIndex.MIN_PURCHASE_SCORE_INDEX, ConfigIndex.SELL_PROFIT, ConfigIndex.MAX_COUNT_INDEX, ConfigIndex.MAX_HOLDING_COUNT, @@ -434,14 +434,34 @@ fun TradingDecisionLog() { arrayOf(ConfigIndex.GRADE_1_BUY, ConfigIndex.GRADE_1_PROFIT, ConfigIndex.GRADE_1_ALLOCATIONRATE), ) - for (items in defaults2) { + // 🌟 [μˆ˜μ •] indexλ₯Ό ν™œμš©ν•΄ Level 번호(5~1)λ₯Ό μ—­μΆ”μ‚°ν•˜λ„λ‘ withIndex() μ‚¬μš© + for ((indexLevel, items) in defaults2.withIndex()) { val common = findLongestCommonSubstring(items.first().label, items.last().label) + val levelNum = 5 - indexLevel // 5, 4, 3, 2, 1 item(span = { GridItemSpan(maxLineSpan) }) { - Text( - common, - style = MaterialTheme.typography.body1, - modifier = Modifier.padding(top = 10.dp, bottom = 8.dp) + // 🌟 [핡심] 제λͺ© ν…μŠ€νŠΈ λŒ€μ‹  μ‚¬μš© μ—¬λΆ€ μŠ€μœ„μΉ˜ λΆ€μ°© + SettingSwitchField( + modifier = Modifier.fillMaxWidth().padding(top = 10.dp, bottom = 8.dp), + label = "$common (LEVEL_$levelNum) μžλ™λ§€μˆ˜", + initialChecked = when (levelNum) { + 5 -> tradeConfig.useGrade5 + 4 -> tradeConfig.useGrade4 + 3 -> tradeConfig.useGrade3 + 2 -> tradeConfig.useGrade2 + else -> tradeConfig.useGrade1 + }, + onCheckedChange = { isChecked -> + when (levelNum) { + 5 -> tradeConfig.useGrade5 = isChecked + 4 -> tradeConfig.useGrade4 = isChecked + 3 -> tradeConfig.useGrade3 = isChecked + 2 -> tradeConfig.useGrade2 = isChecked + 1 -> tradeConfig.useGrade1 = isChecked + } + KisSession.saveTradeConfig() // μ¦‰μ‹œ μ €μž₯ + }, + helperText = "체크 ν•΄μ œ μ‹œ ν•΄λ‹Ή λ“±κΈ‰ νŒμ •μ΄ λ‚˜μ™€λ„ λ§€μˆ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€." ) } @@ -457,7 +477,7 @@ fun TradingDecisionLog() { KisSession.config.setValues(configKey, newValue) } - // labelText μ—…λ°μ΄νŠΈ 둜직 (κΈ°μ‘΄κ³Ό 동일) + // labelText μ—…λ°μ΄νŠΈ 둜직 labelText = if (configKey.name.contains("PROFIT")) { getRemaining(configKey.label, common) + ": κΈ°μ€€μœ¨(${KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)}) * λΉ„μœ¨(${KisSession.config.getValues(configKey)}) + μ„ΈκΈˆ(${KisSession.config.getValues(ConfigIndex.TAX_INDEX)}) = ${(localText.toDoubleOrNull() ?: 0.0) * KisSession.config.getValues(ConfigIndex.PROFIT_INDEX) + KisSession.config.getValues(ConfigIndex.TAX_INDEX)}" } else if (configKey.name.contains("ALLOCATIONRATE")) { @@ -490,79 +510,117 @@ fun TradingDecisionLog() { Text("μžλ™ λ§€λ§€ μ‹œκ°„ μ„€μ •", fontWeight = FontWeight.Bold, color = Color.Blue) Spacer(Modifier.height(8.dp)) - // μ‹œμž‘ μ‹œκ°„ (HH:mm) - TimeInputRow("μžλ™ μ‹œμž‘", tradeConfig.auto_start_time) { tradeConfig.auto_start_time = it - KisSession.saveTradeConfig() } - // μ’…λ£Œ μ‹œκ°„ (HH:mm) - TimeInputRow("μžλ™ μ’…λ£Œ", tradeConfig.auto_end_time) { tradeConfig.auto_end_time = it - KisSession.saveTradeConfig() } + // 🌟 [μˆ˜μ •] μžλ™ μ‹œμž‘/μ’…λ£Œλ₯Ό ν•œ μ€„λ‘œ + Row(horizontalArrangement = Arrangement.SpaceEvenly) { + TimeInputRow( + modifier = Modifier.weight(1f), + label = "μžλ™ μ‹œμž‘", + initialTime = tradeConfig.auto_start_time + ) { + tradeConfig.auto_start_time = it + KisSession.saveTradeConfig() + } + TimeInputRow( + modifier = Modifier.weight(1f), + label = "μžλ™ μ’…λ£Œ", + initialTime = tradeConfig.auto_end_time + ) { + tradeConfig.auto_end_time = it + KisSession.saveTradeConfig() + } + } Divider(Modifier.padding(vertical = 8.dp)) - // 맀수 κΈˆμ§€ μ‹œκ°„ μ„€μ • - TimeInputRow("맀수 μ‹œμž‘", tradeConfig.start_buy_time) { tradeConfig.start_buy_time = it - KisSession.saveTradeConfig() } - TimeInputRow("맀수 μ’…λ£Œ", tradeConfig.end_buy_time) { tradeConfig.end_buy_time = it - KisSession.saveTradeConfig() } + // 🌟 [μˆ˜μ •] 맀수 μ‹œμž‘/μ’…λ£Œλ₯Ό ν•œ μ€„λ‘œ + Row(horizontalArrangement = Arrangement.SpaceEvenly) { + TimeInputRow( + modifier = Modifier.weight(1f), + label = "맀수 μ‹œμž‘", + initialTime = tradeConfig.start_buy_time + ) { + tradeConfig.start_buy_time = it + KisSession.saveTradeConfig() + } + TimeInputRow( + modifier = Modifier.weight(1f), + label = "맀수 μ’…λ£Œ", + initialTime = tradeConfig.end_buy_time + ) { + tradeConfig.end_buy_time = it + KisSession.saveTradeConfig() + } + } } } Spacer(Modifier.height(16.dp)) - Text("βš™οΈ 기타 κ³ κΈ‰ μ„€μ •", style = MaterialTheme.typography.h6, modifier = Modifier.padding(8.dp)) + Text("βš™οΈοΈ 기타 κ³ κΈ‰ μ„€μ •", style = MaterialTheme.typography.h6, modifier = Modifier.padding(8.dp)) Row(horizontalArrangement = Arrangement.SpaceEvenly) { SettingInputField( - modifier = Modifier.weight(1.0f, true), - label = "맀수 뢄석 ν˜„ λ³€λ™μœ¨ μ²˜μ € κΈ°μ€€", + modifier = Modifier.weight(1.0f, true).padding(end = 4.dp), + label = "맀수 뢄석 μ΅œμ € κΈ°μ€€ (%)", initialValue = (tradeConfig.minusFilter * -1).toString(), onSave = { tradeConfig.minusFilter = abs(it.toDouble()) KisSession.saveTradeConfig() }, - helperText = "ν˜„μ œ λ³€λ™μœ¨μ΄ 이것보닀 컀야 뢄석 함." + helperText = "이 μˆ˜μΉ˜λ³΄λ‹€ 컀야 뢄석" ) SettingInputField( - modifier = Modifier.weight(1.0f, true), - label = "맀수 뢄석 ν˜„ λ³€λ™μœ¨ 졜고 κΈ°μ€€", + modifier = Modifier.weight(1.0f, true).padding(start = 4.dp), + label = "맀수 뢄석 졜고 κΈ°μ€€ (%)", initialValue = (tradeConfig.plusFilter).toString(), onSave = { tradeConfig.plusFilter = it.toDouble() KisSession.saveTradeConfig() }, - helperText = "ν˜„μ œ λ³€λ™μœ¨μ΄ 이것보닀 μž‘μ•„μ•Ό 뢄석 함." + helperText = "이 μˆ˜μΉ˜λ³΄λ‹€ μž‘μ•„μ•Ό 뢄석" ) } - // Boolean μ„€μ •λ“€ - SettingSwitchField( - label = "미체결 μžλ™ μ·¨μ†Œ (맀수)", - initialChecked = tradeConfig.auto_cancel_pending_buy, - helperText = "μ§€μ • μ‹œκ°„ κ²½κ³Ό μ‹œ 미체결 맀수 μ£Όλ¬Έ μ·¨μ†Œ", - onCheckedChange = { tradeConfig.auto_cancel_pending_buy = it - KisSession.saveTradeConfig() - } - ) - SettingInputField( - label = "μžλ™ μ·¨μ†Œ λŒ€κΈ° μ‹œκ°„ (λΆ„)", - initialValue = (tradeConfig.auto_cancel_pending_time / (60 * 1000)).toString(), - onSave = { - val minutes = it.toLongOrNull() ?: 60L - tradeConfig.auto_cancel_pending_time = minutes * 60 * 1000 - KisSession.saveTradeConfig() - }, - helperText = "ν˜„μž¬: ${tradeConfig.auto_cancel_pending_time / 1000}초 ν›„ μ·¨μ†Œ" - ) + // 🌟 [μˆ˜μ •] 미체결 μžλ™ μ·¨μ†Œ(μŠ€μœ„μΉ˜, μ‹œκ°„, κ°­ λΉ„μœ¨) 3개λ₯Ό ν•œ μ€„λ‘œ 병합 + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(vertical = 4.dp) + ) { + SettingSwitchField( + modifier = Modifier.weight(1.2f).padding(end = 4.dp), + label = "미체결 μžλ™μ·¨μ†Œ", // 라벨 μΆ•μ†Œ + initialChecked = tradeConfig.auto_cancel_pending_buy, + helperText = "맀수 미체결 μ‹œ μ·¨μ†Œ", + onCheckedChange = { + tradeConfig.auto_cancel_pending_buy = it + KisSession.saveTradeConfig() + } + ) - SettingInputField( - label = "μžλ™ μ·¨μ†Œ κ°­ λΉ„μœ¨", - initialValue = (tradeConfig.auto_cancel_pending_rate).toString(), - onSave = { - val rate = it.toDoubleOrNull() ?: 2.0 - tradeConfig.auto_cancel_pending_rate = rate - KisSession.saveTradeConfig() - }, - helperText = "ν˜„μž¬: ${tradeConfig.auto_cancel_pending_time / 1000}초 ν›„ μ·¨μ†Œ" - ) + SettingInputField( + modifier = Modifier.weight(1f).padding(horizontal = 4.dp), + label = "λŒ€κΈ° μ‹œκ°„ (λΆ„)", + initialValue = (tradeConfig.auto_cancel_pending_time / (60 * 1000)).toString(), + onSave = { + val minutes = it.toLongOrNull() ?: 60L + tradeConfig.auto_cancel_pending_time = minutes * 60 * 1000 + KisSession.saveTradeConfig() + }, + helperText = "${tradeConfig.auto_cancel_pending_time / 1000}초 ν›„ λŒ€μ‘°" + ) + + SettingInputField( + modifier = Modifier.weight(1f).padding(start = 4.dp), + label = "μ·¨μ†Œ κ°­ λΉ„μœ¨", + initialValue = (tradeConfig.auto_cancel_pending_rate).toString(), + onSave = { + val rate = it.toDoubleOrNull() ?: 2.0 + tradeConfig.auto_cancel_pending_rate = rate + KisSession.saveTradeConfig() + }, + helperText = "μ΄νƒˆ μ‹œ μ·¨μ†Œ" + ) + } Row(horizontalArrangement = Arrangement.SpaceEvenly) { SettingSwitchField ( @@ -1072,7 +1130,7 @@ fun FilterChipWithRightClick( .pointerInput(label) { detectTapGestures( onTap = { - onClick() + onClick() coroutineScope.launch { // λΆ€λͺ¨(Row)의 전체 λ„ˆλΉ„ κ°€μ Έμ˜€κΈ° val containerWidth = scrollState.maxValue + scrollState.viewportSize @@ -1121,20 +1179,24 @@ fun FilterChipWithRightClick( * μ‹œ:λΆ„ μž…λ ₯을 μœ„ν•œ μ „μš© 컴포저블 */ @Composable -fun TimeInputRow(label: String, initialTime: String, onTimeChange: (String) -> Unit) { - // πŸ’‘ 화면에 μ¦‰μ‹œ κΈ€μžλ₯Ό 보여주기 μœ„ν•œ 둜컬 μƒνƒœ +fun TimeInputRow( + modifier: Modifier = Modifier, // 🌟 modifier νŒŒλΌλ―Έν„° μΆ”κ°€ + label: String, + initialTime: String, + onTimeChange: (String) -> Unit +) { var localText by remember(initialTime) { mutableStateOf(initialTime) } Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + modifier = modifier.padding(vertical = 4.dp, horizontal = 4.dp), // 🌟 μˆ˜μ • verticalAlignment = Alignment.CenterVertically ) { - Text(label, modifier = Modifier.width(80.dp), fontSize = 13.sp, fontWeight = FontWeight.Medium) + // 🌟 κΈ€μž 크기와 λ„ˆλΉ„λ₯Ό μ•½κ°„ μ€„μ—¬μ„œ ν•œ 쀄에 두 κ°œκ°€ 듀어가도 예쁘게 μ„€μ • + Text(label, modifier = Modifier.width(65.dp), fontSize = 12.sp, fontWeight = FontWeight.Medium) OutlinedTextField( value = localText, onValueChange = { - // 5κΈ€μž(HH:mm) μ œν•œ 및 μž…λ ₯ μ œμ–΄ if (it.length <= 5) { localText = it } @@ -1143,7 +1205,6 @@ fun TimeInputRow(label: String, initialTime: String, onTimeChange: (String) -> U modifier = Modifier .weight(1f) .onFocusChanged { focusState -> - // πŸ’‘ 포컀슀λ₯Ό μžƒμ—ˆμ„ λ•Œ(μž…λ ₯ μ™„λ£Œ μ‹œ) μ‹€μ œ 데이터 객체에 μ €μž₯ if (!focusState.isFocused) { onTimeChange(localText) } @@ -1151,12 +1212,12 @@ fun TimeInputRow(label: String, initialTime: String, onTimeChange: (String) -> U singleLine = true, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Done, - keyboardType = KeyboardType.Number // 숫자 ν‚€νŒ¨λ“œ μœ λ„ + keyboardType = KeyboardType.Number ), keyboardActions = KeyboardActions( onDone = { onTimeChange(localText) } ), - textStyle = androidx.compose.ui.text.TextStyle(fontSize = 14.sp) + textStyle = androidx.compose.ui.text.TextStyle(fontSize = 13.sp) ) } } \ No newline at end of file