simulator: quadratic price function, random bets, change table cols
This commit is contained in:
parent
45640feb81
commit
cfd2f22254
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user