...
This commit is contained in:
@@ -26,6 +26,15 @@ import service.UrlCacheManager
|
||||
import java.nio.file.Paths
|
||||
import java.time.Duration
|
||||
|
||||
interface TradingAnalyst {
|
||||
@dev.langchain4j.service.SystemMessage("""
|
||||
You are a Senior Stock Analyst.
|
||||
Analyze the data and provide a decision in JSON format.
|
||||
You must respond ONLY with a valid JSON object.
|
||||
""")
|
||||
fun analyzeStock(@dev.langchain4j.service.UserMessage prompt: String): TradingDecision
|
||||
}
|
||||
|
||||
object RagService {
|
||||
|
||||
// 임베딩 모델 (8081) 및 채팅 모델 (8080) 설정
|
||||
@@ -39,8 +48,15 @@ object RagService {
|
||||
.apiKey("unused")
|
||||
.temperature(0.0) // [중요] 0.0으로 설정하여 결정론적 응답 유도
|
||||
.timeout(Duration.ofSeconds(60))
|
||||
.frequencyPenalty(1.1)
|
||||
.maxTokens(500) // 👈 루프 방지를 위해 반드시 짧게 제한!
|
||||
// 1.x 버전에서는 responseFormat이 아래처럼 바뀔 수 있으니 체크하세요
|
||||
.responseFormat("json_object")
|
||||
.build()
|
||||
|
||||
private val analyst = dev.langchain4j.service.AiServices.builder(TradingAnalyst::class.java)
|
||||
.chatModel(chatModel)
|
||||
.build()
|
||||
|
||||
private val embeddingStore: LuceneEmbeddingStore by lazy {
|
||||
val path = Paths.get("db/lucene_idx")
|
||||
@@ -63,6 +79,7 @@ object RagService {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 텍스트를 임베딩하여 H2 DB에 저장합니다.
|
||||
*/
|
||||
@@ -255,52 +272,31 @@ object RagService {
|
||||
tempDecision: TradingDecision
|
||||
): TradingDecision? {
|
||||
val prompt = """
|
||||
<|begin_of_text|><|start_header_id|>system<|end_header_id|>
|
||||
당신은 정량적 수치와 정성적 뉴스를 통합 분석하는 'AI 수석 애널리스트'입니다.
|
||||
시스템이 계산한 지표 점수와 실제 재무제표 요약본을 바탕으로 최종 매매 전략을 수립하십시오.
|
||||
# Task: Senior AI Investment Analyst
|
||||
Analyze the stock '$stockName' and determine the final trading decision based on the data below.
|
||||
|
||||
[종목 정보]
|
||||
- 종목명: $stockName
|
||||
# Data
|
||||
1. System Scores: Scalping(${scores.ultraShort}), Short(${scores.shortTerm}), Mid(${scores.midTerm}), Long(${scores.longTerm})
|
||||
2. Financials: Operating Profit ${if(financialStmt.isOperatingProfitPositive) "PROFIT" else "LOSS"} (Growth: ${"%.2f".format(financialStmt.operatingProfitGrowth)}%), ROE: ${"%.2f".format(financialStmt.roe)}%, Debt: ${"%.2f".format(financialStmt.debtRatio)}%
|
||||
3. News Context: ${tempDecision.newsContext?.take(400)} // 👈 뉴스 길이를 물리적으로 제한
|
||||
|
||||
[1. 시스템 산출 스코어 (0-100)]
|
||||
- 초단기(Scalping): ${scores.ultraShort}
|
||||
- 단기(Daily): ${scores.shortTerm}
|
||||
- 중기(Weekly): ${scores.midTerm}
|
||||
- 장기(Monthly): ${scores.longTerm}
|
||||
# Constraints
|
||||
1. 모든 점수와 confidence는 0에서 100 사이의 **정수(Integer)**로만 작성하십시오.
|
||||
- Match Financials with News: If profit is negative but news is hyped, stay CAUTIOUS (HOLD).
|
||||
- Synchronization: High scalping score + positive news momentum = Higher BUY confidence.
|
||||
- Output: Response ONLY in valid JSON format. No extra text.
|
||||
|
||||
[2. 핵심 재무제표 요약]
|
||||
- 영업이익: ${if(financialStmt.isOperatingProfitPositive) "흑자" else "적자"} (성장률: ${"%.2f".format(financialStmt.operatingProfitGrowth)}%)
|
||||
- 당기순이익: ${if(financialStmt.isNetIncomePositive) "흑자" else "적자"} (성장률: ${"%.2f".format(financialStmt.netIncomeGrowth)}%)
|
||||
- 수익성(ROE): ${"%.2f".format(financialStmt.roe)}%
|
||||
- 안정성(부채비율): ${"%.2f".format(financialStmt.debtRatio)}%
|
||||
- 유동성(당좌비율): ${"%.2f".format(financialStmt.quickRatio)}%
|
||||
|
||||
[3. 시장 심리 및 뉴스 컨텍스트]
|
||||
${tempDecision.newsContext}
|
||||
|
||||
[분석 지침]
|
||||
1. **재무-뉴스 정합성**: 재무제표상 영업이익이 적자임에도 뉴스가 장기적 장밋빛 전망만 내놓는다면 '신중(HOLD)' 의견을 제시하십시오.
|
||||
2. **기술-심리 동기화**: 초단기 점수가 높고 뉴스에서 수급 급증 키워드가 포착되면 'BUY' 신뢰도를 높이십시오.
|
||||
3. **종합 결정**: 모든 수치와 컨텍스트를 고려하여 최종 Decision을 내리고, 그 근거를 핵심만 기술하십시오.
|
||||
|
||||
[응답 지침]
|
||||
- JSON 데이터만 출력하십시오. 설명이나 서론은 생략합니다.
|
||||
- 반드시 아래 형식을 엄격히 준수하십시오.
|
||||
|
||||
{
|
||||
"ultraShortScore": ${scores.ultraShort},
|
||||
"shortTermScore": ${scores.shortTerm},
|
||||
"midTermScore": ${scores.midTerm},
|
||||
"longTermScore": ${scores.longTerm},
|
||||
"decision": "BUY" | "SELL" | "HOLD",
|
||||
"reason": "재무 수치와 뉴스 심리를 대조한 최종 결론 한 줄",
|
||||
"confidence": 0~100
|
||||
}
|
||||
<|eot_id|>
|
||||
<|start_header_id|>user<|end_header_id|>
|
||||
상기 데이터를 통합 분석하여 최종 리포트를 생성하십시오.
|
||||
<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||
""".trimIndent()
|
||||
# Output JSON Format (Reason must be in Korean)
|
||||
{
|
||||
"ultraShortScore": ${scores.ultraShort},
|
||||
"shortTermScore": ${scores.shortTerm},
|
||||
"midTermScore": ${scores.midTerm},
|
||||
"longTermScore": ${scores.longTerm},
|
||||
"decision": "BUY/SELL/HOLD",
|
||||
"reason": "재무와 뉴스를 대조한 분석 결과 (한국어)",
|
||||
"confidence": 0~100
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val response = chatModel.chat(UserMessage.from(prompt))
|
||||
val rawResponse = response.aiMessage().text()
|
||||
|
||||
Reference in New Issue
Block a user