Jelajahi Sumber

Add base Odds

Axel Nordh 3 tahun lalu
induk
melakukan
26bb4e7177

+ 21 - 0
Odds/.classpath

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="F:/Resources/JAVA/MysqlConnector/mysql-connector-java-8.0.17.jar"/>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

+ 2 - 0
Odds/.gitignore

@@ -0,0 +1,2 @@
+/bin/
+/target/

+ 30 - 0
Odds/.project

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>Odds</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.statet.r.resourceProjects.RBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.statet.ide.resourceProjects.Statet</nature>
+		<nature>org.eclipse.statet.r.resourceProjects.R</nature>
+	</natures>
+</projectDescription>

+ 15 - 0
Odds/.settings/org.eclipse.jdt.core.prefs

@@ -0,0 +1,15 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=1.8

+ 4 - 0
Odds/.settings/org.eclipse.m2e.core.prefs

@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1

+ 38 - 0
Odds/pom.xml

@@ -0,0 +1,38 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>Odds</groupId>
+  <artifactId>Odds</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.8.1</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+   <dependencies>
+   <dependency>
+	 <groupId>com.google.guava</groupId>
+	 <artifactId>guava</artifactId>
+	 <version>30.0-jre</version>
+	</dependency>
+	<dependency>
+ 	   <groupId>net.sourceforge.htmlunit</groupId>
+	    <artifactId>htmlunit</artifactId>
+	    <version>2.62.0</version>
+	</dependency>
+	<dependency>
+	    <groupId>com.github.jbytecode</groupId>
+	    <artifactId>RCaller</artifactId>
+	    <version>3.0</version>
+	</dependency>
+
+   </dependencies>
+</project>

+ 51 - 0
Odds/src/Dokument/MarginLookbackUpdate.txt

@@ -0,0 +1,51 @@
+Superettan - uppdaterade värden
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-15	12 -> 7				5 -> 25			0 -> 11				0 -> 6			15 -> 15			20 -> 20
+2021-08-22	7 -> 7				25 -> 25		11 -> 9				6 -> 4			15 -> 15			20 -> 20
+2021-08-29	7 -> 7				25 -> 25		9 -> 9				4 -> 4			15 -> 15			20 -> 20
+
+Allsvenskan
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-15	0 -> 0				0 -> 0			7 -> 7				26 -> 27		12 -> 8				12 -> 21
+2021-08-22	0 -> 0				0 -> 0			7 -> 7				27 -> 27		8 -> 9				21 -> 12
+2021-08-29	0 -> 0				0 -> 0			7 -> 7				27 -> 27		9 -> 9				12 -> 10
+
+Premier League
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-15	4 -> 4				16 -> 17		0 -> 0				0 -> 0			14 -> 15			4 -> 6
+2021-08-29	4 -> 4				17 -> 17		0 -> 0				0 -> 0			15 -> 15			6 -> 6
+
+Champinship
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-15	21 -> 15			28 -> 33		22 -> 21			26 -> 21		10 -> 10			31 -> 31
+2021-08-29	15 -> 15			33 -> 33		21 -> 21			21 -> 21		10 -> 10			31 -> 34
+
+Mls
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-15	15 -> 11			33 -> 27		0 -> 0				0 -> 0			0 -> 0				0 -> 0
+2021-08-29	11 -> 11			27 -> 27		0 -> 0				0 -> 0			0 -> 0				0 -> 0
+
+
+Ligue - 1
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-15	0 -> 0				0 -> 0			14 -> 10			26 -> 32		16 -> 8				20 -> 24
+2021-08-29	0 -> 0				0 -> 0			10 -> 10			32 -> 32		8 -> 8				24 -> 24
+
+La Liga
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-15	4 -> 4				6 -> 5			4 -> 4				29 -> 29		0 -> 5				0 -> 1		
+2021-08-29	4 -> 4				5 -> 5			4 -> 4				29 -> 29		0 -> 5				0 -> 1			
+
+Bondesliga
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-15	23 -> 4				18 -> 20		11 -> 11			28 -> 23		8 -> 17				28 -> 27
+2021-08-29	4 -> 21				20 -> 27    	11 -> 11			23 -> 23		17 -> 17			27 -> 27
+
+J1-League
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-17	6					20				5					26				9					7
+2021-08-29	6 => 6				20 => 20		5 => 0 				26 => 0			9 => 6 				7 => 6
+
+Serie-a
+Datum		LookbackHome		MarginHome		LookbackDraw		MarginDraw		LookbackAway		MarginAway
+2021-08-29	0					0				8					20				0					0

+ 51 - 0
Odds/src/Main.java

@@ -0,0 +1,51 @@
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import parser.OddsPortal;
+
+public class Main {
+
+	public static void main(String[] args) throws IOException {
+		final OddsPortal op = new OddsPortal();
+		System.out.println("Getting Yesterdays matches");
+		op.getYesterdaysMatches();
+		System.out.println("Getting Todays Matches");
+		op.getTodaysMatches();
+		System.out.println("Getting tomorrows matches");
+		op.getTomorrowsMatches();
+
+		op.getNextDaysMatches();
+
+		//		op.getHistoricMatches("soccer", "japan", "j2-league", "2020");
+
+		//		callingRScript();
+	}
+
+	private static void callingRScript() throws IOException {
+		System.out.println(System.getProperty("user.dir"));
+		final String rPath = System.getProperty("user.dir") + "\\src\\RScript\\scorePrediction.R";
+
+		final Process exec = Runtime.getRuntime().exec("C:\\Program Files\\R\\R-3.6.3\\bin\\Rscript.exe " + rPath);
+
+
+		final BufferedReader stdInput = new BufferedReader(new
+				InputStreamReader(exec.getInputStream()));
+
+		final BufferedReader stdError = new BufferedReader(new
+				InputStreamReader(exec.getErrorStream()));
+
+		// Read the output from the command
+		System.out.println("Here is the standard output of the command:\n");
+		String s = null;
+		while ((s = stdInput.readLine()) != null) {
+			System.out.println(s);
+		}
+		// Read any errors from the attempted command
+		System.out.println("Here is the standard error of the command (if any):\n");
+		while ((s = stdError.readLine()) != null) {
+			System.out.println(s);
+		}
+	}
+
+}

+ 164 - 0
Odds/src/RScript/scorePrediction.R

@@ -0,0 +1,164 @@
+# Data Preprocessing
+
+# Importing the Dataset
+library(RMySQL)
+mydb = dbConnect(MySQL(), user="OddsNy", password="Odds1_Ny_Password", host="nordh.xyz", dbname="new_odds")
+
+country = 'iran'
+league = 'persian-gulf-pro-league'
+
+sql = paste("SELECT homeTeam, awayTeam, homeScore, awayScore, hTeam.name as homeTeamName, aTeam.name as awayTeamName, (SELECT AVG(homeScore) FROM SoccerResults WHERE gameDate < sr.gameDate AND homeTeam = sr.homeTeam ) AS avgHomeScore, (SELECT AVG(awayScore) FROM SoccerResults WHERE gameDate < sr.gameDate AND awayTeam = sr.awayTeam ) AS avgAwayScore,(homeScore - awayScore) as result FROM `SoccerResults` sr INNER JOIN Team hTeam ON homeTeam = hTeam.id INNER JOIN Team aTeam ON awayTeam = aTeam.id AND sr.leagueId = (SELECT id FROM League where name = '",
+            league,
+            "' AND countryId = (SELECT id FROM Country WHERE name = '",
+            country,
+            "')) ORDER BY sr.gameDate ASC", 
+            sep = "", collapse = "")
+
+
+piSql = paste("SELECT homeTeam, awayTeam, homeScore, awayScore, hTeam.name as homeTeamName, aTeam.name as awayTeamName, (SELECT AVG(homeScore) FROM SoccerResults WHERE gameDate < sr.gameDate AND homeTeam = sr.homeTeam ) AS avgHomeScore, (SELECT AVG(awayScore) FROM SoccerResults WHERE gameDate < sr.gameDate AND awayTeam = sr.awayTeam ) AS avgAwayScore FROM `SoccerResults` sr INNER JOIN Team hTeam ON homeTeam = hTeam.id INNER JOIN Team aTeam ON awayTeam = aTeam.id WHERE sr.leagueId = (SELECT id FROM League where name = '",
+              league,
+              "' AND countryId = (SELECT id FROM Country WHERE name = '",
+              country,
+              "')) ORDER BY sr.gameDate ASC", 
+              sep = "", collapse = "")
+
+rs = dbSendQuery(mydb, sql)
+
+dataset = fetch(rs, n=-1)
+
+###Taking case of missing data
+dataset$avgHomeScore = ifelse(is.na(dataset$avgHomeScore), 0, dataset$avgHomeScore)
+dataset$avgAwayScore = ifelse(is.na(dataset$avgAwayScore), 0, dataset$avgAwayScore)
+#dataset$avgHomeScoreOneYearBack = ifelse(is.na(dataset$avgHomeScoreOneYearBack), 0, dataset$avgHomeScoreOneYearBack)
+#dataset$avgAwayScoreOneYearBack = ifelse(is.na(dataset$avgAwayScoreOneYearBack), 0, dataset$avgAwayScoreOneYearBack)
+
+# Encoding categorical data
+##dataset$overtime = factor(dataset$overtime, levels = c('1-0', '0-1', 'NULL'), labels = c(1, 1, 0))
+##dataset$result = factor(dataset$result, levels = c('1', 'X', '2'), labels = c(1, 0, 2))
+
+
+library(piratings)
+
+rs2 = dbSendQuery(mydb, piSql)
+
+piDataset = fetch(rs2, n=-1)
+
+teams <- as.matrix(piDataset[,c("homeTeamName", "awayTeamName")])
+outcomes <- as.matrix(piDataset[, c("homeScore", "awayScore")])
+
+# England - premiere League seq(0.06, 0.1, 0.005), seq(0.4, 0.9, 0.05)
+grid <- optimize_pi_ratings(teams, outcomes, seq(0.04, 0.1, 0.005), seq(0.3, 0.9, 0.05))
+
+colnames(grid)[1] <- "rating"
+gridOrdered = grid[order(grid$rating),]
+bestRes = head(gridOrdered,1)
+
+library(ggplot2)
+
+# midpoint England Premier-league 2.9
+# midpoint china Super-league 2.7
+# midpoint sweden Allsvenskan 2.8
+print("ggplot")
+ggplot(data = grid, aes(x = lambda, y = gamma, fill = rating)) + 
+geom_tile() + scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 2.7) + 
+labs(x = "lambda", y = "gamma", title = "grid optimization", fill = "Mean \nsquared \nerror") + 
+theme(plot.title = element_text(hjust = 0.5))
+
+
+# outcomes <- rbind(outcomes, c(0,0))
+# teams <- rbind(teams, c("Wolves", "Southampton"))
+
+# England 0.07, 0.7
+# China Super-league 0.07, 0.5
+# Sweden allsvenskan 0.075, 0.7
+pratings <- calculate_pi_ratings(teams, outcomes, bestRes$lambda, bestRes$gamma)
+
+
+print(tail(pratings, n = 15))
+
+# newDataset = dataset
+newDataset = piDataset
+newDataset = cbind(piDataset, pratings)
+
+colnames(newDataset)[9] <- "piOne"
+colnames(newDataset)[10] <- "piTwo"
+
+
+#test = cbind(dataset, pratings)
+#colnames(test)[10] <- "piOne"
+#colnames(test)[11] <- "piTwo"
+
+round_df <- function(df, digits) {
+  nums <- vapply(df, is.numeric, FUN.VALUE = logical(1))
+  
+  df[,nums] <- round(df[,nums], digits = digits)
+  
+  (df)
+}
+
+test = round_df(newDataset, digits = 3)
+
+test2 <- transform(test, piRes=test$piOne - test$piTwo)
+test2 <- transform(test2, bothSame=ifelse(test2$piRes > 0 & test2$avgHomeScore - test2$avgAwayScore > 0,TRUE,
+                                          ifelse(test2$piRes < 0 & test2$avgHomeScore - test2$avgAwayScore < 0, TRUE, 
+                                                 FALSE)))
+
+test2 <- transform(test2, correct=ifelse(test2$homeScore > test2$awayScore & test2$bothSame & test2$piRes > 0, TRUE,
+                                         ifelse(test2$homeScore < test2$awayScore & test2$bothSame & test2$piRes < 0, TRUE,
+                                                FALSE)))
+
+correct = test2[test2$correct == TRUE,]
+
+notSame = test2[test2$bothSame == FALSE,]
+
+draws = test2[test2$homeScore - test2$awayScore == 0,]
+
+drawsDiff = draws[draws$piRes < 0.09 & draws$piRes > -0.09,]
+
+### Regressor/ prediction thing
+
+# Splitting the dataset into the Training set and Test set
+# install.packages('caTools')
+#library(caTools)
+#set.seed(1111)
+# split = sample.split(dataset$result, SplitRatio = 0.8)
+
+smp_size <- floor(0.75 * nrow(newDataset))
+train_ind <- sample(seq_len(nrow(newDataset)), size = smp_size)
+train <- newDataset[1:smp_size,]
+test <- newDataset[smp_size:nrow(newDataset), ]
+
+smp_size <- floor(0.75 * nrow(test2))
+train_ind <- sample(seq_len(nrow(test2)), size = smp_size)
+train <- test2[1:smp_size,]
+test <- test2[smp_size:nrow(test2), ]
+
+
+
+# training_set = subset(dataset, split == TRUE)
+# test_set = subset(dataset, split == FALSE)
+
+
+# Feature scaling
+#training_set[,6:7] = scale(training_set[,6:7])
+#training_set[,9:11] = scale(training_set[,9:11])
+#test_set[,6:7] = scale(test_set[,6:7])
+#test_set[,9:11] = scale(test_set[,9:11])
+
+ regressor = lm(formula = result ~ avgHomeScore + avgAwayScore + piOne + piTwo + piRes,
+               data = train)
+ 
+y_pred = predict(regressor, newdata = test)
+
+
+## Plot the result (probably not working)
+#library(ggplot2)
+#ggplot() +
+#  geom_point(aes(x = train$avgHomeScore, y = train$result), 
+#             colour = 'red') + 
+#  geom_line(aes(x = train$avgHomeScore, y = predict(regressor, newdata = train)),
+#            colour = 'blue') + 
+#  ggtitle('Result plot') + 
+#  xlab('Awarage Home Score') + 
+#  ylab('result')
+

+ 311 - 0
Odds/src/mysql/Mysql.java

@@ -0,0 +1,311 @@
+package mysql;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+import object.CurrentParsing;
+
+public class Mysql {
+
+    private static final Mysql instance = new Mysql();
+
+    private static final String username = "OddsNy";
+    private static final String password = "Odds1_Ny_Password";
+    private static final String database = "new_odds";
+    private static final String url = "jdbc:mysql://nordh.xyz:3306/";
+    private static final String timezoneFix
+            = "?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC";
+
+    private Connection conn;
+
+    protected Mysql() {
+        getConnection();
+    }
+
+    public static Mysql getInstance() {
+        return instance;
+    }
+
+    protected Connection getConnection() {
+        if (conn == null) {
+            try {
+                conn = DriverManager.getConnection(url + database + timezoneFix, username, password);
+            } catch (final SQLException e) {
+                throw new RuntimeException(e.getMessage(), e);
+            }
+        }
+        return conn;
+    }
+
+    public CurrentParsing getCurrentParsing() {
+        final CurrentParsing returnValue = new CurrentParsing();
+        try {
+            final Statement statement = conn.createStatement();
+            final String sql = "SELECT * FROM parsing";
+            final ResultSet rs = statement.executeQuery(sql);
+            while (rs.next()) {
+                returnValue.setDone(rs.getBoolean("done"));
+                returnValue.setCurrentYear(rs.getInt("year"));
+                returnValue.setCurrentDate(rs.getDate("gameDate"));
+                returnValue.setLeague(rs.getString("league"));
+                returnValue.setPage(rs.getInt("page"));
+            }
+        } catch (final SQLException e) {
+            e.printStackTrace();
+        }
+        return returnValue;
+    }
+
+    public int addLeague(String leagueName, String country, String sport) throws SQLException {
+        leagueName = leagueName.trim();
+        leagueName = leagueName.replaceAll(" ", "-");
+        leagueName = leagueName.replaceAll("\\.", "");
+        final int sportId = addSport(sport);
+        final int countryId = addCountry(country);
+        final String sql = "INSERT INTO League (name, sportId, countryId) VALUES (?, ?, ?) " + "ON DUPLICATE KEY UPDATE name = ?";
+        final PreparedStatement statement = conn.prepareStatement(sql);
+        statement.setString(1, leagueName);
+        statement.setInt(2, sportId);
+        statement.setInt(3, countryId);
+        statement.setString(4, leagueName);
+
+        statement.executeUpdate();
+
+        return getId("League", leagueName, countryId);
+    }
+
+    public int addCountry(String name) throws SQLException {
+        name = name.replaceAll(" ", "-");
+        name = name.replaceAll("\\.", "");
+        final String sql = "INSERT INTO Country (name) VALUES (?) ON DUPLICATE KEY UPDATE name = ?";
+        final PreparedStatement statement = conn.prepareStatement(sql);
+        statement.setString(1, name);
+        statement.setString(2, name);
+        statement.executeUpdate();
+
+        return getId("Country", name, -1);
+    }
+
+    public int addSport(String sport) throws SQLException {
+        sport = sport.replaceAll(" ", "-");
+        sport = sport.replaceAll("\\.", "");
+        final String sql = "INSERT INTO Sport (name) VALUES (?) ON DUPLICATE KEY UPDATE name = ?";
+        final PreparedStatement statement = conn.prepareStatement(sql);
+        statement.setString(1, sport);
+        statement.setString(2, sport);
+        statement.executeUpdate();
+        return getId("Sport", sport, -1);
+    }
+
+    private int getId(String table, String name, int countryId) throws SQLException {
+        String sql = "SELECT id FROM " + table + " WHERE name = ?";
+        if (countryId > -1) {
+            sql += " AND countryId = ?";
+        }
+
+        final PreparedStatement stmt = conn.prepareStatement(sql);
+        stmt.setString(1, name.trim());
+        if (countryId > -1) {
+            stmt.setInt(2, countryId);
+        }
+        final ResultSet insertRs = stmt.executeQuery();
+        int id = 0;
+        if (insertRs.next()) {
+            id = insertRs.getInt("id");
+        }
+        return id;
+    }
+
+    public int getLeagueId(int sportId, int countryId, String leagueName) throws SQLException {
+        final String sql = "SELECT id FROM League WHERE name = ? AND countryId = ? AND sportId = ?";
+        final PreparedStatement stmt = conn.prepareStatement(sql);
+        stmt.setString(1, leagueName.trim());
+        stmt.setInt(2, countryId);
+        stmt.setInt(3, sportId);
+
+        final ResultSet rs = stmt.executeQuery();
+
+        int id = 0;
+        while (rs.next()) {
+            id = rs.getInt("id");
+        }
+
+        return id;
+    }
+
+    public int getSportId(String sportName) {
+        final String sql = "SELECT id from Sport WHERE name = ?";
+        PreparedStatement stmt;
+        int id = 0;
+        try {
+            stmt = conn.prepareStatement(sql);
+            stmt.setString(1, sportName.trim());
+
+            final ResultSet rs = stmt.executeQuery();
+            while (rs.next()) {
+                id = rs.getInt("id");
+            }
+        } catch (final SQLException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        return id;
+    }
+
+    public int getCountryId(String country) throws SQLException {
+        final String sql = "SELECT id from Country WHERE name = ?";
+        final PreparedStatement stmt = conn.prepareStatement(sql);
+        stmt.setString(1, country.trim());
+
+        final ResultSet rs = stmt.executeQuery();
+        int id = 0;
+        while (rs.next()) {
+            id = rs.getInt("id");
+        }
+
+        return id;
+    }
+
+    public void addResult(String tableName, LocalDateTime gameDate, String homeTeam, String awayTeam, int homeScore,
+            int awayScore, boolean overtime, float odds1, float oddsX, float odds2, int countryId, String season, int leagueId,
+            int sportId) throws SQLException {
+
+        final int homeTeamId = getOrInsertTeam(homeTeam, countryId, leagueId, sportId);
+        final int awayTeamId = getOrInsertTeam(awayTeam, countryId, leagueId, sportId);
+
+        final String selectSql = "SELECT id FROM SoccerResults WHERE homeTeamId = ? AND awayTeamId = ? AND DATE(gameDate) = ?";
+
+        final PreparedStatement st = conn.prepareStatement(selectSql);
+        final String date = gameDate.format(DateTimeFormatter.ISO_DATE);
+        st.setInt(1, homeTeamId);
+        st.setInt(2, awayTeamId);
+        st.setString(3, date);
+
+        final ResultSet rs = st.executeQuery();
+
+        int gameId = -1;
+        while (rs.next()) {
+            gameId = rs.getInt("id");
+        }
+
+        if (gameId != -1) {
+            final String sql
+                    = "UPDATE " + tableName + " SET homeScore = ?, awayScore = ?, overtime = ?, odds1 = ?, oddsX = ?, odds2 = ? "
+                            + "WHERE homeTeamId = ? AND awayTeamId = ? AND DATE(gameDate) = ?";
+
+            final PreparedStatement statement = conn.prepareStatement(sql);
+            statement.setInt(1, homeScore);
+            statement.setInt(2, awayScore);
+            statement.setBoolean(3, overtime);
+            statement.setFloat(4, odds1);
+            statement.setFloat(5, oddsX);
+            statement.setFloat(6, odds2);
+            statement.setInt(7, homeTeamId);
+            statement.setInt(8, awayTeamId);
+            statement.setString(9, date);
+
+            statement.executeUpdate();
+
+        } else {
+            final String sql = "INSERT INTO " + tableName
+                    + " (homeTeamId, awayTeamId, homeScore, awayScore, overtime, odds1, oddsX, odds2, countryId, gameDate, season, leagueId) "
+                    + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE homeScore = ?, awayScore = ?, odds1 = ?, oddsX = ?, odds2 = ?";
+            final PreparedStatement stmt = conn.prepareStatement(sql);
+            stmt.setInt(1, homeTeamId);
+            stmt.setInt(2, awayTeamId);
+            stmt.setInt(3, homeScore);
+            stmt.setInt(4, awayScore);
+            stmt.setBoolean(5, overtime);
+            stmt.setFloat(6, odds1);
+            stmt.setFloat(7, oddsX);
+            stmt.setFloat(8, odds2);
+            stmt.setInt(9, countryId);
+            stmt.setString(10, gameDate.toString());
+            stmt.setString(11, season);
+            stmt.setInt(12, leagueId);
+            stmt.setInt(13, homeScore);
+            stmt.setInt(14, awayScore);
+            stmt.setFloat(15, odds1);
+            stmt.setFloat(16, oddsX);
+            stmt.setFloat(17, odds2);
+
+            stmt.execute();
+        }
+
+    }
+
+    private int getOrInsertTeam(String teamName, int countryId, int leagueId, int sportId) throws SQLException {
+        teamName = teamName.replace('\u00A0', ' ').trim();
+        int teamId = getId("Team", teamName, countryId);
+        if (teamId <= 0) {
+            final String insertSql = "INSERT INTO Team (name, sportId, countryId, leagueId) VALUES (? ,? ,? ,?)";
+            final PreparedStatement insertTeamStatement = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS);
+            insertTeamStatement.setString(1, teamName.trim());
+            insertTeamStatement.setInt(2, sportId);
+            insertTeamStatement.setInt(3, countryId);
+            insertTeamStatement.setInt(4, leagueId);
+
+            insertTeamStatement.executeUpdate();
+            final ResultSet generatedKeys = insertTeamStatement.getGeneratedKeys();
+            generatedKeys.next();
+            teamId = generatedKeys.getInt(1);
+        }
+
+        return teamId;
+    }
+
+    public void setParsingForLeague(int leagueId, int sportId, int countryId, LocalDateTime gameDate, int currentParsePage,
+            String parsedYear) {
+        final String sql
+                = "UPDATE League SET parsedYear = ?, parsedPage = ?, lastParsedGameDate = ? WHERE sportId = ? AND countryId = ? AND id = ?";
+        try {
+            final PreparedStatement stmt = conn.prepareStatement(sql);
+            stmt.setString(1, parsedYear);
+            stmt.setInt(2, currentParsePage);
+            stmt.setString(3, gameDate.toString());
+            stmt.setInt(4, sportId);
+            stmt.setInt(5, countryId);
+            stmt.setInt(6, leagueId);
+
+            stmt.executeUpdate();
+        } catch (final SQLException e) {
+            System.out.println("Failing sql: " + sql + ", " + parsedYear + ", " + currentParsePage + ", " + gameDate.toString()
+                    + ", " + sportId + ", " + countryId + ", " + leagueId);
+            e.printStackTrace();
+        }
+    }
+
+    public String getLastParsedYear(String leagueName, int countryId) {
+        String returnValue = "";
+
+        final String sql = "SELECT parsedYear FROM League WHERE name = ? AND countryId = ?";
+        try {
+            final PreparedStatement stmt = conn.prepareStatement(sql);
+            stmt.setString(1, leagueName);
+            stmt.setInt(2, countryId);
+
+            final ResultSet rs = stmt.executeQuery();
+
+            while (rs.next()) {
+                returnValue = rs.getString("parsedYear");
+            }
+        } catch (final SQLException e) {
+            e.printStackTrace();
+        }
+
+        return returnValue;
+    }
+
+    public Connection getDbConnection() {
+        if (conn == null) {
+            conn = getConnection();
+        }
+        return conn;
+    }
+}

+ 8 - 0
Odds/src/mysql/RMysql.java

@@ -0,0 +1,8 @@
+package mysql;
+
+public class RMysql extends Mysql {
+
+	public String getSoccerData() {
+		return "";
+	}
+}

+ 52 - 0
Odds/src/object/CurrentParsing.java

@@ -0,0 +1,52 @@
+package object;
+
+import java.util.Date;
+
+public class CurrentParsing {
+
+	private int currentYear;
+	private String league;
+	private Date currentDate;
+	private boolean done;
+	private int page;
+
+	public int getCurrentYear() {
+		return currentYear;
+	}
+	
+	public void setCurrentYear(int currentYear) {
+		this.currentYear = currentYear;
+	}
+	
+	public String getLeague() {
+		return league;
+	}
+	
+	public void setLeague(String league) {
+		this.league = league;
+	}
+	
+	public Date getCurrentDate() {
+		return currentDate;
+	}
+	
+	public void setCurrentDate(Date currentDate) {
+		this.currentDate = currentDate;
+	}
+	
+	public boolean isDone() {
+		return done;
+	}
+	
+	public void setDone(boolean done) {
+		this.done = done;
+	}
+
+	public int getPage() {
+		return page;
+	}
+
+	public void setPage(int page) {
+		this.page = page;
+	}
+}

+ 382 - 0
Odds/src/parser/OddsPortal.java

@@ -0,0 +1,382 @@
+package parser;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
+import com.gargoylesoftware.htmlunit.html.HtmlDivision;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlSpan;
+import com.gargoylesoftware.htmlunit.html.HtmlTable;
+import com.gargoylesoftware.htmlunit.html.HtmlTableCell;
+import com.gargoylesoftware.htmlunit.html.HtmlTableRow;
+import com.google.common.base.Strings;
+
+import mysql.Mysql;
+
+public class OddsPortal implements ParserJoinedFunctions {
+
+    private LocalDateTime baseDate;
+    private int currentParsePage;
+    private int sportId;
+    private int countryId;
+    private int leagueId;
+    private LocalDateTime gameDate;
+
+    public void getYesterdaysMatches() {
+        baseDate = LocalDateTime.now().plusDays(-1);
+        final String date = LocalDate.now().plusDays(-1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        getMatchesByDate(date);
+    }
+
+    public void getTodaysMatches() {
+        baseDate = LocalDateTime.now();
+        final String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        getMatchesByDate(date);
+    }
+
+    public void getTomorrowsMatches() {
+        baseDate = LocalDateTime.now().plusDays(1);
+        final String dateTomorrow = LocalDate.now().plusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        getMatchesByDate(dateTomorrow);
+    }
+
+    public void getNextDaysMatches() {
+        baseDate = LocalDateTime.now().plusDays(2);
+        final String dateTomorrow = LocalDate.now().plusDays(2).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        getMatchesByDate(dateTomorrow);
+    }
+
+    // https://stackoverflow.com/questions/14439991/skip-particular-javascript-execution-in-html-unit
+    // Skip url
+    private void getMatchesByDate(String date) {
+        final String soccerUrl = "https://oddsportal.com/matches/soccer/" + date;
+        // final String hockeyUrl = "https://oddsportal.com/matches/hockey/" + date;
+
+        final WebClient webClient = new WebClient();
+        webClient.getOptions().setUseInsecureSSL(true);
+        webClient.getOptions().setCssEnabled(false);
+        webClient.getOptions().setJavaScriptEnabled(true);
+        webClient.getOptions().setThrowExceptionOnScriptError(false);
+        Logger.getLogger("com.gargoylesoftware").setLevel(Level.OFF);
+
+        webClient.waitForBackgroundJavaScript(3000);
+        parseSoccerMatches(soccerUrl, webClient, date);
+
+        webClient.close();
+    }
+
+    private void parseSoccerMatches(final String soccerUrl, final WebClient webClient, String date) {
+        try {
+            System.out.println("Getting Webpage");
+            final HtmlPage soccerMatches = webClient.getPage(soccerUrl);
+            final HtmlTable matchesTable = soccerMatches.getFirstByXPath("//table[contains(@class, table-main)]");
+            final List<HtmlTableRow> rows = matchesTable.getRows();
+            String countryName = "";
+            String leagueName = "";
+            int i = 1;
+            final int size = rows.size();
+            for (final HtmlTableRow tr : rows) {
+                System.out.println("Processing " + i++ + " of " + size);
+                if (tr.getAttribute("class").equals("dark center")) {
+                    final List<HtmlAnchor> countryLeague = tr.getByXPath(".//a");
+                    countryName = countryLeague.get(0).asNormalizedText().toLowerCase().trim();
+                    leagueName = countryLeague.get(1).asNormalizedText().toLowerCase().trim();
+                    leagueName = leagueName.replaceAll(" ", "-");
+                    leagueName = leagueName.replaceAll("\\.", "");
+                    countryName = countryName.replaceAll(" ", "-");
+                    countryName = countryName.replaceAll("\\.", "");
+                } else {
+                    final List<HtmlTableCell> cells = tr.getCells();
+                    final String[] time = cells.get(0).asNormalizedText().split(":");
+                    final String[] teams = cells.get(1).asNormalizedText().split(" - ");
+                    float odds1 = 0F;
+                    float oddsX = 0F;
+                    float odds2 = 0F;
+                    int homeScore = -1;
+                    int awayScore = -1;
+                    boolean overtime = false;
+
+                    boolean abandon = false;
+
+                    try {
+                        for (final HtmlTableCell tc : cells) {
+                            if (tc.getAttribute("class").contains("live-score")) {
+                                abandon = true;
+                                break;
+                            }
+                            // Score
+                            if (tc.getAttribute("class").contains("table-score")) {
+                                final String[] scoreValue = tc.asNormalizedText().split(":");
+                                homeScore = Integer.valueOf(scoreValue[0]);
+                                if (scoreValue[1].matches("\\D+")) {
+                                    overtime = true;
+                                }
+                                awayScore = Integer.valueOf(scoreValue[1].replaceAll("\\D+", ""));
+                            }
+                            if (tc.getAttribute("class").contains("odds-nowrp")) {
+                                if (tc.asNormalizedText().matches("[+-][0-9][0-9][0-9]")) {
+                                    if (odds1 == 0F) {
+                                        odds1 = convertAmericanOddsToDecimal(Integer.valueOf(tc.asNormalizedText()));
+                                    } else if (oddsX == 0F) {
+                                        oddsX = convertAmericanOddsToDecimal(Integer.valueOf(tc.asNormalizedText()));
+                                    } else if (odds2 == 0F) {
+                                        odds2 = convertAmericanOddsToDecimal(Integer.valueOf(tc.asNormalizedText()));
+                                    }
+                                } else if (tc.asNormalizedText().matches("[0-9].[0-9]+")) {
+                                    if (odds1 == 0F) {
+                                        odds1 = Float.valueOf(tc.asNormalizedText());
+                                    } else if (oddsX == 0F) {
+                                        oddsX = Float.valueOf(tc.asNormalizedText());
+                                    } else if (odds2 == 0F) {
+                                        odds2 = Float.valueOf(tc.asNormalizedText());
+                                    }
+                                }
+                            }
+
+                        }
+                    } catch (final NumberFormatException e) {
+                        System.out.println("Failed to get the match between " + teams[0].trim() + " and " + teams[1].trim()
+                                + " at " + baseDate.withHour(Integer.valueOf(time[0])).withMinute(Integer.valueOf(time[1]))
+                                + " odds1: " + odds1 + " oddsX: " + oddsX + " odds2: " + odds2 + " homeScore " + homeScore
+                                + " awayScore " + awayScore + " overtime: " + (overtime ? "true" : "false"));
+                        continue;
+                    }
+
+                    if (abandon) {
+                        continue;
+                    }
+                    final Mysql mysql = Mysql.getInstance();
+                    final int leagueId = mysql.addLeague(leagueName, countryName, "soccer");
+                    final int countryId = mysql.getCountryId(countryName);
+                    final int sportId = mysql.getSportId("soccer");
+
+                    // String season = mysql.getLastParsedYear(leagueName, countryId); // TODO This
+                    // don't work
+                    String season = String.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyyMMdd")).getYear());
+                    if (Strings.isNullOrEmpty(season)) {
+                        season = String.valueOf(LocalDateTime.now().getYear());
+                    }
+
+                    final LocalDateTime dt = baseDate.withHour(Integer.valueOf(time[0])).withMinute(Integer.valueOf(time[1]))
+                            .withSecond(0).withNano(0);
+                    mysql.addResult("SoccerResults",
+                            dt,
+                            teams[0].trim(),
+                            teams[1].trim(),
+                            homeScore,
+                            awayScore,
+                            overtime,
+                            odds1,
+                            oddsX,
+                            odds2,
+                            countryId,
+                            season,
+                            leagueId,
+                            sportId);
+                }
+            }
+        } catch (FailingHttpStatusCodeException | IOException e) {
+            e.printStackTrace();
+        } catch (final SQLException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void getHistoricMatches(String sport, String country, String league, String year) {
+        final String url = "https://www.oddsportal.com/";
+        final String resultsPage = "/results";
+        final WebClient webClient = new WebClient();
+        webClient.getOptions().setUseInsecureSSL(true);
+        webClient.getOptions().setCssEnabled(false);
+        webClient.getOptions().setJavaScriptEnabled(true);
+        webClient.getOptions().setThrowExceptionOnScriptError(false);
+        Logger.getLogger("com.gargoylesoftware").setLevel(Level.OFF);
+
+        league = league.replaceAll(" ", "-");
+        league = league.replaceAll("\\.", "");
+        country = country.replaceAll(" ", "-");
+        league = league.replaceAll("\\.", "");
+        final Mysql mysql = Mysql.getInstance();
+
+        currentParsePage = 1;
+
+        final String urlYearPart;
+        if (year.equals(String.valueOf(LocalDate.now().getYear()))) {
+            urlYearPart = "";
+        } else {
+            urlYearPart = "-" + year;
+        }
+
+        try {
+            sportId = mysql.getSportId(sport);
+            countryId = mysql.getCountryId(country);
+            leagueId = mysql.getLeagueId(sportId, countryId, league);
+            String season = "";
+
+            final HtmlPage leaguePage
+                    = webClient.getPage(url + "/" + sport + "/" + country + "/" + league + urlYearPart + resultsPage);
+            final List<HtmlAnchor> yearFilter = leaguePage.getByXPath("//ul[contains(@class,'main-filter')]//a");
+            for (final HtmlAnchor a : yearFilter) {
+                System.out.println("Year filter: " + a.getHrefAttribute());
+                final String active = ((HtmlSpan) a.getParentNode().getParentNode()).getAttribute("class");
+                if (active.contains("active") && !active.contains("inactive")) {
+                    season = a.asNormalizedText();
+                    year = season.replace('/', '-');
+                }
+            }
+
+            HtmlDivision tournamentTableDiv = leaguePage.getHtmlElementById("tournamentTable");
+            HtmlTable tournamentTable = (HtmlTable) tournamentTableDiv.getFirstChild();
+
+            gameDate = LocalDateTime.now();
+            final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd MMM yyyy", Locale.ENGLISH);
+            parseTournamentTable(sportId, countryId, leagueId, season, tournamentTable, gameDate, dateFormatter);
+            final HtmlDivision paginationLinksDiv = (HtmlDivision) tournamentTableDiv.getLastChild();
+            final List<HtmlAnchor> pagiantionLinks
+                    = paginationLinksDiv.getByXPath(".//a[contains(@href, 'page') and not(.//span[contains(@class, 'arrow')])]");
+            for (final HtmlAnchor a : pagiantionLinks) {
+                System.out.println("Continuing with Pagination: " + a.getHrefAttribute());
+                // When done with start page click pagiantion
+                final int parsePage = Integer.valueOf(a.getTextContent());
+                if (parsePage > currentParsePage) {
+                    a.click();
+                    webClient.waitForBackgroundJavaScript(1000);
+
+                    tournamentTableDiv = leaguePage.getHtmlElementById("tournamentTable");
+                    tournamentTable = (HtmlTable) tournamentTableDiv.getFirstChild();
+                    parseTournamentTable(sportId, countryId, leagueId, season, tournamentTable, gameDate, dateFormatter);
+                    currentParsePage = parsePage;
+                }
+                // process new tournament table content
+            }
+        } catch (FailingHttpStatusCodeException | IOException e) {
+            e.printStackTrace();
+        } catch (final SQLException sqle) {
+            sqle.printStackTrace();
+        } catch (final ClassCastException cce) {
+            System.out.println("No pagination table");
+            // cce.printStackTrace();
+        } finally {
+            Mysql.getInstance().setParsingForLeague(leagueId, sportId, countryId, gameDate, currentParsePage, year);
+        }
+        webClient.close();
+        System.out.println("DONE with " + country + " (" + countryId + ") league " + league + "(" + leagueId + ")");
+    }
+
+    private void parseTournamentTable(int sportId, int countryId, int leagueId, String season, HtmlTable tournamentTable,
+            LocalDateTime gameDate, DateTimeFormatter dateFormatter) throws SQLException {
+        for (final HtmlTableRow tr : tournamentTable.getRows()) {
+            if (tr.getAttribute("class").contains("deactivate")) {
+                String homeTeam;
+                String awayTeam;
+                int homeScore = -1;
+                int awayScore = -1;
+                float odds1 = 0f;
+                float oddsX = 0f;
+                float odds2 = 0f;
+                boolean overtime = false;
+
+                final HtmlTableCell timeCell = tr.getCell(0);
+                final HtmlTableCell participantsCell = tr.getCell(1);
+
+                // Game Time
+                final String[] timeValue = timeCell.asNormalizedText().split(":");
+                gameDate = gameDate.withHour(Integer.valueOf(timeValue[0]));
+                gameDate = gameDate.withMinute(Integer.valueOf(timeValue[1]));
+
+                // Teams
+                final String[] participantsValue = participantsCell.asNormalizedText().split(" - ");
+                homeTeam = participantsValue[0].trim();
+                awayTeam = participantsValue[1].trim();
+
+                final List<HtmlTableCell> cells = tr.getCells();
+                for (final HtmlTableCell tc : cells) {
+                    // Score
+                    if (tc.getAttribute("class").contains("table-score")) {
+                        final String[] scoreValue = tc.asNormalizedText().split(":");
+                        if (scoreValue[0].matches("\\D+")) {
+                            continue;
+                        }
+                        homeScore = Integer.valueOf(scoreValue[0]);
+                        if (scoreValue[1].matches("\\D+")) {
+                            overtime = true;
+                        }
+                        awayScore = Integer.valueOf(scoreValue[1].replaceAll("\\D+", ""));
+                    }
+
+                    if (tc.getAttribute("class").contains("odds-nowrp")) {
+                        if (tc.asNormalizedText().matches("[+-][0-9][0-9][0-9]")) {
+                            if (odds1 == 0F) {
+                                odds1 = convertAmericanOddsToDecimal(Integer.valueOf(tc.asNormalizedText()));
+                            } else if (oddsX == 0F) {
+                                oddsX = convertAmericanOddsToDecimal(Integer.valueOf(tc.asNormalizedText()));
+                            } else if (odds2 == 0F) {
+                                odds2 = convertAmericanOddsToDecimal(Integer.valueOf(tc.asNormalizedText()));
+                            }
+                        } else if (tc.asNormalizedText().matches("[0-9].[0-9]+")) {
+                            if (odds1 == 0F) {
+                                odds1 = Float.valueOf(tc.asNormalizedText());
+                            } else if (oddsX == 0F) {
+                                oddsX = Float.valueOf(tc.asNormalizedText());
+                            } else if (odds2 == 0F) {
+                                odds2 = Float.valueOf(tc.asNormalizedText());
+                            }
+                        }
+                    }
+                }
+
+                if (gameDate != null && homeTeam != null && awayTeam != null && odds1 != 0 && oddsX != 0 && odds2 != 0
+                        && !Strings.isNullOrEmpty(season)) { // All set.. update sql result table
+                    System.out.println("Adding game between " + homeTeam + " and " + awayTeam + " with score " + homeScore + "-"
+                            + awayScore);
+                    Mysql.getInstance().addResult("SoccerResults",
+                            gameDate,
+                            homeTeam,
+                            awayTeam,
+                            homeScore,
+                            awayScore,
+                            overtime,
+                            odds1,
+                            oddsX,
+                            odds2,
+                            countryId,
+                            season,
+                            leagueId,
+                            sportId);
+                } else {
+                    System.out.println(String.format(
+                            "Not adding, missing somethind.. gameDate: %s, homeTeam %s, awayTeam %s, odds1 %s, oddsX %s, odds2 %s, "
+                                    + "season %s",
+                            gameDate,
+                            homeTeam,
+                            awayTeam,
+                            odds1,
+                            oddsX,
+                            odds2,
+                            season));
+                }
+
+            } else if (tr.getAttribute("class").contains("center nob-border")) { // Datum rader
+                final List<HtmlSpan> dateSpan = tr.getByXPath(".//span[contains(@class, 'datet')]");
+                final String dateString = dateSpan.get(0).asNormalizedText();
+                if (dateString.toLowerCase().contains("yesterday")) {
+                    gameDate = LocalDateTime.now().minusDays(1);
+                } else if (dateString.toLowerCase().contains("today")) {
+                    gameDate = LocalDateTime.now();
+                } else {
+                    gameDate = LocalDate.parse(dateString, dateFormatter).atStartOfDay();
+                }
+            }
+        }
+    }
+}

+ 14 - 0
Odds/src/parser/ParserJoinedFunctions.java

@@ -0,0 +1,14 @@
+package parser;
+
+public interface ParserJoinedFunctions {
+
+	default float convertAmericanOddsToDecimal(int americanOdds) {
+		final float converted;
+		if (americanOdds < 0) {
+			converted = 1 - (100f / americanOdds);
+		} else {
+			converted = (americanOdds / 100f) + 1;
+		}
+		return converted;
+	}
+}