D20 - 用 Swift 和公开资讯,打造投资理财的 Apps { 移动平均线(MA线)实作.3 }

扩充 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


<<:  Day30 测试写起乃 - 完赛感言!

>>:  Day15 - Ptt换页及新增文章列表项目

[Day-26] R语言 - 分群应用(五) 分群预测 - 资料分群 ( data clustering in R.Studio )

您的订阅是我制作影片的动力 订阅点这里~ 影片程序码(延续昨天) #步骤二: 资料分群,哪个演算法?...

第 14 天 我不是要压榨你我是给你个成长的机会|Reactive Form

前情提要 我们整理专案後,现在专案有更明确的模组来封装元件,不仅让 App 效能提升,也让专案更「语...

[进阶指南] 不使用 ES6 开发 React( Day27 )

如果不使用 ES6 的 Class,则可以考虑用 create-react-class 。 var ...

〖WordPress主题〗ASTRA释出「AGENCY BUNDLE」头500名购买只要$149的超级优惠

ASTRA 这个热门的WordPress主题,付费版一共有3种方案+2种付费模式;最引以为傲的是☞一...

Day 6 被动搜查(3)-Google Hacking、shadon、.git 泄漏

常见的搜寻引擎 为什麽要利用搜寻引擎找到有关於目标的资讯。 因为搜寻引擎最方便,透过浏览器最容易可以...