こんにちはsuzukiです。本日はSwiftの記事でも紹介したCryptoWatchのAPIを利用してMPAndroidChartのチャートを表示したいと思います。前回の記事ではOkHttp3の導入で詰まった際の備忘を書いたので、もし詰まってしまった方は参考にしていただければと思います。
仮想通貨のチャートサイトであり、APIを提供してくれています。
APIの詳細な情報はこちらから確認ができます。
本記事ではCryptWatchのAPIは詳しく触れていません。仮想通貨のレートなどの一通りの情報は取得できるみたいです。今回の記事と違う情報を取得したいのであれば探して頂ければと思います。
OHLCとはローソク足の描画に使われる。下記の情報の頭文字です。
上記の情報をCryptWatchAPIで取得します。
ローソク足で表示するため
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形式でデータが取得できます。
{"result":{ "期間":[[終了時間、始値、高値、安値、終値、出来高],,,,,,], "期間":[[終了時間、始値、高値、安値、終値、出来高],,,,,,] } }
今回は一日ごとのデータだけが欲しいため下記の様に指定を行います。
https://api.cryptowat.ch/markets/coinbase-pro/btcusd/ohlc?periods=86400
通信周りはOkHttp3を利用します。
またOkHttpで取得したデータをMoshiというライブラリを利用してマッピングします。
build.gradle(Module:app)に下記の内容を追加
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にパーミッションの追加
OkHttpのレスポンスをマッピングするためのデータクラスを作成します。
//JSONがresult:{86400:[[],[]]}}という形のためデータクラスを二つ作成する。 data class ResponseData( val result: OhlcList ) data class OhlcList ( @field:Json(name = "86400") var ohlcList:List<List<Float>> )
通信のエラーのハンドリングなどは特に記述していません。
//通信を結果の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を元に変更しております。
//ローソク足のデータ作成 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 }
こちらで今回行いたかった通信したデータからチャートの作成が完了です。
念のためコードの全量を記述いたします。
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は業務での使用があまりないので、今更な情報が多く思われたら申し訳ございません。
ライブラリ周りはそれぞれの開発環境の違いを多く感じますが、きちんと学んで行けたらと思います。