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;
}
}
-
MACD和CJL見分時圖
Android原生股票圖-分時圖講解(一)
一、K線圖和其中的各指標(biāo)的繪制:
效果圖:
- 首先我i們定義一個基類ScrollAndScaleView,使其繼承RelativeLayout使其可以在里面封裝試圖組;實現(xiàn)接口GestureDetector.OnGestureListener 和ScaleGestureDetector.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)識符onLongPress置true
@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)化和完善。
歡迎大家提寶貴意見。
源碼