diff --git a/KissMeConsole/Sources/KissConsole+Candle.swift b/KissMeConsole/Sources/KissConsole+Candle.swift index 73c280f..4848a55 100644 --- a/KissMeConsole/Sources/KissConsole+Candle.swift +++ b/KissMeConsole/Sources/KissConsole+Candle.swift @@ -21,6 +21,9 @@ let PreferredShortsTPS: UInt64 = 5 /// Limit to request a top query let PreferredTopTPS: UInt64 = 5 +/// Limit to request a investor query +let PreferredInvestorTPS: UInt64 = 19 + /// How many seconds does 1 day have? let SecondsForOneDay: TimeInterval = 60 * 60 * 24 diff --git a/KissMeConsole/Sources/KissConsole+Investor.swift b/KissMeConsole/Sources/KissConsole+Investor.swift index 36917b0..8ab05e7 100644 --- a/KissMeConsole/Sources/KissConsole+Investor.swift +++ b/KissMeConsole/Sources/KissConsole+Investor.swift @@ -13,18 +13,39 @@ extension KissConsole { func getInvestor(productNo: String) async throws -> Bool { let result = try await account!.getInvestorVolume(productNo: productNo) if let output = result.output { - print(output.count) + print("Total output: \(output.count) productNo: \(productNo)") - guard let recentDay = output.first?.stockBusinessDate else { - print("No investor items") - return false + var months = [String: [InvestorVolumeResult.OutputDetail]]() + for item in output { + let yyyyMM = String(item.stockBusinessDate.prefix(6)) + if let _ = months[yyyyMM] { + months[yyyyMM]!.append(item) + } + else { + months[yyyyMM] = [item] + } } - let fileUrl = KissConsole.investorFileUrl(productNo: productNo, day: recentDay) - try output.writeCsv(toFile: fileUrl, localized: localized) - - try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredCandleTPS) + for (month, items) in months { + let descItems = items.sorted(by: { $0.stockBusinessDate > $1.stockBusinessDate }) + guard descItems.count > 0 else { + continue + } + + let fileUrl = KissConsole.investorFileUrl(productNo: productNo, day: month+"01") + try descItems.mergeCsv(toFile: fileUrl, merging: { this, file in + var merged = this + for old in file { + if nil == this.first(where: { $0.stockBusinessDate == old.stockBusinessDate }) { + merged.append(old) + } + } + merged.sort(by: { $0.stockBusinessDate > $1.stockBusinessDate }) + return merged + }, localized: localized) + } } + try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredInvestorTPS) return true } } diff --git a/KissMeConsole/Sources/KissConsole+Price.swift b/KissMeConsole/Sources/KissConsole+Price.swift index 13c6ef6..ce6abfa 100644 --- a/KissMeConsole/Sources/KissConsole+Price.swift +++ b/KissMeConsole/Sources/KissConsole+Price.swift @@ -111,6 +111,7 @@ extension KissConsole { if let outBlock = result.outBlock { print("Total block: \(outBlock.count) productNo: \(productNo)") + var monthBlock = [String: [DomesticExtra.Shorts]]() for block in outBlock { let yyyyMM = String(block.stockBusinessDate.prefix(6)) diff --git a/KissMeConsole/Sources/test.swift b/KissMeConsole/Sources/test.swift index 13e2dec..e33ce27 100644 --- a/KissMeConsole/Sources/test.swift +++ b/KissMeConsole/Sources/test.swift @@ -252,3 +252,63 @@ private func update_candle_csv_header_field() { print(error) } } + +private func split_investor_csv() { + guard let enumerator = FileManager.subPathFiles("data") else { + return + } + + var urls = [URL]() + for case let fileUrl as URL in enumerator { + guard fileUrl.pathExtension == "csv" else { + continue + } + if fileUrl.lastPathComponent.prefix(9) == "investor-" { + urls.append(fileUrl) + } + } + + do { + for url in urls { + let investorDir = url.deletingLastPathComponent() + let productNoDir = investorDir.deletingLastPathComponent() + let productNo = productNoDir.lastPathComponent + + let output = try [InvestorVolumeResult.OutputDetail].readCsv(fromFile: url, verifyHeader: true) + + var months = [String: [InvestorVolumeResult.OutputDetail]]() + for item in output { + let yyyyMM = String(item.stockBusinessDate.prefix(6)) + if let _ = months[yyyyMM] { + months[yyyyMM]!.append(item) + } + else { + months[yyyyMM] = [item] + } + } + + for (month, items) in months { + let descItems = items.sorted(by: { $0.stockBusinessDate > $1.stockBusinessDate }) + guard descItems.count > 0 else { + continue + } + + let fileUrl = KissConsole.investorFileUrl(productNo: productNo, day: month+"01") + try descItems.mergeCsv(toFile: fileUrl, merging: { this, file in + var merged = this + for old in file { + if nil == this.first(where: { $0.stockBusinessDate == old.stockBusinessDate }) { + merged.append(old) + } + } + merged.sort(by: { $0.stockBusinessDate > $1.stockBusinessDate }) + return merged + }, localized: false) + } + + try FileManager.default.removeItem(at: url) + } + } catch { + print(error) + } +} diff --git a/KissMeIndex/Sources/KissIndex+0002.swift b/KissMeIndex/Sources/KissIndex+0002.swift index 21a6bd2..985dfcf 100644 --- a/KissMeIndex/Sources/KissIndex+0002.swift +++ b/KissMeIndex/Sources/KissIndex+0002.swift @@ -26,7 +26,7 @@ extension KissIndex { } return false } - print(shorts.count) + //print(shorts.count) let prices = try await collectPrices(date: date) { price in if let quantity = Int(price.lastShortSellingConclusionQuantity), quantity > 0 { @@ -36,10 +36,10 @@ extension KissIndex { } return false } - print(prices.count) + //print(prices.count) } catch { - print(error) + //print(error) writeError(error, kmi: kmi) } diff --git a/KissMeIndex/Sources/KissIndex+0004.swift b/KissMeIndex/Sources/KissIndex+0004.swift index 874f837..12a7bac 100644 --- a/KissMeIndex/Sources/KissIndex+0004.swift +++ b/KissMeIndex/Sources/KissIndex+0004.swift @@ -11,6 +11,27 @@ import Foundation extension KissIndex { func indexSet_0004(date: Date, config: String?, kmi: KissIndexType) { + if productsCount == 0 { + loadShop(url: KissIndex.shopProductsUrl) + } + + let semaphore = DispatchSemaphore(value: 0) + Task { + var scoreMap = [String: Double]() + + // TODO: work + + semaphore.signal() + } + semaphore.wait() + } + + private func pickNearInvestorUrl(productNo: String, date: Date) { + let subPath = "data/\(productNo)/investor" + let subFile = "investor-\(date.yyyyMM01).csv" + + let fileUrl = URL.currentDirectory().appending(path: subFile) + // TODO: work } } diff --git a/KissMeIndex/Sources/KissIndex.swift b/KissMeIndex/Sources/KissIndex.swift index d524186..01a5fc4 100644 --- a/KissMeIndex/Sources/KissIndex.swift +++ b/KissMeIndex/Sources/KissIndex.swift @@ -98,7 +98,7 @@ class KissIndex: KissMe.ShopContext { extension KissIndex { - + func collectShorts(date: Date, filter: @escaping (DomesticExtra.Shorts) -> Bool) async throws -> [DomesticExtra.Shorts] { let shorts = try await withThrowingTaskGroup(of: DomesticExtra.Shorts?.self, returning: [DomesticExtra.Shorts].self) { taskGroup in let all = getAllProducts() @@ -110,7 +110,7 @@ extension KissIndex { let shorts = try [DomesticExtra.Shorts].readCsv(fromFile: shortsUrl) let targetShorts = shorts.filter { $0.stockBusinessDate == yyyyMMdd } - + if let aShorts = targetShorts.first, filter(aShorts) { return aShorts } @@ -155,6 +155,10 @@ extension KissIndex { } return prices } +} + + +extension KissIndex { static var shopProductsUrl: URL { URL.currentDirectory().appending(path: "data/shop-products.csv") @@ -182,6 +186,7 @@ extension KissIndex { extension KissIndex { func normalizeAndWrite(scoreMap: [String: Double], includeName: Bool = false, kmi: KissIndexType) { + let totalScores = scoreMap.reduce(0, { $0 + $1.value }) let scoreArray = scoreMap.map { ($0.key, $0.value) }.sorted(by: { $0.1 > $1.1 }) diff --git a/bin/data b/bin/data index 8f92a1c..753bcd9 160000 --- a/bin/data +++ b/bin/data @@ -1 +1 @@ -Subproject commit 8f92a1c7b8857dd05d73317de10b04c3f8f14d40 +Subproject commit 753bcd9c9198a6278c7aa69b10c4a28a980bbd50 diff --git a/documents/KMI/KMI-0003.md b/documents/KMI/KMI-0003.md index 0a39d97..5654716 100644 --- a/documents/KMI/KMI-0003.md +++ b/documents/KMI/KMI-0003.md @@ -6,6 +6,9 @@ PER 의 적정값에 해당되는 종목을 선별합니다. **한국투자증권**에서 우선적으로 제공되는 PER 를 사용합니다. +* [주식현재가 시세[v1_국내주식-008]](https://apiportal.koreainvestment.com/apiservice/apiservice-domestic-stock-quotations#L_07802512-4f49-4486-91b4-1050b6f5dc9d) + * /uapi/domestic-stock/v1/quotations/inquire-price + 차후에는 PER 값을 예측하도록 개선될 예정입니다. 적합한 PER 를 선별하는 기준은 다음과 같습니다. diff --git a/documents/KMI/KMI-0004.md b/documents/KMI/KMI-0004.md index 690bbe4..69b3bc5 100644 --- a/documents/KMI/KMI-0004.md +++ b/documents/KMI/KMI-0004.md @@ -1,2 +1,20 @@ # KMI-0004 +## How to + +외국인 거래량/보유량을 기준으로 종목을 선별합니다. + +여기에 2가지 방식이 제안됩니다. + +* 3일 연속매수하여 계속 증가했는지 검사. +* 4일전보다 보유량이 증가했는지 검사. + +### 3일 연속매수 + +### 4일이후 보유량 상승 + + +## Usage + + +## Configuration