414 lines
14 KiB
Swift
414 lines
14 KiB
Swift
//
|
|
// DomesticStock.swift
|
|
// KissMe
|
|
//
|
|
// Created by ened-book-m1 on 2023/05/13.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
|
|
public struct Domestic {
|
|
public typealias Candle = MinutePriceResult.OutputPrice
|
|
public typealias CandlePeriod = PeriodPriceResult.OutputPrice
|
|
public typealias Top = VolumeRankResult.OutputDetail
|
|
public typealias CurrentPrice = CurrentPriceResult.OutputDetail
|
|
public typealias Investor = InvestorVolumeResult.OutputDetail
|
|
}
|
|
|
|
|
|
extension Domestic {
|
|
|
|
/// 국내주식주문 - 주식주문(현금)
|
|
///
|
|
public struct StockOrderRequest: OrderRequest {
|
|
public typealias KResult = OrderResult
|
|
|
|
public var url: String { "/uapi/domestic-stock/v1/trading/order-cash" }
|
|
public var method: Method { .post }
|
|
|
|
public var header: [String: String?] {
|
|
[
|
|
"authorization": "Bearer \(accessToken)",
|
|
"appkey": credential.appKey,
|
|
"appsecret": credential.appSecret,
|
|
"tr_id": trId,
|
|
]
|
|
}
|
|
public var body: [String: Any] {
|
|
[
|
|
"CANO": accountNo8,
|
|
"ACNT_PRDT_CD": accountNo2,
|
|
"PDNO": productNo,
|
|
"ORD_DVSN": orderDivision.code,
|
|
"ORD_QTY": String(orderQuantity),
|
|
"ORD_UNPR": String(orderPrice),
|
|
]
|
|
}
|
|
public var result: KResult? = nil
|
|
public let credential: Credential
|
|
|
|
|
|
private var trId: String {
|
|
if credential.isMock {
|
|
switch orderType {
|
|
case .buy: return "VTTC0802U"
|
|
case .sell: return "VTTC0801U"
|
|
}
|
|
}
|
|
else {
|
|
switch orderType {
|
|
case .buy: return "TTTC0802U"
|
|
case .sell: return "TTTC0801U"
|
|
}
|
|
}
|
|
}
|
|
|
|
public let accessToken: String
|
|
let productNo: String
|
|
|
|
let orderType: OrderType
|
|
let orderDivision: OrderDivision
|
|
let orderQuantity: Int
|
|
let orderPrice: Int
|
|
|
|
public init(credential: Credential, accessToken: String, contract: Contract) {
|
|
self.credential = credential
|
|
self.accessToken = accessToken
|
|
self.productNo = contract.productNo
|
|
self.orderType = contract.orderType
|
|
self.orderDivision = contract.orderDivision
|
|
self.orderQuantity = contract.orderQuantity
|
|
self.orderPrice = contract.orderPrice
|
|
}
|
|
}
|
|
|
|
|
|
/// 국내주식주문 - 주식주문(정정취소)[v1_국내주식-003]
|
|
///
|
|
public struct StockOrderRevisionRequest: OrderRequest {
|
|
public typealias KResult = OrderRevisionResult
|
|
|
|
public var url: String {
|
|
"/uapi/domestic-stock/v1/trading/order-rvsecncl"
|
|
}
|
|
public var method: Method { .post }
|
|
|
|
public var header: [String: String?] {
|
|
[
|
|
"authorization": "Bearer \(accessToken)",
|
|
"appkey": credential.appKey,
|
|
"appsecret": credential.appSecret,
|
|
"tr_id": trId,
|
|
]
|
|
}
|
|
public var body: [String: Any] {
|
|
[
|
|
"CANO": accountNo8,
|
|
"ACNT_PRDT_CD": accountNo2,
|
|
"KRX_FWDG_ORD_ORGNO": orderOrganizationNo,
|
|
"ORGN_ODNO": orderNo,
|
|
"ORD_DVSN": orderDivision.code,
|
|
"RVSE_CNCL_DVSN_CD": orderRevisionType.code,
|
|
"ORD_QTY": String(orderQuantity),
|
|
"ORD_UNPR": String(orderPrice),
|
|
"QTY_ALL_ORD_YN": isAllQuantity ? "Y": "N"
|
|
]
|
|
}
|
|
public var result: KResult? = nil
|
|
public let credential: Credential
|
|
|
|
|
|
private var trId: String {
|
|
if credential.isMock {
|
|
return "VTTC0803U"
|
|
}
|
|
else {
|
|
return "TTTC0803U"
|
|
}
|
|
}
|
|
|
|
public let accessToken: String
|
|
let productNo: String
|
|
|
|
let orderOrganizationNo: String
|
|
let orderNo: String
|
|
let orderDivision: OrderDivision
|
|
let orderRevisionType: OrderRevisionType
|
|
let orderQuantity: Int
|
|
let orderPrice: Int
|
|
let isAllQuantity: Bool
|
|
|
|
public init(credential: Credential, accessToken: String, productNo: String, orderOrganizationNo: String, orderNo: String, orderDivision: OrderDivision, orderRevisionType: OrderRevisionType, orderQuantity: Int, orderPrice: Int) {
|
|
self.credential = credential
|
|
self.accessToken = accessToken
|
|
self.productNo = productNo
|
|
self.orderOrganizationNo = orderOrganizationNo
|
|
self.orderNo = orderNo
|
|
self.orderDivision = orderDivision
|
|
self.orderRevisionType = orderRevisionType
|
|
self.orderQuantity = orderQuantity
|
|
self.orderPrice = orderPrice
|
|
self.isAllQuantity = (orderQuantity == 0)
|
|
}
|
|
}
|
|
|
|
|
|
/// 국내주식주문 - 주식잔고조회[v1_국내주식-006]
|
|
///
|
|
public struct StockBalanceRequest: OrderRequest {
|
|
public typealias KResult = BalanceResult
|
|
|
|
public var url: String {
|
|
"/uapi/domestic-stock/v1/trading/inquire-balance"
|
|
}
|
|
public var method: Method { .get }
|
|
|
|
public var header: [String: String?] {
|
|
[
|
|
"authorization": "Bearer \(accessToken)",
|
|
"appkey": credential.appKey,
|
|
"appsecret": credential.appSecret,
|
|
"tr_id": trId,
|
|
"tr_cont": isNext ? "N": " ",
|
|
]
|
|
}
|
|
public var body: [String: Any] {
|
|
[
|
|
"CANO": accountNo8,
|
|
"ACNT_PRDT_CD": accountNo2,
|
|
"AFHR_FLPR_YN": "N",
|
|
"OFL_YN": " ",
|
|
"INQR_DVSN": "02",
|
|
"UNPR_DVSN": "01",
|
|
"FUND_STTL_ICLD_YN": "N",
|
|
"FNCG_AMT_AUTO_RDPT_YN": "N",
|
|
"PRCS_DVSN": "01",
|
|
"CTX_AREA_FK100": " ",
|
|
"CTX_AREA_NK100": " ",
|
|
]
|
|
}
|
|
public var result: KResult? = nil
|
|
public let credential: Credential
|
|
|
|
|
|
private var trId: String {
|
|
if credential.isMock {
|
|
return "VTTC8434R"
|
|
}
|
|
else {
|
|
return "TTTC8434R"
|
|
}
|
|
}
|
|
|
|
public let accessToken: String
|
|
let isNext: Bool
|
|
|
|
public init(credential: Credential, accessToken: String, isNext: Bool) {
|
|
self.credential = credential
|
|
self.accessToken = accessToken
|
|
self.isNext = isNext
|
|
}
|
|
}
|
|
|
|
|
|
/// 국내주식주문 - 매수가능조회[v1_국내주식-007]
|
|
///
|
|
public struct StockPossibleOrderRequest: OrderRequest {
|
|
public typealias KResult = PossibleOrderResult
|
|
|
|
public var url: String {
|
|
"/uapi/domestic-stock/v1/trading/inquire-psbl-order"
|
|
}
|
|
public var method: Method { .get }
|
|
|
|
public var header: [String: String?] {
|
|
[
|
|
"authorization": "Bearer \(accessToken)",
|
|
"appkey": credential.appKey,
|
|
"appsecret": credential.appSecret,
|
|
"tr_id": trId,
|
|
"tr_cont": isNext ? "N": " ",
|
|
]
|
|
}
|
|
public var body: [String: Any] {
|
|
[
|
|
"CANO": accountNo8,
|
|
"ACNT_PRDT_CD": accountNo2,
|
|
"PDNO": productNo,
|
|
"ORD_UNPR": String(orderPrice),
|
|
"ORD_DVSN": orderDivision.code,
|
|
"CMA_EVLU_AMT_ICLD_YN": "N",
|
|
"OVRS_ICLD_YN": "N",
|
|
]
|
|
}
|
|
public var result: KResult? = nil
|
|
public let credential: Credential
|
|
|
|
|
|
private var trId: String {
|
|
if credential.isMock {
|
|
return "VTTC8908R"
|
|
}
|
|
else {
|
|
return "TTTC8908R"
|
|
}
|
|
}
|
|
|
|
public let accessToken: String
|
|
let productNo: String
|
|
|
|
let orderDivision: OrderDivision
|
|
let orderPrice: Int
|
|
let isNext: Bool
|
|
|
|
public init(credential: Credential, accessToken: String, productNo: String, orderDivision: OrderDivision, orderPrice: Int, isNext: Bool) {
|
|
self.credential = credential
|
|
self.accessToken = accessToken
|
|
self.productNo = productNo
|
|
self.orderDivision = orderDivision
|
|
self.orderPrice = orderPrice
|
|
self.isNext = isNext
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public struct Contract {
|
|
public let productNo: String
|
|
public let orderType: OrderType
|
|
public let orderDivision: OrderDivision
|
|
public let orderQuantity: Int
|
|
public let orderPrice: Int
|
|
|
|
public init(productNo: String, orderType: OrderType, orderDivision: OrderDivision, orderQuantity: Int, orderPrice: Int) {
|
|
self.productNo = productNo
|
|
self.orderType = orderType
|
|
self.orderDivision = orderDivision
|
|
self.orderQuantity = orderQuantity
|
|
self.orderPrice = orderPrice
|
|
}
|
|
}
|
|
|
|
|
|
public struct ContractCancel {
|
|
public let productNo: String
|
|
public let orderNo: String
|
|
|
|
// 모든 수량을 취소하려면 0 으로 설정
|
|
public let orderQuantity: Int
|
|
|
|
public init(productNo: String, orderNo: String, orderQuantity: Int) {
|
|
self.productNo = productNo
|
|
self.orderNo = orderNo
|
|
self.orderQuantity = orderQuantity
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: Stock Order
|
|
extension KissAccount {
|
|
|
|
/// 주식 주문하기
|
|
///
|
|
public func orderStock(contract: Contract) async throws -> Domestic.OrderResult {
|
|
return try await withUnsafeThrowingContinuation { continuation in
|
|
|
|
guard let accessToken = accessToken else {
|
|
continuation.resume(throwing: GeneralError.invalidAccessToken)
|
|
return
|
|
}
|
|
|
|
let request = Domestic.StockOrderRequest(credential: credential, accessToken: accessToken, contract: contract)
|
|
request.query { result in
|
|
switch result {
|
|
case .success(let result):
|
|
continuation.resume(returning: result)
|
|
case .failure(let error):
|
|
continuation.resume(throwing: error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public func cancelOrder(cancel: ContractCancel) async throws -> Domestic.OrderRevisionResult {
|
|
return try await withUnsafeThrowingContinuation { continuation in
|
|
|
|
guard let accessToken = accessToken else {
|
|
continuation.resume(throwing: GeneralError.invalidAccessToken)
|
|
return
|
|
}
|
|
|
|
let request = Domestic.StockOrderRevisionRequest(credential: credential, accessToken: accessToken, productNo: cancel.productNo, orderOrganizationNo: "", orderNo: cancel.orderNo, orderDivision: .limits, orderRevisionType: .cancel, orderQuantity: cancel.orderQuantity, orderPrice: 0)
|
|
request.query { result in
|
|
switch result {
|
|
case .success(let result):
|
|
continuation.resume(returning: result)
|
|
case .failure(let error):
|
|
continuation.resume(throwing: error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public func changeOrder() async throws -> Bool {
|
|
return try await withUnsafeThrowingContinuation { continuation in
|
|
|
|
guard let _ = accessToken else {
|
|
continuation.resume(throwing: GeneralError.invalidAccessToken)
|
|
return
|
|
}
|
|
|
|
// TODO: work
|
|
}
|
|
}
|
|
|
|
|
|
/// 주식 잔고 조회하기
|
|
///
|
|
public func getStockBalance() async throws -> Domestic.BalanceResult {
|
|
return try await withUnsafeThrowingContinuation { continuation in
|
|
|
|
guard let accessToken = accessToken else {
|
|
continuation.resume(throwing: GeneralError.invalidAccessToken)
|
|
return
|
|
}
|
|
|
|
let request = Domestic.StockBalanceRequest(credential: credential, accessToken: accessToken, isNext: false)
|
|
request.query { result in
|
|
switch result {
|
|
case .success(let result):
|
|
continuation.resume(returning: result)
|
|
case .failure(let error):
|
|
continuation.resume(throwing: error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// 주문 가능한 주식 수량 판단하기
|
|
///
|
|
public func canOrderStock(productNo: String, division: OrderDivision, price: Int) async throws -> Domestic.PossibleOrderResult {
|
|
return try await withUnsafeThrowingContinuation { continuation in
|
|
|
|
guard let accessToken = accessToken else {
|
|
continuation.resume(throwing: GeneralError.invalidAccessToken)
|
|
return
|
|
}
|
|
|
|
let request = Domestic.StockPossibleOrderRequest(credential: credential, accessToken: accessToken, productNo: productNo, orderDivision: division, orderPrice: price, isNext: false)
|
|
request.query { result in
|
|
switch result {
|
|
case .success(let result):
|
|
continuation.resume(returning: result)
|
|
case .failure(let error):
|
|
continuation.resume(throwing: error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|