在原來的 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 }
}
}
先決定我們要的 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 線上了。
台股申購日曆
IT鐵人賽Demo App
下方是這次 D1 ~ D12 的完成品,可以下載來試
App Store - 台股申購日曆