updated to matched betting and partial matched betting
This commit is contained in:
parent
ac646be4e3
commit
140a2de655
2 changed files with 132 additions and 6 deletions
|
|
@ -5,14 +5,40 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Bets and Odds</title>
|
<title>Bets and Odds</title>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<style>
|
||||||
|
.notification {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.notification.info {
|
||||||
|
color: #31708f;
|
||||||
|
background-color: #d9edf7;
|
||||||
|
border-color: #bce8f1;
|
||||||
|
}
|
||||||
|
.notification.success {
|
||||||
|
color: #3c763d;
|
||||||
|
background-color: #dff0d8;
|
||||||
|
border-color: #d6e9c6;
|
||||||
|
}
|
||||||
|
.notification.error {
|
||||||
|
color: #a94442;
|
||||||
|
background-color: #f2dede;
|
||||||
|
border-color: #ebccd1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Betting System</h1>
|
<h1>Betting System</h1>
|
||||||
|
<div id="notification" class="notification info" style="display: none;"></div>
|
||||||
<form id="betForm">
|
<form id="betForm">
|
||||||
<label for="userId">User ID:</label>
|
<label for="userId">User ID:</label>
|
||||||
<input type="number" id="userId" name="userId" required>
|
<input type="number" id="userId" name="userId" required>
|
||||||
<label for="team">Team:</label>
|
<label for="team">Team:</label>
|
||||||
<input type="number" id="team" name="team" required>
|
<button type="button" id="teamAButton" data-team="0">Team A</button>
|
||||||
|
<button type="button" id="teamBButton" data-team="1">Team B</button>
|
||||||
|
<input type="hidden" id="team" name="team" required>
|
||||||
<label for="cashAmount">Cash Amount:</label>
|
<label for="cashAmount">Cash Amount:</label>
|
||||||
<input type="number" id="cashAmount" name="cashAmount" required>
|
<input type="number" id="cashAmount" name="cashAmount" required>
|
||||||
<button type="submit">Place Bet</button>
|
<button type="submit">Place Bet</button>
|
||||||
|
|
@ -34,16 +60,60 @@
|
||||||
|
|
||||||
<h2>All Bets</h2>
|
<h2>All Bets</h2>
|
||||||
<div id="allBetsContainer"></div>
|
<div id="allBetsContainer"></div>
|
||||||
|
<p>Fulfilled Bets: <span id="fulfilledCounter">0</span></p>
|
||||||
|
<p>Waiting to be Fulfilled: <span id="unfulfilledCounter">0</span></p>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const betForm = document.getElementById('betForm');
|
const betForm = document.getElementById('betForm');
|
||||||
const oddsContainer = document.getElementById('oddsContainer');
|
const oddsContainer = document.getElementById('oddsContainer');
|
||||||
const oddsOnOfferContainer = document.getElementById('oddsOnOfferContainer');
|
const oddsOnOfferContainer = document.getElementById('oddsOnOfferContainer');
|
||||||
const allBetsContainer = document.getElementById('allBetsContainer');
|
const allBetsContainer = document.getElementById('allBetsContainer');
|
||||||
|
const notification = document.getElementById('notification');
|
||||||
|
const teamAButton = document.getElementById('teamAButton');
|
||||||
|
const teamBButton = document.getElementById('teamBButton');
|
||||||
|
const teamInput = document.getElementById('team');
|
||||||
const oddsForm = document.getElementById('oddsForm');
|
const oddsForm = document.getElementById('oddsForm');
|
||||||
|
const fulfilledCounter = document.getElementById('fulfilledCounter');
|
||||||
|
const unfulfilledCounter = document.getElementById('unfulfilledCounter');
|
||||||
const ctx = document.getElementById('bettingChart').getContext('2d');
|
const ctx = document.getElementById('bettingChart').getContext('2d');
|
||||||
let chart;
|
let chart;
|
||||||
|
|
||||||
|
const showNotification = (message, type) => {
|
||||||
|
notification.textContent = message;
|
||||||
|
notification.className = `notification ${type}`;
|
||||||
|
notification.style.display = 'block';
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideNotification = () => {
|
||||||
|
notification.style.display = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkBetAvailability = async () => {
|
||||||
|
const response = await fetch('/bets');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
const unmatchedBets = data.unmatchedBets;
|
||||||
|
const opposingBetAvailable = unmatchedBets.some(bet => bet.team !== parseInt(teamInput.value));
|
||||||
|
|
||||||
|
betForm.querySelector('button[type="submit"]').disabled = !opposingBetAvailable;
|
||||||
|
|
||||||
|
if (!opposingBetAvailable) {
|
||||||
|
showNotification('Bets on this team are disabled until there is a matching bet on the other team.', 'info');
|
||||||
|
} else {
|
||||||
|
hideNotification();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
teamAButton.addEventListener('click', () => {
|
||||||
|
teamInput.value = 0;
|
||||||
|
checkBetAvailability();
|
||||||
|
});
|
||||||
|
|
||||||
|
teamBButton.addEventListener('click', () => {
|
||||||
|
teamInput.value = 1;
|
||||||
|
checkBetAvailability();
|
||||||
|
});
|
||||||
|
|
||||||
betForm.addEventListener('submit', async (e) => {
|
betForm.addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const userId = document.getElementById('userId').value;
|
const userId = document.getElementById('userId').value;
|
||||||
|
|
@ -56,6 +126,16 @@
|
||||||
updateOdds(data.odds);
|
updateOdds(data.odds);
|
||||||
updateChart(data.odds);
|
updateChart(data.odds);
|
||||||
updateAllBets(data.allBets);
|
updateAllBets(data.allBets);
|
||||||
|
updateCounters(data.allBets);
|
||||||
|
checkBetAvailability();
|
||||||
|
|
||||||
|
if (data.allBets.some(bet => bet.userId === parseInt(userId) && bet.status === 'matched')) {
|
||||||
|
showNotification('Your bet has been successfully fulfilled!', 'success');
|
||||||
|
} else if (data.allBets.some(bet => bet.userId === parseInt(userId) && bet.status === 'partially matched')) {
|
||||||
|
showNotification('Your bet has been partially fulfilled and is waiting to be fully matched.', 'info');
|
||||||
|
} else {
|
||||||
|
showNotification('Your bet is waiting to be fulfilled.', 'info');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
oddsForm.addEventListener('submit', async (e) => {
|
oddsForm.addEventListener('submit', async (e) => {
|
||||||
|
|
@ -116,10 +196,21 @@
|
||||||
allBetsContainer.innerHTML = '';
|
allBetsContainer.innerHTML = '';
|
||||||
bets.forEach(bet => {
|
bets.forEach(bet => {
|
||||||
const p = document.createElement('p');
|
const p = document.createElement('p');
|
||||||
p.textContent = `User ${bet.userId} bet ${bet.amount} on Team ${bet.team} at odds ${bet.odds}`;
|
p.textContent = `User ${bet.userId} bet ${bet.amount} on Team ${bet.team} at odds ${bet.odds} (${bet.status})`;
|
||||||
allBetsContainer.appendChild(p);
|
allBetsContainer.appendChild(p);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateCounters(bets) {
|
||||||
|
const fulfilledBets = bets.filter(bet => bet.status === 'matched').length;
|
||||||
|
const unfulfilledBets = bets.filter(bet => bet.status === 'unmatched' || bet.status === 'partially matched').length;
|
||||||
|
|
||||||
|
fulfilledCounter.textContent = fulfilledBets;
|
||||||
|
unfulfilledCounter.textContent = unfulfilledBets;
|
||||||
|
}
|
||||||
|
|
||||||
|
teamInput.addEventListener('input', checkBetAvailability);
|
||||||
|
checkBetAvailability();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ const app = express()
|
||||||
const port = 3000
|
const port = 3000
|
||||||
|
|
||||||
const fee = 0.05 // 0.01 = 1%
|
const fee = 0.05 // 0.01 = 1%
|
||||||
const initialLiquidity = 1000 // Initial liquidity for the market
|
const initialLiquidity = 0 // Initial liquidity for the market
|
||||||
|
|
||||||
interface Balances { [key: number]: number }
|
interface Balances { [key: number]: number }
|
||||||
|
|
||||||
|
|
@ -13,12 +13,15 @@ interface Bet {
|
||||||
userId: number
|
userId: number
|
||||||
team: number
|
team: number
|
||||||
amount: number
|
amount: number
|
||||||
|
remainingAmount: number
|
||||||
odds: number[]
|
odds: number[]
|
||||||
|
status: 'unmatched' | 'partially matched' | 'matched'
|
||||||
}
|
}
|
||||||
|
|
||||||
const userBalances: Balances = { 1: 1000, 2: 1000, 3: 1000, 4: 1000 }
|
const userBalances: Balances = { 1: 1000, 2: 1000, 3: 1000, 4: 1000 }
|
||||||
let teamFunds: Balances = { 0: initialLiquidity, 1: initialLiquidity } // Initial funds for each team
|
let teamFunds: Balances = { 0: initialLiquidity, 1: initialLiquidity } // Initial funds for each team
|
||||||
const bets: Bet[] = []
|
const bets: Bet[] = []
|
||||||
|
const unmatchedBets: Bet[] = []
|
||||||
|
|
||||||
function calculateTotalFunds (funds: Balances): number {
|
function calculateTotalFunds (funds: Balances): number {
|
||||||
return Object.values(funds).reduce((acc: number, val: number) => acc + val, 0)
|
return Object.values(funds).reduce((acc: number, val: number) => acc + val, 0)
|
||||||
|
|
@ -40,6 +43,32 @@ function calculateCost (funds: Balances): number {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function matchBet (bet: Bet): Bet | null {
|
||||||
|
for (let i = 0; i < unmatchedBets.length; i++) {
|
||||||
|
const unmatchedBet = unmatchedBets[i]
|
||||||
|
if (unmatchedBet.team !== bet.team && unmatchedBet.remainingAmount > 0) {
|
||||||
|
const matchedAmount = Math.min(bet.remainingAmount, unmatchedBet.remainingAmount)
|
||||||
|
unmatchedBet.remainingAmount -= matchedAmount
|
||||||
|
bet.remainingAmount -= matchedAmount
|
||||||
|
|
||||||
|
if (unmatchedBet.remainingAmount === 0) {
|
||||||
|
unmatchedBet.status = 'matched'
|
||||||
|
unmatchedBets.splice(i, 1)
|
||||||
|
} else {
|
||||||
|
unmatchedBet.status = 'partially matched'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bet.remainingAmount === 0) {
|
||||||
|
bet.status = 'matched'
|
||||||
|
return unmatchedBet
|
||||||
|
} else {
|
||||||
|
bet.status = 'partially matched'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
function placeBet (userBalances: Balances, funds: Balances, userId: number, team: number, cashAmount: number): Balances {
|
function placeBet (userBalances: Balances, funds: Balances, userId: number, team: number, cashAmount: number): Balances {
|
||||||
const initialCost = calculateCost(funds)
|
const initialCost = calculateCost(funds)
|
||||||
|
|
||||||
|
|
@ -72,10 +101,15 @@ function placeBet (userBalances: Balances, funds: Balances, userId: number, team
|
||||||
|
|
||||||
userBalances[userId] -= transactionCost
|
userBalances[userId] -= transactionCost
|
||||||
|
|
||||||
// Record the bet with the current odds
|
|
||||||
const odds = calculateOdds(newFunds)
|
const odds = calculateOdds(newFunds)
|
||||||
bets.push({ userId, team, amount: cashAmount, odds })
|
const newBet: Bet = { userId, team, amount: cashAmount, remainingAmount: cashAmount, odds, status: 'unmatched' }
|
||||||
|
|
||||||
|
const matchedBet = matchBet(newBet)
|
||||||
|
if (matchedBet == null) {
|
||||||
|
unmatchedBets.push(newBet)
|
||||||
|
}
|
||||||
|
|
||||||
|
bets.push(newBet)
|
||||||
return newFunds
|
return newFunds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +147,8 @@ app.get('/bets', (req, res) => {
|
||||||
odds,
|
odds,
|
||||||
yourBets: bets.filter(bet => bet.userId === userId),
|
yourBets: bets.filter(bet => bet.userId === userId),
|
||||||
allBets: bets,
|
allBets: bets,
|
||||||
totalFunds
|
totalFunds,
|
||||||
|
unmatchedBets
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue