This commit is contained in:
lunaticbum 2026-03-13 17:34:48 +09:00
parent 57abcbf8f8
commit 7fa1c36355
4 changed files with 66 additions and 12 deletions

View File

@ -37,6 +37,7 @@ import model.AppConfig
import model.KisSession import model.KisSession
import network.DartCodeManager import network.DartCodeManager
import network.KisTradeService import network.KisTradeService
import network.KisWebSocketManager
import service.LlamaServerManager import service.LlamaServerManager
import network.NewsService import network.NewsService
import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.selectAll
@ -147,6 +148,7 @@ fun main() = application {
AutoTradingManager.isSystemCleanedUpToday = false AutoTradingManager.isSystemCleanedUpToday = false
CoroutineScope(Dispatchers.Default).launch { CoroutineScope(Dispatchers.Default).launch {
AutoTradingManager.startAutoDiscoveryLoop() AutoTradingManager.startAutoDiscoveryLoop()
KisWebSocketManager.onExecutionReceived = AutoTradingManager.onExecutionReceived
} }
if (config.modelPath.isNotEmpty()) { if (config.modelPath.isNotEmpty()) {

View File

@ -1,4 +1,3 @@
import AutoTradeTable.orderNo
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import model.AppConfig import model.AppConfig
@ -399,6 +398,21 @@ object TradingLogStore {
} }
} }
fun addSellLog(stockName: String,sellPrice : String , decision: String, log: String) {
synchronized(this) {
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
decisionLogs.add(
LogEntry(
time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")),
stockName = "${stockName}[${sellPrice}][]",
decision = decision,
confidence = 100.0,
reason = log
)
)
}
}
fun addLog(tradingDecision: TradingDecision , decision: String, log: String) { fun addLog(tradingDecision: TradingDecision , decision: String, log: String) {
synchronized(this) { synchronized(this) {
if (decisionLogs.size > 1000) decisionLogs.removeAt(0) if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
@ -414,5 +428,20 @@ object TradingLogStore {
} }
} }
fun addSettingLog(settingDesc : String, old : String, new : String, log: String) {
synchronized(this) {
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
decisionLogs.add(
LogEntry(
time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")),
stockName = "설정변경[${settingDesc}][$old]->[$new]",
decision = "SETTING",
confidence = 100.0,
reason = log
)
)
}
}
} }

View File

@ -2,6 +2,7 @@ package service
import AutoTradeItem import AutoTradeItem
import TradingDecision import TradingDecision
import TradingLogStore
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import getLlamaBinPath import getLlamaBinPath
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -112,7 +113,7 @@ object AutoTradingManager {
1 1
} }
// 5. 매수 실행 (계산된 finalMargin 전달) // 5. 매수 실행 (계산된 finalMargin 전달)
AutoTradingManager.excuteTrade( excuteTrade(
decision = completeTradingDecision, decision = completeTradingDecision,
orderQty = min(calculatedQty, maxQty).toString(), orderQty = min(calculatedQty, maxQty).toString(),
profitRate1 = finalMargin, profitRate1 = finalMargin,
@ -120,7 +121,7 @@ object AutoTradingManager {
) )
} else if(totalScore >= (minScore * 0.85) && completeTradingDecision.confidence + append >= (MIN_CONFIDENCE * 0.85)) { } else if(totalScore >= (minScore * 0.85) && completeTradingDecision.confidence + append >= (MIN_CONFIDENCE * 0.85)) {
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName)) addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
TradingLogStore.addLog(completeTradingDecision,"HOLD","✋ [관망] 토탈 스코어 또는 신뢰도 미달 이나 약간의 오차로 재분석 대기열에 추가") TradingLogStore.addLog(completeTradingDecision,"HOLD","✋ [관망] 토탈 스코어 또는 신뢰도 미달 이나 약간의 오차로 재분석 대기열에 추가")
} else { } else {
TradingLogStore.addLog(completeTradingDecision,"HOLD","✋ [관망] 토탈 스코어(${String.format("%.1f[${minScore}]", totalScore)}) 또는 신뢰도 (${String.format("%.1f[${MIN_CONFIDENCE}]", completeTradingDecision.confidence)}) 미달") TradingLogStore.addLog(completeTradingDecision,"HOLD","✋ [관망] 토탈 스코어(${String.format("%.1f[${minScore}]", totalScore)}) 또는 신뢰도 (${String.format("%.1f[${MIN_CONFIDENCE}]", completeTradingDecision.confidence)}) 미달")
@ -242,7 +243,13 @@ object AutoTradingManager {
} }
} }
} }
var onExecutionReceived : ((String, String, String, String, Boolean) -> Unit)? = {code, qty, price,orderNo, isBuy ->
scope.launch {
val exec = ExecutionData(orderNo, code, price, qty, isBuy)
executionCache[orderNo] = exec
syncAndExecute(orderNo)
}
}
val executionCache = mutableMapOf<String, ExecutionData>() val executionCache = mutableMapOf<String, ExecutionData>()
val processingIds = mutableSetOf<String>() // 주문번호 기준 잠금 val processingIds = mutableSetOf<String>() // 주문번호 기준 잠금
suspend fun syncAndExecute(orderNo: String) { suspend fun syncAndExecute(orderNo: String) {
@ -278,14 +285,15 @@ object AutoTradingManager {
).onSuccess { newSellOrderNo -> ).onSuccess { newSellOrderNo ->
// 익절가 업데이트 및 상태 변경 // 익절가 업데이트 및 상태 변경
DatabaseFactory.updateStatusAndOrderNo(dbItem.id!!, TradeStatus.SELLING, newSellOrderNo) DatabaseFactory.updateStatusAndOrderNo(dbItem.id!!, TradeStatus.SELLING, newSellOrderNo)
// (선택 사항) 실제 계산된 익절가를 DB에 기록하고 싶다면 별도 update 로직 추가 가능 TradingLogStore.addSellLog(dbItem.name,finalTargetPrice.toString(),"SELL","🎯 [매칭 성공] 익절 주문 실행: ${dbItem.name} | 매수가: ${actualBuyPrice.toInt()} -> 목표가: ${finalTargetPrice.toInt()} (${String.format("%.2f", finalProfitRate)}% 적용)")
executionCache.remove(orderNo) executionCache.remove(orderNo)
}.onFailure { }.onFailure {
println("❌ 익절 주문 실패: ${it.message}") println("❌ 익절 주문 실패: ${it.message}")
TradingLogStore.addSellLog(dbItem.name,finalTargetPrice.toString(),"SELL","❌ 익절 주문 실패: ${it.message}")
} }
} else if (dbItem.status == TradeStatus.SELLING) { } else if (dbItem.status == TradeStatus.SELLING) {
println("🎊 [매칭 성공] 매도 완료 처리: ${dbItem.name}") println("🎊 [매칭 성공] 매도 완료 처리: ${dbItem.name}")
TradingLogStore.addSellLog(dbItem.name,execData.price,"SELL","🎊 [매칭 성공] 매도 완료 처리")
DatabaseFactory.updateStatusAndOrderNo(dbItem.id!!, TradeStatus.COMPLETED) DatabaseFactory.updateStatusAndOrderNo(dbItem.id!!, TradeStatus.COMPLETED)
executionCache.remove(orderNo) executionCache.remove(orderNo)
} }

View File

@ -1,8 +1,6 @@
package ui package ui
import AutoTradeItem
import TradingDecision
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@ -14,7 +12,6 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -25,7 +22,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import model.ConfigIndex import model.ConfigIndex
import model.KisSession import model.KisSession
import service.TechnicalAnalyzer
@Composable @Composable
fun TradingDecisionLog() { fun TradingDecisionLog() {
@ -45,7 +41,13 @@ fun TradingDecisionLog() {
Text("${log.time} - ${log.stockName}", fontWeight = FontWeight.Bold) Text("${log.time} - ${log.stockName}", fontWeight = FontWeight.Bold)
Text( Text(
text = log.decision, text = log.decision,
color = if (log.decision == "BUY") Color.Red else Color.Gray, color = when (log.decision) {
"BUY" -> Color.Red
"SETTING" -> Color(0xFFFFA500) // 주황색
"SELL" -> Color(0xFF800080) // 보라색
"HOLD" -> Color.Gray // HOLD는 그레이
else -> Color.Gray // 그 외 기본값 그레이
},
fontWeight = FontWeight.ExtraBold fontWeight = FontWeight.ExtraBold
) )
} }
@ -63,6 +65,7 @@ fun TradingDecisionLog() {
verticalArrangement = Arrangement.spacedBy(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier.fillMaxWidth().fillMaxHeight().background(Color.White) modifier = Modifier.fillMaxWidth().fillMaxHeight().background(Color.White)
) { ) {
var firstSet = mutableSetOf<ConfigIndex>()
item(span = { GridItemSpan(maxLineSpan) }) { // 2열을 모두 차지함 item(span = { GridItemSpan(maxLineSpan) }) { // 2열을 모두 차지함
Text( Text(
"💰 거래 기본 설정", "💰 거래 기본 설정",
@ -92,9 +95,16 @@ fun TradingDecisionLog() {
// 저장 로직을 공통 함수로 분리 // 저장 로직을 공통 함수로 분리
val saveAction = { val saveAction = {
var newValue = localText.toDoubleOrNull() ?: 0.0 var newValue = localText.toDoubleOrNull() ?: 0.0
var oldValue = KisSession.config.getValues(configKey)
if (configKey.label.contains("PROFIT")) { if (configKey.label.contains("PROFIT")) {
newValue = newValue / KisSession.config.getValues(ConfigIndex.PROFIT_INDEX) newValue = newValue / KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
} }
if (firstSet.contains(configKey)) {
TradingLogStore.addSettingLog(configKey.label,oldValue.toString(),newValue.toString(),"💾 저장됨: ${configKey.label} = $newValue")
} else {
firstSet.add(configKey)
}
KisSession.config.setValues(configKey, newValue) KisSession.config.setValues(configKey, newValue)
DatabaseFactory.saveConfig(KisSession.config) DatabaseFactory.saveConfig(KisSession.config)
println("💾 저장됨: ${configKey.label} = $newValue") println("💾 저장됨: ${configKey.label} = $newValue")
@ -173,11 +183,16 @@ fun TradingDecisionLog() {
} }
val saveAction = { val saveAction = {
var oldValue = KisSession.config.getValues(configKey)
var newValue = localText.toDoubleOrNull() ?: 0.0 var newValue = localText.toDoubleOrNull() ?: 0.0
// //
KisSession.config.setValues(configKey, newValue) KisSession.config.setValues(configKey, newValue)
DatabaseFactory.saveConfig(KisSession.config) DatabaseFactory.saveConfig(KisSession.config)
println("💾 저장됨: ${configKey.label} = $newValue") if (firstSet.contains(configKey)) {
TradingLogStore.addSettingLog(configKey.label,oldValue.toString(),newValue.toString(),"💾 저장됨: ${configKey.label} = $newValue")
} else {
firstSet.add(configKey)
}
labelText = if (configKey.name.contains("PROFIT")) { labelText = if (configKey.name.contains("PROFIT")) {
getRemaining(configKey.label,common) + ": 기준율(${KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)}) * 성향별 비율(${KisSession.config.getValues(configKey)}) + 세금제비용(${KisSession.config.getValues( 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)}) = ${(localText.toDouble() * KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)) + KisSession.config.getValues(