Validate comma before writing csv file

This commit is contained in:
2023-06-22 12:23:19 +09:00
parent 31eeb8e75b
commit 18ae62a540
21 changed files with 406 additions and 181 deletions

View File

@@ -16,6 +16,10 @@ extension Date {
return dateFormatter.string(from: self)
}
public var yyyyMM01: String {
yyyyMM + "01"
}
public var yyyyMMdd: String {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "KST")
@@ -165,8 +169,22 @@ extension FileManager {
public func valueToString(_ any: Any) -> String {
func validateComma(_ s: String) -> String {
let comma: CharacterSet = [","]
if s.rangeOfCharacter(from: comma) != nil {
assertionFailure("There are comma in: \(s)")
}
return s
}
switch any {
case let s as String: return s
case let s as String:
#if DEBUG
return validateComma(s)
#else
return s
#endif
case let i as Int8: return String(i)
case let i as UInt8: return String(i)
case let i as Int16: return String(i)
@@ -184,7 +202,12 @@ public func valueToString(_ any: Any) -> String {
case let d as Double: return String(d)
case let raw as any RawRepresentable:
switch raw.rawValue {
case let s as String: return s
case let s as String:
#if DEBUG
return validateComma(s)
#else
return s
#endif
case let i as Int8: return String(i)
case let i as UInt8: return String(i)
case let i as Int16: return String(i)
@@ -198,7 +221,8 @@ public func valueToString(_ any: Any) -> String {
default:
return ""
}
case let c as CustomStringConvertible: return c.description
case let c as CustomStringConvertible:
return validateComma(c.description)
default:
return ""
}
@@ -279,11 +303,13 @@ extension Array where Element: PropertyIterable {
for (index, item) in items.enumerated() {
if index == 0 {
headerItems = item.split(separator: ",").map { String($0) }
headerItems = item.split(separator: ",", omittingEmptySubsequences: false).map { String($0) }
continue
}
let array = item.split(separator: ",").map { String($0) }
let element = try Element(array: array)
let array = item.split(separator: ",", omittingEmptySubsequences: false).map { String($0) }
//print("index: \(index), \(fromFile.path)")
let element = try Element(array: array, source: item)
if index == 1, verifyHeader {
// Validate property with header
@@ -342,7 +368,7 @@ extension String {
public static func readCsvHeader(fromFile: URL) throws -> [String] {
let header = try String(firstLineOfFile: fromFile.path)
return header.split(separator: ",").map { String($0) }
return header.split(separator: ",", omittingEmptySubsequences: false).map { String($0) }
}
public func writeAppending(toFile path: String) throws {

View File

@@ -34,5 +34,5 @@ public extension PropertyIterable {
public protocol ArrayDecodable {
init(array: [String]) throws
init(array: [String], source: String.SubSequence) throws
}

View File

@@ -37,7 +37,7 @@ public enum GeneralError: Error {
case cannotWriteFile
case cannotReadFileLine
case cannotReadFileToConvertString
case incorrectArrayItems
case incorrectArrayItems(String, Int, Int)
case headerNoFiendName(String)
case noCsvFile
case invalidCandleCsvFile(String)

View File

@@ -12,9 +12,9 @@ public struct LocalName: Codable, PropertyIterable, ArrayDecodable {
public let fieldName: String
public let localizedName: String
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 2 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 2)
}
fieldName = array[0]
localizedName = array[1]

View File

@@ -0,0 +1,149 @@
//
// ShopContext.swift
// KissMe
//
// Created by ened-book-m1 on 2023/06/21.
//
import Foundation
open class ShopContext {
private var productsLock = NSLock()
///
private var products = [String: [DomesticShop.Product]]()
public var productsCount: Int {
productsLock.lock()
defer {
productsLock.unlock()
}
return products.count
}
/// productNo
private var shortCodeLock = NSLock()
private var _currentShortCode: String?
public var currentShortCode: String? {
get {
shortCodeLock.lock()
defer {
shortCodeLock.unlock()
}
return _currentShortCode
}
set {
shortCodeLock.lock()
defer {
shortCodeLock.unlock()
}
_currentShortCode = newValue
}
}
public init() {
}
}
extension ShopContext {
public func loadShop(url: URL, profile: Bool = false) {
let appTime1 = Date.appTime
guard let stringCsv = try? String(contentsOfFile: url.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: ",", omittingEmptySubsequences: false).map { String($0) }
if i == 0, array[0] == "baseDate" {
continue
}
let product = try! DomesticShop.Product(array: array, source: "")
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")
}
private func setProducts(_ products: [String: [DomesticShop.Product]]) {
productsLock.lock()
self.products = products
productsLock.unlock()
}
public func getProducts(similarName: String) -> [String: [DomesticShop.Product]]? {
productsLock.lock()
defer {
productsLock.unlock()
}
return products.filter { $0.key.contains(similarName) }
//return products.filter { $0.key.decomposedStringWithCanonicalMapping.contains(similarName) }
}
public func getProduct(isin: String) -> DomesticShop.Product? {
productsLock.lock()
defer {
productsLock.unlock()
}
return products.compactMap { $0.value.first(where: { $0.isinCode == isin }) }.first
}
public func getProduct(shortCode: String) -> DomesticShop.Product? {
productsLock.lock()
defer {
productsLock.unlock()
}
return products.compactMap { $0.value.first(where: { $0.shortCode == shortCode }) }.first
}
public func getAllProducts() -> [DomesticShop.Product] {
productsLock.lock()
defer {
productsLock.unlock()
}
var all = [DomesticShop.Product]()
for items in products.values {
all.append(contentsOf: items)
}
return all
}
public func setCurrent(productNo: String) {
productsLock.lock()
currentShortCode = productNo
productsLock.unlock()
let productName = getProduct(shortCode: productNo)?.itemName ?? ""
print("current product \(productNo) \(productName)")
}
}

View File

@@ -470,9 +470,9 @@ public struct CurrentPriceResult: Codable {
case shortOverheated = "short_over_yn"
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 80 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 80)
}
self.itemStateCode = StateClass(rawValue: array[0])!
self.marginalRate = array[1]
@@ -557,7 +557,7 @@ public struct CurrentPriceResult: Codable {
}
public static func symbols() -> [String] {
let i = try! OutputDetail(array: Array(repeating: "", count: 80))
let i = try! OutputDetail(array: Array(repeating: "", count: 80), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}
@@ -894,7 +894,7 @@ public struct CapturePrice: Codable, PropertyIterable, ArrayDecodable {
self.totalOutstandingloanRate = p.totalOutstandingloanRate
self.shortSellingAllowable = p.shortSellingAllowable
self.shortProductCode = p.shortProductCode
self.facePriceCurrency = p.facePriceCurrency
self.facePriceCurrency = p.facePriceCurrency.trimmingCharacters(in: CharacterSet([","]))
self.capitalCurrency = p.capitalCurrency
self.approachRate = p.approachRate
self.foreignHoldQuantity = p.foreignHoldQuantity
@@ -906,9 +906,9 @@ public struct CapturePrice: Codable, PropertyIterable, ArrayDecodable {
self.shortOverheated = p.shortOverheated
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 82 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 82)
}
self.stockBusinessDate = array[0]
self.captureTime = array[1]
@@ -995,7 +995,7 @@ public struct CapturePrice: Codable, PropertyIterable, ArrayDecodable {
}
public static func symbols() -> [String] {
let i = try! CapturePrice(array: Array(repeating: "", count: 82))
let i = try! CapturePrice(array: Array(repeating: "", count: 82), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}
@@ -1097,9 +1097,9 @@ public struct MinutePriceResult: Codable {
return stockBusinessDate + stockConclusionTime
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 8 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 8)
}
self.stockBusinessDate = array[0]
self.stockConclusionTime = array[1]
@@ -1112,7 +1112,7 @@ public struct MinutePriceResult: Codable {
}
public static func symbols() -> [String] {
let i = try! OutputPrice(array: Array(repeating: "", count: 8))
let i = try! OutputPrice(array: Array(repeating: "", count: 8), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}
@@ -1398,9 +1398,9 @@ public struct PeriodPriceResult: Codable {
case revaluationIssueReason = "revl_issu_reas"
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 13 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 13)
}
self.stockBusinessDate = array[0]
self.stockClosingPrice = array[1]
@@ -1418,7 +1418,7 @@ public struct PeriodPriceResult: Codable {
}
public static func symbols() -> [String] {
let i = try! OutputPrice(array: Array(repeating: "", count: 13))
let i = try! OutputPrice(array: Array(repeating: "", count: 13), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}

View File

@@ -195,9 +195,9 @@ public struct BalanceResult: Codable {
case stockLoanPrice = "stck_loan_unpr"
}
init(array: [String]) throws {
init(array: [String], source: String.SubSequence) throws {
guard array.count == 26 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 26)
}
self.productNo = array[0]
self.productName = array[1]
@@ -228,7 +228,7 @@ public struct BalanceResult: Codable {
}
public static func symbols() -> [String] {
let i = try! OutputStock(array: Array(repeating: "", count: 26))
let i = try! OutputStock(array: Array(repeating: "", count: 26), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}
@@ -337,9 +337,9 @@ public struct BalanceResult: Codable {
case assetFluctuationRate = "asst_icdc_erng_rt"
}
init(array: [String]) throws {
init(array: [String], source: String.SubSequence) throws {
guard array.count == 24 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 24)
}
self.depositTotalAmount = array[0]
self.nextDayCalcAmount = array[1]
@@ -368,7 +368,7 @@ public struct BalanceResult: Codable {
}
public static func symbols() -> [String] {
let i = try! OutputAmount(array: Array(repeating: "", count: 24))
let i = try! OutputAmount(array: Array(repeating: "", count: 24), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}

View File

@@ -103,9 +103,9 @@ public struct VolumeRankResult: Codable {
case accumulatedTradingAmount = "acml_tr_pbmn"
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 19 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 19)
}
self.htsProductName = array[0]
self.shortProductNo = array[1]
@@ -129,7 +129,7 @@ public struct VolumeRankResult: Codable {
}
public static func symbols() -> [String] {
let i = try! OutputDetail(array: Array(repeating: "", count: 19))
let i = try! OutputDetail(array: Array(repeating: "", count: 19), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}
@@ -188,9 +188,9 @@ public struct HolidyResult: Codable {
case settlementDay = "sttl_day_yn"
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 6 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 6)
}
self.baseDate = array[0]
self.weekday = WeekdayDivision(rawValue: array[1])!
@@ -201,7 +201,7 @@ public struct HolidyResult: Codable {
}
public static func symbols() -> [String] {
let i = try! OutputDetail(array: Array(repeating: "", count: 22))
let i = try! OutputDetail(array: Array(repeating: "", count: 22), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}
@@ -327,9 +327,9 @@ public struct InvestorVolumeResult: Codable {
case organizationSellingTradingAmount = "orgn_seln_tr_pbmn"
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 22 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 22)
}
self.stockBusinessDate = array[0]
self.stockClosingPrice = array[1]
@@ -356,7 +356,7 @@ public struct InvestorVolumeResult: Codable {
}
public static func symbols() -> [String] {
let i = try! OutputDetail(array: Array(repeating: "", count: 22))
let i = try! OutputDetail(array: Array(repeating: "", count: 22), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}
@@ -488,9 +488,9 @@ public struct ForeignOrganizationVolumeResult: Codable {
case etcCorporationNetBuyingTradingAmount = "etc_corp_ntby_tr_pbmn"
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 26 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 26)
}
self.htsProductName = array[0]
self.shortProductNo = array[1]

View File

@@ -117,9 +117,9 @@ extension DomesticShop {
case corporationNo = "crno"
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 6 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 6)
}
self.baseDate = array[0]
@@ -134,7 +134,7 @@ extension DomesticShop {
}
public static func symbols() -> [String] {
let i = try! Item(array: Array(repeating: "", count: 6))
let i = try! Item(array: Array(repeating: "", count: 6), source: #function)
return Mirror(reflecting: i).children.compactMap { $0.label }
}

View File

@@ -155,9 +155,9 @@ extension DomesticExtra {
self.shortSellingBalanceRatio = try container.decode(String.self, forKey: DomesticExtra.ShortSellingBalanceResult.OutBlock.CodingKeys.shortSellingBalanceRatio)
}
public init(array: [String]) throws {
public init(array: [String], source: String.SubSequence) throws {
guard array.count == 6 else {
throw GeneralError.incorrectArrayItems
throw GeneralError.incorrectArrayItems(String(source), array.count, 6)
}
self.stockBusinessDate = array[0]
self.shortSellingBalanceQuantity = array[1]

View File

@@ -87,53 +87,6 @@ extension KissConsole {
}
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 {
@@ -187,7 +140,7 @@ extension KissConsole {
var candles = [Domestic.Candle]()
let rows = stringCsv.split(separator: "\n")
for (i, row) in rows.enumerated() {
let array = row.split(separator: ",").map { String($0) }
let array = row.split(separator: ",", omittingEmptySubsequences: false).map { String($0) }
if i == 0 {
if array.count != 8 {
return .invalidCsvHeader
@@ -195,7 +148,7 @@ extension KissConsole {
continue
}
let candle = try! Domestic.Candle(array: array)
let candle = try! Domestic.Candle(array: array, source: row)
candles.append(candle)
}

View File

@@ -71,7 +71,7 @@ extension KissConsole {
/// -29 , csv .
while startDate < backDate {
let day = backDate.yyyyMM + "01"
let day = backDate.yyyyMM01
let fileUrl = KissConsole.shortsFileUrl(productNo: productNo, day: day)
guard let _ = fileUrl.isFileExists else {
backDate = backDate.addingTimeInterval(-29 * SecondsForOneDay)

View File

@@ -9,18 +9,11 @@ import Foundation
import KissMe
class KissConsole {
class KissConsole: KissMe.ShopContext {
private var credential: Credential? = nil
var account: KissAccount? = nil
private var shop: KissShop? = nil
private var productsLock = NSLock()
///
private var products = [String: [DomesticShop.Product]]()
/// productNo
private var currentShortCode: String?
/// candle productNo
var currentCandleShortCode: String?
@@ -118,12 +111,14 @@ class KissConsole {
account != nil
}
init() {
override init() {
let jsonUrl = URL.currentDirectory().appending(path: "shop-server.json")
shop = try? KissShop(jsonUrl: jsonUrl)
KissConsole.createSubpath("log")
KissConsole.createSubpath("data")
super.init()
lastLogin()
loadLocalName()
@@ -258,52 +253,6 @@ extension KissConsole {
try? FileManager.default.createDirectory(at: subPath, withIntermediateDirectories: true)
}
func setProducts(_ products: [String: [DomesticShop.Product]]) {
productsLock.lock()
self.products = products
productsLock.unlock()
}
private func getProducts(similarName: String) -> [String: [DomesticShop.Product]]? {
productsLock.lock()
defer {
productsLock.unlock()
}
return products.filter { $0.key.contains(similarName) }
//return products.filter { $0.key.decomposedStringWithCanonicalMapping.contains(similarName) }
}
private func getProduct(isin: String) -> DomesticShop.Product? {
productsLock.lock()
defer {
productsLock.unlock()
}
return products.compactMap { $0.value.first(where: { $0.isinCode == isin }) }.first
}
func getProduct(shortCode: String) -> DomesticShop.Product? {
productsLock.lock()
defer {
productsLock.unlock()
}
return products.compactMap { $0.value.first(where: { $0.shortCode == shortCode }) }.first
}
private func getAllProducts() -> [DomesticShop.Product] {
productsLock.lock()
defer {
productsLock.unlock()
}
var all = [DomesticShop.Product]()
for items in products.values {
all.append(contentsOf: items)
}
return all
}
private func lastLogin() {
let profile = KissProfile()
guard let isMock = profile.isMock else {
@@ -332,15 +281,6 @@ extension KissConsole {
private func loadLocalName() {
LocalContext.shared.load(KissConsole.localNamesUrl)
}
func setCurrent(productNo: String) {
productsLock.lock()
currentShortCode = productNo
productsLock.unlock()
let productName = getProduct(shortCode: productNo)?.itemName ?? ""
print("current product \(productNo) \(productName)")
}
}
@@ -695,8 +635,9 @@ extension KissConsole {
private func onCandleDay(_ args: [String]) {
if args.count == 1, args[0] == "all" {
onCandleDayAll()
if args.count >= 1, args[0] == "all" {
let otherArgs = args[1...].map { String($0) }
onCandleDayAll(otherArgs)
return
}
@@ -716,7 +657,9 @@ extension KissConsole {
}
private func onCandleDayAll() {
private func onCandleDayAll(_ args: [String]) {
let resume: Bool = (args.count == 1 && args[0] == "resume")
let all = getAllProducts()
for item in all {
let semaphore = DispatchSemaphore(value: 0)
@@ -743,8 +686,9 @@ extension KissConsole {
private func onCandleWeek(_ args: [String]) {
if args.count == 1, args[0] == "all" {
onCandleWeekAll()
if args.count >= 1, args[0] == "all" {
let otherArgs = args[1...].map { String($0) }
onCandleWeekAll(otherArgs)
return
}
@@ -764,7 +708,9 @@ extension KissConsole {
}
private func onCandleWeekAll() {
private func onCandleWeekAll(_ args: [String]) {
let resume: Bool = (args.count == 1 && args[0] == "resume")
let all = getAllProducts()
for item in all {
let semaphore = DispatchSemaphore(value: 0)
@@ -900,7 +846,7 @@ extension KissConsole {
private func onLoadShop() async {
return await withUnsafeContinuation { continuation in
self.loadShop()
self.loadShop(url: KissConsole.shopProductsUrl)
continuation.resume()
}
}
@@ -1114,7 +1060,7 @@ extension KissConsole {
var curNames = try! [LocalName].readCsv(fromFile: nameUrl, verifyHeader: true)
for name in newNames {
if false == curNames.contains(where: { $0.fieldName == name }) {
let item = try! LocalName(array: [name, ""])
let item = try! LocalName(array: [name, ""], source: "")
curNames.append(item)
addedNameCount += 1
}

View File

@@ -8,3 +8,17 @@
import Foundation
KissConsole().run()
// 1,000 . (...)
// , prices.csv comma .
/*
import KissMe
let path = URL(filePath: "/Users/ened/Kiss/KissMe/bin/data/065350/price/prices.csv")
let data = try [CapturePrice].readCsv(fromFile: path, verifyHeader: true)
if let last = data.last {
print(last)
}
*/

View File

@@ -6,12 +6,131 @@
//
import Foundation
import KissMe
extension KissIndex {
func indexSet_0002(date: Date, config: String?, kmi: KissIndexType) {
// TODO: work
if productsCount == 0 {
loadShop(url: KissIndex.shopProductsUrl)
}
let semaphore = DispatchSemaphore(value: 0)
Task {
// var scoreMap = [String: Int]()
do {
let shorts = try await collectShorts(date: date)
print(shorts.count)
let prices = try await collectPrices(date: date)
print(prices.count)
} catch {
print(error)
}
semaphore.signal()
}
semaphore.wait()
}
private func collectShorts(date: Date) async throws -> [DomesticExtra.Shorts] {
let shorts = try await withThrowingTaskGroup(of: DomesticExtra.Shorts?.self, returning: [DomesticExtra.Shorts].self) { taskGroup in
let all = getAllProducts()
let yyyyMMdd = date.yyyyMMdd
for item in all {
taskGroup.addTask {
let shortsUrl = KissIndex.pickNearShortsUrl(productNo: item.shortCode, date: date)
let shorts = try [DomesticExtra.Shorts].readCsv(fromFile: shortsUrl)
let targetShorts = shorts.filter { $0.stockBusinessDate == yyyyMMdd }
/// (1%)
if let aShorts = targetShorts.first, let ratio = Double(aShorts.shortSellingBalanceRatio), ratio >= 0.01 {
return aShorts
}
return nil
}
}
var taskResult = [DomesticExtra.Shorts]()
for try await result in taskGroup.compactMap( { $0 }) {
taskResult.append(result)
}
return taskResult
}
return shorts
}
private func collectPrices(date: Date) async throws -> [CapturePrice] {
let prices = try await withThrowingTaskGroup(of: CapturePrice?.self, returning: [CapturePrice].self) { taskGroup in
let all = getAllProducts()
let yyyyMMdd = date.yyyyMMdd
let dateHHmmss = date.HHmmss
for item in all {
taskGroup.addTask {
let pricesUrl = KissIndex.pickNearPricesUrl(productNo: item.shortCode, date: date)
let prices = try [CapturePrice].readCsv(fromFile: pricesUrl)
let targetPrices = prices.filter { $0.stockBusinessDate == yyyyMMdd && $0.captureTime <= dateHHmmss }
.sorted(by: { dateHHmmss.diffSecondsTwoHHmmss($0.captureTime) < dateHHmmss.diffSecondsTwoHHmmss($1.captureTime) })
if let price = targetPrices.first, let quantity = Int(price.lastShortSellingConclusionQuantity), quantity > 0 {
/// ?
/// lastShortSellingConclusionQuantity
return price
}
return nil
}
}
var taskResult = [CapturePrice]()
for try await result in taskGroup.compactMap( { $0 }) {
taskResult.append(result)
}
return taskResult
}
return prices
}
private static var shopProductsUrl: URL {
URL.currentDirectory().appending(path: "data/shop-products.csv")
}
private static func pickNearShortsUrl(productNo: String, date: Date) -> URL {
let subPath = "data/\(productNo)/shorts"
let monthFile = "shorts-\(date.yyyyMM01).csv"
return URL.currentDirectory().appending(path: "\(subPath)/\(monthFile)")
}
private static func pickNearPricesUrl(productNo: String, date: Date) -> URL {
let subPath = "data/\(productNo)/price"
let priceFile = "prices.csv"
// TODO: work month file
//let monthFile = "prices-\(date.yyyyMM01).csv"
return URL.currentDirectory().appending(path: "\(subPath)/\(priceFile)")
}
}
extension String {
func diffSecondsTwoHHmmss(_ another: String) -> TimeInterval {
guard let (hour, min, sec) = self.HHmmss else {
return Double.greatestFiniteMagnitude
}
guard let (dHour, dMin, dSec) = another.HHmmss else {
return Double.greatestFiniteMagnitude
}
let seconds = (hour * 60 * 60 + min * 60 + sec)
let dSeconds = (dHour * 60 * 60 + dMin * 60 + dSec)
return TimeInterval(seconds - dSeconds)
}
}

View File

@@ -12,8 +12,7 @@ import KissMe
extension KissIndex {
func indexSet_0005(date: Date, config: String?, kmi: KissIndexType) {
//let belongs: [BelongClassCode] = [.averageVolume, .volumeIncreaseRate, .averageVolumeTurnoverRate, .transactionValue, .averageTransactionValueTurnoverRate]
let belongs: [BelongClassCode] = [.averageVolume]
let belongs: [BelongClassCode] = [.averageVolume, .volumeIncreaseRate, .averageVolumeTurnoverRate, .transactionValue, .averageTransactionValueTurnoverRate]
do {
var scoreMap = [String: Int]()
@@ -51,7 +50,7 @@ extension KissIndex {
}
static func pickNearTopProductsUrl(_ belong: BelongClassCode, date: Date) throws -> URL {
private static func pickNearTopProductsUrl(_ belong: BelongClassCode, date: Date) throws -> URL {
let subPath = "data/top30/\(date.yyyyMMdd)"
let dayFile = "top30-\(belong.fileBelong)-\(date.yyyyMMdd)-"
@@ -106,6 +105,13 @@ extension String {
return TimeInterval(hour * 60 * 60 + min * 60 + sec)
}
var HHmmssBySeconds: TimeInterval? {
guard let (hour, min, sec) = HHmmss else {
return nil
}
return TimeInterval(hour * 60 * 60 + min * 60 + sec)
}
func diffSecondsTwoCsvHHmmss(_ another: String) -> TimeInterval {
(csvHHmmssBySeconds ?? 0) - (another.csvHHmmssBySeconds ?? 0)
}

View File

@@ -19,7 +19,7 @@ enum KissIndexType: String {
}
class KissIndex {
class KissIndex: KissMe.ShopContext {
func run() {
guard CommandLine.argc >= 4 else {

View File

@@ -28,9 +28,9 @@ WIP `modify (PNO) (ONO) (가격) (수량)` | 주문 내역을 변경. (수량)
`candle [PNO]` | 종목의 분봉 열람. PNO 은 생략 가능. **data/(PNO)/min/candle-(yyyyMMdd).csv** 파일로 저장.
`candle all [resume]` | 모든 종목의 분봉 열람. cron job 으로 돌리기 위해서 추가. **data/(PNO)/min/candle-(yyyyMMdd).csv** 파일로 저장. (resume) 을 기입하면, 이미 받은 파일은 검사하여, 데이터에 오류가 있으면 다시 받고 오류가 없으면 새롭게 열람하지 않음.
`candle day [PNO]` | 종목의 최근 250일 동안의 일봉 열람. PNO 은 생략 가능. **data/(PNO)/day/candle-(yyyyMMdd).csv** 파일로 저장.
`candle day all` | 모든 종목의 최근 250일 동안의 일봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/day/candle-(yyyyMMdd).csv** 파일로 저장.
`candle day all [resume]` | 모든 종목의 최근 250일 동안의 일봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/day/candle-(yyyyMMdd).csv** 파일로 저장.
`candle week [PNO]` | 종목의 최근 52주 동안의 주봉 열람. PNO 은 생략 가능. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
`candle week all` | 모든 종목의 최근 52주 동안의 주봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
`candle week all [resume]` | 모든 종목의 최근 52주 동안의 주봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
`candle validate (기간)` | (기간) 타입의 모든 csv 파일에 대해서 데이터가 유효한지 검사. (기간) 으로는 **min**, **day**, **week** 을 지정하고, 생략되면 **min** 으로 간주.
`investor [PNO]` | 종목의 투자자 거래량 열람. PNO 은 생략 가능. **data/(PNO)/investor/investor-(yyyyMMdd).csv** 파일로 저장.
`investor all` | 모든 종목의 투자자 거래량 열람. **data/(PNO)/investor/investor-(yyyyMMdd).csv** 파일로 저장.

View File

@@ -36,8 +36,9 @@
3435A7F72A35D82000D604F1 /* DomesticShortSelling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3435A7F62A35D82000D604F1 /* DomesticShortSelling.swift */; };
349C26AB2A1EAE2400F3EC91 /* KissProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */; };
34D3680F2A2AA0BE005E6756 /* PropertyIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */; };
34F190092A418E130068C697 /* LocalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F190082A418E130068C697 /* LocalContext.swift */; };
34F1900C2A41982A0068C697 /* IndexResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F1900B2A41982A0068C697 /* IndexResult.swift */; };
34F1900F2A426D150068C697 /* ShopContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F1900E2A426D150068C697 /* ShopContext.swift */; };
34F190112A4394EB0068C697 /* LocalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F190102A4394EB0068C697 /* LocalContext.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -81,8 +82,9 @@
3435A7F62A35D82000D604F1 /* DomesticShortSelling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticShortSelling.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>"; };
34F190082A418E130068C697 /* LocalContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalContext.swift; sourceTree = "<group>"; };
34F1900B2A41982A0068C697 /* IndexResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexResult.swift; sourceTree = "<group>"; };
34F1900E2A426D150068C697 /* ShopContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopContext.swift; sourceTree = "<group>"; };
34F190102A4394EB0068C697 /* LocalContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalContext.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -125,6 +127,7 @@
341F5EAD2A0A80EC00962D48 /* KissMe */ = {
isa = PBXGroup;
children = (
34F1900D2A426C1C0068C697 /* Context */,
34F1900A2A41981A0068C697 /* Index */,
341F5EF32A0F88AC00962D48 /* Common */,
341F5EEA2A0F882300962D48 /* Foreign */,
@@ -195,7 +198,6 @@
341F5F022A11A2BC00962D48 /* Credential.swift */,
341F5F062A14634F00962D48 /* Foundation+Extensions.swift */,
34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */,
34F190082A418E130068C697 /* LocalContext.swift */,
);
path = Common;
sourceTree = "<group>";
@@ -224,6 +226,15 @@
path = Index;
sourceTree = "<group>";
};
34F1900D2A426C1C0068C697 /* Context */ = {
isa = PBXGroup;
children = (
34F190102A4394EB0068C697 /* LocalContext.swift */,
34F1900E2A426D150068C697 /* ShopContext.swift */,
);
path = Context;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -342,16 +353,17 @@
341F5EFD2A10931B00962D48 /* DomesticStockSearch.swift in Sources */,
341F5EE52A0F3EF400962D48 /* DomesticStock.swift in Sources */,
341F5EF72A0F8B0500962D48 /* DomesticStockResult.swift in Sources */,
34F1900F2A426D150068C697 /* ShopContext.swift in Sources */,
341F5F0F2A15223A00962D48 /* SeibroRequest.swift in Sources */,
341F5EF02A0F886600962D48 /* ForeignFutures.swift in Sources */,
34F1900C2A41982A0068C697 /* IndexResult.swift in Sources */,
341F5EEC2A0F883900962D48 /* ForeignStock.swift in Sources */,
34F190112A4394EB0068C697 /* LocalContext.swift in Sources */,
341F5EFF2A10955D00962D48 /* OrderRequest.swift in Sources */,
341F5EE92A0F87FB00962D48 /* DomesticStockPrice.swift in Sources */,
341F5EEE2A0F884300962D48 /* ForeignStockPrice.swift in Sources */,
341F5EDE2A0F300100962D48 /* Request.swift in Sources */,
349C26AB2A1EAE2400F3EC91 /* KissProfile.swift in Sources */,
34F190092A418E130068C697 /* LocalContext.swift in Sources */,
341F5F012A11155100962D48 /* DomesticStockSearchResult.swift in Sources */,
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */,
341F5EF22A0F887200962D48 /* DomesticFutures.swift in Sources */,

View File

@@ -53,7 +53,7 @@
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "KMI-0005 20230616 100000"
argument = "KMI-0002 20230616 100000"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>