227 lines
11 KiB
Kotlin
227 lines
11 KiB
Kotlin
package ui
|
|
|
|
|
|
import AutoTradeItem
|
|
import TradingDecision
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.layout.*
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
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.lazy.items
|
|
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.font.FontWeight
|
|
import androidx.compose.ui.text.input.ImeAction
|
|
import androidx.compose.ui.text.input.KeyboardType
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.unit.sp
|
|
import model.ConfigIndex
|
|
import model.KisSession
|
|
import service.TechnicalAnalyzer
|
|
|
|
@Composable
|
|
fun TradingDecisionLog() {
|
|
Row(modifier = Modifier.fillMaxSize().background(Color(0xFFF2F2F2))) {
|
|
Column(modifier = Modifier.weight(0.5f).padding(8.dp).fillMaxHeight().background(Color.White)) {
|
|
Text("AI 자동매매 실시간 로그", style = MaterialTheme.typography.h6)
|
|
Divider(Modifier.padding(vertical = 8.dp))
|
|
|
|
LazyColumn(reverseLayout = true) { // 최신 로그가 위로 오게 함
|
|
items(TradingLogStore.decisionLogs) { log ->
|
|
Card(
|
|
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
|
elevation = 2.dp
|
|
) {
|
|
Column(modifier = Modifier.padding(12.dp)) {
|
|
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
|
Text("${log.time} - ${log.stockName}", fontWeight = FontWeight.Bold)
|
|
Text(
|
|
text = log.decision,
|
|
color = if (log.decision == "BUY") Color.Red else Color.Gray,
|
|
fontWeight = FontWeight.ExtraBold
|
|
)
|
|
}
|
|
Text("신뢰도: ${log.confidence}%", fontSize = 11.sp)
|
|
Text("이유: ${log.reason}", fontSize = 12.sp, color = Color.DarkGray)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Column(modifier = Modifier.weight(0.5f).padding(6.dp).fillMaxHeight().background(Color.White)) {
|
|
LazyVerticalGrid(
|
|
columns = GridCells.Fixed(2), // 2열 병렬 배치
|
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
|
modifier = Modifier.fillMaxWidth().fillMaxHeight().background(Color.White)
|
|
) {
|
|
item(span = { GridItemSpan(maxLineSpan) }) { // 2열을 모두 차지함
|
|
Text(
|
|
"💰 거래 기본 설정",
|
|
style = MaterialTheme.typography.subtitle2,
|
|
modifier = Modifier.padding(top = 10.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.SELL_PROFIT,
|
|
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.subtitle2,
|
|
modifier = Modifier.padding(top = 10.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.body1,
|
|
modifier = Modifier.padding(top = 10.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
|
|
//
|
|
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(ConfigIndex.PROFIT_INDEX)) + 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(ConfigIndex.PROFIT_INDEX)) + 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
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |