202 lines
6.7 KiB
Swift
202 lines
6.7 KiB
Swift
//
|
|
// KissIndex+0004.swift
|
|
// KissMeIndex
|
|
//
|
|
// Created by ened-book-m1 on 2023/06/20.
|
|
//
|
|
|
|
import Foundation
|
|
import KissMe
|
|
|
|
|
|
private struct KMI_0004_Config: Codable {
|
|
|
|
enum Strategy: String, Codable {
|
|
case foreigner3DayFocusing = "3DAY_FOCUSING"
|
|
case foreigner4DayIncreased = "4DAY_INCREASED"
|
|
}
|
|
|
|
let strategy: Strategy
|
|
|
|
static let `default`: Strategy = .foreigner4DayIncreased
|
|
}
|
|
|
|
|
|
extension KissIndex {
|
|
|
|
func indexSet_0004(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 .foreigner3DayFocusing:
|
|
try await foreigner3DayFocusing(date: date, kmi: kmi)
|
|
case .foreigner4DayIncreased:
|
|
try await foreigner4DayIncreased(date: date, kmi: kmi)
|
|
}
|
|
} catch {
|
|
writeError(error, kmi: kmi)
|
|
}
|
|
|
|
semaphore.signal()
|
|
}
|
|
semaphore.wait()
|
|
}
|
|
|
|
|
|
private func loadConfig(_ config: String?) -> KMI_0004_Config.Strategy {
|
|
var strategy = KMI_0004_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_0004_Config.self, from: data)
|
|
strategy = configData.strategy
|
|
} catch {
|
|
}
|
|
}
|
|
return strategy
|
|
}
|
|
|
|
|
|
/// 대상 날짜의 외국인 보유량이 4일전의 보유량보다 크면 선별한다.
|
|
/// 4일전의 보유량의 차이를 이용하여 신호를 계산한다.
|
|
///
|
|
private func foreigner4DayIncreased(date: Date, kmi: KissIndexType) async throws {
|
|
let upLock = NSLock()
|
|
var up = [String: Int]()
|
|
|
|
let prices = try await collectPrices(date: date, recentCount: 4) { prices in
|
|
if prices.count >= 4,
|
|
let endPrice = prices.first,
|
|
let startPrice = prices.last,
|
|
let holdQuantity1 = Int(endPrice.foreignHoldQuantity),
|
|
let holdQuantity2 = Int(startPrice.foreignHoldQuantity),
|
|
startPrice.stockBusinessDate < endPrice.stockBusinessDate {
|
|
|
|
let increased = holdQuantity1 - holdQuantity2
|
|
let increateRatio = Double(increased * 100) / Double(holdQuantity2)
|
|
if abs(increateRatio) >= 1.0 {
|
|
let productNo = startPrice.shortProductCode
|
|
//printError(productNo, increateRatio)
|
|
|
|
upLock.lock()
|
|
if let _ = up[productNo] {
|
|
up[productNo]! += increased
|
|
}
|
|
else {
|
|
up[productNo] = increased
|
|
}
|
|
upLock.unlock()
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var scoreMap = [String: Double]()
|
|
for price in prices {
|
|
let productNo = price.shortProductCode
|
|
let score: Double
|
|
if let inc = up[productNo] {
|
|
if inc < 0 {
|
|
score = -log10(abs(Double(inc)))
|
|
}
|
|
else {
|
|
score = log10(Double(inc))
|
|
}
|
|
if score.isNaN || score.isInfinite {
|
|
printError("\(productNo) score: \(score)")
|
|
continue
|
|
}
|
|
}
|
|
else {
|
|
score = 0
|
|
}
|
|
|
|
if let _ = scoreMap[productNo] {
|
|
scoreMap[productNo]! += score
|
|
}
|
|
else {
|
|
scoreMap[productNo] = score
|
|
}
|
|
}
|
|
|
|
if let maxScore = scoreMap.max(by: { abs($0.value) < abs($1.value) }) {
|
|
let output = normalizeByScale(scoreMap: scoreMap, includeName: true, scale: abs(maxScore.value))
|
|
writeOutput(output, kmi: kmi)
|
|
}
|
|
}
|
|
|
|
|
|
/// 외국인 매수의 물량이 3일 연속 증가이면, 긍정적 신호로 사용한다.
|
|
/// 반대로 3일 연속 감소이면, 부정적 신호로 사용한다.
|
|
///
|
|
private func foreigner3DayFocusing(date: Date, kmi: KissIndexType) async throws {
|
|
let netSumLock = NSLock()
|
|
var netSum = [String: Int]()
|
|
|
|
let investors = try await collectInvestors(date: date, recentCount: 3) { productNo, investors in
|
|
if investors.count == 3,
|
|
let netBuying1 = Int(investors[0].foreignNetBuyingQuantity),
|
|
let netBuying2 = Int(investors[1].foreignNetBuyingQuantity),
|
|
let netBuying3 = Int(investors[2].foreignNetBuyingQuantity) {
|
|
|
|
if (netBuying1 > 0 && netBuying2 > 0 && netBuying3 > 0) ||
|
|
(netBuying1 < 0 && netBuying2 < 0 && netBuying3 < 0) {
|
|
|
|
let sum = netBuying1 + netBuying2 + netBuying3
|
|
|
|
netSumLock.lock()
|
|
if let _ = netSum[productNo] {
|
|
netSum[productNo]! += sum
|
|
}
|
|
else {
|
|
netSum[productNo] = sum
|
|
}
|
|
netSumLock.unlock()
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var scoreMap = [String: Double]()
|
|
for (productNo, _) in investors {
|
|
let score: Double
|
|
if let sum = netSum[productNo] {
|
|
if sum < 0 {
|
|
score = -log10(abs(Double(sum)))
|
|
}
|
|
else {
|
|
score = log10(Double(sum))
|
|
}
|
|
if score.isNaN || score.isInfinite {
|
|
printError("\(productNo) score: \(score)")
|
|
continue
|
|
}
|
|
}
|
|
else {
|
|
score = 0
|
|
}
|
|
|
|
if let _ = scoreMap[productNo] {
|
|
scoreMap[productNo]! += score
|
|
}
|
|
else {
|
|
scoreMap[productNo] = score
|
|
}
|
|
}
|
|
|
|
if let maxScore = scoreMap.max(by: { abs($0.value) < abs($1.value) }) {
|
|
let output = normalizeByScale(scoreMap: scoreMap, includeName: true, scale: abs(maxScore.value))
|
|
writeOutput(output, kmi: kmi)
|
|
}
|
|
}
|
|
}
|