// // KissConsole+CSV.swift // KissMeConsole // // Created by ened-book-m1 on 2023/05/29. // import Foundation import KissMe extension KissConsole { static var shopProductsUrl: URL { URL.currentDirectory().appending(path: "data/shop-products.csv") } static func topProductsUrl(_ belong: BelongClassCode, date: Date) -> URL { let subPath = "data/top30/\(date.yyyyMMdd)" let subFile = "\(subPath)/top30-\(belong.fileBelong)-\(date.yyyyMMdd)-\(date.HHmmss).csv" let fileUrl = URL.currentDirectory().appending(path: subFile) createSubpath(subPath) return fileUrl } static var accountStocksUrl: URL { URL.currentDirectory().appending(path: "data/account-stocks.csv") } static var accountAmountUrl: URL { URL.currentDirectory().appending(path: "data/account-amount.csv") } static var localNamesUrl: URL { URL.currentDirectory().appending(path: "data/localized-names.csv") } static var holidayUrl: URL { URL.currentDirectory().appending(path: "data/holiday.csv") } static func productPriceUrl(productNo: String) -> URL { let subPath = "data/\(productNo)/price" let subFile = "\(subPath)/prices.csv" let fileUrl = URL.currentDirectory().appending(path: subFile) createSubpath(subPath) return fileUrl } static func investorFileUrl(productNo: String, day: String) -> URL { let subPath = "data/\(productNo)/investor" let subFile = "\(subPath)/investor-\(day).csv" let fileUrl = URL.currentDirectory().appending(path: subFile) createSubpath(subPath) return fileUrl } static func shortsFileUrl(productNo: String, day: String) -> URL { let subPath = "data/\(productNo)/shorts" let subFile = "\(subPath)/shorts-\(day).csv" let fileUrl = URL.currentDirectory().appending(path: subFile) createSubpath(subPath) return fileUrl } static func candleFileUrl(productNo: String, period: CandleFilePeriod, day: String) -> URL { assert(day.count == 8) let subPath = "data/\(productNo)/\(period.rawValue)" let subFile: String switch period { case .minute: subFile = "\(subPath)/candle-\(day).csv" case .day: subFile = "\(subPath)/candle-day-250.csv" case .weak: subFile = "\(subPath)/candle-week-42.csv" } let fileUrl = URL.currentDirectory().appending(path: subFile) createSubpath(subPath) return fileUrl } } extension KissConsole { func loadShop(_ profile: Bool = false) { let appTime1 = Date.appTime guard let stringCsv = try? String(contentsOfFile: KissConsole.shopProductsUrl.path) else { return } let appTime2 = Date.appTime if profile { print("\tloading file \(appTime2 - appTime1) elapsed") } var products = [String: [DomesticShop.Product]]() let rows = stringCsv.split(separator: "\n") let appTime3 = Date.appTime if profile { print("\trow split \(appTime3 - appTime2) elapsed") } for (i, row) in rows.enumerated() { let array = row.split(separator: ",").map { String($0) } if i == 0, array[0] == "baseDate" { continue } let product = try! DomesticShop.Product(array: array) if let _ = products[product.itemName] { products[product.itemName]!.append(product) } else { products[product.itemName] = [product] } } let appTime4 = Date.appTime if profile { print("\tparse product \(appTime4 - appTime3) elapsed") } setProducts(products) let totalCount = products.reduce(0, { $0 + $1.value.count }) print("load products \(totalCount) with \(products.count) key") } } extension KissConsole { enum CandleValidation: CustomStringConvertible { case ok case invalidFileName case cannotRead case invalidCsvHeader case invalidBusinessDate case invalidConclusionTime case unimplemetedFunction var description: String { switch self { case .ok: return "ok" case .invalidFileName: return "invalidFileName" case .cannotRead: return "cannotRead" case .invalidCsvHeader: return "invalidCsvHeader" case .invalidBusinessDate: return "invalidBusinessDate" case .invalidConclusionTime: return "invalidConclusionTime" case .unimplemetedFunction: return "unimplemetedFunction" } } } static func validateCandleDay(_ fileUrl: URL) -> CandleValidation { // TODO: validateCandleDay return .unimplemetedFunction } static func validateCandleWeek(_ fileUrl: URL) -> CandleValidation { // TODO: validateCandleWeek return .unimplemetedFunction } static func validateCandleMinute(_ fileUrl: URL) -> CandleValidation { let fileNameFrag = fileUrl.lastPathComponent.split(separator: ".") guard fileNameFrag.count == 2 else { return .invalidFileName } let candlePrefix = "candle-" guard fileNameFrag[0].prefix(candlePrefix.count) == candlePrefix, fileNameFrag[1] == "csv" else { return .invalidFileName } let fileDateFrag = String(fileNameFrag[0].suffix(fileNameFrag[0].count - candlePrefix.count)) guard let stringCsv = try? String(contentsOfFile: fileUrl.path) else { return .cannotRead } var candles = [Domestic.Candle]() let rows = stringCsv.split(separator: "\n") for (i, row) in rows.enumerated() { let array = row.split(separator: ",").map { String($0) } if i == 0 { if array.count != 8 { return .invalidCsvHeader } continue } let candle = try! Domestic.Candle(array: array) candles.append(candle) } var curHH = 9, curMM = 0 for candle in candles.reversed() { if candle.stockBusinessDate != fileDateFrag { return .invalidBusinessDate } guard let (hh, mm, _) = candle.stockConclusionTime.HHmmss else { return .invalidConclusionTime } guard hh == curHH, mm == curMM else { return .invalidConclusionTime } // Finished to check if hh == 18, mm == 0 { break } curMM += 1 if curMM >= 60 { curMM = 0 curHH += 1 } } return .ok } }