Add localized names for csv header

This commit is contained in:
2023-06-05 00:58:43 +09:00
parent e91141251d
commit fa70e7b148
12 changed files with 286 additions and 41 deletions

View File

@@ -31,3 +31,8 @@ public extension PropertyIterable {
return result
}
}
public protocol ArrayDecodable {
init(array: [String]) throws
}

View File

@@ -33,8 +33,11 @@ public enum GeneralError: Error {
case invalidAccountNo
case unsupportedQueryAtMockServer
case emptyData
case incorrectArrayItems
case headerNoFiendName(String)
}
public enum QueryError: Error {
case invalidUrl
case invalidJson

View File

@@ -63,8 +63,10 @@ public enum PeriodDivision: String, Codable, CustomStringConvertible {
///
public enum ExDivision: String, Codable {
case none = ""
/// ( )
case none = "00"
case notApplicable = "00"
///
case exRights = "01"
///
@@ -160,7 +162,7 @@ public struct CurrentPriceResult: Codable {
/// ELW
public let elwPublished: YesNo
///
/// ( )
public let currentStockPrice: String
///
@@ -182,7 +184,7 @@ public struct CurrentPriceResult: Codable {
public let previousDayDiffVolumeRatio: String
///
public let stockPrice: String
public let stockOpenningPrice: String
///
public let highestStockPrice: String
@@ -389,7 +391,7 @@ public struct CurrentPriceResult: Codable {
case accumulatedVolume = "acml_vol"
case previousDayDiffVolumeRatio = "prdy_vrss_vol_rate"
case stockPrice = "stck_oprc"
case stockOpenningPrice = "stck_oprc"
case highestStockPrice = "stck_hgpr"
case lowestStockPrice = "stck_lwpr"
case maximumStockPrice = "stck_mxpr"
@@ -526,11 +528,11 @@ public struct MinutePriceResult: Codable {
///
public let accumulatedTradingAmount: String
///
/// ()
public let currentStockPrice: String
/// 2
public let secondStockPrice: String
///
public let stockOpenningPrice: String
///
public let highestStockPrice: String
@@ -546,7 +548,7 @@ public struct MinutePriceResult: Codable {
case stockConclusionTime = "stck_cntg_hour"
case accumulatedTradingAmount = "acml_tr_pbmn"
case currentStockPrice = "stck_prpr"
case secondStockPrice = "stck_oprc"
case stockOpenningPrice = "stck_oprc"
case highestStockPrice = "stck_hgpr"
case lowestStockPrice = "stck_lwpr"
case conclusionVolume = "cntg_vol"
@@ -556,16 +558,28 @@ public struct MinutePriceResult: Codable {
return stockBusinessDate + stockConclusionTime
}
public init(array: [String]) {
public init(array: [String]) throws {
guard array.count == 8 else {
throw GeneralError.incorrectArrayItems
}
self.stockBusinessDate = array[0]
self.stockConclusionTime = array[1]
self.accumulatedTradingAmount = array[2]
self.currentStockPrice = array[3]
self.secondStockPrice = array[4]
self.stockOpenningPrice = array[4]
self.highestStockPrice = array[5]
self.lowestStockPrice = array[6]
self.conclusionVolume = array[7]
}
public static func symbols() -> [String] {
let i = try! OutputPrice(array: Array(repeating: "", count: 8))
return Mirror(reflecting: i).children.compactMap { $0.label }
}
public static func localizedSymbols() -> [String: String] {
[:]
}
}
}
@@ -623,7 +637,7 @@ public struct PeriodPriceResult: Codable {
public let minimumStockPrice: String
///
public let stockPrice: String
public let stockOpenningPrice: String
///
public let highestStockPrice: String
@@ -690,7 +704,7 @@ public struct PeriodPriceResult: Codable {
case previousDayVolume = "prdy_vol"
case maximumStockPrice = "stck_mxpr"
case minimumStockPrice = "stck_llam"
case stockPrice = "stck_oprc"
case stockOpenningPrice = "stck_oprc"
case highestStockPrice = "stck_hgpr"
case lowestStockPrice = "stck_lwpr"
case yesterdayStockPrice = "stck_prdy_oprc"
@@ -845,7 +859,10 @@ public struct PeriodPriceResult: Codable {
case revaluationIssueReason = "revl_issu_reas"
}
public init(array: [String]) {
public init(array: [String]) throws {
guard array.count == 13 else {
throw GeneralError.incorrectArrayItems
}
self.stockBusinessDate = array[0]
self.stockClosingPrice = array[1]
self.stockOpenningPrice = array[2]
@@ -861,6 +878,15 @@ public struct PeriodPriceResult: Codable {
self.revaluationIssueReason = array[12]
}
public static func symbols() -> [String] {
let i = try! OutputPrice(array: Array(repeating: "", count: 13))
return Mirror(reflecting: i).children.compactMap { $0.label }
}
public static func localizedSymbols() -> [String: String] {
[:]
}
init(stockBusinessDate: String, stockClosingPrice: String, stockOpenningPrice: String, highestStockPrice: String, lowestStockPrice: String, accumulatedVolume: String, accumulatedTradingAmount: String, exDivision: ExDivision, partitionRate: String, partitionModifiable: YesNo, previousDayVariableRatioSign: String, previousDayVariableRatio: String, revaluationIssueReason: String) {
self.stockBusinessDate = stockBusinessDate
self.stockClosingPrice = stockClosingPrice

View File

@@ -194,6 +194,47 @@ public struct BalanceResult: Codable {
case substitutePrice = "sbst_pric"
case stockLoanPrice = "stck_loan_unpr"
}
init(array: [String]) throws {
guard array.count == 26 else {
throw GeneralError.incorrectArrayItems
}
self.productNo = array[0]
self.productName = array[1]
self.tradeDivisionCode = array[2]
self.previousDayBuyQuantity = array[3]
self.previousDaySellQuantity = array[4]
self.todayBuyQuantity = array[5]
self.todaySellQuantity = array[6]
self.holdingQuantity = array[7]
self.orderPossibleQuantity = array[8]
self.averagePurchasePrice = array[9]
self.purchaseAmount = array[10]
self.currentPrice = array[11]
self.evaluationAmount = array[12]
self.evaluationProfitLossAmount = array[13]
self.evaluationProfitLossRate = array[14]
self.evaluationEarningRate = array[15]
self.loanDate = array[16]
self.loanAmount = array[17]
self.shortSellingAmount = array[18]
self.expiredDate = array[19]
self.fluctuationRate = array[20]
self.netChangeFluctuation = array[21]
self.marginRequirementRatioName = array[22]
self.depositRateName = array[23]
self.substitutePrice = array[24]
self.stockLoanPrice = array[25]
}
public static func symbols() -> [String] {
let i = try! OutputStock(array: Array(repeating: "", count: 26))
return Mirror(reflecting: i).children.compactMap { $0.label }
}
public static func localizedSymbols() -> [String: String] {
[:]
}
}
public struct OutputAmount: Codable, PropertyIterable {
@@ -295,6 +336,45 @@ public struct BalanceResult: Codable {
case assetFluctuationAmount = "asst_icdc_amt"
case assetFluctuationRate = "asst_icdc_erng_rt"
}
init(array: [String]) throws {
guard array.count == 24 else {
throw GeneralError.incorrectArrayItems
}
self.depositTotalAmount = array[0]
self.nextDayCalcAmount = array[1]
self.nextTwoDayCalcAmount = array[2]
self.cmaEvaluationAmount = array[3]
self.previousDayBuyAmount = array[4]
self.todayBuyAmount = array[5]
self.nextDayAutoRedemptionAmount = array[6]
self.previousDaySellAmount = array[7]
self.todaySellAmount = array[8]
self.nextTwoDayAutoRedemptionAmount = array[9]
self.previousDayExpensesAmount = array[10]
self.todayExpensesAmount = array[11]
self.totalLoanAmount = array[12]
self.securitiesEvaluationAmount = array[13]
self.totalEvaluationAmount = array[14]
self.netAssetAmount = array[15]
self.loanAutoRedemptionAllowable = YesNo(rawValue: array[16])!
self.purchaseAmountSum = array[17]
self.evaluationAmountSum = array[18]
self.evaluationProfitLossAmountSum = array[19]
self.shortSellingAmountSum = array[20]
self.previousDayAssetEvalutionSum = array[21]
self.assetFluctuationAmount = array[22]
self.assetFluctuationRate = array[23]
}
public static func symbols() -> [String] {
let i = try! OutputAmount(array: Array(repeating: "", count: 24))
return Mirror(reflecting: i).children.compactMap { $0.label }
}
public static func localizedSymbols() -> [String: String] {
[:]
}
}
}

View File

@@ -102,6 +102,40 @@ public struct VolumeRankResult: Codable {
case nDayTradingAmountTurnoverRate = "nday_tr_pbmn_tnrt"
case accumulatedTradingAmount = "acml_tr_pbmn"
}
init(array: [String]) throws {
guard array.count == 19 else {
throw GeneralError.incorrectArrayItems
}
self.htsProductName = array[0]
self.shortProductNo = array[1]
self.dataRank = array[2]
self.currentStockPrice = array[3]
self.previousDayVariableRatioSign = array[4]
self.previousDayVariableRatio = array[5]
self.previousDayDiffRatio = array[6]
self.accumulatedVolume = array[7]
self.previousDayVolume = array[8]
self.listedStockCount = array[9]
self.averageVolume = array[10]
self.nDayBeforeClosingPriceDiffRatio = array[11]
self.volumeIncreaseRate = array[12]
self.volumeTurnoverRate = array[13]
self.nDayVolumeTurnoverRate = array[14]
self.averageTradingAmount = array[15]
self.tradingAmountTurnoverRate = array[16]
self.nDayTradingAmountTurnoverRate = array[17]
self.accumulatedTradingAmount = array[18]
}
public static func symbols() -> [String] {
let i = try! OutputDetail(array: Array(repeating: "", count: 19))
return Mirror(reflecting: i).children.compactMap { $0.label }
}
public static func localizedSymbols() -> [String: String] {
[:]
}
}
}

View File

@@ -107,14 +107,14 @@ extension DomesticShop {
///
public let marketCategory: String
/// ()
///
public let itemName: String
///
public let corporationNo: String
private enum CodingKeys: String, CodingKey {
private enum CodingKeys: String, CodingKey, CaseIterable {
case baseDate = "basDt"
case shortCode = "srtnCd"
case isinCode = "isinCd"
@@ -123,7 +123,10 @@ extension DomesticShop {
case corporationNo = "crno"
}
public init(array: [String]) {
public init(array: [String]) throws {
guard array.count == 6 else {
throw GeneralError.incorrectArrayItems
}
self.baseDate = array[0]
/// shortCode A000000 A .
@@ -135,6 +138,15 @@ extension DomesticShop {
self.itemName = array[4]
self.corporationNo = array[5]
}
public static func symbols() -> [String] {
let i = try! Item(array: Array(repeating: "", count: 6))
return Mirror(reflecting: i).children.compactMap { $0.label }
}
public static func localizedSymbols() -> [String: String] {
[:]
}
}
}

View File

@@ -109,4 +109,36 @@ extension Array where Element: PropertyIterable {
}
try stringCsv.write(toFile: toFile.path, atomically: true, encoding: .utf8)
}
static func readCsv(fromFile: URL, verifyHeader: Bool = true) throws -> [Element] where Element: ArrayDecodable {
let stringCsv = try String(contentsOfFile: fromFile.path, encoding: .utf8)
let items = stringCsv.split(separator: "\n")
guard items.count > 0 else {
return []
}
var headerItems = [String]()
var elements = [Element]()
for (index, item) in items.enumerated() {
if index == 0 {
headerItems = item.split(separator: ",").map { String($0) }
continue
}
let array = item.split(separator: ",").map { String($0) }
let element = try Element(array: array)
if index == 1, verifyHeader {
// Validate property with header
let properties = try element.allProperties()
for (label, _) in properties {
if false == headerItems.contains(where: { $0 == label }) {
throw GeneralError.headerNoFiendName(label)
}
}
}
elements.append(element)
}
return elements
}
}

View File

@@ -26,6 +26,28 @@ extension KissConsole {
var accountAmountUrl: URL {
URL.currentDirectory().appending(path: "data/account-amount.csv")
}
var localNamesUrl: URL {
URL.currentDirectory().appending(path: "data/localized-names.csv")
}
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
}
}
@@ -56,7 +78,7 @@ extension KissConsole {
continue
}
let product = DomesticShop.Product(array: array)
let product = try! DomesticShop.Product(array: array)
if var value = products[product.itemName] {
value.append(product)
products.updateValue(value, forKey: product.itemName)
@@ -135,7 +157,7 @@ extension KissConsole {
continue
}
let candle = Domestic.Candle(array: array)
let candle = try! Domestic.Candle(array: array)
candles.append(candle)
}
@@ -177,3 +199,16 @@ extension BelongClassCode {
}
}
}
struct LocalName: Codable, PropertyIterable, ArrayDecodable {
let fieldName: String
let localizedName: String
init(array: [String]) throws {
guard array.count == 2 else {
throw GeneralError.incorrectArrayItems
}
fieldName = array[0]
localizedName = array[1]
}
}

View File

@@ -25,25 +25,6 @@ extension KissConsole {
}
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
}
func getRecentCandle(productNo: String, period: PeriodDivision, count: Int) async -> Bool {
do {
guard currentCandleShortCode == nil else {

View File

@@ -58,6 +58,10 @@ class KissConsole {
case love = "love" // love nuts.1 ISCD
case hate = "hate" // hate nuts.1 ISCD
//
case localizeNames = "localize names"
var needLogin: Bool {
switch self {
case .quit, .loginMock, .loginReal:
@@ -72,7 +76,7 @@ class KissConsole {
return false
case .showcase:
return false
case .loves, .love, .hate:
case .loves, .love, .hate, .localizeNames:
return false
}
}
@@ -193,6 +197,7 @@ class KissConsole {
case .loves: await onLoves()
case .love: await onLove(args)
case .hate: await onHate(args)
case .localizeNames: await onLocalizeNames()
default:
print("Unknown command: \(line)")
@@ -547,7 +552,7 @@ extension KissConsole {
print("\t누적 거래 대금: ", output.accumulatedTradingAmount)
print("\t누적 거래량: ", output.accumulatedVolume)
print("\t전일 대비 거래량 비율: ", output.previousDayDiffVolumeRatio)
print("\t주식 시가: ", output.stockPrice)
print("\t주식 시가: ", output.stockOpenningPrice)
print("\t주식 최고가: ", output.highestStockPrice)
print("\t주식 최저가: ", output.lowestStockPrice)
print("\t외국인 순매수 수량: ", output.foreignNetBuyingQuantity)
@@ -846,6 +851,37 @@ extension KissConsole {
print("Success \(love.name)")
}
}
private func onLocalizeNames() async {
var symbols = Set<String>()
symbols.formUnion(DomesticShop.ProductResponse.Item.symbols())
symbols.formUnion(BalanceResult.OutputStock.symbols())
symbols.formUnion(BalanceResult.OutputAmount.symbols())
symbols.formUnion(MinutePriceResult.OutputPrice.symbols())
symbols.formUnion(PeriodPriceResult.OutputPrice.symbols())
symbols.formUnion(VolumeRankResult.OutputDetail.symbols())
let newNames = symbols.sorted(by: { $0 < $1 })
var added = 0
var curNames = try! [LocalName].readCsv(fromFile: localNamesUrl, verifyHeader: true)
for name in newNames {
if false == curNames.contains(where: { $0.fieldName == name }) {
let item = try! LocalName(array: [name, ""])
curNames.append(item)
added += 1
}
}
if added > 0 {
do {
try curNames.writeCsv(toFile: localNamesUrl)
} catch {
print(error)
}
}
print("Success \(localNamesUrl.lastPathComponent) total: \(curNames.count), new: \(added)")
}
}

View File

@@ -32,12 +32,13 @@ WIP `modify (PNO) (ONO) (가격) (수량)` | 주문 내역을 변경. (수량)
`candle week [PNO]` | 종목의 최근 52주 동안의 주봉 열람. PNO 은 생략 가능. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
`candle week all` | 모든 종목의 최근 52주 동안의 주봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
`load shop` | data/shop-products.csv 로부터 전체 상품을 로딩.
`update shop` | **금융위원회_KRX상장종목정보** 로부터 전체 상품을 얻어서 data/shop-products.csv 로 저장.
`update shop` | **금융위원회_KRX상장종목정보** 로부터 전체 상품을 얻어서 **data/shop-products.csv** 로 저장.
`look (상품명)` | (상품명) 에 해당되는 PNO 를 표시함.
WIP `showcase` | 추천 상품을 제안함.
`loves` | 관심 종목 전체를 열람. profile.json 에 저장된 관심 종목을 표시함.
`love (탭).(번호) (PNO)` | 관심 종목에 추가함. (번호) 를 지정하지 않으면 (탭) 마지막에 추가함.
`hate (탭) (PNO)` | 관심 종목에서 삭제함.
`localize names` | csv field name 에 대해서 한글명을 제공하는 **data/local-names.csv** 를 저장.
* PNO 는 `Product NO` 의 약자이고, 상품의 `단축코드` (shortCode) 와 동일합니다.
* ONO 는 `Order NO` 의 약자이고, 고유한 주문번호 입니다.