Skip to content

Commit

Permalink
Adds multisim ids to enable multiple streamlit runs/comparisons. (#857)
Browse files Browse the repository at this point in the history
  • Loading branch information
calina-c authored Apr 5, 2024
1 parent 9331825 commit cc039b6
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ parquet_data/
pdr_predictions.parquet

# sim state
sim_state/*.pkl
sim_state

# pdr_backend accuracy output
pdr_backend/accuracy/output/*.json
Expand Down
4 changes: 4 additions & 0 deletions READMEs/trader.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ Simulation uses Python [logging](https://docs.python.org/3/howto/logging.html) f

Plot profit versus time, more: use `streamlit run sim_plots.py` to display real-time plots of the simulation while it is running. After the final iteration, the app settles into an overview of the final state.

By default, streamlit plots the latest sim (even if it is still running). To enable plotting for a specific run, e.g. if you used multisim or manually triggered different simulations, the sim engine assigns unique ids to each run.
Select that unique id from the `sim_state` folder, and run `streamlit run sim_plots.py <unique_id>` e.g. `streamlit run sim_plots.py 97f9633c-a78c-4865-9cc6-b5152c9500a3`
You can run many instances of streamlit at once, with different URLs.

## Run Trader Bot on Sapphire Testnet

Predictoor contracts run on [Oasis Sapphire](https://docs.oasis.io/dapp/sapphire/) testnet and mainnet. Sapphire is a privacy-preserving EVM-compatible L1 chain.
Expand Down
11 changes: 7 additions & 4 deletions pdr_backend/sim/sim_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import os
from typing import Optional
import uuid

import numpy as np
import polars as pl
Expand Down Expand Up @@ -49,7 +50,10 @@ def __init__(self, ppss: PPSS, multi_id: Optional[str] = None):
exchange_params=self.ppss.sim_ss.exchange_params,
)

self.multi_id = multi_id
if multi_id:
self.multi_id = multi_id
else:
self.multi_id = str(uuid.uuid4())

@property
def tokcoin(self) -> str:
Expand Down Expand Up @@ -77,7 +81,7 @@ def run(self):
self._init_loop_attributes()
logger.info("Start run")

self.sim_plotter.init_state()
self.sim_plotter.init_state(self.multi_id)

# main loop!
f = OhlcvDataFactory(self.ppss.lake_ss)
Expand Down Expand Up @@ -218,8 +222,7 @@ def run_one_iter(self, test_i: int, mergedohlcv_df: pl.DataFrame):

save_state, is_final_state = self.save_state(test_i, self.ppss.sim_ss.test_n)

# temporarily we don't allow streamlit supervision of multisim runs
if save_state and not self.multi_id:
if save_state:
colnames = [_shift_one_earlier(colname) for colname in colnames]
most_recent_x = X[-1, :]
slicing_x = most_recent_x # plot about the most recent x
Expand Down
44 changes: 30 additions & 14 deletions pdr_backend/sim/sim_plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def __init__(
):
self.st = None
self.aimodel_plotdata = None
self.multi_id = None

@staticmethod
def available_snapshots():
Expand All @@ -38,28 +39,35 @@ def available_snapshots():

return all_timestamps + ["final"]

def load_state(self, timestamp: Optional[str] = None):
def load_state(self, multi_id, timestamp: Optional[str] = None):
root_path = f"sim_state/{multi_id}"

if not os.path.exists("sim_state"):
raise Exception(
"sim_state folder does not exist. Please run the simulation first."
)

all_state_files = glob.glob("sim_state/st_*.pkl")
if not os.path.exists(root_path):
raise Exception(
f"sim_state/{multi_id} folder does not exist. Please run the simulation first."
)

all_state_files = glob.glob(f"{root_path}/st_*.pkl")
if not all_state_files:
raise Exception("No state files found. Please run the simulation first.")

if timestamp:
with open(f"sim_state/st_{timestamp}.pkl", "rb") as f:
with open(f"{root_path}/st_{timestamp}.pkl", "rb") as f:
self.st = pickle.load(f)

with open(f"sim_state/aimodel_plotdata_{timestamp}.pkl", "rb") as f:
with open(f"{root_path}/aimodel_plotdata_{timestamp}.pkl", "rb") as f:
self.aimodel_plotdata = pickle.load(f)

return self.st, "final"

if not os.path.exists("sim_state/st_final.pkl"):
if not os.path.exists(f"{root_path}/st_final.pkl"):
# plot previous state to avoid using a pickle that hasn't finished
all_state_files = glob.glob("sim_state/st_*.pkl")
all_state_files = glob.glob(f"{root_path}/st_*.pkl")
all_state_files.sort()
latest_file = all_state_files[-1]
with open(latest_file, "rb") as f:
Expand All @@ -68,38 +76,46 @@ def load_state(self, timestamp: Optional[str] = None):
with open(latest_file.replace("st_", "aimodel_plotdata_"), "rb") as f:
self.aimodel_plotdata = pickle.load(f)

return self.st, latest_file.replace("sim_state/st_", "").replace(".pkl", "")
return self.st, latest_file.replace(f"{root_path}/st_", "").replace(
".pkl", ""
)

# make sure the final state is written to disk before unpickling
# avoid race conditions with the pickling itself
if file_age_in_seconds("sim_state/st_final.pkl") < 3:
if file_age_in_seconds(f"{root_path}/st_final.pkl") < 3:
time.sleep(3)

with open("sim_state/st_final.pkl", "rb") as f:
with open(f"{root_path}/st_final.pkl", "rb") as f:
self.st = pickle.load(f)

with open("sim_state/aimodel_plotdata_final.pkl", "rb") as f:
with open(f"{root_path}/aimodel_plotdata_final.pkl", "rb") as f:
self.aimodel_plotdata = pickle.load(f)

return self.st, "final"

def init_state(self):
files = glob.glob("sim_state/*")
def init_state(self, multi_id):
files = glob.glob("sim_state/{multi_id}/*")

self.multi_id = multi_id

for f in files:
os.remove(f)

os.makedirs(f"sim_state/{multi_id}")

def save_state(
self, sim_state, aimodel_plotdata: AimodelPlotdata, is_final: bool = False
):
root_path = f"sim_state/{self.multi_id}"
ts = (
datetime.now().strftime("%Y%m%d_%H%M%S.%f")[:-3]
if not is_final
else "final"
)
with open(f"sim_state/st_{ts}.pkl", "wb") as f:
with open(f"{root_path}/st_{ts}.pkl", "wb") as f:
pickle.dump(sim_state, f)

with open(f"sim_state/aimodel_plotdata_{ts}.pkl", "wb") as f:
with open(f"{root_path}/aimodel_plotdata_{ts}.pkl", "wb") as f:
pickle.dump(aimodel_plotdata, f)

@enforce_types
Expand Down
19 changes: 16 additions & 3 deletions sim_plots.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import os
import sys
import time
from pathlib import Path

import streamlit

Expand All @@ -8,6 +11,7 @@
streamlit.set_page_config(layout="wide")

title = streamlit.empty()
subtitle = streamlit.empty()
inputs = streamlit.empty()
c1, c2, c3 = streamlit.columns((1, 1, 2))
c4, c5 = streamlit.columns((1, 1))
Expand All @@ -29,6 +33,15 @@
sim_plotter = SimPlotter()


def get_latest_state_id():
path = sorted(Path("sim_state").iterdir(), key=os.path.getmtime)[-1]
return str(path).replace("sim_state/", "")


state_id = sys.argv[1] if len(sys.argv) > 1 else get_latest_state_id()
subtitle.markdown(f"Simulation ID: {state_id}")


def load_canvas_on_state(ts):
titletext = f"Iter #{st.iter_number} ({ts})" if ts != "final" else "Final sim state"
title.title(titletext)
Expand All @@ -45,7 +58,7 @@ def load_canvas_on_state(ts):

while True:
try:
sim_plotter.load_state()
sim_plotter.load_state(state_id)
break
except Exception as e:
time.sleep(3)
Expand All @@ -54,7 +67,7 @@ def load_canvas_on_state(ts):

while True:
try:
st, new_ts = sim_plotter.load_state()
st, new_ts = sim_plotter.load_state(state_id)
except EOFError:
time.sleep(1)
continue
Expand All @@ -69,6 +82,6 @@ def load_canvas_on_state(ts):
if last_ts == "final":
snapshots = SimPlotter.available_snapshots()
timestamp = inputs.select_slider("Go to snapshot", snapshots, value="final")
st, new_ts = sim_plotter.load_state(timestamp)
st, new_ts = sim_plotter.load_state(state_id, timestamp)
load_canvas_on_state(timestamp)
break
Empty file removed sim_state/.gitkeep
Empty file.

0 comments on commit cc039b6

Please sign in to comment.