From ebf85a51054e8c06f376c16e4e2d30e8aa6dbc08 Mon Sep 17 00:00:00 2001 From: ened Date: Sun, 11 Jun 2023 19:02:15 +0900 Subject: [PATCH] Implement "now all" command to capture all current prices --- KissMe/Sources/Common/Request.swift | 2 + .../Domestic/DomesticStockPriceResult.swift | 441 +++++++++++++++++- .../Sources/Foundation+Extensions.swift | 45 +- .../Sources/KissConsole+Candle.swift | 2 + .../Sources/KissConsole+Investor.swift | 2 + KissMeConsole/Sources/KissConsole+Price.swift | 67 +++ KissMeConsole/Sources/KissConsole.swift | 85 ++-- KissMeConsole/Sources/KissContext.swift | 6 +- bin/data | 2 +- .../KissMeConsole.xcodeproj/project.pbxproj | 4 + 10 files changed, 601 insertions(+), 55 deletions(-) create mode 100644 KissMeConsole/Sources/KissConsole+Price.swift diff --git a/KissMe/Sources/Common/Request.swift b/KissMe/Sources/Common/Request.swift index a079ad3..2e35a64 100644 --- a/KissMe/Sources/Common/Request.swift +++ b/KissMe/Sources/Common/Request.swift @@ -35,6 +35,8 @@ public enum GeneralError: Error { case emptyData case cannotReadFile case cannotWriteFile + case cannotReadFileLine + case cannotReadFileToConvertString case incorrectArrayItems case headerNoFiendName(String) case noCsvFile diff --git a/KissMe/Sources/Domestic/DomesticStockPriceResult.swift b/KissMe/Sources/Domestic/DomesticStockPriceResult.swift index 0bc2fe8..dc0b967 100644 --- a/KissMe/Sources/Domestic/DomesticStockPriceResult.swift +++ b/KissMe/Sources/Domestic/DomesticStockPriceResult.swift @@ -18,7 +18,7 @@ public enum YesNo: String, Codable { /// 보증금 비율 구분 public enum MarginalRateClass: String, Codable { - case undefined = "" + case undefined = " " /// 20%, 30%, 40% case _40 = "40" @@ -149,7 +149,7 @@ public struct CurrentPriceResult: Codable { public let newHighLowPriceClassCode: String? /// 업종 한글 종목명 - public let koreanBusinessTypeName: String + public let koreanBusinessTypeName: String? /// 임시 정지 여부 public let temporaryStopped: YesNo @@ -568,6 +568,443 @@ public struct CurrentPriceResult: Codable { } +public struct CapturePrice: Codable, PropertyIterable, ArrayDecodable { + /// 주식 영업 일자 + public let stockBusinessDate: String + + /// 수집 시간 + public let captureTime: String // HHmmss + + + // MARK: 이하는 CurrentPriceResult.OutputDetail 와 동일 + + /// 종목 상태 구분 코드 + public let itemStateCode: CurrentPriceResult.StateClass + + /// 증거금 비율 + public let marginalRate: String + + /// 대표 시장 한글 명 + public let koreanMarketName: String + + /// 신 고가 저가 구분 코드 + public let newHighLowPriceClassCode: String? + + /// 업종 한글 종목명 + public let koreanBusinessTypeName: String? + + /// 임시 정지 여부 + public let temporaryStopped: YesNo + + /// 시가 범위 연장 여부 + public let marketPriceRangeExtended: YesNo + + /// 종가 범위 연장 여부 + public let closingPriceRangeExtended: YesNo + + /// 신용 가능 여부 + public let creditAllowable: YesNo + + /// 보증금 비율 구분 코드 + public let marginalRateClassCode: MarginalRateClass + + /// ELW 발행 여부 + public let elwPublished: YesNo + + /// 주식 현재가 (주식 종가) + public let currentStockPrice: String + + /// 전일 대비 + public let previousDayVariableRatio: String + + /// 전일 대비 부호 + public let previousDayVariableRatioSign: String + + /// 전일 대비율 + public let previousDayDiffRatio: String + + /// 누적 거래 대금 + public let accumulatedTradingAmount: String + + /// 누적 거래량 + public let accumulatedVolume: String + + /// 전일 대비 거래량 비율 + public let previousDayDiffVolumeRatio: String + + /// 주식 시가 + public let stockOpenningPrice: String + + /// 주식 최고가 + public let highestStockPrice: String + + /// 주식 최저가 + public let lowestStockPrice: String + + /// 주식 상한가 + public let maximumStockPrice: String + + /// 주식 하한가 + public let minimumStockPrice: String + + /// 주식 기준가 + public let standardStockPrice: String + + /// 가중 평균 주식 가격 + public let weightedAverageStockPrice: String + + /// HTS 외국인 소진율 + public let htsForeignRunoutRate: String + + /// 외국인 순매수 수량 + public let foreignNetBuyingQuantity: String + + /// 프로그램매매 순매수 수량 + public let programTradeNetBuyingQuantity: String + + /// 피벗 2차 디저항 가격 + public let pivotSecondDResistancePrice: String + + /// 피벗 1차 디저항 가격 + public let pivotFirstDResistancePrice: String + + /// 피벗 포인트 값 + public let pivotPointValue: String + + /// 피벗 1차 디지지 가격 + public let pivotFirstDSupportPrice: String + + /// 피벗 2차 디지지 가격 + public let pivotSecondDSupportPrice: String + + /// 디저항 값 + public let dResistanceValue: String + + /// 디지지 값 + public let dSupportValue: String + + /// 자본금 + public let capital: String + + /// 제한 폭 가격 + public let limitWidthPrice: String + + /// 주식 액면가 + public let stockFacePrice: String + + /// 주식 대용가 + public let stockSubstitudePrice: String + + /// 호가단위 + public let askingPriceUnit: String + + /// HTS 매매 수량 단위 값 + public let htsDealQuantityUnit: String + + /// 상장 주수 + public let listedStockCount: String + + /// HTS 시가총액 + public let htsTotalMarketValue: String + + /// PER + public let per: String + + /// PBR + public let pbr: String + + /// 결산 월 + public let settlingMonth: String + + /// 거래량 회전율 + public let volumeTurnoverRate: String + + /// EPS + public let eps: String + + /// BPS + public let bps: String + + /// 250일 최고가 + public let day250HighestPrice: String + + /// 250일 최고가 일자 + public let day250HighestPriceDate: String + + /// 250일 최고가 대비 현재가 비율 + public let day250HighestPriceDiffRatio: String + + /// 250일 최저가 + public let day250LowestPrice: String + + /// 250일 최저가 일자 + public let day250LowestPriceDate: String + + /// 250일 최저가 대비 현재가 비율 + public let day250LowestPriceDiffRatio: String + + /// 주식 연중 최고가 + public let annualHighestPrice: String + + /// 연중 최고가 대비 현재가 비율 + public let annualHighestPriceDiffRatio: String + + /// 연중 최고가 일자 + public let annualHighestPriceDate: String + + /// 주식 연중 최저가 + public let annualLowestPrice: String + + /// 연중 최저가 대비 현재가 비율 + public let annualLowestPriceDiffRatio: String + + /// 연중 최저가 일자 + public let annualLowestPriceDate: String + + /// 52주일 최고가 + public let week52HighestPrice: String + + /// 52주일 최고가 대비 현재가 대비 + public let week52HighestPriceDiffRatio: String + + /// 52주일 최고가 일자 + public let week52HighestPriceDate: String + + /// 52주일 최저가 + public let week52LowestPrice: String + + /// 52주일 최저가 대비 현재가 대비 + public let week52LowestPriceDiffRatio: String + + /// 52주일 최저가 일자 + public let week52LowestPriceDate: String + + /// 전체 융자 잔고 비율 + public let totalOutstandingloanRate: String + + /// 공매도가능여부 + public let shortSellingAllowable: YesNo + + /// 주식 단축 종목코드 + public let shortProductCode: String + + /// 액면가 통화명 + public let facePriceCurrency: String + + /// 자본금 통화명 + public let capitalCurrency: String + + /// 접근도 + public let approachRate: String? + + /// 외국인 보유 수량 + public let foreignHoldQuantity: String + + /// VI적용구분코드 + public let viClassCode: String + + /// 시간외단일가VI적용구분코드 + public let viClassCodeForOvertimeMarketPrice: String + + /// 최종 공매도 체결 수량 + public let lastShortSellingConclusionQuantity: String + + /// 투자유의여부 + public let investmentCareful: YesNo + + /// 시장경고코드 + public let marketWarningCode: MarketWarning + + /// 단기과열여부 + public let shortOverheated: YesNo + + + public init(captureTime: Date, price p: CurrentPriceResult.OutputDetail) { + self.stockBusinessDate = captureTime.yyyyMMdd + self.captureTime = captureTime.HHmmss + + // MARK: CurrentPriceResult.OutputDetail + self.itemStateCode = p.itemStateCode + self.marginalRate = p.marginalRate + self.koreanMarketName = p.koreanMarketName + self.newHighLowPriceClassCode = p.newHighLowPriceClassCode + self.koreanBusinessTypeName = p.koreanBusinessTypeName + self.temporaryStopped = p.temporaryStopped + self.marketPriceRangeExtended = p.marketPriceRangeExtended + self.closingPriceRangeExtended = p.closingPriceRangeExtended + self.creditAllowable = p.creditAllowable + self.marginalRateClassCode = p.marginalRateClassCode + self.elwPublished = p.elwPublished + self.currentStockPrice = p.currentStockPrice + self.previousDayVariableRatio = p.previousDayVariableRatio + self.previousDayVariableRatioSign = p.previousDayVariableRatioSign + self.previousDayDiffRatio = p.previousDayDiffRatio + self.accumulatedTradingAmount = p.accumulatedTradingAmount + self.accumulatedVolume = p.accumulatedVolume + self.previousDayDiffVolumeRatio = p.previousDayDiffVolumeRatio + self.stockOpenningPrice = p.stockOpenningPrice + self.highestStockPrice = p.highestStockPrice + self.lowestStockPrice = p.lowestStockPrice + self.maximumStockPrice = p.maximumStockPrice + self.minimumStockPrice = p.minimumStockPrice + self.standardStockPrice = p.standardStockPrice + self.weightedAverageStockPrice = p.weightedAverageStockPrice + self.htsForeignRunoutRate = p.htsForeignRunoutRate + self.foreignNetBuyingQuantity = p.foreignNetBuyingQuantity + self.programTradeNetBuyingQuantity = p.programTradeNetBuyingQuantity + self.pivotSecondDResistancePrice = p.pivotSecondDResistancePrice + self.pivotFirstDResistancePrice = p.pivotFirstDResistancePrice + self.pivotPointValue = p.pivotPointValue + self.pivotFirstDSupportPrice = p.pivotFirstDSupportPrice + self.pivotSecondDSupportPrice = p.pivotSecondDSupportPrice + self.dResistanceValue = p.dResistanceValue + self.dSupportValue = p.dSupportValue + self.capital = p.capital + self.limitWidthPrice = p.limitWidthPrice + self.stockFacePrice = p.stockFacePrice + self.stockSubstitudePrice = p.stockSubstitudePrice + self.askingPriceUnit = p.askingPriceUnit + self.htsDealQuantityUnit = p.htsDealQuantityUnit + self.listedStockCount = p.listedStockCount + self.htsTotalMarketValue = p.htsTotalMarketValue + self.per = p.per + self.pbr = p.pbr + self.settlingMonth = p.settlingMonth + self.volumeTurnoverRate = p.volumeTurnoverRate + self.eps = p.eps + self.bps = p.bps + self.day250HighestPrice = p.day250HighestPrice + self.day250HighestPriceDate = p.day250HighestPriceDate + self.day250HighestPriceDiffRatio = p.day250HighestPriceDiffRatio + self.day250LowestPrice = p.day250LowestPrice + self.day250LowestPriceDate = p.day250LowestPriceDate + self.day250LowestPriceDiffRatio = p.day250LowestPriceDiffRatio + self.annualHighestPrice = p.annualHighestPrice + self.annualHighestPriceDiffRatio = p.annualHighestPriceDiffRatio + self.annualHighestPriceDate = p.annualHighestPriceDate + self.annualLowestPrice = p.annualLowestPrice + self.annualLowestPriceDiffRatio = p.annualLowestPriceDiffRatio + self.annualLowestPriceDate = p.annualLowestPriceDate + self.week52HighestPrice = p.week52HighestPrice + self.week52HighestPriceDiffRatio = p.week52HighestPriceDiffRatio + self.week52HighestPriceDate = p.week52HighestPriceDate + self.week52LowestPrice = p.week52LowestPrice + self.week52LowestPriceDiffRatio = p.week52LowestPriceDiffRatio + self.week52LowestPriceDate = p.week52LowestPriceDate + self.totalOutstandingloanRate = p.totalOutstandingloanRate + self.shortSellingAllowable = p.shortSellingAllowable + self.shortProductCode = p.shortProductCode + self.facePriceCurrency = p.facePriceCurrency + self.capitalCurrency = p.capitalCurrency + self.approachRate = p.approachRate + self.foreignHoldQuantity = p.foreignHoldQuantity + self.viClassCode = p.viClassCode + self.viClassCodeForOvertimeMarketPrice = p.viClassCodeForOvertimeMarketPrice + self.lastShortSellingConclusionQuantity = p.lastShortSellingConclusionQuantity + self.investmentCareful = p.investmentCareful + self.marketWarningCode = p.marketWarningCode + self.shortOverheated = p.shortOverheated + } + + public init(array: [String]) throws { + guard array.count == 82 else { + throw GeneralError.incorrectArrayItems + } + self.stockBusinessDate = array[0] + self.captureTime = array[1] + self.itemStateCode = CurrentPriceResult.StateClass(rawValue: array[2])! + self.marginalRate = array[3] + self.koreanMarketName = array[4] + self.newHighLowPriceClassCode = array[5] + self.koreanBusinessTypeName = array[6] + self.temporaryStopped = YesNo(rawValue: array[7])! + self.marketPriceRangeExtended = YesNo(rawValue: array[8])! + self.closingPriceRangeExtended = YesNo(rawValue: array[9])! + self.creditAllowable = YesNo(rawValue: array[10])! + self.marginalRateClassCode = MarginalRateClass(rawValue: array[11])! + self.elwPublished = YesNo(rawValue: array[12])! + self.currentStockPrice = array[13] + self.previousDayVariableRatio = array[14] + self.previousDayVariableRatioSign = array[15] + self.previousDayDiffRatio = array[16] + self.accumulatedTradingAmount = array[17] + self.accumulatedVolume = array[18] + self.previousDayDiffVolumeRatio = array[19] + self.stockOpenningPrice = array[20] + self.highestStockPrice = array[21] + self.lowestStockPrice = array[22] + self.maximumStockPrice = array[23] + self.minimumStockPrice = array[24] + self.standardStockPrice = array[25] + self.weightedAverageStockPrice = array[26] + self.htsForeignRunoutRate = array[27] + self.foreignNetBuyingQuantity = array[28] + self.programTradeNetBuyingQuantity = array[29] + self.pivotSecondDResistancePrice = array[30] + self.pivotFirstDResistancePrice = array[31] + self.pivotPointValue = array[32] + self.pivotFirstDSupportPrice = array[33] + self.pivotSecondDSupportPrice = array[34] + self.dResistanceValue = array[35] + self.dSupportValue = array[36] + self.capital = array[37] + self.limitWidthPrice = array[38] + self.stockFacePrice = array[39] + self.stockSubstitudePrice = array[40] + self.askingPriceUnit = array[41] + self.htsDealQuantityUnit = array[42] + self.listedStockCount = array[43] + self.htsTotalMarketValue = array[44] + self.per = array[45] + self.pbr = array[46] + self.settlingMonth = array[47] + self.volumeTurnoverRate = array[48] + self.eps = array[49] + self.bps = array[50] + self.day250HighestPrice = array[51] + self.day250HighestPriceDate = array[52] + self.day250HighestPriceDiffRatio = array[53] + self.day250LowestPrice = array[54] + self.day250LowestPriceDate = array[55] + self.day250LowestPriceDiffRatio = array[56] + self.annualHighestPrice = array[57] + self.annualHighestPriceDiffRatio = array[58] + self.annualHighestPriceDate = array[59] + self.annualLowestPrice = array[60] + self.annualLowestPriceDiffRatio = array[61] + self.annualLowestPriceDate = array[62] + self.week52HighestPrice = array[63] + self.week52HighestPriceDiffRatio = array[64] + self.week52HighestPriceDate = array[65] + self.week52LowestPrice = array[66] + self.week52LowestPriceDiffRatio = array[67] + self.week52LowestPriceDate = array[68] + self.totalOutstandingloanRate = array[69] + self.shortSellingAllowable = YesNo(rawValue: array[70])! + self.shortProductCode = array[71] + self.facePriceCurrency = array[72] + self.capitalCurrency = array[73] + self.approachRate = array[74] + self.foreignHoldQuantity = array[75] + self.viClassCode = array[76] + self.viClassCodeForOvertimeMarketPrice = array[77] + self.lastShortSellingConclusionQuantity = array[78] + self.investmentCareful = YesNo(rawValue: array[79])! + self.marketWarningCode = MarketWarning(rawValue: array[80])! + self.shortOverheated = YesNo(rawValue: array[81])! + } + + public static func symbols() -> [String] { + let i = try! CapturePrice(array: Array(repeating: "", count: 82)) + return Mirror(reflecting: i).children.compactMap { $0.label } + } + + public static func localizedSymbols() -> [String: String] { + [:] + } +} + + public struct MinutePriceResult: Codable { public let resultCode: String public let messageCode: String diff --git a/KissMeConsole/Sources/Foundation+Extensions.swift b/KissMeConsole/Sources/Foundation+Extensions.swift index 2d3c472..c7e0c01 100644 --- a/KissMeConsole/Sources/Foundation+Extensions.swift +++ b/KissMeConsole/Sources/Foundation+Extensions.swift @@ -79,13 +79,19 @@ extension Date { return (hour, minute, second) } - public func changing(hour: Int, min: Int, sec: Int, timeZone: String = "KST") -> Date? { + 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) components.timeZone = TimeZone(abbreviation: timeZone) - components.hour = hour - components.minute = min - components.second = sec + if let hour = hour { + components.hour = hour + } + if let min = min { + components.minute = min + } + if let sec = sec { + components.second = sec + } components.nanosecond = 0 return Calendar.current.date(from: components) } @@ -256,6 +262,30 @@ extension Array where Element == String { extension String { + init(firstLineOfFile path: String) throws { + guard let filePointer = fopen(path, "r") else { + throw GeneralError.cannotReadFile + } + + var cLineBytes: UnsafeMutablePointer? = nil + defer { + fclose(filePointer) + cLineBytes?.deallocate() + } + + var lineCap: Int = 0 + let bytesRead = getline(&cLineBytes, &lineCap, filePointer) + + guard bytesRead > 0, let cLineBytes = cLineBytes else { + throw GeneralError.cannotReadFileLine + } + guard let str = String(cString: cLineBytes, encoding: .utf8) else { + throw GeneralError.cannotReadFileToConvertString + } + self = str.trimmingCharacters(in: .whitespacesAndNewlines) + } + + /* init(firstLineOfFile path: String) throws { guard let handle = FileHandle(forReadingAtPath: path) else { throw GeneralError.cannotReadFile @@ -264,13 +294,15 @@ extension String { try? handle.close() } + var readData = Data() var headerString = "" while (true) { guard let data = try handle.read(upToCount: 512) else { break } - guard let part = String(data: data, encoding: .utf8) else { - throw GeneralError.cannotReadFile + readData.append(data) + guard let part = String(data: readData, encoding: .utf8) else { + continue } if let range = part.range(of: "\n") { @@ -284,6 +316,7 @@ extension String { self = headerString } + */ static func readCsvHeader(fromFile: URL) throws -> [String] { let header = try String(firstLineOfFile: fromFile.path) diff --git a/KissMeConsole/Sources/KissConsole+Candle.swift b/KissMeConsole/Sources/KissConsole+Candle.swift index b52e465..ec3c976 100644 --- a/KissMeConsole/Sources/KissConsole+Candle.swift +++ b/KissMeConsole/Sources/KissConsole+Candle.swift @@ -76,6 +76,7 @@ extension KissConsole { } } } + try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredCandleTPS) candles.sort(by: { $0.stockBusinessDate > $1.stockBusinessDate }) guard let recentDay = candles.first?.stockBusinessDate else { @@ -142,6 +143,7 @@ extension KissConsole { break } } + try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredCandleTPS) candles.sort(by: { $0.stockFullDate > $1.stockFullDate }) guard let maxTime = candles.first?.stockBusinessDate else { diff --git a/KissMeConsole/Sources/KissConsole+Investor.swift b/KissMeConsole/Sources/KissConsole+Investor.swift index d4de892..36917b0 100644 --- a/KissMeConsole/Sources/KissConsole+Investor.swift +++ b/KissMeConsole/Sources/KissConsole+Investor.swift @@ -22,6 +22,8 @@ extension KissConsole { let fileUrl = KissConsole.investorFileUrl(productNo: productNo, day: recentDay) try output.writeCsv(toFile: fileUrl, localized: localized) + + try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredCandleTPS) } return true } diff --git a/KissMeConsole/Sources/KissConsole+Price.swift b/KissMeConsole/Sources/KissConsole+Price.swift new file mode 100644 index 0000000..407412d --- /dev/null +++ b/KissMeConsole/Sources/KissConsole+Price.swift @@ -0,0 +1,67 @@ +// +// KissConsole+Price.swift +// KissMeConsole +// +// Created by ened-book-m1 on 2023/06/11. +// + +import Foundation +import KissMe + + +struct PriceTime { +} + + + +extension KissConsole { + + func getCurrentPrice(productNo: String, printConsole: Bool = false) async -> Bool { + do { + let result = try await account!.getCurrentPrice(productNo: productNo) + guard let output = result.output else { + print("Invalid result's output for \(productNo)") + return false + } + + if printConsole { + printCurrentPrice(output) + } + + let price = CapturePrice(captureTime: Date(), price: output) + + let fileUrl = KissConsole.productPriceUrl(productNo: productNo) + try [price].writeCsv(toFile: fileUrl, appendable: true, localized: localized) + + try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredCandleTPS) + } catch { + print(error) + return false + } + return true + } + + private func printCurrentPrice(_ output: CurrentPriceResult.OutputDetail) { + let productName = getProduct(shortCode: output.shortProductCode)?.itemName ?? "" + print("\t종목명: ", productName) + print("\t업종명: ", output.koreanMarketName, output.koreanBusinessTypeName ?? "") + print("\t주식 현재가: ", output.currentStockPrice) + print("\t전일 대비: ", output.previousDayVariableRatio) + print("\t누적 거래 대금: ", output.accumulatedTradingAmount) + print("\t누적 거래량: ", output.accumulatedVolume) + print("\t전일 대비 거래량 비율: ", output.previousDayDiffVolumeRatio) + print("\t주식 시가: ", output.stockOpenningPrice) + print("\t주식 최고가: ", output.highestStockPrice) + print("\t주식 최저가: ", output.lowestStockPrice) + print("\t외국인 순매수 수량: ", output.foreignNetBuyingQuantity) + print("\t외국인 보유 수량: ", output.foreignHoldQuantity) + print("\t최종 공매도 체결 수량: ", output.lastShortSellingConclusionQuantity) + print("\t프로그램매매 순매수 수량: ", output.programTradeNetBuyingQuantity) + print("\t자본금: ", output.capital) + print("\t상장 주수: ", output.listedStockCount) + print("\tHTS 시가총액: ", output.htsTotalMarketValue) + print("\tPER: ", output.per) + print("\tPBR: ", output.pbr) + print("\t주식 단축 종목코드", output.shortProductCode) + } +} diff --git a/KissMeConsole/Sources/KissConsole.swift b/KissMeConsole/Sources/KissConsole.swift index abf560e..9cfdb9a 100644 --- a/KissMeConsole/Sources/KissConsole.swift +++ b/KissMeConsole/Sources/KissConsole.swift @@ -49,6 +49,7 @@ class KissConsole { // 종목 시세 case now = "now" + case nowAll = "now all" case candle = "candle" case candleAll = "candle all" case candleDay = "candle day" @@ -85,7 +86,7 @@ class KissConsole { return true case .openBag: return true - case .now, .candle, .candleAll, .candleDay, .candleWeek: + case .now, .nowAll, .candle, .candleAll, .candleDay, .candleWeek: return true case .candleValidate: return false @@ -204,6 +205,7 @@ class KissConsole { case .openBag: await onOpenBag() case .now: await onNow(args) + case .nowAll: onNowAll(args) case .candle: await onCandle(args) case .candleAll: onCancleAll(args) case .candleDay: onCandleDay(args) @@ -263,7 +265,7 @@ extension KissConsole { return products.compactMap { $0.value.first(where: { $0.isinCode == isin }) }.first } - private func getProduct(shortCode: String) -> DomesticShop.Product? { + func getProduct(shortCode: String) -> DomesticShop.Product? { productsLock.lock() defer { productsLock.unlock() @@ -314,7 +316,7 @@ extension KissConsole { _ = LocalContext.shared.localNamesDic } - private func setCurrent(productNo: String) { + func setCurrent(productNo: String) { productsLock.lock() currentShortCode = productNo productsLock.unlock() @@ -569,53 +571,45 @@ extension KissConsole { print("Invalid productNo") return } - - do { - let result = try await account!.getCurrentPrice(productNo: productNo) - guard let output = result.output else { - print("Invalid result's output") - return - } - - let productName = getProduct(shortCode: output.shortProductCode)?.itemName ?? "" - print("\t종목명: ", productName) - print("\t업종명: ", output.koreanMarketName, output.koreanBusinessTypeName) - print("\t주식 현재가: ", output.currentStockPrice) - print("\t전일 대비: ", output.previousDayVariableRatio) - print("\t누적 거래 대금: ", output.accumulatedTradingAmount) - print("\t누적 거래량: ", output.accumulatedVolume) - print("\t전일 대비 거래량 비율: ", output.previousDayDiffVolumeRatio) - print("\t주식 시가: ", output.stockOpenningPrice) - print("\t주식 최고가: ", output.highestStockPrice) - print("\t주식 최저가: ", output.lowestStockPrice) - print("\t외국인 순매수 수량: ", output.foreignNetBuyingQuantity) - print("\t외국인 보유 수량: ", output.foreignHoldQuantity) - print("\t최종 공매도 체결 수량: ", output.lastShortSellingConclusionQuantity) - print("\t프로그램매매 순매수 수량: ", output.programTradeNetBuyingQuantity) - print("\t자본금: ", output.capital) - print("\t상장 주수: ", output.listedStockCount) - print("\tHTS 시가총액: ", output.htsTotalMarketValue) - print("\tPER: ", output.per) - print("\tPBR: ", output.pbr) - print("\t주식 단축 종목코드", output.shortProductCode) - setCurrent(productNo: output.shortProductCode) - - - let fileUrl = KissConsole.productPriceUrl(productNo: productNo) - try [output].writeCsv(toFile: fileUrl, appendable: true, localized: localized) - - } catch { - print("\(error)") + let success = await getCurrentPrice(productNo: productNo) + if success { + setCurrent(productNo: productNo) } } + private func onNowAll(_ args: [String]) { + let all = getAllProducts() + for item in all { + let semaphore = DispatchSemaphore(value: 0) + Task { + let holiday = try? await checkHoliday(Date()) + if holiday == true { + print("DONE today is holiday") + return + } + + let success = await getCurrentPrice(productNo: item.shortCode) + #if DEBUG + if !success { + exit(-100) + } + #endif + print("DONE \(success) \(item.shortCode)") + semaphore.signal() + } + semaphore.wait() + } + print("FINISHED") + } + + private func onCancleAll(_ args: [String]) { let semaphore = DispatchSemaphore(value: 0) Task { - await KissContext.shared.update(candleResuming: false) + await KissContext.shared.update(resuming: false) if args.count == 1, args[0] == "resume" { - await KissContext.shared.update(candleResuming: true) + await KissContext.shared.update(resuming: true) } semaphore.signal() } @@ -632,7 +626,7 @@ extension KissConsole { return } - if await KissContext.shared.isCandleResuming { + if await KissContext.shared.isResuming { let curDate = Date() let url = KissConsole.candleFileUrl(productNo: item.shortCode, period: .minute, day: curDate.yyyyMMdd) let r = validateCsv(filePriod: .minute, url: url) @@ -648,6 +642,7 @@ extension KissConsole { } semaphore.wait() } + print("FINISHED") } @@ -695,6 +690,7 @@ extension KissConsole { } semaphore.wait() } + print("FINISHED") } @@ -742,6 +738,7 @@ extension KissConsole { } semaphore.wait() } + print("FINISHED") } @@ -798,6 +795,7 @@ extension KissConsole { } semaphore.wait() } + print("FINISHED") } @@ -996,6 +994,7 @@ extension KissConsole { symbols.formUnion(PeriodPriceResult.OutputPrice.symbols()) symbols.formUnion(VolumeRankResult.OutputDetail.symbols()) symbols.formUnion(CurrentPriceResult.OutputDetail.symbols()) + symbols.formUnion(CapturePrice.symbols()) symbols.formUnion(InvestorVolumeResult.OutputDetail.symbols()) let newNames = symbols.sorted(by: { $0 < $1 }) diff --git a/KissMeConsole/Sources/KissContext.swift b/KissMeConsole/Sources/KissContext.swift index 8533100..9928b51 100644 --- a/KissMeConsole/Sources/KissContext.swift +++ b/KissMeConsole/Sources/KissContext.swift @@ -13,7 +13,7 @@ actor KissContext { private(set) var targetDate: Date = Date(timeIntervalSince1970: 0) private(set) var isHoliday: Bool = false - private(set) var isCandleResuming: Bool = false + private(set) var isResuming: Bool = false private init() { } @@ -22,7 +22,7 @@ actor KissContext { self.targetDate = targetDate } - func update(candleResuming: Bool) { - self.isCandleResuming = candleResuming + func update(resuming: Bool) { + self.isResuming = resuming } } diff --git a/bin/data b/bin/data index 80c2298..3aea689 160000 --- a/bin/data +++ b/bin/data @@ -1 +1 @@ -Subproject commit 80c229837764e6e0eab2303ede7d7daf1c79c842 +Subproject commit 3aea689c730a54f34d56067d4b8cba056d195c76 diff --git a/projects/macos/KissMeConsole.xcodeproj/project.pbxproj b/projects/macos/KissMeConsole.xcodeproj/project.pbxproj index a0ed1c1..478d9d8 100644 --- a/projects/macos/KissMeConsole.xcodeproj/project.pbxproj +++ b/projects/macos/KissMeConsole.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 341F5F052A13B82F00962D48 /* test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F042A13B82F00962D48 /* test.swift */; }; 341F5F092A1463A100962D48 /* KissConsole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F082A1463A100962D48 /* KissConsole.swift */; }; 3435A7F22A35A8A900D604F1 /* KissConsole+Investor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3435A7F12A35A8A900D604F1 /* KissConsole+Investor.swift */; }; + 3435A7F42A35B4D000D604F1 /* KissConsole+Price.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3435A7F32A35B4D000D604F1 /* KissConsole+Price.swift */; }; 348168492A2F92AC00A50BD3 /* KissContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348168482A2F92AC00A50BD3 /* KissContext.swift */; }; 348168692A3420BD00A50BD3 /* LocalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348168682A3420BD00A50BD3 /* LocalContext.swift */; }; 349327F72A20E3E300097063 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349327F62A20E3E300097063 /* Foundation+Extensions.swift */; }; @@ -50,6 +51,7 @@ 341F5F042A13B82F00962D48 /* test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = test.swift; sourceTree = ""; }; 341F5F082A1463A100962D48 /* KissConsole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissConsole.swift; sourceTree = ""; }; 3435A7F12A35A8A900D604F1 /* KissConsole+Investor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KissConsole+Investor.swift"; sourceTree = ""; }; + 3435A7F32A35B4D000D604F1 /* KissConsole+Price.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KissConsole+Price.swift"; sourceTree = ""; }; 348168482A2F92AC00A50BD3 /* KissContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissContext.swift; sourceTree = ""; }; 348168682A3420BD00A50BD3 /* LocalContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalContext.swift; sourceTree = ""; }; 349327F62A20E3E300097063 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = ""; }; @@ -98,6 +100,7 @@ 341F5F082A1463A100962D48 /* KissConsole.swift */, 34D3680C2A280801005E6756 /* KissConsole+Candle.swift */, 349843202A242AC900E85B08 /* KissConsole+CSV.swift */, + 3435A7F32A35B4D000D604F1 /* KissConsole+Price.swift */, 3435A7F12A35A8A900D604F1 /* KissConsole+Investor.swift */, 349327F62A20E3E300097063 /* Foundation+Extensions.swift */, ); @@ -190,6 +193,7 @@ 348168692A3420BD00A50BD3 /* LocalContext.swift in Sources */, 349843212A242AC900E85B08 /* KissConsole+CSV.swift in Sources */, 348168492A2F92AC00A50BD3 /* KissContext.swift in Sources */, + 3435A7F42A35B4D000D604F1 /* KissConsole+Price.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };