simulator: random bet in proportion to prob, delete vue simulator
This commit is contained in:
parent
344b06124f
commit
104257001b
2
market-simulator/.gitattributes
vendored
2
market-simulator/.gitattributes
vendored
|
@ -1,2 +0,0 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
5
market-simulator/.gitignore
vendored
5
market-simulator/.gitignore
vendored
|
@ -1,5 +0,0 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"semi": false
|
||||
}
|
3
market-simulator/.vscode/extensions.json
vendored
3
market-simulator/.vscode/extensions.json
vendored
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"recommendations": ["johnsoncodehk.volar"]
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
# Austin's Starter Project Template
|
||||
|
||||
## Usage
|
||||
|
||||
1. Clone this repository
|
||||
2. `yarn`
|
||||
3. `yarn dev` to start development
|
||||
4. Setup Firebase
|
||||
|
||||
## Setting up Firebase
|
||||
|
||||
1. Go to https://console.firebase.google.com/ and create a new project
|
||||
1. Go to Project Settings and add Firebase to your web app
|
||||
a. Copy firebaseConfig to `src/network/init.ts`
|
||||
1. Create a Firestore Database
|
||||
a. Create a new collection called `users`
|
||||
b. Set up the security rules (see `src/network/example-rules.txt`)
|
||||
|
||||
1. Enable Authetication & Google auth
|
||||
|
||||
## Built on top of
|
||||
|
||||
- [VueJS](https://v3.vuejs.org/guide/introduction.html) on the frontend
|
||||
- [Vite](https://vitejs.dev/) for bundling and serving
|
||||
- [TailwindCSS](https://tailwindcss.com/) for styling
|
||||
- [WindiCSS](https://windicss.org/) specifically for faster loading times
|
||||
- [DaisyUI](https://daisyui.com/) for a default set of components
|
||||
- [Firestore](https://firebase.google.com/docs/firestore) for the database
|
||||
- [Firebase Auth](https://firebase.google.com/docs/auth) for login
|
||||
|
||||
### TODOs:
|
||||
|
||||
- [Netlify](https://www.netlify.com/) for hosting
|
||||
- [Stripe](https://stripe.com/) for payments
|
||||
- [Mailjet](https://www.mailjet.com/) for marketing & transactional emails
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Mantic Markets Simulator</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "starter-project",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"chart.js": "^3.6.1",
|
||||
"daisyui": "^1.16.0",
|
||||
"firebase": "^9.4.1",
|
||||
"vue": "^3.2.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^1.9.3",
|
||||
"typescript": "^4.4.3",
|
||||
"vite": "^2.6.4",
|
||||
"vite-plugin-windicss": "^1.4.12",
|
||||
"vue-tsc": "^0.3.0",
|
||||
"windicss": "^3.2.0"
|
||||
}
|
||||
}
|
1
market-simulator/public/.gitignore
vendored
1
market-simulator/public/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
dump
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="0 0 69.218 70.506" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||
<defs>
|
||||
<linearGradient data-v-fde0c5aa="" gradientTransform="rotate(25)" id="90bb4dbd-5a19-4e7a-8260-84b7646559c5" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop data-v-fde0c5aa="" offset="0" stop-color="#5A5A2D" stop-opacity="1"/>
|
||||
<stop data-v-fde0c5aa="" offset="1" stop-color="#0DE30D" stop-opacity="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g data-v-fde0c5aa="" id="5c4df6fb-50cb-4581-b5fd-961d2467c672" stroke="none" fill="url(#90bb4dbd-5a19-4e7a-8260-84b7646559c5)" transform="matrix(0.801904, 0, 0, 0.801904, 7.879115, 8.276814)">
|
||||
<path d="M29.347 10.688A16.012 16.012 0 0 1 34 10c1.604 0 3.168.238 4.652.688C41.374 3.578 47.976 0 58 0a2 2 0 1 1 0 4c-8.574 0-13.645 2.759-15.688 8.325a16.03 16.03 0 0 1 4.399 3.956C49.892 17.358 52 21.399 52 26c0 4.672-2.173 8.766-5.437 9.767C43.11 40.917 37.801 45 34 45s-9.11-4.083-12.563-9.233C18.173 34.767 16 30.672 16 26c0-4.6 2.108-8.642 5.29-9.72a16.03 16.03 0 0 1 4.398-3.955C23.645 6.76 18.573 4 9.999 4a2 2 0 1 1 0-4c10.024 0 16.627 3.578 19.348 10.688zM34 41c1.894 0 5.359-2.493 8.068-5.87C39.58 33.547 38 29.984 38 26c0-3.898 1.513-7.394 3.91-9.026A11.966 11.966 0 0 0 34 14c-2.972 0-5.76 1.087-7.91 2.974C28.488 18.606 30 22.102 30 26c0 3.983-1.58 7.546-4.068 9.13C28.642 38.508 32.106 41 34 41zm-11-9c1.408 0 3-2.547 3-6s-1.592-6-3-6-3 2.547-3 6 1.592 6 3 6zm22 0c1.408 0 3-2.547 3-6s-1.592-6-3-6-3 2.547-3 6 1.592 6 3 6zM6.883 66.673a2 2 0 0 1-3.297.741C1.13 64.96 0 60.813 0 55c0-6.052 3.982-12.206 11.734-18.548a2 2 0 0 1 3.029.604l15 28a2 2 0 1 1-3.526 1.888l-8.755-16.342c-4.18 2.733-7.74 8.065-10.599 16.07zm5.526-25.54C6.75 46.174 4 50.818 4 55c0 2.566.243 4.666.7 6.304 2.934-6.756 6.547-11.518 10.887-14.24l-3.178-5.932zm48.708 25.54c-2.86-8.006-6.418-13.338-10.599-16.071l-8.755 16.342a2 2 0 1 1-3.526-1.888l15-28a2 2 0 0 1 3.03-.604C64.018 42.794 68 48.948 68 55c0 5.813-1.13 9.96-3.585 12.414a2 2 0 0 1-3.298-.741zm-5.526-25.54l-3.178 5.932c4.34 2.721 7.954 7.483 10.887 14.24.457-1.639.7-3.739.7-6.305 0-4.18-2.75-8.825-8.409-13.868z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.1 KiB |
|
@ -1,15 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
import LoginExample from './components/LoginExample.vue'
|
||||
import Simulator from './components/Simulator.vue'
|
||||
import Header from './components/Header.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header />
|
||||
<Simulator />
|
||||
</template>
|
||||
|
||||
<style></style>
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
|
@ -1,40 +0,0 @@
|
|||
<!-- Partial table row for a given entry -->
|
||||
<template>
|
||||
<!-- Invalid bid -->
|
||||
<template v-if="!entry">
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
</template>
|
||||
<!-- Seed bids -->
|
||||
<template v-else-if="entry.yesBid && entry.noBid">
|
||||
<td>N/A</td>
|
||||
<td>{{ entry.prob.toFixed(2) }}</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
</template>
|
||||
<!-- Yes Bid -->
|
||||
<template v-else-if="entry.yesBid">
|
||||
<td>{{ entry.yesWeight.toFixed(2) }}</td>
|
||||
<td>{{ entry.prob.toFixed(2) }}</td>
|
||||
<td>{{ entry.yesPayout.toFixed(2) }}</td>
|
||||
<td>{{ (entry.yesReturn * 100).toFixed(2) }}%</td>
|
||||
</template>
|
||||
<!-- No Bid -->
|
||||
<template v-else-if="entry.noBid">
|
||||
<td>{{ entry.noWeight.toFixed(2) }}</td>
|
||||
<td>{{ entry.prob.toFixed(2) }}</td>
|
||||
<td>{{ entry.noPayout.toFixed(2) }}</td>
|
||||
<td>{{ (entry.noReturn * 100).toFixed(2) }}%</td>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from '@vue/runtime-core'
|
||||
import { Entry } from './entries'
|
||||
|
||||
defineProps({
|
||||
entry: Object as PropType<Entry>,
|
||||
})
|
||||
</script>
|
|
@ -1,56 +0,0 @@
|
|||
<template>
|
||||
<nav
|
||||
class="
|
||||
relative
|
||||
mx-auto
|
||||
flex
|
||||
items-center
|
||||
justify-between
|
||||
px-12
|
||||
bg-blue-gray-700
|
||||
h-16
|
||||
"
|
||||
aria-label="Global"
|
||||
>
|
||||
<div class="flex items-center flex-1">
|
||||
<div class="flex items-center justify-between w-full md:w-auto">
|
||||
<a
|
||||
href="https://mantic.markets"
|
||||
class="inline-grid grid-flow-col align-items-center h-6 sm:h-10"
|
||||
>
|
||||
<img
|
||||
class="w-auto h-6 sm:h-10 inline-block mr-3"
|
||||
src="/logo-icon.svg"
|
||||
/>
|
||||
<span class="text-white font-logo">Mantic Markets</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="space-x-8 md:flex md:ml-10">
|
||||
<a
|
||||
v-for="item in [
|
||||
{
|
||||
name: 'About',
|
||||
href: 'https://mantic.notion.site/About-Mantic-Markets-09bdde9044614e62a27477b4b1bf77ea',
|
||||
},
|
||||
{ name: 'Simulator', href: 'https://simulator.mantic.markets' },
|
||||
]"
|
||||
:key="item.name"
|
||||
:href="item.href"
|
||||
class="text-base font-medium text-white hover:text-gray-300"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Major+Mono+Display&display=swap');
|
||||
|
||||
.font-logo {
|
||||
font-family: 'Major Mono Display', monospace;
|
||||
text-transform: lowercase;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
</style>
|
|
@ -1,54 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<p>
|
||||
Recommended IDE setup:
|
||||
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
|
||||
+
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
|
||||
</p>
|
||||
|
||||
<p>See <code>README.md</code> for more information.</p>
|
||||
|
||||
<p>
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">
|
||||
Vite Docs
|
||||
</a>
|
||||
|
|
||||
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
|
||||
</p>
|
||||
|
||||
<button type="button" class="btn btn-primary" @click="count++">
|
||||
count is: {{ count }}
|
||||
</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test hot module replacement.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #eee;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
color: #304455;
|
||||
}
|
||||
</style>
|
|
@ -1,30 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
firebaseLogin,
|
||||
firebaseLogout,
|
||||
listenForLogin,
|
||||
User,
|
||||
} from '../network/users'
|
||||
|
||||
const user = ref({} as User)
|
||||
listenForLogin((u) => {
|
||||
user.value = u
|
||||
})
|
||||
|
||||
function objectEmpty(obj: any) {
|
||||
// Functional equivalent:
|
||||
return Object.keys(obj).length === 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="objectEmpty(user)">
|
||||
<p>Not logged in!</p>
|
||||
<button class="btn btn-primary" @click="firebaseLogin">Login</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>Logged in as {{ user.name }}</p>
|
||||
<button class="btn btn-secondary" @click="firebaseLogout">Logout</button>
|
||||
</div>
|
||||
</template>
|
|
@ -1,224 +0,0 @@
|
|||
<template>
|
||||
<div class="overflow-x-auto px-12 mt-8">
|
||||
<!-- Two-column layout (on large screen sizes) -->
|
||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||
<!-- Left column -->
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-600 mb-8">
|
||||
Dynamic Parimutuel Market Simulator
|
||||
</h1>
|
||||
|
||||
<label>Simulation step: {{ steps }} </label>
|
||||
<input
|
||||
class="range"
|
||||
type="range"
|
||||
v-model.number="steps"
|
||||
min="1"
|
||||
:max="bids.length"
|
||||
/>
|
||||
|
||||
<!-- Table to enter a new bid -->
|
||||
<table class="table table-compact my-8 w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order #</th>
|
||||
<th>Type</th>
|
||||
<th>Bid</th>
|
||||
<th>Weight</th>
|
||||
<th>Probability</th>
|
||||
<th>Payout</th>
|
||||
<th>Return</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{{ steps + 1 }}</th>
|
||||
<td>
|
||||
<div
|
||||
@click="toggleBidType"
|
||||
class="badge clickable"
|
||||
:class="newBidType == 'YES' ? 'badge-success' : 'badge-ghost'"
|
||||
>
|
||||
YES
|
||||
</div>
|
||||
<br />
|
||||
<div
|
||||
@click="toggleBidType"
|
||||
class="badge clickable"
|
||||
:class="newBidType == 'NO' ? 'badge-error' : 'badge-ghost'"
|
||||
>
|
||||
NO
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
v-model.number="newBid"
|
||||
placeholder="0"
|
||||
class="input input-bordered"
|
||||
@focus="$event.target.select()"
|
||||
@keyup.enter="submitBid"
|
||||
/>
|
||||
</td>
|
||||
<EntryRow :entry="nextEntry" />
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="newBid <= 0"
|
||||
@click="submitBid"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- List of historical bids -->
|
||||
<table class="table table-compact w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order #</th>
|
||||
<th>Type</th>
|
||||
<th>Bid</th>
|
||||
<th>Weight</th>
|
||||
<th>Probability</th>
|
||||
<th>Payout</th>
|
||||
<th>Return</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(entry, i) in entries">
|
||||
<th>{{ i + 1 }}</th>
|
||||
<template v-if="entry.yesBid && entry.noBid">
|
||||
<td><div class="badge">SEED</div></td>
|
||||
<td>{{ entry.yesBid }} / {{ entry.noBid }}</td>
|
||||
</template>
|
||||
<template v-else-if="entry.yesBid">
|
||||
<td><div class="badge badge-success">YES</div></td>
|
||||
<td>{{ entry.yesBid }}</td>
|
||||
</template>
|
||||
<template v-else>
|
||||
<td><div class="badge badge-error">NO</div></td>
|
||||
<td>{{ entry.noBid }}</td>
|
||||
</template>
|
||||
<EntryRow :entry="entry" />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Right column -->
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-600 mb-8">
|
||||
Probability of
|
||||
<div class="badge badge-success text-2xl h-8 w-18">YES</div>
|
||||
</h1>
|
||||
<canvas id="simChart" width="400" height="400"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Chart from 'chart.js/auto'
|
||||
import { bids as sampleBids } from './sample-bids'
|
||||
import { makeEntries } from './entries'
|
||||
import { ref, computed } from '@vue/reactivity'
|
||||
import { onMounted, watch } from '@vue/runtime-core'
|
||||
import EntryRow from './EntryRow.vue'
|
||||
|
||||
// Copy over the sample bids to seed the simulation
|
||||
const bids = sampleBids.slice()
|
||||
|
||||
// UI parameters
|
||||
const steps = ref(10)
|
||||
|
||||
// Computed variables: stop the simulation at the appropriate number of steps
|
||||
const entries = computed(() => makeEntries(bids.slice(0, steps.value)))
|
||||
|
||||
// Graph the probabilities over time
|
||||
const probs = computed(() => entries.value.map((entry) => entry.prob))
|
||||
|
||||
onMounted(initChart)
|
||||
watch(steps, renderChart)
|
||||
|
||||
let chart: Chart
|
||||
function initChart() {
|
||||
const ctx = document.getElementById('simChart')
|
||||
chart = new Chart(ctx as any, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [...Array(steps.value).keys()],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Implied probability',
|
||||
data: probs.value,
|
||||
borderColor: 'rgb(75, 192, 192)',
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function renderChart() {
|
||||
chart.data.labels = [...Array(steps.value).keys()]
|
||||
chart.data.datasets[0].data = probs.value
|
||||
chart.update()
|
||||
}
|
||||
|
||||
// Add new data to the simulation, at the current step
|
||||
const newBid = ref(0)
|
||||
const newBidType = ref('YES')
|
||||
|
||||
function toggleBidType() {
|
||||
newBidType.value = newBidType.value === 'YES' ? 'NO' : 'YES'
|
||||
}
|
||||
|
||||
function makeBid(type: string, bid: number) {
|
||||
return {
|
||||
yesBid: newBidType.value == 'YES' ? newBid.value : 0,
|
||||
noBid: newBidType.value == 'YES' ? 0 : newBid.value,
|
||||
}
|
||||
}
|
||||
|
||||
function submitBid() {
|
||||
if (newBid.value <= 0) return
|
||||
const bid = makeBid(newBidType.value, newBid.value)
|
||||
bids.splice(steps.value, 0, bid)
|
||||
steps.value++
|
||||
newBid.value = 0
|
||||
}
|
||||
|
||||
// Preview the next bid before it's added
|
||||
const nextEntry = computed(() => {
|
||||
if (newBid.value) {
|
||||
const nextBid = makeBid(newBidType.value, newBid.value)
|
||||
const fakeBids = [...bids.slice(0, steps.value), nextBid]
|
||||
const entries = makeEntries(fakeBids)
|
||||
return entries[entries.length - 1]
|
||||
}
|
||||
return null
|
||||
})
|
||||
</script>
|
|
@ -1,60 +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
|
||||
}
|
||||
|
||||
export function makeEntries(bids: Bid[]): Entry[] {
|
||||
const entries: Entry[] = []
|
||||
let yesPot = 0
|
||||
let noPot = 0
|
||||
// First pass: calculate all the weights
|
||||
for (const { yesBid, noBid } of bids) {
|
||||
const yesWeight =
|
||||
noPot * (Math.log(yesBid + yesPot) - Math.log(yesPot)) || 0
|
||||
const noWeight = yesPot * (Math.log(noBid + noPot) - Math.log(noPot)) || 0
|
||||
|
||||
// Note: Need to calculate weights BEFORE updating pot
|
||||
yesPot += yesBid
|
||||
noPot += noBid
|
||||
const prob = yesPot / (yesPot + noPot)
|
||||
|
||||
entries.push({
|
||||
yesBid,
|
||||
noBid,
|
||||
yesWeight,
|
||||
noWeight,
|
||||
prob,
|
||||
// To be filled in below
|
||||
yesPayout: 0,
|
||||
noPayout: 0,
|
||||
yesReturn: 0,
|
||||
noReturn: 0,
|
||||
})
|
||||
}
|
||||
|
||||
const YES_SEED = bids[0].yesBid
|
||||
const NO_SEED = bids[0].noBid
|
||||
const yesWeightsSum = entries.reduce((sum, entry) => sum + entry.yesWeight, 0)
|
||||
const noWeightsSum = entries.reduce((sum, entry) => sum + entry.noWeight, 0)
|
||||
// Second pass: calculate all the payouts
|
||||
for (const entry of entries) {
|
||||
const { yesBid, noBid, yesWeight, noWeight } = entry
|
||||
// Payout: You get your initial bid back, as well as your share of the
|
||||
// (noPot - seed) according to your yesWeight
|
||||
entry.yesPayout = yesBid + (yesWeight / yesWeightsSum) * (noPot - NO_SEED)
|
||||
entry.noPayout = noBid + (noWeight / noWeightsSum) * (yesPot - YES_SEED)
|
||||
entry.yesReturn = (entry.yesPayout - yesBid) / yesBid
|
||||
entry.noReturn = (entry.noPayout - noBid) / noBid
|
||||
}
|
||||
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'),
|
||||
}
|
||||
})
|
8
market-simulator/src/env.d.ts
vendored
8
market-simulator/src/env.d.ts
vendored
|
@ -1,8 +0,0 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import 'virtual:windi.css'
|
||||
|
||||
createApp(App).mount('#app')
|
|
@ -1,22 +0,0 @@
|
|||
rules_version = '2';
|
||||
service cloud.firestore {
|
||||
match /databases/{database}/documents {
|
||||
function isAdmin() {
|
||||
// Hardcode admins, e.g.:
|
||||
return request.auth.uid == 'mvLxCEDq7YSMmatuLZQSxRtsBeh2' || // akrolsmir@gmail.com
|
||||
request.auth.uid == 'rYFCLWCzSnSjGuSjpLOtWJ1Ewof1' // abc.sinclair@gmail.com
|
||||
}
|
||||
|
||||
// Make sure the uid of the requesting user matches name of the user
|
||||
// document. The wildcard expression {userId} makes the userId variable
|
||||
// available in rules.
|
||||
match /users/{userId} {
|
||||
allow read, update, delete: if request.auth != null && request.auth.uid == userId || isAdmin();
|
||||
allow create: if request.auth != null || isAdmin();
|
||||
}
|
||||
|
||||
match /rooms/{document=**} {
|
||||
allow read, create, update: if true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { initializeApp } from 'firebase/app'
|
||||
const firebaseConfig = {
|
||||
apiKey: 'AIzaSyB1p-PbBT1EcCfJoGtSeZbxlYVvagOoTaY',
|
||||
authDomain: 'starter-project-7fba2.firebaseapp.com',
|
||||
projectId: 'starter-project-7fba2',
|
||||
storageBucket: 'starter-project-7fba2.appspot.com',
|
||||
messagingSenderId: '751858706800',
|
||||
appId: '1:751858706800:web:1a69cfbd58c7acbafd87b5',
|
||||
measurementId: 'G-HPK27K51WM',
|
||||
}
|
||||
|
||||
// Initialize Firebase
|
||||
export const app = initializeApp(firebaseConfig)
|
||||
try {
|
||||
// Note: this is still throwing a console error atm...
|
||||
import('firebase/analytics').then((analytics) => {
|
||||
analytics.getAnalytics(app)
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('Analytics were blocked')
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
import { app } from './init'
|
||||
import { getFirestore, onSnapshot, doc, updateDoc } from 'firebase/firestore'
|
||||
import { Unsubscribe } from '@firebase/util'
|
||||
|
||||
const db = getFirestore(app)
|
||||
|
||||
// Example: Listening for realtime changes to a document
|
||||
let unsubscribe: Unsubscribe
|
||||
export function listenRoom(roomId: string, onChange: (room: Room) => void) {
|
||||
if (unsubscribe) {
|
||||
unsubscribe()
|
||||
}
|
||||
const roomRef = doc(db, 'rooms', roomId)
|
||||
unsubscribe = onSnapshot(roomRef, (snapshot) => {
|
||||
const room = snapshot.data() as Room
|
||||
if (room) {
|
||||
onChange(room)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Example: Pushing an update to a document
|
||||
export async function createMessage(roomId: string, message: Message) {
|
||||
const roomRef = doc(db, 'rooms', roomId)
|
||||
await updateDoc(roomRef, {
|
||||
[`messages.${message.id}`]: message,
|
||||
[`lastKeystrokes.${message.author.id}`]: message.timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
export function registerUser(roomId: string, user: Author) {
|
||||
const roomRef = doc(db, 'rooms', roomId)
|
||||
return updateDoc(roomRef, {
|
||||
[`users.${user.id}`]: user,
|
||||
})
|
||||
}
|
||||
|
||||
export async function registerKeystroke(roomId: string, userId: string) {
|
||||
const roomRef = doc(db, 'rooms', roomId)
|
||||
await updateDoc(roomRef, {
|
||||
[`lastKeystrokes.${userId}`]: Date.now(),
|
||||
})
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
import { app } from './init'
|
||||
import { getFirestore, doc, setDoc, getDoc } from 'firebase/firestore'
|
||||
import { getAuth } from 'firebase/auth'
|
||||
import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage'
|
||||
import {
|
||||
onAuthStateChanged,
|
||||
GoogleAuthProvider,
|
||||
signInWithPopup,
|
||||
} from 'firebase/auth'
|
||||
|
||||
export type User = {
|
||||
id: string
|
||||
name: string
|
||||
avatarUrl: string
|
||||
// Not needed for chat view:
|
||||
email: string
|
||||
description: string
|
||||
createTime: number
|
||||
lastUpdateTime: number
|
||||
}
|
||||
|
||||
const db = getFirestore(app)
|
||||
export const auth = getAuth(app)
|
||||
|
||||
export async function getUser(userId: string) {
|
||||
const docSnap = await getDoc(doc(db, 'users', userId))
|
||||
return docSnap.data() as User
|
||||
}
|
||||
|
||||
export async function setUser(userId: string, user: User) {
|
||||
await setDoc(doc(db, 'users', userId), user)
|
||||
}
|
||||
|
||||
const CACHED_USER_KEY = 'CACHED_USER_KEY'
|
||||
export function listenForLogin(onUser: (user: User) => void) {
|
||||
// Immediately load any persisted user object from browser cache.
|
||||
const cachedUser = localStorage.getItem(CACHED_USER_KEY)
|
||||
if (cachedUser) {
|
||||
onUser(JSON.parse(cachedUser))
|
||||
}
|
||||
|
||||
onAuthStateChanged(auth, async (user) => {
|
||||
if (user) {
|
||||
let fetchedUser = await getUser(user.uid)
|
||||
if (!fetchedUser) {
|
||||
// User just created an account; save them to our database.
|
||||
fetchedUser = {
|
||||
id: user.uid,
|
||||
name: user.displayName || 'Default Name',
|
||||
avatarUrl: user.photoURL || '',
|
||||
email: user.email || 'default@blah.com',
|
||||
description: '',
|
||||
createTime: Date.now(),
|
||||
lastUpdateTime: Date.now(),
|
||||
}
|
||||
await setUser(user.uid, fetchedUser)
|
||||
}
|
||||
onUser(fetchedUser)
|
||||
|
||||
// Persist to local storage, to reduce login blink next time.
|
||||
// Note: Cap on localStorage size is ~5mb
|
||||
localStorage.setItem(CACHED_USER_KEY, JSON.stringify(fetchedUser))
|
||||
} else {
|
||||
// User logged out; reset to the empty object
|
||||
onUser({} as User)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function firebaseLogin() {
|
||||
const provider = new GoogleAuthProvider()
|
||||
signInWithPopup(auth, provider)
|
||||
}
|
||||
|
||||
export async function firebaseLogout() {
|
||||
auth.signOut()
|
||||
localStorage.removeItem(CACHED_USER_KEY)
|
||||
}
|
||||
|
||||
const storage = getStorage(app)
|
||||
// Example: uploadData('avatars/ajfi8iejsf.png', data)
|
||||
export async function uploadData(
|
||||
path: string,
|
||||
data: ArrayBuffer | Blob | Uint8Array
|
||||
) {
|
||||
const uploadRef = ref(storage, path)
|
||||
// Uploaded files should be cached for 1 day, then revalidated
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||
const metadata = { cacheControl: 'public, max-age=86400, must-revalidate' }
|
||||
await uploadBytes(uploadRef, data, metadata)
|
||||
return await getDownloadURL(uploadRef)
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import WindiCSS from 'vite-plugin-windicss'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), WindiCSS()],
|
||||
})
|
|
@ -1,6 +0,0 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { transform } from 'windicss/helpers'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [transform('daisyui')],
|
||||
})
|
File diff suppressed because it is too large
Load Diff
|
@ -2,9 +2,9 @@ import React, { useMemo, useState } from 'react'
|
|||
import { DatumValue } from '@nivo/core'
|
||||
import { ResponsiveLine } from '@nivo/line'
|
||||
|
||||
import { Entry, makeEntries } from '../../lib/simulator/entries'
|
||||
import { Header } from '../../components/header'
|
||||
import { Col } from '../../components/layout/col'
|
||||
import { Entry, makeEntries } from '../lib/simulator/entries'
|
||||
import { Header } from '../components/header'
|
||||
import { Col } from '../components/layout/col'
|
||||
|
||||
function TableBody(props: { entries: Entry[] }) {
|
||||
return (
|
||||
|
@ -149,7 +149,11 @@ function NewBidTable(props: {
|
|||
|
||||
function randomBid() {
|
||||
const bidType = Math.random() < 0.5 ? 'YES' : 'NO'
|
||||
const amount = Math.round(Math.random() * 500)
|
||||
const p = bidType === 'YES'
|
||||
? nextEntry.prob
|
||||
: 1 - nextEntry.prob
|
||||
|
||||
const amount = Math.round(p * Math.random() * 300) + 1
|
||||
const bid = makeBid(bidType, amount)
|
||||
|
||||
bids.splice(steps, 0, bid)
|
||||
|
@ -236,7 +240,7 @@ function NewBidTable(props: {
|
|||
// Show a hello world React page
|
||||
export default function Simulator() {
|
||||
const [steps, setSteps] = useState(1)
|
||||
const [bids, setBids] = useState([{ yesBid: 600, noBid: 400 }])
|
||||
const [bids, setBids] = useState([{ yesBid: 550, noBid: 450 }])
|
||||
|
||||
const entries = useMemo(
|
||||
() => makeEntries(bids.slice(0, steps)),
|
Loading…
Reference in New Issue
Block a user