...
This commit is contained in:
@@ -89,7 +89,7 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
|
||||
val now = java.time.LocalTime.now(java.time.ZoneId.of("Asia/Seoul"))
|
||||
// 08:30 ~ 15:30 사이이고, 키값이 최소한 하나라도 존재할 때 자동 실행
|
||||
if (now.isAfter(java.time.LocalTime.of(8, 30)) && now.isBefore(java.time.LocalTime.of(15, 30))) {
|
||||
if (config.realAppKey.isNotEmpty() || config.vtsAppKey.isNotEmpty()) {
|
||||
if (config.realAppKey.isNotEmpty() && config.vtsAppKey.isNotEmpty() && config.embedModelPath.isNotEmpty() && config.modelPath.isNotEmpty()) {
|
||||
SystemSleepPreventer.wakeDisplay() // 모니터 켜기
|
||||
statusMessage = "⏰ 자동 실행 시간(08:30)입니다. 시스템을 가동합니다."
|
||||
authenticateAndStart()
|
||||
|
||||
@@ -2,6 +2,7 @@ package ui
|
||||
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
@@ -9,15 +10,20 @@ import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.DragData
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.onExternalDrag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
@@ -26,7 +32,10 @@ import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.launch
|
||||
import model.ConfigIndex
|
||||
import model.KisSession
|
||||
import network.StockUniverseLoader
|
||||
import service.AutoTradingManager
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@@ -140,6 +149,19 @@ fun TradingDecisionLog() {
|
||||
}
|
||||
|
||||
Divider(Modifier.padding(bottom = 8.dp))
|
||||
CsvDropZone(
|
||||
onUniverseUpdated = { updatedList ->
|
||||
// UI 갱신 (필요한 경우)
|
||||
// currentUniverse = updatedList
|
||||
|
||||
// 💡 봇의 실제 작업 큐인 loadedTops 에도 갱신된 데이터를 덮어씌워 줌
|
||||
AutoTradingManager.loadedTops.clear()
|
||||
AutoTradingManager.loadedTops.addAll(updatedList)
|
||||
AutoTradingManager.loadedTops.shuffle() // 섞어주면 편향 분석 방지!
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// [수정] filteredLogs를 사용하여 최신 로그가 위로 오게 표시
|
||||
LazyColumn(
|
||||
@@ -426,4 +448,88 @@ fun getRemaining(original: String, common: String): String {
|
||||
if (common.isEmpty()) return original
|
||||
// 가장 처음 발견되는 공통 문자열을 한 번만 제거
|
||||
return original.replaceFirst(common, "").trim()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun parseSmartCsv(file: File): List<Pair<String, String>> {
|
||||
val result = mutableListOf<Pair<String, String>>()
|
||||
try {
|
||||
val lines = file.readLines()
|
||||
if (lines.isEmpty()) return result
|
||||
|
||||
// 1. 헤더 분석하여 열(Column) 인덱스 자동 추적
|
||||
val headers = lines[0].split(",").map { it.replace("\"", "").trim() }
|
||||
val codeIndex = headers.indexOfFirst { it.contains("종목코드") }
|
||||
val nameIndex = headers.indexOfFirst { it.contains("종목명") }
|
||||
|
||||
// 헤더를 못 찾았다면 기본값 (0번: 코드, 1번: 이름)으로 폴백
|
||||
val finalCodeIdx = if (codeIndex != -1) codeIndex else 0
|
||||
val finalNameIdx = if (nameIndex != -1) nameIndex else 1
|
||||
|
||||
// 2. 데이터 추출
|
||||
for (i in 1 until lines.size) {
|
||||
val line = lines[i]
|
||||
if (line.isBlank()) continue
|
||||
|
||||
val parts = line.split(",").map { it.replace("\"", "").trim() }
|
||||
if (parts.size > maxOf(finalCodeIdx, finalNameIdx)) {
|
||||
val code = parts[finalCodeIdx]
|
||||
val name = parts[finalNameIdx]
|
||||
|
||||
// 6자리 숫자로 된 정상적인 종목코드인지 검증
|
||||
if (code.length == 6 && code.all { it.isDigit() }) {
|
||||
result.add(code to name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("❌ CSV 파싱 에러: ${e.message}")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun CsvDropZone(
|
||||
onUniverseUpdated: (List<Pair<String, String>>) -> Unit
|
||||
) {
|
||||
var isDragging by remember { mutableStateOf(false) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp)
|
||||
.background(if (isDragging) Color(0xFFE3F2FD) else Color(0xFFFAFAFA))
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = if (isDragging) Color.Blue else Color.LightGray,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.onExternalDrag(
|
||||
onDragStart = { isDragging = true },
|
||||
onDragExit = { isDragging = false },
|
||||
onDrop = { state ->
|
||||
isDragging = false
|
||||
val dragData = state.dragData
|
||||
if (dragData is DragData.FilesList) {
|
||||
val fileUris = dragData.readFiles()
|
||||
fileUris.firstOrNull { it.endsWith(".csv", ignoreCase = true) }?.let { uri ->
|
||||
// 💡 여기서 깔끔하게 StockUniverseLoader 에 처리를 위임!
|
||||
val file = File(URI(uri))
|
||||
val updatedUniverse = StockUniverseLoader.parseAndMergeCsv(file)
|
||||
onUniverseUpdated(updatedUniverse)
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = if (isDragging) "📥 파일을 놓아서 업데이트!" else "📁 [CSV 추가] 파일을 드래그하여 유니버스 자동 병합",
|
||||
color = if (isDragging) Color.Blue else Color.Gray,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user