Write csv pretty method
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
33
KissMe/Sources/Common/PropertyIterable.swift
Normal file
33
KissMe/Sources/Common/PropertyIterable.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import Foundation
|
||||
public struct Domestic {
|
||||
public typealias Candle = MinutePriceResult.OutputPrice
|
||||
public typealias CandlePeriod = PeriodPriceResult.OutputPrice
|
||||
public typealias Top = VolumeRankResult.OutputDetail
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -94,7 +94,7 @@ extension DomesticShop {
|
||||
public let item: [Item]
|
||||
}
|
||||
|
||||
public struct Item: Codable {
|
||||
public struct Item: Codable, PropertyIterable {
|
||||
/// 기준일자
|
||||
public let baseDate: String
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
|
||||
2
bin/data
2
bin/data
Submodule bin/data updated: 3605948d54...feebeed12f
@@ -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 */,
|
||||
|
||||
Reference in New Issue
Block a user