New Structure Demo (#120)

better demo
---------

Co-authored-by: Young <afe.young@gmail.com>
Co-authored-by: Taozhi Wang <taozhi.mark.wang@gmail.com>
Co-authored-by: you-n-g <you-n-g@users.noreply.github.com>
Co-authored-by: cyncyw <47289405+taozhiwang@users.noreply.github.com>
This commit is contained in:
XianBW
2024-07-30 17:23:05 +08:00
committed by GitHub
parent 1b4e98a752
commit dbbec2ffaf
11 changed files with 984 additions and 73 deletions
@@ -1,13 +1,12 @@
from pathlib import Path
from rdagent.components.coder.model_coder.model import (
ModelExperiment,
)
from rdagent.components.coder.model_coder.model import ModelExperiment
from rdagent.core.prompts import Prompts
from rdagent.core.scenario import Scenario
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
class GeneralModelScenario(Scenario):
@property
def background(self) -> str:
@@ -28,10 +27,10 @@ class GeneralModelScenario(Scenario):
@property
def simulator(self) -> str:
return prompt_dict["general_model_simulator"]
@property
def rich_style_description(self)->str:
return '''
def rich_style_description(self) -> str:
return """
# General Model Scenario
## Overview
@@ -61,7 +60,7 @@ This scenario automates the development of PyTorch models by reading academic pa
- **Time-Series Data:** Sequential data points indexed in time order, useful for forecasting and temporal pattern recognition.
- **Graph Data:** Data structured as nodes and edges, suitable for network analysis and relational tasks.
'''
"""
def get_scenario_all_desc(self) -> str:
return f"""Background of the scenario:
@@ -3,18 +3,24 @@ from dotenv import load_dotenv
load_dotenv(override=True)
import fire
from rdagent.app.model_extraction_and_code.GeneralModel import GeneralModelScenario
from rdagent.components.coder.model_coder.task_loader import (
ModelExperimentLoaderFromPDFfiles,
)
from rdagent.scenarios.qlib.developer.model_coder import QlibModelCoSTEER
from rdagent.app.model_extraction_and_code.GeneralModel import GeneralModelScenario
from rdagent.components.document_reader.document_reader import extract_first_page_screenshot_from_pdf
from rdagent.components.document_reader.document_reader import (
extract_first_page_screenshot_from_pdf,
)
from rdagent.log import rdagent_logger as logger
import fire
from rdagent.scenarios.qlib.developer.model_coder import QlibModelCoSTEER
def extract_models_and_implement(report_file_path: str = "/home/v-xisenwang/RD-Agent/rdagent/app/model_extraction_and_code/test_doc1.pdf") -> None:
def extract_models_and_implement(
report_file_path: str = "/home/v-xisenwang/RD-Agent/rdagent/app/model_extraction_and_code/test_doc1.pdf",
) -> None:
with logger.tag("r"):
# Save Relevant Images
# Save Relevant Images
img = extract_first_page_screenshot_from_pdf(report_file_path)
logger.log_object(img, tag="pdf_image")
scenario = GeneralModelScenario()
@@ -26,5 +32,6 @@ def extract_models_and_implement(report_file_path: str = "/home/v-xisenwang/RD-A
logger.log_object(exp, tag="developed_experiment")
return exp
if __name__ == "__main__":
fire.Fire(extract_models_and_implement)
@@ -41,6 +41,7 @@ class BenchmarkAnalyzer:
processed_data = self.analyze_data(summarized_data)
final_res[experiment] = processed_data.iloc[-1, :]
return final_res
def reformat_succ_rate(self, display_df):
new_idx = []
display_df = display_df[display_df.index.isin(self.index_map.keys())]
@@ -166,6 +167,7 @@ class Plotter:
plt.title("Comparison of Different Methods")
plt.savefig(file_name)
if __name__ == "__main__":
settings = BenchmarkSettings()
benchmark = BenchmarkAnalyzer(settings)
@@ -15,12 +15,6 @@ from rdagent.scenarios.qlib.factor_experiment_loader.json_loader import (
FactorTestCaseLoaderFromJsonFile,
)
from rdagent.core.utils import import_class
from rdagent.core.scenario import Scenario
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorScenario
from pprint import pprint
# 1.read the settings
bs = BenchmarkSettings()
@@ -35,6 +35,7 @@ EVAL_RES = Dict[
List[Tuple[FactorEvaluator, Union[object, RunnerException]]],
]
class TestCase:
def __init__(
self,
+1 -1
View File
@@ -112,7 +112,7 @@ class HypothesisGen(ABC):
class Hypothesis2Experiment(ABC, Generic[ASpecificExp]):
"""
[Abstract description => concrete description] => Code implementation Card
[Abstract description => concrete description] => Code implementation Card
"""
@abstractmethod
+16 -10
View File
@@ -90,22 +90,28 @@ class FileStorage(Storage):
message_end = next_match.start() if next_match else len(content)
message_content = content[message_start:message_end].strip()
if "Logging object in" in message_content:
continue
m = Message(
tag=tag, level=level, timestamp=timestamp, caller=caller, pid_trace=pid, content=message_content
)
if isinstance(m.content, str) and "Logging object in" in m.content:
absolute_p = m.content.split("Logging object in ")[1]
relative_p = "." + absolute_p.split(self.path.name)[1]
pkl_path = self.path / relative_p
try:
with pkl_path.open("rb") as f:
m.content = pickle.load(f)
except:
continue
msg_l.append(m)
for file in self.path.glob("**/*.pkl"):
tag = ".".join(str(file.relative_to(self.path)).replace("/", ".").split(".")[:-3])
pid = file.parent.name
with file.open("rb") as f:
content = pickle.load(f)
timestamp = datetime.strptime(file.stem, "%Y-%m-%d_%H-%M-%S-%f").replace(tzinfo=timezone.utc)
m = Message(tag=tag, level="INFO", timestamp=timestamp, caller="", pid_trace=pid, content=content)
msg_l.append(m)
msg_l.sort(key=lambda x: x.timestamp)
for m in msg_l:
yield m
+472 -20
View File
@@ -1,26 +1,478 @@
import pickle
from pathlib import Path
import time
from collections import defaultdict
from datetime import datetime, timezone
from typing import Callable, Type
from rdagent.core.proposal import Trace
from rdagent.log.storage import FileStorage, Message
from rdagent.log.ui.web import (
SimpleTraceWindow,
TraceObjWindow,
TraceWindow,
WebView,
mock_msg,
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st
from plotly.subplots import make_subplots
from streamlit import session_state as state
from streamlit.delta_generator import DeltaGenerator
from rdagent.components.coder.factor_coder.CoSTEER.evaluators import (
FactorSingleFeedback,
)
from rdagent.components.coder.factor_coder.factor import FactorFBWorkspace, FactorTask
from rdagent.components.coder.model_coder.CoSTEER.evaluators import ModelCoderFeedback
from rdagent.components.coder.model_coder.model import ModelFBWorkspace, ModelTask
from rdagent.core.proposal import Hypothesis, HypothesisFeedback
from rdagent.log.base import Message
from rdagent.log.storage import FileStorage
from rdagent.log.ui.qlib_report_figure import report_figure
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
from rdagent.scenarios.qlib.experiment.model_experiment import (
QlibModelExperiment,
QlibModelScenario,
)
# show logs folder
WebView(TraceWindow()).display(FileStorage("/data/home/bowen/workspace/RD-Agent/log/yuante/2024-07-24_04-03-33-691119"))
# WebView(TraceWindow()).display(FileStorage("/data/home/bowen/workspace/RD-Agent/log/2024-07-22_03-01-12-021659"))
# WebView(TraceWindow()).display(FileStorage("./log/2024-07-18_08-37-00-477228"))
st.set_page_config(layout="wide")
# load Trace obj
# with Path('./log/step_trace.pkl').open('rb') as f:
# obj = pickle.load(f)
# trace: Trace = obj[-1]
if "log_path" not in state:
state.log_path = ""
# show Trace obj
# TraceObjWindow().consume_msg(mock_msg(trace))
if "log_type" not in state:
state.log_type = "qlib_model"
if "fs" not in state:
state.fs = None
if "msgs" not in state:
state.msgs = defaultdict(lambda: defaultdict(list))
if "last_msg" not in state:
state.last_msg = None
if "current_tags" not in state:
state.current_tags = []
if "lround" not in state:
state.lround = 0 # RD Loop Round
if "erounds" not in state:
state.erounds = defaultdict(int) # Evolving Rounds in each RD Loop
# Summary Info
if "hypotheses" not in state:
# Hypotheses in each RD Loop
state.hypotheses = defaultdict(None)
if "h_decisions" not in state:
state.h_decisions = defaultdict(bool)
if "metric_series" not in state:
state.metric_series = []
def refresh():
state.fs = FileStorage(state.log_path).iter_msg()
state.msgs = defaultdict(lambda: defaultdict(list))
state.lround = 0
state.erounds = defaultdict(int)
state.hypotheses = defaultdict(None)
state.h_decisions = defaultdict(bool)
state.metric_series = []
state.last_msg = None
state.current_tags = []
def should_display(msg: Message):
for t in state.excluded_tags:
if t in msg.tag.split("."):
return False
if type(msg.content).__name__ in state.excluded_types:
return False
return True
def get_msgs_until(end_func: Callable[[Message], bool] = lambda _: True):
if state.fs:
while True:
try:
msg = next(state.fs)
if should_display(msg):
tags = msg.tag.split(".")
if "r" not in state.current_tags and "r" in tags:
state.lround += 1
if "evolving code" not in state.current_tags and "evolving code" in tags:
state.erounds[state.lround] += 1
state.current_tags = tags
state.last_msg = msg
state.msgs[state.lround][msg.tag].append(msg)
# Update Summary Info
if "model runner result" in tags or "factor runner result" in tags or "runner result" in tags:
if msg.content.result is None:
state.metric_series.append(pd.Series([None], index=["AUROC"]))
else:
if msg.content.result.name == "AUROC":
ps = msg.content.result
ps.index = ["AUROC"]
state.metric_series.append(ps)
else:
state.metric_series.append(
msg.content.result.loc[
[
"IC",
"1day.excess_return_without_cost.annualized_return",
"1day.excess_return_without_cost.information_ratio",
"1day.excess_return_without_cost.max_drawdown",
]
]
)
elif "hypothesis generation" in tags:
state.hypotheses[state.lround] = msg.content
elif "ef" in tags and "feedback" in tags:
state.h_decisions[state.lround] = msg.content.decision
# Stop Getting Logs
if end_func(msg):
break
except StopIteration:
break
# Config Sidebar
with st.sidebar:
st.text_input("log path", key="log_path", on_change=refresh)
st.selectbox("trace type", ["qlib_model", "qlib_factor", "model_extraction_and_implementation"], key="log_type")
st.multiselect("excluded log tags", ["llm_messages"], ["llm_messages"], key="excluded_tags")
st.multiselect("excluded log types", ["str", "dict", "list"], ["str"], key="excluded_types")
if st.button("refresh"):
refresh()
debug = st.checkbox("debug", value=False)
if debug:
if st.button("Single Step Run"):
get_msgs_until()
# Debug Info Window
if debug:
with st.expander(":red[**Debug Info**]", expanded=True):
dcol1, dcol2 = st.columns([1, 3])
with dcol1:
st.markdown(
f"**trace type**: {state.log_type}\n\n"
f"**log path**: {state.log_path}\n\n"
f"**excluded tags**: {state.excluded_tags}\n\n"
f"**excluded types**: {state.excluded_types}\n\n"
f":blue[**message id**]: {sum(sum(len(tmsgs) for tmsgs in rmsgs.values()) for rmsgs in state.msgs.values())}\n\n"
f":blue[**round**]: {state.lround}\n\n"
f":blue[**evolving round**]: {state.erounds[state.lround]}\n\n"
)
with dcol2:
if state.last_msg:
st.write(state.last_msg)
if isinstance(state.last_msg.content, list):
st.write(state.last_msg.content[0])
elif not isinstance(state.last_msg.content, str):
st.write(state.last_msg.content)
# Main Window
# Project Info
with st.container():
image_c, toc_c = st.columns([3, 3], vertical_alignment="center")
with image_c:
st.image("./docs/_static/scen.jpg")
with toc_c:
st.markdown(
"""
# RD-Agent🤖
## [Scenario Description](#_scenario)
## [Summary](#_summary)
## [RD-Loops](#_rdloops)
### [Research](#_research)
### [Development](#_development)
### [Feedback](#_feedback)
"""
)
with st.container(border=True):
st.header("Scenario Description📖", divider=True, anchor="_scenario")
# TODO: other scenarios
if state.log_type == "qlib_model":
st.markdown(QlibModelScenario().rich_style_description)
elif state.log_type == "model_extraction_and_implementation":
st.markdown(
"""
# General Model Scenario
## Overview
This demo automates the extraction and iterative development of models from academic papers, ensuring functionality and correctness.
### Scenario: Auto-Developing Model Code from Academic Papers
#### Overview
This scenario automates the development of PyTorch models by reading academic papers or other sources. It supports various data types, including tabular, time-series, and graph data. The primary workflow involves two main components: the Reader and the Coder.
#### Workflow Components
1. **Reader**
- Parses and extracts relevant model information from academic papers or sources, including architectures, parameters, and implementation details.
- Uses Large Language Models to convert content into a structured format for the Coder.
2. **Evolving Coder**
- Translates structured information from the Reader into executable PyTorch code.
- Utilizes an evolving coding mechanism to ensure correct tensor shapes, verified with sample input tensors.
- Iteratively refines the code to align with source material specifications.
#### Supported Data Types
- **Tabular Data:** Structured data with rows and columns, such as spreadsheets or databases.
- **Time-Series Data:** Sequential data points indexed in time order, useful for forecasting and temporal pattern recognition.
- **Graph Data:** Data structured as nodes and edges, suitable for network analysis and relational tasks.
"""
)
# Summary Window
@st.experimental_fragment()
def summary_window():
if state.log_type in ["qlib_model", "qlib_factor"]:
with st.container():
st.header("Summary📊", divider=True, anchor="_summary")
hypotheses_c, chart_c = st.columns([2, 3])
# TODO: not fixed height
with hypotheses_c.container(height=600):
st.markdown("**Hypotheses🏅**")
h_str = "\n".join(
f"{id}. :green[**{h.hypothesis}**]\n\t>:green-background[*{h.__dict__.get('concise_reason', '')}*]"
if state.h_decisions[id]
else f"{id}. {h.hypothesis}\n\t>*{h.__dict__.get('concise_reason', '')}*"
for id, h in state.hypotheses.items()
)
st.markdown(h_str)
with chart_c.container(height=600):
mt_c, ms_c = st.columns(2, vertical_alignment="center")
with mt_c:
st.markdown("**Metrics📈**")
with ms_c:
show_true_only = st.checkbox("True Decisions Only", value=False)
labels = [f"Round {i}" for i in range(1, len(state.metric_series) + 1)]
df = pd.DataFrame(state.metric_series, index=labels)
if show_true_only and len(state.hypotheses) >= len(state.metric_series):
df = df.iloc[[i for i in range(df.shape[0]) if state.h_decisions[i + 1]]]
if df.shape[0] == 1:
st.table(df.iloc[0])
elif df.shape[0] > 1:
# TODO: figure label
# TODO: separate into different figures
if df.shape[1] == 1:
# suhan's scenario
fig = px.line(df, x=df.index, y=df.columns, markers=True)
fig.update_layout(legend_title_text="Metrics", xaxis_title="Loop Round", yaxis_title=None)
else:
# 2*2 figure
fig = make_subplots(rows=2, cols=2, subplot_titles=df.columns)
for ci, col in enumerate(df.columns):
row = ci // 2 + 1
col_num = ci % 2 + 1
fig.add_trace(
go.Scatter(x=df.index, y=df[col], mode="lines+markers", name=col), row=row, col=col_num
)
fig.update_layout(title_text="Metrics", showlegend=False)
st.plotly_chart(fig)
summary_window()
# R&D Loops Window
st.header("R&D Loops♾️", divider=True, anchor="_rdloops")
button_c1, button_c2, round_s_c = st.columns([2, 3, 18], vertical_alignment="center")
with button_c1:
if st.button("Run One Loop"):
get_msgs_until(lambda m: "ef.feedback" in m.tag)
with button_c2:
if st.button("Run One Evolving Step"):
get_msgs_until(lambda m: "d.evolving feedback" in m.tag)
if len(state.msgs) > 1:
with round_s_c:
round = st.select_slider("Select RDLoop Round", options=state.msgs.keys(), value=state.lround)
else:
round = 1
rf_c, d_c = st.columns([2, 2])
# Research & Feedback Window
with rf_c:
if state.log_type in ["qlib_model", "qlib_factor"]:
# Research Window
with st.container(border=True):
st.subheader("Research🔍", divider=True, anchor="_research")
# pdf image
if pim := state.msgs[round]["r.extract_factors_and_implement.load_pdf_screenshot"]:
for i in range(min(2, len(pim))):
st.image(pim[i].content)
# Hypothesis
if hg := state.msgs[round]["r.hypothesis generation"]:
st.markdown("**Hypothesis💡**") # 🧠
h: Hypothesis = hg[0].content
st.markdown(
f"""
- **Hypothesis**: {h.hypothesis}
- **Reason**: {h.reason}"""
)
if eg := state.msgs[round]["r.experiment generation"]:
if isinstance(eg[0].content[0], FactorTask):
st.markdown("**Factor Tasks**")
fts = eg[0].content
tabs = st.tabs([f.factor_name for f in fts])
for i, ft in enumerate(fts):
with tabs[i]:
# st.markdown(f"**Factor Name**: {ft.factor_name}")
st.markdown(f"**Description**: {ft.factor_description}")
st.latex(f"Formulation: {ft.factor_formulation}")
variables_df = pd.DataFrame(ft.variables, index=["Description"]).T
variables_df.index.name = "Variable"
st.table(variables_df)
elif isinstance(eg[0].content[0], ModelTask):
st.markdown("**Model Tasks**")
mts = eg[0].content
tabs = st.tabs([m.name for m in mts])
for i, mt in enumerate(mts):
with tabs[i]:
# st.markdown(f"**Model Name**: {mt.name}")
st.markdown(f"**Model Type**: {mt.model_type}")
st.markdown(f"**Description**: {mt.description}")
st.latex(f"Formulation: {mt.formulation}")
variables_df = pd.DataFrame(mt.variables, index=["Value"]).T
variables_df.index.name = "Variable"
st.table(variables_df)
# Feedback Window
with st.container(border=True):
st.subheader("Feedback📝", divider=True, anchor="_feedback")
if fbr := state.msgs[round]["ef.Quantitative Backtesting Chart"]:
st.markdown("**Returns📈**")
fig = report_figure(fbr[0].content)
st.plotly_chart(fig)
if fb := state.msgs[round]["ef.feedback"]:
st.markdown("**Hypothesis Feedback🔍**")
h: HypothesisFeedback = fb[0].content
st.markdown(
f"""
- **Observations**: {h.observations}
- **Hypothesis Evaluation**: {h.hypothesis_evaluation}
- **New Hypothesis**: {h.new_hypothesis}
- **Decision**: {h.decision}
- **Reason**: {h.reason}"""
)
elif state.log_type == "model_extraction_and_implementation":
# Research Window
with st.container(border=True):
# pdf image
st.subheader("Research🔍", divider=True, anchor="_research")
if pim := state.msgs[round]["r.pdf_image"]:
for i in range(len(pim)):
st.image(pim[i].content)
# loaded model exp
if mem := state.msgs[round]["d.load_experiment"]:
me: QlibModelExperiment = mem[0].content
mts: list[ModelTask] = me.sub_tasks
tabs = st.tabs([m.name for m in mts])
for i, mt in enumerate(mts):
with tabs[i]:
# st.markdown(f"**Model Name**: {mt.name}")
st.markdown(f"**Model Type**: {mt.model_type}")
st.markdown(f"**Description**: {mt.description}")
st.latex(f"Formulation: {mt.formulation}")
variables_df = pd.DataFrame(mt.variables, index=["Value"]).T
variables_df.index.name = "Variable"
st.table(variables_df)
# Feedback Window
with st.container(border=True):
st.subheader("Feedback📝", divider=True, anchor="_feedback")
if fbr := state.msgs[round]["d.developed_experiment"]:
st.markdown("**Returns📈**")
result_df = fbr[0].content.result
if result_df:
fig = report_figure(result_df)
st.plotly_chart(fig)
else:
st.markdown("Returns is None")
# Development Window (Evolving)
with d_c.container(border=True):
st.subheader("Development🛠️", divider=True, anchor="_development")
# Evolving Tabs
if state.erounds[round] > 0:
etabs = st.tabs([str(i) for i in range(1, state.erounds[round] + 1)])
for i in range(0, state.erounds[round]):
with etabs[i]:
ws: list[FactorFBWorkspace | ModelFBWorkspace] = state.msgs[round]["d.evolving code"][i].content
ws = [w for w in ws if w]
# All Tasks
tab_names = [
w.target_task.factor_name if isinstance(w.target_task, FactorTask) else w.target_task.name for w in ws
]
wtabs = st.tabs(tab_names)
for j, w in enumerate(ws):
with wtabs[j]:
# Evolving Code
for k, v in w.code_dict.items():
with st.expander(f":green[`{k}`]", expanded=True):
st.code(v, language="python")
# Evolving Feedback
if len(state.msgs[round]["d.evolving feedback"]) > i:
wsf: list[FactorSingleFeedback | ModelCoderFeedback] = state.msgs[round]["d.evolving feedback"][
i
].content[j]
if isinstance(wsf, FactorSingleFeedback):
st.markdown(
f"""#### :blue[Factor Execution Feedback]
{wsf.execution_feedback}
#### :blue[Factor Code Feedback]
{wsf.code_feedback}
#### :blue[Factor Value Feedback]
{wsf.factor_value_feedback}
#### :blue[Factor Final Feedback]
{wsf.final_feedback}
#### :blue[Factor Final Decision]
This implementation is {'SUCCESS' if wsf.final_decision else 'FAIL'}.
"""
)
elif isinstance(wsf, ModelCoderFeedback):
st.markdown(
f"""#### :blue[Model Execution Feedback]
{wsf.execution_feedback}
#### :blue[Model Shape Feedback]
{wsf.shape_feedback}
#### :blue[Model Value Feedback]
{wsf.value_feedback}
#### :blue[Model Code Feedback]
{wsf.code_feedback}
#### :blue[Model Final Feedback]
{wsf.final_feedback}
#### :blue[Model Final Decision]
This implementation is {'SUCCESS' if wsf.final_decision else 'FAIL'}.
"""
)
# TODO: evolving tabs -> slider
# TODO: multi tasks SUCCESS/FAIL
# TODO: evolving progress bar, diff colors
+445
View File
@@ -0,0 +1,445 @@
import importlib
import math
import pandas as pd
import plotly.graph_objs as go
from plotly.subplots import make_subplots
class BaseGraph:
_name = None
def __init__(
self, df: pd.DataFrame = None, layout: dict = None, graph_kwargs: dict = None, name_dict: dict = None, **kwargs
):
"""
:param df:
:param layout:
:param graph_kwargs:
:param name_dict:
:param kwargs:
layout: dict
go.Layout parameters
graph_kwargs: dict
Graph parameters, eg: go.Bar(**graph_kwargs)
"""
self._df = df
self._layout = dict() if layout is None else layout
self._graph_kwargs = dict() if graph_kwargs is None else graph_kwargs
self._name_dict = name_dict
self.data = None
self._init_parameters(**kwargs)
self._init_data()
def _init_data(self):
"""
:return:
"""
if self._df.empty:
raise ValueError("df is empty.")
self.data = self._get_data()
def _init_parameters(self, **kwargs):
"""
:param kwargs
"""
# Instantiate graphics parameters
self._graph_type = self._name.lower().capitalize()
# Displayed column name
if self._name_dict is None:
self._name_dict = {_item: _item for _item in self._df.columns}
@staticmethod
def get_instance_with_graph_parameters(graph_type: str = None, **kwargs):
"""
:param graph_type:
:param kwargs:
:return:
"""
try:
_graph_module = importlib.import_module("plotly.graph_objs")
_graph_class = getattr(_graph_module, graph_type)
except AttributeError:
_graph_module = importlib.import_module("qlib.contrib.report.graph")
_graph_class = getattr(_graph_module, graph_type)
return _graph_class(**kwargs)
def _get_layout(self) -> go.Layout:
"""
:return:
"""
return go.Layout(**self._layout)
def _get_data(self) -> list:
"""
:return:
"""
_data = [
self.get_instance_with_graph_parameters(
graph_type=self._graph_type, x=self._df.index, y=self._df[_col], name=_name, **self._graph_kwargs
)
for _col, _name in self._name_dict.items()
]
return _data
@property
def figure(self) -> go.Figure:
"""
:return:
"""
_figure = go.Figure(data=self.data, layout=self._get_layout())
# NOTE: Use the default theme from plotly version 3.x, template=None
_figure["layout"].update(template=None)
return _figure
class SubplotsGraph:
"""Create subplots same as df.plot(subplots=True)
Simple package for `plotly.tools.subplots`
"""
def __init__(
self,
df: pd.DataFrame = None,
kind_map: dict = None,
layout: dict = None,
sub_graph_layout: dict = None,
sub_graph_data: list = None,
subplots_kwargs: dict = None,
**kwargs,
):
"""
:param df: pd.DataFrame
:param kind_map: dict, subplots graph kind and kwargs
eg: dict(kind='Scatter', kwargs=dict())
:param layout: `go.Layout` parameters
:param sub_graph_layout: Layout of each graphic, similar to 'layout'
:param sub_graph_data: Instantiation parameters for each sub-graphic
eg: [(column_name, instance_parameters), ]
column_name: str or go.Figure
Instance_parameters:
- row: int, the row where the graph is located
- col: int, the col where the graph is located
- name: str, show name, default column_name in 'df'
- kind: str, graph kind, default `kind` param, eg: bar, scatter, ...
- graph_kwargs: dict, graph kwargs, default {}, used in `go.Bar(**graph_kwargs)`
:param subplots_kwargs: `plotly.tools.make_subplots` original parameters
- shared_xaxes: bool, default False
- shared_yaxes: bool, default False
- vertical_spacing: float, default 0.3 / rows
- subplot_titles: list, default []
If `sub_graph_data` is None, will generate 'subplot_titles' according to `df.columns`,
this field will be discarded
- specs: list, see `make_subplots` docs
- rows: int, Number of rows in the subplot grid, default 1
If `sub_graph_data` is None, will generate 'rows' according to `df`, this field will be discarded
- cols: int, Number of cols in the subplot grid, default 1
If `sub_graph_data` is None, will generate 'cols' according to `df`, this field will be discarded
:param kwargs:
"""
self._df = df
self._layout = layout
self._sub_graph_layout = sub_graph_layout
self._kind_map = kind_map
if self._kind_map is None:
self._kind_map = dict(kind="Scatter", kwargs=dict())
self._subplots_kwargs = subplots_kwargs
if self._subplots_kwargs is None:
self._init_subplots_kwargs()
self.__cols = self._subplots_kwargs.get("cols", 2) # pylint: disable=W0238
self.__rows = self._subplots_kwargs.get( # pylint: disable=W0238
"rows", math.ceil(len(self._df.columns) / self.__cols)
)
self._sub_graph_data = sub_graph_data
if self._sub_graph_data is None:
self._init_sub_graph_data()
self._init_figure()
def _init_sub_graph_data(self):
"""
:return:
"""
self._sub_graph_data = []
self._subplot_titles = []
for i, column_name in enumerate(self._df.columns):
row = math.ceil((i + 1) / self.__cols)
_temp = (i + 1) % self.__cols
col = _temp if _temp else self.__cols
res_name = column_name.replace("_", " ")
_temp_row_data = (
column_name,
dict(
row=row,
col=col,
name=res_name,
kind=self._kind_map["kind"],
graph_kwargs=self._kind_map["kwargs"],
),
)
self._sub_graph_data.append(_temp_row_data)
self._subplot_titles.append(res_name)
def _init_subplots_kwargs(self):
"""
:return:
"""
# Default cols, rows
_cols = 2
_rows = math.ceil(len(self._df.columns) / 2)
self._subplots_kwargs = dict()
self._subplots_kwargs["rows"] = _rows
self._subplots_kwargs["cols"] = _cols
self._subplots_kwargs["shared_xaxes"] = False
self._subplots_kwargs["shared_yaxes"] = False
self._subplots_kwargs["vertical_spacing"] = 0.3 / _rows
self._subplots_kwargs["print_grid"] = False
self._subplots_kwargs["subplot_titles"] = self._df.columns.tolist()
def _init_figure(self):
"""
:return:
"""
self._figure = make_subplots(**self._subplots_kwargs)
for column_name, column_map in self._sub_graph_data:
if isinstance(column_name, go.Figure):
_graph_obj = column_name
elif isinstance(column_name, str):
temp_name = column_map.get("name", column_name.replace("_", " "))
kind = column_map.get("kind", self._kind_map.get("kind", "Scatter"))
_graph_kwargs = column_map.get("graph_kwargs", self._kind_map.get("kwargs", {}))
_graph_obj = BaseGraph.get_instance_with_graph_parameters(
kind,
**dict(
x=self._df.index,
y=self._df[column_name],
name=temp_name,
**_graph_kwargs,
),
)
else:
raise TypeError()
row = column_map["row"]
col = column_map["col"]
self._figure.add_trace(_graph_obj, row=row, col=col)
if self._sub_graph_layout is not None:
for k, v in self._sub_graph_layout.items():
self._figure["layout"][k].update(v)
# NOTE: Use the default theme from plotly version 3.x: template=None
self._figure["layout"].update(template=None)
self._figure["layout"].update(self._layout)
@property
def figure(self):
return self._figure
def _calculate_maximum(df: pd.DataFrame, is_ex: bool = False):
"""
:param df:
:param is_ex:
:return:
"""
if is_ex:
end_date = df["cum_ex_return_wo_cost_mdd"].idxmin()
start_date = df.loc[df.index <= end_date]["cum_ex_return_wo_cost"].idxmax()
else:
end_date = df["return_wo_mdd"].idxmin()
start_date = df.loc[df.index <= end_date]["cum_return_wo_cost"].idxmax()
return start_date, end_date
def _calculate_mdd(series):
"""
Calculate mdd
:param series:
:return:
"""
return series - series.cummax()
def _calculate_report_data(raw_df: pd.DataFrame) -> pd.DataFrame:
"""
:param df:
:return:
"""
df = raw_df.copy(deep=True)
index_names = df.index.names
df.index = df.index.strftime("%Y-%m-%d")
report_df = pd.DataFrame()
report_df["cum_bench"] = df["bench"].cumsum()
report_df["cum_return_wo_cost"] = df["return"].cumsum()
report_df["cum_return_w_cost"] = (df["return"] - df["cost"]).cumsum()
# report_df['cum_return'] - report_df['cum_return'].cummax()
report_df["return_wo_mdd"] = _calculate_mdd(report_df["cum_return_wo_cost"])
report_df["return_w_cost_mdd"] = _calculate_mdd((df["return"] - df["cost"]).cumsum())
report_df["cum_ex_return_wo_cost"] = (df["return"] - df["bench"]).cumsum()
report_df["cum_ex_return_w_cost"] = (df["return"] - df["bench"] - df["cost"]).cumsum()
report_df["cum_ex_return_wo_cost_mdd"] = _calculate_mdd((df["return"] - df["bench"]).cumsum())
report_df["cum_ex_return_w_cost_mdd"] = _calculate_mdd((df["return"] - df["cost"] - df["bench"]).cumsum())
# return_wo_mdd , return_w_cost_mdd, cum_ex_return_wo_cost_mdd, cum_ex_return_w
report_df["turnover"] = df["turnover"]
report_df.sort_index(ascending=True, inplace=True)
report_df.index.names = index_names
return report_df
def report_figure(df: pd.DataFrame) -> list | tuple:
"""
:param df:
:return:
"""
# Get data
report_df = _calculate_report_data(df)
# Maximum Drawdown
max_start_date, max_end_date = _calculate_maximum(report_df)
ex_max_start_date, ex_max_end_date = _calculate_maximum(report_df, True)
index_name = report_df.index.name
_temp_df = report_df.reset_index()
_temp_df.loc[-1] = 0
_temp_df = _temp_df.shift(1)
_temp_df.loc[0, index_name] = "T0"
_temp_df.set_index(index_name, inplace=True)
_temp_df.iloc[0] = 0
report_df = _temp_df
# Create figure
_default_kind_map = dict(kind="Scatter", kwargs={"mode": "lines+markers"})
_temp_fill_args = {"fill": "tozeroy", "mode": "lines+markers"}
_column_row_col_dict = [
("cum_bench", dict(row=1, col=1)),
("cum_return_wo_cost", dict(row=1, col=1)),
("cum_return_w_cost", dict(row=1, col=1)),
("return_wo_mdd", dict(row=2, col=1, graph_kwargs=_temp_fill_args)),
("return_w_cost_mdd", dict(row=3, col=1, graph_kwargs=_temp_fill_args)),
("cum_ex_return_wo_cost", dict(row=4, col=1)),
("cum_ex_return_w_cost", dict(row=4, col=1)),
("turnover", dict(row=5, col=1)),
("cum_ex_return_w_cost_mdd", dict(row=6, col=1, graph_kwargs=_temp_fill_args)),
("cum_ex_return_wo_cost_mdd", dict(row=7, col=1, graph_kwargs=_temp_fill_args)),
]
_subplot_layout = dict()
for i in range(1, 8):
# yaxis
_subplot_layout.update({"yaxis{}".format(i): dict(zeroline=True, showline=True, showticklabels=True)})
_show_line = i == 7
_subplot_layout.update({"xaxis{}".format(i): dict(showline=_show_line, type="category", tickangle=45)})
_layout_style = dict(
height=1200,
title=" ",
shapes=[
{
"type": "rect",
"xref": "x",
"yref": "paper",
"x0": max_start_date,
"y0": 0.55,
"x1": max_end_date,
"y1": 1,
"fillcolor": "#d3d3d3",
"opacity": 0.3,
"line": {
"width": 0,
},
},
{
"type": "rect",
"xref": "x",
"yref": "paper",
"x0": ex_max_start_date,
"y0": 0,
"x1": ex_max_end_date,
"y1": 0.55,
"fillcolor": "#d3d3d3",
"opacity": 0.3,
"line": {
"width": 0,
},
},
],
)
_subplot_kwargs = dict(
shared_xaxes=True,
vertical_spacing=0.01,
rows=7,
cols=1,
row_width=[1, 1, 1, 3, 1, 1, 3],
print_grid=False,
)
figure = SubplotsGraph(
df=report_df,
layout=_layout_style,
sub_graph_data=_column_row_col_dict,
subplots_kwargs=_subplot_kwargs,
kind_map=_default_kind_map,
sub_graph_layout=_subplot_layout,
).figure
return figure
+25 -20
View File
@@ -18,7 +18,10 @@ from rdagent.components.coder.model_coder.model import ModelFBWorkspace, ModelTa
from rdagent.core.proposal import Hypothesis, HypothesisFeedback, Trace
from rdagent.log.base import Message, Storage, View
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
from rdagent.scenarios.qlib.experiment.model_experiment import QlibModelExperiment
from rdagent.scenarios.qlib.experiment.model_experiment import (
QlibModelExperiment,
QlibModelScenario,
)
st.set_page_config(layout="wide")
@@ -213,7 +216,7 @@ class ModelTaskWindow(StWindow):
self.container.markdown(f"**Model Name**: {mt.name}")
self.container.markdown(f"**Model Type**: {mt.model_type}")
self.container.markdown(f"**Description**: {mt.description}")
self.container.markdown(f"**Formulation**: {mt.formulation}")
self.container.latex(f"Formulation: {mt.formulation}")
variables_df = pd.DataFrame(mt.variables, index=["Value"]).T
variables_df.index.name = "Variable"
@@ -479,6 +482,10 @@ class ResearchWindow(StWindow):
elif isinstance(msg.content[0], ModelTask):
self.container.markdown("**Model Tasks**")
ObjectsTabsWindow(self.container.container(), ModelTaskWindow, lambda x: x.name).consume_msg(msg)
elif msg.tag.endswith("load_pdf_screenshot"):
self.container.image(msg.content)
elif msg.tag.endswith("load_factor_tasks"):
self.container.json(msg.content)
class EvolvingWindow(StWindow):
@@ -535,16 +542,6 @@ class DevelopmentWindow(StWindow):
def consume_msg(self, msg: Message):
if "evolving" in msg.tag:
self.E_win.consume_msg(msg)
# elif msg.tag.endswith('result'):
# self.container.subheader('Results')
# if isinstance(msg.content[0], FactorFBWorkspace):
# ObjectsTabsWindow(self.container.expander('Factor Workspaces'),
# inner_class=WorkspaceWindow,
# mapper=lambda x: x.target_task.factor_name).consume_msg(msg)
# elif isinstance(msg.content[0], ModelFBWorkspace):
# ObjectsTabsWindow(self.container.expander('Model Workspaces'),
# inner_class=WorkspaceWindow,
# mapper=lambda x: x.target_task.name).consume_msg(msg)
class FeedbackWindow(StWindow):
@@ -552,7 +549,11 @@ class FeedbackWindow(StWindow):
self.container = container
def consume_msg(self, msg: Message):
if isinstance(msg.content, HypothesisFeedback):
if msg.tag.endswith("returns"):
fig = px.line(msg.content)
self.container.markdown("**Returns📈**")
self.container.plotly_chart(fig)
elif isinstance(msg.content, HypothesisFeedback):
HypothesisFeedbackWindow(self.container.container(border=True)).consume_msg(msg)
elif isinstance(msg.content, QlibModelExperiment):
QlibModelExpWindow(self.container.container(border=True)).consume_msg(msg)
@@ -584,13 +585,15 @@ class TraceWindow(StWindow):
):
self.show_llm = show_llm
self.show_common_logs = show_common_logs
image_c, scen_c = container.columns([2, 3], vertical_alignment="center")
image_c.image("scen.jpg")
scen_c.container(border=True).markdown(QlibModelScenario().rich_style_description)
top_container = container.container()
col1, col2 = top_container.columns([2, 3])
chart_c = col2.container(border=True, height=300)
chart_c = col2.container(border=True, height=500)
chart_c.markdown("**Metrics📈**")
self.chart_c = chart_c.empty()
hypothesis_status_c = col1.container(border=True, height=300)
hypothesis_status_c = col1.container(border=True, height=500)
hypothesis_status_c.markdown("**Hypotheses🏅**")
self.summary_c = hypothesis_status_c.empty()
@@ -602,7 +605,7 @@ class TraceWindow(StWindow):
)
self.hypothesis_decisions = defaultdict(bool)
self.current_hypothesis = None
self.hypotheses: list[Hypothesis] = []
self.results = []
@@ -614,12 +617,14 @@ class TraceWindow(StWindow):
if isinstance(msg.content, dict):
return
if msg.tag.endswith("hypothesis generation"):
self.current_hypothesis = msg.content.hypothesis
self.hypotheses.append(msg.content)
elif msg.tag.endswith("ef.feedback"):
self.hypothesis_decisions[self.current_hypothesis] = msg.content.decision
self.hypothesis_decisions[self.hypotheses[-1]] = msg.content.decision
self.summary_c.markdown(
"\n".join(
f"{id+1}. :green[{h}]\n" if d else f"{id+1}. {h}\n"
f"{id+1}. :green[{self.hypotheses[id].hypothesis}]\n\t>*{self.hypotheses[id].concise_reason}*"
if d
else f"{id+1}. {self.hypotheses[id].hypothesis}\n\t>*{self.hypotheses[id].concise_reason}*"
for id, (h, d) in enumerate(self.hypothesis_decisions.items())
)
)
@@ -40,8 +40,8 @@ class QlibModelScenario(Scenario):
return prompt_dict["qlib_model_simulator"]
@property
def rich_style_description(self)->str:
return '''
def rich_style_description(self) -> str:
return """
### Qlib Model Evolving Automatic R&D Demo
#### Overview
@@ -75,7 +75,7 @@ The demo showcases the iterative process of hypothesis generation, knowledge con
To demonstrate the dynamic evolution of models through the Qlib platform, emphasizing how each iteration enhances the accuracy and reliability of the resulting models.
'''
"""
def get_scenario_all_desc(self) -> str:
return f"""Background of the scenario: