package service import TradingDecision import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import model.CandleData import java.time.LocalDateTime import java.time.LocalTime import java.time.ZoneId import java.time.format.DateTimeFormatter import kotlin.collections.List import kotlin.math.* // service/AutoTradingManager.kt typealias TradingDecisionCallback = (TradingDecision?, Boolean)->Unit object AutoTradingManager { private val scope = CoroutineScope(Dispatchers.Default) val targetStocks = mutableListOf>() fun addStock(stockName : String,stockCode : String, result :TradingDecisionCallback) { targetStocks.add(Pair(stockName, stockCode)) startTradingLoop(stockName,stockCode,result) } fun startTradingLoop(stockName : String, stockCode : String, result :TradingDecisionCallback) { scope.launch { println("๐Ÿš€ 10๋ถ„ ์ฃผ๊ธฐ ์ž๋™ ๋ถ„์„ ๋ฐ ๋งค๋งค ์‹œ์ž‘: ${LocalTime.now()}") // targetStocks.forEach { stockCode -> launch { // ์ข…๋ชฉ๋ณ„ ๋ณ‘๋ ฌ ๋ถ„์„ (M3 Pro ํŒŒ์›Œ ํ™œ์šฉ) RagService.processStock(stockName, stockCode,result) // {decision,b -> //// when (decision?.decision) { //// "BUY" -> if (decision.confidence > 70) executeOrder(stockCode, "๋งค์ˆ˜") //// "SELL" -> executeOrder(stockCode, "๋งค๋„") //// else -> println("[$stockCode] ๊ด€๋ง ์œ ์ง€: ${decision?.reason}") //// } // result(decision,b) // } } // } // targetStocks.re // delay(10 * 60 * 1000) // 10๋ถ„ ๋Œ€๊ธฐ } } private fun executeOrder(code: String, type: String) { // ์‹ค์ œ ์ฆ๊ถŒ์‚ฌ API ํ˜ธ์ถœ ๋กœ์ง (ํ•œ๊ตญํˆฌ์ž์ฆ๊ถŒ, ํ‚ค์›€ ๋“ฑ) println("๐Ÿ”ฅ [์ฃผ๋ฌธ ์ง‘ํ–‰] $code $type ์™„๋ฃŒ") } } object TechnicalAnalyzer { var monthly: List = emptyList() var weekly: List = emptyList() var daily: List = emptyList() var min30: List = emptyList() data class InvestmentScores( val ultraShort: Int, // ์ดˆ๋‹จ๊ธฐ (๋ถ„๋ด‰/์—๋„ˆ์ง€) val shortTerm: Int, // ๋‹จ๊ธฐ (์ผ๋ด‰/๋‰ด์Šค) val midTerm: Int, // ์ค‘๊ธฐ (์ฃผ๋ด‰/์žฌ๋ฌด) val longTerm: Int // ์žฅ๊ธฐ (์›”๋ด‰/ํŽ€๋”๋ฉ˜ํ„ธ) ) fun calculateScores( financialScore: Int // ์žฌ๋ฌด์ œํ‘œ ์ ์ˆ˜ (์„ฑ์žฅ๋ฅ  ๋“ฑ ๊ธฐ๋ฐ˜) ): InvestmentScores { // 1. ์ดˆ๋‹จ๊ธฐ (๋ถ„๋ด‰ + ์—๋„ˆ์ง€ ์ง€ํ‘œ ์œ„์ฃผ) val ultra = (TechnicalAnalyzer.calculateMFI(min30, 14) * 0.4 + TechnicalAnalyzer.calculateStochastic(min30) * 0.3 + (if(TechnicalAnalyzer.calculateChange(min30.takeLast(10)) > 0) 30 else 0)).toInt() // 2. ๋‹จ๊ธฐ (์ผ๋ด‰ ์ถ”์„ธ + OBV ์—๋„ˆ์ง€) val short = (TechnicalAnalyzer.calculateRSI(daily) * 0.3 + (if(TechnicalAnalyzer.calculateOBV(daily) > 0) 40 else 10) + (if(TechnicalAnalyzer.calculateChange(daily.takeLast(3)) > 0) 30 else 0)).toInt() // 3. ์ค‘๊ธฐ (์ฃผ๋ด‰ + ์žฌ๋ฌด ์ ์ˆ˜ ํ˜ผํ•ฉ) val mid = (if(TechnicalAnalyzer.calculateChange(weekly) > 0) 40 else 10) + (financialScore * 0.6).toInt() // 4. ์žฅ๊ธฐ (์›”๋ด‰ + ์„นํ„ฐ/๊ธฐ์—… ํŽ€๋”๋ฉ˜ํ„ธ) val long = (if(TechnicalAnalyzer.calculateChange(monthly) > 0) 50 else 0) + (financialScore * 0.5).toInt() return InvestmentScores( ultraShort = ultra.coerceIn(0, 100), shortTerm = short.coerceIn(0, 100), midTerm = mid.coerceIn(0, 100), longTerm = long.coerceIn(0, 100) ) } fun generateComprehensiveReport(): String { // [1] ๋‹จ๊ธฐ ์—๋„ˆ์ง€ ์ง€ํ‘œ ๊ณ„์‚ฐ (์ตœ๊ทผ 30๋ถ„๋ด‰ ๊ธฐ์ค€) val obv = calculateOBV(min30) val mfi = calculateMFI(min30, 14) val adLine = calculateADLine(min30) // [2] ์‹œ๊ณ„์—ด๋ณ„ ๊ฐ€๊ฒฉ ๋ณ€๋™ ๋ฐ ์ถ”์„ธ ์š”์•ฝ val m10 = min30.takeLast(10) val change10 = calculateChange(m10) val change30 = calculateChange(min30) val changeDaily = calculateChange(daily.takeLast(2)) // ์ „์ผ ๋Œ€๋น„ // [3] ์ดํ‰์„  ๋ฐ ๊ฐ€๊ฒฉ ์œ„์น˜ val ma5 = m10.takeLast(5).map { it.stck_prpr.toDouble() }.average() val currentPrice = min30.last().stck_prpr.toDouble() val signal = ScalpingAnalyzer().analyze(min30.toScalpingList(),isDailyBullish()) // [4] ๊ฑฐ๋ž˜๋Ÿ‰ ๊ฐ•๋„ val avgVol30 = min30.map { it.cntg_vol.toLong() }.average() val recentVol5 = m10.takeLast(5).map { it.cntg_vol.toLong() }.average() val volStrength = if (avgVol30 > 0) recentVol5 / avgVol30 else 1.0 val atr = calculateATR(min30) val stochK = calculateStochastic(min30) val priceRange30 = min30.maxOf { it.stck_hgpr.toDouble() } - min30.minOf { it.stck_lwpr.toDouble() } return """ - ์ดˆ/๋‹จํƒ€ ์ข…ํ•ฉ ์Šค์ฝ”์–ด: ${signal.compositeScore} / 100 - ์ดˆ/๋‹จํƒ€ ๋งค์ˆ˜ ์‹ ํ˜ธ ๋ฐœ์ƒ ์—ฌ๋ถ€: ${if (signal.buySignal) "YES" else "NO"} - ์ดˆ/๋‹จํƒ€ ์„ฑ๊ณต ํ™•๋ฅ  ์˜ˆ์ธก: ${signal.successProbPct}% - ์ดˆ/๋‹จํƒ€ ์œ„ํ—˜ ๋“ฑ๊ธ‰: ${signal.riskLevel} (ATR ๋ณ€๋™์„ฑ ๊ธฐ๋ฐ˜) - ์ดˆ/๋‹จํƒ€ RSI: ${"%.1f".format(signal.rsi)} / ๊ฑฐ๋ž˜๋Ÿ‰ ๋น„์œจ: ${"%.1f".format(signal.volRatio)}๋ฐฐ - ์ดˆ/๋‹จํƒ€ ๊ถŒ์žฅ ๊ฐ€๊ฒฉ: ์†์ ˆ๊ฐ€(${signal.suggestedSlPrice.toInt()}์›), ์ต์ ˆ๊ฐ€(${signal.suggestedTpPrice.toInt()}์›) - ์›”๋ด‰/์ฃผ๋ด‰ ์œ„์น˜: ${if(calculateChange(monthly) > 0) "์žฅ๊ธฐ ์ƒ์Šน" else "์žฅ๊ธฐ ํ•˜๋ฝ"} / ${if(calculateChange(weekly) > 0) "์ค‘๊ธฐ ์ƒ์Šน" else "์ค‘๊ธฐ ํ•˜๋ฝ"} - ์ผ๋ด‰ ๋Œ€๋น„: ${ "%.2f".format(changeDaily) }% ๋ณ€๋™ - 30๋ถ„ ๋Œ€๋น„: ${ "%.2f".format(change30) }% ๋ณ€๋™ - 10๋ถ„ ๋Œ€๋น„: ${ "%.2f".format(change10) }% ๋ณ€๋™ - ์ดํ‰์„  ์ƒํƒœ: ํ˜„์žฌ๊ฐ€(${currentPrice.toInt()}) vs MA5(${ma5.toInt()}) -> ${if(currentPrice > ma5) "์ƒ๋‹จ ์œ„์น˜" else "ํ•˜๋‹จ ์œ„์น˜"} - OBV (๋ˆ„์  ๊ฑฐ๋ž˜๋Ÿ‰ ์—๋„ˆ์ง€): ${ "%.0f".format(obv) } - MFI (์ž๊ธˆ ์œ ์ž… ์ง€์ˆ˜): ${ "%.1f".format(mfi) } - A/D (๋ˆ„์  ๋ถ„์‚ฐ ๋ผ์ธ): ${ "%.0f".format(adLine) } - ๊ฑฐ๋ž˜๋Ÿ‰ ๊ฐ•๋„: ์ตœ๊ทผ 5๋ถ„ ํ‰๊ท ์ด 30๋ถ„ ํ‰๊ท ์˜ ${ "%.1f".format(volStrength) }๋ฐฐ ์ˆ˜์ค€ - ATR (ํ‰๊ท  ๋ณ€๋™ํญ): ${"%.0f".format(atr)}์› - 30๋ถ„ ๋‚ด ์ตœ๋Œ€ ์ง„ํญ: ${"%.0f".format(priceRange30)}์› - ์Šคํ† ์บ์Šคํ‹ฑ(%K): ${"%.1f".format(stochK)} - ๋ณ€๋™์„ฑ ๊ฐ•๋„: ํ˜„์žฌ ์ง„ํญ์ด ATR ๋Œ€๋น„ ${"%.1f".format(priceRange30 / atr)}๋ฐฐ ์ˆ˜์ค€ - 30๋ถ„๋ด‰ ์ตœ๊ณ ๊ฐ€: ${min30.maxOf { it.stck_hgpr.toInt() }} - 30๋ถ„๋ด‰ ์ตœ์ €๊ฐ€: ${min30.minOf { it.stck_lwpr.toInt() }} - RSI(14): ${ "%.1f".format(calculateRSI(min30)) } """.trimIndent() } /** * ATR (Average True Range): ์ตœ๊ทผ ๋ณ€๋™ ํญ์˜ ํ‰๊ท . ๊ทธ๋ž˜ํ”„์˜ '์ถœ๋ ์ž„' ํฌ๊ธฐ๋ฅผ ์ธก์ • */ fun calculateATR(candles: List, period: Int = 14): Double { val sub = candles.takeLast(period + 1) val trList = mutableListOf() for (i in 1 until sub.size) { val high = sub[i].stck_hgpr.toDouble() val low = sub[i].stck_lwpr.toDouble() val prevClose = sub[i - 1].stck_prpr.toDouble() val tr = maxOf(high - low, Math.abs(high - prevClose), Math.abs(low - prevClose)) trList.add(tr) } return trList.average() } /** * Stochastic (%K): ์ตœ๊ทผ ๊ฐ€๊ฒฉ ๋ฒ”์œ„ ๋‚ด์—์„œ ํ˜„์žฌ๊ฐ€์˜ ์œ„์น˜ (0~100) * ๋ฐ˜๋ณต๋˜๋Š” ํŒŒ๋™(Ups and Downs)์—์„œ ํ˜„์žฌ๊ฐ€ ๊ณ ์ ์ธ์ง€ ์ €์ ์ธ์ง€ ํŒ๋‹จ */ fun calculateStochastic(candles: List, period: Int = 14): Double { val sub = candles.takeLast(period) val highest = sub.maxOf { it.stck_hgpr.toDouble() } val lowest = sub.minOf { it.stck_lwpr.toDouble() } val current = sub.last().stck_prpr.toDouble() return if (highest != lowest) (current - lowest) / (highest - lowest) * 100 else 50.0 } private fun calculateChange(list: List): Double { val start = list.first().stck_oprc.toDouble() val end = list.last().stck_prpr.toDouble() return if (start != 0.0) ((end - start) / start) * 100 else 0.0 } private fun calculateRSI(list: List): Double { if (list.size < 2) return 50.0 var gains = 0.0 var losses = 0.0 for (i in 1 until list.size) { val diff = list[i].stck_prpr.toDouble() - list[i - 1].stck_prpr.toDouble() if (diff > 0) gains += diff else losses -= diff } return if (gains + losses == 0.0) 50.0 else (gains / (gains + losses)) * 100 } fun isDailyBullish(): Boolean { if (daily.size < 20) return true // ๋ฐ์ดํ„ฐ ๋ถ€์กฑ ์‹œ ๋ณด์ˆ˜์ ์œผ๋กœ true ํ˜น์€ ์˜ˆ์™ธ์ฒ˜๋ฆฌ val currentPrice = daily.last().stck_prpr.toDouble() // 1. MA20 (ํ•œ ๋‹ฌ ์ƒ๋ช…์„ ) ๊ณ„์‚ฐ val ma20 = daily.takeLast(20).map { it.stck_prpr.toDouble() }.average() // 2. MA5 (๋‹จ๊ธฐ ๊ฐ€์†๋„) ๊ณ„์‚ฐ val ma5 = daily.takeLast(5).map { it.stck_prpr.toDouble() }.average() // 3. ๋ฐฉํ–ฅ์„ฑ (์–ด์ œ MA5 vs ์˜ค๋Š˜ MA5) val prevMa5 = daily.dropLast(1).takeLast(5).map { it.stck_prpr.toDouble() }.average() val isMa5Rising = ma5 > prevMa5 // [์ตœ์ข… ํŒ๋ณ„]: ํ˜„์žฌ๊ฐ€๊ฐ€ ์ƒ๋ช…์„  ์œ„์— ์žˆ๊ณ , ๋‹จ๊ธฐ ์ดํ‰์„ ์ด ๊ณ ๊ฐœ๋ฅผ ๋“ค์—ˆ์„ ๋•Œ๋งŒ 'Bull(์ƒ์Šน)'๋กœ ๊ฐ„์ฃผ return currentPrice > ma20 && isMa5Rising } fun calculateOBV(candles: List): Double { var obv = 0.0 for (i in 1 until candles.size) { val prevClose = candles[i - 1].stck_prpr.toDouble() val currClose = candles[i].stck_prpr.toDouble() val currVol = candles[i].cntg_vol.toDouble() when { currClose > prevClose -> obv += currVol currClose < prevClose -> obv -= currVol } } return obv } /** * MFI (Money Flow Index) ๊ณ„์‚ฐ (๊ธฐ๊ฐ„: ๋ณดํ†ต 14์ผ) */ fun calculateMFI(candles: List, period: Int = 14): Double { val subList = candles.takeLast(period + 1) var posFlow = 0.0 var negFlow = 0.0 for (i in 1 until subList.size) { val prevTypical = (subList[i-1].stck_hgpr.toDouble() + subList[i-1].stck_lwpr.toDouble() + subList[i-1].stck_prpr.toDouble()) / 3 val currTypical = (subList[i].stck_hgpr.toDouble() + subList[i].stck_lwpr.toDouble() + subList[i].stck_prpr.toDouble()) / 3 val moneyFlow = currTypical * subList[i].cntg_vol.toDouble() if (currTypical > prevTypical) posFlow += moneyFlow else if (currTypical < prevTypical) negFlow += moneyFlow } return if (negFlow == 0.0) 100.0 else 100 - (100 / (1+ (posFlow / negFlow))) } private fun calculateADLine(candles: List): Double { var ad = 0.0 candles.forEach { val high = it.stck_hgpr.toDouble(); val low = it.stck_lwpr.toDouble(); val close = it.stck_prpr.toDouble() val mfv = if (high != low) ((close - low) - (high - close)) / (high - low) else 0.0 ad += mfv * it.cntg_vol.toDouble() } return ad } fun clear() { monthly = emptyList() weekly = emptyList() daily = emptyList() min30 = emptyList() } } class ScalpingAnalyzer { companion object { private const val SMA_SHORT = 10 private const val SMA_LONG = 20 private const val RSI_WINDOW = 14 private const val VOL_WINDOW = 20 private const val VOL_SURGE_THRESHOLD = 1.5 private const val RSI_THRESHOLD = 50.0 private const val BB_LOWER_POS = 0.2 private const val BB_UPPER_POS = 0.8 private const val ATR_WINDOW = 14 private const val DEFAULT_SL_PCT = -0.5 private const val DEFAULT_TP_PCT = 1.0 private const val HIGH_SCORE_THRESHOLD = 80 } fun computeRSI(closes: List, window: Int = RSI_WINDOW): List { val rsi = mutableListOf() if (closes.size < window + 1) return rsi for (i in window until closes.size) { val gains = mutableListOf() val losses = mutableListOf() for (j in (i - window + 1) until i + 1) { val delta = closes[j] - closes[j - 1] if (delta > 0) gains.add(delta) else losses.add(abs(delta)) } val avgGain = gains.average() val avgLoss = losses.average() val rs = if (avgLoss > 0) avgGain / avgLoss else Double.POSITIVE_INFINITY rsi.add(100.0 - (100.0 / (1.0 + rs))) } return rsi } fun bollingerBands(closes: List, window: Int = SMA_LONG): Triple, List, List> { val sma = mutableListOf() val upper = mutableListOf() val lower = mutableListOf() for (i in window - 1 until closes.size) { val slice = closes.subList(i - window + 1, i + 1) val mean = slice.average() val std = sqrt(slice.map { (it - mean).pow(2.0) }.average()) * 2.0 sma.add(mean) upper.add(mean + std) lower.add(mean - std) } return Triple(upper, sma, lower) } fun analyze(candles: List, isDailyBullish: Boolean): ScalpingSignalModel { if (candles.size < SMA_LONG) throw IllegalArgumentException("์ตœ์†Œ 20๋ด‰ ํ•„์š”") val closes = candles.map { it.close } val volumes = candles.map { it.volume } // ์ง€ํ‘œ ๊ณ„์‚ฐ val sma10 = simpleMovingAverage(closes, SMA_SHORT) val sma20 = simpleMovingAverage(closes, SMA_LONG) val rsiList = computeRSI(closes) val volAvg = simpleMovingAverage(volumes, VOL_WINDOW) val volRatioList = volumes.mapIndexed { i, v -> if (i >= VOL_WINDOW) v / volAvg[i - VOL_WINDOW] else 0.0 } val (bbUpper, bbMiddle, bbLower) = bollingerBands(closes) val current = candles.last() val idx = candles.size - 1 val currentClose = current.close val sma10Now = if (sma10.size > 0) sma10.last() else 0.0 val sma20Now = if (sma20.size > 0) sma20.last() else 0.0 val rsiNow = if (rsiList.isNotEmpty()) rsiList.last() else 0.0 val volRatioNow = volRatioList.last() val bbPos = if (bbUpper.isNotEmpty() && bbLower.isNotEmpty()) { (currentClose - bbLower.last()) / (bbUpper.last() - bbLower.last()) } else 0.5 val nearHigh = candles.takeLast(6).dropLast(1).maxOf { it.high } val isBreakout = currentClose > nearHigh // [์ถ”๊ฐ€] 2. ์บ”๋“ค ํŒจํ„ด: ๋ง์น˜ํ˜•/์—ญ๋ง์น˜ํ˜• ๋“ฑ ๊ผฌ๋ฆฌ ๋ถ„์„ (ํ•˜๋‹จ ์ง€์ง€๋ ฅ ํ™•์ธ) val bodySize = abs(current.close - current.open) val lowerShadow = minOf(current.close, current.open) - current.low val isBottomSupport = lowerShadow > bodySize * 1.5 // ๋ฐ‘๊ผฌ๋ฆฌ๊ฐ€ ๋ชธํ†ต๋ณด๋‹ค ๊ธด ๊ฒฝ์šฐ // ์‹ ํ˜ธ ์กฐ๊ฑด ๊ณ ๋„ํ™” // ์ผ๋ด‰ ์ถ”์„ธ(dailyTrend)๊ฐ€ ์‚ด์•„์žˆ๊ณ , ์ „๊ณ ์ ์„ ๋ŒํŒŒ(isBreakout)ํ•  ๋•Œ ๋” ๋†’์€ ์ ์ˆ˜ // val maBull = currentClose > sma10Now && sma10Now > sma20Now val rsiBull = rsiNow > RSI_THRESHOLD val volSurge = volRatioNow > VOL_SURGE_THRESHOLD val bbGood = bbPos > BB_LOWER_POS && bbPos < BB_UPPER_POS val maBull = currentClose > sma10Now && sma10Now > sma20Now val buySignal = maBull && rsiBull && volSurge && bbGood && isBreakout // val buySignal = maBull && rsiBull && volSurge && bbGood val score = (if (maBull) 25 else 0) + (if (rsiBull) 15 else 0) + (if (isBreakout) 20 else 0) + // ๋ŒํŒŒ ์—๋„ˆ์ง€ ๊ฐ€์ค‘์น˜ (minOf((volRatioNow - 1.0) * 20, 20.0)).toInt() + (if (bbGood) 10 else 0) + (if (isDailyBullish) 10 else 0) // ๋‹จํƒ€/์žฅ๊ธฐ ์ •๋ ฌ ์ ์ˆ˜ // ์œ„ํ—˜๋„ (ATR proxy) val returns = closes.mapIndexed { i, c -> if (i > 0) (c - closes[i-1])/closes[i-1] * 100 else 0.0 } val atrProxy = if (returns.size >= ATR_WINDOW) { returns.subList(returns.size - ATR_WINDOW, returns.size).average() } else 1.0 val riskLevel = when { abs(atrProxy) < 1 -> "Low" abs(atrProxy) < 2 -> "Medium" else -> "High" } // ์„ฑ๊ณต ํ™•๋ฅ  & SL/TP val successProb = if (buySignal) 75.0 else 35.0 + (score / 100.0 * 20) val slPrice = currentClose * (1 + DEFAULT_SL_PCT / 100) val tpPrice = currentClose * (1 + DEFAULT_TP_PCT / 100) val rrRatio = abs(DEFAULT_TP_PCT / DEFAULT_SL_PCT) return ScalpingSignalModel( currentPrice = currentClose, buySignal = buySignal, compositeScore = minOf(score.toInt(), 100), successProbPct = successProb, riskLevel = riskLevel, rsi = rsiNow, volRatio = volRatioNow, suggestedSlPrice = slPrice, suggestedTpPrice = tpPrice, riskRewardRatio = rrRatio ) } private fun simpleMovingAverage(values: List, window: Int): List { val sma = mutableListOf() for (i in window - 1 until values.size) { val slice = values.subList(i - window + 1, i + 1) sma.add(slice.average()) } return sma } } data class Candle( val timestamp: Long, val open: Double, val high: Double, val low: Double, val close: Double, val volume: Double ) data class ScalpingSignalModel( val currentPrice: Double, val buySignal: Boolean, val compositeScore: Int, // 0-100: ์ข…ํ•ฉ ๋งค์ˆ˜ ์ถ”์ฒœ๋„ (80+ ๊ฐ•๋งค์ˆ˜) val successProbPct: Double, // ์„ฑ๊ณต ํ™•๋ฅ  ์ถ”์ • % val riskLevel: String, // "Low", "Medium", "High" val rsi: Double, val volRatio: Double, val suggestedSlPrice: Double, // ์†์ ˆ ๊ฐ€๊ฒฉ val suggestedTpPrice: Double, // ์ต์ ˆ ๊ฐ€๊ฒฉ val riskRewardRatio: Double ) fun CandleData.toScalpingCandle(): Candle { // 1. ๋‚ ์งœ(YYYYMMDD)์™€ ์‹œ๊ฐ„(HHMMSS) ๋ฌธ์ž์—ด ๊ฒฐํ•ฉ val dateTimeStr = "${this.stck_bsop_date}${this.stck_cntg_hour}" val formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss") // 2. ํƒ€์ž„์Šคํƒฌํ”„(Epoch Milliseconds) ๊ณ„์‚ฐ val timestamp = try { val ldt = LocalDateTime.parse(dateTimeStr, formatter) ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() } catch (e: Exception) { // ์‹œ๊ฐ„ ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ ํ˜„์žฌ ์‹œ์Šคํ…œ ์‹œ๊ฐ„ ์‚ฌ์šฉ System.currentTimeMillis() } // 3. String ํ•„๋“œ๋“ค์„ Double๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ Candle ๊ฐ์ฒด ์ƒ์„ฑ return Candle( timestamp = timestamp, open = this.stck_oprc.toDoubleOrNull() ?: 0.0, high = this.stck_hgpr.toDoubleOrNull() ?: 0.0, low = this.stck_lwpr.toDoubleOrNull() ?: 0.0, close = this.stck_prpr.toDoubleOrNull() ?: 0.0, // stck_prpr๊ฐ€ ์ข…๊ฐ€ ์—ญํ•  volume = this.cntg_vol.toDoubleOrNull() ?: 0.0 ) } /** * ๋ฆฌ์ŠคํŠธ ์ „์ฒด๋ฅผ ๋ณ€ํ™˜ํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ */ fun List.toScalpingList(): List { return this.map { it.toScalpingCandle() } }