Write csv pretty method

This commit is contained in:
2023-06-03 07:56:55 +09:00
parent d1888928cc
commit 8a6fc7b799
11 changed files with 136 additions and 73 deletions

View File

@@ -93,3 +93,14 @@ extension URL {
}
}
#endif
@_silgen_name("swift_EnumCaseName")
func _getEnumCaseName<T>(_ value: T) -> UnsafePointer<CChar>?
public func getEnumCaseName<T>(for value: T) -> String? {
if let stringPtr = _getEnumCaseName(value) {
return String(validatingUTF8: stringPtr)
}
return nil
}

View File

@@ -0,0 +1,33 @@
//
// PropertyIterable.swift
// KissMe
//
// Created by ened-book-m1 on 2023/06/03.
//
import Foundation
public protocol PropertyIterable {
func allProperties() throws -> [(String, Any)]
}
public extension PropertyIterable {
func allProperties() throws -> [(String, Any)] {
var result = [(String, Any)]()
let mirror = Mirror(reflecting: self)
// Optional check to make sure we're iterating over a struct or class
guard let style = mirror.displayStyle, style == .struct || style == .class else {
throw NSError()
}
for (property, value) in mirror.children {
guard let property = property else {
continue
}
result.append((property, value))
}
return result
}
}

View File

@@ -11,6 +11,7 @@ import Foundation
public struct Domestic {
public typealias Candle = MinutePriceResult.OutputPrice
public typealias CandlePeriod = PeriodPriceResult.OutputPrice
public typealias Top = VolumeRankResult.OutputDetail
}

View File

@@ -516,7 +516,7 @@ public struct MinutePriceResult: Codable {
}
}
public struct OutputPrice: Codable {
public struct OutputPrice: Codable, PropertyIterable {
///
public let stockBusinessDate: String
@@ -541,7 +541,7 @@ public struct MinutePriceResult: Codable {
///
public let conclusionVolume: String
private enum CodingKeys: String, CodingKey {
private enum CodingKeys: String, CodingKey, CaseIterable {
case stockBusinessDate = "stck_bsop_date"
case stockConclusionTime = "stck_cntg_hour"
case accumulatedTradingAmount = "acml_tr_pbmn"
@@ -789,7 +789,7 @@ public struct PeriodPriceResult: Codable {
}
public struct OutputPrice: Codable {
public struct OutputPrice: Codable, PropertyIterable {
///
public let stockBusinessDate: String
@@ -829,7 +829,7 @@ public struct PeriodPriceResult: Codable {
///
public let revaluationIssueReason: String
private enum CodingKeys: String, CodingKey {
private enum CodingKeys: String, CodingKey, CaseIterable {
case stockBusinessDate = "stck_bsop_date"
case stockClosingPrice = "stck_clpr"
case stockOpenningPrice = "stck_oprc"

View File

@@ -21,7 +21,7 @@ public struct VolumeRankResult: Codable {
case output = "output"
}
public struct OutputDetail: Codable {
public struct OutputDetail: Codable, PropertyIterable {
/// HTS
public let htsProductName: String
@@ -79,7 +79,7 @@ public struct VolumeRankResult: Codable {
///
public let accumulatedTradingAmount: String
private enum CodingKeys: String, CodingKey {
private enum CodingKeys: String, CodingKey, CaseIterable {
case htsProductName = "hts_kor_isnm"
case shortProductNo = "mksc_shrn_iscd"
case dataRank = "data_rank"

View File

@@ -94,7 +94,7 @@ extension DomesticShop {
public let item: [Item]
}
public struct Item: Codable {
public struct Item: Codable, PropertyIterable {
///
public let baseDate: String

View File

@@ -6,6 +6,7 @@
//
import Foundation
import KissMe
extension String {
@@ -92,3 +93,20 @@ extension Date {
}
}
}
extension Array where Element: PropertyIterable {
func writeCsv(toFile: URL) throws {
var stringCsv = ""
for item in self {
let all = try item.allProperties()
if stringCsv.isEmpty {
let header = all.map{ $0.0 }.joined(separator: ",").appending("\n")
stringCsv.append(header)
}
let values = all.map{ ($0.1 as? String) ?? "" }.joined(separator: ",").appending("\n")
stringCsv.append(values)
}
try stringCsv.write(toFile: toFile.path, atomically: true, encoding: .utf8)
}
}

View File

@@ -15,12 +15,20 @@ extension KissConsole {
URL.currentDirectory().appending(path: "data/shop-products.csv")
}
func topProductsUrl(_ belong: BelongClassCode) -> URL {
URL.currentDirectory().appending(path: "data/top-\(belong.fileBelong).csv")
}
}
extension KissConsole {
func loadShop(_ profile: Bool = false) {
let appTime1 = Date.appTime
guard let stringCsv = try? String(contentsOfFile: shopProductsUrl.path) else {
return
}
let appTime2 = Date.appTime
if profile {
print("\tloading file \(appTime2 - appTime1) elapsed")
@@ -60,81 +68,43 @@ extension KissConsole {
}
static func writeShop(_ shopItems: [DomesticShop.Product], fileUrl: URL) {
var stringCsv = ""
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)
try shopItems.writeCsv(toFile: fileUrl)
print("wrote \(fileUrl.lastPathComponent) with \(shopItems.count)")
} catch {
print("\(error)")
print(error)
}
}
static func writeCandle(_ prices: [Domestic.Candle], fileUrl: URL) {
var stringCsv = ""
let header = "stockBusinessDate,stockConclusionTime,accumulatedTradingAmount,currentStockPrice,secondStockPrice,highestStockPrice,lowestStockPrice,conclusionVolume\n"
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)")
}
}
static func writeCandle(_ prices: [Domestic.CandlePeriod], fileUrl: URL) {
var stringCsv = ""
let header = "stockBusinessDate,stockClosingPrice,stockOpenningPrice,highestStockPrice,lowestStockPrice,accumulatedVolume,accumulatedTradingAmount,exDivision,partitionRate,partitionModifiable,previousDayVariableRatioSign,previousDayVariableRatio,revaluationIssueReason\n"
stringCsv.append(header)
for item in prices {
let stringItem = [item.stockBusinessDate,
item.stockClosingPrice,
item.stockOpenningPrice,
item.highestStockPrice,
item.lowestStockPrice,
item.accumulatedVolume,
item.accumulatedTradingAmount,
item.exDivision.rawValue,
item.partitionRate,
item.partitionModifiable.rawValue,
item.previousDayVariableRatioSign,
item.previousDayVariableRatio,
item.revaluationIssueReason].joined(separator: ",")
stringCsv.append(stringItem)
stringCsv.append("\n")
}
do {
try stringCsv.write(toFile: fileUrl.path, atomically: true, encoding: .utf8)
try prices.writeCsv(toFile: fileUrl)
print("wrote \(fileUrl.lastPathComponent) with \(prices.count)")
} catch {
print(error)
}
}
static func writeCandle(_ prices: [Domestic.CandlePeriod], fileUrl: URL) {
do {
try prices.writeCsv(toFile: fileUrl)
print("wrote \(fileUrl.lastPathComponent) with \(prices.count)")
} catch {
print(error)
}
}
static func writeTop(_ tops: [Domestic.Top], fileUrl: URL) {
do {
try tops.writeCsv(toFile: fileUrl)
print("wrote \(fileUrl.lastPathComponent) with \(tops.count)")
} catch {
print(error)
}
}
}
extension KissConsole {
enum CandleValidation: CustomStringConvertible {
case ok
@@ -156,16 +126,28 @@ extension KissConsole {
}
}
static private func validateCandleMinute(_ fileUrl: URL) -> CandleValidation {
// TODO:
return .ok
}
static private func validateCandlePeriod(_ fileUrl: URL) -> CandleValidation {
// TODO:
return .ok
}
static func validateCandle(_ fileUrl: URL) -> CandleValidation {
let fileNameFrag = fileUrl.lastPathComponent.split(separator: ".")
guard fileNameFrag.count == 2 else {
return .invalidFileName
}
guard fileNameFrag[0].prefix(7) == "candle-", fileNameFrag[1] == "csv" else {
let candlePrefix = "candle-"
guard fileNameFrag[0].prefix(candlePrefix.count) == candlePrefix, fileNameFrag[1] == "csv" else {
return .invalidFileName
}
let fileDate = fileNameFrag[0].suffix(fileNameFrag[0].count - 7)
let fileDateFrag = fileNameFrag[0].suffix(fileNameFrag[0].count - candlePrefix.count)
guard let stringCsv = try? String(contentsOfFile: fileUrl.path) else {
return .cannotRead
}
@@ -187,7 +169,7 @@ extension KissConsole {
var curHH = 9, curMM = 0
for candle in candles.reversed() {
if candle.stockBusinessDate != fileDate {
if candle.stockBusinessDate != fileDateFrag {
return .invalidBusinessDate
}
guard let (hh, mm, _) = candle.stockConclusionTime.HHmmss else {
@@ -211,3 +193,15 @@ extension KissConsole {
return .ok
}
}
extension BelongClassCode {
var fileBelong: String {
switch self {
case .averageVolume: return "average-volume"
case .volumeIncreaseRate: return "volume-increase-rate"
case .averageVolumeTurnoverRate: return "average-volume-turnover-rate"
case .transactionValue: return "transaction-value"
case .averageTransactionValueTurnoverRate: return "average-transaction-value-turnover-rate"
}
}
}

View File

@@ -346,6 +346,8 @@ extension KissConsole {
for item in output {
print("\(item.dataRank) \(item.shortProductNo) \(item.htsProductName.maxSpace(20)) \(item.currentStockPrice.maxSpace(10, digitBy: 3)) \(item.averageVolume.maxSpace(15, digitBy: 3)) \(item.accumulatedTradingAmount.maxSpace(25, digitBy: 3))")
}
KissConsole.writeTop(output, fileUrl: topProductsUrl(belongClass))
} catch {
print("\(error)")
}

View File

@@ -34,6 +34,7 @@
341F5F112A1685E700962D48 /* ShopRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F102A1685E700962D48 /* ShopRequest.swift */; };
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */; };
349C26AB2A1EAE2400F3EC91 /* KissProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */; };
34D3680F2A2AA0BE005E6756 /* PropertyIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -75,6 +76,7 @@
341F5F102A1685E700962D48 /* ShopRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopRequest.swift; sourceTree = "<group>"; };
341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticShopProduct.swift; sourceTree = "<group>"; };
349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissProfile.swift; sourceTree = "<group>"; };
34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyIterable.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -184,6 +186,7 @@
341F5F102A1685E700962D48 /* ShopRequest.swift */,
341F5F022A11A2BC00962D48 /* Credential.swift */,
341F5F062A14634F00962D48 /* Foundation+Extensions.swift */,
34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */,
);
path = Common;
sourceTree = "<group>";
@@ -324,6 +327,7 @@
341F5F012A11155100962D48 /* DomesticStockSearchResult.swift in Sources */,
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */,
341F5EF22A0F887200962D48 /* DomesticFutures.swift in Sources */,
34D3680F2A2AA0BE005E6756 /* PropertyIterable.swift in Sources */,
341F5F112A1685E700962D48 /* ShopRequest.swift in Sources */,
341F5EF92A0F907300962D48 /* DomesticStockPriceResult.swift in Sources */,
341F5EE12A0F373B00962D48 /* Login.swift in Sources */,