Implement buy, sell, cancel command

This commit is contained in:
2023-05-30 19:30:22 +09:00
parent ae5537dd13
commit ee491c3107
4 changed files with 178 additions and 38 deletions

View File

@@ -32,7 +32,7 @@ public struct Domestic {
"ACNT_PRDT_CD": accountNo2,
"PDNO": productNo,
"ORD_DVSN": orderDivision.code,
"ORD_QTY": orderQuantity,
"ORD_QTY": String(orderQuantity),
"ORD_UNPR": String(orderPrice),
]
}
@@ -130,7 +130,7 @@ public struct Domestic {
let orderPrice: Int
let isAllQuantity: Bool
public init(credential: Credential, accessToken: String, productNo: String, orderOrganizationNo: String, orderNo: String, orderDivision: OrderDivision, orderRevisionType: OrderRevisionType, orderQuantity: Int = 0, orderPrice: Int) {
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
@@ -229,8 +229,8 @@ public struct Domestic {
"PDNO": productNo,
"ORD_UNPR": String(orderPrice),
"ORD_DVSN": orderDivision.code,
"CMA_EVLU_AMT_ICLD_YN": "",
"OVRS_ICLD_YN": "",
"CMA_EVLU_AMT_ICLD_YN": "N",
"OVRS_ICLD_YN": "N",
]
}
public var result: KResult? = nil
@@ -282,6 +282,21 @@ public struct Contract {
}
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 {
@@ -308,15 +323,23 @@ extension KissAccount {
}
public func cancelOrder() async throws -> Bool {
public func cancelOrder(cancel: ContractCancel) async throws -> OrderRevisionResult {
return try await withUnsafeThrowingContinuation { continuation in
guard let _ = accessToken else {
guard let accessToken = accessToken else {
continuation.resume(throwing: GeneralError.invalidAccessToken)
return
}
// TODO: work
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)
}
}
}
}
@@ -357,7 +380,7 @@ extension KissAccount {
}
///
///
///
public func canOrderStock(productNo: String, division: OrderDivision, price: Int) async throws -> PossibleOrderResult {
return try await withUnsafeThrowingContinuation { continuation in

View File

@@ -18,12 +18,12 @@ public struct OrderResult: Codable {
public let resultCode: String
public let messageCode: String
public let message: String
public let output: [OutputDetail]?
public let output: OutputDetail?
private enum CodingKeys: String, CodingKey {
case resultCode = "rt_cd"
case messageCode = "msg_cd"
case message = "msg"
case message = "msg1"
case output
}
@@ -45,7 +45,7 @@ public struct OrderRevisionResult: Codable {
public let resultCode: String
public let messageCode: String
public let message: String
public let output: [OutputDetail]?
public let output: OutputDetail?
private enum CodingKeys: String, CodingKey {
case resultCode = "rt_cd"
@@ -303,7 +303,7 @@ public struct PossibleOrderResult: Codable {
public let resultCode: String
public let messageCode: String
public let message: String
public let output: [OutputPossibleDetail]?
public let output: OutputPossibleDetail?
private enum CodingKeys: String, CodingKey {
case resultCode = "rt_cd"

View File

@@ -30,8 +30,10 @@ class KissConsole {
//
case buy = "buy"
case buyCheck = "buy check"
case sell = "sell"
case cancel = "cancel"
case modify = "modify"
//
case openBag = "open bag"
@@ -58,7 +60,7 @@ class KissConsole {
switch self {
case .quit, .loginMock, .loginReal:
return false
case .logout, .top, .buy, .sell, .cancel:
case .logout, .top, .buy, .buyCheck, .sell, .cancel, .modify:
return true
case .openBag:
return true
@@ -85,13 +87,16 @@ class KissConsole {
createSubpath("log")
createSubpath("data")
lastLogin()
let semaphore = DispatchSemaphore(value: 0)
Task {
await onLoadShop()
semaphore.signal()
}
semaphore.wait()
// 005930:
setCurrent(productNo: "005930")
}
private func getCommand(_ line: String) -> (KissCommand?, [String]) {
@@ -165,8 +170,10 @@ class KissConsole {
case .top: await onTop(args)
case .buy: await onBuy(args)
case .buyCheck: await onBuyCheck(args)
case .sell: await onSell(args)
case .cancel: await onCancel(args)
case .modify: await onModify(args)
case .openBag: await onOpenBag()
@@ -312,6 +319,15 @@ extension KissConsole {
print("\(error)")
}
}
private func setCurrent(productNo: String) {
productsLock.lock()
currentShortCode = productNo
productsLock.unlock()
let productName = getProduct(shortCode: productNo)?.itemName ?? ""
print("current product \(productNo) \(productName)")
}
}
@@ -369,30 +385,65 @@ extension KissConsole {
private func onBuy(_ args: [String]) async {
guard args.count == 3 else {
print("Missing buy paramters: buy (PNO) (PRICE) (QUANTITY)")
return
}
guard let price = Int(args[1]) else {
return
}
guard let quantity = Int(args[2]) else {
return
}
let productNo = args[0]
if price < 100 || quantity <= 0 {
print("Invalid price or quantity")
guard let product = getProduct(shortCode: productNo) else {
print("No product \(productNo)")
return
}
guard let price = Int(args[1]), (price == -8282 || price >= 100) else {
print("Invalid price: \(args[1])")
return
}
guard let quantity = Int(args[2]), (quantity == -82 || quantity > 0) else {
print("Invalid quantity: \(args[2])")
return
}
let division: OrderDivision = (price == -8282 ? .marketPrice: .limits)
let contract = Contract(productNo: productNo,
orderType: .buy,
orderDivision: .limits,
orderDivision: division,
orderQuantity: quantity, orderPrice: price)
do {
let result = try await account?.orderStock(contract: contract)
// TODO:
print(result)
let result = try await account!.orderStock(contract: contract)
if let output = result.output {
print("Success \(product.itemName) orderNo: \(output.orderNo) at \(output.orderTime)")
}
else {
print("Failed \(result.resultCode) \(result.messageCode) \(result.message)")
}
} catch {
print("\(error)")
}
}
private func onBuyCheck(_ args: [String]) async {
guard args.count == 2 else {
print("Missing buy check paramters: buy check (PNO) (PRICE)")
return
}
let productNo = args[0]
guard let product = getProduct(shortCode: productNo) else {
print("No product \(productNo)")
return
}
guard let price = Int(args[1]), price >= 100 else {
print("Invalid price: \(args[1])")
return
}
do {
let result = try await account!.canOrderStock(productNo: productNo, division: .limits, price: price)
if let output = result.output {
print("Success \(product.itemName) \(output)")
}
else {
print("Failed \(result.resultCode) \(result.messageCode) \(result.message)")
}
} catch {
print("\(error)")
}
@@ -400,22 +451,85 @@ extension KissConsole {
private func onSell(_ args: [String]) async {
// TODO: work
guard args.count == 3 else {
print("Missing sell paramters: sell (PNO) (PRICE) (QUANTITY)")
return
}
let productNo = args[0]
guard let product = getProduct(shortCode: productNo) else {
print("No product \(productNo)")
return
}
guard let price = Int(args[1]), (price == -8282 || price >= 100) else {
print("Invalid price: \(args[1])")
return
}
guard let quantity = Int(args[2]), quantity > 0 else {
print("Invalid quantity: \(args[2])")
return
}
let division: OrderDivision = (price == -8282 ? .marketPrice: .limits)
let contract = Contract(productNo: productNo,
orderType: .sell,
orderDivision: division,
orderQuantity: quantity, orderPrice: price)
do {
let result = try await account!.orderStock(contract: contract)
if let output = result.output {
print("Success \(product.itemName) orderNo: \(output.orderNo) at \(output.orderTime)")
}
else {
print("Failed \(result.resultCode) \(result.messageCode) \(result.message)")
}
} catch {
print("\(error)")
}
}
private func onCancel(_ args: [String]) async {
// TODO: work
guard args.count == 3 else {
print("Missing cancel paramters: cancel (PNO) (QUANTITY)")
return
}
let productNo = args[0]
guard let product = getProduct(shortCode: productNo) else {
print("No product \(productNo)")
return
}
let orderNo = args[1]
guard orderNo.count >= 1 else {
print("Invalid orderNo: \(args[1])")
return
}
guard let quantity = Int(args[2]), (quantity == -82 || quantity > 0) else {
print("Invalid quantity: \(args[2])")
return
}
do {
let _ = try await account?.cancelOrder()
let cancel = ContractCancel(productNo: productNo,
orderNo: orderNo,
orderQuantity: (quantity == -82 ? 0: quantity))
let result = try await account!.cancelOrder(cancel: cancel)
if let output = result.output {
print("Success \(product.itemName) orderNo: \(output.orderNo) at \(output.orderTime)")
}
else {
print("Failed \(result.resultCode) \(result.messageCode) \(result.message)")
}
} catch {
print("\(error)")
}
}
private func onModify(_ args: [String]) async {
// TODO: work
}
private func onOpenBag() async {
do {
let result = try await account!.getStockBalance()
@@ -445,7 +559,6 @@ extension KissConsole {
do {
let result = try await account!.getCurrentPrice(productNo: productNo)
if let output = result.output {
currentShortCode = output.shortProductCode
let productName = getProduct(shortCode: output.shortProductCode)?.itemName ?? ""
print("\t종목명: ", productName)
print("\t업종명: ", output.koreanMarketName, output.koreanBusinessTypeName)
@@ -467,6 +580,7 @@ extension KissConsole {
print("\tPER: ", output.per)
print("\tPBR: ", output.pbr)
print("\t주식 단축 종목코드", output.shortProductCode)
setCurrent(productNo: output.shortProductCode)
}
} catch {
print("\(error)")
@@ -637,11 +751,11 @@ extension KissConsole {
}
for item in items {
if let first = item.value.first {
currentShortCode = first.shortCode
print("\tISIN: ", first.isinCode)
print("\t상품명: ", item.key.maxSpace(20))
print("\t단축코드: ", first.shortCode)
print("\t시장구분: ", first.marketCategory, "\t기준일자: ", first.baseDate)
setCurrent(productNo: first.shortCode)
}
}
}
@@ -697,7 +811,7 @@ extension KissConsole {
if account.setLove(KissProfile.Love(isin: isin, name: product.itemName), index: index, for: key) {
print("Success \(product.itemName)")
account.saveProfile()
currentShortCode = product.shortCode
setCurrent(productNo: product.shortCode)
}
else {
print("Invalid index: \(index) for \(key)")
@@ -715,7 +829,7 @@ extension KissConsole {
account.addLove(KissProfile.Love(isin: isin, name: product.itemName), for: keys[0])
print("Success \(product.itemName)")
account.saveProfile()
currentShortCode = product.shortCode
setCurrent(productNo: product.shortCode)
}
}
}

View File

@@ -18,9 +18,11 @@ command | 설명
`login real` | Real 서버로 로그인. real-server.json 을 credential 로 사용.
`logout` | 접속한 서버에서 로그아웃.
`top` | 상위 거래량 30종목 (평균거래량)
WIP `buy (PNO) (수량)` | 구매
WIP `sell (PNO) (수량)` | 판매
WIP `cancel (PNO)` | 주문 취소
`buy (PNO) (가격) (수량)` | 상품을 구매. (가격) 에 -8282 로 입력하면 시장가격. (수량) 에 -82 로 입력하면 최대수량.
`buy check (PNO) (가격)` | 현재 잔고로 구매가 가능한 수량을 확인.
`sell (PNO) (가격) (수량)` | 보유한 상품을 판매. (가격) 에 -8282 로 입력하면 시장가격.
`cancel (PNO) (ONO) (수량)` | 주문 내역의 일부를 취소. (수량) 에 -82 로 입력하면 전체수량.
WIP `modify (PNO) (ONO) (가격) (수량)` | 주문 내역을 변경. (수량) 에 -82 로 입력하면 전체수량.
`open bag` | 보유 종목 열람.
`now [PNO]` | 종목의 현재가 열람. PNO 은 생략 가능.
`candle [PNO]` | 종목의 분봉 열람. PNO 은 생략 가능.
@@ -34,6 +36,7 @@ WIP `showcase` | 추천 상품을 제안함.
`hate (탭) (PNO)` | 관심 종목에서 삭제함.
* PNO 는 `Product NO` 의 약자이고, 상품의 `단축코드` (shortCode) 와 동일합니다.
* ONO 는 `Order NO` 의 약자이고, 고유한 주문번호 입니다.
# KissMeMatrix