Working on simulator
This commit is contained in:
@@ -44,11 +44,15 @@ public struct KissIndexResult: Codable {
|
||||
public struct KissMatrixResult: Codable {
|
||||
public let code: Int
|
||||
public let kmis: [String]
|
||||
public let day: String
|
||||
public let time: String
|
||||
public let output: [KissIndexResult.Output]
|
||||
|
||||
public init(code: Int, kmis: [String], output: [KissIndexResult.Output]) {
|
||||
public init(code: Int, kmis: [String], day: String, time: String, output: [KissIndexResult.Output]) {
|
||||
self.code = code
|
||||
self.kmis = kmis
|
||||
self.day = day
|
||||
self.time = time
|
||||
self.output = output
|
||||
}
|
||||
}
|
||||
|
||||
154
KissMeGolder/Sources/KissSimulator.swift
Normal file
154
KissMeGolder/Sources/KissSimulator.swift
Normal file
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// KissSimulator.swift
|
||||
// KissMeGolder
|
||||
//
|
||||
// Created by ened-book-m1 on 2023/06/30.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import KissMe
|
||||
|
||||
|
||||
struct Stock: Codable {
|
||||
let productNo: String
|
||||
let quantity: Int
|
||||
let averagePrice: Int
|
||||
}
|
||||
|
||||
|
||||
class Balance: Codable {
|
||||
var cash: Int
|
||||
var stocks: [Stock]
|
||||
|
||||
init() {
|
||||
cash = 10_000_000
|
||||
stocks = []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class KissSimulator: ShopContext {
|
||||
let balance: Balance
|
||||
private var dataPath: URL
|
||||
private let candleCache = CandleCache()
|
||||
|
||||
override init() {
|
||||
balance = Balance()
|
||||
dataPath = URL.currentDirectory().appending(path: "data")
|
||||
|
||||
super.init()
|
||||
loadShop(url: dataPath.appending(path: "shop-products.csv"))
|
||||
}
|
||||
|
||||
|
||||
init(balanceJson: URL) {
|
||||
do {
|
||||
let data = try Data(contentsOf: balanceJson, options: .uncached)
|
||||
let balance = try JSONDecoder().decode(Balance.self, from: data)
|
||||
self.balance = balance
|
||||
} catch {
|
||||
printError(error)
|
||||
self.balance = Balance()
|
||||
}
|
||||
dataPath = URL.currentDirectory().appending(path: "data")
|
||||
}
|
||||
|
||||
|
||||
func simulate(_ result: KissMatrixResult) {
|
||||
// 전체 잔고를 설정하고, 최소한의 현금 비중을 설정함.
|
||||
// 상위 30 종목에 대해서, 밸런싱을 수행
|
||||
// 상위 30 종목에서 멀어질수록, (수익이 발생할수록) 보유 수량을 점차 줄여가고, 반대로 상위 30 종목에 대해서는 보유 수량을 늘림
|
||||
|
||||
// weight 의 비율에 따라서 1-2개씩 사모으는 전략으로 갈 것인가?
|
||||
// weight 의 비율에 따라서 1-2개씩 팔아서 현금을 모으는 전략일까?
|
||||
|
||||
let topCount = min(result.output.count / 2, 30)
|
||||
let topResult = result.output.prefix(topCount)
|
||||
|
||||
let bottomCount = min(result.output.count - topCount, 20)
|
||||
let bottomResult = result.output.suffix(bottomCount)
|
||||
|
||||
/// 매수 전략을 수행
|
||||
for item in topResult {
|
||||
guard let price = candleCache.getPrice(productNo: item.shortCode, day: result.day, time: result.time) else {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// 매도 전략을 수행
|
||||
for item in bottomResult {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func simulate(logAt: String, startDate: Date, endDate: Date, interval: TimeInterval = 10 * 60) {
|
||||
var curDate = startDate
|
||||
while curDate <= endDate {
|
||||
guard let result = loadLog(logAt: logAt, day: curDate.yyyyMMdd, time: curDate.HHmmss) else {
|
||||
printError("Cannot load log at \(curDate.yyyyMMdd_HHmmss_forTime)")
|
||||
break
|
||||
}
|
||||
|
||||
simulate(result)
|
||||
curDate.addTimeInterval(interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension KissSimulator {
|
||||
|
||||
private func loadLog(logAt: String, day: String, time: String) -> KissMatrixResult? {
|
||||
let subPath = "\(logAt)/\(day)_\(time)_0000.log"
|
||||
let logUrl = URL.currentDirectory().appending(path: subPath)
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: logUrl, options: .uncached)
|
||||
let result = try JSONDecoder().decode(KissMatrixResult.self, from: data)
|
||||
return result
|
||||
} catch {
|
||||
printError(error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class CandleCache {
|
||||
|
||||
private var candleCaches: [String: [Domestic.Candle]] = [:]
|
||||
|
||||
func getCandle(productNo: String, day: String, time: String) -> Domestic.Candle? {
|
||||
let basePath = URL.currentDirectory().appending(path: "data")
|
||||
let candleUrl = basePath.appending(path: "\(productNo)/min/candle-\(day).log")
|
||||
|
||||
let candles: [Domestic.Candle]
|
||||
|
||||
if let cache = candleCaches[candleUrl.path] {
|
||||
candles = cache
|
||||
}
|
||||
else {
|
||||
do {
|
||||
candles = try [Domestic.Candle].readCsv(fromFile: candleUrl)
|
||||
candleCaches[candleUrl.path] = candles
|
||||
} catch {
|
||||
printError(error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
guard let candle = candles.first(where: { $0.stockConclusionTime == time }) else {
|
||||
return nil
|
||||
}
|
||||
return candle
|
||||
}
|
||||
|
||||
|
||||
func getPrice(productNo: String, day: String, time: String) -> Int? {
|
||||
guard let candle = getCandle(productNo: productNo, day: day, time: time) else {
|
||||
return nil
|
||||
}
|
||||
return Int(candle.currentStockPrice)
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if true
|
||||
KissGolder().run()
|
||||
|
||||
#else
|
||||
let startDate = "20230626 090000".modelDate!
|
||||
let endDate = "20230630 153000".modelDate!
|
||||
|
||||
KissSimulator().simulate(logAt: "data", startDate: startDate, endDate: endDate)
|
||||
#endif
|
||||
|
||||
@@ -85,7 +85,7 @@ class KissMatrix {
|
||||
return taskResult
|
||||
}
|
||||
|
||||
mergeResult(results)
|
||||
mergeResult(results, date: runDate)
|
||||
|
||||
semaphore.signal()
|
||||
}
|
||||
@@ -144,7 +144,7 @@ class KissMatrix {
|
||||
}
|
||||
|
||||
|
||||
private func mergeResult(_ results: [KissIndexResult]) {
|
||||
private func mergeResult(_ results: [KissIndexResult], date: Date) {
|
||||
let indexCount = results.count
|
||||
var mergedOutput = [String: [KissIndexResult.Output]]()
|
||||
for result in results {
|
||||
@@ -168,7 +168,7 @@ class KissMatrix {
|
||||
|
||||
|
||||
let kmis = results.map { $0.kmi }
|
||||
let matrixResult = KissMatrixResult(code: 200, kmis: kmis, output: normalized)
|
||||
let matrixResult = KissMatrixResult(code: 200, kmis: kmis, day: date.yyyyMMdd, time: date.HHmmss, output: normalized)
|
||||
do {
|
||||
let jsonData = try JSONEncoder().encode(matrixResult)
|
||||
try FileHandle.standardOutput.write(contentsOf: jsonData)
|
||||
|
||||
2
bin/data
2
bin/data
Submodule bin/data updated: 8ef77ece18...5f307c3bb1
@@ -8,6 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
340A4DCC2A4E4C37005A1FBA /* KissGolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340A4DCB2A4E4C37005A1FBA /* KissGolder.swift */; };
|
||||
340A4DD22A4EBEB0005A1FBA /* KissSimulator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340A4DD12A4EBEB0005A1FBA /* KissSimulator.swift */; };
|
||||
3498435C2A24DC1300E85B08 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3498435B2A24DC1300E85B08 /* main.swift */; };
|
||||
349843642A24DC7800E85B08 /* KissMe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349843632A24DC7800E85B08 /* KissMe.framework */; };
|
||||
349843652A24DC7800E85B08 /* KissMe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 349843632A24DC7800E85B08 /* KissMe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@@ -38,6 +39,7 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
340A4DCB2A4E4C37005A1FBA /* KissGolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissGolder.swift; sourceTree = "<group>"; };
|
||||
340A4DD12A4EBEB0005A1FBA /* KissSimulator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissSimulator.swift; sourceTree = "<group>"; };
|
||||
349843582A24DC1300E85B08 /* KissMeGolder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = KissMeGolder; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3498435B2A24DC1300E85B08 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
349843632A24DC7800E85B08 /* KissMe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KissMe.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -77,6 +79,7 @@
|
||||
children = (
|
||||
3498435B2A24DC1300E85B08 /* main.swift */,
|
||||
340A4DCB2A4E4C37005A1FBA /* KissGolder.swift */,
|
||||
340A4DD12A4EBEB0005A1FBA /* KissSimulator.swift */,
|
||||
);
|
||||
name = KissMeGolder;
|
||||
path = ../../KissMeGolder/Sources;
|
||||
@@ -150,6 +153,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
340A4DCC2A4E4C37005A1FBA /* KissGolder.swift in Sources */,
|
||||
340A4DD22A4EBEB0005A1FBA /* KissSimulator.swift in Sources */,
|
||||
3498435C2A24DC1300E85B08 /* main.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
Reference in New Issue
Block a user