This commit is contained in:
2026-10-06 14:47:26 +09:00
parent 25c1485b1e
commit 4b1805b9b8
3 changed files with 144 additions and 65 deletions
+5
View File
@@ -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
}
+14 -1
View File
@@ -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(
+125 -64
View File
@@ -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)
)
}
}