This commit is contained in:
2026-09-15 16:17:45 +09:00
parent 1a2fe8b6e6
commit 84f2202f17
5 changed files with 20 additions and 13 deletions
@@ -352,7 +352,7 @@ class TechnicalAnalyzer {
candles: List<CandleData>, candles: List<CandleData>,
avgReboundTerm: Double, avgReboundTerm: Double,
dropThreshold: Double = 5.0, dropThreshold: Double = 5.0,
timeTolerance: Double = 1.5 timeTolerance: Double = 2.0
): Boolean { ): Boolean {
if (candles.size < 20 || avgReboundTerm <= 0.0) return false if (candles.size < 20 || avgReboundTerm <= 0.0) return false
@@ -771,7 +771,7 @@ data class VolatilityForecast(
) )
data class ReboundStats( data class ReboundStats(
val avgReboundPeriod: Double = 0.0, // 평균 반등 소요 캔들 (일/주/월) val avgReboundPeriod: Double = 0.0, // 평균 반등 소요 캔들 (일/주/월)
val timeTolerance: Double = 1.5, // 오차 허용 범위 (표준편차) val timeTolerance: Double = 2.0, // 오차 허용 범위 (표준편차)
val avgDropRate: Double = 5.0, // 평균 하락폭 val avgDropRate: Double = 5.0, // 평균 하락폭
val avgReboundAmplitude: Double = 0.0, // 🌟 [신규] 바닥 찍고 평균적으로 몇 % 올랐는가? val avgReboundAmplitude: Double = 0.0, // 🌟 [신규] 바닥 찍고 평균적으로 몇 % 올랐는가?
val isValid: Boolean = false val isValid: Boolean = false
+4
View File
@@ -365,4 +365,8 @@ object KisSession {
return now.isBefore(LocalTime.of(15,30)) && now.isAfter(LocalTime.of(9,0)) return now.isBefore(LocalTime.of(15,30)) && now.isAfter(LocalTime.of(9,0))
} }
fun isMarketAnalyzerTime(now: LocalTime) : Boolean {
return now.isBefore(LocalTime.of(18,0)) && now.isAfter(LocalTime.of(8,0))
}
} }
+9 -6
View File
@@ -31,6 +31,7 @@ import kotlinx.serialization.json.jsonPrimitive
import model.* import model.*
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
@@ -426,12 +427,14 @@ object KisTradeService {
isDomestic && !config.isSimulation -> if (isBuy) "TTTC0802U" else "TTTC0801U" isDomestic && !config.isSimulation -> if (isBuy) "TTTC0802U" else "TTTC0801U"
else -> if (isBuy) "TTTS3002U" else "TTTS3001U" else -> if (isBuy) "TTTS3002U" else "TTTS3001U"
} }
val finalOrderDivision = when { var finalOrderDivision = when {
orderDivision.isNotEmpty() -> orderDivision orderDivision.isNotEmpty() -> orderDivision
marketCode.equals("SOR") || price == "0" || price.isEmpty() -> "01" // 시장가 marketCode.equals("SOR") || price == "0" || price.isEmpty() -> "01" // 시장가
else -> "00" // 지정가 else -> "00" // 지정가
} }
if (marketCode.equals("KRX") && LocalTime.now(ZoneId.of("Asia/Seoul")).isAfter(LocalTime.of(16,0))) {
finalOrderDivision = "41"
}
return try { return try {
val response = client.post("$baseUrl/uapi/${if(isDomestic) "domestic" else "overseas"}-stock/v1/trading/order-cash") { val response = client.post("$baseUrl/uapi/${if(isDomestic) "domestic" else "overseas"}-stock/v1/trading/order-cash") {
@@ -684,7 +687,7 @@ object KisTradeService {
try { try {
do { do {
if (!coroutineContext.isActive) throw _root_ide_package_.io.ktor.utils.io.CancellationException("UI에서 작업을 취소함") // [추가] if (!coroutineContext.isActive) throw _root_ide_package_.io.ktor.utils.io.CancellationException("UI에서 작업을 취소함") // [추가]
println("📡 [Step $pageCount] 요청 전송 중... (tr_cont: $trCont)") // println("📡 [Step $pageCount] 요청 전송 중... (tr_cont: $trCont)")
val response = client.get("$baseUrl/uapi/domestic-stock/v1/trading/inquire-balance") { val response = client.get("$baseUrl/uapi/domestic-stock/v1/trading/inquire-balance") {
header("authorization", "Bearer ${config.tradeToken}") header("authorization", "Bearer ${config.tradeToken}")
header("appkey", config.realAppKey) header("appkey", config.realAppKey)
@@ -714,8 +717,8 @@ object KisTradeService {
} }
val body = response.body<StockBalanceResponse>() val body = response.body<StockBalanceResponse>()
println("✅ [Step $pageCount] $markgetCode 수신 완료 - 종목 수: ${body.output1.size}\n${body.output2}\n\n") // println("✅ [Step $pageCount] $markgetCode 수신 완료 - 종목 수: ${body.output1.size}\n${body.output2}\n\n")
// println("✅ [Step $pageCount] $markgetCode 수신 완료 - 종목 수: ${body.output1.size}") println("✅ [Step $pageCount] $markgetCode 수신 완료 - 종목 수: ${body.output1.size}")
allHoldings.addAll(body.output1) allHoldings.addAll(body.output1)
if (totalBalance == null) totalBalance = body if (totalBalance == null) totalBalance = body
@@ -725,7 +728,7 @@ object KisTradeService {
ctxAreaFk = body.ctx_area_fk100 ?: "" ctxAreaFk = body.ctx_area_fk100 ?: ""
ctxAreaNk = body.ctx_area_nk100 ?: "" ctxAreaNk = body.ctx_area_nk100 ?: ""
println("📝 [Header Check] tr_cont: $trCont, ctx_area_nk100: $ctxAreaNk") // println("📝 [Header Check] tr_cont: $trCont, ctx_area_nk100: $ctxAreaNk")
if ( trCont == "M") { if ( trCont == "M") {
pageCount++ pageCount++
+1 -1
View File
@@ -435,7 +435,7 @@ object RagService {
put("type", "json_object") put("type", "json_object")
} }
}.toString() }.toString()
println("requestBodyJson =>> $requestBodyJson") // println("requestBodyJson =>> $requestBodyJson")
val request = Request.Builder() val request = Request.Builder()
.url(LLM_API_URL()) .url(LLM_API_URL())
.post(requestBodyJson.toRequestBody(jsonMediaType)) .post(requestBodyJson.toRequestBody(jsonMediaType))
@@ -116,7 +116,7 @@ object AutoTradingManager {
val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean -> val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean ->
val seoulZone = ZoneId.of("Asia/Seoul") val seoulZone = ZoneId.of("Asia/Seoul")
val now = LocalTime.now(ZoneId.of("Asia/Seoul")) val now = LocalTime.now(ZoneId.of("Asia/Seoul"))
if (KisSession.isMarketOpenTime(now) && isSuccess && completeTradingDecision != null) { if (KisSession.isMarketAnalyzerTime(now) && isSuccess && completeTradingDecision != null) {
val decision = completeTradingDecision val decision = completeTradingDecision
println("${decision.stockName} ${decision.decision}") println("${decision.stockName} ${decision.decision}")
@@ -1262,7 +1262,7 @@ object AutoTradingManager {
while (iterator.hasNext()) { while (iterator.hasNext()) {
totalCount-- totalCount--
val stock = iterator.next() val stock = iterator.next()
if (KisSession.isMarketOpenTime(now)) { if (KisSession.isMarketAnalyzerTime(now)) {
if (BLACKLISTEDSTOCKCODES.contains(stock.code)) { if (BLACKLISTEDSTOCKCODES.contains(stock.code)) {
println("❌ 차단 처리된 주식 : ${stock.name}") println("❌ 차단 처리된 주식 : ${stock.name}")
} else { } else {
@@ -1385,7 +1385,7 @@ object AutoTradingManager {
// 🌟 [핵심] 물타기 대상 여부 플래그 식별 // 🌟 [핵심] 물타기 대상 여부 플래그 식별
val targetHolding = currentBalance?.getHoldings()?.firstOrNull { val targetHolding = currentBalance?.getHoldings()?.firstOrNull {
it.code == stock.code && it.quantity.toInt() > 0 it.code == stock.code && it.quantity.toInt() > KisSession.tradeConfig.lowerAverageTargetCount
} }
val isWatering = targetHolding != null && KisSession.tradeConfig.lowerAveragePrice val isWatering = targetHolding != null && KisSession.tradeConfig.lowerAveragePrice
@@ -1450,7 +1450,7 @@ object AutoTradingManager {
val tempAnalyzer = TechnicalAnalyzer().apply { this.daily = dailyData } val tempAnalyzer = TechnicalAnalyzer().apply { this.daily = dailyData }
val volatility = tempAnalyzer.calculateVolatilityForecast(dailyData, 20) val volatility = tempAnalyzer.calculateVolatilityForecast(dailyData, 20)
val expectedProfitRate = ((volatility.realisticHigh - currentPrice) / currentPrice) * 100.0 val expectedProfitRate = ((volatility.realisticHigh - currentPrice) / currentPrice) * 100.0
val dailyStats = tempAnalyzer.calculateDynamicReboundStats(dailyData, 5.0) val dailyStats = tempAnalyzer.calculateDynamicReboundStats(dailyData, 3.0)
// 🌟 [완화 3] 기대수익률 및 진입 타이밍 이원화 // 🌟 [완화 3] 기대수익률 및 진입 타이밍 이원화
val isProfitable: Boolean val isProfitable: Boolean