K线技术指标实现详解—筹码分布

概念介绍

筹码分布是主流炒股软件中必不可少的一项技术指标。筹码分布表明了对应K线下的股民持仓价格分布情况,能让投资者据以判断个股的未来走势(压力位和支撑位)。下图展示了筹码分布图通常的展示形式:

在这幅图中我们可以看到当前股票的大致筹码分布情况:图中红色筹码为获利盘,绿色筹码为亏损盘,最大部分的筹码集中在42.21-44.74价格区间内。此时获利的股民很多,因此这部分持仓者很有可能卖出其手中的股票从而形成空方力量,从K线图中也可看出此时的股价已经到达了一个新高,随时可能下跌。
对于筹码分布技术指标的实现,理论上有两种实现方式,而实际产品中使用的实现方式往往是大家意想不到的方式。

实现方式一:

这也是大家心目中的筹码分布“理应如此”的实现形式:后台将每个交易日的分笔成交存入后台,然后在界面展示不同日期的筹码分布的时候将对应周期(此处的周期往往是一个流通股本范围,例如30个交易日)内的数据从数据库中读出,前端界面再将这些分笔数据按照价格区间进行排列展示。
很遗憾,对于这种实现方式,市面上真正这么做的产品应该是没有的。理由很简单:第一,存储数据量巨大的每日分笔成交是一个很高的成本(而且本身每日分笔数据也不可能100%精确获得);第二,以这种方式实现的筹码分布将会导致客户端网络流量的大量增加。由于个股一定周期内的分笔成交数据是一个相当庞大的数据量,一次请求就可能传输几十K甚至更多的数据量。第三,Level2收费成本,政策因素。基于以上几点,现实中很少有产品采用这种实现方式。

一个流通股本范围的周期是多少?
在计算筹码分布时我们知道两个数据:一个是个股每天都会产生的一个当日成交量;另一个是个股的流通股本。当我们从当前日向前累加成交量,直到累加的成交量约等于该个股的流通股本,此时累加经过的天数就称为“一个流通股本范围的周期”。例如对于600570.SS个股在2017年8月上旬左右的成交活跃度情况,日成交量累加到其流通股本大约需要35-45个交易日,因此对于该股,我们计算筹码分布截取的分笔成交量就要向前取35-45个交易日(具体数值每天都会变)。

实现方式二:

这种实现方式将筹码分布完全视为一个纯粹的“技术指标”来实现(这种算法是一种比较耗时的算法,后文会详细说明该程序的时间复杂度):首先计算出一个流通股本范围的周期,然后将周期内每日成交量按照一定的分布算法(下文中会详细介绍分布算法)均匀分布到当日最低价至最高价的范围内。对周期内的每个交易日进行这个处理之后,我们就得到了一个“计算获得”的每日分笔成交数据。剩下的操作和算法一相同,将这些分笔成交数据按照价格区间进行排列展示即可。下图展示了这种算法的筹码计算形式。

在这种算法实现下,我们一视同仁的认为每天的筹码都按照相同的分布方式分布在当日价格区间内,从而得出一个“计算获得”的每日分笔成交数据。这种实现方式解决了方案一的缺陷,但是带来了另一个问题:这样算出来的筹码分布准吗?答案肯定是不准,但是到底偏差会有多少,我目前无从探究。

单日筹码分布的实现算法
对于单日分布,简单来说有两类分布算法:一种就是直筒分布,另一种是三角形分布,如下图所示。

直筒分布实现简单,三角分布实现相对复杂一些。并且对于三角分布,我们可以想象出无数种三角形来模拟当日筹码的分布(如上图所示,可以任意配置三角形的形状)。不过根据理论分析和实践经验,顶点在股价中点的等腰三角形应该是最接近真实筹码分布的情况。

计算及实现

以下我们以一组编造的K线数据为输入,来完成筹码分布的算法实现(编程使用JavaScript语言实现):

var input,circulationAmount,storage;
var getPosition,calcSeparate,calcSingledayChip,calcDayChip,calcChip;
/** K线数据* 开盘价,收盘价,最低价,最高价,成交量*/
input=[{open:3.89,close:3.89,low:3.86,high:3.93,amount:300},{open:3.88,close:3.85,low:3.81,high:3.89,amount:320},{open:3.85,close:3.91,low:3.82,high:3.95,amount:260},{open:3.89,close:4.02,low:3.89,high:4.07,amount:250},{open:4.04,close:4.05,low:4.00,high:4.08,amount:280},{open:4.05,close:4.00,low:3.98,high:4.08,amount:320},{open:4.00,close:4.00,low:3.97,high:4.04,amount:330},{open:3.99,close:3.90,low:3.88,high:4.00,amount:350},{open:3.89,close:3.90,low:3.88,high:3.92,amount:310},{open:3.89,close:3.98,low:3.88,high:3.98,amount:260},{open:3.99,close:3.98,low:3.95,high:4.03,amount:280},{open:3.98,close:4.06,low:3.96,high:4.08,amount:240},{open:4.08,close:4.08,low:4.02,high:4.08,amount:200}
];
/** 流通股本*/
circulationAmount=3000;
/** 存储计算过程变量* {number} separateAmount:全部数据的分价分隔数* {number} chipMax:一个流通股本内的最高价* {number} chipMin:一个流通股本内的最低价* {number} chipStep:每一级分价价格* {array} chipSeparate:分价价格数组表* {array} chipStorage:单K线筹码分布存储*/
storage={separateAmount:100,chipMax:0,chipMin:0,chipStep:0,chipSeparate:[],chipStorage:[]
};/** 在总分价数组中定位价格索引,二分搜索* @param {array} array 有序数组表* @param {number} num 查询数*/
getPosition=function(arr,num){var i,l;for(i=0,l=arr.length;i<l;i++){if(arr[i]>=num){return i;}}
};/** 计算整个数据段的总分加数组* @param {array} data 输入数据*/
calcSeparate=function(data){var i,l,max,min;i=0;max=min=data[i].high;for(i=1,l=data.length;i<l;i++){if(max<data[i].high){max=data[i].high;}if(min>data[i].low){min=data[i].low;}}storage.chipStep=(max-min)/storage.separateAmount;for(i=0,l=storage.separateAmount;i<=l;i++){storage.chipSeparate[i]=min+i*storage.chipStep;}
};/** 计算一根K线的筹码分布* @param {array} data 输入数据*/
calcSingledayChip=function(data){var i,l,angle,tan,minIndex,maxIndex,midIndex,j,divideAmount,triangleAmount,triangleIndex,triangleCount,price,dayChip;var getTriangleAmount;//三角形分布,angle为底角角度angle=45;tan=Math.tan(Math.PI*angle/180);for(i=0,l=data.length;i<l;i++){minIndex=getPosition(storage.chipSeparate,data[i].low);maxIndex=getPosition(storage.chipSeparate,data[i].high);divideAmount=maxIndex-minIndex;midIndex=divideAmount/2+minIndex;triangleAmount=0;triangleIndex=minIndex;triangleCount=1;/** 计算三角形分布*/getTriangleAmount=function(){//底部到中间while(triangleIndex<=midIndex){triangleAmount+=triangleCount;triangleCount++;triangleIndex++;}//中间到顶部while(triangleIndex<=maxIndex){triangleCount--;triangleAmount+=triangleCount;triangleIndex++;}};getTriangleAmount();//单日分价赋值triangleCount=1;dayChip={};for(j=minIndex;j<midIndex;j++){price=storage.chipSeparate[j];dayChip[price]=data[i].amount*triangleCount/triangleAmount;triangleCount++;}for(;j<=maxIndex;j++){price=storage.chipSeparate[j];dayChip[price]=data[i].amount*triangleCount/triangleAmount;triangleCount--;}storage.chipStorage[i]=dayChip;}
};/** 计算某一日的累计筹码分布* @param {array} data 输入数据* @param {number} index 索引*/
calcDayChip=function(data,index){var i,j,minIndex,dayPrice,chipData,sumAmount;//计算几日对等流通股本,确定起始索引;同时计算流通股本内的最大值最小值sumAmount=0;storage.chipMax=storage.chipMin=data[index].high;for(i=index;i>=0 && sumAmount<circulationAmount;i--){sumAmount+=data[i].amount;if(storage.chipMax<data[i].high){storage.chipMax=data[i].high;}if(storage.chipMin>data[i].low){storage.chipMin=data[i].low;}}i++;//初始化当日的筹码分价区间chipData={};minIndex=getPosition(storage.chipSeparate,storage.chipMin);dayPrice=storage.chipSeparate[minIndex];while(dayPrice<=storage.chipMax){chipData[dayPrice]=0;minIndex++;dayPrice=storage.chipSeparate[minIndex];}//叠加筹码交易量for(;i<=index;i++){for(j in storage.chipStorage[i]){chipData[j]+=storage.chipStorage[i][j];}}return chipData;
};calcChip=function(){calcSeparate(input);calcSingledayChip(input);console.log(calcDayChip(input,input.length-1));
};
calcChip();

在calcChip方法中我们集中调用了所有方法来计算筹码,这里我们以计算输入数据最后一天的筹码为例,最终输出的结果如下图所示:

可以看到,成交量筹码已经按照我们设置好的分价表排列在了各个价格区间,这个数据就是算法计算得出的当日筹码分布情况。下面我们来具体分析下程序的时间复杂度。

时间复杂度讨论

要得到上图所示的输出结果,我们首先需要确定以什么样的精度来细分分价表,此处我们通过calcSeparate方法来计算得出。在calcSeparate方法中我们先得到整个数据的最大值和最小值,然后通过(max-min)/storage. separateAmount得到分价间隔,这里的storage.separateAmount完全使我们自己定义的,对于价格波动较小的个股需要定义的稍小一点,对于价格波动较大的股票则需要定义的大一些,本例中我们定义为100。根据最小值、最大值以及分价间隔,我们得到了总的分价数组storage.chipSeparate。这个方法的时间复杂度是显而易见的,我们仅仅在获得最大值最小值的时候对整个数据进行了一次遍历,而后进行了分价数组的赋值工作(该赋值操作的耗时和storage.separateAmount相关),因此时间复杂度为O(n)。
接下来计算累加筹码之前,我们需要首先按照三角形分布算法计算出输入数据的单日筹码,这里我们通过calcSingledayChip方法来实现。在这个方法中,我们将每日价格区间映射到分价数组中,然后以三角函数将每日的成交量均匀分布到分价数组对应的映射区间上,最终将单日筹码分布的dayChip对象赋给单日筹码存储器storage.chipStorage中,整个for循环走完之后,输入数据的单日筹码也就全部计算完成。在这个方法中,我们用一个大的for循环来遍历输入数据,在循环内部,有两次getPosition的查找操作,一个计算当日三角形形状的getTriangleAmount方法,和两个内部for循环来完成当日筹码的赋值(这两个for循环实际上是对一个完整for循环的拆分),因此理论上比较精确的时间复杂度为O(4n^2)。
最后我们进行的操作是对某日的筹码进行累加,在calcDayChip方法中我们进行了这个操作。首先通过一个循环得出当日对应的流通股本周期以及周期内的最大最小值,之后进行对应这个流通股本价格区间内的分价表初始化操作,最后我们通过一个嵌套循环完成了筹码的累加。对于最后的累加算法,其时间复杂度为O(2n+n^2)。
最终我们得到,整个筹码分布的算法时间复杂度为O(3n+5n^2)。

分价数storage.separateAmount的赋值
经过前文的讨论,我们发现筹码分布算法相当的损耗性能,并且该算法的时间复杂度和分价数storage.separateAmount赋值的大小关系紧密,这个值设得越大,时间复杂度中的减速系数就会越大。因此,尽量找到一个合适的分价数对算法的性能至关重要。本例中对于输入数据3.81-4.08的价格波动我们设置了storage.separateAmount=100的分价数显然是偏大了,各位读者请不要认为这个数字在实践中就是按照这个比例来设置的(因为将这个数设的越大,可以得到更精细的结果)。实际应用中10元的价格波动设置100的分价数就可以得到比较合适的筹码分布结果,如果希望得到更精细的结果可以适当增大这个值。
算法优化
如果认真的阅读了本文所示的源代码,各位读者在读到getPosition和getTriangleAmount这两个方法的时候一定会感到困惑,为什么我将这两个内嵌逻辑提取出来构造了两个独立的方法。因为此处将会提出对这两个方法的算法改进!
对于getPosition的调用,我们知道他被嵌套在一个for循环内,查找方法的O(n)嵌套在遍历方法的O(n)中也就形成了一个O(n^2)的时间消耗,因此我们有理由对这个查找方法进行优化。我们通过calcSeparate得出的分价表本身是一个有序数组,在有序数组中进行查找的最快算法就是二分查找,由此以下给出一个getPosition的二分搜索算法实现:

/** 在总分价数组中定位价格索引,二分搜索* @param {array} array 数组表* @param {number} num 查询数*/
getPosition=function(arr,num){var lower,upper,middle;lower=0;upper=arr.length-1;while(lower<=upper){middle=parseInt((lower+upper)/2);if(arr[middle]<num){lower=middle+1;}else if(arr[middle]>num){upper=middle-1;}else{upper=middle;break;}}return upper;
};

对于getTriangleAmount方法,我们知道这个方法是用来计算三角形分布总量的方法,举个例子这个方法就是一个计算1+2+3…+n+n-1…+3+2+1的过程。我们都知道对于1+2+3…+n可以用首项加末项乘以项数除以二(1+n)*n/2的公式来简化,同理,这里我们也将计算三角形分布总量的方法简化成一个计算三角形(或梯形)面积的问题,以下给出对getTriangleAmount的优化算法:

getTriangleAmount=function(){var a,h;if((maxIndex-minIndex)%2==0){//三角形a=maxIndex-minIndex+2;h=midIndex-minIndex+1;triangleAmount=a*h/2;}else{//梯形a=maxIndex-minIndex+2;h=midIndex-minIndex+1;triangleAmount=(a+1)*h/2;}
};

将这两个方法优化之后,整个程序的性能将减少O(2n^2)的时间复杂度。

技术指标使用建议

注意!
每个技术指标都有很多种使用方法,不同行情下适用情况也不尽相同。笔者在此处只给出自己的一些使用心得,经验不多,见解也不是很深,仅供参考。

正如前文所述,筹码分布代表了当前K线下股民的大致持股分价情况。据此我们有理由相信,大部分股民集中的价格点一定是个股趋势的一个阻力点。
1.大部分筹码价格高于目前的价格:此时意味着大部分持股者为套牢盘,也就是所谓的“空方势力”,当出现这种筹码形态时我们认为该个股的价格上涨会遇到较大阻力。庄家在拉升该股时会浪费更多资金,因此很大可能下会选择“盘震”等手段来洗出套牢盘,而后再进行拉升。
2.大部分筹码价格低于目前的价格:此时意味着大部分持股者已经获利不小,随时有可能甩盘从而引起个股价格下跌。所谓高处不胜寒,对于个股价格远高于筹码分布主要集中区的个股,很大情况下后续就会出现价格下跌。
3.大部分筹码集中在目前价格附近:对于这种情况不能妄下定论,如果个股处于上行通道并且在之后的交易日内突破了大部分筹码集中区,那么剩下的股价拉升会非常顺利,并且阻力较小,直至股价高出太多导致持股者想出手获利;如果个股处于下行通道并且即将跌破大部分筹码集中区,那往往在跌破之后会一如倾泻之势,因为跌穿之后,许多亏损的股民会选择不止损套牢,从而多方的力量也就随之消失,价格一跌再跌毫无抵抗。

熬夜不易,请作者喝杯酒!

K线技术指标实现详解—筹码分布相关推荐

  1. 怎么看k线图_详解下降三角形

    在黄金白银的价格走势K线图中,下降三角形并不罕见,不过大家是否了解它的技术要点和市场含义呢? 下降三角形的形状的上升三角形恰好相反.贵金属价格在某一水平价位处出获得了一定的买盘支持,因此每回落到该水平 ...

  2. 指标详解(3)-- K线九转指标详解

    一.定义:九转序列是根据TD马克序列的思想产生的趋势叠加反弹指标,因徐小明的交易师而成为股海中的"网红". 二.实现图解:(来源于网络) 下九转形态: 上九转形态: 三.运用技巧: ...

  3. DDA画线算法+代码详解-直线扫描算法之一

    #DDA画线算法+代码详解-直线扫描算法之一 本文目录结构如下 1.直线扫描算法简介 2.DDA直线扫描算法 2.1 公式推理 1.求斜率K: 2.当|K| <= 1 时 3.当|K| > ...

  4. 机箱主板跳线接法详解(图) (机箱面板的POWER LED线,POWER SW线,HD线,RESET线,usb线)

    机箱主板跳线接法详解(图) 作为一名新手,要真正从头组装好自己的电脑并不容易,也许你知道CPU应该插哪儿,内存应该插哪儿,但遇到一排排复杂跳线的时候,很多新手都不知道如何下手. 钥匙开机其实并不神秘 ...

  5. Java多线程之volatile详解

    Java多线程之volatile详解 目录: 什么是volatile? JMM内存模型之可见性 volatile三大特性之一:保证可见性 volatile三大特性之二:不保证原子性 volatile三 ...

  6. python移动平均线绘图_对python pandas 画移动平均线的方法详解

    数据文件 66001_.txt 内容格式: date,jz0,jz1,jz2,jz3,jz4,jz5 2012-12-28,0.9326,0.8835,1.0289,1.0027,1.1067,1.0 ...

  7. ftm模块linux驱动,飞思卡尔k系列_ftm模块详解.doc

    飞思卡尔k系列_ftm模块详解 1.5FTM模块1.5.1 FTM模块简介FTM模块是一个多功能定时器模块,主要功能有,PWM输出.输入捕捉.输出比较.定时中断.脉冲加减计数.脉冲周期脉宽测量.在K1 ...

  8. K线技术指标实现—同花顺多空趋势点

    K线技术指标实现-同花顺多空趋势点 概念介绍 当多空趋势发出多点信号时,表示行情看多,可积极做多,多点为最早的买点:当多空趋势发出空点信号时,表示行情看空,可持币观望等待机会,空点为最晚的卖点. 多空 ...

  9. 多线程之callable详解

    多线程之callable详解 面试有人会问:线程的实现方式有几种? 很多人可能回答:2种,继承Thread类,实现Runnable接口. 很多忽略了callable这种方式. 也许有人知道callab ...

最新文章

  1. 你需要知道的缓存击穿/穿透/雪崩
  2. XDP/eBPF — 基于 eBPF 的 Linux Kernel 可观测性
  3. 【转】强大的B树B+树
  4. python拼接mysql时遇到unsupported format character ‘?‘ “(0x82f1)“
  5. 写最少的代码,避免给自己找麻烦
  6. spring—拦截器和异常
  7. hdu 1233 最小生成树
  8. python方向键键值_python字典键值对的添加和遍历方法
  9. python遍历集合_Python 高效遍历 集合所有子集的全组合
  10. 初学者python笔记(迭代器、生成器、三元表达式、列表解析、send()与yield())
  11. python让繁琐工作自动化 第12章 web页面抓取
  12. paip.XXListener is already configured监听器已经被配置的解决
  13. 第一章 批判性思维概念
  14. 天池-小布助手对话短文本语义匹配 复赛rank3、决赛rank4代码及解决方案
  15. 深圳学校积分计算机,深圳市龙岗区小学积分入学排行榜
  16. 群晖网络不通_网络菜鸟入手Synology群晖 DS218+,求问网络安装环境几个问题?
  17. 堆排序、归并排序、快速排序
  18. hall 状态下,禁用指纹解锁
  19. 浅谈Robots.txt文件给网站带来的好处
  20. Redis6在Liunx系统下的安装、启动和关闭

热门文章

  1. 让 AirDrop 支持有线传输,甚至让不支持 AirDrop 的 Mac 也能使用该功能 黑苹果也可以的哦
  2. 如何编写优质嵌入式C程序
  3. Vue 基础 (二)
  4. 合天实验室:渗透测试项目一
  5. 【会议征稿|SPIE独立出版|往届已检索】第二届人工智能、虚拟现实与可视化国际学术会议(AIVRV 2022)
  6. java 封装 setter getter
  7. 爱回收递交招股书背后,“买旧不买新”的市场价值如何看待?
  8. 大连理工优化方法matlab,大连理工大学庞丽萍最优化方法matlab程序.doc
  9. 如何判断一个String字符串不为空或这不为空字符串
  10. 基于51单片机的电梯控制器