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