From 05a9d01bba4eb6043a774b9f4743eaa8a2c53d65 Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Wed, 1 Apr 2026 14:35:56 +0900 Subject: [PATCH] ... --- .../kotlin/network/KisWebSocketManager.kt | 5 +- src/main/kotlin/network/NewsService.kt | 90 ---------------- src/main/kotlin/network/RagService.kt | 102 ++++++++++++++++-- src/main/kotlin/service/AutoTradingManager.kt | 8 +- src/main/kotlin/service/DynamicNewsScraper.kt | 12 +-- 5 files changed, 102 insertions(+), 115 deletions(-) diff --git a/src/main/kotlin/network/KisWebSocketManager.kt b/src/main/kotlin/network/KisWebSocketManager.kt index 81292ef..b879570 100644 --- a/src/main/kotlin/network/KisWebSocketManager.kt +++ b/src/main/kotlin/network/KisWebSocketManager.kt @@ -120,9 +120,10 @@ object KisWebSocketManager { println("๐Ÿ ์›น์†Œ์ผ“ finally ๋ธ”๋ก ์ง„์ž… (์—ฐ๊ฒฐ ์‹œ๋„ ์ข…๋ฃŒ)") isConnected.set(false) session = null + val retry = 10000L AutoTradingManager.webSocketConnect = false - println("โณ 5์ดˆ ํ›„ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„...") - delay(5000) // 5์ดˆ ํ›„ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ + println("โณ ${(retry / 1000).toInt()}์ดˆ ํ›„ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„...") + delay(retry) // 5์ดˆ ํ›„ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ } } } diff --git a/src/main/kotlin/network/NewsService.kt b/src/main/kotlin/network/NewsService.kt index 94fde6f..ee6ca89 100644 --- a/src/main/kotlin/network/NewsService.kt +++ b/src/main/kotlin/network/NewsService.kt @@ -71,18 +71,6 @@ object NewsService { } -// suspend fun fetchCorpInfo(corpCode: String): String { -// val apiKey = "61143d2af0759f6c28ce372d9e339d1e01687abc" -// val url = "https://opendart.fss.or.kr/api/company.json?crtfc_key=$apiKey&corp_code=$corpCode" -// -// return try { -// val response = client.get(url).body() -// "๊ธฐ์—…๋ช…: ${response.corp_name}, ์ฃผ์š”์‚ฌ์—…: ${response.main_business}" -// } catch (e: Exception) { -// "๊ธฐ์—… ์ •๋ณด ๋กœ๋“œ ์‹คํŒจ" -// } -// } - suspend fun fetchFinancialGrowth(corpCode: String?): String { if (corpCode != null) { val apiKey = KisSession.config.dAppKey @@ -106,81 +94,3 @@ object NewsService { } } } - -object FinancialMapper { - /** - * ์ œ๊ณต๋œ ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ FinancialStatement ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ - */ - fun mapRawTextToStatement(rawText: String): FinancialStatement { - if (rawText.isBlank()) { - return FinancialStatement() - } -// println(rawText) - val currentValues = extractYearlyValues(rawText, "๋‹น๊ธฐ") - val previousValues = extractYearlyValues(rawText, "์ „๊ธฐ") - - // 1. ์˜์—…์ด์ต ์ฆ๊ฐ€์œจ: (๋‹น๊ธฐ - ์ „๊ธฐ) / |์ „๊ธฐ| * 100 - val opCurrent = currentValues["์˜์—…์ด์ต"] ?: 0.0 - val opPrevious = previousValues["์˜์—…์ด์ต"] ?: 0.0 - val opGrowth = if (opPrevious != 0.0) ((opCurrent - opPrevious) / Math.abs(opPrevious)) * 100 else 0.0 - - // 2. ๋‹น๊ธฐ์ˆœ์ด์ต ์ฆ๊ฐ€์œจ - val niCurrent = currentValues["๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)"] ?: 0.0 - val niPrevious = previousValues["๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)"] ?: 0.0 - val niGrowth = if (niPrevious != 0.0) ((niCurrent - niPrevious) / Math.abs(niPrevious)) * 100 else 0.0 - - // 3. ROE: ๋‹น๊ธฐ์ˆœ์ด์ต / ๋‹น๊ธฐ ์ž๋ณธ์ด๊ณ„ * 100 - val equityCurrent = currentValues["์ž๋ณธ์ด๊ณ„"] ?: 1.0 - val roe = (niCurrent / equityCurrent) * 100 - - // 4. ๋ถ€์ฑ„๋น„์œจ: ๋‹น๊ธฐ ๋ถ€์ฑ„์ด๊ณ„ / ๋‹น๊ธฐ ์ž๋ณธ์ด๊ณ„ * 100 - val debtCurrent = currentValues["๋ถ€์ฑ„์ด๊ณ„"] ?: 0.0 - val debtRatio = (debtCurrent / equityCurrent) * 100 - - // 5. ๋‹น์ขŒ๋น„์œจ(์œ ๋™์„ฑ): ๋‹น๊ธฐ ์œ ๋™์ž์‚ฐ / ๋‹น๊ธฐ ์œ ๋™๋ถ€์ฑ„ * 100 - val currentAssets = currentValues["์œ ๋™์ž์‚ฐ"] ?: 0.0 - val currentLiabilities = currentValues["์œ ๋™๋ถ€์ฑ„"] ?: 1.0 - val quickRatio = (currentAssets / currentLiabilities) * 100 - - return FinancialStatement( - operatingProfitGrowth = opGrowth, - netIncomeGrowth = niGrowth, - roe = roe, - debtRatio = debtRatio, - quickRatio = quickRatio, - isOperatingProfitPositive = opCurrent > 0, - isNetIncomePositive = niCurrent > 0 - ).apply { - println("๋‹น๊ธฐ์ˆœ์ด์ต: ${niCurrent} , isSafetyBeltMet ${FinancialAnalyzer.isSafetyBeltMet(this)}") - } - } - - private fun extractYearlyValues(text: String, type: String): Map { - val result = mutableMapOf() - - // ํ•ต์‹ฌ ์ˆ˜์ •: ํ•ญ๋ชฉ๋ช… ๋’ค์— (๋‹น๊ธฐ) ๋˜๋Š” (์ „๊ธฐ)๊ฐ€ ์˜ค๊ณ , ๊ทธ ์งํ›„์˜ ์ˆซ์ž(๋งˆ์ด๋„ˆ์Šค, ์‰ผํ‘œ ํฌํ•จ)๋ฅผ ์บก์ฒ˜ - // ์‰ผํ‘œ๋‚˜ ๊ณต๋ฐฑ์œผ๋กœ ๋๋‚˜๋Š” ์ง€์ ๊นŒ์ง€ ์ฐพ์Šต๋‹ˆ๋‹ค. - val regex = Regex("""([๊ฐ€-ํžฃ\s()]+)\s\($type\)([-0-9,.]+)""") - - regex.findAll(text).forEach { match -> - val key = match.groupValues[1].trim() - // ์ˆซ์ž ๋‚ด ์‰ผํ‘œ ์ œ๊ฑฐ ํ›„ Double ๋ณ€ํ™˜ - val rawValue = match.groupValues[2].replace(",", "").toDoubleOrNull() ?: 0.0 - result[key] = rawValue - } - return result - } -} - - -@Serializable -data class FinancialStatement( - val revenueGrowth: Double = 0.0, // ๋งค์ถœ์•ก ์ฆ๊ฐ€์œจ - val operatingProfitGrowth: Double = 0.0, // ์˜์—…์ด์ต ์ฆ๊ฐ€์œจ - val netIncomeGrowth: Double = 0.0, // ๋‹น๊ธฐ์ˆœ์ด์ต ์ฆ๊ฐ€์œจ - val roe: Double = 0.0, // ROE - val debtRatio: Double = 0.0, // ๋ถ€์ฑ„๋น„์œจ - val quickRatio: Double = 0.0, // ๋‹น์ขŒ๋น„์œจ - val isOperatingProfitPositive: Boolean = false, // ๋‹น๊ธฐ ์˜์—…์ด์ต ํ‘์ž ์—ฌ๋ถ€ - val isNetIncomePositive: Boolean = false -) \ No newline at end of file diff --git a/src/main/kotlin/network/RagService.kt b/src/main/kotlin/network/RagService.kt index dd44870..ba2af49 100644 --- a/src/main/kotlin/network/RagService.kt +++ b/src/main/kotlin/network/RagService.kt @@ -201,16 +201,6 @@ object RagService { println("โฑ๏ธ [$stockName] ์žฌ๋ฌด ๋ถ„์„ ์†Œ์š”: ${financialDuration}ms") if (FinancialAnalyzer.isSafetyBeltMet(financialStmt)) { - // 2. ๋‰ด์Šค ์Šคํฌ๋ž˜ํ•‘ ๋ฐ ํ•™์Šต ์‹œ๊ฐ„ ์ธก์ • - val newsIngestStartTime = System.currentTimeMillis() - corpInfo?.let { - try { - NewsService.fetchAndIngestNews(it) - } catch (e: Exception) {} - } - val newsIngestDuration = System.currentTimeMillis() - newsIngestStartTime - println("โฑ๏ธ [$stockName] ๋‰ด์Šค ์ˆ˜์ง‘/์ธ๋ฑ์‹ฑ ์†Œ์š”: ${newsIngestDuration}ms") - // 3. ๊ธฐ์ˆ ์  ์ง€ํ‘œ ๊ณ„์‚ฐ ์‹œ๊ฐ„ ์ธก์ • val techStartTime = System.currentTimeMillis() val financialScore = FinancialAnalyzer.calculateScore(financialStmt) @@ -218,6 +208,16 @@ object RagService { val techDuration = System.currentTimeMillis() - techStartTime println("โฑ๏ธ [$stockName] ๊ธฐ์ˆ ์  ์ง€ํ‘œ ๊ณ„์‚ฐ ์†Œ์š”: ${techDuration}ms") if (scores.avg() > 50) { + // 2. ๋‰ด์Šค ์Šคํฌ๋ž˜ํ•‘ ๋ฐ ํ•™์Šต ์‹œ๊ฐ„ ์ธก์ • + val newsIngestStartTime = System.currentTimeMillis() + corpInfo?.let { + try { + NewsService.fetchAndIngestNews(it) + } catch (e: Exception) {} + } + val newsIngestDuration = System.currentTimeMillis() - newsIngestStartTime + println("โฑ๏ธ [$stockName] ๋‰ด์Šค ์ˆ˜์ง‘/์ธ๋ฑ์‹ฑ ์†Œ์š”: ${newsIngestDuration}ms") + result(tradingDecision, false) tradingDecision.techSummary = technicalAnalyzer.generateComprehensiveReport() result(tradingDecision, false) @@ -566,4 +566,84 @@ confidence: $confidence ์žฌ๋ฌด์žฌํ‘œ: $financialData """.trimIndent() } -} \ No newline at end of file +} + + + +object FinancialMapper { + /** + * ์ œ๊ณต๋œ ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ FinancialStatement ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ + */ + fun mapRawTextToStatement(rawText: String): FinancialStatement { + if (rawText.isBlank()) { + return FinancialStatement() + } +// println(rawText) + val currentValues = extractYearlyValues(rawText, "๋‹น๊ธฐ") + val previousValues = extractYearlyValues(rawText, "์ „๊ธฐ") + + // 1. ์˜์—…์ด์ต ์ฆ๊ฐ€์œจ: (๋‹น๊ธฐ - ์ „๊ธฐ) / |์ „๊ธฐ| * 100 + val opCurrent = currentValues["์˜์—…์ด์ต"] ?: 0.0 + val opPrevious = previousValues["์˜์—…์ด์ต"] ?: 0.0 + val opGrowth = if (opPrevious != 0.0) ((opCurrent - opPrevious) / Math.abs(opPrevious)) * 100 else 0.0 + + // 2. ๋‹น๊ธฐ์ˆœ์ด์ต ์ฆ๊ฐ€์œจ + val niCurrent = currentValues["๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)"] ?: 0.0 + val niPrevious = previousValues["๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)"] ?: 0.0 + val niGrowth = if (niPrevious != 0.0) ((niCurrent - niPrevious) / Math.abs(niPrevious)) * 100 else 0.0 + + // 3. ROE: ๋‹น๊ธฐ์ˆœ์ด์ต / ๋‹น๊ธฐ ์ž๋ณธ์ด๊ณ„ * 100 + val equityCurrent = currentValues["์ž๋ณธ์ด๊ณ„"] ?: 1.0 + val roe = (niCurrent / equityCurrent) * 100 + + // 4. ๋ถ€์ฑ„๋น„์œจ: ๋‹น๊ธฐ ๋ถ€์ฑ„์ด๊ณ„ / ๋‹น๊ธฐ ์ž๋ณธ์ด๊ณ„ * 100 + val debtCurrent = currentValues["๋ถ€์ฑ„์ด๊ณ„"] ?: 0.0 + val debtRatio = (debtCurrent / equityCurrent) * 100 + + // 5. ๋‹น์ขŒ๋น„์œจ(์œ ๋™์„ฑ): ๋‹น๊ธฐ ์œ ๋™์ž์‚ฐ / ๋‹น๊ธฐ ์œ ๋™๋ถ€์ฑ„ * 100 + val currentAssets = currentValues["์œ ๋™์ž์‚ฐ"] ?: 0.0 + val currentLiabilities = currentValues["์œ ๋™๋ถ€์ฑ„"] ?: 1.0 + val quickRatio = (currentAssets / currentLiabilities) * 100 + + return FinancialStatement( + operatingProfitGrowth = opGrowth, + netIncomeGrowth = niGrowth, + roe = roe, + debtRatio = debtRatio, + quickRatio = quickRatio, + isOperatingProfitPositive = opCurrent > 0, + isNetIncomePositive = niCurrent > 0 + ).apply { + println("๋‹น๊ธฐ์ˆœ์ด์ต: ${niCurrent} , isSafetyBeltMet ${FinancialAnalyzer.isSafetyBeltMet(this)}") + } + } + + private fun extractYearlyValues(text: String, type: String): Map { + val result = mutableMapOf() + + // ํ•ต์‹ฌ ์ˆ˜์ •: ํ•ญ๋ชฉ๋ช… ๋’ค์— (๋‹น๊ธฐ) ๋˜๋Š” (์ „๊ธฐ)๊ฐ€ ์˜ค๊ณ , ๊ทธ ์งํ›„์˜ ์ˆซ์ž(๋งˆ์ด๋„ˆ์Šค, ์‰ผํ‘œ ํฌํ•จ)๋ฅผ ์บก์ฒ˜ + // ์‰ผํ‘œ๋‚˜ ๊ณต๋ฐฑ์œผ๋กœ ๋๋‚˜๋Š” ์ง€์ ๊นŒ์ง€ ์ฐพ์Šต๋‹ˆ๋‹ค. + val regex = Regex("""([๊ฐ€-ํžฃ\s()]+)\s\($type\)([-0-9,.]+)""") + + regex.findAll(text).forEach { match -> + val key = match.groupValues[1].trim() + // ์ˆซ์ž ๋‚ด ์‰ผํ‘œ ์ œ๊ฑฐ ํ›„ Double ๋ณ€ํ™˜ + val rawValue = match.groupValues[2].replace(",", "").toDoubleOrNull() ?: 0.0 + result[key] = rawValue + } + return result + } +} + + +@Serializable +data class FinancialStatement( + val revenueGrowth: Double = 0.0, // ๋งค์ถœ์•ก ์ฆ๊ฐ€์œจ + val operatingProfitGrowth: Double = 0.0, // ์˜์—…์ด์ต ์ฆ๊ฐ€์œจ + val netIncomeGrowth: Double = 0.0, // ๋‹น๊ธฐ์ˆœ์ด์ต ์ฆ๊ฐ€์œจ + val roe: Double = 0.0, // ROE + val debtRatio: Double = 0.0, // ๋ถ€์ฑ„๋น„์œจ + val quickRatio: Double = 0.0, // ๋‹น์ขŒ๋น„์œจ + val isOperatingProfitPositive: Boolean = false, // ๋‹น๊ธฐ ์˜์—…์ด์ต ํ‘์ž ์—ฌ๋ถ€ + val isNetIncomePositive: Boolean = false +) \ No newline at end of file diff --git a/src/main/kotlin/service/AutoTradingManager.kt b/src/main/kotlin/service/AutoTradingManager.kt index b6c20e0..ec86eee 100644 --- a/src/main/kotlin/service/AutoTradingManager.kt +++ b/src/main/kotlin/service/AutoTradingManager.kt @@ -282,7 +282,13 @@ object AutoTradingManager { } .onFailure { println("๋งค์ˆ˜ ์‹คํŒจ: ${it.message} ${stockCode} $orderQty $finalPrice") - TradingLogStore.addLog(decision,"BUY",it.message ?: "๋งค์ˆ˜ ์‹คํŒจ") + + if (it.message?.contains("์ฃผ๋ฌธ๊ฐ€๋Šฅ๊ธˆ์•ก์„ ์ดˆ๊ณผ") == true) { + AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode,hts_kor_isnm = stockName)) + TradingLogStore.addLog(decision,"BUY","${it.message ?: " ๋งค์ˆ˜ ์‹คํŒจ"} => ์žฌ๋ถ„์„ ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€") + } else { + TradingLogStore.addLog(decision,"BUY",it.message ?: "๋งค์ˆ˜ ์‹คํŒจ") + } } } } diff --git a/src/main/kotlin/service/DynamicNewsScraper.kt b/src/main/kotlin/service/DynamicNewsScraper.kt index a35266f..0364fd2 100644 --- a/src/main/kotlin/service/DynamicNewsScraper.kt +++ b/src/main/kotlin/service/DynamicNewsScraper.kt @@ -107,10 +107,6 @@ object BrowserManager { } object DynamicNewsScraper { -// private val playwright by lazy { Playwright.create() } -// private val browser by lazy { -// playwright.chromium().launch(BrowserType.LaunchOptions().setHeadless(true)) -// } fun extractSmartContentWithLineFilter(page: Page): String { val script = """ @@ -273,13 +269,7 @@ object SafeScraper { private val totalRam = HardwareDetector.getTotalRamGb() // RAM 8GB๋‹น 1๊ฐœ ์ˆ˜์ค€์œผ๋กœ ์„ค์ •ํ•˜๋˜, ์ตœ๋Œ€ 10~12๊ฐœ๋กœ ์ œํ•œ (CPU ๋ถ€ํ•˜ ๋ฐฉ์ง€) - private val maxParallel = when { - totalRam >= 128 -> 8 - totalRam >= 64 -> 6 - totalRam >= 32 -> 4 - totalRam >= 16 -> 2 - else -> 1 - } + private val maxParallel = totalRam.div(6).toInt() // ๋™์‹œ ์ฒ˜๋ฆฌ๋ฅผ 1๊ฐœ๋กœ ์ค„์—ฌ์„œ ์•ˆ์ •์„ฑ์„ ๊ทน๋Œ€ํ™” (์ถ”์ฒœ) // Playwright๋Š” ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€๋ฅผ ๋„์šธ ๋•Œ CPU/๋ฉ”๋ชจ๋ฆฌ ์ ์œ ์œจ์ด ๋งค์šฐ ๋†’์Šต๋‹ˆ๋‹ค.