Validate comma before writing csv file
This commit is contained in:
@@ -16,6 +16,10 @@ extension Date {
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
|
||||
public var yyyyMM01: String {
|
||||
yyyyMM + "01"
|
||||
}
|
||||
|
||||
public var yyyyMMdd: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.timeZone = TimeZone(abbreviation: "KST")
|
||||
@@ -165,8 +169,22 @@ extension FileManager {
|
||||
|
||||
|
||||
public func valueToString(_ any: Any) -> String {
|
||||
|
||||
func validateComma(_ s: String) -> String {
|
||||
let comma: CharacterSet = [","]
|
||||
if s.rangeOfCharacter(from: comma) != nil {
|
||||
assertionFailure("There are comma in: \(s)")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
switch any {
|
||||
case let s as String: return s
|
||||
case let s as String:
|
||||
#if DEBUG
|
||||
return validateComma(s)
|
||||
#else
|
||||
return s
|
||||
#endif
|
||||
case let i as Int8: return String(i)
|
||||
case let i as UInt8: return String(i)
|
||||
case let i as Int16: return String(i)
|
||||
@@ -184,7 +202,12 @@ public func valueToString(_ any: Any) -> String {
|
||||
case let d as Double: return String(d)
|
||||
case let raw as any RawRepresentable:
|
||||
switch raw.rawValue {
|
||||
case let s as String: return s
|
||||
case let s as String:
|
||||
#if DEBUG
|
||||
return validateComma(s)
|
||||
#else
|
||||
return s
|
||||
#endif
|
||||
case let i as Int8: return String(i)
|
||||
case let i as UInt8: return String(i)
|
||||
case let i as Int16: return String(i)
|
||||
@@ -198,7 +221,8 @@ public func valueToString(_ any: Any) -> String {
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
case let c as CustomStringConvertible: return c.description
|
||||
case let c as CustomStringConvertible:
|
||||
return validateComma(c.description)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -279,11 +303,13 @@ extension Array where Element: PropertyIterable {
|
||||
|
||||
for (index, item) in items.enumerated() {
|
||||
if index == 0 {
|
||||
headerItems = item.split(separator: ",").map { String($0) }
|
||||
headerItems = item.split(separator: ",", omittingEmptySubsequences: false).map { String($0) }
|
||||
continue
|
||||
}
|
||||
let array = item.split(separator: ",").map { String($0) }
|
||||
let element = try Element(array: array)
|
||||
let array = item.split(separator: ",", omittingEmptySubsequences: false).map { String($0) }
|
||||
|
||||
//print("index: \(index), \(fromFile.path)")
|
||||
let element = try Element(array: array, source: item)
|
||||
|
||||
if index == 1, verifyHeader {
|
||||
// Validate property with header
|
||||
@@ -342,7 +368,7 @@ extension String {
|
||||
|
||||
public static func readCsvHeader(fromFile: URL) throws -> [String] {
|
||||
let header = try String(firstLineOfFile: fromFile.path)
|
||||
return header.split(separator: ",").map { String($0) }
|
||||
return header.split(separator: ",", omittingEmptySubsequences: false).map { String($0) }
|
||||
}
|
||||
|
||||
public func writeAppending(toFile path: String) throws {
|
||||
|
||||
@@ -34,5 +34,5 @@ public extension PropertyIterable {
|
||||
|
||||
|
||||
public protocol ArrayDecodable {
|
||||
init(array: [String]) throws
|
||||
init(array: [String], source: String.SubSequence) throws
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public enum GeneralError: Error {
|
||||
case cannotWriteFile
|
||||
case cannotReadFileLine
|
||||
case cannotReadFileToConvertString
|
||||
case incorrectArrayItems
|
||||
case incorrectArrayItems(String, Int, Int)
|
||||
case headerNoFiendName(String)
|
||||
case noCsvFile
|
||||
case invalidCandleCsvFile(String)
|
||||
|
||||
@@ -12,9 +12,9 @@ public struct LocalName: Codable, PropertyIterable, ArrayDecodable {
|
||||
public let fieldName: String
|
||||
public let localizedName: String
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 2 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 2)
|
||||
}
|
||||
fieldName = array[0]
|
||||
localizedName = array[1]
|
||||
149
KissMe/Sources/Context/ShopContext.swift
Normal file
149
KissMe/Sources/Context/ShopContext.swift
Normal file
@@ -0,0 +1,149 @@
|
||||
//
|
||||
// ShopContext.swift
|
||||
// KissMe
|
||||
//
|
||||
// Created by ened-book-m1 on 2023/06/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
open class ShopContext {
|
||||
|
||||
private var productsLock = NSLock()
|
||||
/// 전체 종목 정보
|
||||
private var products = [String: [DomesticShop.Product]]()
|
||||
|
||||
public var productsCount: Int {
|
||||
productsLock.lock()
|
||||
defer {
|
||||
productsLock.unlock()
|
||||
}
|
||||
return products.count
|
||||
}
|
||||
|
||||
/// 현재 기본으로 선택된 productNo
|
||||
private var shortCodeLock = NSLock()
|
||||
private var _currentShortCode: String?
|
||||
public var currentShortCode: String? {
|
||||
get {
|
||||
shortCodeLock.lock()
|
||||
defer {
|
||||
shortCodeLock.unlock()
|
||||
}
|
||||
return _currentShortCode
|
||||
}
|
||||
set {
|
||||
shortCodeLock.lock()
|
||||
defer {
|
||||
shortCodeLock.unlock()
|
||||
}
|
||||
_currentShortCode = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ShopContext {
|
||||
|
||||
public func loadShop(url: URL, profile: Bool = false) {
|
||||
let appTime1 = Date.appTime
|
||||
guard let stringCsv = try? String(contentsOfFile: url.path) else {
|
||||
return
|
||||
}
|
||||
|
||||
let appTime2 = Date.appTime
|
||||
if profile {
|
||||
print("\tloading file \(appTime2 - appTime1) elapsed")
|
||||
}
|
||||
|
||||
var products = [String: [DomesticShop.Product]]()
|
||||
let rows = stringCsv.split(separator: "\n")
|
||||
|
||||
let appTime3 = Date.appTime
|
||||
if profile {
|
||||
print("\trow split \(appTime3 - appTime2) elapsed")
|
||||
}
|
||||
|
||||
for (i, row) in rows.enumerated() {
|
||||
let array = row.split(separator: ",", omittingEmptySubsequences: false).map { String($0) }
|
||||
if i == 0, array[0] == "baseDate" {
|
||||
continue
|
||||
}
|
||||
|
||||
let product = try! DomesticShop.Product(array: array, source: "")
|
||||
if let _ = products[product.itemName] {
|
||||
products[product.itemName]!.append(product)
|
||||
}
|
||||
else {
|
||||
products[product.itemName] = [product]
|
||||
}
|
||||
}
|
||||
let appTime4 = Date.appTime
|
||||
if profile {
|
||||
print("\tparse product \(appTime4 - appTime3) elapsed")
|
||||
}
|
||||
|
||||
setProducts(products)
|
||||
let totalCount = products.reduce(0, { $0 + $1.value.count })
|
||||
print("load products \(totalCount) with \(products.count) key")
|
||||
}
|
||||
|
||||
private func setProducts(_ products: [String: [DomesticShop.Product]]) {
|
||||
productsLock.lock()
|
||||
self.products = products
|
||||
productsLock.unlock()
|
||||
}
|
||||
|
||||
public func getProducts(similarName: String) -> [String: [DomesticShop.Product]]? {
|
||||
productsLock.lock()
|
||||
defer {
|
||||
productsLock.unlock()
|
||||
}
|
||||
return products.filter { $0.key.contains(similarName) }
|
||||
//return products.filter { $0.key.decomposedStringWithCanonicalMapping.contains(similarName) }
|
||||
}
|
||||
|
||||
public func getProduct(isin: String) -> DomesticShop.Product? {
|
||||
productsLock.lock()
|
||||
defer {
|
||||
productsLock.unlock()
|
||||
}
|
||||
|
||||
return products.compactMap { $0.value.first(where: { $0.isinCode == isin }) }.first
|
||||
}
|
||||
|
||||
public func getProduct(shortCode: String) -> DomesticShop.Product? {
|
||||
productsLock.lock()
|
||||
defer {
|
||||
productsLock.unlock()
|
||||
}
|
||||
|
||||
return products.compactMap { $0.value.first(where: { $0.shortCode == shortCode }) }.first
|
||||
}
|
||||
|
||||
public func getAllProducts() -> [DomesticShop.Product] {
|
||||
productsLock.lock()
|
||||
defer {
|
||||
productsLock.unlock()
|
||||
}
|
||||
|
||||
var all = [DomesticShop.Product]()
|
||||
for items in products.values {
|
||||
all.append(contentsOf: items)
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
public func setCurrent(productNo: String) {
|
||||
productsLock.lock()
|
||||
currentShortCode = productNo
|
||||
productsLock.unlock()
|
||||
|
||||
let productName = getProduct(shortCode: productNo)?.itemName ?? ""
|
||||
print("current product \(productNo) \(productName)")
|
||||
}
|
||||
}
|
||||
@@ -470,9 +470,9 @@ public struct CurrentPriceResult: Codable {
|
||||
case shortOverheated = "short_over_yn"
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 80 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 80)
|
||||
}
|
||||
self.itemStateCode = StateClass(rawValue: array[0])!
|
||||
self.marginalRate = array[1]
|
||||
@@ -557,7 +557,7 @@ public struct CurrentPriceResult: Codable {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 80))
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 80), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
@@ -894,7 +894,7 @@ public struct CapturePrice: Codable, PropertyIterable, ArrayDecodable {
|
||||
self.totalOutstandingloanRate = p.totalOutstandingloanRate
|
||||
self.shortSellingAllowable = p.shortSellingAllowable
|
||||
self.shortProductCode = p.shortProductCode
|
||||
self.facePriceCurrency = p.facePriceCurrency
|
||||
self.facePriceCurrency = p.facePriceCurrency.trimmingCharacters(in: CharacterSet([","]))
|
||||
self.capitalCurrency = p.capitalCurrency
|
||||
self.approachRate = p.approachRate
|
||||
self.foreignHoldQuantity = p.foreignHoldQuantity
|
||||
@@ -906,9 +906,9 @@ public struct CapturePrice: Codable, PropertyIterable, ArrayDecodable {
|
||||
self.shortOverheated = p.shortOverheated
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 82 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 82)
|
||||
}
|
||||
self.stockBusinessDate = array[0]
|
||||
self.captureTime = array[1]
|
||||
@@ -995,7 +995,7 @@ public struct CapturePrice: Codable, PropertyIterable, ArrayDecodable {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! CapturePrice(array: Array(repeating: "", count: 82))
|
||||
let i = try! CapturePrice(array: Array(repeating: "", count: 82), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
@@ -1097,9 +1097,9 @@ public struct MinutePriceResult: Codable {
|
||||
return stockBusinessDate + stockConclusionTime
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 8 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 8)
|
||||
}
|
||||
self.stockBusinessDate = array[0]
|
||||
self.stockConclusionTime = array[1]
|
||||
@@ -1112,7 +1112,7 @@ public struct MinutePriceResult: Codable {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputPrice(array: Array(repeating: "", count: 8))
|
||||
let i = try! OutputPrice(array: Array(repeating: "", count: 8), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
@@ -1398,9 +1398,9 @@ public struct PeriodPriceResult: Codable {
|
||||
case revaluationIssueReason = "revl_issu_reas"
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 13 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 13)
|
||||
}
|
||||
self.stockBusinessDate = array[0]
|
||||
self.stockClosingPrice = array[1]
|
||||
@@ -1418,7 +1418,7 @@ public struct PeriodPriceResult: Codable {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputPrice(array: Array(repeating: "", count: 13))
|
||||
let i = try! OutputPrice(array: Array(repeating: "", count: 13), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
|
||||
@@ -195,9 +195,9 @@ public struct BalanceResult: Codable {
|
||||
case stockLoanPrice = "stck_loan_unpr"
|
||||
}
|
||||
|
||||
init(array: [String]) throws {
|
||||
init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 26 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 26)
|
||||
}
|
||||
self.productNo = array[0]
|
||||
self.productName = array[1]
|
||||
@@ -228,7 +228,7 @@ public struct BalanceResult: Codable {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputStock(array: Array(repeating: "", count: 26))
|
||||
let i = try! OutputStock(array: Array(repeating: "", count: 26), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
@@ -337,9 +337,9 @@ public struct BalanceResult: Codable {
|
||||
case assetFluctuationRate = "asst_icdc_erng_rt"
|
||||
}
|
||||
|
||||
init(array: [String]) throws {
|
||||
init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 24 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 24)
|
||||
}
|
||||
self.depositTotalAmount = array[0]
|
||||
self.nextDayCalcAmount = array[1]
|
||||
@@ -368,7 +368,7 @@ public struct BalanceResult: Codable {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputAmount(array: Array(repeating: "", count: 24))
|
||||
let i = try! OutputAmount(array: Array(repeating: "", count: 24), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
|
||||
@@ -103,9 +103,9 @@ public struct VolumeRankResult: Codable {
|
||||
case accumulatedTradingAmount = "acml_tr_pbmn"
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 19 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 19)
|
||||
}
|
||||
self.htsProductName = array[0]
|
||||
self.shortProductNo = array[1]
|
||||
@@ -129,7 +129,7 @@ public struct VolumeRankResult: Codable {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 19))
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 19), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
@@ -188,9 +188,9 @@ public struct HolidyResult: Codable {
|
||||
case settlementDay = "sttl_day_yn"
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 6 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 6)
|
||||
}
|
||||
self.baseDate = array[0]
|
||||
self.weekday = WeekdayDivision(rawValue: array[1])!
|
||||
@@ -201,7 +201,7 @@ public struct HolidyResult: Codable {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 22))
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 22), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
@@ -327,9 +327,9 @@ public struct InvestorVolumeResult: Codable {
|
||||
case organizationSellingTradingAmount = "orgn_seln_tr_pbmn"
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 22 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 22)
|
||||
}
|
||||
self.stockBusinessDate = array[0]
|
||||
self.stockClosingPrice = array[1]
|
||||
@@ -356,7 +356,7 @@ public struct InvestorVolumeResult: Codable {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 22))
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 22), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
@@ -488,9 +488,9 @@ public struct ForeignOrganizationVolumeResult: Codable {
|
||||
case etcCorporationNetBuyingTradingAmount = "etc_corp_ntby_tr_pbmn"
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 26 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 26)
|
||||
}
|
||||
self.htsProductName = array[0]
|
||||
self.shortProductNo = array[1]
|
||||
|
||||
@@ -117,9 +117,9 @@ extension DomesticShop {
|
||||
case corporationNo = "crno"
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 6 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 6)
|
||||
}
|
||||
self.baseDate = array[0]
|
||||
|
||||
@@ -134,7 +134,7 @@ extension DomesticShop {
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! Item(array: Array(repeating: "", count: 6))
|
||||
let i = try! Item(array: Array(repeating: "", count: 6), source: #function)
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
|
||||
@@ -155,9 +155,9 @@ extension DomesticExtra {
|
||||
self.shortSellingBalanceRatio = try container.decode(String.self, forKey: DomesticExtra.ShortSellingBalanceResult.OutBlock.CodingKeys.shortSellingBalanceRatio)
|
||||
}
|
||||
|
||||
public init(array: [String]) throws {
|
||||
public init(array: [String], source: String.SubSequence) throws {
|
||||
guard array.count == 6 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
throw GeneralError.incorrectArrayItems(String(source), array.count, 6)
|
||||
}
|
||||
self.stockBusinessDate = array[0]
|
||||
self.shortSellingBalanceQuantity = array[1]
|
||||
|
||||
@@ -87,53 +87,6 @@ extension KissConsole {
|
||||
}
|
||||
|
||||
|
||||
extension KissConsole {
|
||||
|
||||
func loadShop(_ profile: Bool = false) {
|
||||
let appTime1 = Date.appTime
|
||||
guard let stringCsv = try? String(contentsOfFile: KissConsole.shopProductsUrl.path) else {
|
||||
return
|
||||
}
|
||||
|
||||
let appTime2 = Date.appTime
|
||||
if profile {
|
||||
print("\tloading file \(appTime2 - appTime1) elapsed")
|
||||
}
|
||||
|
||||
var products = [String: [DomesticShop.Product]]()
|
||||
let rows = stringCsv.split(separator: "\n")
|
||||
|
||||
let appTime3 = Date.appTime
|
||||
if profile {
|
||||
print("\trow split \(appTime3 - appTime2) elapsed")
|
||||
}
|
||||
|
||||
for (i, row) in rows.enumerated() {
|
||||
let array = row.split(separator: ",").map { String($0) }
|
||||
if i == 0, array[0] == "baseDate" {
|
||||
continue
|
||||
}
|
||||
|
||||
let product = try! DomesticShop.Product(array: array)
|
||||
if let _ = products[product.itemName] {
|
||||
products[product.itemName]!.append(product)
|
||||
}
|
||||
else {
|
||||
products[product.itemName] = [product]
|
||||
}
|
||||
}
|
||||
let appTime4 = Date.appTime
|
||||
if profile {
|
||||
print("\tparse product \(appTime4 - appTime3) elapsed")
|
||||
}
|
||||
|
||||
setProducts(products)
|
||||
let totalCount = products.reduce(0, { $0 + $1.value.count })
|
||||
print("load products \(totalCount) with \(products.count) key")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension KissConsole {
|
||||
|
||||
enum CandleValidation: CustomStringConvertible {
|
||||
@@ -187,7 +140,7 @@ extension KissConsole {
|
||||
var candles = [Domestic.Candle]()
|
||||
let rows = stringCsv.split(separator: "\n")
|
||||
for (i, row) in rows.enumerated() {
|
||||
let array = row.split(separator: ",").map { String($0) }
|
||||
let array = row.split(separator: ",", omittingEmptySubsequences: false).map { String($0) }
|
||||
if i == 0 {
|
||||
if array.count != 8 {
|
||||
return .invalidCsvHeader
|
||||
@@ -195,7 +148,7 @@ extension KissConsole {
|
||||
continue
|
||||
}
|
||||
|
||||
let candle = try! Domestic.Candle(array: array)
|
||||
let candle = try! Domestic.Candle(array: array, source: row)
|
||||
candles.append(candle)
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ extension KissConsole {
|
||||
|
||||
/// -29일씩 이전으로 돌아가면서, 마지막으로 csv 로 저장했던 날짜를 찾는다.
|
||||
while startDate < backDate {
|
||||
let day = backDate.yyyyMM + "01"
|
||||
let day = backDate.yyyyMM01
|
||||
let fileUrl = KissConsole.shortsFileUrl(productNo: productNo, day: day)
|
||||
guard let _ = fileUrl.isFileExists else {
|
||||
backDate = backDate.addingTimeInterval(-29 * SecondsForOneDay)
|
||||
|
||||
@@ -9,18 +9,11 @@ import Foundation
|
||||
import KissMe
|
||||
|
||||
|
||||
class KissConsole {
|
||||
class KissConsole: KissMe.ShopContext {
|
||||
private var credential: Credential? = nil
|
||||
var account: KissAccount? = nil
|
||||
private var shop: KissShop? = nil
|
||||
|
||||
private var productsLock = NSLock()
|
||||
/// 전체 종목 정보
|
||||
private var products = [String: [DomesticShop.Product]]()
|
||||
|
||||
/// 현재 기본으로 선택된 productNo
|
||||
private var currentShortCode: String?
|
||||
|
||||
/// 현재 candle 파일로 저장 중인 productNo
|
||||
var currentCandleShortCode: String?
|
||||
|
||||
@@ -118,12 +111,14 @@ class KissConsole {
|
||||
account != nil
|
||||
}
|
||||
|
||||
init() {
|
||||
override init() {
|
||||
let jsonUrl = URL.currentDirectory().appending(path: "shop-server.json")
|
||||
shop = try? KissShop(jsonUrl: jsonUrl)
|
||||
|
||||
KissConsole.createSubpath("log")
|
||||
KissConsole.createSubpath("data")
|
||||
|
||||
super.init()
|
||||
lastLogin()
|
||||
loadLocalName()
|
||||
|
||||
@@ -258,52 +253,6 @@ extension KissConsole {
|
||||
try? FileManager.default.createDirectory(at: subPath, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
func setProducts(_ products: [String: [DomesticShop.Product]]) {
|
||||
productsLock.lock()
|
||||
self.products = products
|
||||
productsLock.unlock()
|
||||
}
|
||||
|
||||
private func getProducts(similarName: String) -> [String: [DomesticShop.Product]]? {
|
||||
productsLock.lock()
|
||||
defer {
|
||||
productsLock.unlock()
|
||||
}
|
||||
return products.filter { $0.key.contains(similarName) }
|
||||
//return products.filter { $0.key.decomposedStringWithCanonicalMapping.contains(similarName) }
|
||||
}
|
||||
|
||||
private func getProduct(isin: String) -> DomesticShop.Product? {
|
||||
productsLock.lock()
|
||||
defer {
|
||||
productsLock.unlock()
|
||||
}
|
||||
|
||||
return products.compactMap { $0.value.first(where: { $0.isinCode == isin }) }.first
|
||||
}
|
||||
|
||||
func getProduct(shortCode: String) -> DomesticShop.Product? {
|
||||
productsLock.lock()
|
||||
defer {
|
||||
productsLock.unlock()
|
||||
}
|
||||
|
||||
return products.compactMap { $0.value.first(where: { $0.shortCode == shortCode }) }.first
|
||||
}
|
||||
|
||||
private func getAllProducts() -> [DomesticShop.Product] {
|
||||
productsLock.lock()
|
||||
defer {
|
||||
productsLock.unlock()
|
||||
}
|
||||
|
||||
var all = [DomesticShop.Product]()
|
||||
for items in products.values {
|
||||
all.append(contentsOf: items)
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
private func lastLogin() {
|
||||
let profile = KissProfile()
|
||||
guard let isMock = profile.isMock else {
|
||||
@@ -332,15 +281,6 @@ extension KissConsole {
|
||||
private func loadLocalName() {
|
||||
LocalContext.shared.load(KissConsole.localNamesUrl)
|
||||
}
|
||||
|
||||
func setCurrent(productNo: String) {
|
||||
productsLock.lock()
|
||||
currentShortCode = productNo
|
||||
productsLock.unlock()
|
||||
|
||||
let productName = getProduct(shortCode: productNo)?.itemName ?? ""
|
||||
print("current product \(productNo) \(productName)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -695,8 +635,9 @@ extension KissConsole {
|
||||
|
||||
|
||||
private func onCandleDay(_ args: [String]) {
|
||||
if args.count == 1, args[0] == "all" {
|
||||
onCandleDayAll()
|
||||
if args.count >= 1, args[0] == "all" {
|
||||
let otherArgs = args[1...].map { String($0) }
|
||||
onCandleDayAll(otherArgs)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -716,7 +657,9 @@ extension KissConsole {
|
||||
}
|
||||
|
||||
|
||||
private func onCandleDayAll() {
|
||||
private func onCandleDayAll(_ args: [String]) {
|
||||
let resume: Bool = (args.count == 1 && args[0] == "resume")
|
||||
|
||||
let all = getAllProducts()
|
||||
for item in all {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
@@ -743,8 +686,9 @@ extension KissConsole {
|
||||
|
||||
|
||||
private func onCandleWeek(_ args: [String]) {
|
||||
if args.count == 1, args[0] == "all" {
|
||||
onCandleWeekAll()
|
||||
if args.count >= 1, args[0] == "all" {
|
||||
let otherArgs = args[1...].map { String($0) }
|
||||
onCandleWeekAll(otherArgs)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -764,7 +708,9 @@ extension KissConsole {
|
||||
}
|
||||
|
||||
|
||||
private func onCandleWeekAll() {
|
||||
private func onCandleWeekAll(_ args: [String]) {
|
||||
let resume: Bool = (args.count == 1 && args[0] == "resume")
|
||||
|
||||
let all = getAllProducts()
|
||||
for item in all {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
@@ -900,7 +846,7 @@ extension KissConsole {
|
||||
|
||||
private func onLoadShop() async {
|
||||
return await withUnsafeContinuation { continuation in
|
||||
self.loadShop()
|
||||
self.loadShop(url: KissConsole.shopProductsUrl)
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
@@ -1114,7 +1060,7 @@ extension KissConsole {
|
||||
var curNames = try! [LocalName].readCsv(fromFile: nameUrl, verifyHeader: true)
|
||||
for name in newNames {
|
||||
if false == curNames.contains(where: { $0.fieldName == name }) {
|
||||
let item = try! LocalName(array: [name, ""])
|
||||
let item = try! LocalName(array: [name, ""], source: "")
|
||||
curNames.append(item)
|
||||
addedNameCount += 1
|
||||
}
|
||||
|
||||
@@ -8,3 +8,17 @@
|
||||
import Foundation
|
||||
|
||||
KissConsole().run()
|
||||
|
||||
|
||||
// 액면가가 1,000원 이상인 종목들 추려보자. (미챠...)
|
||||
// 액면가가 넘어가면, prices.csv 에 저장된 comma 값을 다시 수정해야 함.
|
||||
/*
|
||||
import KissMe
|
||||
|
||||
let path = URL(filePath: "/Users/ened/Kiss/KissMe/bin/data/065350/price/prices.csv")
|
||||
let data = try [CapturePrice].readCsv(fromFile: path, verifyHeader: true)
|
||||
|
||||
if let last = data.last {
|
||||
print(last)
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -6,12 +6,131 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import KissMe
|
||||
|
||||
|
||||
extension KissIndex {
|
||||
|
||||
func indexSet_0002(date: Date, config: String?, kmi: KissIndexType) {
|
||||
// TODO: work
|
||||
if productsCount == 0 {
|
||||
loadShop(url: KissIndex.shopProductsUrl)
|
||||
}
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
Task {
|
||||
// var scoreMap = [String: Int]()
|
||||
|
||||
do {
|
||||
let shorts = try await collectShorts(date: date)
|
||||
print(shorts.count)
|
||||
|
||||
let prices = try await collectPrices(date: date)
|
||||
print(prices.count)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
|
||||
|
||||
private func collectShorts(date: Date) async throws -> [DomesticExtra.Shorts] {
|
||||
let shorts = try await withThrowingTaskGroup(of: DomesticExtra.Shorts?.self, returning: [DomesticExtra.Shorts].self) { taskGroup in
|
||||
let all = getAllProducts()
|
||||
let yyyyMMdd = date.yyyyMMdd
|
||||
|
||||
for item in all {
|
||||
taskGroup.addTask {
|
||||
let shortsUrl = KissIndex.pickNearShortsUrl(productNo: item.shortCode, date: date)
|
||||
|
||||
let shorts = try [DomesticExtra.Shorts].readCsv(fromFile: shortsUrl)
|
||||
let targetShorts = shorts.filter { $0.stockBusinessDate == yyyyMMdd }
|
||||
|
||||
/// 공매도 잔고 비중 (1%) 이상 종목 리스트
|
||||
if let aShorts = targetShorts.first, let ratio = Double(aShorts.shortSellingBalanceRatio), ratio >= 0.01 {
|
||||
return aShorts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var taskResult = [DomesticExtra.Shorts]()
|
||||
for try await result in taskGroup.compactMap( { $0 }) {
|
||||
taskResult.append(result)
|
||||
}
|
||||
return taskResult
|
||||
}
|
||||
return shorts
|
||||
}
|
||||
|
||||
|
||||
private func collectPrices(date: Date) async throws -> [CapturePrice] {
|
||||
let prices = try await withThrowingTaskGroup(of: CapturePrice?.self, returning: [CapturePrice].self) { taskGroup in
|
||||
let all = getAllProducts()
|
||||
let yyyyMMdd = date.yyyyMMdd
|
||||
let dateHHmmss = date.HHmmss
|
||||
|
||||
for item in all {
|
||||
taskGroup.addTask {
|
||||
let pricesUrl = KissIndex.pickNearPricesUrl(productNo: item.shortCode, date: date)
|
||||
let prices = try [CapturePrice].readCsv(fromFile: pricesUrl)
|
||||
let targetPrices = prices.filter { $0.stockBusinessDate == yyyyMMdd && $0.captureTime <= dateHHmmss }
|
||||
.sorted(by: { dateHHmmss.diffSecondsTwoHHmmss($0.captureTime) < dateHHmmss.diffSecondsTwoHHmmss($1.captureTime) })
|
||||
|
||||
if let price = targetPrices.first, let quantity = Int(price.lastShortSellingConclusionQuantity), quantity > 0 {
|
||||
/// 최종 공매도 체결 수량이 잔고량에 비해서 높으면?
|
||||
/// lastShortSellingConclusionQuantity
|
||||
return price
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var taskResult = [CapturePrice]()
|
||||
for try await result in taskGroup.compactMap( { $0 }) {
|
||||
taskResult.append(result)
|
||||
}
|
||||
return taskResult
|
||||
}
|
||||
return prices
|
||||
}
|
||||
|
||||
|
||||
private static var shopProductsUrl: URL {
|
||||
URL.currentDirectory().appending(path: "data/shop-products.csv")
|
||||
}
|
||||
|
||||
private static func pickNearShortsUrl(productNo: String, date: Date) -> URL {
|
||||
let subPath = "data/\(productNo)/shorts"
|
||||
let monthFile = "shorts-\(date.yyyyMM01).csv"
|
||||
|
||||
return URL.currentDirectory().appending(path: "\(subPath)/\(monthFile)")
|
||||
}
|
||||
|
||||
private static func pickNearPricesUrl(productNo: String, date: Date) -> URL {
|
||||
let subPath = "data/\(productNo)/price"
|
||||
let priceFile = "prices.csv"
|
||||
|
||||
// TODO: work month file
|
||||
//let monthFile = "prices-\(date.yyyyMM01).csv"
|
||||
|
||||
return URL.currentDirectory().appending(path: "\(subPath)/\(priceFile)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension String {
|
||||
func diffSecondsTwoHHmmss(_ another: String) -> TimeInterval {
|
||||
guard let (hour, min, sec) = self.HHmmss else {
|
||||
return Double.greatestFiniteMagnitude
|
||||
}
|
||||
guard let (dHour, dMin, dSec) = another.HHmmss else {
|
||||
return Double.greatestFiniteMagnitude
|
||||
}
|
||||
let seconds = (hour * 60 * 60 + min * 60 + sec)
|
||||
let dSeconds = (dHour * 60 * 60 + dMin * 60 + dSec)
|
||||
return TimeInterval(seconds - dSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@ import KissMe
|
||||
extension KissIndex {
|
||||
|
||||
func indexSet_0005(date: Date, config: String?, kmi: KissIndexType) {
|
||||
//let belongs: [BelongClassCode] = [.averageVolume, .volumeIncreaseRate, .averageVolumeTurnoverRate, .transactionValue, .averageTransactionValueTurnoverRate]
|
||||
let belongs: [BelongClassCode] = [.averageVolume]
|
||||
let belongs: [BelongClassCode] = [.averageVolume, .volumeIncreaseRate, .averageVolumeTurnoverRate, .transactionValue, .averageTransactionValueTurnoverRate]
|
||||
|
||||
do {
|
||||
var scoreMap = [String: Int]()
|
||||
@@ -51,7 +50,7 @@ extension KissIndex {
|
||||
}
|
||||
|
||||
|
||||
static func pickNearTopProductsUrl(_ belong: BelongClassCode, date: Date) throws -> URL {
|
||||
private static func pickNearTopProductsUrl(_ belong: BelongClassCode, date: Date) throws -> URL {
|
||||
let subPath = "data/top30/\(date.yyyyMMdd)"
|
||||
let dayFile = "top30-\(belong.fileBelong)-\(date.yyyyMMdd)-"
|
||||
|
||||
@@ -106,6 +105,13 @@ extension String {
|
||||
return TimeInterval(hour * 60 * 60 + min * 60 + sec)
|
||||
}
|
||||
|
||||
var HHmmssBySeconds: TimeInterval? {
|
||||
guard let (hour, min, sec) = HHmmss else {
|
||||
return nil
|
||||
}
|
||||
return TimeInterval(hour * 60 * 60 + min * 60 + sec)
|
||||
}
|
||||
|
||||
func diffSecondsTwoCsvHHmmss(_ another: String) -> TimeInterval {
|
||||
(csvHHmmssBySeconds ?? 0) - (another.csvHHmmssBySeconds ?? 0)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ enum KissIndexType: String {
|
||||
}
|
||||
|
||||
|
||||
class KissIndex {
|
||||
class KissIndex: KissMe.ShopContext {
|
||||
|
||||
func run() {
|
||||
guard CommandLine.argc >= 4 else {
|
||||
|
||||
@@ -28,9 +28,9 @@ WIP `modify (PNO) (ONO) (가격) (수량)` | 주문 내역을 변경. (수량)
|
||||
`candle [PNO]` | 종목의 분봉 열람. PNO 은 생략 가능. **data/(PNO)/min/candle-(yyyyMMdd).csv** 파일로 저장.
|
||||
`candle all [resume]` | 모든 종목의 분봉 열람. cron job 으로 돌리기 위해서 추가. **data/(PNO)/min/candle-(yyyyMMdd).csv** 파일로 저장. (resume) 을 기입하면, 이미 받은 파일은 검사하여, 데이터에 오류가 있으면 다시 받고 오류가 없으면 새롭게 열람하지 않음.
|
||||
`candle day [PNO]` | 종목의 최근 250일 동안의 일봉 열람. PNO 은 생략 가능. **data/(PNO)/day/candle-(yyyyMMdd).csv** 파일로 저장.
|
||||
`candle day all` | 모든 종목의 최근 250일 동안의 일봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/day/candle-(yyyyMMdd).csv** 파일로 저장.
|
||||
`candle day all [resume]` | 모든 종목의 최근 250일 동안의 일봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/day/candle-(yyyyMMdd).csv** 파일로 저장.
|
||||
`candle week [PNO]` | 종목의 최근 52주 동안의 주봉 열람. PNO 은 생략 가능. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
|
||||
`candle week all` | 모든 종목의 최근 52주 동안의 주봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
|
||||
`candle week all [resume]` | 모든 종목의 최근 52주 동안의 주봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
|
||||
`candle validate (기간)` | (기간) 타입의 모든 csv 파일에 대해서 데이터가 유효한지 검사. (기간) 으로는 **min**, **day**, **week** 을 지정하고, 생략되면 **min** 으로 간주.
|
||||
`investor [PNO]` | 종목의 투자자 거래량 열람. PNO 은 생략 가능. **data/(PNO)/investor/investor-(yyyyMMdd).csv** 파일로 저장.
|
||||
`investor all` | 모든 종목의 투자자 거래량 열람. **data/(PNO)/investor/investor-(yyyyMMdd).csv** 파일로 저장.
|
||||
|
||||
2
bin/data
2
bin/data
Submodule bin/data updated: 659b8ff169...8f92a1c7b8
@@ -36,8 +36,9 @@
|
||||
3435A7F72A35D82000D604F1 /* DomesticShortSelling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3435A7F62A35D82000D604F1 /* DomesticShortSelling.swift */; };
|
||||
349C26AB2A1EAE2400F3EC91 /* KissProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */; };
|
||||
34D3680F2A2AA0BE005E6756 /* PropertyIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */; };
|
||||
34F190092A418E130068C697 /* LocalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F190082A418E130068C697 /* LocalContext.swift */; };
|
||||
34F1900C2A41982A0068C697 /* IndexResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F1900B2A41982A0068C697 /* IndexResult.swift */; };
|
||||
34F1900F2A426D150068C697 /* ShopContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F1900E2A426D150068C697 /* ShopContext.swift */; };
|
||||
34F190112A4394EB0068C697 /* LocalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F190102A4394EB0068C697 /* LocalContext.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -81,8 +82,9 @@
|
||||
3435A7F62A35D82000D604F1 /* DomesticShortSelling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticShortSelling.swift; sourceTree = "<group>"; };
|
||||
349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissProfile.swift; sourceTree = "<group>"; };
|
||||
34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyIterable.swift; sourceTree = "<group>"; };
|
||||
34F190082A418E130068C697 /* LocalContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalContext.swift; sourceTree = "<group>"; };
|
||||
34F1900B2A41982A0068C697 /* IndexResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexResult.swift; sourceTree = "<group>"; };
|
||||
34F1900E2A426D150068C697 /* ShopContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopContext.swift; sourceTree = "<group>"; };
|
||||
34F190102A4394EB0068C697 /* LocalContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalContext.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -125,6 +127,7 @@
|
||||
341F5EAD2A0A80EC00962D48 /* KissMe */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
34F1900D2A426C1C0068C697 /* Context */,
|
||||
34F1900A2A41981A0068C697 /* Index */,
|
||||
341F5EF32A0F88AC00962D48 /* Common */,
|
||||
341F5EEA2A0F882300962D48 /* Foreign */,
|
||||
@@ -195,7 +198,6 @@
|
||||
341F5F022A11A2BC00962D48 /* Credential.swift */,
|
||||
341F5F062A14634F00962D48 /* Foundation+Extensions.swift */,
|
||||
34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */,
|
||||
34F190082A418E130068C697 /* LocalContext.swift */,
|
||||
);
|
||||
path = Common;
|
||||
sourceTree = "<group>";
|
||||
@@ -224,6 +226,15 @@
|
||||
path = Index;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
34F1900D2A426C1C0068C697 /* Context */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
34F190102A4394EB0068C697 /* LocalContext.swift */,
|
||||
34F1900E2A426D150068C697 /* ShopContext.swift */,
|
||||
);
|
||||
path = Context;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -342,16 +353,17 @@
|
||||
341F5EFD2A10931B00962D48 /* DomesticStockSearch.swift in Sources */,
|
||||
341F5EE52A0F3EF400962D48 /* DomesticStock.swift in Sources */,
|
||||
341F5EF72A0F8B0500962D48 /* DomesticStockResult.swift in Sources */,
|
||||
34F1900F2A426D150068C697 /* ShopContext.swift in Sources */,
|
||||
341F5F0F2A15223A00962D48 /* SeibroRequest.swift in Sources */,
|
||||
341F5EF02A0F886600962D48 /* ForeignFutures.swift in Sources */,
|
||||
34F1900C2A41982A0068C697 /* IndexResult.swift in Sources */,
|
||||
341F5EEC2A0F883900962D48 /* ForeignStock.swift in Sources */,
|
||||
34F190112A4394EB0068C697 /* LocalContext.swift in Sources */,
|
||||
341F5EFF2A10955D00962D48 /* OrderRequest.swift in Sources */,
|
||||
341F5EE92A0F87FB00962D48 /* DomesticStockPrice.swift in Sources */,
|
||||
341F5EEE2A0F884300962D48 /* ForeignStockPrice.swift in Sources */,
|
||||
341F5EDE2A0F300100962D48 /* Request.swift in Sources */,
|
||||
349C26AB2A1EAE2400F3EC91 /* KissProfile.swift in Sources */,
|
||||
34F190092A418E130068C697 /* LocalContext.swift in Sources */,
|
||||
341F5F012A11155100962D48 /* DomesticStockSearchResult.swift in Sources */,
|
||||
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */,
|
||||
341F5EF22A0F887200962D48 /* DomesticFutures.swift in Sources */,
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "KMI-0005 20230616 100000"
|
||||
argument = "KMI-0002 20230616 100000"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
|
||||
Reference in New Issue
Block a user