Compare commits
9 Commits
b1d334a6cd
...
488e4e72b3
| Author | SHA1 | Date | |
|---|---|---|---|
| 488e4e72b3 | |||
| 9af9f46748 | |||
| e0fbe9a9a2 | |||
| 4c27d71701 | |||
| 74abc6314d | |||
| 92d0a84629 | |||
| b9ba8efc1a | |||
| 27356f0fc2 | |||
| ad6d00ac39 |
@ -88,7 +88,12 @@ fun getLlamaBinPath(): String {
|
|||||||
}
|
}
|
||||||
// Windows NUC
|
// Windows NUC
|
||||||
os.contains("win") -> {
|
os.contains("win") -> {
|
||||||
"$basePath/win-x64-n/llama-server.exe"
|
if (KisSession.tradeConfig.isLowPerformanceMonitoring) {
|
||||||
|
"$basePath/win-x64/llama-server.exe"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
"$basePath/win-x64-n/llama-server.exe"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> "$basePath/llama-server"
|
else -> "$basePath/llama-server"
|
||||||
}
|
}
|
||||||
@ -111,6 +116,7 @@ private var isAppStarted = false
|
|||||||
fun main() = application {
|
fun main() = application {
|
||||||
if (!isAppStarted) {
|
if (!isAppStarted) {
|
||||||
initLogger(DETAILLOG)
|
initLogger(DETAILLOG)
|
||||||
|
KisSession.tradeConfig = KisSession.loadTradeConfig()
|
||||||
try {
|
try {
|
||||||
val (port1, port2) = PortFinder.findAvailablePortPair(18080, false)
|
val (port1, port2) = PortFinder.findAvailablePortPair(18080, false)
|
||||||
if (port1 > 18000 && port2 > port1) {
|
if (port1 > 18000 && port2 > port1) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import kotlinx.coroutines.SupervisorJob
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import model.AppConfig
|
import model.AppConfig
|
||||||
|
import model.KisSession
|
||||||
import model.TradingDecision
|
import model.TradingDecision
|
||||||
import network.NewsService
|
import network.NewsService
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
@ -490,7 +491,18 @@ object TradingLogStore {
|
|||||||
decision = decision.decision ?: "HOLD",
|
decision = decision.decision ?: "HOLD",
|
||||||
confidence = decision.confidence,
|
confidence = decision.confidence,
|
||||||
reason = decision.reason ?: ""
|
reason = decision.reason ?: ""
|
||||||
))
|
).apply {
|
||||||
|
if (KisSession.tradeConfig.useTagsShare.contains(this.decision) && KisSession.tradeConfig.useLogKeywordsShare.any {
|
||||||
|
reason.contains(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
NewsService.sendTelegramMessage("${this@apply.decision}$stockName ${reason}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,11 +516,25 @@ object TradingLogStore {
|
|||||||
decision = decision,
|
decision = decision,
|
||||||
confidence = 100.0,
|
confidence = 100.0,
|
||||||
reason = log
|
reason = log
|
||||||
)
|
).apply {
|
||||||
|
if (KisSession.tradeConfig.useTagsShare.contains(decision) && KisSession.tradeConfig.useLogKeywordsShare.any {
|
||||||
|
log.contains(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
println("CALLED sendTelegramMessage")
|
||||||
|
NewsService.sendTelegramMessage("${this@apply.decision}$stockName ${log}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun addLog(tradingDecision: TradingDecision, decision: String, log: String) {
|
fun addLog(tradingDecision: TradingDecision, decision: String, log: String) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
|
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
|
||||||
@ -521,11 +547,11 @@ object TradingLogStore {
|
|||||||
reason = log
|
reason = log
|
||||||
).apply {
|
).apply {
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
println("CALLED sendTelegramMessage -1")
|
if (((tradingDecision.investmentGrade?.name?.length ?: 0) > 0 && KisSession.tradeConfig.useGradeShare.any {
|
||||||
if (decision.contains("WATCH") || ((tradingDecision.investmentGrade?.ordinal
|
tradingDecision.investmentGrade?.name?.contains(
|
||||||
?: 0) < 2)
|
it
|
||||||
) {
|
) ?: false
|
||||||
println("CALLED sendTelegramMessage OK")
|
})) {
|
||||||
NewsService.sendTelegramMessage("${this@apply.decision} ${tradingDecision.stockName}[${tradingDecision.currentPrice}] ${log}")
|
NewsService.sendTelegramMessage("${this@apply.decision} ${tradingDecision.stockName}[${tradingDecision.currentPrice}] ${log}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -534,6 +560,32 @@ object TradingLogStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addWatchLog(tradingDecision: TradingDecision, decision: String, log: String) {
|
||||||
|
synchronized(this) {
|
||||||
|
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
|
||||||
|
decisionLogs.add(
|
||||||
|
LogEntry(
|
||||||
|
time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")),
|
||||||
|
stockName = "${tradingDecision.stockName}[${tradingDecision.currentPrice}]",
|
||||||
|
decision = decision,
|
||||||
|
confidence = tradingDecision.confidence,
|
||||||
|
reason = log
|
||||||
|
).apply {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
if (KisSession.tradeConfig.useTagsShare.contains(decision) && KisSession.tradeConfig.useLogKeywordsShare.any {
|
||||||
|
log.contains(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
NewsService.sendTelegramMessage("${this@apply.decision} ${tradingDecision.stockName}[${tradingDecision.currentPrice}] ${log}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun addAnalyzer(name : String, code : String, log: String, positive : Boolean = false) {
|
fun addAnalyzer(name : String, code : String, log: String, positive : Boolean = false) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
|
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
|
||||||
@ -545,7 +597,17 @@ object TradingLogStore {
|
|||||||
decision = if (positive) "ANALYZER" else "PASS",
|
decision = if (positive) "ANALYZER" else "PASS",
|
||||||
confidence = 100.0,
|
confidence = 100.0,
|
||||||
reason = log
|
reason = log
|
||||||
)
|
).apply {
|
||||||
|
if (KisSession.tradeConfig.useTagsShare.contains(decision) && KisSession.tradeConfig.useLogKeywordsShare.any {
|
||||||
|
log.contains(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
NewsService.sendTelegramMessage("${this@apply.decision}$name[$code] ${log}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -562,9 +624,19 @@ object TradingLogStore {
|
|||||||
confidence = 100.0,
|
confidence = 100.0,
|
||||||
reason = log
|
reason = log
|
||||||
).apply {
|
).apply {
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
if (KisSession.tradeConfig.useTagsShare.contains(decision) && KisSession.tradeConfig.useLogKeywordsShare.any {
|
||||||
println("CALLED sendTelegramMessage")
|
log.contains(
|
||||||
NewsService.sendTelegramMessage("${this@apply.decision}$name[$code] ${log}")
|
it
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
var current = System.currentTimeMillis()
|
||||||
|
var sendable = noticeFilter.filter { it.key.equals(code, true) && ((current - it.value) > 1000 * 60 * 30L)}.isNotEmpty()
|
||||||
|
if (sendable) {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
NewsService.sendTelegramMessage("${this@apply.decision}$name[$code] ${log}")
|
||||||
|
|
||||||
|
}}
|
||||||
|
noticeFilter[code] = current
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -572,6 +644,8 @@ object TradingLogStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var noticeFilter = hashMapOf<String, Long>()
|
||||||
fun addNotice(name : String, code : String, log: String, qty: Int? = null) {
|
fun addNotice(name : String, code : String, log: String, qty: Int? = null) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
|
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
|
||||||
|
|||||||
@ -241,6 +241,19 @@ class TradeConfig {
|
|||||||
var end_buy_time : String = "15:10"
|
var end_buy_time : String = "15:10"
|
||||||
var enableOverSea : Boolean = false
|
var enableOverSea : Boolean = false
|
||||||
var tlg_id : String = ""
|
var tlg_id : String = ""
|
||||||
|
var CYCLE_TIMEOUT = 15 * 60 * 1000L // 한 사이클 최대 10분
|
||||||
|
var WATCHDOG_CHECK_INTERVAL = 30 * 1000L // 30초마다 생존 확인
|
||||||
|
var STUCK_THRESHOLD = 7 * 60 * 1000L // 5분간 반응 없으면 'Stuck'으로 판단
|
||||||
|
var ONE_STOCK_ALYSIS_TIME = 180000L
|
||||||
|
var isLowPerformanceMonitoring: Boolean = false
|
||||||
|
var useGradeShare : List<String> = listOf("LEVEL_4","LEVEL_5")
|
||||||
|
var useTagsShare : List<String> = listOf("NOTICE", "WATCH")
|
||||||
|
var useLogKeywordsShare : List<String> = listOf("재분석")
|
||||||
|
var useAutoRepost : Boolean = false
|
||||||
|
var minusFilter : Double = 15.0
|
||||||
|
var plusFilter : Double = 15.0
|
||||||
|
var excuteCountOnMin : Int = 2
|
||||||
|
var autoSellOrder : Boolean = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -331,7 +331,7 @@ object KisTradeService {
|
|||||||
stockCode: String,
|
stockCode: String,
|
||||||
qty: String,
|
qty: String,
|
||||||
price: String,
|
price: String,
|
||||||
isBuy: Boolean,
|
isBuy: Boolean = false,
|
||||||
orderDivision: String = "",
|
orderDivision: String = "",
|
||||||
marketCode : String = "KRX"
|
marketCode : String = "KRX"
|
||||||
): Result<String> {
|
): Result<String> {
|
||||||
@ -766,4 +766,64 @@ object KisTradeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun reserveSell(stockCode: String,
|
||||||
|
qty: String,
|
||||||
|
price: String,
|
||||||
|
targetDate : String) :Result<String> {
|
||||||
|
|
||||||
|
val config = KisSession.config
|
||||||
|
val isDomestic = stockCode.length == 6 && stockCode.all { it.isDigit() }
|
||||||
|
val baseUrl = if (config.isSimulation) vtsUrl else prodUrl
|
||||||
|
|
||||||
|
// 계좌번호 처리: 8자리면 01 자동 추가
|
||||||
|
var pureAccount = config.accountNo.replace("-", "").trim()
|
||||||
|
if (pureAccount.length == 8) pureAccount += "01"
|
||||||
|
|
||||||
|
val cano = pureAccount.take(8)
|
||||||
|
val acntPrdtCd = pureAccount.takeLast(2)
|
||||||
|
|
||||||
|
val trId = "CTSC0008U"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val response = client.post("$baseUrl/uapi/${if(isDomestic) "domestic" else "overseas"}-stock/v1/trading/order-resv") {
|
||||||
|
header("authorization", "Bearer ${config.tradeToken}")
|
||||||
|
header("appkey", if (config.isSimulation) config.vtsAppKey else config.realAppKey)
|
||||||
|
header("appsecret", if (config.isSimulation) config.vtsSecretKey else config.realSecretKey)
|
||||||
|
header("tr_id", trId)
|
||||||
|
header("custtype", "P") // [해결] 필수 헤더 추가
|
||||||
|
header("Content-Type", "application/json")
|
||||||
|
|
||||||
|
setBody(mapOf(
|
||||||
|
"CANO" to cano,
|
||||||
|
"ACNT_PRDT_CD" to acntPrdtCd,
|
||||||
|
"PDNO" to stockCode,
|
||||||
|
"ORD_DVSN_CD" to "00",
|
||||||
|
"SLL_BUY_DVSN_CD" to "01",
|
||||||
|
"ORD_QTY" to qty,
|
||||||
|
"ORD_UNPR" to price,
|
||||||
|
"ORD_OBJT_CBLC_DVSN_CD" to "10",
|
||||||
|
"RSVN_ORD_END_DT" to targetDate
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = response.body<JsonObject>() // [해결] Polymorphic 직렬화 에러 방지
|
||||||
|
val rtCd = body["rt_cd"]?.jsonPrimitive?.content
|
||||||
|
val msg = body["msg1"]?.jsonPrimitive?.content ?: "메시지 없음"
|
||||||
|
|
||||||
|
if (rtCd == "0") {
|
||||||
|
// 응답의 output 객체에서 주문 번호(ODNO) 추출
|
||||||
|
val orderNo = body["output"]?.jsonObject?.get("ODNO")?.jsonPrimitive?.content
|
||||||
|
?: body["output"]?.jsonObject?.get("odno")?.jsonPrimitive?.content // API마다 대소문자가 다를 수 있음
|
||||||
|
?: ""
|
||||||
|
Result.success(orderNo) // 성공 시 주문 번호 반환
|
||||||
|
} else {
|
||||||
|
val msg = body["msg1"]?.jsonPrimitive?.content ?: "메시지 없음"
|
||||||
|
Result.failure(Exception("❌ 오류 ($rtCd): $msg"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) { Result.failure(e) }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -671,9 +671,6 @@ object RagService {
|
|||||||
val alignmentBonus = if (s.ultraShort > s.shortTerm && s.shortTerm > s.midTerm) 3.0 else 0.0
|
val alignmentBonus = if (s.ultraShort > s.shortTerm && s.shortTerm > s.midTerm) 3.0 else 0.0
|
||||||
return (base + alignmentBonus).coerceIn(0.0, 25.0)
|
return (base + alignmentBonus).coerceIn(0.0, 25.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,9 @@ object TradingReportManager : TradingReportService {
|
|||||||
private val activePositions = mutableMapOf<String, String>()
|
private val activePositions = mutableMapOf<String, String>()
|
||||||
|
|
||||||
override fun recordAssetSnapshot(type: SnapshotType, balance: UnifiedBalance, remark: String?) {
|
override fun recordAssetSnapshot(type: SnapshotType, balance: UnifiedBalance, remark: String?) {
|
||||||
|
// if (!KisSession.tradeConfig.useAutoRepost) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
println("❌ [Report] 리포트 비동기 생성 중 오류 발생: gggg")
|
println("❌ [Report] 리포트 비동기 생성 중 오류 발생: gggg")
|
||||||
val todayDate = LocalDate.now().toString()
|
val todayDate = LocalDate.now().toString()
|
||||||
|
|||||||
@ -61,10 +61,10 @@ object AutoTradingManager {
|
|||||||
private val lastTickTime = AtomicLong(System.currentTimeMillis())
|
private val lastTickTime = AtomicLong(System.currentTimeMillis())
|
||||||
private var watchdogJob: Job? = null
|
private var watchdogJob: Job? = null
|
||||||
|
|
||||||
private const val CYCLE_TIMEOUT = 15 * 60 * 1000L // 한 사이클 최대 10분
|
var CYCLE_TIMEOUT = KisSession.tradeConfig.CYCLE_TIMEOUT
|
||||||
private const val WATCHDOG_CHECK_INTERVAL = 30 * 1000L // 30초마다 생존 확인
|
var WATCHDOG_CHECK_INTERVAL = KisSession.tradeConfig.WATCHDOG_CHECK_INTERVAL
|
||||||
private const val STUCK_THRESHOLD = 7 * 60 * 1000L // 5분간 반응 없으면 'Stuck'으로 판단
|
var STUCK_THRESHOLD = KisSession.tradeConfig.STUCK_THRESHOLD
|
||||||
private const val ONE_STOCK_ALYSIS_TIME = 180000L
|
var ONE_STOCK_ALYSIS_TIME = KisSession.tradeConfig.ONE_STOCK_ALYSIS_TIME
|
||||||
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>() // 중복 처리 방지용 (선택 사항)
|
||||||
@ -216,8 +216,8 @@ object AutoTradingManager {
|
|||||||
println("🚫 [안전 장치 작동] 현재 포지션이 가득 찼습니다. (최대 ${myOredsAndBalanceCodes.size}/${maxStocks}종목). 신규 매수를 일시 중단하고 매도에 집중합니다.")
|
println("🚫 [안전 장치 작동] 현재 포지션이 가득 찼습니다. (최대 ${myOredsAndBalanceCodes.size}/${maxStocks}종목). 신규 매수를 일시 중단하고 매도에 집중합니다.")
|
||||||
TradingLogStore.addNotice("SYSTEM", "LIMIT", "최대 보유 종목 도달로 신규 매수 일시 중단")
|
TradingLogStore.addNotice("SYSTEM", "LIMIT", "최대 보유 종목 도달로 신규 매수 일시 중단")
|
||||||
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode,hts_kor_isnm = stockName))
|
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode,hts_kor_isnm = stockName))
|
||||||
TradingLogStore.addLog(decision,"WATCH","매수 실패 : 최대 보유 종목 도달로 신규 매수 일시 중단 => 재분석 대기열에 추가")
|
TradingLogStore.addWatchLog(decision,"WATCH","매수 실패 : 최대 보유 종목 도달로 신규 매수 일시 중단 => 재분석 대기열에 추가")
|
||||||
} else {
|
} else if (KisSession.isAvailBuyTime(LocalTime.now())){
|
||||||
println("basePrice : $basePrice, oneTickLowerPrice : $oneTickLowerPrice, finalPrice : $finalPrice")
|
println("basePrice : $basePrice, oneTickLowerPrice : $oneTickLowerPrice, finalPrice : $finalPrice")
|
||||||
|
|
||||||
KisTradeService.postOrder(stockCode, orderQty, finalPrice.toLong().toString(), isBuy = true)
|
KisTradeService.postOrder(stockCode, orderQty, finalPrice.toLong().toString(), isBuy = true)
|
||||||
@ -266,11 +266,22 @@ object AutoTradingManager {
|
|||||||
|
|
||||||
if (it.message?.contains("주문가능금액을 초과") == true) {
|
if (it.message?.contains("주문가능금액을 초과") == true) {
|
||||||
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode,hts_kor_isnm = stockName))
|
AutoTradingManager.addToReanalysis(RankingStock(mksc_shrn_iscd = stockCode,hts_kor_isnm = stockName))
|
||||||
TradingLogStore.addLog(decision,"WATCH","${it.message ?: " 매수 실패"} => 재분석 대기열에 추가")
|
TradingLogStore.addWatchLog(decision,"WATCH","${it.message ?: " 매수 실패"} => 재분석 대기열에 추가")
|
||||||
} else {
|
} else {
|
||||||
TradingLogStore.addLog(decision,"BUY",it.message ?: "매수 실패")
|
TradingLogStore.addLog(decision,"BUY",it.message ?: "매수 실패")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
val unfilledResult = KisTradeService.fetchUnfilledOrders()
|
||||||
|
unfilledResult.onSuccess { response ->
|
||||||
|
response.filter { it.sll_buy_dvsn_cd == "02" }.forEach { order ->
|
||||||
|
TradingLogStore.addNotice(order.prdt_name,order.pdno,"[주문 취소] 매수시간 종료 후 모든 매수 취소")
|
||||||
|
KisTradeService.cancelOrder(
|
||||||
|
order.ord_no, // 원주문번호
|
||||||
|
order.pdno
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -442,11 +453,32 @@ object AutoTradingManager {
|
|||||||
&& holding.valuationProfitAmount.toDouble() >= KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)) {
|
&& holding.valuationProfitAmount.toDouble() >= KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)) {
|
||||||
println("${holding.name} ${holding.profitRate.toDouble()} ${holding.valuationProfitAmount.toDouble()} ${KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)} , ${KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)} , ${KisSession.config.getValues(ConfigIndex.STOP_LOSS)}")
|
println("${holding.name} ${holding.profitRate.toDouble()} ${holding.valuationProfitAmount.toDouble()} ${KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)} , ${KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)} , ${KisSession.config.getValues(ConfigIndex.STOP_LOSS)}")
|
||||||
val profit = holding.profitRate.toDouble()
|
val profit = holding.profitRate.toDouble()
|
||||||
TradingLogStore.addNotice(
|
// TradingLogStore.addNotice(
|
||||||
"보유주식[${holding.name}]",
|
// "보유주식[${holding.name}]",
|
||||||
holding.code,
|
// holding.code,
|
||||||
"수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함."
|
// "수익률($profit%) -> ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함."
|
||||||
)
|
// )
|
||||||
|
|
||||||
|
var targetPrice = holding.avgPrice.toDouble()
|
||||||
|
|
||||||
|
tradeService.postOrder(
|
||||||
|
stockCode = holding.code,
|
||||||
|
qty = holding.availOrderCount,
|
||||||
|
price = targetPrice.toInt().toString(),
|
||||||
|
isBuy = false,
|
||||||
|
orderDivision = if (marketCode.equals("Y")) "07" else "",
|
||||||
|
marketCode = if (marketCode.equals("Y")) "KRX" else "NXT"
|
||||||
|
).onSuccess { newOrderNo ->
|
||||||
|
println("✅ [${if(marketCode.equals("Y"))"시간외 단일가" else "대체거래소"} 손절가이드에 따라 매매 주문 완료] ${holding.name}: $newOrderNo")
|
||||||
|
TradingLogStore.addSellLog(
|
||||||
|
holding.code,
|
||||||
|
targetPrice.toString(),
|
||||||
|
"SELL",
|
||||||
|
"☠️ 보유 주식 손절 처리 [수익률 : ${profit}%] ${holding.valuationProfitAmount} 손해 중이며 ${if(marketCode.equals("Y"))"시간외 단일가" else "대체거래소"}에 손절가이드에 따라 매매 주문 완료."
|
||||||
|
)
|
||||||
|
}.onFailure { err->
|
||||||
|
println("✅ [${if(marketCode.equals("Y"))"시간외 단일가" else "대체거래소"} 손절가이드에 따라 매매 주문 실패] ${holding.name}: $err")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
analyzeDeepLossHoldingsAfterMarket(holding)
|
analyzeDeepLossHoldingsAfterMarket(holding)
|
||||||
}
|
}
|
||||||
@ -482,7 +514,9 @@ object AutoTradingManager {
|
|||||||
targetPrice = targetPrice
|
targetPrice = targetPrice
|
||||||
isBefore930 = true
|
isBefore930 = true
|
||||||
} else {
|
} else {
|
||||||
targetPrice = MarketUtil.roundToTickSize(targetPrice + MarketUtil.getTickSize(targetPrice))
|
targetPrice = MarketUtil.roundToTickSize(
|
||||||
|
targetPrice + MarketUtil.getTickSize(targetPrice)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
println("🔄 [보유 주식 주문] ${holding.name} (${holding.code}) 매도 목표 ${targetPrice} 미체결 매도 건 재주문 시도")
|
println("🔄 [보유 주식 주문] ${holding.name} (${holding.code}) 매도 목표 ${targetPrice} 미체결 매도 건 재주문 시도")
|
||||||
tradeService.postOrder(
|
tradeService.postOrder(
|
||||||
@ -498,18 +532,20 @@ object AutoTradingManager {
|
|||||||
"SELL",
|
"SELL",
|
||||||
"🎊 보유 주식[예상수익 : ${holding.profitRate}] ${if (isBefore930) "09:30 이전 현시세{${holding.currentPrice}}로 매도[$targetPrice] 주문" else "09:30 이후 시세{${holding.currentPrice}} 기준 호가 위 매도[$targetPrice] 주문"} 완료"
|
"🎊 보유 주식[예상수익 : ${holding.profitRate}] ${if (isBefore930) "09:30 이전 현시세{${holding.currentPrice}}로 매도[$targetPrice] 주문" else "09:30 이후 시세{${holding.currentPrice}} 기준 호가 위 매도[$targetPrice] 주문"} 완료"
|
||||||
)
|
)
|
||||||
DatabaseFactory.saveAutoTrade(AutoTradeItem(
|
DatabaseFactory.saveAutoTrade(
|
||||||
orderNo = newOrderNo,
|
AutoTradeItem(
|
||||||
code = holding.code,
|
orderNo = newOrderNo,
|
||||||
name = holding.name,
|
code = holding.code,
|
||||||
quantity = holding.quantity.toInt(),
|
name = holding.name,
|
||||||
profitRate = 0.0,
|
quantity = holding.quantity.toInt(),
|
||||||
stopLossRate = 0.0,
|
profitRate = 0.0,
|
||||||
targetPrice = targetPrice.toDouble(),
|
stopLossRate = 0.0,
|
||||||
stopLossPrice = 0.0,
|
targetPrice = targetPrice.toDouble(),
|
||||||
status = "SELLING",
|
stopLossPrice = 0.0,
|
||||||
isDomestic = true
|
status = "SELLING",
|
||||||
))
|
isDomestic = true
|
||||||
|
)
|
||||||
|
)
|
||||||
syncAndExecute(newOrderNo)
|
syncAndExecute(newOrderNo)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
TradingLogStore.addSellLog(
|
TradingLogStore.addSellLog(
|
||||||
@ -528,8 +564,9 @@ object AutoTradingManager {
|
|||||||
&& holding.valuationProfitAmount.toDouble() >= KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)) {
|
&& holding.valuationProfitAmount.toDouble() >= KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)) {
|
||||||
println("${holding.name} ${holding.profitRate.toDouble()} ${holding.valuationProfitAmount.toDouble()} ${KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)} , ${KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)} , ${KisSession.config.getValues(ConfigIndex.STOP_LOSS)}")
|
println("${holding.name} ${holding.profitRate.toDouble()} ${holding.valuationProfitAmount.toDouble()} ${KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY)} , ${KisSession.config.getValues(ConfigIndex.LOSS_MINRATE)} , ${KisSession.config.getValues(ConfigIndex.STOP_LOSS)}")
|
||||||
val profit = holding.profitRate.toDouble()
|
val profit = holding.profitRate.toDouble()
|
||||||
var targetPrice = holding.currentPrice.toDouble()
|
|
||||||
targetPrice = MarketUtil.roundToTickSize(targetPrice + (MarketUtil.getTickSize(targetPrice) * 3.0))
|
var targetPrice = if (KisSession.tradeConfig.autoSellOrder ) holding.avgPrice.toDouble() else holding.currentPrice.toDouble()
|
||||||
|
targetPrice = MarketUtil.roundToTickSize(targetPrice + MarketUtil.getTickSize(targetPrice) * 3.0)
|
||||||
|
|
||||||
tradeService.postOrder(
|
tradeService.postOrder(
|
||||||
stockCode = holding.code,
|
stockCode = holding.code,
|
||||||
@ -542,7 +579,7 @@ object AutoTradingManager {
|
|||||||
holding.code,
|
holding.code,
|
||||||
targetPrice.toString(),
|
targetPrice.toString(),
|
||||||
"SELL",
|
"SELL",
|
||||||
"☠️ 보유 주식 손절 처리 [수익률 : ${profit}%] ${holding.valuationProfitAmount} 손해 중이며 현시세{${holding.currentPrice}}로 기준 호가 위 매도[$targetPrice] 주문 완료"
|
"☠️ 보유 주식 손절 처리 [수익률 : ${profit}%] ${holding.valuationProfitAmount} 손해 중이며 현제 손절 가이드에 적합함 시장가 매도."
|
||||||
)
|
)
|
||||||
}.onFailure { err->
|
}.onFailure { err->
|
||||||
println("✅ [보유 주식 손절 처리] 실패 ${err.message}")
|
println("✅ [보유 주식 손절 처리] 실패 ${err.message}")
|
||||||
@ -565,7 +602,9 @@ object AutoTradingManager {
|
|||||||
private suspend fun analyzeDeepLossHoldingsAfterMarket(holding: UnifiedStockHolding, isForce : Boolean = false) { // 💡 [신규 추가] 수익률이 크게 마이너스인 종목(-5.0% 이하) 심층 가이드 분석
|
private suspend fun analyzeDeepLossHoldingsAfterMarket(holding: UnifiedStockHolding, isForce : Boolean = false) { // 💡 [신규 추가] 수익률이 크게 마이너스인 종목(-5.0% 이하) 심층 가이드 분석
|
||||||
val now = LocalTime.now()
|
val now = LocalTime.now()
|
||||||
val currentMinute = now.minute
|
val currentMinute = now.minute
|
||||||
if ((!isForce && (now.hour == 8 || now.hour == 16 || now.hour == 17)) || (isForce && (currentMinute % 5 == 0))) {
|
if ((holding.availOrderCount.toInt()
|
||||||
|
?: 0) > 0 && ((!isForce && (now.hour == 8 || now.hour == 16 || now.hour == 17)) || (isForce && (currentMinute % 5 == 0)))
|
||||||
|
) {
|
||||||
val profit = holding.profitRate.toDouble()
|
val profit = holding.profitRate.toDouble()
|
||||||
val lossThreshold = -5.0 // 가이드를 작동시킬 손실 기준선 (필요시 ConfigIndex 로 빼셔도 좋습니다)
|
val lossThreshold = -5.0 // 가이드를 작동시킬 손실 기준선 (필요시 ConfigIndex 로 빼셔도 좋습니다)
|
||||||
if (profit <= lossThreshold) {
|
if (profit <= lossThreshold) {
|
||||||
@ -768,15 +807,6 @@ object AutoTradingManager {
|
|||||||
suspend fun checkBalance(isMorning: Boolean = true) {
|
suspend fun checkBalance(isMorning: Boolean = true) {
|
||||||
if (isMorning) {
|
if (isMorning) {
|
||||||
currentBalance = KisTradeService.fetchIntegratedBalance().getOrNull()
|
currentBalance = KisTradeService.fetchIntegratedBalance().getOrNull()
|
||||||
// currentBalance?.let { currentBalance ->
|
|
||||||
// if (LocalTime.now().isBefore(LocalTime.of(18,1))) {
|
|
||||||
// TradingReportManager.recordAssetSnapshot(
|
|
||||||
// if (LocalTime.now().isAfter(LocalTime.of(18, 0))
|
|
||||||
// ) SnapshotType.END else SnapshotType.MIDDLE, currentBalance, ""
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (KisSession.config.take_profit) currentBalance?.let { resumePendingSellOrders(KisTradeService, it) }
|
if (KisSession.config.take_profit) currentBalance?.let { resumePendingSellOrders(KisTradeService, it) }
|
||||||
if (KisSession.tradeConfig.auto_cancel_pending_buy) { checkAndCancelPendingBuyOrders() }
|
if (KisSession.tradeConfig.auto_cancel_pending_buy) { checkAndCancelPendingBuyOrders() }
|
||||||
} else {
|
} else {
|
||||||
@ -795,19 +825,15 @@ object AutoTradingManager {
|
|||||||
|
|
||||||
val orderTimeMillis = parseOrderTime(order.ord_tmd)
|
val orderTimeMillis = parseOrderTime(order.ord_tmd)
|
||||||
val elapsedMillis = currentTime - orderTimeMillis
|
val elapsedMillis = currentTime - orderTimeMillis
|
||||||
|
if (elapsedMillis >= KisSession.tradeConfig.auto_cancel_pending_time ) {
|
||||||
// 조건 A: 설정된 대기 시간 경과 여부
|
|
||||||
if (elapsedMillis >= KisSession.tradeConfig.auto_cancel_pending_time) {
|
|
||||||
|
|
||||||
// 2. 현재가 조회 (가격을 비교하기 위해)
|
// 2. 현재가 조회 (가격을 비교하기 위해)
|
||||||
val currentPrice = KisTradeService.fetchCurrentPrice(order.pdno).getOrNull()?.stck_prpr?.toDouble() ?: 0.0
|
val currentPrice = KisTradeService.fetchCurrentPrice(order.pdno).getOrNull()?.stck_prpr?.toDouble() ?: 0.0
|
||||||
val orderedPrice = order.ord_unpr.toDoubleOrNull() ?: 0.0
|
val orderedPrice = order.ord_unpr.toDoubleOrNull() ?: 0.0
|
||||||
|
|
||||||
// 조건 B: 현재가와 주문가의 괴리율 체크 (현재가가 너무 올라갔거나 내려갔을 때)
|
// 조건 B: 현재가와 주문가의 괴리율 체크 (현재가가 너무 올라갔거나 내려갔을 때)
|
||||||
val priceGap = Math.abs(currentPrice - orderedPrice) / orderedPrice
|
val priceGap = Math.abs(currentPrice - orderedPrice) / orderedPrice
|
||||||
println("checkAndCancelPendingBuyOrders order $order ${elapsedMillis / 1000L}초 ${priceGap}% 차이")
|
println("checkAndCancelPendingBuyOrders order $order ${elapsedMillis / 1000L}초 ${priceGap}% 차이")
|
||||||
if (priceGap >= KisSession.tradeConfig.auto_cancel_pending_rate) {
|
if (priceGap >= KisSession.tradeConfig.auto_cancel_pending_rate) {
|
||||||
TradingLogStore.addNotice(order.prdt_name,order.pdno,"[주문 취소] ${order.prdt_name} (${order.pdno}) - 시간경과 및 가격괴리(${String.format("%.2f", priceGap * 100)}%)로 취소 시도")
|
TradingLogStore.addNotice(order.prdt_name,order.pdno,"[주문 취소] ${order.prdt_name} (${order.pdno}) - 시간경과 및 가격괴리(${String.format("%.2f", priceGap)}%)로 취소 시도")
|
||||||
KisTradeService.cancelOrder(
|
KisTradeService.cancelOrder(
|
||||||
order.ord_no, // 원주문번호
|
order.ord_no, // 원주문번호
|
||||||
order.pdno
|
order.pdno
|
||||||
@ -818,6 +844,21 @@ object AutoTradingManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun cancelAllPendingSellOrders(
|
||||||
|
) {
|
||||||
|
// 1. 미체결 내역 조회
|
||||||
|
val unfilledResult = KisTradeService.fetchUnfilledOrders()
|
||||||
|
unfilledResult.onSuccess { response ->
|
||||||
|
response.filter { it.sll_buy_dvsn_cd == "01" }.forEach { order ->
|
||||||
|
TradingLogStore.addNotice(order.prdt_name,order.pdno,"[주문 취소] 정규장 시작전 모든 매도 주문 취소")
|
||||||
|
KisTradeService.cancelOrder(
|
||||||
|
order.ord_no, // 원주문번호
|
||||||
|
order.pdno
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 주문 시간 문자열을 Millis로 변환하는 유틸리티 (당일 주문 기준)
|
// 주문 시간 문자열을 Millis로 변환하는 유틸리티 (당일 주문 기준)
|
||||||
fun parseOrderTime(ordTmd: String): Long {
|
fun parseOrderTime(ordTmd: String): Long {
|
||||||
return try {
|
return try {
|
||||||
@ -859,7 +900,7 @@ object AutoTradingManager {
|
|||||||
}.filter {
|
}.filter {
|
||||||
val rate = it.prdy_ctrt.toDouble()
|
val rate = it.prdy_ctrt.toDouble()
|
||||||
val corpInfo = DartCodeManager.getCorpCode(it.code)
|
val corpInfo = DartCodeManager.getCorpCode(it.code)
|
||||||
val isOk = (rate > 0 && rate < 15) || (rate < 0 && rate > -15)
|
val isOk = (rate > 0 && rate < KisSession.tradeConfig.plusFilter) || (rate < 0 && rate > (KisSession.tradeConfig.minusFilter * -1))
|
||||||
if (corpInfo?.cName.isNullOrEmpty()) {
|
if (corpInfo?.cName.isNullOrEmpty()) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
@ -905,47 +946,62 @@ object AutoTradingManager {
|
|||||||
}
|
}
|
||||||
println("⏱️ [Cycle End] ${LocalTime.now()}")
|
println("⏱️ [Cycle End] ${LocalTime.now()}")
|
||||||
}
|
}
|
||||||
private var lastForceCheckMinute = -1 // 마지막으로 강제 체크를 수행한 '분'을 저장
|
// private var lastForceCheckMinute = -1 // 마지막으로 강제 체크를 수행한 '분'을 저장
|
||||||
|
private val executionCountMap = mutableMapOf<String, Int>()
|
||||||
suspend fun sellSchedule() {
|
suspend fun sellSchedule() {
|
||||||
if (KisSession.config.take_profit == false) {
|
if (KisSession.config.take_profit == false) return
|
||||||
|
val now = LocalTime.now()
|
||||||
|
val timeKey = String.format("%02d:%02d", now.hour, now.minute) // 예: "09:05"
|
||||||
|
val currentCount = executionCountMap.getOrDefault(timeKey, 0)
|
||||||
|
if (currentCount >= KisSession.tradeConfig.excuteCountOnMin) { return }
|
||||||
|
|
||||||
} else {
|
var isExecuted = false
|
||||||
val now = LocalTime.now()
|
val currentMinute = now.minute
|
||||||
val currentMinute = now.minute
|
if (now.isBefore(LocalTime.of(8,50)) && now.isAfter(LocalTime.of(8,45))) {
|
||||||
if (now.hour == 9 && currentMinute % 2 == 1
|
cancelAllPendingSellOrders()
|
||||||
) {
|
isExecuted = true
|
||||||
if (lastForceCheckMinute != currentMinute) {
|
} else if ( (now.isBefore(LocalTime.of(16,0)) && now.isAfter(KisSession.endBuyTime())) ) {
|
||||||
TradingLogStore.addAnalyzer(
|
val unfilledResult = KisTradeService.fetchUnfilledOrders()
|
||||||
" - ",
|
unfilledResult.onSuccess { response ->
|
||||||
" - ",
|
response.filter { it.sll_buy_dvsn_cd == "02" }.forEach { order ->
|
||||||
"⏰ [강제 스케줄 실행] 오전 9시 ${currentMinute}분 - 보유주식 매도 체크를 시작합니다.",
|
TradingLogStore.addNotice(order.prdt_name,order.pdno,"[주문 취소] 정규장 후 모든 매수 취소")
|
||||||
true
|
KisTradeService.cancelOrder(
|
||||||
|
order.ord_no, // 원주문번호
|
||||||
|
order.pdno
|
||||||
)
|
)
|
||||||
println("⏰ [강제 스케줄 실행] 오전 9시 ${currentMinute}분 - 보유주식 매도 체크를 시작합니다.")
|
|
||||||
checkBalance()
|
|
||||||
lastForceCheckMinute = currentMinute // 실행 완료 기록
|
|
||||||
}
|
|
||||||
} else if (((now.hour == 8 && KisSession.tradeConfig.before_nxt) || (now.hour >= 16 && now.hour < 20 && KisSession.tradeConfig.after_nxt)) && (currentMinute % 2 == 1)) {
|
|
||||||
if (lastForceCheckMinute != currentMinute) {
|
|
||||||
TradingLogStore.addAnalyzer(
|
|
||||||
" - ",
|
|
||||||
" - ",
|
|
||||||
"⏰ [강제 스케줄 실행] 오후 ${now.hour}시 ${currentMinute}분 - 보유주식 시간외 단일가 또는 대체마켓 체크를 시작합니다.",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
var list = mutableListOf<String>("X")
|
|
||||||
if (now.hour != 8 && now.hour < 18) {
|
|
||||||
list.add("Y")
|
|
||||||
}
|
|
||||||
list.forEach { code ->
|
|
||||||
KisTradeService.fetchIntegratedBalance(code).getOrNull()?.let {
|
|
||||||
sellingAfterMarketOnePrice(KisTradeService, it, code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastForceCheckMinute = currentMinute // 실행 완료 기록
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isExecuted = true
|
||||||
|
} else if (now.hour == 9) {
|
||||||
|
TradingLogStore.addAnalyzer(
|
||||||
|
" - ",
|
||||||
|
" - ",
|
||||||
|
"⏰ [강제 스케줄 실행] 오전 9시 ${currentMinute}분 - 보유주식 매도 체크를 시작합니다.",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
println("⏰ [강제 스케줄 실행] 오전 9시 ${currentMinute}분 - 보유주식 매도 체크를 시작합니다.")
|
||||||
|
checkBalance()
|
||||||
|
isExecuted = true
|
||||||
|
} else if (((now.hour == 8 && KisSession.tradeConfig.before_nxt && currentMinute < 45) || (now.hour >= 16 && now.hour < 20 && KisSession.tradeConfig.after_nxt)) && (currentMinute % 2 == 1)) {
|
||||||
|
TradingLogStore.addAnalyzer(
|
||||||
|
" - ",
|
||||||
|
" - ",
|
||||||
|
"⏰ [강제 스케줄 실행] 오후 ${now.hour}시 ${currentMinute}분 - 보유주식 시간외 단일가 또는 대체마켓 체크를 시작합니다.",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
var list = mutableListOf<String>("X")
|
||||||
|
if (now.hour != 8 && now.hour < 18) {
|
||||||
|
list.add("Y")
|
||||||
|
}
|
||||||
|
list.forEach { code ->
|
||||||
|
KisTradeService.fetchIntegratedBalance(code).getOrNull()?.let {
|
||||||
|
sellingAfterMarketOnePrice(KisTradeService, it, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isExecuted = true
|
||||||
}
|
}
|
||||||
|
if (isExecuted) { executionCountMap[timeKey] = currentCount + 1 }
|
||||||
|
if (now.hour >= 20) { executionCountMap.clear() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -291,7 +291,7 @@ object SafeScraper {
|
|||||||
private val totalRam = HardwareDetector.getTotalRamGb()
|
private val totalRam = HardwareDetector.getTotalRamGb()
|
||||||
|
|
||||||
// RAM 8GB당 1개 수준으로 설정하되, 최대 10~12개로 제한 (CPU 부하 방지)
|
// RAM 8GB당 1개 수준으로 설정하되, 최대 10~12개로 제한 (CPU 부하 방지)
|
||||||
private val maxParallel = totalRam.div(6).toInt()
|
private val maxParallel = totalRam.div(4).toInt()
|
||||||
|
|
||||||
// 동시 처리를 1개로 줄여서 안정성을 극대화 (추천)
|
// 동시 처리를 1개로 줄여서 안정성을 극대화 (추천)
|
||||||
// Playwright는 여러 페이지를 띄울 때 CPU/메모리 점유율이 매우 높습니다.
|
// Playwright는 여러 페이지를 띄울 때 CPU/메모리 점유율이 매우 높습니다.
|
||||||
|
|||||||
@ -82,22 +82,12 @@ object SystemSleepPreventer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process?.isAlive == true) return
|
if (process?.isAlive == true) return
|
||||||
if (!isWin) {
|
|
||||||
// try {
|
|
||||||
// // -i: 시스템 절전 방지, -d: 디스플레이 취침 방지, -m: 디스크 유휴 상태 방지
|
|
||||||
// val command = listOf("caffeinate", "-i", "-d", "-m")
|
|
||||||
// process = ProcessBuilder(command).start()
|
|
||||||
// println("☕ [System] caffeinate 실행됨: 앱이 켜져 있는 동안 절전 모드가 방지됩니다.")
|
|
||||||
// } catch (e: Exception) {
|
|
||||||
// println("⚠️ [System] caffeinate 실행 실패: ${e.message}")
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
start2()
|
start2()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start2() {
|
fun start2() {
|
||||||
println("🚀 화면 잠금 방지 프로그램이 시작되었습니다. (작동 시간: $startTime ~ $endTime)")
|
println("🚀 화면 잠금 방지 프로그램이 시작되었습니다. (작동 시간: $startTime ~ $endTime)")
|
||||||
|
|
||||||
// 1분(60초)마다 체크
|
// 1분(60초)마다 체크
|
||||||
scheduler.scheduleAtFixedRate({
|
scheduler.scheduleAtFixedRate({
|
||||||
if (isWorkingTime()) {
|
if (isWorkingTime()) {
|
||||||
@ -105,7 +95,7 @@ object SystemSleepPreventer {
|
|||||||
} else {
|
} else {
|
||||||
println("💤 현재는 휴식 시간입니다. (${LocalTime.now().withNano(0)})")
|
println("💤 현재는 휴식 시간입니다. (${LocalTime.now().withNano(0)})")
|
||||||
}
|
}
|
||||||
}, 0, 60 * 2, TimeUnit.SECONDS)
|
}, 0, 150, TimeUnit.SECONDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isWorkingTime(): Boolean {
|
private fun isWorkingTime(): Boolean {
|
||||||
@ -136,21 +126,6 @@ object SystemSleepPreventer {
|
|||||||
|
|
||||||
private val osName = System.getProperty("os.name").lowercase()
|
private val osName = System.getProperty("os.name").lowercase()
|
||||||
|
|
||||||
// 설정 시간
|
|
||||||
private val dimTime = LocalTime.of(16, 0) // 오후 4시 이후 최저 밝기
|
|
||||||
|
|
||||||
fun start3() {
|
|
||||||
scheduler.scheduleAtFixedRate({
|
|
||||||
val now = LocalTime.now()
|
|
||||||
|
|
||||||
// 16:00 이후라면 밝기를 낮춤
|
|
||||||
if (now.isAfter(dimTime) || now.isBefore(LocalTime.of(8, 30))) {
|
|
||||||
setBrightness(0)
|
|
||||||
} else {
|
|
||||||
setBrightness(100) // 업무 시간에는 다시 밝게 (80%)
|
|
||||||
}
|
|
||||||
}, 0, 10, TimeUnit.MINUTES) // 10분마다 체크
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setBrightness(level: Int) {
|
private fun setBrightness(level: Int) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -68,7 +68,6 @@ fun TradingDecisionLog() {
|
|||||||
val filterOptions = listOf("전체", "BUY", "SELL", "SETTING","ANALYZER","WATCH","AFTER","NOTICE")//"PASS",,"RETRY""HOLD",
|
val filterOptions = listOf("전체", "BUY", "SELL", "SETTING","ANALYZER","WATCH","AFTER","NOTICE")//"PASS",,"RETRY""HOLD",
|
||||||
var llmAnalyser by remember { mutableStateOf(AutoTradingManager.llmAnalyser) }
|
var llmAnalyser by remember { mutableStateOf(AutoTradingManager.llmAnalyser) }
|
||||||
val tradeConfig by remember {
|
val tradeConfig by remember {
|
||||||
KisSession.tradeConfig = KisSession.loadTradeConfig()
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
println("CALLED sendTelegramMessage -1")
|
println("CALLED sendTelegramMessage -1")
|
||||||
val now = java.time.LocalTime.now(java.time.ZoneId.of("Asia/Seoul"))
|
val now = java.time.LocalTime.now(java.time.ZoneId.of("Asia/Seoul"))
|
||||||
|
|||||||
@ -11238,5 +11238,145 @@
|
|||||||
{
|
{
|
||||||
"code": "238490",
|
"code": "238490",
|
||||||
"name": "힘스"
|
"name": "힘스"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000980",
|
||||||
|
"name": "교보19호스팩"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "001320",
|
||||||
|
"name": "교보20호스팩"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "478340",
|
||||||
|
"name": "나라스페이스테크놀로지"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "403850",
|
||||||
|
"name": "더핑크퐁컴퍼니"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000010",
|
||||||
|
"name": "덕양에너젠"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "491000",
|
||||||
|
"name": "리브스메드"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "394420",
|
||||||
|
"name": "리센스메디컬"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000930",
|
||||||
|
"name": "미래에셋비전스팩8호"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000960",
|
||||||
|
"name": "미래에셋비전스팩9호"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "488900",
|
||||||
|
"name": "비츠로넥스텍"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "001150",
|
||||||
|
"name": "삼성스팩13호"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000130",
|
||||||
|
"name": "삼진식품"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "061090",
|
||||||
|
"name": "세나테크놀로지"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "490470",
|
||||||
|
"name": "세미파이브"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "001300",
|
||||||
|
"name": "신한제17호스팩"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "388210",
|
||||||
|
"name": "씨엠티엑스"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "493280",
|
||||||
|
"name": "아이엠바이오로직스"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "476830",
|
||||||
|
"name": "알지노믹스"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "459550",
|
||||||
|
"name": "알트"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000110",
|
||||||
|
"name": "액스비스"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "458350",
|
||||||
|
"name": "에스팀"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000090",
|
||||||
|
"name": "에임드바이오"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "001050",
|
||||||
|
"name": "유진스팩12호"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "469610",
|
||||||
|
"name": "이노테크"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "261520",
|
||||||
|
"name": "이지스"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "493330",
|
||||||
|
"name": "지에프아이"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000820",
|
||||||
|
"name": "카나프테라퓨틱스"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "439960",
|
||||||
|
"name": "코스모로보틱스"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "464490",
|
||||||
|
"name": "쿼드메디슨"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "494120",
|
||||||
|
"name": "큐리오시스"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "466690",
|
||||||
|
"name": "키움히어로제1호스팩"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "001310",
|
||||||
|
"name": "키움히어로제2호스팩"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "487580",
|
||||||
|
"name": "폴레드"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "001010",
|
||||||
|
"name": "하나36호스팩"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "408470",
|
||||||
|
"name": "한패스"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Loading…
x
Reference in New Issue
Block a user