168 lines
5.6 KiB
Swift
168 lines
5.6 KiB
Swift
//
|
|
// KissIndex+0002.swift
|
|
// KissMeIndex
|
|
//
|
|
// Created by ened-book-m1 on 2023/06/20.
|
|
//
|
|
|
|
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 {
|
|
switch strategy {
|
|
case .balanceRatioIncreased:
|
|
try await balanceRatioIncreased(date: date, kmi: kmi)
|
|
case .shorts3DayIncreased:
|
|
try await shorts3DayIncreased(date: date, kmi: kmi)
|
|
}
|
|
} catch {
|
|
writeError(error, kmi: kmi)
|
|
}
|
|
|
|
semaphore.signal()
|
|
}
|
|
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)
|
|
}
|
|
}
|