....
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package analyzer
|
||||
|
||||
import model.CurrentPriceOutput
|
||||
import model.RiskValidationResult
|
||||
|
||||
object RiskManager {
|
||||
|
||||
/**
|
||||
* 통합된 시세 데이터를 바탕으로 매수 불가능한 '위험 종목'을 걸러냅니다.
|
||||
* @param data API에서 받아온 통합 CurrentPriceOutput 객체
|
||||
* @return RiskValidationResult (isSafe == true 이면 매수 진행)
|
||||
*/
|
||||
fun evaluateRisk(data: CurrentPriceOutput): RiskValidationResult {
|
||||
|
||||
// 1. 거래 정지 및 임시 정지 확인
|
||||
if (data.iscd_stat_cls_code == "58" || data.temp_stop_yn.uppercase() == "Y") {
|
||||
return RiskValidationResult(false, "거래정지 또는 임시정지 종목")
|
||||
}
|
||||
|
||||
// 2. 상장폐지 직전 단계 (정리매매, 관리종목)
|
||||
if (data.sltr_yn.uppercase() == "Y") {
|
||||
return RiskValidationResult(false, "정리매매 진행 중인 종목")
|
||||
}
|
||||
if (data.iscd_stat_cls_code == "51" || data.mang_issu_cls_code == "1" || data.mang_issu_cls_code.uppercase() == "Y") {
|
||||
return RiskValidationResult(false, "상장폐지 위험이 있는 관리종목")
|
||||
}
|
||||
|
||||
// 3. 거래소 경고 (투자위험, 투자경고, 투자주의, 단기과열종목)
|
||||
val warningCodes = listOf("52", "53", "54", "59")
|
||||
if (warningCodes.contains(data.iscd_stat_cls_code)) {
|
||||
return RiskValidationResult(false, "거래소 경고 상태 (상태코드: ${data.iscd_stat_cls_code})")
|
||||
}
|
||||
|
||||
if (data.short_over_yn.uppercase() == "Y") {
|
||||
return RiskValidationResult(false, "단기과열 지정 종목")
|
||||
}
|
||||
|
||||
if (data.invt_caful_yn.uppercase() == "Y") {
|
||||
return RiskValidationResult(false, "투자유의 지정 종목")
|
||||
}
|
||||
|
||||
// 4. 시장경고코드 필터링 (정상 상태인 "00"이나 빈 값이 아닌 경우)
|
||||
if (data.mrkt_warn_cls_code.isNotEmpty() && data.mrkt_warn_cls_code != "00") {
|
||||
return RiskValidationResult(false, "시장경고 발동 중 (경고코드: ${data.mrkt_warn_cls_code})")
|
||||
}
|
||||
|
||||
// 모든 위험 요소를 무사히 통과한 경우
|
||||
return RiskValidationResult(true, "안전")
|
||||
}
|
||||
}
|
||||
@@ -247,6 +247,7 @@ data class ExecutionData(
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
data class CurrentPriceResponse(
|
||||
val rt_cd: String, // 0: 성공, 0 이외: 실패
|
||||
val msg_cd: String,
|
||||
@@ -254,7 +255,10 @@ data class CurrentPriceResponse(
|
||||
val output: CurrentPriceOutput
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CurrentPriceOutput(
|
||||
// --- 📊 기존 시세 및 기본 펀더멘털 필드 ---
|
||||
val stck_shrn_iscd: String, // 종목 코드
|
||||
val stck_prpr: String, // 주식 현재가
|
||||
val prdy_vrss: String, // 전일 대비
|
||||
val prdy_ctrt: String, // 전일 대비율
|
||||
@@ -265,8 +269,22 @@ data class CurrentPriceOutput(
|
||||
val hts_avls: String, // 시가총액
|
||||
val per: String,
|
||||
val pbr: String,
|
||||
val stck_shrn_iscd: String // 종목 코드
|
||||
// ... 필요한 필드가 있다면 Python 모델을 참고하여 추가하세요.
|
||||
|
||||
// --- 🚨 신규 추가: 리스크 필터링 및 상태 필드 ---
|
||||
val rprs_mrkt_kor_name: String, // 대표 시장 한글 명 (KOSPI, KOSDAQ 등)
|
||||
val iscd_stat_cls_code: String, // 종목 상태 구분 코드 (51:관리, 52:위험, 58:정지 등)
|
||||
val mrkt_warn_cls_code: String, // 시장경고코드 (보통 "00"이 정상)
|
||||
val short_over_yn: String, // 단기과열여부 (Y/N)
|
||||
val sltr_yn: String, // 정리매매여부 (Y/N)
|
||||
val mang_issu_cls_code: String, // 관리종목여부 (1/0 또는 Y/N)
|
||||
val temp_stop_yn: String, // 임시 정지 여부 (Y/N)
|
||||
val invt_caful_yn: String // 투자유의여부 (Y/N)
|
||||
)
|
||||
|
||||
// 필터링 결과를 담을 객체
|
||||
data class RiskValidationResult(
|
||||
val isSafe: Boolean,
|
||||
val rejectReason: String = ""
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -570,14 +570,19 @@ object KisTradeService {
|
||||
if (response.status.isSuccess()) {
|
||||
val body = response.body<CurrentPriceResponse>()
|
||||
if (body.rt_cd == "0") {
|
||||
println("${body.output}")
|
||||
Result.success(body.output)
|
||||
} else {
|
||||
println("API 에러: ${body.msg1}")
|
||||
Result.failure(Exception("API 에러: ${body.msg1}"))
|
||||
}
|
||||
} else {
|
||||
println("HTTP 에러: ${response.status}")
|
||||
Result.failure(Exception("HTTP 에러: ${response.status}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
println("HTTP 에러: ${e.message}")
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import Defines.LLM_PORT
|
||||
import TradingLogStore
|
||||
import TradingLogStore.noticeFilter
|
||||
import analyzer.AdvancedTradeAssistant
|
||||
import analyzer.RiskManager
|
||||
import analyzer.TechnicalAnalyzer
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -1147,19 +1148,35 @@ object AutoTradingManager {
|
||||
val today = dailyData.lastOrNull() ?: null
|
||||
var rate = today?.getFluctuationRate() ?: 0.0
|
||||
val isOk = ((rate < KisSession.tradeConfig.plusFilter) && (rate > (abs(KisSession.tradeConfig.minusFilter) * -1)))
|
||||
println("${stock.name}[${stock.code}] 변동률 : ${rate} , 거래 기준 : ${isOk}")
|
||||
if (today == null) {
|
||||
failList.add(stock.code)
|
||||
delay(50)
|
||||
// 1. var 대신 val을 사용해야 아래에서 스마트 캐스트가 작동하여 !!를 안 써도 됩니다.
|
||||
val currentStock = KisTradeService.fetchCurrentPrice(stock.code).getOrNull()
|
||||
|
||||
if (today == null || currentStock == null) {
|
||||
// failList.add(stock.code)
|
||||
print("-> 금일 금액 조회 실패 | ${isOk}")
|
||||
return@withTimeout
|
||||
}
|
||||
val currentPrice = today.stck_prpr.toDouble()
|
||||
// 3. 위에서 확실하게 null 체크를 했으므로, 이제 currentStock은 절대 null이 아닙니다.
|
||||
// 안전하게(Safe call ? 없이) 바로 접근 가능합니다.
|
||||
val currentPrice = currentStock.stck_prpr.toDouble()
|
||||
println("${stock.name}[${stock.code}] 현재가 : ${currentPrice} , 변동률 : ${rate} , 거래 기준 : ${isOk}")
|
||||
|
||||
// 4. 위험한 !! 단언 기호 없이 깔끔하게 호출
|
||||
val riskResult = RiskManager.evaluateRisk(currentStock)
|
||||
if (!riskResult.isSafe) {
|
||||
print("-> ${stock.name}[${stock.code}] 검문소 탈락: ${riskResult.rejectReason}")
|
||||
return@withTimeout
|
||||
}
|
||||
|
||||
|
||||
if (!isOk || (myCash > 10L && currentPrice > myCash) || currentPrice > maxBudget || currentPrice > maxPrice || currentPrice < minPrice) {
|
||||
print("-> [${stock.name}] 가격 정책으로 제외 [1주:${currentPrice}, 자산:${myCash}, 최소 기준:${minPrice}, 최대 기준:${maxPrice}] | ")
|
||||
return@withTimeout
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 🌟 [추가] 고도화된 사전 필터링 (검문소)
|
||||
val tempAnalyzer = TechnicalAnalyzer().apply { this.daily = dailyData }
|
||||
|
||||
@@ -1258,6 +1275,7 @@ object AutoTradingManager {
|
||||
println("✅ [분석 종료] ${stock.name} (${LocalTime.now()})")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
println("❌ [Stock Error] ${stock.name}: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user