....
This commit is contained in:
parent
6494784bbc
commit
af72ffae1c
@ -17,6 +17,7 @@ import ConfigTable.loss_max_money
|
|||||||
import ConfigTable.loss_maxrate
|
import ConfigTable.loss_maxrate
|
||||||
import ConfigTable.loss_minrate
|
import ConfigTable.loss_minrate
|
||||||
import ConfigTable.max_count
|
import ConfigTable.max_count
|
||||||
|
import ConfigTable.max_holding_count
|
||||||
import ConfigTable.stop_Loss
|
import ConfigTable.stop_Loss
|
||||||
import ConfigTable.take_profit
|
import ConfigTable.take_profit
|
||||||
import Defines.DETAILLOG
|
import Defines.DETAILLOG
|
||||||
@ -215,6 +216,7 @@ fun main() = application {
|
|||||||
loss_min = it[loss_minrate],
|
loss_min = it[loss_minrate],
|
||||||
loss_money = it[loss_max_money],
|
loss_money = it[loss_max_money],
|
||||||
MAX_COUNT = it[max_count],
|
MAX_COUNT = it[max_count],
|
||||||
|
max_holding_count = it[max_holding_count],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,10 +71,11 @@ object ConfigTable : Table("app_config") {
|
|||||||
val loss_max_money = double("loss_max_money").default(10000.0)
|
val loss_max_money = double("loss_max_money").default(10000.0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val max_count = integer("max_count").default(20)
|
val max_count = integer("max_count").default(20)
|
||||||
|
|
||||||
|
val max_holding_count = double("max_holding_count").default(100.0)
|
||||||
|
|
||||||
|
|
||||||
override val primaryKey = PrimaryKey(id)
|
override val primaryKey = PrimaryKey(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +290,7 @@ object DatabaseFactory {
|
|||||||
loss_max = it[ConfigTable.loss_maxrate],
|
loss_max = it[ConfigTable.loss_maxrate],
|
||||||
loss_money = it[ConfigTable.loss_max_money],
|
loss_money = it[ConfigTable.loss_max_money],
|
||||||
MAX_COUNT = it[ConfigTable.max_count],
|
MAX_COUNT = it[ConfigTable.max_count],
|
||||||
|
max_holding_count = it[ConfigTable.max_holding_count],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,6 +341,7 @@ object DatabaseFactory {
|
|||||||
it[loss_minrate] = config.loss_min
|
it[loss_minrate] = config.loss_min
|
||||||
it[loss_max_money] = config.loss_money
|
it[loss_max_money] = config.loss_money
|
||||||
it[max_count] = config.MAX_COUNT
|
it[max_count] = config.MAX_COUNT
|
||||||
|
it[max_holding_count] = config.max_holding_count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ enum class ConfigIndex(val index : Int,val label : String) {
|
|||||||
LOSS_MINRATE(STOP_LOSS.index + 1, "손절 최소 기준") ,
|
LOSS_MINRATE(STOP_LOSS.index + 1, "손절 최소 기준") ,
|
||||||
LOSS_MAXRATE(LOSS_MINRATE.index + 1, "손절 최소 기준") ,
|
LOSS_MAXRATE(LOSS_MINRATE.index + 1, "손절 최소 기준") ,
|
||||||
LOSS_MAX_MONEY(LOSS_MAXRATE.index + 1, "손절 최대 금액") ,
|
LOSS_MAX_MONEY(LOSS_MAXRATE.index + 1, "손절 최대 금액") ,
|
||||||
|
MAX_HOLDING_COUNT(LOSS_MAX_MONEY.index + 1, "최대 보유 가능 종목 수")
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -110,6 +110,7 @@ data class AppConfig(
|
|||||||
var loss_min : Double = 3.5,
|
var loss_min : Double = 3.5,
|
||||||
var loss_max : Double = 10.0,
|
var loss_max : Double = 10.0,
|
||||||
var loss_money : Double = 10000.0,
|
var loss_money : Double = 10000.0,
|
||||||
|
var max_holding_count : Double = 100.0,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val accountNo : String
|
val accountNo : String
|
||||||
@ -152,6 +153,7 @@ data class AppConfig(
|
|||||||
ConfigIndex.LOSS_MAX_MONEY -> { loss_money = value}
|
ConfigIndex.LOSS_MAX_MONEY -> { loss_money = value}
|
||||||
ConfigIndex.STOP_LOSS -> { stop_Loss = value > 0.1}
|
ConfigIndex.STOP_LOSS -> { stop_Loss = value > 0.1}
|
||||||
ConfigIndex.TAKE_PROFIT -> { take_profit = value > 0.1 }
|
ConfigIndex.TAKE_PROFIT -> { take_profit = value > 0.1 }
|
||||||
|
ConfigIndex.MAX_HOLDING_COUNT -> { max_holding_count = value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun getValues(index :ConfigIndex) : Double {
|
fun getValues(index :ConfigIndex) : Double {
|
||||||
@ -200,6 +202,7 @@ data class AppConfig(
|
|||||||
ConfigIndex.STOP_LOSS -> {if(!stop_Loss) 0.0 else 1.0}
|
ConfigIndex.STOP_LOSS -> {if(!stop_Loss) 0.0 else 1.0}
|
||||||
ConfigIndex.TAKE_PROFIT -> {if(!take_profit) 0.0 else 1.0}
|
ConfigIndex.TAKE_PROFIT -> {if(!take_profit) 0.0 else 1.0}
|
||||||
ConfigIndex.MAX_COUNT_INDEX -> {MAX_COUNT.toDouble()}
|
ConfigIndex.MAX_COUNT_INDEX -> {MAX_COUNT.toDouble()}
|
||||||
|
ConfigIndex.MAX_HOLDING_COUNT -> { max_holding_count}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -178,7 +178,11 @@ data class UnfilledOrder(
|
|||||||
val rmnd_qty: String, // JSON의 psbl_qty를 rmnd_qty로 매핑
|
val rmnd_qty: String, // JSON의 psbl_qty를 rmnd_qty로 매핑
|
||||||
val ord_dvsn_name: String,
|
val ord_dvsn_name: String,
|
||||||
val rvse_cncl_dvsn_name: String
|
val rvse_cncl_dvsn_name: String
|
||||||
)
|
) {
|
||||||
|
fun isBuyOrder() : Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UnfilledResponse(
|
data class UnfilledResponse(
|
||||||
|
|||||||
@ -391,7 +391,7 @@ object KisTradeService {
|
|||||||
*/
|
*/
|
||||||
suspend fun fetchUnfilledOrders(): Result<List<UnfilledOrder>> {
|
suspend fun fetchUnfilledOrders(): Result<List<UnfilledOrder>> {
|
||||||
val config = KisSession.config
|
val config = KisSession.config
|
||||||
if (config.isSimulation) return Result.success(emptyList<UnfilledOrder>())
|
// if (config.isSimulation) return Result.success(emptyList<UnfilledOrder>())
|
||||||
val baseUrl = if (config.isSimulation) vtsUrl else prodUrl
|
val baseUrl = if (config.isSimulation) vtsUrl else prodUrl
|
||||||
val trId = "TTTC0084R"
|
val trId = "TTTC0084R"
|
||||||
var pureAccount = config.accountNo.replace("-", "").trim()
|
var pureAccount = config.accountNo.replace("-", "").trim()
|
||||||
|
|||||||
@ -45,6 +45,7 @@ import java.time.LocalTime
|
|||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import kotlin.collections.List
|
import kotlin.collections.List
|
||||||
|
import kotlin.collections.filter
|
||||||
|
|
||||||
// service/AutoTradingManager.kt
|
// service/AutoTradingManager.kt
|
||||||
typealias TradingDecisionCallback = (TradingDecision?, Boolean)->Unit
|
typealias TradingDecisionCallback = (TradingDecision?, Boolean)->Unit
|
||||||
@ -98,94 +99,10 @@ object AutoTradingManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean ->
|
|
||||||
// if (isSuccess && completeTradingDecision != null) {
|
|
||||||
// // 1. 로그 저장소에 기록 (UI에서 이걸 읽음)
|
|
||||||
// TradingLogStore.addLog(completeTradingDecision)
|
|
||||||
//
|
|
||||||
// println("🚀 [자동매수 실행] ${completeTradingDecision.stockName}")
|
|
||||||
// if (completeTradingDecision.confidence < 10) {
|
|
||||||
// addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
|
|
||||||
// TradingLogStore.addLog(completeTradingDecision,"RETRY","분석 신뢰도 오류 인지로 재분석 대기열에 추가")
|
|
||||||
// }else if (completeTradingDecision != null && !completeTradingDecision.stockCode.isNullOrEmpty()) {
|
|
||||||
// var basePrice = completeTradingDecision.currentPrice
|
|
||||||
// var stockCode = completeTradingDecision.stockCode
|
|
||||||
// println("basePrice $basePrice")
|
|
||||||
// val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX)
|
|
||||||
// var maxBudget = KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX)
|
|
||||||
// val buyWeight = KisSession.config.getValues(ConfigIndex.BUY_WEIGHT_INDEX)
|
|
||||||
// val baseProfit = KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
|
|
||||||
//
|
|
||||||
// fun resultCheck(completeTradingDecision :TradingDecision) {
|
|
||||||
// val weights = mapOf(
|
|
||||||
// "short" to 0.2, // 초단기 점수가 낮아도 전체에 미치는 영향 감소
|
|
||||||
// "profit" to 0.4,
|
|
||||||
// "safe" to 0.4 // 중장기 점수 비중 강화
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// val totalScore =
|
|
||||||
// ((completeTradingDecision.shortPossible() + append) * weights["short"]!!) +
|
|
||||||
// ((completeTradingDecision.profitPossible() + append) * weights["profit"]!!) +
|
|
||||||
// ((completeTradingDecision.safePossible() + append) * weights["safe"]!!)
|
|
||||||
//
|
|
||||||
// if (totalScore >= minScore && completeTradingDecision.confidence >= MIN_CONFIDENCE) {
|
|
||||||
// var investmentGrade = completeTradingDecision.investmentGrade ?: InvestmentGrade.LEVEL_1_SPECULATIVE
|
|
||||||
//
|
|
||||||
// val finalMargin = baseProfit * KisSession.config.getValues(investmentGrade.profitGuide)
|
|
||||||
// println("🚀 [매수 진행] 토탈 스코어: ${String.format("%.1f", totalScore)} -> 종목: ${completeTradingDecision.stockCode}")
|
|
||||||
//
|
|
||||||
// // basePrice(현재가 혹은 지정가)를 기준으로 매수 가능 수량 산출 (최소 1주 보장)
|
|
||||||
// val gradeRate = KisSession.config.getValues(investmentGrade.allocationRate)
|
|
||||||
// val maxQty = (KisSession.config.getValues(ConfigIndex.MAX_COUNT_INDEX) * gradeRate).roundToInt()
|
|
||||||
// maxBudget = maxBudget * gradeRate
|
|
||||||
// val calculatedQty = if (basePrice > 0) {
|
|
||||||
// (maxBudget / basePrice).toInt().coerceAtLeast(1)
|
|
||||||
// } else {
|
|
||||||
// 1
|
|
||||||
// }
|
|
||||||
// // 5. 매수 실행 (계산된 finalMargin 전달)
|
|
||||||
// excuteTrade(
|
|
||||||
// decision = completeTradingDecision,
|
|
||||||
// orderQty = min(calculatedQty, maxQty).toString(),
|
|
||||||
// profitRate1 = finalMargin,
|
|
||||||
// investmentGrade = investmentGrade,
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// } else if(totalScore >= (minScore * 0.9) && completeTradingDecision.confidence + append >= (MIN_CONFIDENCE * 0.9)) {
|
|
||||||
// addToReanalysis(RankingStock(mksc_shrn_iscd = completeTradingDecision.stockCode,hts_kor_isnm = completeTradingDecision.stockName))
|
|
||||||
// TradingLogStore.addLog(completeTradingDecision,"RETRY","✋ [관망] 토탈 스코어[$totalScore] 또는 신뢰도[${completeTradingDecision.confidence}] 미달 이나 약간의 오차로 재분석 대기열에 추가")
|
|
||||||
// } else {
|
|
||||||
// TradingLogStore.addLog(completeTradingDecision,"HOLD","✋ [관망] 토탈 스코어(${String.format("%.1f[${minScore}]", totalScore)}) 또는 신뢰도 (${String.format("%.1f[${MIN_CONFIDENCE}]", completeTradingDecision.confidence)}) 미달")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (completeTradingDecision?.decision?.contains("매수") == true) {
|
|
||||||
// completeTradingDecision.decision = "BUY"
|
|
||||||
// }
|
|
||||||
// when (completeTradingDecision?.decision) {
|
|
||||||
// "BUY","매수" -> {
|
|
||||||
// append = buyWeight
|
|
||||||
// TradingLogStore.addLog(completeTradingDecision,"BUY","[$stockCode] 매수 추천 : ${completeTradingDecision?.reason}")
|
|
||||||
// resultCheck(completeTradingDecision)
|
|
||||||
// }
|
|
||||||
// "SELL" -> {
|
|
||||||
// TradingLogStore.addLog(completeTradingDecision,"SELL","[$stockCode] 매도 추천 : ${completeTradingDecision?.reason}")
|
|
||||||
// println("[$stockCode] 매도: ${completeTradingDecision?.reason}")
|
|
||||||
// }
|
|
||||||
// "HOLD" -> {
|
|
||||||
// append = 0.0
|
|
||||||
// TradingLogStore.addLog(completeTradingDecision,"HOLD","[$stockCode] 관망 유지 : ${completeTradingDecision?.reason}")
|
|
||||||
// resultCheck(completeTradingDecision)
|
|
||||||
// }
|
|
||||||
// else -> {
|
|
||||||
// append = 0.0
|
|
||||||
// println("[$stockCode] ${completeTradingDecision?.decision} : ${completeTradingDecision?.reason}")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean ->
|
val globalCallback = { completeTradingDecision: TradingDecision?, isSuccess: Boolean ->
|
||||||
if (isSuccess && completeTradingDecision != null) {
|
val seoulZone = ZoneId.of("Asia/Seoul")
|
||||||
|
val now = LocalTime.now(ZoneId.of("Asia/Seoul"))
|
||||||
|
if (now.isBefore(H15M30) && now.isAfter(H08M45) && isSuccess && completeTradingDecision != null) {
|
||||||
val decision = completeTradingDecision
|
val decision = completeTradingDecision
|
||||||
|
|
||||||
// 1. 이미 AI가 결정한 decision과 confidence를 신뢰함
|
// 1. 이미 AI가 결정한 decision과 confidence를 신뢰함
|
||||||
@ -209,6 +126,8 @@ object AutoTradingManager {
|
|||||||
} else if (decision.decision.equals("RETRY") || decision.confidence >= 60.0) { // 아까운 종목만 재분석
|
} else if (decision.decision.equals("RETRY") || decision.confidence >= 60.0) { // 아까운 종목만 재분석
|
||||||
addToReanalysis(RankingStock(decision.stockCode, decision.stockName))
|
addToReanalysis(RankingStock(decision.stockCode, decision.stockName))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,51 +209,63 @@ object AutoTradingManager {
|
|||||||
var stockCode = decision.stockCode
|
var stockCode = decision.stockCode
|
||||||
var stockName = decision.stockName
|
var stockName = decision.stockName
|
||||||
val finalPrice = MarketUtil.roundToTickSize(oneTickLowerPrice.toDouble())
|
val finalPrice = MarketUtil.roundToTickSize(oneTickLowerPrice.toDouble())
|
||||||
|
val maxStocks = KisSession.config.getValues(ConfigIndex.MAX_HOLDING_COUNT).toInt()
|
||||||
|
|
||||||
println("basePrice : $basePrice, oneTickLowerPrice : $oneTickLowerPrice, finalPrice : $finalPrice")
|
// 💡 2. 매수 실행 전, 안전장치 통과 여부 확인
|
||||||
KisTradeService.postOrder(stockCode, orderQty, finalPrice.toLong().toString(), isBuy = true)
|
if (!canAddNewPosition(maxStocks)) {
|
||||||
.onSuccess { realOrderNo ->
|
// 제한에 걸렸다면, 매수 로직을 건너뛰고 매도(보유 종목 관리) 로직으로만 넘어갑니다.
|
||||||
// 💡 [개선 1] 첫 번째 성공 로그에 등급 이름 추가
|
println("🚫 [안전 장치 작동] 현재 포지션이 가득 찼습니다. (최대 ${maxStocks}종목). 신규 매수를 일시 중단하고 매도에 집중합니다.")
|
||||||
println("[${investmentGrade.displayName}] 주문 성공: $realOrderNo $stockCode $orderQty $finalPrice")
|
|
||||||
TradingLogStore.addLog(decision, "BUY", "[${investmentGrade.displayName}] 주문 성공: $realOrderNo")
|
|
||||||
|
|
||||||
// 손절 라인 하드코딩 (필요시 Config로 빼는 것 권장)
|
// UI나 로그에 상태를 띄워주면 좋습니다.
|
||||||
val sRate = -1.5
|
TradingLogStore.addNotice("SYSTEM", "LIMIT", "최대 보유 종목 도달로 신규 매수 일시 중단")
|
||||||
var tax = KisSession.config.getValues(ConfigIndex.TAX_INDEX)
|
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode,hts_kor_isnm = stockName))
|
||||||
// 최소 보장 수익률(전역 설정)과 요청 수익률 중 큰 값 선택 후 세금 더하기
|
TradingLogStore.addLog(decision,"WATCH","매수 실패 : 최대 보유 종목 도달로 신규 매수 일시 중단 => 재분석 대기열에 추가")
|
||||||
val effectiveProfitRate = (profitRate1 ?: KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)) + tax
|
} else {
|
||||||
|
println("basePrice : $basePrice, oneTickLowerPrice : $oneTickLowerPrice, finalPrice : $finalPrice")
|
||||||
|
KisTradeService.postOrder(stockCode, orderQty, finalPrice.toLong().toString(), isBuy = true)
|
||||||
|
.onSuccess { realOrderNo ->
|
||||||
|
// 💡 [개선 1] 첫 번째 성공 로그에 등급 이름 추가
|
||||||
|
println("[${investmentGrade.displayName}] 주문 성공: $realOrderNo $stockCode $orderQty $finalPrice")
|
||||||
|
TradingLogStore.addLog(decision, "BUY", "[${investmentGrade.displayName}] 주문 성공: $realOrderNo")
|
||||||
|
|
||||||
val calculatedTarget = MarketUtil.roundToTickSize(basePrice * (1 + effectiveProfitRate / 100.0))
|
// 손절 라인 하드코딩 (필요시 Config로 빼는 것 권장)
|
||||||
val calculatedStop = MarketUtil.roundToTickSize(basePrice * (1 + sRate / 100.0))
|
val sRate = -1.5
|
||||||
val inputQty = orderQty.replace(",", "").toIntOrNull() ?: 0
|
var tax = KisSession.config.getValues(ConfigIndex.TAX_INDEX)
|
||||||
|
// 최소 보장 수익률(전역 설정)과 요청 수익률 중 큰 값 선택 후 세금 더하기
|
||||||
|
val effectiveProfitRate = (profitRate1 ?: KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)) + tax
|
||||||
|
|
||||||
DatabaseFactory.saveAutoTrade(AutoTradeItem(
|
val calculatedTarget = MarketUtil.roundToTickSize(basePrice * (1 + effectiveProfitRate / 100.0))
|
||||||
orderNo = realOrderNo,
|
val calculatedStop = MarketUtil.roundToTickSize(basePrice * (1 + sRate / 100.0))
|
||||||
code = stockCode,
|
val inputQty = orderQty.replace(",", "").toIntOrNull() ?: 0
|
||||||
name = stockName,
|
|
||||||
quantity = inputQty,
|
|
||||||
profitRate = effectiveProfitRate,
|
|
||||||
stopLossRate = sRate,
|
|
||||||
targetPrice = calculatedTarget,
|
|
||||||
stopLossPrice = calculatedStop,
|
|
||||||
status = "PENDING_BUY",
|
|
||||||
isDomestic = true
|
|
||||||
))
|
|
||||||
syncAndExecute(realOrderNo)
|
|
||||||
|
|
||||||
// 💡 [개선 3] 감시 설정 로그에도 등급 정보 노출
|
DatabaseFactory.saveAutoTrade(AutoTradeItem(
|
||||||
TradingLogStore.addLog(decision, "BUY", "[${investmentGrade.displayName}] 매수 및 감시 설정 완료 (목표 수익률: ${String.format("%.4f", effectiveProfitRate)}%): $realOrderNo")
|
orderNo = realOrderNo,
|
||||||
}
|
code = stockCode,
|
||||||
.onFailure {
|
name = stockName,
|
||||||
println("매수 실패: ${it.message} ${stockCode} $orderQty $finalPrice")
|
quantity = inputQty,
|
||||||
|
profitRate = effectiveProfitRate,
|
||||||
|
stopLossRate = sRate,
|
||||||
|
targetPrice = calculatedTarget,
|
||||||
|
stopLossPrice = calculatedStop,
|
||||||
|
status = "PENDING_BUY",
|
||||||
|
isDomestic = true
|
||||||
|
))
|
||||||
|
syncAndExecute(realOrderNo)
|
||||||
|
|
||||||
if (it.message?.contains("주문가능금액을 초과") == true) {
|
// 💡 [개선 3] 감시 설정 로그에도 등급 정보 노출
|
||||||
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode,hts_kor_isnm = stockName))
|
TradingLogStore.addLog(decision, "BUY", "[${investmentGrade.displayName}] 매수 및 감시 설정 완료 (목표 수익률: ${String.format("%.4f", effectiveProfitRate)}%): $realOrderNo")
|
||||||
TradingLogStore.addLog(decision,"WATCH","${it.message ?: " 매수 실패"} => 재분석 대기열에 추가")
|
|
||||||
} else {
|
|
||||||
TradingLogStore.addLog(decision,"BUY",it.message ?: "매수 실패")
|
|
||||||
}
|
}
|
||||||
}
|
.onFailure {
|
||||||
|
println("매수 실패: ${it.message} ${stockCode} $orderQty $finalPrice")
|
||||||
|
|
||||||
|
if (it.message?.contains("주문가능금액을 초과") == true) {
|
||||||
|
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode,hts_kor_isnm = stockName))
|
||||||
|
TradingLogStore.addLog(decision,"WATCH","${it.message ?: " 매수 실패"} => 재분석 대기열에 추가")
|
||||||
|
} else {
|
||||||
|
TradingLogStore.addLog(decision,"BUY",it.message ?: "매수 실패")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var onExecutionReceived : ((String, String, String, String, Boolean) -> Unit)? = {code, qty, price,orderNo, isBuy ->
|
var onExecutionReceived : ((String, String, String, String, Boolean) -> Unit)? = {code, qty, price,orderNo, isBuy ->
|
||||||
@ -384,6 +315,7 @@ object AutoTradingManager {
|
|||||||
}
|
}
|
||||||
} else if (dbItem.status == TradeStatus.SELLING) {
|
} else if (dbItem.status == TradeStatus.SELLING) {
|
||||||
println("🎊 [매칭 성공] 매도 완료 처리: ${dbItem.name}")
|
println("🎊 [매칭 성공] 매도 완료 처리: ${dbItem.name}")
|
||||||
|
myOredsAndBalanceCodes.remove(dbItem.code)
|
||||||
TradingLogStore.addSellLog(dbItem.name,execData.price,"SELL","🎊 [매칭 성공] 매도 완료 처리")
|
TradingLogStore.addSellLog(dbItem.name,execData.price,"SELL","🎊 [매칭 성공] 매도 완료 처리")
|
||||||
DatabaseFactory.updateStatusAndOrderNo(dbItem.id!!, TradeStatus.COMPLETED)
|
DatabaseFactory.updateStatusAndOrderNo(dbItem.id!!, TradeStatus.COMPLETED)
|
||||||
executionCache.remove(orderNo)
|
executionCache.remove(orderNo)
|
||||||
@ -583,6 +515,15 @@ object AutoTradingManager {
|
|||||||
private var lastRetryTime = 0L
|
private var lastRetryTime = 0L
|
||||||
val binPath = getLlamaBinPath()
|
val binPath = getLlamaBinPath()
|
||||||
|
|
||||||
|
fun canAddNewPosition( // 대표님의 시스템에 맞는 미체결 주문 객체 리스트
|
||||||
|
maxAllowedStocks: Int
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
|
|
||||||
|
// 현재 노출 수가 최대 허용치보다 작을 때만 true(매수 가능) 반환
|
||||||
|
return myOredsAndBalanceCodes.size < maxAllowedStocks
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun tryRefreshToken() {
|
suspend fun tryRefreshToken() {
|
||||||
try {
|
try {
|
||||||
// 2분 간격 재시도 로직 (처음 실행 시에는 lastRetryTime이 0이므로 즉시 실행)
|
// 2분 간격 재시도 로직 (처음 실행 시에는 lastRetryTime이 0이므로 즉시 실행)
|
||||||
@ -621,6 +562,7 @@ object AutoTradingManager {
|
|||||||
var now = LocalTime.now(ZoneId.of("Asia/Seoul"))
|
var now = LocalTime.now(ZoneId.of("Asia/Seoul"))
|
||||||
var currentTimeMillis = System.currentTimeMillis()
|
var currentTimeMillis = System.currentTimeMillis()
|
||||||
var waitTime = 0.2
|
var waitTime = 0.2
|
||||||
|
val H15M30 = LocalTime.of(15, 30)
|
||||||
val H16 = LocalTime.of(16, 0)
|
val H16 = LocalTime.of(16, 0)
|
||||||
val H18 = LocalTime.of(18, 0)
|
val H18 = LocalTime.of(18, 0)
|
||||||
val H08M00 = LocalTime.of(8, 0)
|
val H08M00 = LocalTime.of(8, 0)
|
||||||
@ -717,25 +659,28 @@ object AutoTradingManager {
|
|||||||
|
|
||||||
return batch
|
return batch
|
||||||
}
|
}
|
||||||
|
var currentBalance : UnifiedBalance? = null
|
||||||
suspend fun checkBalance(isMorning: Boolean = true) : UnifiedBalance? {
|
var myOredsAndBalanceCodes : MutableSet<String> = mutableSetOf()
|
||||||
var balance : UnifiedBalance? = null
|
suspend fun checkBalance(isMorning: Boolean = true) {
|
||||||
if (isMorning) {
|
if (isMorning) {
|
||||||
balance = KisTradeService.fetchIntegratedBalance().getOrNull()
|
currentBalance = KisTradeService.fetchIntegratedBalance().getOrNull()
|
||||||
if (AUTOSELL) balance?.let { resumePendingSellOrders(KisTradeService, it) }
|
if (AUTOSELL) currentBalance?.let { resumePendingSellOrders(KisTradeService, it) }
|
||||||
return balance
|
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun executeMarketLoop() {
|
suspend fun executeMarketLoop() {
|
||||||
val balance = checkBalance()
|
myOredsAndBalanceCodes.clear()
|
||||||
// if (AUTOSELL) balance?.let { resumePendingSellOrders(KisTradeService, it) }
|
checkBalance()
|
||||||
|
val myCash = currentBalance?.deposit?.replace(",", "")?.toLongOrNull() ?: 0L
|
||||||
|
val myHoldings = currentBalance?.holdings?.filter { it.quantity.toInt() > 0 }?.map {
|
||||||
|
myOredsAndBalanceCodes.add(it.code)
|
||||||
|
it.code }?.toSet() ?: emptySet()
|
||||||
|
val pendingStocks = DatabaseFactory.findAllMonitoringTrades().map {
|
||||||
|
myOredsAndBalanceCodes.add(it.code)
|
||||||
|
it.code
|
||||||
|
}
|
||||||
|
|
||||||
val myCash = balance?.deposit?.replace(",", "")?.toLongOrNull() ?: 0L
|
|
||||||
val myHoldings = balance?.holdings?.filter { it.quantity.toInt() > 0 }?.map { it.code }?.toSet() ?: emptySet()
|
|
||||||
val pendingStocks = DatabaseFactory.findAllMonitoringTrades().map { it.code }
|
|
||||||
if (remainingCandidates.isEmpty()) {
|
if (remainingCandidates.isEmpty()) {
|
||||||
if (loadedTops.size < 100) {
|
if (loadedTops.size < 100) {
|
||||||
loadedTops.addAll(StockUniverseLoader.loadUniverse())
|
loadedTops.addAll(StockUniverseLoader.loadUniverse())
|
||||||
|
|||||||
@ -206,6 +206,7 @@ fun TradingDecisionLog() {
|
|||||||
ConfigIndex.MIN_PURCHASE_SCORE_INDEX,
|
ConfigIndex.MIN_PURCHASE_SCORE_INDEX,
|
||||||
ConfigIndex.SELL_PROFIT,
|
ConfigIndex.SELL_PROFIT,
|
||||||
ConfigIndex.MAX_COUNT_INDEX,
|
ConfigIndex.MAX_COUNT_INDEX,
|
||||||
|
ConfigIndex.MAX_HOLDING_COUNT,
|
||||||
)
|
)
|
||||||
items(defaults.size) { index ->
|
items(defaults.size) { index ->
|
||||||
val configKey = defaults.get(index)
|
val configKey = defaults.get(index)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user