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 { DatumValue } from '@nivo/core'
|
||||||
import { ResponsiveLine } from '@nivo/line'
|
import { ResponsiveLine } from '@nivo/line'
|
||||||
|
|
||||||
import { Entry, makeEntries } from '../../lib/simulator/entries'
|
import { Entry, makeEntries } from '../lib/simulator/entries'
|
||||||
import { Header } from '../../components/header'
|
import { Header } from '../components/header'
|
||||||
import { Col } from '../../components/layout/col'
|
import { Col } from '../components/layout/col'
|
||||||
|
|
||||||
function TableBody(props: { entries: Entry[] }) {
|
function TableBody(props: { entries: Entry[] }) {
|
||||||
return (
|
return (
|
||||||
|
@ -149,7 +149,11 @@ function NewBidTable(props: {
|
||||||
|
|
||||||
function randomBid() {
|
function randomBid() {
|
||||||
const bidType = Math.random() < 0.5 ? 'YES' : 'NO'
|
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)
|
const bid = makeBid(bidType, amount)
|
||||||
|
|
||||||
bids.splice(steps, 0, bid)
|
bids.splice(steps, 0, bid)
|
||||||
|
@ -236,7 +240,7 @@ function NewBidTable(props: {
|
||||||
// Show a hello world React page
|
// Show a hello world React page
|
||||||
export default function Simulator() {
|
export default function Simulator() {
|
||||||
const [steps, setSteps] = useState(1)
|
const [steps, setSteps] = useState(1)
|
||||||
const [bids, setBids] = useState([{ yesBid: 600, noBid: 400 }])
|
const [bids, setBids] = useState([{ yesBid: 550, noBid: 450 }])
|
||||||
|
|
||||||
const entries = useMemo(
|
const entries = useMemo(
|
||||||
() => makeEntries(bids.slice(0, steps)),
|
() => makeEntries(bids.slice(0, steps)),
|
Loading…
Reference in New Issue
Block a user