diff --git a/web/lib/simulator/entries.ts b/web/lib/simulator/entries.ts deleted file mode 100644 index 535a59ad..00000000 --- a/web/lib/simulator/entries.ts +++ /dev/null @@ -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 -} diff --git a/web/lib/simulator/sample-bids.ts b/web/lib/simulator/sample-bids.ts deleted file mode 100644 index 547e6dce..00000000 --- a/web/lib/simulator/sample-bids.ts +++ /dev/null @@ -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'), - } -}) diff --git a/web/pages/simulator.tsx b/web/pages/simulator.tsx deleted file mode 100644 index 756e483b..00000000 --- a/web/pages/simulator.tsx +++ /dev/null @@ -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())}%` -}