はじめに
こんにちは鈴木です。最近ローソク足の記事を書いているにあたり、テスト用のランダムデータではなく、実際のOHLCのデータを使いたい思っておりました。今回色々調べた結果CryptoWatchのAPIが簡単にJSONデータの取得ができそうだったため実装しました。
CryptoWatchについて
仮想通貨のチャートサイトであり、APIを提供してくれています。
APIの詳細な情報はこちらから確認ができます。
本記事ではCryptWatchのAPIは詳しく触れていませんので詳しく知りたい方は是非!
OHLC
OHLCとはローソク足の描画に使われる。下記の情報の頭文字です。
- Open/始値
- High/高値
- Low/安値
- Close/終値
上記の情報をCryptWatchAPIで取得します。
リクエスト方法
CryptoWatchのAPI
ローソク足で表示するため
- OHLCのデータが複数取得できる
- スパンは1日ごと
CryptWatchの下記のリンクからリクエストを作成します。
https://cryptowat.ch/docs/api#market-ohlc
下記が基本の形式です。ExchangeとPairが気になる方はそれぞれリストをJSON形式で取得することも可能です。
market/[Exchange]/[Pair]/ohlc
例として値を入力するとhttps://api.cryptowat.ch/markets/coinbase-pro/btcusd/ohlc
アクセスするとJSON形式でデータが取得できます。
1 2 3 4 5 | {"result":{ "期間":[[終了時間、始値、高値、安値、終値、出来高],,,,,,], "期間":[[終了時間、始値、高値、安値、終値、出来高],,,,,,] } } |
今回は一日ごとのデータだけが欲しいため下記の様に指定を行います。
https://api.cryptowat.ch/markets/coinbase-pro/btcusd/ohlc?periods=86400
Swiftでの実装方法
通信周りは外部ライブラリを使用しないで下記で実装しました。
- Codable
- JSONDecoder
- URLSession
Codable
1 2 3 4 5 6 7 8 9 10 11 | //一月ごとのデータ struct MonthOHLCListResult: Codable{ let result: OHLCList } struct OHLCList: Codable{ //本来であれば別クラスを定義すべきかもしれませんが、処理的にもあまり多くしたくないため[[Double]]としてそのまま利用 let list: [[Double]] private enum CodingKeys: String, CodingKey { case list = "86400" } } |
JSONDecoder・URLSession
関数として成功時にOHLCListをコールバックする様な実装です。エラー処理は特に考えていません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | func fetchOHLCData(completionHandler: @escaping (OHLCList) -> Void ){ guard let url = URL(string: "https://api.cryptowat.ch/markets/coinbase-pro/btcusd/ohlc?periods=86400") else { return } URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in guard let data = data else{ return } //JSONDecoder let decoder = JSONDecoder() //JSONDecoderでレスポンス→MonthOHLCListResultクラスとして変換 if let response = try? decoder.decode(MonthOHLCListResult.self, from: data){ //メインスレッドで返却 DispatchQueue.main.async{ completionHandler(response.result) } } }.resume() } |
以前の実装の移動平均線の描画箇所をコールバックの値を使用するように変更しました。
細かな説明は不要かとは思いますので今回の変更を含めてまとめて記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | import UIKit import Charts class ViewController: UIViewController,ChartViewDelegate { @IBOutlet weak var combinedChartView: CombinedChartView! override func viewDidLoad() { super.viewDidLoad() //データ取得できたら更新 fetchOHLCData { ohlcList in let combinedData = CombinedChartData() combinedData.candleData = self.generateCandleData(ohlcList) combinedData.lineData = self.generateMovingAverageData(ohlcList) self.setupCombineChart(self.combinedChartView,data: combinedData) } } func generateCandleData(_ ohlcList: OHLCList) -> CandleChartData{ let candleChartDataEntries:[CandleChartDataEntry] = ohlcList.list.enumerated().compactMap{ return CandleChartDataEntry(x: Double($0.offset), shadowH: $0.element[2], shadowL: $0.element[3], open: $0.element[1], close: $0.element[4]) } let set = CandleChartDataSet(values: candleChartDataEntries, label: "Candle DataSet") set.setColor(.lightText) set.decreasingColor = .blue set.decreasingFilled = true set.increasingColor = .red set.increasingFilled = true set.shadowColor = .darkGray set.valueFont = .systemFont(ofSize: 10) set.drawValuesEnabled = false return CandleChartData(dataSet: set) } func generateMovingAverageData(_ ohlcList: OHLCList) -> LineChartData{ //幾つのデータの平均値を取得するか let movingDataCount = 5 var movingLineChartData:[ChartDataEntry] = [] ohlcList.list.enumerated().forEach { (i, data) in //最初の4件はデータを表示しない guard i >= movingDataCount - 1 else{ return } //移動平均線用にデータを計算頭の4つはデータの取得が出来ていないのでこのような形 let val = ohlcList.list[i - (movingDataCount - 1) ... i].reduce(0, { (result, ohlc) -> Double in result + ohlc[4] }) / Double(movingDataCount) movingLineChartData.append(ChartDataEntry(x: Double(i), y: val)) } let set1 = LineChartDataSet(values: movingLineChartData, label: "MovingAverageData") set1.lineWidth = 1.75 set1.setColor(.black) set1.highlightColor = .yellow set1.drawValuesEnabled = false set1.drawCirclesEnabled = false return LineChartData(dataSet: set1) } func fetchOHLCData(completionHandler: @escaping (OHLCList) -> Void ){ guard let url = URL(string: "https://api.cryptowat.ch/markets/coinbase-pro/btcusd/ohlc?periods=86400") else { return } URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in guard let data = data else{ return } //JSONDecoder let decoder = JSONDecoder() //JSONDecoderでレスポンス→MonthOHLCListResultクラスとして変換 if let response = try? decoder.decode(MonthOHLCListResult.self, from: data){ print(response.result) DispatchQueue.main.async{ completionHandler(response.result) } } }.resume() } // 複合チャートの描画 func setupCombineChart(_ chart: CombinedChartView, data: CombinedChartData){ chart.dragEnabled = true chart.setScaleEnabled(true) chart.pinchZoomEnabled = true // legendの設定 chart.legend.horizontalAlignment = .left chart.legend.verticalAlignment = .bottom chart.legend.orientation = .horizontal chart.legend.drawInside = false chart.legend.font = UIFont(name: "HelveticaNeue-Light", size: 10)! // 左の目盛りの設定 chart.leftAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: 10)! chart.leftAxis.spaceTop = 0.3 chart.leftAxis.spaceBottom = 0.3 // chart.leftAxis.axisMinimum = 90 // chart.rightAxis.enabled = false chart.xAxis.enabled = false chart.data = data chart.setVisibleYRange(minYRange: data.yMax - 2000, maxYRange: data.yMax, axis: .left) chart.setVisibleXRange(minXRange: 20, maxXRange: 50) } } //一月ごとのデータ取得 struct MonthOHLCListResult: Codable{ let result: OHLCList } struct OHLCList: Codable{ //本来であれば別クラスを定義すべきかもしれませんが、処理的にもあまり多くしたくないため[[Double]]としてそのまま利用 let list: [[Double]] private enum CodingKeys: String, CodingKey { case list = "86400" } } |
さいごに
最後までありがとうございます。今回JSONDecoder+Codableを使いたかったため、通信周りはよそのライブラリを使用しないで実装しました。
APIにもよりますがライブラリ不要でシンプルな実装ができるのは嬉しいですね。