Update 2024.11.14 可随意转载

一、研究背景

研究者常常阅读英文论文并进行编程验证来寻找潜在的研究方向,一般来说,这一过程工作负担较重。过去十年间,随着科学的发展,实验也越来越复杂,研究者阅读论文和编程验证的负担日益加剧,不利于从大量的论文中找出有价值的点。因此,自动化这一Reasearch & Development(R&D)流程变得很迫切。

二、研究框架和研究方法

2.1 研究框架

Framework-RDAgent

标签为“r”的模块根据预设的模板阅读文档,并生成任务(ModelTask)。

标签为’d’的模块共有两个部分:

  1. 组合任务、RAG、知识图谱等文字内容,与GPT-4对话,得到反馈和代码
  2. 根据反馈和代码部署docker运行环境,并评估它们。

2.2 研究方法

将pdf内容格式化为json,并生成研究代码,读取数据集,并通过多轮循环修改bug,提出假设,作出评价最终得到验证结果。

2.3 研究成果展示

fin_model

三、源码阅读

https://github.com/microsoft/RD-Agent

vscode运行配置如下:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python:Algorithem",
            "type": "python",
            "request": "launch",
            "program": "${workspaceFolder}/rdagent/app/cli.py",
            "console": "integratedTerminal",
            "args": [
                "general_model", "https://arxiv.org/pdf/2210.09789"
            ],
            "env": {
                "PYTHONPATH": "${workspaceFolder}"
            }
        },
        {
            "name": "Python:fin_factor",
            "type": "python",
            "request": "launch",
            "program": "${workspaceFolder}/rdagent/app/cli.py",
            "console": "integratedTerminal",
            "args": [
                "fin_factor"
            ],
            "env": {
                "PYTHONPATH": "${workspaceFolder}"
            }
        },
        {
            "name": "Python:UI",
            "type": "python",
            "request": "launch",
            "program": "${workspaceFolder}/rdagent/app/cli.py",
            "console": "integratedTerminal",
            "args": [
                "ui"
            ],
            "env": {
                "PYTHONPATH": "${workspaceFolder}"
            }
        }
    ]
}

3.1 general_model任务源码

任务目标:读取指定论文,并写出论文研究课题对应的代码,并且运行代码,评估代码的效果。

在rdagent/app/general_model/general_model.py文件中。

初始化代码(tag init):

    with logger.tag("init"):
        scenario = GeneralModelScenario()
        logger.log_object(scenario, tag="scenario")

主要是用来加载预先设定的prompt,是项目作这精挑细选的。包括以下主要提示词:

  • background
  • interface
  • output_format
  • rich_style_description
  • simulator
  • source_data

Reasearch部分代码(tag r):

    with logger.tag("r"):
        # Save Relevant Images
        img = extract_first_page_screenshot_from_pdf(report_file_path)
        logger.log_object(img, tag="pdf_image")
        exp = ModelExperimentLoaderFromPDFfiles().load(report_file_path)
        logger.log_object(exp, tag="load_experiment")

用来提取pdf中的内容,并为了下一步模型使用,格式化成model_dict,代码如下:

# rdagent/components/coder/model_coder/task_loader.py
    def load(self, file_or_folder_path: str) -> dict:
        docs_dict = load_and_process_pdfs_by_langchain(file_or_folder_path)  # dict{file_path:content}
        model_dict = extract_model_from_docs(
            docs_dict
        )  # dict{file_name: dict{model_name: dict{description, formulation, variables}}}
        model_dict = merge_file_to_model_dict_to_model_dict(
            model_dict
        )  # dict {model_name: dict{description, formulation, variables}}
        return ModelExperimentLoaderFromDict().load(model_dict)

再次转换格式为ModelTask:

# rdagent/components/coder/model_coder/task_loader.py 
   def load(self, model_dict: dict) -> list:
        """Load data from a dict."""
        task_l = []
        for model_name, model_data in model_dict.items():
            task = ModelTask(
                name=model_name,
                description=model_data["description"],
                formulation=model_data["formulation"],
                architecture=model_data["architecture"],
                variables=model_data["variables"],
                hyperparameters=model_data["hyperparameters"],
                model_type=model_data["model_type"],
            )
            task_l.append(task)
        return QlibModelExperiment(sub_tasks=task_l)

Development部分代码(tag d):

先初始化类QlibModelCoSTEER

# rdagent/components/coder/model_coder/CoSTEER/__init__.py
初始化参数:max_loop,knowledge_base_path,new_knowledge_base_path,with_knowledge,with_feedback,knowledge_self_gen,filter_final_evo,evolving_strategy,model_evaluator

再将ModelTask的封装传入develop方法,方法中调用multistep_evolve方法,执行下面的代码:

        for _ in tqdm(range(self.max_loop), "Implementing"):
...
            # 3. evolve
            evo = self.evolving_strategy.evolve(
                evo=evo,
                evolving_trace=self.evolving_trace,
                queried_knowledge=queried_knowledge,
            )
...
        return evo

然后evolve会调用implement_one_model方法,构造上下文并和GPT-4的对话:

# rdagent/components/coder/model_coder/CoSTEER/evolving_strategy.py
    def implement_one_model(...):
        model_information_str = target_task.get_task_information()
        model_type = target_task.model_type
...
            queried_former_failed_knowledge_to_render = queried_former_failed_knowledge
            system_prompt = (...)
...
            for _ in range(10):  # max attempt to reduce the length of user_prompt
                user_prompt = (...)
...
            code = json.loads(
                APIBackend(use_chat_cache=MODEL_IMPL_SETTINGS.coder_use_cache)
.build_messages_and_create_chat_completion(
                    user_prompt=user_prompt,
                    system_prompt=system_prompt,
                    json_mode=True,
                ),
            )["code"]
            return code

最后,建立docker镜像并把代码插入进入,然后运行镜像评估GPT-4编写的代码正确与否:

            # 5. Evaluation
            if self.with_feedback:
                es.feedback = (
                    eva
                    if isinstance(eva, Feedback)
                    else eva.evaluate(evo, queried_knowledge=queried_knowledge)  
                )
                logger.log_object(es.feedback, tag="evolving feedback")

3.2 fin_factor任务源码

任务目标:持续挖掘金融量化因子。

rdagent/app/qlib_rd_loop/factor.py中的代码:

def main(path=None, step_n=None):
    """
    自动进行金融科技的研发迭代循环

    """
    if path is None:
        model_loop = FactorRDLoop(FACTOR_PROP_SETTING)
    else:
        model_loop = FactorRDLoop.load(path)
    model_loop.run(step_n=step_n)

其中FactorRDLoop初始化调用了RDLoop类的__init__方法,初始化了下面变量:

其中run方法会按照steps里面定义的workflow各个阶段逐一执行。

    def run(self, step_n: int | None = None):
        with tqdm(total=len(self.steps), desc="Workflow Progress", unit="step") as pbar:
            while True:
...
                # 取出每个step的名字,通过名字动态调用对应的方法。
                name = self.steps[si]
                func = getattr(self, name)
                try:
                    self.loop_prev_out[name] = func(self.loop_prev_out)
                except self.skip_loop_error as e:

按设定好的workflow:[‘propose’, ‘exp_gen’, ‘coding’, ‘running’, ‘feedback’] 执行对应的代码:

    @measure_time
    def propose(self, prev_out: dict[str, Any]):
        with logger.tag("r"):  # research
            hypothesis = self.hypothesis_gen.gen(self.trace)
            logger.log_object(hypothesis, tag="hypothesis generation")
        return hypothesis

    @measure_time
    def exp_gen(self, prev_out: dict[str, Any]):
        with logger.tag("r"):  # research
            exp = self.hypothesis2experiment.convert(prev_out["propose"], self.trace)
            logger.log_object(exp.sub_tasks, tag="experiment generation")
        return exp

    @measure_time
    def coding(self, prev_out: dict[str, Any]):
        with logger.tag("d"):  # develop
            exp = self.coder.develop(prev_out["exp_gen"])
            logger.log_object(exp.sub_workspace_list, tag="coder result")
        return exp

    @measure_time
    def running(self, prev_out: dict[str, Any]):
        with logger.tag("ef"):  # evaluate and feedback
            exp = self.runner.develop(prev_out["coding"])
            logger.log_object(exp, tag="runner result")
        return exp

    @measure_time
    def feedback(self, prev_out: dict[str, Any]):
        feedback = self.summarizer.generate_feedback(prev_out["running"], prev_out["propose"], self.trace)
        with logger.tag("ef"):  # evaluate and feedback
            logger.log_object(feedback, tag="feedback")
        self.trace.hist.append((prev_out["propose"], prev_out["running"], feedback))

比如propose阶段的GPT-4上下文简化如下:

[
    {
        "role": "system",
        "content": "用户正在为数据驱动的量化投资研究开发假设,这些因子用于解释投资组合或单一资产的回报和风险。因子帮助识别超额回报,并是量化策略的核心。用户的模型将基于过去的因子值预测未来几天的回报。每个因子包含:1) 名称,2) 描述,3) 公式,4) 变量。每个因子定义静态输出和固定数据源。例如,10天和20天的动量应视为不同因子。数据源包括 daily_pv.h5,包含一段时间内的股票价格和成交量数据。\n\n代码接口应包括:导入部分、函数部分和主函数部分,主函数命名为 calculate_{function_name}。Python代码应将因子值保存为 result.h5 文件,文件内容为一个按日期和股票代码索引的 pandas DataFrame。用户将使用 Qlib 进行模型训练和投资组合评估。用户已提出一些假设,任务是验证并改进这些假设。开始时因子应简单,之后逐步增加复杂度,每次生成 1-3 个因子。确保每个假设符合给定的指引。\n\n输出格式:JSON,包含假设、推理、简明理由、观察、论证和知识等信息。"
    },
    {
        "role": "user",
        "content": "生成推理和提炼知识的相关键,特别是在领域特定的背景下解释,而非泛泛的理论知识。"
    }
]

比如exp_gen阶段的GPT-4上下文简化如下:

[{
    "role": "system",
    "content": "用户生成因子用于量化投资,关注组合的回报与风险。因子用于训练模型,基于过去的数据预测回报。因子包含名称、描述、公式和变量。每个因子定义一个输出,使用特定的数据集。数据包括每日的价格和成交量(开盘价、收盘价、最高价、最低价、成交量、因子值)以HDF5格式存储。Python代码应计算并将因子结果保存到一个HDF5文件,文件中使用日期时间和证券代码作为索引,因子值作为唯一列。Qlib将用于评估和基于因子构建投资组合。输出格式为JSON,包含因子的详细信息。"
}, {
    "role": "user",
    "content": "生成因子的目标假设是:引入一个30日的MACD因子,基于12日和26日指数加权移动平均(EMA)收盘价的差值。MACD有助于识别短期动量和趋势变化,是未来价格走势的有效预测指标。简洁知识:MACD结合短期和长期EMA,能够信号未来价格变化的方向。"
}]

比如coding阶段的GPT-4上下文简化如下:

[
  {
    "role": "system",
    "content": "用户正在实现量化投资中的因子,因子用于解释资产或投资组合的回报与风险。用户将基于前几天的因子值训练模型预测未来几天的回报。因子包括名称、描述、公式和变量,可能不完全包含所有部分。因子应静态定义一个输出和数据源。代码需按照给定接口格式编写,并保存结果为HDF5文件。输出的DataFrame包含时间戳和证券代码,因子值为数据列。"
  },
  {
    "role": "user",
    "content": "目标因子:MACD_30,描述:基于12日与26日指数移动平均(EMA)差值的30日MACD,用于预测未来回报。公式:MACD_{30} = EMA_{12}(Close) - EMA_{26}(Close)。变量:EMA_{12}为12日EMA,EMA_{26}为26日EMA,Close为收盘价。"
  }
]

coding包括子阶段:evolve,它GPT-4上下文简化如下:

[
  {
    "role": "system",
    "content": "用户正在实现量化投资中的因子。因子用于解释资产或投资组合的回报和风险,帮助识别超额回报来源,是量化策略的核心。每个因子在特定日期对一个工具产生物理值。用户将训练模型,基于历史因子值预测未来回报。因子包括:名称、描述、公式和变量,并指定如窗口大小和回溯期等超参数。不同的因子应基于静态数据计算,例如‘过去10天的动量’和‘过去20天的动量’应作为不同的因子。源数据为 daily_pv.h5,包含多个工具的每日价格和因子值。结果应保存在 ‘result.h5’ 文件中。用户的代码将计算因子值并保存为 HDF5 格式,以便后续在 Qlib 中进行模型训练,预测回报并评估投资组合表现。"
  },
  {
    "role": "user",
    "content": "目标因子信息:因子名称:Signal_Line_9。描述:MACD的9日信号线,用于平滑值并识别买卖信号。公式:Signal_{9} = EMA_{9}(MACD_{30})。变量:EMA_{9}为MACD的9日指数移动平均,MACD_{30}为30日MACD。"
  }
]

之后,它会调用FactorEvolvingStrategyWithGraph类中的方法implement_one_factor,而这个方法的GPT-4上下文简化如下:

[
  {
    "role": "system",
    "content": "用户正在为量化投资实现因子。因子帮助解释投资组合的收益和风险。用户将训练模型,根据历史因子预测收益。每个因子包括名称、描述、公式和变量。因子应具有窗口大小和回溯期等超参数。数据来源是'daily_pv.h5'文件。用户编写代码计算因子值并保存在'result.h5'文件中。代码需遵循接口,生成因子、训练模型、评估投资组合表现,使用Qlib平台进行。"
  },
  {
    "role": "user",
    "content": {
      "factor_name": "MACD_Histogram",
      "factor_description": "MACD柱状图,衡量MACD与信号线之间的距离。",
      "factor_formulation": "MACDHist = MACD_{30} - Signal_{9}",
      "variables": {
        "MACD_{30}": "30日移动平均收敛/发散指标。",
        "Signal_{9}": "MACD的9日信号线。"
      }
    }
  }
]

coding包括子阶段:evaluate(多个),它GPT-4上下文简化如下:

{
  "背景": {
    "描述": "用户正在实现量化投资中的因子,用于根据因子值预测资产的回报。因子由名称、描述、公式和变量定义,帮助解释投资组合或单一资产的回报和风险。",
    "数据格式": {
      "类型": "HDF5",
      "列": ["$open", "$close", "$high", "$low", "$volume", "$factor"],
      "数据": "调整后的每日价格和成交量数据,使用多重索引: (datetime, instrument)"
    },
    "任务": {
      "模型": "基于因子值训练模型来预测回报。",
      "文件": "result.h5",
      "输出格式": "DataFrame,使用多重索引(datetime, instrument),单列存储因子值。"
    }
  },
  "接口": {
    "python代码": "代码必须包含导入部分、函数部分和主函数部分,主函数名为 'calculate_{function_name}',并将输出保存到 'result.h5'。",
    "要求": "因子的计算应针对特定周期,比如 '过去10天动量' 和 '过去20天动量' 应定义为不同因子。"
  },
  "因子数据": {
    "示例输出": {
      "数据框": {
        "多重索引": "(Timestamp, 'instrument')",
        "列": "因子名称(单列)",
        "数据类型": "float64"
      },
      "模拟": "因子用于在Qlib中训练模型,预测回报、管理投资组合并评估性能。"
    }
  },
  "用户状态": {
    "任务": "用户正在处理与某个特性相关的任务。",
    "输出示例": {
      "数据框信息": {
        "多重索引": "48700条记录,(Timestamp('2018-01-02 00:00:00'), 'SH000300') 到 (Timestamp('2019-12-31 00:00:00'), 'SH600121')",
        "列": "MACD_30",
        "非空计数": 48548,
        "数据类型": "float64"
      }
    }
  }
}

running阶段不会调用GPT-4,它会创建docker并运行

比如feedback阶段的GPT-4上下文简化如下:

{
  "Observations": "新因子(MACD_30、Signal_Line_9 和 MACD_Histogram)在年化回报和信息比率上优于 SOTA,但最大回撤有所恶化。IC 值在当前和 SOTA 结果之间相似。",
  "Feedback for Hypothesis": "使用 30 天 MACD 预测未来回报的假设得到了支持,因为这些因子在年化回报和信息比率上表现出色。然而,较高的最大回撤表明在降低风险方面仍需进一步优化。",
  "New Hypothesis": "通过整合波动率调整的移动平均线或加入额外的平滑技术来优化 MACD 因子,以在降低风险的同时保持高回报。",
  "Reasoning": "尽管年化回报和信息比率有所提升,但较高的最大回撤表明风险管理需要改进。通过对 MACD 加入波动率调整或平滑方法,可以在不牺牲回报的情况下减少风险。",
  "Replace Best Result": "yes"
}