Implement "index portfolio", "load index", "update index" command
This commit is contained in:
@@ -78,6 +78,13 @@ extension Date {
|
||||
return (hour, minute, second)
|
||||
}
|
||||
|
||||
public var isBeforeMarketOpenning: Bool {
|
||||
guard let (hour, _, _) = HHmmss_split else {
|
||||
return true
|
||||
}
|
||||
return hour >= 0 && hour <= 9
|
||||
}
|
||||
|
||||
public func changing(hour: Int?, min: Int?, sec: Int?, timeZone: String = "KST") -> Date? {
|
||||
let sets: Set<Calendar.Component> = [.year, .month, .day, .hour, .minute, .second]
|
||||
var components = Calendar.current.dateComponents(sets, from: self)
|
||||
@@ -163,6 +170,14 @@ extension String {
|
||||
return (hh, mm, ss)
|
||||
}
|
||||
|
||||
public var krxDate: Date? {
|
||||
// ex) 2023.06.28 PM 12:06:57
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.timeZone = TimeZone(abbreviation: "KST")
|
||||
dateFormatter.dateFormat = "yyyy.MM.dd a hh:mm:ss"
|
||||
return dateFormatter.date(from: self)
|
||||
}
|
||||
|
||||
public var hasComma: Bool {
|
||||
return nil != rangeOfCharacter(from: commaCharSet)
|
||||
}
|
||||
|
||||
74
KissMe/Sources/Context/IndexContext.swift
Normal file
74
KissMe/Sources/Context/IndexContext.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// IndexContext.swift
|
||||
// KissMe
|
||||
//
|
||||
// Created by ened-book-m1 on 2023/06/28.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
open class IndexContext {
|
||||
|
||||
private var indicesLock = NSLock()
|
||||
/// 전체 지수 정보
|
||||
private var indices = [String: [DomesticExtra.IndexProduct]]()
|
||||
|
||||
public var indicesCount: Int {
|
||||
indicesLock.lock()
|
||||
defer {
|
||||
indicesLock.unlock()
|
||||
}
|
||||
return indices.count
|
||||
}
|
||||
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension IndexContext {
|
||||
|
||||
public func loadIndex(url: URL, loggable: Bool = false) {
|
||||
do {
|
||||
let products = try [DomesticExtra.IndexProduct].readCsv(fromFile: url)
|
||||
var indices = [String: [DomesticExtra.IndexProduct]]()
|
||||
for product in products {
|
||||
if let _ = indices[product.productName] {
|
||||
indices[product.productName]!.append(product)
|
||||
}
|
||||
else {
|
||||
indices[product.productName] = [product]
|
||||
}
|
||||
}
|
||||
|
||||
setIndices(indices)
|
||||
if loggable {
|
||||
let totalCount = indices.reduce(0, { $0 + $1.value.count })
|
||||
print("load indices \(totalCount) with \(products.count) key")
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func setIndices(_ indices: [String: [DomesticExtra.IndexProduct]]) {
|
||||
indicesLock.lock()
|
||||
self.indices = indices
|
||||
indicesLock.unlock()
|
||||
}
|
||||
|
||||
public func getAllIndices() -> [DomesticExtra.IndexProduct] {
|
||||
indicesLock.lock()
|
||||
defer {
|
||||
indicesLock.unlock()
|
||||
}
|
||||
|
||||
var all = [DomesticExtra.IndexProduct]()
|
||||
for index in indices.values {
|
||||
all.append(contentsOf: index)
|
||||
}
|
||||
all.sort(by: { $0.indexFullCode < $1.indexFullCode })
|
||||
return all
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public enum YesNo: String, Codable {
|
||||
|
||||
/// 보증금 비율 구분
|
||||
public enum MarginalRateClass: String, Codable {
|
||||
case undefined2 = ""
|
||||
case undefined = " "
|
||||
|
||||
/// 20%, 30%, 40%
|
||||
|
||||
@@ -15,6 +15,15 @@ extension DomesticExtra {
|
||||
case kospi = "02"
|
||||
case kosdaq = "03"
|
||||
case theme = "04"
|
||||
|
||||
public var name: String {
|
||||
switch self {
|
||||
case .krx: return "krx"
|
||||
case .kospi: return "kospi"
|
||||
case .kosdaq: return "kosdaq"
|
||||
case .theme: return "theme"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ShareType: Int {
|
||||
@@ -33,7 +42,7 @@ extension DomesticExtra {
|
||||
/// 한국거래소 - 주가지수 - 전체지수 시세
|
||||
///
|
||||
public struct IndexPriceRequest: KrxRequest {
|
||||
public typealias KResult = String
|
||||
public typealias KResult = IndexPriceResult
|
||||
|
||||
public var domain: String {
|
||||
"http://data.krx.co.kr"
|
||||
@@ -41,7 +50,7 @@ extension DomesticExtra {
|
||||
public var url: String {
|
||||
"/comm/bldAttendant/getJsonData.cmd"
|
||||
}
|
||||
public var method: Method { .post }
|
||||
public var method: Method { .get }
|
||||
|
||||
public var header: [String : String?] {
|
||||
[:]
|
||||
@@ -49,67 +58,17 @@ extension DomesticExtra {
|
||||
public var body: [String: Any] {
|
||||
return [
|
||||
"idxIndMidclssCd": indexType.rawValue,
|
||||
"strtDd": range.startDate.yyyyMMdd,
|
||||
"endDd": range.endDate.yyyyMMdd,
|
||||
"trdDd": date.yyyyMMdd,
|
||||
"share": shareType.rawValue,
|
||||
"money": moneyType.rawValue,
|
||||
"csvxls_isNo": false,
|
||||
"bld": "dbms/MDC/STAT/standard/MDCSTAT00201"
|
||||
"bld": "dbms/MDC/STAT/standard/MDCSTAT00101"
|
||||
]
|
||||
}
|
||||
public var result: KResult? = nil
|
||||
|
||||
|
||||
struct DataRange {
|
||||
let startDate: Date // yyyyMMdd
|
||||
let endDate: Date // yyyyMMdd
|
||||
}
|
||||
let range: DataRange
|
||||
let indexType: IndexType
|
||||
let shareType: ShareType
|
||||
let moneyType: MoneyType
|
||||
|
||||
init(range: DataRange, indexType: IndexType) {
|
||||
self.range = range
|
||||
self.indexType = indexType
|
||||
self.shareType = .unitOne
|
||||
self.moneyType = .moneyWon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 한국거래소 - 주가지수 - 지수구성종목
|
||||
///
|
||||
public struct IndexPortfolioRequest: KrxRequest {
|
||||
public typealias KResult = String
|
||||
|
||||
public var domain: String {
|
||||
"http://data.krx.co.kr"
|
||||
}
|
||||
public var url: String {
|
||||
"/comm/bldAttendant/getJsonData.cmd"
|
||||
}
|
||||
public var method: Method { .post }
|
||||
|
||||
public var header: [String : String?] {
|
||||
[:]
|
||||
}
|
||||
|
||||
public var body: [String: Any] {
|
||||
return [
|
||||
"idxIndMidclssCd": indexType.rawValue,
|
||||
"trdDb": date.yyyyMMdd,
|
||||
"share": shareType.rawValue,
|
||||
"money": moneyType.rawValue,
|
||||
"csvxls_isNo": false,
|
||||
"bld": "dbms/MDC/STAT/standard/MDCSTAT00601"
|
||||
]
|
||||
}
|
||||
public var result: KResult? = nil
|
||||
|
||||
|
||||
let indexType: IndexType
|
||||
let date: Date
|
||||
let indexType: IndexType
|
||||
let shareType: ShareType
|
||||
let moneyType: MoneyType
|
||||
|
||||
@@ -120,6 +79,306 @@ extension DomesticExtra {
|
||||
self.moneyType = .moneyWon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 한국거래소 - 주가지수 - 지수구성종목
|
||||
///
|
||||
public struct IndexPortfolioRequest: KrxRequest {
|
||||
public typealias KResult = IndexPortfolioResult
|
||||
|
||||
public var domain: String {
|
||||
"http://data.krx.co.kr"
|
||||
}
|
||||
public var url: String {
|
||||
"/comm/bldAttendant/getJsonData.cmd"
|
||||
}
|
||||
public var method: Method { .get }
|
||||
|
||||
public var header: [String : String?] {
|
||||
[:]
|
||||
}
|
||||
|
||||
public var body: [String: Any] {
|
||||
return [
|
||||
"indIdx": indexId,
|
||||
"indIdx2": indexId2,
|
||||
"trdDd": date.yyyyMMdd,
|
||||
"money": moneyType.rawValue,
|
||||
"bld": "dbms/MDC/STAT/standard/MDCSTAT00601"
|
||||
]
|
||||
}
|
||||
public var result: KResult? = nil
|
||||
|
||||
|
||||
let indexId: String
|
||||
let indexId2: String
|
||||
let date: Date
|
||||
let moneyType: MoneyType
|
||||
|
||||
init(indexId: String, indexId2: String, date: Date) {
|
||||
self.indexId = indexId
|
||||
self.indexId2 = indexId2
|
||||
self.date = date
|
||||
self.moneyType = .moneyWon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 모든 지수 이름 가져오기
|
||||
///
|
||||
public struct AllIndicesRequest: KrxRequest {
|
||||
public typealias KResult = AllIndicesResult
|
||||
|
||||
public var domain: String {
|
||||
"http://data.krx.co.kr"
|
||||
}
|
||||
public var url: String {
|
||||
"/comm/bldAttendant/getJsonData.cmd"
|
||||
}
|
||||
public var method: Method { .get }
|
||||
|
||||
public var header: [String : String?] {
|
||||
[:]
|
||||
}
|
||||
public var body: [String: Any] {
|
||||
return [
|
||||
"mktsel": market.rawValue,
|
||||
"bld": "dbms/comm/finder/finder_equidx"
|
||||
]
|
||||
}
|
||||
public var result: KResult? = nil
|
||||
|
||||
|
||||
enum MarketSelection: String {
|
||||
case all = "1"
|
||||
case krx = "2"
|
||||
case kospi = "3"
|
||||
case kosdaq = "4"
|
||||
case theme = "T"
|
||||
}
|
||||
let market: MarketSelection = .all
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension DomesticExtra {
|
||||
|
||||
public struct IndexPriceResult: Codable {
|
||||
/// 전체 지수 가격들
|
||||
public let output: [Output]
|
||||
/// 서버의 시간
|
||||
public let currentDatetime: String /// 2023.06.28 PM 12:06:57
|
||||
|
||||
public var currentDate: Date {
|
||||
currentDatetime.krxDate!
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
case output
|
||||
case currentDatetime = "CURRENT_DATETIME"
|
||||
}
|
||||
|
||||
public struct Output: Codable, PropertyIterable, ArrayDecodable {
|
||||
/// 지수명
|
||||
public let indexName: String
|
||||
/// 지수 종가
|
||||
public let indexClosingPrice: String
|
||||
/// 등락 타입 코드
|
||||
public let fluctuationTypeCode: String
|
||||
/// 전일 대비
|
||||
public let previousDayVariableRatio: String
|
||||
/// 등락율
|
||||
public let fluctuationRate: String
|
||||
/// 지수 시가
|
||||
public let indexOpenningPrice: String
|
||||
/// 지수 고가
|
||||
public let highestIndexPrice: String
|
||||
/// 지수 저가
|
||||
public let lowestIndexPrice: String
|
||||
/// 누적 거래량
|
||||
public let accumulatedVolume: String
|
||||
/// 누적 거래대금
|
||||
public let accumulatedTradingAmount: String
|
||||
/// 자본금
|
||||
public let marketCapital: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
case indexName = "IDX_NM"
|
||||
case indexClosingPrice = "CLSPRC_IDX"
|
||||
case fluctuationTypeCode = "FLUC_TP_CD"
|
||||
case previousDayVariableRatio = "CMPPREVDD_IDX"
|
||||
case fluctuationRate = "FLUC_RT"
|
||||
case indexOpenningPrice = "OPNPRC_IDX"
|
||||
case highestIndexPrice = "HGPRC_IDX"
|
||||
case lowestIndexPrice = "LWPRC_IDX"
|
||||
case accumulatedVolume = "ACC_TRDVOL"
|
||||
case accumulatedTradingAmount = "ACC_TRDVAL"
|
||||
case marketCapital = "MKTCAP"
|
||||
}
|
||||
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 11 else {
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 11)
|
||||
}
|
||||
self.indexName = array[0]
|
||||
self.indexClosingPrice = array[1]
|
||||
self.fluctuationTypeCode = array[2]
|
||||
self.previousDayVariableRatio = array[3]
|
||||
self.fluctuationRate = array[4]
|
||||
self.indexOpenningPrice = array[5]
|
||||
self.highestIndexPrice = array[6]
|
||||
self.lowestIndexPrice = array[7]
|
||||
self.accumulatedVolume = array[8]
|
||||
self.accumulatedTradingAmount = array[9]
|
||||
self.marketCapital = array[10]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! Output(array: Array(repeating: "", count: 11), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct IndexPortfolioResult: Codable {
|
||||
public let output: [Output]
|
||||
/// 서버의 시간
|
||||
public let currentDatetime: String
|
||||
|
||||
public var currentDate: Date {
|
||||
currentDatetime.krxDate!
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
case output = "output"
|
||||
case currentDatetime = "CURRENT_DATETIME"
|
||||
}
|
||||
|
||||
public struct Output: Codable, PropertyIterable, ArrayDecodable {
|
||||
/// 종목코드
|
||||
public let shortProductCode: String
|
||||
/// 종목명
|
||||
public let productName: String
|
||||
/// 종가
|
||||
public let indexClosingPrice: String
|
||||
/// 등락률 타입
|
||||
public let fluctuationTypeCode: String
|
||||
/// 대비 가격
|
||||
public let comparisionPrice: String
|
||||
/// 등락률
|
||||
public let fluctuationRate: String
|
||||
/// 상장시가총액
|
||||
public let marketCapital: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
case shortProductCode = "ISU_SRT_CD"
|
||||
case productName = "ISU_ABBRV"
|
||||
case indexClosingPrice = "TDD_CLSPRC"
|
||||
case fluctuationTypeCode = "FLUC_TP_CD"
|
||||
case comparisionPrice = "STR_CMP_PRC"
|
||||
case fluctuationRate = "FLUC_RT"
|
||||
case marketCapital = "MKTCAP"
|
||||
}
|
||||
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 7 else {
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 7)
|
||||
}
|
||||
self.shortProductCode = array[0]
|
||||
self.productName = array[1]
|
||||
self.indexClosingPrice = array[2]
|
||||
self.fluctuationTypeCode = array[3]
|
||||
self.comparisionPrice = array[4]
|
||||
self.fluctuationRate = array[5]
|
||||
self.marketCapital = array[6]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! Output(array: Array(repeating: "", count: 7), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct AllIndicesResult: Codable {
|
||||
public let block: [Block]
|
||||
/// 서버의 시간
|
||||
public let currentDatetime: String
|
||||
|
||||
public var currentDate: Date {
|
||||
currentDatetime.krxDate!
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
case block = "block1"
|
||||
case currentDatetime = "CURRENT_DATETIME"
|
||||
}
|
||||
|
||||
public struct Block: Codable, PropertyIterable, ArrayDecodable {
|
||||
/// 지수 코드1
|
||||
public let index1Code: String
|
||||
/// 지수 코드2
|
||||
public let index2Code: String
|
||||
/// 상품명
|
||||
public let productName: String
|
||||
/// 지수 시장 코드
|
||||
public let indexMarketCode: KrxMarketCode
|
||||
/// 지수 시장명
|
||||
public let marketName: String
|
||||
|
||||
public var indexFullCode: String {
|
||||
index1Code + index2Code
|
||||
}
|
||||
|
||||
public var indexType: DomesticExtra.IndexType {
|
||||
switch marketName {
|
||||
case "KRX": return .krx
|
||||
case "STK": return .kospi
|
||||
case "KOSDAQ": return .kosdaq
|
||||
case "테마": return .theme
|
||||
default: return .krx
|
||||
}
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
case index1Code = "full_code"
|
||||
case index2Code = "short_code"
|
||||
case productName = "codeName"
|
||||
case indexMarketCode = "marketCode"
|
||||
case marketName
|
||||
}
|
||||
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 5 else {
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 5)
|
||||
}
|
||||
self.index1Code = array[0]
|
||||
self.index2Code = array[1]
|
||||
self.productName = array[2]
|
||||
self.indexMarketCode = KrxMarketCode(rawValue: array[3])!
|
||||
self.marketName = array[4]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! Block(array: Array(repeating: "", count: 5), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,16 +386,14 @@ extension KissAccount {
|
||||
|
||||
/// 전체지수 시세 가져오기
|
||||
///
|
||||
static public func getIndexPrice(indexType: DomesticExtra.IndexType, startDate: Date, endDate: Date) async throws -> String {
|
||||
static public func getIndexPrice(indexType: DomesticExtra.IndexType, date: Date) async throws -> DomesticExtra.IndexPriceResult {
|
||||
return try await withUnsafeThrowingContinuation { continuation in
|
||||
|
||||
let range = DomesticExtra.IndexPriceRequest.DataRange(startDate: startDate, endDate: endDate)
|
||||
|
||||
let request = DomesticExtra.IndexPriceRequest(range: range, indexType: indexType)
|
||||
let request = DomesticExtra.IndexPriceRequest(indexType: indexType, date: date)
|
||||
request.query { result in
|
||||
switch result {
|
||||
case .success(let result):
|
||||
continuation.resume(returning: (result))
|
||||
continuation.resume(returning: result)
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
@@ -146,14 +403,31 @@ extension KissAccount {
|
||||
|
||||
/// 지수구성종목 가져오기
|
||||
///
|
||||
static public func getIndexPortfolio(indexType: DomesticExtra.IndexType, date: Date) async throws -> String {
|
||||
static public func getIndexPortfolio(indexId: String, indexId2: String, date: Date) async throws -> DomesticExtra.IndexPortfolioResult {
|
||||
return try await withUnsafeThrowingContinuation { continuation in
|
||||
|
||||
let request = DomesticExtra.IndexPortfolioRequest(indexType: indexType, date: date)
|
||||
let request = DomesticExtra.IndexPortfolioRequest(indexId: indexId, indexId2: indexId2, date: date)
|
||||
request.query { result in
|
||||
switch result {
|
||||
case .success(let result):
|
||||
continuation.resume(returning: (result))
|
||||
continuation.resume(returning: result)
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 모든 지수 이름 가져오기
|
||||
///
|
||||
static public func getAllIndices() async throws -> DomesticExtra.AllIndicesResult {
|
||||
return try await withUnsafeThrowingContinuation { continuation in
|
||||
|
||||
let request = DomesticExtra.AllIndicesRequest()
|
||||
request.query { result in
|
||||
switch result {
|
||||
case .success(let result):
|
||||
continuation.resume(returning: result)
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import Foundation
|
||||
|
||||
public struct DomesticExtra {
|
||||
public typealias Shorts = ShortSellingBalanceResult.OutBlock
|
||||
public typealias IndexProduct = AllIndicesResult.Block
|
||||
}
|
||||
|
||||
protocol KrxRequest: Request {
|
||||
@@ -66,6 +67,17 @@ extension DomesticExtra {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum KrxMarketCode: String, Codable {
|
||||
case undefined = ""
|
||||
|
||||
case stock = "STK"
|
||||
case kosdaq = "KSQ"
|
||||
case krx = "KRX"
|
||||
case gbl = "GBL"
|
||||
}
|
||||
|
||||
|
||||
extension DomesticExtra {
|
||||
|
||||
public struct ShortSellingBalanceResult: Codable {
|
||||
@@ -73,11 +85,11 @@ extension DomesticExtra {
|
||||
public let block: [Block]?
|
||||
/// 개발 상품의 기간별 잔고
|
||||
public let outBlock: [OutBlock]?
|
||||
public let currentDatetime: String
|
||||
/// 서버의 시간
|
||||
public let currentDatetime: String /// 2023.06.11 PM 07:26:14
|
||||
|
||||
public var currentDate: Date {
|
||||
// 2023.06.11 PM 07:26:14
|
||||
return Date()
|
||||
currentDatetime.krxDate!
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
@@ -87,21 +99,22 @@ extension DomesticExtra {
|
||||
}
|
||||
|
||||
public struct Block: Codable {
|
||||
/// 전체 상품코드
|
||||
public let fullCode: String
|
||||
/// 상품번호
|
||||
public let shortCode: String
|
||||
/// 지수 코드1
|
||||
public let index1Code: String
|
||||
/// 지수 코드2
|
||||
public let index2Code: String
|
||||
/// 상품명
|
||||
public let productName: String
|
||||
///
|
||||
public let marketCode: String
|
||||
/// 지수 시장 코드
|
||||
public let indexMarketCode: KrxMarketCode
|
||||
/// 시장명
|
||||
public let marketName: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
case fullCode = "full_code"
|
||||
case shortCode = "short_code"
|
||||
case index1Code = "full_code"
|
||||
case index2Code = "short_code"
|
||||
case productName = "codeName"
|
||||
case marketCode
|
||||
case indexMarketCode = "marketCode"
|
||||
case marketName
|
||||
}
|
||||
}
|
||||
@@ -116,7 +129,7 @@ extension DomesticExtra {
|
||||
/// 공매도 잔고 금액
|
||||
public let shortSellingBalanceAmount: String
|
||||
/// 시가총액
|
||||
public let totalMarketValue: String
|
||||
public let marketCapital: String
|
||||
/// 공매도 잔고 비중
|
||||
public let shortSellingBalanceRatio: String
|
||||
|
||||
@@ -125,7 +138,7 @@ extension DomesticExtra {
|
||||
case shortSellingBalanceQuantity = "BAL_QTY" /// 29,330
|
||||
case listedStockCount = "LIST_SHRS" /// 46,822,295
|
||||
case shortSellingBalanceAmount = "BAL_AMT" /// 130,518,500
|
||||
case totalMarketValue = "MKTCAP" /// 208,359,212,750
|
||||
case marketCapital = "MKTCAP" /// 208,359,212,750
|
||||
case shortSellingBalanceRatio = "BAL_RTO" /// 0.06
|
||||
}
|
||||
|
||||
@@ -148,9 +161,9 @@ extension DomesticExtra {
|
||||
shortSellingBalanceAmount.removeAll(where: { $0 == "," })
|
||||
self.shortSellingBalanceAmount = shortSellingBalanceAmount
|
||||
|
||||
var totalMarketValue = try container.decode(String.self, forKey: DomesticExtra.ShortSellingBalanceResult.OutBlock.CodingKeys.totalMarketValue)
|
||||
totalMarketValue.removeAll(where: { $0 == "," })
|
||||
self.totalMarketValue = totalMarketValue
|
||||
var marketCapital = try container.decode(String.self, forKey: DomesticExtra.ShortSellingBalanceResult.OutBlock.CodingKeys.marketCapital)
|
||||
marketCapital.removeAll(where: { $0 == "," })
|
||||
self.marketCapital = marketCapital
|
||||
|
||||
self.shortSellingBalanceRatio = try container.decode(String.self, forKey: DomesticExtra.ShortSellingBalanceResult.OutBlock.CodingKeys.shortSellingBalanceRatio)
|
||||
}
|
||||
@@ -163,9 +176,18 @@ extension DomesticExtra {
|
||||
self.shortSellingBalanceQuantity = array[1]
|
||||
self.listedStockCount = array[2]
|
||||
self.shortSellingBalanceAmount = array[3]
|
||||
self.totalMarketValue = array[4]
|
||||
self.marketCapital = array[4]
|
||||
self.shortSellingBalanceRatio = array[5]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutBlock(array: Array(repeating: "", count: 6), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,33 @@ extension KissConsole {
|
||||
createSubpath(subPath)
|
||||
return fileUrl
|
||||
}
|
||||
|
||||
static func indexPriceFileUrl(date: Date) -> URL {
|
||||
let subPath = "data/index/\(date.yyyyMMdd)"
|
||||
let subFile = "\(subPath)/price-\(date.yyyyMMdd)-\(date.HHmmss).csv"
|
||||
|
||||
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
||||
createSubpath(subPath)
|
||||
return fileUrl
|
||||
}
|
||||
|
||||
static func indexPortfolioFileUrl(date: Date, type: DomesticExtra.IndexType, indexFullCode: String) -> URL {
|
||||
let subPath = "data/index/\(date.yyyyMMdd)"
|
||||
let subFile = "\(subPath)/portfolio-\(type.name)-\(indexFullCode).csv"
|
||||
|
||||
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
||||
createSubpath(subPath)
|
||||
return fileUrl
|
||||
}
|
||||
|
||||
static func indexProductsFileUrl() -> URL {
|
||||
let subPath = "data"
|
||||
let subFile = "\(subPath)/index-products.csv"
|
||||
|
||||
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
||||
createSubpath(subPath)
|
||||
return fileUrl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ let PreferredShortsTPS: UInt64 = 5
|
||||
/// Limit to request a top query
|
||||
let PreferredTopTPS: UInt64 = 5
|
||||
|
||||
/// Limit to reqeust a index query
|
||||
let PreferredIndexTPS: UInt64 = 5
|
||||
|
||||
/// Limit to request a investor query
|
||||
let PreferredInvestorTPS: UInt64 = 19
|
||||
|
||||
|
||||
@@ -10,8 +10,13 @@ import KissMe
|
||||
|
||||
|
||||
class KissConsole: KissMe.ShopContext {
|
||||
/// 인증 관련 json 정보
|
||||
private var credential: Credential? = nil
|
||||
|
||||
/// 한국투자증권 개인 계정
|
||||
var account: KissAccount? = nil
|
||||
|
||||
/// 주식시장 거래 상품 목록들
|
||||
private var shop: KissShop? = nil
|
||||
|
||||
/// 현재 candle 파일로 저장 중인 productNo
|
||||
@@ -20,6 +25,8 @@ class KissConsole: KissMe.ShopContext {
|
||||
/// CSV 파일을 저장할 때, field name 에 대해서 한글(true) 또는 영문(false)로 기록할지 설정
|
||||
var localized: Bool = false
|
||||
|
||||
var indexContext: IndexContext
|
||||
|
||||
|
||||
private enum KissCommand: String {
|
||||
case quit = "quit"
|
||||
@@ -62,13 +69,16 @@ class KissConsole: KissMe.ShopContext {
|
||||
|
||||
// KRX 지수 열람
|
||||
case index = "index"
|
||||
case indexAll = "index all"
|
||||
case indexPortfolio = "index portfolio"
|
||||
|
||||
// 종목 열람
|
||||
case loadShop = "load shop"
|
||||
case updateShop = "update shop"
|
||||
case look = "look"
|
||||
|
||||
case loadIndex = "load index"
|
||||
case updateIndex = "update index"
|
||||
|
||||
// 휴장일
|
||||
case holiday = "holiday"
|
||||
|
||||
@@ -99,10 +109,12 @@ class KissConsole: KissMe.ShopContext {
|
||||
return false
|
||||
case .investor, .investorAll:
|
||||
return true
|
||||
case .shorts, .shortsAll, .index, .indexAll:
|
||||
case .shorts, .shortsAll, .index, .indexPortfolio:
|
||||
return false
|
||||
case .loadShop, .updateShop, .look, .holiday:
|
||||
return false
|
||||
case .loadIndex, .updateIndex:
|
||||
return false
|
||||
case .showcase:
|
||||
return false
|
||||
case .loves, .love, .hate, .localizeNames, .localizeOnOff:
|
||||
@@ -124,6 +136,8 @@ class KissConsole: KissMe.ShopContext {
|
||||
KissConsole.createSubpath("log")
|
||||
KissConsole.createSubpath("data")
|
||||
|
||||
indexContext = IndexContext()
|
||||
|
||||
super.init()
|
||||
lastLogin()
|
||||
loadLocalName()
|
||||
@@ -131,6 +145,7 @@ class KissConsole: KissMe.ShopContext {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
Task {
|
||||
await onLoadShop()
|
||||
await onLoadIndex()
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
@@ -233,13 +248,16 @@ class KissConsole: KissMe.ShopContext {
|
||||
case .shortsAll: onShortsAll(args)
|
||||
|
||||
case .index: await onIndex(args)
|
||||
case .indexAll: onIndexAll(args)
|
||||
case .indexPortfolio: await onIndexPortfolio(args)
|
||||
|
||||
case .loadShop: await onLoadShop()
|
||||
case .updateShop: await onUpdateShop()
|
||||
case .look: await onLook(args)
|
||||
case .holiday: await onHoliday(args)
|
||||
|
||||
case .loadIndex: await onLoadIndex()
|
||||
case .updateIndex: await onUpdateIndex()
|
||||
|
||||
case .showcase: await onShowcase()
|
||||
case .loves: await onLoves()
|
||||
case .love: await onLove(args)
|
||||
@@ -853,16 +871,47 @@ extension KissConsole {
|
||||
return
|
||||
}
|
||||
do {
|
||||
// TODO: Work more
|
||||
// _ = try await getIndexPrice(indexType, startDate: , endDate: )
|
||||
let curDate = Date()
|
||||
if curDate.isBeforeMarketOpenning {
|
||||
print("Before market openning")
|
||||
return
|
||||
}
|
||||
let result = try await KissAccount.getIndexPrice(indexType: indexType, date: curDate)
|
||||
guard result.output.isEmpty == false else {
|
||||
print("empty result")
|
||||
return
|
||||
}
|
||||
|
||||
let fileUrl = KissConsole.indexPriceFileUrl(date: result.currentDate)
|
||||
try result.output.writeCsv(toFile: fileUrl, localized: localized)
|
||||
print("DONE \(result.output.count)")
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func onIndexAll(_ args: [String]) {
|
||||
// TODO: Work more
|
||||
private func onIndexPortfolio(_ args: [String]) async {
|
||||
do {
|
||||
let indices = indexContext.getAllIndices()
|
||||
let date = Date()
|
||||
|
||||
for index in indices {
|
||||
let result = try await KissAccount.getIndexPortfolio(indexId: index.index1Code, indexId2: index.index2Code, date: date)
|
||||
guard result.output.isEmpty == false else {
|
||||
print("empty result on \(index.indexFullCode)")
|
||||
continue
|
||||
}
|
||||
|
||||
let fileUrl = KissConsole.indexPortfolioFileUrl(date: date, type: index.indexType, indexFullCode: index.indexFullCode)
|
||||
try result.output.writeCsv(toFile: fileUrl, localized: localized)
|
||||
print("DONE \(result.output.count) \(index.indexFullCode)")
|
||||
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000 / PreferredIndexTPS)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -915,6 +964,28 @@ extension KissConsole {
|
||||
}
|
||||
|
||||
|
||||
private func onLoadIndex() async {
|
||||
return await withUnsafeContinuation { continuation in
|
||||
self.indexContext.loadIndex(url: KissConsole.indexProductsFileUrl(), loggable: true)
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func onUpdateIndex() async {
|
||||
do {
|
||||
let result = try await KissAccount.getAllIndices()
|
||||
if result.block.isEmpty == false {
|
||||
let fileUrl = KissConsole.indexProductsFileUrl()
|
||||
try result.block.writeCsv(toFile: fileUrl, localized: localized)
|
||||
}
|
||||
print("DONE \(result.block.count)")
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getAllProduct(baseDate: Date) async -> [DomesticShop.Product] {
|
||||
var pageNo = 0
|
||||
var shopItems = [DomesticShop.Product]()
|
||||
@@ -1085,6 +1156,10 @@ extension KissConsole {
|
||||
symbols.formUnion(CurrentPriceResult.OutputDetail.symbols())
|
||||
symbols.formUnion(CapturePrice.symbols())
|
||||
symbols.formUnion(InvestorVolumeResult.OutputDetail.symbols())
|
||||
symbols.formUnion(DomesticExtra.IndexPriceResult.Output.symbols())
|
||||
symbols.formUnion(DomesticExtra.IndexPortfolioResult.Output.symbols())
|
||||
symbols.formUnion(DomesticExtra.AllIndicesResult.Block.symbols())
|
||||
symbols.formUnion(DomesticExtra.ShortSellingBalanceResult.OutBlock.symbols())
|
||||
let newNames = symbols.sorted(by: { $0 < $1 })
|
||||
|
||||
let nameUrl = KissConsole.localNamesUrl
|
||||
|
||||
@@ -15,15 +15,6 @@ enum RunMode: String {
|
||||
}
|
||||
|
||||
|
||||
struct Param: Codable {
|
||||
|
||||
/// simulator 일 경우에 참고하는 날짜 정보 (begin ~ end)
|
||||
///
|
||||
let beginDate: String // yyyyMMdd HHmmss
|
||||
let endDate: String // yyyyMMdd HHmmss
|
||||
}
|
||||
|
||||
|
||||
struct Model: Codable {
|
||||
let indexSets: [IndexSet]
|
||||
|
||||
|
||||
2
bin/data
2
bin/data
Submodule bin/data updated: 8ecb2d68fe...1f53137aef
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
340A4DBD2A4C34BE005A1FBA /* IndexContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340A4DBC2A4C34BE005A1FBA /* IndexContext.swift */; };
|
||||
341F5EB02A0A80EC00962D48 /* KissMe.docc in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EAF2A0A80EC00962D48 /* KissMe.docc */; };
|
||||
341F5EB62A0A80EC00962D48 /* KissMe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 341F5EAB2A0A80EC00962D48 /* KissMe.framework */; };
|
||||
341F5EBB2A0A80EC00962D48 /* KissMeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EBA2A0A80EC00962D48 /* KissMeTests.swift */; };
|
||||
@@ -53,6 +54,7 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
340A4DBC2A4C34BE005A1FBA /* IndexContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexContext.swift; sourceTree = "<group>"; };
|
||||
341F5EAB2A0A80EC00962D48 /* KissMe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KissMe.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
341F5EAE2A0A80EC00962D48 /* KissMe.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KissMe.h; sourceTree = "<group>"; };
|
||||
341F5EAF2A0A80EC00962D48 /* KissMe.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; name = KissMe.docc; path = ../KissMe.docc; sourceTree = "<group>"; };
|
||||
@@ -242,6 +244,7 @@
|
||||
children = (
|
||||
34F190102A4394EB0068C697 /* LocalContext.swift */,
|
||||
34F1900E2A426D150068C697 /* ShopContext.swift */,
|
||||
340A4DBC2A4C34BE005A1FBA /* IndexContext.swift */,
|
||||
);
|
||||
path = Context;
|
||||
sourceTree = "<group>";
|
||||
@@ -383,6 +386,7 @@
|
||||
341F5EF92A0F907300962D48 /* DomesticStockPriceResult.swift in Sources */,
|
||||
341F5EE12A0F373B00962D48 /* Login.swift in Sources */,
|
||||
341F5EF52A0F891200962D48 /* KissAccount.swift in Sources */,
|
||||
340A4DBD2A4C34BE005A1FBA /* IndexContext.swift in Sources */,
|
||||
34E7B9112A49BD2800B3AB9F /* DomesticIndex.swift in Sources */,
|
||||
341F5F0D2A15222E00962D48 /* AuthRequest.swift in Sources */,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user