This commit is contained in:
lunaticbum 2026-01-20 15:13:50 +09:00
parent 91f9e4ee9a
commit 5c088414c5
4 changed files with 55 additions and 13 deletions

View File

@ -123,11 +123,16 @@ data class UnifiedBalance(
@Serializable @Serializable
data class UnfilledOrder( data class UnfilledOrder(
val orgn_odno: String, val orgn_odno: String,
@SerialName("odno") val ord_no: String, // JSON의 odno를 ord_no로 매핑 @SerialName("odno")
val ord_no: String, // JSON의 odno를 ord_no로 매핑
val pdno: String, val pdno: String,
@SerialName("prdt_name") val prdt_name: String, @SerialName("prdt_name")
val prdt_name: String,
val ord_unpr: String, // JSON이 문자열이므로 String 권장 val ord_unpr: String, // JSON이 문자열이므로 String 권장
@SerialName("psbl_qty") val rmnd_qty: String, // JSON의 psbl_qty를 rmnd_qty로 매핑 val ord_qty : String,
@SerialName("psbl_qty")
val rmnd_qty: String, // JSON의 psbl_qty를 rmnd_qty로 매핑
val ord_dvsn_name: String, val ord_dvsn_name: String,
val rvse_cncl_dvsn_name: String val rvse_cncl_dvsn_name: String
) )
@ -145,7 +150,7 @@ fun UnfilledOrder.toAutoTradeItem(isDomestic: Boolean): AutoTradeItem {
code = this.pdno, code = this.pdno,
name = this.prdt_name, name = this.prdt_name,
orderedPrice = this.ord_unpr.toDoubleOrNull() ?: 0.0, orderedPrice = this.ord_unpr.toDoubleOrNull() ?: 0.0,
quantity = 0, // 미체결 내역에서는 원 주문 수량을 알기 어려우므로 0 또는 별도 처리 quantity = this.ord_qty.toIntOrNull() ?: 0, // 미체결 내역에서는 원 주문 수량을 알기 어려우므로 0 또는 별도 처리
remainedQuantity = this.rmnd_qty.toIntOrNull() ?: 0, remainedQuantity = this.rmnd_qty.toIntOrNull() ?: 0,
status = "PENDING_BUY", // 기본적으로 미체결은 매수/매도 대기 상태 status = "PENDING_BUY", // 기본적으로 미체결은 매수/매도 대기 상태
isDomestic = isDomestic isDomestic = isDomestic

View File

@ -79,7 +79,13 @@ class KisWebSocketManager {
)) ))
if (tradeLogs.size > 50) tradeLogs.removeLast() if (tradeLogs.size > 50) tradeLogs.removeLast()
} }
"H0STT084R" -> onExecutionReceived?.invoke(dataRows[5], dataRows[9], dataRows[12], dataRows[13], dataRows[15] == "02") "H0STT084R" -> {
println("채결 데이터")
onExecutionReceived?.invoke(dataRows[5], dataRows[9], dataRows[12], dataRows[13], dataRows[15] == "02")
}
else -> {
println("쓰레기? ${trId}")
}
} }
} }
fun clearData() { fun clearData() {

View File

@ -33,17 +33,32 @@ fun AutoTradeSection(
var tradeList by remember { mutableStateOf(emptyList<AutoTradeItem>()) } var tradeList by remember { mutableStateOf(emptyList<AutoTradeItem>()) }
// refreshTrigger가 바뀔 때마다 실행됨 // refreshTrigger가 바뀔 때마다 실행됨
LaunchedEffect(refreshTrigger) { LaunchedEffect(refreshTrigger) {
// 1. 서버에서 실제 미체결 내역 가져오기
val serverUnfilled = tradeService.fetchUnfilledOrders().getOrNull()?.map { it.toAutoTradeItem(isDomestic) } ?: emptyList() val serverUnfilled = tradeService.fetchUnfilledOrders().getOrNull()?.map { it.toAutoTradeItem(isDomestic) } ?: emptyList()
// 2. DB에서 로컬 감시 데이터 가져오기 // 2. DB에서 로컬 감시 데이터 가져오기
val localTrades = DatabaseFactory.getActiveAutoTrades() val localTrades = DatabaseFactory.getActiveAutoTrades()
// 3. 동기화 로직: 서버에 없는 주문번호를 가진 로컬 데이터는 EXPIRED로 표시 // 3. 리스트 병합 및 동기화
tradeList = localTrades.map { local -> val mergedList = mutableListOf<AutoTradeItem>()
if (local.status != "COMPLETED" && serverUnfilled.none { it.orderNo == local.orderNo }) {
local.copy(status = "EXPIRED") // (A) DB에 있는 항목 처리
} else local localTrades.forEach { local ->
val serverMatch = serverUnfilled.find { it.orderNo == local.orderNo }
if (local.status != "COMPLETED" && serverMatch == null) {
// 서버에 없으면 만료 처리
mergedList.add(local.copy(status = "EXPIRED"))
} else {
// 서버에 있으면 그대로 표시 (필요시 잔량 등 업데이트)
mergedList.add(local.copy(remainedQuantity = serverMatch?.remainedQuantity ?: 0))
}
} }
// (B) 서버에는 있지만 DB에는 없는 항목(수동 주문 등) 추가
val manualOrders = serverUnfilled.filter { server -> localTrades.none { it.orderNo == server.orderNo } }
mergedList.addAll(manualOrders.map { it.copy(status = "MANUAL_ORDER") }) // 수동 주문 상태 등으로 표시
tradeList = mergedList
} }
Column(modifier = Modifier.fillMaxSize().padding(8.dp)) { Column(modifier = Modifier.fillMaxSize().padding(8.dp)) {

View File

@ -61,9 +61,25 @@ fun DashboardScreen() {
if (dbItem != null) { if (dbItem != null) {
when (dbItem.status) { when (dbItem.status) {
TradeStatus.PENDING_BUY -> { TradeStatus.PENDING_BUY -> {
// 매수 주문 체결 -> 감시(MONITORING)로 전환 및 익절 주문 발주 // 1. 매수 주문 체결 확인됨 -> 즉시 익절 매도 주문 발주
// 여기서 익절 주문 후 받은 신규 주문번호를 DB에 갱신 println("✅ 매수 체결 확인 [${dbItem.name}]: 익절가 ${dbItem.targetPrice}로 매도 주문을 생성합니다.")
// updateStatusAndOrderNo(dbItem.id!!, TradeStatus.MONITORING, newSellOrderNo) tradeService.postOrder(
stockCode = dbItem.code,
qty = dbItem.quantity.toString(),
price = dbItem.targetPrice.toLong().toString(), // 가격은 정수형 문자열로 전달
isBuy = false
).onSuccess { newSellOrderNo ->
// 2. 매도 주문 성공 시 DB 상태를 SELLING으로 변경하고 새로운 주문번호로 갱신
DatabaseFactory.updateStatusAndOrderNo(
id = dbItem.id!!,
newStatus = TradeStatus.SELLING,
newOrderNo = newSellOrderNo
)
println("🚀 익절 매도 주문 완료: 주문번호 $newSellOrderNo")
refreshTrigger++ // UI 갱신
}.onFailure {
println("❌ 매수 체결 후 익절 주문 발주 실패: ${it.message}")
}
} }
TradeStatus.SELLING -> { TradeStatus.SELLING -> {
// 매도(손절/익절) 주문 체결 -> COMPLETED // 매도(손절/익절) 주문 체결 -> COMPLETED