iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
Mobile Development

使用 Swift 和公開資訊,打造投資理財的 Apps系列 第 14

D14 - 用 Swift 和公開資訊,打造投資理財的 Apps { 加權指數K線圖實作.2 }

制作 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

https://ithelp.ithome.com.tw/upload/images/20210923/20140622Lmoe5bpGRh.png

接下來,就可以專心的畫圖了,因為你已經取得了需要呈現的資料。

下方是這次 D1 ~ D12 的完成品,可以下載來試
App Store - 台股申購日曆

https://ithelp.ithome.com.tw/upload/images/20210924/20140622ypOBM0tgrZ.png


上一篇
D13 - 用 Swift 和公開資訊,打造投資理財的 Apps { 加權指數K線圖實作.1 }
下一篇
D14.5 - 用 Swift 和公開資訊,打造投資理財的 Apps { 來個中場回億番 }
系列文
使用 Swift 和公開資訊,打造投資理財的 Apps37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言