From cc039b6e5359a6bdfc19879b95322920157bd0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C4=83lina=20Cenan?= Date: Fri, 5 Apr 2024 12:12:35 +0300 Subject: [PATCH] Adds multisim ids to enable multiple streamlit runs/comparisons. (#857) --- .gitignore | 2 +- READMEs/trader.md | 4 ++++ pdr_backend/sim/sim_engine.py | 11 +++++---- pdr_backend/sim/sim_plotter.py | 44 +++++++++++++++++++++++----------- sim_plots.py | 19 ++++++++++++--- sim_state/.gitkeep | 0 6 files changed, 58 insertions(+), 22 deletions(-) delete mode 100644 sim_state/.gitkeep diff --git a/.gitignore b/.gitignore index bddace341..d4a5f0a6b 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/READMEs/trader.md b/READMEs/trader.md index efadc25f5..351b5aeb0 100644 --- a/READMEs/trader.md +++ b/READMEs/trader.md @@ -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 ` 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. diff --git a/pdr_backend/sim/sim_engine.py b/pdr_backend/sim/sim_engine.py index 322f61d6b..8d89d3a85 100644 --- a/pdr_backend/sim/sim_engine.py +++ b/pdr_backend/sim/sim_engine.py @@ -2,6 +2,7 @@ import logging import os from typing import Optional +import uuid import numpy as np import polars as pl @@ -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: @@ -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) @@ -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 diff --git a/pdr_backend/sim/sim_plotter.py b/pdr_backend/sim/sim_plotter.py index c661ec085..744443d7f 100644 --- a/pdr_backend/sim/sim_plotter.py +++ b/pdr_backend/sim/sim_plotter.py @@ -24,6 +24,7 @@ def __init__( ): self.st = None self.aimodel_plotdata = None + self.multi_id = None @staticmethod def available_snapshots(): @@ -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: @@ -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 diff --git a/sim_plots.py b/sim_plots.py index 150784b0e..441296278 100755 --- a/sim_plots.py +++ b/sim_plots.py @@ -1,4 +1,7 @@ +import os +import sys import time +from pathlib import Path import streamlit @@ -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)) @@ -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) @@ -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) @@ -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 @@ -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 diff --git a/sim_state/.gitkeep b/sim_state/.gitkeep deleted file mode 100644 index e69de29bb..000000000