From 79ab809e9e82c09a68e0fadaa1bb64e246910502 Mon Sep 17 00:00:00 2001 From: ened Date: Mon, 29 May 2023 15:49:39 +0900 Subject: [PATCH] Write candle data --- ...ions.swift => Foundation+Extensions.swift} | 19 +++++- .../Sources/Domestic/DomesticStockPrice.swift | 4 +- ...ions.swift => Foundation+Extensions.swift} | 8 ++- KissMeConsole/Sources/KissConsole+CSV.swift | 60 +++++++++++++++++ KissMeConsole/Sources/KissConsole.swift | 67 +++++++++++-------- .../macos/KissMe.xcodeproj/project.pbxproj | 8 +-- .../KissMeConsole.xcodeproj/project.pbxproj | 23 +++++-- 7 files changed, 150 insertions(+), 39 deletions(-) rename KissMe/Sources/Common/{KissExtensions.swift => Foundation+Extensions.swift} (76%) rename KissMeConsole/Sources/{ConsoleExtensions.swift => Foundation+Extensions.swift} (85%) create mode 100644 KissMeConsole/Sources/KissConsole+CSV.swift diff --git a/KissMe/Sources/Common/KissExtensions.swift b/KissMe/Sources/Common/Foundation+Extensions.swift similarity index 76% rename from KissMe/Sources/Common/KissExtensions.swift rename to KissMe/Sources/Common/Foundation+Extensions.swift index 19ec0f8..b1d3b03 100644 --- a/KissMe/Sources/Common/KissExtensions.swift +++ b/KissMe/Sources/Common/Foundation+Extensions.swift @@ -1,5 +1,5 @@ // -// Extensions.swift +// Foundation+Extensions.swift // KissMe // // Created by ened-book-m1 on 2023/05/17. @@ -39,6 +39,23 @@ extension Date { } +extension String { + public var HHmmss: (Int, Int, Int)? { + guard utf8.count == 6 else { + return nil + } + let mmStartIndex = index(startIndex, offsetBy: 2) + let mmEndIndex = index(mmStartIndex, offsetBy: 2) + guard let hh = Int(String(prefix(2))), + let mm = Int(self[mmStartIndex.. URL { diff --git a/KissMe/Sources/Domestic/DomesticStockPrice.swift b/KissMe/Sources/Domestic/DomesticStockPrice.swift index 4319b0d..0a925aa 100644 --- a/KissMe/Sources/Domestic/DomesticStockPrice.swift +++ b/KissMe/Sources/Domestic/DomesticStockPrice.swift @@ -134,7 +134,7 @@ extension KissAccount { /// 현재 종목 분봉을 가져오기 /// - public func getMinutePrice(productNo: String, startTodayTime: Date) async throws -> MinutePriceResult { + public func getMinutePrice(productNo: String, startTodayTime: Date, more: Bool = false) async throws -> MinutePriceResult { return try await withUnsafeThrowingContinuation { continuation in guard let accessToken = accessToken else { @@ -142,7 +142,7 @@ extension KissAccount { return } - let request = Domestic.StockTodayMinutePriceRequest(credential: credential, accessToken: accessToken, productNo: productNo, startTodayTime: startTodayTime.HHmmss, isNext: false) + let request = Domestic.StockTodayMinutePriceRequest(credential: credential, accessToken: accessToken, productNo: productNo, startTodayTime: startTodayTime.HHmmss, isNext: more) request.query { result in switch result { case .success(let result): diff --git a/KissMeConsole/Sources/ConsoleExtensions.swift b/KissMeConsole/Sources/Foundation+Extensions.swift similarity index 85% rename from KissMeConsole/Sources/ConsoleExtensions.swift rename to KissMeConsole/Sources/Foundation+Extensions.swift index 0f308c2..bf2f93b 100644 --- a/KissMeConsole/Sources/ConsoleExtensions.swift +++ b/KissMeConsole/Sources/Foundation+Extensions.swift @@ -1,5 +1,5 @@ // -// ConsoleExtensions.swift +// Foundation+Extensions.swift // KissMeConsole // // Created by ened-book-m1 on 2023/05/26. @@ -50,4 +50,10 @@ extension Date { components.second = sec return Calendar.current.date(from: components) } + + public mutating func change(hour: Int, min: Int, sec: Int, timeZone: String = "KST") { + if let newDate = changing(hour: hour, min: min, sec: sec, timeZone: timeZone) { + self = newDate + } + } } diff --git a/KissMeConsole/Sources/KissConsole+CSV.swift b/KissMeConsole/Sources/KissConsole+CSV.swift new file mode 100644 index 0000000..8f37460 --- /dev/null +++ b/KissMeConsole/Sources/KissConsole+CSV.swift @@ -0,0 +1,60 @@ +// +// KissConsole+CSV.swift +// KissMeConsole +// +// Created by ened-book-m1 on 2023/05/29. +// + +import Foundation +import KissMe + + +extension KissConsole { + + func writeShop(_ shopItems: [DomesticShop.Product], fileUrl: URL) { + var stringCsv: String = "" + let header = "baseDate,shortCode,isinCode,marketCategory,itemName,corporationNo,\n" + stringCsv.append(header) + for item in shopItems { + let stringItem = [item.baseDate, + item.shortCode, + item.isinCode, + item.marketCategory, + item.itemName.trimmingCharacters(in: .whitespaces), + item.corporationNo].joined(separator: ",") + stringCsv.append(stringItem) + stringCsv.append("\n") + } + + do { + try stringCsv.write(toFile: fileUrl.path, atomically: true, encoding: .utf8) + print("wrote \(fileUrl.lastPathComponent) with \(shopItems.count)") + } catch { + print("\(error)") + } + } + + func writeCandle(_ prices: [MinutePriceResult.OutputPrice], fileUrl: URL) { + var stringCsv: String = "stockBusinessDate,stockConclusionTime,accumulatedTradingAmount,currentStockPrice,secondStockPrice,highestStockPrice,lowestStockPrice,conclusionVolume" + let header = "" + stringCsv.append(header) + for item in prices { + let stringItem = [item.stockBusinessDate, + item.stockConclusionTime, + item.accumulatedTradingAmount, + item.currentStockPrice, + item.secondStockPrice, + item.highestStockPrice, + item.lowestStockPrice, + item.conclusionVolume].joined(separator: ",") + stringCsv.append(stringItem) + stringCsv.append("\n") + } + do { + try stringCsv.write(toFile: fileUrl.path, atomically: true, encoding: .utf8) + print("wrote \(fileUrl.lastPathComponent) with \(prices.count)") + } catch { + print("\(error)") + } + } +} diff --git a/KissMeConsole/Sources/KissConsole.swift b/KissMeConsole/Sources/KissConsole.swift index eee82cb..3ece378 100644 --- a/KissMeConsole/Sources/KissConsole.swift +++ b/KissMeConsole/Sources/KissConsole.swift @@ -164,10 +164,6 @@ extension KissConsole { try? FileManager.default.createDirectory(at: subPath, withIntermediateDirectories: true) } - private var shopProducts: URL { - URL.currentDirectory().appending(path: "data/shop-products.csv") - } - private func setProducts(_ products: [String: [DomesticShop.Product]]) { productsLock.lock() self.products = products @@ -201,9 +197,13 @@ extension KissConsole { return products.compactMap { $0.value.first(where: { $0.shortCode == shortCode }) }.first } + private var shopProductsUrl: URL { + URL.currentDirectory().appending(path: "data/shop-products.csv") + } + private func loadShop(_ profile: Bool = false) { let appTime1 = Date.appTime - guard let stringCsv = try? String(contentsOfFile: shopProducts.path) else { + guard let stringCsv = try? String(contentsOfFile: shopProductsUrl.path) else { return } @@ -267,6 +267,7 @@ extension KissConsole { } } + extension KissConsole { private func onLogin(isMock: Bool) async { @@ -292,6 +293,7 @@ extension KissConsole { _ = try await account?.logout() credential = nil account = nil + print("Success") } catch { print("\(error)") } @@ -433,16 +435,40 @@ extension KissConsole { } do { - let lastWeek = Date().addingTimeInterval(-60 * 60 * 24 * 7) - guard let startTime = lastWeek.changing(hour: 9, min: 0, sec: 0) else { - print("Invalid start time") + var nextTime = Date() + nextTime.change(hour: 17, min: 0, sec: 0) + + var candles = [MinutePriceResult.OutputPrice]() + var count = 0 + + while count < 3 { + let more = (count > 0) + count += 1 + print("minute price \(productNo) from \(nextTime.yyyyMMdd_HHmmss_forTime) \(more)") + let result = try await account!.getMinutePrice(productNo: productNo, startTodayTime: nextTime, more: more) + + if let prices = result.output2 { + candles.append(contentsOf: prices) + if let last = prices.last { + if let (hh, mm, ss) = last.stockConclusionTime.HHmmss { + print("next: \(last.stockConclusionTime) / \(hh) \(mm) \(ss)") + nextTime.change(hour: hh, min: mm-1, sec: ss) + } + } + } + } + + candles.sort(by: { $0.stockBusinessDate < $1.stockBusinessDate }) + guard let minTime = candles.first?.stockBusinessDate else { + print("No price items") return } - let result = try await account!.getMinutePrice(productNo: productNo, startTodayTime: startTime) - // TODO: work on result - - print(result) + let subPath = "data/\(productNo)" + let subFile = "\(subPath)/candle-\(minTime).csv" + let fileUrl = URL.currentDirectory().appending(path: subFile) + createSubpath(subPath) + writeCandle(candles, fileUrl: fileUrl) } catch { print("\(error)") } @@ -478,21 +504,7 @@ extension KissConsole { } } - var stringCsv: String = "" - let header = "baseDate,shortCode,isinCode,marketCategory,itemName,corporationNo,\n" - stringCsv.append(header) - for item in shopItems { - let stringItem = [item.baseDate, item.shortCode, item.isinCode, item.marketCategory, item.itemName.trimmingCharacters(in: .whitespaces), item.corporationNo].joined(separator: ",") - stringCsv.append(stringItem) - stringCsv.append("\n") - } - - do { - try stringCsv.write(toFile: shopProducts.path, atomically: true, encoding: .utf8) - print("wrote \(shopProducts.lastPathComponent) with \(shopItems.count)") - } catch { - print("\(error)") - } + writeShop(shopItems, fileUrl: shopProductsUrl) } private func getAllProduct(baseDate: Date) async -> [DomesticShop.Product] { @@ -562,6 +574,7 @@ extension KissConsole { } } + private func onLove(_ args: [String]) async { guard let account = account else { return } guard args.count > 0 else { diff --git a/projects/macos/KissMe.xcodeproj/project.pbxproj b/projects/macos/KissMe.xcodeproj/project.pbxproj index f322f45..b73d08a 100644 --- a/projects/macos/KissMe.xcodeproj/project.pbxproj +++ b/projects/macos/KissMe.xcodeproj/project.pbxproj @@ -27,7 +27,7 @@ 341F5EFF2A10955D00962D48 /* OrderRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EFE2A10955D00962D48 /* OrderRequest.swift */; }; 341F5F012A11155100962D48 /* DomesticStockSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F002A11155100962D48 /* DomesticStockSearchResult.swift */; }; 341F5F032A11A2BC00962D48 /* Credential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F022A11A2BC00962D48 /* Credential.swift */; }; - 341F5F072A14634F00962D48 /* KissExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F062A14634F00962D48 /* KissExtensions.swift */; }; + 341F5F072A14634F00962D48 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F062A14634F00962D48 /* Foundation+Extensions.swift */; }; 341F5F0B2A15115400962D48 /* KissShop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F0A2A15115400962D48 /* KissShop.swift */; }; 341F5F0D2A15222E00962D48 /* AuthRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F0C2A15222E00962D48 /* AuthRequest.swift */; }; 341F5F0F2A15223A00962D48 /* SeibroRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F0E2A15223A00962D48 /* SeibroRequest.swift */; }; @@ -68,7 +68,7 @@ 341F5EFE2A10955D00962D48 /* OrderRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderRequest.swift; sourceTree = ""; }; 341F5F002A11155100962D48 /* DomesticStockSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticStockSearchResult.swift; sourceTree = ""; }; 341F5F022A11A2BC00962D48 /* Credential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credential.swift; sourceTree = ""; }; - 341F5F062A14634F00962D48 /* KissExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissExtensions.swift; sourceTree = ""; }; + 341F5F062A14634F00962D48 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = ""; }; 341F5F0A2A15115400962D48 /* KissShop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissShop.swift; sourceTree = ""; }; 341F5F0C2A15222E00962D48 /* AuthRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthRequest.swift; sourceTree = ""; }; 341F5F0E2A15223A00962D48 /* SeibroRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeibroRequest.swift; sourceTree = ""; }; @@ -183,7 +183,7 @@ 341F5F0E2A15223A00962D48 /* SeibroRequest.swift */, 341F5F102A1685E700962D48 /* ShopRequest.swift */, 341F5F022A11A2BC00962D48 /* Credential.swift */, - 341F5F062A14634F00962D48 /* KissExtensions.swift */, + 341F5F062A14634F00962D48 /* Foundation+Extensions.swift */, ); path = Common; sourceTree = ""; @@ -307,7 +307,7 @@ files = ( 341F5EFB2A10909D00962D48 /* LoginResult.swift in Sources */, 341F5F0B2A15115400962D48 /* KissShop.swift in Sources */, - 341F5F072A14634F00962D48 /* KissExtensions.swift in Sources */, + 341F5F072A14634F00962D48 /* Foundation+Extensions.swift in Sources */, 341F5F032A11A2BC00962D48 /* Credential.swift in Sources */, 341F5EB02A0A80EC00962D48 /* KissMe.docc in Sources */, 341F5EFD2A10931B00962D48 /* DomesticStockSearch.swift in Sources */, diff --git a/projects/macos/KissMeConsole.xcodeproj/project.pbxproj b/projects/macos/KissMeConsole.xcodeproj/project.pbxproj index 7055071..3d10e65 100644 --- a/projects/macos/KissMeConsole.xcodeproj/project.pbxproj +++ b/projects/macos/KissMeConsole.xcodeproj/project.pbxproj @@ -10,7 +10,8 @@ 341F5ED42A0A8B9000962D48 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5ED32A0A8B9000962D48 /* main.swift */; }; 341F5F052A13B82F00962D48 /* test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F042A13B82F00962D48 /* test.swift */; }; 341F5F092A1463A100962D48 /* KissConsole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F082A1463A100962D48 /* KissConsole.swift */; }; - 349327F72A20E3E300097063 /* ConsoleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349327F62A20E3E300097063 /* ConsoleExtensions.swift */; }; + 349327F72A20E3E300097063 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349327F62A20E3E300097063 /* Foundation+Extensions.swift */; }; + 349843212A242AC900E85B08 /* KissConsole+CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349843202A242AC900E85B08 /* KissConsole+CSV.swift */; }; 34EE76862A1C391B009761D2 /* KissMe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 341F5EDB2A0A8C4600962D48 /* KissMe.framework */; }; 34EE76872A1C391B009761D2 /* KissMe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 341F5EDB2A0A8C4600962D48 /* KissMe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -44,7 +45,9 @@ 341F5EDB2A0A8C4600962D48 /* KissMe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KissMe.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 341F5F042A13B82F00962D48 /* test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = test.swift; sourceTree = ""; }; 341F5F082A1463A100962D48 /* KissConsole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissConsole.swift; sourceTree = ""; }; - 349327F62A20E3E300097063 /* ConsoleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleExtensions.swift; sourceTree = ""; }; + 349327F62A20E3E300097063 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = ""; }; + 3498431E2A24287600E85B08 /* KissMeConsoleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KissMeConsoleTests.swift; sourceTree = ""; }; + 349843202A242AC900E85B08 /* KissConsole+CSV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KissConsole+CSV.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -63,6 +66,7 @@ isa = PBXGroup; children = ( 341F5ED22A0A8B9000962D48 /* KissMeConsole */, + 3498431D2A24284600E85B08 /* KissMeConsoleTests */, 341F5ED12A0A8B9000962D48 /* Products */, 341F5EDA2A0A8C4600962D48 /* Frameworks */, ); @@ -82,7 +86,8 @@ 341F5ED32A0A8B9000962D48 /* main.swift */, 341F5F042A13B82F00962D48 /* test.swift */, 341F5F082A1463A100962D48 /* KissConsole.swift */, - 349327F62A20E3E300097063 /* ConsoleExtensions.swift */, + 349843202A242AC900E85B08 /* KissConsole+CSV.swift */, + 349327F62A20E3E300097063 /* Foundation+Extensions.swift */, ); name = KissMeConsole; path = ../../KissMeConsole/Sources; @@ -96,6 +101,15 @@ name = Frameworks; sourceTree = ""; }; + 3498431D2A24284600E85B08 /* KissMeConsoleTests */ = { + isa = PBXGroup; + children = ( + 3498431E2A24287600E85B08 /* KissMeConsoleTests.swift */, + ); + name = KissMeConsoleTests; + path = ../../KissMeConsole/Tests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -156,9 +170,10 @@ buildActionMask = 2147483647; files = ( 341F5ED42A0A8B9000962D48 /* main.swift in Sources */, - 349327F72A20E3E300097063 /* ConsoleExtensions.swift in Sources */, + 349327F72A20E3E300097063 /* Foundation+Extensions.swift in Sources */, 341F5F092A1463A100962D48 /* KissConsole.swift in Sources */, 341F5F052A13B82F00962D48 /* test.swift in Sources */, + 349843212A242AC900E85B08 /* KissConsole+CSV.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };