193 lines
7.3 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|