This commit is contained in:
lunaticbum 2026-05-06 15:53:55 +09:00
parent 0413fa3e2e
commit d7efc433bd
7 changed files with 107 additions and 4 deletions

View File

@ -328,6 +328,7 @@ fun main() = application {
// DashboardScreen() // DashboardScreen()
} }
AppScreen.TradingDecision -> { AppScreen.TradingDecision -> {
TradingDecisionLog() TradingDecisionLog()
} }
} }

View File

@ -1,10 +1,16 @@
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import model.AppConfig import model.AppConfig
import model.TradingDecision import model.TradingDecision
import network.NewsService
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.datetime import org.jetbrains.exposed.sql.javatime.datetime
import org.jetbrains.exposed.sql.transactions.experimental.suspendedTransactionAsync
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import report.TradingReportManager import report.TradingReportManager
import report.TradingReportService import report.TradingReportService
@ -509,11 +515,21 @@ object TradingLogStore {
decisionLogs.add( decisionLogs.add(
LogEntry( LogEntry(
time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")), time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")),
stockName = "${tradingDecision.stockName}[${tradingDecision.currentPrice}][]", stockName = "${tradingDecision.stockName}[${tradingDecision.currentPrice}]",
decision = decision, decision = decision,
confidence = tradingDecision.confidence, confidence = tradingDecision.confidence,
reason = log reason = log
) ).apply {
CoroutineScope(Dispatchers.Default).launch {
println("CALLED sendTelegramMessage -1")
if (decision.contains("WATCH") || ((tradingDecision.investmentGrade?.ordinal
?: 0) < 2)
) {
println("CALLED sendTelegramMessage OK")
NewsService.sendTelegramMessage("${this@apply.decision} ${tradingDecision.stockName}[${tradingDecision.currentPrice}] ${log}")
}
}
}
) )
} }
} }
@ -545,8 +561,14 @@ object TradingLogStore {
decision = "NOTICE", decision = "NOTICE",
confidence = 100.0, confidence = 100.0,
reason = log reason = log
).apply {
CoroutineScope(Dispatchers.Default).launch {
println("CALLED sendTelegramMessage")
NewsService.sendTelegramMessage("${this@apply.decision}$name[$code] ${log}")
}
}
) )
)
} }
} }

View File

@ -240,6 +240,7 @@ class TradeConfig {
var start_buy_time : String = "08:55" var start_buy_time : String = "08:55"
var end_buy_time : String = "15:10" var end_buy_time : String = "15:10"
var enableOverSea : Boolean = false var enableOverSea : Boolean = false
var tlg_id : String = ""
} }

View File

@ -45,7 +45,7 @@ object KisTradeService {
// [수정] 모든 로그(Headers + Body)를 찍도록 설정 // [수정] 모든 로그(Headers + Body)를 찍도록 설정
install(Logging) { install(Logging) {
logger = Logger.DEFAULT logger = Logger.DEFAULT
level = LogLevel.NONE level = LogLevel.ALL
} }
} }

View File

@ -17,10 +17,14 @@ import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.ContentType.Application.Json import io.ktor.http.ContentType.Application.Json
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters import io.ktor.http.Parameters
import io.ktor.http.Url import io.ktor.http.Url
import io.ktor.http.contentType
import io.ktor.network.tls.TLSConfigBuilder
import io.ktor.serialization.kotlinx.json.json import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -29,10 +33,16 @@ import model.KisSession
import model.NaverNewsResponse import model.NaverNewsResponse
import service.SafeScraper import service.SafeScraper
import service.UrlCacheManager import service.UrlCacheManager
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URL
import java.net.URLEncoder
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.Locale import java.util.Locale
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import kotlin.Double import kotlin.Double
object NewsService { object NewsService {
@ -122,4 +132,62 @@ object NewsService {
return "" return ""
} }
} }
suspend fun sendTelegramMessage(data: String) {
Thread {
try {
var chatId = KisSession.tradeConfig.tlg_id
println("sendTelegramMessage $chatId")
sendViaSystemCurl("https://lunaticbum.kr/tlg/sendToMe.bjx",chatId,data)
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
fun sendViaSystemCurl(url : String, chatId: String, message: String) {
try {
// 메시지 내 공백이나 한글이 깨지지 않도록 인코딩 (필수)
val encodedMessage = URLEncoder.encode(message, "UTF-8")
// OS 확인
val isWindows = System.getProperty("os.name").lowercase().contains("win")
val command = if (isWindows) {
// 윈도우용: 큰따옴표 이스케이프에 주의해야 합니다.
val jsonBody = "{\"id\":\"$chatId\",\"message\":\"$encodedMessage\"}"
listOf("cmd", "/c", "curl -s -X POST $url -H \"Content-Type: application/json\" -d \"$jsonBody\"")
} else {
// 맥/리눅스용: 홑따옴표를 사용하여 JSON 구조를 보호합니다.
val jsonBody = "{\"id\":\"$chatId\",\"message\":\"$message\"}"
listOf("curl", "-s", "-X", "POST", url, "-H", "Content-Type: application/json", "-d", jsonBody)
}
val process = ProcessBuilder(command)
.redirectErrorStream(true) // 에러 출력(stderr)을 표준 출력(stdout)으로 합침
.start()
// 프로세스의 출력을 읽어오는 블록
BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
val output = StringBuilder()
var line: String?
while (reader.readLine().also { line = it } != null) {
output.append(line).append("\n")
}
val exitCode = process.waitFor() // 프로세스가 종료될 때까지 대기
println("--- Telegram Curl Log Start ---")
println("Exit Code: $exitCode") // 0이면 성공, 그 외는 curl 에러 코드
println("Response:\n$output")
println("--- Telegram Curl Log End ---")
}
} catch (e: Exception) {
println("시스템 명령어 실행 중 예외 발생: ${e.message}")
e.printStackTrace()
}
}
} }

View File

@ -45,6 +45,7 @@ import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.DataFlavor
import java.io.File import java.io.File
import androidx.compose.ui.input.key.* import androidx.compose.ui.input.key.*
import network.NewsService
fun getPastedPathFromClipboard(): String? { fun getPastedPathFromClipboard(): String? {
val clipboard = Toolkit.getDefaultToolkit().systemClipboard val clipboard = Toolkit.getDefaultToolkit().systemClipboard
@ -138,6 +139,7 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
SystemSleepPreventer.wakeDisplay() // 모니터 켜기 SystemSleepPreventer.wakeDisplay() // 모니터 켜기
statusMessage = "⏰ 자동 실행 시간(08:30)입니다. 시스템을 가동합니다." statusMessage = "⏰ 자동 실행 시간(08:30)입니다. 시스템을 가동합니다."
authenticateAndStart() authenticateAndStart()
break // 성공하면 루프 탈출 break // 성공하면 루프 탈출
} }
} }

View File

@ -42,10 +42,13 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import model.ConfigIndex import model.ConfigIndex
import model.KisSession import model.KisSession
import network.KisTradeService import network.KisTradeService
import network.NewsService
import network.StockUniverseLoader import network.StockUniverseLoader
import service.AutoTradingManager import service.AutoTradingManager
import java.io.File import java.io.File
@ -62,7 +65,13 @@ fun TradingDecisionLog() {
var llmAnalyser by remember { mutableStateOf(AutoTradingManager.llmAnalyser) } var llmAnalyser by remember { mutableStateOf(AutoTradingManager.llmAnalyser) }
val tradeConfig by remember { val tradeConfig by remember {
KisSession.tradeConfig = KisSession.loadTradeConfig() KisSession.tradeConfig = KisSession.loadTradeConfig()
CoroutineScope(Dispatchers.Default).launch {
println("CALLED sendTelegramMessage -1")
val now = java.time.LocalTime.now(java.time.ZoneId.of("Asia/Seoul"))
NewsService.sendTelegramMessage("⏰ 자동 실행 시간(${now.hour}:${now.minute})입니다. 시스템을 가동합니다.")
}
mutableStateOf(KisSession.tradeConfig) mutableStateOf(KisSession.tradeConfig)
} }
LaunchedEffect(AutoTradingManager.llmAnalyser) { LaunchedEffect(AutoTradingManager.llmAnalyser) {
llmAnalyser = AutoTradingManager.llmAnalyser llmAnalyser = AutoTradingManager.llmAnalyser