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>,
avgReboundTerm: Double,
dropThreshold: Double = 5.0,
timeTolerance: Double = 1.5
timeTolerance: Double = 2.0
): Boolean {
if (candles.size < 20 || avgReboundTerm <= 0.0) return false
@@ -771,7 +771,7 @@ data class VolatilityForecast(
)
data class ReboundStats(
val avgReboundPeriod: Double = 0.0, // 평균 반등 소요 캔들 (일/주/월)
val timeTolerance: Double = 1.5, // 오차 허용 범위 (표준편차)
val timeTolerance: Double = 2.0, // 오차 허용 범위 (표준편차)
val avgDropRate: Double = 5.0, // 평균 하락폭
val avgReboundAmplitude: Double = 0.0, // 🌟 [신규] 바닥 찍고 평균적으로 몇 % 올랐는가?
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))
}
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 java.time.LocalDate
import java.time.LocalTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlin.coroutines.coroutineContext
@@ -426,12 +427,14 @@ object KisTradeService {
isDomestic && !config.isSimulation -> if (isBuy) "TTTC0802U" else "TTTC0801U"
else -> if (isBuy) "TTTS3002U" else "TTTS3001U"
}
val finalOrderDivision = when {
var finalOrderDivision = when {
orderDivision.isNotEmpty() -> orderDivision
marketCode.equals("SOR") || price == "0" || price.isEmpty() -> "01" // 시장가
else -> "00" // 지정가
}
if (marketCode.equals("KRX") && LocalTime.now(ZoneId.of("Asia/Seoul")).isAfter(LocalTime.of(16,0))) {
finalOrderDivision = "41"
}
return try {
val response = client.post("$baseUrl/uapi/${if(isDomestic) "domestic" else "overseas"}-stock/v1/trading/order-cash") {
@@ -684,7 +687,7 @@ object KisTradeService {
try {
do {
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") {
header("authorization", "Bearer ${config.tradeToken}")
header("appkey", config.realAppKey)
@@ -714,8 +717,8 @@ object KisTradeService {
}
val body = response.body<StockBalanceResponse>()
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}\n${body.output2}\n\n")
println("✅ [Step $pageCount] $markgetCode 수신 완료 - 종목 수: ${body.output1.size}")
allHoldings.addAll(body.output1)
if (totalBalance == null) totalBalance = body
@@ -725,7 +728,7 @@ object KisTradeService {
ctxAreaFk = body.ctx_area_fk100 ?: ""
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") {
pageCount++
+1 -1
View File
@@ -435,7 +435,7 @@ object RagService {
put("type", "json_object")
}
}.toString()
println("requestBodyJson =>> $requestBodyJson")
// println("requestBodyJson =>> $requestBodyJson")
val request = Request.Builder()
.url(LLM_API_URL())
.post(requestBodyJson.toRequestBody(jsonMediaType))
@@ -116,7 +116,7 @@ object AutoTradingManager {
val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean ->
val seoulZone = 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
println("${decision.stockName} ${decision.decision}")
@@ -1262,7 +1262,7 @@ object AutoTradingManager {
while (iterator.hasNext()) {
totalCount--
val stock = iterator.next()
if (KisSession.isMarketOpenTime(now)) {
if (KisSession.isMarketAnalyzerTime(now)) {
if (BLACKLISTEDSTOCKCODES.contains(stock.code)) {
println("❌ 차단 처리된 주식 : ${stock.name}")
} else {
@@ -1385,7 +1385,7 @@ object AutoTradingManager {
// 🌟 [핵심] 물타기 대상 여부 플래그 식별
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
@@ -1450,7 +1450,7 @@ object AutoTradingManager {
val tempAnalyzer = TechnicalAnalyzer().apply { this.daily = dailyData }
val volatility = tempAnalyzer.calculateVolatilityForecast(dailyData, 20)
val expectedProfitRate = ((volatility.realisticHigh - currentPrice) / currentPrice) * 100.0
val dailyStats = tempAnalyzer.calculateDynamicReboundStats(dailyData, 5.0)
val dailyStats = tempAnalyzer.calculateDynamicReboundStats(dailyData, 3.0)
// 🌟 [완화 3] 기대수익률 및 진입 타이밍 이원화
val isProfitable: Boolean