Compare commits
10
Commits
main
...
488e4e72b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
488e4e72b3 | ||
|
|
e0fbe9a9a2 | ||
|
|
74abc6314d | ||
|
|
92d0a84629 | ||
|
|
27356f0fc2 | ||
|
|
b1d334a6cd | ||
|
|
93907c2dac | ||
|
|
acd1b13760 | ||
|
|
81ce68e07b | ||
|
|
95f43e105d |
@@ -103,6 +103,32 @@ object LocalReportGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun generateAndOpenAsyncDirectly(
|
||||||
|
summary: RawSummaryData,
|
||||||
|
rawHoldings: List<RawHoldingData>,
|
||||||
|
rawTrades: List<RawTradeData>
|
||||||
|
) {
|
||||||
|
reportScope.launch {
|
||||||
|
try {
|
||||||
|
// 1. [핵심] 대시보드 통계 지표 추출 (Generator가 직접 계산)
|
||||||
|
val stats = calculateDashboardStats(rawHoldings, rawTrades)
|
||||||
|
|
||||||
|
// 2. 탭 2 & 3 HTML 가공
|
||||||
|
val holdingsHtml = processHoldings(rawHoldings)
|
||||||
|
val tradesHtml = processTrades(rawTrades)
|
||||||
|
|
||||||
|
// 3. 전체 HTML 조립
|
||||||
|
val htmlContent = buildHtml(summary, stats, holdingsHtml, tradesHtml)
|
||||||
|
if (summary.type.equals("END", true) || summary.type.equals("MIDDLE", true)) {
|
||||||
|
saveAndOpen(summary.type, htmlContent)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("❌ [Report] 리포트 비동기 생성 중 오류 발생: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- [새로운 통계 계산 로직] ---
|
// --- [새로운 통계 계산 로직] ---
|
||||||
private fun calculateDashboardStats(holdings: List<RawHoldingData>, trades: List<RawTradeData>): DashboardStats {
|
private fun calculateDashboardStats(holdings: List<RawHoldingData>, trades: List<RawTradeData>): DashboardStats {
|
||||||
val tradesByStock = trades.groupBy { it.stockCode }
|
val tradesByStock = trades.groupBy { it.stockCode }
|
||||||
|
|||||||
@@ -59,10 +59,11 @@ object TradingReportManager : TradingReportService {
|
|||||||
private val activePositions = mutableMapOf<String, String>()
|
private val activePositions = mutableMapOf<String, String>()
|
||||||
|
|
||||||
override fun recordAssetSnapshot(type: SnapshotType, balance: UnifiedBalance, remark: String?) {
|
override fun recordAssetSnapshot(type: SnapshotType, balance: UnifiedBalance, remark: String?) {
|
||||||
if (!KisSession.tradeConfig.useAutoRepost) {
|
// if (!KisSession.tradeConfig.useAutoRepost) {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
println("❌ [Report] 리포트 비동기 생성 중 오류 발생: gggg")
|
||||||
val todayDate = LocalDate.now().toString()
|
val todayDate = LocalDate.now().toString()
|
||||||
|
|
||||||
// 1. 중복 없는 전체 종목 코드 리스트 추출
|
// 1. 중복 없는 전체 종목 코드 리스트 추출
|
||||||
@@ -232,7 +233,7 @@ object TradingReportManager : TradingReportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6. 코루틴 기반 제너레이터 호출
|
// 6. 코루틴 기반 제너레이터 호출
|
||||||
LocalReportGenerator.generateAndOpenAsync(summaryData, holdingLogs, tradeLogs)
|
LocalReportGenerator.generateAndOpenAsyncDirectly(summaryData, holdingLogs, tradeLogs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -453,11 +453,32 @@ object AutoTradingManager {
|
|||||||
&& holding.valuationProfitAmount.toDouble() >= KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)) {
|
&& holding.valuationProfitAmount.toDouble() >= KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)) {
|
||||||
println("${holding.name} ${holding.profitRate.toDouble()} ${holding.valuationProfitAmount.toDouble()} ${KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)} , ${KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)} , ${KisSession.config.getValues(ConfigIndex.STOP_LOSS)}")
|
println("${holding.name} ${holding.profitRate.toDouble()} ${holding.valuationProfitAmount.toDouble()} ${KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)} , ${KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)} , ${KisSession.config.getValues(ConfigIndex.STOP_LOSS)}")
|
||||||
val profit = holding.profitRate.toDouble()
|
val profit = holding.profitRate.toDouble()
|
||||||
TradingLogStore.addNotice(
|
// TradingLogStore.addNotice(
|
||||||
"보유주식[${holding.name}]",
|
// "보유주식[${holding.name}]",
|
||||||
|
// holding.code,
|
||||||
|
// "수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함."
|
||||||
|
// )
|
||||||
|
|
||||||
|
var targetPrice = holding.avgPrice.toDouble()
|
||||||
|
|
||||||
|
tradeService.postOrder(
|
||||||
|
stockCode = holding.code,
|
||||||
|
qty = holding.availOrderCount,
|
||||||
|
price = targetPrice.toInt().toString(),
|
||||||
|
isBuy = false,
|
||||||
|
orderDivision = if (marketCode.equals("Y")) "07" else "",
|
||||||
|
marketCode = if (marketCode.equals("Y")) "KRX" else "NXT"
|
||||||
|
).onSuccess { newOrderNo ->
|
||||||
|
println("✅ [${if(marketCode.equals("Y"))"시간외 단일가" else "대체거래소"} 손절가이드에 따라 매매 주문 완료] ${holding.name}: $newOrderNo")
|
||||||
|
TradingLogStore.addSellLog(
|
||||||
holding.code,
|
holding.code,
|
||||||
"수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함."
|
targetPrice.toString(),
|
||||||
|
"SELL",
|
||||||
|
"☠️ 보유 주식 손절 처리 [수익률 : ${profit}%] ${holding.valuationProfitAmount} 손해 중이며 ${if(marketCode.equals("Y"))"시간외 단일가" else "대체거래소"}에 손절가이드에 따라 매매 주문 완료."
|
||||||
)
|
)
|
||||||
|
}.onFailure { err->
|
||||||
|
println("✅ [${if(marketCode.equals("Y"))"시간외 단일가" else "대체거래소"} 손절가이드에 따라 매매 주문 실패] ${holding.name}: $err")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
analyzeDeepLossHoldingsAfterMarket(holding)
|
analyzeDeepLossHoldingsAfterMarket(holding)
|
||||||
}
|
}
|
||||||
@@ -493,7 +514,9 @@ object AutoTradingManager {
|
|||||||
targetPrice = targetPrice
|
targetPrice = targetPrice
|
||||||
isBefore930 = true
|
isBefore930 = true
|
||||||
} else {
|
} else {
|
||||||
targetPrice = MarketUtil.roundToTickSize(targetPrice + MarketUtil.getTickSize(targetPrice))
|
targetPrice = MarketUtil.roundToTickSize(
|
||||||
|
targetPrice + MarketUtil.getTickSize(targetPrice)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
println("🔄 [보유 주식 주문] ${holding.name} (${holding.code}) 매도 목표 ${targetPrice} 미체결 매도 건 재주문 시도")
|
println("🔄 [보유 주식 주문] ${holding.name} (${holding.code}) 매도 목표 ${targetPrice} 미체결 매도 건 재주문 시도")
|
||||||
tradeService.postOrder(
|
tradeService.postOrder(
|
||||||
@@ -509,7 +532,8 @@ object AutoTradingManager {
|
|||||||
"SELL",
|
"SELL",
|
||||||
"🎊 보유 주식[예상수익 : ${holding.profitRate}] ${if (isBefore930) "09:30 이전 현시세{${holding.currentPrice}}로 매도[$targetPrice] 주문" else "09:30 이후 시세{${holding.currentPrice}} 기준 호가 위 매도[$targetPrice] 주문"} 완료"
|
"🎊 보유 주식[예상수익 : ${holding.profitRate}] ${if (isBefore930) "09:30 이전 현시세{${holding.currentPrice}}로 매도[$targetPrice] 주문" else "09:30 이후 시세{${holding.currentPrice}} 기준 호가 위 매도[$targetPrice] 주문"} 완료"
|
||||||
)
|
)
|
||||||
DatabaseFactory.saveAutoTrade(AutoTradeItem(
|
DatabaseFactory.saveAutoTrade(
|
||||||
|
AutoTradeItem(
|
||||||
orderNo = newOrderNo,
|
orderNo = newOrderNo,
|
||||||
code = holding.code,
|
code = holding.code,
|
||||||
name = holding.name,
|
name = holding.name,
|
||||||
@@ -520,7 +544,8 @@ object AutoTradingManager {
|
|||||||
stopLossPrice = 0.0,
|
stopLossPrice = 0.0,
|
||||||
status = "SELLING",
|
status = "SELLING",
|
||||||
isDomestic = true
|
isDomestic = true
|
||||||
))
|
)
|
||||||
|
)
|
||||||
syncAndExecute(newOrderNo)
|
syncAndExecute(newOrderNo)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
TradingLogStore.addSellLog(
|
TradingLogStore.addSellLog(
|
||||||
@@ -539,10 +564,10 @@ object AutoTradingManager {
|
|||||||
&& holding.valuationProfitAmount.toDouble() >= KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)) {
|
&& holding.valuationProfitAmount.toDouble() >= KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)) {
|
||||||
println("${holding.name} ${holding.profitRate.toDouble()} ${holding.valuationProfitAmount.toDouble()} ${KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)} , ${KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)} , ${KisSession.config.getValues(ConfigIndex.STOP_LOSS)}")
|
println("${holding.name} ${holding.profitRate.toDouble()} ${holding.valuationProfitAmount.toDouble()} ${KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)} , ${KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)} , ${KisSession.config.getValues(ConfigIndex.STOP_LOSS)}")
|
||||||
val profit = holding.profitRate.toDouble()
|
val profit = holding.profitRate.toDouble()
|
||||||
|
|
||||||
var targetPrice = if (KisSession.tradeConfig.autoSellOrder ) holding.avgPrice.toDouble() else holding.currentPrice.toDouble()
|
var targetPrice = if (KisSession.tradeConfig.autoSellOrder ) holding.avgPrice.toDouble() else holding.currentPrice.toDouble()
|
||||||
targetPrice = MarketUtil.roundToTickSize(targetPrice + MarketUtil.getTickSize(targetPrice) * 3.0)
|
targetPrice = MarketUtil.roundToTickSize(targetPrice + MarketUtil.getTickSize(targetPrice) * 3.0)
|
||||||
|
|
||||||
|
|
||||||
tradeService.postOrder(
|
tradeService.postOrder(
|
||||||
stockCode = holding.code,
|
stockCode = holding.code,
|
||||||
qty = holding.availOrderCount,
|
qty = holding.availOrderCount,
|
||||||
@@ -550,15 +575,21 @@ object AutoTradingManager {
|
|||||||
isBuy = false,
|
isBuy = false,
|
||||||
).onSuccess { newOrderNo ->
|
).onSuccess { newOrderNo ->
|
||||||
println("✅ [보유 주식 손절 처리] 수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함 시장가 매도.")
|
println("✅ [보유 주식 손절 처리] 수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함 시장가 매도.")
|
||||||
|
TradingLogStore.addSellLog(
|
||||||
|
holding.code,
|
||||||
|
targetPrice.toString(),
|
||||||
|
"SELL",
|
||||||
|
"☠️ 보유 주식 손절 처리 [수익률 : ${profit}%] ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함 시장가 매도."
|
||||||
|
)
|
||||||
}.onFailure { err->
|
}.onFailure { err->
|
||||||
println("✅ [보유 주식 손절 처리] 실패 ${err.message}")
|
println("✅ [보유 주식 손절 처리] 실패 ${err.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
TradingLogStore.addNotice(
|
// TradingLogStore.addNotice(
|
||||||
"보유주식[${holding.name}]",
|
// "보유주식[${holding.name}]",
|
||||||
holding.code,
|
// holding.code,
|
||||||
"수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함 시장가 매도."
|
// "수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함 시장가 매도."
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
analyzeDeepLossHoldingsAfterMarket(holding , true)
|
analyzeDeepLossHoldingsAfterMarket(holding , true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,13 @@ import model.KisSession
|
|||||||
import network.KisTradeService
|
import network.KisTradeService
|
||||||
import network.NewsService
|
import network.NewsService
|
||||||
import network.StockUniverseLoader
|
import network.StockUniverseLoader
|
||||||
|
import report.SnapshotType
|
||||||
|
import report.TradingReportManager
|
||||||
import service.AutoTradingManager
|
import service.AutoTradingManager
|
||||||
|
import service.AutoTradingManager.currentBalance
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -104,15 +108,35 @@ fun TradingDecisionLog() {
|
|||||||
|
|
||||||
Row(modifier = Modifier.fillMaxSize().background(Color(0xFFF2F2F2))) {
|
Row(modifier = Modifier.fillMaxSize().background(Color(0xFFF2F2F2))) {
|
||||||
Column(modifier = Modifier.weight(1f).padding(8.dp).fillMaxHeight().background(Color.White)) {
|
Column(modifier = Modifier.weight(1f).padding(8.dp).fillMaxHeight().background(Color.White)) {
|
||||||
|
Row(modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
// index 0으로 부드럽게 스크롤 (즉시 이동은 scrollToItem(0))
|
// index 0으로 부드럽게 스크롤 (즉시 이동은 scrollToItem(0))
|
||||||
listState.animateScrollToItem(filteredLogs.size - 1)
|
listState.animateScrollToItem(if (filteredLogs.size - 1 >= 0) filteredLogs.size - 1 else 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { Text("AI 자동매매 실시간 로그", style = MaterialTheme.typography.h6) }
|
) { Text("AI 자동매매 실시간 로그", style = MaterialTheme.typography.h6) }
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
currentBalance = KisTradeService.fetchIntegratedBalance().getOrNull()
|
||||||
|
currentBalance?.let { currentBalance ->
|
||||||
|
if (LocalTime.now().isBefore(LocalTime.of(18,1))) {
|
||||||
|
TradingReportManager.recordAssetSnapshot(
|
||||||
|
if (LocalTime.now().isAfter(LocalTime.of(18, 0))
|
||||||
|
) SnapshotType.END else SnapshotType.MIDDLE, currentBalance, ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { Text("Open the report", style = MaterialTheme.typography.body2) }
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
@@ -223,9 +247,9 @@ fun TradingDecisionLog() {
|
|||||||
Text(
|
Text(
|
||||||
text = log.decision,
|
text = log.decision,
|
||||||
color = when (log.decision) {
|
color = when (log.decision) {
|
||||||
"BUY" -> Color.Red
|
"BUY" -> Color(0xFF800080)
|
||||||
"SETTING" -> Color(0xFFFFA500)
|
"SETTING" -> Color(0xFFFFA500)
|
||||||
"SELL" -> Color(0xFF800080)
|
"SELL" -> if (log.reason.contains("손절 처리")) Color.Blue else Color.Red
|
||||||
"HOLD" -> Color.DarkGray
|
"HOLD" -> Color.DarkGray
|
||||||
"ANALYZER" -> Color.Green
|
"ANALYZER" -> Color.Green
|
||||||
"PASS" -> Color.Yellow
|
"PASS" -> Color.Yellow
|
||||||
|
|||||||
Reference in New Issue
Block a user