小乐数据

足球走地大小球、让球、角球预测之理性分析软件开发及逻辑详细说明

原创  数据分析 2024年08月09日 00:15:39 ly 0评论 552

前言

足球发展已经超百余年,但发现市面上没有真正比较好的预测分析软件,本着十几年的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;
	}

胜率界面结果:

编辑

前端展示效果图:

编辑

总结

该软件适合初盘、滚球的大小球、让球、角球的相关分析,非常灵活,这些实践付出了不少,但也收获了很多,从陌生到熟悉,到最后慢慢深入,所以任何领域都需要我们认真学习,想干就干,敢于实践,才会做到你想做的东西。


相关系统演示地址:

http://www.lcsjfx.com/web/

谢谢大家。


评论区

face
😀 😃 😄 😁 😆 😅 🤣 😂 🙂 🙃 😉 😊 😇 🥰 😍 🤩 😘 😗 😙 😋 😛 😜 🤪 😝 🤑 🤗 🤭 🤫 🤔 🤐 🤨 😐 😑 😶 😏 😒 🙄 😬 🤥 😌 😔 😪 🤤 😴 😷 🤒 🤕 🤢 🤮 🤧 🥵 🥶 🥴 😵 🤯 🤠 🥳 😎 🤓 🧐 😕 😟 🙁 ☹️ 😮 😯 😲 😳 🥺 😦 😧 😨 😰 😥 😢 😭 😱 😖 😣 😞 😓 😩 😫 😤 😡 😠 🤬 😈 👿 💀 ☠️ 💩 🤡 👹 👺 👻 👽 👾 🤖 😺 😸 😹 😻 😼 😽 🙀 😿 😾 🙈 🙉 🙊 💋 💌 💘 💝 💖 💗 💓 💞 💕 💟 ❣️ 💔 ❤️ 🧡 💛 💚 💙 💜 🖤 💯 💢 💥 💫 💦 💨 🕳️ 🕳 💣 💬 👁️‍🗨️ 👁‍🗨️ 👁️‍🗨 👁‍🗨 🗨️ 🗨 🗯️ 🗯 💭 💤 👋
笑脸icon


选择主题