Compare commits
No commits in common. "d7efc433bdfa36c2cdf03481aea50796cdbaac81" and "23d505eabe0ff18d77185ffa418b3149885e2a41" have entirely different histories.
d7efc433bd
...
23d505eabe
@ -328,7 +328,6 @@ fun main() = application {
|
|||||||
// DashboardScreen()
|
// DashboardScreen()
|
||||||
}
|
}
|
||||||
AppScreen.TradingDecision -> {
|
AppScreen.TradingDecision -> {
|
||||||
|
|
||||||
TradingDecisionLog()
|
TradingDecisionLog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,10 @@
|
|||||||
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
|
||||||
@ -515,21 +509,11 @@ 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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -561,14 +545,8 @@ 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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -240,7 +240,6 @@ 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 = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,8 +17,6 @@ import java.util.zip.ZipInputStream
|
|||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
import kotlinx.serialization.encodeToString // 추가 필요
|
import kotlinx.serialization.encodeToString // 추가 필요
|
||||||
import java.nio.charset.Charset
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class StockItem(
|
data class StockItem(
|
||||||
val code: String,
|
val code: String,
|
||||||
@ -31,25 +29,6 @@ object StockUniverseLoader {
|
|||||||
private val json = Json { ignoreUnknownKeys = true; prettyPrint = true }
|
private val json = Json { ignoreUnknownKeys = true; prettyPrint = true }
|
||||||
private const val DEFAULT_FILE_PATH = "stocks_universe.json"
|
private const val DEFAULT_FILE_PATH = "stocks_universe.json"
|
||||||
|
|
||||||
fun readSafeLines(file: File): List<String> {
|
|
||||||
val eucKr = Charset.forName("EUC-KR")
|
|
||||||
val utf8 = Charsets.UTF_8
|
|
||||||
|
|
||||||
// 우선 EUC-KR로 읽어봄
|
|
||||||
val lines = file.readLines(eucKr)
|
|
||||||
|
|
||||||
// 첫 줄에서 한글이 깨졌는지 검사 (정규식 활용)
|
|
||||||
// 한글이 하나도 없고 깨진 특수문자만 있다면 UTF-8로 재시도
|
|
||||||
val hasKorean = lines.firstOrNull()?.any { it in '\uAC00'..'\uD7A3' } ?: false
|
|
||||||
|
|
||||||
return if (hasKorean) {
|
|
||||||
lines
|
|
||||||
} else {
|
|
||||||
println("⚠️ EUC-KR에서 한글 미검출. UTF-8로 재시도합니다.")
|
|
||||||
file.readLines(utf8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadUniverse(filePath: String = DEFAULT_FILE_PATH): List<Pair<String, String>> {
|
fun loadUniverse(filePath: String = DEFAULT_FILE_PATH): List<Pair<String, String>> {
|
||||||
return try {
|
return try {
|
||||||
val file = File(filePath)
|
val file = File(filePath)
|
||||||
@ -71,8 +50,7 @@ object StockUniverseLoader {
|
|||||||
try {
|
try {
|
||||||
val stockItems = items.map { StockItem(it.first, it.second) }
|
val stockItems = items.map { StockItem(it.first, it.second) }
|
||||||
val jsonString = json.encodeToString(stockItems)
|
val jsonString = json.encodeToString(stockItems)
|
||||||
// File(filePath).writeText(jsonString)
|
File(filePath).writeText(jsonString)
|
||||||
File(filePath).writeText(jsonString, Charsets.UTF_8)
|
|
||||||
println("💾 [System] 유니버스 영구 저장 완료: 총 ${items.size}종목")
|
println("💾 [System] 유니버스 영구 저장 완료: 총 ${items.size}종목")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("❌ 유니버스 저장 실패: ${e.message}")
|
println("❌ 유니버스 저장 실패: ${e.message}")
|
||||||
@ -83,7 +61,7 @@ object StockUniverseLoader {
|
|||||||
fun parseAndMergeCsv(file: File, targetJsonPath: String = DEFAULT_FILE_PATH): List<Pair<String, String>> {
|
fun parseAndMergeCsv(file: File, targetJsonPath: String = DEFAULT_FILE_PATH): List<Pair<String, String>> {
|
||||||
val newItems = mutableListOf<Pair<String, String>>()
|
val newItems = mutableListOf<Pair<String, String>>()
|
||||||
try {
|
try {
|
||||||
val lines = readSafeLines(file)
|
val lines = file.readLines()
|
||||||
if (lines.isEmpty()) return loadUniverse(targetJsonPath)
|
if (lines.isEmpty()) return loadUniverse(targetJsonPath)
|
||||||
|
|
||||||
// 헤더 자동 추적
|
// 헤더 자동 추적
|
||||||
|
|||||||
@ -45,7 +45,7 @@ object KisTradeService {
|
|||||||
// [수정] 모든 로그(Headers + Body)를 찍도록 설정
|
// [수정] 모든 로그(Headers + Body)를 찍도록 설정
|
||||||
install(Logging) {
|
install(Logging) {
|
||||||
logger = Logger.DEFAULT
|
logger = Logger.DEFAULT
|
||||||
level = LogLevel.ALL
|
level = LogLevel.NONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,14 +17,10 @@ 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
|
||||||
@ -33,16 +29,10 @@ 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 {
|
||||||
@ -132,62 +122,4 @@ 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -707,7 +707,7 @@ object AutoTradingManager {
|
|||||||
println("⏳ [Cycle Timeout] 사이클이 너무 길어져 초기화 후 재시작합니다.")
|
println("⏳ [Cycle Timeout] 사이클이 너무 길어져 초기화 후 재시작합니다.")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("⚠️ [Loop Error] ${e.message}")
|
println("⚠️ [Loop Error] ${e.message}")
|
||||||
delay(1000)
|
delay(1500)
|
||||||
}
|
}
|
||||||
waitForNextCycle(waitTime)
|
waitForNextCycle(waitTime)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,7 +45,6 @@ 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
|
||||||
@ -139,7 +138,6 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
|
|||||||
SystemSleepPreventer.wakeDisplay() // 모니터 켜기
|
SystemSleepPreventer.wakeDisplay() // 모니터 켜기
|
||||||
statusMessage = "⏰ 자동 실행 시간(08:30)입니다. 시스템을 가동합니다."
|
statusMessage = "⏰ 자동 실행 시간(08:30)입니다. 시스템을 가동합니다."
|
||||||
authenticateAndStart()
|
authenticateAndStart()
|
||||||
|
|
||||||
break // 성공하면 루프 탈출
|
break // 성공하면 루프 탈출
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,13 +42,10 @@ 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
|
||||||
@ -65,13 +62,7 @@ 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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user