iT邦幫忙

2021 iThome 鐵人賽

DAY 25
0
Mobile Development

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

D25 - 用 Swift 和公開資訊,打造投資理財的 Apps { 三大法人成交比重 資料分析 }

台灣股市有揭露三大法人當日買進賣出的金額,在市場上會有流派依照這些進出的資訊,調整手上的資金部位。因為有一種說法是,當法人決定對某檔個股買進,或是對台灣股市執行某向策略,在某個時期內,方向不會變。

不過...現在台股有當日沖銷這樣的機制,而且政府為了推行這個制度,在當沖稅率上還有優惠,這個優惠政策目前看起來會持續到 2024 年。因為稅率有優惠,而且現在台股的波動大,觀察新聞可以看到很多「少年股神」的新聞。這些少年股神們的進出,是足以影響當日的波動。另外,也不是所有的法人進出大量都是長期佈局的,也是有特定的外資法人交易的頻率就是在當天敲大單買進,然後隔天倒出的。因為文章不想提及特定券商法人,建議可以蒐尋「隔日沖」、「券商」等關鍵字,你就會看到別人整理的隔日沖券商和特定分點資料。這邊比較要注意的是特定分點的資料,就只是某帳戶在該分點進出的量而,並不是那個分點自己下單,即使今天你看到特定分點下巨量的單,也不代表明天就會賣出。

資料下載頁面

https://www.twse.com.tw/zh/page/trading/fund/BFI82U.html

如果要拿當前最新資料(英文版)

https://www.twse.com.tw/en/fund/BFI82U?response=csv&dayDate=&weekDate=&monthDate=&type=day

如果要拿某月某日的資料(英文版)

https://www.twse.com.tw/en/fund/BFI82U?response=csv&dayDate=20210914&weekDate=20210913&monthDate=20210915&type=day

因為這隻 api 拿取資料的方式是一次一天,沒辦法像前面的 K 線資料,可以一次拿取多天的資料,所以使用一次 API 的 query,沒辦法把計算後的三大法人百分比,用 line chart 表示。

如果考慮目前拿到資料的方式,已經有目前大盤當日成交量,那就能算出[三大法人] / [非三大法人] 的比例。而呈現比例最方便的 chart 就是 pie chart,接下來,我們就用 pie chart 呈現三大法人佔當日成交量的百分比。

先進行資料分析

https://ithelp.ithome.com.tw/upload/images/20211004/20140622fe8edhnA3u.png

需要的欄位是 [Total Buy] [Total Sell] [Difference]

如果不確定 item 中每一行代表的意義,可以下載一下中文的資料做對照。

https://ithelp.ithome.com.tw/upload/images/20211004/20140622igRdsoacib.png

DataModel 設計如下

struct MajorInvestor {

    let typeString: String
    let date: Date
    let totalBuyString: String
    let totalSellString: String
    let diffString: String
    
    private var numberFormatter: NumberFormatter {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }
    
    var totalBuy: Double {
        return numberFormatter.number(from: totalBuyString)?.doubleValue ?? 0
    }
    
    var totalSell: Double {
        return numberFormatter.number(from: totalSellString)?.doubleValue ?? 0
    }
    
    var difference: Double {
        return numberFormatter.number(from: diffString)?.doubleValue ?? 0
    }
}

而日期的資訊,在第一行的第一個空白之前

所以我們需要把 yyyy/MM/dd 這一段 String 拿出來,再給 DateUtility 轉成 Date 型別

    private func getFirstLine(_ string: String) -> String {
        return CSVAdapter.getFirstLine(string).replacingOccurrences(of: "\"", with: "")
    }
    
    private func getDateString(from firstLine: String) -> String {
        let separatedString = firstLine.components(separatedBy: " ")
        return separatedString.first ?? ""
    }

整個拉取資料的類別設計如下

import Foundation

//三大法人
//https://www.twse.com.tw/zh/page/trading/fund/BFI82U.html
//https://www.twse.com.tw/en/fund/BFI82U?response=csv&dayDate=&weekDate=&monthDate=&type=day
// 如果要 query 某個日期
//https://www.twse.com.tw/en/fund/BFI82U?response=csv&dayDate=20210914&weekDate=20210913&monthDate=20210915&type=day
class ThreeMajorInvestorsManager {
    
    private lazy var alamofireAdapter: AlamofireAdapter = {
        return AlamofireAdapter()
    }()
    
    func requestInvestorsInfo(completion: @escaping (([MajorInvestor], Error?) -> Void)) {
        
        let urlString = "https://www.twse.com.tw/en/fund/BFI82U?response=csv&dayDate=&weekDate=&monthDate=&type=day"
        
        alamofireAdapter.requestForString(urlString, method: .get) { [weak self] result in
            guard let self = self else { return }
            
            switch result {
            case .success(let string):
                
                let firstLine = self.getFirstLine(string)
                let dateString = self.getDateString(from: firstLine)
                let majorInvestors = self.convert(from: string, dateString: dateString)
                completion(majorInvestors, nil)
            case .failure(let error):
                completion([], error)
            }
        }
    }
    
    private func trimmedFirstLine(_ string: String) -> String {
        
        return CSVAdapter.removeLine(string, at: 1)
    }
    
    private func getFirstLine(_ string: String) -> String {
        return CSVAdapter.getFirstLine(string).replacingOccurrences(of: "\"", with: "")
    }
    
    private func getDateString(from firstLine: String) -> String {
        let separatedString = firstLine.components(separatedBy: " ")
        return separatedString.first ?? ""
    }
    
    private func convert(from string: String, dateString: String) -> [MajorInvestor] {
        
        var majorInvestors = [MajorInvestor]()
        
        let trimmedString = self.trimmedFirstLine(string)
        let dateUtility = DateUtility()
        
        if let csv = CSVAdapter(rawString: trimmedString),
           let date = dateUtility.getDate(from: dateString, format: "yyyy/MM/dd") {
            
            for each in csv.namedRows {
                
                let item = each["Item"] ?? ""
                let totalBuy = each["Total Buy"] ?? ""
                let totalSell = each["Total Sell"] ?? ""
                let difference = each["Difference"] ?? ""
                
                // 這一份 csv 檔有 footer,如果 difference 沒有字,就是 footer
                if !difference.isEmpty {
                    let majorInvestor = MajorInvestor(typeString: item, date: date, totalBuyString: totalBuy, totalSellString: totalSell, diffString: difference)
                    majorInvestors.append(majorInvestor)
                }
            }
        }
        
        return majorInvestors
    }
}

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

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

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


上一篇
D24 - 用 Swift 和公開資訊,打造投資理財的 Apps { 台股成交量實作.4 }
下一篇
D26 - 用 Swift 和公開資訊,打造投資理財的 Apps { 三大法人成交比重實作.1 }
系列文
使用 Swift 和公開資訊,打造投資理財的 Apps37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言