From 3fcce5e5c68f6dcc77e1875c43ff7ab6d27f2d62 Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Fri, 29 May 2026 13:29:17 +0900 Subject: [PATCH] .... --- src/main/kotlin/model/AppConfig.kt | 6 +- src/main/kotlin/service/AutoTradingManager.kt | 11 +- src/main/kotlin/ui/TradingDecisionLog.kt | 106 +++++++++++++++++- 3 files changed, 114 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/model/AppConfig.kt b/src/main/kotlin/model/AppConfig.kt index 568da25..21ae1a4 100644 --- a/src/main/kotlin/model/AppConfig.kt +++ b/src/main/kotlin/model/AppConfig.kt @@ -256,10 +256,10 @@ class TradeConfig { var autoSellOrder : Boolean = false var excuteMinCheck : Int = 2 var noticeGapTime : Int = 60 - var lowerAveragePrice : Boolean = false + var lowerAveragePrice : Boolean = true var lowerAverageStockCount : Int = 1 - var lowerAverageMaxRate : Double = 1.0 - var lowerAverageMinRate : Double = 40.0 + var lowerAverageMaxRate : Double = 15.0 + var lowerAverageMinRate : Double = 25.0 var lowerAverageTargetCount : Int = 2 } diff --git a/src/main/kotlin/service/AutoTradingManager.kt b/src/main/kotlin/service/AutoTradingManager.kt index 83ecbbb..b008e5c 100644 --- a/src/main/kotlin/service/AutoTradingManager.kt +++ b/src/main/kotlin/service/AutoTradingManager.kt @@ -219,9 +219,9 @@ object AutoTradingManager { var stockName = decision.stockName val finalPrice = MarketUtil.roundToTickSize(oneTickLowerPrice.toDouble()) val maxStocks = KisSession.config.getValues(ConfigIndex.MAX_HOLDING_COUNT).toInt() - + var hasCodes = currentBalance?.getHoldings()?.any { it.code.equals(decision.stockCode) && it.quantity.toInt() > 2 && !it.isTodayEntry} if (!canAddNewPosition(maxStocks)) { - + TradingLogStore.addNotice("SYSTEM", "LIMIT", "최대 보유 종목 도달로 신규 매수 일시 중단") AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode,hts_kor_isnm = stockName)) TradingLogStore.addWatchLog(decision,"WATCH","매수 실패 : 최대 보유 종목 도달로 신규 매수 일시 중단 => 재분석 대기열에 추가") @@ -702,7 +702,7 @@ object AutoTradingManager { // 현재 노출 수가 최대 허용치보다 작을 때만 true(매수 가능) 반환 - return true//myOredsAndBalanceCodes.size < maxAllowedStocks + return true //(currentBalance?.getHoldings()?.count { it.availOrderCount.toInt() > 0 } ?: 0) > maxAllowedStocks } suspend fun tryRefreshToken() { @@ -942,7 +942,10 @@ object AutoTradingManager { reanalysisList.clear() if (KisSession.tradeConfig.lowerAveragePrice) { currentBalance?.getHoldings()?.map { - if(!it.isTodayEntry && it.quantity.toInt() > KisSession.tradeConfig.lowerAverageTargetCount && it.profitRate.toDouble() < (KisSession.tradeConfig.lowerAverageMaxRate * -1) && it.profitRate.toDouble() > (KisSession.tradeConfig.lowerAverageMinRate * -1) ) { + if(!it.isTodayEntry && + it.quantity.toInt() > KisSession.tradeConfig.lowerAverageTargetCount && + it.profitRate.toDouble() < (KisSession.tradeConfig.lowerAverageMaxRate * -1) && + it.profitRate.toDouble() > (KisSession.tradeConfig.lowerAverageMinRate * -1) ) { remainingCandidates.add(RankingStock(mksc_shrn_iscd = it.code, hts_kor_isnm = it.name)) } } diff --git a/src/main/kotlin/ui/TradingDecisionLog.kt b/src/main/kotlin/ui/TradingDecisionLog.kt index 5d15962..fa9f877 100644 --- a/src/main/kotlin/ui/TradingDecisionLog.kt +++ b/src/main/kotlin/ui/TradingDecisionLog.kt @@ -571,7 +571,56 @@ fun TradingDecisionLog() { }, helperText = "본인의 텔레그램 아뒤" ) - + SettingSwitchField( + label = "물타기", + initialChecked = tradeConfig.lowerAveragePrice, + onCheckedChange = { tradeConfig.lowerAveragePrice = it + KisSession.saveTradeConfig() } + ) + Row(horizontalArrangement = Arrangement.SpaceEvenly) { + SettingInputField( + modifier = Modifier.weight(1.0f, true), + label = "물타기 최저선", + initialValue = (tradeConfig.lowerAverageMaxRate).toString(), + onSave = { + tradeConfig.lowerAverageMaxRate = it.toDouble() + KisSession.saveTradeConfig() + }, + helperText = "이것보다 커야 삼" + ) + SettingInputField( + modifier = Modifier.weight(1.0f, true), + label = "물타기 최고선", + initialValue = (tradeConfig.lowerAverageMinRate).toString(), + onSave = { + tradeConfig.lowerAverageMinRate = it.toDouble() + KisSession.saveTradeConfig() + }, + helperText = "이것보다 작아야 삼" + ) + } + Row(horizontalArrangement = Arrangement.SpaceEvenly) { + SettingInputField( + modifier = Modifier.weight(1.0f, true), + label = "물타기 기준 최소 보유 수량", + initialValue = (tradeConfig.lowerAverageTargetCount).toString(), + onSave = { + tradeConfig.lowerAverageTargetCount = it.toInt() + KisSession.saveTradeConfig() + }, + helperText = "이거 이상 갖고 있어야 삼" + ) + SettingInputField( + modifier = Modifier.weight(1.0f, true), + label = "물타기 개수", + initialValue = (tradeConfig.lowerAverageStockCount).toString(), + onSave = { + tradeConfig.lowerAverageStockCount = it.toInt() + KisSession.saveTradeConfig() + }, + helperText = "1만큼 사고 팜" + ) + } } } } @@ -716,9 +765,61 @@ fun CsvDropZone( } +//@OptIn(ExperimentalMaterialApi::class) +//@Composable +//fun SettingInputField( +// label: String, +// initialValue: String, // 💡 value -> initialValue 로 변경 +// placeholder: String = "", +// helperText: String = "", +// onSave: (String) -> Unit // 💡 타자 칠 때마다가 아니라, 완료 시 저장하도록 콜백 변경 +//) { +// // 💡 화면에 즉시 글자를 그려주기 위한 로컬 상태 (핵심 해결책) +// var localText by remember { mutableStateOf(initialValue) } +// +// Column(modifier = Modifier.fillMaxWidth()) { +// OutlinedTextField( +// value = localText, +// onValueChange = { localText = it }, // 타자 칠 때 화면 즉시 반영 +// label = { Text(label, fontWeight = FontWeight.Bold) }, +// placeholder = { Text(placeholder) }, +// modifier = Modifier +// .fillMaxWidth() +// .onFocusChanged { focusState -> +// // 💡 포커스를 잃었을 때 (다른 칸을 클릭했을 때) 저장 +// if (!focusState.isFocused) { +// onSave(localText) +// } +// }, +// singleLine = true, +// keyboardOptions = KeyboardOptions( +// imeAction = ImeAction.Done, +// keyboardType = KeyboardType.Decimal +// ), +// keyboardActions = KeyboardActions( +// // 💡 모바일 키보드나 키보드에서 엔터(Done) 쳤을 때 저장 +// onDone = { +// onSave(localText) +// } +// ) +// ) +// +// if (helperText.isNotEmpty()) { +// Spacer(modifier = Modifier.height(4.dp)) +// Text( +// text = helperText, +// color = Color.Gray, +// fontSize = 11.sp, +// modifier = Modifier.padding(start = 4.dp) +// ) +// } +// } +//} + @OptIn(ExperimentalMaterialApi::class) @Composable fun SettingInputField( + modifier: Modifier? = null, label: String, initialValue: String, // 💡 value -> initialValue 로 변경 placeholder: String = "", @@ -728,7 +829,7 @@ fun SettingInputField( // 💡 화면에 즉시 글자를 그려주기 위한 로컬 상태 (핵심 해결책) var localText by remember { mutableStateOf(initialValue) } - Column(modifier = Modifier.fillMaxWidth()) { + Column(modifier = modifier ?: Modifier.fillMaxWidth()) { OutlinedTextField( value = localText, onValueChange = { localText = it }, // 타자 칠 때 화면 즉시 반영 @@ -767,6 +868,7 @@ fun SettingInputField( } } + @Composable fun SettingSwitchField( label: String,