iT邦幫忙

2021 iThome 鐵人賽

0
Mobile Development

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

D33 - 用 Swift 和公開資訊,打造投資理財的 Apps { 台股申購功能擴充.4 }

處理申購 VC 的資料,是由 StockSubscriptionModel 處理的。

在前面,我們在 AppDelegate 的 didFinishLaunch 下載了台股上市收盤資料,並存在 UserDefaults 內,所以這個 model 在 init 的時候,理論上就可以拿到上市收盤資料了,如果沒有,那你可以在這個時機點再發動一次下載。

註: 這邊有優化空間,但更細膩的操作,就留待讀者在實務上各自發揮

1-選擇存放資料結構

在 StockSubscriptionModel 中,可以放日收的結構有 Array, Dictionary。這邊我選擇了 Dictionary,並把股票代號當成 key 值,value 為日收的 data model - StockDayTick。

2-實作

先在 StockSubscriptionModel 加上 key vs. value 的 property

private var twMarketDayTicks: [String: StockDayTick] = [:]

再加上日收的 manager,而且有要從 String 轉成 NSNumber, Double 的地方,所以也要一個 NumberFormatter

private lazy var dayPriceManager: StockDayPriceManager = {
        return StockDayPriceManager()
    }()
    
private lazy var numberFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }()

在 Model 一開始,就把上市日收從 UserDefaults 中拿出,並存起來

init() {
        setupDayTicks()
    }
    
    // MARK: - private func
    
    // 將 UserDefaults 中的 day ticks 拿出,並把 stockCode 當 key 值
    private func setupDayTicks() {

        let dayTicks = dayPriceManager.getTwAllStockDayPriceFromUserDefaults()
        let keys = dayTicks.map { tick in
            return tick.stockCode
        }
        for (index, tick) in dayTicks.enumerated() {
            let key = keys[index]
            self.twMarketDayTicks[key] = tick
        }
    }

然後做出讓 VC 呼叫的接口,當 VC 把 subscription 輸入,就可以得到價差的 String

/// 命名可以用 rename 改得更符合變數意義,請自行變更
    func getPricePercentDifferenctString(subscription: StockSubscriptionInfo) -> String? {
        
        let code = subscription.stockCode
        let subscriptionPrice = subscription.price
        if let tick = getTick(from: code),
           let subscriptionPriceDouble = numberFormatter.number(from: subscriptionPrice)?.doubleValue,
           let closePrice = tick.close {
            
            let percentage = (closePrice -  subscriptionPriceDouble) / subscriptionPriceDouble
            let string = String(format: "%.1f", percentage * 100)
            return string
        }
        
        return nil
    }

整個 Model 的 Code

//
//  StockSubscriptionModel.swift
//  ITIronMan
//
//  Created by Marvin on 2021/9/4.
//

import Foundation

protocol StockSubscriptionModelDelegate: AnyObject {
    
    func didRecieveList(_ subscriptionList: [StockSubscriptionInfo], error: Error?)
}

/// 股票申購 VC 所需的 Model
class StockSubscriptionModel {
    
    weak var delegate: StockSubscriptionModelDelegate?
    
    var subscriptionList: [StockSubscriptionInfo] = []
    
    private var twMarketDayTicks: [String: StockDayTick] = [:]
    
    private lazy var subscriptionManager: StockSubscriptionManager = {
        return StockSubscriptionManager()
    }()
    
    private lazy var dayPriceManager: StockDayPriceManager = {
        return StockDayPriceManager()
    }()
    
    private lazy var numberFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }()
    
    var count: Int {
        return subscriptionList.count
    }
    
    init() {
        setupDayTicks()
    }
    
    // MARK: - private func
    
    // 將 UserDefaults 中的 day ticks 拿出,並把 stockCode 當 key 值
    private func setupDayTicks() {

        let dayTicks = dayPriceManager.getTwAllStockDayPriceFromUserDefaults()
        let keys = dayTicks.map { tick in
            return tick.stockCode
        }
        for (index, tick) in dayTicks.enumerated() {
            let key = keys[index]
            self.twMarketDayTicks[key] = tick
        }
    }
    
    private func filterNotAvailable(_ subscriptionList: [StockSubscriptionInfo]) -> [StockSubscriptionInfo] {
        
        let list = subscriptionList.filter { info in
            let code = info.stockCode
            let firstCharacter = code.first ?? "0"
            return firstCharacter != "A"
        }
        
        return list
    }
    
    private func getQueryYear() -> Int {
        
        let dateUtility = DateUtility()
        
        return dateUtility.getIntFromDate(component: .year)
    }
    
    private func getTick(from stockCode: String) -> StockDayTick? {
        
        if let tick = twMarketDayTicks[stockCode] {
            return tick
        }
        
        return nil
    }
    
    // MARK: - public func
    func getSubscriptionInfo(at indexPath: IndexPath) -> StockSubscriptionInfo? {
        
        let index = indexPath.row
        
        if subscriptionList.indices.contains(index) {
            return subscriptionList[index]
        }
        
        return nil
    }
    
    func requestStockSubscription() {
        
        let year = getQueryYear()
        subscriptionManager.requestStockSubscriptionInfo(year: year) { [weak self] subscriptionList, error in
            
            // 需要去掉中央債的資料
            self?.subscriptionList = self?.filterNotAvailable(subscriptionList) ?? []
            self?.delegate?.didRecieveList(subscriptionList, error: error)
        }
    }
    
    /// 命名可以用 rename 改得更符合變數意義,請自行變更
    func getPricePercentDifferenctString(subscription: StockSubscriptionInfo) -> String? {
        
        let code = subscription.stockCode
        let subscriptionPrice = subscription.price
        if let tick = getTick(from: code),
           let subscriptionPriceDouble = numberFormatter.number(from: subscriptionPrice)?.doubleValue,
           let closePrice = tick.close {
            
            let percentage = (closePrice -  subscriptionPriceDouble) / subscriptionPriceDouble
            let string = String(format: "%.1f", percentage * 100)
            return string
        }
        
        return nil
    }
}

extension StockSubscriptionModel {
    
    enum SubscriptionState {
        
        case beforeSubscription
        case duringSubscription
        case finishedSubscription
        case notDefined
    }
}

extension StockSubscriptionModel {
    
    func getSubscriptionState(info: StockSubscriptionInfo) -> SubscriptionState {
        
        let currentTime = Date().timeIntervalSince1970
        
        if let startTime = info.subscriptionStart?.timeIntervalSince1970,
           let endTime = info.subscriptionEnd?.timeIntervalSince1970 {
            
            if currentTime < startTime {
                return .beforeSubscription
            } else if currentTime > endTime {
                return .finishedSubscription
            } else {
                return .duringSubscription
            }
        }
        
        return .notDefined
    }
}

台股申購日曆
IT鐵人賽Demo App

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

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


上一篇
D32 - 用 Swift 和公開資訊,打造投資理財的 Apps { 台股申購功能擴充,算出價差.3 }
下一篇
D34 - 用 Swift 和公開資訊,打造投資理財的 Apps { 台股申購功能擴充.5 }
系列文
使用 Swift 和公開資訊,打造投資理財的 Apps37

尚未有邦友留言

立即登入留言