Spaces:
Sleeping
Sleeping
from collections import defaultdict | |
import seaborn as sns | |
import streamlit as st | |
import streamlit.components.v1 as components | |
import plotly.graph_objects as go | |
import plotly.express as px | |
import numpy as np | |
import pandas as pd | |
from scipy.stats import norm | |
st.set_page_config(page_title='Can you be truly random ?', layout = 'wide', page_icon = 'favicon.jpg', initial_sidebar_state = 'auto') | |
cm = sns.light_palette("green", as_cmap=True) | |
# Custom CSS to styles | |
st.markdown(""" | |
<style> | |
button { | |
padding-top: 50px !important; | |
padding-bottom: 50px !important; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
def set_state(x): | |
if x == 1: st.toast('The journey begins!', icon='π') | |
st.session_state.stage = x | |
def reset_game(): | |
set_state(0) | |
if 'n_buttons' not in st.session_state: | |
st.session_state.n_buttons = 2 | |
st.session_state.history = [] | |
st.session_state.preds = defaultdict(lambda: defaultdict(int)) | |
st.session_state.pnl = [0] | |
st.session_state.min = [0] | |
st.session_state.max = [0] | |
st.session_state.max_history = 10 | |
if 'stage' not in st.session_state: | |
reset_game() | |
st.title('Can your brain be random?') | |
n_plays = 50 | |
if st.session_state.stage == 0: | |
st.button('Begin', on_click=set_state, args=[1],use_container_width=True) | |
st.session_state.n_buttons = st.slider(label="How many buttons to play with?", min_value=2, max_value=5,value=2) | |
st.markdown(f'You will be presented with {st.session_state.n_buttons} buttons to randomly choose from') | |
st.markdown(f"At each round, I will try to predict which button you click") | |
st.session_state.max_history = st.slider(label="How much memory can I use?", min_value=2, max_value=20,value=10) | |
st.markdown(f"I will examine {st.session_state.max_history}-long sequences you played in the past") | |
st.markdown('This will help me make a guess') | |
st.markdown(f"If I get it right, I earn {st.session_state.n_buttons-1} point(s), otherwise you earn 1 point") | |
st.subheader(f'This is a fair game, as you have {(1-1/st.session_state.n_buttons):.0%} chances of winning') | |
st.markdown(f"Play as long as you want and try to beat me!") | |
st.markdown(f"I will start showing you my strategy from {n_plays} rounds") | |
def make_pred(max_history=st.session_state.max_history,alpha=2): | |
history = st.session_state.history | |
denominator = 0 | |
scores = {str(i):0 for i in range(st.session_state.n_buttons)} | |
recent = np.array(history[-max_history:]) | |
data = [] | |
for i in range(1,len(history)): | |
past = np.array(history[-i-max_history:-i]) | |
played = history[-i] | |
decay = np.exp(-alpha*np.linspace(1,0,len(past))) | |
same = past == recent[-len(past):] | |
similarity = (same*decay).sum() / decay.sum() | |
weight = np.exp(-alpha*i/(len(history)+1)) * similarity # len(history)/(len(history)+i) * similarity | |
scores[played] += weight | |
denominator += weight | |
if similarity > 0.5: | |
data.append([played,similarity,weight,i] + [None]* (max_history-len(past)) +list(same)) | |
if not denominator: | |
return get_random_play(),data | |
result = {str(i):scores[str(i)]/denominator for i in range(st.session_state.n_buttons)} | |
return result, data | |
def get_random_play(): | |
return {str(i):1/st.session_state.n_buttons for i in range(st.session_state.n_buttons)} | |
def update_pnl(user_win): | |
current_score = st.session_state.pnl[-1] | |
current_score += -1 if user_win else (st.session_state.n_buttons-1) | |
st.session_state.pnl.append(current_score) | |
expected_change = (2**0.5) * ((st.session_state.n_buttons-1)/st.session_state.n_buttons) | |
st.session_state.min.append(st.session_state.min[-1]-expected_change) | |
st.session_state.max.append(st.session_state.max[-1]+expected_change) | |
def user_select(i,choice): | |
st.session_state.history.append(str(i)) | |
if i == choice: | |
st.toast("I win!", icon='π€ͺ') | |
update_pnl(user_win=False) | |
else: | |
st.toast('Well done!', icon='π') | |
update_pnl(user_win=True) | |
# refresh_preds() | |
def compute_perf(): | |
data = np.array(st.session_state.pnl) | |
if len(data) > 1: | |
data = data[1:] - data[:-1] | |
perf = data.mean() | |
std = data.std() | |
win = (data>0).mean() | |
sharpe = perf / std | |
return win,sharpe | |
else: | |
return 0,0 | |
if st.session_state.stage == 1: | |
pred,data = make_pred() | |
choice = max(pred,key=pred.get) | |
progress = min(1.,float(len(st.session_state.history))/n_plays) | |
my_bar = st.progress(progress) | |
cols = st.columns(st.session_state.n_buttons) | |
for i,col in enumerate(cols): | |
col.button(str(i),on_click=user_select, args=[str(i),choice], | |
use_container_width=True) | |
col1,col2 = st.columns(2) | |
win,sharpe = compute_perf() | |
if progress >= 1.: | |
if sharpe > 0.1: | |
emoticon = "π" | |
elif sharpe < 0: | |
emoticon = "π" | |
else: | |
emoticon = "π§ " | |
text = f'%age win={win:.0%} Sharpe={sharpe:.3f} {emoticon}' | |
col1.subheader('My earnings so far...') | |
col1.write(text) | |
dev = abs(st.session_state.pnl[-1] / st.session_state.max[-1]) | |
chance = 1 - norm.cdf(dev) | |
col1.write(f'There is only {chance:.2%} chances of achieving this by chance') | |
fig = px.line(st.session_state.pnl) | |
fig.update_layout(showlegend=False) | |
fig.update_layout(xaxis={'visible': True, 'showticklabels': False}) | |
col1.plotly_chart(fig, use_container_width=True) | |
cols = ['you_played','similarity','weight','how_long_ago'] + [f'same_{i}' for i in range(st.session_state.max_history)] | |
data = pd.DataFrame(data,columns=cols).set_index('how_long_ago').sort_values('weight',ascending=False)[:10] | |
col2.subheader(f'In the past, you played') | |
col2.dataframe(data.style.background_gradient(cmap=cm), use_container_width=True) | |
won = choice == st.session_state.history[-1] | |
col2.write(f'So I chose {choice} (p={pred[choice]:.0%}) and ' + ('won π' if won else 'lost π')) | |
st.button('Start over', on_click=reset_game, args=[], | |
use_container_width=True) |