diff --git a/KissMe/Sources/Common/Foundation+Extensions.swift b/KissMe/Sources/Common/Foundation+Extensions.swift index ee4b06f..f7c6599 100644 --- a/KissMe/Sources/Common/Foundation+Extensions.swift +++ b/KissMe/Sources/Common/Foundation+Extensions.swift @@ -78,6 +78,13 @@ extension Date { return (hour, minute, second) } + public var isBeforeMarketOpenning: Bool { + guard let (hour, _, _) = HHmmss_split else { + return true + } + return hour >= 0 && hour <= 9 + } + public func changing(hour: Int?, min: Int?, sec: Int?, timeZone: String = "KST") -> Date? { let sets: Set = [.year, .month, .day, .hour, .minute, .second] var components = Calendar.current.dateComponents(sets, from: self) @@ -163,6 +170,14 @@ extension String { return (hh, mm, ss) } + public var krxDate: Date? { + // ex) 2023.06.28 PM 12:06:57 + let dateFormatter = DateFormatter() + dateFormatter.timeZone = TimeZone(abbreviation: "KST") + dateFormatter.dateFormat = "yyyy.MM.dd a hh:mm:ss" + return dateFormatter.date(from: self) + } + public var hasComma: Bool { return nil != rangeOfCharacter(from: commaCharSet) } diff --git a/KissMe/Sources/Context/IndexContext.swift b/KissMe/Sources/Context/IndexContext.swift new file mode 100644 index 0000000..44d886e --- /dev/null +++ b/KissMe/Sources/Context/IndexContext.swift @@ -0,0 +1,74 @@ +// +// IndexContext.swift +// KissMe +// +// Created by ened-book-m1 on 2023/06/28. +// + +import Foundation + + +open class IndexContext { + + private var indicesLock = NSLock() + /// 전체 지수 정보 + private var indices = [String: [DomesticExtra.IndexProduct]]() + + public var indicesCount: Int { + indicesLock.lock() + defer { + indicesLock.unlock() + } + return indices.count + } + + public init() { + } +} + + +extension IndexContext { + + public func loadIndex(url: URL, loggable: Bool = false) { + do { + let products = try [DomesticExtra.IndexProduct].readCsv(fromFile: url) + var indices = [String: [DomesticExtra.IndexProduct]]() + for product in products { + if let _ = indices[product.productName] { + indices[product.productName]!.append(product) + } + else { + indices[product.productName] = [product] + } + } + + setIndices(indices) + if loggable { + let totalCount = indices.reduce(0, { $0 + $1.value.count }) + print("load indices \(totalCount) with \(products.count) key") + } + } catch { + print(error) + } + } + + private func setIndices(_ indices: [String: [DomesticExtra.IndexProduct]]) { + indicesLock.lock() + self.indices = indices + indicesLock.unlock() + } + + public func getAllIndices() -> [DomesticExtra.IndexProduct] { + indicesLock.lock() + defer { + indicesLock.unlock() + } + + var all = [DomesticExtra.IndexProduct]() + for index in indices.values { + all.append(contentsOf: index) + } + all.sort(by: { $0.indexFullCode < $1.indexFullCode }) + return all + } +} diff --git a/KissMe/Sources/Domestic/DomesticStockPriceResult.swift b/KissMe/Sources/Domestic/DomesticStockPriceResult.swift index 363c54e..84e33af 100644 --- a/KissMe/Sources/Domestic/DomesticStockPriceResult.swift +++ b/KissMe/Sources/Domestic/DomesticStockPriceResult.swift @@ -18,6 +18,7 @@ public enum YesNo: String, Codable { /// 보증금 비율 구분 public enum MarginalRateClass: String, Codable { + case undefined2 = "" case undefined = " " /// 20%, 30%, 40% diff --git a/KissMe/Sources/Domestic/KRX300/DomesticIndex.swift b/KissMe/Sources/Domestic/KRX300/DomesticIndex.swift index 25f8831..434985e 100644 --- a/KissMe/Sources/Domestic/KRX300/DomesticIndex.swift +++ b/KissMe/Sources/Domestic/KRX300/DomesticIndex.swift @@ -15,6 +15,15 @@ extension DomesticExtra { case kospi = "02" case kosdaq = "03" case theme = "04" + + public var name: String { + switch self { + case .krx: return "krx" + case .kospi: return "kospi" + case .kosdaq: return "kosdaq" + case .theme: return "theme" + } + } } public enum ShareType: Int { @@ -33,7 +42,7 @@ extension DomesticExtra { /// 한국거래소 - 주가지수 - 전체지수 시세 /// public struct IndexPriceRequest: KrxRequest { - public typealias KResult = String + public typealias KResult = IndexPriceResult public var domain: String { "http://data.krx.co.kr" @@ -41,7 +50,7 @@ extension DomesticExtra { public var url: String { "/comm/bldAttendant/getJsonData.cmd" } - public var method: Method { .post } + public var method: Method { .get } public var header: [String : String?] { [:] @@ -49,67 +58,17 @@ extension DomesticExtra { public var body: [String: Any] { return [ "idxIndMidclssCd": indexType.rawValue, - "strtDd": range.startDate.yyyyMMdd, - "endDd": range.endDate.yyyyMMdd, + "trdDd": date.yyyyMMdd, "share": shareType.rawValue, "money": moneyType.rawValue, - "csvxls_isNo": false, - "bld": "dbms/MDC/STAT/standard/MDCSTAT00201" + "bld": "dbms/MDC/STAT/standard/MDCSTAT00101" ] } public var result: KResult? = nil - struct DataRange { - let startDate: Date // yyyyMMdd - let endDate: Date // yyyyMMdd - } - let range: DataRange - let indexType: IndexType - let shareType: ShareType - let moneyType: MoneyType - - init(range: DataRange, indexType: IndexType) { - self.range = range - self.indexType = indexType - self.shareType = .unitOne - self.moneyType = .moneyWon - } - } - - - /// 한국거래소 - 주가지수 - 지수구성종목 - /// - public struct IndexPortfolioRequest: KrxRequest { - public typealias KResult = String - - public var domain: String { - "http://data.krx.co.kr" - } - public var url: String { - "/comm/bldAttendant/getJsonData.cmd" - } - public var method: Method { .post } - - public var header: [String : String?] { - [:] - } - - public var body: [String: Any] { - return [ - "idxIndMidclssCd": indexType.rawValue, - "trdDb": date.yyyyMMdd, - "share": shareType.rawValue, - "money": moneyType.rawValue, - "csvxls_isNo": false, - "bld": "dbms/MDC/STAT/standard/MDCSTAT00601" - ] - } - public var result: KResult? = nil - - - let indexType: IndexType let date: Date + let indexType: IndexType let shareType: ShareType let moneyType: MoneyType @@ -120,6 +79,306 @@ extension DomesticExtra { self.moneyType = .moneyWon } } + + + /// 한국거래소 - 주가지수 - 지수구성종목 + /// + public struct IndexPortfolioRequest: KrxRequest { + public typealias KResult = IndexPortfolioResult + + public var domain: String { + "http://data.krx.co.kr" + } + public var url: String { + "/comm/bldAttendant/getJsonData.cmd" + } + public var method: Method { .get } + + public var header: [String : String?] { + [:] + } + + public var body: [String: Any] { + return [ + "indIdx": indexId, + "indIdx2": indexId2, + "trdDd": date.yyyyMMdd, + "money": moneyType.rawValue, + "bld": "dbms/MDC/STAT/standard/MDCSTAT00601" + ] + } + public var result: KResult? = nil + + + let indexId: String + let indexId2: String + let date: Date + let moneyType: MoneyType + + init(indexId: String, indexId2: String, date: Date) { + self.indexId = indexId + self.indexId2 = indexId2 + self.date = date + self.moneyType = .moneyWon + } + } + + + /// 모든 지수 이름 가져오기 + /// + public struct AllIndicesRequest: KrxRequest { + public typealias KResult = AllIndicesResult + + public var domain: String { + "http://data.krx.co.kr" + } + public var url: String { + "/comm/bldAttendant/getJsonData.cmd" + } + public var method: Method { .get } + + public var header: [String : String?] { + [:] + } + public var body: [String: Any] { + return [ + "mktsel": market.rawValue, + "bld": "dbms/comm/finder/finder_equidx" + ] + } + public var result: KResult? = nil + + + enum MarketSelection: String { + case all = "1" + case krx = "2" + case kospi = "3" + case kosdaq = "4" + case theme = "T" + } + let market: MarketSelection = .all + } +} + + +extension DomesticExtra { + + public struct IndexPriceResult: Codable { + /// 전체 지수 가격들 + public let output: [Output] + /// 서버의 시간 + public let currentDatetime: String /// 2023.06.28 PM 12:06:57 + + public var currentDate: Date { + currentDatetime.krxDate! + } + + private enum CodingKeys: String, CodingKey, CaseIterable { + case output + case currentDatetime = "CURRENT_DATETIME" + } + + public struct Output: Codable, PropertyIterable, ArrayDecodable { + /// 지수명 + public let indexName: String + /// 지수 종가 + public let indexClosingPrice: String + /// 등락 타입 코드 + public let fluctuationTypeCode: String + /// 전일 대비 + public let previousDayVariableRatio: String + /// 등락율 + public let fluctuationRate: String + /// 지수 시가 + public let indexOpenningPrice: String + /// 지수 고가 + public let highestIndexPrice: String + /// 지수 저가 + public let lowestIndexPrice: String + /// 누적 거래량 + public let accumulatedVolume: String + /// 누적 거래대금 + public let accumulatedTradingAmount: String + /// 자본금 + public let marketCapital: String + + private enum CodingKeys: String, CodingKey, CaseIterable { + case indexName = "IDX_NM" + case indexClosingPrice = "CLSPRC_IDX" + case fluctuationTypeCode = "FLUC_TP_CD" + case previousDayVariableRatio = "CMPPREVDD_IDX" + case fluctuationRate = "FLUC_RT" + case indexOpenningPrice = "OPNPRC_IDX" + case highestIndexPrice = "HGPRC_IDX" + case lowestIndexPrice = "LWPRC_IDX" + case accumulatedVolume = "ACC_TRDVOL" + case accumulatedTradingAmount = "ACC_TRDVAL" + case marketCapital = "MKTCAP" + } + + public init(array: [String], source: String.SubSequence) throws { + guard array.count == 11 else { + throw GeneralError.incorrectArrayItems(String(source), array.count, 11) + } + self.indexName = array[0] + self.indexClosingPrice = array[1] + self.fluctuationTypeCode = array[2] + self.previousDayVariableRatio = array[3] + self.fluctuationRate = array[4] + self.indexOpenningPrice = array[5] + self.highestIndexPrice = array[6] + self.lowestIndexPrice = array[7] + self.accumulatedVolume = array[8] + self.accumulatedTradingAmount = array[9] + self.marketCapital = array[10] + } + + public static func symbols() -> [String] { + let i = try! Output(array: Array(repeating: "", count: 11), source: #function) + return Mirror(reflecting: i).children.compactMap { $0.label } + } + + public static func localizedSymbols() -> [String: String] { + [:] + } + } + } + + + public struct IndexPortfolioResult: Codable { + public let output: [Output] + /// 서버의 시간 + public let currentDatetime: String + + public var currentDate: Date { + currentDatetime.krxDate! + } + + private enum CodingKeys: String, CodingKey, CaseIterable { + case output = "output" + case currentDatetime = "CURRENT_DATETIME" + } + + public struct Output: Codable, PropertyIterable, ArrayDecodable { + /// 종목코드 + public let shortProductCode: String + /// 종목명 + public let productName: String + /// 종가 + public let indexClosingPrice: String + /// 등락률 타입 + public let fluctuationTypeCode: String + /// 대비 가격 + public let comparisionPrice: String + /// 등락률 + public let fluctuationRate: String + /// 상장시가총액 + public let marketCapital: String + + private enum CodingKeys: String, CodingKey, CaseIterable { + case shortProductCode = "ISU_SRT_CD" + case productName = "ISU_ABBRV" + case indexClosingPrice = "TDD_CLSPRC" + case fluctuationTypeCode = "FLUC_TP_CD" + case comparisionPrice = "STR_CMP_PRC" + case fluctuationRate = "FLUC_RT" + case marketCapital = "MKTCAP" + } + + public init(array: [String], source: String.SubSequence) throws { + guard array.count == 7 else { + throw GeneralError.incorrectArrayItems(String(source), array.count, 7) + } + self.shortProductCode = array[0] + self.productName = array[1] + self.indexClosingPrice = array[2] + self.fluctuationTypeCode = array[3] + self.comparisionPrice = array[4] + self.fluctuationRate = array[5] + self.marketCapital = array[6] + } + + public static func symbols() -> [String] { + let i = try! Output(array: Array(repeating: "", count: 7), source: #function) + return Mirror(reflecting: i).children.compactMap { $0.label } + } + + public static func localizedSymbols() -> [String: String] { + [:] + } + } + } + + + public struct AllIndicesResult: Codable { + public let block: [Block] + /// 서버의 시간 + public let currentDatetime: String + + public var currentDate: Date { + currentDatetime.krxDate! + } + + private enum CodingKeys: String, CodingKey, CaseIterable { + case block = "block1" + case currentDatetime = "CURRENT_DATETIME" + } + + public struct Block: Codable, PropertyIterable, ArrayDecodable { + /// 지수 코드1 + public let index1Code: String + /// 지수 코드2 + public let index2Code: String + /// 상품명 + public let productName: String + /// 지수 시장 코드 + public let indexMarketCode: KrxMarketCode + /// 지수 시장명 + public let marketName: String + + public var indexFullCode: String { + index1Code + index2Code + } + + public var indexType: DomesticExtra.IndexType { + switch marketName { + case "KRX": return .krx + case "STK": return .kospi + case "KOSDAQ": return .kosdaq + case "테마": return .theme + default: return .krx + } + } + + private enum CodingKeys: String, CodingKey, CaseIterable { + case index1Code = "full_code" + case index2Code = "short_code" + case productName = "codeName" + case indexMarketCode = "marketCode" + case marketName + } + + public init(array: [String], source: String.SubSequence) throws { + guard array.count == 5 else { + throw GeneralError.incorrectArrayItems(String(source), array.count, 5) + } + self.index1Code = array[0] + self.index2Code = array[1] + self.productName = array[2] + self.indexMarketCode = KrxMarketCode(rawValue: array[3])! + self.marketName = array[4] + } + + public static func symbols() -> [String] { + let i = try! Block(array: Array(repeating: "", count: 5), source: #function) + return Mirror(reflecting: i).children.compactMap { $0.label } + } + + public static func localizedSymbols() -> [String: String] { + [:] + } + } + } } @@ -127,16 +386,14 @@ extension KissAccount { /// 전체지수 시세 가져오기 /// - static public func getIndexPrice(indexType: DomesticExtra.IndexType, startDate: Date, endDate: Date) async throws -> String { + static public func getIndexPrice(indexType: DomesticExtra.IndexType, date: Date) async throws -> DomesticExtra.IndexPriceResult { return try await withUnsafeThrowingContinuation { continuation in - let range = DomesticExtra.IndexPriceRequest.DataRange(startDate: startDate, endDate: endDate) - - let request = DomesticExtra.IndexPriceRequest(range: range, indexType: indexType) + let request = DomesticExtra.IndexPriceRequest(indexType: indexType, date: date) request.query { result in switch result { case .success(let result): - continuation.resume(returning: (result)) + continuation.resume(returning: result) case .failure(let error): continuation.resume(throwing: error) } @@ -146,14 +403,31 @@ extension KissAccount { /// 지수구성종목 가져오기 /// - static public func getIndexPortfolio(indexType: DomesticExtra.IndexType, date: Date) async throws -> String { + static public func getIndexPortfolio(indexId: String, indexId2: String, date: Date) async throws -> DomesticExtra.IndexPortfolioResult { return try await withUnsafeThrowingContinuation { continuation in - let request = DomesticExtra.IndexPortfolioRequest(indexType: indexType, date: date) + let request = DomesticExtra.IndexPortfolioRequest(indexId: indexId, indexId2: indexId2, date: date) request.query { result in switch result { case .success(let result): - continuation.resume(returning: (result)) + continuation.resume(returning: result) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + /// 모든 지수 이름 가져오기 + /// + static public func getAllIndices() async throws -> DomesticExtra.AllIndicesResult { + return try await withUnsafeThrowingContinuation { continuation in + + let request = DomesticExtra.AllIndicesRequest() + request.query { result in + switch result { + case .success(let result): + continuation.resume(returning: result) case .failure(let error): continuation.resume(throwing: error) } diff --git a/KissMe/Sources/Domestic/ShortSelling/DomesticShortSelling.swift b/KissMe/Sources/Domestic/ShortSelling/DomesticShortSelling.swift index fb6c3fa..56e41c4 100644 --- a/KissMe/Sources/Domestic/ShortSelling/DomesticShortSelling.swift +++ b/KissMe/Sources/Domestic/ShortSelling/DomesticShortSelling.swift @@ -10,6 +10,7 @@ import Foundation public struct DomesticExtra { public typealias Shorts = ShortSellingBalanceResult.OutBlock + public typealias IndexProduct = AllIndicesResult.Block } protocol KrxRequest: Request { @@ -66,6 +67,17 @@ extension DomesticExtra { } } + +public enum KrxMarketCode: String, Codable { + case undefined = "" + + case stock = "STK" + case kosdaq = "KSQ" + case krx = "KRX" + case gbl = "GBL" +} + + extension DomesticExtra { public struct ShortSellingBalanceResult: Codable { @@ -73,11 +85,11 @@ extension DomesticExtra { public let block: [Block]? /// 개발 상품의 기간별 잔고 public let outBlock: [OutBlock]? - public let currentDatetime: String + /// 서버의 시간 + public let currentDatetime: String /// 2023.06.11 PM 07:26:14 public var currentDate: Date { - // 2023.06.11 PM 07:26:14 - return Date() + currentDatetime.krxDate! } private enum CodingKeys: String, CodingKey, CaseIterable { @@ -87,21 +99,22 @@ extension DomesticExtra { } public struct Block: Codable { - /// 전체 상품코드 - public let fullCode: String - /// 상품번호 - public let shortCode: String + /// 지수 코드1 + public let index1Code: String + /// 지수 코드2 + public let index2Code: String /// 상품명 public let productName: String - /// - public let marketCode: String + /// 지수 시장 코드 + public let indexMarketCode: KrxMarketCode + /// 시장명 public let marketName: String private enum CodingKeys: String, CodingKey, CaseIterable { - case fullCode = "full_code" - case shortCode = "short_code" + case index1Code = "full_code" + case index2Code = "short_code" case productName = "codeName" - case marketCode + case indexMarketCode = "marketCode" case marketName } } @@ -116,7 +129,7 @@ extension DomesticExtra { /// 공매도 잔고 금액 public let shortSellingBalanceAmount: String /// 시가총액 - public let totalMarketValue: String + public let marketCapital: String /// 공매도 잔고 비중 public let shortSellingBalanceRatio: String @@ -125,7 +138,7 @@ extension DomesticExtra { case shortSellingBalanceQuantity = "BAL_QTY" /// 29,330 case listedStockCount = "LIST_SHRS" /// 46,822,295 case shortSellingBalanceAmount = "BAL_AMT" /// 130,518,500 - case totalMarketValue = "MKTCAP" /// 208,359,212,750 + case marketCapital = "MKTCAP" /// 208,359,212,750 case shortSellingBalanceRatio = "BAL_RTO" /// 0.06 } @@ -148,9 +161,9 @@ extension DomesticExtra { shortSellingBalanceAmount.removeAll(where: { $0 == "," }) self.shortSellingBalanceAmount = shortSellingBalanceAmount - var totalMarketValue = try container.decode(String.self, forKey: DomesticExtra.ShortSellingBalanceResult.OutBlock.CodingKeys.totalMarketValue) - totalMarketValue.removeAll(where: { $0 == "," }) - self.totalMarketValue = totalMarketValue + var marketCapital = try container.decode(String.self, forKey: DomesticExtra.ShortSellingBalanceResult.OutBlock.CodingKeys.marketCapital) + marketCapital.removeAll(where: { $0 == "," }) + self.marketCapital = marketCapital self.shortSellingBalanceRatio = try container.decode(String.self, forKey: DomesticExtra.ShortSellingBalanceResult.OutBlock.CodingKeys.shortSellingBalanceRatio) } @@ -163,9 +176,18 @@ extension DomesticExtra { self.shortSellingBalanceQuantity = array[1] self.listedStockCount = array[2] self.shortSellingBalanceAmount = array[3] - self.totalMarketValue = array[4] + self.marketCapital = array[4] self.shortSellingBalanceRatio = array[5] } + + public static func symbols() -> [String] { + let i = try! OutBlock(array: Array(repeating: "", count: 6), source: #function) + return Mirror(reflecting: i).children.compactMap { $0.label } + } + + public static func localizedSymbols() -> [String: String] { + [:] + } } } } diff --git a/KissMeConsole/Sources/KissConsole+CSV.swift b/KissMeConsole/Sources/KissConsole+CSV.swift index 3e74c39..54fd0e6 100644 --- a/KissMeConsole/Sources/KissConsole+CSV.swift +++ b/KissMeConsole/Sources/KissConsole+CSV.swift @@ -84,6 +84,33 @@ extension KissConsole { createSubpath(subPath) return fileUrl } + + static func indexPriceFileUrl(date: Date) -> URL { + let subPath = "data/index/\(date.yyyyMMdd)" + let subFile = "\(subPath)/price-\(date.yyyyMMdd)-\(date.HHmmss).csv" + + let fileUrl = URL.currentDirectory().appending(path: subFile) + createSubpath(subPath) + return fileUrl + } + + static func indexPortfolioFileUrl(date: Date, type: DomesticExtra.IndexType, indexFullCode: String) -> URL { + let subPath = "data/index/\(date.yyyyMMdd)" + let subFile = "\(subPath)/portfolio-\(type.name)-\(indexFullCode).csv" + + let fileUrl = URL.currentDirectory().appending(path: subFile) + createSubpath(subPath) + return fileUrl + } + + static func indexProductsFileUrl() -> URL { + let subPath = "data" + let subFile = "\(subPath)/index-products.csv" + + let fileUrl = URL.currentDirectory().appending(path: subFile) + createSubpath(subPath) + return fileUrl + } } diff --git a/KissMeConsole/Sources/KissConsole+Candle.swift b/KissMeConsole/Sources/KissConsole+Candle.swift index 93e4c74..e429ac2 100644 --- a/KissMeConsole/Sources/KissConsole+Candle.swift +++ b/KissMeConsole/Sources/KissConsole+Candle.swift @@ -21,6 +21,9 @@ let PreferredShortsTPS: UInt64 = 5 /// Limit to request a top query let PreferredTopTPS: UInt64 = 5 +/// Limit to reqeust a index query +let PreferredIndexTPS: UInt64 = 5 + /// Limit to request a investor query let PreferredInvestorTPS: UInt64 = 19 diff --git a/KissMeConsole/Sources/KissConsole.swift b/KissMeConsole/Sources/KissConsole.swift index 3af3955..4fdc8e7 100644 --- a/KissMeConsole/Sources/KissConsole.swift +++ b/KissMeConsole/Sources/KissConsole.swift @@ -10,8 +10,13 @@ import KissMe class KissConsole: KissMe.ShopContext { + /// 인증 관련 json 정보 private var credential: Credential? = nil + + /// 한국투자증권 개인 계정 var account: KissAccount? = nil + + /// 주식시장 거래 상품 목록들 private var shop: KissShop? = nil /// 현재 candle 파일로 저장 중인 productNo @@ -20,6 +25,8 @@ class KissConsole: KissMe.ShopContext { /// CSV 파일을 저장할 때, field name 에 대해서 한글(true) 또는 영문(false)로 기록할지 설정 var localized: Bool = false + var indexContext: IndexContext + private enum KissCommand: String { case quit = "quit" @@ -62,13 +69,16 @@ class KissConsole: KissMe.ShopContext { // KRX 지수 열람 case index = "index" - case indexAll = "index all" + case indexPortfolio = "index portfolio" // 종목 열람 case loadShop = "load shop" case updateShop = "update shop" case look = "look" + case loadIndex = "load index" + case updateIndex = "update index" + // 휴장일 case holiday = "holiday" @@ -99,10 +109,12 @@ class KissConsole: KissMe.ShopContext { return false case .investor, .investorAll: return true - case .shorts, .shortsAll, .index, .indexAll: + case .shorts, .shortsAll, .index, .indexPortfolio: return false case .loadShop, .updateShop, .look, .holiday: return false + case .loadIndex, .updateIndex: + return false case .showcase: return false case .loves, .love, .hate, .localizeNames, .localizeOnOff: @@ -124,6 +136,8 @@ class KissConsole: KissMe.ShopContext { KissConsole.createSubpath("log") KissConsole.createSubpath("data") + indexContext = IndexContext() + super.init() lastLogin() loadLocalName() @@ -131,6 +145,7 @@ class KissConsole: KissMe.ShopContext { let semaphore = DispatchSemaphore(value: 0) Task { await onLoadShop() + await onLoadIndex() semaphore.signal() } semaphore.wait() @@ -233,13 +248,16 @@ class KissConsole: KissMe.ShopContext { case .shortsAll: onShortsAll(args) case .index: await onIndex(args) - case .indexAll: onIndexAll(args) + case .indexPortfolio: await onIndexPortfolio(args) case .loadShop: await onLoadShop() case .updateShop: await onUpdateShop() case .look: await onLook(args) case .holiday: await onHoliday(args) + case .loadIndex: await onLoadIndex() + case .updateIndex: await onUpdateIndex() + case .showcase: await onShowcase() case .loves: await onLoves() case .love: await onLove(args) @@ -853,16 +871,47 @@ extension KissConsole { return } do { - // TODO: Work more -// _ = try await getIndexPrice(indexType, startDate: , endDate: ) + let curDate = Date() + if curDate.isBeforeMarketOpenning { + print("Before market openning") + return + } + let result = try await KissAccount.getIndexPrice(indexType: indexType, date: curDate) + guard result.output.isEmpty == false else { + print("empty result") + return + } + + let fileUrl = KissConsole.indexPriceFileUrl(date: result.currentDate) + try result.output.writeCsv(toFile: fileUrl, localized: localized) + print("DONE \(result.output.count)") } catch { print(error) } } - private func onIndexAll(_ args: [String]) { - // TODO: Work more + private func onIndexPortfolio(_ args: [String]) async { + do { + let indices = indexContext.getAllIndices() + let date = Date() + + for index in indices { + let result = try await KissAccount.getIndexPortfolio(indexId: index.index1Code, indexId2: index.index2Code, date: date) + guard result.output.isEmpty == false else { + print("empty result on \(index.indexFullCode)") + continue + } + + let fileUrl = KissConsole.indexPortfolioFileUrl(date: date, type: index.indexType, indexFullCode: index.indexFullCode) + try result.output.writeCsv(toFile: fileUrl, localized: localized) + print("DONE \(result.output.count) \(index.indexFullCode)") + + try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredIndexTPS) + } + } catch { + print(error) + } } @@ -915,6 +964,28 @@ extension KissConsole { } + private func onLoadIndex() async { + return await withUnsafeContinuation { continuation in + self.indexContext.loadIndex(url: KissConsole.indexProductsFileUrl(), loggable: true) + continuation.resume() + } + } + + + private func onUpdateIndex() async { + do { + let result = try await KissAccount.getAllIndices() + if result.block.isEmpty == false { + let fileUrl = KissConsole.indexProductsFileUrl() + try result.block.writeCsv(toFile: fileUrl, localized: localized) + } + print("DONE \(result.block.count)") + } catch { + print(error) + } + } + + func getAllProduct(baseDate: Date) async -> [DomesticShop.Product] { var pageNo = 0 var shopItems = [DomesticShop.Product]() @@ -1085,6 +1156,10 @@ extension KissConsole { symbols.formUnion(CurrentPriceResult.OutputDetail.symbols()) symbols.formUnion(CapturePrice.symbols()) symbols.formUnion(InvestorVolumeResult.OutputDetail.symbols()) + symbols.formUnion(DomesticExtra.IndexPriceResult.Output.symbols()) + symbols.formUnion(DomesticExtra.IndexPortfolioResult.Output.symbols()) + symbols.formUnion(DomesticExtra.AllIndicesResult.Block.symbols()) + symbols.formUnion(DomesticExtra.ShortSellingBalanceResult.OutBlock.symbols()) let newNames = symbols.sorted(by: { $0 < $1 }) let nameUrl = KissConsole.localNamesUrl diff --git a/KissMeMatrix/Sources/KissMatrix.swift b/KissMeMatrix/Sources/KissMatrix.swift index dfd103d..99a2a1a 100644 --- a/KissMeMatrix/Sources/KissMatrix.swift +++ b/KissMeMatrix/Sources/KissMatrix.swift @@ -15,15 +15,6 @@ enum RunMode: String { } -struct Param: Codable { - - /// simulator 일 경우에 참고하는 날짜 정보 (begin ~ end) - /// - let beginDate: String // yyyyMMdd HHmmss - let endDate: String // yyyyMMdd HHmmss -} - - struct Model: Codable { let indexSets: [IndexSet] diff --git a/bin/data b/bin/data index 8ecb2d6..1f53137 160000 --- a/bin/data +++ b/bin/data @@ -1 +1 @@ -Subproject commit 8ecb2d68fe4b0bfb2bae569bfc90638812e62beb +Subproject commit 1f53137aefb70fc6126f76ab7bca49987a2b502a diff --git a/projects/macos/KissMe.xcodeproj/project.pbxproj b/projects/macos/KissMe.xcodeproj/project.pbxproj index d941ad4..7170cb9 100644 --- a/projects/macos/KissMe.xcodeproj/project.pbxproj +++ b/projects/macos/KissMe.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 340A4DBD2A4C34BE005A1FBA /* IndexContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340A4DBC2A4C34BE005A1FBA /* IndexContext.swift */; }; 341F5EB02A0A80EC00962D48 /* KissMe.docc in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EAF2A0A80EC00962D48 /* KissMe.docc */; }; 341F5EB62A0A80EC00962D48 /* KissMe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 341F5EAB2A0A80EC00962D48 /* KissMe.framework */; }; 341F5EBB2A0A80EC00962D48 /* KissMeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EBA2A0A80EC00962D48 /* KissMeTests.swift */; }; @@ -53,6 +54,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 340A4DBC2A4C34BE005A1FBA /* IndexContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexContext.swift; sourceTree = ""; }; 341F5EAB2A0A80EC00962D48 /* KissMe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KissMe.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 341F5EAE2A0A80EC00962D48 /* KissMe.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KissMe.h; sourceTree = ""; }; 341F5EAF2A0A80EC00962D48 /* KissMe.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; name = KissMe.docc; path = ../KissMe.docc; sourceTree = ""; }; @@ -242,6 +244,7 @@ children = ( 34F190102A4394EB0068C697 /* LocalContext.swift */, 34F1900E2A426D150068C697 /* ShopContext.swift */, + 340A4DBC2A4C34BE005A1FBA /* IndexContext.swift */, ); path = Context; sourceTree = ""; @@ -383,6 +386,7 @@ 341F5EF92A0F907300962D48 /* DomesticStockPriceResult.swift in Sources */, 341F5EE12A0F373B00962D48 /* Login.swift in Sources */, 341F5EF52A0F891200962D48 /* KissAccount.swift in Sources */, + 340A4DBD2A4C34BE005A1FBA /* IndexContext.swift in Sources */, 34E7B9112A49BD2800B3AB9F /* DomesticIndex.swift in Sources */, 341F5F0D2A15222E00962D48 /* AuthRequest.swift in Sources */, );