Android原生股票圖-K線圖講解和繪制(一)

Android原生股票圖-分時圖講解和繪制(一)
Android原生股票圖-分時圖講解和繪制(二)
Android原生股票圖-K線圖講解和繪制(一)

一、簡介:

K線圖

K線又稱陰陽線、棒線、紅黑線或蠟燭線,起原于日本德川幕府時代(1603-1867)的米市交易,經(jīng)過200多年的演進,形成了現(xiàn)在具有完整形式和分析理論的一種技術(shù)分析方法。下面先介紹以下柱狀圖表示的含義:

  • 根據(jù)當(dāng)日的開盤價,收盤價,最高價,最低價四項數(shù)據(jù),可以繪制出以下的柱子:


1、 陽 K 線–1 代表強升勢,2 必需高開盤,3 回折不能超過陽 K 線的 35%。表示收盤價大于開盤價。
2、綠色陰K線-1代表強跌勢,2 必須低開盤,3 回折不能超過陰K線的35%。表示收盤價小于開盤價。
3、十字星的形成表示強烈的市場,方向的移動或者方向的改變。表示收盤價等于開盤價。
更多K線圖的走勢講解見k線圖基礎(chǔ)知識k線基本形態(tài)分析

MA 移動平均線(均線)

移動平均線,英文名稱為Moving Average,簡稱MA,原本意思是移動 平均。由于我們將其制作成線形,所以一般稱為移動平均線,簡稱均線。

  • 計算公式為:
    MA ( 5 ) = ( C1+C2 +C3 +C4 +C5 ) /5
    其中:Cn為第n日收盤價。例如C1,貝U為第1日收盤價。
  • 代碼:
/**
     * 計算ma
     *
     * @param datas
     */
    static void calculateMA(List<KLine> datas) {
        float ma5 = 0;
        float ma10 = 0;
        for (int i = 0; i < datas.size(); i++) {
            KLine point = datas.get(i);
            final float closePrice = point.getClosePrice();
            ma5 += closePrice;
            ma10 += closePrice;
            if (i >= 5) {
                ma5 -= datas.get(i - 5).getClosePrice();
                point.MA5Price = ma5 / 5f;
            } else {
                point.MA5Price = ma5 / (i + 1f);
            }
            if (i >= 10) {
                ma10 -= datas.get(i - 10).getClosePrice();
                point.MA10Price = ma10 / 10f;
            } else {
                point.MA10Price = ma10 / (i + 1f);
            }
        }
    }

BOLL布林線

BOLL指標(biāo) [2] 是美國股市分析家約翰·布林根據(jù)統(tǒng)計學(xué)中的標(biāo)準(zhǔn)差原理設(shè)計出來的一種非常簡單實用的技術(shù)分析指標(biāo)。一般而言,股價的運動總是圍繞某一價值中樞(如均線、成本線等)在一定的范圍內(nèi)變動,布林線指標(biāo)正是在上述條件的基礎(chǔ)上,引進了“股價信道”的概念,其認(rèn)為股價信道的寬窄隨著股價波動幅度的大小而變化,而且股價信道又具有變異性,它會隨著股價的變化而自動調(diào)整。正是由于它具有靈活性、直觀性和趨勢性的特點,BOLL指標(biāo)漸漸成為投資者廣為應(yīng)用的市場上熱門指標(biāo)。

  • 在常態(tài)范圍內(nèi),布林線使用的技術(shù)和方法
    1、 當(dāng)股價穿越上限壓力線時,賣點信號
    2、當(dāng)股價穿越下限支撐線時,買點信號
    3、當(dāng)股價由下向上穿越中界限時,為加碼信號
    4、 當(dāng)股價由上向下穿越中界線時,為賣出信號
  • 計算方法
      中軌線=N日的移動平均線   上軌線=中軌線+兩倍的標(biāo)準(zhǔn)差   下軌線=中軌線-兩倍的標(biāo)準(zhǔn)差
    日BOLL指標(biāo)的計算過程
      (1)計算MA   MA=N日內(nèi)的收盤價之和÷N   
    (2)計算標(biāo)準(zhǔn)差MD   MD=平方根(N-1)日的(C-MA)的兩次方之和除以N  
    (3)計算MB、UP、DN線   MB=(N-1)日的MA   UP=MB+k×MD   DN=MB-k×MD   (K為參數(shù),可根據(jù)股票的特性來做相應(yīng)的調(diào)整,一般默認(rèn)為2, c 為收盤價)
  • 代碼:
  /**
     * 計算 BOLL 需要在計算ma之后進行
     *
     * @param datas
     */
    static void calculateBOLL(List<KLine> datas) {
        for (int i = 0; i < datas.size(); i++) {
            KLine point = datas.get(i);
            final float closePrice = point.getClosePrice();
            if (i == 0) {
                point.mb = closePrice;
                point.up = Float.NaN;
                point.dn = Float.NaN;
            } else {
                int n = 26;//20
                if (i < 26) {
                    n = i + 1;
                }
                float md = 0;
                for (int j = i - n + 1; j <= i; j++) {
                    float c = datas.get(j).getClosePrice();
                    float m = point.getMA26Price();
                    float value = c - m;
                    md += value * value;
                }
                md = md / (n - 1);
                md = (float) Math.sqrt(md);
                point.mb = point.getMA26Price();
                point.up = point.mb + 2f * md;
                point.dn = point.mb - 2f * md;
            }
            XLog.e("boll-mb:",point.mb+"");
        }

    }

KDJ是隨機指標(biāo)

隨機指標(biāo)KDJ一般是用于股票分析的統(tǒng)計體系,根據(jù)統(tǒng)計學(xué)原理,通過一個特定的周期(常為9日、9周等)內(nèi)出現(xiàn)過的最高價、最低價及最后一個計算周期的收盤價及這三者之間的比例關(guān)系,來計算最后一個計算周期的未成熟隨機值RSV,然后根據(jù)平滑移動平均線的方法來計算K值、D值與J值,并繪成曲線圖來研判股票價格走勢。

  • 計算方法
    KDJ(9,3,3)
    RSV=(收盤價-最近N個周期最低價)/(最近N個周期最高價-最近N個周期最低價)×100
    k線(白線):RSV的m1個周期移動平均
    D線(黃線):k值的m2個周期移動平均
    J線(藍(lán)線):3×D-2×K
  • 代碼:
 /**
     * 計算kdj
     *
     * @param datas
     */
    static void calculateKDJ(List<KLine> datas) {
        float k = 0;
        float d = 0;

        for (int i = 0; i < datas.size(); i++) {
            KLine point = datas.get(i);
            final float closePrice = point.getClosePrice();
            int startIndex = i - 8;
            if (startIndex < 0) {
                startIndex = 0;
            }
            float max9 = Float.MIN_VALUE;
            float min9 = Float.MAX_VALUE;
            for (int index = startIndex; index <= i; index++) {
                max9 = Math.max(max9, datas.get(index).getHighPrice());
                min9 = Math.min(min9, datas.get(index).getLowPrice());
            }
            float rsv = 100f * (closePrice - min9) / (max9 - min9);
            if (i == 0) {
                k = rsv;
                d = rsv;
            } else {
                k = (rsv + 2f * k) / 3f;
                d = (k + 2f * d) / 3f;
            }
            point.k =Float.isNaN(k)?0:k;
            point.d = Float.isNaN(k)?0:d;
            float valueD=3f * k - 2 * d;
            point.j =Float.isNaN(valueD)?0:valueD;
        }

    }

一、K線圖和其中的各指標(biāo)的繪制:

效果圖:


k線圖
  • 首先我i們定義一個基類ScrollAndScaleView,使其繼承RelativeLayout使其可以在里面封裝試圖組;實現(xiàn)接口GestureDetector.OnGestureListenerScaleGestureDetector.OnScaleGestureListener實現(xiàn)縮放、滑動和點擊事件。
    1、縮放事件:
    我們實現(xiàn)ScaleGestureDetector.OnScaleGestureListener接口中的onScale方法,從中控制最大縮放率mScaleXMax和最小縮放率mScaleXMin
 @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (isClosePress) {
            if (!isScaleEnable()) {
                return false;
            }
            float oldScale = mScaleX;
            mScaleX *= detector.getScaleFactor();
            if (mScaleX < mScaleXMin) {
                mScaleX = mScaleXMin;
            } else if (mScaleX > mScaleXMax) {
                mScaleX = mScaleXMax;
            } else {
                onScaleChanged(mScaleX, oldScale);
            }
            if (mScaleX >= 2.0f || mScaleX <= 0.5f) {
                isScale = true;
            }
        }
        return true;
    }

2、滑動事件
向左或向右滑動K線圖是我們通過setScrollX(int scrollX)來確定我們需要滑動的位置mScrollX

    public void setScrollX(int scrollX) {
        this.mScrollX = scrollX;
        scrollTo(scrollX, 0);
    }

獲取需要滑動的位置mScrollX,然后調(diào)用scrollTo(int x, int y)來指定當(dāng)前位置

  @Override
    public void scrollTo(int x, int y) {
        if (isClosePress) {
            if (!isScrollEnable()) {
                mScroller.forceFinished(true);
                return;
            }
            int oldX = mScrollX;
            mScrollX = x;
            if (mScrollX < getMinScrollX()) {
                mScrollX = getMinScrollX();
                onRightSide();
                mScroller.forceFinished(true);
            } else if (mScrollX > getMaxScrollX()) {
                mScrollX = getMaxScrollX();
                onLeftSide();
                mScroller.forceFinished(true);
            }
            onScrollChanged(mScrollX, 0, oldX, 0);
            invalidate();
        }
    }

3、手勢沖突事件的處理
K線圖的效果,當(dāng)長按時會彈出對話框展示當(dāng)前點對應(yīng)的各指標(biāo)的值;當(dāng)單指左右滑動時試圖跟著左右滑動;當(dāng)雙指進行操作時可控制放大縮小的縮放手勢。
我們通過變量isLongPress來控制長按手勢,isClosePress表示是否關(guān)閉縮放手勢。

    protected boolean isLongPress = false;
    protected boolean isClosePress = true; //關(guān)閉長按時間

點擊事件和長按事件可以在onTouchEvent() 處理:

@Override
   public boolean onTouchEvent(MotionEvent event) {
       switch (event.getAction() & MotionEvent.ACTION_MASK) {
           case MotionEvent.ACTION_DOWN:
               mClickTime = System.currentTimeMillis();
               break;
           case MotionEvent.ACTION_MOVE:
               //一個點的時候滑動
               if (event.getPointerCount() == 1) {
                   //長按之后移動
                   if (isLongPress || !isClosePress) {
                       calculateSelectedX(event.getX());
                       invalidate();
                   }
               }
               break;
           case MotionEvent.ACTION_UP:
               if (!isClosePress) {
                   isLongPress = false;
               }
               invalidate();
               break;
           case MotionEvent.ACTION_CANCEL:
               if (!isClosePress) {
                   isLongPress = false;
               }
               invalidate();
               break;
       }
       this.mDetector.onTouchEvent(event);
       this.mScaleDetector.onTouchEvent(event);
       return true;
   }

當(dāng)長按時,會在onLongPress()方法中觸發(fā)長安時間,此時我們把標(biāo)識符onLongPresstrue

    @Override
    public void onLongPress(MotionEvent e) {
        isLongPress = true;
        isClosePress = false;
    }

4、繪制視圖水印
首先創(chuàng)建Bitmap對象:

private Bitmap mBitmapLogo = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_app_logo);

然后根據(jù)試圖的位置來確定水印的位置,進行繪制

//主視圖水印
 public void drawMainViewLogo(Canvas canvas) {
        if (mBitmapLogo != null) {
            int mLeft = getWidth() / 2 - mBitmapLogo.getWidth() / 2;
            if (!showBottomView) {
                mTopPadding = 0;
                maTextHeight=0;
            }
            int mTop = (mTopPadding + mMainHeight + maTextHeight) / 2 - mBitmapLogo.getHeight() / 2;
            canvas.drawBitmap(mBitmapLogo, mLeft, mTop, null);
        }
    }
//子試圖水印
 public void drawChildViewLogo(Canvas canvas) {
        if (mBitmapLogo != null) {
            int mLeft = getWidth() / 2 - mBitmapLogo.getWidth() / 2;
            int mTop = mTopPadding + mMainHeight + mMainChildSpace + (mChildHeight / 2) - mBitmapLogo.getHeight() / 2;
            canvas.drawBitmap(mBitmapLogo, mLeft, mTop, null);
        }
    }

5、繪制K線

  • 坐標(biāo)的轉(zhuǎn)換, 繪制時我們需要當(dāng)前點在屏幕中位置,當(dāng)前點在坐標(biāo)軸中位置和當(dāng)前點translateX位置。
    繪制步驟如下:
    1)、view中的x轉(zhuǎn)化為TranslateX:
    public float xToTranslateX(float x) {
        return -mTranslateX + x / mScaleX;
    }

2)、translateX轉(zhuǎn)化為view中的x

  public float translateXtoX(float translateX) {
        return (translateX + mTranslateX) * mScaleX;
    }

3)、二分查找當(dāng)前值的index

 public int indexOfTranslateX(float translateX, int start, int end) {
        if (end == start) {
            return start;
        }
        if (end - start == 1) {
            float startValue = getX(start);
            float endValue = getX(end);
            return Math.abs(translateX - startValue) < Math.abs(translateX - endValue) ? start : end;
        }
        int mid = start + (end - start) / 2;
        float midValue = getX(mid);
        if (translateX < midValue) {
            return indexOfTranslateX(translateX, start, mid);
        } else if (translateX > midValue) {
            return indexOfTranslateX(translateX, mid, end);
        } else {
            return mid;
        }
    }

4)、 計算當(dāng)前的顯示區(qū)域位置和X、Y軸的單位長度

 private void calculateValue() {
        if (!isLongPress()) {
            mSelectedIndex = -1;
        }
        mMainMaxValue = Float.MIN_VALUE;
        mMainMinValue = Float.MAX_VALUE;
        mChildMaxValue = Float.MIN_VALUE;
        mChildMinValue = Float.MAX_VALUE;

        mChildRightMaxValue = Float.MIN_VALUE;
        mChildRightMinValue = Float.MAX_VALUE;

        mStartIndex = indexOfTranslateX(xToTranslateX(0));
        mStopIndex = indexOfTranslateX(xToTranslateX(mWidth));
        for (int i = mStartIndex; i <= mStopIndex; i++) {
            IKLine point = (IKLine) getItem(i);
            if (mMainDraw != null) {
                mMainMaxValue = Float.parseFloat(formatValue(Math.max(mMainMaxValue, mMainDraw.getMaxValue(point))));
                mMainMinValue = Float.parseFloat(formatValue(Math.min(mMainMinValue, mMainDraw.getMinValue(point))));
            }
            if (mChildDraw != null) {
                mChildMaxValue = Float.parseFloat(formatValue(Math.max(mChildMaxValue, mChildDraw.getMaxValue(point))));
                mChildMinValue = Float.parseFloat(formatValue(Math.min(mChildMinValue, mChildDraw.getMinValue(point))));
                if (mShowChildRightYvalue) {//子視圖右邊Y軸最值
                    mChildRightMaxValue = Float.parseFloat(formatValue(Math.max(mChildRightMaxValue, mChildDraw.getRightMaxValue(point))));
                    mChildRightMinValue = Float.parseFloat(formatValue((Math.min(mChildRightMinValue, mChildDraw.getRightMinValue(point)))));
                }
            }
        }
        //最大值和最小值不相等時
        if (mMainMaxValue != mMainMinValue) {
            float padding = (mMainMaxValue - mMainMinValue) * 0.05f;
            mMainMaxValue += padding;
            mMainMinValue -= padding;
        } else {
            //當(dāng)最大值和最小值都相等的時候 分別增大最大值和 減小最小值
            mMainMaxValue += Math.abs(mMainMaxValue * 0.05f);
            mMainMinValue -= Math.abs(mMainMinValue * 0.05f);
            if (mMainMaxValue == 0) {
                mMainMaxValue = 1;
            }
        }
        if (mChildMaxValue == mChildMinValue) {
            //當(dāng)最大值和最小值都相等的時候 分別增大最大值和 減小最小值
            mChildMaxValue += Math.abs(mChildMaxValue * 0.05f);
            mChildMinValue -= Math.abs(mChildMinValue * 0.05f);
            if (mChildMaxValue == 0) {
                mChildMaxValue = 1;
            }
        }
        mMainScaleY = mMainRect.height() * 1f / (mMainMaxValue - mMainMinValue);
        mChildScaleY = mChildRect.height() * 1f / (mChildMaxValue - mChildMinValue);
        //右側(cè)
        if (mShowChildRightYvalue) {
            if (mChildRightMaxValue == mChildRightMinValue) {
                //當(dāng)最大值和最小值都相等的時候 分別增大最大值和 減小最小值
                mChildRightMaxValue += Math.abs(mChildRightMaxValue * 0.05f);
                mChildRightMinValue -= Math.abs(mChildRightMinValue * 0.05f);
                if (mChildRightMaxValue == 0) {
                    mChildRightMaxValue = 1;
                }
            }
            mChildRightScaleY = mChildRect.height() * 1f / (mChildRightMaxValue - mChildRightMinValue);
        }

        if (mAnimator.isRunning()) {
            float value = (float) mAnimator.getAnimatedValue();
            mStopIndex = mStartIndex + Math.round(value * (mStopIndex - mStartIndex));
        }
    }

5)、繪制試圖

private void drawK(Canvas canvas) {
        //保存之前的平移,縮放
        canvas.save();
        canvas.translate(mTranslateX * mScaleX, 0);
        canvas.scale(mScaleX, 1);

        mMaxValue = ((ICandle) getItem(mStartIndex)).getHighPrice();
        mMinValue = ((ICandle) getItem(mStartIndex)).getLowPrice();

        for (int i = mStartIndex; i <= mStopIndex; i++) {
            Object currentPoint = getItem(i);
            float currentPointX = getX(i);
            Object lastPoint = i == 0 ? currentPoint : getItem(i - 1);
            float lastX = i == 0 ? currentPointX : getX(i - 1);
            if (mMainDraw != null) {
                if (mMaxValue < ((ICandle) getItem(i)).getHighPrice()) {
                    mMaxValue = ((ICandle) getItem(i)).getHighPrice();
                    mMaxPoint = (ICandle) getItem(i);
                    mMaxX = currentPointX;
                } else if (mMinValue >= ((ICandle) getItem(i)).getLowPrice()) {
                    mMinValue = ((ICandle) getItem(i)).getLowPrice();
                    mMinPoint = (ICandle) getItem(i);
                    mMinX = currentPointX;
                }
                mMainDraw.drawTranslated(lastPoint, currentPoint, lastX, currentPointX, canvas, this, i);
            }
            if (mChildDraw != null) {
                mChildDraw.drawTranslated(lastPoint, currentPoint, lastX, currentPointX, canvas, this, i);
            }

        }
        if (mMainDraw != null && mMinPoint != null && mMaxPoint != null) {
            mMainDraw.drawMaxAndMin(this, canvas, mMaxX, mMinX, mMaxPoint, mMinPoint);
        }

        //畫選擇線
        if (isLongPress || !isClosePress) {
            IKLine point = (IKLine) getItem(mSelectedIndex);
            if (point == null) {
                return;
            }
            float x = getX(mSelectedIndex);
            float y = getMainY(point.getClosePrice());

            mSelectedLinePaint.setColor(ContextCompat.getColor(getContext(), R.color.chart_press_xian));//長按時線條顯示文字的顏色
            canvas.drawLine(x, mMainRect.top, x, mChildRect.bottom, mSelectedLinePaint);
            canvas.drawLine(-mTranslateX, y, -mTranslateX + mWidth / mScaleX, y, mSelectedLinePaint);//隱藏橫線
        }
        //還原 平移縮放
        canvas.restore();
    }

6)、橫豎屏切換處理

 @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {//橫屏
            mMainDraw.setScreenStatus(false);
            paddingTopMA = mMainDraw.getLineFeed() ? DensityUtil.dp2px(30) : paddingTopBoll;
            Log.e("橫屏:---------------", "" + mMainDraw.getLineFeed() + paddingTopMA);
            setTopPadding(mShowMA ? paddingTopMA : paddingTopBoll);
        } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {//豎屏
            mMainDraw.setScreenStatus(true);
            paddingTopMA = DensityUtil.dp2px(30);
            Log.e("橫屏:---------------", "" + paddingTopMA);
            setTopPadding(mShowMA ? paddingTopMA : paddingTopBoll);
        }
        invalidate();
    }

7)、釋放內(nèi)存

   //釋放內(nèi)存
    public void releaseMemory(){
        if (mBitmapLogo != null){
            if (!mBitmapLogo.isRecycled()){
                mBitmapLogo.recycle();
                mBitmapLogo = null;
            }
        }
    }

關(guān)于K線和分時繪制過程大體上就是這些,代碼還在進一步優(yōu)化和完善。
歡迎大家提寶貴意見。
源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • BIAS 乖離率 移動平均線代表投資人的平均成本,而乖離率則可代表投資人的平均報酬率。乖離率的應(yīng)用,回檔或反彈行情...
    Mr_Xiao閱讀 1,834評論 0 9
  • 我們分析股票時通常會在行情軟件上觀察股票的走勢圖,通過走勢圖的技術(shù)指標(biāo)來判斷未來股價的變動方向。對于中小股民來說,...
    愛修仙的道友閱讀 4,430評論 0 2
  • 股本指標(biāo) 總股本 在指定日期,公司已發(fā)行的普通股股份總數(shù)(不含優(yōu)先股)。 A股 流通A股 在指定日期,公司已發(fā)行的...
    古月白毛小狐貍閱讀 4,896評論 1 2
  • 時代的列車轟轟地往前開。我們坐在車上,經(jīng)過的也許不過是幾條熟悉的街衢,可是在漫天的火光中也有驚心動魄。就可惜我們只...
    曹輝_凌波仙子閱讀 135評論 0 0
  • Description 古希臘有個關(guān)于西西弗斯的神話:西西弗斯被眾神判決推運一塊石頭至山頂。由于巨石本身的重量,它...
    小路子好閱讀 590評論 0 0