算法-基于MACD的Adaboost股价涨跌预测模型

   郑重声明:股市有风险,投资需谨慎,利用本模型实盘请自行承担风险



MACD是一个指标,具体用法是MACD>0看涨,反之看跌,果真是这样的吗?由于所有的技术指标都基于对历史数据的统计,指标的滞后性也就难免,有时候MACD明明大于0,股价仍然跌,有时候macd小于0,股价仍然涨。 本文基于Adaboost算法提出了一种基于macd的线性阀值分类器作为若分类器,通过在限定的解空间内寻找使得错误率最小的阀值和偏置,这样做避免了训练若分类器的复杂性,经验证,这种方法有效。

  

    关于什么是adaboost算法,这里还需要简单说一下。算法的核心思想是,用大量的若分类器进行决策,采用一定的方法对若分类器的结果进行加权,从而得出一个具有强分类能力的分类器。值得注意的是,在计算若分类器的错误率e的时候,应该针对每个类别分别计算错误率,多个类别的错误率,这里是两类,涨和跌,分别用1和-1表示。每个类别的错误率同时<0.5,这样的弱分类器才合格。好了闲话少说,一下程序完全是Java写的,并没有做什么优化,先跑出结果再说。


首先我们需要一个弱分类器,定义如下:

package implementation;

import java.util.List;

public /**
 * 你可以根据自己的需要,继承这个类,实现自己的弱分类器,只需要实现两个方法即可
 * @author zhangshiming
 */
abstract class WeakClassifier{
	public static final int RIGHT = 1;
	public static final int WRONG = 0;
	public double weight;//alpha
	public final double calculateErrorPositive(double[][] inputX, double[] inputY, int[] rightOrWrong){
		double errorTimes = 0;//预测错误的次数
		double pnum = 0;
		for(int i = 0; i < inputX.length; i++){
			if(inputY[i] == 1){
				pnum++;
				int res = predict(inputX[i], inputY[i]);
				if(res == WRONG){
					errorTimes++;
				}
				rightOrWrong[i] = res;
			}
		}

		return errorTimes / pnum;//错误率
	}

	public final double calculateErrorNegative(double[][] inputX, double[] inputY, int[] rightOrWrong){
		double errorTimes = 0;//预测错误的次数
		double nnum = 0;
		for(int i = 0; i < inputX.length; i++){
			if(inputY[i] == -1){
				nnum++;
				int res = predict(inputX[i], inputY[i]);
				if(res == WRONG){
					errorTimes++;
				}
				rightOrWrong[i] = res;
			}
			
		}

		return errorTimes / nnum;//错误率
	}
	
	public final double calculateError(double[][] inputX, double[] inputY, int[] rightOrWrong){
		double errorTimes = 0;//预测错误的次数
		for(int i = 0; i < inputX.length; i++){
			int res = predict(inputX[i], inputY[i]);
			if(res == WRONG){
				errorTimes++;
			}
			rightOrWrong[i] = res;
		}

		return errorTimes / inputY.length;//错误率
	}
	public final double calculateIR(double[][] inputX, double[] inputY, int[] rightOrWrong,List<double[]> irlist){
		double sumIr = 0;
		for(int i = 0; i < inputX.length; i++){
			if(inputY[i] > 0 && rightOrWrong[i] == WeakClassifier.RIGHT){
				sumIr += irlist.get(i)[0];
			}
			
			if(inputY[i] < 0 && rightOrWrong[i] == WeakClassifier.WRONG){
				sumIr += irlist.get(i)[0];
			}
		}

		return sumIr;
	}
	
	
	
	//预测正确返回RIGHT,错误返回WRONG
	public final int predict(double[] x, double y){
		double res = predict(x);
		
		//System.out.println(res);s

		if(res == y){
			return RIGHT;
		}else{
			return WRONG;
		}
	}

	public abstract double predict(double[] x);

	public abstract void train(double[][] inputX, double[] inputY, double[] weights);

}


扫描二维码关注公众号,回复: 1880135 查看本文章

这个弱分类器是个抽象类,由你负责实现训练和预测两个抽象方法。如果你了解训练,那么参数是很容易理解的。


接下来我们继承这个类来实现我们的阀值分类器:


package implementation;

class ThreadHoldWeakClassifier extends WeakClassifier{
	public double mThreadHold;
	public double mBias;
	private static final int maxBias = 20;
	private static final int minBias = -maxBias;
	private static double lastThreadHold = -10000;
	private static double lastBias = -10000;
	@Override
	public double predict(double[] x) {
		if(mThreadHold * x[0] + mBias >= 0){
			return 1;
		}else{
			return -1;
		}
	}

	@Override
	public String toString() {
		return "bias=" + mBias + " threadHold=" + mThreadHold;
	}

	@Override
	public void train(double[][] inputX, double[] inputY, double[] weights) {
		double max = 0, min = 0;
		double step = 0.1;
		// find max min
		for(int i = 0; i < inputX.length; i++){
			double val = inputX[i][0];
			if(val > max){
				max = val;
			}
			if(val < min){
				min = val;
			}
		}
		//test threadHold
		if(lastThreadHold == -10000){
			lastThreadHold = min + step;
			lastBias = minBias;
		}else{
			if(lastBias == -10000){
				lastBias = minBias;
			}else{
				lastBias += 0.1;
				if(lastBias > maxBias){
					lastBias = minBias;
					lastThreadHold += step;
				}
			}
			
		}
		mThreadHold = lastThreadHold;
		mBias = lastBias;
		//System.out.println("bias = " + mBias + " threashHold = " + mThreadHold);
	}
	
}

实现非常简单,就是每次训练都生成一个阀值和偏置,根据给定的范围用step步长进行穷举,当然你也可以用线性回归或者其他的方法来生成自己的弱分类器。




然后我们就要实现我们的 Adaboost算法了,这个算法比较长,需要解释一下,看注释吧:


package implementation;

import java.util.ArrayList;
import java.util.List;

public class Adaboost{
	private double[][] mInputX = null;//样本
	private double[] mInputY = null;//样本标签
	private double[] mWeights = null;//样本权重
	private int mSampleNum = -1;
	private List<WeakClassifier> mWeakClassifierSet = new ArrayList<WeakClassifier>();

	
	public Adaboost(){}

	public Adaboost(double[][] X, double[] Y){
		setInput(X, Y);//构造函数,初始化训练样本,和标签1,-1
	}

	
	public Adaboost(double[][] input){
		if(input == null || input.length == 0){
			new RuntimeException("no input data, please check !");
		}
	    final int cols = input[0].length - 1;
		double[][] X = new double[input.length][cols];
		double[] Y = new double[input.length];
		for(int i = 0; i < input.length; i++){
			for(int j = 0; j < input[i].length; j++){
				if(j < input[i].length -1){
					X[i][j] = input[i][j];
				}else{
					Y[i] = input[i][j];
				}
			}
			
		}
		setInput(X, Y);
	}
	public void setInput(double[][] X, double[] Y){
		if(X == null || Y == null){
			throw new RuntimeException(
					"input X or input Y can not be null, please check!");
		}

		if(X.length != Y.length){
			throw new RuntimeException(
					"input X or input Y belongs to different dimension, please check!");
		}

		mInputX = X;
		mInputY = Y;
		mSampleNum = mInputX.length;
		mWeights = new double[mSampleNum];
	}

	private void initWeights(){
		for(int i = 0; i < mSampleNum; i++){
			mWeights[i] = 1.0 / mSampleNum;
		}
	}
	
	public double predict(double[] x){//按照adaboost方法组合多个弱分类器
		double res = 0;
		if(mWeakClassifierSet.size() == 0){
			throw new RuntimeException(
					"no weak classifiers !!");
		}
		for(int i = 0; i < mWeakClassifierSet.size(); i++){
			res += mWeakClassifierSet.get(i).weight *
					mWeakClassifierSet.get(i).predict(x);
		}
		return res;
	}
	
	private void updateWeights(int[] rightOrWrong, double alpha){
		//更新样本权重,被分错的样本总是具有很大的权重,读者可自行根据权重来特殊训练这些容易被分错的样本
		double Z = 0;
		for(int i = 0; i < rightOrWrong.length; i++){
			if(rightOrWrong[i] == WeakClassifier.RIGHT){
				mWeights[i] *= Math.exp(-alpha);
			}else if(rightOrWrong[i] == WeakClassifier.WRONG){
				mWeights[i] *= Math.exp(alpha);
			}else{
				throw new RuntimeException(
						"unknown right or wrong flag, please check!");
			}

			Z += mWeights[i];
		}

		//权重归一化
		for(int i = 0; i < rightOrWrong.length; i++){
			mWeights[i] /= Z;
		}
	}

	
	
	//这个方法是核心,也就是寻找合格的若分类器,并保存在一个List中
	public void trainWeakClassifiers(int epoch,List<double[]> irlist){
		if(epoch <= 1){
			throw new RuntimeException(
					"training epoch must be greater than 1, please check!");
		}

		System.out.println("start training......");
		
		initWeights();//初始化样本权重

		for(int i = 0; i < epoch; i++){
			WeakClassifier weakClassifier = new ThreadHoldWeakClassifier();
			weakClassifier.train(mInputX, mInputY, mWeights);
			int[] rightOrWrong = new int[mSampleNum];// 1 right, 0 wrong
			double errorP = weakClassifier.calculateErrorPositive(mInputX, mInputY,rightOrWrong);//计算正样本错误率
			double errorN = weakClassifier.calculateErrorNegative(mInputX, mInputY,rightOrWrong);//计算负样本错误率
			double error = weakClassifier.calculateError(mInputX, mInputY,rightOrWrong);//计算总体错误率
			double sumIr = weakClassifier.calculateIR(mInputX, mInputY,rightOrWrong,irlist);//计算利润率
			//System.out.println("perror = " + errorP + " nerror = " + errorN + " error = " + error);
			if(errorP > 0.5 || errorN > 0.5){
				continue;//不满足错误率的分类器就抛弃
			}
			
			//if(sumIr <=0){
			//	continue;
			//}
			//System.out.println("perror = " + errorP + " nerror = " + errorN);
			
			double alpha = Math.log((1 - error) / error) / 2;
			weakClassifier.weight = alpha;//保存若分类器的权重
			updateWeights(rightOrWrong, alpha);
			mWeakClassifierSet.add(weakClassifier);
			System.out.println("epoch " + i +
					" got one weak classifier, haha " + weakClassifier.toString() + " error=" + error + " ir=" + sumIr);
		}
		System.out.println("train finish!!  " + mWeakClassifierSet.size() + " weak classifier(s) was trained !!");
//		for(int i = 0; i < mWeakClassifierSet.size(); i++){
//			ThreadHoldWeakClassifier twc = (ThreadHoldWeakClassifier) mWeakClassifierSet.get(i);
//			//System.out.println("thread hold = " + twc.mThreadHold);
//		}
	}
}


训练好后,每个若分类器都有自己的参数,阀值,偏置,权重等,可进行组合。

接着看一下主体代码:

package implementation;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Adaboost的一个实现框架,你可以根据自己的需要,训练自己的弱分类器
 * @author zhangshiming
 * @email [email protected]
 *
 */
public class Main {

	public static void main(String[] args) throws Exception {
		int epoch = 110000;//训练次数
		//初始化训练样本,最后一列为标签
		FileReader fr = new FileReader(new File("c:\\input.txt"));
		BufferedReader bfr = new BufferedReader(fr);
		String linestr = null;
		boolean flag = true;
		
		List<double[]> trainData = new ArrayList<double[]>();
		List<double[]> trainRate = new ArrayList<double[]>();
		List<double[]> testData = new ArrayList<double[]>();
		List<double[]> testRate = new ArrayList<double[]>();
		while((linestr = bfr.readLine()) != null){
			if(linestr.startsWith("test")){
				flag = false;
				continue;
			}
			String[] s = linestr.split("\t");
			double[] darr = new double[2];
			darr[0] = Double.valueOf(s[1]);
			darr[1] = Double.valueOf(s[2]);
			double[] rarr = new double[1];
			rarr[0] = Double.valueOf(s[3]);
			if(flag){
				//读入输入数据
				trainData.add(darr);
				trainRate.add(rarr);
			}else{
				//读入测试数据
				testData.add(darr);
				
				testRate.add(rarr);
			}

		}
		bfr.close();
		final int trainRows = trainData.size();
		final int testRows = testData.size();
		double[][] X = new double[trainRows][trainData.get(0).length];
		double[][] testInput = new double[testRows][testData.get(0).length];
		for(int i = 0; i < trainRows; i++){
			X[i][0] = trainData.get(i)[0];
			X[i][1] = trainData.get(i)[1];
			
		}
		for(int i = 0; i < testRows; i++){
			testInput[i][0] = testData.get(i)[0];
			testInput[i][1] = testData.get(i)[1];
		}
		trainData.clear();
		testData.clear();
		

		if(testInput == null || testInput.length == 0){
			new RuntimeException("no input data, please check !");
		}
	    final int cols = testInput[0].length - 1;
	    double testX[][] = new double[testInput.length][cols];
	    double testY[] = new double[testInput.length];
		for(int i = 0; i < testInput.length; i++){
			for(int j = 0; j < testInput[i].length; j++){
				if(j < testInput[i].length -1){
					testX[i][j] = testInput[i][j];
				}else{
					testY[i] = testInput[i][j];
				}
			}
			
		}


		Adaboost adaboost = new Adaboost(X);
		adaboost.trainWeakClassifiers(epoch, trainRate);
		double testErrorTimes = 0;
		double total = 0;
		double testErrorTimes1 = 0;
		double total1 = 0;
		double ir = 0;
		double ir1 = 0;
		for(int i = 0; i < testX.length; i++){
			double res = adaboost.predict(testX[i]);
			/******************正做*******************/
			if(testY[i] > 0 && res >= 0){
				ir += testRate.get(i)[0];
				System.out.println("正做:" + testRate.get(i)[0]);
				total++;
			}
			
			if(testY[i] < 0 && res >= 0){
				ir += testRate.get(i)[0];
				System.out.println("正做:" + testRate.get(i)[0]);
				testErrorTimes++;
				total++;
			}
			/******************反做*******************/
			if(testY[i] > 0 && res < 0){
				ir1 += testRate.get(i)[0];
				System.out.println("反做:" + testRate.get(i)[0]);
				testErrorTimes1++;
				total1++;
			}
			
			if(testY[i] < 0 && res < 0){
				ir1 += testRate.get(i)[0];
				System.out.println("反做:" + testRate.get(i)[0]);
				total1++;
			}

			System.out.println("在测试数据上的IR=" + ir + "    IR1=" + ir1);
			System.out.println();
		}
		System.out.println();
		System.out.println("在测试数据上的IR=" + ir + " error=" + (testErrorTimes/total*100));
		System.out.println("在测试数据上的IR1=" + ir1 + " error=" + (testErrorTimes1/total1*100));
		
		
		
	}
}


主体代码也是相当的容易理解,那就是读入文本文件的数据,然后调用adaboost开始训练。

这里说一下文本文件数据的格式:

2015/06/11    -16.51    -1    -3.088
2015/06/12    -22.534    1    6.735
2015/06/15    2.576    -1    -7.579
2015/06/16    -21.514    -1    -5.374
2015/06/17    -28.798    1    2.545
2015/06/18    -11.445    -1    -0.939
2015/06/19    -8.888    -1    -2.741
2015/06/23    -11.124    1    0.404
2015/06/24    -3.842    1    6.566
testData
2015/06/25    17.84    -1    -8.508
2015/06/26    -8.278    -1    -11.105
2015/06/29    -27.741    -1    -11.139




testData后的数据会被读入测试样本中,之前的数据用于训练,第一列是日期,第二列是昨天与前天的macd之差,第三列是当天涨跌,1涨,-1跌,第四列是当天涨跌点数



好了,我选就用这只票作为训练,输出的结果大概是这样的:

以下是每一次操作后的利润率,正做反做对比

反做:-0.17
在测试数据上的IR=-90.90899999999996    IR1=5.994999999999985

反做:1.917
在测试数据上的IR=-90.90899999999996    IR1=7.911999999999985

反做:4.077
在测试数据上的IR=-90.90899999999996    IR1=11.988999999999985

反做:-1.956
在测试数据上的IR=-90.90899999999996    IR1=10.032999999999985

反做:-2.08
在测试数据上的IR=-90.90899999999996    IR1=7.952999999999985

反做:-1.093
在测试数据上的IR=-90.90899999999996    IR1=6.859999999999985

反做:4.113
在测试数据上的IR=-90.90899999999996    IR1=10.972999999999985

反做:1.038
在测试数据上的IR=-90.90899999999996    IR1=12.010999999999985

反做:-2.789
在测试数据上的IR=-90.90899999999996    IR1=9.221999999999985

以下是测试数据上总的IR率
在测试数据上的IR=-90.90899999999996 error=54.0
在测试数据上的IR1=9.221999999999985 error=49.24242424242424


IR是正做,IR1是反做,就是如果模型输出的res>0就是正做,买入,res<0就是反做,本应该卖出,可是我们买入,这就是反做

可以看出,在这只股票上从去年的熊市到现在,如果你反做的话那么应该维持收益的稳定,相反,如果你正做,就是模型发出买入信号你即买入,则会吃大亏,你看亏了90%


关于正做与反做的解释,还记得我们的阀值分类器吗,我们规定 阀值*macd差值+偏置>0则第二天涨,反之第二天跌,但是假如我们反过来做,依然可以取得这个错误率下的模型,总之反做与正做,哪个有效就用哪个(在一段时间内)。


再解释一下反做与正做:

正做:如果模型输出1,代表模型认为明天能涨,那么我们今天尾盘买入,明天收盘卖出,盈亏发生。

反做:如果模型输出-1,代表模型认为明天会跌,那么我们今天尾盘买入,明天收盘卖出,盈亏发生。

其他情况误动作,也不发生盈亏



类似的,我实现了一个线性分类器的版本,并利用了样本权重进行训练,如果程序哪里有错误,欢迎指正,源码:http://download.csdn.net/detail/mtaxot/9578709


猜你喜欢

转载自blog.csdn.net/mtaxot/article/details/51924353