remove simulator
This commit is contained in:
parent
c0383bcf26
commit
085b9aeb2a
|
@ -1,73 +0,0 @@
|
||||||
type Bid = { yesBid: number; noBid: number }
|
|
||||||
|
|
||||||
// An entry has a yes/no for bid, weight, payout, return. Also a current probability
|
|
||||||
export type Entry = {
|
|
||||||
yesBid: number
|
|
||||||
noBid: number
|
|
||||||
yesWeight: number
|
|
||||||
noWeight: number
|
|
||||||
yesPayout: number
|
|
||||||
noPayout: number
|
|
||||||
yesReturn: number
|
|
||||||
noReturn: number
|
|
||||||
prob: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeWeights(bids: Bid[]) {
|
|
||||||
const weights = []
|
|
||||||
let yesPot = 0
|
|
||||||
let noPot = 0
|
|
||||||
|
|
||||||
// First pass: calculate all the weights
|
|
||||||
for (const { yesBid, noBid } of bids) {
|
|
||||||
const yesWeight =
|
|
||||||
yesBid +
|
|
||||||
(yesBid * Math.pow(noPot, 2)) /
|
|
||||||
(Math.pow(yesPot, 2) + yesBid * yesPot) || 0
|
|
||||||
const noWeight =
|
|
||||||
noBid +
|
|
||||||
(noBid * Math.pow(yesPot, 2)) / (Math.pow(noPot, 2) + noBid * noPot) ||
|
|
||||||
0
|
|
||||||
|
|
||||||
// Note: Need to calculate weights BEFORE updating pot
|
|
||||||
yesPot += yesBid
|
|
||||||
noPot += noBid
|
|
||||||
const prob =
|
|
||||||
Math.pow(yesPot, 2) / (Math.pow(yesPot, 2) + Math.pow(noPot, 2))
|
|
||||||
|
|
||||||
weights.push({
|
|
||||||
yesBid,
|
|
||||||
noBid,
|
|
||||||
yesWeight,
|
|
||||||
noWeight,
|
|
||||||
prob,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return weights
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeEntries(bids: Bid[]): Entry[] {
|
|
||||||
const YES_SEED = bids[0].yesBid
|
|
||||||
const NO_SEED = bids[0].noBid
|
|
||||||
|
|
||||||
const weights = makeWeights(bids)
|
|
||||||
const yesPot = weights.reduce((sum, { yesBid }) => sum + yesBid, 0)
|
|
||||||
const noPot = weights.reduce((sum, { noBid }) => sum + noBid, 0)
|
|
||||||
const yesWeightsSum = weights.reduce((sum, entry) => sum + entry.yesWeight, 0)
|
|
||||||
const noWeightsSum = weights.reduce((sum, entry) => sum + entry.noWeight, 0)
|
|
||||||
|
|
||||||
const potSize = yesPot + noPot - YES_SEED - NO_SEED
|
|
||||||
|
|
||||||
// Second pass: calculate all the payouts
|
|
||||||
const entries: Entry[] = []
|
|
||||||
|
|
||||||
for (const weight of weights) {
|
|
||||||
const { yesBid, noBid, yesWeight, noWeight } = weight
|
|
||||||
const yesPayout = (yesWeight / yesWeightsSum) * potSize
|
|
||||||
const noPayout = (noWeight / noWeightsSum) * potSize
|
|
||||||
const yesReturn = (yesPayout - yesBid) / yesBid
|
|
||||||
const noReturn = (noPayout - noBid) / noBid
|
|
||||||
entries.push({ ...weight, yesPayout, noPayout, yesReturn, noReturn })
|
|
||||||
}
|
|
||||||
return entries
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
const data = `1,9
|
|
||||||
8,
|
|
||||||
,1
|
|
||||||
1,
|
|
||||||
,1
|
|
||||||
1,
|
|
||||||
,5
|
|
||||||
5,
|
|
||||||
,5
|
|
||||||
5,
|
|
||||||
,1
|
|
||||||
1,
|
|
||||||
100,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,
|
|
||||||
,10
|
|
||||||
10,`
|
|
||||||
|
|
||||||
// Parse data into Yes/No orders
|
|
||||||
// E.g. `8,\n,1\n1,` =>
|
|
||||||
// [{yesBid: 8, noBid: 0}, {yesBid: 0, noBid: 1}, {yesBid: 1, noBid: 0}]
|
|
||||||
export const bids = data.split('\n').map((line) => {
|
|
||||||
const [yesBid, noBid] = line.split(',')
|
|
||||||
return {
|
|
||||||
yesBid: parseInt(yesBid || '0'),
|
|
||||||
noBid: parseInt(noBid || '0'),
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,332 +0,0 @@
|
||||||
import React, { useMemo, useState } from 'react'
|
|
||||||
import { DatumValue } from '@nivo/core'
|
|
||||||
import { ResponsiveLine } from '@nivo/line'
|
|
||||||
|
|
||||||
import { Entry, makeEntries } from 'web/lib/simulator/entries'
|
|
||||||
import { Col } from 'web/components/layout/col'
|
|
||||||
|
|
||||||
function TableBody(props: { entries: Entry[] }) {
|
|
||||||
return (
|
|
||||||
<tbody>
|
|
||||||
{props.entries.map((entry, i) => (
|
|
||||||
<tr key={i}>
|
|
||||||
<th>{props.entries.length - i}</th>
|
|
||||||
<TableRowStart entry={entry} />
|
|
||||||
<TableRowEnd entry={entry} />
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function TableRowStart(props: { entry: Entry }) {
|
|
||||||
const { entry } = props
|
|
||||||
if (entry.yesBid && entry.noBid) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<td>
|
|
||||||
<div className="badge">ANTE</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
${entry.yesBid} / ${entry.noBid}
|
|
||||||
</td>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
} else if (entry.yesBid) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<td>
|
|
||||||
<div className="badge badge-success">YES</div>
|
|
||||||
</td>
|
|
||||||
<td>${entry.yesBid}</td>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<td>
|
|
||||||
<div className="badge badge-error">NO</div>
|
|
||||||
</td>
|
|
||||||
<td>${entry.noBid}</td>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function TableRowEnd(props: { entry: Entry | null; isNew?: boolean }) {
|
|
||||||
const { entry } = props
|
|
||||||
if (!entry) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<td>0</td>
|
|
||||||
<td>0</td>
|
|
||||||
{!props.isNew && (
|
|
||||||
<>
|
|
||||||
<td>N/A</td>
|
|
||||||
<td>N/A</td>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
} else if (entry.yesBid && entry.noBid) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<td>{(entry.prob * 100).toFixed(1)}%</td>
|
|
||||||
<td>N/A</td>
|
|
||||||
{!props.isNew && (
|
|
||||||
<>
|
|
||||||
<td>N/A</td>
|
|
||||||
<td>N/A</td>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
} else if (entry.yesBid) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<td>{(entry.prob * 100).toFixed(1)}%</td>
|
|
||||||
<td>${entry.yesWeight.toFixed(0)}</td>
|
|
||||||
{!props.isNew && (
|
|
||||||
<>
|
|
||||||
<td>${entry.yesPayout.toFixed(0)}</td>
|
|
||||||
<td>{(entry.yesReturn * 100).toFixed(0)}%</td>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<td>{(entry.prob * 100).toFixed(1)}%</td>
|
|
||||||
<td>${entry.noWeight.toFixed(0)}</td>
|
|
||||||
{!props.isNew && (
|
|
||||||
<>
|
|
||||||
<td>${entry.noPayout.toFixed(0)}</td>
|
|
||||||
<td>{(entry.noReturn * 100).toFixed(0)}%</td>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bid = { yesBid: number; noBid: number }
|
|
||||||
|
|
||||||
function NewBidTable(props: {
|
|
||||||
steps: number
|
|
||||||
bids: Array<Bid>
|
|
||||||
setSteps: (steps: number) => void
|
|
||||||
setBids: (bids: Array<Bid>) => void
|
|
||||||
}) {
|
|
||||||
const { steps, bids, setSteps, setBids } = props
|
|
||||||
// Prepare for new bids
|
|
||||||
const [newBid, setNewBid] = useState(0)
|
|
||||||
const [newBidType, setNewBidType] = useState('YES')
|
|
||||||
|
|
||||||
function makeBid(type: string, bid: number) {
|
|
||||||
return {
|
|
||||||
yesBid: type == 'YES' ? bid : 0,
|
|
||||||
noBid: type == 'YES' ? 0 : bid,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitBid() {
|
|
||||||
if (newBid <= 0) return
|
|
||||||
const bid = makeBid(newBidType, newBid)
|
|
||||||
bids.splice(steps, 0, bid)
|
|
||||||
setBids(bids)
|
|
||||||
setSteps(steps + 1)
|
|
||||||
setNewBid(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleBidType() {
|
|
||||||
setNewBidType(newBidType === 'YES' ? 'NO' : 'YES')
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextBid = makeBid(newBidType, newBid)
|
|
||||||
const fakeBids = [...bids.slice(0, steps), nextBid]
|
|
||||||
const entries = makeEntries(fakeBids)
|
|
||||||
const nextEntry = entries[entries.length - 1]
|
|
||||||
|
|
||||||
function randomBid() {
|
|
||||||
const bidType = Math.random() < 0.5 ? 'YES' : 'NO'
|
|
||||||
// const p = bidType === 'YES' ? nextEntry.prob : 1 - nextEntry.prob
|
|
||||||
|
|
||||||
const amount = Math.floor(Math.random() * 300) + 1
|
|
||||||
const bid = makeBid(bidType, amount)
|
|
||||||
|
|
||||||
bids.splice(steps, 0, bid)
|
|
||||||
setBids(bids)
|
|
||||||
setSteps(steps + 1)
|
|
||||||
setNewBid(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<table className="table-compact my-8 table w-full text-center">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Order #</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Bet</th>
|
|
||||||
<th>Prob</th>
|
|
||||||
<th>Est Payout</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>{steps + 1}</th>
|
|
||||||
<td>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
`badge hover:cursor-pointer ` +
|
|
||||||
(newBidType == 'YES' ? 'badge-success' : 'badge-ghost')
|
|
||||||
}
|
|
||||||
onClick={toggleBidType}
|
|
||||||
>
|
|
||||||
YES
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
`badge hover:cursor-pointer ` +
|
|
||||||
(newBidType == 'NO' ? 'badge-error' : 'badge-ghost')
|
|
||||||
}
|
|
||||||
onClick={toggleBidType}
|
|
||||||
>
|
|
||||||
NO
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{/* Note: Would love to make this input smaller... */}
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
placeholder="0"
|
|
||||||
className="input input-bordered max-w-[100px]"
|
|
||||||
value={newBid.toString()}
|
|
||||||
onChange={(e) => setNewBid(parseInt(e.target.value) || 0)}
|
|
||||||
onKeyUp={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
submitBid()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onFocus={(e) => e.target.select()}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<TableRowEnd entry={nextEntry} isNew />
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="btn btn-primary mt-2"
|
|
||||||
onClick={() => submitBid()}
|
|
||||||
disabled={newBid <= 0}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<button className="btn btn-secondary mb-4" onClick={randomBid}>
|
|
||||||
Random bet!
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show a hello world React page
|
|
||||||
export default function Simulator() {
|
|
||||||
const [steps, setSteps] = useState(1)
|
|
||||||
const [bids, setBids] = useState([{ yesBid: 100, noBid: 100 }])
|
|
||||||
|
|
||||||
const entries = useMemo(
|
|
||||||
() => makeEntries(bids.slice(0, steps)),
|
|
||||||
[bids, steps]
|
|
||||||
)
|
|
||||||
|
|
||||||
const reversedEntries = [...entries].reverse()
|
|
||||||
|
|
||||||
const probs = entries.map((entry) => entry.prob)
|
|
||||||
const points = probs.map((prob, i) => ({ x: i + 1, y: prob * 100 }))
|
|
||||||
const data = [{ id: 'Yes', data: points, color: '#11b981' }]
|
|
||||||
const tickValues = [0, 25, 50, 75, 100]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col>
|
|
||||||
<div className="mx-auto mt-8 grid w-full grid-cols-1 gap-4 p-2 text-center xl:grid-cols-2">
|
|
||||||
{/* Left column */}
|
|
||||||
<div>
|
|
||||||
<h1 className="mb-8 text-2xl font-bold">
|
|
||||||
Dynamic Parimutuel Market Simulator
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<NewBidTable {...{ steps, bids, setSteps, setBids }} />
|
|
||||||
|
|
||||||
{/* History of bids */}
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<table className="table w-full text-center">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Order #</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Bet</th>
|
|
||||||
<th>Prob</th>
|
|
||||||
<th>Est Payout</th>
|
|
||||||
<th>Payout</th>
|
|
||||||
<th>Return</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<TableBody entries={reversedEntries} />
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right column */}
|
|
||||||
<Col>
|
|
||||||
<h1 className="mb-8 text-2xl font-bold">
|
|
||||||
Probability of
|
|
||||||
<div className="badge badge-success w-18 ml-3 h-8 text-2xl">
|
|
||||||
YES
|
|
||||||
</div>
|
|
||||||
</h1>
|
|
||||||
<div className="mb-10 h-[500px] w-full">
|
|
||||||
<ResponsiveLine
|
|
||||||
data={data}
|
|
||||||
yScale={{ min: 0, max: 100, type: 'linear' }}
|
|
||||||
yFormat={formatPercent}
|
|
||||||
gridYValues={tickValues}
|
|
||||||
axisLeft={{
|
|
||||||
tickValues,
|
|
||||||
format: formatPercent,
|
|
||||||
}}
|
|
||||||
enableGridX={false}
|
|
||||||
colors={{ datum: 'color' }}
|
|
||||||
pointSize={8}
|
|
||||||
pointBorderWidth={1}
|
|
||||||
pointBorderColor="#fff"
|
|
||||||
enableSlices="x"
|
|
||||||
enableArea
|
|
||||||
margin={{ top: 20, right: 10, bottom: 20, left: 40 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* 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))}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatPercent(y: DatumValue) {
|
|
||||||
return `${Math.round(+y.toString())}%`
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user