565 lines
17 KiB
Swift
565 lines
17 KiB
Swift
//
|
|
// main.swift
|
|
// KissMeConsole
|
|
//
|
|
// Created by ened-book-m1 on 2023/05/09.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
//KissConsole().run()
|
|
|
|
import KissMe
|
|
|
|
//test_get_websocket_key_and_contact_price()
|
|
test_get_websocket_key_and_asking_price()
|
|
|
|
|
|
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"
|
|
}
|
|
|
|
|
|
enum MarketOperationCode: UInt8 {
|
|
// TODO: work
|
|
// (1) 첫 번째 비트
|
|
// 1 : 장개시전
|
|
// 2 : 장중
|
|
// 3 : 장종료후
|
|
// 4 : 시간외단일가
|
|
// 7 : 일반Buy-in
|
|
// 8 : 당일Buy-in
|
|
//
|
|
// (2) 두 번째 비트
|
|
// 0 : 보통
|
|
// 1 : 종가
|
|
// 2 : 대량
|
|
// 3 : 바스켓
|
|
// 7 : 정리매매
|
|
// 8 : Buy-in
|
|
case aa = 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
|
|
}
|
|
|
|
|
|
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
|
|
}
|
|
|
|
|
|
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
|
|
}
|
|
|
|
|
|
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\"}}"
|
|
|
|
}
|
|
|
|
|
|
enum JsonTrDataType {
|
|
case json(String)
|
|
case contractPrice(ContractPrice)
|
|
case askingPrice(AskingPrice)
|
|
case contractNotice(ContractNotice)
|
|
}
|
|
|
|
|
|
func parseJsonTrData(_ str: String) throws -> [JsonTrDataType] {
|
|
var dataArray = [JsonTrDataType]()
|
|
var startAt = str.startIndex
|
|
|
|
while startAt < str.endIndex {
|
|
switch str.first {
|
|
case "{":
|
|
var openedCount = 0
|
|
let charset = CharacterSet(charactersIn: "{}")
|
|
|
|
repeat {
|
|
guard let r = str.rangeOfCharacter(from: charset, options: [], range: startAt ..< 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[str.startIndex ..< r.upperBound])
|
|
dataArray.append(.json(jsonString))
|
|
startAt = r.upperBound
|
|
}
|
|
default:
|
|
throw GeneralError.impossibleJsonCharacter
|
|
}
|
|
startAt = r.upperBound
|
|
} while true
|
|
|
|
case "0":
|
|
let data = try parseTrData(false, str: str, startAt: startAt)
|
|
dataArray.append(contentsOf: data)
|
|
|
|
case "1":
|
|
let data = try parseTrData(true, str: str, startAt: startAt)
|
|
dataArray.append(contentsOf: data)
|
|
|
|
default:
|
|
throw GeneralError.invalidWebSocketData
|
|
}
|
|
}
|
|
|
|
return dataArray
|
|
}
|
|
|
|
|
|
private func parseTrData(_ encrypted: Bool, str: String, startAt: String.Index) throws -> [JsonTrDataType] {
|
|
var dataArray = [JsonTrDataType]()
|
|
var startAt = str.startIndex
|
|
let charset = CharacterSet(charactersIn: "|")
|
|
|
|
guard let i1 = str.rangeOfCharacter(from: charset, options: [], range: startAt ..< str.endIndex) else {
|
|
throw GeneralError.notEnoughWebSocketData
|
|
}
|
|
|
|
let encryption = String(str[startAt ..< i1.upperBound])
|
|
guard encryption == "0" || encryption == "1" else {
|
|
throw GeneralError.invalidWebSocketData_EncryptionField
|
|
}
|
|
startAt = i1.upperBound
|
|
guard let i2 = str.rangeOfCharacter(from: charset, options: [], range: startAt ..< str.endIndex) else {
|
|
throw GeneralError.notEnoughWebSocketData
|
|
}
|
|
|
|
let trId = String(str[startAt ..< i2.upperBound])
|
|
startAt = i2.upperBound
|
|
guard let i3 = str.rangeOfCharacter(from: charset, options: [], range: startAt ..< str.endIndex) else {
|
|
throw GeneralError.notEnoughWebSocketData
|
|
}
|
|
|
|
let dataCountString = String(str[startAt ..< i3.upperBound])
|
|
guard let dataCount = Int(dataCountString) else {
|
|
throw GeneralError.notEnoughWebSocketData
|
|
}
|
|
|
|
switch trId {
|
|
case "H0STCNT0":
|
|
for _ in 0 ..< dataCount {
|
|
|
|
}
|
|
|
|
case "H0STASP0":
|
|
break
|
|
|
|
case "H0STCNI0":
|
|
break
|
|
|
|
default:
|
|
throw GeneralError.invalidWebSocketData_TrIdField
|
|
}
|
|
|
|
return dataArray
|
|
}
|
|
|
|
|
|
func test_get_websocket_key_and_asking_price() {
|
|
let isMock = false
|
|
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
Task {
|
|
guard let (account, approvalKey) = await test_get_websocket_key(isMock: isMock) else {
|
|
return
|
|
}
|
|
|
|
let webSocketCredential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey)
|
|
|
|
var socket = Domestic.AskingPriceWebSocket(credential: webSocketCredential, productCode: KissConsole.defaultProductNo)
|
|
|
|
do {
|
|
try await socket.connect()
|
|
let result = try await socket.subscribe()
|
|
print(result)
|
|
|
|
try await Task.sleep(nanoseconds: 1_000_000_000 * 3)
|
|
let result2 = try await socket.unsubscribe()
|
|
print(result2)
|
|
|
|
if let message = try await socket.receive() {
|
|
print(message)
|
|
}
|
|
|
|
try await Task.sleep(nanoseconds: 1_000_000_000 * 3)
|
|
socket.disconnect()
|
|
|
|
try await Task.sleep(nanoseconds: 1_000_000_000 * 1)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
|
|
semaphore.signal()
|
|
}
|
|
semaphore.wait()
|
|
}
|
|
|
|
|
|
func test_get_websocket_key_and_contact_price() {
|
|
let isMock = false
|
|
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
Task {
|
|
guard let (account, approvalKey) = await test_get_websocket_key(isMock: isMock) else {
|
|
return
|
|
}
|
|
|
|
let webSocketCredential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey)
|
|
|
|
var socket = Domestic.ContractPriceWebSocket(credential: webSocketCredential, productCode: KissConsole.defaultProductNo)
|
|
|
|
do {
|
|
try await socket.connect()
|
|
let result = try await socket.subscribe()
|
|
print(result)
|
|
|
|
try await Task.sleep(nanoseconds: 1_000_000_000 * 3)
|
|
let result2 = try await socket.unsubscribe()
|
|
print(result2)
|
|
|
|
if let message = try await socket.receive() {
|
|
print(message)
|
|
}
|
|
|
|
try await Task.sleep(nanoseconds: 1_000_000_000 * 3)
|
|
socket.disconnect()
|
|
|
|
try await Task.sleep(nanoseconds: 1_000_000_000 * 1)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
|
|
semaphore.signal()
|
|
}
|
|
semaphore.wait()
|
|
}
|
|
|
|
|
|
func test_get_websocket_key(isMock: Bool) async -> (KissAccount, String)? {
|
|
let credential: Credential
|
|
|
|
do {
|
|
credential = try KissCredential(isMock: isMock)
|
|
} catch {
|
|
print(error)
|
|
return nil
|
|
}
|
|
|
|
let account = KissAccount(credential: credential)
|
|
do {
|
|
/// Return existing valid key
|
|
if let approvalKey = account.approvalKey {
|
|
return (account, approvalKey)
|
|
}
|
|
|
|
if try await account.login() {
|
|
let approvalKey = try await account.getApprovalKey()
|
|
print("approvalKey : \(approvalKey)")
|
|
return (account, approvalKey)
|
|
}
|
|
} catch {
|
|
print(error)
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|