142 lines
6.4 KiB
Kotlin
142 lines
6.4 KiB
Kotlin
|
|
// src/main/kotlin/ui/IntegratedOrderSection.kt
|
||
|
|
package ui
|
||
|
|
|
||
|
|
import androidx.compose.foundation.background
|
||
|
|
import androidx.compose.foundation.layout.*
|
||
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||
|
|
import androidx.compose.material.*
|
||
|
|
import androidx.compose.runtime.*
|
||
|
|
import androidx.compose.ui.Alignment
|
||
|
|
import androidx.compose.ui.Modifier
|
||
|
|
import androidx.compose.ui.graphics.Color
|
||
|
|
import androidx.compose.ui.text.font.FontWeight
|
||
|
|
import androidx.compose.ui.unit.dp
|
||
|
|
import androidx.compose.ui.unit.sp
|
||
|
|
import kotlinx.coroutines.launch
|
||
|
|
import network.KisTradeService
|
||
|
|
|
||
|
|
@Composable
|
||
|
|
fun IntegratedOrderSection(
|
||
|
|
stockCode: String,
|
||
|
|
currentPrice: String,
|
||
|
|
tradeService: KisTradeService,
|
||
|
|
onOrderResult: (String, Boolean) -> Unit
|
||
|
|
) {
|
||
|
|
val scope = rememberCoroutineScope()
|
||
|
|
var orderQty by remember { mutableStateOf("1") }
|
||
|
|
var orderPrice by remember { mutableStateOf("") } // 빈 값이면 시장가
|
||
|
|
|
||
|
|
// 자동 매도 설정
|
||
|
|
var isAutoSellEnabled by remember { mutableStateOf(false) }
|
||
|
|
var profitRate by remember { mutableStateOf("5.0") }
|
||
|
|
var stopLossRate by remember { mutableStateOf("-3.0") }
|
||
|
|
|
||
|
|
val basePrice = (if (orderPrice.isEmpty()) currentPrice.replace(",", "") else orderPrice).toDoubleOrNull() ?: 0.0
|
||
|
|
val qty = orderQty.toDoubleOrNull() ?: 0.0
|
||
|
|
|
||
|
|
Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
|
||
|
|
Text("주문 및 자동 매도 설정", style = MaterialTheme.typography.subtitle2, fontWeight = FontWeight.Bold)
|
||
|
|
|
||
|
|
// 1. 가격 및 수량 입력
|
||
|
|
Row(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)) {
|
||
|
|
OutlinedTextField(
|
||
|
|
value = orderQty,
|
||
|
|
onValueChange = { if (it.all { c -> c.isDigit() }) orderQty = it },
|
||
|
|
label = { Text("수량") },
|
||
|
|
modifier = Modifier.weight(1f).padding(end = 4.dp)
|
||
|
|
)
|
||
|
|
OutlinedTextField(
|
||
|
|
value = orderPrice,
|
||
|
|
onValueChange = { if (it.all { c -> c.isDigit() }) orderPrice = it },
|
||
|
|
label = { Text("가격") },
|
||
|
|
placeholder = { Text("시장가 (${currentPrice})") },
|
||
|
|
modifier = Modifier.weight(1f)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. 수익률 시뮬레이션 표 (신규 추가)
|
||
|
|
if (basePrice > 0 && qty > 0) {
|
||
|
|
Text("익절/손절 시뮬레이션 (수수료/세금 약 0.22% 반영)", fontSize = 11.sp, color = Color.Gray, modifier = Modifier.padding(bottom = 4.dp))
|
||
|
|
Card(backgroundColor = Color(0xFFF1F3F5), shape = RoundedCornerShape(4.dp), elevation = 0.dp) {
|
||
|
|
Column(modifier = Modifier.padding(8.dp)) {
|
||
|
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||
|
|
SimulationColumn("수익률", listOf("+5%", "+3%", "+1%", "-1%", "-3%", "-5%"), true)
|
||
|
|
SimulationColumn("목표가", listOf(1.05, 1.03, 1.01, 0.99, 0.97, 0.95).map { (basePrice * it).toLong().toString() }, false)
|
||
|
|
SimulationColumn("예상수령액", listOf(1.05, 1.03, 1.01, 0.99, 0.97, 0.95).map { rate ->
|
||
|
|
val sellPrice = basePrice * rate
|
||
|
|
val totalAmount = sellPrice * qty
|
||
|
|
val netAmount = totalAmount * (1 - 0.0022) // 수수료+세금 약 0.22% 차감
|
||
|
|
String.format("%,d", netAmount.toLong())
|
||
|
|
}, false)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Spacer(modifier = Modifier.height(12.dp))
|
||
|
|
|
||
|
|
// 3. 자동 매도 옵션
|
||
|
|
Card(backgroundColor = Color(0xFFF8F9FA), elevation = 0.dp) {
|
||
|
|
Column(modifier = Modifier.padding(8.dp)) {
|
||
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||
|
|
Checkbox(checked = isAutoSellEnabled, onCheckedChange = { isAutoSellEnabled = it })
|
||
|
|
Text("매수 체결 시 자동 매도 감시 시작", fontSize = 12.sp)
|
||
|
|
}
|
||
|
|
if (isAutoSellEnabled) {
|
||
|
|
Row {
|
||
|
|
OutlinedTextField(
|
||
|
|
value = profitRate, onValueChange = { profitRate = it },
|
||
|
|
label = { Text("익절 %") }, modifier = Modifier.weight(1f).padding(end = 4.dp)
|
||
|
|
)
|
||
|
|
OutlinedTextField(
|
||
|
|
value = stopLossRate, onValueChange = { stopLossRate = it },
|
||
|
|
label = { Text("손절 %") }, modifier = Modifier.weight(1f)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Spacer(modifier = Modifier.height(12.dp))
|
||
|
|
|
||
|
|
// 4. 매수/매도 버튼
|
||
|
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||
|
|
Button(
|
||
|
|
onClick = {
|
||
|
|
scope.launch {
|
||
|
|
val finalPrice = if (orderPrice.isBlank()) "0" else orderPrice
|
||
|
|
tradeService.postOrder(stockCode, orderQty, finalPrice, isBuy = true)
|
||
|
|
.onSuccess {
|
||
|
|
onOrderResult(it, true)
|
||
|
|
if (isAutoSellEnabled) { /* 자동매도 등록 로직 호출 */ }
|
||
|
|
}
|
||
|
|
.onFailure { onOrderResult(it.message ?: "에러", false) }
|
||
|
|
}
|
||
|
|
},
|
||
|
|
modifier = Modifier.weight(1f).padding(end = 4.dp),
|
||
|
|
colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFFE03E2D))
|
||
|
|
) { Text("매수", color = Color.White) }
|
||
|
|
|
||
|
|
Button(
|
||
|
|
onClick = { /* 매도 로직동일 */ },
|
||
|
|
modifier = Modifier.weight(1f),
|
||
|
|
colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFF0E62CF))
|
||
|
|
) { Text("매도", color = Color.White) }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@Composable
|
||
|
|
fun SimulationColumn(title: String, items: List<String>, isHeader: Boolean) {
|
||
|
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||
|
|
Text(title, fontSize = 10.sp, fontWeight = FontWeight.Bold, color = Color.DarkGray)
|
||
|
|
items.forEach { text ->
|
||
|
|
Text(
|
||
|
|
text = text,
|
||
|
|
fontSize = 11.sp,
|
||
|
|
color = if (text.contains("+")) Color(0xFFE03E2D) else if (text.contains("-")) Color(0xFF0E62CF) else Color.Black,
|
||
|
|
modifier = Modifier.padding(vertical = 1.dp)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|