...
This commit is contained in:
parent
f6bce36924
commit
0479d5777a
@ -174,37 +174,53 @@ object RagService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun processStock(currentPrice : Double , technicalAnalyzer: TechnicalAnalyzer,stockName: String,stockCode: String,result : TradingDecisionCallback) {
|
suspend fun processStock(currentPrice: Double, technicalAnalyzer: TechnicalAnalyzer, stockName: String, stockCode: String, result: TradingDecisionCallback) {
|
||||||
// 1. 10분간의 데이터 가져오기 (API 호출)
|
val totalStartTime = System.currentTimeMillis() // 전체 시작 시간
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
try {
|
try {
|
||||||
var tradingDecision: TradingDecision = TradingDecision()
|
var tradingDecision = TradingDecision()
|
||||||
tradingDecision.stockCode = stockCode
|
tradingDecision.stockCode = stockCode
|
||||||
tradingDecision.analyzer = technicalAnalyzer
|
tradingDecision.analyzer = technicalAnalyzer
|
||||||
tradingDecision.currentPrice = currentPrice
|
tradingDecision.currentPrice = currentPrice
|
||||||
|
|
||||||
var corpInfo = DartCodeManager.getCorpCode(stockCode)
|
var corpInfo = DartCodeManager.getCorpCode(stockCode)
|
||||||
corpInfo?.stockName = stockName
|
corpInfo?.stockName = stockName
|
||||||
tradingDecision.stockName = stockName
|
tradingDecision.stockName = stockName
|
||||||
tradingDecision.corpName = corpInfo?.cName ?: ""
|
tradingDecision.corpName = corpInfo?.cName ?: ""
|
||||||
|
|
||||||
|
// 1. 재무 데이터 가져오기 시간 측정
|
||||||
|
val financialStartTime = System.currentTimeMillis()
|
||||||
val financialDataDeferred = async { NewsService.fetchFinancialGrowth(corpInfo?.cCode ?: "") }
|
val financialDataDeferred = async { NewsService.fetchFinancialGrowth(corpInfo?.cCode ?: "") }
|
||||||
|
|
||||||
tradingDecision.financialData = financialDataDeferred.await()
|
tradingDecision.financialData = financialDataDeferred.await()
|
||||||
|
|
||||||
val financialStmt = FinancialMapper.mapRawTextToStatement(tradingDecision.financialData ?: "")
|
val financialStmt = FinancialMapper.mapRawTextToStatement(tradingDecision.financialData ?: "")
|
||||||
|
val financialDuration = System.currentTimeMillis() - financialStartTime
|
||||||
|
println("⏱️ [$stockName] 재무 분석 소요: ${financialDuration}ms")
|
||||||
|
|
||||||
if (FinancialAnalyzer.isSafetyBeltMet(financialStmt)) {
|
if (FinancialAnalyzer.isSafetyBeltMet(financialStmt)) {
|
||||||
|
// 2. 뉴스 스크래핑 및 학습 시간 측정
|
||||||
|
val newsIngestStartTime = System.currentTimeMillis()
|
||||||
corpInfo?.let {
|
corpInfo?.let {
|
||||||
try {
|
try {
|
||||||
NewsService.fetchAndIngestNews(it)
|
NewsService.fetchAndIngestNews(it)
|
||||||
} catch (e: Exception) {}
|
} catch (e: Exception) {}
|
||||||
}
|
}
|
||||||
|
val newsIngestDuration = System.currentTimeMillis() - newsIngestStartTime
|
||||||
|
println("⏱️ [$stockName] 뉴스 수집/인덱싱 소요: ${newsIngestDuration}ms")
|
||||||
|
|
||||||
|
// 3. 기술적 지표 계산 시간 측정
|
||||||
|
val techStartTime = System.currentTimeMillis()
|
||||||
val financialScore = FinancialAnalyzer.calculateScore(financialStmt)
|
val financialScore = FinancialAnalyzer.calculateScore(financialStmt)
|
||||||
val scores = technicalAnalyzer.calculateScores(financialScore)
|
val scores = technicalAnalyzer.calculateScores(financialScore)
|
||||||
|
val techDuration = System.currentTimeMillis() - techStartTime
|
||||||
|
println("⏱️ [$stockName] 기술적 지표 계산 소요: ${techDuration}ms")
|
||||||
if (scores.avg() > 50) {
|
if (scores.avg() > 50) {
|
||||||
result(tradingDecision, false)
|
result(tradingDecision, false)
|
||||||
tradingDecision.techSummary = technicalAnalyzer.generateComprehensiveReport()
|
tradingDecision.techSummary = technicalAnalyzer.generateComprehensiveReport()
|
||||||
result(tradingDecision, false)
|
result(tradingDecision, false)
|
||||||
|
|
||||||
|
// 4. RAG 뉴스 검색 및 임베딩 시간 측정
|
||||||
|
val ragStartTime = System.currentTimeMillis()
|
||||||
val question = "${corpInfo?.cName} $stockName[$stockCode]의 향후 실적 전망과 관련된 핵심 뉴스"
|
val question = "${corpInfo?.cName} $stockName[$stockCode]의 향후 실적 전망과 관련된 핵심 뉴스"
|
||||||
val questionEmbedding = embeddingModel.embed(question).content()
|
val questionEmbedding = embeddingModel.embed(question).content()
|
||||||
val searchResult = embeddingStore.search(
|
val searchResult = embeddingStore.search(
|
||||||
@ -214,23 +230,36 @@ object RagService {
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
tradingDecision.newsContext = searchResult.matches().joinToString("\n") { it.embedded().text() }
|
tradingDecision.newsContext = searchResult.matches().joinToString("\n") { it.embedded().text() }
|
||||||
|
val ragDuration = System.currentTimeMillis() - ragStartTime
|
||||||
|
println("⏱️ [$stockName] RAG 뉴스 검색 소요: ${ragDuration}ms")
|
||||||
|
|
||||||
result(tradingDecision, false)
|
result(tradingDecision, false)
|
||||||
TradingLogStore.addAnalyzer(stockName,stockCode, "${FinancialAnalyzer.toString(financialStmt)}${scores.toString()}",true)
|
TradingLogStore.addAnalyzer(stockName, stockCode, "${FinancialAnalyzer.toString(financialStmt)}${scores.toString()}", true)
|
||||||
println("${stockName}[${stockCode}] : ${FinancialAnalyzer.toString(financialStmt)}${scores.toString()}")
|
|
||||||
result(decideTrading(stockCode, scores, financialStmt, tradingDecision), true)
|
// 5. AI 최종 결정(LLM 호출) 시간 측정
|
||||||
|
val aiDecisionStartTime = System.currentTimeMillis()
|
||||||
|
val finalDecision = decideTrading(stockCode, scores, financialStmt, tradingDecision)
|
||||||
|
val aiDecisionDuration = System.currentTimeMillis() - aiDecisionStartTime
|
||||||
|
println("⏱️ [$stockName] AI 최종 판단 소요: ${aiDecisionDuration}ms")
|
||||||
|
|
||||||
|
val totalDuration = System.currentTimeMillis() - totalStartTime
|
||||||
|
println("✅ [$stockName] 전체 분석 완료 총 소요: ${totalDuration}ms")
|
||||||
|
|
||||||
|
// 상세 로그 남기기
|
||||||
|
TradingLogStore.addAnalyzer(stockName, stockCode, "분석시간 상세: 재무(${financialDuration}ms), 뉴스(${newsIngestDuration}ms), RAG(${ragDuration}ms), AI(${aiDecisionDuration}ms)", true)
|
||||||
|
|
||||||
|
result(finalDecision, true)
|
||||||
} else {
|
} else {
|
||||||
println("${stockName}[${stockCode}] : ${FinancialAnalyzer.toString(financialStmt)}${scores.toString()}")
|
println("✋ [$stockName] 기술 점수 미달로 분석 중단")
|
||||||
TradingLogStore.addAnalyzer(stockName,stockCode, "${FinancialAnalyzer.toString(financialStmt)}${scores.toString()}")
|
|
||||||
tradingDecision.confidence = 1.0
|
tradingDecision.confidence = 1.0
|
||||||
result(tradingDecision, false)
|
result(tradingDecision, false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TradingLogStore.addAnalyzer(stockName,stockCode, "${FinancialAnalyzer.toString(financialStmt)}")
|
println("🚨 [$stockName] 재무 안전벨트 미달")
|
||||||
println("${stockName}[${stockCode}] : ${FinancialAnalyzer.toString(financialStmt)}")
|
|
||||||
tradingDecision.confidence = 1.0
|
tradingDecision.confidence = 1.0
|
||||||
result(tradingDecision, false)
|
result(tradingDecision, false)
|
||||||
}
|
}
|
||||||
}catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import kotlinx.coroutines.withTimeout
|
|||||||
import model.NewsItem
|
import model.NewsItem
|
||||||
import network.CorpInfo
|
import network.CorpInfo
|
||||||
import network.RagService
|
import network.RagService
|
||||||
|
import util.HardwareDetector
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@ -269,9 +270,20 @@ object DynamicNewsScraper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object SafeScraper {
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// 동시 처리를 1개로 줄여서 안정성을 극대화 (추천)
|
// 동시 처리를 1개로 줄여서 안정성을 극대화 (추천)
|
||||||
// Playwright는 여러 페이지를 띄울 때 CPU/메모리 점유율이 매우 높습니다.
|
// Playwright는 여러 페이지를 띄울 때 CPU/메모리 점유율이 매우 높습니다.
|
||||||
private val semaphore = Semaphore(2)
|
private val semaphore = Semaphore(maxParallel)
|
||||||
|
|
||||||
suspend fun scrapeParallel(corpInfo: CorpInfo, urls: List<NewsItem>) = coroutineScope {
|
suspend fun scrapeParallel(corpInfo: CorpInfo, urls: List<NewsItem>) = coroutineScope {
|
||||||
urls.forEach { item -> // map + awaitAll 대신 순차 처리가 현재 상황에선 더 안정적입니다.
|
urls.forEach { item -> // map + awaitAll 대신 순차 처리가 현재 상황에선 더 안정적입니다.
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.RagService
|
import network.RagService
|
||||||
|
import util.HardwareDetector
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
@ -46,31 +47,48 @@ object LlamaServerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startServer(binPath: String, modelPath: String, port: Int, nGpuLayers: Int = 99) {
|
fun startServer(binPath: String, modelPath: String, port: Int) {
|
||||||
// 이미 해당 포트에서 실행 중이거나 모델 경로가 비었으면 무시합니다.
|
|
||||||
if (processes.containsKey(port) || modelPath.isBlank()) return
|
if (processes.containsKey(port) || modelPath.isBlank()) return
|
||||||
val os = System.getProperty("os.name").lowercase()
|
val os = System.getProperty("os.name").lowercase()
|
||||||
val arch = System.getProperty("os.arch").lowercase()
|
val arch = System.getProperty("os.arch").lowercase()
|
||||||
val isWin = os.contains("win")
|
val isWin = os.contains("win")
|
||||||
val (nGpuLayers, threads) = when {
|
val isMacArm = os.contains("mac") && (arch.contains("arm64") || arch.contains("aarch64"))
|
||||||
os.contains("mac") && (arch.contains("arm64") || arch.contains("aarch64")) -> 99 to 8
|
|
||||||
isWin -> 4 to 12 // NUC Core Ultra 7: GPU 레이어 40 내외, 스레드 12 권장
|
val cpuCores = Runtime.getRuntime().availableProcessors() // HardwareDetector.getCpuCores()와 동일
|
||||||
else -> 0 to 4 // 인텔 맥 2017 등
|
val hasGpu = HardwareDetector.hasNvidiaGpu()
|
||||||
}
|
|
||||||
|
// 1. optimalThreads: 할당 비율 적용 및 최소/최대 범위 제한(Safety Boundary)
|
||||||
|
// 과도한 스레드 할당은 오히려 컨텍스트 스위칭 비용을 높여 성능을 저하시킬 수 있습니다.
|
||||||
|
val ratio = if (isWin) 0.5 else 0.7
|
||||||
|
val optimalThreads = (cpuCores * ratio).toInt().coerceIn(4, 16)
|
||||||
|
|
||||||
|
// 2. optimalGpuLayers: GPU 가속 조건 (윈도우 NVIDIA 또는 맥 ARM)
|
||||||
|
val optimalGpuLayers = if ((isWin && hasGpu) || isMacArm) 99 else 4
|
||||||
|
|
||||||
|
println("🖥️ OS: $os / Arch: $arch")
|
||||||
|
println("⚙️ 할당 스레드: $optimalThreads (Core: $cpuCores, Ratio: $ratio)")
|
||||||
|
println("🚀 GPU 레이어: $optimalGpuLayers (NVIDIA/MacArm: ${if(optimalGpuLayers == 99) "YES" else "NO"})")
|
||||||
|
|
||||||
|
// val (nGpuLayers, threads) = when {
|
||||||
|
// os.contains("mac") && (arch.contains("arm64") || arch.contains("aarch64")) -> 99 to 8
|
||||||
|
// isWin -> optimalGpuLayers to optimalThreads // NUC Core Ultra 7: GPU 레이어 40 내외, 스레드 12 권장
|
||||||
|
// else -> 0 to 4 // 인텔 맥 2017 등
|
||||||
|
// }
|
||||||
|
|
||||||
val command = mutableListOf(
|
val command = mutableListOf(
|
||||||
binPath,
|
binPath,
|
||||||
"-m", modelPath,
|
"-m", modelPath,
|
||||||
"--port", port.toString(),
|
"--port", port.toString(),
|
||||||
"-c", if (port == 8081) "512" else "8192",
|
"-c", if (port == 8081) "512" else "8192",
|
||||||
"-ngl", nGpuLayers.toString(),
|
"-ngl", optimalGpuLayers.toString(),
|
||||||
"-t", threads.toString(),
|
"-t", optimalThreads.toString(),
|
||||||
"--embedding"
|
"--embedding"
|
||||||
)
|
)
|
||||||
if (port != 8081) { // 텍스트 생성용 모델에만 적용
|
if (port != 8081) { // 텍스트 생성용 모델에만 적용
|
||||||
command.addAll(listOf(
|
command.addAll(listOf(
|
||||||
"-b", "512", // Batch size (토큰 병렬 처리량 제한으로 연산 안정화)
|
"-b", "512", // Batch size (토큰 병렬 처리량 제한으로 연산 안정화)
|
||||||
"--threads-batch", threads.toString(),
|
"--threads-batch", optimalThreads.toString(),
|
||||||
"-fa","on" // Flash Attention 활성화 (메모리 절약 및 긴 컨텍스트 연산 안정성 증가)
|
"-fa","on" // Flash Attention 활성화 (메모리 절약 및 긴 컨텍스트 연산 안정성 증가)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/main/kotlin/util/HardwareDetector.kt
Normal file
21
src/main/kotlin/util/HardwareDetector.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
object HardwareDetector {
|
||||||
|
fun getCpuCores(): Int = Runtime.getRuntime().availableProcessors()
|
||||||
|
|
||||||
|
fun getTotalRamGb(): Long {
|
||||||
|
val bean = java.lang.management.ManagementFactory.getOperatingSystemMXBean()
|
||||||
|
as com.sun.management.OperatingSystemMXBean
|
||||||
|
return bean.totalMemorySize / (1024 * 1024 * 1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows 환경에서 NVIDIA GPU 존재 여부 확인 (간이 로직)
|
||||||
|
fun hasNvidiaGpu(): Boolean {
|
||||||
|
return try {
|
||||||
|
val process = Runtime.getRuntime().exec("nvidia-smi")
|
||||||
|
process.waitFor() == 0
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user