This commit is contained in:
lunaticbum 2026-04-03 17:02:42 +09:00
parent 0b7684a176
commit 794b3fb5cb
3 changed files with 118 additions and 45 deletions

View File

@ -321,7 +321,8 @@ object KisTradeService {
stockCode: String,
qty: String,
price: String,
isBuy: Boolean
isBuy: Boolean,
orderDivision: String = ""
): Result<String> {
val config = KisSession.config
val isDomestic = stockCode.length == 6 && stockCode.all { it.isDigit() }
@ -339,6 +340,12 @@ object KisTradeService {
isDomestic && !config.isSimulation -> if (isBuy) "TTTC0802U" else "TTTC0801U"
else -> if (isBuy) "TTTS3002U" else "TTTS3001U"
}
val finalOrderDivision = when {
orderDivision.isNotEmpty() -> orderDivision // 외부에서 넘겨준 코드 우선 (02, 03, 34 등)
price == "0" || price.isEmpty() -> "01" // 시장가
else -> "00" // 지정가
}
return try {
val response = client.post("$baseUrl/uapi/${if(isDomestic) "domestic" else "overseas"}-stock/v1/trading/order-cash") {
@ -353,7 +360,7 @@ object KisTradeService {
"CANO" to cano,
"ACNT_PRDT_CD" to acntPrdtCd,
"PDNO" to stockCode,
"ORD_DVSN" to if (price == "0" || price.isEmpty()) "01" else "00",
"ORD_DVSN" to finalOrderDivision,
"ORD_QTY" to qty,
"ORD_UNPR" to if (price.isEmpty() || price == "0") "0" else price
))

View File

@ -365,10 +365,8 @@ object AutoTradingManager {
runDiscoveryLoop(globalCallback)
}
suspend fun resumePendingSellOrders(tradeService: KisTradeService,balance : UnifiedBalance) {
// 1. DB에서 매도 중(SELLING)이거나 만료(EXPIRED)된 매도 건을 가져옵니다.
println("resumePendingSellOrders")
suspend fun sellingAfterMarketOnePrice(tradeService: KisTradeService,balance : UnifiedBalance) {
println("sellingAfterMarketOnePrice")
balance.holdings.forEach { holding ->
if (BLACKLISTEDSTOCKCODES.contains(holding.code)){
println("❌ 차단 처리된 주식 : ${holding.name}")
@ -378,46 +376,100 @@ object AutoTradingManager {
"거랙 차단 대상 : ${holding.currentPrice}[${holding.quantity}주] 보유, 수익률(${holding.profitRate.toDouble()})"
)
} else {
if (holding != null && holding.quantity.toInt() > 0 && holding.availOrderCount.toInt() > 0 && holding.profitRate.toDouble() > KisSession.config.SELL_PROFIT) {
println("${holding.name} - 매수 : ${holding.avgPrice} - 현재 : ${holding.currentPrice} , 주문 가능 : ${holding.availOrderCount}, 수익율 : ${holding.profitRate}")
if (holding != null && holding.quantity.toInt() > 0 && holding.availOrderCount.toInt() > 0 && holding.profitRate.toDouble() > 0.5) {
println("${holding.name} - 매수 : ${holding.avgPrice} - 현재 : ${holding.currentPrice} ")
// 3. 기존 목표가(targetPrice)로 다시 매도 주문 전송
var targetPrice = holding.currentPrice.toDouble()
targetPrice = MarketUtil.roundToTickSize(targetPrice + MarketUtil.getTickSize(targetPrice))
println("🔄 [재주문] ${holding.name} (${holding.code}) 매도 목표 ${targetPrice} 미체결 매도 건 재주문 시도")
// TradingLogStore.addSellLog(holding.code,targetPrice.toString(),"SELL","🎊 보유 주식 매도 주문[예상수익 : ${holding.profitRate}] ")
targetPrice = MarketUtil.roundToTickSize(targetPrice)
tradeService.postOrder(
stockCode = holding.code,
qty = holding.availOrderCount,
price = targetPrice.toInt().toString(),
isBuy = false
isBuy = false,
"34"
).onSuccess { newOrderNo ->
// 4. 새로운 주문번호로 DB 업데이트 및 상태를 SELLING으로 유지
// DatabaseFactory.updateStatusAndOrderNo(item.id!!, TradeStatus.SELLING, newOrderNo)
println("✅ [재주문 완료] ${holding.name}: $newOrderNo")
TradingLogStore.addSellLog(
holding.code,
targetPrice.toString(),
"SELL",
"🎊 보유 주식 매도 주문 완료[예상수익 : ${holding.profitRate}] "
"🎊 시간외 단일가 주식 재고털이 주문 완료"
)
}.onFailure {
TradingLogStore.addSellLog(
holding.code,
targetPrice.toString(),
"SELL",
"🎊 보유 주식 매도 주문 실패[${it.message}] "
"🎊 시간외 단일가 주식 재고털이 주문 실패[${it.message}] "
)
}
} else {
TradingLogStore.addAnalyzer(
"보유주식[${holding.name}]",
holding.code,
"수익률 미달 : ${holding.currentPrice}[${holding.quantity}주] 보유, 수익률(${holding.profitRate.toDouble()})"
)
}
delay(200) // API 호출 부하 방지
delay(300) // API 호출 부하 방지
}
}
}
suspend fun resumePendingSellOrders(tradeService: KisTradeService,balance : UnifiedBalance) {
if (isRunning()) return
val now = LocalTime.now()
val currentMinute = now.minute
if (now.isBefore(H16) && now.isAfter(H08M35)) {
println("resumePendingSellOrders")
balance.holdings.forEach { holding ->
if (BLACKLISTEDSTOCKCODES.contains(holding.code)){
println("❌ 차단 처리된 주식 : ${holding.name}")
TradingLogStore.addAnalyzer(
holding.name,
holding.code,
"거랙 차단 대상 : ${holding.currentPrice}[${holding.quantity}주] 보유, 수익률(${holding.profitRate.toDouble()})"
)
} else {
if (holding != null && holding.quantity.toInt() > 0 && holding.availOrderCount.toInt() > 0 && holding.profitRate.toDouble() > KisSession.config.SELL_PROFIT) {
println("${holding.name} - 매수 : ${holding.avgPrice} - 현재 : ${holding.currentPrice} ")
// 3. 기존 목표가(targetPrice)로 다시 매도 주문 전송
var targetPrice = holding.currentPrice.toDouble()
val now = LocalTime.now()
val currentMinute = now.minute
var isBefore930 = false
if (now.hour == 9 && currentMinute < 30) {
targetPrice = targetPrice
isBefore930 = true
} else {
targetPrice = MarketUtil.roundToTickSize(targetPrice + MarketUtil.getTickSize(targetPrice))
}
println("🔄 [재주문] ${holding.name} (${holding.code}) 매도 목표 ${targetPrice} 미체결 매도 건 재주문 시도")
tradeService.postOrder(
stockCode = holding.code,
qty = holding.availOrderCount,
price = targetPrice.toInt().toString(),
isBuy = false,
).onSuccess { newOrderNo ->
println("✅ [재주문 완료] ${holding.name}: $newOrderNo")
TradingLogStore.addSellLog(
holding.code,
targetPrice.toString(),
"SELL",
"🎊 보유 주식[예상수익 : ${holding.profitRate}] ${if (isBefore930) "09:30 이전 현시세{${holding.currentPrice}}로 매도[$targetPrice] 주문" else "09:30 이후 시세{${holding.currentPrice}} 기준 호가 위 매도[$targetPrice] 주문"} 완료"
)
}.onFailure {
TradingLogStore.addSellLog(
holding.code,
targetPrice.toString(),
"SELL",
"🎊 보유 주식 매도 주문 실패[${it.message}] "
)
}
} else {
TradingLogStore.addAnalyzer(
"보유주식[${holding.name}]",
holding.code,
"수익률 미달 : ${holding.currentPrice}[${holding.quantity}주] 보유, 수익률(${holding.profitRate.toDouble()})"
)
}
delay(200) // API 호출 부하 방지
}
}
}
}
@ -464,6 +516,7 @@ object AutoTradingManager {
var now = LocalTime.now(ZoneId.of("Asia/Seoul"))
var currentTimeMillis = System.currentTimeMillis()
var waitTime = 0.2
val H16 = LocalTime.of(16, 0)
val H18 = LocalTime.of(18, 0)
val H08M35 = LocalTime.of(8, 35)
val H08M30 = LocalTime.of(8, 30)
@ -558,9 +611,13 @@ object AutoTradingManager {
return batch
}
suspend fun checkBalance() : UnifiedBalance? {
suspend fun checkBalance(isMorning: Boolean = true) : UnifiedBalance? {
val balance = KisTradeService.fetchIntegratedBalance().getOrNull()
if (AUTOSELL) balance?.let { resumePendingSellOrders(KisTradeService, it) }
if (isMorning) {
if (AUTOSELL) balance?.let { resumePendingSellOrders(KisTradeService, it) }
} else {
balance?.let { sellingAfterMarketOnePrice(KisTradeService, it) }
}
return balance
}
@ -612,18 +669,20 @@ object AutoTradingManager {
while (iterator.hasNext()) {
totalCount--
val stock = iterator.next()
if (BLACKLISTEDSTOCKCODES.contains(stock.code)){
println("❌ 차단 처리된 주식 : ${stock.name}")
} else {
try {
processSingleStock(stock, myCash, KisTradeService, globalCallback)
} catch (e: Exception) {
println("❌ 처리 중 오류 발생 (건너뜀): ${stock.name}")
} finally {
iterator.remove()
if (now.isBefore(H16) && now.isAfter(H08M35)) {
if (BLACKLISTEDSTOCKCODES.contains(stock.code)) {
println("❌ 차단 처리된 주식 : ${stock.name}")
} else {
try {
processSingleStock(stock, myCash, KisTradeService, globalCallback)
} catch (e: Exception) {
println("❌ 처리 중 오류 발생 (건너뜀): ${stock.name}")
} finally {
iterator.remove()
}
println("남은 후보군 개수 : ${totalCount}")
delay(100)
}
println("남은 후보군 개수 : ${totalCount}")
delay(100)
}
sellSchedule()
}
@ -643,12 +702,19 @@ object AutoTradingManager {
lastForceCheckMinute = currentMinute // 실행 완료 기록
}
}
// else if(now.hour % 2 == 1 && (currentMinute == 43)) {
// TradingLogStore.addAnalyzer(" - ", " - ", "⏰ [강제 스케줄 실행] 오전 9시 ${currentMinute}분 - 보유주식 매도 체크를 시작합니다.", true)
// println("⏰ [강제 스케줄 실행] 오전 ${now.hour}시 ${currentMinute}분 - 보유주식 매도 체크를 시작합니다.")
// checkBalance()
// lastForceCheckMinute = currentMinute // 실행 완료 기록
// }
else if((now.hour == 16 || now.hour == 17) && (currentMinute == 5 || currentMinute == 55)) {
if (lastForceCheckMinute != currentMinute) {
TradingLogStore.addAnalyzer(
" - ",
" - ",
"⏰ [강제 스케줄 실행] 오후 ${now.hour}${currentMinute}분 - 보유주식 시간외 단일가 매도 체크를 시작합니다.",
true
)
println("⏰ [강제 스케줄 실행] 오후 ${now.hour}${currentMinute}분 - 보유주식 시간외 단일가 매도 체크를 시작합니다.")
checkBalance(false)
lastForceCheckMinute = currentMinute // 실행 완료 기록
}
}
}
suspend fun finalizeMarketClose(now: LocalTime) {

View File

@ -63,7 +63,7 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
KisSession.config = config
DatabaseFactory.saveConfig(config)
DartCodeManager.updateCorpCodes(HttpClient(CIO) {
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
})