前言
足球发展已经超百余年,但发现市面上没有真正比较好的预测分析软件,本着十几年的JAVA开发经验,想着亲手做一个关于足球走地大小球、让球、角球的分析软件看看情况是怎么样的。
开发本类工具需要按以下步骤进行,
一、选择稳定的网站足球网站数据采集数据
做此工具,我选择的是球琛网的走地数据,主要采集“即时比分”相关列表数据,及亚欧大相关初盘、终盘、滚球相关赔率。
编辑
软件采集的代码片段如下:
//从网页取得数据
InputStream in =null; try{ JavaScriptPage jspage = (JavaScriptPage) HtmlunitHelper.getPage(url, requestHead);
in=jspage.getWebResponse().getContentAsStream();
jspage.getEnclosingWindow().getWebClient().closeAllWindows();
}catch(Exception e){ HtmlPage jspage = (HtmlPage) HtmlunitHelper.getPage(url, requestHead);
in=jspage.getWebResponse().getContentAsStream();
jspage.getEnclosingWindow().getWebClient().closeAllWindows();
}
InputStreamReader inr = new InputStreamReader(in, "UTF-8"); BufferedReader br = new BufferedReader(inr); String s = null;
//String site="live";
while((s=br.readLine())!=null){ if(pattern.matcher(s).find()){
listStr.add(s);
}
}for(String s:listStr) { Match match = new Match(); String strData = s.split("\"", s.lastIndexOf("\""))[1];
String[] data = StringUtil.split(strData,"\\^"); try{
match.setJqqc(data[48]+"-"+data[49]);
}catch(Exception e){}
match.setId(data[0]);
match.setGameName(data[2]);
match.setGameFirstName(data[2].substring(0,1));
match.setGameNameC(data[3]);
match.setTeams1(data[5].replace("<font color=#880000>", "").replace("</font>", ""));
match.setTeams1C(data[6].replace("<font color=#880000>", "").replace("</font>", ""));
match.setTeams2(data[8].replace("<font color=#880000>", "").replace("</font>", ""));
match.setTeams2C(data[9].replace("<font color=#880000>", "").replace("</font>", ""));
match.setScore(data[14]+"-"+data[15]);
match.setYellowCard1(Integer.parseInt(data[20]));
match.setYellowCard2(Integer.parseInt(data[21]));
match.setRedCard1(Integer.parseInt(data[18]));
String team1=match.getTeams1();
String team2=match.getTeams2(); try{
team1 = (String)engine.eval("T[\""+data[37]+"_3\"][0]");
team2 = (String)engine.eval("T[\""+data[38]+"_3\"][0]");
team1 = team1.replaceAll(" ", "");
team2 = team2.replaceAll(" ", "");
}catch(Exception e){} try{
match.setRedCard2(Integer.parseInt(data[19]));
}catch(Exception e){}
team1=team1.replace("U19", "");
team1=team1.replace("U20", "");
team1=team1.replace("U21", "");
team1=team1.replace("U22", "");
team1=team1.replace("U23", "");
team1=team1.replace("B队", "");
team1=team1.replace("(中)", "");
team2=team2.replace("U19", "");
team2=team2.replace("U20", "");
team2=team2.replace("U21", "");
team2=team2.replace("U22", "");
team2=team2.replace("U23", "");
team2=team2.replace("B队", "");
team2=team2.replace("(中)", "");
match.setTeamHg1(team1);
match.setTeamHg2(team2);
match.setHalfCourt(data[16]+"-"+data[17]); if("-".equals(match.getHalfCourt())){
match.setHalfCourt(match.getScore());
}
String[] t = data[12].split(","); int statas = Integer.parseInt(data[13]);
Date date = new Date(Integer.parseInt(t[0])-1900,Integer.parseInt(t[1]),Integer.parseInt(t[2]),Integer.parseInt(t[3]),Integer.parseInt(t[4]),Integer.parseInt(t[5])); long gotime = (System.currentTimeMillis() - date.getTime())/(1000*60); String strGotime = ""; try{ if(statas==1){
strGotime = gotime+""; if(gotime>45) strGotime = "45+";
}else if(statas==3){
strGotime = gotime+46+""; if(gotime+46>90) strGotime = "90+";
}else{
strGotime = state_ch[statas+14];
}
}catch(Exception e){ //e.printStackTrace();
strGotime="";
log.info("007获取状态出错,team1:"+match.getTeams1()+",statas:"+statas+",gotime:"+gotime+",error:"+e.getMessage());
}
match.setStatus(strGotime);
// if(match.getStatus()==null||"".equals(match.getStatus())||"推迟".equals(match.getStatus())||"待定".equals(match.getStatus())){// if(!"true".equals(wkbsMap.get("wkbs"))){// continue;// }// }
/*if("莫斯科斯巴达".equals(team1)||"清水心跳".equals(team1)||"托利马".equals(team1)){
log.info("比赛状态========>>"+strGotime+",team1==>>"+team1);
}*/
//log.info("角球比分:"+match.getJqqc()+"==>>"+match.getTeamHg1()+" VS "+match.getTeamHg2());
//String strdataTime = t[0]+"-"+t[1]+"-"+t[2]+" "+data[11];// 2018,3,10,20,15,00
//log.info("data[12].trim()===>>"+data[12].trim())
match.setDataTime(DateUtils.parseDate(data[12].trim(), "yyyy,MM,dd,HH,mm,ss")); Calendar dateTime = Calendar.getInstance();
dateTime.setTime(match.getDataTime());
dateTime.add(Calendar.MONTH, 1);
match.setDataTime(dateTime.getTime());
// Date dataTime = new Date(Integer.parseInt(t[0])-1900,Integer.parseInt(t[1]),Integer.parseInt(t[2]),Integer.parseInt(data[11].split(":")[0]),Integer.parseInt(data[11].split(":")[1]),0);// match.setDataTime(dataTime);
String runMath="0"; try{ if(data[28].equals("True")){
String[] data1 = ch_goin.get(match.getId()); if(data1!=null){ if(data1[5].equals("2"))
runMath ="2";
}else{
runMath ="1";
}
}else{
runMath ="0";
}
}catch(Exception e){
log.error("获取runMath异常");
}
String[] goindata = goin.get(match.getId()); Double exponential = null; Double sbExponentiald = null; String sb = ""; if(goindata!=null){ if(goindata[14].equals("0")){ try {
exponential = Double.parseDouble(goindata[3]);
sbExponentiald = Double.parseDouble(goindata[4]);
sb = this.Goal2GoalCn(goindata[2]);
}catch(Exception e) {
log.error("获取SB异常,"+team1+" VS "+team2+",goindata[3]:"+goindata[3]);
}
}else{
exponential = null;
sbExponentiald = null;
sb = "封";
}
}
match.setRunMath(runMath);
采集还是相对简单,主要是读取球琛的JSON数组,前端是通过开源的UI展示出来,界面如下:
编辑
但单是赔率数据是不够的,因此我们还采集了球琛的技术统计数据,如危险进攻数、射门数、射正射门数、传球成功率、控球率、进攻等,后面实现策略逻辑时,需要从这些数据中去查找规律。
二、开发筛选策略平台,动态设置筛选参数逻辑
这一步主要设置自己的动态策略,条件组合,参数调配等,下面简单列举其中一个策略,如:
70分钟前,主队让球,
控球率主队大于客队
危险进攻数主队大于客队12个以上
进攻主队大于客队20个
射门数主队大于客队
射正射门数主队大于客队
让球盘小于2
主队比分大于或等于客队比分
主队进攻大于 50
符合此所有条件后,看好主队获胜。
因此需要开发一些常用的界面,我们的界面如下,在这里就需要有一点点SQL的经验了:
编辑
三、定时任务执行,根据筛选策略生成相关符合策略的明细记录
策略写好后,就来到了这一步,这一步主要是配置系统的定任务,让定任务去执行条件策略的频繁筛选,
public void updatePreJudgment(SearchResult model, String todayStr,Integer writeType) {
String smQuery=""; try { SearchMap searchMap = new SearchMap();
searchMap.eq("isHide", 0);// 是否隐藏 0--否 1--是
searchMap.notEq("status", "完");// 完成的不再判断
searchMap.notEq("status", "推迟");
searchMap.notEq("status", "待定"); // searchMap.notEq("status","未开");//未开的比赛不判断
searchMap.eq("sysTimeStr", todayStr);
smQuery = "(" + model.getSmResult() + ")"; if (model.getIfInclude() != null && model.getIfInclude() == 1) {// 不包括
if (model.getGameName() != null && !"".equals(model.getGameName())) {
model.setGameName(model.getGameName().replace(",", ",")); String gameName = ""; if (model.getGameName().indexOf(",") != -1) { for (String value : model.getGameName().split(",")) {
gameName += "and gameNameC not like '%" + value + "%' ";
} if (!"".equals(gameName)) {
gameName = gameName.substring(3);
}
} else {
gameName += " gameNameC not like '%" + model.getGameName() + "%' ";
}
smQuery += " and (" + gameName + ") ";
}
} else {// 包括
if (model.getGameName() != null && !"".equals(model.getGameName())) {
model.setGameName(model.getGameName().replace(",", ",")); String gameName = ""; if (model.getGameName().indexOf(",") != -1) { for (String value : model.getGameName().split(",")) {
gameName += "or gameNameC like '%" + value + "%' ";
} if (!"".equals(gameName)) {
gameName = gameName.substring(2);
}
} else {
gameName += " gameNameC like '%" + model.getGameName() + "%' ";
}
smQuery += " and (" + gameName + ") ";
}
}
searchMap.addHQL(smQuery);
List<Match> listMatch = this.matchDAO.findObjects(searchMap, Match.class);
for (Match match : listMatch) {
match.setPreJudgment(model.getResultDesc()); /*match.setPreJudgment(((match.getPreJudgment() == null || "".equals(match.getPreJudgment())) ? ""
: match.getPreJudgment() + ";\n") + model.getResultDesc());*/
String nowTime = CustomDateUtil.format(CustomDateUtil.createSysDate(), "HH:mm"); // 比分/+结论
String status = (match.getStatus() == null ? "" : match.getStatus()) + " "; String resultDesc = (match.getScore() == null ? "" : match.getScore()) + "/"
+ (model.getResultDesc() == null ? "" : model.getResultDesc()); if (model.getResultDesc() != null && model.getResultDesc().indexOf("角球") != -1) {
resultDesc = match.getScore() + "/" + match.getJqqc() + "(角)" + "/"
+ (model.getResultDesc() == null ? "" : model.getResultDesc());
} String preJudgment = nowTime + " " + status + resultDesc; String searchResultId = match.getSearchResultId() == null ? "" : match.getSearchResultId();// 条件ID
if (searchResultId.indexOf(model.getId()) == -1) {// 不存在相同的条件ID
searchResultId += "," + model.getId();
} if (searchResultId.startsWith(",")) {
searchResultId = searchResultId.substring(1);
}
match.setSearchResultId(searchResultId); String percentage = model.getPercentageStr();// 胜率
if (model.getWriteType() != null && model.getWriteType() == 1) {// 结论写入备注2
// 状态+比分+结论
String remark = ""; if (match.getRemark2() == null || "".equals(match.getRemark2())) {
remark += preJudgment;
} else { if (model.getType() != null && model.getType() == 1) {// 报警条件
if (match.getRemark2().indexOf(resultDesc) == -1) {// 不存在相同的报警结果
remark += match.getRemark2() + "\n" + preJudgment;
}
} else { if (match.getRemark2().indexOf(resultDesc) == -1) {// 不存在相同的预判结果
remark += match.getRemark2() + "\n" + preJudgment;
}
}
} if (!"".equals(remark)) {
match.setRemark2(remark + "; " + percentage);
}
} else {// 写入备注1
// 状态+比分+结论
String remark = ""; if (match.getRemark() == null || "".equals(match.getRemark())) {
remark += preJudgment;
} else { if (model.getType() != null && model.getType() == 1) {// 报警条件
if (match.getRemark().indexOf(resultDesc) == -1) {// 不存在相同的报警结果
remark += match.getRemark() + "\n" + preJudgment;
}
} else { if (match.getRemark().indexOf(resultDesc) == -1) {// 不存在相同的预判结果
remark += match.getRemark() + "\n" + preJudgment;
}
}
} if (!"".equals(remark)) {
match.setRemark(remark + "; " + percentage);
}
} if ((model.getType() != null && model.getType() == 1)) {// 报警条件
//log.info("报警条件生成统计明细********************************matchid:"+match.getId());
// 插入统计明细
SearchResult modelNew=new SearchResult();
modelNew = (SearchResult) MethodUtil.copyProperties(modelNew, model); this.createStatistics(match, modelNew); //log.info("========插入统计明细结束=======>>matchid:"+match.getId());
} if ((model.getType() != null && model.getType() == 1)
|| (model.getIfCanBet() != null && model.getIfCanBet() == 1)) {// 报警条件
match.setIsWarn(1); if (match.getBetTime() != null) { long interval = (new Date().getTime() - match.getBetTime().getTime()) / 1000; long seconds = Long.parseLong(CustomCommonConfig.getVoiceMap().get("seconds")); if (interval - seconds >= 0) {// 停止报警声音
match.setIsWarn(2);
}
}
match.setIsTop(1);// 只有在报警时才自动置顶
match.setIsResult(1);// 符合结果
match.setColor(model.getColor());
match.setIsShow(1);// 显示
if (model.getIfCanBet() != null && model.getIfCanBet() == 1) {
match.setIfCanBet(1); /*if (model.getWriteType() == 1) {// 赛前的
}*/
} // ****************保存结果*********************
if ((model.getType() != null && model.getType() == 1)) {// 报警条件
if (model.getShowResult() != null && !"".equals(model.getShowResult())) { String showResult = match.getScore() == null ? "0-0" : match.getScore();
showResult += " " + model.getShowResult(); if (match.getShowResult() == null || "".equals(match.getShowResult())) {
match.setShowResult(status + showResult);
} else { if (match.getShowResult().indexOf(showResult) == -1) {
match.setShowResult(match.getShowResult() + ";\n" + status + showResult);
}
}
}
} // ******************************************
} this.matchDAO.update(match);
} if (model.getIfRight() != null && model.getIfRight() == 0) { this.matchDAO.updateJQL("update from SearchResult set ifRight=1 where id='"+model.getId()+"'", null);
}
} catch (Exception e) {
e.printStackTrace();
log.error("007更新判断结果出错:" + e.getMessage() + ",预判结论:" + model.getResultDesc() + " 预判条件:"+ model.getSmResult()+"拼接后错误语句:"+smQuery); this.matchDAO.updateJQL("update from SearchResult set ifRight=0 where id='"+model.getId()+"'", null);
}
}
四、比赛完成后需要对生成的明细记录进行结算,统计每条策略的胜率情况
这是最后一步,就是对生成的预测条件进行胜率统计,下面展示一下我们的比赛结果结算方法代码及界面效果:
代码:
public static Integer getJsStatusNew(Statistics betRecord,String score){ //胜平负,如果主队进一球以上,购买胜的全赢,其它全输,如果均不进球,购买平的全赢,其它全输,如果客队进一球,购买负的全赢,其它全输
//赛前让球
//下1.14,盘口在上,如果当前比分是2-1,那么计算方法是2-1+(-0.25)下主队计算方法:,=0走水,=0.25赢半,>0.25全赢,=-0.25输半,<-0.25全输
//下0.51,盘口在下,如果当前比分是2-1,那么计算方法是2-1+(+0.5)
//主分-客分+(±盘口)=0走水,主队计算方法=0.25赢半,>0.25全赢,=-0.25输半,<-0.25全输
//走地让球
//再说个例子 投注时候2-1,比赛结束2-2,投注的时候主队让球0.25 (2-2)-(2-1)+(-0.25)=-1.25 主队全输
//(主队分数-投注时候的主队分数)-(客队分数-投注时候的客队分数)+(±盘口)
//大小球计算公式:两队-盘口(如果盘口带/,则取/后面的盘口), 主队计算方法=0走水,=0.25赢半,>0.25全赢,=-0.25输半,<-0.25全输
/*38 39 40
+0.5的应该是171
-0.5的-100
+0.5/1的是163*/
Integer jsStatus=0;
Double score1=0D;//最终主队总分
Double score2=0D;//最终客队总分
Double betscore1=0D;//下注时主队总分
Double betscore2=0D;//下注时客队总分
betscore1=Double.parseDouble(betRecord.getScore().split("-")[0]);
betscore2=Double.parseDouble(betRecord.getScore().split("-")[1]);
score1=Double.parseDouble(score.split("-")[0]);
score2=Double.parseDouble(score.split("-")[1]);
Double zhpk=0D;//转换盘口
String handicap=betRecord.getHandicap(); if(handicap!=null&&!"".equals(handicap)&&betRecord.getBetType()!=20){
handicap=handicap.replace("+", ""); if("".equals(handicap)){
handicap="0";
} if(handicap.indexOf("/")!=-1){
zhpk=Double.parseDouble(handicap.split("/")[1])-0.25;
}else{
zhpk=Double.parseDouble(handicap);
}// if(ArithUtil.addDouble(zhpk, 0D)<0D){// zhpk=-zhpk;// }
} if(betRecord.getBetType()==1||betRecord.getBetType()==8){//胜
if(score1>score2){//全赢,赔率包括本金
jsStatus=2;
}else{//全输
jsStatus=4;
}
}else if(betRecord.getBetType()==3||betRecord.getBetType()==10){//平
if(ArithUtil.subDouble(score1, score2)==0D){//全赢,赔率包括本金
jsStatus=2;
}else{//全输
jsStatus=4;
}
}else if(betRecord.getBetType()==2||betRecord.getBetType()==9){//负,赔率包括本金
if(score1<score2){//全赢
jsStatus=2;
}else{//全输
jsStatus=4;
}
}else if(betRecord.getBetType()==20){//总进球数
Double zjq=Double.parseDouble(betRecord.getHandicap()); if(ArithUtil.subDouble(zjq, ArithUtil.addDouble(score1, score2))==0D||(zjq==7D&&ArithUtil.addDouble(score1, score2)>=7D)){//全赢
jsStatus=2;
}else{//全输
jsStatus=4;
}
}else if(betRecord.getBetType()==4||betRecord.getBetType()==11){//让球主队
Double countVal=0D; //走地让球
//再说个例子 投注时候2-1,比赛结束2-2,投注的时候主队让球0.25 (2-2)-(2-1)+(-0.25)=-1.25 主队全输
//(主队分数-投注时候的主队分数)-(客队分数-投注时候的客队分数)+(±盘口)
countVal=(score1-betscore1)-(score2-betscore2)+zhpk; if(countVal==0){//走水
jsStatus=6;
}else if(countVal==0.25){//赢半
jsStatus=3;
}else if(countVal>0.25){//全赢
jsStatus=2;
}else if(countVal==-0.25){//输半
jsStatus=5;
}else if(countVal<-0.25){//全输
jsStatus=4;
}
}else if(betRecord.getBetType()==5||betRecord.getBetType()==12){//让球客队
Double countVal=0D; //走地让球
//再说个例子 投注时候2-1,比赛结束2-2,投注的时候主队让球0.25 (2-2)-(2-1)+(-0.25)=-1.25 主队全输
//(主队分数-投注时候的主队分数)-(客队分数-投注时候的客队分数)+(±盘口)
countVal=(score2-betscore2)-(score1-betscore1)+zhpk; if(countVal==0){//走水
jsStatus=6;
}else if(countVal==0.25){//赢半
jsStatus=3;
}else if(countVal>0.25){//全赢
jsStatus=2;
}else if(countVal==-0.25){//输半
jsStatus=5;
}else if(countVal<-0.25){//全输
jsStatus=4;
}
}else if(betRecord.getBetType()==6||betRecord.getBetType()==13||betRecord.getBetType()==15){//大球
//0走水,=0.25赢半,>0.25全赢,=-0.25输半,<-0.25全输
Double countVal=score1+score2-zhpk; if(countVal==0){
jsStatus=6;
}else if(countVal==0.25){
jsStatus=3;
}else if(countVal>0.25){
jsStatus=2;
}else if(countVal==-0.25){
jsStatus=5;
}else if(countVal<-0.25){
jsStatus=4;
}
}else if(betRecord.getBetType()==7||betRecord.getBetType()==14||betRecord.getBetType()==16){//小球
Double countVal=score1+score2-zhpk; if(countVal==0){
jsStatus=6;
}else if(countVal==0.25){
jsStatus=5;
}else if(countVal>0.25){
jsStatus=4;
}else if(countVal==-0.25){
jsStatus=3;
}else if(countVal<-0.25){
jsStatus=2;
}
} return jsStatus;
}
胜率界面结果:
编辑
前端展示效果图:
编辑
总结
该软件适合初盘、滚球的大小球、让球、角球的相关分析,非常灵活,这些实践付出了不少,但也收获了很多,从陌生到熟悉,到最后慢慢深入,所以任何领域都需要我们认真学习,想干就干,敢于实践,才会做到你想做的东西。
相关系统演示地址:
数据分析、初盘数据、走地数据、分析管理系统、AI大模型预测系统、全自动化下单系统、智能娱乐竞猜系统-乐彩云
谢谢大家。