...
This commit is contained in:
parent
5ba71f37c0
commit
05a9d01bba
@ -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초 후 재연결 시도
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<CorpInfo>()
|
||||
// "기업명: ${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<String, Double> {
|
||||
val result = mutableMapOf<String, Double>()
|
||||
|
||||
// 핵심 수정: 항목명 뒤에 (당기) 또는 (전기)가 오고, 그 직후의 숫자(마이너스, 쉼표 포함)를 캡처
|
||||
// 쉼표나 공백으로 끝나는 지점까지 찾습니다.
|
||||
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
|
||||
)
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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<String, Double> {
|
||||
val result = mutableMapOf<String, Double>()
|
||||
|
||||
// 핵심 수정: 항목명 뒤에 (당기) 또는 (전기)가 오고, 그 직후의 숫자(마이너스, 쉼표 포함)를 캡처
|
||||
// 쉼표나 공백으로 끝나는 지점까지 찾습니다.
|
||||
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
|
||||
)
|
||||
@ -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 ?: "매수 실패")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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/메모리 점유율이 매우 높습니다.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user