Get market product

This commit is contained in:
2023-05-19 09:57:23 +09:00
parent e01c539adc
commit 2192b1137a
10 changed files with 283 additions and 161 deletions

View File

@@ -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 */,

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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"

View 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
}
}
}

View File

@@ -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)
}

View File

@@ -95,7 +95,7 @@ class KissConsole {
func onLoadShop() async {
do {
_ = try await shop?.getProduct(market: .kosdaq)
_ = try await shop?.getProduct()
} catch {
print("\(error)")
}