注:MapReduce环境配置,Jar包导出,上传,hadoop执行等操作可以查看文章:MapReduce程序设计,只需替换为本篇文章的代码即可实现
https://blog.csdn.net/m0_69488210/article/details/131432125
数据集:https://download.csdn.net/download/m0_69488210/87959387
1、滚动收益率计算方法:
(1) 忽略N/A所在日的股票数据,思考:可使用插值算法填充异常N/A数据,但退市股票同样会造成N/A数据,需要识别那种数据是退市造成的,而哪种数据是异常形成的。
(2)第t日的5日滚动收益
Rt= (C_t - C_(t-5) ) / C_(t-5) ,Ct:第t日收盘价 Rt:第t日滚动收益
(3) 5日滚动正收益率
所有交易日的5日滚动收益为正(赚钱)的概率
- 所有计算忽略非交易日(节假日)
2、二次排序和组排序
MapReduce中的二次排序是指在MapReduce任务中对键值对进行排序时,除了根据键进行排序之外,还可以根据值进行排序。在二次排序中,首先按照键进行排序,然后对于具有相同键的按照指定的值进行排序,最终输出排序后的键值对序列。
组排序是将Map任务输出的键值对按照key进行排序并分组,具有相同key的键值对会被划分到同一组,并发送给同一个Reduce任务进行处理。这就确保了拥有相同key的键值对能够在Reduce阶段被合并处理,从而得出结果。
3、基本代码逻辑要求:
(1) CodeTimeTuple implements WritableComparable
封装一个代码时间类,用以在键中存放股票代码和时间,用以按股票和时间进行二次排序;定义key排序比较器,按股票代码进行一次排序,按时间进行二次排序
package cn.edu.swpu.secondary;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
//自定义Tuple
public class CodeTimeTuple implements WritableComparable<CodeTimeTuple> {
private LongWritable time = new LongWritable();
private Text code = new Text();
public LongWritable getTime() { return time; }
public void setTime(LongWritable time) { this.time = time; }
public Text getCode() { return code; }
public void setCode(Text code) { this.code = code; }
//写入数据至流
//用于框架对数据的处理
//注意读readFields和写write的顺序一致
@Override
public void write(DataOutput dataOutput) throws IOException {
code.write(dataOutput);
time.write(dataOutput);
}
//从流中读取数据
//将框架返回的数据提取出到对应属性中来
//注意读readFields和写write的顺序一致
@Override
public void readFields(DataInput dataInput) throws IOException {
code.readFields(dataInput);
time.readFields(dataInput);
}
//Key排序
@Override
public int compareTo(CodeTimeTuple o) {
//一次排序:股票代码排序(这里要与组排序逻辑相同)
int cmp = this.getCode().compareTo(o.getCode());
//如果股票代码相同,则按时间排序
if(cmp != 0)
return cmp;
//二次排序:时间排序,结果乘以-1则降序排列,否则为升序排列
return this.getTime().compareTo(o.getTime());
}
}
(2) Map extends Mapper
输入:一行数据(一只股票的日数据)
处理:使用 \t 将字符串split成数组,提取需要计算的值,并转为浮点数
输出:<代码时间对象, 收盘价>
遇到无效数据不输出(停牌股票或有N/A数据无法提取为浮点数)
在Map阶段首先分割传入的每一行的信息,忽略空置,取出收盘价,股票代码和日期,把股票编号和日期封装到CodeTimeTuple的序列化对象tuple里,在map输出的时候,tuple作为键,收盘价为对应的值,在CodeTimeTuple类里实现了按照股票代码和时间的二次排序,保证传入reduce的是按照股票代码和时间二次排序之后的升序排序
(3) GroupSort extends WritableComparator
创建一个排序比较器,修改组排序逻辑,按股票代码排序
通过组排序保证了传入Reduce的数据是排序之后按照股票代码分组的数据,保证了reduce可以合并相同股票代码的数据
(4) Reduce extends Reducer
输入:<代码时间对象,[收盘价]>
处理:计算每个5日的滚动收益,并统计滚动收益为正的概率
输出:<股票代码,滚动收益为正的概率>
把传入reduce的值存入到列表中,通过Rt= (C_t - C_(t-5) ) / C_(t-5)计算第t日的5日滚动收益,依次判断每个股票代码对应的所有的五日滚动收益是否为正,把正数的数量除以相同股票代码的所有数量就可以得到每个股票代码的5日滚动收益为正(赚钱)的概率,reduce输出的键为股票代码,值为每个股票代码的5日滚动收益为正(赚钱)的概率。
package cn.edu.swpu.secondary;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
public class Ranking {
public static void main(String[] args) throws Exception {
//1、获取job
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//2、设置jar包路径
job.setJarByClass(Ranking.class);
//3、关联Mapper和Reducer和Grouping
job.setMapperClass(Map.class);
job.setGroupingComparatorClass(Grouping.class);
job.setReducerClass(Reduce.class);
//4、设置map输出的kv类型
job.setMapOutputKeyClass(CodeTimeTuple.class);
job.setMapOutputValueClass(FloatWritable.class);
//5、设置最终输出的kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FloatWritable.class);
//6、设置输入路径和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//7、提交job
boolean res = job.waitForCompletion(true);
System.exit(res ? 0 : 1);
}
public static String value;
public static class Map extends Mapper<LongWritable, Text, CodeTimeTuple, FloatWritable> {
private final FloatWritable outV = new FloatWritable();
@Override
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString();
String[] items = line.split("\t");
try {
if ((items[12].equals("False")) && (items[2].equals("N/A") == false)) {
CodeTimeTuple tuple = new CodeTimeTuple();
tuple.setCode(new Text(items[0])); //股票代码
Date date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(items[13] + " 00:00:00");
tuple.setTime(new LongWritable(date.getTime()));//时间戳
outV.set(Float.valueOf(items[3]));
//context.write(tuple, outV);
context.write(tuple, outV);
//System.out.println("key: " + items[0] + " line: " + line);
}
} catch (ParseException e) {
System.out.println(line);
System.out.println(e.getMessage());
}
}
}
// Reduce过程
public static class Reduce extends Reducer<CodeTimeTuple, FloatWritable, Text, FloatWritable> {
// 创建一个股票代码对象作为输出的key
Text stockCode = new Text();
// 创建一个概率对象作为输出的value
FloatWritable probability = new FloatWritable();
int j = 0;
@Override
public void reduce(CodeTimeTuple key, Iterable<FloatWritable> val, Context context) throws IOException, InterruptedException {
try {
String mark = "1";
// 将收盘价列表转为数组
List<Float> closePrices = new ArrayList<Float>();
for (FloatWritable value : val) {
Date date1 = new Date(key.getTime().get());
String dateString1 = new SimpleDateFormat("yyyy-MM-dd").format(date1);
closePrices.add(value.get());
System.out.println("INFO[" + mark + "] key:" + key.getCode().toString() + " " + dateString1 + " value:" + " " + value.toString() + " index:" + j);
}
j++;
// 计算每个5日的滚动收益,并统计滚动收益为正的次数和总次数
int positiveCount = 0;
int totalCount = 0;
for (int i = 5; i < closePrices.size(); i++) {
float returnRate = (closePrices.get(i) - closePrices.get(i - 5)) / closePrices.get(i - 5);
if (returnRate > 0) {
positiveCount++;
}
totalCount++;
}
// 计算滚动收益为正的概率
float positiveProbability = (float) positiveCount / totalCount;
stockCode.set(key.getCode());
probability.set(positiveProbability);
// 输出<股票代码,滚动收益为正的概率>
context.write(stockCode, probability);
} catch (Exception e) {
e.printStackTrace();
//System.out.println("ERROR " + key.toString());
}
}
}
//组排序
public static class Grouping extends WritableComparator {
protected Grouping() {
super(CodeTimeTuple.class, true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
CodeTimeTuple key1 = (CodeTimeTuple) a;
CodeTimeTuple key2 = (CodeTimeTuple) b;
return key1.getCode().compareTo(key2.getCode());
//直接return 0则表示不分组(所有key一个组)
//return 0;
}
}
}