mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 23:47:46 +00:00
help yuante on the final version of data code
This commit is contained in:
@@ -25,7 +25,7 @@ hypothesis2experiment: Hypothesis2Experiment = import_class(PROP_SETTING.qlib_mo
|
||||
qlib_model_coder: TaskGenerator = import_class(PROP_SETTING.qlib_model_coder)(scen)
|
||||
qlib_model_runner: TaskGenerator = import_class(PROP_SETTING.qlib_model_runner)(scen)
|
||||
|
||||
qlib_model_summarizer: HypothesisExperiment2Feedback = import_class(PROP_SETTING.qlib_model_hypothesis2experiment)()
|
||||
qlib_model_summarizer: HypothesisExperiment2Feedback = import_class(PROP_SETTING.qlib_model_summarizer)()
|
||||
|
||||
trace = Trace(scen=scen)
|
||||
for _ in range(PROP_SETTING.evolving_n):
|
||||
|
||||
@@ -84,7 +84,7 @@ class FactorCodeEvaluator(FactorEvaluator):
|
||||
gt_implementation: Implementation = None,
|
||||
**kwargs,
|
||||
):
|
||||
factor_information = target_task.get_factor_information()
|
||||
factor_information = target_task.get_task_information()
|
||||
code = implementation.code
|
||||
|
||||
system_prompt = (
|
||||
@@ -181,6 +181,28 @@ class FactorOutputFormatEvaluator(FactorEvaluator):
|
||||
)
|
||||
|
||||
|
||||
class FactorDatetimeDailyEvaluator(FactorEvaluator):
|
||||
def evaluate(
|
||||
self,
|
||||
implementation: Implementation,
|
||||
gt_implementation: Implementation,
|
||||
) -> Tuple[str | object]:
|
||||
_, gen_df = self._get_df(gt_implementation, implementation)
|
||||
if gen_df is None:
|
||||
return "The source dataframe is None. Skip the evaluation of the datetime format.", False
|
||||
|
||||
if "datetime" not in gen_df.index.names:
|
||||
return "The source dataframe does not have a datetime index. Please check the implementation.", False
|
||||
|
||||
time_diff = gen_df.index.get_level_values("datetime").to_series().diff().dropna().unique()
|
||||
if pd.Timedelta(minutes=1) in time_diff:
|
||||
return (
|
||||
"The generated dataframe is not daily. The implementation is definitely wrong. Please check the implementation.",
|
||||
False,
|
||||
)
|
||||
return "The generated dataframe is daily.", True
|
||||
|
||||
|
||||
class FactorRowCountEvaluator(FactorEvaluator):
|
||||
def evaluate(
|
||||
self,
|
||||
@@ -314,6 +336,9 @@ class FactorValueEvaluator(FactorEvaluator):
|
||||
feedback_str, _ = FactorOutputFormatEvaluator(self.scen).evaluate(implementation, gt_implementation)
|
||||
conclusions.append(feedback_str)
|
||||
|
||||
feedback_str, _ = FactorDatetimeDailyEvaluator(self.scen).evaluate(implementation, gt_implementation)
|
||||
conclusions.append(feedback_str)
|
||||
|
||||
# Check if both dataframe have the same rows count
|
||||
if gt_implementation is not None:
|
||||
feedback_str, _ = FactorRowCountEvaluator(self.scen).evaluate(implementation, gt_implementation)
|
||||
@@ -373,7 +398,7 @@ class FactorFinalDecisionEvaluator(Evaluator):
|
||||
evaluate_prompts["evaluator_final_decision_v1_user"],
|
||||
)
|
||||
.render(
|
||||
factor_information=target_task.get_factor_information(),
|
||||
factor_information=target_task.get_task_information(),
|
||||
execution_feedback=execution_feedback_to_render,
|
||||
code_feedback=code_feedback,
|
||||
factor_value_feedback=(
|
||||
@@ -475,7 +500,7 @@ class FactorEvaluatorForCoder(FactorEvaluator):
|
||||
if implementation is None:
|
||||
return None
|
||||
|
||||
target_task_information = target_task.get_factor_information()
|
||||
target_task_information = target_task.get_task_information()
|
||||
if (
|
||||
queried_knowledge is not None
|
||||
and target_task_information in queried_knowledge.success_task_to_knowledge_dict
|
||||
|
||||
@@ -59,7 +59,7 @@ class MultiProcessEvolvingStrategy(EvolvingStrategy):
|
||||
# 1.找出需要evolve的factor
|
||||
to_be_finished_task_index = []
|
||||
for index, target_factor_task in enumerate(new_evo.sub_tasks):
|
||||
target_factor_task_desc = target_factor_task.get_factor_information()
|
||||
target_factor_task_desc = target_factor_task.get_task_information()
|
||||
if target_factor_task_desc in queried_knowledge.success_task_to_knowledge_dict:
|
||||
new_evo.sub_implementations[index] = queried_knowledge.success_task_to_knowledge_dict[
|
||||
target_factor_task_desc
|
||||
@@ -119,7 +119,7 @@ class FactorEvolvingStrategy(MultiProcessEvolvingStrategy):
|
||||
target_task: FactorTask,
|
||||
queried_knowledge: FactorQueriedKnowledgeV1 = None,
|
||||
) -> Implementation:
|
||||
factor_information_str = target_task.get_factor_information()
|
||||
factor_information_str = target_task.get_task_information()
|
||||
|
||||
if queried_knowledge is not None and factor_information_str in queried_knowledge.success_task_to_knowledge_dict:
|
||||
return queried_knowledge.success_task_to_knowledge_dict[factor_information_str].implementation
|
||||
@@ -208,7 +208,7 @@ class FactorEvolvingStrategyWithGraph(MultiProcessEvolvingStrategy):
|
||||
) -> Implementation:
|
||||
error_summary = FACTOR_IMPLEMENT_SETTINGS.v2_error_summary
|
||||
# 1. 提取因子的背景信息
|
||||
target_factor_task_information = target_task.get_factor_information()
|
||||
target_factor_task_information = target_task.get_task_information()
|
||||
|
||||
# 2. 检查该因子是否需要继续做(是否已经作对,是否做错太多)
|
||||
if (
|
||||
|
||||
@@ -114,7 +114,7 @@ class FactorRAGStrategyV1(RAGStrategy):
|
||||
feedback = evo_step.feedback
|
||||
for task_index in range(len(implementations.sub_tasks)):
|
||||
target_task = implementations.sub_tasks[task_index]
|
||||
target_task_information = target_task.get_factor_information()
|
||||
target_task_information = target_task.get_task_information()
|
||||
implementation = implementations.sub_implementations[task_index]
|
||||
single_feedback = feedback[task_index]
|
||||
if single_feedback is None:
|
||||
@@ -147,7 +147,7 @@ class FactorRAGStrategyV1(RAGStrategy):
|
||||
|
||||
queried_knowledge = FactorQueriedKnowledgeV1()
|
||||
for target_factor_task in evo.sub_tasks:
|
||||
target_factor_task_information = target_factor_task.get_factor_information()
|
||||
target_factor_task_information = target_factor_task.get_task_information()
|
||||
if target_factor_task_information in self.knowledgebase.success_task_info_set:
|
||||
queried_knowledge.success_task_to_knowledge_dict[target_factor_task_information] = (
|
||||
self.knowledgebase.implementation_trace[target_factor_task_information][-1]
|
||||
@@ -233,7 +233,7 @@ class FactorGraphRAGStrategy(RAGStrategy):
|
||||
for task_index in range(len(implementations.sub_tasks)):
|
||||
single_feedback = feedback[task_index]
|
||||
target_task = implementations.sub_tasks[task_index]
|
||||
target_task_information = target_task.get_factor_information()
|
||||
target_task_information = target_task.get_task_information()
|
||||
implementation = implementations.sub_implementations[task_index]
|
||||
single_feedback = feedback[task_index]
|
||||
if single_feedback is None:
|
||||
@@ -395,7 +395,7 @@ class FactorGraphRAGStrategy(RAGStrategy):
|
||||
fail_task_trial_limit = FACTOR_IMPLEMENT_SETTINGS.fail_task_trial_limit
|
||||
|
||||
for target_factor_task in evo.sub_tasks:
|
||||
target_factor_task_information = target_factor_task.get_factor_information()
|
||||
target_factor_task_information = target_factor_task.get_task_information()
|
||||
if (
|
||||
target_factor_task_information not in self.knowledgebase.success_task_to_knowledge_dict
|
||||
and target_factor_task_information in self.knowledgebase.working_trace_knowledge
|
||||
@@ -442,7 +442,7 @@ class FactorGraphRAGStrategy(RAGStrategy):
|
||||
) -> QueriedKnowledge | None:
|
||||
# queried_component_knowledge = FactorQueriedGraphComponentKnowledge()
|
||||
for target_factor_task in evo.sub_tasks:
|
||||
target_factor_task_information = target_factor_task.get_factor_information()
|
||||
target_factor_task_information = target_factor_task.get_task_information()
|
||||
if (
|
||||
target_factor_task_information in self.knowledgebase.success_task_to_knowledge_dict
|
||||
or target_factor_task_information in factor_implementation_queried_graph_knowledge.failed_task_info_set
|
||||
@@ -582,7 +582,7 @@ class FactorGraphRAGStrategy(RAGStrategy):
|
||||
) -> QueriedKnowledge | None:
|
||||
# queried_error_knowledge = FactorQueriedGraphErrorKnowledge()
|
||||
for task_index, target_factor_task in enumerate(evo.sub_tasks):
|
||||
target_factor_task_information = target_factor_task.get_factor_information()
|
||||
target_factor_task_information = target_factor_task.get_task_information()
|
||||
factor_implementation_queried_graph_knowledge.error_with_success_task[target_factor_task_information] = {}
|
||||
if (
|
||||
target_factor_task_information in self.knowledgebase.success_task_to_knowledge_dict
|
||||
|
||||
@@ -39,7 +39,7 @@ def LLMSelect(
|
||||
tasks = []
|
||||
for i in to_be_finished_task_index:
|
||||
# find corresponding former trace for each task
|
||||
target_factor_task_information = evo.sub_tasks[i].get_factor_information()
|
||||
target_factor_task_information = evo.sub_tasks[i].get_task_information()
|
||||
if target_factor_task_information in former_trace:
|
||||
tasks.append((i, evo.sub_tasks[i], former_trace[target_factor_task_information]))
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class FactorTask(Task):
|
||||
self.variables = variables
|
||||
self.factor_resources = resource
|
||||
|
||||
def get_factor_information(self):
|
||||
def get_task_information(self):
|
||||
return f"""factor_name: {self.factor_name}
|
||||
factor_description: {self.factor_description}
|
||||
factor_formulation: {self.factor_formulation}
|
||||
|
||||
@@ -68,7 +68,7 @@ evolving_strategy_factor_implementation_v1_user: |-
|
||||
--------------Correct code to similar factors:---------------
|
||||
{% for similar_successful_knowledge in queried_similar_successful_knowledge %}
|
||||
=====Factor {{loop.index}}:=====
|
||||
{{ similar_successful_knowledge.target_task.get_factor_information() }}
|
||||
{{ similar_successful_knowledge.target_task.get_task_information() }}
|
||||
=====Code:=====
|
||||
{{ similar_successful_knowledge.implementation.code }}
|
||||
{% endfor %}
|
||||
@@ -94,7 +94,7 @@ evolving_strategy_factor_implementation_v2_user: |-
|
||||
When doing other tasks, you met some similar errors but you finally solve them. Here are some examples:
|
||||
{% for error_content, similar_error_knowledge in queried_similar_error_knowledge %}
|
||||
--------------Factor information to similar error ({{error_content}}):---------------
|
||||
{{ similar_error_knowledge[0].target_task.get_factor_information() }}
|
||||
{{ similar_error_knowledge[0].target_task.get_task_information() }}
|
||||
=====Code with similar error ({{error_content}}):=====
|
||||
{{ similar_error_knowledge[0].implementation.code }}
|
||||
=====Success code to former code with similar error ({{error_content}}):=====
|
||||
@@ -111,7 +111,7 @@ evolving_strategy_factor_implementation_v2_user: |-
|
||||
--------------Correct code to similar factors:---------------
|
||||
{% for similar_component_knowledge in queried_similar_component_knowledge %}
|
||||
=====Factor {{loop.index}}:=====
|
||||
{{ similar_component_knowledge.target_task.get_factor_information() }}
|
||||
{{ similar_component_knowledge.target_task.get_task_information() }}
|
||||
=====Code:=====
|
||||
{{ similar_component_knowledge.implementation.code }}
|
||||
{% endfor %}
|
||||
@@ -137,7 +137,7 @@ evolving_strategy_error_summary_v2_user: |-
|
||||
{% if queried_similar_error_knowledge|length != 0 %}
|
||||
{% for error_content, similar_error_knowledge in queried_similar_error_knowledge %}
|
||||
--------------Factor information to similar error ({{error_content}}):---------------
|
||||
{{ similar_error_knowledge[0].target_task.get_factor_information() }}
|
||||
{{ similar_error_knowledge[0].target_task.get_task_information() }}
|
||||
=====Code with similar error ({{error_content}}):=====
|
||||
{{ similar_error_knowledge[0].implementation.code }}
|
||||
=====Success code to former code with similar error ({{error_content}}):=====
|
||||
|
||||
@@ -72,7 +72,7 @@ class ModelCodeEvaluator(Evaluator):
|
||||
if gt_implementation is not None:
|
||||
assert isinstance(gt_implementation, ModelImplementation)
|
||||
|
||||
model_task_information = target_task.get_information()
|
||||
model_task_information = target_task.get_task_information()
|
||||
code = implementation.code
|
||||
|
||||
system_prompt = (
|
||||
@@ -146,7 +146,7 @@ class ModelFinalEvaluator(Evaluator):
|
||||
evaluate_prompts["evaluator_final_feedback"]["user"],
|
||||
)
|
||||
.render(
|
||||
model_information=target_task.get_information(),
|
||||
model_information=target_task.get_task_information(),
|
||||
model_execution_feedback=execution_feedback_to_render,
|
||||
model_code_feedback=model_code_feedback,
|
||||
model_value_feedback=model_value_feedback,
|
||||
@@ -224,7 +224,7 @@ class ModelCoderEvaluator(Evaluator):
|
||||
queried_knowledge: QueriedKnowledge = None,
|
||||
**kwargs,
|
||||
) -> ModelCoderFeedback:
|
||||
target_task_information = target_task.get_information()
|
||||
target_task_information = target_task.get_task_information()
|
||||
if (
|
||||
queried_knowledge is not None
|
||||
and target_task_information in queried_knowledge.success_task_to_knowledge_dict
|
||||
|
||||
@@ -27,7 +27,7 @@ class ModelCoderEvolvingStrategy(EvolvingStrategy):
|
||||
target_task: ModelTask,
|
||||
queried_knowledge: ModelQueriedKnowledge = None,
|
||||
) -> ModelImplementation:
|
||||
model_information_str = target_task.get_information()
|
||||
model_information_str = target_task.get_task_information()
|
||||
|
||||
if queried_knowledge is not None and model_information_str in queried_knowledge.success_task_to_knowledge_dict:
|
||||
return queried_knowledge.success_task_to_knowledge_dict[model_information_str].implementation
|
||||
@@ -113,7 +113,7 @@ class ModelCoderEvolvingStrategy(EvolvingStrategy):
|
||||
# 1.找出需要evolve的model
|
||||
to_be_finished_task_index = []
|
||||
for index, target_model_task in enumerate(new_evo.sub_tasks):
|
||||
target_model_task_desc = target_model_task.get_information()
|
||||
target_model_task_desc = target_model_task.get_task_information()
|
||||
if target_model_task_desc in queried_knowledge.success_task_to_knowledge_dict:
|
||||
new_evo.sub_implementations[index] = queried_knowledge.success_task_to_knowledge_dict[
|
||||
target_model_task_desc
|
||||
|
||||
@@ -86,7 +86,7 @@ class ModelRAGStrategy(RAGStrategy):
|
||||
feedback = evo_step.feedback
|
||||
for task_index in range(len(implementations.sub_tasks)):
|
||||
target_task = implementations.sub_tasks[task_index]
|
||||
target_task_information = target_task.get_information()
|
||||
target_task_information = target_task.get_task_information()
|
||||
implementation = implementations.sub_implementations[task_index]
|
||||
single_feedback = feedback[task_index]
|
||||
if single_feedback is None:
|
||||
@@ -119,7 +119,7 @@ class ModelRAGStrategy(RAGStrategy):
|
||||
|
||||
queried_knowledge = ModelQueriedKnowledge()
|
||||
for target_model_task in evo.sub_tasks:
|
||||
target_model_task_information = target_model_task.get_information()
|
||||
target_model_task_information = target_model_task.get_task_information()
|
||||
if target_model_task_information in self.knowledgebase.success_task_info_set:
|
||||
queried_knowledge.success_task_to_knowledge_dict[target_model_task_information] = (
|
||||
self.knowledgebase.implementation_trace[target_model_task_information][-1]
|
||||
|
||||
@@ -24,7 +24,7 @@ class ModelTask(Task):
|
||||
self.variables: str = variables
|
||||
self.model_type: str = model_type # Tabular for tabular model, TimesSeries for time series model
|
||||
|
||||
def get_information(self):
|
||||
def get_task_information(self):
|
||||
return f"""name: {self.name}
|
||||
description: {self.description}
|
||||
formulation: {self.formulation}
|
||||
|
||||
@@ -7,11 +7,17 @@ This file contains the all the class about organizing the task in RD-Agent.
|
||||
"""
|
||||
|
||||
|
||||
class Task:
|
||||
class Task(ABC):
|
||||
# TODO: 把name放在这里作为主键
|
||||
# Please refer to rdagent/model_implementation/task.py for the implementation
|
||||
# I think the task version applies to the base class.
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_task_information(self):
|
||||
"""
|
||||
Get the task information string to build the unique key
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
ASpecificTask = TypeVar("ASpecificTask", bound=Task)
|
||||
@@ -117,6 +123,7 @@ class Experiment(ABC, Generic[ASpecificTask, ASpecificImp]):
|
||||
"""
|
||||
The experiment is a sequence of tasks and the implementations of the tasks after generated by the TaskGenerator.
|
||||
"""
|
||||
|
||||
result_ws: Optional[FBImplementation]
|
||||
|
||||
def __init__(self, sub_tasks: Sequence[ASpecificTask]) -> None:
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
# make sure that env variable is loaded while calling Config()
|
||||
load_dotenv(verbose=True, override=True)
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class QlibRDAgentSettings(BaseSettings):
|
||||
runner_cache_result: bool = True # whether to cache the result of the docker execution
|
||||
runner_cache_path: str = str(Path.cwd() / "runner_cache/") # the path to store the cache
|
||||
|
||||
|
||||
Qlib_RD_AGENT_SETTINGS = QlibRDAgentSettings()
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM pytorch/pytorch:latest
|
||||
|
||||
RUN apt-get clean && apt-get update && apt-get install -y \
|
||||
curl \
|
||||
vim \
|
||||
git \
|
||||
build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN git clone https://github.com/microsoft/qlib.git
|
||||
|
||||
WORKDIR /workspace/qlib
|
||||
|
||||
RUN git reset c9ed050ef034fe6519c14b59f3d207abcb693282 --hard
|
||||
|
||||
RUN python -m pip install --upgrade numpy
|
||||
RUN python -m pip install --upgrade cython
|
||||
RUN python -m pip install -e .
|
||||
|
||||
RUN pip install catboost
|
||||
RUN pip install xgboost
|
||||
@@ -1,20 +1,23 @@
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from typing import List
|
||||
import pandas as pd
|
||||
import pickle
|
||||
from rdagent.app.qlib_rd_loop.conf import PROP_SETTING
|
||||
from rdagent.core.task_generator import TaskGenerator
|
||||
from rdagent.utils.env import QTDockerEnv, LocalConf, LocalEnv
|
||||
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from rdagent.core.log import RDAgentLog
|
||||
from rdagent.core.task_generator import TaskGenerator
|
||||
from rdagent.oai.llm_utils import md5_hash
|
||||
from rdagent.scenarios.qlib.conf import Qlib_RD_AGENT_SETTINGS
|
||||
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
|
||||
from rdagent.utils.env import QTDockerEnv
|
||||
|
||||
DIRNAME = Path(__file__).absolute().resolve().parent
|
||||
DIRNAME_local = Path.cwd()
|
||||
logger = RDAgentLog()
|
||||
|
||||
# class QlibFactorExpWorkspace:
|
||||
|
||||
|
||||
# def prepare():
|
||||
# # create a folder;
|
||||
# # copy template
|
||||
@@ -26,6 +29,7 @@ logger = RDAgentLog()
|
||||
|
||||
# TODO: supporting multiprocessing and keep previous results
|
||||
|
||||
|
||||
class QlibFactorRunner(TaskGenerator[QlibFactorExperiment]):
|
||||
"""
|
||||
Docker run
|
||||
@@ -35,141 +39,95 @@ class QlibFactorRunner(TaskGenerator[QlibFactorExperiment]):
|
||||
- `data.py` + Adaptor to Factor implementation
|
||||
- results in `mlflow`
|
||||
"""
|
||||
|
||||
def FetchAlpha158ResultFromDocker(self):
|
||||
"""
|
||||
Run Docker to get alpha158 result.
|
||||
|
||||
This method prepares the Qlib Docker environment, executes the necessary commands to
|
||||
run the backtest, and fetches the results stored in a pickle file.
|
||||
def get_cache_key(self, exp: QlibFactorExperiment) -> str:
|
||||
all_tasks = []
|
||||
for based_exp in exp.based_experiments:
|
||||
all_tasks.extend(based_exp.sub_tasks)
|
||||
all_tasks.extend(exp.sub_tasks)
|
||||
task_info_list = [task.get_task_information() for task in all_tasks]
|
||||
task_info_str = "\n".join(task_info_list)
|
||||
return md5_hash(task_info_str)
|
||||
|
||||
Returns:
|
||||
Any: The alpha158 result. If successful, returns a pandas DataFrame. Otherwise, returns None.
|
||||
"""
|
||||
# Initialize and prepare the Qlib Docker environment
|
||||
qtde = QTDockerEnv()
|
||||
qtde.prepare()
|
||||
|
||||
# Clean up any previous run artifacts by deleting the mlruns directory
|
||||
result = qtde.run(local_path=str(DIRNAME / "env_factor"), entry="rm -r mlruns", env={"PYTHONPATH": "./"})
|
||||
|
||||
# Run the Qlib backtest using the configuration file conf.yaml
|
||||
result = qtde.run(local_path=str(DIRNAME / "env_factor"), entry="qrun conf.yaml", env={"PYTHONPATH": "./"})
|
||||
|
||||
# Execute a Python script to extract the experiment results
|
||||
result = qtde.run(local_path=str(DIRNAME / "env_factor"), entry="python read_exp_res.py")
|
||||
|
||||
pkl_path = DIRNAME / 'env_factor/qlib_res.pkl'
|
||||
|
||||
if not pkl_path.exists():
|
||||
logger.error(f"File {pkl_path} does not exist.")
|
||||
return None
|
||||
|
||||
with open(pkl_path, 'rb') as f:
|
||||
result = pickle.load(f)
|
||||
|
||||
# Check if the loaded result is a pandas DataFrame and not empty
|
||||
if isinstance(result, pd.DataFrame):
|
||||
if not result.empty:
|
||||
logger.info("Successfully retrieved alpha158 result.")
|
||||
return result
|
||||
else:
|
||||
logger.error("Result DataFrame is empty.")
|
||||
return None
|
||||
def get_cache_result(self, exp: QlibFactorExperiment) -> Tuple[bool, object]:
|
||||
task_info_key = self.get_cache_key(exp)
|
||||
Path(Qlib_RD_AGENT_SETTINGS.runner_cache_path).mkdir(parents=True, exist_ok=True)
|
||||
cache_path = Path(Qlib_RD_AGENT_SETTINGS.runner_cache_path) / f"{task_info_key}.pkl"
|
||||
if cache_path.exists():
|
||||
return True, pickle.load(open(cache_path, "rb"))
|
||||
else:
|
||||
logger.error("Data format error.")
|
||||
return None
|
||||
return False, None
|
||||
|
||||
def dump_cache_result(self, exp: QlibFactorExperiment, result: object):
|
||||
task_info_key = self.get_cache_key(exp)
|
||||
cache_path = Path(Qlib_RD_AGENT_SETTINGS.runner_cache_path) / f"{task_info_key}.pkl"
|
||||
pickle.dump(result, open(cache_path, "wb"))
|
||||
|
||||
def generate(self, exp: QlibFactorExperiment) -> QlibFactorExperiment:
|
||||
"""
|
||||
Generate the experiment by processing and combining factor data,
|
||||
then passing the combined data to Docker for backtest results.
|
||||
"""
|
||||
SOTA_factor = None
|
||||
if exp.based_experiments.__len__() != 1:
|
||||
SOTA_factor = self.process_factor_data(exp.based_experiments)
|
||||
|
||||
if exp.based_experiments[-1].result is None:
|
||||
exp.based_experiments[-1].result = self.FetchAlpha158ResultFromDocker()
|
||||
|
||||
# Process the new factors data
|
||||
new_factors = self.process_factor_data(exp)
|
||||
|
||||
# Combine the SOTA factor and new factors if SOTA factor exists
|
||||
if SOTA_factor is not None:
|
||||
combined_factors = pd.concat([SOTA_factor, new_factors], axis=1).dropna()
|
||||
else:
|
||||
combined_factors = new_factors
|
||||
|
||||
# Sort and nest the combined factors under 'feature'
|
||||
combined_factors = combined_factors.sort_index()
|
||||
new_columns = pd.MultiIndex.from_product([['feature'], combined_factors.columns])
|
||||
combined_factors.columns = new_columns
|
||||
|
||||
# logger.info(combined_factors)
|
||||
|
||||
# Save the combined factors to a pickle file
|
||||
combined_factors_path = DIRNAME / 'env_factor/combined_factors_df.pkl'
|
||||
with open(combined_factors_path, 'wb') as f:
|
||||
pickle.dump(combined_factors, f)
|
||||
if exp.based_experiments and exp.based_experiments[-1].result is None:
|
||||
exp.based_experiments[-1] = self.generate(exp.based_experiments[-1])
|
||||
|
||||
if Qlib_RD_AGENT_SETTINGS.runner_cache_result:
|
||||
cache_hit, result = self.get_cache_result(exp)
|
||||
if cache_hit:
|
||||
exp.result = result
|
||||
return exp
|
||||
|
||||
if exp.based_experiments:
|
||||
SOTA_factor = None
|
||||
if exp.based_experiments.__len__() != 1:
|
||||
SOTA_factor = self.process_factor_data(exp.based_experiments)
|
||||
|
||||
# Process the new factors data
|
||||
new_factors = self.process_factor_data(exp)
|
||||
|
||||
# Combine the SOTA factor and new factors if SOTA factor exists
|
||||
if SOTA_factor is not None and not SOTA_factor.empty:
|
||||
combined_factors = pd.concat([SOTA_factor, new_factors], axis=1).dropna()
|
||||
else:
|
||||
combined_factors = new_factors
|
||||
|
||||
# Sort and nest the combined factors under 'feature'
|
||||
combined_factors = combined_factors.sort_index()
|
||||
new_columns = pd.MultiIndex.from_product([["feature"], combined_factors.columns])
|
||||
combined_factors.columns = new_columns
|
||||
|
||||
# Save the combined factors to a pickle file
|
||||
combined_factors_path = DIRNAME / "env_factor/combined_factors_df.pkl"
|
||||
with open(combined_factors_path, "wb") as f:
|
||||
pickle.dump(combined_factors, f)
|
||||
|
||||
# Docker run
|
||||
# Call Docker, pass the combined factors to Docker, and generate backtest results
|
||||
qtde = QTDockerEnv()
|
||||
qtde.prepare()
|
||||
|
||||
|
||||
# Run the Docker command
|
||||
result = qtde.run(local_path=str(DIRNAME / "env_factor"), entry="rm -r mlruns", env={"PYTHONPATH": "./"})
|
||||
execute_log = qtde.run(local_path=str(DIRNAME / "env_factor"), entry="rm -r mlruns")
|
||||
# Run the Qlib backtest
|
||||
result = qtde.run(local_path=str(DIRNAME / "env_factor"), entry="qrun conf_combined.yaml", env={"PYTHONPATH": "./"})
|
||||
execute_log = qtde.run(
|
||||
local_path=str(DIRNAME / "env_factor"),
|
||||
entry=f"qrun conf.yaml" if len(exp.based_experiments) == 0 else "qrun conf_combined.yaml",
|
||||
)
|
||||
|
||||
result = qtde.run(local_path=str(DIRNAME / "env_factor"), entry="python read_exp_res.py")
|
||||
execute_log = qtde.run(local_path=str(DIRNAME / "env_factor"), entry="python read_exp_res.py")
|
||||
|
||||
pkl_path = DIRNAME / 'env_factor/qlib_res.pkl'
|
||||
pkl_path = DIRNAME / "env_factor/qlib_res.pkl"
|
||||
|
||||
if not pkl_path.exists():
|
||||
logger.error(f"File {pkl_path} does not exist.")
|
||||
return None
|
||||
|
||||
with open(pkl_path, 'rb') as f:
|
||||
with open(pkl_path, "rb") as f:
|
||||
result = pickle.load(f)
|
||||
|
||||
"""
|
||||
# TODO: Implement the Docker run in the following way
|
||||
# Local run
|
||||
# Clean up any previous run artifacts by deleting the mlruns directory
|
||||
mlruns_path = DIRNAME_local / 'mlruns' / '1'
|
||||
if mlruns_path.exists() and mlruns_path.is_dir():
|
||||
shutil.rmtree(mlruns_path)
|
||||
|
||||
# Prepare local Qlib environment
|
||||
local_conf = LocalConf(
|
||||
py_bin=PROP_SETTING.py_bin,
|
||||
default_entry="qrun conf_combined.yaml",
|
||||
)
|
||||
qle = LocalEnv(conf=local_conf)
|
||||
qle.prepare()
|
||||
conf_path = str(DIRNAME / "env_factor" / "conf_combined.yaml")
|
||||
qle.run(entry="qrun " + conf_path, local_path=PROP_SETTING.local_qlib_folder)
|
||||
|
||||
# Verify if the new folder is created
|
||||
mlrun_p = DIRNAME_local / 'mlruns' / '1'
|
||||
assert mlrun_p.exists(), f"Expected output file {mlrun_p} not found"
|
||||
|
||||
# Locate the newly generated folder in mlruns/1/
|
||||
new_folders = [folder for folder in mlrun_p.iterdir() if folder.is_dir()]
|
||||
if not new_folders:
|
||||
raise FileNotFoundError("No new folders found in 'mlruns/1/'.")
|
||||
|
||||
new_folder = new_folders[0] # Assuming there's only one new folder
|
||||
pickle_file = new_folder / 'artifacts' / 'portfolio_analysis' / 'port_analysis_1day.pkl'
|
||||
assert pickle_file.exists(), f"Expected pickle file {pickle_file} not found"
|
||||
|
||||
with open(pickle_file, 'rb') as f:
|
||||
result = pickle.load(f)
|
||||
"""
|
||||
exp.result = result
|
||||
if Qlib_RD_AGENT_SETTINGS.runner_cache_result:
|
||||
self.dump_cache_result(exp, result)
|
||||
|
||||
# Check if the result is valid and is a DataFrame
|
||||
if isinstance(result, pd.DataFrame):
|
||||
@@ -204,18 +162,14 @@ class QlibFactorRunner(TaskGenerator[QlibFactorExperiment]):
|
||||
message, df = implementation.execute()
|
||||
|
||||
# Check if factor generation was successful
|
||||
if 'Execution succeeded without error.\nExpected output file found.' in message:
|
||||
factor_dfs.append(df)
|
||||
if df is not None:
|
||||
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:
|
||||
combined_factors = pd.concat(factor_dfs, axis=1)
|
||||
|
||||
# Remove rows with NaN values
|
||||
combined_factors = combined_factors.dropna()
|
||||
|
||||
# print(combined_factors)
|
||||
return combined_factors
|
||||
return pd.concat(factor_dfs, axis=1)
|
||||
else:
|
||||
logger.error("No valid factor data found to merge.")
|
||||
return pd.DataFrame() # Return an empty DataFrame if no valid data
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
from pathlib import Path
|
||||
import qlib
|
||||
from mlflow.tracking import MlflowClient
|
||||
from mlflow.entities import ViewType
|
||||
import pandas as pd
|
||||
import pickle
|
||||
import os
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
import qlib
|
||||
from mlflow.entities import ViewType
|
||||
from mlflow.tracking import MlflowClient
|
||||
|
||||
qlib.init()
|
||||
|
||||
from qlib.workflow import R
|
||||
|
||||
# here is the documents of the https://qlib.readthedocs.io/en/latest/component/recorder.html
|
||||
|
||||
# TODO: list all the recorder and metrics
|
||||
# TODO: list all the recorder and metrics
|
||||
|
||||
# Assuming you have already listed the experiments
|
||||
experiments = R.list_experiments()
|
||||
@@ -26,8 +28,8 @@ for experiment in experiments:
|
||||
if recorder_id is not None:
|
||||
experiment_name = experiment
|
||||
recorder = R.get_recorder(recorder_id=recorder_id, experiment_name=experiment)
|
||||
end_time = recorder.info['end_time']
|
||||
if latest_recorder is None or end_time > latest_recorder.info['end_time']:
|
||||
end_time = recorder.info["end_time"]
|
||||
if latest_recorder is None or end_time > latest_recorder.info["end_time"]:
|
||||
latest_recorder = recorder
|
||||
|
||||
# Check if the latest recorder is found
|
||||
@@ -43,10 +45,9 @@ else:
|
||||
# Optionally convert to DataFrame if not already in DataFrame format
|
||||
if not isinstance(indicator_analysis_df, pd.DataFrame):
|
||||
indicator_analysis_df = pd.DataFrame(indicator_analysis_df)
|
||||
|
||||
|
||||
output_path = os.path.join(str(Path(__file__).resolve().parent), "qlib_res.pkl")
|
||||
with open(output_path, "wb") as f:
|
||||
pickle.dump(indicator_analysis_df, f)
|
||||
|
||||
print("here2")
|
||||
print(output_path)
|
||||
print(f"Output has been saved to {output_path}")
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
# TODO:
|
||||
# Implement to feedback.
|
||||
|
||||
import json
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import HypothesisExperiment2Feedback
|
||||
from rdagent.core.proposal import Trace
|
||||
|
||||
from rdagent.core.experiment import Experiment
|
||||
from rdagent.core.proposal import Hypothesis, HypothesisFeedback
|
||||
from rdagent.core.log import RDAgentLog
|
||||
from rdagent.core.prompts import Prompts
|
||||
from rdagent.core.proposal import (
|
||||
Hypothesis,
|
||||
HypothesisExperiment2Feedback,
|
||||
HypothesisFeedback,
|
||||
Trace,
|
||||
)
|
||||
from rdagent.oai.llm_utils import APIBackend
|
||||
from rdagent.utils.env import QTDockerEnv
|
||||
from rdagent.core.log import RDAgentLog
|
||||
import json
|
||||
import pandas as pd
|
||||
import pickle
|
||||
|
||||
feedback_prompts = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
|
||||
DIRNAME = Path(__file__).absolute().resolve().parent
|
||||
logger = RDAgentLog()
|
||||
|
||||
|
||||
class QlibModelHypothesisExperiment2Feedback(HypothesisExperiment2Feedback): ...
|
||||
|
||||
|
||||
class QlibFactorHypothesisExperiment2Feedback(HypothesisExperiment2Feedback):
|
||||
def generateFeedback(self, exp: Experiment, hypothesis: Hypothesis, trace: Trace) -> HypothesisFeedback:
|
||||
"""
|
||||
@@ -38,18 +44,26 @@ class QlibFactorHypothesisExperiment2Feedback(HypothesisExperiment2Feedback):
|
||||
logger.info("Generating feedback...")
|
||||
hypothesis_text = hypothesis.hypothesis
|
||||
current_result = exp.result
|
||||
tasks_factors = [task.get_factor_information() for task in exp.sub_tasks]
|
||||
tasks_factors = [task.get_task_information() for task in exp.sub_tasks]
|
||||
sota_result = exp.based_experiments[-1].result
|
||||
|
||||
# Generate the system prompt
|
||||
sys_prompt = Environment(undefined=StrictUndefined).from_string(feedback_prompts["data_feedback_generation"]["system"]).render(scenario=self.scen.get_scenario_all_desc())
|
||||
sys_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(feedback_prompts["data_feedback_generation"]["system"])
|
||||
.render(scenario=self.scen.get_scenario_all_desc())
|
||||
)
|
||||
|
||||
# Generate the user prompt
|
||||
usr_prompt = Environment(undefined=StrictUndefined).from_string(feedback_prompts["data_feedback_generation"]["user"]).render(
|
||||
hypothesis_text=hypothesis_text,
|
||||
task_details=tasks_factors,
|
||||
current_result=current_result,
|
||||
sota_result=sota_result
|
||||
usr_prompt = (
|
||||
Environment(undefined=StrictUndefined)
|
||||
.from_string(feedback_prompts["data_feedback_generation"]["user"])
|
||||
.render(
|
||||
hypothesis_text=hypothesis_text,
|
||||
task_details=tasks_factors,
|
||||
current_result=current_result,
|
||||
sota_result=sota_result,
|
||||
)
|
||||
)
|
||||
|
||||
# Call the APIBackend to generate the response for hypothesis feedback
|
||||
@@ -61,21 +75,21 @@ class QlibFactorHypothesisExperiment2Feedback(HypothesisExperiment2Feedback):
|
||||
|
||||
# Parse the JSON response to extract the feedback
|
||||
response_json = json.loads(response)
|
||||
|
||||
|
||||
# Extract fields from JSON response
|
||||
observations = response_json.get("Observations", "No observations provided")
|
||||
hypothesis_evaluation = response_json.get("Feedback for Hypothesis", "No feedback provided")
|
||||
new_hypothesis = response_json.get("New Hypothesis", "No new hypothesis provided")
|
||||
reason = response_json.get("Reasoning", "No reasoning provided")
|
||||
decision = response_json.get("Replace Best Result", "no").lower() == "yes"
|
||||
|
||||
|
||||
# Create HypothesisFeedback object
|
||||
hypothesis_feedback = HypothesisFeedback(
|
||||
observations=observations,
|
||||
hypothesis_evaluation=hypothesis_evaluation,
|
||||
new_hypothesis=new_hypothesis,
|
||||
reason=reason,
|
||||
decision=decision
|
||||
decision=decision,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
|
||||
+40
-29
@@ -5,14 +5,21 @@ Tries to create uniform environment for the agent to run;
|
||||
- All the code and data is expected included in one folder
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import docker
|
||||
import subprocess
|
||||
import sys
|
||||
from abc import abstractmethod
|
||||
from pydantic import BaseModel
|
||||
from typing import Generic, TypeVar, Optional, Dict
|
||||
from pathlib import Path
|
||||
from typing import Dict, Generic, Optional, TypeVar
|
||||
|
||||
import docker
|
||||
import docker.models
|
||||
import docker.models.containers
|
||||
from pydantic import BaseModel
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
from rdagent.core.log import RDAgentLog
|
||||
|
||||
ASpecificBaseModel = TypeVar("ASpecificBaseModel", bound=BaseModel)
|
||||
|
||||
@@ -71,6 +78,7 @@ class LocalEnv(Env[LocalConf]):
|
||||
"""
|
||||
Sometimes local environment may be more convinient for testing
|
||||
"""
|
||||
|
||||
def prepare(self):
|
||||
if not (Path("~/.qlib/qlib_data/cn_data").expanduser().resolve().exists()):
|
||||
self.run(
|
||||
@@ -79,10 +87,7 @@ class LocalEnv(Env[LocalConf]):
|
||||
else:
|
||||
print("Data already exists. Download skipped.")
|
||||
|
||||
def run(self,
|
||||
entry: str | None = None,
|
||||
local_path: Optional[str] = None,
|
||||
env: dict | None = None) -> str:
|
||||
def run(self, entry: str | None = None, local_path: Optional[str] = None, env: dict | None = None) -> str:
|
||||
if env is None:
|
||||
env = {}
|
||||
|
||||
@@ -94,15 +99,7 @@ class LocalEnv(Env[LocalConf]):
|
||||
cwd = None
|
||||
if local_path:
|
||||
cwd = Path(local_path).resolve()
|
||||
print(f"CWD: {cwd}")
|
||||
|
||||
result = subprocess.run(
|
||||
command,
|
||||
cwd=cwd,
|
||||
env={**os.environ, **env},
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
result = subprocess.run(command, cwd=cwd, env={**os.environ, **env}, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Error while running the command: {result.stderr}")
|
||||
@@ -113,8 +110,10 @@ class LocalEnv(Env[LocalConf]):
|
||||
## Docker Environment -----
|
||||
|
||||
|
||||
class DockerConf(BaseModel):
|
||||
image: str # the image you want to run
|
||||
class DockerConf(BaseSettings):
|
||||
build_from_dockerfile: bool = False
|
||||
dockerfile_folder_path: Path # the path to the dockerfile
|
||||
image: str # the image you want to build
|
||||
mount_path: str # the path in the docker image to mount the folder
|
||||
default_entry: str # the entry point of the image
|
||||
|
||||
@@ -122,14 +121,16 @@ class DockerConf(BaseModel):
|
||||
# Sometime, we need maintain some extra data for the workspace.
|
||||
# And the extra data may be shared and the downloading can be time consuming.
|
||||
# So we just want to download it once.
|
||||
network: str | None = "bridge" # the network mode for the docker
|
||||
|
||||
|
||||
QLIB_TORCH_IMAGE = DockerConf(
|
||||
image="linlanglv/qlib_image_nightly_pytorch:240711",
|
||||
mount_path="/workspace",
|
||||
default_entry="qrun conf.yaml",
|
||||
extra_volumes={Path("~/.qlib/").expanduser().resolve(): "/root/.qlib/"},
|
||||
)
|
||||
class QlibDockerConf(DockerConf):
|
||||
build_from_dockerfile: bool = True
|
||||
dockerfile_folder_path: Path = Path(__file__).parent.parent / "scenarios" / "qlib" / "docker"
|
||||
image: str = "local_qlib:latest"
|
||||
mount_path: str = "/workspace/qlib_workspace/"
|
||||
default_entry: str = "qrun conf.yaml"
|
||||
extra_volumes: dict = {Path("~/.qlib/").expanduser().resolve(): "/root/.qlib/"}
|
||||
|
||||
|
||||
class DockerEnv(Env[DockerConf]):
|
||||
@@ -140,6 +141,12 @@ class DockerEnv(Env[DockerConf]):
|
||||
Download image if it doesn't exist
|
||||
"""
|
||||
client = docker.from_env()
|
||||
if self.conf.build_from_dockerfile is not None and self.conf.dockerfile_folder_path.exists():
|
||||
RDAgentLog().info(f"Building the image from dockerfile: {self.conf.dockerfile_folder_path}")
|
||||
image, logs = client.images.build(
|
||||
path=str(self.conf.dockerfile_folder_path), tag=self.conf.image, network_mode=self.conf.network
|
||||
)
|
||||
RDAgentLog().info(f"Finished building the image from dockerfile: {self.conf.dockerfile_folder_path}")
|
||||
try:
|
||||
client.images.get(self.conf.image)
|
||||
except docker.errors.ImageNotFound:
|
||||
@@ -164,14 +171,15 @@ class DockerEnv(Env[DockerConf]):
|
||||
|
||||
log_output = ""
|
||||
try:
|
||||
container = client.containers.run(
|
||||
container: docker.models.containers.Container = client.containers.run(
|
||||
image=self.conf.image,
|
||||
command=entry,
|
||||
volumes=volumns,
|
||||
environment=env,
|
||||
detach=True,
|
||||
working_dir=self.conf.mount_path,
|
||||
auto_remove=True,
|
||||
# auto_remove=True, # remove too fast might cause the logs not to be get
|
||||
network=self.conf.network,
|
||||
)
|
||||
logs = container.logs(stream=True)
|
||||
for log in logs:
|
||||
@@ -179,6 +187,8 @@ class DockerEnv(Env[DockerConf]):
|
||||
print(decoded_log)
|
||||
log_output += decoded_log + "\n"
|
||||
container.wait()
|
||||
container.stop()
|
||||
container.remove()
|
||||
return log_output
|
||||
except docker.errors.ContainerError as e:
|
||||
raise RuntimeError(f"Error while running the container: {e}")
|
||||
@@ -191,7 +201,7 @@ class DockerEnv(Env[DockerConf]):
|
||||
class QTDockerEnv(DockerEnv):
|
||||
"""Qlib Torch Docker"""
|
||||
|
||||
def __init__(self, conf: DockerConf = QLIB_TORCH_IMAGE):
|
||||
def __init__(self, conf: DockerConf = QlibDockerConf()):
|
||||
super().__init__(conf)
|
||||
|
||||
def prepare(self):
|
||||
@@ -201,7 +211,8 @@ class QTDockerEnv(DockerEnv):
|
||||
super().prepare()
|
||||
qlib_data_path = next(iter(self.conf.extra_volumes.keys()))
|
||||
if not (Path(qlib_data_path) / "qlib_data" / "cn_data").exists():
|
||||
RDAgentLog().info("We are downloading!")
|
||||
cmd = "python -m qlib.run.get_data qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn --interval 1d --delete_old False"
|
||||
self.run(entry=cmd)
|
||||
else:
|
||||
print("Data already exists. Download skipped.")
|
||||
RDAgentLog().info("Data already exists. Download skipped.")
|
||||
|
||||
@@ -18,6 +18,7 @@ matplotlib
|
||||
langchain
|
||||
tiktoken
|
||||
scikit-learn
|
||||
docker
|
||||
|
||||
# azure identity related
|
||||
azure.identity
|
||||
|
||||
Reference in New Issue
Block a user