iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
Mobile Development

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

D20 - 用 Swift 和公開資訊,打造投資理財的 Apps { 移動平均線(MA線)實作.3 }

  • 分享至 

  • xImage
  •  

擴充 MAUtility,讓原來的 func 能計算 n 條均線

在原來的 func 上加上 range: Int 的輸入,然後把原來計算的區間全部用代入變數的替換。

import Foundation

/// 專門處理移動平均線的物件
struct MovingAverageUtility {
    
    /// 將已得到的 K 棒,轉出 n MA 的資料
    /// - Parameter stockTicks: 傳入的 K 棒需先保證 date 從遠排到近
    /// - Returns: 回傳的 MA 點也保證會是 x 從小排到大
    func getMAPoints(from stockTicks: [StockKLine], range: Int) -> [MovingAveragePoint] {
        
        let maPeriod = range
        
        let tickIndices = Array(stockTicks.indices).sorted { $0 > $1 } // 先拿出 index 並把 index 從大到小排
        
        var maPoints = [MovingAveragePoint]()
        
        for tickIndex in tickIndices {

            let startIndex = tickIndex - maPeriod + 1
            
            if !stockTicks.indices.contains(startIndex) || !stockTicks.indices.contains(tickIndex) {
                break
            }
            
            let needCalculateTicks = Array(stockTicks[startIndex...tickIndex])
            
            // 從這裡開始計算 n 日內收盤價的平均,有更有效率的做法,像是動態規畫的方式實作。這一段就留給讀者自行優化
            let closePriceList = needCalculateTicks.map { tick in
                
                return tick.close ?? 0 // 這邊先不考慮如果沒有收盤價(暫停交易)的情況,如果有,應該把這個點去除掉,使用 filter 即可
            }
            
            let sum = closePriceList.reduce(0, +) //總合
            let maValue = sum / Double(maPeriod)
            
            let point = MovingAveragePoint(x: Double(tickIndex), y: maValue)
            maPoints.append(point)
        }
        
        return maPoints.sorted { $0.x < $1.x }
    }
}

修改 ChartsAdapter 在 combinedChartView 相關功能

先決定我們要的 MA 線分別是 5MA, 10MA, 20 MA 的線。再長下去,目前 csv 的數量是不夠的。如果要更大範圍的 MA,那所需要的資料量也是相對大。

在 ChartsAdapter 補上輸入 Range 和 Color,回傳 LineChartDataSet 的 func 即可

private func getMALineData(stockSticks: [StockKLine], range: Int, color: UIColor) -> LineChartDataSet

整個 ChartsAdapter 的程式碼如下

// MARK: - Combine Charts 相關 func
extension ChartsAdapter {
    
    private var maUtiltiy: MovingAverageUtility {
        return MovingAverageUtility()
    }
    
    func getCombineChartView() -> UIView {
        let view = CombinedChartView()
        setupCombinedChartView(view)
        return view
    }
    
    func updateWithMALine(stockSticks: [StockKLine], combinedView: UIView) {
        
        if let combinedView = combinedView as? CombinedChartView {
            
            let ma5DataSet = getMALineData(stockSticks: stockSticks, range: 5, color: .blue)
            let ma10DataSet = getMALineData(stockSticks: stockSticks, range: 10, color: .red)
            let ma20DataSet = getMALineData(stockSticks: stockSticks, range: 20, color: .systemOrange)
            
            let lineData = LineChartData(dataSets: [ma5DataSet, ma10DataSet, ma20DataSet])
            let candleData = getCandleData(stockSticks: stockSticks)
            
            let combinedData = CombinedChartData()
            combinedData.lineData = lineData
            combinedData.candleData = candleData
            
            combinedView.data = combinedData
            
            // 這邊有優化空間,請讀者自行優化
            let candleDataEntry = convert(stockStick: stockSticks)
            let dataSet = convert(dataEntry: candleDataEntry)
            updateMaxMin(combinedView, dataSet: dataSet)
            
            let indexDateLabels = getIndexDateLabels(from: stockSticks)
            updateXAxis(combinedView, indexDateLabels: indexDateLabels)
        }
    }
    
    private func getMALineData(stockSticks: [StockKLine], range: Int, color: UIColor) -> LineChartDataSet {
        
        var lineDataEntry = [ChartDataEntry]()
        
        let maPoints = maUtiltiy.getMAPoints(from: stockSticks, range: range)
        
        for point in maPoints {
            let dataEntry = ChartDataEntry(x: point.x, y: point.y)
            lineDataEntry.append(dataEntry)
        }
        
        // maPoints 得到了
        let maDataSet = LineChartDataSet(entries: lineDataEntry, label: "\(range) MA")
        
        maDataSet.setColor(color)
        maDataSet.lineWidth = 1
        maDataSet.drawCirclesEnabled = false
        maDataSet.drawValuesEnabled = false
        maDataSet.axisDependency = .left
        maDataSet.highlightEnabled = true
        
        return maDataSet
    }
    
    private func getCandleData(stockSticks: [StockKLine]) -> CandleChartData {
        
        let candleDataEntry = convert(stockStick: stockSticks)
        let candleDataSet = convert(dataEntry: candleDataEntry)
        let candleData = convert(dataSet: candleDataSet)
        return candleData
    }
    
    private func setupCombinedChartView(_ chartView: CombinedChartView) {
        
        chartView.dragEnabled = false
        chartView.setScaleEnabled(true)
        chartView.maxVisibleCount = 1000
        chartView.pinchZoomEnabled = true
        
        chartView.legend.horizontalAlignment = .right
        chartView.legend.verticalAlignment = .top
        chartView.legend.orientation = .vertical
        chartView.legend.drawInside = false
        chartView.legend.font = UIFont.systemFont(ofSize: 10)
        
        chartView.leftAxis.labelFont = UIFont.systemFont(ofSize: 10)
        chartView.leftAxis.spaceTop = 0.3
        chartView.leftAxis.spaceBottom = 0.3
        chartView.leftAxis.axisMinimum = 0
        
        chartView.rightAxis.enabled = false
        
        chartView.xAxis.labelPosition = .bottom
        chartView.xAxis.labelFont = UIFont.systemFont(ofSize: 10)
        chartView.xAxis.labelCount = 10
    }
}

Build and Run App 後,均線就在 K 線上了。

https://ithelp.ithome.com.tw/upload/images/20210929/20140622lHODDCrzeQ.png

三條均線的範例程式碼

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

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

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


上一篇
D19 - 用 Swift 和公開資訊,打造投資理財的 Apps { 移動平均線(MA線)實作.2 }
下一篇
D21 - 用 Swift 和公開資訊,打造投資理財的 Apps { 台股成交量實作.1 }
系列文
使用 Swift 和公開資訊,打造投資理財的 Apps37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言