證券投資書中對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作爲數據集合,代碼:<script src = "http://data.gtimg.cn/flashdata/hushen/daily/17/sh600000.js"></script>
對數據的解析代碼如下:
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: