证券投资书中对K线分了12种,对于输入的股票开盘,收盘,最高,最低好像不太适合完全套用,毕竟不是机器说了算,也是人为分的,总觉得不靠谱(一个屌丝程序员中的毒^_^)。所以还是想要让机器自己判断。
之前一直用scikit-learn直接实现,最近一个前端的朋友也想研究,就用javascript帮忙写了一下,算是记录一下心得吧。
首先介绍一下K均值聚类算法的原理吧。摘要一下百度百科:K均值聚类算法是先随机选取K个对象作为初始的聚类中心。然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。一旦全部对象都被分配了,每个聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。
按照百度百科的描述,很容易整理出流程:
        创建K个点作为初始中心;
        判断是否满足分类结果
                遍历数据集中每个数据点
                {
                            计算数据点到每个中心的距离;
                            分配数据点到最近中心所定义的簇
                }
                计算每个簇中所有点的均值并使其作为该簇新的中心
        满足分类结果输出结果

最重要的是计算两个点的距离,这里直接使用欧式距离作为距离函数,实现代码:

function distEclud(vecA,vecB){
    var sum = 0;
    for(var i = 0;i < vecA.length;i++){
        var deta = vecA[i] - vecB[i];
        sum = sum + deta * deta;
    }
    return Math.sqrt(sum);
}

其次就是随机产生K个初始中心:

function randCent(dataSet,k){
    //var n = dataSet[0].length;
    //var minJ = $M.min(dataSet,1);
    //var rangeJ = $V.sub($M.max(dataSet,1),minJ);
    var centroids = []
    for(var i = 0;i < k;i++){
        //centroids.push($V.add(minJ,$V.mul(rangeJ,$V.rand(n))));
        centroids.push(dataSet[i]);
    }
    return centroids;
}

上面非注释代码是将数据集的K个数据点作为中心数据,也可以采用注释的代码,注释代码产生K个随机数据点,$M.min函数计算矩阵(二维数组)列最小值,$M.max计算最大值,$V.add,$V.mul分别计算数组的加与乘,$V.rand生成n维随机数组。

接下来就是对数据集进行聚类,实现代码如下:

function kMeans(dataSet,k){
    var m = dataSet.length;
    var clusterAssment0 = [];
    var clusterAssment1 = [];
    for(var i = 0;i < m;i++){
        clusterAssment0.push(0);
        clusterAssment1.push(1);
    }
    var centroids = randCent(dataSet,k);
    var clusterChanged = true;
    while(clusterChanged){
        clusterChanged = false;

        for(var i = 0;i < m;i++){
            var minDist = 10000000;minIndex = -1;
            for(var j = 0;j < k;j++){
                var distJI = distEclud(centroids[j],dataSet[i]);

                if(distJI < minDist){
                    minDist = distJI;
                    minIndex = j;
                }
            }
            if(clusterAssment0[i] != minIndex){
                clusterChanged = true;
            }
            clusterAssment0[i] = minIndex;
            clusterAssment1[i] = minDist * minDist;
        }
        for(var i = 0;i < k;i++){
            var ptsInClust = $M.subm(dataSet,$V.where(clusterAssment0,"==",i),0);
            if(ptsInClust.length == 0){
                continue;
            }
            centroids[i] = $M.mean(ptsInClust,1);
        }
    }
    return {
        centroids:centroids,
        cluster:clusterAssment0
    };

}

结果返回聚类中心与聚类结果。
以上K均值聚类的代码已经实现。
接下来就是使用K均值聚类算法应用到股票数据,股票数据我们使用腾讯数据,我们采用2017年浦发银行的日K作为数据集合,代码:
&lt;script src = "http://data.gtimg.cn/flashdata/hushen/daily/17/sh600000.js"&gt;&lt;/script&gt;
对数据的解析代码如下:

var ev_data = daily_data_17.split("\n");
var open = [];
var high = [], low = [],close = [],volume = [],date = [];bar = [];
for(var i = 1;i < ev_data.length - 1;i++){
    var es = ev_data[i].split(" ");
    date.push(es[0]);
    open.push(es[1]);
    close.push(es[2]);
    high.push(es[3]);
    low.push(es[4]);
    volume.push(es[5]);
    bar.push([es[1],es[2],es[4],es[3],es[5]]);
}

为了计算K线的形态,股价的大小不能作为特征值的大小,开盘、收盘等之间存在关联,所以我们要对各个数据进行整理,下面是我对数据处理的方式:

var data = [];
for(var i = 1;i < close.length;i++){
    var mean = (parseFloat(close[i]) + parseFloat(open[i]) + parseFloat(high[i]) + parseFloat(low[i]))/4;
    var tmp = [((parseFloat(high[i]) - mean) == 0)?1:(parseFloat(close[i]) - mean)/(parseFloat(high[i]) - mean),
                ((parseFloat(high[i]) - mean) == 0)?1:(parseFloat(low[i]) - mean)/(parseFloat(high[i]) - mean),
                ((parseFloat(high[i]) - mean) == 0)?1:(parseFloat(open[i]) - mean)/(parseFloat(high[i]) - mean),
                (volume[i] - volume[i - 1])/volume[i - 1]
            ]
    data.push(tmp);
}
var max = $M.max(data,1);
var min = $M.min(data,1);
var tz = $M.div_vector($M.sub_vector(data,min,1),$V.sub(max,min),1);

将tz作为数据集输入K均值聚类模型,并画出K线图和分类图:
var result = kMeans(tz,12);
聚类结果如下:
0:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11: