Get market product
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
341F5F0D2A15222E00962D48 /* AuthRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F0C2A15222E00962D48 /* AuthRequest.swift */; };
|
||||
341F5F0F2A15223A00962D48 /* SeibroRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F0E2A15223A00962D48 /* SeibroRequest.swift */; };
|
||||
341F5F112A1685E700962D48 /* ShopRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F102A1685E700962D48 /* ShopRequest.swift */; };
|
||||
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -71,6 +72,7 @@
|
||||
341F5F0C2A15222E00962D48 /* AuthRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthRequest.swift; sourceTree = "<group>"; };
|
||||
341F5F0E2A15223A00962D48 /* SeibroRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeibroRequest.swift; sourceTree = "<group>"; };
|
||||
341F5F102A1685E700962D48 /* ShopRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopRequest.swift; sourceTree = "<group>"; };
|
||||
341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticShopProduct.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -145,6 +147,7 @@
|
||||
341F5EE62A0F3EFB00962D48 /* Domestic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
341F5F122A16CD3C00962D48 /* Shop */,
|
||||
341F5EE42A0F3EF400962D48 /* DomesticStock.swift */,
|
||||
341F5EF62A0F8B0500962D48 /* DomesticStockResult.swift */,
|
||||
341F5EE82A0F87FB00962D48 /* DomesticStockPrice.swift */,
|
||||
@@ -180,6 +183,14 @@
|
||||
path = Common;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
341F5F122A16CD3C00962D48 /* Shop */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */,
|
||||
);
|
||||
path = Shop;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -305,6 +316,7 @@
|
||||
341F5EEE2A0F884300962D48 /* ForeignStockPrice.swift in Sources */,
|
||||
341F5EDE2A0F300100962D48 /* Request.swift in Sources */,
|
||||
341F5F012A11155100962D48 /* DomesticStockSearchResult.swift in Sources */,
|
||||
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */,
|
||||
341F5EF22A0F887200962D48 /* DomesticFutures.swift in Sources */,
|
||||
341F5F112A1685E700962D48 /* ShopRequest.swift in Sources */,
|
||||
341F5EF92A0F907300962D48 /* DomesticStockPriceResult.swift in Sources */,
|
||||
|
||||
@@ -20,86 +20,4 @@ extension AuthRequest {
|
||||
"https://openapivts.koreainvestment.com:29443":
|
||||
"https://openapi.koreainvestment.com:9443"
|
||||
}
|
||||
|
||||
public var timeout: TimeInterval {
|
||||
15
|
||||
}
|
||||
|
||||
var queryUrl: URL? {
|
||||
URL(string: domain + url)
|
||||
}
|
||||
|
||||
func query(completion: @escaping (Result<KResult, Error>) -> Void) {
|
||||
guard let url = queryUrl else {
|
||||
completion(.failure(QueryError.invalidUrl))
|
||||
return
|
||||
}
|
||||
|
||||
if isMockAvailable == false, credential.isMock == true {
|
||||
completion(.failure(GeneralError.unsupportedQueryAtMockServer))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: timeout)
|
||||
|
||||
request.httpMethod = String(describing: method)
|
||||
|
||||
switch method {
|
||||
case .post:
|
||||
let jsonBody: Data?
|
||||
do {
|
||||
jsonBody = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
|
||||
} catch {
|
||||
completion(.failure(QueryError.invalidJson))
|
||||
return
|
||||
}
|
||||
|
||||
request.httpBody = jsonBody
|
||||
request.setValue("\(jsonBody?.count ?? 0)", forHTTPHeaderField: "Content-Length")
|
||||
|
||||
case .get:
|
||||
var queryItems = [URLQueryItem]()
|
||||
for item in body {
|
||||
if let value = item.value as? String {
|
||||
queryItems.append(URLQueryItem(name: item.key, value: value))
|
||||
}
|
||||
}
|
||||
|
||||
request.url?.append(queryItems: queryItems)
|
||||
}
|
||||
|
||||
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("JSON", forHTTPHeaderField: "Format")
|
||||
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
|
||||
|
||||
for field in header {
|
||||
request.setValue(field.value, forHTTPHeaderField: field.key)
|
||||
}
|
||||
|
||||
URLSession.shared.dataTask(with: request) { (data, response, error) in
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
completion(.failure(QueryError.missingData))
|
||||
return
|
||||
}
|
||||
|
||||
let stringData = String(data: data, encoding: .utf8) ?? ""
|
||||
print(stringData)
|
||||
|
||||
do {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .useDefaultKeys
|
||||
|
||||
let result = try decoder.decode(KResult.self, from: data)
|
||||
completion(.success(result))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,3 +6,18 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
extension Date {
|
||||
var yyyyMMdd: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd"
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
|
||||
var yyyyMMdd_HHmmss: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,3 +64,89 @@ extension Request {
|
||||
public var method: Method { .get }
|
||||
public var header: [String: String?] { [:] }
|
||||
}
|
||||
|
||||
|
||||
extension Request {
|
||||
|
||||
public var timeout: TimeInterval {
|
||||
15
|
||||
}
|
||||
|
||||
var queryUrl: URL? {
|
||||
URL(string: domain + url)
|
||||
}
|
||||
|
||||
func query(completion: @escaping (Result<KResult, Error>) -> Void) {
|
||||
guard let url = queryUrl else {
|
||||
completion(.failure(QueryError.invalidUrl))
|
||||
return
|
||||
}
|
||||
|
||||
if isMockAvailable == false {
|
||||
completion(.failure(GeneralError.unsupportedQueryAtMockServer))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: timeout)
|
||||
|
||||
request.httpMethod = String(describing: method)
|
||||
|
||||
switch method {
|
||||
case .post:
|
||||
let jsonBody: Data?
|
||||
do {
|
||||
jsonBody = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
|
||||
} catch {
|
||||
completion(.failure(QueryError.invalidJson))
|
||||
return
|
||||
}
|
||||
|
||||
request.httpBody = jsonBody
|
||||
request.setValue("\(jsonBody?.count ?? 0)", forHTTPHeaderField: "Content-Length")
|
||||
|
||||
case .get:
|
||||
var queryItems = [URLQueryItem]()
|
||||
for item in body {
|
||||
if let value = item.value as? String {
|
||||
queryItems.append(URLQueryItem(name: item.key, value: value))
|
||||
}
|
||||
}
|
||||
|
||||
request.url?.append(queryItems: queryItems)
|
||||
}
|
||||
|
||||
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("JSON", forHTTPHeaderField: "Format")
|
||||
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
|
||||
|
||||
for field in header {
|
||||
request.setValue(field.value, forHTTPHeaderField: field.key)
|
||||
}
|
||||
|
||||
URLSession.shared.dataTask(with: request) { (data, response, error) in
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
completion(.failure(QueryError.missingData))
|
||||
return
|
||||
}
|
||||
|
||||
let stringData = String(data: data, encoding: .utf8) ?? ""
|
||||
print(stringData)
|
||||
|
||||
do {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .useDefaultKeys
|
||||
|
||||
let result = try decoder.decode(KResult.self, from: data)
|
||||
completion(.success(result))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,6 @@ extension SeibroRequest {
|
||||
"http://api.seibro.or.kr"
|
||||
}
|
||||
|
||||
public var timeout: TimeInterval {
|
||||
15
|
||||
}
|
||||
|
||||
var queryUrl: URL? {
|
||||
URL(string: domain + url)
|
||||
}
|
||||
|
||||
func query(completion: @escaping (Result<KResult, Error>) -> Void) {
|
||||
guard let url = queryUrl else {
|
||||
completion(.failure(QueryError.invalidUrl))
|
||||
@@ -64,8 +56,8 @@ extension SeibroRequest {
|
||||
request.url?.append(queryItems: queryItems)
|
||||
}
|
||||
|
||||
request.setValue("application/xml", forHTTPHeaderField: "Content-Type")
|
||||
//request.setValue("XML", forHTTPHeaderField: "Format")
|
||||
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("JSON", forHTTPHeaderField: "Format")
|
||||
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
|
||||
|
||||
for field in header {
|
||||
|
||||
@@ -8,23 +8,14 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
protocol ShopRequest: AuthRequest {
|
||||
protocol ShopRequest: Request {
|
||||
var openApiKey: String { get }
|
||||
}
|
||||
|
||||
|
||||
/// 금융위원회_KRX상장종목정보
|
||||
extension ShopRequest {
|
||||
|
||||
public var domain: String {
|
||||
"https://apis.data.go.kr"
|
||||
}
|
||||
|
||||
public var timeout: TimeInterval {
|
||||
15
|
||||
}
|
||||
|
||||
var queryUrl: URL? {
|
||||
URL(string: domain + url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ extension Domestic {
|
||||
public struct StockVolumeRankRequest: TokenRequest {
|
||||
public typealias KResult = VolumeRankResult
|
||||
|
||||
public var isMockAvailable: Bool { false }
|
||||
public var isMockAvailable: Bool {
|
||||
credential.isMock == false
|
||||
}
|
||||
|
||||
public var url: String {
|
||||
"/uapi/domestic-stock/v1/quotations/volume-rank"
|
||||
|
||||
155
KissMe/Domestic/Shop/DomesticShopProduct.swift
Normal file
155
KissMe/Domestic/Shop/DomesticShopProduct.swift
Normal file
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// DomesticShopProduct.swift
|
||||
// KissMe
|
||||
//
|
||||
// Created by ened-book-m1 on 2023/05/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public struct DomesticShop {
|
||||
}
|
||||
|
||||
|
||||
extension DomesticShop {
|
||||
|
||||
public struct Product10Result: Codable {
|
||||
// TODO: work
|
||||
}
|
||||
|
||||
/// 한국예탁결제원_주식정보서비스 - 시장구분을 기준으로 단축종목번호와 종목명 조회
|
||||
public struct Product10Request: SeibroRequest {
|
||||
public typealias KResult = String
|
||||
|
||||
public var url: String {
|
||||
"/openapi/service/StockSvc/getShotnByMartN1"
|
||||
}
|
||||
public var method: Method { .get }
|
||||
|
||||
public var body: [String : Any] {
|
||||
[
|
||||
"ServiceKey": openApiKey,
|
||||
"martTpcd": String(market.rawValue),
|
||||
]
|
||||
}
|
||||
public var result: KResult? = nil
|
||||
|
||||
|
||||
public enum MarketCode: Int {
|
||||
/// 유가증권시장
|
||||
case stock = 11
|
||||
/// 코스닥
|
||||
case kosdaq = 12
|
||||
/// K-OTC
|
||||
case kotc = 13
|
||||
/// 코넥스
|
||||
case konex = 14
|
||||
/// 기타시장
|
||||
case etc = 50
|
||||
}
|
||||
|
||||
public let openApiKey: String
|
||||
let market: MarketCode
|
||||
|
||||
init(openApiKey: String, market: MarketCode) {
|
||||
self.openApiKey = openApiKey
|
||||
self.market = market
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension DomesticShop {
|
||||
|
||||
|
||||
public struct ProductResponse: Codable {
|
||||
public let response: Response
|
||||
|
||||
public struct Response: Codable {
|
||||
public let header: Header
|
||||
public let body: Body
|
||||
}
|
||||
|
||||
public struct Header: Codable {
|
||||
public let resultCode: String
|
||||
public let resultMessage: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case resultCode
|
||||
case resultMessage = "resultMsg"
|
||||
}
|
||||
}
|
||||
|
||||
public struct Body: Codable {
|
||||
public let numOfRows: Int
|
||||
public let pageNo: Int
|
||||
public let totalCount: Int
|
||||
public let items: Items?
|
||||
}
|
||||
|
||||
public struct Items: Codable {
|
||||
public let item: [Item]
|
||||
}
|
||||
|
||||
public struct Item: Codable {
|
||||
let baseDate: String
|
||||
let shortCode: String
|
||||
let isinCode: String
|
||||
let marketCategory: String
|
||||
let itemName: String
|
||||
let corporationNo: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case baseDate = "basDt"
|
||||
case shortCode = "srtnCd"
|
||||
case isinCode = "isinCd"
|
||||
case marketCategory = "mrktCtg"
|
||||
case itemName = "itmsNm"
|
||||
case corporationNo = "crno"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 금융위원회_KRX상장종목정보
|
||||
///
|
||||
public struct ProductRequest: Request {
|
||||
public typealias KResult = ProductResponse
|
||||
|
||||
public var domain: String {
|
||||
"https://apis.data.go.kr"
|
||||
}
|
||||
public var url: String {
|
||||
"/1160100/service/GetKrxListedInfoService/getItemInfo"
|
||||
}
|
||||
public var method: Method { .get }
|
||||
|
||||
public var header: [String : String?] {
|
||||
[:]
|
||||
}
|
||||
public var body: [String: Any] {
|
||||
var body: [String: Any] =
|
||||
[
|
||||
"numOfRows": String(9999),
|
||||
"pageNo": String(1),
|
||||
"resultType": "json",
|
||||
"serviceKey": openApiKey,
|
||||
]
|
||||
if let baseDate = baseDate {
|
||||
body["beginBasDt"] = baseDate.yyyyMMdd
|
||||
}
|
||||
return body
|
||||
}
|
||||
public var result: KResult? = nil
|
||||
|
||||
|
||||
public let openApiKey: String
|
||||
let baseDate: Date?
|
||||
|
||||
init(openApiKey: String, baseDate: Date?) {
|
||||
self.openApiKey = openApiKey
|
||||
self.baseDate = baseDate
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,70 +36,21 @@ public class KissShop {
|
||||
}
|
||||
|
||||
|
||||
public struct DomesticShop {
|
||||
}
|
||||
|
||||
|
||||
extension DomesticShop {
|
||||
|
||||
public enum MarketCode: Int {
|
||||
/// 유가증권시장
|
||||
case stock = 11
|
||||
/// 코스닥
|
||||
case kosdaq = 12
|
||||
/// K-OTC
|
||||
case kotc = 13
|
||||
/// 코넥스
|
||||
case konex = 14
|
||||
/// 기타시장
|
||||
case etc = 50
|
||||
}
|
||||
|
||||
public struct ProductResult: Codable {
|
||||
// TODO: work
|
||||
}
|
||||
|
||||
/// 한국예탁결제원_주식정보서비스 - 시장구분을 기준으로 단축종목번호와 종목명 조회
|
||||
public struct ProductRequest: SeibroRequest {
|
||||
public typealias KResult = String
|
||||
|
||||
public var url: String {
|
||||
"/openapi/service/StockSvc/getShotnByMartN1"
|
||||
}
|
||||
public var method: Method { .get }
|
||||
|
||||
public var body: [String : Any] {
|
||||
[
|
||||
"ServiceKey": openApiKey,
|
||||
"martTpcd": String(market.rawValue),
|
||||
"pageNo": 1,
|
||||
"numOfRows": 10000,
|
||||
]
|
||||
}
|
||||
public var result: KResult? = nil
|
||||
|
||||
|
||||
public let openApiKey: String
|
||||
let market: MarketCode
|
||||
|
||||
init(openApiKey: String, market: MarketCode) {
|
||||
self.openApiKey = openApiKey
|
||||
self.market = market
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension KissShop {
|
||||
|
||||
public func getProduct(market: DomesticShop.MarketCode) async throws -> String {
|
||||
public func getProduct() async throws -> [DomesticShop.ProductResponse.Item] {
|
||||
return try await withUnsafeThrowingContinuation { continuation in
|
||||
|
||||
let request = DomesticShop.ProductRequest(openApiKey: self.credential.openApiKey, market: market)
|
||||
let request = DomesticShop.ProductRequest(openApiKey: self.credential.openApiKey, baseDate: Date())
|
||||
request.query { result in
|
||||
switch result {
|
||||
case .success(let result):
|
||||
continuation.resume(returning: result)
|
||||
if let items = result.response.body.items {
|
||||
continuation.resume(returning: items.item)
|
||||
}
|
||||
else {
|
||||
continuation.resume(returning: [])
|
||||
}
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class KissConsole {
|
||||
|
||||
func onLoadShop() async {
|
||||
do {
|
||||
_ = try await shop?.getProduct(market: .kosdaq)
|
||||
_ = try await shop?.getProduct()
|
||||
} catch {
|
||||
print("\(error)")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user