...
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import AutoTradeTable.orderNo
|
||||
import kotlinx.serialization.Serializable
|
||||
import model.AppConfig
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
@@ -6,6 +8,14 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.io.File
|
||||
import java.time.LocalDateTime
|
||||
|
||||
object TradeStatus {
|
||||
const val PENDING_BUY = "PENDING_BUY" // 매수 주문 중
|
||||
const val MONITORING = "MONITORING" // 매수 체결 후 감시 중
|
||||
const val SELLING = "SELLING" // 손절/익절 매도 주문 중
|
||||
const val EXPIRED = "EXPIRED" // 서버와 불일치 (유저 판단 대기)
|
||||
const val COMPLETED = "COMPLETED" // 거래 종료
|
||||
}
|
||||
|
||||
// 1. 앱 설정 테이블
|
||||
object ConfigTable : Table("app_config") {
|
||||
val id = integer("id").autoIncrement()
|
||||
@@ -26,13 +36,18 @@ object AutoTradeTable : Table("auto_trades") {
|
||||
val id = integer("id").autoIncrement()
|
||||
val stockCode = varchar("stock_code", 20)
|
||||
val stockName = varchar("stock_name", 100)
|
||||
val targetPrice = double("target_price") // 익절 목표가
|
||||
val stopLossPrice = double("stop_loss_price") // 손절 목표가
|
||||
val status = varchar("status", 20).default("MONITORING") // MONITORING, COMPLETED
|
||||
val quantity = integer("quantity").default(0)
|
||||
val profitRate = double("profit_rate").default(0.0)
|
||||
val stopLossRate = double("stop_loss_rate").default(0.0)
|
||||
val targetPrice = double("target_price").default(0.0)
|
||||
val stopLossPrice = double("stop_loss_price").default(0.0)
|
||||
val orderNo = varchar("order_no", 50).uniqueIndex()
|
||||
val status = varchar("status", 20).default("PENDING_BUY")
|
||||
val isDomestic = bool("is_domestic").default(true)
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
||||
|
||||
// 3. 거래 내역 테이블
|
||||
object TradeLogTable : Table("trade_logs") {
|
||||
val id = long("id").autoIncrement()
|
||||
@@ -62,59 +77,70 @@ object DatabaseFactory {
|
||||
|
||||
// --- 자동매매(감시) 관련 함수 ---
|
||||
|
||||
|
||||
/**
|
||||
* [추가] 종목코드로 현재 감시 중인 설정 가져오기 (웹소켓 감시용)
|
||||
* 새로운 자동매매 건 등록 (주로 PENDING_BUY 상태로 시작)
|
||||
*/
|
||||
fun saveAutoTrade(item: AutoTradeItem) = transaction {
|
||||
AutoTradeTable.insert {
|
||||
it[stockCode] = item.code
|
||||
it[stockName] = item.name
|
||||
it[quantity] = item.quantity
|
||||
it[profitRate] = item.profitRate
|
||||
it[stopLossRate] = item.stopLossRate
|
||||
it[targetPrice] = item.targetPrice
|
||||
it[stopLossPrice] = item.stopLossPrice
|
||||
it[orderNo] = item.orderNo
|
||||
it[status] = item.status
|
||||
it[isDomestic] = item.isDomestic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 변경 및 가격 업데이트 (예: PENDING_BUY -> MONITORING)
|
||||
*/
|
||||
fun updateAutoTrade(item: AutoTradeItem) = transaction {
|
||||
val id = item.id ?: return@transaction
|
||||
AutoTradeTable.update({ AutoTradeTable.id eq id }) {
|
||||
it[targetPrice] = item.targetPrice
|
||||
it[stopLossPrice] = item.stopLossPrice
|
||||
it[orderNo] = item.orderNo
|
||||
it[status] = item.status
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 감시 중인 모든 종목 리스트 반환 (ActiveTradeSection UI용)
|
||||
*/
|
||||
fun getActiveAutoTrades(): List<AutoTradeItem> = transaction {
|
||||
AutoTradeTable.select {
|
||||
AutoTradeTable.status inList listOf("MONITORING", "SELLING", "PENDING_BUY")
|
||||
}.map { mapToAutoTradeItem(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 종목코드로 현재 감시 중인 설정이 있는지 확인 (UI 체크박스 상태용)
|
||||
*/
|
||||
fun findConfigByCode(code: String): AutoTradeItem? = transaction {
|
||||
AutoTradeTable.select {
|
||||
(AutoTradeTable.stockCode eq code) and (AutoTradeTable.status eq "MONITORING")
|
||||
}.lastOrNull()?.let {
|
||||
mapToAutoTradeItem(it)
|
||||
}
|
||||
}.lastOrNull()?.let { mapToAutoTradeItem(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* [추가] 매수 체결 시 새로운 자동매매 감시 대상 등록
|
||||
*/
|
||||
fun saveAutoTrade(item: AutoTradeItem) {
|
||||
transaction {
|
||||
// 동일 종목이 이미 감시 중이면 삭제 후 재등록 (중복 방지)
|
||||
AutoTradeTable.deleteWhere { stockCode eq item.code }
|
||||
|
||||
AutoTradeTable.insert {
|
||||
it[stockCode] = item.code
|
||||
it[stockName] = item.name
|
||||
it[targetPrice] = item.targetPrice
|
||||
it[stopLossPrice] = item.stopLossPrice
|
||||
it[status] = "MONITORING"
|
||||
it[isDomestic] = item.isDomestic
|
||||
}
|
||||
}
|
||||
fun deleteAutoTrade(id: Int) = transaction {
|
||||
AutoTradeTable.deleteWhere { AutoTradeTable.id eq id }
|
||||
}
|
||||
|
||||
/**
|
||||
* [추가] 매도 완료 또는 취소 시 감시 대상 삭제
|
||||
*/
|
||||
fun deleteAutoTrade(code: String) {
|
||||
transaction {
|
||||
AutoTradeTable.deleteWhere { stockCode eq code }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [수정] 감시 중인 모든 종목 리스트 반환 (ActiveTradeSection UI용)
|
||||
*/
|
||||
fun getActiveAutoTrades(): List<AutoTradeItem> = transaction {
|
||||
AutoTradeTable.select { AutoTradeTable.status eq "MONITORING" }
|
||||
.map { mapToAutoTradeItem(it) }
|
||||
}
|
||||
|
||||
// ResultRow를 AutoTradeItem으로 매핑하는 내부 함수
|
||||
private fun mapToAutoTradeItem(it: ResultRow) = AutoTradeItem(
|
||||
id = it[AutoTradeTable.id],
|
||||
code = it[AutoTradeTable.stockCode],
|
||||
name = it[AutoTradeTable.stockName],
|
||||
quantity = it[AutoTradeTable.quantity],
|
||||
profitRate = it[AutoTradeTable.profitRate],
|
||||
stopLossRate = it[AutoTradeTable.stopLossRate],
|
||||
targetPrice = it[AutoTradeTable.targetPrice],
|
||||
stopLossPrice = it[AutoTradeTable.stopLossPrice],
|
||||
orderNo = it[AutoTradeTable.orderNo],
|
||||
status = it[AutoTradeTable.status],
|
||||
isDomestic = it[AutoTradeTable.isDomestic]
|
||||
)
|
||||
@@ -169,16 +195,90 @@ object DatabaseFactory {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveOrUpdate(item: AutoTradeItem) = transaction {
|
||||
val existing = AutoTradeTable.select { AutoTradeTable.orderNo eq item.orderNo }.firstOrNull()
|
||||
if (existing == null) {
|
||||
AutoTradeTable.insert {
|
||||
it[orderNo] = item.orderNo
|
||||
it[stockCode] = item.code
|
||||
it[stockName] = item.name
|
||||
it[status] = item.status
|
||||
it[targetPrice] = item.targetPrice
|
||||
it[stopLossPrice] = item.stopLossPrice
|
||||
it[quantity] = item.quantity
|
||||
it[isDomestic] = item.isDomestic
|
||||
}
|
||||
} else {
|
||||
AutoTradeTable.update({ AutoTradeTable.orderNo eq item.orderNo }) {
|
||||
it[status] = item.status
|
||||
it[targetPrice] = item.targetPrice
|
||||
it[stopLossPrice] = item.stopLossPrice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 주문번호로 항목 조회 (가장 핵심적인 식별자)
|
||||
*/
|
||||
fun findByOrderNo(orderNo: String): AutoTradeItem? = transaction {
|
||||
AutoTradeTable.select { AutoTradeTable.orderNo eq orderNo }
|
||||
.map { mapToAutoTradeItem(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* 서버 동기화: DB에는 PENDING_BUY/MONITORING인데 서버 미체결 내역에 없는 경우 EXPIRED로 변경
|
||||
*/
|
||||
fun syncWithServer(serverOrderNos: List<String>) = transaction {
|
||||
AutoTradeTable.update({
|
||||
(AutoTradeTable.status inList listOf(TradeStatus.PENDING_BUY, TradeStatus.MONITORING)) and
|
||||
(AutoTradeTable.orderNo notInList serverOrderNos)
|
||||
}) {
|
||||
it[status] = TradeStatus.EXPIRED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 업데이트 및 주문번호 갱신 (예: 매수체결 시 신규 익절 주문번호로 교체)
|
||||
*/
|
||||
fun updateStatusAndOrderNo(id: Int, newStatus: String, newOrderNo: String? = null) = transaction {
|
||||
AutoTradeTable.update({ AutoTradeTable.id eq id }) {
|
||||
it[status] = newStatus
|
||||
if (newOrderNo != null) it[orderNo] = newOrderNo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 감시 중인 모든 종목 리스트 (Status별 필터링 용이하게 수정)
|
||||
*/
|
||||
fun getAutoTradesByStatus(statusList: List<String>): List<AutoTradeItem> = transaction {
|
||||
AutoTradeTable.select { AutoTradeTable.status inList statusList }
|
||||
.map { mapToAutoTradeItem(it) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* [수정] 감시 가격(익절/손절) 정보를 포함하도록 모델 확장
|
||||
*/
|
||||
@Serializable
|
||||
data class AutoTradeItem(
|
||||
val code: String,
|
||||
val name: String,
|
||||
val targetPrice: Double,
|
||||
val stopLossPrice: Double, // 손절가 추가
|
||||
val status: String,
|
||||
val isDomestic: Boolean
|
||||
val id: Int? = null, // DB 식별자
|
||||
val orderNo: String, // 핵심 키: KIS 주문번호 (odno)
|
||||
val code: String, // 종목 코드
|
||||
val name: String, // 종목 명
|
||||
|
||||
// 상태 머신 (PENDING_BUY, MONITORING, SELLING, EXPIRED, COMPLETED)
|
||||
var status: String = "PENDING_BUY",
|
||||
|
||||
// 가격 정보
|
||||
val orderedPrice: Double = 0.0, // 주문 단가
|
||||
var targetPrice: Double = 0.0, // 익절 목표가
|
||||
var stopLossPrice: Double = 0.0, // 손절 목표가
|
||||
|
||||
// 수량 정보
|
||||
val quantity: Int = 0, // 총 주문 수량
|
||||
var remainedQuantity: Int = 0, // 미체결 잔량 (서버 동기화용)
|
||||
|
||||
val isDomestic: Boolean = true,
|
||||
val profitRate: Double = 0.0, // 설정 시 사용한 목표 비율
|
||||
val stopLossRate: Double = 0.0
|
||||
)
|
||||
Reference in New Issue
Block a user