美文网首页Android开发Android DetailAndroid 技术开发
Android原生股票图-K线图讲解和绘制(一)

Android原生股票图-K线图讲解和绘制(一)

作者: 涛涛123759 | 来源:发表于2019-01-02 11:31 被阅读6次

    Android原生股票图-分时图讲解和绘制(一)
    Android原生股票图-分时图讲解和绘制(二)
    Android原生股票图-K线图讲解和绘制(一)

    一、简介:

    K线图

    K线又称阴阳线、棒线、红黑线或蜡烛线,起原于日本德川幕府时代(1603-1867)的米市交易,经过200多年的演进,形成了现在具有完整形式和分析理论的一种技术分析方法。下面先介绍以下柱状图表示的含义:

    • 根据当日的开盘价,收盘价,最高价,最低价四项数据,可以绘制出以下的柱子:


    1、 阳 K 线–1 代表强升势,2 必需高开盘,3 回折不能超过阳 K 线的 35%。表示收盘价大于开盘价。
    2、绿色阴K线-1代表强跌势,2 必须低开盘,3 回折不能超过阴K线的35%。表示收盘价小于开盘价。
    3、十字星的形成表示强烈的市场,方向的移动或者方向的改变。表示收盘价等于开盘价。
    更多K线图的走势讲解见k线图基础知识k线基本形态分析

    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指标 [2] 是美国股市分析家约翰·布林根据统计学中的标准差原理设计出来的一种非常简单实用的技术分析指标。一般而言,股价的运动总是围绕某一价值中枢(如均线、成本线等)在一定的范围内变动,布林线指标正是在上述条件的基础上,引进了“股价信道”的概念,其认为股价信道的宽窄随着股价波动幅度的大小而变化,而且股价信道又具有变异性,它会随着股价的变化而自动调整。正是由于它具有灵活性、直观性和趋势性的特点,BOLL指标渐渐成为投资者广为应用的市场上热门指标。

    • 在常态范围内,布林线使用的技术和方法
      1、 当股价穿越上限压力线时,卖点信号
      2、当股价穿越下限支撑线时,买点信号
      3、当股价由下向上穿越中界限时,为加码信号
      4、 当股价由上向下穿越中界线时,为卖出信号
    • 计算方法
        中轨线=N日的移动平均线   上轨线=中轨线+两倍的标准差   下轨线=中轨线-两倍的标准差
      日BOLL指标的计算过程
        (1)计算MA   MA=N日内的收盘价之和÷N   
      (2)计算标准差MD   MD=平方根(N-1)日的(C-MA)的两次方之和除以N  
      (3)计算MB、UP、DN线   MB=(N-1)日的MA   UP=MB+k×MD   DN=MB-k×MD   (K为参数,可根据股票的特性来做相应的调整,一般默认为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是随机指标

    随机指标KDJ一般是用于股票分析的统计体系,根据统计学原理,通过一个特定的周期(常为9日、9周等)内出现过的最高价、最低价及最后一个计算周期的收盘价及这三者之间的比例关系,来计算最后一个计算周期的未成熟随机值RSV,然后根据平滑移动平均线的方法来计算K值、D值与J值,并绘成曲线图来研判股票价格走势。

    • 计算方法
      KDJ(9,3,3)
      RSV=(收盘价-最近N个周期最低价)/(最近N个周期最高价-最近N个周期最低价)×100
      k线(白线):RSV的m1个周期移动平均
      D线(黄线):k值的m2个周期移动平均
      J线(蓝线):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线图和其中的各指标的绘制:

    效果图:


    k线图
    • 首先我i们定义一个基类ScrollAndScaleView,使其继承RelativeLayout使其可以在里面封装试图组;实现接口GestureDetector.OnGestureListenerScaleGestureDetector.OnScaleGestureListener实现缩放、滑动和点击事件。
      1、缩放事件:
      我们实现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,然后调用scrollTo(int x, int y)来指定当前位置

      @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线图的效果,当长按时会弹出对话框展示当前点对应的各指标的值;当单指左右滑动时试图跟着左右滑动;当双指进行操作时可控制放大缩小的缩放手势。
    我们通过变量isLongPress来控制长按手势,isClosePress表示是否关闭缩放手势。

        protected boolean isLongPress = false;
        protected boolean isClosePress = true; //关闭长按时间
    

    点击事件和长按事件可以在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;
       }
    

    当长按时,会在onLongPress()方法中触发长安时间,此时我们把标识符onLongPresstrue

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

    4、绘制视图水印
    首先创建Bitmap对象:

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

    然后根据试图的位置来确定水印的位置,进行绘制

    //主视图水印
     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线

    • 坐标的转换, 绘制时我们需要当前点在屏幕中位置,当前点在坐标轴中位置和当前点translateX位置。
      绘制步骤如下:
      1)、view中的x转化为TranslateX:
        public float xToTranslateX(float x) {
            return -mTranslateX + x / mScaleX;
        }
    
    

    2)、translateX转化为view中的x

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

    3)、二分查找当前值的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)、 计算当前的显示区域位置和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 {
                //当最大值和最小值都相等的时候 分别增大最大值和 减小最小值
                mMainMaxValue += Math.abs(mMainMaxValue * 0.05f);
                mMainMinValue -= Math.abs(mMainMinValue * 0.05f);
                if (mMainMaxValue == 0) {
                    mMainMaxValue = 1;
                }
            }
            if (mChildMaxValue == mChildMinValue) {
                //当最大值和最小值都相等的时候 分别增大最大值和 减小最小值
                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);
            //右侧
            if (mShowChildRightYvalue) {
                if (mChildRightMaxValue == mChildRightMinValue) {
                    //当最大值和最小值都相等的时候 分别增大最大值和 减小最小值
                    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();
        }
    

    关于K线和分时绘制过程大体上就是这些,代码还在进一步优化和完善。
    欢迎大家提宝贵意见。如有疑问加QQ:1067899750相互讨论。

    相关文章

      网友评论

        本文标题:Android原生股票图-K线图讲解和绘制(一)

        本文链接:https://www.haomeiwen.com/subject/zwculqtx.html