240 lines
7.4 KiB
Swift
240 lines
7.4 KiB
Swift
//
|
|
// KissConsole+CSV.swift
|
|
// KissMeConsole
|
|
//
|
|
// Created by ened-book-m1 on 2023/05/29.
|
|
//
|
|
|
|
import Foundation
|
|
import KissMe
|
|
|
|
|
|
extension KissConsole {
|
|
|
|
static var shopProductsUrl: URL {
|
|
URL.currentDirectory().appending(path: "data/shop-products.csv")
|
|
}
|
|
|
|
static func topProductsUrl(_ belong: BelongClassCode, date: Date) -> URL {
|
|
let subPath = "data/top30/\(date.yyyyMMdd)"
|
|
let subFile = "\(subPath)/top30-\(belong.fileBelong)-\(date.yyyyMMdd)-\(date.HHmmss).csv"
|
|
|
|
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
|
createSubpath(subPath)
|
|
return fileUrl
|
|
}
|
|
|
|
static var accountStocksUrl: URL {
|
|
URL.currentDirectory().appending(path: "data/account-stocks.csv")
|
|
}
|
|
|
|
static var accountAmountUrl: URL {
|
|
URL.currentDirectory().appending(path: "data/account-amount.csv")
|
|
}
|
|
|
|
static var localNamesUrl: URL {
|
|
URL.currentDirectory().appending(path: "data/localized-names.csv")
|
|
}
|
|
|
|
static var holidayUrl: URL {
|
|
URL.currentDirectory().appending(path: "data/holiday.csv")
|
|
}
|
|
|
|
static func productPriceUrl(productNo: String) -> URL {
|
|
let subPath = "data/\(productNo)/price"
|
|
let subFile = "\(subPath)/prices.csv"
|
|
|
|
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
|
createSubpath(subPath)
|
|
return fileUrl
|
|
}
|
|
|
|
static func investorFileUrl(productNo: String, day: String) -> URL {
|
|
let subPath = "data/\(productNo)/investor"
|
|
let subFile = "\(subPath)/investor-\(day).csv"
|
|
|
|
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
|
createSubpath(subPath)
|
|
return fileUrl
|
|
}
|
|
|
|
static func shortsFileUrl(productNo: String, day: String) -> URL {
|
|
let subPath = "data/\(productNo)/shorts"
|
|
let subFile = "\(subPath)/shorts-\(day).csv"
|
|
|
|
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
|
createSubpath(subPath)
|
|
return fileUrl
|
|
}
|
|
|
|
static 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
|
|
}
|
|
}
|
|
|
|
|
|
extension KissConsole {
|
|
|
|
func loadShop(_ profile: Bool = false) {
|
|
let appTime1 = Date.appTime
|
|
guard let stringCsv = try? String(contentsOfFile: KissConsole.shopProductsUrl.path) else {
|
|
return
|
|
}
|
|
|
|
let appTime2 = Date.appTime
|
|
if profile {
|
|
print("\tloading file \(appTime2 - appTime1) elapsed")
|
|
}
|
|
|
|
var products = [String: [DomesticShop.Product]]()
|
|
let rows = stringCsv.split(separator: "\n")
|
|
|
|
let appTime3 = Date.appTime
|
|
if profile {
|
|
print("\trow split \(appTime3 - appTime2) elapsed")
|
|
}
|
|
|
|
for (i, row) in rows.enumerated() {
|
|
let array = row.split(separator: ",").map { String($0) }
|
|
if i == 0, array[0] == "baseDate" {
|
|
continue
|
|
}
|
|
|
|
let product = try! DomesticShop.Product(array: array)
|
|
if let _ = products[product.itemName] {
|
|
products[product.itemName]!.append(product)
|
|
}
|
|
else {
|
|
products[product.itemName] = [product]
|
|
}
|
|
}
|
|
let appTime4 = Date.appTime
|
|
if profile {
|
|
print("\tparse product \(appTime4 - appTime3) elapsed")
|
|
}
|
|
|
|
setProducts(products)
|
|
let totalCount = products.reduce(0, { $0 + $1.value.count })
|
|
print("load products \(totalCount) with \(products.count) key")
|
|
}
|
|
}
|
|
|
|
|
|
extension KissConsole {
|
|
|
|
enum CandleValidation: CustomStringConvertible {
|
|
case ok
|
|
case invalidFileName
|
|
case cannotRead
|
|
case invalidCsvHeader
|
|
case invalidBusinessDate
|
|
case invalidConclusionTime
|
|
case unimplemetedFunction
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .ok: return "ok"
|
|
case .invalidFileName: return "invalidFileName"
|
|
case .cannotRead: return "cannotRead"
|
|
case .invalidCsvHeader: return "invalidCsvHeader"
|
|
case .invalidBusinessDate: return "invalidBusinessDate"
|
|
case .invalidConclusionTime: return "invalidConclusionTime"
|
|
case .unimplemetedFunction: return "unimplemetedFunction"
|
|
}
|
|
}
|
|
}
|
|
|
|
static func validateCandleDay(_ fileUrl: URL) -> CandleValidation {
|
|
// TODO: validateCandleDay
|
|
return .unimplemetedFunction
|
|
}
|
|
|
|
static func validateCandleWeek(_ fileUrl: URL) -> CandleValidation {
|
|
// TODO: validateCandleWeek
|
|
return .unimplemetedFunction
|
|
}
|
|
|
|
static func validateCandleMinute(_ fileUrl: URL) -> CandleValidation {
|
|
let fileNameFrag = fileUrl.lastPathComponent.split(separator: ".")
|
|
guard fileNameFrag.count == 2 else {
|
|
return .invalidFileName
|
|
}
|
|
|
|
let candlePrefix = "candle-"
|
|
guard fileNameFrag[0].prefix(candlePrefix.count) == candlePrefix, fileNameFrag[1] == "csv" else {
|
|
return .invalidFileName
|
|
}
|
|
|
|
let fileDateFrag = String(fileNameFrag[0].suffix(fileNameFrag[0].count - candlePrefix.count))
|
|
guard let stringCsv = try? String(contentsOfFile: fileUrl.path) else {
|
|
return .cannotRead
|
|
}
|
|
|
|
var candles = [Domestic.Candle]()
|
|
let rows = stringCsv.split(separator: "\n")
|
|
for (i, row) in rows.enumerated() {
|
|
let array = row.split(separator: ",").map { String($0) }
|
|
if i == 0 {
|
|
if array.count != 8 {
|
|
return .invalidCsvHeader
|
|
}
|
|
continue
|
|
}
|
|
|
|
let candle = try! Domestic.Candle(array: array)
|
|
candles.append(candle)
|
|
}
|
|
|
|
var curHH = 9, curMM = 0
|
|
for candle in candles.reversed() {
|
|
if candle.stockBusinessDate != fileDateFrag {
|
|
return .invalidBusinessDate
|
|
}
|
|
guard let (hh, mm, _) = candle.stockConclusionTime.HHmmss else {
|
|
return .invalidConclusionTime
|
|
}
|
|
guard hh == curHH, mm == curMM else {
|
|
return .invalidConclusionTime
|
|
}
|
|
|
|
// Finished to check
|
|
if hh == 18, mm == 0 {
|
|
break
|
|
}
|
|
|
|
curMM += 1
|
|
if curMM >= 60 {
|
|
curMM = 0
|
|
curHH += 1
|
|
}
|
|
}
|
|
return .ok
|
|
}
|
|
}
|
|
|
|
extension BelongClassCode {
|
|
var fileBelong: String {
|
|
switch self {
|
|
case .averageVolume: return "av"
|
|
case .volumeIncreaseRate: return "vir"
|
|
case .averageVolumeTurnoverRate: return "avtr"
|
|
case .transactionValue: return "tv"
|
|
case .averageTransactionValueTurnoverRate: return "atvtr"
|
|
}
|
|
}
|
|
}
|