simulator: quadratic price function, random bets, change table cols

This commit is contained in:
mantikoros 2021-12-12 17:06:37 -06:00
parent 45640feb81
commit cfd2f22254
2 changed files with 95 additions and 58 deletions

View File

@ -17,16 +17,16 @@ function makeWeights(bids: Bid[]) {
const weights = [] const weights = []
let yesPot = 0 let yesPot = 0
let noPot = 0 let noPot = 0
// First pass: calculate all the weights // First pass: calculate all the weights
for (const { yesBid, noBid } of bids) { for (const { yesBid, noBid } of bids) {
const yesWeight = const yesWeight = yesBid * Math.pow(noPot, 2) / (Math.pow(yesPot, 2) + yesBid * yesPot) || 0
noPot * (Math.log(yesBid + yesPot) - Math.log(yesPot)) || 0 const noWeight = noBid * Math.pow(yesPot, 2) / (Math.pow(noPot, 2) + noBid * noPot) || 0
const noWeight = yesPot * (Math.log(noBid + noPot) - Math.log(noPot)) || 0
// Note: Need to calculate weights BEFORE updating pot // Note: Need to calculate weights BEFORE updating pot
yesPot += yesBid yesPot += yesBid
noPot += noBid noPot += noBid
const prob = yesPot / (yesPot + noPot) const prob = Math.pow(yesPot, 2) / (Math.pow(yesPot, 2) + Math.pow(noPot, 2))
weights.push({ weights.push({
yesBid, yesBid,
@ -42,13 +42,16 @@ function makeWeights(bids: Bid[]) {
export function makeEntries(bids: Bid[]): Entry[] { export function makeEntries(bids: Bid[]): Entry[] {
const YES_SEED = bids[0].yesBid const YES_SEED = bids[0].yesBid
const NO_SEED = bids[0].noBid const NO_SEED = bids[0].noBid
const weights = makeWeights(bids) const weights = makeWeights(bids)
const yesPot = weights.reduce((sum, { yesBid }) => sum + yesBid, 0) const yesPot = weights.reduce((sum, { yesBid }) => sum + yesBid, 0)
const noPot = weights.reduce((sum, { noBid }) => sum + noBid, 0) const noPot = weights.reduce((sum, { noBid }) => sum + noBid, 0)
const yesWeightsSum = weights.reduce((sum, entry) => sum + entry.yesWeight, 0) const yesWeightsSum = weights.reduce((sum, entry) => sum + entry.yesWeight, 0)
const noWeightsSum = weights.reduce((sum, entry) => sum + entry.noWeight, 0) const noWeightsSum = weights.reduce((sum, entry) => sum + entry.noWeight, 0)
// Second pass: calculate all the payouts // Second pass: calculate all the payouts
const entries: Entry[] = [] const entries: Entry[] = []
for (const weight of weights) { for (const weight of weights) {
const { yesBid, noBid, yesWeight, noWeight } = weight const { yesBid, noBid, yesWeight, noWeight } = weight
// Payout: You get your initial bid back, as well as your share of the // Payout: You get your initial bid back, as well as your share of the

View File

@ -1,4 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react' import React, { useEffect, useMemo, useState } from 'react'
import { Line } from 'react-chartjs-2'
import { import {
CategoryScale, CategoryScale,
Chart, Chart,
@ -9,8 +10,7 @@ import {
Tooltip, Tooltip,
Legend, Legend,
} from 'chart.js' } from 'chart.js'
import { Line } from 'react-chartjs-2'
import { bids as sampleBids } from '../../lib/simulator/sample-bids'
import { Entry, makeEntries } from '../../lib/simulator/entries' import { Entry, makeEntries } from '../../lib/simulator/entries'
import { Header } from '../../components/header' import { Header } from '../../components/header'
@ -31,7 +31,7 @@ function TableBody(props: { entries: Entry[] }) {
<tbody> <tbody>
{props.entries.map((entry, i) => ( {props.entries.map((entry, i) => (
<tr key={i}> <tr key={i}>
<th>{i + 1}</th> <th>{props.entries.length - i}</th>
<TableRowStart entry={entry} /> <TableRowStart entry={entry} />
<TableRowEnd entry={entry} /> <TableRowEnd entry={entry} />
</tr> </tr>
@ -46,10 +46,10 @@ function TableRowStart(props: { entry: Entry }) {
return ( return (
<> <>
<td> <td>
<div className="badge">SEED</div> <div className="badge">ANTE</div>
</td> </td>
<td> <td>
{entry.yesBid} / {entry.noBid} ${entry.yesBid} / ${entry.noBid}
</td> </td>
</> </>
) )
@ -59,7 +59,7 @@ function TableRowStart(props: { entry: Entry }) {
<td> <td>
<div className="badge badge-success">YES</div> <div className="badge badge-success">YES</div>
</td> </td>
<td>{entry.yesBid}</td> <td>${entry.yesBid}</td>
</> </>
) )
} else { } else {
@ -68,48 +68,56 @@ function TableRowStart(props: { entry: Entry }) {
<td> <td>
<div className="badge badge-error">NO</div> <div className="badge badge-error">NO</div>
</td> </td>
<td>{entry.noBid}</td> <td>${entry.noBid}</td>
</> </>
) )
} }
} }
function TableRowEnd(props: { entry: Entry | null }) { function TableRowEnd(props: { entry: Entry | null, isNew?: boolean }) {
const { entry } = props const { entry } = props
if (!entry) { if (!entry) {
return ( return (
<> <>
<td>N/A</td> <td>0</td>
<td>N/A</td> <td>0</td>
<td>N/A</td> {!props.isNew && <>
<td>N/A</td> <td>N/A</td>
<td>N/A</td>
</>}
</> </>
) )
} else if (entry.yesBid && entry.noBid) { } else if (entry.yesBid && entry.noBid) {
return ( return (
<> <>
<td>{(entry.prob * 100).toFixed(1)}%</td>
<td>N/A</td> <td>N/A</td>
<td>{entry.prob.toFixed(2)}</td> {!props.isNew && <>
<td>N/A</td> <td>N/A</td>
<td>N/A</td> <td>N/A</td>
</>}
</> </>
) )
} else if (entry.yesBid) { } else if (entry.yesBid) {
return ( return (
<> <>
<td>{entry.yesWeight.toFixed(2)}</td> <td>{(entry.prob * 100).toFixed(1)}%</td>
<td>{entry.prob.toFixed(2)}</td> <td>${(entry.yesBid + entry.yesWeight).toFixed(0)}</td>
<td>{entry.yesPayout.toFixed(2)}</td> {!props.isNew && <>
<td>{(entry.yesReturn * 100).toFixed(2)}%</td> <td>${entry.yesPayout.toFixed(0)}</td>
<td>{(entry.yesReturn * 100).toFixed(0)}%</td>
</>}
</> </>
) )
} else { } else {
return ( return (
<> <>
<td>{entry.noWeight.toFixed(2)}</td> <td>{(entry.prob * 100).toFixed(1)}%</td>
<td>{entry.prob.toFixed(2)}</td> <td>${(entry.noBid + entry.noWeight).toFixed(0)}</td>
<td>{entry.noPayout.toFixed(2)}</td> {!props.isNew && <>
<td>{(entry.noReturn * 100).toFixed(2)}%</td> <td>${entry.noPayout.toFixed(0)}</td>
<td>{(entry.noReturn * 100).toFixed(0)}%</td>
</>}
</> </>
) )
} }
@ -142,30 +150,39 @@ function NewBidTable(props: {
setNewBid(0) setNewBid(0)
} }
function toggleBidType() { function toggleBidType() {
setNewBidType(newBidType === 'YES' ? 'NO' : 'YES') setNewBidType(newBidType === 'YES' ? 'NO' : 'YES')
} }
let nextEntry: Entry | null = null const nextBid = makeBid(newBidType, newBid)
const fakeBids = [...bids.slice(0, steps), nextBid]
const entries = makeEntries(fakeBids)
const nextEntry = entries[entries.length - 1]
if (newBid) { function randomBid() {
const nextBid = makeBid(newBidType, newBid) const bidType = Math.random() < 0.5
const fakeBids = [...bids.slice(0, steps), nextBid] ? 'YES'
const entries = makeEntries(fakeBids) : 'NO'
nextEntry = entries[entries.length - 1] const amount = Math.round(Math.random() * 500)
const bid = makeBid(bidType, amount)
bids.splice(steps, 0, bid)
setBids(bids)
setSteps(steps + 1)
setNewBid(0)
} }
return ( return <>
<table className="table table-compact my-8 w-full text-center"> <table className="table table-compact my-8 w-full text-center">
<thead> <thead>
<tr> <tr>
<th>Order #</th> <th>Order #</th>
<th>Type</th> <th>Type</th>
<th>Bid</th> <th>Bet</th>
<th>Weight</th>
<th>Prob</th> <th>Prob</th>
<th>Payout</th> <th>Est Payout</th>
<th>Return</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -200,7 +217,7 @@ function NewBidTable(props: {
placeholder="0" placeholder="0"
className="input input-bordered" className="input input-bordered"
style={{ maxWidth: 100 }} style={{ maxWidth: 100 }}
value={newBid} value={newBid.toString()}
onChange={(e) => setNewBid(parseInt(e.target.value) || 0)} onChange={(e) => setNewBid(parseInt(e.target.value) || 0)}
onKeyUp={(e) => { onKeyUp={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
@ -210,7 +227,12 @@ function NewBidTable(props: {
onFocus={(e) => e.target.select()} onFocus={(e) => e.target.select()}
/> />
</td> </td>
<TableRowEnd entry={nextEntry} />
<TableRowEnd
entry={nextEntry}
isNew
/>
<td> <td>
<button <button
className="btn btn-primary" className="btn btn-primary"
@ -219,26 +241,37 @@ function NewBidTable(props: {
> >
Submit Submit
</button> </button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
)
<button
className="btn btn-secondary mb-4"
onClick={randomBid}
>
Random bet!
</button>
</>
} }
// Show a hello world React page // Show a hello world React page
export default function Simulator() { export default function Simulator() {
const [steps, setSteps] = useState(10) const [steps, setSteps] = useState(1)
const [bids, setBids] = useState(sampleBids) const [bids, setBids] = useState([{ yesBid: 600, noBid: 400 }])
const entries = useMemo( const entries = useMemo(
() => makeEntries(bids.slice(0, steps)), () => makeEntries(bids.slice(0, steps)),
[bids, steps] [bids, steps]
) )
const reversedEntries = [...entries].reverse()
const probs = entries.map((entry) => entry.prob) const probs = entries.map((entry) => entry.prob)
const chartData = { const chartData = {
labels: Array.from({ length: steps }, (_, i) => i + 1), labels: Array.from({ length: steps }, (_, i) => 1 + i),
datasets: [ datasets: [
{ {
label: 'Implied probability', label: 'Implied probability',
@ -257,16 +290,7 @@ export default function Simulator() {
<h1 className="text-2xl font-bold mb-8"> <h1 className="text-2xl font-bold mb-8">
Dynamic Parimutuel Market Simulator Dynamic Parimutuel Market Simulator
</h1> </h1>
{/* Range slider that sets the current step */}
<label>Simulation step: {steps}</label>
<input
type="range"
className="range"
min="1"
max={bids.length}
value={steps}
onChange={(e) => setSteps(parseInt(e.target.value))}
/>
<NewBidTable {...{ steps, bids, setSteps, setBids }} /> <NewBidTable {...{ steps, bids, setSteps, setBids }} />
@ -277,15 +301,15 @@ export default function Simulator() {
<tr> <tr>
<th>Order #</th> <th>Order #</th>
<th>Type</th> <th>Type</th>
<th>Bid</th> <th>Bet</th>
<th>Weight</th>
<th>Prob</th> <th>Prob</th>
<th>Max Payout</th> <th>Est Payout</th>
<th>Payout</th>
<th>Return</th> <th>Return</th>
</tr> </tr>
</thead> </thead>
<TableBody entries={entries} /> <TableBody entries={reversedEntries} />
</table> </table>
</div> </div>
</div> </div>
@ -297,6 +321,16 @@ export default function Simulator() {
<div className="badge badge-success text-2xl h-8 w-18">YES</div> <div className="badge badge-success text-2xl h-8 w-18">YES</div>
</h1> </h1>
<Line data={chartData} height={200} /> <Line data={chartData} height={200} />
{/* Range slider that sets the current step */}
<label>Orders # 1 - {steps}</label>
<input
type="range"
className="range"
min="1"
max={bids.length}
value={steps}
onChange={(e) => setSteps(parseInt(e.target.value))}
/>
</div> </div>
</div> </div>
</div> </div>