Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert streamlit dashboard to Dash #938

Merged
merged 17 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions READMEs/predictoor.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ cd ~/code/pdr-backend # or wherever your pdr-backend dir is
source venv/bin/activate

#display real-time plots of the simulation
streamlit run sim_plots.py
sim_plots
```

"Predict" actions are _two-sided_: it does one "up" prediction tx, and one "down" tx, with more stake to the higher-confidence direction. Two-sided is more profitable than one-sided prediction.
Expand All @@ -101,10 +101,10 @@ To see simulation CLI options: `pdr sim -h`.

Simulation uses Python [logging](https://docs.python.org/3/howto/logging.html) framework. Configure it via [`logging.yaml`](../logging.yaml). [Here's](https://medium.com/@cyberdud3/a-step-by-step-guide-to-configuring-python-logging-with-yaml-files-914baea5a0e5) a tutorial on yaml settings.

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`
By default, Dash 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 `sim_plots --run_id <unique_id>` e.g. `sim_plots --run-id 97f9633c-a78c-4865-9cc6-b5152c9500a3`

You can run many instances of streamlit at once, with different URLs.
You can run many instances of Dash at once, with different URLs. To run on different ports, use the `--port` argument.

## 3. Run Predictoor Bot on Sapphire Testnet

Expand Down
8 changes: 4 additions & 4 deletions READMEs/trader.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ cd ~/code/pdr-backend # or wherever your pdr-backend dir is
source venv/bin/activate

#display real-time plots of the simulation
streamlit run sim_plots.py
sim_plots
```

"Predict" actions are _two-sided_: it does one "up" prediction tx, and one "down" tx, with more stake to the higher-confidence direction. Two-sided is more profitable than one-sided prediction.
Expand All @@ -93,10 +93,10 @@ To see simulation CLI options: `pdr sim -h`.

Simulation uses Python [logging](https://docs.python.org/3/howto/logging.html) framework. Configure it via [`logging.yaml`](../logging.yaml). [Here's](https://medium.com/@cyberdud3/a-step-by-step-guide-to-configuring-python-logging-with-yaml-files-914baea5a0e5) a tutorial on yaml settings.

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`
By default, Dash 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 `sim_plots --run_id <unique_id>` e.g. `sim_plots --run_id 97f9633c-a78c-4865-9cc6-b5152c9500a3`

You can run many instances of streamlit at once, with different URLs.
You can run many instances of Dash at once, with different URLs. To run on different ports, use the `--port` argument.

## Run Trader Bot on Sapphire Testnet

Expand Down
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ ignore_missing_imports = True
[mypy-ccxt.*]
ignore_missing_imports = True

[mypy-dash.*]
ignore_missing_imports = True

[mypy-ecies.*]
ignore_missing_imports = True

Expand Down
24 changes: 21 additions & 3 deletions pdr_backend/aimodel/aimodel_plotdata.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from typing import List
from typing import List, Optional

from enforce_typing import enforce_types
import numpy as np

from pdr_backend.aimodel.aimodel import Aimodel


@enforce_types
class AimodelPlotdata:
"""Simple class to manage many inputs going into plot_model."""

Expand All @@ -17,14 +16,18 @@ def __init__(
ytrue_train: np.ndarray,
colnames: List[str],
slicing_x: np.ndarray,
sweep_vars: Optional[List[int]] = None,
):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't make this optional, make it mandatory. If it's optional, it's too much risk of someone forgetting to specify it when they mean to.

Copy link
Contributor Author

@calina-c calina-c Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's feasible. We create the aimodel itself, then we sweep through it. We don't even know what we are going to sweep when we create it. I added sweep_vars as None when we don't sweep anything. We can adjust this as needed, but in case the property doesn't exist I consider all vars.

"""
@arguments
model -- Aimodel
X_train -- 2d array [sample_i, var_i]:cont_value -- model trn inputs
ytrue_train -- 1d array [sample_i]:bool_value -- model trn outputs
colnames -- [var_i]:str -- name for each of the X inputs
slicing_x -- arrat [dim_i]:floatval - when >2 dims, plot about this pt
slicing_x -- array [var_i]:floatval - values for non-sweep vars
sweep_vars -- list with [sweepvar_i] or [sweepvar_i, sweepvar_j]
-- If 1 entry, do line plot (1 var), where y-axis is response
-- If 2 entries, do contour plot (2 vars), where z-axis is response
"""
# preconditions
assert X_train.shape[1] == len(colnames) == slicing_x.shape[0], (
Expand All @@ -36,15 +39,30 @@ def __init__(
X_train.shape[0],
ytrue_train.shape[0],
)
assert sweep_vars is None or len(sweep_vars) in [1, 2]

# set values
self.model = model
self.X_train = X_train
self.ytrue_train = ytrue_train
self.colnames = colnames
self.slicing_x = slicing_x
self.sweep_vars = sweep_vars

@property
@enforce_types
def n(self) -> int:
"""Number of input dimensions == # columns in X"""
return self.X_train.shape[1]

@property
@enforce_types
def n_sweep(self) -> int:
"""Number of variables to sweep in the plot"""
if self.sweep_vars is None:
return 0

Comment on lines +62 to +63
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if self.sweep_vars is None:
            return 0

Delete this ^, in line with comment above. (Sweep vars should be a list with one or two entries)

if self.n == 1:
return 1

return len(self.sweep_vars)
80 changes: 59 additions & 21 deletions pdr_backend/aimodel/aimodel_plotter.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
from enforce_typing import enforce_types
import numpy as np
import plotly.graph_objects as go
from enforce_typing import enforce_types

from pdr_backend.aimodel.aimodel_plotdata import AimodelPlotdata


@enforce_types
def plot_aimodel_response(
aimodel_plotdata: AimodelPlotdata,
):
def plot_aimodel_response(aimodel_plotdata: AimodelPlotdata):
"""
@description
Plot the model response in a line plot (1 var) contour plot (>1 vars)
Plot the model response in a line plot (1 var) or contour plot (2 vars).
And overlay X-data. (Training data or otherwise.)
If the model has >2 vars, it plots the 2 most important vars.

@arguments
aimodel_plotdata -- holds:
model -- Aimodel
X_train -- array [sample_i][var_i]:floatval -- trn model inputs (or other)
ytrue_train -- array [sample_i]:boolval -- trn model outputs (or other)
colnames -- list [var_i]:X_column_name
slicing_x -- arrat [var_i]:floatval - when >2 dims, plot about this pt
fig_ax -- None or (fig, ax) to easily embed into existing plot
legend_loc -- eg "upper left". Applies only to contour plots.
"""
if aimodel_plotdata.n == 1:
return _plot_aimodel_lineplot(aimodel_plotdata)
d = aimodel_plotdata
# assert d.n_sweep in [1, 2]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# assert d.n_sweep in [1, 2]

This should not be commented out. (In line with comments above)

if d.n_sweep == 1 and d.n == 1 or d.n == 1:
return _plot_aimodel_lineplot_1var(aimodel_plotdata)

if d.n_sweep == 1 and d.n > 1:
return _plot_aimodel_lineplot_nvars(aimodel_plotdata)

return _plot_aimodel_contour(aimodel_plotdata)

Expand All @@ -35,15 +28,17 @@ def plot_aimodel_response(


@enforce_types
def _plot_aimodel_lineplot(aimodel_plotdata: AimodelPlotdata):
def _plot_aimodel_lineplot_1var(aimodel_plotdata: AimodelPlotdata):
"""
@description
Plot the model, when there's 1 input x-var. Use a line plot.
Do a 1d lineplot, when exactly 1 input x-var
Will fail if not 1 var.
Because one var total, we can show more info of true-vs-actual
"""
# aimodel data
assert aimodel_plotdata.n == 1
d = aimodel_plotdata
assert d.n == 1
assert d.n_sweep == 1
X, ytrue = d.X_train, d.ytrue_train

x = X[:, 0]
Expand Down Expand Up @@ -128,6 +123,49 @@ def _plot_aimodel_lineplot(aimodel_plotdata: AimodelPlotdata):
return fig_bars


@enforce_types
def _plot_aimodel_lineplot_nvars(aimodel_plotdata: AimodelPlotdata):
"""
@description
Do a 1d lineplot, when >1 input x-var, and we have chosen the var.
Because >1 var total, we can show more info of true-vs-actual
"""
# input data
d = aimodel_plotdata
assert d.n >= 1
assert d.n_sweep == 1

# construct sweep_x
sweepvar_i = d.sweep_vars[0] # type: ignore[index]
mn_x, mx_x = min(d.X_train[:, sweepvar_i]), max(d.X_train[:, sweepvar_i])
N = 200
sweep_x = np.linspace(mn_x, mx_x, N)

# construct X
X = np.empty((N, d.n), dtype=float)
X[:, sweepvar_i] = sweep_x
for var_i in range(d.n):
if var_i == sweepvar_i:
continue
X[:, var_i] = d.slicing_x[var_i]

# calc model response
yptrue = d.model.predict_ptrue(X) # [sample_i]: prob_of_being_true

# line plot: model response surface
fig_line = go.Figure(
data=go.Scatter(
x=sweep_x,
y=yptrue,
mode="lines",
line={"color": "gray"},
name="model prob(true)",
)
)

return fig_line


@enforce_types
def _plot_aimodel_contour(
aimodel_plotdata: AimodelPlotdata,
Expand Down
61 changes: 59 additions & 2 deletions pdr_backend/aimodel/test/test_aimodel_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def test_aimodel_accuracy_from_create_xy():


@enforce_types
def test_aimodel_factory_1var_main():
def test_aimodel_factory_1varmodel_lineplot():
"""1 input var. It will plot that var on both axes"""
# settings, factory
ss = AimodelSS(aimodel_ss_test_dict(approach="LinearLogistic"))
Expand All @@ -159,7 +159,64 @@ def test_aimodel_factory_1var_main():
# plot
colnames = ["x0"]
slicing_x = np.array([0.1]) # arbitrary
aimodel_plotdata = AimodelPlotdata(model, X, ytrue, colnames, slicing_x)
sweep_vars = [0]
aimodel_plotdata = AimodelPlotdata(
model,
X,
ytrue,
colnames,
slicing_x,
sweep_vars,
)
figure = plot_aimodel_response(aimodel_plotdata)
assert isinstance(figure, Figure)

if SHOW_PLOT:
figure.show()


@enforce_types
def test_aimodel_factory_5varmodel_lineplot():
"""5 input vars; sweep 1 var."""
# settings, factory
ss = AimodelSS(aimodel_ss_test_dict(approach="LinearLogistic"))
factory = AimodelFactory(ss)

# data
N = 1000
mn, mx = -10.0, +10.0
X = np.random.uniform(mn, mx, (N, 5))
ycont = (
10.0
+ 0.1 * X[:, 0]
+ 1.0 * X[:, 1]
+ 2.0 * X[:, 2]
+ 3.0 * X[:, 3]
+ 4.0 * X[:, 4]
)
y_thr = np.average(ycont) # avg gives good class balance
ytrue = ycont > y_thr

# build model
model = factory.build(X, ytrue, show_warnings=False)

# test variable importances
imps = model.importance_per_var()
assert len(imps) == 5
assert imps[0] < imps[1] < imps[2] < imps[3] < imps[4]

# plot
colnames = ["x0", "x1", "x2", "x3", "x4"]
slicing_x = np.array([0.1] * 5) # arbitrary
sweep_vars = [2] # x2
aimodel_plotdata = AimodelPlotdata(
model,
X,
ytrue,
colnames,
slicing_x,
sweep_vars,
)
figure = plot_aimodel_response(aimodel_plotdata)
assert isinstance(figure, Figure)

Expand Down
10 changes: 1 addition & 9 deletions pdr_backend/cli/test/test_cli_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ def test_do_topup(monkeypatch):


@enforce_types
def test_do_main(monkeypatch, capfd):
def test_do_main(capfd):
with patch("sys.argv", ["pdr", "help"]):
with pytest.raises(SystemExit):
_do_main()
Expand All @@ -420,11 +420,3 @@ def test_do_main(monkeypatch, capfd):
_do_main()

assert "Predictoor tool" in capfd.readouterr().out

mock_f = Mock()
monkeypatch.setattr(f"{_CLI_PATH}.SimEngine.run", mock_f)

with patch("sys.argv", ["streamlit_entrypoint.py", "sim", "ppss.yaml"]):
_do_main()

assert mock_f.called
6 changes: 3 additions & 3 deletions pdr_backend/sim/sim_plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ def __init__(
self.multi_id = None

@staticmethod
def available_snapshots():
all_state_files = glob.glob("sim_state/st_*.pkl")
def available_snapshots(multi_id):
all_state_files = glob.glob(f"sim_state/{multi_id}/st_*.pkl")

all_timestamps = [
f.replace("sim_state/st_", "").replace(".pkl", "")
f.replace(f"sim_state/{multi_id}/st_", "").replace(".pkl", "")
for f in all_state_files
if "final" not in f
]
Expand Down
Empty file added pdr_dash_plots/__init__.py
Empty file.
Loading
Loading