This commit is contained in:
2026-02-19 15:47:31 +09:00
parent df124612ab
commit c4f58f159a
11 changed files with 609 additions and 113 deletions
+1 -1
View File
@@ -51,8 +51,8 @@ fun AiAnalysisView(technicalAnalyzer: TechnicalAnalyzer,stockCode:String,stockNa
modifier = Modifier.fillMaxWidth()
) {
Column(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.fillMaxHeight(0.15F)
.verticalScroll(rememberScrollState()) // 스크롤 활성화
.padding(16.dp)
.background(Color(0xFFF5F5F5), RoundedCornerShape(8.dp))) {
+215 -10
View File
@@ -5,22 +5,28 @@ import AutoTradeItem
import TradingDecision
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import model.CandleData
import model.ConfigIndex
import model.ExecutionData
import model.KisSession
import model.StockBasicInfo
import model.feesAndTaxRate
import model.minimumNetProfit
import network.KisTradeService
import network.KisWebSocketManager
import service.AutoTradingManager
@@ -54,7 +60,7 @@ fun DashboardScreen() {
CoroutineScope(Dispatchers.Default).launch {
// while (true) {
// delay(60000) // 1분마다 체크
AutoTradingManager.checkAndRestart(tradeService, callback)
AutoTradingManager.checkAndRestart(tradeService, callback)
// }
}
}
@@ -121,7 +127,7 @@ fun DashboardScreen() {
// 2. 최소 마진 설정 (수수료/세금 0.3% + 순수익 1.5% = 1.8%)
val minEffectiveRate = minimumNetProfit + feesAndTaxRate
val minEffectiveRate = KisSession.config.getValues(ConfigIndex.PROFIT_INDEX) + KisSession.config.getValues(ConfigIndex.TAX_INDEX)
// 3. DB에 설정된 목표 수익률과 최소 보장 수익률 중 큰 값 선택
val finalProfitRate = maxOf(dbItem.profitRate, minEffectiveRate)
@@ -175,7 +181,7 @@ fun DashboardScreen() {
val monitoringTrades = DatabaseFactory.getAutoTradesByStatus(listOf(TradeStatus.MONITORING, TradeStatus.PENDING_BUY))
val monitoringCodes = monitoringTrades.map { it.code }.toSet()
wsManager.updateSubscriptions(monitoringCodes)
AutoTradingManager.resumePendingSellOrders(tradeService)
refreshTrigger++
}
@@ -191,7 +197,7 @@ fun DashboardScreen() {
Row(modifier = Modifier.fillMaxSize().background(Color(0xFFF2F2F2))) {
// [좌측 25%] 내 자산 및 통합 잔고
Column(modifier = Modifier.weight(0.125f).fillMaxHeight().padding(8.dp)) {
Column(modifier = Modifier.weight(0.12f).fillMaxHeight().padding(8.dp)) {
BalanceSection(tradeService,
onRefresh = { refreshTrigger++ },
refreshTrigger = refreshTrigger) { code, name, isDom,qty ->
@@ -235,7 +241,8 @@ fun DashboardScreen() {
}
VerticalDivider()
Column(modifier = Modifier.weight(0.2f).fillMaxHeight().padding(8.dp)) {
Column(modifier = Modifier.weight(0.25f).padding(8.dp).fillMaxHeight().background(Color.White)) {
AiAnalysisView(
technicalAnalyzer = TechnicalAnalyzer().apply {
this.min30 = min30
@@ -254,9 +261,177 @@ fun DashboardScreen() {
}
}
)
Spacer(modifier = Modifier.height(16.dp))
Text("설정값 관리", style = MaterialTheme.typography.subtitle2, modifier = Modifier.padding(bottom = 4.dp))
LazyVerticalGrid(
columns = GridCells.Fixed(2), // 2열 병렬 배치
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth().weight(0.3f)
) {
item(span = { GridItemSpan(maxLineSpan) }) { // 2열을 모두 차지함
Text(
"💰 거래 기본 설정",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)
)
}
var defaults = arrayOf(
ConfigIndex.TAX_INDEX,
ConfigIndex.PROFIT_INDEX,
ConfigIndex.BUY_WEIGHT_INDEX,
ConfigIndex.MAX_BUDGET_INDEX,
ConfigIndex.MAX_PRICE_INDEX,
ConfigIndex.MIN_PRICE_INDEX,
ConfigIndex.MIN_PURCHASE_SCORE_INDEX,
ConfigIndex.MAX_COUNT_INDEX,
)
items(defaults.size) { index ->
val configKey = defaults.get(index)
// 1. 키보드 입력을 실시간으로 보여줄 로컬 상태 (String)
var localText by remember(configKey) {
mutableStateOf(KisSession.config.getValues(configKey)?.toString() ?: "")
}
// 저장 로직을 공통 함수로 분리
val saveAction = {
var newValue = localText.toDoubleOrNull() ?: 0.0
if (configKey.label.contains("PROFIT")) {
newValue = newValue / KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
}
KisSession.config.setValues(configKey, newValue)
DatabaseFactory.saveConfig(KisSession.config)
println("💾 저장됨: ${configKey.label} = $newValue")
}
var text = if (configKey.label.contains("PROFIT")) {
"${(localText.toDoubleOrNull() ?: 1.0) * KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)}"
} else {
localText
}
OutlinedTextField(
value = text,
onValueChange = { localText = it }, // 화면에는 즉시 반영
label = { Text(configKey.label) },
modifier = Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
// 2. 포커스를 잃었을 때 저장
if (!focusState.isFocused) {
saveAction()
}
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Decimal
),
keyboardActions = KeyboardActions(
// 3. 엔터(Done) 키를 눌렀을 때 저장
onDone = {
saveAction()
}
),
singleLine = true
)
}
item(span = { GridItemSpan(maxLineSpan) }) { // 2열을 모두 차지함
Text(
"💰매수 정책 및 기대 수익률",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)
)
}
var defaults2 = arrayOf(
arrayOf(ConfigIndex.GRADE_5_BUY,
ConfigIndex.GRADE_5_PROFIT,),
arrayOf(ConfigIndex.GRADE_4_BUY,
ConfigIndex.GRADE_4_PROFIT,),
arrayOf(ConfigIndex.GRADE_3_BUY,
ConfigIndex.GRADE_3_PROFIT,),
arrayOf(ConfigIndex.GRADE_2_BUY,
ConfigIndex.GRADE_2_PROFIT,),
arrayOf(ConfigIndex.GRADE_1_BUY,
ConfigIndex.GRADE_1_PROFIT,),
)
for (items in defaults2) {
val common = findLongestCommonSubstring(items.first().label,items.last().label)
item(span = { GridItemSpan(maxLineSpan) }) { // 2열을 모두 차지함
Text(
common,
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)
)
}
items(items.size) { index ->
val configKey = items.get(index)
// 1. 키보드 입력을 실시간으로 보여줄 로컬 상태 (String)
var localText by remember(configKey) {
mutableStateOf(KisSession.config.getValues(configKey)?.toString() ?: "")
}
var labelText by remember(configKey) {
mutableStateOf(KisSession.config.getValues(configKey)?.toString() ?: "")
}
val saveAction = {
var newValue = localText.toDoubleOrNull() ?: 0.0
// if (configKey.name.contains("PROFIT")) {
// newValue = newValue / KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
// }
KisSession.config.setValues(configKey, newValue)
DatabaseFactory.saveConfig(KisSession.config)
println("💾 저장됨: ${configKey.label} = $newValue")
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.toDouble() * KisSession.config.getValues(configKey) + KisSession.config.getValues(
ConfigIndex.TAX_INDEX)}"
} else {
getRemaining(configKey.label,common) + ": -${localText} 호가 매수}"
}
}
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.toDouble() * KisSession.config.getValues(configKey) + KisSession.config.getValues(
ConfigIndex.TAX_INDEX)} "
} else {
getRemaining(configKey.label,common) + ": -${localText} 호가 매수}"
}
OutlinedTextField(
value = localText,
onValueChange = { localText = it }, // 화면에는 즉시 반영
label = { Text(labelText) },
modifier = Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
// 2. 포커스를 잃었을 때 저장
if (!focusState.isFocused) {
saveAction()
}
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Decimal
),
keyboardActions = KeyboardActions(
// 3. 엔터(Done) 키를 눌렀을 때 저장
onDone = {
saveAction()
}
),
singleLine = true
)
}
}
}
}
VerticalDivider()
Column(modifier = Modifier.weight(0.125f).fillMaxHeight().padding(8.dp)) {
Column(modifier = Modifier.weight(0.12f).fillMaxHeight().padding(8.dp)) {
AutoTradeSection(
isDomestic = isDomestic,
tradeService = tradeService,
@@ -277,7 +452,7 @@ fun DashboardScreen() {
}
VerticalDivider()
// [우측 30%] 시장 추천 TOP 20 (실전 데이터)
Column(modifier = Modifier.weight(0.125f).fillMaxHeight().padding(8.dp)) {
Column(modifier = Modifier.weight(0.12f).fillMaxHeight().padding(8.dp)) {
MarketSection(tradeService) { code, name, isDom ->
val info = StockBasicInfo(
code = code,
@@ -301,4 +476,34 @@ fun DashboardScreen() {
@Composable
fun VerticalDivider() {
Box(Modifier.fillMaxHeight().width(1.dp).background(Color.LightGray))
}
fun findLongestCommonSubstring(s1: String, s2: String): String {
if (s1.isEmpty() || s2.isEmpty()) return ""
var longest = ""
// 더 짧은 문자열을 기준으로 삼아 반복 횟수를 줄임
val reference = if (s1.length <= s2.length) s1 else s2
val target = if (s1.length <= s2.length) s2 else s1
for (i in reference.indices) {
for (j in (i + longest.length + 1)..reference.length) {
val sub = reference.substring(i, j)
if (target.contains(sub)) {
if (sub.length > longest.length) {
longest = sub
}
} else {
// target에 포함되지 않으면 더 긴 substring은 존재할 수 없으므로 탈출
break
}
}
}
return longest
}
fun getRemaining(original: String, common: String): String {
if (common.isEmpty()) return original
// 가장 처음 발견되는 공통 문자열을 한 번만 제거
return original.replaceFirst(common, "").trim()
}
+43 -37
View File
@@ -18,17 +18,16 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.min
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
import model.MAX_BUDGET
import model.MIN_PURCHASE_SCORE
import model.ConfigIndex
import model.KisSession
import model.RankingStock
import model.buyWeight
import model.feesAndTaxRate
import model.minimumNetProfit
import network.KisTradeService
import service.AutoTradingManager
import util.MarketUtil
import kotlin.math.min
enum class InvestmentGrade(
val displayName: String,
@@ -36,7 +35,8 @@ enum class InvestmentGrade(
val shortWeight: Double = 0.0,
val midWeight: Double = 0.0,
val longWeight: Double = 0.0,
val profitGuide: Double = 0.0,
val profitGuide: ConfigIndex,
val buyGuide: ConfigIndex,
) {
LEVEL_5_STRONG_RECOMMEND(
displayName = "최상급 추천",
@@ -44,7 +44,8 @@ enum class InvestmentGrade(
shortWeight = 1.0,
midWeight = 1.0,
longWeight = 1.0,
profitGuide = 1.8,
profitGuide = ConfigIndex.GRADE_5_PROFIT,
buyGuide = ConfigIndex.GRADE_5_BUY,
),
LEVEL_4_BALANCED_RECOMMEND(
displayName = "균형 추천",
@@ -52,7 +53,8 @@ enum class InvestmentGrade(
shortWeight = 0.8,
midWeight = 1.0,
longWeight = 1.0,
profitGuide = 1.3,
profitGuide = ConfigIndex.GRADE_4_PROFIT,
buyGuide = ConfigIndex.GRADE_4_BUY,
),
LEVEL_3_CAUTIOUS_RECOMMEND(
displayName = "보수적 추천",
@@ -60,7 +62,8 @@ enum class InvestmentGrade(
shortWeight = 0.6,
midWeight = 1.0,
longWeight = 1.0,
profitGuide = 0.9,
profitGuide = ConfigIndex.GRADE_3_PROFIT,
buyGuide = ConfigIndex.GRADE_3_BUY,
),
LEVEL_2_HIGH_RISK(
displayName = "고위험 추천",
@@ -68,7 +71,8 @@ enum class InvestmentGrade(
shortWeight = 1.0,
midWeight = 0.4,
longWeight = 0.4,
profitGuide = 0.7,
profitGuide = ConfigIndex.GRADE_2_PROFIT,
buyGuide = ConfigIndex.GRADE_2_BUY,
),
LEVEL_1_SPECULATIVE(
displayName = "순수 공격적 선택",
@@ -76,7 +80,8 @@ enum class InvestmentGrade(
shortWeight = 1.0,
midWeight = 0.2,
longWeight = 0.2,
profitGuide = 0.5,
profitGuide = ConfigIndex.GRADE_1_PROFIT,
buyGuide = ConfigIndex.GRADE_1_BUY,
)
}
@@ -128,7 +133,7 @@ fun IntegratedOrderSection(
}
var profitRate by remember(monitoringItem) {
mutableStateOf(monitoringItem?.profitRate?.toString() ?: minimumNetProfit.toString())
mutableStateOf(monitoringItem?.profitRate?.toString() ?: KisSession.config.getValues(ConfigIndex.PROFIT_INDEX).toString())
}
var stopLossRate by remember(monitoringItem) {
mutableStateOf(monitoringItem?.stopLossRate?.toString() ?: "-1.5")
@@ -164,19 +169,19 @@ fun IntegratedOrderSection(
return when {
// LEVEL_5: 단기·중기·장기 모두 매우 높고, 신뢰도까지 높음
shortAvg >= 85.0 && midLongAvg >= 80.0 ->
InvestmentGrade.LEVEL_5_STRONG_RECOMMEND
if (ts.analyzer?.isOverheatedStock() ?: true) InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND else InvestmentGrade.LEVEL_5_STRONG_RECOMMEND
// LEVEL_4: 중기·장기 기본 준수, 단기까지 양호
midLongAvg >= 75.0 && shortAvg >= 70.0 ->
InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND
if (ts.analyzer?.isOverheatedStock() ?: true) InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND else InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND
// LEVEL_3: 중기·장기 기본 이상, 단기만 단기 변동성 높은 보수형
midLongAvg >= 70.0 && shortAvg in 60.0..70.0 ->
InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND
if (ts.analyzer?.isOverheatedStock() ?: true) InvestmentGrade.LEVEL_2_HIGH_RISK else InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND
// LEVEL_2: 단기/초단기만 강하고, 중기·장기 애매
shortAvg >= 75.0 && midLongAvg < 65.0 ->
InvestmentGrade.LEVEL_2_HIGH_RISK
if (ts.analyzer?.isOverheatedStock() ?: true) InvestmentGrade.LEVEL_1_SPECULATIVE else InvestmentGrade.LEVEL_2_HIGH_RISK
// LEVEL_1: 단기/초단기만 의미 있고, 중기·장기 심각히 약함
shortAvg >= 70.0 && midLongAvg < 55.0 ->
@@ -184,30 +189,24 @@ fun IntegratedOrderSection(
// 기본 조건은 충족했지만, 패턴에 잘 맞지 않을 때 (예: 중립)
else ->
InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND
if (ts.analyzer?.isOverheatedStock() ?: true) InvestmentGrade.LEVEL_1_SPECULATIVE else InvestmentGrade.LEVEL_2_HIGH_RISK
}
}
fun excuteTrade(willEnableAutoSell: Boolean, orderQty: String, profitRate1: Double?,investmentGrade: InvestmentGrade = InvestmentGrade.LEVEL_2_HIGH_RISK) {
scope.launch {
val tickSize = MarketUtil.getTickSize(basePrice)
val oneTickLowerPrice = basePrice - (tickSize * when(investmentGrade) {
InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> 0
InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND -> 1
InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND -> 2
InvestmentGrade.LEVEL_2_HIGH_RISK -> 2
InvestmentGrade.LEVEL_1_SPECULATIVE -> 3
else -> 3
})
val oneTickLowerPrice = basePrice - (tickSize * KisSession.config.getValues(investmentGrade.buyGuide).toInt())
// 2. 주문 가격 설정 (직접 입력값이 없으면 한 틱 낮은 가격 사용)
val finalPrice = if (orderPrice.isBlank()) {
oneTickLowerPrice.toLong().toString()
val finalPrice = MarketUtil.roundToTickSize(if (orderPrice.isBlank()) {
oneTickLowerPrice.toDouble()
} else {
orderPrice
}
orderPrice.toDouble()
})
println("basePrice : $basePrice, oneTickLowerPrice : $oneTickLowerPrice, finalPrice : $finalPrice")
tradeService.postOrder(stockCode, orderQty, finalPrice, isBuy = true)
tradeService.postOrder(stockCode, orderQty, finalPrice.toLong().toString(), isBuy = true)
.onSuccess { realOrderNo -> // KIS 서버에서 생성된 실제 주문번호
onOrderResult("주문 성공: $realOrderNo", true)
if (willEnableAutoSell) {
@@ -220,7 +219,9 @@ fun IntegratedOrderSection(
// 3. 실질 목표 수익률 계산
// 사용자가 입력한 pRate와 (최소 순수익 + 제반 비용) 중 큰 값을 선택합니다.
val effectiveProfitRate = maxOf((profitRate1 ?: pRate) + feesAndTaxRate, minimumNetProfit + feesAndTaxRate)
var tax = KisSession.config.getValues(ConfigIndex.TAX_INDEX)
val effectiveProfitRate = maxOf(((profitRate1 ?: pRate) + tax), (KisSession.config.getValues(
ConfigIndex.PROFIT_INDEX) + tax))
// 4. 보정된 수익률을 적용하여 목표가 계산
val calculatedTarget = MarketUtil.roundToTickSize(basePrice * (1 + effectiveProfitRate / 100.0))
@@ -241,7 +242,7 @@ fun IntegratedOrderSection(
))
monitoringItem = DatabaseFactory.findConfigByCode(stockCode)
onOrderSaved(realOrderNo)
onOrderResult("매수 및 감시 설정 완료 (목표 수익률: ${String.format("%.2f", effectiveProfitRate)}%): $realOrderNo", true)
onOrderResult("매수 및 감시 설정 완료 (목표 수익률: ${String.format("%.4f", effectiveProfitRate)}%): $realOrderNo", true)
}
}
.onFailure { onOrderResult(it.message ?: "매수 실패", false) }
@@ -257,6 +258,11 @@ fun IntegratedOrderSection(
completeTradingDecision.stockCode.equals(stockCode)) {
basePrice = completeTradingDecision.currentPrice
println("basePrice $basePrice")
val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX)
val maxBudget = KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX)
val buyWeight = KisSession.config.getValues(ConfigIndex.BUY_WEIGHT_INDEX)
val baseProfit = KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
fun resultCheck(completeTradingDecision :TradingDecision) {
val weights = mapOf(
"short" to 0.3, // 초단기 점수가 낮아도 전체에 미치는 영향 감소
@@ -269,10 +275,10 @@ fun IntegratedOrderSection(
((completeTradingDecision.profitPossible() + append) * weights["profit"]!!) +
((completeTradingDecision.safePossible() + append) * weights["safe"]!!)
if (totalScore >= MIN_PURCHASE_SCORE && completeTradingDecision.confidence >= MIN_CONFIDENCE) {
if (totalScore >= minScore && completeTradingDecision.confidence >= MIN_CONFIDENCE) {
var investmentGrade : InvestmentGrade = getInvestmentGrade(completeTradingDecision,totalScore, completeTradingDecision.confidence)
val finalMargin = minimumNetProfit * investmentGrade.profitGuide
val finalMargin = baseProfit * KisSession.config.getValues(investmentGrade.profitGuide)
println("""
사명 : ${completeTradingDecision.corpName}
신뢰도 : ${completeTradingDecision.confidence + append}
@@ -287,19 +293,19 @@ fun IntegratedOrderSection(
// basePrice(현재가 혹은 지정가)를 기준으로 매수 가능 수량 산출 (최소 1주 보장)
val calculatedQty = if (basePrice > 0) {
(MAX_BUDGET / basePrice).toInt().coerceAtLeast(1)
(maxBudget / basePrice).toInt().coerceAtLeast(1)
} else {
1
}
// 5. 매수 실행 (계산된 finalMargin 전달)
excuteTrade(
willEnableAutoSell = true,
orderQty = calculatedQty.toString(),
orderQty = min(calculatedQty, KisSession.config.getValues(ConfigIndex.MAX_COUNT_INDEX).toInt()).toString(),
profitRate1 = finalMargin,
investmentGrade = investmentGrade,
)
} else if(totalScore >= (MIN_PURCHASE_SCORE * 0.85) && completeTradingDecision.confidence + append >= (MIN_CONFIDENCE * 0.85)) {
} else if(totalScore >= (minScore * 0.85) && completeTradingDecision.confidence + append >= (MIN_CONFIDENCE * 0.85)) {
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
println("✋ [관망] 토탈 스코어 또는 신뢰도 미달 이나 약간의 오차로 재분석 대기열에 추가")
} else {
+11 -11
View File
@@ -47,16 +47,16 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
item {
Text("거래 방식 선택", style = MaterialTheme.typography.h6)
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(selected = !config.isSimulation, onClick = { config = config.copy(isSimulation = false) })
RadioButton(selected = !config.isSimulation, onClick = { config = config.copy(isSimulation = false,) })
Text("실전투자")
Spacer(Modifier.width(16.dp))
RadioButton(selected = config.isSimulation, onClick = { config = config.copy(isSimulation = true) })
RadioButton(selected = config.isSimulation, onClick = { config = config.copy() })
Text("모의투자")
}
Divider(Modifier.padding(vertical = 12.dp))
OutlinedTextField(
value = config.htsId,
onValueChange = { config = config.copy(htsId = it) },
onValueChange = { config = config.copy(htsId = it,) },
label = { Text("HTS ID (실시간 체결 통보용)") },
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
placeholder = { Text("한국투자증권 HTS 접속 ID를 입력하세요") }
@@ -64,22 +64,22 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
// 실전 3종 입력
Text("실전투자 정보 (시세 조회 필수)", fontWeight = FontWeight.Bold)
OutlinedTextField(value = config.realAccountNo, onValueChange = {
config = config.copy(realAccountNo = it)
config = config.copy(realAccountNo = it,)
if(it.length >= 8) checkAndLoadConfig(it, true)
}, label = { Text("실전 계좌번호") }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(value = config.realAppKey, onValueChange = { config = config.copy(realAppKey = it) }, label = { Text("실전 App Key") }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(value = config.realSecretKey, onValueChange = { config = config.copy(realSecretKey = it) }, label = { Text("실전 Secret Key") }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation())
OutlinedTextField(value = config.realAppKey, onValueChange = { config = config.copy(realAppKey = it,) }, label = { Text("실전 App Key") }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(value = config.realSecretKey, onValueChange = { config = config.copy(realSecretKey = it,) }, label = { Text("실전 Secret Key") }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation())
Spacer(Modifier.height(16.dp))
// 모의 3종 입력
Text("모의투자 정보", fontWeight = FontWeight.Bold)
OutlinedTextField(value = config.vtsAccountNo, onValueChange = {
config = config.copy(vtsAccountNo = it)
config = config.copy(vtsAccountNo = it,)
if(it.length >= 8) checkAndLoadConfig(it, false)
}, label = { Text("모의 계좌번호") }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(value = config.vtsAppKey, onValueChange = { config = config.copy(vtsAppKey = it) }, label = { Text("모의 App Key") }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(value = config.vtsSecretKey, onValueChange = { config = config.copy(vtsSecretKey = it) }, label = { Text("모의 Secret Key") }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation())
OutlinedTextField(value = config.vtsAppKey, onValueChange = { config = config.copy(vtsAppKey = it,) }, label = { Text("모의 App Key") }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(value = config.vtsSecretKey, onValueChange = { config = config.copy(vtsSecretKey = it,) }, label = { Text("모의 Secret Key") }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation())
Divider(Modifier.padding(vertical = 16.dp))
@@ -91,7 +91,7 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
val data = state.dragData
if (data is DragData.FilesList) {
val path = data.readFiles().firstOrNull()?.removePrefix("file:")
if (path?.endsWith(".gguf") == true) config = config.copy(modelPath = path)
if (path?.endsWith(".gguf") == true) config = config.copy(modelPath = path,)
}
}),
contentAlignment = Alignment.Center
@@ -104,7 +104,7 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
val data = state.dragData
if (data is DragData.FilesList) {
val embedModelPath = data.readFiles().firstOrNull()?.removePrefix("file:")
if (embedModelPath?.endsWith(".gguf") == true) config = config.copy(embedModelPath = embedModelPath)
if (embedModelPath?.endsWith(".gguf") == true) config = config.copy(embedModelPath = embedModelPath,)
}
}),
contentAlignment = Alignment.Center