カテゴリー: Android

Kotlin MPAndroidChartライブラリを使いボリンジャーバンドを描画してみる。

はじめに

こんにちはsuzukiです。今回はMPAndroidChartを使いボリンジャーバンドの描画に挑戦します。
前回記事で移動平均線を描画する際に少し詳しくMPAndroidChartライブラリの説明をしています。よければご覧ください。

ボリンジャーバンドとは

ボリンジャーバンドはローソク足を分析する際の手法です。詳しい内容自体はリンクを参照ください。
今回プログラムでは下記の三点と一部のデータだけ画面に表示するということを行っていきたいと思います。

  • 標準偏差σを求める
  • 移動平均線に±σを行いグラフのデータを作成する
  • CombineChartで描画を行う

ライブラリの導入

build.gradle(Project)

repositories {
    maven { url 'https://jitpack.io' }
}

build.gradle(Module)

dependencies {
    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0-alpha'
}

上記を追加してgradleのSyncを行いましょう。

描画の準備

次にLayoutFileでチャートの描画範囲を決めましょう。

  • CombinedChartを追加
  • 上下左右に制約を追加
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <com.github.mikephil.charting.charts.CombinedChart
            android:id="@+id/combinedChart"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent"
    />

</android.support.constraint.ConstraintLayout>

ボリンジャーバンドを描画する

それでは実際にボリンジャーバンドを描画しましょう。
完成予定

CombinedChartの設定

combineChartの設定は、新たにデータ数を増やす影響もありX軸の表示数に制限を行います。
combinedChart.setVisibleXRangeMinimum(5.0f)
combinedChart.setVisibleXRangeMaximum(30.0f)
またスワイプの動作を行うためdragEnabledをtrueに変更します
isDragEnabled = true

 //  複合チャートの描画
    private fun setupCombinedChart(combinedChart: CombinedChart, data: CombinedData) {
        combinedChart.apply {
            setOnChartValueSelectedListener(this@MainActivity)
            //タッチ検出
            setTouchEnabled(true)
            //ドラッグの有効化
            isDragEnabled = true
            isScaleXEnabled = true
            description.isEnabled = 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)
            }
            setData(data)
            setVisibleXRangeMaximum(30.0f)
            setVisibleXRangeMinimum(5.0f)
        }
    }

CombinedChart用のデータ作成

ボリンジャーバンドを作成するためのデータを作成します。
ローソク足用のデータは移動平均線と同様の作成方法を行い
下記のendPointsに終値のデータを保持します。
private var endPoints = mutableListOf()
移動平均線は5日としてデータ作成したのですが、ボリンジャーバンドは期間が20or25と書いてあったため下記を宣言します。
private val periodCount = 20

    private fun generateCandleData(count: Int, range: Float): CandleData {
        val values = mutableListOf<CandleEntry>()
        endPoints = mutableListOf()

        for (i in 0 until count) {
            val multi = (range + 1)
            val value = (Math.random() * 40).toFloat() + multi
            val high = (Math.random() * 9).toFloat() + 8f
            val low = (Math.random() * 9).toFloat() + 8f
            val open = (Math.random() * 6).toFloat() + 1f
            val close = (Math.random() * 6).toFloat() + 1f
            val even = i % 2 == 0
            endPoints.add(if (even) value - close else value + close)
            values.add(
                CandleEntry(
                    i.toFloat() + 0.5f, value + high,
                    value - low,
                    if (even) value + open else value - open,
                    if (even) value - close else value + close
                )
            )
        }
        return CandleData(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
        })
    }

続いてボリンジャーバンド用のデータです。
移動平均線は一つの折れ線でしたがボリンジャーバンドは下記の5つの折れ線です。

  • アッパーバンド2:単純移動平均線+2標準偏差(σ)
  • アッパーバンド1:単純移動平均線+1標準偏差(σ)
  • ミッドバンド:単純移動平均線
  • ロワーバンド1:単純移動平均線-1標準偏差(σ)
  • ロワーバンド2:単純移動平均線-2標準偏差(σ)

前回と同じようなコードだととても縦長になってしまうため。下記の関数を作成します。

    ///Entry,ラベルの文字列、線の色を受け取り LineDataSetを返却する関数 
   private fun createLineDataSet(chartData: MutableList<Entry>, label: String, color: Int): LineDataSet {
        return LineDataSet(chartData, label).apply {
            lineWidth = 2f
            setDrawValues(false)
            axisDependency = YAxis.AxisDependency.LEFT
            this.color = color
            setDrawCircles(false)
            setDrawCircleHole(false)
        }
    }

続いてendPointからボリンジャーバンド用にデータを作成します。
引数でendPointsを明示的に渡すようにしました。
sliceでarrayを作ったのでそのまま書いていますが、標準偏差sigmaを求める関数作った方がシンプルに見えるかもしれません。

    private fun bolingerBandData(endPoints: MutableList<Float>): LineData {

        //アッパーバンド2=単純移動平均線+2標準偏差(σ)
        val upperBand2Entry = mutableListOf<Entry>()
        //アッパーバンド1=単純移動平均線+1標準偏差(σ)
        val upperBand1Entry = mutableListOf<Entry>()
        //ミッドバンド=単純移動平均線
        val midBandEntry = mutableListOf<Entry>()
        //ロワーバンド1=単純移動平均線-1標準偏差(σ)
        val lowerBand1Entry = mutableListOf<Entry>()
        //ロワーバンド2=単純移動平均線-2標準偏差(σ)
        val lowerBand2Entry = mutableListOf<Entry>()

        endPoints.forEachIndexed { index, _ ->
            if (index >= periodCount - 1) {
                //期間中のデータ
                val array = endPoints.slice(index - (periodCount - 1)..index)
                //単純移動平均線用のデータ
                val value = array.reduce { acc, fl -> acc + fl } / periodCount

                ///標準偏差用の計算式
                //期間毎価格の合計の二乗の合計
                val left = array.map { it.pow(2) }.reduce { acc, fl -> acc + fl } * periodCount
                //価格の合計の自乗
                val right = array.reduce { acc, fl -> acc + fl }.pow(2)
                //期間*(期間-1)
                val under = periodCount * (periodCount - 1)
                //標準偏差
                val sigma = sqrt((left - right) / under)
                upperBand2Entry.add(Entry(index.toFloat() + 0.5f, value + (sigma * 2)))
                upperBand1Entry.add(Entry(index.toFloat() + 0.5f, value + (sigma)))
                midBandEntry.add(Entry(index.toFloat() + 0.5f, value))
                lowerBand1Entry.add(Entry(index.toFloat(), value - (sigma)))
                lowerBand2Entry.add(Entry(index.toFloat(), value - (sigma * 2)))
            }
        }
        return LineData(
            listOf(
                createLineDataSet(upperBand2Entry, "upper2", Color.RED),
                createLineDataSet(upperBand1Entry, "upper1", Color.BLUE),
                createLineDataSet(midBandEntry, "mid", Color.BLACK),
                createLineDataSet(lowerBand1Entry, "lower1", Color.BLUE),
                createLineDataSet(lowerBand2Entry, "lower2", Color.RED)
            )
        )
    }

CombinedChartに描画

ローソク足の個数はとりあえず200で描画しています。

    private var mTypeface = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL)
    private var endPoints = mutableListOf<Float>()
    private val chartDataCount = 200
    //幾つのデータの平均値を取得するか
    private val periodCount = 20

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val combineData = CombinedData()
        combineData.setData(generateCandleData(chartDataCount, 100f))
        combineData.setData(bolingerBandData(endPoints))
        setupCombinedChart(combinedChart, combineData)

    }

さいごに

AndroidStudioのcommand + option + l がとても便利ですね。ブログではコード量少ないため気にならないですが、参照されていない変数がわかるのも気に入っております。XCodeが一番使っているIDEなので使い慣れていますが、AndroidStudioも使いこなして生産性あげて行きたいです。

おすすめ書籍

suzuki

シェア
執筆者:
suzuki
タグ: Kotlin

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前