..
This commit is contained in:
parent
d5ab55b336
commit
55b82c23af
@ -22,7 +22,8 @@ data class StockHolding(
|
|||||||
val pchs_avg_pric: String = "0", // 매입평균가
|
val pchs_avg_pric: String = "0", // 매입평균가
|
||||||
val prpr: String = "0", // 현재가
|
val prpr: String = "0", // 현재가
|
||||||
val evlu_pfls_rt: String = "0.0", // 평가손익률
|
val evlu_pfls_rt: String = "0.0", // 평가손익률
|
||||||
val evlu_amt: String = "0" // 평가금액
|
val evlu_amt: String = "0" , // 평가금액
|
||||||
|
val ord_psbl_qty : String = "0",
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -148,7 +149,9 @@ data class UnifiedStockHolding(
|
|||||||
val currentPrice: String, // 현재가
|
val currentPrice: String, // 현재가
|
||||||
val profitRate: String, // 수익률
|
val profitRate: String, // 수익률
|
||||||
val evalAmount: String, // 평가금액
|
val evalAmount: String, // 평가금액
|
||||||
val isDomestic: Boolean // 국내/해외 구분
|
val isDomestic: Boolean, // 국내/해외 구분
|
||||||
|
val availOrderCount : String,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@ -71,10 +71,11 @@ object KisTradeService {
|
|||||||
combinedHoldings.add(UnifiedStockHolding(
|
combinedHoldings.add(UnifiedStockHolding(
|
||||||
code = it.pdno, name = it.prdt_name, quantity = it.hldg_qty,
|
code = it.pdno, name = it.prdt_name, quantity = it.hldg_qty,
|
||||||
avgPrice = it.pchs_avg_pric, currentPrice = it.prpr,
|
avgPrice = it.pchs_avg_pric, currentPrice = it.prpr,
|
||||||
profitRate = it.evlu_pfls_rt, evalAmount = it.evlu_amt, isDomestic = true
|
profitRate = it.evlu_pfls_rt, evalAmount = it.evlu_amt, isDomestic = true,
|
||||||
|
availOrderCount = it.ord_psbl_qty
|
||||||
).apply {
|
).apply {
|
||||||
if (it.hldg_qty.toLong() > 0) {
|
if (it.hldg_qty.toLong() > 0) {
|
||||||
println("보유 종목 : ${it.prdt_name} , 수량 : ${it.hldg_qty}")
|
// println("보유 종목 : ${it.prdt_name} , 수량 : ${it.hldg_qty}")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -84,7 +85,8 @@ object KisTradeService {
|
|||||||
combinedHoldings.add(UnifiedStockHolding(
|
combinedHoldings.add(UnifiedStockHolding(
|
||||||
code = it.pdno, name = it.prdt_name, quantity = it.hldg_qty,
|
code = it.pdno, name = it.prdt_name, quantity = it.hldg_qty,
|
||||||
avgPrice = it.pchs_avg_pric, currentPrice = it.prpr,
|
avgPrice = it.pchs_avg_pric, currentPrice = it.prpr,
|
||||||
profitRate = it.evlu_pfls_rt, evalAmount = it.evlu_amt, isDomestic = false
|
profitRate = it.evlu_pfls_rt, evalAmount = it.evlu_amt, isDomestic = false,
|
||||||
|
availOrderCount = it.ord_psbl_qty
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -203,50 +203,50 @@ object RagService {
|
|||||||
/**
|
/**
|
||||||
* 질문과 가장 유사한 정보를 H2에서 검색하여 AI 답변을 생성합니다.
|
* 질문과 가장 유사한 정보를 H2에서 검색하여 AI 답변을 생성합니다.
|
||||||
*/
|
*/
|
||||||
fun askWithContext(question: String,
|
// fun askWithContext(question: String,
|
||||||
corpInfo: String,
|
// corpInfo: String,
|
||||||
financialData: String,
|
// financialData: String,
|
||||||
days : List<CandleData>,
|
// days : List<CandleData>,
|
||||||
weeks : List<CandleData>,
|
// weeks : List<CandleData>,
|
||||||
monthly : List<CandleData>): String {
|
// monthly : List<CandleData>): String {
|
||||||
val questionEmbedding = embeddingModel.embed(question).content()
|
// val questionEmbedding = embeddingModel.embed(question).content()
|
||||||
val searchResult = embeddingStore.search(
|
// val searchResult = embeddingStore.search(
|
||||||
EmbeddingSearchRequest.builder()
|
// EmbeddingSearchRequest.builder()
|
||||||
.queryEmbedding(questionEmbedding)
|
// .queryEmbedding(questionEmbedding)
|
||||||
.maxResults(5)
|
// .maxResults(5)
|
||||||
.build()
|
// .build()
|
||||||
)
|
// )
|
||||||
val newsContext = searchResult.matches().joinToString("\n") { it.embedded().text() }
|
// val newsContext = searchResult.matches().joinToString("\n") { it.embedded().text() }
|
||||||
|
//
|
||||||
// 2. 종합 분석 프롬프트 구성
|
// // 2. 종합 분석 프롬프트 구성
|
||||||
val finalPrompt = """
|
// val finalPrompt = """
|
||||||
<|begin_of_text|><|start_header_id|>system<|end_header_id|>
|
// <|begin_of_text|><|start_header_id|>system<|end_header_id|>
|
||||||
당신은 뉴스(심리), 재무(본질), 차트(추세)를 통합 분석하는 'AI 수석 애널리스트'입니다.
|
// 당신은 뉴스(심리), 재무(본질), 차트(추세)를 통합 분석하는 'AI 수석 애널리스트'입니다.
|
||||||
제공된 데이터를 바탕으로 아래 형식을 엄격히 지켜 분석 리포트를 작성하세요.
|
// 제공된 데이터를 바탕으로 아래 형식을 엄격히 지켜 분석 리포트를 작성하세요.
|
||||||
|
//
|
||||||
[데이터 세트]
|
// [데이터 세트]
|
||||||
1. 기업 기본 정보: $corpInfo
|
// 1. 기업 기본 정보: $corpInfo
|
||||||
2. 재무 성장성: $financialData
|
// 2. 재무 성장성: $financialData
|
||||||
3. 기술적 추세: ${monthly}, ${weeks}, ${days}
|
// 3. 기술적 추세: ${monthly}, ${weeks}, ${days}
|
||||||
4. 최신 이슈(뉴스): $newsContext
|
// 4. 최신 이슈(뉴스): $newsContext
|
||||||
|
//
|
||||||
[분석 요청 사항]
|
// [분석 요청 사항]
|
||||||
1. **업계 상황**: 해당 종목이 속한 업종의 현재 전체적인 흐름을 먼저 정리하세요.
|
// 1. **업계 상황**: 해당 종목이 속한 업종의 현재 전체적인 흐름을 먼저 정리하세요.
|
||||||
2. **종목 이슈 분석**: 뉴스에서 포착된 핵심 키워드와 시장의 반응을 요약하세요.
|
// 2. **종목 이슈 분석**: 뉴스에서 포착된 핵심 키워드와 시장의 반응을 요약하세요.
|
||||||
3. **장기/단기 전략**:
|
// 3. **장기/단기 전략**:
|
||||||
- 장기(재무/월봉 기반): 추천 혹은 비추천 사유
|
// - 장기(재무/월봉 기반): 추천 혹은 비추천 사유
|
||||||
- 단기(뉴스/일봉 기반): 추천 혹은 비추천 사유
|
// - 단기(뉴스/일봉 기반): 추천 혹은 비추천 사유
|
||||||
4. **최종 결론**: '매수/관망/매도' 의견과 그에 따른 근거를 단호하게 제시하세요.
|
// 4. **최종 결론**: '매수/관망/매도' 의견과 그에 따른 근거를 단호하게 제시하세요.
|
||||||
<|eot_id|>
|
// <|eot_id|>
|
||||||
<|start_header_id|>user<|end_header_id|>
|
// <|start_header_id|>user<|end_header_id|>
|
||||||
질문: $question
|
// 질문: $question
|
||||||
<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
// <|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||||
""".trimIndent()
|
// """.trimIndent()
|
||||||
|
//
|
||||||
val response = chatModel.chat(UserMessage.from(finalPrompt))
|
// val response = chatModel.chat(UserMessage.from(finalPrompt))
|
||||||
// println(response)
|
//// println(response)
|
||||||
return response.aiMessage().text()
|
// return response.aiMessage().text()
|
||||||
}
|
// }
|
||||||
|
|
||||||
suspend fun decideTrading(
|
suspend fun decideTrading(
|
||||||
stockName: String,
|
stockName: String,
|
||||||
@ -342,6 +342,8 @@ object RagService {
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class TradingDecision {
|
class TradingDecision {
|
||||||
var corpName : String = ""
|
var corpName : String = ""
|
||||||
|
|||||||
@ -89,13 +89,13 @@ object AutoTradingManager {
|
|||||||
// val balance = tradeService.fetchIntegratedBalance().getOrNull()
|
// val balance = tradeService.fetchIntegratedBalance().getOrNull()
|
||||||
val holding = balance?.holdings?.find { it.code == item.code }
|
val holding = balance?.holdings?.find { it.code == item.code }
|
||||||
|
|
||||||
if (holding != null && holding.quantity.toInt() > 0) {
|
if (holding != null && holding.quantity.toInt() > 0 && holding.availOrderCount.toInt() > 0) {
|
||||||
var final = MarketUtil.roundToTickSize(item.targetPrice)
|
var final = MarketUtil.roundToTickSize(item.targetPrice)
|
||||||
println("🔄 [재주문] ${item.name} (${item.code}) ${item.orderedPrice} ${final} 전날 미체결 매도 건 재주문 시도")
|
println("🔄 [재주문] ${item.name} (${item.code}) ${item.orderedPrice} ${final} 전날 미체결 매도 건 재주문 시도")
|
||||||
// 3. 기존 목표가(targetPrice)로 다시 매도 주문 전송
|
// 3. 기존 목표가(targetPrice)로 다시 매도 주문 전송
|
||||||
tradeService.postOrder(
|
tradeService.postOrder(
|
||||||
stockCode = item.code,
|
stockCode = item.code,
|
||||||
qty = item.quantity.toString(),
|
qty = min(item.quantity,holding.availOrderCount.toInt()).toString(),
|
||||||
price = final.toLong().toString(),
|
price = final.toLong().toString(),
|
||||||
isBuy = false
|
isBuy = false
|
||||||
).onSuccess { newOrderNo ->
|
).onSuccess { newOrderNo ->
|
||||||
@ -125,12 +125,12 @@ object AutoTradingManager {
|
|||||||
println("⏱️ [Cycle Start] ${LocalTime.now()}")
|
println("⏱️ [Cycle Start] ${LocalTime.now()}")
|
||||||
|
|
||||||
// [프로세스 1] 장 마감 및 잔고 체크
|
// [프로세스 1] 장 마감 및 잔고 체크
|
||||||
// val now = LocalTime.now(ZoneId.of("Asia/Seoul"))
|
val now = LocalTime.now(ZoneId.of("Asia/Seoul"))
|
||||||
// //&& now.isBefore(LocalTime.of(15, 30))
|
//&& now.isBefore(LocalTime.of(15, 30))
|
||||||
// if (now.isAfter(LocalTime.of(15, 30)) ) {
|
if (now.isAfter(LocalTime.of(15, 15)) ) {
|
||||||
//// executeClosingLiquidation(tradeService)
|
executeClosingLiquidation(tradeService)
|
||||||
// return@withTimeout
|
return@withTimeout
|
||||||
// }
|
}
|
||||||
|
|
||||||
val balance = tradeService.fetchIntegratedBalance().getOrNull()
|
val balance = tradeService.fetchIntegratedBalance().getOrNull()
|
||||||
|
|
||||||
@ -142,7 +142,18 @@ object AutoTradingManager {
|
|||||||
if (remainingCandidates.isEmpty()) {
|
if (remainingCandidates.isEmpty()) {
|
||||||
val candidates: MutableList<RankingStock> = fetchCandidates(tradeService).apply {
|
val candidates: MutableList<RankingStock> = fetchCandidates(tradeService).apply {
|
||||||
println("후보군 총 개수 : $size")
|
println("후보군 총 개수 : $size")
|
||||||
}.filter { !it.name.contains("호스팩", true) }
|
}.filter {
|
||||||
|
val rate = it.prdy_ctrt.toDouble()
|
||||||
|
val corpInfo = DartCodeManager.getCorpCode(it.code)
|
||||||
|
val isOk = (rate > 0 && rate < 15) || (rate < 0 && rate > -15)
|
||||||
|
// if (isOk) {println("${it.name} : ${it.prdy_ctrt}")}
|
||||||
|
if (corpInfo?.cName.isNullOrEmpty()) {
|
||||||
|
false
|
||||||
|
}else {
|
||||||
|
isOk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { !it.name.contains("호스팩", true) }
|
||||||
.sortedBy { (it.prdy_ctrt.toDoubleOrNull() ?: 0.0) }
|
.sortedBy { (it.prdy_ctrt.toDoubleOrNull() ?: 0.0) }
|
||||||
.toMutableList()
|
.toMutableList()
|
||||||
|
|
||||||
@ -299,25 +310,25 @@ object AutoTradingManager {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// private suspend fun executeClosingLiquidation(tradeService: KisTradeService) {
|
private suspend fun executeClosingLiquidation(tradeService: KisTradeService) {
|
||||||
// val activeTrades = DatabaseFactory.findAllMonitoringTrades()
|
val activeTrades = DatabaseFactory.findAllMonitoringTrades()
|
||||||
// val balanceResult = tradeService.fetchIntegratedBalance().getOrNull()
|
val balanceResult = tradeService.fetchIntegratedBalance().getOrNull()
|
||||||
// val realHoldings = balanceResult?.holdings?.associateBy { it.code } ?: emptyMap()
|
val realHoldings = balanceResult?.holdings?.associateBy { it.code } ?: emptyMap()
|
||||||
//
|
|
||||||
// activeTrades.forEach { trade ->
|
activeTrades.forEach { trade ->
|
||||||
// try {
|
try {
|
||||||
// if (!realHoldings.containsKey(trade.code)) {
|
if (!realHoldings.containsKey(trade.code)) {
|
||||||
// DatabaseFactory.updateStatusAndOrderNo(trade.id!!, TradeStatus.COMPLETED)
|
DatabaseFactory.updateStatusAndOrderNo(trade.id!!, TradeStatus.EXPIRED)
|
||||||
// return@forEach
|
return@forEach
|
||||||
// }
|
}
|
||||||
// // 마감 정리 로직 (필요 시 주석 해제하여 사용)
|
// 마감 정리 로직 (필요 시 주석 해제하여 사용)
|
||||||
// println("📢 [마감 정리 체크] ${trade.name}")
|
println("📢 [마감 정리 체크] ${trade.name}")
|
||||||
// } catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// println("⚠️ [마감 에러] ${trade.name}: ${e.message}")
|
println("⚠️ [마감 에러] ${trade.name}: ${e.message}")
|
||||||
// }
|
}
|
||||||
// delay(200)
|
delay(200)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
fun stopDiscovery() {
|
fun stopDiscovery() {
|
||||||
discoveryJob?.cancel()
|
discoveryJob?.cancel()
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import network.KisTradeService
|
|||||||
import service.AutoTradingManager
|
import service.AutoTradingManager
|
||||||
import util.MarketUtil
|
import util.MarketUtil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
enum class InvestmentGrade(
|
enum class InvestmentGrade(
|
||||||
val displayName: String,
|
val displayName: String,
|
||||||
@ -259,7 +260,7 @@ fun IntegratedOrderSection(
|
|||||||
basePrice = completeTradingDecision.currentPrice
|
basePrice = completeTradingDecision.currentPrice
|
||||||
println("basePrice $basePrice")
|
println("basePrice $basePrice")
|
||||||
val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX)
|
val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX)
|
||||||
val maxBudget = KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX)
|
var maxBudget = KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX)
|
||||||
val buyWeight = KisSession.config.getValues(ConfigIndex.BUY_WEIGHT_INDEX)
|
val buyWeight = KisSession.config.getValues(ConfigIndex.BUY_WEIGHT_INDEX)
|
||||||
val baseProfit = KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
|
val baseProfit = KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
|
||||||
|
|
||||||
@ -291,7 +292,9 @@ fun IntegratedOrderSection(
|
|||||||
println("🚀 [매수 진행] 토탈 스코어: ${String.format("%.1f", totalScore)} -> 종목: ${completeTradingDecision.stockCode}")
|
println("🚀 [매수 진행] 토탈 스코어: ${String.format("%.1f", totalScore)} -> 종목: ${completeTradingDecision.stockCode}")
|
||||||
|
|
||||||
// basePrice(현재가 혹은 지정가)를 기준으로 매수 가능 수량 산출 (최소 1주 보장)
|
// basePrice(현재가 혹은 지정가)를 기준으로 매수 가능 수량 산출 (최소 1주 보장)
|
||||||
|
val gradeRate = (1.0 - (investmentGrade.ordinal * 0.1))
|
||||||
|
val maxQty = (KisSession.config.getValues(ConfigIndex.MAX_COUNT_INDEX) * gradeRate).roundToInt()
|
||||||
|
maxBudget = maxBudget * gradeRate
|
||||||
val calculatedQty = if (basePrice > 0) {
|
val calculatedQty = if (basePrice > 0) {
|
||||||
(maxBudget / basePrice).toInt().coerceAtLeast(1)
|
(maxBudget / basePrice).toInt().coerceAtLeast(1)
|
||||||
} else {
|
} else {
|
||||||
@ -300,7 +303,7 @@ fun IntegratedOrderSection(
|
|||||||
// 5. 매수 실행 (계산된 finalMargin 전달)
|
// 5. 매수 실행 (계산된 finalMargin 전달)
|
||||||
excuteTrade(
|
excuteTrade(
|
||||||
willEnableAutoSell = true,
|
willEnableAutoSell = true,
|
||||||
orderQty = min(calculatedQty, KisSession.config.getValues(ConfigIndex.MAX_COUNT_INDEX).toInt()).toString(),
|
orderQty = min(calculatedQty, maxQty).toString(),
|
||||||
profitRate1 = finalMargin,
|
profitRate1 = finalMargin,
|
||||||
investmentGrade = investmentGrade,
|
investmentGrade = investmentGrade,
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user