...
This commit is contained in:
parent
ad5e7e0ccb
commit
904e3011c9
@ -66,7 +66,12 @@ enum class RankingType(
|
|||||||
mapOf("FID_RANK_SORT_CLS_CODE" to "2", "FID_TRGT_CLS_CODE" to "11111111")),
|
mapOf("FID_RANK_SORT_CLS_CODE" to "2", "FID_TRGT_CLS_CODE" to "11111111")),
|
||||||
VALUE("거래대금순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank",
|
VALUE("거래대금순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank",
|
||||||
mapOf("FID_RANK_SORT_CLS_CODE" to "3", "FID_TRGT_CLS_CODE" to "11111111")),
|
mapOf("FID_RANK_SORT_CLS_CODE" to "3", "FID_TRGT_CLS_CODE" to "11111111")),
|
||||||
|
VOLUME0("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank",
|
||||||
|
mapOf("FID_RANK_SORT_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "11111111")),
|
||||||
|
VOLUME1("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank",
|
||||||
|
mapOf("FID_RANK_SORT_CLS_CODE" to "1", "FID_TRGT_CLS_CODE" to "11111111")),
|
||||||
|
VOLUME4("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank",
|
||||||
|
mapOf("FID_RANK_SORT_CLS_CODE" to "4", "FID_TRGT_CLS_CODE" to "11111111")),
|
||||||
// [4] 기타 랭킹 (예상체결만 FID_MK_OP_CLS_CODE 필요)
|
// [4] 기타 랭킹 (예상체결만 FID_MK_OP_CLS_CODE 필요)
|
||||||
EXPECTED_RISE("예상상승", "FHPST01820000", "20182", "/uapi/domestic-stock/v1/ranking/exp-trans-updown",
|
EXPECTED_RISE("예상상승", "FHPST01820000", "20182", "/uapi/domestic-stock/v1/ranking/exp-trans-updown",
|
||||||
mapOf("FID_MK_OP_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "11111111")),
|
mapOf("FID_MK_OP_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "11111111")),
|
||||||
|
|||||||
@ -131,12 +131,13 @@ object RagService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun processStock(technicalAnalyzer: TechnicalAnalyzer,stockName: String,stockCode: String,result : TradingDecisionCallback) {
|
suspend fun processStock(currentPrice : Double , technicalAnalyzer: TechnicalAnalyzer,stockName: String,stockCode: String,result : TradingDecisionCallback) {
|
||||||
// 1. 10분간의 데이터 가져오기 (API 호출)
|
// 1. 10분간의 데이터 가져오기 (API 호출)
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
try {
|
try {
|
||||||
var tradingDecision: TradingDecision = TradingDecision()
|
var tradingDecision: TradingDecision = TradingDecision()
|
||||||
tradingDecision.stockCode = stockCode
|
tradingDecision.stockCode = stockCode
|
||||||
|
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
|
||||||
|
|||||||
@ -49,7 +49,7 @@ object AutoTradingManager {
|
|||||||
|
|
||||||
fun isRunning(): Boolean = discoveryJob?.isActive == true
|
fun isRunning(): Boolean = discoveryJob?.isActive == true
|
||||||
private var remainingCandidates = mutableListOf<RankingStock>()
|
private var remainingCandidates = mutableListOf<RankingStock>()
|
||||||
// private val processedCodes = mutableSetOf<String>() // 중복 처리 방지용 (선택 사항)
|
// private val processedCodes = mutableSetOf<String>() // 중복 처리 방지용 (선택 사항)
|
||||||
private val reanalysisList = mutableListOf<RankingStock>()
|
private val reanalysisList = mutableListOf<RankingStock>()
|
||||||
private val retryCountMap = mutableMapOf<String, Int>()
|
private val retryCountMap = mutableMapOf<String, Int>()
|
||||||
/**
|
/**
|
||||||
@ -102,16 +102,14 @@ 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 { (it.prdy_ctrt.toDoubleOrNull() ?: 0.0) in MIN_RISE_RATE..MAX_RISE_RATE }
|
|
||||||
.filter { it.code !in myHoldings && it.code !in pendingStocks }
|
|
||||||
.filter { !it.name.contains("호스팩", true) }
|
|
||||||
.sortedBy { (it.prdy_ctrt.toDoubleOrNull() ?: 0.0) }
|
.sortedBy { (it.prdy_ctrt.toDoubleOrNull() ?: 0.0) }
|
||||||
.toMutableList()
|
.toMutableList()
|
||||||
|
|
||||||
if (reanalysisList.isNotEmpty()) {
|
if (reanalysisList.isNotEmpty()) {
|
||||||
candidates.addAll(reanalysisList)
|
candidates.addAll(reanalysisList)
|
||||||
}
|
}
|
||||||
remainingCandidates.addAll(candidates.distinctBy { it.code })
|
remainingCandidates.addAll(candidates.filter { it.code !in myHoldings && it.code !in pendingStocks }.distinctBy { it.code })
|
||||||
} else {
|
} else {
|
||||||
println("미확인 데이터 ${remainingCandidates.size}")
|
println("미확인 데이터 ${remainingCandidates.size}")
|
||||||
}
|
}
|
||||||
@ -148,7 +146,7 @@ object AutoTradingManager {
|
|||||||
delay(5000)
|
delay(5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForNextCycle(1)
|
waitForNextCycle(0.5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,7 +198,7 @@ object AutoTradingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
RagService.processStock(analyzer, stock.name, stock.code) { decision, isSuccess ->
|
RagService.processStock(currentPrice,analyzer, stock.name, stock.code) { decision, isSuccess ->
|
||||||
callback(decision?.apply { this.currentPrice = currentPrice }, isSuccess)
|
callback(decision?.apply { this.currentPrice = currentPrice }, isSuccess)
|
||||||
}
|
}
|
||||||
println("✅ [분석 종료] ${stock.name} (${LocalTime.now()})")
|
println("✅ [분석 종료] ${stock.name} (${LocalTime.now()})")
|
||||||
@ -217,6 +215,9 @@ object AutoTradingManager {
|
|||||||
// async { tradeService.fetchMarketRanking(RankingType.VOLUME1, true).getOrDefault(emptyList()) },
|
// async { tradeService.fetchMarketRanking(RankingType.VOLUME1, true).getOrDefault(emptyList()) },
|
||||||
// async { tradeService.fetchMarketRanking(RankingType.VOLUME0, true).getOrDefault(emptyList()) },
|
// async { tradeService.fetchMarketRanking(RankingType.VOLUME0, true).getOrDefault(emptyList()) },
|
||||||
async { tradeService.fetchMarketRanking(RankingType.VOLUME, true).getOrDefault(emptyList()) },
|
async { tradeService.fetchMarketRanking(RankingType.VOLUME, true).getOrDefault(emptyList()) },
|
||||||
|
async { tradeService.fetchMarketRanking(RankingType.VOLUME0, true).getOrDefault(emptyList()) },
|
||||||
|
async { tradeService.fetchMarketRanking(RankingType.VOLUME1, true).getOrDefault(emptyList()) },
|
||||||
|
async { tradeService.fetchMarketRanking(RankingType.VOLUME4, true).getOrDefault(emptyList()) },
|
||||||
async { tradeService.fetchMarketRanking(RankingType.RISE, true).getOrDefault(emptyList()) },
|
async { tradeService.fetchMarketRanking(RankingType.RISE, true).getOrDefault(emptyList()) },
|
||||||
async { tradeService.fetchMarketRanking(RankingType.FALL, true).getOrDefault(emptyList()) },
|
async { tradeService.fetchMarketRanking(RankingType.FALL, true).getOrDefault(emptyList()) },
|
||||||
// async { tradeService.fetchMarketRanking(RankingType.RISE2, true).getOrDefault(emptyList()) },
|
// async { tradeService.fetchMarketRanking(RankingType.RISE2, true).getOrDefault(emptyList()) },
|
||||||
@ -236,12 +237,13 @@ object AutoTradingManager {
|
|||||||
startAutoDiscoveryLoop(tradeService, callback)
|
startAutoDiscoveryLoop(tradeService, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun waitForNextCycle(minutes: Int) {
|
private suspend fun waitForNextCycle(minutes: Double) {
|
||||||
println("💤 대기 모드 진입...")
|
println("💤 대기 모드 진입...")
|
||||||
val endWait = System.currentTimeMillis() + (minutes * 60 * 1000L)
|
val endWait = System.currentTimeMillis() + (minutes * 60 * 1000L)
|
||||||
while (System.currentTimeMillis() < endWait && isRunning()) {
|
while (System.currentTimeMillis() < endWait && isRunning()) {
|
||||||
lastTickTime.set(System.currentTimeMillis()) // 대기 중에도 Watchdog에 생존 신고
|
lastTickTime.set(System.currentTimeMillis()) // 대기 중에도 Watchdog에 생존 신고
|
||||||
delay(10000)
|
println("💤 대기 모드 상태 확인...")
|
||||||
|
delay(5000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,9 +275,9 @@ object AutoTradingManager {
|
|||||||
println("🛑 [AutoTrading] 자동 발굴 중단됨")
|
println("🛑 [AutoTrading] 자동 발굴 중단됨")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addStock(technicalAnalyzer : TechnicalAnalyzer,stockName: String, stockCode: String, result: TradingDecisionCallback) {
|
fun addStock(currentPrice : Double , technicalAnalyzer : TechnicalAnalyzer,stockName: String, stockCode: String, result: TradingDecisionCallback) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
RagService.processStock(technicalAnalyzer,stockName, stockCode, result)
|
RagService.processStock(currentPrice,technicalAnalyzer,stockName, stockCode, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -68,7 +68,7 @@ fun AiAnalysisView(technicalAnalyzer: TechnicalAnalyzer,stockCode:String,stockNa
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
isAnalyzing = true
|
isAnalyzing = true
|
||||||
try {
|
try {
|
||||||
AutoTradingManager.addStock(technicalAnalyzer,stockName,stockCode) { decision,success ->
|
AutoTradingManager.addStock(currentPrice.replace(",","").toDouble(),technicalAnalyzer,stockName,stockCode) { decision,success ->
|
||||||
aiOpinion = decision.toString()
|
aiOpinion = decision.toString()
|
||||||
isAnalyzing = !success
|
isAnalyzing = !success
|
||||||
tradingDecisionCallback.invoke(decision,success)
|
tradingDecisionCallback.invoke(decision,success)
|
||||||
|
|||||||
@ -192,12 +192,12 @@ fun IntegratedOrderSection(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
val tickSize = MarketUtil.getTickSize(basePrice)
|
val tickSize = MarketUtil.getTickSize(basePrice)
|
||||||
val oneTickLowerPrice = basePrice - (tickSize * when(investmentGrade) {
|
val oneTickLowerPrice = basePrice - (tickSize * when(investmentGrade) {
|
||||||
InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> 1
|
InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> 0
|
||||||
InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND -> 1
|
InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND -> 1
|
||||||
InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND -> 2
|
InvestmentGrade.LEVEL_3_CAUTIOUS_RECOMMEND -> 2
|
||||||
InvestmentGrade.LEVEL_2_HIGH_RISK -> 2
|
InvestmentGrade.LEVEL_2_HIGH_RISK -> 2
|
||||||
InvestmentGrade.LEVEL_1_SPECULATIVE -> 4
|
InvestmentGrade.LEVEL_1_SPECULATIVE -> 3
|
||||||
else -> 4
|
else -> 3
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2. 주문 가격 설정 (직접 입력값이 없으면 한 틱 낮은 가격 사용)
|
// 2. 주문 가격 설정 (직접 입력값이 없으면 한 틱 낮은 가격 사용)
|
||||||
@ -255,6 +255,7 @@ fun IntegratedOrderSection(
|
|||||||
var append = 0.0
|
var append = 0.0
|
||||||
if (completeTradingDecision != null &&
|
if (completeTradingDecision != null &&
|
||||||
completeTradingDecision.stockCode.equals(stockCode)) {
|
completeTradingDecision.stockCode.equals(stockCode)) {
|
||||||
|
basePrice = completeTradingDecision.currentPrice
|
||||||
println("basePrice $basePrice")
|
println("basePrice $basePrice")
|
||||||
fun resultCheck(completeTradingDecision :TradingDecision) {
|
fun resultCheck(completeTradingDecision :TradingDecision) {
|
||||||
val weights = mapOf(
|
val weights = mapOf(
|
||||||
@ -264,14 +265,13 @@ fun IntegratedOrderSection(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val totalScore =
|
val totalScore =
|
||||||
(completeTradingDecision.shortPossible() * weights["short"]!!) +
|
((completeTradingDecision.shortPossible() + append) * weights["short"]!!) +
|
||||||
(completeTradingDecision.profitPossible() * weights["profit"]!!) +
|
((completeTradingDecision.profitPossible() + append) * weights["profit"]!!) +
|
||||||
(completeTradingDecision.safePossible() * weights["safe"]!!)
|
((completeTradingDecision.safePossible() + append) * weights["safe"]!!)
|
||||||
|
|
||||||
if (totalScore >= MIN_PURCHASE_SCORE && completeTradingDecision.confidence >= MIN_CONFIDENCE) {
|
if (totalScore >= MIN_PURCHASE_SCORE && completeTradingDecision.confidence >= MIN_CONFIDENCE) {
|
||||||
var investmentGrade : InvestmentGrade = getInvestmentGrade(completeTradingDecision,totalScore, completeTradingDecision.confidence)
|
var investmentGrade : InvestmentGrade = getInvestmentGrade(completeTradingDecision,totalScore, completeTradingDecision.confidence)
|
||||||
// 4. 점수에 따른 가변 마진 적용
|
|
||||||
// 토탈 스코어가 85점 이상이면 마진을 3.0으로 고정하거나 추가 가산(append) 적용
|
|
||||||
val finalMargin = minimumNetProfit * investmentGrade.profitGuide
|
val finalMargin = minimumNetProfit * investmentGrade.profitGuide
|
||||||
println("""
|
println("""
|
||||||
사명 : ${completeTradingDecision.corpName}
|
사명 : ${completeTradingDecision.corpName}
|
||||||
@ -299,7 +299,7 @@ fun IntegratedOrderSection(
|
|||||||
investmentGrade = investmentGrade,
|
investmentGrade = investmentGrade,
|
||||||
)
|
)
|
||||||
|
|
||||||
} else if(totalScore >= (MIN_PURCHASE_SCORE * 0.85) && completeTradingDecision.confidence >= (MIN_CONFIDENCE * 0.85)) {
|
} else if(totalScore >= (MIN_PURCHASE_SCORE * 0.85) && completeTradingDecision.confidence + append >= (MIN_CONFIDENCE * 0.85)) {
|
||||||
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
|
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
|
||||||
println("✋ [관망] 토탈 스코어 또는 신뢰도 미달 이나 약간의 오차로 재분석 대기열에 추가")
|
println("✋ [관망] 토탈 스코어 또는 신뢰도 미달 이나 약간의 오차로 재분석 대기열에 추가")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -60,7 +60,7 @@ fun StockDetailSection(
|
|||||||
|
|
||||||
// 이전 종목 코드를 기억하기 위한 상태
|
// 이전 종목 코드를 기억하기 위한 상태
|
||||||
var previousCode by remember { mutableStateOf("") }
|
var previousCode by remember { mutableStateOf("") }
|
||||||
var latestPrice by remember { mutableStateOf("0") }
|
var lastPrice by remember { mutableStateOf("0") }
|
||||||
|
|
||||||
|
|
||||||
// 종목 변경 시 데이터 로드 및 웹소켓 구독 관리
|
// 종목 변경 시 데이터 로드 및 웹소켓 구독 관리
|
||||||
@ -90,7 +90,7 @@ fun StockDetailSection(
|
|||||||
val price = tradeLog.price
|
val price = tradeLog.price
|
||||||
wsManager.tradeLogs.add(tradeLog)
|
wsManager.tradeLogs.add(tradeLog)
|
||||||
if (wsManager.tradeLogs.size > 50) wsManager.tradeLogs.removeLast()
|
if (wsManager.tradeLogs.size > 50) wsManager.tradeLogs.removeLast()
|
||||||
println("code $code ,price $price")
|
// println("code $code ,price $price")
|
||||||
val currentPrice = price
|
val currentPrice = price
|
||||||
if (chartData.isNotEmpty() && currentPrice != "0") {
|
if (chartData.isNotEmpty() && currentPrice != "0") {
|
||||||
val priceDouble = currentPrice.replace(",", "").toDoubleOrNull() ?: 0.0
|
val priceDouble = currentPrice.replace(",", "").toDoubleOrNull() ?: 0.0
|
||||||
@ -123,7 +123,7 @@ fun StockDetailSection(
|
|||||||
chartData = chartData.dropLast(1) + updatedCandle
|
chartData = chartData.dropLast(1) + updatedCandle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
latestPrice = currentPrice
|
lastPrice = currentPrice
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -218,7 +218,7 @@ fun StockDetailSection(
|
|||||||
code = stockCode,
|
code = stockCode,
|
||||||
isDomestic = isDomestic,
|
isDomestic = isDomestic,
|
||||||
previousClose = previousClose,
|
previousClose = previousClose,
|
||||||
openPrice = latestPrice,
|
openPrice = lastPrice,
|
||||||
resultMessage = resultMessage,
|
resultMessage = resultMessage,
|
||||||
resultMessageClear = {resultMessage = ""},
|
resultMessageClear = {resultMessage = ""},
|
||||||
isSuccess = isSuccess
|
isSuccess = isSuccess
|
||||||
@ -227,10 +227,10 @@ fun StockDetailSection(
|
|||||||
// 실시간 가격 표시 (WebSocket 데이터)
|
// 실시간 가격 표시 (WebSocket 데이터)
|
||||||
Column(horizontalAlignment = Alignment.End) {
|
Column(horizontalAlignment = Alignment.End) {
|
||||||
Text(
|
Text(
|
||||||
text = "${latestPrice} 원",
|
text = "${lastPrice} 원",
|
||||||
style = MaterialTheme.typography.h4,
|
style = MaterialTheme.typography.h4,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = if (latestPrice?.contains("-") ?: false) Color.Blue else Color.Red
|
color = if (lastPrice?.contains("-") ?: false) Color.Blue else Color.Red
|
||||||
)
|
)
|
||||||
Text("실시간 체결가", style = MaterialTheme.typography.caption, color = Color.Gray)
|
Text("실시간 체결가", style = MaterialTheme.typography.caption, color = Color.Gray)
|
||||||
}
|
}
|
||||||
@ -276,7 +276,7 @@ fun StockDetailSection(
|
|||||||
stockCode = stockCode,
|
stockCode = stockCode,
|
||||||
stockName = stockName,
|
stockName = stockName,
|
||||||
isDomestic = isDomestic,
|
isDomestic = isDomestic,
|
||||||
currentPrice = latestPrice,
|
currentPrice = lastPrice,
|
||||||
holdingQuantity = holdingQuantity,
|
holdingQuantity = holdingQuantity,
|
||||||
tradeService = tradeService,
|
tradeService = tradeService,
|
||||||
onOrderSaved = onOrderSaved,
|
onOrderSaved = onOrderSaved,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user