Moving code into kissme framework
This commit is contained in:
@@ -8,12 +8,6 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
enum KissWebSocketSubscription: String, Codable {
|
||||
case subscribed = "1"
|
||||
case unsubscribed = "2"
|
||||
}
|
||||
|
||||
|
||||
// MARK: ContractPriceWebSocket
|
||||
|
||||
extension Domestic {
|
||||
@@ -66,98 +60,3 @@ extension Domestic.ContractPriceWebSocket.Event: URLSessionWebSocketDelegate {
|
||||
print("disconnected...")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Domestic {
|
||||
|
||||
struct TypeResponse: Codable {
|
||||
let header: Header
|
||||
|
||||
struct Header: Codable {
|
||||
let trId: String
|
||||
/// yyyyMMddHHmmss
|
||||
let dateTime: String?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case trId = "tr_id"
|
||||
case dateTime = "datetime"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SubscriptionRequest: Codable {
|
||||
let header: Header
|
||||
let body: Body
|
||||
|
||||
struct Header: Codable {
|
||||
let approvalKey: String
|
||||
let customerType: CustomerType
|
||||
let trType: KissWebSocketSubscription
|
||||
let contentType: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case approvalKey = "approval_key"
|
||||
case customerType = "custtype"
|
||||
case trType = "tr_type"
|
||||
case contentType = "content-type"
|
||||
}
|
||||
}
|
||||
|
||||
struct Body: Codable {
|
||||
let input: Input
|
||||
}
|
||||
|
||||
struct Input: Codable {
|
||||
let trId: String
|
||||
let trKey: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case trId = "tr_id"
|
||||
case trKey = "tr_key"
|
||||
}
|
||||
}
|
||||
|
||||
init(approvalKey: String, type: KissWebSocketSubscription, trId: String, trKey: String) {
|
||||
|
||||
header = Header(approvalKey: approvalKey, customerType: .personal, trType: type, contentType: "utf-8")
|
||||
body = Body(input: Input(trId: trId, trKey: trKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct SubscriptionResult: Codable {
|
||||
let header: Header
|
||||
let body: Body
|
||||
|
||||
struct Header: Codable {
|
||||
let trId: String
|
||||
let trKey: String
|
||||
let encrypted: YesNo
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case trId = "tr_id"
|
||||
case trKey = "tr_key"
|
||||
case encrypted = "encrypt"
|
||||
}
|
||||
}
|
||||
|
||||
struct Body: Codable {
|
||||
let resultCode: String
|
||||
let messageCode: String
|
||||
let message: String
|
||||
let output: Output?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case resultCode = "rt_cd"
|
||||
case messageCode = "msg_cd"
|
||||
case message = "msg1"
|
||||
case output
|
||||
}
|
||||
}
|
||||
|
||||
struct Output: Codable {
|
||||
let iv: String
|
||||
let key: String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
751
KissMe/Sources/Domestic/Realtime/Domestic.WebSocketData.swift
Normal file
751
KissMe/Sources/Domestic/Realtime/Domestic.WebSocketData.swift
Normal file
@@ -0,0 +1,751 @@
|
||||
//
|
||||
// Domestic.WebSocketResponse.swift
|
||||
// KissMe
|
||||
//
|
||||
// Created by ened-book-m1 on 2023/08/26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
extension Domestic {
|
||||
|
||||
enum KissWebSocketSubscription: String, Codable {
|
||||
case subscribed = "1"
|
||||
case unsubscribed = "2"
|
||||
}
|
||||
|
||||
|
||||
struct SubscriptionRequest: Codable {
|
||||
let header: Header
|
||||
let body: Body
|
||||
|
||||
struct Header: Codable {
|
||||
let approvalKey: String
|
||||
let customerType: CustomerType
|
||||
let trType: KissWebSocketSubscription
|
||||
let contentType: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case approvalKey = "approval_key"
|
||||
case customerType = "custtype"
|
||||
case trType = "tr_type"
|
||||
case contentType = "content-type"
|
||||
}
|
||||
}
|
||||
|
||||
struct Body: Codable {
|
||||
let input: Input
|
||||
}
|
||||
|
||||
struct Input: Codable {
|
||||
let trId: String
|
||||
let trKey: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case trId = "tr_id"
|
||||
case trKey = "tr_key"
|
||||
}
|
||||
}
|
||||
|
||||
init(approvalKey: String, type: KissWebSocketSubscription, trId: String, trKey: String) {
|
||||
|
||||
header = Header(approvalKey: approvalKey, customerType: .personal, trType: type, contentType: "utf-8")
|
||||
body = Body(input: Input(trId: trId, trKey: trKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct GeneralResult: Codable {
|
||||
let header: Header
|
||||
|
||||
struct Header: Codable {
|
||||
let trId: String
|
||||
/// yyyyMMddHHmmss
|
||||
let dateTime: String?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case trId = "tr_id"
|
||||
case dateTime = "datetime"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct SubscriptionResult: Codable {
|
||||
let header: Header
|
||||
let body: Body
|
||||
|
||||
struct Header: Codable {
|
||||
let trId: String
|
||||
let trKey: String
|
||||
let encrypted: YesNo
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case trId = "tr_id"
|
||||
case trKey = "tr_key"
|
||||
case encrypted = "encrypt"
|
||||
}
|
||||
}
|
||||
|
||||
struct Body: Codable {
|
||||
let resultCode: String
|
||||
let messageCode: String
|
||||
let message: String
|
||||
let output: Output?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case resultCode = "rt_cd"
|
||||
case messageCode = "msg_cd"
|
||||
case message = "msg1"
|
||||
case output
|
||||
}
|
||||
}
|
||||
|
||||
struct Output: Codable {
|
||||
let iv: String
|
||||
let key: String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Domestic {
|
||||
|
||||
public enum VariableRatioSignType: UInt8 {
|
||||
/// 상한
|
||||
case upLimit = 1
|
||||
/// 상승
|
||||
case up = 2
|
||||
/// 보합
|
||||
case noChange = 3
|
||||
/// 하한
|
||||
case downLimit = 4
|
||||
/// 하락
|
||||
case down = 5
|
||||
}
|
||||
|
||||
|
||||
public enum ConclusionType: UInt8 {
|
||||
/// 매수(+)
|
||||
case buying = 1
|
||||
/// 장전
|
||||
case load = 3
|
||||
/// 매도(-)
|
||||
case selling = 5
|
||||
}
|
||||
|
||||
|
||||
public enum HourClassCode: Character {
|
||||
/// 장중
|
||||
case onMarket = "0"
|
||||
/// 장후예상
|
||||
case afterMarket = "A"
|
||||
/// 장전예상
|
||||
case beforeMarket = "B"
|
||||
/// 9시이후의 예상가, VI발동
|
||||
case viInvoking = "C"
|
||||
/// 시간외 단일가 예상
|
||||
case overTime_SinglePrice = "D"
|
||||
}
|
||||
|
||||
|
||||
public typealias MarketOperationCode = String
|
||||
|
||||
|
||||
public enum ContractClass: String {
|
||||
/// 매도
|
||||
case selling = "01"
|
||||
/// 매수
|
||||
case buying = "02"
|
||||
}
|
||||
|
||||
|
||||
public enum OrderClass: String {
|
||||
/// 지정가
|
||||
case limits = "00"
|
||||
/// 시장가
|
||||
case marketPrice = "01"
|
||||
/// 조건부지정가
|
||||
case conditionalLimits = "02"
|
||||
/// 최유리지정가
|
||||
case mostFavorLimits = "03"
|
||||
/// 최우선지정가
|
||||
case mostPriorLimits = "04"
|
||||
/// 장전 시간외
|
||||
case overTime_BeforeMarket = "05"
|
||||
/// 장후 시간외
|
||||
case overTime_AfterMarket = "06"
|
||||
/// 시간외 단일가
|
||||
case overTime_SinglePrice = "07"
|
||||
/// 자기주식
|
||||
case treasury = "08"
|
||||
/// 자기주식S-Option
|
||||
case treasury_SOption = "09"
|
||||
/// 자기주식금전신탁
|
||||
case treasury_MoneyTrust = "10"
|
||||
/// IOC지정가 (즉시체결,잔량취소)
|
||||
case iocLimits_Conclusion_CancelResidualQuantity = "11"
|
||||
/// FOK지정가 (즉시체결,전량취소)
|
||||
case fokLimits_Conclusion_CancelEntireQuantity = "12"
|
||||
/// IOC시장가 (즉시체결,잔량취소)
|
||||
case iocMarketPrice_Conclusion_CancelResidualQuantity = "13"
|
||||
/// FOK시장가 (즉시체결,전량취소)
|
||||
case fokMarketPrice_Conclusion_CancelEntireQuantity = "14"
|
||||
/// IOC최유리 (즉시체결,잔량취소)
|
||||
case iocMostFavor_Conclusion_CancelResidualQuantity = "15"
|
||||
/// FOK최유리 (즉시체결,전량취소)
|
||||
case fokMostFavor_Conclusion_CancelEntireQuantity = "16"
|
||||
}
|
||||
|
||||
|
||||
public enum RefuseYesNo: Int {
|
||||
/// 승인
|
||||
case approval = 0
|
||||
/// 거부
|
||||
case refuse = 1
|
||||
}
|
||||
|
||||
|
||||
public enum ConclusionYesNo: Int {
|
||||
/// 주문,정정,취소,거부,
|
||||
case unconclused = 1
|
||||
/// 체결 (★ 체결만 보실경우 2번만 보시면 됩니다)
|
||||
case conclused = 2
|
||||
}
|
||||
|
||||
|
||||
public enum AcceptYesNo: Int {
|
||||
/// 주문접수
|
||||
case orderAccept = 1
|
||||
/// 확인
|
||||
case confirm = 2
|
||||
/// 취소(FOK/IOC)
|
||||
case cancel = 3
|
||||
}
|
||||
|
||||
|
||||
public struct ContractPrice {
|
||||
/// 유가증권 단축 종목코드
|
||||
public let shortCode: String
|
||||
/// 주식 체결 시간 (HHmmss)
|
||||
public let conclusionTime: String
|
||||
/// 주식 현재가
|
||||
public let currentPrice: Int
|
||||
/// 전일 대비 부호
|
||||
public let previousDayVariableRatioSign: VariableRatioSignType
|
||||
/// 전일 대비
|
||||
public let previousDayVariableRatio: Double
|
||||
/// 전일 대비율
|
||||
public let previousDayDiffRatio: Double
|
||||
/// 가중 평균 주식 가격
|
||||
public let weightedAveragePrice: Int
|
||||
/// 주식 시가
|
||||
public let openningPrice: Int
|
||||
/// 주식 최고가
|
||||
public let highestPrice: Int
|
||||
/// 주식 최저가
|
||||
public let lowestPrice: Int
|
||||
/// 매도호가1
|
||||
public let askingPrice: Int
|
||||
/// 매수호가1
|
||||
public let biddingPrice: Int
|
||||
/// 체결 거래량
|
||||
public let conclusionVolume: Int
|
||||
/// 누적 거래량
|
||||
public let accumulatedVolume: Int
|
||||
/// 누적 거래 대금
|
||||
public let accumulatedTradingAmount: Int
|
||||
/// 매도 체결 건수
|
||||
public let sellingConclusionCount: Int
|
||||
/// 매수 체결 건수
|
||||
public let buyingConclusionCount: Int
|
||||
/// 순매수 체결 건수
|
||||
public let netBuyingConclusionCount: Int
|
||||
/// 체결강도
|
||||
public let conclusionStrength: Double
|
||||
/// 총 매도 수량
|
||||
public let totalSellingQuantity: Int
|
||||
/// 총 매수 수량
|
||||
public let totalBuyingQuantity: Int
|
||||
/// 체결구분
|
||||
public let conclusionType: ConclusionType
|
||||
/// 매수비율
|
||||
public let buyingRatio: Double
|
||||
/// 전일 거래량 대비 등락율
|
||||
public let previousDay_VolumeDiff_FluctuationRate: Double
|
||||
/// 시가 시간
|
||||
public let openningPriceHour: String
|
||||
/// 시가대비구분
|
||||
public let openningPrice_VariableRatioSign: VariableRatioSignType
|
||||
/// 시가대비
|
||||
public let openningPrice_VariableRatio: Int
|
||||
/// 최고가 시간
|
||||
public let highestPriceHour: String
|
||||
/// 고가대비구분
|
||||
public let highPrice_VariableRatioSign: VariableRatioSignType
|
||||
/// 고가대비
|
||||
public let highPrice_VariableRatio: Int
|
||||
/// 최저가 시간
|
||||
public let lowestPriceHour: String
|
||||
/// 저가대비구분
|
||||
public let lowPrice_VariableRatioSign: VariableRatioSignType
|
||||
/// 저가대비
|
||||
public let lowPrice_VariableRatio: Int
|
||||
/// 영업 일자
|
||||
public let businessDate: String
|
||||
/// 신 장운영 구분 코드
|
||||
public let marketOperationCode: MarketOperationCode
|
||||
/// 거래정지 여부
|
||||
public let tradeStopped: Bool
|
||||
/// 매도호가 잔량1
|
||||
public let askingPrice_ResidualQuantity: Int
|
||||
/// 매수호가 잔량1
|
||||
public let biddingPrice_ResidualQuantity: Int
|
||||
/// 총 매도호가 잔량
|
||||
public let askingPrice_TotalResidualQuantity: Int
|
||||
/// 총 매수호가 잔량
|
||||
public let biddingPrice_TotalResidualQuantity: Int
|
||||
/// 거래량 회전율
|
||||
public let volumeTurnoverRate: Double
|
||||
/// 전일 동시간 누적 거래량
|
||||
public let previousDaySameTime_AccumulatedTradingQuantity: Int
|
||||
/// 전일 동시간 누적 거래량 비율
|
||||
public let previousDaySameTime_AccumulatedTradingQuantityRatio: Double
|
||||
/// 시간 구분 코드
|
||||
public let hourClassCode: HourClassCode
|
||||
/// 임의종료구분코드
|
||||
public let marketTerminationCode: String
|
||||
/// 정적VI발동기준가
|
||||
public let viStandardPrice: Int
|
||||
|
||||
|
||||
static let propertiesCount = 46
|
||||
|
||||
public init(array: [Substring], source: Substring) throws {
|
||||
guard array.count == ContractPrice.propertiesCount else {
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, ContractPrice.propertiesCount)
|
||||
}
|
||||
|
||||
self.shortCode = String(array[0])
|
||||
self.conclusionTime = String(array[1])
|
||||
self.currentPrice = Int(array[2]) ?? 0
|
||||
self.previousDayVariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[3])!)!
|
||||
self.previousDayVariableRatio = Double(array[4]) ?? 0
|
||||
self.previousDayDiffRatio = Double(array[5]) ?? 0
|
||||
self.weightedAveragePrice = Int(array[6]) ?? 0
|
||||
self.openningPrice = Int(array[7]) ?? 0
|
||||
self.highestPrice = Int(array[8]) ?? 0
|
||||
self.lowestPrice = Int(array[9]) ?? 0
|
||||
self.askingPrice = Int(array[10]) ?? 0
|
||||
self.biddingPrice = Int(array[11]) ?? 0
|
||||
self.conclusionVolume = Int(array[12]) ?? 0
|
||||
self.accumulatedVolume = Int(array[13]) ?? 0
|
||||
self.accumulatedTradingAmount = Int(array[14]) ?? 0
|
||||
self.sellingConclusionCount = Int(array[15]) ?? 0
|
||||
self.buyingConclusionCount = Int(array[16]) ?? 0
|
||||
self.netBuyingConclusionCount = Int(array[17]) ?? 0
|
||||
self.conclusionStrength = Double(array[18]) ?? 0
|
||||
self.totalSellingQuantity = Int(array[19]) ?? 0
|
||||
self.totalBuyingQuantity = Int(array[20]) ?? 0
|
||||
self.conclusionType = ConclusionType(rawValue: UInt8(array[21])!)!
|
||||
self.buyingRatio = Double(array[22]) ?? 0
|
||||
self.previousDay_VolumeDiff_FluctuationRate = Double(array[23]) ?? 0
|
||||
self.openningPriceHour = String(array[24])
|
||||
self.openningPrice_VariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[25])!)!
|
||||
self.openningPrice_VariableRatio = Int(array[26]) ?? 0
|
||||
self.highestPriceHour = String(array[27])
|
||||
self.highPrice_VariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[28])!)!
|
||||
self.highPrice_VariableRatio = Int(array[29]) ?? 0
|
||||
self.lowestPriceHour = String(array[30])
|
||||
self.lowPrice_VariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[31])!)!
|
||||
self.lowPrice_VariableRatio = Int(array[32]) ?? 0
|
||||
self.businessDate = String(array[33])
|
||||
self.marketOperationCode = String(array[34])
|
||||
self.tradeStopped = String(array[35]) == "Y"
|
||||
self.askingPrice_ResidualQuantity = Int(array[36]) ?? 0
|
||||
self.biddingPrice_ResidualQuantity = Int(array[37]) ?? 0
|
||||
self.askingPrice_TotalResidualQuantity = Int(array[38]) ?? 0
|
||||
self.biddingPrice_TotalResidualQuantity = Int(array[39]) ?? 0
|
||||
self.volumeTurnoverRate = Double(array[40]) ?? 0
|
||||
self.previousDaySameTime_AccumulatedTradingQuantity = Int(array[41]) ?? 0
|
||||
self.previousDaySameTime_AccumulatedTradingQuantityRatio = Double(array[42]) ?? 0
|
||||
self.hourClassCode = HourClassCode(rawValue: String(array[43]).first!)!
|
||||
self.marketTerminationCode = String(array[44])
|
||||
self.viStandardPrice = Int(array[45]) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct AskingPrice {
|
||||
/// 유가증권 단축 종목코드
|
||||
public let shortCode: String
|
||||
/// 영업 시간
|
||||
public let businessTime: String
|
||||
/// 시간 구분 코드
|
||||
public let hourClassCode: HourClassCode
|
||||
/// 매도호가1 ~ 매도호가10
|
||||
public let askingPrices: [Int]
|
||||
/// 매수호가1 ~ 매수호가10
|
||||
public let biddingPrices: [Int]
|
||||
/// 매도호가 잔량1 ~ 매도호가 잔량4
|
||||
public let askingPriceVolumes: [Int]
|
||||
/// 매수호가 잔량1 ~ 매수호가 잔량10
|
||||
public let biddingPriceVolumes: [Int]
|
||||
/// 총 매도호가 잔량
|
||||
public let askingPrice_TotalResidualQuantity: Int
|
||||
/// 총 매수호가 잔량
|
||||
public let biddingPrice_TotalResidualQuantity: Int
|
||||
/// 시간외 총 매도호가 잔량
|
||||
public let askingPrice_Overtime_TotalResidualQuantity: Int
|
||||
/// 시간외 총 매수호가 잔량
|
||||
public let biddingPrice_Overtime_TotalResidualQuantity: Int
|
||||
/// 예상 체결가
|
||||
public let expectedConclusionPrice: Int
|
||||
/// 예상 체결량
|
||||
public let expectedConclusionQuantity: Int
|
||||
/// 예상 거래량
|
||||
public let expectedVolume: Int
|
||||
/// 예상 체결 대비
|
||||
public let expectedConclusion_VariableRatio: Double
|
||||
/// 예상 체결 대비 부호
|
||||
public let expectedConclusion_VariableRatioSign: VariableRatioSignType
|
||||
/// 예상 체결 전일 대비율
|
||||
public let expectedConclusion_PreviousDayDiffRatio: Double
|
||||
/// 누적 거래량
|
||||
public let accumulatedVolume: Int
|
||||
/// 총 매도호가 잔량 증감
|
||||
public let askingPrice_TotalResidualQuantity_IncreaseDecrease: Int
|
||||
/// 총 매수호가 잔량 증감
|
||||
public let biddingPrice_TotalResidualQuantity_IncreaseDecrease: Int
|
||||
/// 시간외 총 매도호가 증감
|
||||
public let askingPrice_Overtime_TotalResidualQuantity_IncreaseDecrease: Int
|
||||
/// 시간외 총 매수호가 증감
|
||||
public let biddingPrice_Overtime_TotalResidualQuantity_IncreaseDecrease: Int
|
||||
/// 주식 매매 구분 코드
|
||||
public let tradeCode: String
|
||||
|
||||
|
||||
static let propertiesCount = 59
|
||||
|
||||
public init(array: [Substring], source: Substring) throws {
|
||||
guard array.count == AskingPrice.propertiesCount else {
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, AskingPrice.propertiesCount)
|
||||
}
|
||||
|
||||
func getIntArray(_ fromIndex: Int, _ toIndex: Int) -> [Int] {
|
||||
var array = [Int]()
|
||||
for i in fromIndex ..< toIndex {
|
||||
let value = Int(array[i])
|
||||
array.append(value)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
self.shortCode = String(array[0])
|
||||
self.businessTime = String(array[1])
|
||||
self.hourClassCode = HourClassCode(rawValue: String(array[2]).first!)!
|
||||
self.askingPrices = getIntArray(3, 3+10)
|
||||
self.biddingPrices = getIntArray(13, 13+10)
|
||||
self.askingPriceVolumes = getIntArray(23, 23+10)
|
||||
self.biddingPriceVolumes = getIntArray(33, 33+10)
|
||||
self.askingPrice_TotalResidualQuantity = Int(array[43]) ?? 0
|
||||
self.biddingPrice_TotalResidualQuantity = Int(array[44]) ?? 0
|
||||
self.askingPrice_Overtime_TotalResidualQuantity = Int(array[45]) ?? 0
|
||||
self.biddingPrice_Overtime_TotalResidualQuantity = Int(array[46]) ?? 0
|
||||
self.expectedConclusionPrice = Int(array[47]) ?? 0
|
||||
self.expectedConclusionQuantity = Int(array[48]) ?? 0
|
||||
self.expectedVolume = Int(array[49]) ?? 0
|
||||
self.expectedConclusion_VariableRatio = Double(array[50]) ?? 0
|
||||
self.expectedConclusion_VariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[51])!)!
|
||||
self.expectedConclusion_PreviousDayDiffRatio = Double(array[52]) ?? 0
|
||||
self.accumulatedVolume = Int(array[53]) ?? 0
|
||||
self.askingPrice_TotalResidualQuantity_IncreaseDecrease = Int(array[54]) ?? 0
|
||||
self.biddingPrice_TotalResidualQuantity_IncreaseDecrease = Int(array[55]) ?? 0
|
||||
self.askingPrice_Overtime_TotalResidualQuantity_IncreaseDecrease = Int(array[56]) ?? 0
|
||||
self.biddingPrice_Overtime_TotalResidualQuantity_IncreaseDecrease = Int(array[57]) ?? 0
|
||||
self.tradeCode = String(array[58])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct ContractNotice {
|
||||
/// 고객 ID
|
||||
public let customerID: String
|
||||
/// 계좌번호
|
||||
public let accountNo: String
|
||||
/// 주문번호
|
||||
public let orderNo: String
|
||||
/// 원주문번호
|
||||
public let originalOrderNo: String
|
||||
/// 매도매수구분
|
||||
public let contractClass: ContractClass
|
||||
/// 정정구분
|
||||
public let revisionType: String
|
||||
/// 주문종류
|
||||
public let orderClass: OrderClass
|
||||
/// 주문조건
|
||||
public let orderCondition: String
|
||||
/// 주식 단축 종목코드
|
||||
public let shortCode: String
|
||||
/// 체결 수량
|
||||
public let conclusionQuantity: Int
|
||||
/// 체결단가
|
||||
public let conclusionPrice: Int
|
||||
/// 주식 체결 시간
|
||||
public let conclusionTime: String
|
||||
/// 거부여부
|
||||
public let refused: RefuseYesNo
|
||||
/// 체결여부
|
||||
public let conclused: ConclusionYesNo
|
||||
/// 접수여부
|
||||
public let accepted: AcceptYesNo
|
||||
/// 지점번호
|
||||
public let branchNo: String
|
||||
/// 주문수량
|
||||
public let orderQuantity: Int
|
||||
/// 계좌명
|
||||
public let accountName: String
|
||||
/// 체결종목명
|
||||
public let productName: String
|
||||
/// 신용구분
|
||||
public let creditClass: String
|
||||
/// 신용대출일자
|
||||
public let creditLoanDate: String
|
||||
/// 체결종목명40
|
||||
public let productName40: String
|
||||
/// 주문가격
|
||||
public let orderPrice: Int
|
||||
|
||||
|
||||
static let propertiesCount = 23
|
||||
|
||||
public init(array: [Substring], source: Substring) throws {
|
||||
guard array.count == ContractNotice.propertiesCount else {
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, ContractNotice.propertiesCount)
|
||||
}
|
||||
|
||||
self.customerID = String(array[0])
|
||||
self.accountNo = String(array[1])
|
||||
self.orderNo = String(array[2])
|
||||
self.originalOrderNo = String(array[3])
|
||||
self.contractClass = ContractClass(rawValue: String(array[4]))!
|
||||
self.revisionType = String(array[5])
|
||||
self.orderClass = OrderClass(rawValue: String(array[6]))!
|
||||
self.orderCondition = String(array[7])
|
||||
self.shortCode = String(array[8])
|
||||
self.conclusionQuantity = Int(array[9]) ?? 0
|
||||
self.conclusionPrice = Int(array[10]) ?? 0
|
||||
self.conclusionTime = String(array[11])
|
||||
self.refused = RefuseYesNo(rawValue: Int(array[12])!)!
|
||||
self.conclused = ConclusionYesNo(rawValue: Int(array[13])!)!
|
||||
self.accepted = AcceptYesNo(rawValue: Int(array[14])!)!
|
||||
self.branchNo = String(array[15])
|
||||
self.orderQuantity = Int(array[16]) ?? 0
|
||||
self.accountName = String(array[17])
|
||||
self.productName = String(array[18])
|
||||
self.creditClass = String(array[19])
|
||||
self.creditLoanDate = String(array[20])
|
||||
self.productName40 = String(array[21])
|
||||
self.orderPrice = Int(array[22]) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum WebSocketResult {
|
||||
case json(String)
|
||||
case contractPrice(ContractPrice)
|
||||
case askingPrice(AskingPrice)
|
||||
case contractNotice(ContractNotice)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Domestic.MarketOperationCode {
|
||||
// MARK: 첫번째 비트
|
||||
|
||||
/// 장개시전
|
||||
public var isBeforeMarket: Bool { first == "1" }
|
||||
/// 장중
|
||||
public var isOnMarket: Bool { first == "2" }
|
||||
/// 장종료후
|
||||
public var isAfterMarket: Bool { first == "3" }
|
||||
/// 시간외단일가
|
||||
public var isOverTime_SinglePrice: Bool { first == "4" }
|
||||
/// 일반Buy-in
|
||||
public var isGeneralBuyIn: Bool { first == "5" }
|
||||
/// 당일Buy-in
|
||||
public var isTheDayBuyIn: Bool { first == "6" }
|
||||
|
||||
// MARK: 두번째 비트
|
||||
|
||||
/// 보통
|
||||
public var isGeneral: Bool { second == "0" }
|
||||
/// 종가
|
||||
public var isClosingPrice: Bool { second == "1" }
|
||||
/// 대량
|
||||
public var isBulk: Bool { second == "2" }
|
||||
/// 바스켓
|
||||
public var isBasket: Bool { second == "3" }
|
||||
/// 정리매매
|
||||
public var isClearanceSale: Bool { second == "7" }
|
||||
/// Buy-in
|
||||
public var isBuyIn: Bool { second == "8" }
|
||||
|
||||
private var first: Character { self[startIndex] }
|
||||
private var second: Character { self[index(startIndex, offsetBy: 1)] }
|
||||
}
|
||||
|
||||
|
||||
extension Domestic.WebSocketResult {
|
||||
|
||||
static public func parse(_ str: String) throws -> [Domestic.WebSocketResult] {
|
||||
var dataArray = [Domestic.WebSocketResult]()
|
||||
var nextAt = str.startIndex
|
||||
let charset = CharacterSet(charactersIn: "{}")
|
||||
|
||||
while nextAt < str.endIndex {
|
||||
print("nextAt... \(nextAt.utf16Offset(in: str))")
|
||||
|
||||
switch str[nextAt] {
|
||||
case "{":
|
||||
/// Scan end of json data
|
||||
var openedCount = 1
|
||||
let startAt = nextAt
|
||||
nextAt = str.index(nextAt, offsetBy: 1)
|
||||
|
||||
while openedCount > 0 {
|
||||
guard let r = str.rangeOfCharacter(from: charset, options: [], range: nextAt ..< str.endIndex) else {
|
||||
break
|
||||
}
|
||||
|
||||
switch str[r.lowerBound] {
|
||||
case "{":
|
||||
openedCount += 1
|
||||
case "}":
|
||||
openedCount -= 1
|
||||
if openedCount == 0 {
|
||||
/// end of json data
|
||||
let jsonString = String(str[startAt ..< r.upperBound])
|
||||
dataArray.append(.json(jsonString))
|
||||
nextAt = r.upperBound
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw GeneralError.impossibleJsonCharacter
|
||||
}
|
||||
nextAt = r.upperBound
|
||||
}
|
||||
|
||||
case "0":
|
||||
let (data, endAt) = try parseTrData(false, str: str, startAt: nextAt)
|
||||
dataArray.append(contentsOf: data)
|
||||
nextAt = endAt
|
||||
|
||||
case "1":
|
||||
let (data, endAt) = try parseTrData(true, str: str, startAt: nextAt)
|
||||
dataArray.append(contentsOf: data)
|
||||
nextAt = endAt
|
||||
|
||||
default:
|
||||
print(str[nextAt ..< str.endIndex])
|
||||
throw GeneralError.invalidWebSocketData
|
||||
}
|
||||
}
|
||||
|
||||
return dataArray
|
||||
}
|
||||
|
||||
|
||||
private static func parseTrData(_ encrypted: Bool, str: String, startAt: String.Index) throws -> ([Domestic.WebSocketResult], String.Index) {
|
||||
var dataArray = [Domestic.WebSocketResult]()
|
||||
var nextAt = startAt
|
||||
let charset = CharacterSet(charactersIn: "|")
|
||||
|
||||
guard let i1 = str.rangeOfCharacter(from: charset, options: [], range: nextAt ..< str.endIndex) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
/// Get encryption field
|
||||
let encryption = String(str[nextAt ..< i1.lowerBound])
|
||||
guard encryption == "0" || encryption == "1" else {
|
||||
throw GeneralError.invalidWebSocketData_EncryptionField
|
||||
}
|
||||
nextAt = i1.upperBound
|
||||
guard let i2 = str.rangeOfCharacter(from: charset, options: [], range: nextAt ..< str.endIndex) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
/// Get trId field
|
||||
let trId = String(str[nextAt ..< i2.lowerBound])
|
||||
nextAt = i2.upperBound
|
||||
guard let i3 = str.rangeOfCharacter(from: charset, options: [], range: nextAt ..< str.endIndex) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
/// Get data count field
|
||||
let dataCountString = String(str[nextAt ..< i3.lowerBound])
|
||||
guard let dataCount = Int(dataCountString) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
nextAt = i3.upperBound
|
||||
|
||||
/// Retrieve multiple data
|
||||
switch trId {
|
||||
case "H0STCNT0":
|
||||
for _ in 0 ..< dataCount {
|
||||
let (stringArray, string, endAt) = try getTrDataString(str, startAt: nextAt, seperatorCount: Domestic.ContractPrice.propertiesCount-1)
|
||||
let price = try Domestic.ContractPrice(array: stringArray, source: string)
|
||||
dataArray.append(.contractPrice(price))
|
||||
nextAt = endAt
|
||||
}
|
||||
|
||||
case "H0STASP0":
|
||||
for _ in 0 ..< dataCount {
|
||||
let (stringArray, string, endAt) = try getTrDataString(str, startAt: nextAt, seperatorCount: Domestic.AskingPrice.propertiesCount-1)
|
||||
let price = try Domestic.ContractPrice(array: stringArray, source: string)
|
||||
dataArray.append(.contractPrice(price))
|
||||
nextAt = endAt
|
||||
}
|
||||
|
||||
case "H0STCNI0":
|
||||
for _ in 0 ..< dataCount {
|
||||
let (stringArray, string, endAt) = try getTrDataString(str, startAt: startAt, seperatorCount: Domestic.ContractNotice.propertiesCount-1)
|
||||
let notice = try Domestic.ContractNotice(array: stringArray, source: string)
|
||||
dataArray.append(.contractNotice(notice))
|
||||
nextAt = endAt
|
||||
}
|
||||
|
||||
default:
|
||||
throw GeneralError.invalidWebSocketData_TrIdField
|
||||
}
|
||||
|
||||
return (dataArray, nextAt)
|
||||
}
|
||||
|
||||
|
||||
private static func getTrDataString(_ str: String, startAt: String.Index, seperatorCount: Int) throws -> ([Substring], Substring, String.Index) {
|
||||
var currentSeperator = 0
|
||||
var nextAt = startAt
|
||||
var array = [Substring]()
|
||||
|
||||
/// Find last seperator
|
||||
while currentSeperator < seperatorCount {
|
||||
guard let last = str.range(of: "^", options: [], range: nextAt ..< str.endIndex) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
currentSeperator += 1
|
||||
array.append(str[nextAt ..< last.lowerBound])
|
||||
nextAt = last.upperBound
|
||||
}
|
||||
|
||||
/// Look forward to find final field
|
||||
if let end = str.rangeOfCharacter(from: .alphanumerics.inverted, options: [], range: nextAt ..< str.endIndex) {
|
||||
array.append(str[nextAt ..< end.lowerBound])
|
||||
let string = str[startAt ..< end.lowerBound]
|
||||
return (array, string, end.lowerBound)
|
||||
}
|
||||
else {
|
||||
array.append(str[nextAt ..< str.endIndex])
|
||||
let string = str[startAt ..< str.endIndex]
|
||||
return (array, string, str.endIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,486 +16,13 @@ import KissMe
|
||||
test_parse_contact_price_response()
|
||||
|
||||
|
||||
enum VariableRatioSignType: UInt8 {
|
||||
/// 상한
|
||||
case upLimit = 1
|
||||
/// 상승
|
||||
case up = 2
|
||||
/// 보합
|
||||
case noChange = 3
|
||||
/// 하한
|
||||
case downLimit = 4
|
||||
/// 하락
|
||||
case down = 5
|
||||
}
|
||||
|
||||
|
||||
enum ConclusionType: UInt8 {
|
||||
/// 매수(+)
|
||||
case buying = 1
|
||||
/// 장전
|
||||
case load = 3
|
||||
/// 매도(-)
|
||||
case selling = 5
|
||||
}
|
||||
|
||||
|
||||
enum HourClassCode: Character {
|
||||
/// 장중
|
||||
case onMarket = "0"
|
||||
/// 장후예상
|
||||
case afterMarket = "A"
|
||||
/// 장전예상
|
||||
case beforeMarket = "B"
|
||||
/// 9시이후의 예상가, VI발동
|
||||
case viInvoking = "C"
|
||||
/// 시간외 단일가 예상
|
||||
case overTime_SinglePrice = "D"
|
||||
}
|
||||
|
||||
|
||||
typealias MarketOperationCode = String
|
||||
|
||||
extension MarketOperationCode {
|
||||
// MARK: 첫번째 비트
|
||||
|
||||
/// 장개시전
|
||||
public var isBeforeMarket: Bool { first == "1" }
|
||||
/// 장중
|
||||
public var isOnMarket: Bool { first == "2" }
|
||||
/// 장종료후
|
||||
public var isAfterMarket: Bool { first == "3" }
|
||||
/// 시간외단일가
|
||||
public var isOverTime_SinglePrice: Bool { first == "4" }
|
||||
/// 일반Buy-in
|
||||
public var isGeneralBuyIn: Bool { first == "5" }
|
||||
/// 당일Buy-in
|
||||
public var isTheDayBuyIn: Bool { first == "6" }
|
||||
|
||||
// MARK: 두번째 비트
|
||||
|
||||
/// 보통
|
||||
public var isGeneral: Bool { second == "0" }
|
||||
/// 종가
|
||||
public var isClosingPrice: Bool { second == "1" }
|
||||
/// 대량
|
||||
public var isBulk: Bool { second == "2" }
|
||||
/// 바스켓
|
||||
public var isBasket: Bool { second == "3" }
|
||||
/// 정리매매
|
||||
public var isClearanceSale: Bool { second == "7" }
|
||||
/// Buy-in
|
||||
public var isBuyIn: Bool { second == "8" }
|
||||
|
||||
var first: Character { self[startIndex] }
|
||||
var second: Character { self[index(startIndex, offsetBy: 1)] }
|
||||
}
|
||||
|
||||
|
||||
struct ContractPrice {
|
||||
/// 유가증권 단축 종목코드
|
||||
public let shortCode: String
|
||||
/// 주식 체결 시간 (HHmmss)
|
||||
public let conclusionTime: String
|
||||
/// 주식 현재가
|
||||
public let currentPrice: Int
|
||||
/// 전일 대비 부호
|
||||
public let previousDayVariableRatioSign: VariableRatioSignType
|
||||
/// 전일 대비
|
||||
public let previousDayVariableRatio: Double
|
||||
/// 전일 대비율
|
||||
public let previousDayDiffRatio: Double
|
||||
/// 가중 평균 주식 가격
|
||||
public let weightedAveragePrice: Int
|
||||
/// 주식 시가
|
||||
public let openningPrice: Int
|
||||
/// 주식 최고가
|
||||
public let highestPrice: Int
|
||||
/// 주식 최저가
|
||||
public let lowestPrice: Int
|
||||
/// 매도호가1
|
||||
public let askingPrice: Int
|
||||
/// 매수호가1
|
||||
public let biddingPrice: Int
|
||||
/// 체결 거래량
|
||||
public let conclusionVolume: Int
|
||||
/// 누적 거래량
|
||||
public let accumulatedVolume: Int
|
||||
/// 누적 거래 대금
|
||||
public let accumulatedTradingAmount: Int
|
||||
/// 매도 체결 건수
|
||||
public let sellingConclusionCount: Int
|
||||
/// 매수 체결 건수
|
||||
public let buyingConclusionCount: Int
|
||||
/// 순매수 체결 건수
|
||||
public let netBuyingConclusionCount: Int
|
||||
/// 체결강도
|
||||
public let conclusionStrength: Double
|
||||
/// 총 매도 수량
|
||||
public let totalSellingQuantity: Int
|
||||
/// 총 매수 수량
|
||||
public let totalBuyingQuantity: Int
|
||||
/// 체결구분
|
||||
public let conclusionType: ConclusionType
|
||||
/// 매수비율
|
||||
public let buyingRatio: Double
|
||||
/// 전일 거래량 대비 등락율
|
||||
public let previousDay_VolumeDiff_FluctuationRate: Double
|
||||
/// 시가 시간
|
||||
public let openningPriceHour: String
|
||||
/// 시가대비구분
|
||||
public let openningPrice_VariableRatioSign: VariableRatioSignType
|
||||
/// 시가대비
|
||||
public let openningPrice_VariableRatio: Int
|
||||
/// 최고가 시간
|
||||
public let highestPriceHour: String
|
||||
/// 고가대비구분
|
||||
public let highPrice_VariableRatioSign: VariableRatioSignType
|
||||
/// 고가대비
|
||||
public let highPrice_VariableRatio: Int
|
||||
/// 최저가 시간
|
||||
public let lowestPriceHour: String
|
||||
/// 저가대비구분
|
||||
public let lowPrice_VariableRatioSign: VariableRatioSignType
|
||||
/// 저가대비
|
||||
public let lowPrice_VariableRatio: Int
|
||||
/// 영업 일자
|
||||
public let businessDate: String
|
||||
/// 신 장운영 구분 코드
|
||||
public let marketOperationCode: MarketOperationCode
|
||||
/// 거래정지 여부
|
||||
public let tradeStopped: Bool
|
||||
/// 매도호가 잔량1
|
||||
public let askingPrice_ResidualQuantity: Int
|
||||
/// 매수호가 잔량1
|
||||
public let biddingPrice_ResidualQuantity: Int
|
||||
/// 총 매도호가 잔량
|
||||
public let askingPrice_TotalResidualQuantity: Int
|
||||
/// 총 매수호가 잔량
|
||||
public let biddingPrice_TotalResidualQuantity: Int
|
||||
/// 거래량 회전율
|
||||
public let volumeTurnoverRate: Double
|
||||
/// 전일 동시간 누적 거래량
|
||||
public let previousDaySameTime_AccumulatedTradingQuantity: Int
|
||||
/// 전일 동시간 누적 거래량 비율
|
||||
public let previousDaySameTime_AccumulatedTradingQuantityRatio: Double
|
||||
/// 시간 구분 코드
|
||||
public let hourClassCode: HourClassCode
|
||||
/// 임의종료구분코드
|
||||
public let marketTerminationCode: String
|
||||
/// 정적VI발동기준가
|
||||
public let viStandardPrice: Int
|
||||
|
||||
|
||||
static let propertiesCount = 46
|
||||
|
||||
public init(array: [Substring], source: Substring) throws {
|
||||
guard array.count == ContractPrice.propertiesCount else {
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, ContractPrice.propertiesCount)
|
||||
}
|
||||
|
||||
self.shortCode = String(array[0])
|
||||
self.conclusionTime = String(array[1])
|
||||
self.currentPrice = Int(array[2]) ?? 0
|
||||
self.previousDayVariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[3])!)!
|
||||
self.previousDayVariableRatio = Double(array[4]) ?? 0
|
||||
self.previousDayDiffRatio = Double(array[5]) ?? 0
|
||||
self.weightedAveragePrice = Int(array[6]) ?? 0
|
||||
self.openningPrice = Int(array[7]) ?? 0
|
||||
self.highestPrice = Int(array[8]) ?? 0
|
||||
self.lowestPrice = Int(array[9]) ?? 0
|
||||
self.askingPrice = Int(array[10]) ?? 0
|
||||
self.biddingPrice = Int(array[11]) ?? 0
|
||||
self.conclusionVolume = Int(array[12]) ?? 0
|
||||
self.accumulatedVolume = Int(array[13]) ?? 0
|
||||
self.accumulatedTradingAmount = Int(array[14]) ?? 0
|
||||
self.sellingConclusionCount = Int(array[15]) ?? 0
|
||||
self.buyingConclusionCount = Int(array[16]) ?? 0
|
||||
self.netBuyingConclusionCount = Int(array[17]) ?? 0
|
||||
self.conclusionStrength = Double(array[18]) ?? 0
|
||||
self.totalSellingQuantity = Int(array[19]) ?? 0
|
||||
self.totalBuyingQuantity = Int(array[20]) ?? 0
|
||||
self.conclusionType = ConclusionType(rawValue: UInt8(array[21])!)!
|
||||
self.buyingRatio = Double(array[22]) ?? 0
|
||||
self.previousDay_VolumeDiff_FluctuationRate = Double(array[23]) ?? 0
|
||||
self.openningPriceHour = String(array[24])
|
||||
self.openningPrice_VariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[25])!)!
|
||||
self.openningPrice_VariableRatio = Int(array[26]) ?? 0
|
||||
self.highestPriceHour = String(array[27])
|
||||
self.highPrice_VariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[28])!)!
|
||||
self.highPrice_VariableRatio = Int(array[29]) ?? 0
|
||||
self.lowestPriceHour = String(array[30])
|
||||
self.lowPrice_VariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[31])!)!
|
||||
self.lowPrice_VariableRatio = Int(array[32]) ?? 0
|
||||
self.businessDate = String(array[33])
|
||||
self.marketOperationCode = String(array[34])
|
||||
self.tradeStopped = String(array[35]) == "Y"
|
||||
self.askingPrice_ResidualQuantity = Int(array[36]) ?? 0
|
||||
self.biddingPrice_ResidualQuantity = Int(array[37]) ?? 0
|
||||
self.askingPrice_TotalResidualQuantity = Int(array[38]) ?? 0
|
||||
self.biddingPrice_TotalResidualQuantity = Int(array[39]) ?? 0
|
||||
self.volumeTurnoverRate = Double(array[40]) ?? 0
|
||||
self.previousDaySameTime_AccumulatedTradingQuantity = Int(array[41]) ?? 0
|
||||
self.previousDaySameTime_AccumulatedTradingQuantityRatio = Double(array[42]) ?? 0
|
||||
self.hourClassCode = HourClassCode(rawValue: String(array[43]).first!)!
|
||||
self.marketTerminationCode = String(array[44])
|
||||
self.viStandardPrice = Int(array[45]) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct AskingPrice {
|
||||
/// 유가증권 단축 종목코드
|
||||
public let shortCode: String
|
||||
/// 영업 시간
|
||||
public let businessTime: String
|
||||
/// 시간 구분 코드
|
||||
public let hourClassCode: HourClassCode
|
||||
/// 매도호가1 ~ 매도호가10
|
||||
public let askingPrices: [Int]
|
||||
/// 매수호가1 ~ 매수호가10
|
||||
public let biddingPrices: [Int]
|
||||
/// 매도호가 잔량1 ~ 매도호가 잔량4
|
||||
public let askingPriceVolumes: [Int]
|
||||
/// 매수호가 잔량1 ~ 매수호가 잔량10
|
||||
public let biddingPriceVolumes: [Int]
|
||||
/// 총 매도호가 잔량
|
||||
public let askingPrice_TotalResidualQuantity: Int
|
||||
/// 총 매수호가 잔량
|
||||
public let biddingPrice_TotalResidualQuantity: Int
|
||||
/// 시간외 총 매도호가 잔량
|
||||
public let askingPrice_Overtime_TotalResidualQuantity: Int
|
||||
/// 시간외 총 매수호가 잔량
|
||||
public let biddingPrice_Overtime_TotalResidualQuantity: Int
|
||||
/// 예상 체결가
|
||||
public let expectedConclusionPrice: Int
|
||||
/// 예상 체결량
|
||||
public let expectedConclusionQuantity: Int
|
||||
/// 예상 거래량
|
||||
public let expectedVolume: Int
|
||||
/// 예상 체결 대비
|
||||
public let expectedConclusion_VariableRatio: Double
|
||||
/// 예상 체결 대비 부호
|
||||
public let expectedConclusion_VariableRatioSign: VariableRatioSignType
|
||||
/// 예상 체결 전일 대비율
|
||||
public let expectedConclusion_PreviousDayDiffRatio: Double
|
||||
/// 누적 거래량
|
||||
public let accumulatedVolume: Int
|
||||
/// 총 매도호가 잔량 증감
|
||||
public let askingPrice_TotalResidualQuantity_IncreaseDecrease: Int
|
||||
/// 총 매수호가 잔량 증감
|
||||
public let biddingPrice_TotalResidualQuantity_IncreaseDecrease: Int
|
||||
/// 시간외 총 매도호가 증감
|
||||
public let askingPrice_Overtime_TotalResidualQuantity_IncreaseDecrease: Int
|
||||
/// 시간외 총 매수호가 증감
|
||||
public let biddingPrice_Overtime_TotalResidualQuantity_IncreaseDecrease: Int
|
||||
/// 주식 매매 구분 코드
|
||||
public let tradeCode: String
|
||||
|
||||
|
||||
static let propertiesCount = 59
|
||||
|
||||
public init(array: [Substring], source: Substring) throws {
|
||||
guard array.count == AskingPrice.propertiesCount else {
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, AskingPrice.propertiesCount)
|
||||
}
|
||||
|
||||
func getIntArray(_ fromIndex: Int, _ toIndex: Int) -> [Int] {
|
||||
var array = [Int]()
|
||||
for i in fromIndex ..< toIndex {
|
||||
let value = Int(array[i])
|
||||
array.append(value)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
self.shortCode = String(array[0])
|
||||
self.businessTime = String(array[1])
|
||||
self.hourClassCode = HourClassCode(rawValue: String(array[2]).first!)!
|
||||
self.askingPrices = getIntArray(3, 3+10)
|
||||
self.biddingPrices = getIntArray(13, 13+10)
|
||||
self.askingPriceVolumes = getIntArray(23, 23+10)
|
||||
self.biddingPriceVolumes = getIntArray(33, 33+10)
|
||||
self.askingPrice_TotalResidualQuantity = Int(array[43]) ?? 0
|
||||
self.biddingPrice_TotalResidualQuantity = Int(array[44]) ?? 0
|
||||
self.askingPrice_Overtime_TotalResidualQuantity = Int(array[45]) ?? 0
|
||||
self.biddingPrice_Overtime_TotalResidualQuantity = Int(array[46]) ?? 0
|
||||
self.expectedConclusionPrice = Int(array[47]) ?? 0
|
||||
self.expectedConclusionQuantity = Int(array[48]) ?? 0
|
||||
self.expectedVolume = Int(array[49]) ?? 0
|
||||
self.expectedConclusion_VariableRatio = Double(array[50]) ?? 0
|
||||
self.expectedConclusion_VariableRatioSign = VariableRatioSignType(rawValue: UInt8(array[51])!)!
|
||||
self.expectedConclusion_PreviousDayDiffRatio = Double(array[52]) ?? 0
|
||||
self.accumulatedVolume = Int(array[53]) ?? 0
|
||||
self.askingPrice_TotalResidualQuantity_IncreaseDecrease = Int(array[54]) ?? 0
|
||||
self.biddingPrice_TotalResidualQuantity_IncreaseDecrease = Int(array[55]) ?? 0
|
||||
self.askingPrice_Overtime_TotalResidualQuantity_IncreaseDecrease = Int(array[56]) ?? 0
|
||||
self.biddingPrice_Overtime_TotalResidualQuantity_IncreaseDecrease = Int(array[57]) ?? 0
|
||||
self.tradeCode = String(array[58])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum ContractType: String {
|
||||
/// 매도
|
||||
case selling = "01"
|
||||
/// 매수
|
||||
case buying = "02"
|
||||
}
|
||||
|
||||
|
||||
enum OrderType: String {
|
||||
/// 지정가
|
||||
case limits = "00"
|
||||
/// 시장가
|
||||
case marketPrice = "01"
|
||||
/// 조건부지정가
|
||||
case conditionalLimits = "02"
|
||||
/// 최유리지정가
|
||||
case mostFavorLimits = "03"
|
||||
/// 최우선지정가
|
||||
case mostPriorLimits = "04"
|
||||
/// 장전 시간외
|
||||
case overTime_BeforeMarket = "05"
|
||||
/// 장후 시간외
|
||||
case overTime_AfterMarket = "06"
|
||||
/// 시간외 단일가
|
||||
case overTime_SinglePrice = "07"
|
||||
/// 자기주식
|
||||
case treasury = "08"
|
||||
/// 자기주식S-Option
|
||||
case treasury_SOption = "09"
|
||||
/// 자기주식금전신탁
|
||||
case treasury_MoneyTrust = "10"
|
||||
/// IOC지정가 (즉시체결,잔량취소)
|
||||
case iocLimits_Conclusion_CancelResidualQuantity = "11"
|
||||
/// FOK지정가 (즉시체결,전량취소)
|
||||
case fokLimits_Conclusion_CancelEntireQuantity = "12"
|
||||
/// IOC시장가 (즉시체결,잔량취소)
|
||||
case iocMarketPrice_Conclusion_CancelResidualQuantity = "13"
|
||||
/// FOK시장가 (즉시체결,전량취소)
|
||||
case fokMarketPrice_Conclusion_CancelEntireQuantity = "14"
|
||||
/// IOC최유리 (즉시체결,잔량취소)
|
||||
case iocMostFavor_Conclusion_CancelResidualQuantity = "15"
|
||||
/// FOK최유리 (즉시체결,전량취소)
|
||||
case fokMostFavor_Conclusion_CancelEntireQuantity = "16"
|
||||
}
|
||||
|
||||
|
||||
enum RefuseYesNo: Int {
|
||||
/// 승인
|
||||
case approval = 0
|
||||
/// 거부
|
||||
case refuse = 1
|
||||
}
|
||||
|
||||
|
||||
enum ConclusionYesNo: Int {
|
||||
/// 주문,정정,취소,거부,
|
||||
case unconclused = 1
|
||||
/// 체결 (★ 체결만 보실경우 2번만 보시면 됩니다)
|
||||
case conclused = 2
|
||||
}
|
||||
|
||||
|
||||
enum AcceptYesNo: Int {
|
||||
/// 주문접수
|
||||
case orderAccept = 1
|
||||
/// 확인
|
||||
case confirm = 2
|
||||
/// 취소(FOK/IOC)
|
||||
case cancel = 3
|
||||
}
|
||||
|
||||
|
||||
struct ContractNotice {
|
||||
/// 고객 ID
|
||||
public let customerID: String
|
||||
/// 계좌번호
|
||||
public let accountNo: String
|
||||
/// 주문번호
|
||||
public let orderNo: String
|
||||
/// 원주문번호
|
||||
public let originalOrderNo: String
|
||||
/// 매도매수구분
|
||||
public let contractType: ContractType
|
||||
/// 정정구분
|
||||
public let revisionType: String
|
||||
/// 주문종류
|
||||
public let orderType: OrderType
|
||||
/// 주문조건
|
||||
public let orderCondition: String
|
||||
/// 주식 단축 종목코드
|
||||
public let shortCode: String
|
||||
/// 체결 수량
|
||||
public let conclusionQuantity: Int
|
||||
/// 체결단가
|
||||
public let conclusionPrice: Int
|
||||
/// 주식 체결 시간
|
||||
public let conclusionTime: String
|
||||
/// 거부여부
|
||||
public let refused: RefuseYesNo
|
||||
/// 체결여부
|
||||
public let conclused: ConclusionYesNo
|
||||
/// 접수여부
|
||||
public let accepted: AcceptYesNo
|
||||
/// 지점번호
|
||||
public let branchNo: String
|
||||
/// 주문수량
|
||||
public let orderQuantity: Int
|
||||
/// 계좌명
|
||||
public let accountName: String
|
||||
/// 체결종목명
|
||||
public let productName: String
|
||||
/// 신용구분
|
||||
public let creditClass: String
|
||||
/// 신용대출일자
|
||||
public let creditLoanDate: String
|
||||
/// 체결종목명40
|
||||
public let productName40: String
|
||||
/// 주문가격
|
||||
public let orderPrice: Int
|
||||
|
||||
|
||||
static let propertiesCount = 23
|
||||
|
||||
public init(array: [Substring], source: Substring) throws {
|
||||
guard array.count == ContractNotice.propertiesCount else {
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, ContractNotice.propertiesCount)
|
||||
}
|
||||
|
||||
self.customerID = String(array[0])
|
||||
self.accountNo = String(array[1])
|
||||
self.orderNo = String(array[2])
|
||||
self.originalOrderNo = String(array[3])
|
||||
self.contractType = ContractType(rawValue: String(array[4]))!
|
||||
self.revisionType = String(array[5])
|
||||
self.orderType = OrderType(rawValue: String(array[6]))!
|
||||
self.orderCondition = String(array[7])
|
||||
self.shortCode = String(array[8])
|
||||
self.conclusionQuantity = Int(array[9]) ?? 0
|
||||
self.conclusionPrice = Int(array[10]) ?? 0
|
||||
self.conclusionTime = String(array[11])
|
||||
self.refused = RefuseYesNo(rawValue: Int(array[12])!)!
|
||||
self.conclused = ConclusionYesNo(rawValue: Int(array[13])!)!
|
||||
self.accepted = AcceptYesNo(rawValue: Int(array[14])!)!
|
||||
self.branchNo = String(array[15])
|
||||
self.orderQuantity = Int(array[16]) ?? 0
|
||||
self.accountName = String(array[17])
|
||||
self.productName = String(array[18])
|
||||
self.creditClass = String(array[19])
|
||||
self.creditLoanDate = String(array[20])
|
||||
self.productName40 = String(array[21])
|
||||
self.orderPrice = Int(array[22]) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func test_parse_contact_price_response() {
|
||||
let str = "{\"header\":{\"tr_id\":\"H0STCNT0\",\"tr_key\":\"005930\",\"encrypt\":\"N\"},\"body\":{\"rt_cd\":\"0\",\"msg_cd\":\"OPSP0000\",\"msg1\":\"SUBSCRIBE SUCCESS\",\"output\":{\"iv\":\"dcc3c442acfb8b9a\",\"key\":\"vcvxscahuklwkiawiuxbsfcmsulqjejf\"}}}0|H0STCNT0|001|005930^134305^68100^2^1000^1.49^68305.67^68300^68700^67900^68100^68000^1^11393808^778261604700^32559^26679^-5880^79.02^6084367^4807987^1^0.43^119.32^090027^5^-200^091809^5^-600^113615^2^200^20230824^20^N^309354^354766^2143494^2041321^0.19^6698642^170.09^0^^68300{\"header\":{\"tr_id\":\"PINGPONG\",\"datetime\":\"20230824212922\"}}"
|
||||
|
||||
do {
|
||||
let dataArray = try parseJsonTrData(str)
|
||||
let dataArray = try Domestic.WebSocketResult.parse(str)
|
||||
for data in dataArray {
|
||||
switch data {
|
||||
case .json(let str):
|
||||
@@ -514,165 +41,6 @@ func test_parse_contact_price_response() {
|
||||
}
|
||||
|
||||
|
||||
enum JsonTrDataType {
|
||||
case json(String)
|
||||
case contractPrice(ContractPrice)
|
||||
case askingPrice(AskingPrice)
|
||||
case contractNotice(ContractNotice)
|
||||
}
|
||||
|
||||
|
||||
func parseJsonTrData(_ str: String) throws -> [JsonTrDataType] {
|
||||
var dataArray = [JsonTrDataType]()
|
||||
var nextAt = str.startIndex
|
||||
let charset = CharacterSet(charactersIn: "{}")
|
||||
|
||||
while nextAt < str.endIndex {
|
||||
print("nextAt... \(nextAt.utf16Offset(in: str))")
|
||||
|
||||
switch str[nextAt] {
|
||||
case "{":
|
||||
/// Scan end of json data
|
||||
var openedCount = 1
|
||||
let startAt = nextAt
|
||||
nextAt = str.index(nextAt, offsetBy: 1)
|
||||
|
||||
while openedCount > 0 {
|
||||
guard let r = str.rangeOfCharacter(from: charset, options: [], range: nextAt ..< str.endIndex) else {
|
||||
break
|
||||
}
|
||||
|
||||
switch str[r.lowerBound] {
|
||||
case "{":
|
||||
openedCount += 1
|
||||
case "}":
|
||||
openedCount -= 1
|
||||
if openedCount == 0 {
|
||||
/// end of json data
|
||||
let jsonString = String(str[startAt ..< r.upperBound])
|
||||
dataArray.append(.json(jsonString))
|
||||
nextAt = r.upperBound
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw GeneralError.impossibleJsonCharacter
|
||||
}
|
||||
nextAt = r.upperBound
|
||||
}
|
||||
|
||||
case "0":
|
||||
let (data, endAt) = try parseTrData(false, str: str, startAt: nextAt)
|
||||
dataArray.append(contentsOf: data)
|
||||
nextAt = endAt
|
||||
|
||||
case "1":
|
||||
let (data, endAt) = try parseTrData(true, str: str, startAt: nextAt)
|
||||
dataArray.append(contentsOf: data)
|
||||
nextAt = endAt
|
||||
|
||||
default:
|
||||
print(str[nextAt ..< str.endIndex])
|
||||
throw GeneralError.invalidWebSocketData
|
||||
}
|
||||
}
|
||||
|
||||
return dataArray
|
||||
}
|
||||
|
||||
|
||||
private func parseTrData(_ encrypted: Bool, str: String, startAt: String.Index) throws -> ([JsonTrDataType], String.Index) {
|
||||
var dataArray = [JsonTrDataType]()
|
||||
var nextAt = startAt
|
||||
let charset = CharacterSet(charactersIn: "|")
|
||||
|
||||
guard let i1 = str.rangeOfCharacter(from: charset, options: [], range: nextAt ..< str.endIndex) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
/// Get encryption field
|
||||
let encryption = String(str[nextAt ..< i1.lowerBound])
|
||||
guard encryption == "0" || encryption == "1" else {
|
||||
throw GeneralError.invalidWebSocketData_EncryptionField
|
||||
}
|
||||
nextAt = i1.upperBound
|
||||
guard let i2 = str.rangeOfCharacter(from: charset, options: [], range: nextAt ..< str.endIndex) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
/// Get trId field
|
||||
let trId = String(str[nextAt ..< i2.lowerBound])
|
||||
nextAt = i2.upperBound
|
||||
guard let i3 = str.rangeOfCharacter(from: charset, options: [], range: nextAt ..< str.endIndex) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
/// Get data count field
|
||||
let dataCountString = String(str[nextAt ..< i3.lowerBound])
|
||||
guard let dataCount = Int(dataCountString) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
nextAt = i3.upperBound
|
||||
|
||||
/// Retrieve multiple data
|
||||
switch trId {
|
||||
case "H0STCNT0":
|
||||
for _ in 0 ..< dataCount {
|
||||
let (stringArray, string, endAt) = try getTrDataString(str, startAt: nextAt, seperatorCount: ContractPrice.propertiesCount-1)
|
||||
let price = try ContractPrice(array: stringArray, source: string)
|
||||
dataArray.append(.contractPrice(price))
|
||||
nextAt = endAt
|
||||
}
|
||||
|
||||
case "H0STASP0":
|
||||
for _ in 0 ..< dataCount {
|
||||
let (stringArray, string, endAt) = try getTrDataString(str, startAt: nextAt, seperatorCount: AskingPrice.propertiesCount-1)
|
||||
let price = try ContractPrice(array: stringArray, source: string)
|
||||
dataArray.append(.contractPrice(price))
|
||||
nextAt = endAt
|
||||
}
|
||||
|
||||
case "H0STCNI0":
|
||||
for _ in 0 ..< dataCount {
|
||||
let (stringArray, string, endAt) = try getTrDataString(str, startAt: startAt, seperatorCount: ContractNotice.propertiesCount-1)
|
||||
let notice = try ContractNotice(array: stringArray, source: string)
|
||||
dataArray.append(.contractNotice(notice))
|
||||
nextAt = endAt
|
||||
}
|
||||
|
||||
default:
|
||||
throw GeneralError.invalidWebSocketData_TrIdField
|
||||
}
|
||||
|
||||
return (dataArray, nextAt)
|
||||
}
|
||||
|
||||
|
||||
private func getTrDataString(_ str: String, startAt: String.Index, seperatorCount: Int) throws -> ([Substring], Substring, String.Index) {
|
||||
var currentSeperator = 0
|
||||
var nextAt = startAt
|
||||
var array = [Substring]()
|
||||
|
||||
/// Find last seperator
|
||||
while currentSeperator < seperatorCount {
|
||||
guard let last = str.range(of: "^", options: [], range: nextAt ..< str.endIndex) else {
|
||||
throw GeneralError.notEnoughWebSocketData
|
||||
}
|
||||
currentSeperator += 1
|
||||
array.append(str[nextAt ..< last.lowerBound])
|
||||
nextAt = last.upperBound
|
||||
}
|
||||
|
||||
/// Look forward to find final field
|
||||
if let end = str.rangeOfCharacter(from: .alphanumerics.inverted, options: [], range: nextAt ..< str.endIndex) {
|
||||
array.append(str[nextAt ..< end.lowerBound])
|
||||
let string = str[startAt ..< end.lowerBound]
|
||||
return (array, string, end.lowerBound)
|
||||
}
|
||||
else {
|
||||
array.append(str[nextAt ..< str.endIndex])
|
||||
let string = str[startAt ..< str.endIndex]
|
||||
return (array, string, str.endIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func test_get_websocket_key_and_asking_price() {
|
||||
let isMock = false
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
34C1BA8A2A5DA00A00423D64 /* DomesticDartMajorReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C1BA892A5DA00A00423D64 /* DomesticDartMajorReport.swift */; };
|
||||
34C97D0B2A602A3D00ED8B33 /* DomesticDartSecuritiesReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C97D0A2A602A3D00ED8B33 /* DomesticDartSecuritiesReport.swift */; };
|
||||
34D3680F2A2AA0BE005E6756 /* PropertyIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */; };
|
||||
34DA3E9D2A9A028200BB3439 /* Domestic.WebSocketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34DA3E9C2A9A028200BB3439 /* Domestic.WebSocketData.swift */; };
|
||||
34E7B9112A49BD2800B3AB9F /* DomesticIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E7B9102A49BD2800B3AB9F /* DomesticIndex.swift */; };
|
||||
34EC4D212A7ACB07002F947C /* CompanyPartitionMergerDecisionResult.json in Resources */ = {isa = PBXBuildFile; fileRef = 34EC4D202A7ACB06002F947C /* CompanyPartitionMergerDecisionResult.json */; };
|
||||
34EC4D242A7F27A8002F947C /* CompanyPartitionDecisionResult.json in Resources */ = {isa = PBXBuildFile; fileRef = 34EC4D232A7F27A8002F947C /* CompanyPartitionDecisionResult.json */; };
|
||||
@@ -195,6 +196,7 @@
|
||||
34C1BA892A5DA00A00423D64 /* DomesticDartMajorReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticDartMajorReport.swift; sourceTree = "<group>"; };
|
||||
34C97D0A2A602A3D00ED8B33 /* DomesticDartSecuritiesReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticDartSecuritiesReport.swift; sourceTree = "<group>"; };
|
||||
34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyIterable.swift; sourceTree = "<group>"; };
|
||||
34DA3E9C2A9A028200BB3439 /* Domestic.WebSocketData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Domestic.WebSocketData.swift; sourceTree = "<group>"; };
|
||||
34E7B9102A49BD2800B3AB9F /* DomesticIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticIndex.swift; sourceTree = "<group>"; };
|
||||
34EC4D202A7ACB06002F947C /* CompanyPartitionMergerDecisionResult.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = CompanyPartitionMergerDecisionResult.json; sourceTree = "<group>"; };
|
||||
34EC4D232A7F27A8002F947C /* CompanyPartitionDecisionResult.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = CompanyPartitionDecisionResult.json; sourceTree = "<group>"; };
|
||||
@@ -408,6 +410,7 @@
|
||||
34BC44742A8656250052D8EB /* Realtime */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
34DA3E9C2A9A028200BB3439 /* Domestic.WebSocketData.swift */,
|
||||
34BC447C2A86635A0052D8EB /* Domestic.ContractPriceWebSocket.swift */,
|
||||
34BC447A2A8663430052D8EB /* Domestic.AskingPriceWebSocket.swift */,
|
||||
34BC44752A8656570052D8EB /* Domestic.ContractNoticeWebSocket.swift */,
|
||||
@@ -841,6 +844,7 @@
|
||||
341F5EE12A0F373B00962D48 /* Login.swift in Sources */,
|
||||
341F5EF52A0F891200962D48 /* KissAccount.swift in Sources */,
|
||||
340A4DBD2A4C34BE005A1FBA /* IndexContext.swift in Sources */,
|
||||
34DA3E9D2A9A028200BB3439 /* Domestic.WebSocketData.swift in Sources */,
|
||||
34BC44762A8656570052D8EB /* Domestic.ContractNoticeWebSocket.swift in Sources */,
|
||||
34E7B9112A49BD2800B3AB9F /* DomesticIndex.swift in Sources */,
|
||||
341F5F0D2A15222E00962D48 /* AuthRequest.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user