Implement KMI-0002 index set
This commit is contained in:
@@ -49,25 +49,14 @@ open class ShopContext {
|
||||
|
||||
extension ShopContext {
|
||||
|
||||
public func loadShop(url: URL, profile: Bool = false) {
|
||||
let appTime1 = Date.appTime
|
||||
public func loadShop(url: URL, loggable: Bool = false) {
|
||||
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" {
|
||||
@@ -82,14 +71,12 @@ extension ShopContext {
|
||||
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")
|
||||
if loggable {
|
||||
let totalCount = products.reduce(0, { $0 + $1.value.count })
|
||||
print("load products \(totalCount) with \(products.count) key")
|
||||
}
|
||||
}
|
||||
|
||||
private func setProducts(_ products: [String: [DomesticShop.Product]]) {
|
||||
|
||||
@@ -851,7 +851,7 @@ extension KissConsole {
|
||||
|
||||
private func onLoadShop() async {
|
||||
return await withUnsafeContinuation { continuation in
|
||||
self.loadShop(url: KissConsole.shopProductsUrl)
|
||||
self.loadShop(url: KissConsole.shopProductsUrl, loggable: true)
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,37 +9,37 @@ import Foundation
|
||||
import KissMe
|
||||
|
||||
|
||||
private struct KMI_0002_Config: Codable {
|
||||
|
||||
enum Strategy: String, Codable {
|
||||
case balanceRatioIncreased = "BALANCE_RATIO_INCREASED"
|
||||
case shorts3DayIncreased = "3DAY_INCREASED"
|
||||
}
|
||||
|
||||
let strategy: Strategy
|
||||
|
||||
static let `default`: Strategy = .balanceRatioIncreased
|
||||
}
|
||||
|
||||
|
||||
extension KissIndex {
|
||||
|
||||
func indexSet_0002(date: Date, config: String?, kmi: KissIndexType) {
|
||||
if productsCount == 0 {
|
||||
loadShop(url: KissIndex.shopProductsUrl)
|
||||
}
|
||||
let strategy = loadConfig(config)
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
Task {
|
||||
do {
|
||||
let shorts = try await collectShorts(date: date) { aShorts in
|
||||
/// 공매도 잔고 비중 (1%) 이상 종목 리스트
|
||||
if let ratio = Double(aShorts.shortSellingBalanceRatio), ratio >= 0.01 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
switch strategy {
|
||||
case .balanceRatioIncreased:
|
||||
try await balanceRatioIncreased(date: date, kmi: kmi)
|
||||
case .shorts3DayIncreased:
|
||||
try await shorts3DayIncreased(date: date, kmi: kmi)
|
||||
}
|
||||
//print(shorts.count)
|
||||
|
||||
let prices = try await collectPrices(date: date) { price in
|
||||
if let quantity = Int(price.lastShortSellingConclusionQuantity), quantity > 0 {
|
||||
/// 최종 공매도 체결 수량이 잔고량에 비해서 높으면?
|
||||
/// lastShortSellingConclusionQuantity
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
//print(prices.count)
|
||||
|
||||
} catch {
|
||||
//print(error)
|
||||
writeError(error, kmi: kmi)
|
||||
}
|
||||
|
||||
@@ -47,4 +47,121 @@ extension KissIndex {
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
|
||||
|
||||
private func loadConfig(_ config: String?) -> KMI_0002_Config.Strategy {
|
||||
var strategy = KMI_0002_Config.default
|
||||
if let config = config {
|
||||
do {
|
||||
let configUrl = URL.currentDirectory().appending(path: config)
|
||||
let data = try Data(contentsOf: configUrl, options: .uncached)
|
||||
let configData = try JSONDecoder().decode(KMI_0002_Config.self, from: data)
|
||||
strategy = configData.strategy
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return strategy
|
||||
}
|
||||
|
||||
|
||||
/// 3일전보다 공매도 잔고 비중이 일정량(0.01%) 상승한 종목을 추려낸다.
|
||||
///
|
||||
private func balanceRatioIncreased(date: Date, kmi: KissIndexType) async throws {
|
||||
let increasedRatioLock = NSLock()
|
||||
var increasedRatio = [String: Double]()
|
||||
|
||||
let _ = try await collectShorts(date: date, recentCount: 3) { productNo, shorts in
|
||||
if shorts.count >= 3, let endShort = shorts.first, let startShort = shorts.last {
|
||||
if let endRatio = Double(endShort.shortSellingBalanceRatio),
|
||||
let startRatio = Double(startShort.shortSellingBalanceRatio) {
|
||||
let increaseRatio = endRatio - startRatio
|
||||
if abs(increaseRatio) > 0.01 {
|
||||
|
||||
increasedRatioLock.lock()
|
||||
if let _ = increasedRatio[productNo] {
|
||||
increasedRatio[productNo]! += increaseRatio
|
||||
}
|
||||
else {
|
||||
increasedRatio[productNo] = increaseRatio
|
||||
}
|
||||
increasedRatioLock.unlock()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var scoreMap = [String: Double]()
|
||||
for (productNo, ratio) in increasedRatio {
|
||||
let score = -ratio
|
||||
|
||||
if let _ = scoreMap[productNo] {
|
||||
scoreMap[productNo]! += score
|
||||
}
|
||||
else {
|
||||
scoreMap[productNo] = score
|
||||
}
|
||||
}
|
||||
|
||||
normalizeAndWrite(scoreMap: scoreMap, includeName: true, kmi: kmi)
|
||||
}
|
||||
|
||||
|
||||
/// 3일동안 누적 공매도 체결 수량이 상장주식수의 일정 비율(0.01%) 이상 증가한 종목을 추려낸다.
|
||||
///
|
||||
private func shorts3DayIncreased(date: Date, kmi: KissIndexType) async throws {
|
||||
let sumRatioLock = NSLock()
|
||||
var sumRatio = [String: Double]()
|
||||
|
||||
let prices = try await collectPrices(date: date, recentCount: 3) { prices in
|
||||
if prices.count >= 3 {
|
||||
var curDay: String? = nil
|
||||
var aPriceEachDay = [CapturePrice]()
|
||||
for price in prices {
|
||||
if curDay != price.stockBusinessDate {
|
||||
aPriceEachDay.append(price)
|
||||
curDay = price.stockBusinessDate
|
||||
}
|
||||
}
|
||||
|
||||
/// lastShortSellingConclusionQuantity 값에 음수는 없음.
|
||||
let sumConclusionQuantity = aPriceEachDay.reduce(0, { $0 + (Int($1.lastShortSellingConclusionQuantity) ?? 0) })
|
||||
|
||||
let ratio = Double(sumConclusionQuantity * 100) / Double(prices[0].listedStockCount)!
|
||||
//print("ratio: \(ratio), sumConclusionQuantity: \(sumConclusionQuantity)")
|
||||
guard ratio > 0.01 else {
|
||||
return false
|
||||
}
|
||||
|
||||
let productNo = prices[0].shortProductCode
|
||||
|
||||
sumRatioLock.lock()
|
||||
if let _ = sumRatio[productNo] {
|
||||
sumRatio[productNo]! += ratio
|
||||
}
|
||||
else {
|
||||
sumRatio[productNo] = ratio
|
||||
}
|
||||
sumRatioLock.unlock()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var scoreMap = [String: Double]()
|
||||
for price in prices {
|
||||
let productNo = price.shortProductCode
|
||||
let score = -(sumRatio[productNo] ?? 0)
|
||||
|
||||
if let _ = scoreMap[productNo] {
|
||||
scoreMap[productNo]! += score
|
||||
}
|
||||
else {
|
||||
scoreMap[productNo] = score
|
||||
}
|
||||
}
|
||||
|
||||
normalizeAndWrite(scoreMap: scoreMap, includeName: true, kmi: kmi)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
struct KMI_0004_Config: Codable {
|
||||
private struct KMI_0004_Config: Codable {
|
||||
|
||||
enum Strategy: String, Codable {
|
||||
case foreigner3DayFocusing = "3DAY_FOCUSING"
|
||||
|
||||
@@ -104,20 +104,43 @@ class KissIndex: KissMe.ShopContext {
|
||||
|
||||
extension KissIndex {
|
||||
|
||||
func collectShorts(date: Date, filter: @escaping (DomesticExtra.Shorts) -> Bool) async throws -> [DomesticExtra.Shorts] {
|
||||
func collectShorts(date: Date, recentCount: Int, filter: @escaping (String, [DomesticExtra.Shorts]) -> Bool) 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
|
||||
let (yyyy, mm, dd) = date.yyyyMMdd_split!
|
||||
|
||||
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 }
|
||||
let prevMonthDate = date.changing(year: yyyy, month: mm-1, day: dd)!
|
||||
let prevMonthShortsUrl = KissIndex.pickNearShortsUrl(productNo: item.shortCode, date: prevMonthDate)
|
||||
|
||||
if let aShorts = targetShorts.first, filter(aShorts) {
|
||||
return aShorts
|
||||
var shorts = try [DomesticExtra.Shorts].readCsv(fromFile: shortsUrl)
|
||||
if let prevShorts = try? [DomesticExtra.Shorts].readCsv(fromFile: prevMonthShortsUrl) {
|
||||
shorts.append(contentsOf: prevShorts)
|
||||
}
|
||||
|
||||
var targetShorts = [DomesticExtra.Shorts]()
|
||||
var collectedDay = 0
|
||||
var prevDays = 1
|
||||
var desiredDate: Date? = date
|
||||
|
||||
while desiredDate != nil, prevDays < recentCount * 7 {
|
||||
let selected = shorts.filter { $0.stockBusinessDate == desiredDate!.yyyyMMdd }
|
||||
targetShorts.append(contentsOf: selected)
|
||||
if selected.count > 0 {
|
||||
collectedDay += 1
|
||||
}
|
||||
if collectedDay >= recentCount {
|
||||
break
|
||||
}
|
||||
desiredDate = desiredDate!.changing(year: yyyy, month: mm, day: dd-prevDays)
|
||||
prevDays += 1
|
||||
}
|
||||
|
||||
if filter(item.shortCode, targetShorts), let first = targetShorts.first {
|
||||
return first
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "KMI-0004 20230622 100000"
|
||||
argument = "KMI-0002 20230623 100000"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
|
||||
Reference in New Issue
Block a user