557 lines
22 KiB
Kotlin
557 lines
22 KiB
Kotlin
import androidx.compose.runtime.mutableStateListOf
|
|
import kotlinx.serialization.Serializable
|
|
import model.AppConfig
|
|
import network.TradingDecision
|
|
import org.jetbrains.exposed.sql.*
|
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
|
import org.jetbrains.exposed.sql.javatime.datetime
|
|
import org.jetbrains.exposed.sql.transactions.transaction
|
|
import java.io.File
|
|
import java.time.LocalDateTime
|
|
import java.time.LocalTime
|
|
import java.time.format.DateTimeFormatter
|
|
|
|
|
|
object TradeStatus {
|
|
const val PENDING_BUY = "PENDING_BUY" // 매수 주문 중
|
|
const val MONITORING = "MONITORING" // 매수 체결 후 감시 중
|
|
const val SELLING = "SELLING" // 손절/익절 매도 주문 중
|
|
const val EXPIRED = "EXPIRED" // 서버와 불일치 (유저 판단 대기)
|
|
const val COMPLETED = "COMPLETED" // 거래 종료
|
|
}
|
|
|
|
// 1. 앱 설정 테이블
|
|
object ConfigTable : Table("app_config") {
|
|
val id = integer("id").autoIncrement()
|
|
val realAppKey = varchar("real_app_key", 255).default("")
|
|
val realSecretKey = varchar("real_secret_key", 255).default("")
|
|
val realAccountNo = varchar("real_account_no", 20).default("")
|
|
val vtsAppKey = varchar("vts_app_key", 255).default("")
|
|
val vtsSecretKey = varchar("vts_secret_key", 255).default("")
|
|
val vtsAccountNo = varchar("vts_account_no", 20).default("")
|
|
|
|
val nAppKey = varchar("naver_app_key", 255).default("")
|
|
val nSecretKey = varchar("naver_secret_key", 255).default("")
|
|
val dAppKey = varchar("dart_api_key", 255).default("")
|
|
|
|
val isSimulation = bool("is_simulation").default(false)
|
|
val modelPath = varchar("model_path", 512).default("")
|
|
val embedModelPath = varchar("embed_model_path", 512).default("")
|
|
val htsId = varchar("hts_id", 50).default("") // HTS ID 컬럼 추가
|
|
val fees_and_taxrate = double("fees_and_taxrate").default( 0.33)
|
|
val minimum_net_profit = double("minimum_net_profit").default( 0.5)
|
|
val buy_weight = double("buy_weight").default( 2.0)
|
|
val max_budget = double("max_budget").default( 80000.0)
|
|
val max_price = double("max_price").default( 40000.0)
|
|
val min_price = double("min_price").default( 800.0)
|
|
val min_purchase_score = double("min_purchase_score").default( 65.0)
|
|
val sell_profit = double("sell_profit").default( 1.0)
|
|
val grade_5_buy = integer("grade_5_buy").default(0)
|
|
val grade_4_buy = integer("grade_4_buy").default(1)
|
|
val grade_3_buy = integer("grade_3_buy").default(1)
|
|
val grade_2_buy = integer("grade_2_buy").default(2)
|
|
val grade_1_buy = integer("grade_1_buy").default(3)
|
|
val grade_5_profit = double("grade_5_profit").default(1.8)
|
|
val grade_4_profit = double("grade_4_profit").default(1.3)
|
|
val grade_3_profit = double("grade_3_profit").default(0.9)
|
|
val grade_2_profit = double("grade_2_profit").default(0.7)
|
|
val grade_1_profit = double("grade_1_profit").default(0.5)
|
|
|
|
val grade_5_allocationrate = double("grade_5_allocationrate").default(1.0)
|
|
val grade_4_allocationrate = double("grade_4_allocationrate").default(0.8)
|
|
val grade_3_allocationrate = double("grade_3_allocationrate").default(0.6)
|
|
val grade_2_allocationrate = double("grade_2_allocationrate").default(0.4)
|
|
val grade_1_allocationrate = double("grade_1_allocationrate").default(0.3)
|
|
|
|
val take_profit = bool("take_profit").default(false)
|
|
val stop_Loss = bool("stop_Loss").default(false)
|
|
|
|
val loss_minrate = double("loss_minrate").default(3.5)
|
|
val loss_maxrate = double("loss_maxrate").default(10.0)
|
|
val loss_max_money = double("loss_max_money").default(10000.0)
|
|
|
|
|
|
|
|
|
|
|
|
val max_count = integer("max_count").default(20)
|
|
override val primaryKey = PrimaryKey(id)
|
|
}
|
|
|
|
// 2. 자동매매 감시 테이블
|
|
object AutoTradeTable : Table("auto_trades") {
|
|
val id = integer("id").autoIncrement()
|
|
val stockCode = varchar("stock_code", 20)
|
|
val stockName = varchar("stock_name", 100)
|
|
val quantity = integer("quantity").default(0)
|
|
val profitRate = double("profit_rate").default(0.0)
|
|
val stopLossRate = double("stop_loss_rate").default(0.0)
|
|
val targetPrice = double("target_price").default(0.0)
|
|
val stopLossPrice = double("stop_loss_price").default(0.0)
|
|
val orderNo = varchar("order_no", 50).uniqueIndex()
|
|
val status = varchar("status", 20).default("PENDING_BUY")
|
|
val isDomestic = bool("is_domestic").default(true)
|
|
override val primaryKey = PrimaryKey(id)
|
|
}
|
|
|
|
|
|
// 3. 거래 내역 테이블
|
|
object TradeLogTable : Table("trade_logs") {
|
|
val id = long("id").autoIncrement()
|
|
val stockCode = varchar("stock_code", 20)
|
|
val stockName = varchar("stock_name", 50)
|
|
val tradeType = varchar("trade_type", 10)
|
|
val price = double("price")
|
|
val quantity = integer("quantity")
|
|
val timestamp = datetime("timestamp")
|
|
val logMessage = text("log_message")
|
|
override val primaryKey = PrimaryKey(id)
|
|
}
|
|
|
|
object HolidayTable : Table("holiday_cache") {
|
|
val bassDt = varchar("bass_dt", 8) // YYYYMMDD
|
|
val isHoliday = bool("is_holiday")
|
|
override val primaryKey = PrimaryKey(bassDt)
|
|
}
|
|
|
|
object DatabaseFactory {
|
|
fun init() {
|
|
val dbPath = File("db/autotrade_db").absolutePath
|
|
Database.connect(
|
|
"jdbc:h2:$dbPath;DB_CLOSE_DELAY=-1;",
|
|
driver = "org.h2.Driver"
|
|
)
|
|
|
|
transaction {
|
|
|
|
// 테이블 생성 (AutoTradeTable 포함)
|
|
SchemaUtils.createMissingTablesAndColumns(ConfigTable, TradeLogTable, AutoTradeTable,HolidayTable)
|
|
}
|
|
}
|
|
|
|
fun saveHoliday(date: String, holiday: Boolean) = transaction {
|
|
HolidayTable.replace {
|
|
it[bassDt] = date
|
|
it[isHoliday] = holiday
|
|
}
|
|
}
|
|
|
|
// 특정 날짜의 휴장 여부 조회
|
|
fun getHoliday(date: String): Boolean? = transaction {
|
|
HolidayTable.select { HolidayTable.bassDt eq date }
|
|
.map { it[HolidayTable.isHoliday] }
|
|
.singleOrNull()
|
|
}
|
|
|
|
/**
|
|
* 새로운 자동매매 건 등록 (주로 PENDING_BUY 상태로 시작)
|
|
*/
|
|
fun saveAutoTrade(item: AutoTradeItem) = transaction {
|
|
AutoTradeTable.insert {
|
|
it[stockCode] = item.code
|
|
it[stockName] = item.name
|
|
it[quantity] = item.quantity
|
|
it[profitRate] = item.profitRate
|
|
it[stopLossRate] = item.stopLossRate
|
|
it[targetPrice] = item.targetPrice
|
|
it[stopLossPrice] = item.stopLossPrice
|
|
it[orderNo] = item.orderNo
|
|
it[status] = item.status
|
|
it[isDomestic] = item.isDomestic
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 상태 변경 및 가격 업데이트 (예: PENDING_BUY -> MONITORING)
|
|
*/
|
|
fun updateAutoTrade(item: AutoTradeItem) = transaction {
|
|
val id = item.id ?: return@transaction
|
|
AutoTradeTable.update({ AutoTradeTable.id eq id }) {
|
|
it[targetPrice] = item.targetPrice
|
|
it[stopLossPrice] = item.stopLossPrice
|
|
it[orderNo] = item.orderNo
|
|
it[status] = item.status
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 감시 중인 모든 종목 리스트 반환 (ActiveTradeSection UI용)
|
|
*/
|
|
fun getActiveAutoTrades(): List<AutoTradeItem> = transaction {
|
|
AutoTradeTable.select {
|
|
AutoTradeTable.status inList listOf("MONITORING", "SELLING", "PENDING_BUY")
|
|
}.map { mapToAutoTradeItem(it) }
|
|
}
|
|
|
|
/**
|
|
* 종목코드로 현재 감시 중인 설정이 있는지 확인 (UI 체크박스 상태용)
|
|
*/
|
|
fun findConfigByCode(code: String): AutoTradeItem? = transaction {
|
|
AutoTradeTable.select {
|
|
(AutoTradeTable.stockCode eq code) and (AutoTradeTable.status eq "MONITORING")
|
|
}.lastOrNull()?.let { mapToAutoTradeItem(it) }
|
|
}
|
|
|
|
fun deleteAutoTrade(id: Int) = transaction {
|
|
AutoTradeTable.deleteWhere { AutoTradeTable.id eq id }
|
|
}
|
|
|
|
fun findAllPendingBuyCodes(): Set<String> {
|
|
return transaction {
|
|
AutoTradeTable.select {
|
|
(AutoTradeTable.status eq "PENDING_BUY") or (AutoTradeTable.status eq "ORDERED")
|
|
}.map { it[AutoTradeTable.stockCode] }.toSet()
|
|
}
|
|
}
|
|
|
|
fun findAllMonitoringTrades(): List<AutoTradeItem> {
|
|
return transaction {
|
|
AutoTradeTable.select {
|
|
AutoTradeTable.status neq "COMPLETED"
|
|
}.map { mapToAutoTradeItem(it) }
|
|
}
|
|
}
|
|
|
|
private fun mapToAutoTradeItem(it: ResultRow) = AutoTradeItem(
|
|
id = it[AutoTradeTable.id],
|
|
code = it[AutoTradeTable.stockCode],
|
|
name = it[AutoTradeTable.stockName],
|
|
quantity = it[AutoTradeTable.quantity],
|
|
profitRate = it[AutoTradeTable.profitRate],
|
|
stopLossRate = it[AutoTradeTable.stopLossRate],
|
|
targetPrice = it[AutoTradeTable.targetPrice],
|
|
stopLossPrice = it[AutoTradeTable.stopLossPrice],
|
|
orderNo = it[AutoTradeTable.orderNo],
|
|
status = it[AutoTradeTable.status],
|
|
isDomestic = it[AutoTradeTable.isDomestic],
|
|
)
|
|
|
|
// --- 기존 설정 및 로그 관련 함수 ---
|
|
|
|
fun saveTradeLog(code: String, name: String, type: String, price: Double, qty: Int, msg: String) {
|
|
transaction {
|
|
TradeLogTable.insert {
|
|
it[stockCode] = code
|
|
it[stockName] = name
|
|
it[tradeType] = type
|
|
it[TradeLogTable.price] = price
|
|
it[quantity] = qty
|
|
it[timestamp] = LocalDateTime.now()
|
|
it[logMessage] = msg
|
|
}
|
|
}
|
|
}
|
|
|
|
fun findConfigByAccount(accountNo: String): AppConfig? = transaction {
|
|
ConfigTable.select {
|
|
(ConfigTable.realAccountNo eq accountNo) or (ConfigTable.vtsAccountNo eq accountNo)
|
|
}.lastOrNull()?.let {
|
|
AppConfig(
|
|
realAppKey = it[ConfigTable.realAppKey],
|
|
realSecretKey = it[ConfigTable.realSecretKey],
|
|
realAccountNo = it[ConfigTable.realAccountNo],
|
|
vtsAppKey = it[ConfigTable.vtsAppKey],
|
|
vtsSecretKey = it[ConfigTable.vtsSecretKey],
|
|
vtsAccountNo = it[ConfigTable.vtsAccountNo],
|
|
nAppKey = it[ConfigTable.nAppKey],
|
|
nSecretKey = it[ConfigTable.nSecretKey],
|
|
dAppKey = it[ConfigTable.dAppKey],
|
|
htsId = it[ConfigTable.htsId],
|
|
isSimulation = it[ConfigTable.isSimulation], // htsId 로드
|
|
modelPath = it[ConfigTable.modelPath],
|
|
embedModelPath = it[ConfigTable.embedModelPath],
|
|
FEES_AND_TAXRATE = it[ConfigTable.fees_and_taxrate],
|
|
MINIMUM_NET_PROFIT = it[ConfigTable.minimum_net_profit],
|
|
BUY_WEIGHT = it[ConfigTable.buy_weight],
|
|
MAX_BUDGET = it[ConfigTable.max_budget],
|
|
MAX_PRICE = it[ConfigTable.max_price],
|
|
MIN_PRICE = it[ConfigTable.min_price],
|
|
MIN_PURCHASE_SCORE = it[ConfigTable.min_purchase_score],
|
|
SELL_PROFIT = it[ConfigTable.sell_profit],
|
|
GRADE_5_BUY = it[ConfigTable.grade_5_buy],
|
|
GRADE_5_PROFIT = it[ConfigTable.grade_5_profit],
|
|
GRADE_4_BUY = it[ConfigTable.grade_4_buy],
|
|
GRADE_4_PROFIT = it[ConfigTable.grade_4_profit],
|
|
GRADE_3_BUY = it[ConfigTable.grade_3_buy],
|
|
GRADE_3_PROFIT = it[ConfigTable.grade_3_profit],
|
|
GRADE_2_BUY = it[ConfigTable.grade_2_buy],
|
|
GRADE_2_PROFIT = it[ConfigTable.grade_2_profit],
|
|
GRADE_1_BUY = it[ConfigTable.grade_1_buy],
|
|
GRADE_1_PROFIT = it[ConfigTable.grade_1_profit],
|
|
GRADE_1_ALLOCATIONRATE = it[ConfigTable.grade_1_allocationrate],
|
|
GRADE_2_ALLOCATIONRATE = it[ConfigTable.grade_2_allocationrate],
|
|
GRADE_3_ALLOCATIONRATE = it[ConfigTable.grade_3_allocationrate],
|
|
GRADE_4_ALLOCATIONRATE = it[ConfigTable.grade_4_allocationrate],
|
|
GRADE_5_ALLOCATIONRATE = it[ConfigTable.grade_5_allocationrate],
|
|
stop_Loss = it[ConfigTable.stop_Loss],
|
|
take_profit = it[ConfigTable.take_profit],
|
|
loss_min = it[ConfigTable.loss_minrate],
|
|
loss_max = it[ConfigTable.loss_maxrate],
|
|
loss_money = it[ConfigTable.loss_max_money],
|
|
MAX_COUNT = it[ConfigTable.max_count],
|
|
)
|
|
}
|
|
}
|
|
|
|
fun saveConfig(config: AppConfig) {
|
|
transaction {
|
|
ConfigTable.deleteAll()
|
|
ConfigTable.insert {
|
|
it[realAppKey] = config.realAppKey
|
|
it[realSecretKey] = config.realSecretKey
|
|
it[vtsAppKey] = config.vtsAppKey
|
|
it[vtsSecretKey] = config.vtsSecretKey
|
|
it[realAccountNo] = config.realAccountNo
|
|
it[vtsAccountNo] = config.vtsAccountNo
|
|
it[nAppKey] = config.nAppKey
|
|
it[nSecretKey] = config.nSecretKey
|
|
it[dAppKey] = config.dAppKey
|
|
it[isSimulation] = config.isSimulation
|
|
it[htsId] = config.htsId
|
|
it[modelPath] = config.modelPath
|
|
it[embedModelPath] = config.embedModelPath
|
|
it[fees_and_taxrate] = config.FEES_AND_TAXRATE
|
|
it[minimum_net_profit] = config.MINIMUM_NET_PROFIT
|
|
it[buy_weight] = config.BUY_WEIGHT
|
|
it[max_budget] = config.MAX_BUDGET
|
|
it[max_price] = config.MAX_PRICE
|
|
it[min_price] = config.MIN_PRICE
|
|
it[min_purchase_score] = config.MIN_PURCHASE_SCORE
|
|
it[sell_profit] = config.SELL_PROFIT
|
|
it[grade_5_buy] = config.GRADE_5_BUY
|
|
it[grade_5_profit] = config.GRADE_5_PROFIT
|
|
it[grade_4_buy] = config.GRADE_4_BUY
|
|
it[grade_4_profit] = config.GRADE_4_PROFIT
|
|
it[grade_3_buy] = config.GRADE_3_BUY
|
|
it[grade_3_profit] = config.GRADE_3_PROFIT
|
|
it[grade_2_buy] = config.GRADE_2_BUY
|
|
it[grade_2_profit] = config.GRADE_2_PROFIT
|
|
it[grade_1_buy] = config.GRADE_1_BUY
|
|
it[grade_1_profit] = config.GRADE_1_PROFIT
|
|
it[grade_5_allocationrate] = config.GRADE_5_ALLOCATIONRATE
|
|
it[grade_4_allocationrate] = config.GRADE_4_ALLOCATIONRATE
|
|
it[grade_3_allocationrate] = config.GRADE_3_ALLOCATIONRATE
|
|
it[grade_2_allocationrate] = config.GRADE_2_ALLOCATIONRATE
|
|
it[grade_1_allocationrate] = config.GRADE_1_ALLOCATIONRATE
|
|
it[stop_Loss] = config.stop_Loss
|
|
it[take_profit] = config.take_profit
|
|
it[loss_maxrate] = config.loss_max
|
|
it[loss_minrate] = config.loss_min
|
|
it[loss_max_money] = config.loss_money
|
|
it[max_count] = config.MAX_COUNT
|
|
}
|
|
}
|
|
}
|
|
|
|
fun saveOrUpdate(item: AutoTradeItem) = transaction {
|
|
val existing = AutoTradeTable.select { AutoTradeTable.orderNo eq item.orderNo }.firstOrNull()
|
|
if (existing == null) {
|
|
AutoTradeTable.insert {
|
|
it[orderNo] = item.orderNo
|
|
it[stockCode] = item.code
|
|
it[stockName] = item.name
|
|
it[status] = item.status
|
|
it[targetPrice] = item.targetPrice
|
|
it[stopLossPrice] = item.stopLossPrice
|
|
it[quantity] = item.quantity
|
|
it[isDomestic] = item.isDomestic
|
|
}
|
|
} else {
|
|
AutoTradeTable.update({ AutoTradeTable.orderNo eq item.orderNo }) {
|
|
it[status] = item.status
|
|
it[targetPrice] = item.targetPrice
|
|
it[stopLossPrice] = item.stopLossPrice
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 주문번호로 항목 조회 (가장 핵심적인 식별자)
|
|
*/
|
|
fun findByOrderNo(orderNo: String): AutoTradeItem? = transaction {
|
|
AutoTradeTable.select { AutoTradeTable.orderNo eq orderNo }
|
|
.map { mapToAutoTradeItem(it) }
|
|
.singleOrNull()
|
|
}
|
|
|
|
/**
|
|
* 서버 동기화: DB에는 PENDING_BUY/MONITORING인데 서버 미체결 내역에 없는 경우 EXPIRED로 변경
|
|
*/
|
|
fun syncWithServer(serverOrderNos: List<String>) = transaction {
|
|
AutoTradeTable.update({
|
|
(AutoTradeTable.status inList listOf(TradeStatus.PENDING_BUY, TradeStatus.MONITORING)) and
|
|
(AutoTradeTable.orderNo notInList serverOrderNos)
|
|
}) {
|
|
it[status] = TradeStatus.EXPIRED
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 상태 업데이트 및 주문번호 갱신 (예: 매수체결 시 신규 익절 주문번호로 교체)
|
|
*/
|
|
fun updateStatusAndOrderNo(id: Int, newStatus: String, newOrderNo: String? = null) = transaction {
|
|
AutoTradeTable.update({ AutoTradeTable.id eq id }) {
|
|
it[status] = newStatus
|
|
if (newOrderNo != null) it[orderNo] = newOrderNo
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 감시 중인 모든 종목 리스트 (Status별 필터링 용이하게 수정)
|
|
*/
|
|
fun getAutoTradesByStatus(statusList: List<String>): List<AutoTradeItem> = transaction {
|
|
AutoTradeTable.select { AutoTradeTable.status inList statusList }
|
|
.map { mapToAutoTradeItem(it) }
|
|
}
|
|
|
|
}
|
|
|
|
@Serializable
|
|
data class AutoTradeItem(
|
|
val id: Int? = null, // DB 식별자
|
|
val orderNo: String, // 핵심 키: KIS 주문번호 (odno)
|
|
val code: String, // 종목 코드
|
|
val name: String, // 종목 명
|
|
|
|
// 상태 머신 (PENDING_BUY, MONITORING, SELLING, EXPIRED, COMPLETED)
|
|
var status: String = "PENDING_BUY",
|
|
|
|
// 가격 정보
|
|
val orderedPrice: Double = 0.0, // 주문 단가
|
|
var targetPrice: Double = 0.0, // 익절 목표가
|
|
var stopLossPrice: Double = 0.0, // 손절 목표가
|
|
|
|
// 수량 정보
|
|
val quantity: Int = 0, // 총 주문 수량
|
|
var remainedQuantity: Int = 0, // 미체결 잔량 (서버 동기화용)
|
|
|
|
val isDomestic: Boolean = true,
|
|
val profitRate: Double = 0.0, // 설정 시 사용한 목표 비율
|
|
val stopLossRate: Double = 0.0
|
|
)
|
|
|
|
|
|
object TradingLogStore {
|
|
// UI에서 관찰할 수 있는 경량 로그 리스트
|
|
val decisionLogs = mutableStateListOf<LogEntry>()
|
|
|
|
data class LogEntry(
|
|
val time: String,
|
|
val stockName: String,
|
|
val decision: String,
|
|
val confidence: Double,
|
|
val reason: String
|
|
)
|
|
|
|
fun addLog(decision: TradingDecision) {
|
|
synchronized(this) {
|
|
if (decisionLogs.size > 100) decisionLogs.removeAt(0)
|
|
decisionLogs.add(LogEntry(
|
|
time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")),
|
|
stockName = "${decision.stockName}[${decision.currentPrice}]",
|
|
decision = decision.decision ?: "HOLD",
|
|
confidence = decision.confidence,
|
|
reason = decision.reason ?: ""
|
|
))
|
|
}
|
|
}
|
|
|
|
fun addSellLog(stockName: String,sellPrice : String , 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 = "${stockName}[${sellPrice}][]",
|
|
decision = decision,
|
|
confidence = 100.0,
|
|
reason = log
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
fun addLog(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
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
fun addAnalyzer(name : String, code : String, log: String, positive : Boolean = false) {
|
|
synchronized(this) {
|
|
if (decisionLogs.size > 1000) decisionLogs.removeAt(0)
|
|
decisionLogs.add(
|
|
LogEntry(
|
|
time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")),
|
|
stockName = "$name[$code] 분석",
|
|
decision = if(positive) "ANALYZER" else "PASS",
|
|
confidence = 100.0,
|
|
reason = log
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
fun addNotice(name : String, code : 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 = "$name[$code] 분석",
|
|
decision = "NOTICE",
|
|
confidence = 100.0,
|
|
reason = log
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
fun addAfterMarketLog(name : String, code : 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 = "$name[$code] 분석",
|
|
decision = "AFTER",
|
|
confidence = 100.0,
|
|
reason = log
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
fun addSettingLog(settingDesc : String, old : String, new : 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 = "설정변경[${settingDesc}][$old]->[$new]",
|
|
decision = "SETTING",
|
|
confidence = 100.0,
|
|
reason = log
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
fun clear() {
|
|
synchronized(this) {
|
|
decisionLogs.clear()
|
|
}
|
|
}
|
|
}
|
|
|