....
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package analyzer
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import model.CandleData
|
||||
import service.Candle
|
||||
import java.time.LocalDateTime
|
||||
@@ -7,6 +8,7 @@ import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.math.*
|
||||
|
||||
@Serializable
|
||||
data class ScalpingSignalModel(
|
||||
val currentPrice: Double,
|
||||
val buySignal: Boolean,
|
||||
|
||||
@@ -7,6 +7,7 @@ import analyzer.FinancialAnalyzer
|
||||
import analyzer.FinancialMapper
|
||||
import analyzer.FinancialStatement
|
||||
import analyzer.InvestmentScores
|
||||
import analyzer.ScalpingSignalModel
|
||||
import analyzer.TechnicalAnalyzer
|
||||
import dev.langchain4j.community.rag.content.retriever.lucene.LuceneEmbeddingStore
|
||||
import dev.langchain4j.data.document.Metadata
|
||||
@@ -248,9 +249,9 @@ object RagService {
|
||||
val techStartTime = System.currentTimeMillis()
|
||||
val financialScore = FinancialAnalyzer.calculateScore(financialStmt)
|
||||
val scores = technicalAnalyzer.calculateScores(financialScore)
|
||||
val techSignal = technicalAnalyzer.generateComprehensiveSignal()
|
||||
tradingDecision.signalModel = technicalAnalyzer.generateComprehensiveSignal()
|
||||
val techDuration = System.currentTimeMillis() - techStartTime
|
||||
println("techSignal.compositeScore ${techSignal.compositeScore}")
|
||||
println("techSignal.compositeScore ${tradingDecision.signalModel}")
|
||||
|
||||
if (!FinancialAnalyzer.isSafetyBeltMet(financialStmt)) {
|
||||
logTime(stockName, "재무 미달 조기 종료", finDuration, System.currentTimeMillis() - totalStartTime)
|
||||
@@ -258,7 +259,7 @@ object RagService {
|
||||
return@coroutineScope
|
||||
}
|
||||
|
||||
if (techSignal.compositeScore < 50) {
|
||||
if ((tradingDecision.signalModel?.compositeScore ?: 0) < 50) {
|
||||
logTime(stockName, "기술 점수 미달 조기 종료", techDuration, System.currentTimeMillis() - totalStartTime)
|
||||
result(tradingDecision.apply { decision = "HOLD"; reason = "매수 타점 미도달" }, false)
|
||||
return@coroutineScope
|
||||
@@ -282,7 +283,9 @@ object RagService {
|
||||
)
|
||||
|
||||
// 3. 검색된 내용을 하나의 문자열로 합쳐서 전달
|
||||
tradingDecision.newsContext = finalSearchResult.matches().joinToString("\n\n") {
|
||||
tradingDecision.newsContext = finalSearchResult.matches().distinct() // 중복 제거
|
||||
.take(4) // 10개에서 4개로 축소
|
||||
.joinToString("\n\n") {
|
||||
it.embedded().text()
|
||||
}
|
||||
|
||||
@@ -431,8 +434,7 @@ object RagService {
|
||||
|
||||
// 1-2. 기술 분석 및 리포트 생성 시간 측정
|
||||
val techStartTime = System.currentTimeMillis()
|
||||
val techSignal = tempDecision.analyzer?.generateComprehensiveSignal()
|
||||
val techScore100 = techSignal?.compositeScore?.toDouble() ?: 0.0
|
||||
val techScore100 = tempDecision.signalModel?.compositeScore?.toDouble() ?: 0.0
|
||||
val isOverheated = tempDecision.analyzer?.isOverheatedStock() ?: false
|
||||
tempDecision.techSummary = tempDecision.analyzer?.generateComprehensiveReport(finScore100.toInt())
|
||||
val techDuration = System.currentTimeMillis() - techStartTime
|
||||
@@ -611,6 +613,7 @@ class TradingDecision {
|
||||
var newsContext : String? = null
|
||||
var financialData : String? = null
|
||||
var analyzer : TechnicalAnalyzer? = null
|
||||
var signalModel : ScalpingSignalModel? = null
|
||||
|
||||
fun shortPossible() =
|
||||
listOf<Double>(ultraShortScore,
|
||||
|
||||
@@ -102,93 +102,119 @@ object AutoTradingManager {
|
||||
}
|
||||
}
|
||||
|
||||
val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean ->
|
||||
if (isSuccess && completeTradingDecision != null) {
|
||||
// 1. 로그 저장소에 기록 (UI에서 이걸 읽음)
|
||||
TradingLogStore.addLog(completeTradingDecision)
|
||||
// val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean ->
|
||||
// if (isSuccess && completeTradingDecision != null) {
|
||||
// // 1. 로그 저장소에 기록 (UI에서 이걸 읽음)
|
||||
// TradingLogStore.addLog(completeTradingDecision)
|
||||
//
|
||||
// println("🚀 [자동매수 실행] ${completeTradingDecision.stockName}")
|
||||
// if (completeTradingDecision.confidence < 10) {
|
||||
// addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
|
||||
// TradingLogStore.addLog(completeTradingDecision,"RETRY","분석 신뢰도 오류 인지로 재분석 대기열에 추가")
|
||||
// }else if (completeTradingDecision != null && !completeTradingDecision.stockCode.isNullOrEmpty()) {
|
||||
// var basePrice = completeTradingDecision.currentPrice
|
||||
// var stockCode = completeTradingDecision.stockCode
|
||||
// println("basePrice $basePrice")
|
||||
// val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX)
|
||||
// var maxBudget = KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX)
|
||||
// val buyWeight = KisSession.config.getValues(ConfigIndex.BUY_WEIGHT_INDEX)
|
||||
// val baseProfit = KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
|
||||
//
|
||||
// fun resultCheck(completeTradingDecision :TradingDecision) {
|
||||
// val weights = mapOf(
|
||||
// "short" to 0.2, // 초단기 점수가 낮아도 전체에 미치는 영향 감소
|
||||
// "profit" to 0.4,
|
||||
// "safe" to 0.4 // 중장기 점수 비중 강화
|
||||
// )
|
||||
//
|
||||
// val totalScore =
|
||||
// ((completeTradingDecision.shortPossible() + append) * weights["short"]!!) +
|
||||
// ((completeTradingDecision.profitPossible() + append) * weights["profit"]!!) +
|
||||
// ((completeTradingDecision.safePossible() + append) * weights["safe"]!!)
|
||||
//
|
||||
// if (totalScore >= minScore && completeTradingDecision.confidence >= MIN_CONFIDENCE) {
|
||||
// var investmentGrade = completeTradingDecision.investmentGrade ?: InvestmentGrade.LEVEL_1_SPECULATIVE
|
||||
//
|
||||
// val finalMargin = baseProfit * KisSession.config.getValues(investmentGrade.profitGuide)
|
||||
// println("🚀 [매수 진행] 토탈 스코어: ${String.format("%.1f", totalScore)} -> 종목: ${completeTradingDecision.stockCode}")
|
||||
//
|
||||
// // basePrice(현재가 혹은 지정가)를 기준으로 매수 가능 수량 산출 (최소 1주 보장)
|
||||
// val gradeRate = KisSession.config.getValues(investmentGrade.allocationRate)
|
||||
// val maxQty = (KisSession.config.getValues(ConfigIndex.MAX_COUNT_INDEX) * gradeRate).roundToInt()
|
||||
// maxBudget = maxBudget * gradeRate
|
||||
// val calculatedQty = if (basePrice > 0) {
|
||||
// (maxBudget / basePrice).toInt().coerceAtLeast(1)
|
||||
// } else {
|
||||
// 1
|
||||
// }
|
||||
// // 5. 매수 실행 (계산된 finalMargin 전달)
|
||||
// excuteTrade(
|
||||
// decision = completeTradingDecision,
|
||||
// orderQty = min(calculatedQty, maxQty).toString(),
|
||||
// profitRate1 = finalMargin,
|
||||
// investmentGrade = investmentGrade,
|
||||
// )
|
||||
//
|
||||
// } else if(totalScore >= (minScore * 0.9) && completeTradingDecision.confidence + append >= (MIN_CONFIDENCE * 0.9)) {
|
||||
// addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
|
||||
// TradingLogStore.addLog(completeTradingDecision,"RETRY","✋ [관망] 토탈 스코어[$totalScore] 또는 신뢰도[${completeTradingDecision.confidence}] 미달 이나 약간의 오차로 재분석 대기열에 추가")
|
||||
// } else {
|
||||
// TradingLogStore.addLog(completeTradingDecision,"HOLD","✋ [관망] 토탈 스코어(${String.format("%.1f[${minScore}]", totalScore)}) 또는 신뢰도 (${String.format("%.1f[${MIN_CONFIDENCE}]", completeTradingDecision.confidence)}) 미달")
|
||||
// }
|
||||
// }
|
||||
// if (completeTradingDecision?.decision?.contains("매수") == true) {
|
||||
// completeTradingDecision.decision = "BUY"
|
||||
// }
|
||||
// when (completeTradingDecision?.decision) {
|
||||
// "BUY","매수" -> {
|
||||
// append = buyWeight
|
||||
// TradingLogStore.addLog(completeTradingDecision,"BUY","[$stockCode] 매수 추천 : ${completeTradingDecision?.reason}")
|
||||
// resultCheck(completeTradingDecision)
|
||||
// }
|
||||
// "SELL" -> {
|
||||
// TradingLogStore.addLog(completeTradingDecision,"SELL","[$stockCode] 매도 추천 : ${completeTradingDecision?.reason}")
|
||||
// println("[$stockCode] 매도: ${completeTradingDecision?.reason}")
|
||||
// }
|
||||
// "HOLD" -> {
|
||||
// append = 0.0
|
||||
// TradingLogStore.addLog(completeTradingDecision,"HOLD","[$stockCode] 관망 유지 : ${completeTradingDecision?.reason}")
|
||||
// resultCheck(completeTradingDecision)
|
||||
// }
|
||||
// else -> {
|
||||
// append = 0.0
|
||||
// println("[$stockCode] ${completeTradingDecision?.decision} : ${completeTradingDecision?.reason}")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean ->
|
||||
if (isSuccess && completeTradingDecision != null) {
|
||||
val decision = completeTradingDecision
|
||||
|
||||
println("🚀 [자동매수 실행] ${completeTradingDecision.stockName}")
|
||||
if (completeTradingDecision.confidence < 10) {
|
||||
addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
|
||||
TradingLogStore.addLog(completeTradingDecision,"RETRY","분석 신뢰도 오류 인지로 재분석 대기열에 추가")
|
||||
}else if (completeTradingDecision != null && !completeTradingDecision.stockCode.isNullOrEmpty()) {
|
||||
var basePrice = completeTradingDecision.currentPrice
|
||||
var stockCode = completeTradingDecision.stockCode
|
||||
println("basePrice $basePrice")
|
||||
val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX)
|
||||
var maxBudget = KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX)
|
||||
val buyWeight = KisSession.config.getValues(ConfigIndex.BUY_WEIGHT_INDEX)
|
||||
val baseProfit = KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
|
||||
// 1. 이미 AI가 결정한 decision과 confidence를 신뢰함
|
||||
if (decision.decision == "BUY") {
|
||||
val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX)
|
||||
|
||||
fun resultCheck(completeTradingDecision :TradingDecision) {
|
||||
val weights = mapOf(
|
||||
"short" to 0.2, // 초단기 점수가 낮아도 전체에 미치는 영향 감소
|
||||
"profit" to 0.4,
|
||||
"safe" to 0.4 // 중장기 점수 비중 강화
|
||||
)
|
||||
// AI가 이미 검증한 등급을 사용 (재계산 불필요)
|
||||
val grade = decision.investmentGrade ?: InvestmentGrade.LEVEL_1_SPECULATIVE
|
||||
|
||||
val totalScore =
|
||||
((completeTradingDecision.shortPossible() + append) * weights["short"]!!) +
|
||||
((completeTradingDecision.profitPossible() + append) * weights["profit"]!!) +
|
||||
((completeTradingDecision.safePossible() + append) * weights["safe"]!!)
|
||||
// 2. 최종 매수 실행
|
||||
val gradeRate = KisSession.config.getValues(grade.allocationRate)
|
||||
val maxBudget = KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX) * gradeRate
|
||||
val calculatedQty = (maxBudget / decision.currentPrice).toInt().coerceAtLeast(1)
|
||||
|
||||
if (totalScore >= minScore && completeTradingDecision.confidence >= MIN_CONFIDENCE) {
|
||||
var investmentGrade = completeTradingDecision.investmentGrade ?: InvestmentGrade.LEVEL_1_SPECULATIVE
|
||||
|
||||
val finalMargin = baseProfit * KisSession.config.getValues(investmentGrade.profitGuide)
|
||||
println("🚀 [매수 진행] 토탈 스코어: ${String.format("%.1f", totalScore)} -> 종목: ${completeTradingDecision.stockCode}")
|
||||
|
||||
// basePrice(현재가 혹은 지정가)를 기준으로 매수 가능 수량 산출 (최소 1주 보장)
|
||||
val gradeRate = KisSession.config.getValues(investmentGrade.allocationRate)
|
||||
val maxQty = (KisSession.config.getValues(ConfigIndex.MAX_COUNT_INDEX) * gradeRate).roundToInt()
|
||||
maxBudget = maxBudget * gradeRate
|
||||
val calculatedQty = if (basePrice > 0) {
|
||||
(maxBudget / basePrice).toInt().coerceAtLeast(1)
|
||||
} else {
|
||||
1
|
||||
}
|
||||
// 5. 매수 실행 (계산된 finalMargin 전달)
|
||||
excuteTrade(
|
||||
decision = completeTradingDecision,
|
||||
orderQty = min(calculatedQty, maxQty).toString(),
|
||||
profitRate1 = finalMargin,
|
||||
investmentGrade = investmentGrade,
|
||||
)
|
||||
|
||||
} else if(totalScore >= (minScore * 0.9) && completeTradingDecision.confidence + append >= (MIN_CONFIDENCE * 0.9)) {
|
||||
addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
|
||||
TradingLogStore.addLog(completeTradingDecision,"RETRY","✋ [관망] 토탈 스코어[$totalScore] 또는 신뢰도[${completeTradingDecision.confidence}] 미달 이나 약간의 오차로 재분석 대기열에 추가")
|
||||
} else {
|
||||
TradingLogStore.addLog(completeTradingDecision,"HOLD","✋ [관망] 토탈 스코어(${String.format("%.1f[${minScore}]", totalScore)}) 또는 신뢰도 (${String.format("%.1f[${MIN_CONFIDENCE}]", completeTradingDecision.confidence)}) 미달")
|
||||
}
|
||||
}
|
||||
if (completeTradingDecision?.decision?.contains("매수") == true) {
|
||||
completeTradingDecision.decision = "BUY"
|
||||
}
|
||||
when (completeTradingDecision?.decision) {
|
||||
"BUY","매수" -> {
|
||||
append = buyWeight
|
||||
TradingLogStore.addLog(completeTradingDecision,"BUY","[$stockCode] 매수 추천 : ${completeTradingDecision?.reason}")
|
||||
resultCheck(completeTradingDecision)
|
||||
}
|
||||
"SELL" -> {
|
||||
TradingLogStore.addLog(completeTradingDecision,"SELL","[$stockCode] 매도 추천 : ${completeTradingDecision?.reason}")
|
||||
println("[$stockCode] 매도: ${completeTradingDecision?.reason}")
|
||||
}
|
||||
"HOLD" -> {
|
||||
append = 0.0
|
||||
TradingLogStore.addLog(completeTradingDecision,"HOLD","[$stockCode] 관망 유지 : ${completeTradingDecision?.reason}")
|
||||
resultCheck(completeTradingDecision)
|
||||
}
|
||||
else -> {
|
||||
append = 0.0
|
||||
println("[$stockCode] ${completeTradingDecision?.decision} : ${completeTradingDecision?.reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
excuteTrade(
|
||||
decision = decision,
|
||||
orderQty = calculatedQty.toString(),
|
||||
profitRate1 = KisSession.config.getValues(ConfigIndex.PROFIT_INDEX) * KisSession.config.getValues(grade.profitGuide),
|
||||
investmentGrade = grade
|
||||
)
|
||||
} else if (decision.confidence >= 60.0) { // 아까운 종목만 재분석
|
||||
addToReanalysis(RankingStock(decision.stockCode, decision.stockName))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
val MIN_CONFIDENCE = 60.0 // 최소 신뢰도
|
||||
var append = 0.0
|
||||
|
||||
|
||||
@@ -83,12 +83,12 @@ object LlamaServerManager {
|
||||
val cpuCores = Runtime.getRuntime().availableProcessors() // HardwareDetector.getCpuCores()와 동일
|
||||
val hasGpu = HardwareDetector.hasNvidiaGpu()
|
||||
val ratio = if (isWin) 0.5 else 0.7
|
||||
val optimalThreads = (cpuCores * ratio).toInt().coerceIn(4, 16)
|
||||
val optimalThreads = if(isWin)(cpuCores * ratio).toInt().coerceIn(4, 12) else (cpuCores * ratio).toInt().coerceIn(4, 16)
|
||||
|
||||
var optimalGpuLayers = if ((isWin && hasGpu) || isMacArm) 99 else 4
|
||||
if(HardwareDetector.getCpuName().contains("i7")) {
|
||||
optimalGpuLayers = 0
|
||||
}
|
||||
var optimalGpuLayers = 99 //if ((isWin && hasGpu) || isMacArm) 99 else 4
|
||||
// if(HardwareDetector.getCpuName().contains("i7")) {
|
||||
// optimalGpuLayers = 0
|
||||
// }
|
||||
println("🖥️ OS: $os / Arch: $arch")
|
||||
println("⚙️ 할당 스레드: $optimalThreads (Core: $cpuCores, Ratio: $ratio)")
|
||||
println("🚀 GPU 레이어: $optimalGpuLayers (NVIDIA/MacArm: ${if(optimalGpuLayers == 99) "YES" else "NO"})")
|
||||
@@ -97,12 +97,14 @@ object LlamaServerManager {
|
||||
binPath,
|
||||
"-m", modelPath,
|
||||
"--port", port.toString(),
|
||||
"-c", if (port == EMBEDDING_PORT) "512" else "8192",
|
||||
"-c", if (port == EMBEDDING_PORT) "2048" else "8192",
|
||||
"-ngl", optimalGpuLayers.toString(),
|
||||
"-t", optimalThreads.toString(),
|
||||
"--embedding",
|
||||
"--mlock", // RAM 고정으로 스왑 방지
|
||||
"--no-mmap"
|
||||
"--no-mmap",
|
||||
|
||||
|
||||
)
|
||||
if (port != EMBEDDING_PORT) { // 텍스트 생성용 모델에만 적용
|
||||
command.addAll(listOf(
|
||||
@@ -112,6 +114,11 @@ object LlamaServerManager {
|
||||
"--cont-batching"
|
||||
))
|
||||
}
|
||||
if (isWin) {
|
||||
command.addAll(listOf(
|
||||
"--numa", "distribute"
|
||||
))
|
||||
}
|
||||
scope.launch {
|
||||
try {
|
||||
val pb = ProcessBuilder(command)
|
||||
|
||||
Reference in New Issue
Block a user