mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
feat: add RD-Agent-Quant scenario (#838)
* fix model input shape bug and costeer_model bug * fix a bug * fix a bug in docker result extraction * a system-level optimization * add a filter of stdout * update * add stdout to model * model training_hyperparameters update * quant scenario * update some quant settings * llm choose action * Thompson Sampling Bandit for action choosing * refine both scens * add trace messages for quant scen * fix some bugs * fix some bugs * update * update * update * fix * fix * fix * update for merge * fix ci * fix some bugs * fix ci * fix ci * fix ci * fix ci * refactor * default qlib4rdagent local env downloading * fix ci * fix ci * fix a bug * fix ci * fix: align all prompts on template (#908) * use template to render all prompts * fix CI --------- Co-authored-by: Xu Yang <xuyang1@microsoft.com> * add fin_quant in cli * fix a bug * fix ci * fix some bugs * refactor * remove the columns in hypothesis if no value generated in this column * fix a bug * fix ci * fix conda env * add qlib gitignore * remove existed qlib folder & install torch in qlib conda * fix workspace ui in feedback * align model config in coder and runner in docker or conda * fix CI * fix CI --------- Co-authored-by: Xu Yang <peteryang@vip.qq.com> Co-authored-by: Xu Yang <xuyang1@microsoft.com>
This commit is contained in:
@@ -5,6 +5,7 @@ Pipfile
|
||||
public
|
||||
release-notes.md
|
||||
typescript*
|
||||
qlib
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
||||
@@ -25,6 +25,7 @@ from rdagent.app.general_model.general_model import (
|
||||
from rdagent.app.qlib_rd_loop.factor import main as fin_factor
|
||||
from rdagent.app.qlib_rd_loop.factor_from_report import main as fin_factor_report
|
||||
from rdagent.app.qlib_rd_loop.model import main as fin_model
|
||||
from rdagent.app.qlib_rd_loop.quant import main as fin_quant
|
||||
from rdagent.app.utils.health_check import health_check
|
||||
from rdagent.app.utils.info import collect_info
|
||||
|
||||
@@ -50,6 +51,7 @@ def app():
|
||||
"fin_factor": fin_factor,
|
||||
"fin_factor_report": fin_factor_report,
|
||||
"fin_model": fin_model,
|
||||
"fin_quant": fin_quant,
|
||||
"med_model": med_model,
|
||||
"general_model": general_model,
|
||||
"ui": ui,
|
||||
|
||||
@@ -71,6 +71,50 @@ class FactorFromReportPropSetting(FactorBasePropSetting):
|
||||
"""Limits report processing count if True; processes all if False"""
|
||||
|
||||
|
||||
class QuantBasePropSetting(BasePropSetting):
|
||||
model_config = SettingsConfigDict(env_prefix="QLIB_QUANT_", protected_namespaces=())
|
||||
|
||||
# 1) override base settings
|
||||
scen: str = "rdagent.scenarios.qlib.experiment.quant_experiment.QlibQuantScenario"
|
||||
"""Scenario class for Qlib Model"""
|
||||
|
||||
quant_hypothesis_gen: str = "rdagent.scenarios.qlib.proposal.quant_proposal.QlibQuantHypothesisGen"
|
||||
"""Hypothesis generation class"""
|
||||
|
||||
model_hypothesis2experiment: str = "rdagent.scenarios.qlib.proposal.model_proposal.QlibModelHypothesis2Experiment"
|
||||
"""Hypothesis to experiment class"""
|
||||
|
||||
model_coder: str = "rdagent.scenarios.qlib.developer.model_coder.QlibModelCoSTEER"
|
||||
"""Coder class"""
|
||||
|
||||
model_runner: str = "rdagent.scenarios.qlib.developer.model_runner.QlibModelRunner"
|
||||
"""Runner class"""
|
||||
|
||||
model_summarizer: str = "rdagent.scenarios.qlib.developer.feedback.QlibModelExperiment2Feedback"
|
||||
"""Summarizer class"""
|
||||
|
||||
factor_hypothesis2experiment: str = (
|
||||
"rdagent.scenarios.qlib.proposal.factor_proposal.QlibFactorHypothesis2Experiment"
|
||||
)
|
||||
"""Hypothesis to experiment class"""
|
||||
|
||||
factor_coder: str = "rdagent.scenarios.qlib.developer.factor_coder.QlibFactorCoSTEER"
|
||||
"""Coder class"""
|
||||
|
||||
factor_runner: str = "rdagent.scenarios.qlib.developer.factor_runner.QlibFactorRunner"
|
||||
"""Runner class"""
|
||||
|
||||
factor_summarizer: str = "rdagent.scenarios.qlib.developer.feedback.QlibFactorExperiment2Feedback"
|
||||
"""Summarizer class"""
|
||||
|
||||
evolving_n: int = 10
|
||||
"""Number of evolutions"""
|
||||
|
||||
action_selection: str = "bandit"
|
||||
"""Action selection strategy: 'bandit' for bandit-based selection, 'llm' for LLM-based selection, 'random' for random selection"""
|
||||
|
||||
|
||||
FACTOR_PROP_SETTING = FactorBasePropSetting()
|
||||
FACTOR_FROM_REPORT_PROP_SETTING = FactorFromReportPropSetting()
|
||||
MODEL_PROP_SETTING = ModelBasePropSetting()
|
||||
QUANT_PROP_SETTING = QuantBasePropSetting()
|
||||
|
||||
@@ -3,7 +3,6 @@ from pathlib import Path
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
import fire
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.app.qlib_rd_loop.conf import FACTOR_FROM_REPORT_PROP_SETTING
|
||||
from rdagent.app.qlib_rd_loop.factor import FactorRDLoop
|
||||
@@ -11,7 +10,6 @@ from rdagent.components.document_reader.document_reader import (
|
||||
extract_first_page_screenshot_from_pdf,
|
||||
load_and_process_pdfs_by_langchain,
|
||||
)
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import Hypothesis
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
@@ -19,11 +17,9 @@ from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperi
|
||||
from rdagent.scenarios.qlib.factor_experiment_loader.pdf_loader import (
|
||||
FactorExperimentLoaderFromPDFfiles,
|
||||
)
|
||||
from rdagent.utils.agent.tpl import T
|
||||
from rdagent.utils.workflow import LoopMeta
|
||||
|
||||
prompts_path = Path(__file__).parent / "prompts.yaml"
|
||||
prompts = Prompts(file_path=prompts_path)
|
||||
|
||||
|
||||
def generate_hypothesis(factor_result: dict, report_content: str) -> str:
|
||||
"""
|
||||
@@ -36,13 +32,9 @@ def generate_hypothesis(factor_result: dict, report_content: str) -> str:
|
||||
Returns:
|
||||
str: The generated hypothesis.
|
||||
"""
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined).from_string(prompts["hypothesis_generation"]["system"]).render()
|
||||
)
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompts["hypothesis_generation"]["user"])
|
||||
.render(factor_descriptions=json.dumps(factor_result), report_content=report_content)
|
||||
system_prompt = T(".prompts:hypothesis_generation.system").r()
|
||||
user_prompt = T(".prompts:hypothesis_generation.user").r(
|
||||
factor_descriptions=json.dumps(factor_result), report_content=report_content
|
||||
)
|
||||
|
||||
response = APIBackend().build_messages_and_create_chat_completion(
|
||||
|
||||
@@ -5,10 +5,6 @@ hypothesis_generation:
|
||||
{
|
||||
"hypothesis": "A clear and concise hypothesis based on the provided information.",
|
||||
"reason": "A detailed explanation supporting the generated hypothesis.",
|
||||
"concise_reason": "One line summary that focuses on the justification for the change that leads to the hypothesis (like a part of a knowledge that we are building)",
|
||||
"concise_observation": "One line summary. It focuses on the observation of the given scenario, data characteristics, or previous experiences (failures & succeses).",
|
||||
"concise_justification": "One line summary. It focuses on the justification for the change in new hypothesis and the route of exploration supporting the growth of the hypothesis, based on the observation. ",
|
||||
"concise_knowledge": "One line summary. It focuses on a transferable knowledege that comes with the new hypothesis. Use conditional grammar. eg. "If...., ..; When..., .; and etc"
|
||||
}
|
||||
|
||||
user: |-
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Quant (Factor & Model) workflow with session control
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import fire
|
||||
|
||||
from rdagent.app.qlib_rd_loop.conf import QUANT_PROP_SETTING
|
||||
from rdagent.components.workflow.conf import BasePropSetting
|
||||
from rdagent.components.workflow.rd_loop import RDLoop
|
||||
from rdagent.core.developer import Developer
|
||||
from rdagent.core.exception import FactorEmptyError, ModelEmptyError
|
||||
from rdagent.core.proposal import (
|
||||
Experiment2Feedback,
|
||||
Hypothesis2Experiment,
|
||||
HypothesisFeedback,
|
||||
HypothesisGen,
|
||||
)
|
||||
from rdagent.core.scenario import Scenario
|
||||
from rdagent.core.utils import import_class
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.scenarios.qlib.proposal.quant_proposal import QuantTrace
|
||||
|
||||
|
||||
class QuantRDLoop(RDLoop):
|
||||
skip_loop_error = (
|
||||
FactorEmptyError,
|
||||
ModelEmptyError,
|
||||
)
|
||||
|
||||
def __init__(self, PROP_SETTING: BasePropSetting):
|
||||
with logger.tag("init"):
|
||||
scen: Scenario = import_class(PROP_SETTING.scen)()
|
||||
logger.log_object(scen, tag="scenario")
|
||||
|
||||
self.hypothesis_gen: HypothesisGen = import_class(PROP_SETTING.quant_hypothesis_gen)(scen)
|
||||
logger.log_object(self.hypothesis_gen, tag="quant hypothesis generator")
|
||||
|
||||
self.factor_hypothesis2experiment: Hypothesis2Experiment = import_class(
|
||||
PROP_SETTING.factor_hypothesis2experiment
|
||||
)()
|
||||
logger.log_object(self.factor_hypothesis2experiment, tag="factor hypothesis2experiment")
|
||||
self.model_hypothesis2experiment: Hypothesis2Experiment = import_class(
|
||||
PROP_SETTING.model_hypothesis2experiment
|
||||
)()
|
||||
logger.log_object(self.model_hypothesis2experiment, tag="model hypothesis2experiment")
|
||||
|
||||
self.factor_coder: Developer = import_class(PROP_SETTING.factor_coder)(scen)
|
||||
logger.log_object(self.factor_coder, tag="factor coder")
|
||||
self.model_coder: Developer = import_class(PROP_SETTING.model_coder)(scen)
|
||||
logger.log_object(self.model_coder, tag="model coder")
|
||||
|
||||
self.factor_runner: Developer = import_class(PROP_SETTING.factor_runner)(scen)
|
||||
logger.log_object(self.factor_runner, tag="factor runner")
|
||||
self.model_runner: Developer = import_class(PROP_SETTING.model_runner)(scen)
|
||||
logger.log_object(self.model_runner, tag="model runner")
|
||||
|
||||
self.factor_summarizer: Experiment2Feedback = import_class(PROP_SETTING.factor_summarizer)(scen)
|
||||
logger.log_object(self.factor_summarizer, tag="factor summarizer")
|
||||
self.model_summarizer: Experiment2Feedback = import_class(PROP_SETTING.model_summarizer)(scen)
|
||||
logger.log_object(self.model_summarizer, tag="model summarizer")
|
||||
|
||||
self.trace = QuantTrace(scen=scen)
|
||||
super(RDLoop, self).__init__()
|
||||
|
||||
def direct_exp_gen(self, prev_out: dict[str, Any]):
|
||||
with logger.tag("r"): # research
|
||||
hypo = self._propose()
|
||||
assert hypo.action in ["factor", "model"]
|
||||
if hypo.action == "factor":
|
||||
exp = self.factor_hypothesis2experiment.convert(hypo, self.trace)
|
||||
else:
|
||||
exp = self.model_hypothesis2experiment.convert(hypo, self.trace)
|
||||
logger.log_object(exp.sub_tasks, tag="experiment generation")
|
||||
return {"propose": hypo, "exp_gen": exp}
|
||||
|
||||
def coding(self, prev_out: dict[str, Any]):
|
||||
with logger.tag("d"): # development
|
||||
if prev_out["direct_exp_gen"]["propose"].action == "factor":
|
||||
exp = self.factor_coder.develop(prev_out["direct_exp_gen"]["exp_gen"])
|
||||
elif prev_out["direct_exp_gen"]["propose"].action == "model":
|
||||
exp = self.model_coder.develop(prev_out["direct_exp_gen"]["exp_gen"])
|
||||
logger.log_object(exp, tag="coder result")
|
||||
return exp
|
||||
|
||||
def running(self, prev_out: dict[str, Any]):
|
||||
with logger.tag("ef"):
|
||||
if prev_out["direct_exp_gen"]["propose"].action == "factor":
|
||||
exp = self.factor_runner.develop(prev_out["coding"])
|
||||
if exp is None:
|
||||
logger.error(f"Factor extraction failed.")
|
||||
raise FactorEmptyError("Factor extraction failed.")
|
||||
elif prev_out["direct_exp_gen"]["propose"].action == "model":
|
||||
exp = self.model_runner.develop(prev_out["coding"])
|
||||
logger.log_object(exp, tag="runner result")
|
||||
return exp
|
||||
|
||||
def feedback(self, prev_out: dict[str, Any]):
|
||||
e = prev_out.get(self.EXCEPTION_KEY, None)
|
||||
if e is not None:
|
||||
feedback = HypothesisFeedback(
|
||||
observations=e,
|
||||
hypothesis_evaluation="",
|
||||
new_hypothesis="",
|
||||
reason="",
|
||||
decision=False,
|
||||
)
|
||||
with logger.tag("ef"): # evaluate and feedback
|
||||
logger.log_object(feedback, tag="feedback")
|
||||
self.trace.hist.append((prev_out["direct_exp_gen"]["exp_gen"], feedback))
|
||||
else:
|
||||
if prev_out["direct_exp_gen"]["propose"].action == "factor":
|
||||
feedback = self.factor_summarizer.generate_feedback(prev_out["running"], self.trace)
|
||||
elif prev_out["direct_exp_gen"]["propose"].action == "model":
|
||||
feedback = self.model_summarizer.generate_feedback(prev_out["running"], self.trace)
|
||||
with logger.tag("ef"):
|
||||
logger.log_object(feedback, tag="feedback")
|
||||
self.trace.hist.append((prev_out["running"], feedback))
|
||||
|
||||
|
||||
def main(path=None, step_n=None):
|
||||
"""
|
||||
Auto R&D Evolving loop for fintech factors.
|
||||
You can continue running session by
|
||||
.. code-block:: python
|
||||
dotenv run -- python rdagent/app/qlib_rd_loop/quant.py $LOG_PATH/__session__/1/0_propose --step_n 1 # `step_n` is a optional paramter
|
||||
"""
|
||||
if path is None:
|
||||
quant_loop = QuantRDLoop(QUANT_PROP_SETTING)
|
||||
else:
|
||||
quant_loop = QuantRDLoop.load(path)
|
||||
quant_loop.run(step_n=step_n)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fire.Fire(main)
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from pathlib import Path
|
||||
|
||||
from rdagent.components.coder.CoSTEER.config import CoSTEERSettings
|
||||
from rdagent.components.coder.CoSTEER.evaluators import (
|
||||
@@ -15,12 +14,9 @@ from rdagent.components.coder.CoSTEER.knowledge_management import (
|
||||
from rdagent.core.conf import RD_AGENT_SETTINGS
|
||||
from rdagent.core.evolving_framework import EvolvingStrategy, EvoStep, QueriedKnowledge
|
||||
from rdagent.core.experiment import FBWorkspace, Task
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.scenario import Scenario
|
||||
from rdagent.core.utils import multiprocessing_wrapper
|
||||
|
||||
implement_prompts = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
|
||||
|
||||
class MultiProcessEvolvingStrategy(EvolvingStrategy):
|
||||
def __init__(self, scen: Scenario, settings: CoSTEERSettings):
|
||||
|
||||
@@ -8,8 +8,6 @@ from itertools import combinations
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.CoSTEER.config import CoSTEERSettings
|
||||
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERSingleFeedback
|
||||
from rdagent.components.knowledge_management.graph import (
|
||||
@@ -26,12 +24,12 @@ from rdagent.core.evolving_framework import (
|
||||
RAGStrategy,
|
||||
)
|
||||
from rdagent.core.experiment import FBWorkspace, Task
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.oai.llm_utils import (
|
||||
APIBackend,
|
||||
calculate_embedding_distance_between_str_list,
|
||||
)
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class CoSTEERKnowledge(Knowledge):
|
||||
@@ -216,8 +214,6 @@ class CoSTEERQueriedKnowledgeV2(CoSTEERQueriedKnowledgeV1):
|
||||
|
||||
|
||||
class CoSTEERRAGStrategyV2(RAGStrategy):
|
||||
prompt = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
|
||||
def __init__(self, knowledgebase: CoSTEERKnowledgeBaseV2, settings: CoSTEERSettings) -> None:
|
||||
super().__init__(knowledgebase)
|
||||
self.current_generated_trace_count = 0
|
||||
@@ -324,12 +320,8 @@ class CoSTEERRAGStrategyV2(RAGStrategy):
|
||||
all_component_content = ""
|
||||
for _, component_node in enumerate(all_component_nodes):
|
||||
all_component_content += f"{component_node.content}, \n"
|
||||
analyze_component_system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(self.prompt["analyze_component_prompt_v1_system"])
|
||||
.render(
|
||||
all_component_content=all_component_content,
|
||||
)
|
||||
analyze_component_system_prompt = T(".prompts:analyze_component_prompt_v1_system").r(
|
||||
all_component_content=all_component_content,
|
||||
)
|
||||
|
||||
analyze_component_user_prompt = target_task_information
|
||||
|
||||
@@ -11,9 +11,7 @@ File structure
|
||||
- Each coder could be tested.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class FactorCoSTEERSettings(CoSTEERSettings):
|
||||
simple_background: bool = False
|
||||
"""Whether to use simple background information for code feedback"""
|
||||
|
||||
file_based_execution_timeout: int = 120
|
||||
file_based_execution_timeout: int = 3600
|
||||
"""Timeout in seconds for each factor implementation execution"""
|
||||
|
||||
select_method: str = "random"
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import io
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Dict, Tuple
|
||||
|
||||
import pandas as pd
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.factor_coder.config import FACTOR_COSTEER_SETTINGS
|
||||
from rdagent.components.coder.factor_coder.factor import FactorTask
|
||||
from rdagent.core.experiment import Task, Workspace
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.oai.llm_conf import LLM_SETTINGS
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
|
||||
evaluate_prompts = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class FactorEvaluator:
|
||||
@@ -81,36 +77,26 @@ class FactorCodeEvaluator(FactorEvaluator):
|
||||
factor_information = target_task.get_task_information()
|
||||
code = implementation.all_codes
|
||||
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(evaluate_prompts["evaluator_code_feedback_v1_system"])
|
||||
.render(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(
|
||||
target_task,
|
||||
filtered_tag="feature",
|
||||
simple_background=FACTOR_COSTEER_SETTINGS.simple_background,
|
||||
)
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
system_prompt = T(".prompts:evaluator_code_feedback_v1_system").r(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(
|
||||
target_task,
|
||||
filtered_tag="feature",
|
||||
simple_background=FACTOR_COSTEER_SETTINGS.simple_background,
|
||||
)
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
)
|
||||
)
|
||||
|
||||
execution_feedback_to_render = execution_feedback
|
||||
for _ in range(10): # 10 times to split the content is enough
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
evaluate_prompts["evaluator_code_feedback_v1_user"],
|
||||
)
|
||||
.render(
|
||||
factor_information=factor_information,
|
||||
code=code,
|
||||
execution_feedback=execution_feedback_to_render,
|
||||
value_feedback=value_feedback,
|
||||
gt_code=gt_implementation.code if gt_implementation else None,
|
||||
)
|
||||
user_prompt = T(".prompts:evaluator_code_feedback_v1_user").r(
|
||||
factor_information=factor_information,
|
||||
code=code,
|
||||
execution_feedback=execution_feedback_to_render,
|
||||
value_feedback=value_feedback,
|
||||
gt_code=gt_implementation.code if gt_implementation else None,
|
||||
)
|
||||
if (
|
||||
APIBackend().build_messages_and_calculate_token(
|
||||
@@ -189,17 +175,11 @@ class FactorOutputFormatEvaluator(FactorEvaluator):
|
||||
buffer = io.StringIO()
|
||||
gen_df.info(buf=buffer)
|
||||
gen_df_info_str = f"The user is currently working on a feature related task.\nThe output dataframe info is:\n{buffer.getvalue()}"
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
evaluate_prompts["evaluator_output_format_system"],
|
||||
)
|
||||
.render(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(implementation.target_task, filtered_tag="feature")
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
)
|
||||
system_prompt = T(".prompts:evaluator_output_format_system").r(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(implementation.target_task, filtered_tag="feature")
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -504,35 +484,25 @@ class FactorFinalDecisionEvaluator(FactorEvaluator):
|
||||
code_feedback: str,
|
||||
**kwargs,
|
||||
) -> Tuple:
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(evaluate_prompts["evaluator_final_decision_v1_system"])
|
||||
.render(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(target_task, filtered_tag="feature")
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
)
|
||||
system_prompt = T(".prompts:evaluator_final_decision_v1_system").r(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(target_task, filtered_tag="feature")
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
)
|
||||
)
|
||||
execution_feedback_to_render = execution_feedback
|
||||
|
||||
for _ in range(10): # 10 times to split the content is enough
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
evaluate_prompts["evaluator_final_decision_v1_user"],
|
||||
)
|
||||
.render(
|
||||
factor_information=target_task.get_task_information(),
|
||||
execution_feedback=execution_feedback_to_render,
|
||||
code_feedback=code_feedback,
|
||||
value_feedback=(
|
||||
value_feedback
|
||||
if value_feedback is not None
|
||||
else "No Ground Truth Value provided, so no evaluation on value is performed."
|
||||
),
|
||||
)
|
||||
user_prompt = T(".prompts:evaluator_final_decision_v1_user").r(
|
||||
factor_information=target_task.get_task_information(),
|
||||
execution_feedback=execution_feedback_to_render,
|
||||
code_feedback=code_feedback,
|
||||
value_feedback=(
|
||||
value_feedback
|
||||
if value_feedback is not None
|
||||
else "No Ground Truth Value provided, so no evaluation on value is performed."
|
||||
),
|
||||
)
|
||||
if (
|
||||
APIBackend().build_messages_and_calculate_token(
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERSingleFeedback
|
||||
from rdagent.components.coder.CoSTEER.evolving_strategy import (
|
||||
MultiProcessEvolvingStrategy,
|
||||
@@ -17,11 +14,9 @@ from rdagent.components.coder.CoSTEER.knowledge_management import (
|
||||
from rdagent.components.coder.factor_coder.config import FACTOR_COSTEER_SETTINGS
|
||||
from rdagent.components.coder.factor_coder.factor import FactorFBWorkspace, FactorTask
|
||||
from rdagent.core.experiment import FBWorkspace
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.oai.llm_conf import LLM_SETTINGS
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
|
||||
implement_prompts = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class FactorMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
|
||||
@@ -36,24 +31,14 @@ class FactorMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
|
||||
queried_former_failed_knowledge_to_render: list,
|
||||
queried_similar_error_knowledge_to_render: list,
|
||||
) -> str:
|
||||
error_summary_system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(implement_prompts["evolving_strategy_error_summary_v2_system"])
|
||||
.render(
|
||||
scenario=self.scen.get_scenario_all_desc(target_task),
|
||||
factor_information_str=target_task.get_task_information(),
|
||||
code_and_feedback=queried_former_failed_knowledge_to_render[-1].get_implementation_and_feedback_str(),
|
||||
)
|
||||
.strip("\n")
|
||||
error_summary_system_prompt = T(".prompts:evolving_strategy_error_summary_v2_system").r(
|
||||
scenario=self.scen.get_scenario_all_desc(target_task),
|
||||
factor_information_str=target_task.get_task_information(),
|
||||
code_and_feedback=queried_former_failed_knowledge_to_render[-1].get_implementation_and_feedback_str(),
|
||||
)
|
||||
for _ in range(10): # max attempt to reduce the length of error_summary_user_prompt
|
||||
error_summary_user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(implement_prompts["evolving_strategy_error_summary_v2_user"])
|
||||
.render(
|
||||
queried_similar_error_knowledge=queried_similar_error_knowledge_to_render,
|
||||
)
|
||||
.strip("\n")
|
||||
error_summary_user_prompt = T(".prompts:evolving_strategy_error_summary_v2_user").r(
|
||||
queried_similar_error_knowledge=queried_similar_error_knowledge_to_render,
|
||||
)
|
||||
if (
|
||||
APIBackend().build_messages_and_calculate_token(
|
||||
@@ -106,16 +91,9 @@ class FactorMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
|
||||
latest_attempt_to_latest_successful_execution = queried_knowledge.task_to_former_failed_traces[
|
||||
target_factor_task_information
|
||||
][1]
|
||||
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
implement_prompts["evolving_strategy_factor_implementation_v1_system"],
|
||||
)
|
||||
.render(
|
||||
scenario=self.scen.get_scenario_all_desc(target_task, filtered_tag="feature"),
|
||||
queried_former_failed_knowledge=queried_former_failed_knowledge_to_render,
|
||||
)
|
||||
system_prompt = T(".prompts:evolving_strategy_factor_implementation_v1_system").r(
|
||||
scenario=self.scen.get_scenario_all_desc(target_task, filtered_tag="feature"),
|
||||
queried_former_failed_knowledge=queried_former_failed_knowledge_to_render,
|
||||
)
|
||||
queried_similar_successful_knowledge_to_render = queried_similar_successful_knowledge
|
||||
queried_similar_error_knowledge_to_render = queried_similar_error_knowledge
|
||||
@@ -136,19 +114,12 @@ class FactorMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
|
||||
else:
|
||||
error_summary_critics = None
|
||||
# 构建user_prompt。开始写代码
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
implement_prompts["evolving_strategy_factor_implementation_v2_user"],
|
||||
)
|
||||
.render(
|
||||
factor_information_str=target_factor_task_information,
|
||||
queried_similar_successful_knowledge=queried_similar_successful_knowledge_to_render,
|
||||
queried_similar_error_knowledge=queried_similar_error_knowledge_to_render,
|
||||
error_summary_critics=error_summary_critics,
|
||||
latest_attempt_to_latest_successful_execution=latest_attempt_to_latest_successful_execution,
|
||||
)
|
||||
.strip("\n")
|
||||
user_prompt = T(".prompts:evolving_strategy_factor_implementation_v2_user").r(
|
||||
factor_information_str=target_factor_task_information,
|
||||
queried_similar_successful_knowledge=queried_similar_successful_knowledge_to_render,
|
||||
queried_similar_error_knowledge=queried_similar_error_knowledge_to_render,
|
||||
error_summary_critics=error_summary_critics,
|
||||
latest_attempt_to_latest_successful_execution=latest_attempt_to_latest_successful_execution,
|
||||
)
|
||||
if (
|
||||
APIBackend().build_messages_and_calculate_token(user_prompt=user_prompt, system_prompt=system_prompt)
|
||||
|
||||
@@ -49,6 +49,12 @@ class FactorTask(CoSTEERTask):
|
||||
return f"""factor_name: {self.factor_name}
|
||||
factor_description: {self.factor_description}
|
||||
factor_formulation: {self.factor_formulation}
|
||||
variables: {str(self.variables)}"""
|
||||
|
||||
def get_task_brief_information(self):
|
||||
return f"""factor_name: {self.factor_name}
|
||||
factor_description: {self.factor_description}
|
||||
factor_formulation: {self.factor_formulation}
|
||||
variables: {str(self.variables)}"""
|
||||
|
||||
def get_task_information_and_implementation_result(self):
|
||||
|
||||
@@ -116,6 +116,9 @@ evolving_strategy_error_summary_v2_system: |-
|
||||
|
||||
You suggestion should not include any code, just some clear and short suggestions. Please point out very critical issues in your response, ignore non-important issues to avoid confusion. If no big issue found in the code, you can response "No critics found".
|
||||
|
||||
[NOTE]
|
||||
1. When processing data, avoid time leakage.
|
||||
|
||||
Please response the critic in the following format. Here is an example structure for the output:
|
||||
critic 1: The critic message to critic 1
|
||||
critic 2: The critic message to critic 2
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from pydantic_settings import SettingsConfigDict
|
||||
|
||||
from rdagent.components.coder.CoSTEER.config import CoSTEERSettings
|
||||
|
||||
|
||||
class ModelCoSTEERSettings(CoSTEERSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="MODEL_CoSTEER_")
|
||||
|
||||
env_type: str = "conda" # or "docker"
|
||||
"""Environment to run model code in coder and runner: 'conda' for local conda env, 'docker' for Docker container"""
|
||||
|
||||
|
||||
MODEL_COSTEER_SETTINGS = ModelCoSTEERSettings()
|
||||
@@ -1,18 +1,14 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, Tuple
|
||||
|
||||
import numpy as np
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.CoSTEER.evaluators import CoSTEEREvaluator
|
||||
from rdagent.components.coder.model_coder.model import ModelFBWorkspace, ModelTask
|
||||
from rdagent.core.experiment import Task, Workspace
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.oai.llm_conf import LLM_SETTINGS
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
|
||||
evaluate_prompts = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
# This shape evaluator is also used in data_science
|
||||
@@ -70,32 +66,21 @@ class ModelCodeEvaluator(CoSTEEREvaluator):
|
||||
model_task_information = target_task.get_task_information()
|
||||
code = implementation.all_codes
|
||||
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(evaluate_prompts["evaluator_code_feedback"]["system"])
|
||||
.render(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(target_task, filtered_tag=target_task.model_type)
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
)
|
||||
system_prompt = T(".prompts:evaluator_code_feedback.system").r(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(target_task, filtered_tag=target_task.model_type)
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
)
|
||||
)
|
||||
|
||||
execution_feedback_to_render = model_execution_feedback
|
||||
for _ in range(10): # 10 times to split the content is enough
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
evaluate_prompts["evaluator_code_feedback"]["user"],
|
||||
)
|
||||
.render(
|
||||
model_information=model_task_information,
|
||||
code=code,
|
||||
model_execution_feedback=execution_feedback_to_render,
|
||||
model_value_feedback=model_value_feedback,
|
||||
gt_code=gt_implementation.all_codes if gt_implementation else None,
|
||||
)
|
||||
user_prompt = T(".prompts:evaluator_code_feedback.user").r(
|
||||
model_information=model_task_information,
|
||||
code=code,
|
||||
model_execution_feedback=execution_feedback_to_render,
|
||||
model_value_feedback=model_value_feedback,
|
||||
gt_code=gt_implementation.all_codes if gt_implementation else None,
|
||||
)
|
||||
if (
|
||||
APIBackend().build_messages_and_calculate_token(
|
||||
@@ -133,34 +118,25 @@ class ModelFinalEvaluator(CoSTEEREvaluator):
|
||||
if gt_implementation is not None:
|
||||
assert isinstance(gt_implementation, ModelFBWorkspace)
|
||||
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(evaluate_prompts["evaluator_final_feedback"]["system"])
|
||||
.render(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(target_task, filtered_tag=target_task.model_type)
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
)
|
||||
system_prompt = T(".prompts:evaluator_final_feedback.system").r(
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(target_task, filtered_tag=target_task.model_type)
|
||||
if self.scen is not None
|
||||
else "No scenario description."
|
||||
)
|
||||
)
|
||||
|
||||
execution_feedback_to_render = model_execution_feedback
|
||||
|
||||
for _ in range(10): # 10 times to split the content is enough
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
evaluate_prompts["evaluator_final_feedback"]["user"],
|
||||
)
|
||||
.render(
|
||||
model_information=target_task.get_task_information(),
|
||||
model_execution_feedback=execution_feedback_to_render,
|
||||
model_shape_feedback=model_shape_feedback,
|
||||
model_code_feedback=model_code_feedback,
|
||||
model_value_feedback=model_value_feedback,
|
||||
)
|
||||
user_prompt = T(".prompts:evaluator_final_feedback.user").r(
|
||||
model_information=target_task.get_task_information(),
|
||||
model_execution_feedback=execution_feedback_to_render,
|
||||
model_shape_feedback=model_shape_feedback,
|
||||
model_code_feedback=model_code_feedback,
|
||||
model_value_feedback=model_value_feedback,
|
||||
)
|
||||
|
||||
if (
|
||||
APIBackend().build_messages_and_calculate_token(
|
||||
user_prompt=user_prompt,
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.CoSTEER.config import CoSTEER_SETTINGS
|
||||
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERSingleFeedback
|
||||
from rdagent.components.coder.CoSTEER.evolving_strategy import (
|
||||
@@ -14,16 +11,13 @@ from rdagent.components.coder.CoSTEER.knowledge_management import (
|
||||
CoSTEERQueriedKnowledgeV2,
|
||||
)
|
||||
from rdagent.components.coder.model_coder.model import (
|
||||
ModelExperiment,
|
||||
ModelFBWorkspace,
|
||||
ModelTask,
|
||||
)
|
||||
from rdagent.core.experiment import FBWorkspace
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.oai.llm_conf import LLM_SETTINGS
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
|
||||
coder_prompts = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class ModelMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
|
||||
@@ -52,31 +46,18 @@ class ModelMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
|
||||
if isinstance(queried_knowledge, CoSTEERQueriedKnowledgeV2)
|
||||
else queried_former_failed_knowledge
|
||||
)
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
coder_prompts["evolving_strategy_model_coder"]["system"],
|
||||
)
|
||||
.render(
|
||||
scenario=self.scen.get_scenario_all_desc(filtered_tag=target_task.model_type),
|
||||
queried_former_failed_knowledge=queried_former_failed_knowledge_to_render,
|
||||
current_code=workspace.file_dict.get("model.py"),
|
||||
)
|
||||
system_prompt = T(".prompts:evolving_strategy_model_coder.system").r(
|
||||
scenario=self.scen.get_scenario_all_desc(filtered_tag="model"),
|
||||
queried_former_failed_knowledge=queried_former_failed_knowledge_to_render,
|
||||
current_code=workspace.file_dict.get("model.py"),
|
||||
)
|
||||
|
||||
queried_similar_successful_knowledge_to_render = queried_similar_successful_knowledge
|
||||
for _ in range(10): # max attempt to reduce the length of user_prompt
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
coder_prompts["evolving_strategy_model_coder"]["user"],
|
||||
)
|
||||
.render(
|
||||
model_information_str=model_information_str,
|
||||
queried_similar_successful_knowledge=queried_similar_successful_knowledge_to_render,
|
||||
queried_former_failed_knowledge=queried_former_failed_knowledge_to_render,
|
||||
)
|
||||
.strip("\n")
|
||||
user_prompt = T(".prompts:evolving_strategy_model_coder.user").r(
|
||||
model_information_str=model_information_str,
|
||||
queried_similar_successful_knowledge=queried_similar_successful_knowledge_to_render,
|
||||
queried_former_failed_knowledge=queried_former_failed_knowledge_to_render,
|
||||
)
|
||||
if (
|
||||
APIBackend().build_messages_and_calculate_token(
|
||||
|
||||
@@ -5,10 +5,11 @@ from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
|
||||
from rdagent.components.coder.CoSTEER.task import CoSTEERTask
|
||||
from rdagent.components.coder.model_coder.conf import MODEL_COSTEER_SETTINGS
|
||||
from rdagent.core.experiment import Experiment, FBWorkspace
|
||||
from rdagent.core.utils import cache_with_pickle
|
||||
from rdagent.oai.llm_utils import md5_hash
|
||||
from rdagent.utils.env import KGDockerEnv, QTDockerEnv
|
||||
from rdagent.utils.env import KGDockerEnv, QlibCondaConf, QlibCondaEnv, QTDockerEnv
|
||||
|
||||
|
||||
class ModelTask(CoSTEERTask):
|
||||
@@ -19,6 +20,7 @@ class ModelTask(CoSTEERTask):
|
||||
architecture: str,
|
||||
*args,
|
||||
hyperparameters: Dict[str, str],
|
||||
training_hyperparameters: Dict[str, str],
|
||||
formulation: str = None,
|
||||
variables: Dict[str, str] = None,
|
||||
model_type: Optional[str] = None,
|
||||
@@ -28,6 +30,7 @@ class ModelTask(CoSTEERTask):
|
||||
self.architecture: str = architecture
|
||||
self.variables: str = variables
|
||||
self.hyperparameters: str = hyperparameters
|
||||
self.training_hyperparameters: str = training_hyperparameters
|
||||
self.model_type: str = (
|
||||
model_type # Tabular for tabular model, TimesSeries for time series model, Graph for graph model, XGBoost for XGBoost model
|
||||
)
|
||||
@@ -41,6 +44,17 @@ description: {self.description}
|
||||
task_desc += f"architecture: {self.architecture}\n"
|
||||
task_desc += f"variables: {self.variables}\n" if self.variables else ""
|
||||
task_desc += f"hyperparameters: {self.hyperparameters}\n"
|
||||
task_desc += f"training_hyperparameters: {self.training_hyperparameters}\n"
|
||||
task_desc += f"model_type: {self.model_type}\n"
|
||||
return task_desc
|
||||
|
||||
def get_task_brief_information(self):
|
||||
task_desc = f"""name: {self.name}
|
||||
description: {self.description}
|
||||
"""
|
||||
task_desc += f"architecture: {self.architecture}\n"
|
||||
task_desc += f"hyperparameters: {self.hyperparameters}\n"
|
||||
task_desc += f"training_hyperparameters: {self.training_hyperparameters}\n"
|
||||
task_desc += f"model_type: {self.model_type}\n"
|
||||
return task_desc
|
||||
|
||||
@@ -99,7 +113,15 @@ class ModelFBWorkspace(FBWorkspace):
|
||||
):
|
||||
self.before_execute()
|
||||
try:
|
||||
qtde = QTDockerEnv() if self.target_task.version == 1 else KGDockerEnv()
|
||||
if self.target_task.version == 1:
|
||||
if MODEL_COSTEER_SETTINGS.env_type == "docker":
|
||||
qtde = QTDockerEnv()
|
||||
elif MODEL_COSTEER_SETTINGS.env_type == "conda":
|
||||
qtde = QlibCondaEnv(conf=QlibCondaConf())
|
||||
else:
|
||||
raise ValueError(f"Unknown env_type: {MODEL_COSTEER_SETTINGS.env_type}")
|
||||
else:
|
||||
qtde = KGDockerEnv()
|
||||
qtde.prepare()
|
||||
|
||||
if self.target_task.version == 1:
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.model_coder.model import ModelExperiment, ModelFBWorkspace
|
||||
from rdagent.core.developer import Developer
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
DIRNAME = Path(__file__).absolute().resolve().parent
|
||||
|
||||
@@ -17,18 +15,14 @@ class ModelCodeWriter(Developer[ModelExperiment]):
|
||||
for t in exp.sub_tasks:
|
||||
mti = ModelFBWorkspace(t)
|
||||
mti.prepare()
|
||||
pr = Prompts(file_path=DIRNAME / "prompt.yaml")
|
||||
|
||||
user_prompt_tpl = Environment(undefined=StrictUndefined).from_string(pr["code_implement_user"])
|
||||
sys_prompt_tpl = Environment(undefined=StrictUndefined).from_string(pr["code_implement_sys"])
|
||||
|
||||
user_prompt = user_prompt_tpl.render(
|
||||
user_prompt = T(".prompts:code_implement_user").r(
|
||||
name=t.name,
|
||||
description=t.description,
|
||||
formulation=t.formulation,
|
||||
variables=t.variables,
|
||||
)
|
||||
system_prompt = sys_prompt_tpl.render()
|
||||
system_prompt = T(".prompts:code_implement_sys").r()
|
||||
|
||||
resp = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt)
|
||||
|
||||
|
||||
@@ -2,19 +2,16 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from rdagent.components.coder.model_coder.model import ModelExperiment, ModelTask
|
||||
from rdagent.components.coder.model_coder.model import ModelTask
|
||||
from rdagent.components.document_reader.document_reader import (
|
||||
load_and_process_pdfs_by_langchain,
|
||||
)
|
||||
from rdagent.components.loader.task_loader import ModelTaskLoader
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.scenarios.qlib.experiment.model_experiment import QlibModelExperiment
|
||||
|
||||
document_process_prompts = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
def extract_model_from_doc(doc_content: str) -> dict:
|
||||
@@ -32,7 +29,7 @@ def extract_model_from_doc(doc_content: str) -> dict:
|
||||
{model_name: dict{description, formulation, variables}}
|
||||
"""
|
||||
session = APIBackend().build_chat_session(
|
||||
session_system_prompt=document_process_prompts["extract_model_formulation_system"],
|
||||
session_system_prompt=T(".prompts:extract_model_formulation_system").r(),
|
||||
)
|
||||
current_user_prompt = doc_content
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
from abc import abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.core.experiment import Experiment
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import (
|
||||
Hypothesis,
|
||||
Hypothesis2Experiment,
|
||||
@@ -14,8 +10,7 @@ from rdagent.core.proposal import (
|
||||
Trace,
|
||||
)
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class LLMHypothesisGen(HypothesisGen):
|
||||
@@ -32,27 +27,31 @@ class LLMHypothesisGen(HypothesisGen):
|
||||
def gen(self, trace: Trace) -> Hypothesis:
|
||||
context_dict, json_flag = self.prepare_context(trace)
|
||||
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_gen"]["system_prompt"])
|
||||
.render(
|
||||
targets=self.targets,
|
||||
scenario=self.scen.get_scenario_all_desc(filtered_tag="hypothesis_and_experiment"),
|
||||
hypothesis_output_format=context_dict["hypothesis_output_format"],
|
||||
hypothesis_specification=context_dict["hypothesis_specification"],
|
||||
)
|
||||
system_prompt = T(".prompts:hypothesis_gen.system_prompt").r(
|
||||
targets=self.targets,
|
||||
scenario=(
|
||||
self.scen.get_scenario_all_desc(filtered_tag=self.targets)
|
||||
if self.targets in ["factor", "model"]
|
||||
else self.scen.get_scenario_all_desc(filtered_tag="hypothesis_and_experiment")
|
||||
),
|
||||
hypothesis_output_format=context_dict["hypothesis_output_format"],
|
||||
hypothesis_specification=context_dict["hypothesis_specification"],
|
||||
)
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_gen"]["user_prompt"])
|
||||
.render(
|
||||
targets=self.targets,
|
||||
hypothesis_and_feedback=context_dict["hypothesis_and_feedback"],
|
||||
RAG=context_dict["RAG"],
|
||||
)
|
||||
user_prompt = T(".prompts:hypothesis_gen.user_prompt").r(
|
||||
targets=self.targets,
|
||||
hypothesis_and_feedback=context_dict["hypothesis_and_feedback"],
|
||||
last_hypothesis_and_feedback=(
|
||||
context_dict["last_hypothesis_and_feedback"] if "last_hypothesis_and_feedback" in context_dict else ""
|
||||
),
|
||||
sota_hypothesis_and_feedback=(
|
||||
context_dict["sota_hypothesis_and_feedback"] if "sota_hypothesis_and_feedback" in context_dict else ""
|
||||
),
|
||||
RAG=context_dict["RAG"],
|
||||
)
|
||||
|
||||
resp = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt, json_mode=json_flag)
|
||||
resp = APIBackend().build_messages_and_create_chat_completion(
|
||||
user_prompt, system_prompt, json_mode=json_flag, json_target_type=dict[str, str]
|
||||
)
|
||||
|
||||
hypothesis = self.convert_response(resp)
|
||||
|
||||
@@ -86,28 +85,30 @@ class LLMHypothesis2Experiment(Hypothesis2Experiment[Experiment]):
|
||||
|
||||
def convert(self, hypothesis: Hypothesis, trace: Trace) -> Experiment:
|
||||
context, json_flag = self.prepare_context(hypothesis, trace)
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis2experiment"]["system_prompt"])
|
||||
.render(
|
||||
targets=self.targets,
|
||||
scenario=trace.scen.get_scenario_all_desc(filtered_tag="hypothesis_and_experiment"),
|
||||
experiment_output_format=context["experiment_output_format"],
|
||||
)
|
||||
system_prompt = T(".prompts:hypothesis2experiment.system_prompt").r(
|
||||
targets=self.targets,
|
||||
scenario=trace.scen.get_scenario_all_desc(filtered_tag=self.targets),
|
||||
experiment_output_format=context["experiment_output_format"],
|
||||
)
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis2experiment"]["user_prompt"])
|
||||
.render(
|
||||
targets=self.targets,
|
||||
target_hypothesis=context["target_hypothesis"],
|
||||
hypothesis_and_feedback=context["hypothesis_and_feedback"],
|
||||
target_list=context["target_list"],
|
||||
RAG=context["RAG"],
|
||||
)
|
||||
user_prompt = T(".prompts:hypothesis2experiment.user_prompt").r(
|
||||
targets=self.targets,
|
||||
target_hypothesis=context["target_hypothesis"],
|
||||
hypothesis_and_feedback=(
|
||||
context["hypothesis_and_feedback"] if "hypothesis_and_feedback" in context else ""
|
||||
),
|
||||
last_hypothesis_and_feedback=(
|
||||
context["last_hypothesis_and_feedback"] if "last_hypothesis_and_feedback" in context else ""
|
||||
),
|
||||
sota_hypothesis_and_feedback=(
|
||||
context["sota_hypothesis_and_feedback"] if "sota_hypothesis_and_feedback" in context else ""
|
||||
),
|
||||
target_list=context["target_list"],
|
||||
RAG=context["RAG"],
|
||||
)
|
||||
|
||||
resp = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt, json_mode=json_flag)
|
||||
resp = APIBackend().build_messages_and_create_chat_completion(
|
||||
user_prompt, system_prompt, json_mode=json_flag, json_target_type=dict[str, dict[str, str | dict]]
|
||||
)
|
||||
|
||||
return self.convert_response(resp, hypothesis, trace)
|
||||
|
||||
|
||||
@@ -1,48 +1,64 @@
|
||||
hypothesis_gen:
|
||||
system_prompt: |-
|
||||
The user is working on generating new hypotheses for the {{targets}} in a data-driven research and development process.
|
||||
The {{targets}} are used in the following scenario:
|
||||
{{scenario}}
|
||||
The user has already proposed several hypotheses and conducted evaluations on them. This information will be provided to you. Your task is to check whether a similar hypothesis has already been generated.
|
||||
The user is working on generating new hypotheses for the {{ targets }} in a data-driven research and development process.
|
||||
The {{ targets }} are used in the following scenario:
|
||||
{{ scenario }}
|
||||
The user has already proposed several hypotheses and conducted evaluations on them. This information will be provided to you. Your task is to analyze previous experiments, reflect on the decision made in each experiment, and consider why experiments with a decision of true were successful while those with a decision of false failed. Then, think about how to improve further — either by refining the existing approach or by exploring an entirely new direction.
|
||||
If one exists and you agree with it, feel free to use it. If you disagree, please generate an improved version.
|
||||
{% if hypothesis_specification %}
|
||||
To assist you in formulating new hypotheses, the user has provided some additional information: {{hypothesis_specification}}.
|
||||
To assist you in formulating new hypotheses, the user has provided some additional information: {{ hypothesis_specification }}.
|
||||
**Important:** If the hypothesis_specification outlines the next steps you need to follow, ensure you adhere to those instructions.
|
||||
{% endif %}
|
||||
Please generate the output using the following format and specifications:
|
||||
{{ hypothesis_output_format }}
|
||||
|
||||
user_prompt: |-
|
||||
{% if hypothesis_and_feedback|length == 0 %}It is the first round of hypothesis generation. The user has no hypothesis on this scenario yet.
|
||||
{% else %}It is not the first round, the user has made several hypothesis on this scenario and did several evaluation on them.
|
||||
The former hypothesis and the corresponding feedbacks are as follows (focus on the last one & the new hypothesis that it provides and reasoning to see if you agree):
|
||||
{% if hypothesis_and_feedback|length == 0 %}
|
||||
It is the first round of hypothesis generation. The user has no hypothesis on this scenario yet.
|
||||
{% else %}
|
||||
The former hypothesis and the corresponding feedbacks are as follows:
|
||||
{{ hypothesis_and_feedback }}
|
||||
{% endif %}
|
||||
{% if RAG %}
|
||||
To assist you in generating new {{targets}}, we have provided the following information: {{RAG}}.
|
||||
**Note:** The provided RAG is for reference only.
|
||||
You must carefully assess whether the RAG aligns with the {{targets}}.
|
||||
If it does not, it should not be used. Exercise caution and make your own judgment.
|
||||
{% if last_hypothesis_and_feedback %}
|
||||
Here is the last trial's hypothesis and the corresponding feedback (The main feedback contains a new hypothesis for your reference only. You need to evaluate the complete trace chain to decide whether to adopt it or propose a more appropriate hypothesis):
|
||||
{{ last_hypothesis_and_feedback }}
|
||||
{% endif %}
|
||||
{% if sota_hypothesis_and_feedback != "" %}
|
||||
Here is the SOTA trail's hypothesis and the corresponding feedback:
|
||||
{{ sota_hypothesis_and_feedback }}
|
||||
{% endif %}
|
||||
{% if RAG %}
|
||||
To assist you in generating new {{ targets }}, we have provided the following information: {{ RAG }}.
|
||||
{% endif %}
|
||||
Also generate the relevant keys for the reasoning and the distilled knowledge that follows. For those keys, in particular for knowledge, explain in the context of the specific scenario to build up domain knowledge in the specific field rather than general knowledge.
|
||||
|
||||
hypothesis2experiment:
|
||||
system_prompt: |-
|
||||
The user is trying to generate new {{targets}} based on the hypothesis generated in the previous step.
|
||||
The {{targets}} are used in certain scenario, the scenario is as follows:
|
||||
The user is trying to generate new {{ targets }} based on the hypothesis generated in the previous step.
|
||||
The {{ targets }} are used in certain scenario, the scenario is as follows:
|
||||
{{ scenario }}
|
||||
The user will use the {{targets}} generated to do some experiments. The user will provide this information to you:
|
||||
1. The target hypothesis you are targeting to generate {{targets}} for.
|
||||
The user will use the {{ targets }} generated to do some experiments. The user will provide this information to you:
|
||||
1. The target hypothesis you are targeting to generate {{ targets }} for.
|
||||
2. The hypothesis generated in the previous steps and their corresponding feedbacks.
|
||||
3. Former proposed {{targets}} on similar hypothesis.
|
||||
4. Some additional information to help you generate new {{targets}}.
|
||||
3. Former proposed {{ targets }} on similar hypothesis.
|
||||
4. Some additional information to help you generate new {{ targets }}.
|
||||
Please generate the output following the format below:
|
||||
{{ experiment_output_format }}
|
||||
|
||||
user_prompt: |-
|
||||
The user has made several hypothesis on this scenario and did several evaluation on them.
|
||||
The target hypothesis you are targeting to generate {{targets}} for is as follows:
|
||||
The target hypothesis you are targeting to generate {{ targets }} for is as follows:
|
||||
{{ target_hypothesis }}
|
||||
{% if hypothesis_and_feedback %}
|
||||
The former hypothesis and the corresponding feedbacks are as follows:
|
||||
{{ hypothesis_and_feedback }}
|
||||
Please generate the new {{targets}} based on the information above.
|
||||
{% endif %}
|
||||
{% if last_hypothesis_and_feedback %}
|
||||
The latest hypothesis and the corresponding feedback are as follows:
|
||||
{{ last_hypothesis_and_feedback }}
|
||||
{% endif %}
|
||||
{% if sota_hypothesis_and_feedback %}
|
||||
The SOTA hypothesis and the corresponding feedback are as follows:
|
||||
{{ sota_hypothesis_and_feedback }}
|
||||
{% endif %}
|
||||
|
||||
Please generate the new {{ targets }} based on the information above.
|
||||
|
||||
@@ -78,12 +78,14 @@ class RDLoop(LoopBase, metaclass=LoopMeta):
|
||||
e = prev_out.get(self.EXCEPTION_KEY, None)
|
||||
if e is not None:
|
||||
feedback = HypothesisFeedback(
|
||||
observations="Error occurred in loop, skip this loop",
|
||||
observations=e,
|
||||
hypothesis_evaluation="",
|
||||
new_hypothesis="",
|
||||
reason="",
|
||||
decision=False,
|
||||
)
|
||||
with logger.tag("ef"): # evaluate and feedback
|
||||
logger.log_object(feedback, tag="feedback")
|
||||
self.trace.hist.append((prev_out["direct_exp_gen"]["exp_gen"], feedback))
|
||||
else:
|
||||
feedback = self.summarizer.generate_feedback(prev_out["running"], self.trace)
|
||||
|
||||
@@ -82,5 +82,7 @@ class RDAgentSettings(ExtendedBaseSettings):
|
||||
|
||||
enable_mlflow: bool = False
|
||||
|
||||
initial_fator_library_size: int = 20
|
||||
|
||||
|
||||
RD_AGENT_SETTINGS = RDAgentSettings()
|
||||
|
||||
@@ -3,16 +3,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from rdagent.core.evaluation import Feedback
|
||||
from rdagent.core.experiment import ASpecificExp, Experiment
|
||||
from rdagent.core.knowledge_base import KnowledgeBase
|
||||
from rdagent.core.scenario import Scenario
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rdagent.core.prompts import Prompts
|
||||
|
||||
# class data_ana: XXX
|
||||
|
||||
|
||||
@@ -42,12 +39,7 @@ class Hypothesis:
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""Hypothesis: {self.hypothesis}
|
||||
Reason: {self.reason}
|
||||
Concise Reason & Knowledge: {self.concise_reason}
|
||||
Concise Observation: {self.concise_observation}
|
||||
Concise Justification: {self.concise_justification}
|
||||
Concise Knowledge: {self.concise_knowledge}
|
||||
"""
|
||||
Reason: {self.reason}"""
|
||||
|
||||
# source: data_ana | model_nan = None
|
||||
|
||||
@@ -187,11 +179,6 @@ class ExpGen(ABC):
|
||||
|
||||
|
||||
class HypothesisGen(ABC):
|
||||
# NOTE: the design is a little wierd
|
||||
# - Sometimes we want accurate access the prompts in a specific level
|
||||
# - It renders the prompt to a specific abstract level
|
||||
# - Sometimes we want to access the most recent level prompts
|
||||
prompts: Prompts # this is a class level prompt.
|
||||
|
||||
def __init__(self, scen: Scenario) -> None:
|
||||
self.scen = scen
|
||||
|
||||
+326
-9
@@ -35,6 +35,7 @@ from rdagent.scenarios.qlib.experiment.model_experiment import (
|
||||
QlibModelExperiment,
|
||||
QlibModelScenario,
|
||||
)
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
|
||||
st.set_page_config(layout="wide", page_title="RD-Agent", page_icon="🎓", initial_sidebar_state="expanded")
|
||||
|
||||
@@ -55,12 +56,19 @@ else:
|
||||
|
||||
QLIB_SELECTED_METRICS = [
|
||||
"IC",
|
||||
"1day.excess_return_without_cost.annualized_return",
|
||||
"1day.excess_return_without_cost.information_ratio",
|
||||
"1day.excess_return_without_cost.max_drawdown",
|
||||
"1day.excess_return_with_cost.annualized_return",
|
||||
"1day.excess_return_with_cost.information_ratio",
|
||||
"1day.excess_return_with_cost.max_drawdown",
|
||||
]
|
||||
|
||||
SIMILAR_SCENARIOS = (QlibModelScenario, DMModelScenario, QlibFactorScenario, QlibFactorFromReportScenario, KGScenario)
|
||||
SIMILAR_SCENARIOS = (
|
||||
QlibModelScenario,
|
||||
DMModelScenario,
|
||||
QlibFactorScenario,
|
||||
QlibFactorFromReportScenario,
|
||||
QlibQuantScenario,
|
||||
KGScenario,
|
||||
)
|
||||
|
||||
|
||||
def filter_log_folders(main_log_path):
|
||||
@@ -123,6 +131,9 @@ if "h_decisions" not in state:
|
||||
if "metric_series" not in state:
|
||||
state.metric_series = []
|
||||
|
||||
if "all_metric_series" not in state:
|
||||
state.all_metric_series = []
|
||||
|
||||
# Factor Task Baseline
|
||||
if "alpha158_metrics" not in state:
|
||||
state.alpha158_metrics = None
|
||||
@@ -164,7 +175,10 @@ def get_msgs_until(end_func: Callable[[Message], bool] = lambda _: True):
|
||||
# Update Summary Info
|
||||
if "model runner result" in tags or "factor runner result" in tags or "runner result" in tags:
|
||||
# factor baseline exp metrics
|
||||
if isinstance(state.scenario, QlibFactorScenario) and state.alpha158_metrics is None:
|
||||
if (
|
||||
isinstance(state.scenario, (QlibFactorScenario, QlibQuantScenario))
|
||||
and state.alpha158_metrics is None
|
||||
):
|
||||
sms = msg.content.based_experiments[0].result.loc[QLIB_SELECTED_METRICS]
|
||||
sms.name = "alpha158"
|
||||
state.alpha158_metrics = sms
|
||||
@@ -178,11 +192,19 @@ def get_msgs_until(end_func: Callable[[Message], bool] = lambda _: True):
|
||||
if isinstance(state.scenario, DMModelScenario):
|
||||
sms.index = ["AUROC"]
|
||||
elif isinstance(
|
||||
state.scenario, (QlibModelScenario, QlibFactorFromReportScenario, QlibFactorScenario)
|
||||
state.scenario,
|
||||
(
|
||||
QlibModelScenario,
|
||||
QlibFactorFromReportScenario,
|
||||
QlibFactorScenario,
|
||||
QlibQuantScenario,
|
||||
),
|
||||
):
|
||||
sms_all = sms
|
||||
sms = sms.loc[QLIB_SELECTED_METRICS]
|
||||
sms.name = f"Baseline"
|
||||
state.metric_series.append(sms)
|
||||
state.all_metric_series.append(sms_all)
|
||||
|
||||
# common metrics
|
||||
if msg.content.result is None:
|
||||
@@ -195,12 +217,21 @@ def get_msgs_until(end_func: Callable[[Message], bool] = lambda _: True):
|
||||
if isinstance(state.scenario, DMModelScenario):
|
||||
sms.index = ["AUROC"]
|
||||
elif isinstance(
|
||||
state.scenario, (QlibModelScenario, QlibFactorFromReportScenario, QlibFactorScenario)
|
||||
state.scenario,
|
||||
(
|
||||
QlibModelScenario,
|
||||
QlibFactorFromReportScenario,
|
||||
QlibFactorScenario,
|
||||
QlibQuantScenario,
|
||||
),
|
||||
):
|
||||
sms_all = sms
|
||||
sms = sms.loc[QLIB_SELECTED_METRICS]
|
||||
|
||||
sms.name = f"Round {state.lround}"
|
||||
sms_all.name = f"Round {state.lround}"
|
||||
state.metric_series.append(sms)
|
||||
state.all_metric_series.append(sms_all)
|
||||
elif "hypothesis generation" in tags:
|
||||
state.hypotheses[state.lround] = msg.content
|
||||
elif "ef" in tags and "feedback" in tags:
|
||||
@@ -254,8 +285,9 @@ def refresh(same_trace: bool = False):
|
||||
|
||||
# detect scenario
|
||||
if not same_trace:
|
||||
get_msgs_until(lambda m: not isinstance(m.content, str))
|
||||
get_msgs_until(lambda m: "debug_" not in m.tag and not isinstance(m.content, str))
|
||||
if state.last_msg is None or not isinstance(state.last_msg.content, Scenario):
|
||||
st.write(state.msgs)
|
||||
st.toast(":red[**No Scenario Info detected**]", icon="❗")
|
||||
state.scenario = None
|
||||
else:
|
||||
@@ -269,6 +301,7 @@ def refresh(same_trace: bool = False):
|
||||
state.hypotheses = defaultdict(None)
|
||||
state.h_decisions = defaultdict(bool)
|
||||
state.metric_series = []
|
||||
state.all_metric_series = []
|
||||
state.last_msg = None
|
||||
state.current_tags = []
|
||||
state.alpha158_metrics = None
|
||||
@@ -335,6 +368,9 @@ def display_hypotheses(hypotheses: dict[int, Hypothesis], decisions: dict[int, b
|
||||
df.drop(["concise_reason"], axis=1, inplace=True)
|
||||
|
||||
df.columns = df.columns.map(lambda x: name_dict.get(x, x))
|
||||
for col in list(df.columns):
|
||||
if all([value is None for value in df[col]]):
|
||||
df.drop([col], axis=1, inplace=True)
|
||||
|
||||
def style_rows(row):
|
||||
if decisions[row.name]:
|
||||
@@ -396,6 +432,13 @@ def metrics_window(df: pd.DataFrame, R: int, C: int, *, height: int = 300, color
|
||||
)
|
||||
st.plotly_chart(fig)
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
buffer = BytesIO()
|
||||
df.to_csv(buffer)
|
||||
buffer.seek(0)
|
||||
st.download_button(label="download the metrics (csv)", data=buffer, file_name="metrics.csv", mime="text/csv")
|
||||
|
||||
|
||||
def summary_window():
|
||||
if isinstance(state.scenario, SIMILAR_SCENARIOS):
|
||||
@@ -422,6 +465,8 @@ def summary_window():
|
||||
with chart_c:
|
||||
if isinstance(state.scenario, QlibFactorScenario) and state.alpha158_metrics is not None:
|
||||
df = pd.DataFrame([state.alpha158_metrics] + state.metric_series)
|
||||
elif isinstance(state.scenario, QlibQuantScenario) and state.alpha158_metrics is not None:
|
||||
df = pd.DataFrame([state.alpha158_metrics] + state.metric_series)
|
||||
else:
|
||||
df = pd.DataFrame(state.metric_series)
|
||||
if show_true_only and len(state.hypotheses) >= len(state.metric_series):
|
||||
@@ -516,6 +561,7 @@ def tasks_window(tasks: list[FactorTask | ModelTask]):
|
||||
for v, d in mt.variables.items():
|
||||
mks += f"| ${v}$ | {d} |\n"
|
||||
st.markdown(mks)
|
||||
st.markdown(f"**Train Para**: {mt.training_hyperparameters}")
|
||||
|
||||
|
||||
def research_window():
|
||||
@@ -562,13 +608,54 @@ def research_window():
|
||||
|
||||
|
||||
def feedback_window():
|
||||
# st.write(round)
|
||||
# # Check if metric series exists and has the matching round
|
||||
# if state.all_metric_series:
|
||||
# for metric in state.all_metric_series:
|
||||
# if metric.name == f"Round {round}":
|
||||
# # Select specific metrics with cost
|
||||
# selected_metrics_with_cost = {
|
||||
# 'IC': float(f"{metric['IC']:.4f}"),
|
||||
# 'ICIR': float(f"{metric['ICIR']:.4f}"),
|
||||
# 'Rank IC': float(f"{metric['Rank IC']:.4f}"),
|
||||
# 'Rank ICIR': float(f"{metric['Rank ICIR']:.4f}"),
|
||||
# 'ARR': float(f"{metric['1day.excess_return_with_cost.annualized_return']:.4f}"),
|
||||
# 'IR': float(f"{metric['1day.excess_return_with_cost.information_ratio']:.4f}"),
|
||||
# 'MDD': float(f"{metric['1day.excess_return_with_cost.max_drawdown']:.4f}"),
|
||||
# 'Sharpe': float(f"{metric['1day.excess_return_with_cost.annualized_return'] / abs(metric['1day.excess_return_with_cost.max_drawdown']):.4f}")
|
||||
# }
|
||||
# st.write("With Cost Metrics:")
|
||||
# st.write(pd.Series(selected_metrics_with_cost))
|
||||
|
||||
# # Select specific metrics without cost
|
||||
# selected_metrics_without_cost = {
|
||||
# 'IC': float(f"{metric['IC']:.4f}"),
|
||||
# 'ICIR': float(f"{metric['ICIR']:.4f}"),
|
||||
# 'Rank IC': float(f"{metric['Rank IC']:.4f}"),
|
||||
# 'Rank ICIR': float(f"{metric['Rank ICIR']:.4f}"),
|
||||
# 'ARR': float(f"{metric['1day.excess_return_without_cost.annualized_return']:.4f}"),
|
||||
# 'IR': float(f"{metric['1day.excess_return_without_cost.information_ratio']:.4f}"),
|
||||
# 'MDD': float(f"{metric['1day.excess_return_without_cost.max_drawdown']:.4f}"),
|
||||
# 'Sharpe': float(f"{metric['1day.excess_return_without_cost.annualized_return'] / abs(metric['1day.excess_return_without_cost.max_drawdown']):.4f}")
|
||||
# }
|
||||
# st.write("Without Cost Metrics:")
|
||||
# st.write(pd.Series(selected_metrics_without_cost))
|
||||
# break
|
||||
if isinstance(state.scenario, SIMILAR_SCENARIOS):
|
||||
with st.container(border=True):
|
||||
st.subheader("Feedback📝", divider="orange", anchor="_feedback")
|
||||
|
||||
if state.lround > 0 and isinstance(
|
||||
state.scenario, (QlibModelScenario, QlibFactorScenario, QlibFactorFromReportScenario, KGScenario)
|
||||
state.scenario,
|
||||
(QlibModelScenario, QlibFactorScenario, QlibFactorFromReportScenario, QlibQuantScenario, KGScenario),
|
||||
):
|
||||
if fbr := state.msgs[round]["ef.runner result"]:
|
||||
try:
|
||||
st.write("workspace")
|
||||
st.write(fbr[0].content.experiment_workspace.workspace_path)
|
||||
st.write(fbr[0].content.stdout)
|
||||
except Exception as e:
|
||||
st.error(f"Error displaying workspace path: {str(e)}")
|
||||
with st.expander("**Config⚙️**", expanded=True):
|
||||
st.markdown(state.scenario.experiment_setting, unsafe_allow_html=True)
|
||||
|
||||
@@ -812,8 +899,238 @@ def show_times(round: int):
|
||||
st.markdown(f"**:blue[{k}]**: :red[**{minutes}**] minutes :orange[**{seconds}**] seconds")
|
||||
|
||||
|
||||
def analyze_task_completion():
|
||||
st.header("Task Completion Analysis", divider="orange")
|
||||
|
||||
# Dictionary to store results for all loops
|
||||
completion_stats = {}
|
||||
|
||||
# Iterate through all loops
|
||||
for loop_round in state.msgs.keys():
|
||||
if loop_round == 0: # Skip initialization round
|
||||
continue
|
||||
|
||||
max_evolving_round = state.erounds[loop_round]
|
||||
if max_evolving_round == 0:
|
||||
continue
|
||||
|
||||
# Track tasks that pass in each evolving round
|
||||
tasks_passed_by_round = {}
|
||||
cumulative_passed = set()
|
||||
|
||||
# For each evolving round in this loop
|
||||
for e_round in range(1, max_evolving_round + 1):
|
||||
if len(state.msgs[loop_round]["d.evolving feedback"]) >= e_round:
|
||||
# Get feedback for this evolving round
|
||||
feedback = state.msgs[loop_round]["d.evolving feedback"][e_round - 1].content
|
||||
|
||||
# Count passed tasks and track their indices
|
||||
passed_tasks = set()
|
||||
for j, task_feedback in enumerate(feedback):
|
||||
if task_feedback.final_decision:
|
||||
passed_tasks.add(j)
|
||||
cumulative_passed.add(j)
|
||||
|
||||
# Store both individual round results and cumulative results
|
||||
tasks_passed_by_round[e_round] = {
|
||||
"count": len(passed_tasks),
|
||||
"indices": passed_tasks,
|
||||
"cumulative_count": len(cumulative_passed),
|
||||
"cumulative_indices": cumulative_passed.copy(),
|
||||
}
|
||||
|
||||
completion_stats[loop_round] = {
|
||||
"total_tasks": len(state.msgs[loop_round]["d.evolving feedback"][0].content),
|
||||
"rounds": tasks_passed_by_round,
|
||||
"max_round": max_evolving_round,
|
||||
}
|
||||
|
||||
# Display results
|
||||
if completion_stats:
|
||||
# Add an aggregate view at the top
|
||||
st.subheader("🔄 Aggregate Completion Across All Loops")
|
||||
|
||||
# Create summary data for comparison
|
||||
summary_data = []
|
||||
total_tasks_across_loops = 0
|
||||
total_passed_r1 = 0
|
||||
total_passed_r3 = 0
|
||||
total_passed_r5 = 0
|
||||
total_passed_r10 = 0
|
||||
total_passed_final = 0
|
||||
|
||||
for loop_round, stats in completion_stats.items():
|
||||
total_tasks = stats["total_tasks"]
|
||||
total_tasks_across_loops += total_tasks
|
||||
|
||||
# Find data for specific rounds
|
||||
r1_passed = stats["rounds"].get(1, {}).get("cumulative_count", 0)
|
||||
total_passed_r1 += r1_passed
|
||||
|
||||
# For round 3, use the closest round if exactly 3 doesn't exist
|
||||
if 3 in stats["rounds"]:
|
||||
r3_passed = stats["rounds"][3]["cumulative_count"]
|
||||
elif stats["max_round"] >= 3:
|
||||
max_r_below_3 = max([r for r in stats["rounds"].keys() if r <= 3])
|
||||
r3_passed = stats["rounds"][max_r_below_3]["cumulative_count"]
|
||||
else:
|
||||
r3_passed = stats["rounds"][stats["max_round"]]["cumulative_count"] if stats["rounds"] else 0
|
||||
total_passed_r3 += r3_passed
|
||||
|
||||
# For round 5, use the closest round if exactly 5 doesn't exist
|
||||
if 5 in stats["rounds"]:
|
||||
r5_passed = stats["rounds"][5]["cumulative_count"]
|
||||
elif stats["max_round"] >= 5:
|
||||
max_r_below_5 = max([r for r in stats["rounds"].keys() if r <= 5])
|
||||
r5_passed = stats["rounds"][max_r_below_5]["cumulative_count"]
|
||||
else:
|
||||
r5_passed = stats["rounds"][stats["max_round"]]["cumulative_count"] if stats["rounds"] else 0
|
||||
total_passed_r5 += r5_passed
|
||||
|
||||
# For round 10
|
||||
if 10 in stats["rounds"]:
|
||||
r10_passed = stats["rounds"][10]["cumulative_count"]
|
||||
else:
|
||||
r10_passed = stats["rounds"][stats["max_round"]]["cumulative_count"] if stats["rounds"] else 0
|
||||
total_passed_r10 += r10_passed
|
||||
|
||||
# Final round completion
|
||||
final_passed = stats["rounds"][stats["max_round"]]["cumulative_count"] if stats["rounds"] else 0
|
||||
total_passed_final += final_passed
|
||||
|
||||
# Add to summary table
|
||||
summary_data.append(
|
||||
{
|
||||
"Loop": f"Loop {loop_round}",
|
||||
"Total Tasks": total_tasks,
|
||||
"Passed (Round 1)": (
|
||||
f"{r1_passed}/{total_tasks} ({r1_passed/total_tasks:.0%})" if total_tasks > 0 else "N/A"
|
||||
),
|
||||
"Passed (Round 3)": (
|
||||
f"{r3_passed}/{total_tasks} ({r3_passed/total_tasks:.0%})" if total_tasks > 0 else "N/A"
|
||||
),
|
||||
"Passed (Round 5)": (
|
||||
f"{r5_passed}/{total_tasks} ({r5_passed/total_tasks:.0%})" if total_tasks > 0 else "N/A"
|
||||
),
|
||||
"Passed (Final)": (
|
||||
f"{final_passed}/{total_tasks} ({final_passed/total_tasks:.0%})" if total_tasks > 0 else "N/A"
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
if total_tasks_across_loops > 0:
|
||||
summary_data.append(
|
||||
{
|
||||
"Loop": "**TOTAL**",
|
||||
"Total Tasks": total_tasks_across_loops,
|
||||
"Passed (Round 1)": f"**{total_passed_r1}/{total_tasks_across_loops} ({total_passed_r1/total_tasks_across_loops:.0%})**",
|
||||
"Passed (Round 3)": f"**{total_passed_r3}/{total_tasks_across_loops} ({total_passed_r3/total_tasks_across_loops:.0%})**",
|
||||
"Passed (Round 5)": f"**{total_passed_r5}/{total_tasks_across_loops} ({total_passed_r5/total_tasks_across_loops:.0%})**",
|
||||
"Passed (Final)": f"**{total_passed_final}/{total_tasks_across_loops} ({total_passed_final/total_tasks_across_loops:.0%})**",
|
||||
}
|
||||
)
|
||||
|
||||
st.table(pd.DataFrame(summary_data))
|
||||
|
||||
# Summary statistics
|
||||
st.markdown("### 📊 Overall Completion Progress:")
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
with col1:
|
||||
st.metric(
|
||||
label="After Round 1",
|
||||
value=f"{total_passed_r1/total_tasks_across_loops:.0%}",
|
||||
help=f"{total_passed_r1}/{total_tasks_across_loops} tasks",
|
||||
)
|
||||
with col2:
|
||||
st.metric(
|
||||
label="After Round 3",
|
||||
value=f"{total_passed_r3/total_tasks_across_loops:.0%}",
|
||||
delta=f"{(total_passed_r3-total_passed_r1)/total_tasks_across_loops:.0%}",
|
||||
help=f"{total_passed_r3}/{total_tasks_across_loops} tasks",
|
||||
)
|
||||
with col3:
|
||||
st.metric(
|
||||
label="After Round 5",
|
||||
value=f"{total_passed_r5/total_tasks_across_loops:.0%}",
|
||||
delta=f"{(total_passed_r5-total_passed_r3)/total_tasks_across_loops:.0%}",
|
||||
help=f"{total_passed_r5}/{total_tasks_across_loops} tasks",
|
||||
)
|
||||
with col4:
|
||||
st.metric(
|
||||
label="Final Completion",
|
||||
value=f"{total_passed_final/total_tasks_across_loops:.0%}",
|
||||
delta=f"{(total_passed_final-total_passed_r5)/total_tasks_across_loops:.0%}",
|
||||
help=f"{total_passed_final}/{total_tasks_across_loops} tasks",
|
||||
)
|
||||
|
||||
# Show detailed results by loop
|
||||
st.markdown("---")
|
||||
st.subheader("Detailed Results by Loop")
|
||||
|
||||
for loop_round, stats in completion_stats.items():
|
||||
with st.expander(f"Loop {loop_round} Details"):
|
||||
total_tasks = stats["total_tasks"]
|
||||
|
||||
# Create a results table
|
||||
data = []
|
||||
for e_round in range(1, min(11, stats["max_round"] + 1)):
|
||||
if e_round in stats["rounds"]:
|
||||
round_data = stats["rounds"][e_round]
|
||||
data.append(
|
||||
{
|
||||
"Evolving Round": e_round,
|
||||
"Tasks Passed": f"{round_data['count']}/{total_tasks} ({round_data['count']/total_tasks:.0%})",
|
||||
"Cumulative Passed": f"{round_data['cumulative_count']}/{total_tasks} ({round_data['cumulative_count']/total_tasks:.0%})",
|
||||
}
|
||||
)
|
||||
else:
|
||||
data.append({"Evolving Round": e_round, "Tasks Passed": "N/A", "Cumulative Passed": "N/A"})
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
st.table(df)
|
||||
|
||||
st.markdown("### Summary:")
|
||||
if 1 in stats["rounds"]:
|
||||
st.markdown(
|
||||
f"- After round 1: **{stats['rounds'][1]['cumulative_count']}/{total_tasks}** tasks passed ({stats['rounds'][1]['cumulative_count']/total_tasks:.0%})"
|
||||
)
|
||||
|
||||
if 3 in stats["rounds"]:
|
||||
st.markdown(
|
||||
f"- After round 3: **{stats['rounds'][3]['cumulative_count']}/{total_tasks}** tasks passed ({stats['rounds'][3]['cumulative_count']/total_tasks:.0%})"
|
||||
)
|
||||
elif stats["max_round"] >= 3:
|
||||
max_round_below_3 = max([r for r in stats["rounds"].keys() if r <= 3])
|
||||
st.markdown(
|
||||
f"- After round 3: **{stats['rounds'][max_round_below_3]['cumulative_count']}/{total_tasks}** tasks passed ({stats['rounds'][max_round_below_3]['cumulative_count']/total_tasks:.0%})"
|
||||
)
|
||||
|
||||
if 5 in stats["rounds"]:
|
||||
st.markdown(
|
||||
f"- After round 5: **{stats['rounds'][5]['cumulative_count']}/{total_tasks}** tasks passed ({stats['rounds'][5]['cumulative_count']/total_tasks:.0%})"
|
||||
)
|
||||
elif stats["max_round"] >= 5:
|
||||
max_round_below_5 = max([r for r in stats["rounds"].keys() if r <= 5])
|
||||
st.markdown(
|
||||
f"- After round 5: **{stats['rounds'][max_round_below_5]['cumulative_count']}/{total_tasks}** tasks passed ({stats['rounds'][max_round_below_5]['cumulative_count']/total_tasks:.0%})"
|
||||
)
|
||||
|
||||
if 10 in stats["rounds"]:
|
||||
st.markdown(
|
||||
f"- After round 10: **{stats['rounds'][10]['cumulative_count']}/{total_tasks}** tasks passed ({stats['rounds'][10]['cumulative_count']/total_tasks:.0%})"
|
||||
)
|
||||
elif stats["max_round"] >= 1:
|
||||
st.markdown(
|
||||
f"- After final round ({stats['max_round']}): **{stats['rounds'][stats['max_round']]['cumulative_count']}/{total_tasks}** tasks passed ({stats['rounds'][stats['max_round']]['cumulative_count']/total_tasks:.0%})"
|
||||
)
|
||||
else:
|
||||
st.info("No task completion data available.")
|
||||
|
||||
|
||||
if state.scenario is not None:
|
||||
summary_window()
|
||||
if st.toggle("show analyse_task_competition"):
|
||||
analyze_task_completion()
|
||||
|
||||
# R&D Loops Window
|
||||
if isinstance(state.scenario, SIMILAR_SCENARIOS):
|
||||
|
||||
@@ -5,21 +5,13 @@ import json
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.core.experiment import Experiment
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import (
|
||||
Experiment2Feedback,
|
||||
Hypothesis,
|
||||
HypothesisFeedback,
|
||||
Trace,
|
||||
)
|
||||
from rdagent.core.proposal import Experiment2Feedback, HypothesisFeedback, Trace
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.utils import convert2bool
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
feedback_prompts = Prompts(file_path=Path(__file__).parent.parent.parent / "qlib" / "prompts.yaml")
|
||||
DIRNAME = Path(__file__).absolute().resolve().parent
|
||||
|
||||
|
||||
@@ -35,24 +27,20 @@ class DMModelExperiment2Feedback(Experiment2Feedback):
|
||||
|
||||
logger.info("Generating feedback...")
|
||||
# Define the system prompt for hypothesis feedback
|
||||
system_prompt = feedback_prompts["model_feedback_generation"]["system"]
|
||||
system_prompt = T("scenarios.qlib.prompts:model_feedback_generation.system").r()
|
||||
|
||||
# Define the user prompt for hypothesis feedback
|
||||
context = trace.scen
|
||||
SOTA_hypothesis, SOTA_experiment = trace.get_sota_hypothesis_and_experiment()
|
||||
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(feedback_prompts["model_feedback_generation"]["user"])
|
||||
.render(
|
||||
context=context,
|
||||
last_hypothesis=SOTA_hypothesis,
|
||||
last_task=SOTA_experiment.sub_tasks[0].get_task_information() if SOTA_hypothesis else None,
|
||||
last_code=SOTA_experiment.sub_workspace_list[0].file_dict.get("model.py") if SOTA_hypothesis else None,
|
||||
last_result=SOTA_experiment.result if SOTA_hypothesis else None,
|
||||
hypothesis=hypothesis,
|
||||
exp=exp,
|
||||
)
|
||||
user_prompt = T("scenarios.qlib.prompts:model_feedback_generation.user").r(
|
||||
context=context,
|
||||
last_hypothesis=SOTA_hypothesis,
|
||||
last_task=SOTA_experiment.sub_tasks[0].get_task_information() if SOTA_hypothesis else None,
|
||||
last_code=SOTA_experiment.sub_workspace_list[0].file_dict.get("model.py") if SOTA_hypothesis else None,
|
||||
last_result=SOTA_experiment.result if SOTA_hypothesis else None,
|
||||
hypothesis=hypothesis,
|
||||
exp=exp,
|
||||
)
|
||||
|
||||
# Call the APIBackend to generate the response for hypothesis feedback
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from rdagent.components.runner import CachedRunner
|
||||
from rdagent.core.exception import ModelEmptyError
|
||||
from rdagent.core.utils import cache_with_pickle
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.scenarios.data_mining.experiment.model_experiment import DMModelExperiment
|
||||
|
||||
|
||||
@@ -14,7 +15,11 @@ class DMModelRunner(CachedRunner[DMModelExperiment]):
|
||||
|
||||
env_to_use = {"PYTHONPATH": "./"}
|
||||
|
||||
result = exp.experiment_workspace.execute(run_env=env_to_use)
|
||||
result, stdout = exp.experiment_workspace.execute(run_env=env_to_use)
|
||||
|
||||
if result is None:
|
||||
logger.error(f"Experiment failed to run, stdout: {stdout}")
|
||||
raise ModelEmptyError(f"Failed to run this experiment, because {stdout}")
|
||||
|
||||
exp.result = result
|
||||
|
||||
|
||||
@@ -6,11 +6,9 @@ from rdagent.components.coder.model_coder.model import (
|
||||
ModelTask,
|
||||
)
|
||||
from rdagent.core.experiment import Task
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.scenario import Scenario
|
||||
from rdagent.scenarios.data_mining.experiment.workspace import DMFBWorkspace
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class DMModelExperiment(ModelExperiment[ModelTask, DMFBWorkspace, ModelFBWorkspace]):
|
||||
@@ -22,7 +20,7 @@ class DMModelExperiment(ModelExperiment[ModelTask, DMFBWorkspace, ModelFBWorkspa
|
||||
class DMModelScenario(Scenario):
|
||||
@property
|
||||
def background(self) -> str:
|
||||
return prompt_dict["dm_model_background"]
|
||||
return T(".prompts:dm_model_background").r()
|
||||
|
||||
@property
|
||||
def source_data(self) -> str:
|
||||
@@ -30,15 +28,15 @@ class DMModelScenario(Scenario):
|
||||
|
||||
@property
|
||||
def output_format(self) -> str:
|
||||
return prompt_dict["dm_model_output_format"]
|
||||
return T(".prompts:dm_model_output_format").r()
|
||||
|
||||
@property
|
||||
def interface(self) -> str:
|
||||
return prompt_dict["dm_model_interface"]
|
||||
return T(".prompts:dm_model_interface").r()
|
||||
|
||||
@property
|
||||
def simulator(self) -> str:
|
||||
return prompt_dict["dm_model_simulator"]
|
||||
return T(".prompts:dm_model_simulator").r()
|
||||
|
||||
@property
|
||||
def rich_style_description(self) -> str:
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.model_coder.model import ModelExperiment, ModelTask
|
||||
from rdagent.components.proposal import (
|
||||
Hypothesis,
|
||||
ModelHypothesis2Experiment,
|
||||
ModelHypothesisGen,
|
||||
)
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import Hypothesis, Scenario, Trace
|
||||
from rdagent.scenarios.data_mining.experiment.model_experiment import DMModelExperiment
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent.parent.parent / "qlib" / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
DMModelHypothesis = Hypothesis
|
||||
|
||||
@@ -36,19 +31,27 @@ class DMModelHypothesisGen(ModelHypothesisGen):
|
||||
|
||||
def prepare_context(self, trace: Trace) -> Tuple[dict, bool]:
|
||||
hypothesis_and_feedback = (
|
||||
(
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_and_feedback"])
|
||||
.render(trace=trace)
|
||||
T("scenarios.qlib.prompts:hypothesis_and_feedback").r(
|
||||
trace=trace,
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
|
||||
last_hypothesis_and_feedback = (
|
||||
T("scenarios.qlib.prompts:last_hypothesis_and_feedback").r(
|
||||
experiment=trace.hist[-1][0], feedback=trace.hist[-1][1]
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
|
||||
context_dict = {
|
||||
"hypothesis_and_feedback": hypothesis_and_feedback,
|
||||
"last_hypothesis_and_feedback": last_hypothesis_and_feedback,
|
||||
"RAG": None,
|
||||
"hypothesis_output_format": prompt_dict["hypothesis_output_format"],
|
||||
"hypothesis_specification": prompt_dict["model_hypothesis_specification"],
|
||||
"hypothesis_output_format": T("scenarios.qlib.prompts:hypothesis_output_format").r(),
|
||||
"hypothesis_specification": T("scenarios.qlib.prompts:model_hypothesis_specification").r(),
|
||||
}
|
||||
return context_dict, True
|
||||
|
||||
@@ -68,13 +71,11 @@ class DMModelHypothesisGen(ModelHypothesisGen):
|
||||
class DMModelHypothesis2Experiment(ModelHypothesis2Experiment):
|
||||
def prepare_context(self, hypothesis: Hypothesis, trace: Trace) -> Tuple[dict, bool]:
|
||||
scenario = trace.scen.get_scenario_all_desc()
|
||||
experiment_output_format = prompt_dict["model_experiment_output_format"]
|
||||
experiment_output_format = T("scenarios.qlib.prompts:model_experiment_output_format").r()
|
||||
|
||||
hypothesis_and_feedback = (
|
||||
(
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_and_feedback"])
|
||||
.render(trace=trace)
|
||||
T("scenarios.qlib.prompts:hypothesis_and_feedback").r(
|
||||
trace=trace,
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
from rdagent.core.experiment import Task
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.scenario import Scenario
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class GeneralModelScenario(Scenario):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._background = deepcopy(prompt_dict["general_model_background"])
|
||||
self._output_format = deepcopy(prompt_dict["general_model_output_format"])
|
||||
self._interface = deepcopy(prompt_dict["general_model_interface"])
|
||||
self._simulator = deepcopy(prompt_dict["general_model_simulator"])
|
||||
self._rich_style_description = deepcopy(prompt_dict["general_model_rich_style_description"])
|
||||
self._background = deepcopy(T(".prompts:general_model_background").r())
|
||||
self._output_format = deepcopy(T(".prompts:general_model_output_format").r())
|
||||
self._interface = deepcopy(T(".prompts:general_model_interface").r())
|
||||
self._simulator = deepcopy(T(".prompts:general_model_simulator").r())
|
||||
self._rich_style_description = deepcopy(T(".prompts:general_model_rich_style_description").r())
|
||||
|
||||
@property
|
||||
def background(self) -> str:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
@@ -7,7 +6,6 @@ from jinja2 import Environment, StrictUndefined
|
||||
from rdagent.components.coder.factor_coder import FactorCoSTEER
|
||||
from rdagent.components.coder.model_coder import ModelCoSTEER
|
||||
from rdagent.core.developer import Developer
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.scenarios.kaggle.experiment.kaggle_experiment import (
|
||||
KG_SELECT_MAPPING,
|
||||
@@ -16,8 +14,7 @@ from rdagent.scenarios.kaggle.experiment.kaggle_experiment import (
|
||||
|
||||
KGModelCoSTEER = ModelCoSTEER
|
||||
KGFactorCoSTEER = FactorCoSTEER
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
DEFAULT_SELECTION_CODE = """
|
||||
import pandas as pd
|
||||
@@ -46,15 +43,12 @@ class KGModelFeatureSelectionCoder(Developer[KGModelExperiment]):
|
||||
.render(feature_index_list=None)
|
||||
)
|
||||
else:
|
||||
system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["model_feature_selection"]["system"])
|
||||
.render(scenario=self.scen.get_scenario_all_desc(), model_type=exp.sub_tasks[0].model_type)
|
||||
system_prompt = T("scenarios.kaggle.prompts:model_feature_selection.system").r(
|
||||
scenario=exp.scen.get_scenario_all_desc(),
|
||||
model_type=exp.sub_tasks[0].model_type,
|
||||
)
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["model_feature_selection"]["user"])
|
||||
.render(feature_groups=[desc[0] for desc in exp.experiment_workspace.data_description])
|
||||
user_prompt = T("scenarios.kaggle.prompts:model_feature_selection.user").r(
|
||||
feature_groups=[desc[0] for desc in exp.experiment_workspace.data_description]
|
||||
)
|
||||
|
||||
chosen_index = json.loads(
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
import pandas as pd
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.knowledge_management.graph import UndirectedNode
|
||||
from rdagent.core.experiment import Experiment
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import (
|
||||
Experiment2Feedback,
|
||||
Hypothesis,
|
||||
HypothesisFeedback,
|
||||
Trace,
|
||||
)
|
||||
from rdagent.core.proposal import Experiment2Feedback, HypothesisFeedback, Trace
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.scenarios.kaggle.experiment.kaggle_experiment import KG_SELECT_MAPPING
|
||||
from rdagent.utils import convert2bool
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
|
||||
DIRNAME = Path(__file__).absolute().resolve().parent
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class KGExperiment2Feedback(Experiment2Feedback):
|
||||
@@ -87,10 +77,8 @@ class KGExperiment2Feedback(Experiment2Feedback):
|
||||
prompt_key = "factor_feedback_generation"
|
||||
|
||||
# Generate the system prompt
|
||||
sys_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict[prompt_key]["system"])
|
||||
.render(scenario=self.scen.get_scenario_all_desc(filtered_tag="feedback"))
|
||||
sys_prompt = T(f"scenarios.kaggle.prompts:{prompt_key}.system").r(
|
||||
scenario=self.scen.get_scenario_all_desc(filtered_tag="feedback")
|
||||
)
|
||||
|
||||
sota_exp = exp.based_experiments[-1] if exp.based_experiments else None
|
||||
@@ -139,11 +127,7 @@ class KGExperiment2Feedback(Experiment2Feedback):
|
||||
"last_hypothesis_and_feedback": last_hypothesis_and_feedback,
|
||||
}
|
||||
|
||||
usr_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["kg_feedback_generation_user"])
|
||||
.render(**render_dict)
|
||||
)
|
||||
usr_prompt = T(f"scenarios.kaggle.prompts:kg_feedback_generation_user").r(**render_dict)
|
||||
|
||||
response = APIBackend().build_messages_and_create_chat_completion(
|
||||
user_prompt=usr_prompt,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import json
|
||||
import pickle
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
@@ -8,7 +6,6 @@ import pandas as pd
|
||||
from rdagent.components.runner import CachedRunner
|
||||
from rdagent.core.exception import CoderError, FactorEmptyError, ModelEmptyError
|
||||
from rdagent.core.experiment import ASpecificExp, Experiment
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.utils import cache_with_pickle
|
||||
from rdagent.oai.llm_utils import md5_hash
|
||||
from rdagent.scenarios.kaggle.experiment.kaggle_experiment import (
|
||||
@@ -16,8 +13,6 @@ from rdagent.scenarios.kaggle.experiment.kaggle_experiment import (
|
||||
KGModelExperiment,
|
||||
)
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
|
||||
|
||||
|
||||
class KGCachedRunner(CachedRunner[ASpecificExp]):
|
||||
def get_cache_key(self, exp: ASpecificExp) -> str:
|
||||
|
||||
@@ -6,11 +6,9 @@ from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
import pandas as pd
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.app.kaggle.conf import KAGGLE_IMPLEMENT_SETTING
|
||||
from rdagent.core.experiment import Task
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.scenario import Scenario
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.scenarios.kaggle.experiment.kaggle_experiment import KGFactorExperiment
|
||||
@@ -21,8 +19,7 @@ from rdagent.scenarios.kaggle.kaggle_crawler import (
|
||||
from rdagent.scenarios.kaggle.knowledge_management.vector_base import (
|
||||
KaggleExperienceBase,
|
||||
)
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
KG_ACTION_FEATURE_PROCESSING = "Feature processing"
|
||||
KG_ACTION_FEATURE_ENGINEERING = "Feature engineering"
|
||||
@@ -74,20 +71,11 @@ class KGScenario(Scenario):
|
||||
self.initial_performance = 0.0
|
||||
|
||||
def _analysis_competition_description(self):
|
||||
sys_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["kg_description_template"]["system"])
|
||||
.render()
|
||||
)
|
||||
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["kg_description_template"]["user"])
|
||||
.render(
|
||||
competition_descriptions=self.competition_descriptions,
|
||||
raw_data_information=self.source_data,
|
||||
evaluation_metric_direction=self.evaluation_metric_direction,
|
||||
)
|
||||
sys_prompt = T(".prompts:kg_description_template.system").r()
|
||||
user_prompt = T(".prompts:kg_description_template.user").r(
|
||||
competition_descriptions=self.competition_descriptions,
|
||||
raw_data_information=self.source_data,
|
||||
evaluation_metric_direction=self.evaluation_metric_direction,
|
||||
)
|
||||
|
||||
response_analysis = APIBackend().build_messages_and_create_chat_completion(
|
||||
@@ -124,26 +112,22 @@ class KGScenario(Scenario):
|
||||
|
||||
@property
|
||||
def background(self) -> str:
|
||||
background_template = prompt_dict["kg_background"]
|
||||
|
||||
train_script = (
|
||||
Path(__file__).parent / "templates" / KAGGLE_IMPLEMENT_SETTING.competition / "train.py"
|
||||
).read_text()
|
||||
|
||||
background_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(background_template)
|
||||
.render(
|
||||
train_script=train_script,
|
||||
competition_type=self.competition_type,
|
||||
competition_description=self.competition_description,
|
||||
target_description=self.target_description,
|
||||
competition_features=self.competition_features,
|
||||
submission_specifications=self.submission_specifications,
|
||||
evaluation_desc=self.evaluation_desc,
|
||||
evaluate_bool=self.evaluation_metric_direction,
|
||||
)
|
||||
background_prompt = T(".prompts:kg_background").r(
|
||||
train_script=train_script,
|
||||
competition_type=self.competition_type,
|
||||
competition_description=self.competition_description,
|
||||
target_description=self.target_description,
|
||||
competition_features=self.competition_features,
|
||||
submission_specifications=self.submission_specifications,
|
||||
evaluation_desc=self.evaluation_desc,
|
||||
evaluate_bool=self.evaluation_metric_direction,
|
||||
)
|
||||
|
||||
return background_prompt
|
||||
|
||||
@property
|
||||
@@ -183,12 +167,11 @@ class KGScenario(Scenario):
|
||||
def output_format(self, tag=None) -> str:
|
||||
assert tag in [None, "feature", "model"]
|
||||
feature_output_format = f"""The feature code should output following the format:
|
||||
{prompt_dict['kg_feature_output_format']}"""
|
||||
model_output_format = f"""The model code should output following the format:\n""" + (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["kg_model_output_format"])
|
||||
.render(channel=self.model_output_channel)
|
||||
)
|
||||
{T(".prompts:kg_feature_output_format").r()}"""
|
||||
model_output_format = f"""The model code should output following the format:\n""" + T(
|
||||
".prompts:kg_model_output_format"
|
||||
).r(channel=self.model_output_channel)
|
||||
|
||||
if tag is None:
|
||||
return feature_output_format + "\n" + model_output_format
|
||||
elif tag == "feature":
|
||||
@@ -199,12 +182,12 @@ class KGScenario(Scenario):
|
||||
def interface(self, tag=None) -> str:
|
||||
assert tag in [None, "feature", "XGBoost", "RandomForest", "LightGBM", "NN"]
|
||||
feature_interface = f"""The feature code should follow the interface:
|
||||
{prompt_dict['kg_feature_interface']}"""
|
||||
{T(".prompts:kg_feature_interface").r()}"""
|
||||
if tag == "feature":
|
||||
return feature_interface
|
||||
|
||||
model_interface = "The model code should follow the interface:\n" + (
|
||||
Environment(undefined=StrictUndefined).from_string(prompt_dict["kg_model_interface"]).render(tag=tag)
|
||||
model_interface = "The model code should follow the interface:\n" + T(".prompts:kg_model_interface").r(
|
||||
tag=tag,
|
||||
)
|
||||
if tag is None:
|
||||
return feature_interface + "\n" + model_interface
|
||||
@@ -213,13 +196,14 @@ class KGScenario(Scenario):
|
||||
|
||||
def simulator(self, tag=None) -> str:
|
||||
assert tag in [None, "feature", "model"]
|
||||
kg_feature_simulator = "The feature code will be sent to the simulator:\n" + prompt_dict["kg_feature_simulator"]
|
||||
|
||||
kg_model_simulator = "The model code will be sent to the simulator:\n" + (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["kg_model_simulator"])
|
||||
.render(submission_specifications=self.submission_specifications)
|
||||
kg_feature_simulator = (
|
||||
"The feature code will be sent to the simulator:\n" + T(".prompts:kg_feature_simulator").r()
|
||||
)
|
||||
kg_model_simulator = "The model code will be sent to the simulator:\n" + T(".prompts:kg_model_simulator").r(
|
||||
submission_specifications=self.submission_specifications,
|
||||
)
|
||||
|
||||
if tag is None:
|
||||
return kg_feature_simulator + "\n" + kg_model_simulator
|
||||
elif tag == "feature":
|
||||
|
||||
@@ -9,7 +9,6 @@ from itertools import chain
|
||||
from pathlib import Path
|
||||
|
||||
import nbformat
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
from rich import print
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
def extract_knowledge_from_high_score_answers(content: str):
|
||||
sys_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["extract_kaggle_knowledge_prompts"]["system"])
|
||||
.render()
|
||||
)
|
||||
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["extract_kaggle_knowledge_prompts"]["user"])
|
||||
.render(file_content=content)
|
||||
)
|
||||
sys_prompt = T(".prompts:extract_kaggle_knowledge_prompts.system").r()
|
||||
user_prompt = T(".prompts:extract_kaggle_knowledge_prompts.user").r(file_content=content)
|
||||
|
||||
response_analysis = APIBackend().build_messages_and_create_chat_completion(
|
||||
user_prompt=user_prompt,
|
||||
@@ -41,16 +27,9 @@ def extract_knowledge_from_feedback(feedback_response: dict) -> dict:
|
||||
"""
|
||||
Extracts knowledge from LLM-generated feedback and structures it.
|
||||
"""
|
||||
sys_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["extract_kaggle_knowledge_from_feedback_prompts"]["system"])
|
||||
.render()
|
||||
)
|
||||
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["extract_kaggle_knowledge_from_feedback_prompts"]["user"])
|
||||
.render(experiment_strategy=feedback_response)
|
||||
sys_prompt = T(".prompts:extract_kaggle_knowledge_from_feedback_prompts.system").r()
|
||||
user_prompt = T(".prompts:extract_kaggle_knowledge_from_feedback_prompts.user").r(
|
||||
experiment_strategy=feedback_response
|
||||
)
|
||||
|
||||
response_analysis = APIBackend().build_messages_and_create_chat_completion(
|
||||
|
||||
@@ -3,7 +3,6 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
from tqdm import tqdm
|
||||
|
||||
from rdagent.app.kaggle.conf import KAGGLE_IMPLEMENT_SETTING
|
||||
@@ -12,12 +11,10 @@ from rdagent.components.knowledge_management.graph import (
|
||||
UndirectedNode,
|
||||
)
|
||||
from rdagent.core.conf import RD_AGENT_SETTINGS
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.utils import multiprocessing_wrapper
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.scenarios.kaggle.experiment.scenario import KGScenario
|
||||
|
||||
PROMPT_DICT = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class KGKnowledgeGraph(UndirectedGraph):
|
||||
@@ -42,19 +39,15 @@ class KGKnowledgeGraph(UndirectedGraph):
|
||||
self.dump() # Each valid experiment will overwrite this file once again.
|
||||
|
||||
def analyze_one_document(self, document_content: str, scenario: KGScenario | None) -> list:
|
||||
session_system_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(PROMPT_DICT["extract_knowledge_graph_from_document"]["system"])
|
||||
.render(scenario=scenario.get_scenario_all_desc() if scenario is not None else "")
|
||||
session_system_prompt = T(".prompts:extract_knowledge_graph_from_document.system").r(
|
||||
scenario=scenario.get_scenario_all_desc() if scenario is not None else ""
|
||||
)
|
||||
|
||||
session = APIBackend().build_chat_session(
|
||||
session_system_prompt=session_system_prompt,
|
||||
)
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(PROMPT_DICT["extract_knowledge_graph_from_document"]["user"])
|
||||
.render(document_content=document_content)
|
||||
user_prompt = T(".prompts:extract_knowledge_graph_from_document.user").r(
|
||||
document_content=document_content,
|
||||
)
|
||||
knowledge_list = []
|
||||
for _ in range(10):
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
|
||||
import pandas as pd
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.knowledge_management.vector_base import Document, PDVectorBase
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.scenarios.kaggle.knowledge_management.extract_knowledge import (
|
||||
extract_knowledge_from_feedback,
|
||||
)
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class KGKnowledgeDocument(Document):
|
||||
@@ -270,17 +268,8 @@ class KaggleExperienceBase(PDVectorBase):
|
||||
return kaggle_docs, similarities
|
||||
|
||||
def refine_with_LLM(self, target: str, text: str) -> str:
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
|
||||
sys_prompt = (
|
||||
Environment(undefined=StrictUndefined).from_string(prompt_dict["refine_with_LLM"]["system"]).render()
|
||||
)
|
||||
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["refine_with_LLM"]["user"])
|
||||
.render(target=target, text=text)
|
||||
)
|
||||
sys_prompt = T(".prompts:refine_with_LLM.system").r()
|
||||
user_prompt = T(".prompts:refine_with_LLM.user").r(target=target, text=text)
|
||||
|
||||
response = APIBackend().build_messages_and_create_chat_completion(
|
||||
user_prompt=user_prompt,
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.app.kaggle.conf import KAGGLE_IMPLEMENT_SETTING
|
||||
from rdagent.components.coder.factor_coder.factor import FactorTask
|
||||
from rdagent.components.coder.model_coder.model import ModelExperiment, ModelTask
|
||||
from rdagent.components.knowledge_management.vector_base import VectorBase
|
||||
from rdagent.components.proposal import (
|
||||
FactorAndModelHypothesis2Experiment,
|
||||
FactorAndModelHypothesisGen,
|
||||
)
|
||||
from rdagent.core.exception import ModelEmptyError
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import Hypothesis, Scenario, Trace
|
||||
from rdagent.scenarios.kaggle.experiment.kaggle_experiment import (
|
||||
KG_MODEL_MAPPING,
|
||||
@@ -22,22 +16,16 @@ from rdagent.scenarios.kaggle.experiment.kaggle_experiment import (
|
||||
KGFactorExperiment,
|
||||
KGModelExperiment,
|
||||
)
|
||||
from rdagent.scenarios.kaggle.experiment.scenario import KGScenario
|
||||
from rdagent.scenarios.kaggle.knowledge_management.graph import KGKnowledgeGraph
|
||||
from rdagent.scenarios.kaggle.knowledge_management.vector_base import (
|
||||
KaggleExperienceBase,
|
||||
)
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
|
||||
|
||||
|
||||
from rdagent.scenarios.kaggle.experiment.scenario import (
|
||||
KG_ACTION_FEATURE_ENGINEERING,
|
||||
KG_ACTION_FEATURE_PROCESSING,
|
||||
KG_ACTION_LIST,
|
||||
KG_ACTION_MODEL_FEATURE_SELECTION,
|
||||
KG_ACTION_MODEL_TUNING,
|
||||
KGScenario,
|
||||
)
|
||||
from rdagent.scenarios.kaggle.knowledge_management.graph import KGKnowledgeGraph
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class KGHypothesis(Hypothesis):
|
||||
@@ -187,10 +175,9 @@ def generate_RAG_content(
|
||||
insight["conclusion"] = "No conclusion information available."
|
||||
insights.append(insight)
|
||||
|
||||
RAG_content = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["KG_hypothesis_gen_RAG"])
|
||||
.render(insights=insights, experiences=experiences)
|
||||
RAG_content = T("scenarios.kaggle.prompts:KG_hypothesis_gen_RAG").r(
|
||||
insights=insights,
|
||||
experiences=experiences,
|
||||
)
|
||||
return RAG_content
|
||||
|
||||
@@ -262,10 +249,8 @@ class KGHypothesisGen(FactorAndModelHypothesisGen):
|
||||
|
||||
def prepare_context(self, trace: Trace) -> Tuple[dict, bool]:
|
||||
hypothesis_and_feedback = (
|
||||
(
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_and_feedback"])
|
||||
.render(trace=trace)
|
||||
T("scenarios.kaggle.prompts:hypothesis_and_feedback").r(
|
||||
trace=trace,
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
@@ -287,7 +272,7 @@ class KGHypothesisGen(FactorAndModelHypothesisGen):
|
||||
"\n\nNext experiment action is "
|
||||
+ action
|
||||
+ "\nspecification: "
|
||||
+ prompt_dict["hypothesis_specification"][action]
|
||||
+ T(f"scenarios.kaggle.prompts:hypothesis_specification.{action}").r()
|
||||
)
|
||||
|
||||
context_dict = {
|
||||
@@ -298,7 +283,7 @@ class KGHypothesisGen(FactorAndModelHypothesisGen):
|
||||
hypothesis_and_feedback=hypothesis_and_feedback,
|
||||
target=action if self.scen.if_action_choosing_based_on_UCB else None,
|
||||
),
|
||||
"hypothesis_output_format": prompt_dict["hypothesis_output_format"],
|
||||
"hypothesis_output_format": T("scenarios.kaggle.prompts:hypothesis_output_format").r(),
|
||||
"hypothesis_specification": hypothesis_specification,
|
||||
}
|
||||
return context_dict, True
|
||||
@@ -324,17 +309,15 @@ class KGHypothesis2Experiment(FactorAndModelHypothesis2Experiment):
|
||||
scenario = trace.scen.get_scenario_all_desc(filtered_tag="hypothesis_and_experiment")
|
||||
assert isinstance(hypothesis, KGHypothesis)
|
||||
experiment_output_format = (
|
||||
prompt_dict["feature_experiment_output_format"]
|
||||
T("scenarios.kaggle.prompts:feature_experiment_output_format").r()
|
||||
if hypothesis.action in [KG_ACTION_FEATURE_ENGINEERING, KG_ACTION_FEATURE_PROCESSING]
|
||||
else prompt_dict["model_experiment_output_format"]
|
||||
else T("scenarios.kaggle.prompts:model_experiment_output_format").r()
|
||||
)
|
||||
self.current_action = hypothesis.action
|
||||
|
||||
hypothesis_and_feedback = (
|
||||
(
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_and_feedback"])
|
||||
.render(trace=trace)
|
||||
T("scenarios.kaggle.prompts:hypothesis_and_feedback").r(
|
||||
trace=trace,
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import pandas as pd
|
||||
from pandarallel import pandarallel
|
||||
|
||||
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERMultiFeedback
|
||||
from rdagent.core.conf import RD_AGENT_SETTINGS
|
||||
from rdagent.core.utils import cache_with_pickle, multiprocessing_wrapper
|
||||
from rdagent.core.utils import cache_with_pickle
|
||||
|
||||
pandarallel.initialize(verbose=1)
|
||||
|
||||
from rdagent.components.runner import CachedRunner
|
||||
from rdagent.core.exception import FactorEmptyError
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.scenarios.qlib.developer.utils import process_factor_data
|
||||
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
|
||||
from rdagent.scenarios.qlib.experiment.model_experiment import QlibModelExperiment
|
||||
|
||||
DIRNAME = Path(__file__).absolute().resolve().parent
|
||||
DIRNAME_local = Path.cwd()
|
||||
@@ -78,24 +77,33 @@ class QlibFactorRunner(CachedRunner[QlibFactorExperiment]):
|
||||
then passing the combined data to Docker for backtest results.
|
||||
"""
|
||||
if exp.based_experiments and exp.based_experiments[-1].result is None:
|
||||
logger.info(f"Baseline experiment execution ...")
|
||||
exp.based_experiments[-1] = self.develop(exp.based_experiments[-1])
|
||||
|
||||
if exp.based_experiments:
|
||||
SOTA_factor = None
|
||||
if len(exp.based_experiments) > 1:
|
||||
SOTA_factor = self.process_factor_data(exp.based_experiments)
|
||||
# Filter and retain only QlibFactorExperiment instances
|
||||
sota_factor_experiments_list = [
|
||||
base_exp for base_exp in exp.based_experiments if isinstance(base_exp, QlibFactorExperiment)
|
||||
]
|
||||
if len(sota_factor_experiments_list) > 1:
|
||||
logger.info(f"SOTA factor processing ...")
|
||||
SOTA_factor = process_factor_data(sota_factor_experiments_list)
|
||||
|
||||
logger.info(f"New factor processing ...")
|
||||
# Process the new factors data
|
||||
new_factors = self.process_factor_data(exp)
|
||||
new_factors = process_factor_data(exp)
|
||||
|
||||
if new_factors.empty:
|
||||
raise FactorEmptyError("No valid factor data found to merge.")
|
||||
raise FactorEmptyError("Factors failed to run on the full sample, this round of experiment failed.")
|
||||
|
||||
# Combine the SOTA factor and new factors if SOTA factor exists
|
||||
if SOTA_factor is not None and not SOTA_factor.empty:
|
||||
new_factors = self.deduplicate_new_factors(SOTA_factor, new_factors)
|
||||
if new_factors.empty:
|
||||
raise FactorEmptyError("No valid factor data found to merge.")
|
||||
raise FactorEmptyError(
|
||||
"The factors generated in this round are highly similar to the previous factors. Please change the direction for creating new factors."
|
||||
)
|
||||
combined_factors = pd.concat([SOTA_factor, new_factors], axis=1).dropna()
|
||||
else:
|
||||
combined_factors = new_factors
|
||||
@@ -105,6 +113,9 @@ class QlibFactorRunner(CachedRunner[QlibFactorExperiment]):
|
||||
combined_factors = combined_factors.loc[:, ~combined_factors.columns.duplicated(keep="last")]
|
||||
new_columns = pd.MultiIndex.from_product([["feature"], combined_factors.columns])
|
||||
combined_factors.columns = new_columns
|
||||
num_features = RD_AGENT_SETTINGS.initial_fator_library_size + len(combined_factors.columns)
|
||||
logger.info(f"Factor data processing completed.")
|
||||
|
||||
# Due to the rdagent and qlib docker image in the numpy version of the difference,
|
||||
# the `combined_factors_df.pkl` file could not be loaded correctly in qlib dokcer,
|
||||
# so we changed the file type of `combined_factors_df` from pkl to parquet.
|
||||
@@ -113,52 +124,58 @@ class QlibFactorRunner(CachedRunner[QlibFactorExperiment]):
|
||||
# Save the combined factors to the workspace
|
||||
combined_factors.to_parquet(target_path, engine="pyarrow")
|
||||
|
||||
result = exp.experiment_workspace.execute(
|
||||
qlib_config_name=f"conf.yaml" if len(exp.based_experiments) == 0 else "conf_combined.yaml"
|
||||
)
|
||||
# If model exp exists in the previous experiment
|
||||
exist_sota_model_exp = False
|
||||
for base_exp in reversed(exp.based_experiments):
|
||||
if isinstance(base_exp, QlibModelExperiment):
|
||||
sota_model_exp = base_exp
|
||||
exist_sota_model_exp = True
|
||||
break
|
||||
logger.info(f"Experiment execution ...")
|
||||
if exist_sota_model_exp:
|
||||
exp.experiment_workspace.inject_files(
|
||||
**{"model.py": sota_model_exp.sub_workspace_list[0].file_dict["model.py"]}
|
||||
)
|
||||
env_to_use = {"PYTHONPATH": "./"}
|
||||
sota_training_hyperparameters = sota_model_exp.sub_tasks[0].training_hyperparameters
|
||||
if sota_training_hyperparameters:
|
||||
env_to_use.update(
|
||||
{
|
||||
"n_epochs": str(sota_training_hyperparameters.get("n_epochs", "100")),
|
||||
"lr": str(sota_training_hyperparameters.get("lr", "2e-4")),
|
||||
"early_stop": str(sota_training_hyperparameters.get("early_stop", 10)),
|
||||
"batch_size": str(sota_training_hyperparameters.get("batch_size", 256)),
|
||||
"weight_decay": str(sota_training_hyperparameters.get("weight_decay", 0.0)),
|
||||
}
|
||||
)
|
||||
sota_model_type = sota_model_exp.sub_tasks[0].model_type
|
||||
if sota_model_type == "TimeSeries":
|
||||
env_to_use.update(
|
||||
{"dataset_cls": "TSDatasetH", "num_features": num_features, "step_len": 20, "num_timesteps": 20}
|
||||
)
|
||||
elif sota_model_type == "Tabular":
|
||||
env_to_use.update({"dataset_cls": "DatasetH", "num_features": num_features})
|
||||
|
||||
# model + combined factors
|
||||
result, stdout = exp.experiment_workspace.execute(
|
||||
qlib_config_name="conf_model_combined.yaml", run_env=env_to_use
|
||||
)
|
||||
else:
|
||||
# LGBM + combined factors
|
||||
result, stdout = exp.experiment_workspace.execute(
|
||||
qlib_config_name=f"conf.yaml" if len(exp.based_experiments) == 0 else "conf_combined.yaml"
|
||||
)
|
||||
else:
|
||||
logger.info(f"Experiment execution ...")
|
||||
result, stdout = exp.experiment_workspace.execute(
|
||||
qlib_config_name=f"conf.yaml" if len(exp.based_experiments) == 0 else "conf_combined.yaml"
|
||||
)
|
||||
|
||||
if result is None:
|
||||
logger.error(f"Failed to run this experiment, because {stdout}")
|
||||
raise FactorEmptyError(f"Failed to run this experiment, because {stdout}")
|
||||
|
||||
exp.result = result
|
||||
exp.stdout = stdout
|
||||
|
||||
return exp
|
||||
|
||||
def process_factor_data(self, exp_or_list: List[QlibFactorExperiment] | QlibFactorExperiment) -> pd.DataFrame:
|
||||
"""
|
||||
Process and combine factor data from experiment implementations.
|
||||
|
||||
Args:
|
||||
exp (ASpecificExp): The experiment containing factor data.
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: Combined factor data without NaN values.
|
||||
"""
|
||||
if isinstance(exp_or_list, QlibFactorExperiment):
|
||||
exp_or_list = [exp_or_list]
|
||||
factor_dfs = []
|
||||
|
||||
# Collect all exp's dataframes
|
||||
for exp in exp_or_list:
|
||||
if len(exp.sub_tasks) > 0:
|
||||
# if it has no sub_tasks, the experiment is results from template project.
|
||||
# otherwise, it is developed with designed task. So it should have feedback.
|
||||
assert isinstance(exp.prop_dev_feedback, CoSTEERMultiFeedback)
|
||||
# Iterate over sub-implementations and execute them to get each factor data
|
||||
message_and_df_list = multiprocessing_wrapper(
|
||||
[
|
||||
(implementation.execute, ("All",))
|
||||
for implementation, fb in zip(exp.sub_workspace_list, exp.prop_dev_feedback)
|
||||
if implementation and fb
|
||||
], # only execute successfully feedback
|
||||
n=RD_AGENT_SETTINGS.multi_proc_n,
|
||||
)
|
||||
for message, df in message_and_df_list:
|
||||
# Check if factor generation was successful
|
||||
if df is not None and "datetime" in df.index.names:
|
||||
time_diff = df.index.get_level_values("datetime").to_series().diff().dropna().unique()
|
||||
if pd.Timedelta(minutes=1) not in time_diff:
|
||||
factor_dfs.append(df)
|
||||
|
||||
# Combine all successful factor data
|
||||
if factor_dfs:
|
||||
return pd.concat(factor_dfs, axis=1)
|
||||
else:
|
||||
raise FactorEmptyError("No valid factor data found to merge.")
|
||||
|
||||
@@ -3,23 +3,27 @@ from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
import pandas as pd
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.core.experiment import Experiment
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import (
|
||||
Experiment2Feedback,
|
||||
Hypothesis,
|
||||
HypothesisFeedback,
|
||||
Trace,
|
||||
)
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
from rdagent.utils import convert2bool
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
feedback_prompts = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
|
||||
DIRNAME = Path(__file__).absolute().resolve().parent
|
||||
|
||||
IMPORTANT_METRICS = [
|
||||
"IC",
|
||||
"1day.excess_return_with_cost.annualized_return",
|
||||
"1day.excess_return_with_cost.max_drawdown",
|
||||
]
|
||||
|
||||
|
||||
def process_results(current_result, sota_result):
|
||||
# Convert the results to dataframes
|
||||
@@ -37,24 +41,18 @@ def process_results(current_result, sota_result):
|
||||
# Combine the dataframes on the Metric index
|
||||
combined_df = pd.concat([current_df, sota_df], axis=1)
|
||||
|
||||
# Select important metrics for comparison
|
||||
important_metrics = [
|
||||
"1day.excess_return_without_cost.max_drawdown",
|
||||
"1day.excess_return_without_cost.information_ratio",
|
||||
"1day.excess_return_without_cost.annualized_return",
|
||||
"IC",
|
||||
]
|
||||
|
||||
# Filter the combined DataFrame to retain only the important metrics
|
||||
filtered_combined_df = combined_df.loc[important_metrics]
|
||||
filtered_combined_df = combined_df.loc[IMPORTANT_METRICS]
|
||||
|
||||
filtered_combined_df[
|
||||
"Bigger columns name (Didn't consider the direction of the metric, you should judge it by yourself that bigger is better or smaller is better)"
|
||||
] = filtered_combined_df.apply(
|
||||
lambda row: "Current Result" if row["Current Result"] > row["SOTA Result"] else "SOTA Result", axis=1
|
||||
)
|
||||
def format_filtered_combined_df(filtered_combined_df: pd.DataFrame) -> str:
|
||||
results = []
|
||||
for metric, row in filtered_combined_df.iterrows():
|
||||
current = row["Current Result"]
|
||||
sota = row["SOTA Result"]
|
||||
results.append(f"{metric} of Current Result is {current:.6f}, of SOTA Result is {sota:.6f}")
|
||||
return "; ".join(results)
|
||||
|
||||
return filtered_combined_df.to_string()
|
||||
return format_filtered_combined_df(filtered_combined_df)
|
||||
|
||||
|
||||
class QlibFactorExperiment2Feedback(Experiment2Feedback):
|
||||
@@ -81,21 +79,20 @@ class QlibFactorExperiment2Feedback(Experiment2Feedback):
|
||||
combined_result = process_results(current_result, sota_result)
|
||||
|
||||
# Generate the system prompt
|
||||
sys_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(feedback_prompts["factor_feedback_generation"]["system"])
|
||||
.render(scenario=self.scen.get_scenario_all_desc())
|
||||
)
|
||||
if isinstance(self.scen, QlibQuantScenario):
|
||||
sys_prompt = T("scenarios.qlib.prompts:factor_feedback_generation.system").r(
|
||||
scenario=self.scen.get_scenario_all_desc(action="factor")
|
||||
)
|
||||
else:
|
||||
sys_prompt = T("scenarios.qlib.prompts:factor_feedback_generation.system").r(
|
||||
scenario=self.scen.get_scenario_all_desc()
|
||||
)
|
||||
|
||||
# Generate the user prompt
|
||||
usr_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(feedback_prompts["factor_feedback_generation"]["user"])
|
||||
.render(
|
||||
hypothesis_text=hypothesis_text,
|
||||
task_details=tasks_factors,
|
||||
combined_result=combined_result,
|
||||
)
|
||||
usr_prompt = T("scenarios.qlib.prompts:factor_feedback_generation.user").r(
|
||||
hypothesis_text=hypothesis_text,
|
||||
task_details=tasks_factors,
|
||||
combined_result=combined_result,
|
||||
)
|
||||
|
||||
# Call the APIBackend to generate the response for hypothesis feedback
|
||||
@@ -126,40 +123,58 @@ class QlibFactorExperiment2Feedback(Experiment2Feedback):
|
||||
|
||||
|
||||
class QlibModelExperiment2Feedback(Experiment2Feedback):
|
||||
"""Generated feedbacks on the hypothesis from **Executed** Implementations of different tasks & their comparisons with previous performances"""
|
||||
|
||||
def generate_feedback(self, exp: Experiment, trace: Trace) -> HypothesisFeedback:
|
||||
"""
|
||||
The `ti` should be executed and the results should be included, as well as the comparison between previous results (done by LLM).
|
||||
For example: `mlflow` of Qlib will be included.
|
||||
Generate feedback for the given experiment and hypothesis.
|
||||
|
||||
Args:
|
||||
exp (QlibModelExperiment): The experiment to generate feedback for.
|
||||
hypothesis (QlibModelHypothesis): The hypothesis to generate feedback for.
|
||||
trace (Trace): The trace of the experiment.
|
||||
|
||||
Returns:
|
||||
HypothesisFeedback: The feedback generated for the given experiment and hypothesis.
|
||||
"""
|
||||
hypothesis = exp.hypothesis
|
||||
logger.info("Generating feedback...")
|
||||
# Define the system prompt for hypothesis feedback
|
||||
system_prompt = feedback_prompts["model_feedback_generation"]["system"]
|
||||
|
||||
# Define the user prompt for hypothesis feedback
|
||||
context = trace.scen
|
||||
SOTA_hypothesis, SOTA_experiment = trace.get_sota_hypothesis_and_experiment()
|
||||
|
||||
user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(feedback_prompts["model_feedback_generation"]["user"])
|
||||
.render(
|
||||
context=context,
|
||||
last_hypothesis=SOTA_hypothesis,
|
||||
last_task=SOTA_experiment.sub_tasks[0].get_task_information() if SOTA_hypothesis else None,
|
||||
last_code=SOTA_experiment.sub_workspace_list[0].file_dict.get("model.py") if SOTA_hypothesis else None,
|
||||
last_result=SOTA_experiment.result if SOTA_hypothesis else None,
|
||||
hypothesis=hypothesis,
|
||||
exp=exp,
|
||||
# Generate the system prompt
|
||||
if isinstance(self.scen, QlibQuantScenario):
|
||||
sys_prompt = T("scenarios.qlib.prompts:model_feedback_generation.system").r(
|
||||
scenario=self.scen.get_scenario_all_desc(action="model")
|
||||
)
|
||||
else:
|
||||
sys_prompt = T("scenarios.qlib.prompts:factor_feedback_generation.system").r(
|
||||
scenario=self.scen.get_scenario_all_desc()
|
||||
)
|
||||
|
||||
# Generate the user prompt
|
||||
SOTA_hypothesis, SOTA_experiment = trace.get_sota_hypothesis_and_experiment()
|
||||
user_prompt = T("scenarios.qlib.prompts:model_feedback_generation.user").r(
|
||||
sota_hypothesis=SOTA_hypothesis,
|
||||
sota_task=SOTA_experiment.sub_tasks[0].get_task_information() if SOTA_hypothesis else None,
|
||||
sota_code=SOTA_experiment.sub_workspace_list[0].file_dict.get("model.py") if SOTA_hypothesis else None,
|
||||
sota_result=SOTA_experiment.result.loc[IMPORTANT_METRICS] if SOTA_hypothesis else None,
|
||||
hypothesis=hypothesis,
|
||||
exp=exp,
|
||||
exp_result=exp.result.loc[IMPORTANT_METRICS] if exp.result is not None else "execution failed",
|
||||
)
|
||||
|
||||
# Call the APIBackend to generate the response for hypothesis feedback
|
||||
response = APIBackend().build_messages_and_create_chat_completion(
|
||||
user_prompt=user_prompt,
|
||||
system_prompt=sys_prompt,
|
||||
json_mode=True,
|
||||
json_target_type=Dict[str, str | bool | int],
|
||||
)
|
||||
|
||||
# Parse the JSON response to extract the feedback
|
||||
response_json_hypothesis = json.loads(response)
|
||||
|
||||
# Call the APIBackend to generate the response for hypothesis feedback
|
||||
response_hypothesis = APIBackend().build_messages_and_create_chat_completion(
|
||||
user_prompt=user_prompt,
|
||||
system_prompt=system_prompt,
|
||||
system_prompt=sys_prompt,
|
||||
json_mode=True,
|
||||
json_target_type=Dict[str, str | bool | int],
|
||||
)
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import pandas as pd
|
||||
|
||||
from rdagent.components.runner import CachedRunner
|
||||
from rdagent.core.conf import RD_AGENT_SETTINGS
|
||||
from rdagent.core.exception import ModelEmptyError
|
||||
from rdagent.core.utils import cache_with_pickle
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.scenarios.qlib.developer.utils import process_factor_data
|
||||
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
|
||||
from rdagent.scenarios.qlib.experiment.model_experiment import QlibModelExperiment
|
||||
|
||||
|
||||
@@ -19,6 +25,34 @@ class QlibModelRunner(CachedRunner[QlibModelExperiment]):
|
||||
|
||||
@cache_with_pickle(CachedRunner.get_cache_key, CachedRunner.assign_cached_result)
|
||||
def develop(self, exp: QlibModelExperiment) -> QlibModelExperiment:
|
||||
if exp.based_experiments and exp.based_experiments[-1].result is None:
|
||||
exp.based_experiments[-1] = self.develop(exp.based_experiments[-1])
|
||||
|
||||
exist_sota_factor_exp = False
|
||||
if exp.based_experiments:
|
||||
SOTA_factor = None
|
||||
# Filter and retain only QlibFactorExperiment instances
|
||||
sota_factor_experiments_list = [
|
||||
base_exp for base_exp in exp.based_experiments if isinstance(base_exp, QlibFactorExperiment)
|
||||
]
|
||||
if len(sota_factor_experiments_list) > 1:
|
||||
logger.info(f"SOTA factor processing ...")
|
||||
SOTA_factor = process_factor_data(sota_factor_experiments_list)
|
||||
|
||||
if SOTA_factor is not None and not SOTA_factor.empty:
|
||||
exist_sota_factor_exp = True
|
||||
combined_factors = SOTA_factor
|
||||
combined_factors = combined_factors.sort_index()
|
||||
combined_factors = combined_factors.loc[:, ~combined_factors.columns.duplicated(keep="last")]
|
||||
new_columns = pd.MultiIndex.from_product([["feature"], combined_factors.columns])
|
||||
combined_factors.columns = new_columns
|
||||
num_features = str(RD_AGENT_SETTINGS.initial_fator_library_size + len(combined_factors.columns))
|
||||
|
||||
target_path = exp.experiment_workspace.workspace_path / "combined_factors_df.parquet"
|
||||
|
||||
# Save the combined factors to the workspace
|
||||
combined_factors.to_parquet(target_path, engine="pyarrow")
|
||||
|
||||
if exp.sub_workspace_list[0].file_dict.get("model.py") is None:
|
||||
raise ModelEmptyError("model.py is empty")
|
||||
# to replace & inject code
|
||||
@@ -26,13 +60,45 @@ class QlibModelRunner(CachedRunner[QlibModelExperiment]):
|
||||
|
||||
env_to_use = {"PYTHONPATH": "./"}
|
||||
|
||||
if exp.sub_tasks[0].model_type == "TimeSeries":
|
||||
env_to_use.update({"dataset_cls": "TSDatasetH", "step_len": 20, "num_timesteps": 20})
|
||||
elif exp.sub_tasks[0].model_type == "Tabular":
|
||||
env_to_use.update({"dataset_cls": "DatasetH"})
|
||||
training_hyperparameters = exp.sub_tasks[0].training_hyperparameters
|
||||
if training_hyperparameters:
|
||||
env_to_use.update(
|
||||
{
|
||||
"n_epochs": str(training_hyperparameters.get("n_epochs", "100")),
|
||||
"lr": str(training_hyperparameters.get("lr", "1e-3")),
|
||||
"early_stop": str(training_hyperparameters.get("early_stop", 10)),
|
||||
"batch_size": str(training_hyperparameters.get("batch_size", 256)),
|
||||
"weight_decay": str(training_hyperparameters.get("weight_decay", 0.0001)),
|
||||
}
|
||||
)
|
||||
|
||||
result = exp.experiment_workspace.execute(qlib_config_name="conf.yaml", run_env=env_to_use)
|
||||
logger.info(f"start to run {exp.sub_tasks[0].name} model")
|
||||
if exp.sub_tasks[0].model_type == "TimeSeries":
|
||||
if exist_sota_factor_exp:
|
||||
env_to_use.update(
|
||||
{"dataset_cls": "TSDatasetH", "num_features": num_features, "step_len": 20, "num_timesteps": 20}
|
||||
)
|
||||
result, stdout = exp.experiment_workspace.execute(
|
||||
qlib_config_name="conf_model_combined.yaml", run_env=env_to_use
|
||||
)
|
||||
else:
|
||||
env_to_use.update({"dataset_cls": "TSDatasetH", "step_len": 20, "num_timesteps": 20})
|
||||
result, stdout = exp.experiment_workspace.execute(qlib_config_name="conf.yaml", run_env=env_to_use)
|
||||
elif exp.sub_tasks[0].model_type == "Tabular":
|
||||
if exist_sota_factor_exp:
|
||||
env_to_use.update({"dataset_cls": "DatasetH", "num_features": num_features})
|
||||
result, stdout = exp.experiment_workspace.execute(
|
||||
qlib_config_name="conf_model_combined.yaml", run_env=env_to_use
|
||||
)
|
||||
else:
|
||||
env_to_use.update({"dataset_cls": "DatasetH"})
|
||||
result, stdout = exp.experiment_workspace.execute(qlib_config_name="conf.yaml", run_env=env_to_use)
|
||||
|
||||
exp.result = result
|
||||
exp.stdout = stdout
|
||||
|
||||
if result is None:
|
||||
logger.error(f"Failed to run {exp.sub_tasks[0].name}, because {stdout}")
|
||||
raise ModelEmptyError(f"Failed to run {exp.sub_tasks[0].name} model, because {stdout}")
|
||||
|
||||
return exp
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
from typing import List
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERMultiFeedback
|
||||
from rdagent.core.conf import RD_AGENT_SETTINGS
|
||||
from rdagent.core.exception import FactorEmptyError
|
||||
from rdagent.core.utils import multiprocessing_wrapper
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
|
||||
|
||||
|
||||
def process_factor_data(exp_or_list: List[QlibFactorExperiment] | QlibFactorExperiment) -> pd.DataFrame:
|
||||
"""
|
||||
Process and combine factor data from experiment implementations.
|
||||
|
||||
Args:
|
||||
exp (ASpecificExp): The experiment containing factor data.
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: Combined factor data without NaN values.
|
||||
"""
|
||||
if isinstance(exp_or_list, QlibFactorExperiment):
|
||||
exp_or_list = [exp_or_list]
|
||||
factor_dfs = []
|
||||
|
||||
# Collect all exp's dataframes
|
||||
for exp in exp_or_list:
|
||||
if isinstance(exp, QlibFactorExperiment):
|
||||
if len(exp.sub_tasks) > 0:
|
||||
# if it has no sub_tasks, the experiment is results from template project.
|
||||
# otherwise, it is developed with designed task. So it should have feedback.
|
||||
assert isinstance(exp.prop_dev_feedback, CoSTEERMultiFeedback)
|
||||
# Iterate over sub-implementations and execute them to get each factor data
|
||||
message_and_df_list = multiprocessing_wrapper(
|
||||
[
|
||||
(implementation.execute, ("All",))
|
||||
for implementation, fb in zip(exp.sub_workspace_list, exp.prop_dev_feedback)
|
||||
if implementation and fb
|
||||
], # only execute successfully feedback
|
||||
n=RD_AGENT_SETTINGS.multi_proc_n,
|
||||
)
|
||||
error_message = ""
|
||||
for message, df in message_and_df_list:
|
||||
# Check if factor generation was successful
|
||||
if df is not None and "datetime" in df.index.names:
|
||||
time_diff = df.index.get_level_values("datetime").to_series().diff().dropna().unique()
|
||||
if pd.Timedelta(minutes=1) not in time_diff:
|
||||
factor_dfs.append(df)
|
||||
logger.info(
|
||||
f"Factor data from {exp.hypothesis.concise_justification} is successfully generated."
|
||||
)
|
||||
else:
|
||||
logger.warning(f"Factor data from {exp.hypothesis.concise_justification} is not generated.")
|
||||
else:
|
||||
error_message += f"Factor data from {exp.hypothesis.concise_justification} is not generated because of {message}"
|
||||
logger.warning(
|
||||
f"Factor data from {exp.hypothesis.concise_justification} is not generated because of {message}"
|
||||
)
|
||||
|
||||
# Combine all successful factor data
|
||||
if factor_dfs:
|
||||
return pd.concat(factor_dfs, axis=1)
|
||||
else:
|
||||
raise FactorEmptyError(
|
||||
f"No valid factor data found to merge (in process_factor_data) because of {error_message}."
|
||||
)
|
||||
@@ -13,7 +13,7 @@ RUN git clone https://github.com/microsoft/qlib.git
|
||||
|
||||
WORKDIR /workspace/qlib
|
||||
|
||||
RUN git fetch && git reset dbde1a109fb53e742a4dd210552ebf943576add9 --hard
|
||||
RUN git fetch && git reset 3e72593b8c985f01979bebcf646658002ac43b00 --hard
|
||||
|
||||
RUN python -m pip install --upgrade cython
|
||||
RUN python -m pip install -e .
|
||||
|
||||
@@ -7,31 +7,30 @@ from rdagent.components.coder.factor_coder.factor import (
|
||||
FactorTask,
|
||||
)
|
||||
from rdagent.core.experiment import Task
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.scenario import Scenario
|
||||
from rdagent.scenarios.qlib.experiment.utils import get_data_folder_intro
|
||||
from rdagent.scenarios.qlib.experiment.workspace import QlibFBWorkspace
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class QlibFactorExperiment(FactorExperiment[FactorTask, QlibFBWorkspace, FactorFBWorkspace]):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.experiment_workspace = QlibFBWorkspace(template_folder_path=Path(__file__).parent / "factor_template")
|
||||
self.stdout = ""
|
||||
|
||||
|
||||
class QlibFactorScenario(Scenario):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._background = deepcopy(prompt_dict["qlib_factor_background"])
|
||||
self._background = deepcopy(T(".prompts:qlib_factor_background").r())
|
||||
self._source_data = deepcopy(get_data_folder_intro())
|
||||
self._output_format = deepcopy(prompt_dict["qlib_factor_output_format"])
|
||||
self._interface = deepcopy(prompt_dict["qlib_factor_interface"])
|
||||
self._strategy = deepcopy(prompt_dict["qlib_factor_strategy"])
|
||||
self._simulator = deepcopy(prompt_dict["qlib_factor_simulator"])
|
||||
self._rich_style_description = deepcopy(prompt_dict["qlib_factor_rich_style_description"])
|
||||
self._experiment_setting = deepcopy(prompt_dict["qlib_factor_experiment_setting"])
|
||||
self._output_format = deepcopy(T(".prompts:qlib_factor_output_format").r())
|
||||
self._interface = deepcopy(T(".prompts:qlib_factor_interface").r())
|
||||
self._strategy = deepcopy(T(".prompts:qlib_factor_strategy").r())
|
||||
self._simulator = deepcopy(T(".prompts:qlib_factor_simulator").r())
|
||||
self._rich_style_description = deepcopy(T(".prompts:qlib_factor_rich_style_description").r())
|
||||
self._experiment_setting = deepcopy(T(".prompts:qlib_factor_experiment_setting").r())
|
||||
|
||||
@property
|
||||
def background(self) -> str:
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
from rdagent.components.coder.factor_coder.factor import (
|
||||
FactorExperiment,
|
||||
FactorFBWorkspace,
|
||||
FactorTask,
|
||||
)
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.scenario import Scenario
|
||||
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorScenario
|
||||
from rdagent.scenarios.qlib.experiment.workspace import QlibFBWorkspace
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class QlibFactorFromReportScenario(QlibFactorScenario):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._rich_style_description = deepcopy(prompt_dict["qlib_factor_from_report_rich_style_description"])
|
||||
self._rich_style_description = deepcopy(T(".prompts:qlib_factor_from_report_rich_style_description").r())
|
||||
|
||||
@property
|
||||
def rich_style_description(self) -> str:
|
||||
|
||||
@@ -11,6 +11,28 @@ data_handler_config: &data_handler_config
|
||||
fit_start_time: 2008-01-01
|
||||
fit_end_time: 2014-12-31
|
||||
instruments: *market
|
||||
infer_processors:
|
||||
- class: FilterCol
|
||||
kwargs:
|
||||
fields_group: feature
|
||||
col_list: ["RESI5", "WVMA5", "RSQR5", "KLEN", "RSQR10", "CORR5", "CORD5", "CORR10",
|
||||
"ROC60", "RESI10", "VSTD5", "RSQR60", "CORR60", "WVMA60", "STD5",
|
||||
"RSQR20", "CORD60", "CORD10", "CORR20", "KLOW"
|
||||
]
|
||||
- class: RobustZScoreNorm
|
||||
kwargs:
|
||||
fields_group: feature
|
||||
clip_outlier: true
|
||||
- class: Fillna
|
||||
kwargs:
|
||||
fields_group: feature
|
||||
learn_processors:
|
||||
- class: DropnaLabel
|
||||
- class: CSRankNorm
|
||||
kwargs:
|
||||
fields_group: label
|
||||
label: ["Ref($close, -2) / Ref($close, -1) - 1"]
|
||||
|
||||
port_analysis_config: &port_analysis_config
|
||||
strategy:
|
||||
class: TopkDropoutStrategy
|
||||
|
||||
@@ -19,6 +19,18 @@ data_handler_config: &data_handler_config
|
||||
label:
|
||||
- ["Ref($close, -2)/Ref($close, -1) - 1"]
|
||||
- ["LABEL0"]
|
||||
feature:
|
||||
- ["Resi($close, 5)/$close", "Std(Abs($close/Ref($close, 1)-1)*$volume, 5)/(Mean(Abs($close/Ref($close, 1)-1)*$volume, 5)+1e-12)",
|
||||
"Rsquare($close, 5)", "($high-$low)/$open", "Rsquare($close, 10)", "Corr($close, Log($volume+1), 5)",
|
||||
"Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 5)", "Corr($close, Log($volume+1), 10)",
|
||||
"Ref($close, 60)/$close", "Resi($close, 10)/$close", "Std($volume, 5)/($volume+1e-12)",
|
||||
"Rsquare($close, 60)", "Corr($close, Log($volume+1), 60)", "Std(Abs($close/Ref($close, 1)-1)*$volume, 60)/(Mean(Abs($close/Ref($close, 1)-1)*$volume, 60)+1e-12)",
|
||||
"Std($close, 5)/$close", "Rsquare($close, 20)", "Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 60)",
|
||||
"Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 10)", "Corr($close, Log($volume+1), 20)",
|
||||
"(Less($open, $close)-$low)/$open"]
|
||||
- ["RESI5", "WVMA5", "RSQR5", "KLEN", "RSQR10", "CORR5", "CORD5", "CORR10",
|
||||
"ROC60", "RESI10", "VSTD5", "RSQR60", "CORR60", "WVMA60", "STD5",
|
||||
"RSQR20", "CORD60", "CORD10", "CORR20", "KLOW"]
|
||||
- class: qlib.data.dataset.loader.StaticDataLoader
|
||||
kwargs:
|
||||
config: "combined_factors_df.parquet"
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
qlib_init:
|
||||
provider_uri: "~/.qlib/qlib_data/cn_data"
|
||||
region: cn
|
||||
|
||||
market: &market csi300
|
||||
benchmark: &benchmark SH000300
|
||||
|
||||
data_handler_config: &data_handler_config
|
||||
start_time: 2008-01-01
|
||||
end_time: 2022-08-01
|
||||
instruments: *market
|
||||
data_loader:
|
||||
class: NestedDataLoader
|
||||
kwargs:
|
||||
dataloader_l:
|
||||
- class: qlib.contrib.data.loader.Alpha158DL
|
||||
kwargs:
|
||||
config:
|
||||
label:
|
||||
- ["Ref($close, -2)/Ref($close, -1) - 1"]
|
||||
- ["LABEL0"]
|
||||
feature:
|
||||
- ["Resi($close, 5)/$close", "Std(Abs($close/Ref($close, 1)-1)*$volume, 5)/(Mean(Abs($close/Ref($close, 1)-1)*$volume, 5)+1e-12)",
|
||||
"Rsquare($close, 5)", "($high-$low)/$open", "Rsquare($close, 10)", "Corr($close, Log($volume+1), 5)",
|
||||
"Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 5)", "Corr($close, Log($volume+1), 10)",
|
||||
"Ref($close, 60)/$close", "Resi($close, 10)/$close", "Std($volume, 5)/($volume+1e-12)",
|
||||
"Rsquare($close, 60)", "Corr($close, Log($volume+1), 60)", "Std(Abs($close/Ref($close, 1)-1)*$volume, 60)/(Mean(Abs($close/Ref($close, 1)-1)*$volume, 60)+1e-12)",
|
||||
"Std($close, 5)/$close", "Rsquare($close, 20)", "Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 60)",
|
||||
"Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 10)", "Corr($close, Log($volume+1), 20)",
|
||||
"(Less($open, $close)-$low)/$open"]
|
||||
- ["RESI5", "WVMA5", "RSQR5", "KLEN", "RSQR10", "CORR5", "CORD5", "CORR10",
|
||||
"ROC60", "RESI10", "VSTD5", "RSQR60", "CORR60", "WVMA60", "STD5",
|
||||
"RSQR20", "CORD60", "CORD10", "CORR20", "KLOW"]
|
||||
- class: qlib.data.dataset.loader.StaticDataLoader
|
||||
kwargs:
|
||||
config: "combined_factors_df.parquet"
|
||||
|
||||
infer_processors:
|
||||
- class: RobustZScoreNorm
|
||||
kwargs:
|
||||
fields_group: feature
|
||||
clip_outlier: true
|
||||
fit_start_time: 2008-01-01
|
||||
fit_end_time: 2014-12-31
|
||||
- class: Fillna
|
||||
kwargs:
|
||||
fields_group: feature
|
||||
learn_processors:
|
||||
- class: DropnaLabel
|
||||
- class: CSZScoreNorm
|
||||
kwargs:
|
||||
fields_group: label
|
||||
|
||||
port_analysis_config: &port_analysis_config
|
||||
strategy:
|
||||
class: TopkDropoutStrategy
|
||||
module_path: qlib.contrib.strategy
|
||||
kwargs:
|
||||
signal: <PRED>
|
||||
topk: 50
|
||||
n_drop: 5
|
||||
backtest:
|
||||
start_time: 2017-01-01
|
||||
end_time: 2020-08-01
|
||||
account: 100000000
|
||||
benchmark: *benchmark
|
||||
exchange_kwargs:
|
||||
limit_threshold: 0.095
|
||||
deal_price: close
|
||||
open_cost: 0.0005
|
||||
close_cost: 0.0015
|
||||
min_cost: 5
|
||||
|
||||
task:
|
||||
model:
|
||||
class: GeneralPTNN
|
||||
module_path: qlib.contrib.model.pytorch_general_nn
|
||||
kwargs:
|
||||
n_epochs: {{ n_epochs }}
|
||||
lr: {{ lr }}
|
||||
early_stop: {{ early_stop }}
|
||||
batch_size: {{ batch_size }}
|
||||
weight_decay: {{ weight_decay }}
|
||||
metric: loss
|
||||
loss: mse
|
||||
n_jobs: 20
|
||||
GPU: 0
|
||||
pt_model_uri: "model.model_cls"
|
||||
pt_model_kwargs: {
|
||||
"num_features": {{ num_features }}{% if num_timesteps %}, "num_timesteps": {{ num_timesteps }}{% endif %}
|
||||
}
|
||||
dataset:
|
||||
class: {{ dataset_cls | default("DatasetH") }}
|
||||
module_path: qlib.data.dataset
|
||||
kwargs:
|
||||
handler:
|
||||
class: DataHandlerLP
|
||||
module_path: qlib.contrib.data.handler
|
||||
kwargs: *data_handler_config
|
||||
segments:
|
||||
train: [2008-01-01, 2014-12-31]
|
||||
valid: [2015-01-01, 2016-12-31]
|
||||
test: [2017-01-01, 2020-08-01]
|
||||
{% if step_len %}step_len: {{ step_len }}{% endif %}
|
||||
record:
|
||||
- class: SignalRecord
|
||||
module_path: qlib.workflow.record_temp
|
||||
kwargs:
|
||||
model: <MODEL>
|
||||
dataset: <DATASET>
|
||||
- class: SigAnaRecord
|
||||
module_path: qlib.workflow.record_temp
|
||||
kwargs:
|
||||
ana_long_short: False
|
||||
ann_scaler: 252
|
||||
- class: PortAnaRecord
|
||||
module_path: qlib.workflow.record_temp
|
||||
kwargs:
|
||||
config: *port_analysis_config
|
||||
@@ -7,28 +7,27 @@ from rdagent.components.coder.model_coder.model import (
|
||||
ModelTask,
|
||||
)
|
||||
from rdagent.core.experiment import Task
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.scenario import Scenario
|
||||
from rdagent.scenarios.qlib.experiment.workspace import QlibFBWorkspace
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class QlibModelExperiment(ModelExperiment[ModelTask, QlibFBWorkspace, ModelFBWorkspace]):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.experiment_workspace = QlibFBWorkspace(template_folder_path=Path(__file__).parent / "model_template")
|
||||
self.stdout = ""
|
||||
|
||||
|
||||
class QlibModelScenario(Scenario):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._background = deepcopy(prompt_dict["qlib_model_background"])
|
||||
self._output_format = deepcopy(prompt_dict["qlib_model_output_format"])
|
||||
self._interface = deepcopy(prompt_dict["qlib_model_interface"])
|
||||
self._simulator = deepcopy(prompt_dict["qlib_model_simulator"])
|
||||
self._rich_style_description = deepcopy(prompt_dict["qlib_model_rich_style_description"])
|
||||
self._experiment_setting = deepcopy(prompt_dict["qlib_model_experiment_setting"])
|
||||
self._background = deepcopy(T(".prompts:qlib_model_background").r())
|
||||
self._output_format = deepcopy(T(".prompts:qlib_model_output_format").r())
|
||||
self._interface = deepcopy(T(".prompts:qlib_model_interface").r())
|
||||
self._simulator = deepcopy(T(".prompts:qlib_model_simulator").r())
|
||||
self._rich_style_description = deepcopy(T(".prompts:qlib_model_rich_style_description").r())
|
||||
self._experiment_setting = deepcopy(T(".prompts:qlib_model_experiment_setting").r())
|
||||
|
||||
@property
|
||||
def background(self) -> str:
|
||||
|
||||
@@ -55,30 +55,20 @@ task:
|
||||
class: GeneralPTNN
|
||||
module_path: qlib.contrib.model.pytorch_general_nn
|
||||
kwargs:
|
||||
n_epochs: 100
|
||||
lr: 1e-3
|
||||
early_stop: 10
|
||||
batch_size: 2000
|
||||
n_epochs: {{ n_epochs }}
|
||||
lr: {{ lr }}
|
||||
early_stop: {{ early_stop }}
|
||||
batch_size: {{ batch_size }}
|
||||
weight_decay: {{ weight_decay }}
|
||||
metric: loss
|
||||
loss: mse
|
||||
n_jobs: 20
|
||||
GPU: 0
|
||||
# loss: mse
|
||||
# lr: 0.002
|
||||
# optimizer: adam
|
||||
# batch_size: 8192
|
||||
# GPU: 0
|
||||
weight_decay: 0.0001
|
||||
# pt_model_uri: "qlib.contrib.model.pytorch_nn.Net"
|
||||
# pt_model_uri: "env_tpl.model.Net"
|
||||
# pt_model_uri: "./model.py:Net"
|
||||
pt_model_uri: "model.model_cls"
|
||||
pt_model_kwargs: {
|
||||
"num_features": 20,
|
||||
{% if num_timesteps %}num_timesteps: {{ num_timesteps }}{% endif %}
|
||||
}
|
||||
# input_dim: 20
|
||||
# How should I use jinja to put step len here conditionally
|
||||
}
|
||||
dataset:
|
||||
class: {{ dataset_cls | default("DatasetH") }}
|
||||
module_path: qlib.data.dataset
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
qlib_init:
|
||||
provider_uri: "~/.qlib/qlib_data/cn_data"
|
||||
region: cn
|
||||
|
||||
market: &market csi300
|
||||
benchmark: &benchmark SH000300
|
||||
|
||||
data_handler_config: &data_handler_config
|
||||
start_time: 2008-01-01
|
||||
end_time: 2022-08-01
|
||||
instruments: *market
|
||||
data_loader:
|
||||
class: NestedDataLoader
|
||||
kwargs:
|
||||
dataloader_l:
|
||||
- class: qlib.contrib.data.loader.Alpha158DL
|
||||
kwargs:
|
||||
config:
|
||||
label:
|
||||
- ["Ref($close, -2)/Ref($close, -1) - 1"]
|
||||
- ["LABEL0"]
|
||||
feature:
|
||||
- ["Resi($close, 5)/$close", "Std(Abs($close/Ref($close, 1)-1)*$volume, 5)/(Mean(Abs($close/Ref($close, 1)-1)*$volume, 5)+1e-12)",
|
||||
"Rsquare($close, 5)", "($high-$low)/$open", "Rsquare($close, 10)", "Corr($close, Log($volume+1), 5)",
|
||||
"Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 5)", "Corr($close, Log($volume+1), 10)",
|
||||
"Ref($close, 60)/$close", "Resi($close, 10)/$close", "Std($volume, 5)/($volume+1e-12)",
|
||||
"Rsquare($close, 60)", "Corr($close, Log($volume+1), 60)", "Std(Abs($close/Ref($close, 1)-1)*$volume, 60)/(Mean(Abs($close/Ref($close, 1)-1)*$volume, 60)+1e-12)",
|
||||
"Std($close, 5)/$close", "Rsquare($close, 20)", "Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 60)",
|
||||
"Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 10)", "Corr($close, Log($volume+1), 20)",
|
||||
"(Less($open, $close)-$low)/$open"]
|
||||
- ["RESI5", "WVMA5", "RSQR5", "KLEN", "RSQR10", "CORR5", "CORD5", "CORR10",
|
||||
"ROC60", "RESI10", "VSTD5", "RSQR60", "CORR60", "WVMA60", "STD5",
|
||||
"RSQR20", "CORD60", "CORD10", "CORR20", "KLOW"]
|
||||
- class: qlib.data.dataset.loader.StaticDataLoader
|
||||
kwargs:
|
||||
config: "combined_factors_df.parquet"
|
||||
|
||||
infer_processors:
|
||||
- class: RobustZScoreNorm
|
||||
kwargs:
|
||||
fields_group: feature
|
||||
clip_outlier: true
|
||||
fit_start_time: 2008-01-01
|
||||
fit_end_time: 2014-12-31
|
||||
- class: Fillna
|
||||
kwargs:
|
||||
fields_group: feature
|
||||
learn_processors:
|
||||
- class: DropnaLabel
|
||||
- class: CSZScoreNorm
|
||||
kwargs:
|
||||
fields_group: label
|
||||
|
||||
port_analysis_config: &port_analysis_config
|
||||
strategy:
|
||||
class: TopkDropoutStrategy
|
||||
module_path: qlib.contrib.strategy
|
||||
kwargs:
|
||||
signal: <PRED>
|
||||
topk: 50
|
||||
n_drop: 5
|
||||
backtest:
|
||||
start_time: 2017-01-01
|
||||
end_time: 2020-08-01
|
||||
account: 100000000
|
||||
benchmark: *benchmark
|
||||
exchange_kwargs:
|
||||
limit_threshold: 0.095
|
||||
deal_price: close
|
||||
open_cost: 0.0005
|
||||
close_cost: 0.0015
|
||||
min_cost: 5
|
||||
|
||||
task:
|
||||
model:
|
||||
class: GeneralPTNN
|
||||
module_path: qlib.contrib.model.pytorch_general_nn
|
||||
kwargs:
|
||||
n_epochs: {{ n_epochs }}
|
||||
lr: {{ lr }}
|
||||
early_stop: {{ early_stop }}
|
||||
batch_size: {{ batch_size }}
|
||||
weight_decay: {{ weight_decay }}
|
||||
metric: loss
|
||||
loss: mse
|
||||
n_jobs: 20
|
||||
GPU: 0
|
||||
pt_model_uri: "model.model_cls"
|
||||
pt_model_kwargs: {
|
||||
"num_features": {{ num_features }}{% if num_timesteps %}, "num_timesteps": {{ num_timesteps }}{% endif %}
|
||||
}
|
||||
dataset:
|
||||
class: {{ dataset_cls | default("DatasetH") }}
|
||||
module_path: qlib.data.dataset
|
||||
kwargs:
|
||||
handler:
|
||||
class: DataHandlerLP
|
||||
module_path: qlib.contrib.data.handler
|
||||
kwargs: *data_handler_config
|
||||
segments:
|
||||
train: [2008-01-01, 2014-12-31]
|
||||
valid: [2015-01-01, 2016-12-31]
|
||||
test: [2017-01-01, 2020-08-01]
|
||||
{% if step_len %}step_len: {{ step_len }}{% endif %}
|
||||
record:
|
||||
- class: SignalRecord
|
||||
module_path: qlib.workflow.record_temp
|
||||
kwargs:
|
||||
model: <MODEL>
|
||||
dataset: <DATASET>
|
||||
- class: SigAnaRecord
|
||||
module_path: qlib.workflow.record_temp
|
||||
kwargs:
|
||||
ana_long_short: False
|
||||
ann_scaler: 252
|
||||
- class: PortAnaRecord
|
||||
module_path: qlib.workflow.record_temp
|
||||
kwargs:
|
||||
config: *port_analysis_config
|
||||
@@ -1,3 +1,9 @@
|
||||
qlib_quant_background: |-
|
||||
Quantitative investment is a data-driven approach to asset management that relies on mathematical models, statistical techniques, and computational methods to analyze financial markets and make investment decisions. Two essential components of this approach are factors and models.
|
||||
|
||||
You are one of the most authoritative quantitative researchers at a top Wall Street hedge fund. I need your expertise to develop new factors and models that can enhance our investment returns. Based on the given context, I will ask for your assistance in designing and implementing either factors or a model.
|
||||
|
||||
|
||||
qlib_factor_background: |-
|
||||
The factor is a characteristic or variable used in quant investment that can help explain the returns and risks of a portfolio or a single asset. Factors are used by investors to identify and exploit sources of excess returns, and they are central to many quantitative investment strategies.
|
||||
Each number in the factor represents a physics value to an instrument on a day.
|
||||
@@ -154,8 +160,9 @@ qlib_model_background: |-
|
||||
1. Name: The name of the model.
|
||||
2. Description: The description of the model.
|
||||
3. Architecture: The detailed architecture of the model, such as neural network layers or tree structures.
|
||||
4. Hyperparameters: The hyperparameters used in the model, such as learning rate, number of epochs, etc.
|
||||
5. ModelType: The type of the model, "Tabular" for tabular model and "TimeSeries" for time series model.
|
||||
4. Hyperparameters: The hyperparameters used in the model.
|
||||
5. Training_hyperparameters: The hyperparameters used during the training process.
|
||||
6. ModelType: The type of the model, "Tabular" for tabular model and "TimeSeries" for time series model.
|
||||
The model should provide clear and detailed documentation of its architecture and hyperparameters. One model should statically define one output with a fixed architecture and hyperparameters.
|
||||
|
||||
qlib_model_interface: |-
|
||||
@@ -176,9 +183,8 @@ qlib_model_interface: |-
|
||||
model_cls = XXXModel
|
||||
```
|
||||
|
||||
The model has two types, "Tabular" for tabular model and "TimeSeries" for time series model. The input shape to a tabular model is (batch_size, num_features) and the input shape to a time series model is (batch_size, num_timesteps, num_features). The output shape of the model should be (batch_size, 1).
|
||||
The "batch_size" is a dynamic value which is determined by the input of forward function.
|
||||
The "num_features" and "num_timesteps" are static which will be provided to the model through init function.
|
||||
The model can be configured as either "Tabular" for tabular models or "TimeSeries" for time series models. For a tabular model, the input shape is (batch_size, num_features), while for a time series model, the input shape is (batch_size, num_timesteps, num_features). In both cases, the output shape of the model should be (batch_size, 1).
|
||||
`num_features` will be directly set for the model based on the input data shape.
|
||||
User will initialize the tabular model with the following code:
|
||||
```python
|
||||
model = model_cls(num_features=num_features)
|
||||
@@ -206,7 +212,7 @@ qlib_model_simulator: |-
|
||||
1. Generate a baseline factor table.
|
||||
2. Train the model defined in your class Net to predict the next several days' returns based on the factor values.
|
||||
3. Build a portfolio based on the predicted returns using a specific strategy.
|
||||
4. Evaluate the portfolio's performance, including metrics such as return, Sharpe ratio, max drawdown, and others.
|
||||
4. Evaluate the portfolio's performance, including metrics such as return, IC, max drawdown, and others.
|
||||
5. Iterate on growing the hypothesis to enable model improvements based on performance evaluations and feedback.
|
||||
|
||||
qlib_model_rich_style_description: |-
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
# Factor
|
||||
from rdagent.components.coder.factor_coder.factor import (
|
||||
FactorExperiment,
|
||||
FactorFBWorkspace,
|
||||
FactorTask,
|
||||
)
|
||||
|
||||
# Model
|
||||
from rdagent.components.coder.model_coder.model import (
|
||||
ModelExperiment,
|
||||
ModelFBWorkspace,
|
||||
ModelTask,
|
||||
)
|
||||
from rdagent.core.experiment import Task
|
||||
from rdagent.core.scenario import Scenario
|
||||
from rdagent.scenarios.qlib.experiment.utils import get_data_folder_intro
|
||||
from rdagent.scenarios.qlib.experiment.workspace import QlibFBWorkspace
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class QlibFactorExperiment(FactorExperiment[FactorTask, QlibFBWorkspace, FactorFBWorkspace]):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.experiment_workspace = QlibFBWorkspace(template_folder_path=Path(__file__).parent / "factor_template")
|
||||
|
||||
|
||||
class QlibModelExperiment(ModelExperiment[ModelTask, QlibFBWorkspace, ModelFBWorkspace]):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.experiment_workspace = QlibFBWorkspace(template_folder_path=Path(__file__).parent / "model_template")
|
||||
|
||||
|
||||
class QlibQuantScenario(Scenario):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._source_data = deepcopy(get_data_folder_intro())
|
||||
|
||||
self._rich_style_description = deepcopy(T(".prompts:qlib_factor_rich_style_description").r())
|
||||
self._experiment_setting = deepcopy(T(".prompts:qlib_factor_experiment_setting").r())
|
||||
|
||||
def background(self, tag=None) -> str:
|
||||
assert tag in [None, "factor", "model"]
|
||||
quant_background = "The background of the scenario is as follows:\n" + T(".prompts:qlib_quant_background").r()
|
||||
factor_background = (
|
||||
"This time, I need your help with the research and development of the factor. The background of the factor scenario is as follows:\n"
|
||||
+ T(".prompts:qlib_factor_background").r()
|
||||
)
|
||||
model_background = (
|
||||
"This time, I need your help with the research and development of the model. The background of the model scenario is as follows:\n"
|
||||
+ T(".prompts:qlib_model_background").r()
|
||||
)
|
||||
|
||||
# TODO: There are some issues here
|
||||
if tag is None:
|
||||
return quant_background + "\n" + factor_background + "\n" + model_background
|
||||
elif tag == "factor":
|
||||
return factor_background
|
||||
else:
|
||||
return model_background
|
||||
|
||||
def get_source_data_desc(self) -> str:
|
||||
return self._source_data
|
||||
|
||||
def output_format(self, tag=None) -> str:
|
||||
assert tag in [None, "factor", "model"]
|
||||
factor_output_format = (
|
||||
"The factor code should output the following format:\n" + T(".prompts:qlib_factor_output_format").r()
|
||||
)
|
||||
model_output_format = (
|
||||
"The model code should output the following format:\n" + T(".prompts:qlib_model_output_format").r()
|
||||
)
|
||||
|
||||
if tag is None:
|
||||
return factor_output_format + "\n" + model_output_format
|
||||
elif tag == "factor":
|
||||
return factor_output_format
|
||||
else:
|
||||
return model_output_format
|
||||
|
||||
def interface(self, tag=None) -> str:
|
||||
assert tag in [None, "factor", "model"]
|
||||
factor_interface = (
|
||||
"The factor code should be written in the following interface:\n" + T(".prompts:qlib_factor_interface").r()
|
||||
)
|
||||
model_interface = (
|
||||
"The model code should be written in the following interface:\n" + T(".prompts:qlib_model_interface").r()
|
||||
)
|
||||
|
||||
if tag is None:
|
||||
return factor_interface + "\n" + model_interface
|
||||
elif tag == "factor":
|
||||
return factor_interface
|
||||
else:
|
||||
return model_interface
|
||||
|
||||
def simulator(self, tag=None) -> str:
|
||||
assert tag in [None, "factor", "model"]
|
||||
factor_simulator = "The factor code will be sent to the simulator:\n" + T(".prompts:qlib_factor_simulator").r()
|
||||
model_simulator = "The model code will be sent to the simulator:\n" + T(".prompts:qlib_model_simulator").r()
|
||||
|
||||
if tag is None:
|
||||
return factor_simulator + "\n" + model_simulator
|
||||
elif tag == "factor":
|
||||
return factor_simulator
|
||||
else:
|
||||
return model_simulator
|
||||
|
||||
@property
|
||||
def rich_style_description(self) -> str:
|
||||
return self._rich_style_description
|
||||
|
||||
@property
|
||||
def experiment_setting(self) -> str:
|
||||
return self._experiment_setting
|
||||
|
||||
def get_scenario_all_desc(
|
||||
self,
|
||||
task: Task | None = None,
|
||||
filtered_tag: str | None = None,
|
||||
simple_background: bool | None = None,
|
||||
action: str | None = None,
|
||||
) -> str:
|
||||
def common_description(action: str | None = None) -> str:
|
||||
return f"""\n------Background of the scenario------
|
||||
{self.background(action)}
|
||||
------The source dataset you can use------
|
||||
{self.get_source_data_desc()}
|
||||
"""
|
||||
|
||||
# TODO: There are still some issues with handling source_data here
|
||||
def source_data() -> str:
|
||||
return f"""
|
||||
------The source data you can use------
|
||||
{self.get_source_data_desc()}
|
||||
"""
|
||||
|
||||
def interface(tag: str | None) -> str:
|
||||
return f"""
|
||||
------The interface you should follow to write the runnable code------
|
||||
{self.interface(tag)}
|
||||
"""
|
||||
|
||||
def output(tag: str | None) -> str:
|
||||
return f"""
|
||||
------The output of your code should be in the format------
|
||||
{self.output_format(tag)}
|
||||
"""
|
||||
|
||||
def simulator(tag: str | None) -> str:
|
||||
return f"""
|
||||
------The simulator user can use to test your solution------
|
||||
{self.simulator(tag)}
|
||||
"""
|
||||
|
||||
if simple_background:
|
||||
return common_description()
|
||||
elif filtered_tag == "hypothesis_and_experiment" or filtered_tag == "feedback":
|
||||
return common_description() + simulator(None)
|
||||
elif filtered_tag == "factor" or filtered_tag == "feature" or filtered_tag == "factors":
|
||||
return common_description("factor") + interface("factor") + output("factor") + simulator("factor")
|
||||
elif filtered_tag == "model" or filtered_tag == "model tuning":
|
||||
return common_description("model") + interface("model") + output("model") + simulator("model")
|
||||
elif action == "factor" or action == "model":
|
||||
return common_description(action) + interface(action) + output(action) + simulator(action)
|
||||
@@ -1,11 +1,9 @@
|
||||
import io
|
||||
import random
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
# render it with jinja
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.factor_coder.config import FACTOR_COSTEER_SETTINGS
|
||||
@@ -69,53 +67,77 @@ def get_file_desc(p: Path, variable_list=[]) -> str:
|
||||
|
||||
JJ_TPL = Environment(undefined=StrictUndefined).from_string(
|
||||
"""
|
||||
{{file_name}}
|
||||
```{{type_desc}}
|
||||
# {{file_name}}
|
||||
|
||||
## File Type
|
||||
{{type_desc}}
|
||||
|
||||
## Content Overview
|
||||
{{content}}
|
||||
```
|
||||
"""
|
||||
)
|
||||
|
||||
if p.name.endswith(".h5"):
|
||||
df = pd.read_hdf(p)
|
||||
# get df.head() as string with full width
|
||||
pd.set_option("display.max_columns", None) # or 1000
|
||||
pd.set_option("display.max_rows", None) # or 1000
|
||||
pd.set_option("display.max_colwidth", None) # or 199
|
||||
pd.set_option("display.max_columns", None)
|
||||
pd.set_option("display.max_rows", None)
|
||||
pd.set_option("display.max_colwidth", None)
|
||||
|
||||
if isinstance(df.index, pd.MultiIndex):
|
||||
df_info = f"MultiIndex names:, {df.index.names})\n"
|
||||
else:
|
||||
df_info = f"Index name: {df.index.name}\n"
|
||||
df_info = "### Data Structure\n"
|
||||
df_info += (
|
||||
f"- Index: MultiIndex with levels {df.index.names}\n"
|
||||
if isinstance(df.index, pd.MultiIndex)
|
||||
else f"- Index: {df.index.name}\n"
|
||||
)
|
||||
|
||||
df_info += "\n### Columns\n"
|
||||
columns = df.dtypes.to_dict()
|
||||
filtered_columns = [f"{i, j}" for i, j in columns.items() if i in variable_list]
|
||||
if filtered_columns:
|
||||
df_info += "Related Data columns: \n"
|
||||
df_info += ",".join(filtered_columns)
|
||||
grouped_columns = {}
|
||||
|
||||
for col in columns:
|
||||
if col.startswith("$"):
|
||||
prefix = col.split("_")[0] if "_" in col else col
|
||||
grouped_columns.setdefault(prefix, []).append(col)
|
||||
else:
|
||||
grouped_columns.setdefault("other", []).append(col)
|
||||
|
||||
if variable_list:
|
||||
df_info += "#### Relevant Columns:\n"
|
||||
relevant_line = ", ".join(f"{col}: {columns[col]}" for col in variable_list if col in columns)
|
||||
df_info += relevant_line + "\n"
|
||||
else:
|
||||
df_info += "Data columns: \n"
|
||||
df_info += ",".join(columns)
|
||||
df_info += "\n"
|
||||
df_info += "#### All Columns:\n"
|
||||
grouped_items = list(grouped_columns.items())
|
||||
random.shuffle(grouped_items)
|
||||
for prefix, cols in grouped_items:
|
||||
header = "Other Columns" if prefix == "other" else f"{prefix} Related Columns"
|
||||
df_info += f"\n#### {header}:\n"
|
||||
random.shuffle(cols)
|
||||
line = ", ".join(f"{col}: {columns[col]}" for col in cols)
|
||||
df_info += line + "\n"
|
||||
|
||||
if "REPORT_PERIOD" in df.columns:
|
||||
one_instrument = df.index.get_level_values("instrument")[0]
|
||||
df_on_one_instrument = df.loc[pd.IndexSlice[:, one_instrument], ["REPORT_PERIOD"]]
|
||||
df_info += f"""
|
||||
A snapshot of one instrument, from which you can tell the distribution of the data:
|
||||
{df_on_one_instrument.head(5)}
|
||||
"""
|
||||
df_info += "\n### Sample Data\n"
|
||||
df_info += f"Showing data for instrument {one_instrument}:\n"
|
||||
df_info += str(df_on_one_instrument.head(5))
|
||||
|
||||
return JJ_TPL.render(
|
||||
file_name=p.name,
|
||||
type_desc="h5 info",
|
||||
type_desc="HDF5 Data File",
|
||||
content=df_info,
|
||||
)
|
||||
|
||||
elif p.name.endswith(".md"):
|
||||
with open(p) as f:
|
||||
content = f.read()
|
||||
return JJ_TPL.render(
|
||||
file_name=p.name,
|
||||
type_desc="markdown",
|
||||
type_desc="Markdown Documentation",
|
||||
content=content,
|
||||
)
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"file type {p.name} is not supported. Please implement its description function.",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from rdagent.components.coder.model_coder.conf import MODEL_COSTEER_SETTINGS
|
||||
from rdagent.core.experiment import FBWorkspace
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.utils.env import QTDockerEnv
|
||||
from rdagent.utils.env import QlibCondaConf, QlibCondaEnv, QTDockerEnv
|
||||
|
||||
|
||||
class QlibFBWorkspace(FBWorkspace):
|
||||
@@ -14,29 +16,45 @@ class QlibFBWorkspace(FBWorkspace):
|
||||
self.inject_code_from_folder(template_folder_path)
|
||||
|
||||
def execute(self, qlib_config_name: str = "conf.yaml", run_env: dict = {}, *args, **kwargs) -> str:
|
||||
qtde = QTDockerEnv()
|
||||
if MODEL_COSTEER_SETTINGS.env_type == "docker":
|
||||
qtde = QTDockerEnv()
|
||||
elif MODEL_COSTEER_SETTINGS.env_type == "conda":
|
||||
qtde = QlibCondaEnv(conf=QlibCondaConf())
|
||||
else:
|
||||
logger.error(f"Unknown env_type: {MODEL_COSTEER_SETTINGS.env_type}")
|
||||
return None, "Unknown environment type"
|
||||
qtde.prepare()
|
||||
|
||||
# Run the Qlib backtest
|
||||
execute_log = qtde.run(
|
||||
execute_qlib_log = qtde.run(
|
||||
local_path=str(self.workspace_path),
|
||||
entry=f"qrun {qlib_config_name}",
|
||||
env=run_env,
|
||||
)
|
||||
logger.log_object(execute_qlib_log, tag="Qlib_execute_log")
|
||||
|
||||
# TODO: We should handle the case when Docker times out.
|
||||
execute_log = qtde.run(
|
||||
local_path=str(self.workspace_path),
|
||||
entry="python read_exp_res.py",
|
||||
env=run_env,
|
||||
)
|
||||
|
||||
ret_df = pd.read_pickle(self.workspace_path / "ret.pkl")
|
||||
logger.log_object(ret_df, tag="Quantitative Backtesting Chart")
|
||||
pattern = r"(Epoch\d+: train -[0-9\.]+, valid -[0-9\.]+|best score: -[0-9\.]+ @ \d+ epoch)"
|
||||
matches = re.findall(pattern, execute_qlib_log)
|
||||
execute_qlib_log = "\n".join(matches)
|
||||
|
||||
csv_path = self.workspace_path / "qlib_res.csv"
|
||||
quantitative_backtesting_chart_path = self.workspace_path / "ret.pkl"
|
||||
if quantitative_backtesting_chart_path.exists():
|
||||
ret_df = pd.read_pickle(quantitative_backtesting_chart_path)
|
||||
logger.log_object(ret_df, tag="Quantitative Backtesting Chart")
|
||||
else:
|
||||
logger.error("No result file found.")
|
||||
return None, execute_qlib_log
|
||||
|
||||
if not csv_path.exists():
|
||||
logger.error(f"File {csv_path} does not exist.")
|
||||
return None
|
||||
|
||||
return pd.read_csv(csv_path, index_col=0).iloc[:, 0]
|
||||
qlib_res_path = self.workspace_path / "qlib_res.csv"
|
||||
if qlib_res_path.exists():
|
||||
return pd.read_csv(qlib_res_path, index_col=0).iloc[:, 0], execute_qlib_log
|
||||
else:
|
||||
logger.error(f"File {qlib_res_path} does not exist.")
|
||||
return None, execute_qlib_log
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import multiprocessing as mp
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Mapping
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
from sklearn.cluster import KMeans
|
||||
from sklearn.metrics.pairwise import cosine_similarity
|
||||
from sklearn.preprocessing import normalize
|
||||
@@ -19,7 +15,6 @@ from rdagent.components.document_reader.document_reader import (
|
||||
)
|
||||
from rdagent.components.loader.experiment_loader import FactorExperimentLoader
|
||||
from rdagent.core.conf import RD_AGENT_SETTINGS
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.utils import multiprocessing_wrapper
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.oai.llm_conf import LLM_SETTINGS
|
||||
@@ -27,8 +22,7 @@ from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.scenarios.qlib.factor_experiment_loader.json_loader import (
|
||||
FactorExperimentLoaderFromDict,
|
||||
)
|
||||
|
||||
document_process_prompts = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
def classify_report_from_dict(
|
||||
@@ -62,7 +56,7 @@ def classify_report_from_dict(
|
||||
# )
|
||||
|
||||
res_dict = {}
|
||||
classify_prompt = document_process_prompts["classify_system"]
|
||||
classify_prompt = T(".prompts:classify_system").r()
|
||||
|
||||
for key, value in tqdm(report_dict.items()):
|
||||
if not key.endswith(".pdf"):
|
||||
@@ -121,7 +115,7 @@ def __extract_factors_name_and_desc_from_content(
|
||||
content: str,
|
||||
) -> dict[str, dict[str, str]]:
|
||||
session = APIBackend().build_chat_session(
|
||||
session_system_prompt=document_process_prompts["extract_factors_system"],
|
||||
session_system_prompt=T(".prompts:extract_factors_system").r(),
|
||||
)
|
||||
|
||||
extracted_factor_dict = {}
|
||||
@@ -138,7 +132,7 @@ def __extract_factors_name_and_desc_from_content(
|
||||
break
|
||||
for factor_name, factor_description in factors.items():
|
||||
extracted_factor_dict[factor_name] = factor_description
|
||||
current_user_prompt = document_process_prompts["extract_factors_follow_user"]
|
||||
current_user_prompt = T(".prompts:extract_factors_follow_user").r()
|
||||
|
||||
return extracted_factor_dict
|
||||
|
||||
@@ -152,13 +146,10 @@ def __extract_factors_formulation_from_content(
|
||||
columns=["factor_name", "factor_description"],
|
||||
)
|
||||
|
||||
system_prompt = document_process_prompts["extract_factor_formulation_system"]
|
||||
current_user_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(
|
||||
document_process_prompts["extract_factor_formulation_user"],
|
||||
)
|
||||
.render(report_content=content, factor_dict=factor_dict_df.to_string())
|
||||
system_prompt = T(".prompts:extract_factor_formulation_system").r()
|
||||
current_user_prompt = T(".prompts:extract_factor_formulation_user").r(
|
||||
report_content=content,
|
||||
factor_dict=factor_dict_df.to_string(),
|
||||
)
|
||||
|
||||
session = APIBackend().build_chat_session(session_system_prompt=system_prompt)
|
||||
@@ -279,7 +270,7 @@ def __check_factor_dict_relevance(
|
||||
factor_df_string: str,
|
||||
) -> dict[str, dict[str, str]]:
|
||||
extract_result_resp = APIBackend().build_messages_and_create_chat_completion(
|
||||
system_prompt=document_process_prompts["factor_relevance_system"],
|
||||
system_prompt=T(".prompts:factor_relevance_system").r(),
|
||||
user_prompt=factor_df_string,
|
||||
json_mode=True,
|
||||
)
|
||||
@@ -322,7 +313,7 @@ def __check_factor_dict_viability_simulate_json_mode(
|
||||
factor_df_string: str,
|
||||
) -> dict[str, dict[str, str]]:
|
||||
extract_result_resp = APIBackend().build_messages_and_create_chat_completion(
|
||||
system_prompt=document_process_prompts["factor_viability_system"],
|
||||
system_prompt=T(".prompts:factor_viability_system").r(),
|
||||
user_prompt=factor_df_string,
|
||||
json_mode=True,
|
||||
)
|
||||
@@ -373,7 +364,7 @@ def __check_factor_duplication_simulate_json_mode(
|
||||
current_df = working_list.pop(0)
|
||||
if (
|
||||
APIBackend().build_messages_and_calculate_token(
|
||||
user_prompt=current_df.to_string(), system_prompt=document_process_prompts["factor_duplicate_system"]
|
||||
user_prompt=current_df.to_string(), system_prompt=T(".prompts:factor_duplicate_system").r()
|
||||
)
|
||||
> LLM_SETTINGS.chat_token_limit
|
||||
):
|
||||
@@ -386,7 +377,7 @@ def __check_factor_duplication_simulate_json_mode(
|
||||
for current_df in final_list:
|
||||
current_factor_to_string = current_df.to_string()
|
||||
session = APIBackend().build_chat_session(
|
||||
session_system_prompt=document_process_prompts["factor_duplicate_system"],
|
||||
session_system_prompt=T(".prompts:factor_duplicate_system").r(),
|
||||
)
|
||||
for _ in range(10):
|
||||
extract_result_resp = session.build_chat_completion(
|
||||
|
||||
+177
-150
@@ -1,91 +1,120 @@
|
||||
hypothesis_and_feedback: |-
|
||||
{% for experiment, feedback in trace.hist[-10:] %}
|
||||
Hypothesis {{ loop.index }}: {{ experiment.hypothesis }}
|
||||
|
||||
Corresponding Code (that leads to the difference in performance):
|
||||
{% for workspace in experiment.sub_workspace_list %}
|
||||
{% if workspace is not none %}
|
||||
Workspace {{loop.index}}:
|
||||
{{workspace.all_codes}}{% endif %}{% endfor %}
|
||||
|
||||
Observation on the result with the hypothesis: {{ feedback.observations }}
|
||||
Feedback on the original hypothesis: {{ feedback.hypothesis_evaluation }}
|
||||
New Feedback for Context (For you to agree or improve upon): {{ feedback.new_hypothesis }}
|
||||
Reasoning for new hypothesis: {{ feedback.reason }}
|
||||
Did changing to this hypothesis work? (focus on the change): {{ feedback.decision }}
|
||||
=========================================================
|
||||
{% for experiment, feedback in trace.hist %}
|
||||
# Trial {{ loop.index }}:
|
||||
## Hypothesis
|
||||
{{ experiment.hypothesis }}
|
||||
## Specific task:
|
||||
{% for task in experiment.sub_tasks %}
|
||||
{% if task is not none and task.get_task_brief_information is defined %}
|
||||
{{ task.get_task_brief_information() }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
## Backtest Analysis and Feedback:
|
||||
{% if experiment.result is not none %}
|
||||
Backtest Result: {{ experiment.result.loc[["IC", "1day.excess_return_without_cost.annualized_return", "1day.excess_return_without_cost.max_drawdown"]] }}
|
||||
{% endif %}
|
||||
Observation: {{ feedback.observations }}
|
||||
Hypothesis Evaluation: {{ feedback.hypothesis_evaluation }}
|
||||
Decision (Whether the hypothesis was successful): {{ feedback.decision }}
|
||||
=========================================================
|
||||
{% endfor %}
|
||||
|
||||
last_hypothesis_and_feedback: |-
|
||||
## Hypothesis
|
||||
{{ experiment.hypothesis }}
|
||||
## Specific task:
|
||||
{% for task in experiment.sub_tasks %}
|
||||
{% if task is not none and task.get_task_brief_information is defined %}
|
||||
{{ task.get_task_brief_information() }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
## Backtest Analysis and Feedback:
|
||||
{% if experiment.result is not none %}
|
||||
Backtest Result: {{ experiment.result.loc[["IC", "1day.excess_return_without_cost.annualized_return", "1day.excess_return_without_cost.max_drawdown"]] }}
|
||||
{% endif %}
|
||||
Training Log:
|
||||
Here, you need to focus on analyzing whether there are any issues with the training. If any problems are identified, you must correct them in the next iteration and clearly describe how the changes will be made in the hypothesis.
|
||||
{{ experiment.stdout }}
|
||||
Observation: {{ feedback.observations }}
|
||||
Evaluation: {{ feedback.hypothesis_evaluation }}
|
||||
Decision (Whether this experiment is SOTA): {{ feedback.decision }}
|
||||
New Hypothesis (Given in feedback stage, just for reference, and can be accepted or rejected in the next round): {{ feedback.new_hypothesis }}
|
||||
Reasoning (Justification for the new hypothesis): {{ feedback.reason }}
|
||||
|
||||
sota_hypothesis_and_feedback: |-
|
||||
## Hypothesis
|
||||
{{ experiment.hypothesis }}
|
||||
## Specific task:
|
||||
{% for task in experiment.sub_tasks %}
|
||||
{% if task is not none and task.get_task_brief_information is defined %}
|
||||
{{ task.get_task_brief_information() }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
## Backtest Analysis and Feedback:
|
||||
{% if experiment.result is not none %}
|
||||
Backtest Result: {{ experiment.result.loc[["IC", "1day.excess_return_without_cost.annualized_return", "1day.excess_return_without_cost.max_drawdown"]] }}
|
||||
{% endif %}
|
||||
Training Log: {{ experiment.stdout }}
|
||||
Observation: {{ feedback.observations }}
|
||||
Evaluation: {{ feedback.hypothesis_evaluation }}
|
||||
Decision (Whether this experiment is SOTA): {{ feedback.decision }}
|
||||
|
||||
hypothesis_output_format: |-
|
||||
The output should follow JSON format. The schema is as follows:
|
||||
{
|
||||
"hypothesis": "The new hypothesis generated based on the information provided.",
|
||||
"reason": "The reason why you generate this hypothesis. It should be comprehensive and logical. It should cover the other keys below and extend them.",
|
||||
"concise_reason": "Two-line summary. First line focuses on a concise justification for the change. Second line generalizes a knowledge statement.",
|
||||
"concise_observation": "One line summary. It focuses on the observation of the given scenario, data characteristics, or previous experiences (failures & succeses).",
|
||||
"concise_justification": "One line summary. Justify the hypothesis based on theoretical principles or initial assumptions.",
|
||||
"concise_knowledge": "One line summary. Transferable knowledge based on theoretical principles. Use conditional grammar. eg. "If...., ..; When..., .; and etc" Make sure that you state things clearly without ambiguity. Eg. avoid saying "previous hypothesis", because one wouldn't know what that is."
|
||||
"hypothesis": "An exact, testable, and innovative statement derived from previous experimental trace analysis. Avoid overly general ideas and ensure precision. The hypothesis should clearly specify the exact approach and expected improvement in performance in two or three sentences.",
|
||||
"reason": "Provide a clear, logical explanation for why this hypothesis was proposed, grounded in evidence (e.g., trace history, domain principles). Reason should be short with no more than two sentences.",
|
||||
}
|
||||
|
||||
factor_hypothesis_output_format: |-
|
||||
The output should follow JSON format. The schema is as follows:
|
||||
{
|
||||
"hypothesis": "The new hypothesis generated based on the information provided. Limit in two or three sentences.",
|
||||
"reason": "The reason why you generate this hypothesis. It should be comprehensive and logical. It should cover the other keys below and extend them. Limit in two or three sentences.",
|
||||
}
|
||||
|
||||
hypothesis_output_format_with_action: |-
|
||||
The output should follow JSON format. The schema is as follows:
|
||||
{
|
||||
"action": "If `hypothesis_specification` provides the action you need to take, please follow "hypothesis_specification" to choose the action. Otherwise, based on previous experimental results, suggest the action you believe is most appropriate at the moment. It should be one of [`factor`, `model`].",
|
||||
"hypothesis": "The new hypothesis generated based on the information provided.",
|
||||
"reason": "The reason why you generate this hypothesis. It should be comprehensive and logical. It should cover the other keys below and extend them. Limit in two or three sentences.",
|
||||
}
|
||||
|
||||
model_hypothesis_specification: |-
|
||||
Additional Specifications:
|
||||
|
||||
Hypotheses should grow and evolve based on the previous hypothesis. If there is no previous hypothesis, start with something simple. Gradually Build Up Upon previous hypothesis & feedbacks. In each round, hypothesis is different. Pay attention to your previous hypothesis.
|
||||
|
||||
Ensure that the hypothesis focuses on the architecture of a PyTorch model. Each hypothesis should address specific architectural choices such as the type of layers, activation functions, regularization techniques, and the overall structure of the model. Avoid hypotheses related to input features or optimization processes.
|
||||
|
||||
Remember: if there is no hypothesis, start with something simple like MLP.
|
||||
|
||||
Usually, a larger model works better than a smaller one.
|
||||
|
||||
Logic for generating a new hypothesis: If the previous hypothesis works, try to inherit from it and grow deeper. If the previous hypotheis doesn't work, try to make changes in the current level.
|
||||
|
||||
Sample hypothesis evolution loop: (This is the entire loop, see what stage you are at. We want hypothesis to continue growing.) Levels include **Model Type**, **Layer Configuration**, **Activation Functions**, **Regularization Techniques**
|
||||
|
||||
1st Round Hypothesis: The model should be a CNN.
|
||||
|
||||
2nd Round Hypothesis (If first round worked: CNN is the model type level, which means that we should extend to the next level, like layer configuration): The model should be a CNN. The CNN should have 5 convolutional layers. (Reasoning: As CNN worked, we now specify the layers specification to grow the hypothesis deeper.)
|
||||
|
||||
3rd Round Hypothesis (If second round didn't work): The model should be a CNN. The CNN should have 3 convolutional layers. (Reasoning: As 5-layer structure didn't work in the 2nd round hypothesis, try something else within the layer configuration level.)
|
||||
|
||||
4th Round Hypothesis (If third round worked): The model should be a CNN. The CNN should have 3 convolutional layers. Use Leaky ReLU activation for all layers. (As last round worked, now proceed to the next level: activation functions)
|
||||
|
||||
5th Round Hypothesis (If fourth round worked): The model should be a CNN. The CNN should have 3 convolutional layers. Use Leaky ReLU activation for all layers. Use dropout regularization with a rate of 0.5. (Similar Reasoning & Continuing to Grow to the dropout setup)
|
||||
|
||||
6th Round Hypothesis (If fourth round didn't work): The model should be a CNN. The CNN should have 5 convolutional layers. Use Leaky ReLU activation for all layers. Use dropout regularization with a rate of 0.3. (Reasoning: As regularisation rate of 0.5 didn't work, we only change a new regularisation and keep the other elements that worked. This means making changes in the current level.)
|
||||
1. First, observe and analyze the overall experimental progression in `hypothesis_and_feedback`. Analyze where the previous model designs were inadequate — whether it was due to parameter settings, architectural flaws, or a lack of novelty (proposing entirely new concepts is highly encouraged as long as they demonstrate effectiveness).
|
||||
2. Second, `last_hypothesis_and_feedback` and `sota_hypothesis_and_feedback` are key references you should pay close attention to. You can choose to optimize based on either of them or generate new ideas to form hypotheses and experiments.
|
||||
3. If there is no prior experiment or result available at the beginning, you can start by implementing a simple and small architecture.
|
||||
4. If a series of attempts fail to achieve SOTA, consider exploring entirely new directions; at this point, it is acceptable to return to simple architectures.
|
||||
5. Focus exclusively on the architecture of PyTorch models. Each hypothesis should specifically address architectural decisions, such as layer configurations, activation functions, regularization methods, and overall model structure. DO NOT do any feature-specific processing. Instead, you can propose innovative transformations on the input time-series data to enhance model training effectiveness.
|
||||
6. Avoid including aspects unrelated to architecture, such as input features or optimization strategies.
|
||||
7. Sometimes, when training performance is poor, adjusting hyperparameters can also be an effective strategy for improvement.
|
||||
8. Use standard libraries for baseline models, but also explore custom architecture designs to investigate novel structures. After sufficient trials with traditional models, aim for innovation comparable to top-tier AI conferences (NeurIPS, ICLR, ICML, SIGKDD, etc.) in time series modeling.
|
||||
|
||||
factor_hypothesis_specification: |-
|
||||
1. **Type of Factor and Financial Trends:**
|
||||
- Define the type of factor introduced.
|
||||
- Explain the financial trends or market behaviors indicated by this factor.
|
||||
- Omit unnecessary or redundant details.
|
||||
|
||||
1. **1-5 Factors per Generation:**
|
||||
- Ensure each generation produces 1-5 factors.
|
||||
- Balance simplicity and complexity to build a robust factor library.
|
||||
- Make full use of the financial data provided to you instead of focusing solely on a specific field.
|
||||
2. **Simple and Effective Factors First:**
|
||||
- Start with factors that are simple and likely effective.
|
||||
- Start with factors that are simple, easy to achieve and likely effective.
|
||||
- Concisely explain why these factors are expected to work.
|
||||
- Avoid complex or combined factors initially.
|
||||
|
||||
3. **Gradual Complexity Increase:**
|
||||
- Introduce more complex factors as more experimental results are gathered.
|
||||
- Discuss potential advantages and complexities.
|
||||
- Introduce more complex factors (e.g. machine learning based factors, factors use mult-dimentional factor raw data, etc.) as more experimental results are gathered.
|
||||
- Combine factors only after simpler ones are tested and validated.
|
||||
|
||||
4. **New Directions and Optimizations:**
|
||||
- If a new direction is needed, explain why based on financial principles, economic theories, or market behaviors.
|
||||
- Suggest only one new direction at a time for clarity.
|
||||
- If a previous hypothesis did not surpass SOTA but seems optimizable, you may continue in the same direction.
|
||||
- If multiple consecutive iterations fail to produce factors surpassing SOTA, consider switching to a new direction and can starting with simple factors again.
|
||||
- If optimizing a specific type of factor, proceed from simple to complex.
|
||||
5. Note
|
||||
- Highlight that factors surpassing SOTA are included in the library to avoid re-implementation.
|
||||
|
||||
5. **1-3 Factors per Generation:**
|
||||
- Ensure each generation produces 1-3 factors.
|
||||
- Balance simplicity and complexity to build a robust factor library.
|
||||
|
||||
factor_experiment_output_format: |-
|
||||
The output should follow JSON format. The schema is as follows:
|
||||
{
|
||||
"factor name 1": {
|
||||
"description": "description of factor 1",
|
||||
"description": "description of factor 1, start with its type, e.g. [Momentum Factor]",
|
||||
"formulation": "latex formulation of factor 1",
|
||||
"variables": {
|
||||
"variable or function name 1": "description of variable or function 1",
|
||||
@@ -93,7 +122,7 @@ factor_experiment_output_format: |-
|
||||
}
|
||||
},
|
||||
"factor name 2": {
|
||||
"description": "description of factor 1",
|
||||
"description": "description of factor 2, start with its type, e.g. [Machine Learning based Factor]",
|
||||
"formulation": "latex formulation of factor 2",
|
||||
"variables": {
|
||||
"variable or function name 1": "description of variable or function 1",
|
||||
@@ -105,9 +134,9 @@ factor_experiment_output_format: |-
|
||||
|
||||
model_experiment_output_format: |-
|
||||
So far please only design one model to test the hypothesis!
|
||||
The output should follow JSON format. The schema is as follows:
|
||||
The output should follow JSON format. The schema is as follows (value in training_hyperparameters is a basic setting for reference, you CAN CHANGE depends on the previous training log):
|
||||
{
|
||||
"model_name 1 (The name of the model)": {
|
||||
"model_name (The name of the model)": {
|
||||
"description": "A detailed description of the model",
|
||||
"formulation": "A LaTeX formula representing the model's formulation",
|
||||
"architecture": "A detailed description of the model's architecture, e.g., neural network layers or tree structures",
|
||||
@@ -121,13 +150,16 @@ model_experiment_output_format: |-
|
||||
"hyperparameter_name_2": "value of hyperparameter 2",
|
||||
"hyperparameter_name_3": "value of hyperparameter 3"
|
||||
},
|
||||
"training_hyperparameters" { # All values are for reference; you can set them yourself
|
||||
"n_epochs": "100",
|
||||
"lr": "1e-3",
|
||||
"early_stop": 10,
|
||||
"batch_size": 256,
|
||||
"weight_decay": 1e-4,
|
||||
}
|
||||
"model_type": "Tabular or TimeSeries" # Should be one of "Tabular" or "TimeSeries"
|
||||
},
|
||||
"model_name 2 (The name of the model)": {
|
||||
...
|
||||
}
|
||||
}
|
||||
Usually a larger model works better than a smaller one. Hence, the parameters should be larger.
|
||||
|
||||
factor_feedback_generation:
|
||||
system: |-
|
||||
@@ -141,28 +173,23 @@ factor_feedback_generation:
|
||||
|
||||
Please understand the following operation logic and then make your feedback that is suitable for the scenario:
|
||||
1. Logic Explanation:
|
||||
- If the previous hypothesis factor surpasses the SOTA, include this factor in the SOTA factor library.
|
||||
- New experiments will generate new factors, which will be combined with the factors in the SOTA library.
|
||||
- These combined factors will be backtested and compared against the current SOTA to continuously iterate.
|
||||
a) All factors that have surpassed SOTA in previous attempts will be included in the SOTA factor library.
|
||||
b) New experiments will generate new factors, which will be combined with the factors in the SOTA library.
|
||||
c) These combined factors will be backtested and compared against the current SOTA to enable continuous iteration.
|
||||
2. Development Directions:
|
||||
- New Direction:
|
||||
- Propose a new factor direction for exploration and development.
|
||||
- Optimization of Existing Direction:
|
||||
- If the previous experiment's factor replaced the SOTA, suggest further improvements to that factor.
|
||||
- Clearly specify the differences in name and improvements compared to the previous factor.
|
||||
- Continued Research:
|
||||
- If the previous experiment's factor did not replace the SOTA, suggest ways to optimize and develop factors in this direction.
|
||||
3. Final Goal:
|
||||
- The ultimate goal is to continuously accumulate factors that surpass each iteration to maintain the best SOTA.
|
||||
a) New Direction: Propose a new factor direction for exploration and development.
|
||||
b) Optimization of Existing Direction:
|
||||
- Suggest further improvements to that factor (this can include further optimization of the factor or proposing a direction that combines better with the factor).
|
||||
- Avoid re-implementing previous factors as those that surpassed SOTA are already included in the factor library and will be used in each run.
|
||||
3. Final Goal: To continuously accumulate factors that surpass each iteration to maintain the best SOTA.
|
||||
|
||||
When judging the results:
|
||||
1. **Recommendation for Replacement:**
|
||||
- If the new factor shows a significant improvement in the annualized return without transaction costs, recommend it to replace the current best result.
|
||||
- If the annualized return and any other single metric are better than SOTA, recommend the replacement.
|
||||
- Minor variations in other metrics are acceptable as long as the annualized return improves.
|
||||
When judging the results:
|
||||
1. Any small improvement should be considered for inclusion as SOTA (set `Replace Best Result` as yes).
|
||||
2. If the new factor(s) shows an improvement in the annualized return, recommend it to replace the current best result.
|
||||
3. Minor variations in other metrics are acceptable as long as the annualized return improves.
|
||||
|
||||
Consider Changing Direction for Significant Gaps with SOTA:
|
||||
- If the new results significantly differ from the SOTA, consider exploring a new direction.
|
||||
- If the new results significantly differ from the SOTA, consider exploring a new direction (write new type factors).
|
||||
- Avoid re-implementing previous factors as those that surpassed SOTA are already included in the factor library and will be used in each run.
|
||||
|
||||
Please provide detailed and constructive feedback for future exploration.
|
||||
@@ -193,80 +220,80 @@ factor_feedback_generation:
|
||||
Analyze the combined result in the context of its ability to:
|
||||
1. Support or refute the hypothesis.
|
||||
2. Show improvement or deterioration compared to the SOTA experiment.
|
||||
|
||||
Evaluation Metrics Explanations:
|
||||
Below are the financial meanings of each metric, which should be used to judge the results:
|
||||
|
||||
- 1day.excess_return_without_cost.max_drawdown: Measures the maximum loss from a peak to a trough without considering transaction costs. (the smaller the better)
|
||||
- 1day.excess_return_without_cost.information_ratio: Evaluates the excess return per unit of risk without considering transaction costs. (the bigger the better)
|
||||
- 1day.excess_return_without_cost.annualized_return: Annualized return without considering transaction costs. (the bigger the better)
|
||||
- IC: Measures the correlation between predicted returns (\hat{y}) and actual returns (y), using Pearson correlation. (the bigger the better)
|
||||
|
||||
When judging the results:
|
||||
1. **Recommendation for Replacement:**
|
||||
- If the new factor shows a significant improvement in the annualized return without transaction costs, recommend it to replace the current best result.
|
||||
- If the annualized return and any other single metric are better than SOTA, recommend the replacement.
|
||||
- Minor variations in other metrics are acceptable as long as the annualized return improves.
|
||||
|
||||
Consider Changing Direction for Significant Gaps with SOTA:
|
||||
- If the new results significantly differ from the SOTA, consider exploring a new direction.
|
||||
- Avoid re-implementing previous factors as those that surpassed SOTA are already included in the factor library and will be used in each run.
|
||||
|
||||
|
||||
Note: Only factors with 'Factor Implementation' as True are implemented and tested in this experiment. If 'Factor Implementation' is False, the hypothesis for that factor cannot be verified in this run.
|
||||
|
||||
model_feedback_generation:
|
||||
system: |-
|
||||
You are a professional result analysis assistant. You will receive a result and a hypothesis.
|
||||
Your task is to provide feedback on how well the result supports or refutes the hypothesis by judging from the observation of performance increase or decrease.
|
||||
Please provide detailed and constructive feedback. Note that as hypothesis evolve, a general trend should be that the model grows larger.
|
||||
You are a professional quantitative analysis assistant in top-tier hedge fund.
|
||||
|
||||
The task is described in the following scenario:
|
||||
{{ scenario }}
|
||||
|
||||
You will receive a quantitative model hypothesis, its specific task description, and it market backtest result.
|
||||
Your feedback should specify whether the current result supports or refutes the hypothesis, compare it with previous SOTA results, examine the model's training logs to analyze whether there are issues with hyperparameter settings, and suggest improvements or new directions.
|
||||
|
||||
Please provide detailed and constructive feedback.
|
||||
Example JSON Structure for Result Analysis:
|
||||
{
|
||||
"Observations": "Your overall observations here",
|
||||
"Feedback for Hypothesis": "Observations related to the hypothesis",
|
||||
"New Hypothesis": "Put your new hypothesis here.",
|
||||
"Reasoning": "Provide reasoning for the hypothesis here.",
|
||||
"Observations": "First analyze the model's training logs to determine whether there are any issues with its parameter settings. Then clearly summarize the current results and the SOTA results with exact scores and any notable patterns. Limit your summary to no more than three concise, data-focused sentences.",
|
||||
"Feedback for Hypothesis": "Explicitly confirm or refute the hypothesis based on specific data points or performance trends. Limit to two sentences.",
|
||||
"New Hypothesis": "Propose a revised hypothesis, considering observed patterns and limitations in the current one. Limit to no more than two sentences.",
|
||||
"Reasoning": "Explain the rationale for the new hypothesis using specific trends or performance shifts. Be concise but technically complete. Limit to two sentences.",
|
||||
"Decision": <true or false>,
|
||||
}
|
||||
|
||||
Focus on the changes in hypothesis and justify why do hypothesis evolve like this. Also, increase complexity as the hypothesis evolves (give more layers, more neurons, and etc)
|
||||
|
||||
Logic for generating a new hypothesis: If the previous hypothesis works, try to inherit from it and grow deeper. If the previous hypotheis doesn't work, try to make changes in the current level.
|
||||
|
||||
Sample hypothesis evolution loop: (This is the entire loop, see what stage you are at. We want hypothesis to continue growing.) Levels include **Model Type**, **Layer Configuration**, **Activation Functions**, **Regularization Techniques**
|
||||
|
||||
1st Round Hypothesis: The model should be a CNN.
|
||||
|
||||
2nd Round Hypothesis (If first round worked: CNN is the model type level, which means that we should extend to the next level, like layer configuration): The model should be a CNN. The CNN should have 5 convolutional layers. (Reasoning: As CNN worked, we now specify the layers specification to grow the hypothesis deeper.)
|
||||
|
||||
3rd Round Hypothesis (If second round didn't work): The model should be a CNN. The CNN should have 3 convolutional layers. (Reasoning: As 5-layer structure didn't work in the 2nd round hypothesis, try something else within the layer configuration level.)
|
||||
|
||||
4th Round Hypothesis (If third round worked): The model should be a CNN. The CNN should have 3 convolutional layers. Use Leaky ReLU activation for all layers. (As last round worked, now proceed to the next level: activation functions)
|
||||
|
||||
5th Round Hypothesis (If fourth round worked): The model should be a CNN. The CNN should have 3 convolutional layers. Use Leaky ReLU activation for all layers. Use dropout regularization with a rate of 0.5. (Similar Reasoning & Continuing to Grow to the dropout setup)
|
||||
|
||||
6th Round Hypothesis (If fourth round didn't work): The model should be a CNN. The CNN should have 5 convolutional layers. Use Leaky ReLU activation for all layers. Use dropout regularization with a rate of 0.3. (Reasoning: As regularisation rate of 0.5 didn't work, we only change a new regularisation and keep the other elements that worked. This means making changes in the current level.)
|
||||
|
||||
|
||||
user: |-
|
||||
We are in an experiment of finding hypothesis and validating or rejecting them so that in the end we have a powerful model generated.
|
||||
Here are the context: {{context}}.
|
||||
|
||||
{% if last_hypothesis %}
|
||||
Last Round Information:
|
||||
Hypothesis: {{last_hypothesis.hypothesis}}
|
||||
Task: {{last_task}}
|
||||
Code Implemented: {{last_code}}
|
||||
Result: {{last_result}}
|
||||
{% if sota_hypothesis %}
|
||||
# SOTA Round Information:
|
||||
Hypothesis: {{ sota_hypothesis.hypothesis }}
|
||||
Specific Task: {{ sota_task }}
|
||||
Code Implementation: {{ sota_code }}
|
||||
Result: {{ sota_result }}
|
||||
{% else %}
|
||||
This is the first round. No previous information available. As long as the performance is not too negative (eg.ICIR is greater than 0), treat it as successful. Do not set the threshold too high.
|
||||
# This is the first round. No previous information available. As long as the performance is not too negative (eg.ICIR is greater than 0), treat it as successful. Do not set the threshold too high.
|
||||
{% endif %}
|
||||
|
||||
Now let's come to this round. You will receive the result and you will evaluate if the performance increases or decreases.
|
||||
Hypothesis: {{hypothesis.hypothesis}}
|
||||
Experiment Setup: {{exp.sub_tasks[0]}}
|
||||
Code Implemented: {{exp.sub_workspace_list[0].file_dict.get("model.py")}}
|
||||
Relevant Reasoning: {{hypothesis.reason}}
|
||||
Result: {{exp.result}}
|
||||
# Current Round Information:
|
||||
Hypothesis: {{ hypothesis.hypothesis }}
|
||||
Why propose this hypothesis: {{ hypothesis.reason }}
|
||||
Specific Task: {{ exp.sub_tasks[0].get_task_information() }}
|
||||
Code Implementation: {{ exp.sub_workspace_list[0].file_dict.get("model.py") }}
|
||||
Training Log: {{ exp.stdout }}
|
||||
Result: {{ exp_result }}
|
||||
|
||||
Compare and observe. Which result has a better return and lower risk? If the performance increases, the hypothesis should be considered positive (working).
|
||||
Hence, with the hypotheses, relevant reasoning, and results in mind (comparison), provide detailed and constructive feedback and suggest a new hypothesis.
|
||||
# When judging the results:
|
||||
1. **Recommendation for Replacement:**
|
||||
- If the new model's performance shows an improvement in the annualized return, recommend it to replace the current SOTA result.
|
||||
- Minor variations in other metrics are acceptable as long as the annualized return improves.
|
||||
2. Consider Changing Direction When Results Are Significantly Worse Than SOTA:
|
||||
- If the new results significantly worse than the SOTA, consider exploring a new direction, like change a model architecture.
|
||||
|
||||
action_gen:
|
||||
system: |-
|
||||
Quantitative investment is a data-driven approach to asset management that relies on mathematical models, statistical techniques, and computational methods to analyze financial markets and make investment decisions. Two essential components of this approach are factors and models.
|
||||
|
||||
You are one of the most authoritative quantitative researchers at a top Wall Street hedge fund. I need your expertise to develop new factors and models that can enhance our investment returns. Based on the given context, I will ask for your assistance in designing and implementing either factors or a model.
|
||||
|
||||
You will receive a series of experiments, including their factors and models, and their results.
|
||||
Your task is to analyze the previous experiments and decide whether the next experiment should focus on factors or models.
|
||||
|
||||
Example JSON Structure for your return:
|
||||
{
|
||||
"action": "factor" or "model", # You must choose one of the two
|
||||
}
|
||||
|
||||
user: |-
|
||||
{% if hypothesis_and_feedback|length == 0 %}
|
||||
It is the first round of hypothesis generation. The user has no hypothesis on this scenario yet.
|
||||
{% else %}
|
||||
The former hypothesis and the corresponding feedbacks are as follows:
|
||||
{{ hypothesis_and_feedback }}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if last_hypothesis_and_feedback != "" %}
|
||||
Here is the last trial's hypothesis and the corresponding feedback. The main feedback includes a new hypothesis for your reference only. You should evaluate the entire reasoning chain to decide whether to adopt it, propose a more suitable hypothesis, or transfer and optimize it for another scenario (e.g., factor/model), since transfers are generally encouraged:
|
||||
{{ last_hypothesis_and_feedback }}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,109 @@
|
||||
import json
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import List, Literal, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
@dataclass
|
||||
class Metrics:
|
||||
ic: float = 0.0
|
||||
icir: float = 0.0
|
||||
rank_ic: float = 0.0
|
||||
rank_icir: float = 0.0
|
||||
arr: float = 0.0
|
||||
ir: float = 0.0
|
||||
mdd: float = 0.0
|
||||
sharpe: float = 0.0
|
||||
|
||||
def as_vector(self) -> np.ndarray:
|
||||
return np.array(
|
||||
[
|
||||
self.ic,
|
||||
self.icir,
|
||||
self.rank_ic,
|
||||
self.rank_icir,
|
||||
self.arr,
|
||||
self.ir,
|
||||
-self.mdd,
|
||||
self.sharpe,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def extract_metrics_from_experiment(experiment) -> Metrics:
|
||||
"""Extract metrics from experiment feedback"""
|
||||
try:
|
||||
result = experiment.result
|
||||
ic = result.get("IC", 0.0)
|
||||
icir = result.get("ICIR", 0.0)
|
||||
rank_ic = result.get("Rank IC", 0.0)
|
||||
rank_icir = result.get("Rank ICIR", 0.0)
|
||||
arr = result.get("1day.excess_return_with_cost.annualized_return ", 0.0)
|
||||
ir = result.get("1day.excess_return_with_cost.information_ratio", 0.0)
|
||||
mdd = result.get("1day.excess_return_with_cost.max_drawdown", 1.0) # Avoid division by zero
|
||||
sharpe = arr / -mdd if mdd != 0 else 0.0
|
||||
|
||||
return Metrics(ic=ic, icir=icir, rank_ic=rank_ic, rank_icir=rank_icir, arr=arr, ir=ir, mdd=mdd, sharpe=sharpe)
|
||||
except Exception as e:
|
||||
print(f"Error extracting metrics: {e}")
|
||||
return Metrics()
|
||||
|
||||
|
||||
class LinearThompsonTwoArm:
|
||||
def __init__(self, dim: int, prior_var: float = 1.0, noise_var: float = 1.0):
|
||||
self.dim = dim
|
||||
self.noise_var = noise_var
|
||||
# Each arm has its own posterior: mean & inverse of covariance (precision matrix)
|
||||
self.mean = {
|
||||
"factor": np.zeros(dim),
|
||||
"model": np.zeros(dim),
|
||||
}
|
||||
self.precision = {
|
||||
"factor": np.eye(dim) / prior_var,
|
||||
"model": np.eye(dim) / prior_var,
|
||||
}
|
||||
|
||||
def sample_reward(self, arm: str, x: np.ndarray) -> float:
|
||||
P = self.precision[arm]
|
||||
P = 0.5 * (P + P.T)
|
||||
|
||||
eps = 1e-6
|
||||
try:
|
||||
cov = np.linalg.inv(P + eps * np.eye(self.dim))
|
||||
L = np.linalg.cholesky(cov)
|
||||
z = np.random.randn(self.dim)
|
||||
w_sample = self.mean[arm] + L @ z
|
||||
except np.linalg.LinAlgError:
|
||||
w_sample = self.mean[arm]
|
||||
|
||||
return float(np.dot(w_sample, x))
|
||||
|
||||
def update(self, arm: str, x: np.ndarray, r: float) -> None:
|
||||
P = self.precision[arm]
|
||||
P += np.outer(x, x) / self.noise_var
|
||||
self.precision[arm] = P
|
||||
self.mean[arm] = np.linalg.solve(P, P @ self.mean[arm] + (r / self.noise_var) * x)
|
||||
|
||||
def next_arm(self, x: np.ndarray) -> str:
|
||||
scores = {arm: self.sample_reward(arm, x) for arm in ("factor", "model")}
|
||||
return max(scores, key=scores.get)
|
||||
|
||||
|
||||
class EnvController:
|
||||
def __init__(self, weights: Tuple[float, ...] = None) -> None:
|
||||
self.weights = np.asarray(weights or (0.1, 0.1, 0.05, 0.05, 0.25, 0.15, 0.1, 0.2))
|
||||
self.bandit = LinearThompsonTwoArm(dim=8, prior_var=10.0, noise_var=0.5)
|
||||
|
||||
def reward(self, m: Metrics) -> float:
|
||||
return float(np.dot(self.weights, m.as_vector()))
|
||||
|
||||
def decide(self, m: Metrics) -> str:
|
||||
x = m.as_vector()
|
||||
return self.bandit.next_arm(x)
|
||||
|
||||
def record(self, m: Metrics, arm: str) -> None:
|
||||
r = self.reward(m)
|
||||
self.bandit.update(arm, m.as_vector(), r)
|
||||
@@ -1,16 +1,13 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.factor_coder.factor import FactorExperiment, FactorTask
|
||||
from rdagent.components.proposal import FactorHypothesis2Experiment, FactorHypothesisGen
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import Hypothesis, Scenario, Trace
|
||||
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
|
||||
from rdagent.scenarios.qlib.experiment.model_experiment import QlibModelExperiment
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
QlibFactorHypothesis = Hypothesis
|
||||
|
||||
@@ -21,49 +18,68 @@ class QlibFactorHypothesisGen(FactorHypothesisGen):
|
||||
|
||||
def prepare_context(self, trace: Trace) -> Tuple[dict, bool]:
|
||||
hypothesis_and_feedback = (
|
||||
(
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_and_feedback"])
|
||||
.render(trace=trace)
|
||||
T("scenarios.qlib.prompts:hypothesis_and_feedback").r(
|
||||
trace=trace,
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
last_hypothesis_and_feedback = (
|
||||
T("scenarios.qlib.prompts:last_hypothesis_and_feedback").r(
|
||||
experiment=trace.hist[-1][0], feedback=trace.hist[-1][1]
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
|
||||
context_dict = {
|
||||
"hypothesis_and_feedback": hypothesis_and_feedback,
|
||||
"RAG": None,
|
||||
"hypothesis_output_format": prompt_dict["hypothesis_output_format"],
|
||||
"hypothesis_specification": prompt_dict["factor_hypothesis_specification"],
|
||||
"last_hypothesis_and_feedback": last_hypothesis_and_feedback,
|
||||
"RAG": (
|
||||
"Try the easiest and fastest factors to experiment with from various perspectives first."
|
||||
if len(trace.hist) < 15
|
||||
else "Now, you need to try factors that can achieve high IC (e.g., machine learning-based factors)."
|
||||
),
|
||||
"hypothesis_output_format": T("scenarios.qlib.prompts:factor_hypothesis_output_format").r(),
|
||||
"hypothesis_specification": T("scenarios.qlib.prompts:factor_hypothesis_specification").r(),
|
||||
}
|
||||
return context_dict, True
|
||||
|
||||
def convert_response(self, response: str) -> Hypothesis:
|
||||
response_dict = json.loads(response)
|
||||
hypothesis = QlibFactorHypothesis(
|
||||
hypothesis=response_dict["hypothesis"],
|
||||
reason=response_dict["reason"],
|
||||
concise_reason=response_dict["concise_reason"],
|
||||
concise_observation=response_dict["concise_observation"],
|
||||
concise_justification=response_dict["concise_justification"],
|
||||
concise_knowledge=response_dict["concise_knowledge"],
|
||||
hypothesis=response_dict.get("hypothesis"),
|
||||
reason=response_dict.get("reason"),
|
||||
concise_reason=response_dict.get("concise_reason"),
|
||||
concise_observation=response_dict.get("concise_observation"),
|
||||
concise_justification=response_dict.get("concise_justification"),
|
||||
concise_knowledge=response_dict.get("concise_knowledge"),
|
||||
)
|
||||
return hypothesis
|
||||
|
||||
|
||||
class QlibFactorHypothesis2Experiment(FactorHypothesis2Experiment):
|
||||
def prepare_context(self, hypothesis: Hypothesis, trace: Trace) -> Tuple[dict | bool]:
|
||||
scenario = trace.scen.get_scenario_all_desc()
|
||||
experiment_output_format = prompt_dict["factor_experiment_output_format"]
|
||||
if isinstance(trace.scen, QlibQuantScenario):
|
||||
scenario = trace.scen.get_scenario_all_desc(action="factor")
|
||||
else:
|
||||
scenario = trace.scen.get_scenario_all_desc()
|
||||
experiment_output_format = T("scenarios.qlib.prompts:factor_experiment_output_format").r()
|
||||
|
||||
hypothesis_and_feedback = (
|
||||
(
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_and_feedback"])
|
||||
.render(trace=trace)
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
if len(trace.hist) == 0:
|
||||
hypothesis_and_feedback = "No previous hypothesis and feedback available since it's the first round."
|
||||
else:
|
||||
specific_trace = Trace(trace.scen)
|
||||
for i in range(len(trace.hist) - 1, -1, -1): # Reverse iteration
|
||||
if not hasattr(trace.hist[i][0].hypothesis, "action") or trace.hist[i][0].hypothesis.action == "factor":
|
||||
specific_trace.hist.insert(0, trace.hist[i])
|
||||
if len(specific_trace.hist) > 0:
|
||||
specific_trace.hist.reverse()
|
||||
hypothesis_and_feedback = T("scenarios.qlib.prompts:hypothesis_and_feedback").r(
|
||||
trace=specific_trace,
|
||||
)
|
||||
else:
|
||||
hypothesis_and_feedback = "No previous hypothesis and feedback available."
|
||||
|
||||
experiment_list: List[FactorExperiment] = [t[0] for t in trace.hist]
|
||||
|
||||
@@ -105,6 +121,8 @@ class QlibFactorHypothesis2Experiment(FactorHypothesis2Experiment):
|
||||
for task in tasks:
|
||||
duplicate = False
|
||||
for based_exp in exp.based_experiments:
|
||||
if isinstance(based_exp, QlibModelExperiment):
|
||||
continue
|
||||
for sub_task in based_exp.sub_tasks:
|
||||
if task.factor_name == sub_task.factor_name:
|
||||
duplicate = True
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
|
||||
from rdagent.components.coder.model_coder.model import ModelExperiment, ModelTask
|
||||
from rdagent.components.proposal import ModelHypothesis2Experiment, ModelHypothesisGen
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import Hypothesis, Scenario, Trace
|
||||
from rdagent.scenarios.qlib.experiment.model_experiment import QlibModelExperiment
|
||||
|
||||
prompt_dict = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
|
||||
from rdagent.scenarios.qlib.experiment.quant_experiment import QlibQuantScenario
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
QlibModelHypothesis = Hypothesis
|
||||
|
||||
@@ -21,50 +17,109 @@ class QlibModelHypothesisGen(ModelHypothesisGen):
|
||||
|
||||
def prepare_context(self, trace: Trace) -> Tuple[dict, bool]:
|
||||
hypothesis_and_feedback = (
|
||||
(
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_and_feedback"])
|
||||
.render(trace=trace)
|
||||
T("scenarios.qlib.prompts:hypothesis_and_feedback").r(
|
||||
trace=trace,
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
|
||||
last_hypothesis_and_feedback = (
|
||||
T("scenarios.qlib.prompts:last_hypothesis_and_feedback").r(
|
||||
experiment=trace.hist[-1][0], feedback=trace.hist[-1][1]
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
|
||||
sota_hypothesis_and_feedback = ""
|
||||
if len(trace.hist) == 0:
|
||||
sota_hypothesis_and_feedback = "No SOTA hypothesis and feedback available since it is the first round."
|
||||
else:
|
||||
for i in range(len(trace.hist) - 1, -1, -1):
|
||||
if trace.hist[i][1].decision:
|
||||
sota_hypothesis_and_feedback = T("scenarios.qlib.prompts:sota_hypothesis_and_feedback").r(
|
||||
experiment=trace.hist[i][0], feedback=trace.hist[i][1]
|
||||
)
|
||||
break
|
||||
else:
|
||||
sota_hypothesis_and_feedback = (
|
||||
"No SOTA hypothesis and feedback available since previous experiments were not accepted."
|
||||
)
|
||||
|
||||
context_dict = {
|
||||
"hypothesis_and_feedback": hypothesis_and_feedback,
|
||||
"RAG": "In Quantitative Finance, market data could be time-series, and GRU model/LSTM model are suitable for them. Do not generate GNN model as for now.",
|
||||
"hypothesis_output_format": prompt_dict["hypothesis_output_format"],
|
||||
"hypothesis_specification": prompt_dict["model_hypothesis_specification"],
|
||||
"last_hypothesis_and_feedback": last_hypothesis_and_feedback,
|
||||
"SOTA_hypothesis_and_feedback": sota_hypothesis_and_feedback,
|
||||
"RAG": "1. In Quantitative Finance, market data could be time-series, and GRU model/LSTM model are suitable for them. Do not generate GNN model as for now.\n2. The training data consists of less than 1 million samples for the training set and approximately 250,000 samples for the validation set. Please design the hyperparameters accordingly and control the model size. This has a significant impact on the training results. If you believe that the previous model itself is good but the training hyperparameters or model hyperparameters are not optimal, you can return the same model and adjust these parameters instead.",
|
||||
"hypothesis_output_format": T("scenarios.qlib.prompts:hypothesis_output_format").r(),
|
||||
"hypothesis_specification": T("scenarios.qlib.prompts:model_hypothesis_specification").r(),
|
||||
}
|
||||
return context_dict, True
|
||||
|
||||
def convert_response(self, response: str) -> Hypothesis:
|
||||
response_dict = json.loads(response)
|
||||
hypothesis = QlibModelHypothesis(
|
||||
hypothesis=response_dict["hypothesis"],
|
||||
reason=response_dict["reason"],
|
||||
concise_reason=response_dict["concise_reason"],
|
||||
concise_observation=response_dict["concise_observation"],
|
||||
concise_justification=response_dict["concise_justification"],
|
||||
concise_knowledge=response_dict["concise_knowledge"],
|
||||
hypothesis=response_dict.get("hypothesis"),
|
||||
reason=response_dict.get("reason"),
|
||||
concise_reason=response_dict.get("concise_reason"),
|
||||
concise_observation=response_dict.get("concise_observation"),
|
||||
concise_justification=response_dict.get("concise_justification"),
|
||||
concise_knowledge=response_dict.get("concise_knowledge"),
|
||||
)
|
||||
return hypothesis
|
||||
|
||||
|
||||
class QlibModelHypothesis2Experiment(ModelHypothesis2Experiment):
|
||||
def prepare_context(self, hypothesis: Hypothesis, trace: Trace) -> Tuple[dict, bool]:
|
||||
scenario = trace.scen.get_scenario_all_desc()
|
||||
experiment_output_format = prompt_dict["model_experiment_output_format"]
|
||||
if isinstance(trace.scen, QlibQuantScenario):
|
||||
scenario = trace.scen.get_scenario_all_desc(action="model")
|
||||
else:
|
||||
scenario = trace.scen.get_scenario_all_desc()
|
||||
experiment_output_format = T("scenarios.qlib.prompts:model_experiment_output_format").r()
|
||||
|
||||
hypothesis_and_feedback = (
|
||||
(
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(prompt_dict["hypothesis_and_feedback"])
|
||||
.render(trace=trace)
|
||||
last_experiment = None
|
||||
last_feedback = None
|
||||
sota_experiment = None
|
||||
sota_feedback = None
|
||||
|
||||
if len(trace.hist) == 0:
|
||||
hypothesis_and_feedback = "No previous hypothesis and feedback available since it's the first round."
|
||||
else:
|
||||
specific_trace = Trace(trace.scen)
|
||||
for i in range(len(trace.hist) - 1, -1, -1): # Reverse iteration
|
||||
if not hasattr(trace.hist[i][0].hypothesis, "action") or trace.hist[i][0].hypothesis.action == "model":
|
||||
if last_experiment is None:
|
||||
last_experiment = trace.hist[i][0]
|
||||
last_feedback = trace.hist[i][1]
|
||||
if trace.hist[i][1].decision is True and sota_experiment is None:
|
||||
sota_experiment = trace.hist[i][0]
|
||||
sota_feedback = trace.hist[i][1]
|
||||
specific_trace.hist.insert(0, trace.hist[i])
|
||||
if len(specific_trace.hist) > 0:
|
||||
specific_trace.hist.reverse()
|
||||
hypothesis_and_feedback = T("scenarios.qlib.prompts:hypothesis_and_feedback").r(
|
||||
trace=specific_trace,
|
||||
)
|
||||
else:
|
||||
hypothesis_and_feedback = "No previous hypothesis and feedback available."
|
||||
|
||||
last_hypothesis_and_feedback = (
|
||||
T("scenarios.qlib.prompts:last_hypothesis_and_feedback").r(
|
||||
experiment=last_experiment, feedback=last_feedback
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
if last_experiment is not None
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
|
||||
sota_hypothesis_and_feedback = (
|
||||
T("scenarios.qlib.prompts:sota_hypothesis_and_feedback").r(
|
||||
experiment=sota_experiment, feedback=sota_feedback
|
||||
)
|
||||
if sota_experiment is not None
|
||||
else "No SOTA hypothesis and feedback available since previous experiments were not accepted."
|
||||
)
|
||||
|
||||
experiment_list: List[ModelExperiment] = [t[0] for t in trace.hist]
|
||||
|
||||
model_list = []
|
||||
@@ -75,9 +130,11 @@ class QlibModelHypothesis2Experiment(ModelHypothesis2Experiment):
|
||||
"target_hypothesis": str(hypothesis),
|
||||
"scenario": scenario,
|
||||
"hypothesis_and_feedback": hypothesis_and_feedback,
|
||||
"last_hypothesis_and_feedback": last_hypothesis_and_feedback,
|
||||
"SOTA_hypothesis_and_feedback": sota_hypothesis_and_feedback,
|
||||
"experiment_output_format": experiment_output_format,
|
||||
"target_list": model_list,
|
||||
"RAG": None,
|
||||
"RAG": "Note, the training data consists of less than 1 million samples for the training set and approximately 250,000 samples for the validation set. Please design the hyperparameters accordingly and control the model size. This has a significant impact on the training results. If you believe that the previous model itself is good but the training hyperparameters or model hyperparameters are not optimal, you can return the same model and adjust these parameters instead.",
|
||||
}, True
|
||||
|
||||
def convert_response(self, response: str, hypothesis: Hypothesis, trace: Trace) -> ModelExperiment:
|
||||
@@ -89,6 +146,7 @@ class QlibModelHypothesis2Experiment(ModelHypothesis2Experiment):
|
||||
architecture = response_dict[model_name]["architecture"]
|
||||
variables = response_dict[model_name]["variables"]
|
||||
hyperparameters = response_dict[model_name]["hyperparameters"]
|
||||
training_hyperparameters = response_dict[model_name]["training_hyperparameters"]
|
||||
model_type = response_dict[model_name]["model_type"]
|
||||
tasks.append(
|
||||
ModelTask(
|
||||
@@ -98,6 +156,7 @@ class QlibModelHypothesis2Experiment(ModelHypothesis2Experiment):
|
||||
architecture=architecture,
|
||||
variables=variables,
|
||||
hyperparameters=hyperparameters,
|
||||
training_hyperparameters=training_hyperparameters,
|
||||
model_type=model_type,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import json
|
||||
import random
|
||||
from typing import Tuple
|
||||
|
||||
from rdagent.app.qlib_rd_loop.conf import QUANT_PROP_SETTING
|
||||
from rdagent.components.proposal import FactorAndModelHypothesisGen
|
||||
from rdagent.core.proposal import Hypothesis, Scenario, Trace
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.scenarios.qlib.proposal.bandit import (
|
||||
EnvController,
|
||||
extract_metrics_from_experiment,
|
||||
)
|
||||
from rdagent.utils.agent.tpl import T
|
||||
|
||||
|
||||
class QuantTrace(Trace):
|
||||
def __init__(self, scen: Scenario) -> None:
|
||||
super().__init__(scen)
|
||||
# Initialize the controller with default weights
|
||||
self.controller = EnvController()
|
||||
|
||||
|
||||
class QlibQuantHypothesis(Hypothesis):
|
||||
def __init__(
|
||||
self,
|
||||
hypothesis: str,
|
||||
reason: str,
|
||||
concise_reason: str,
|
||||
concise_observation: str,
|
||||
concise_justification: str,
|
||||
concise_knowledge: str,
|
||||
action: str,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
hypothesis, reason, concise_reason, concise_observation, concise_justification, concise_knowledge
|
||||
)
|
||||
self.action = action
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""Chosen Action: {self.action}
|
||||
Hypothesis: {self.hypothesis}
|
||||
Reason: {self.reason}
|
||||
"""
|
||||
|
||||
|
||||
class QlibQuantHypothesisGen(FactorAndModelHypothesisGen):
|
||||
def __init__(self, scen: Scenario) -> Tuple[dict, bool]:
|
||||
super().__init__(scen)
|
||||
|
||||
def prepare_context(self, trace: Trace) -> Tuple[dict, bool]:
|
||||
|
||||
# ========= Bandit ==========
|
||||
if QUANT_PROP_SETTING.action_selection == "bandit":
|
||||
if len(trace.hist) > 0:
|
||||
metric = extract_metrics_from_experiment(trace.hist[-1][0])
|
||||
prev_action = trace.hist[-1][0].hypothesis.action
|
||||
trace.controller.record(metric, prev_action)
|
||||
action = trace.controller.decide(metric)
|
||||
else:
|
||||
action = "factor"
|
||||
# ========= LLM ==========
|
||||
elif QUANT_PROP_SETTING.action_selection == "llm":
|
||||
hypothesis_and_feedback = (
|
||||
T("scenarios.qlib.prompts:hypothesis_and_feedback").render(trace=trace)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
|
||||
last_hypothesis_and_feedback = (
|
||||
T("scenarios.qlib.prompts:last_hypothesis_and_feedback").r(
|
||||
experiment=trace.hist[-1][0], feedback=trace.hist[-1][1]
|
||||
)
|
||||
if len(trace.hist) > 0
|
||||
else "No previous hypothesis and feedback available since it's the first round."
|
||||
)
|
||||
|
||||
system_prompt = T("scenarios.qlib.prompts:action_gen.system").r()
|
||||
user_prompt = T("scenarios.qlib.prompts:action_gen.user").r(
|
||||
hypothesis_and_feedback=hypothesis_and_feedback,
|
||||
last_hypothesis_and_feedback=last_hypothesis_and_feedback,
|
||||
)
|
||||
resp = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt, json_mode=True)
|
||||
|
||||
action = json.loads(resp).get("action", "factor")
|
||||
# ========= random ==========
|
||||
elif QUANT_PROP_SETTING.action_selection == "random":
|
||||
action = random.choice(["factor", "model"])
|
||||
self.targets = action
|
||||
|
||||
qaunt_rag = None
|
||||
if action == "factor":
|
||||
if len(trace.hist) < 6:
|
||||
qaunt_rag = "Try the easiest and fastest factors to experiment with from various perspectives first."
|
||||
else:
|
||||
qaunt_rag = "Now, you need to try factors that can achieve high IC (e.g., machine learning-based factors)! Do not include factors that are similar to those in the SOTA factor library!"
|
||||
elif action == "model":
|
||||
qaunt_rag = "1. In Quantitative Finance, market data could be time-series, and GRU model/LSTM model are suitable for them. Do not generate GNN model as for now.\n2. The training data consists of approximately 478,000 samples for the training set and about 128,000 samples for the validation set. Please design the hyperparameters accordingly and control the model size. This has a significant impact on the training results. If you believe that the previous model itself is good but the training hyperparameters or model hyperparameters are not optimal, you can return the same model and adjust these parameters instead.\n"
|
||||
|
||||
if len(trace.hist) == 0:
|
||||
hypothesis_and_feedback = "No previous hypothesis and feedback available since it's the first round."
|
||||
else:
|
||||
specific_trace = Trace(trace.scen)
|
||||
if action == "factor":
|
||||
# all factor experiments and the SOTA model experiment
|
||||
model_inserted = False
|
||||
for i in range(len(trace.hist) - 1, -1, -1): # Reverse iteration
|
||||
if trace.hist[i][0].hypothesis.action == "factor":
|
||||
specific_trace.hist.insert(0, trace.hist[i])
|
||||
elif (
|
||||
trace.hist[i][0].hypothesis.action == "model"
|
||||
and trace.hist[i][1].decision is True
|
||||
and model_inserted == False
|
||||
):
|
||||
specific_trace.hist.insert(0, trace.hist[i])
|
||||
model_inserted = True
|
||||
elif action == "model":
|
||||
# all model experiments and all SOTA factor experiments
|
||||
factor_inserted = False
|
||||
for i in range(len(trace.hist) - 1, -1, -1): # Reverse iteration
|
||||
if trace.hist[i][0].hypothesis.action == "model":
|
||||
specific_trace.hist.insert(0, trace.hist[i])
|
||||
elif (
|
||||
trace.hist[i][0].hypothesis.action == "factor"
|
||||
and trace.hist[i][1].decision is True
|
||||
and factor_inserted == False
|
||||
):
|
||||
specific_trace.hist.insert(0, trace.hist[i])
|
||||
factor_inserted = True
|
||||
if len(specific_trace.hist) > 0:
|
||||
specific_trace.hist.reverse()
|
||||
hypothesis_and_feedback = T("scenarios.qlib.prompts:hypothesis_and_feedback").r(
|
||||
trace=specific_trace,
|
||||
)
|
||||
else:
|
||||
hypothesis_and_feedback = "No previous hypothesis and feedback available."
|
||||
|
||||
last_hypothesis_and_feedback = None
|
||||
for i in range(len(trace.hist) - 1, -1, -1):
|
||||
if trace.hist[i][0].hypothesis.action == action:
|
||||
last_hypothesis_and_feedback = T("scenarios.qlib.prompts:last_hypothesis_and_feedback").r(
|
||||
experiment=trace.hist[i][0], feedback=trace.hist[i][1]
|
||||
)
|
||||
break
|
||||
|
||||
sota_hypothesis_and_feedback = None
|
||||
if action == "model":
|
||||
for i in range(len(trace.hist) - 1, -1, -1):
|
||||
if trace.hist[i][0].hypothesis.action == "model" and trace.hist[i][1].decision is True:
|
||||
sota_hypothesis_and_feedback = T("scenarios.qlib.prompts:sota_hypothesis_and_feedback").r(
|
||||
experiment=trace.hist[i][0], feedback=trace.hist[i][1]
|
||||
)
|
||||
break
|
||||
|
||||
context_dict = {
|
||||
"hypothesis_and_feedback": hypothesis_and_feedback,
|
||||
"last_hypothesis_and_feedback": last_hypothesis_and_feedback,
|
||||
"SOTA_hypothesis_and_feedback": sota_hypothesis_and_feedback,
|
||||
"RAG": qaunt_rag,
|
||||
"hypothesis_output_format": T("scenarios.qlib.prompts:hypothesis_output_format_with_action").r(),
|
||||
"hypothesis_specification": (
|
||||
T("scenarios.qlib.prompts:factor_hypothesis_specification").r()
|
||||
if action == "factor"
|
||||
else T("scenarios.qlib.prompts:model_hypothesis_specification").r()
|
||||
),
|
||||
}
|
||||
return context_dict, True
|
||||
|
||||
def convert_response(self, response: str) -> Hypothesis:
|
||||
response_dict = json.loads(response)
|
||||
hypothesis = QlibQuantHypothesis(
|
||||
hypothesis=response_dict.get("hypothesis"),
|
||||
reason=response_dict.get("reason"),
|
||||
concise_reason=response_dict.get("concise_reason"),
|
||||
concise_observation=response_dict.get("concise_observation"),
|
||||
concise_justification=response_dict.get("concise_justification"),
|
||||
concise_knowledge=response_dict.get("concise_knowledge"),
|
||||
action=response_dict.get("action"),
|
||||
)
|
||||
return hypothesis
|
||||
+125
-39
@@ -11,6 +11,7 @@ import json
|
||||
import os
|
||||
import pickle
|
||||
import re
|
||||
import select
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
@@ -69,7 +70,7 @@ def pull_image_with_progress(image: str) -> None:
|
||||
class EnvConf(ExtendedBaseSettings):
|
||||
default_entry: str
|
||||
extra_volumes: dict = {}
|
||||
running_timeout_period: int = 600 # 10 minutes
|
||||
running_timeout_period: int = 3600 # 10 minutes
|
||||
# helper settings to support transparent;
|
||||
enable_cache: bool = True
|
||||
retry_count: int = 5 # retry count for the docker run
|
||||
@@ -307,6 +308,33 @@ class Env(Generic[ASpecificEnvConf]):
|
||||
"""
|
||||
pass
|
||||
|
||||
def dump_python_code_run_and_get_results(
|
||||
self,
|
||||
code: str,
|
||||
dump_file_names: list[str],
|
||||
local_path: str,
|
||||
env: dict | None = None,
|
||||
running_extra_volume: Mapping = MappingProxyType({}),
|
||||
code_dump_file_py_name: Optional[str] = None,
|
||||
) -> tuple[str, list]:
|
||||
"""
|
||||
Dump the code into the local path and run the code.
|
||||
"""
|
||||
random_file_name = f"{uuid.uuid4()}.py" if code_dump_file_py_name is None else f"{code_dump_file_py_name}.py"
|
||||
with open(os.path.join(local_path, random_file_name), "w") as f:
|
||||
f.write(code)
|
||||
entry = f"python {random_file_name}"
|
||||
log_output = self.run(entry, local_path, env, running_extra_volume=dict(running_extra_volume))
|
||||
results = []
|
||||
os.remove(os.path.join(local_path, random_file_name))
|
||||
for name in dump_file_names:
|
||||
if os.path.exists(os.path.join(local_path, f"{name}")):
|
||||
results.append(pickle.load(open(os.path.join(local_path, f"{name}"), "rb")))
|
||||
os.remove(os.path.join(local_path, f"{name}"))
|
||||
else:
|
||||
return log_output, []
|
||||
return log_output, results
|
||||
|
||||
|
||||
# class EnvWithCache
|
||||
#
|
||||
@@ -339,7 +367,8 @@ class LocalEnv(Env[ASpecificLocalConf]):
|
||||
running_extra_volume: Mapping = MappingProxyType({}),
|
||||
**kwargs: dict,
|
||||
) -> tuple[str, int]:
|
||||
# mocking the volumes
|
||||
|
||||
# Handle volume links
|
||||
volumes = {}
|
||||
if self.conf.extra_volumes is not None:
|
||||
for lp, rp in self.conf.extra_volumes.items():
|
||||
@@ -349,7 +378,6 @@ class LocalEnv(Env[ASpecificLocalConf]):
|
||||
volumes[cache_path] = "/tmp/cache"
|
||||
for lp, rp in running_extra_volume.items():
|
||||
volumes[lp] = rp
|
||||
|
||||
for rp, lp in volumes.items():
|
||||
link_path = Path(lp)
|
||||
real_path = Path(rp)
|
||||
@@ -359,9 +387,9 @@ class LocalEnv(Env[ASpecificLocalConf]):
|
||||
link_path.unlink()
|
||||
link_path.symlink_to(real_path)
|
||||
|
||||
# Setup environment
|
||||
if env is None:
|
||||
env = {}
|
||||
|
||||
path = [*self.conf.bin_path.split(":"), "/bin/", "/usr/bin/", *env.get("PATH", "").split(":")]
|
||||
env["PATH"] = ":".join(path)
|
||||
|
||||
@@ -373,21 +401,68 @@ class LocalEnv(Env[ASpecificLocalConf]):
|
||||
table.add_column("Key", style="bold cyan")
|
||||
table.add_column("Value", style="bold magenta")
|
||||
table.add_row("Entry", entry)
|
||||
table.add_row("Local Path", local_path)
|
||||
table.add_row("Local Path", local_path or "")
|
||||
table.add_row("Env", "\n".join(f"{k}:{v}" for k, v in env.items()))
|
||||
table.add_row("Volumes", "\n".join(f"{k}:{v}" for k, v in volumes.items()))
|
||||
print(table)
|
||||
|
||||
cwd = None
|
||||
if local_path:
|
||||
cwd = Path(local_path).resolve()
|
||||
cwd = Path(local_path).resolve() if local_path else None
|
||||
env = {k: str(v) if isinstance(v, int) else v for k, v in env.items()}
|
||||
|
||||
result = subprocess.run(entry, cwd=cwd, env={**os.environ, **env}, capture_output=True, text=True, shell=True)
|
||||
combined_output = result.stderr + result.stdout # Combine stdout and stderr
|
||||
Console().print(combined_output, markup=False)
|
||||
process = subprocess.Popen(
|
||||
entry,
|
||||
cwd=cwd,
|
||||
env={**os.environ, **env},
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
shell=True,
|
||||
bufsize=1,
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
# Setup polling
|
||||
if process.stdout is None or process.stderr is None:
|
||||
raise RuntimeError("The subprocess did not correctly create stdout/stderr pipes")
|
||||
|
||||
stdout_fd = process.stdout.fileno()
|
||||
stderr_fd = process.stderr.fileno()
|
||||
|
||||
poller = select.poll()
|
||||
poller.register(stdout_fd, select.POLLIN)
|
||||
poller.register(stderr_fd, select.POLLIN)
|
||||
|
||||
combined_output = ""
|
||||
while True:
|
||||
if process.poll() is not None:
|
||||
break
|
||||
events = poller.poll(100)
|
||||
for fd, event in events:
|
||||
if event & select.POLLIN:
|
||||
if fd == stdout_fd:
|
||||
output = process.stdout.readline()
|
||||
if output:
|
||||
Console().print(output.strip(), markup=False)
|
||||
combined_output += output
|
||||
elif fd == stderr_fd:
|
||||
error = process.stderr.readline()
|
||||
if error:
|
||||
Console().print(error.strip(), markup=False)
|
||||
combined_output += error
|
||||
|
||||
# Capture any final output
|
||||
remaining_output, remaining_error = process.communicate()
|
||||
if remaining_output:
|
||||
Console().print(remaining_output.strip(), markup=False)
|
||||
combined_output += remaining_output
|
||||
if remaining_error:
|
||||
Console().print(remaining_error.strip(), markup=False)
|
||||
combined_output += remaining_error
|
||||
|
||||
return_code = process.returncode
|
||||
print(Rule("[bold green]LocalEnv Logs End[/bold green]", style="dark_orange"))
|
||||
|
||||
return combined_output, result.returncode
|
||||
return combined_output, return_code
|
||||
|
||||
|
||||
class CondaConf(LocalConf):
|
||||
@@ -439,6 +514,44 @@ class DockerConf(EnvConf):
|
||||
retry_wait_seconds: int = 10 # retry wait seconds for the docker run
|
||||
|
||||
|
||||
class QlibCondaConf(CondaConf):
|
||||
conda_env_name: str = "rdagent4qlib"
|
||||
enable_cache: bool = False
|
||||
default_entry: str = "qrun conf.yaml"
|
||||
# extra_volumes: dict = {str(Path("~/.qlib/").expanduser().resolve().absolute()): "/root/.qlib/"}
|
||||
|
||||
|
||||
class QlibCondaEnv(LocalEnv[QlibCondaConf]):
|
||||
def prepare(self) -> None:
|
||||
"""Prepare the conda environment if not already created."""
|
||||
try:
|
||||
envs = subprocess.run("conda env list", capture_output=True, text=True, shell=True)
|
||||
if self.conf.conda_env_name not in envs.stdout:
|
||||
print(f"[yellow]Conda env '{self.conf.conda_env_name}' not found, creating...[/yellow]")
|
||||
subprocess.check_call(
|
||||
f"conda create -y -n {self.conf.conda_env_name} python=3.10",
|
||||
shell=True,
|
||||
)
|
||||
subprocess.check_call(
|
||||
f"conda run -n {self.conf.conda_env_name} pip install --upgrade pip cython",
|
||||
shell=True,
|
||||
)
|
||||
subprocess.check_call(
|
||||
f"conda run -n {self.conf.conda_env_name} rm -rf qlib; git clone https://github.com/microsoft/qlib.git",
|
||||
shell=True,
|
||||
)
|
||||
subprocess.check_call(
|
||||
f"conda run -n {self.conf.conda_env_name} bash -c 'cd qlib && git reset --hard 3e72593b8c985f01979bebcf646658002ac43b00 && make dev .'",
|
||||
shell=True,
|
||||
)
|
||||
subprocess.check_call(
|
||||
f"conda run -n {self.conf.conda_env_name} pip install catboost xgboost scipy==1.11.4 tables torch",
|
||||
shell=True,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[red]Failed to prepare conda env: {e}[/red]")
|
||||
|
||||
|
||||
class QlibDockerConf(DockerConf):
|
||||
model_config = SettingsConfigDict(env_prefix="QLIB_DOCKER_")
|
||||
|
||||
@@ -706,33 +819,6 @@ class DockerEnv(Env[DockerConf]):
|
||||
except docker.errors.APIError as e:
|
||||
raise RuntimeError(f"Error while running the container: {e}")
|
||||
|
||||
def dump_python_code_run_and_get_results(
|
||||
self,
|
||||
code: str,
|
||||
dump_file_names: list[str],
|
||||
local_path: str,
|
||||
env: dict | None = None,
|
||||
running_extra_volume: Mapping = MappingProxyType({}),
|
||||
code_dump_file_py_name: Optional[str] = None,
|
||||
) -> tuple[str, list]:
|
||||
"""
|
||||
Dump the code into the local path and run the code.
|
||||
"""
|
||||
random_file_name = f"{uuid.uuid4()}.py" if code_dump_file_py_name is None else f"{code_dump_file_py_name}.py"
|
||||
with open(os.path.join(local_path, random_file_name), "w") as f:
|
||||
f.write(code)
|
||||
entry = f"python {random_file_name}"
|
||||
log_output = self.run(entry, local_path, env, running_extra_volume=dict(running_extra_volume))
|
||||
results = []
|
||||
os.remove(os.path.join(local_path, random_file_name))
|
||||
for name in dump_file_names:
|
||||
if os.path.exists(os.path.join(local_path, f"{name}")):
|
||||
results.append(pickle.load(open(os.path.join(local_path, f"{name}"), "rb")))
|
||||
os.remove(os.path.join(local_path, f"{name}"))
|
||||
else:
|
||||
return log_output, []
|
||||
return log_output, results
|
||||
|
||||
|
||||
class QTDockerEnv(DockerEnv):
|
||||
"""Qlib Torch Docker"""
|
||||
|
||||
Reference in New Issue
Block a user