制作 K 线的 Data Model,从前面文章 [加权指数K线图实作.2] 的 response 可以知道,我们的 csv 档需要 [开]、[高]、[低]、[收] 四个值。考虑到台股所有股票都会用到技术分析,再加上 stockCode 和 stockName,让这个 Data Model 可以用在其他股票项目上。
import Foundation
struct StockKLine {
let stockCode: String
let stockName: String
let dateString: String
let openString: String
let highestString: String
let lowestString: String
let closeString: String
}
在完成 Data Model 的宣告之後,就回去修改 TwStockKLineManager。之前传出来的是 String,但现在已经有 StockKLine 的 Data Model 了,那就应该传出对应的型别,而不是 String了。
TwStockKLineManager parse csv,并传出 KLine model 的程序码如下
import Foundation
//加权指数-公开资讯观测站 https://www.twse.com.tw/zh/page/trading/indices/MI_5MINS_HIST.html
// https://www.twse.com.tw/en/indicesReport/MI_5MINS_HIST?response=csv&date=20210907
class TwStockKLineManager {
private var dateUtility: DateUtility {
return DateUtility()
}
private lazy var alamofireAdapter: AlamofireAdapter = {
return AlamofireAdapter()
}()
func requestTwStockKLine(date: Date, completion: @escaping (([StockKLine], Error?) -> Void)) {
let format = "yyyyMMdd"
let string = dateUtility.getString(date: date, format: format)
let urlString = "https://www.twse.com.tw/en/indicesReport/MI_5MINS_HIST?response=csv&date=\(string)"
alamofireAdapter.requestForString(urlString, method: .get) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let string):
let kLineDataSet = self.convertToKLineList(string)
completion(kLineDataSet, nil)
case .failure(let error):
completion([], error)
}
}
}
private func convertToKLineList(_ string: String) -> [StockKLine] {
var kLineDataSet = [StockKLine]()
let trimmedString = trimmedFirstLine(string)
if let csv = try? CSVAdapter(rawString: trimmedString) {
for each in csv.namedRows {
let stockCode = "taiex"
let stockName = "台股加权指数"
let dateString = each["Date"] ?? ""
let openString = each["Opening Index"] ?? ""
let highestString = each["Highest Index"] ?? ""
let lowestString = each["Lowest Index"] ?? ""
let closeString = each["Closing Index"] ?? ""
let kLine = StockKLine(stockCode: stockCode, stockName: stockName, dateString: dateString, openString: openString, highestString: highestString, lowestString: lowestString, closeString: closeString)
kLineDataSet.append(kLine)
}
}
return kLineDataSet
}
private func trimmedFirstLine(_ string: String) -> String {
return CSVAdapter.removeLine(string, at: 1)
}
}
接下来,在 TwStockMarketKLineModel 中,加上让 VC 呼叫,可取得 K 线资料。在呼叫後,只有 Model 知道要拿取几个月份的资料,因前述设定,为取得当月和上一个月的 K 线资料,所以 requestTwExKLineInfo() 会呼叫 requestTwExThisMonthKLineInfo() 和 requestTwExLastMonthKLineInfo()。
/// 会取这个月和前一个月台股加权指的 KLine data,单一个月,有可能 k 棒数量太少
func requestTwExKLineInfo() {
requestTwExThisMonthKLineInfo()
requestTwExLastMonthKLineInfo()
}
private func requestTwExThisMonthKLineInfo() {
let date = dateUtility.getStartOfMonth()
manager.requestTwStockKLine(date: date) { [weak self] kLineDataSet, error in
self?.delegate?.didRecieveTaiEx(kLineDataSet: kLineDataSet, error: error)
self?.update(kLineDataSet)
}
}
private func requestTwExLastMonthKLineInfo() {
let date = dateUtility.getLastMonthStartDate()
manager.requestTwStockKLine(date: date) { [weak self] kLineDataSet, error in
self?.delegate?.didRecieveTaiEx(kLineDataSet: kLineDataSet, error: error)
self?.update(kLineDataSet)
}
}
private func update(_ dataSet: [StockKLine]) {
let updatedData = Set(self.twExStockDataSet + dataSet)
self.twExStockDataSet = Array(updatedData).sorted { $0.dateString < $1.dateString }
}
在完成後,使用 delegate pattern 通知 VC 有资料更新。
整个 TwStockMarketKLineModelDelegate 的程序码如下
import Foundation
protocol TwStockMarketKLineModelDelegate: AnyObject {
func didRecieveTaiEx(kLineDataSet: [StockKLine], error: Error?)
}
class TwStockMarketKLineModel {
weak var delegate: TwStockMarketKLineModelDelegate?
private var dateUtility: DateUtility {
return DateUtility()
}
private lazy var manager: TwStockKLineManager = {
return TwStockKLineManager()
}()
var twExStockDataSet = [StockKLine]()
/// 会取这个月和前一个月台股加权指的 KLine data,单一个月,有可能 k 棒数量太少
func requestTwExKLineInfo() {
requestTwExThisMonthKLineInfo()
requestTwExLastMonthKLineInfo()
}
private func requestTwExThisMonthKLineInfo() {
let date = dateUtility.getStartOfMonth()
manager.requestTwStockKLine(date: date) { [weak self] kLineDataSet, error in
self?.delegate?.didRecieveTaiEx(kLineDataSet: kLineDataSet, error: error)
self?.update(kLineDataSet)
}
}
private func requestTwExLastMonthKLineInfo() {
let date = dateUtility.getLastMonthStartDate()
manager.requestTwStockKLine(date: date) { [weak self] kLineDataSet, error in
self?.delegate?.didRecieveTaiEx(kLineDataSet: kLineDataSet, error: error)
self?.update(kLineDataSet)
}
}
private func update(_ dataSet: [StockKLine]) {
let updatedData = Set(self.twExStockDataSet + dataSet)
self.twExStockDataSet = Array(updatedData).sorted { $0.dateString < $1.dateString }
}
}
然後在 VC 中设定好成为发动 Model 的 Button action,并成为 Model 的 delegate 即可。
import UIKit
class TwStockMarketKLineViewController: UIViewController {
@IBOutlet weak var debugTextView: UITextView!
private lazy var model: TwStockMarketKLineModel = {
let model = TwStockMarketKLineModel()
model.delegate = self
return model
}()
// MARK: - life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - IBAction
@IBAction func fetchTwStockKLineButtonDidTap(_ sender: Any) {
model.requestTwExKLineInfo()
}
@IBAction func dubugButtonDidTap(_ sender: Any) {
var string = ""
for kLine in model.twExStockDataSet {
string += "\(kLine)\n"
}
debugTextView.text = string
}
}
extension TwStockMarketKLineViewController: TwStockMarketKLineModelDelegate {
func didRecieveTaiEx(kLineDataSet: [StockKLine], error: Error?) {
print(model.twExStockDataSet)
}
}
为了 debug 方便,在这边加上 textView 和 debug button
接下来,就可以专心的画图了,因为你已经取得了需要呈现的资料。
下方是这次 D1 ~ D12 的完成品,可以下载来试
App Store - 台股申购日历
>>: Day9 - 2D渲染环境基础篇 V[Canvas动画概论] - 成为Canvas Ninja ~ 理解2D渲染的精髓
前序检查(preorder) 中序检查(inorder) 後序检查(postorder) 後序检查来...
点击进入React源码调试仓库。 UI产生交互的根本原因是各种事件,这也就意味着事件与更新有着直接关...
前言 在前面总结完laravel的技巧後 今天想要跟各位分享 开启一个专案的时候 会进行什麽步骤 U...
去年参加 Software Development 类别的铁人赛,主题为PHP 大师之路 - 开源的...
前言 为了让使用者的密码更安全,昨天介绍了所谓的「加盐杂凑」,虽然加盐杂凑已经足够应付现今 CPU ...