Add candle day/week commands
This commit is contained in:
142
KissMeConsole/Sources/KissConsole+Candle.swift
Normal file
142
KissMeConsole/Sources/KissConsole+Candle.swift
Normal file
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// KissConsole+Candle.swift
|
||||
// KissMeConsole
|
||||
//
|
||||
// Created by ened-book-m1 on 2023/06/01.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import KissMe
|
||||
|
||||
|
||||
/// Limit to request a candle query
|
||||
let PreferredCandleTPS: UInt64 = 19
|
||||
|
||||
/// How many seconds does 1 day have?
|
||||
let SecondsForOneDay: TimeInterval = 60 * 60 * 24
|
||||
|
||||
|
||||
extension KissConsole {
|
||||
|
||||
var last250Days: (startDate: Date, endDate: Date) {
|
||||
let endDate = Date().changing(hour: 0, min: 0, sec: 0)!
|
||||
let startDate = endDate.addingTimeInterval(-250 * SecondsForOneDay)
|
||||
return (startDate, endDate)
|
||||
}
|
||||
|
||||
|
||||
var last52Weeks: (startDate: Date, endDate: Date) {
|
||||
// TODO: 주당 가격을 얻기 위해, 거래 시작일을 마지막 장 개설일로 보정할 필요가 있을까?
|
||||
let endDate = Date().changing(hour: 0, min: 0, sec: 0)!
|
||||
let startDate = endDate.addingTimeInterval(-52 * 7 * SecondsForOneDay)
|
||||
return (startDate, endDate)
|
||||
}
|
||||
|
||||
|
||||
enum CandleFilePeriod: String {
|
||||
case minute = "min"
|
||||
case day = "day"
|
||||
case weak = "week"
|
||||
}
|
||||
|
||||
|
||||
func candleFileUrl(productNo: String, period: CandleFilePeriod, day: String) -> URL {
|
||||
assert(day.count == 6)
|
||||
let subPath = "data/\(productNo)/\(period.rawValue)"
|
||||
let subFile = "\(subPath)/candle-\(day).csv"
|
||||
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
||||
createSubpath(subPath)
|
||||
return fileUrl
|
||||
}
|
||||
|
||||
|
||||
func getCandle(productNo: String, period: PeriodDivision, startDate: Date, endDate: Date) async -> Bool {
|
||||
do {
|
||||
guard currentCandleShortCode == nil else {
|
||||
print("Already candle collecting")
|
||||
return false
|
||||
}
|
||||
currentCandleShortCode = productNo
|
||||
defer {
|
||||
currentCandleShortCode = nil
|
||||
}
|
||||
|
||||
var nextTime = Date()
|
||||
var candles = [Domestic.Candle]()
|
||||
var count = 0
|
||||
|
||||
let result = try await account!.getPeriodPrice(productNo: productNo, startDate: startDate, endDate: endDate, period: .daily)
|
||||
|
||||
// let fileUrl = candleFileUrl(productNo: productNo, period: "min", day: minTime)
|
||||
// KissConsole.writeCandle(candles, fileUrl: fileUrl)
|
||||
|
||||
return true
|
||||
} catch {
|
||||
print(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getCandle(productNo: String) async -> Bool {
|
||||
do {
|
||||
guard currentCandleShortCode == nil else {
|
||||
print("Already candle collecting")
|
||||
return false
|
||||
}
|
||||
currentCandleShortCode = productNo
|
||||
defer {
|
||||
currentCandleShortCode = nil
|
||||
}
|
||||
|
||||
var nextTime = Date()
|
||||
var candles = [Domestic.Candle]()
|
||||
var count = 0
|
||||
|
||||
while true {
|
||||
let more = (count > 0)
|
||||
count += 1
|
||||
print("minute price \(productNo) from \(nextTime.yyyyMMdd_HHmmss_forTime) \(more)")
|
||||
let result = try await account!.getMinutePrice(productNo: productNo, startTodayTime: nextTime, more: more)
|
||||
|
||||
if let prices = result.output2, prices.isEmpty == false {
|
||||
candles.append(contentsOf: prices)
|
||||
if let last = prices.last {
|
||||
if nextTime.yyyyMMdd != last.stockBusinessDate {
|
||||
if let (yyyy, mm, dd) = last.stockBusinessDate.yyyyMMdd {
|
||||
print("next: \(last.stockBusinessDate)")
|
||||
nextTime.change(year: yyyy, month: mm, day: dd)
|
||||
}
|
||||
}
|
||||
if let (hh, mm, ss) = last.stockConclusionTime.HHmmss {
|
||||
print("next: \(last.stockConclusionTime) / \(hh) \(mm) \(ss)")
|
||||
nextTime.change(hour: hh, min: mm-1, sec: ss)
|
||||
if hh == 9, mm == 0, ss == 0 {
|
||||
print("minute price finished")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredCandleTPS)
|
||||
}
|
||||
else {
|
||||
print("minute price finished")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
candles.sort(by: { $0.stockBusinessDate < $1.stockBusinessDate })
|
||||
guard let minTime = candles.first?.stockBusinessDate else {
|
||||
print("No price items")
|
||||
return false
|
||||
}
|
||||
|
||||
let fileUrl = candleFileUrl(productNo: productNo, period: .minute, day: minTime)
|
||||
KissConsole.writeCandle(candles, fileUrl: fileUrl)
|
||||
return true
|
||||
} catch {
|
||||
print("\(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,13 @@ import KissMe
|
||||
|
||||
class KissConsole {
|
||||
private var credential: Credential? = nil
|
||||
private var account: KissAccount? = nil
|
||||
var account: KissAccount? = nil
|
||||
private var shop: KissShop? = nil
|
||||
|
||||
private var productsLock = NSLock()
|
||||
private var products = [String: [DomesticShop.Product]]()
|
||||
private var currentShortCode: String?
|
||||
private var currentCandleShortCode: String?
|
||||
var currentCandleShortCode: String?
|
||||
|
||||
private enum KissCommand: String {
|
||||
case quit = "quit"
|
||||
@@ -42,6 +42,8 @@ class KissConsole {
|
||||
case now = "now"
|
||||
case candle = "candle"
|
||||
case candleAll = "candle all"
|
||||
case candleDay = "candle day"
|
||||
case candleWeek = "candle week"
|
||||
|
||||
// 종목 열람
|
||||
case loadShop = "load shop"
|
||||
@@ -64,7 +66,7 @@ class KissConsole {
|
||||
return true
|
||||
case .openBag:
|
||||
return true
|
||||
case .now, .candle, .candleAll:
|
||||
case .now, .candle, .candleAll, .candleDay, .candleWeek:
|
||||
return true
|
||||
case .loadShop, .updateShop, .look:
|
||||
return false
|
||||
@@ -180,6 +182,8 @@ class KissConsole {
|
||||
case .now: await onNow(args)
|
||||
case .candle: await onCandle(args)
|
||||
case .candleAll: onCancleAll()
|
||||
case .candleDay: onCandleDay(args)
|
||||
case .candleWeek: onCandleWeek(args)
|
||||
|
||||
case .loadShop: await onLoadShop()
|
||||
case .updateShop: await onUpdateShop()
|
||||
@@ -200,7 +204,7 @@ class KissConsole {
|
||||
|
||||
extension KissConsole {
|
||||
|
||||
private func createSubpath(_ name: String) {
|
||||
func createSubpath(_ name: String) {
|
||||
let subPath = URL.currentDirectory().appending(path: name)
|
||||
try? FileManager.default.createDirectory(at: subPath, withIntermediateDirectories: true)
|
||||
}
|
||||
@@ -319,8 +323,17 @@ extension KissConsole {
|
||||
}
|
||||
|
||||
|
||||
private func onTop(_ arg: [String]) async {
|
||||
let option = RankingOption(divisionClass: .all, belongClass: .averageVolume)
|
||||
private func onTop(_ args: [String]) async {
|
||||
var belongCode = "0"
|
||||
if args.count == 1, let code = Int(args[0]) {
|
||||
belongCode = String(code)
|
||||
}
|
||||
guard let belongClass = BelongClassCode(rawValue: belongCode) else {
|
||||
print("Incorrect belong type: \(belongCode)")
|
||||
return
|
||||
}
|
||||
print("TOP: \(belongClass.description)")
|
||||
let option = RankingOption(divisionClass: .all, belongClass: belongClass)
|
||||
|
||||
do {
|
||||
let rank = try await account!.getVolumeRanking(option: option)
|
||||
@@ -558,6 +571,82 @@ extension KissConsole {
|
||||
}
|
||||
|
||||
|
||||
private func onCandleDay(_ args: [String]) {
|
||||
if args.count == 1, args[0] == "all" {
|
||||
onCandleDayAll()
|
||||
return
|
||||
}
|
||||
|
||||
let productNo: String? = (args.isEmpty ? currentShortCode: args[0])
|
||||
guard let productNo = productNo else {
|
||||
print("Invalid productNo")
|
||||
return
|
||||
}
|
||||
|
||||
let (startDate, endDate) = last250Days
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
Task {
|
||||
let success = await getCandle(productNo: productNo, period: .daily, startDate: startDate, endDate: endDate)
|
||||
print("DONE \(success) \(productNo)")
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
|
||||
|
||||
private func onCandleDayAll() {
|
||||
let (startDate, endDate) = last250Days
|
||||
let all = getAllProducts()
|
||||
for item in all {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
Task {
|
||||
let success = await getCandle(productNo: item.shortCode, period: .daily, startDate: startDate, endDate: endDate)
|
||||
print("DONE \(success) \(item.shortCode)")
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func onCandleWeek(_ args: [String]) {
|
||||
if args.count == 1, args[0] == "all" {
|
||||
onCandleDayAll()
|
||||
return
|
||||
}
|
||||
|
||||
let productNo: String? = (args.isEmpty ? currentShortCode: args[0])
|
||||
guard let productNo = productNo else {
|
||||
print("Invalid productNo")
|
||||
return
|
||||
}
|
||||
|
||||
let (startDate, endDate) = last52Weeks
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
Task {
|
||||
let success = await getCandle(productNo: productNo, period: .weekly, startDate: startDate, endDate: endDate)
|
||||
print("DONE \(success) \(productNo)")
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
|
||||
|
||||
private func onCandleWeekAll() {
|
||||
let (startDate, endDate) = last52Weeks
|
||||
let all = getAllProducts()
|
||||
for item in all {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
Task {
|
||||
let success = await getCandle(productNo: item.shortCode, period: .weekly, startDate: startDate, endDate: endDate)
|
||||
print("DONE \(success) \(item.shortCode)")
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func onCandle(_ args: [String]) async {
|
||||
let productNo: String? = (args.isEmpty ? currentShortCode: args[0])
|
||||
guard let productNo = productNo else {
|
||||
@@ -566,80 +655,6 @@ extension KissConsole {
|
||||
}
|
||||
_ = await getCandle(productNo: productNo)
|
||||
}
|
||||
|
||||
/// Limit to request candle with `preferCandleTPS`
|
||||
private var preferCandleTPS: UInt64 {
|
||||
return 19
|
||||
}
|
||||
|
||||
private func getCandle(productNo: String) async -> Bool {
|
||||
do {
|
||||
guard currentCandleShortCode == nil else {
|
||||
print("Already candle collecting")
|
||||
return false
|
||||
}
|
||||
currentCandleShortCode = productNo
|
||||
defer {
|
||||
currentCandleShortCode = nil
|
||||
}
|
||||
|
||||
var nextTime = Date()
|
||||
//nextTime.change(hour: 17, min: 0, sec: 0)
|
||||
//nextTime.change(year: 2023, month: 5, day: 26)
|
||||
//nextTime.change(hour: 9, min: 1, sec: 0)
|
||||
|
||||
var candles = [Domestic.Candle]()
|
||||
var count = 0
|
||||
|
||||
while true {
|
||||
let more = (count > 0)
|
||||
count += 1
|
||||
print("minute price \(productNo) from \(nextTime.yyyyMMdd_HHmmss_forTime) \(more)")
|
||||
let result = try await account!.getMinutePrice(productNo: productNo, startTodayTime: nextTime, more: more)
|
||||
|
||||
if let prices = result.output2, prices.isEmpty == false {
|
||||
candles.append(contentsOf: prices)
|
||||
if let last = prices.last {
|
||||
if nextTime.yyyyMMdd != last.stockBusinessDate {
|
||||
if let (yyyy, mm, dd) = last.stockBusinessDate.yyyyMMdd {
|
||||
print("next: \(last.stockBusinessDate)")
|
||||
nextTime.change(year: yyyy, month: mm, day: dd)
|
||||
}
|
||||
}
|
||||
if let (hh, mm, ss) = last.stockConclusionTime.HHmmss {
|
||||
print("next: \(last.stockConclusionTime) / \(hh) \(mm) \(ss)")
|
||||
nextTime.change(hour: hh, min: mm-1, sec: ss)
|
||||
if hh == 9, mm == 0, ss == 0 {
|
||||
print("minute price finished")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000 / preferCandleTPS)
|
||||
}
|
||||
else {
|
||||
print("minute price finished")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
candles.sort(by: { $0.stockBusinessDate < $1.stockBusinessDate })
|
||||
guard let minTime = candles.first?.stockBusinessDate else {
|
||||
print("No price items")
|
||||
return false
|
||||
}
|
||||
|
||||
let subPath = "data/\(productNo)"
|
||||
let subFile = "\(subPath)/candle-\(minTime).csv"
|
||||
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
||||
createSubpath(subPath)
|
||||
KissConsole.writeCandle(candles, fileUrl: fileUrl)
|
||||
return true
|
||||
} catch {
|
||||
print("\(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func onLoadShop() async {
|
||||
|
||||
@@ -8,3 +8,5 @@
|
||||
import Foundation
|
||||
|
||||
KissConsole().run()
|
||||
|
||||
//move_candles_to_min_subdir()
|
||||
|
||||
@@ -159,3 +159,36 @@ private func check_candle_csv() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func move_candles_to_min_subdir() {
|
||||
guard let enumerator = subPathFiles("data") else {
|
||||
return
|
||||
}
|
||||
|
||||
var urls = [URL]()
|
||||
for case let fileUrl as URL in enumerator {
|
||||
guard fileUrl.pathExtension == "csv" else {
|
||||
continue
|
||||
}
|
||||
urls.append(fileUrl)
|
||||
}
|
||||
|
||||
for fileUrl in urls {
|
||||
let fileName = fileUrl.lastPathComponent
|
||||
let upper = fileUrl.deletingLastPathComponent()
|
||||
|
||||
let newPath = upper.appending(path: "min")
|
||||
let newUrl = newPath.appending(path: fileName)
|
||||
|
||||
//print("file: \(fileUrl) -> \(newUrl)")
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: upper, withIntermediateDirectories: true)
|
||||
try FileManager.default.moveItem(at: fileUrl, to: newUrl)
|
||||
}
|
||||
catch {
|
||||
print(error)
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user