Files
KissMe/KissMeConsole/Sources/KissConsole+Candle.swift
2023-06-10 13:21:51 +09:00

193 lines
7.3 KiB
Swift

//
// 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 {
enum CandleFilePeriod: String {
case minute = "min"
case day = "day"
case weak = "week"
}
func getRecentCandle(productNo: String, period: PeriodDivision, count: Int) async -> Bool {
do {
guard currentCandleShortCode == nil else {
print("Already candle collecting")
return false
}
currentCandleShortCode = productNo
defer {
currentCandleShortCode = nil
}
var reqEndDate = Date()
var reqStartDate = reqEndDate.addingTimeInterval(-period.secondsForPeriodRequest)
var reqCount = 0
var candles = [Domestic.CandlePeriod]()
while true {
reqCount += 1
print("\(period) price \(productNo) from \(reqStartDate.yyyyMMdd_HHmmss_forTime) to \(reqEndDate.yyyyMMdd_HHmmss_forTime)")
let result = try await account!.getPeriodPrice(productNo: productNo, startDate: reqStartDate, endDate: reqEndDate, period: period)
if let prices = result.output2?.compactMap({ $0.validObject }), prices.isEmpty == false {
candles.append(contentsOf: prices)
if candles.count >= count {
print("\(period) price finished")
break
}
if let last = prices.last {
if let (yyyy, mm, dd) = last.stockBusinessDate.yyyyMMdd {
print("next: \(last.stockBusinessDate)")
reqEndDate.change(year: yyyy, month: mm, day: dd-1)
reqStartDate = reqEndDate.addingTimeInterval(-period.secondsForPeriodRequest)
}
}
try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredCandleTPS)
}
else {
/// , (ex. ),
/// .
if reqCount < 5 {
reqEndDate = reqStartDate.addingTimeInterval(-SecondsForOneDay)
reqStartDate = reqEndDate.addingTimeInterval(-period.secondsForPeriodRequest)
}
else {
print("\(period) price finished")
break
}
}
}
candles.sort(by: { $0.stockBusinessDate > $1.stockBusinessDate })
guard let maxTime = candles.first?.stockBusinessDate else {
print("No price items")
return false
}
let fileUrl = KissConsole.candleFileUrl(productNo: productNo, period: period.filePeriod, day: maxTime)
try candles.writeCsv(toFile: fileUrl, localized: localized)
print("wrote \(fileUrl.lastPathComponent) with \(candles.count)")
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.stockFullDate > $1.stockFullDate })
guard let maxTime = candles.first?.stockBusinessDate else {
print("No price items")
return false
}
let fileUrl = KissConsole.candleFileUrl(productNo: productNo, period: .minute, day: maxTime)
try candles.writeCsv(toFile: fileUrl, localized: localized)
print("wrote \(fileUrl.lastPathComponent) with \(candles.count)")
return true
} catch {
print("\(error)")
return false
}
}
func checkHoliday(_ date: Date) async throws -> Bool {
guard await KissContext.shared.targetDate.yyyyMMdd != date.yyyyMMdd else {
return await KissContext.shared.isHoliday
}
let isHoliday = try await account!.getHolyday(baseDate: date).isHoliday(date)
await KissContext.shared.updateHoliday(isHoliday, targetDate: date)
return isHoliday
}
}
extension PeriodDivision {
var filePeriod: KissConsole.CandleFilePeriod {
switch self {
case .daily: return .day
case .weekly: return .weak
case .monthly: assertionFailure()
case .yearly: assertionFailure()
}
return .minute
}
var secondsForPeriodRequest: TimeInterval {
switch self {
case .daily: return 99 * SecondsForOneDay /// 100 . ~ , 99
case .weekly: return 99 * 7 * SecondsForOneDay
case .monthly: return 99 * 28 * SecondsForOneDay
case .yearly: return 99 * 365 * SecondsForOneDay
}
}
}