はじめに
こんにちはsuzukiです。本日はSwiftの記事でも紹介したCryptoWatchのAPIを利用してMPAndroidChartのチャートを表示したいと思います。前回の記事ではOkHttp3の導入で詰まった際の備忘を書いたので、もし詰まってしまった方は参考にしていただければと思います。
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
Androidでの実装
通信周りはOkHttp3を利用します。
またOkHttpで取得したデータをMoshiというライブラリを利用してマッピングします。
実装前の準備
build.gradle(Module:app)に下記の内容を追加
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 | android { compileSdkVersion 28 defaultConfig { applicationId "com.example.macbook007.blogcharts" minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } //下記を追加 compileOptions { targetCompatibility = "8" sourceCompatibility = "8" } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' //OkHttpの追加 implementation("com.squareup.okhttp3:okhttp:3.14.1") //Moshiの追加 implementation("com.squareup.moshi:moshi:1.8.0") testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3' } |
AndroidManifestにパーミッションの追加
1 | <uses-permission android:name="android.permission.INTERNET" /> |
データクラスの作成
OkHttpのレスポンスをマッピングするためのデータクラスを作成します。
1 2 3 4 5 6 7 8 | //JSONがresult:{86400:[[],[]]}}という形のためデータクラスを二つ作成する。 data class ResponseData( val result: OhlcList ) data class OhlcList ( @field:Json(name = "86400") var ohlcList:List<List<Float>> ) |
リクエストの作成
通信のエラーのハンドリングなどは特に記述していません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //通信を結果のOhlcListをコールバックで返却を行う。 fun fetchOHLCData( callback: (OhlcList) -> Unit){ val client = OkHttpClient() val request = Request.Builder() .url("https://api.cryptowat.ch/markets/coinbase-pro/btcusd/ohlc?periods=86400") .build() client.newCall(request).enqueue(object : okhttp3.Callback { @Throws(IOException::class) override fun onResponse(call: Call, response: Response) { response.body()?.string()?.let { val adapter = Moshi.Builder().build().adapter(ResponseData::class.java) adapter.fromJson(it)?.result?.let { callback(it) } } } override fun onFailure(call: Call, arg1: IOException) { } }) } |
データ作成
行なった変更は今までランダム生成していたキャンドル用のデータから通信結果から取得したOhlcListを元に変更しております。
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 | //ローソク足のデータ作成 private fun generateCandleData( ohlcList: OhlcList): CandleData{ val values = mutableListOf<CandleEntry>() ohlcList.list.forEachIndexed { index, list -> values.add( CandleEntry(index.toFloat(),list[2],list[3],list[1],list[4]) ) } val set1 = CandleDataSet(values, "SampleChandleData").apply { axisDependency = YAxis.AxisDependency.LEFT shadowColor = Color.DKGRAY shadowWidth = 0.7f //アイコン描画 setDrawIcons(false) //color for open < close decreasingColor = Color.RED decreasingPaintStyle = Paint.Style.FILL //color for open > close increasingColor = Color.rgb(122, 242, 84) increasingPaintStyle = Paint.Style.STROKE neutralColor = Color.BLUE } return CandleData(set1) } //移動平均線のデータ作成 fun movingAverageData(ohlcList: OhlcList): LineData{ //幾つのデータの平均値を取得するか val movingDataCount = 5 var movingLineChartData = mutableListOf<Entry>() ohlcList.list.forEachIndexed { index, list -> if (index >= movingDataCount -1) { //多元配列をreduceでうまく扱えませんでした。一時的にsumという変数を作成しています。 var sum = 0.0f ohlcList.list.slice(index - (movingDataCount - 1)..index).forEach { sum += it[4] } movingLineChartData.add(Entry(index.toFloat() + 0.5f, sum / movingDataCount)) } } val yVals = LineDataSet(movingLineChartData, "MovingAverageData").apply { lineWidth = 2f highLightColor = Color.YELLOW setDrawValues(false) axisDependency = YAxis.AxisDependency.LEFT color = Color.BLACK setDrawCircles(false) setDrawCircleHole(false) } val data = LineData(yVals) data.setValueTextColor(Color.BLACK) data.setValueTextSize(9f) return data } |
こちらで今回行いたかった通信したデータからチャートの作成が完了です。
念のためコードの全量を記述いたします。
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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | class MainActivity : AppCompatActivity(),OnChartValueSelectedListener { private var mTypeface = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) fetchOHLCData { val combineData = CombinedData() combineData.setData(generateCandleData(it)) combineData.setData(movingAverageData(it)) setCombinedChart(combineData) } } //データからしてチャートを更新 private fun setCombinedChart(combinedData: CombinedData){ combinedChart.apply { setOnChartValueSelectedListener(this@MainActivity) description.isEnabled = false setTouchEnabled(true) isDragEnabled = true isScaleXEnabled = true setPinchZoom(false) setDrawGridBackground(false) //データラベルの表示 legend.apply { form = Legend.LegendForm.LINE typeface = mTypeface textSize = 11f textColor = Color.BLACK verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM horizontalAlignment = Legend.LegendHorizontalAlignment.LEFT orientation = Legend.LegendOrientation.HORIZONTAL setDrawInside(false) } //y軸右側の表示 axisRight.isEnabled = false //X軸表示 xAxis.apply { typeface = mTypeface //表示領域の調整 setDrawLabels(false) setDrawGridLines(true) } //y軸左側の表示 axisLeft.apply { typeface = mTypeface textColor = Color.BLACK setDrawGridLines(true) } data = combinedData //チャートの初期表示を調整 setVisibleYRange( data.yMax - 2000, data.yMax,YAxis.AxisDependency.LEFT) setVisibleXRange(20f, 50f) //チャートの更新を行うため必要 notifyDataSetChanged() invalidate() } } fun fetchOHLCData( callback: (OhlcList) -> Unit){ val client = OkHttpClient() val request = Request.Builder() .url("https://api.cryptowat.ch/markets/coinbase-pro/btcusd/ohlc?periods=86400") .build() client.newCall(request).enqueue(object : okhttp3.Callback { @Throws(IOException::class) override fun onResponse(call: Call, response: Response) { response.body()?.string()?.let { s -> val adapter = Moshi.Builder().build().adapter(ResponseData::class.java) adapter.fromJson(s)?.result?.let { callback(it) } } } override fun onFailure(call: Call, arg1: IOException) { } }) } private fun generateCandleData( ohlcList: OhlcList): CandleData{ val values = mutableListOf<CandleEntry>() ohlcList.list.forEachIndexed { index, list -> values.add( CandleEntry(index.toFloat(),list[2],list[3],list[1],list[4]) ) } val set1 = CandleDataSet(values, "SampleChandleData").apply { axisDependency = YAxis.AxisDependency.LEFT shadowColor = Color.DKGRAY shadowWidth = 0.7f //アイコン描画 setDrawIcons(false) //color for open < close decreasingColor = Color.RED decreasingPaintStyle = Paint.Style.FILL //color for open > close increasingColor = Color.rgb(122, 242, 84) increasingPaintStyle = Paint.Style.STROKE neutralColor = Color.BLUE } return CandleData(set1) } fun movingAverageData(ohlcList: OhlcList): LineData{ //幾つのデータの平均値を取得するか val movingDataCount = 5 var movingLineChartData = mutableListOf<Entry>() ohlcList.list.forEachIndexed { index, list -> if (index >= movingDataCount -1) { //多元配列をreduceでうまく扱えませんでした。一時的にsumという変数を作成しています。 var sum = 0.0f ohlcList.list.slice(index - (movingDataCount - 1)..index).forEach { sum += it[4] } movingLineChartData.add(Entry(index.toFloat() + 0.5f, sum / movingDataCount)) } } val yVals = LineDataSet(movingLineChartData, "MovingAverageData").apply { lineWidth = 2f highLightColor = Color.YELLOW setDrawValues(false) axisDependency = YAxis.AxisDependency.LEFT color = Color.BLACK setDrawCircles(false) setDrawCircleHole(false) } val data = LineData(yVals) data.setValueTextColor(Color.BLACK) data.setValueTextSize(9f) return data } override fun onValueSelected(e: Entry, h: Highlight) { Log.i("Entry selected", e.toString()) } override fun onNothingSelected() { Log.i("Nothing selected", "Nothing selected.") } } data class ResponseData( val result: OhlcList ) data class OhlcList ( @field:Json(name = "86400") var list:List<List<Float>> ) |
最後に
なんとか通信の内容をチャートに表示することができました。
kotlinは業務での使用があまりないので、今更な情報が多く思われたら申し訳ございません。
ライブラリ周りはそれぞれの開発環境の違いを多く感じますが、きちんと学んで行けたらと思います。