1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-03 02:50:58 +08:00

merge into one commit

This commit is contained in:
Xu Yang
2023-06-30 10:18:37 +08:00
parent 7e84f3aae2
commit 73bd79ca1a
6 changed files with 706 additions and 443 deletions

View File

@@ -29,3 +29,4 @@ class Config(Singleton):
)
self.debug_mode = os.getenv("DEBUG_MODE") == "True" if os.getenv("DEBUG_MODE") is not None else False
self.workspace = os.getenv("WORKSPACE") if os.getenv("WORKSPACE") is not None else "./finco_workspace"
self.max_past_message_include = int(os.getenv("MAX_PAST_MESSAGE_INCLUDE") or 6) // 2 * 2

View File

@@ -27,7 +27,7 @@ class APIBackend(Singleton):
json.load(open(self.cache_file_location, "r")) if os.path.exists(self.cache_file_location) else {}
)
def build_messages_and_create_chat_completion(self, user_prompt, system_prompt=None):
def build_messages_and_create_chat_completion(self, user_prompt, system_prompt=None, former_messages=[], **kwargs):
"""build the messages to avoid implementing several redundant lines of code"""
cfg = Config()
# TODO: system prompt should always be provided. In development stage we can use default value
@@ -35,20 +35,23 @@ class APIBackend(Singleton):
try:
system_prompt = cfg.system_prompt
except AttributeError:
get_module_logger("finco").warning("system_prompt is not set, using default value.")
FinCoLog().warning("system_prompt is not set, using default value.")
system_prompt = "You are an AI assistant who helps to answer user's questions about finance."
messages = [
{
"role": "system",
"content": system_prompt,
},
}
]
messages.extend(former_messages[-1*cfg.max_past_message_include:])
messages.append(
{
"role": "user",
"content": user_prompt,
},
]
}
)
fcl = FinCoLog()
response = self.try_create_chat_completion(messages=messages)
response = self.try_create_chat_completion(messages=messages, **kwargs)
fcl.log_message(messages)
fcl.log_response(response)
return response
@@ -59,7 +62,7 @@ class APIBackend(Singleton):
try:
response = self.create_chat_completion(**kwargs)
return response
except openai.error.RateLimitError as e:
except (openai.error.RateLimitError, openai.error.Timeout) as e:
print(e)
print(f"Retrying {i+1}th time...")
time.sleep(1)
@@ -75,8 +78,9 @@ class APIBackend(Singleton):
) -> str:
if self.debug_mode:
if messages[1]["content"] in self.cache:
return self.cache[messages[1]["content"]]
key = json.dumps(messages)
if key in self.cache:
return self.cache[key]
if temperature is None:
temperature = self.cfg.temperature
@@ -96,6 +100,6 @@ class APIBackend(Singleton):
)
resp = response.choices[0].message["content"]
if self.debug_mode:
self.cache[messages[1]["content"]] = resp
self.cache[key] = resp
json.dump(self.cache, open(self.cache_file_location, "w"))
return resp

View File

@@ -17,11 +17,6 @@ class PromptTemplate(Singleton):
continue
self.__setattr__(k, Template(v))
for target_name, module_to_render_params in _template["mods"].items():
for module_name, params in module_to_render_params.items():
self.__setattr__(f"{target_name}_{module_name}",
Template(self.__getattribute__(target_name).render(**params)))
def get(self, key: str):
return self.__dict__.get(key, Template(""))

View File

@@ -25,28 +25,40 @@ WorkflowTask_user : |-
SLPlanTask_system : |-
Your task is to design the 6 crucial components in Qlib (Dataset, DataHandler, Model, Record, Strategy, Backtest) ensuring the workflow can meet the user's requirements.
For each component, you first point out whether to use default module in Qlib or implement the new module (Default or Personized). Default module means the class has already be implemented by Qlib which can be found in document and source code. Default class can be directed called from config file without additional implementation. Personized module means new python class is implemented and called from config file. You should always provide the reason of your choice.
The predefined class in Qlib modules can be listed in format of {module_path}-{class name}:
Dataset: {qlib.data.dataset}-{DatasetH}, {qlib.contrib.data.dataset}-{MTSDatasetH}
DataHandler: {qlib.contrib.data.handler}-{Alpha158}, {qlib.contrib.data.handler}-{Alpha158vwap}, {qlib.contrib.data.handler}-{Alpha360}, {qlib.contrib.data.handler}-{Alpha360vwap}, {qlib.data.dataset.loader}-{QlibDataLoader}
Model: {qlib.contrib.model.catboost_model}-{CatBoostModel}, {qlib.contrib.model.double_ensemble}-{DoubleEnsembleModel}, {qlib.contrib.model.gbdt}-{LGBModel}, {qlib.contrib.model.highfreq_gdbt_model}-{HFLGBModel}, {qlib.contrib.model.linear}-{LinearModel}, {qlib.contrib.model.pytorch_adarnn}-{AdaRNNModel}, {qlib.contrib.model.pytorch_add}-{ADD}, {qlib.contrib.model.pytorch_alstm_ts}-{ALSTM}, {qlib.contrib.model.pytorch_alstm}-{ALSTM}, {qlib.contrib.model.pytorch_gats}-{GATs}, {qlib.contrib.model.pytorch_gats_ts}-{GATs}, {qlib.contrib.model.pytorch_gru}-{GRU}, {qlib.contrib.model.pytorch_gru_ts}-{GRU}, {qlib.contrib.model.pytorch_hist}-{HIST}, {qlib.contrib.model.pytorch_igmtf}-{IGMTF}, {qlib.contrib.model.pytorch_localformer}-{LocalformerModel}, {qlib.contrib.model.pytorch_localformer_ts}-{LocalformerModel}, {qlib.contrib.model.pytorch_lstm}-{LSTM}, {qlib.contrib.model.pytorch_lstm_ts}-{LSTM}, {qlib.contrib.model.pytorch_nn}-{DNNModelPytorch}, {qlib.contrib.model.pytorch_sfm}-{SFM}, {qlib.contrib.model.pytorch_tabnet}-{TabnetModel}, {qlib.contrib.model.pytorch_tcn_ts}-{TCN}, {qlib.contrib.model.pytorch_tcn}-{TCN}, {qlib.contrib.model.pytorch_tcts.}-{TCTS}, {qlib.contrib.model.pytorch_tra}-{TRA}, {qlib.contrib.model.pytorch_transformer}-{TransformerModel}, {qlib.contrib.model.pytorch_transformer_ts}-{TransformerModel}, {qlib.contrib.model.xgboost}-{XGBModel}
Record: {qlib.workflow.record_temp}-{SignalRecord}, {qlib.workflow.record_temp}-{SigAnaRecord},
Strategy: {qlib.contrib.strategy}-{TopkDropoutStrategy}, {qlib.contrib.strategy}-{WeightStrategyBase}, {qlib.contrib.strategy}-{EnhancedIndexingStrategy}, {qlib.contrib.strategy}-{TWAPStrategy}, {qlib.contrib.strategy}-{SBBStrategyBase}, {qlib.contrib.strategy}-{SBBStrategyEMA}, {qlib.contrib.strategy}-{SoftTopkStrategy}
The list will be called as "predefined classes" in the following prompts.
For each component, you first point out whether to use default module in Qlib or implement the new module (Default or Personized). Default module means picking one of the predefined classes to meet the user's requirement. Personized module means new python class implemented and called from config file. The new class should always inherit from one of the class in the predefined classes.
If choose Default, provide the predefined class after the choice, otherwise, provide the predefined class your code plans to inherit from. the format of predefined class should follow the previous format. Backtest module has no predefined class so you don't need to provide.
If the user's requirement can be met with Default module, always use default module to avoid code error!!!
Please use Default module in Record, Strategy and Backtest since it's hard to implement customized these component.
The user will provide the requirements, you will provide only the output the choice in exact format specified below with no explanation or conversation. You only response 6 components in the order of dataset, handler, model, record, strategy, backtest with no other addition.
Example input:
Help me build a low turnover quant investment strategy that focus more on long turn return in China a stock market. I have some data in csv format and I want to merge them with the data in Qlib.
Help me build a low turnover quant investment strategy that focus more on long turn return in China a stock market. I want to use a big LSTM model and add several MLP layer before the head.
Example output:
components:
- Dataset: (Personized) I will implement a CustomDataset inherited from DatasetH in qlib.data.dataset and merge the user-provided CSV data with the existing Qlib data. Because this will allow the user to leverage both their custom data and Qlib's data for the China A stock market.
- Dataset: (Default) {qlib.data.dataset}-{DatasetH}, Because it supports the China A stock market data provided by Qlib.
- DataHandler: (Personized) I will implement a CustomDataHandler inherited from Alpha360 in qlib.contrib.data.handler to handle the merged dataset from the custom dataset. Because it is necessary to handle the combined data from Qlib's Alpha360 and the user's CSV file.
- DataHandler: (Default) {qlib.contrib.data.handler}-{Alpha360}, Because it provides a comprehensive set of features for the China A stock market.
- Model: (Default) I will use LGBModel in qlib.contrib.model.gbdt for the low turnover strategy. Because it is a popular and efficient model for quant investment strategies and can capture long-term patterns in the data.
- Model: (Personized) {qlib.contrib.model.pytorch_lstm}-{LSTM}, Because the user wants to use a big LSTM model with additional MLP layers before the head.
- Record: (Default) I will use SignalRecord in qlib.workflow.record_temp and SigAnaRecord in qlib.workflow.record_temp to save all the signals and the analysis results. Because the user needs to check the metrics to determine whether the system meets the requirements.
- Record: (Default) {qlib.workflow.record_temp}-{SignalRecord}{qlib.workflow.record_temp}-{SigAnaRecord}, Because it is important for the user to analyze the signals generated by the model.
- Strategy: (Default) I will use TopkDropoutStrategy in qlib.contrib.strategy. Because it is a more robust strategy which saves turnover fee and focuses on long-term return.
- Strategy: (Default) {qlib.contrib.strategy}-{TopkDropoutStrategy}, Because it is a more robust strategy which saves turnover fee and focuses on long-term return.
- Backtest: (Default) I will use the default backtest module in Qlib. Because it can tell the user a more real performance result of the model we build.
- Backtest: (Default) Because it can tell the user a more real performance result of the model we build.
SLPlanTask_user : |-
User input: '{{user_prompt}}'
@@ -82,33 +94,432 @@ CMDTask_user : |-
- User OS: "{{user_os}}"
Example output:
ConfigActionTask_system : |-
Your task is to write the config of the target component in Qlib(Dataset, DataHandler, Model, Record, Strategy, Backtest).
HyperparameterActionTask_system : |-
Your task is to determine the hyperparameters to initialize the target class of the target component in Qlib(Dataset, DataHandler, Model, Record, Strategy, Backtest). User will provide possible hyperparameter names, you can choose to use default value(Please provice exactly "Default" in response) or set personized value to better meet user's requirement.
Config means the yaml file in Qlib. You can find the default config in qlib/contrib/config_template. You can also find the config in Qlib document. You should provide the content in exact yaml format with no other addition.
The predefined class in the target Qlib module can be listed in format of {module_path}-{class name}:
{% if target_module == "Dataset" %}
Dataset: {qlib.data.dataset}-{DatasetH}, {qlib.contrib.data.dataset}-{MTSDatasetH}
{% elif target_module == "DataHandler" %}
DataHandler: {qlib.contrib.data.handler}-{Alpha158}, {qlib.contrib.data.handler}-{Alpha158vwap}, {qlib.contrib.data.handler}-{Alpha360}, {qlib.contrib.data.handler}-{Alpha360vwap}, {qlib.data.dataset.loader}-{QlibDataLoader}
{% elif target_module == "Model" %}
Model: {qlib.contrib.model.catboost_model}-{CatBoostModel}, {qlib.contrib.model.double_ensemble}-{DoubleEnsembleModel}, {qlib.contrib.model.gbdt}-{LGBModel}, {qlib.contrib.model.highfreq_gdbt_model}-{HFLGBModel}, {qlib.contrib.model.linear}-{LinearModel}, {qlib.contrib.model.pytorch_adarnn}-{AdaRNNModel}, {qlib.contrib.model.pytorch_add}-{ADD}, {qlib.contrib.model.pytorch_alstm_ts}-{ALSTM}, {qlib.contrib.model.pytorch_alstm}-{ALSTM}, {qlib.contrib.model.pytorch_gats}-{GATs}, {qlib.contrib.model.pytorch_gats_ts}-{GATs}, {qlib.contrib.model.pytorch_gru}-{GRU}, {qlib.contrib.model.pytorch_gru_ts}-{GRU}, {qlib.contrib.model.pytorch_hist}-{HIST}, {qlib.contrib.model.pytorch_igmtf}-{IGMTF}, {qlib.contrib.model.pytorch_localformer}-{LocalformerModel}, {qlib.contrib.model.pytorch_localformer_ts}-{LocalformerModel}, {qlib.contrib.model.pytorch_lstm}-{LSTM}, {qlib.contrib.model.pytorch_lstm_ts}-{LSTM}, {qlib.contrib.model.pytorch_nn}-{DNNModelPytorch}, {qlib.contrib.model.pytorch_sfm}-{SFM}, {qlib.contrib.model.pytorch_tabnet}-{TabnetModel}, {qlib.contrib.model.pytorch_tcn_ts}-{TCN}, {qlib.contrib.model.pytorch_tcn}-{TCN}, {qlib.contrib.model.pytorch_tcts.}-{TCTS}, {qlib.contrib.model.pytorch_tra}-{TRA}, {qlib.contrib.model.pytorch_transformer}-{TransformerModel}, {qlib.contrib.model.pytorch_transformer_ts}-{TransformerModel}, {qlib.contrib.model.xgboost}-{XGBModel}
{% elif target_module == "Record" %}
Record: {qlib.workflow.record_temp}-{SignalRecord}, {qlib.workflow.record_temp}-{SigAnaRecord},
{% elif target_module == "Strategy" %}
Strategy: {qlib.contrib.strategy}-{TopkDropoutStrategy}, {qlib.contrib.strategy}-{WeightStrategyBase}, {qlib.contrib.strategy}-{EnhancedIndexingStrategy}, {qlib.contrib.strategy}-{TWAPStrategy}, {qlib.contrib.strategy}-{SBBStrategyBase}, {qlib.contrib.strategy}-{SBBStrategyEMA}, {qlib.contrib.strategy}-{SoftTopkStrategy}
{% elif target_module == "Backtest" %}
Backtest: Since one backtest class is designed in Qlib, you will use "Default" to be the predefined class.
{% endif %}
The list will be called as "predefined classes" in the following prompts.
{% if target_module != "Backtest" %}
You have chosen Default backtest module.
{% elif choice == "Default" %}
You have chosen several predefined classes from Qlib(more than one classes might be picked, you should provide the each class's hyperparameters):
{%for module_path, class_name in classes%}{% raw %}{{% endraw %}{{module_path}}{% raw %}}{% endraw %}-{% raw %}{{% endraw %}{{class_name}}{% raw %}}{% endraw %}.{% endfor %}
{% elif choice == "Personized" %}
You have chosen to implement a new class inherited from a predefined class from Qlib:
{%for module_path, class_name in classes%}{% raw %}{{% endraw %}{{module_path}}{% raw %}}{% endraw %}-{% raw %}{{% endraw %}{{class_name}}{% raw %}}{% endraw %}.{% endfor %}, you should also provide the each class's hyperparameters to initialize the class.
{% endif %}
Caution: User's hyperparameter list is to hint, you can add more hyperparameters if necessary. Especially, some hyperparameters are set through kwargs, so always consider these hyperparameters and add them if necessary!!!
The user has provided the requirements and made plan and reason to each component. You should strictly follow user's plan and you should provide the reason of your hyperparameter choices if exist and some suggestion if user wants to finetune the hyperparameters after the config. Default means you should only use classes in Qlib without any other new code while Personized has no such restriction. class in Qlib means Qlib has implemented the class and you can find it in Qlib document or source code.
The user has provided the requirements, chose the predefined classes and made plan and reason to each component. You should strictly follow user's choice and you should provide the reason of your hyperparameter choices if exist and some suggestion if the user wants to finetune the hyperparameters after the hyperparameter.
"Config", "Reason" and "Improve suggestion" should always be provided with exactly the same letters.
{{target_component_desc}}
You only need to write the config of the target component in the exact format specified below with no explanation or conversation.
You only need to response the hyperparameters in the exact format in exsample below with no explanation or conversation. "Hyperparameters:", "Reason:", "Improve suggestion:" are key tags so always include them in response.
{% if target_module == "DataHandler" %}
Qlib has these processors {processor_name}-{hyperparameter kwargs}:
{DropnaProcessor}-{['fields_group']},{DropnaLabel}-{['fields_group']},{CSRankNorm}-{['fields_group']},{ProcessInf}-{[]},{Processor}-{[]},{MinMaxNorm}-{['fit_start_time', 'fit_end_time', 'fields_group']},{CSZFillna}-{['fields_group']},{TanhProcess}-{[]},{CSZScoreNorm}-{['fields_group', 'method']},{RobustZScoreNorm}-{['fit_start_time', 'fit_end_time', 'fields_group', 'clip_outlier']},{FilterCol}-{['fields_group', 'col_list']},{HashStockFormat}-{[]},{ZScoreNorm}-{['fit_start_time', 'fit_end_time', 'fields_group']},{DropCol}-{['col_list']},{Fillna}-{['fields_group', 'fill_value']}.
You can choose some of them to use in {infer_processors} or {learn_processors} if necessary and pick the kwargs of them.
freq should pick one from {year}/{quarter}/{month}/{week}/{day}.
{% endif %}
Example input:
user requirement: Help me build a low turnover quant investment strategy that focus more on long turn return in China a stock market. I have some data in csv format and I want to merge them with the data in Qlib.
user plan:
{{target_component_example_input}}
target component: {{target_component}}
user requirement: Help me build a low turnover quant investment strategy that focus more on long turn return in China a stock market. I want to use a big LSTM model and add several MLP layer before the head.
user plan:{% if target_module == "Dataset" %}
Dataset: (Default) {qlib.data.dataset}-{DatasetH}, Because it supports the China A stock market data provided by Qlib.
{% elif target_module == "DataHandler" %}
DataHandler: (Default) {qlib.contrib.data.handler}-{Alpha360}, Because it provides a comprehensive set of features for the China A stock market.
{% elif target_module == "Model" %}
Model: (Default) {qlib.contrib.model.gbdt}-{LGBModel}, Because it uses the LightGBM model requested by the user.
{% elif target_module == "Record" %}
Record: (Default) {qlib.workflow.record_temp}-{SignalRecord}{qlib.workflow.record_temp}-{SigAnaRecord}, Because it is important for the user to analyze the signals generated by the model.
{% elif target_module == "Strategy" %}
Strategy: (Default) {qlib.contrib.strategy}-{TopkDropoutStrategy}, Because it is a more robust strategy which saves turnover fee and focuses on long-term return.
{% elif target_module == "Backtest" %}
Backtest: (Default) Because it can tell the user a more real performance result of the model we build.
{% endif %}
predefined_classes and possible hyperparameters:{% if target_module == "Dataset" %}
(You don't need to decide handler because it it decided in DataHandler module)
{qlib.data.dataset}-{DatasetH}:
{segments}
{fetch_kwargs}
target component: Dataset
{% elif target_module == "DataHandler" %}
{qlib.contrib.data.handler}-{Alpha360}:
{instruments}
{start_time}
{end_time}
{freq}
{infer_processors}
{learn_processors}
{fit_start_time}
{fit_end_time}
{filter_pipe}
{inst_processors}
{data_loader}
{label}
target component: DataHandler
{% elif target_module == "Model" %}
{qlib.contrib.model.pytorch_lstm}-{LSTM}
{d_feat}
{hidden_size}
{num_layers}
{dropout}
{n_epochs}
{lr}
{metric}
{batch_size}
{early_stop}
{loss}
{optimizer}
{GPU}
{seed}
target component: Model
{% elif target_module == "Record" %}
{qlib.workflow.record_temp}-{SignalRecord}:
{model}
{dataset}
{recorder}
{workspace}
{qlib.workflow.record_temp}-{SigAnaRecord}:
{recorder}
{ana_long_short}
{ann_scaler}
{label_col}
{skip_existing}
target component: Record
{% elif target_module == "Strategy" %}
{qlib.contrib.strategy}-{TopkDropoutStrategy}:
{topk}
{n_drop}
{method_sell}
{method_buy}
{hold_thresh}
{only_tradable}
{forbid_all_trade_at_limit}
target component: Strategy
{% elif target_module == "Backtest" %}
Default:
{start_time}
{end_time}
{strategy}
{executor}
{benchmark}
{account}
{exchange_kwargs}
{pos_type}
target component: Backtest
{% endif %}
Example output:
{{target_component_example_output}}
Hyperparameters:
{% if target_module == "Dataset" %}
{qlib.data.dataset}-{DatasetH}:
{segments}:{"train":["2008-01-01", "2014-12-31"],"valid": ["2015-01-01", "2016-12-31"],"test": ["2017-01-01", "2020-08-01"]}
{fetch_kwargs}:Default
Reason: I chose these hyperparameters to provide a robust long-term investment strategy by dividing the China A stock market data into train, valid, and test segments, while using default settings for fetching data in Qlib's DatasetH.
Improve suggestion: To further improve the model's performance, you can experiment with different segment lengths for train, valid, and test sets, and explore custom fetch_kwargs options to include additional features or modify data preprocessing steps in Qlib's DatasetH.
{% elif target_module == "DataHandler" %}
{qlib.contrib.data.handler}-{Alpha360}:
{instruments}:csi300
{start_time}:2008-01-01
{end_time}:2020-08-01
{freq}:Default
{infer_processors}:[{"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"}}}]
{fit_start_time}:2008-01-01
{fit_end_time}:2014-12-31
{filter_pipe}:Default
{inst_processors}:Default
{data_loader}:Default
{label}:["Ref($close, -2) / Ref($close, -1) - 1"]
Reason: The hyperparameters are chosen to focus on the China A stock market, provide a long-term historical dataset, ensure a robust training period, and align with your longer-term return focus, thus creating a solid starting point for your low turnover quant investment strategy.
Improve suggestion: To improve DataHandler performance, consider using more recent data, experimenting with different frequencies, customizing learn_processors and infer_processors, exploring feature selection techniques, and trying different labels. Be aware that these suggestions may require additional resources and experimentation to optimize your strategy.
{% elif target_module == "Model" %}
{qlib.contrib.model.pytorch_lstm}-{LSTM}
{d_feat}:6
{hidden_size}:64
{num_layers}:2
{dropout}:0.0
{n_epochs}:200
{lr}:0.001
{metric}:loss
{batch_size}:800
{early_stop}:20
{loss}:mse
{optimizer}:Default
{GPU}:Default
{seed}:Default
Reason: I chose these hyperparameters based on your requirement of a big LSTM model for a low turnover quant investment strategy focusing on long-term returns in the China A-stock market. The hidden_size is set to 128 to create a larger LSTM model, while the num_layers is set to 2 to add complexity. The dropout is set to 0.0 to avoid any regularization, as you didn't mention any concerns about overfitting. The learning rate (lr) is set to 0.001, which is a standard value for training deep learning models. The number of epochs (n_epochs) is set to 200, and the early stopping (early_stop) is set to 20, which will help in preventing overfitting and save training time. The batch_size is set to 800, which is a reasonable size for training the model. The loss function is set to mean squared error (mse) as it is a common choice for regression tasks. The optimizer, GPU, and seed are set to their default values as no specific requirements were provided.
Improve suggestion: To further improve the performance of the LSTM model, you can consider tuning the following hyperparameters: hidden_size, num_layers, dropout, learning rate (lr), and batch_size. You can try increasing the hidden_size and num_layers to make the model more expressive, but be cautious of overfitting. Introducing dropout (e.g., 0.1 to 0.5) can help with regularization and reduce overfitting. Adjusting the learning rate (e.g., trying values between 0.0001 and 0.01) can help find the optimal balance between fast convergence and stable training. Lastly, experimenting with different batch sizes (e.g., 256, 512, or 1024) can impact the model's performance and training speed. You can use techniques like grid search or random search to systematically explore these hyperparameter combinations and select the best performing configuration.
{% elif target_module == "Record" %}
{qlib.workflow.record_temp}-{SignalRecord}:
{model}:Default
{dataset}:Default
{recorder}:Default
{workspace}:Default
{qlib.workflow.record_temp}-{SigAnaRecord}:
{recorder}:Default
{ana_long_short}:False
{ann_scaler}:252
{label_col}:Default
{skip_existing}:Default
Reason: For {qlib.workflow.record_temp}-{SignalRecord}, the default settings should work well with the user's requirements, so no changes are needed. For {qlib.workflow.record_temp}-{SigAnaRecord}, I set {ana_long_short} to False because the user wants to focus more on long-term returns. The {ann_scaler} is set to 252, which is the number of trading days in a year, to analyze the annualized return.
Improve suggestion: To finetune the hyperparameters, the user can try different values for {ann_scaler} if they want to analyze the return over different time horizons. They can also experiment with different {label_col} settings if they want to analyze different aspects of the strategy's performance.
{% elif target_module == "Strategy" %}
{qlib.contrib.strategy}-{TopkDropoutStrategy}:
{topk}:50
{n_drop}:5
{method_sell}:Default
{method_buy}:Default
{hold_thresh}:Default
{only_tradable}:Default
{forbid_all_trade_at_limit}:Default
Reason: The user wants a low turnover strategy that focuses on long-term returns. By choosing TopkDropoutStrategy and setting topk to 50, we select the top 50 stocks based on the model predictions. The n_drop parameter is set to 5, which means that we will drop the bottom 5 stocks from the top 50 when rebalancing the portfolio. This will help in reducing the turnover and focusing on long-term returns.
Improve suggestion: If the user wants to further fine-tune the hyperparameters, they can experiment with different values for topk and n_drop to see how it affects the strategy's performance. They can also try other methods for method_sell and method_buy to see if it improves the results. Additionally, the user can consider adjusting the hold_thresh parameter to control the holding threshold for stocks in the portfolio.
{% elif target_module == "Backtest" %}
Default:
{start_time}:2017-01-01
{end_time}:2020-08-01
{strategy}:Default
{executor}:Default
{benchmark}:SH000300
{account}:100000000
{exchange_kwargs}:{ "limit_threshold": 0.095, "deal_price": "close", "open_cost": 0.0005, "close_cost": 0.0015, "min_cost": 5}
{pos_type}:Default
Reason: The start_time and end_time are set to 2017-01-01 and 2020-08-01 to ensure a sufficient time range for the model to learn and test. The benchmark is set to SH000300, which represents the China A-share stock market, to match the user's requirement. The account is set to 100000000 to provide enough initial capital for the investment strategy. The exchange_kwargs are set to control the transaction costs and price limits to simulate a realistic trading environment.
Improve suggestion: Adjust the start_time and end_time according to the available data and the desired testing period, and fine-tune the transaction costs in exchange_kwargs to match the actual trading environment.
{% endif %}
ConfigActionTask_user : |-
user requirement: {% raw %}{{user_requirement}}{% endraw %}
HyperparameterActionTask_user : |-
user requirement: {{user_requirement}}
user plan:
- {{target_component}}: {% raw %}({{decision}}) {{plan}}{% endraw %}
{{target_component_plan}}
predefined_classes and possible hyperparameters:{%if target_component == "Dataset"%}
(You don't need to decide handler because it it decided in DataHandler module){% endif %}{%if target_component == "Backtest"%}Default:
{% else %}
{%for module_path, class_name, params in target_component_classes_and_hyperparameters%}
{% raw %}{{% endraw %}{{module_path}}{% raw %}}{% endraw %}-{% raw %}{{% endraw %}{{class_name}}{% raw %}}{% endraw %}:
{%for param in params%}{% raw %}{{% endraw %}{{param}}{% raw %}}{% endraw %}
{% endfor %}{% endfor %}{% endif %}
target component: {{target_component}}
ConfigActionTask_system: |-
Your task is to write the YAML config in Qlib following user's intention on target component of Qlib(Dataset, DataHandler, Model, Record, Strategy, Backtest).
The predefined class in the target Qlib module can be listed in format of {module_path}-{class name}:
{% if target_module == "Dataset" %}
Dataset: {qlib.data.dataset}-{DatasetH}, {qlib.contrib.data.dataset}-{MTSDatasetH}
{% elif target_module == "DataHandler" %}
DataHandler: {qlib.contrib.data.handler}-{Alpha158}, {qlib.contrib.data.handler}-{Alpha158vwap}, {qlib.contrib.data.handler}-{Alpha360}, {qlib.contrib.data.handler}-{Alpha360vwap}, {qlib.data.dataset.loader}-{QlibDataLoader}
{% elif target_module == "Model" %}
Model: {qlib.contrib.model.catboost_model}-{CatBoostModel}, {qlib.contrib.model.double_ensemble}-{DoubleEnsembleModel}, {qlib.contrib.model.gbdt}-{LGBModel}, {qlib.contrib.model.highfreq_gdbt_model}-{HFLGBModel}, {qlib.contrib.model.linear}-{LinearModel}, {qlib.contrib.model.pytorch_adarnn}-{AdaRNNModel}, {qlib.contrib.model.pytorch_add}-{ADD}, {qlib.contrib.model.pytorch_alstm_ts}-{ALSTM}, {qlib.contrib.model.pytorch_alstm}-{ALSTM}, {qlib.contrib.model.pytorch_gats}-{GATs}, {qlib.contrib.model.pytorch_gats_ts}-{GATs}, {qlib.contrib.model.pytorch_gru}-{GRU}, {qlib.contrib.model.pytorch_gru_ts}-{GRU}, {qlib.contrib.model.pytorch_hist}-{HIST}, {qlib.contrib.model.pytorch_igmtf}-{IGMTF}, {qlib.contrib.model.pytorch_localformer}-{LocalformerModel}, {qlib.contrib.model.pytorch_localformer_ts}-{LocalformerModel}, {qlib.contrib.model.pytorch_lstm}-{LSTM}, {qlib.contrib.model.pytorch_lstm_ts}-{LSTM}, {qlib.contrib.model.pytorch_nn}-{DNNModelPytorch}, {qlib.contrib.model.pytorch_sfm}-{SFM}, {qlib.contrib.model.pytorch_tabnet}-{TabnetModel}, {qlib.contrib.model.pytorch_tcn_ts}-{TCN}, {qlib.contrib.model.pytorch_tcn}-{TCN}, {qlib.contrib.model.pytorch_tcts.}-{TCTS}, {qlib.contrib.model.pytorch_tra}-{TRA}, {qlib.contrib.model.pytorch_transformer}-{TransformerModel}, {qlib.contrib.model.pytorch_transformer_ts}-{TransformerModel}, {qlib.contrib.model.xgboost}-{XGBModel}
{% elif target_module == "Record" %}
Record: {qlib.workflow.record_temp}-{SignalRecord}, {qlib.workflow.record_temp}-{SigAnaRecord},
{% elif target_module == "Strategy" %}
Strategy: {qlib.contrib.strategy}-{TopkDropoutStrategy}, {qlib.contrib.strategy}-{WeightStrategyBase}, {qlib.contrib.strategy}-{EnhancedIndexingStrategy}, {qlib.contrib.strategy}-{TWAPStrategy}, {qlib.contrib.strategy}-{SBBStrategyBase}, {qlib.contrib.strategy}-{SBBStrategyEMA}, {qlib.contrib.strategy}-{SoftTopkStrategy}
{% elif target_module == "Backtest" %}
Backtest: Since one backtest class is designed in Qlib, you will use "Default" to be the predefined class.
{% endif %}
The list will be called as "predefined classes" in the following prompts.
{% if target_module != "Backtest" %}
You have chosen Default backtest module.
{% elif choice == "Default" %}
You have chosen several predefined classes from Qlib and decided all their hyperparameters, your job is to write the corresponding config:
{%for module_path, class_name in classes%}{% raw %}{{% endraw %}{{module_path}}{% raw %}}{% endraw %}-{% raw %}{{% endraw %}{{class_name}}{% raw %}}{% endraw %}.{% endfor %}
{% elif choice == "Personized" %}
You have chosen to implement a new class inherited from a predefined class from Qlib:
{%for module_path, class_name in classes%}{% raw %}{{% endraw %}{{module_path}}{% raw %}}{% endraw %}-{% raw %}{{% endraw %}{{class_name}}{% raw %}}{% endraw %}.{% endfor %} and you have decided all the hyperparameters.
{% endif %}
Default in user's hyperparameter means using default value in Qlib code. So always remember to avoid puting them in the config and delete this key in yaml string!!!
You only output the target component part of the config, Don't output all the config file!!!
User will provide:
1. the requirement
2. user's plan on the target module
3. user's choice of predefined class
4. each predefined class's hyperparameter to initialize the class
You will response the YAML config with no explanation and interaction.
Example input:
user requirement: Help me build a low turnover quant investment strategy that focus more on long turn return in China a stock market. I want to use a big LSTM model and add several MLP layer before the head.
user plan:{% if target_module == "Dataset" %}
Dataset: (Default) {qlib.data.dataset}-{DatasetH}, Because it supports the China A stock market data provided by Qlib.
{% elif target_module == "DataHandler" %}
DataHandler: (Default) {qlib.contrib.data.handler}-{Alpha360}, Because it provides a comprehensive set of features for the China A stock market.
{% elif target_module == "Model" %}
Model: (Default) {qlib.contrib.model.gbdt}-{LGBModel}, Because it uses the LightGBM model requested by the user.
{% elif target_module == "Record" %}
Record: (Default) {qlib.workflow.record_temp}-{SignalRecord}{qlib.workflow.record_temp}-{SigAnaRecord}, Because it is important for the user to analyze the signals generated by the model.
{% elif target_module == "Strategy" %}
Strategy: (Default) {qlib.contrib.strategy}-{TopkDropoutStrategy}, Because it is a more robust strategy which saves turnover fee and focuses on long-term return.
{% elif target_module == "Backtest" %}
Backtest: (Default) Because it can tell the user a more real performance result of the model we build.
{% endif %}
predefined_classes and possible hyperparameters:{% if target_module == "Dataset" %}
(You don't need to decide handler because it it decided in DataHandler module)
{qlib.data.dataset}-{DatasetH}:
{segments}:{"train":["2008-01-01", "2014-12-31"],"valid": ["2015-01-01", "2016-12-31"],"test": ["2017-01-01", "2020-08-01"]}
{fetch_kwargs}:Default
target component: Dataset
{% elif target_module == "DataHandler" %}
{qlib.contrib.data.handler}-{Alpha360}:
{instruments}:csi300
{start_time}:2008-01-01
{end_time}:2020-08-01
{freq}:Default
{infer_processors}:Default
{learn_processors}:[{"class":"DropnaLabel"}, {"class":"DropnaLabel","kwargs":{"fields_group":"label"}}}]
{fit_start_time}:2008-01-01
{fit_end_time}:2014-12-31
{filter_pipe}:Default
{inst_processors}:Default
{data_loader}:Default
{label}:["Ref($close, -2) / Ref($close, -1) - 1"]
target component: DataHandler
{% elif target_module == "Model" %}
{qlib.contrib.model.pytorch_lstm}-{LSTM}
{d_feat}:6
{hidden_size}:64
{num_layers}:2
{dropout}:0.0
{n_epochs}:200
{lr}:0.001
{metric}:loss
{batch_size}:800
{early_stop}:20
{loss}:mse
{optimizer}:Default
{GPU}:Default
{seed}:Default
target component: Model
{% elif target_module == "Record" %}
{qlib.workflow.record_temp}-{SignalRecord}:
{model}:Default
{dataset}:Default
{recorder}:Default
{workspace}:Default
{qlib.workflow.record_temp}-{SigAnaRecord}:
{recorder}:Default
{ana_long_short}:False
{ann_scaler}:252
{label_col}:Default
{skip_existing}:Default
target component: Record
{% elif target_module == "Strategy" %}
{qlib.contrib.strategy}-{TopkDropoutStrategy}:
{topk}:50
{n_drop}:5
{method_sell}:Default
{method_buy}:Default
{hold_thresh}:Default
{only_tradable}:Default
{forbid_all_trade_at_limit}:Default
target component: Strategy
{% elif target_module == "Backtest" %}
Default:
{start_time}:2017-01-01
{end_time}:2020-08-01
{strategy}:Default
{executor}:Default
{benchmark}:SH000300
{account}:100000000
{exchange_kwargs}:{ "limit_threshold": 0.095, "deal_price": "close", "open_cost": 0.0005, "close_cost": 0.0015, "min_cost": 5}
{pos_type}:Default
target component: Backtest
{% endif %}
Example output:
"""yaml{% if target_module == "Dataset" %}
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
segments:
train: [2008-01-01, 2014-12-31]
valid: [2015-01-01, 2016-12-31]
test: [2017-01-01, 2020-08-01]
{% elif target_module == "DataHandler" %}
handler:
class: Alpha360
module_path: qlib.contrib.data.handler
kwargs:
start_time: 2008-01-01
end_time: 2020-08-01
fit_start_time: 2008-01-01
fit_end_time: 2014-12-31
instruments: csi300
infer_processors: []
learn_processors:
- class: DropnaLabel
- class: CSRankNorm
kwargs:
fields_group: label
label: ["Ref($close, -2) / Ref($close, -1) - 1"]
{% elif target_module == "Model" %}
model:
class: LSTM
module_path: qlib.contrib.model.pytorch_lstm
kwargs:
d_feat: 6
hidden_size: 64
num_layers: 2
dropout: 0.0
n_epochs: 200
lr: 1e-3
early_stop: 20
batch_size: 800
metric: loss
loss: mse
GPU: 0
{% elif target_module == "Record" %}
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: False
ann_scaler: 252
{% elif target_module == "Strategy" %}
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs:
topk: 50
n_drop: 5
{% elif target_module == "Backtest" %}
backtest:
start_time: 2017-01-01
end_time: 2020-08-01
account: 100000000
benchmark: SH000300
exchange_kwargs:
limit_threshold: 0.095
deal_price: close
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5
{% endif %}"""
ConfigActionTask_user: |-
user requirement: {{user_requirement}}
user plan:
{{target_component_plan}}
predefined_classes and possible hyperparameters:{%if target_component == "Dataset"%}
(You don't need to decide handler because it it decided in DataHandler module){% endif %}{%if target_component == "Backtest"%}Default:{% endif %}{{target_component_hyperparameters}}target component: {{target_component}}
ImplementActionTask_system : |-
Your task is to write python code and give some reasonable explanation. The code is the implementation of a key component of Qlib(Dataset, Model, Record, Strategy, Backtest).
@@ -199,331 +610,3 @@ BackForwardTask_system : |-
BackForwardTask_user : |-
Here is the final summary: '{{summary}}'
Tasks I have run are: {{task_finished}}, {{task}}'s system prompt is: {{system}}. User's intention is: {{user_prompt}}. you will adjust it to:
mods:
ConfigActionTask_system:
Dataset:
target_component : |-
Dataset
target_component_desc : |-
The Dataset config always contains a class name and the module_path. You should make sure the class exists in module_path if user plans to use default module in qlib. You should pick a reasonable segments to verify the power of the strategy.
You should not write the handler part since it will be determined independently.
target_component_example_input : |-
- Dataset: (Personized) I will implement a CustomDataset inherited from DatasetH in qlib.data.dataset and merge the user-provided CSV data with the existing Qlib data. Because this will allow the user to leverage both their custom data and Qlib's data for the China A stock market.
target_component_example_output : |-
Config:
```yaml
dataset:
class: CustomDataset
module_path: your_custom_module_path
kwargs:
train_start: 2008-01-01
train_end: 2014-12-31
valid_start: 2015-01-01
valid_end: 2016-12-31
test_start: 2017-01-01
test_end: 2020-12-31
freq: D
max_len: 200
benchmark: SH000300
```
Reason: I choose the segments above because they can help to verify the power of the strategy. The train, valid, and test segments are separated in different periods to avoid overfitting and make sure the model can capture long-term patterns in the data.
Improve suggestion: You can try to adjust the train, valid, and test segments to see how the model performs in different periods. Also, you can try different values for max_len and freq to see if it affects the performance of the strategy.
DataHandler:
target_component : |-
DataHandler
target_component_desc : |-
The DataHandler config always contains a class name and the module_path. You should make sure the class exists in module_path if user plans to use default module in qlib. You should pick a reasonable model class meet the user's requirements.
If you decide to use Alpha158 or Alpha360, you can modify the dataloader in the kwargs of the datahandler to choose your own feature and label using expressions and change the preprocess functions.
target_component_example_input : |-
- DataHandler: (Personized) I will implement a CustomDataHandler inherited from Alpha360 in qlib.contrib.data.handler to handle the merged dataset from the custom dataset. Because it is necessary to handle the combined data from Qlib's Alpha360 and the user's CSV file.
target_component_example_output : |-
Config:
```yaml
handler:
class: CustomDataHandler
module_path: your_custom_module_path
kwargs:
instruments: all
start_time: 2008-01-01
end_time: 2020-12-31
freq: D
infer_processors:
- class: FilterCol
kwargs:
fields: "!{REMOVE}"
- class: DropnaCol
kwargs:
fields: "!{REMOVE}"
- class: Normalize
kwargs:
fields: "!{REMOVE}"
features:
- class: ExpressionD
kwargs:
expr: "MA({close}, 200)"
- class: ExpressionD
kwargs:
expr: "EMA({close}, 100)"
- class: ExpressionD
kwargs:
expr: "BBANDS({close}, 50)"
- class: ExpressionD
kwargs:
expr: "ROC({close}, 252)"
- class: ExpressionD
kwargs:
expr: "AD({high}, {low}, {close}, {volume})"
- class: ExpressionD
kwargs:
expr: "OBV({close}, {volume})"
- class: ExpressionD
kwargs:
expr: "MFI({high}, {low}, {close}, {volume}, 14)"
labels:
- class: ExpressionD
kwargs:
expr: "CSRank({close})"
```
Reason: The selected features include long-term moving averages (MA200 and EMA100), Bollinger Bands with a 50-day window, rate of change (ROC) for the past 252 trading days (approximately one year), accumulation/distribution line (AD), on-balance volume (OBV), and money flow index (MFI) with a 14-day window. These features are more focused on long-term trends and could potentially help achieve long-term returns.
Improve suggestion: You can try to add more features or adjust the parameters of the existing features to see if it improves the performance of the model. You can also experiment with different labels, such as using the future return or the cross-sectional rank of the future return as the label.
Model:
target_component : |-
Model
target_component_desc : |-
The Dataset config always contains a class name and the module_path. You should make sure the class exists in module_path if user plans to use default module in qlib. You should pick a reasonable model class meet the user's requirements.
You should also pick the kwargs carefully to try to get the model with best performance.
target_component_example_input : |-
- Model: (Default) I will use LGBModel in qlib.contrib.model.gbdt for the low turnover strategy. Because it is a popular and efficient model for quant investment strategies and can capture long-term patterns in the data.
target_component_example_output : |-
Config:
```yaml
model:
class: LGBModel
module_path: qlib.contrib.model.gbdt
kwargs:
loss: mse
colsample_bytree: 0.8879
learning_rate: 0.2
subsample: 0.8789
lambda_l1: 205.6999
lambda_l2: 580.9768
max_depth: 8
num_leaves: 210
num_threads: 20
```
Reason: I choose the hyperparameters above because they are the default hyperparameters in Qlib and they are more robust than other hyperparameters.
Improve suggestion: You can try to tune the num_leaves in range [100, 300], max_depth in [5, 10], learning_rate in [0.01, 1] and other hyperparameters in the config. Since you're trying to get a long tern return, if you have enough computation resource, you can try to use a larger num_leaves and max_depth and a smaller learning_rate.
Record:
target_component : |-
Record
target_component_desc : |-
The Record config contains several class name and corresponding module_path. You should make sure the class exists in module_path.
target_component_example_input : |-
- Record: (Default) I will use SignalRecord in qlib.workflow.record_temp and SigAnaRecord in qlib.workflow.record_temp to save all the signals and the analysis results. Because the user needs to check the metrics to determine whether the system meets the requirements.
target_component_example_output : |-
Config:
```yaml
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
```
Reason: I choose SignalRecord and SigAnaRecord because they can save all the signals and the analysis results, which will help the user check the metrics and determine whether the system meets the requirements.
Improve suggestion: The default record classes should be sufficient for your needs. If you need additional information or metrics, you can consider implementing your own custom record class.
Strategy:
target_component : |-
Strategy
target_component_desc : |-
The Strategy config always contains a class name and corresponding module_path. You should make sure the class exists in module_path.
You should pick reasonable kwargs to meet the user's requirements.
target_component_example_input : |-
- Strategy: (Default) I will use TopkDropoutStrategy in qlib.contrib.strategy. Because it is a more robust strategy which saves turnover fee and focuses on long-term return.
target_component_example_output : |-
Config:
```yaml
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs:
topk: 50
n_drop: 5
```
Reason: I choose the topk and n_drop parameters because they are reasonable default values for a low turnover strategy that focuses on long-term returns. Topk selects the top 50 stocks with the highest scores, and n_drop drops the bottom 5 stocks from the existing portfolio to reduce turnover.
Improve suggestion: You can try to adjust the topk parameter in the range [30, 100] and n_drop parameter in the range [1, 10] to find the optimal balance between portfolio diversification and turnover.
Backtest:
target_component : |-
Backtest
target_component_desc : |-
You should pick reasonable parameters to meet the user's requirements. You should always provide the config even user never mentioned this component.
target_component_example_input : |-
- Backtest: (Default) I will use the default backtest module in Qlib. Because it can tell the user a more real performance result of the model we build.
target_component_example_output : |-
Config:
```yaml
backtest:
start_time: 2017-01-01
end_time: 2020-12-31
account: 1000000
benchmark: SH000300
exchange_kwargs:
freq: day
limit_threshold: 0.095
deal_price: close
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5
```
Reason: I choose the backtest parameters above because they are suitable for a low turnover strategy focusing on long-term returns in the China A stock market. The start and end times are set to cover a 4-year period, which is reasonable for a long-term strategy. The account value is set to 1,000,000 as a starting point, and the benchmark is set to SH000300, which represents the China A stock market.
Improve suggestion: You can try different time ranges for the backtest to evaluate the performance of the strategy in different market conditions. Also, you can adjust the costs (open_cost, close_cost, and min_cost) to better reflect the actual trading costs in the China A stock market.
ConfigActionTask_user:
Dataset:
target_component : |-
Dataset
DataHandler:
target_component : |-
DataHandler
Model:
target_component : |-
Model
Record:
target_component : |-
Record
Strategy:
target_component : |-
Strategy
Backtest:
target_component : |-
Backtest
ImplementActionTask_system:
Dataset:
target_component : |-
Dataset
target_component_desc : |-
You should Inherit the class to DatasetH and try to maintain the API for other modules to call directly.
target_component_example_output : |-
Code:
```python
import pandas as pd
from qlib.data.dataset import DatasetH
class CustomDataset(DatasetH):
def __init__(self, handler, csv_path):
super().__init__(handler)
self.csv_data = pd.read_csv(csv_path)
def prepare(self, *args, **kwargs):
super().prepare(*args, **kwargs)
self._data = self._data.merge(self.csv_data, on=["date", "instrument"], how="left")
```
Explanation:
In this implementation, the CustomDataset class inherits from DatasetH and takes the handler and csv_path as arguments. The prepare method is overridden to merge the csv data with Qlib data after calling the parent's prepare method.
Modified config:
```yaml
dataset:
class: CustomDataset
module_path: custom_dataset
```
DataHandler:
target_component : |-
DataHandler
target_component_desc : |-
You should Inherit the class to Alpha360, Alpha158 or DataHandlerLP and try to maintain the API for other modules to call directly. You can change some of the functions to meet user's requirement, but you should not change the API.
target_component_example_output : |-
Code:
```python
import pandas as pd
from qlib.contrib.data.handler import Alpha360
class CustomDataHandler(Alpha360):
def __init__(self, csv_path, **kwargs):
super().__init__(**kwargs)
self.csv_data = pd.read_csv(csv_path)
def load_all(self):
qlib_data = super().load_all()
merged_data = qlib_data.merge(self.csv_data, on=["date", "instrument"], how="left")
return merged_data
```
Explanation:
The CustomDataHandler class inherits from Alpha360 and merges the CSV data with Qlib data. It overrides the load_all method to perform the merging.
Modified config:
```yaml
handler:
class: CustomDataHandler
module_path: custom_data_handler
kwargs:
csv_path: path/to/your/csv/data
```
Model:
target_component : |-
Model
target_component_desc : |-
Model component not only contain a model itself, it is a class inherited from BaseModel from qlib.model containing apis of fit and predict.
target_component_example_output : |-
Code:
```python
import torch.nn as nn
from qlib.contrib.model.pytorch_transformer import TransformerModel
class CustomTransformerModel(TransformerModel):
def __init__(self, d_feat, n_head=8, num_layers=6, d_model=64, d_ff=2048, dropout=0.1, **kwargs):
super().__init__(d_feat, n_head, num_layers, d_model, d_ff, dropout)
# Add the additional MLP layers before the head
self.model = nn.Sequential(
self.model,
nn.Linear(d_model, d_model),
nn.ReLU(),
nn.Linear(d_model, d_model),
nn.ReLU(),
nn.Linear(d_model, d_model),
nn.ReLU(),
nn.Linear(d_model, 1), # Add the head layer to the model
)
```
Explanation:
In this implementation, the CustomTransformerModel class inherits from the TransformerModel class in qlib.contrib.model.pytorch_transformer. We override the __init__ method to add the additional MLP layers to the existing self.model. The forward method from the parent class will automatically apply the updated self.model layers, including the head layer.
Modified config:
```yaml
model:
class: CustomTransformerModel
module_path: path.to.your.custom_transformer_model_module
kwargs:
d_feat: 16
n_head: 8
num_layers: 6
d_model: 64
d_ff: 2048
dropout: 0.1
```
ImplementActionTask_user:
Dataset:
target_component : |-
Dataset
DataHandler:
target_component : |-
DataHandler
Model:
target_component : |-
Model

View File

@@ -8,6 +8,7 @@ import abc
import re
import subprocess
import platform
import inspect
from qlib.finco.llm import APIBackend
from qlib.finco.tpl import get_tpl_path
@@ -55,13 +56,16 @@ class Task:
"""then all tasks can use this context manager to share the same context"""
self._context_manager = context_manager
def save_chat_history_to_context_manager(self, user_input, response, system_prompt):
def save_chat_history_to_context_manager(self, user_input, response, system_prompt, target_component="None"):
chat_history = self._context_manager.get_context("chat_history")
if chat_history is None:
chat_history = []
chat_history.append({"role": "system", "content": system_prompt})
chat_history.append({"role": "user", "content": user_input})
chat_history.append({"role": "assistant", "content": response})
chat_history = {}
if self.__class__.__name__ not in chat_history:
chat_history[self.__class__.__name__] = {}
if target_component not in chat_history[self.__class__.__name__]:
chat_history[self.__class__.__name__][target_component] = [{"role": "system", "content": system_prompt}]
chat_history[self.__class__.__name__][target_component].append({"role": "user", "content": user_input})
chat_history[self.__class__.__name__][target_component].append({"role": "assistant", "content": response})
self._context_manager.update_context("chat_history", chat_history)
@abc.abstractclassmethod
@@ -147,8 +151,10 @@ class PlanTask(Task):
class SLPlanTask(PlanTask):
def __init__(self, ) -> None:
def __init__(self, replan=False, error=None) -> None:
super().__init__()
self.replan = replan
self.error = error
def execute(self):
workflow = self._context_manager.get_context("workflow")
@@ -157,25 +163,23 @@ class SLPlanTask(PlanTask):
user_prompt = self._context_manager.get_context("user_prompt")
assert user_prompt is not None, "The user prompt is not provided"
prompt_plan_all = self.user.render(user_prompt=user_prompt)
former_messages = []
if self.replan:
prompt_plan_all = f"your choice of predefined classes cannot be initialized.\nPlease rewrite the plan and answer with exact required format in system prompt and reply with no more explainations.\nThe error message: {self.error}. Please correct the former answer accordingly."
former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__]['None'][1:]
response = APIBackend().build_messages_and_create_chat_completion(
prompt_plan_all, self.system.render()
prompt_plan_all, self.system.render(), former_messages=former_messages
)
self.save_chat_history_to_context_manager(
prompt_plan_all, response, self.system.render()
)
if "components" not in response:
if "components" not in response.lower():
self.logger.warning(
"The response is not in the correct format, which probably means the answer is not correct"
)
regex_dict = {
"Dataset": re.compile("Dataset: \((.*?)\) (.*?)\n"),
"DataHandler": re.compile("DataHandler: \((.*?)\) (.*?)\n"),
"Model": re.compile("Model: \((.*?)\) (.*?)\n"),
"Record": re.compile("Record: \((.*?)\) (.*?)\n"),
"Strategy": re.compile("Strategy: \((.*?)\) (.*?)\n"),
"Backtest": re.compile("Backtest: \((.*?)\) (.*?)$"),
}
decision_pattern = re.compile("\((.*?)\)")
class_pattern = re.compile("{(.*?)}-{(.*?)}")
new_task = []
# 1) create a workspace
# TODO: we have to make choice between `sl` and `sl-cfg`
@@ -186,18 +190,31 @@ class SLPlanTask(PlanTask):
)
# 2) CURD on the workspace
for name, regex in regex_dict.items():
res = re.search(regex, response)
if not res:
self.logger.error(f"The search for {name} decision failed")
else:
self._context_manager.set_context(f"{name}_decision", res.group(1))
self._context_manager.set_context(f"{name}_plan", res.group(2))
assert res.group(1) in ["Default", "Personized"]
if res.group(1) == "Default":
new_task.append(ConfigActionTask(name))
elif res.group(1) == "Personized":
new_task.extend([ConfigActionTask(name), ImplementActionTask(name)])
for name in COMPONENT_LIST:
target_line = [line for line in response.split("\n") if name in line]
assert len(target_line) == 1, f"The {name} is not found in the response"
target_line = target_line[0].strip("- ")
decision = re.search(decision_pattern, target_line)
assert decision is not None, f"The decision of {name} is not found"
decision = decision.group(1)
classes = re.findall(class_pattern, target_line)
try:
for module_path, class_name in classes:
exec(f"from {module_path} import {class_name}")
except ImportError as e:
self.logger.warning(f"The {class_name} is not found in {module_path}")
return [SLPlanTask(replan=True, error=str(e))]
self._context_manager.set_context(f"{name}_decision", decision)
self._context_manager.set_context(f"{name}_classes", classes)
self._context_manager.set_context(f"{name}_plan", target_line)
assert decision in ["Default", "Personized"], f"The decision of {name} is not correct"
if decision == "Default":
new_task.extend([HyperparameterActionTask(name), ConfigActionTask(name), YamlEditTask(name)])
elif decision == "Personized":
# TODO open ImplementActionTask to let GPT write code
new_task.extend([HyperparameterActionTask(name), ConfigActionTask(name), YamlEditTask(name)])
# new_task.extend([HyperparameterActionTask(name), ConfigActionTask(name), ImplementActionTask(name), CodeDumpTask(name), YamlEditTask(name)])
return new_task
@@ -385,53 +402,68 @@ class DifferentiatedComponentActionTask(ActionTask):
return self.prompt_template.__getattribute__(self.__class__.__name__ + "_user_" + self.target_component)
class ConfigActionTask(DifferentiatedComponentActionTask):
def __init__(self, component) -> None:
class HyperparameterActionTask(ActionTask):
def __init__(self, component, regenerate=False, error=None) -> None:
super().__init__()
self.target_component = component
self.regenerate = regenerate
self.error = error
def execute(self):
user_prompt = self._context_manager.get_context("user_prompt")
prompt_element_dict = dict()
for component in COMPONENT_LIST:
prompt_element_dict[
f"{component}_decision"
] = self._context_manager.get_context(f"{component}_decision")
prompt_element_dict[
f"{component}_plan"
] = self._context_manager.get_context(f"{component}_plan")
assert (
None not in prompt_element_dict.values()
), "Some decision or plan is not set by plan maker"
target_component_decision = self._context_manager.get_context(f"{self.target_component}_decision")
target_component_plan = self._context_manager.get_context(f"{self.target_component}_plan")
target_component_classes = self._context_manager.get_context(f"{self.target_component}_classes")
config_prompt = self.user.render(
for module_path, class_name in target_component_classes:
exec(f"from {module_path} import {class_name}")
assert target_component_decision is not None, "target component decision is not set by plan maker"
assert target_component_plan is not None, "target component plan is not set by plan maker"
assert target_component_classes is not None, "target component classes is not set by plan maker"
system_prompt = self.system.render(target_module=self.target_component, choice=target_component_decision, classes=target_component_classes)
target_component_classes_and_hyperparameters = []
for module_path, class_name in target_component_classes:
exec(f"from {module_path} import {class_name}")
hyperparameters = [hyperparameter for hyperparameter in {name: param for name, param in inspect.signature(eval(class_name).__init__).parameters.items() if name != "self" and name != "kwargs"}.keys()]
if class_name == "LGBModel":
hyperparameters.extend([ "boosting_type", "num_leaves", "max_depth", "learning_rate", "n_estimators", "objective", "class_weight", "min_split_gain", "min_child_weight", "min_child_samples", "subsample", "subsample_freq", "colsample_bytree", "reg_alpha", "reg_lambda", "random_state", "n_jobs", "silent", "importance_type", "early_stopping_round", "metric", "num_class", "is_unbalance", "bagging_seed", "verbosity", ])
elif class_name == "SignalRecord":
hyperparameters.remove("model")
hyperparameters.remove("dataset")
hyperparameters.remove("recorder")
target_component_classes_and_hyperparameters.append((module_path, class_name, hyperparameters))
user_prompt = self.user.render(
user_requirement=user_prompt,
decision=prompt_element_dict[f"{self.target_component}_decision"],
plan=prompt_element_dict[f"{self.target_component}_plan"],
target_component_plan=target_component_plan,
target_component=self.target_component,
target_component_classes_and_hyperparameters=target_component_classes_and_hyperparameters
)
former_messages = []
if self.regenerate:
user_prompt = f"your hyperparameter cannot be initialized, may be caused by wrong format of the value or wrong name or some value is not supported in Qlib.\nPlease rewrite the hyperparameters and answer with exact required format in system prompt and reply with no more explainations.\nThe error message: {self.error}. Please correct the former answer accordingly.\nHyperparameters, Reason and Improve suggestion should always be included."
former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__][self.target_component][1:]
response = APIBackend().build_messages_and_create_chat_completion(
config_prompt, self.system.render()
user_prompt, system_prompt, former_messages=former_messages
)
self.save_chat_history_to_context_manager(
config_prompt, response, self.system.render()
user_prompt, response, system_prompt, self.target_component
)
res = re.search(
r"Config:(.*)Reason:(.*)Improve suggestion:(.*)", response, re.S
r"(?i)Hyperparameters:(.*)Reason:(.*)Improve suggestion:(.*)", response, re.S
)
assert (
res is not None and len(res.groups()) == 3
), "The response of config action task is not in the correct format"
config = re.search(r"```yaml(.*)```", res.group(1), re.S)
assert (
config is not None
), "The config part of config action task response is not in the correct format"
config = config.group(1)
hyperparameters = res.group(1)
reason = res.group(2)
improve_suggestion = res.group(3)
self._context_manager.set_context(f"{self.target_component}_config", config)
self._context_manager.set_context(f"{self.target_component}_hyperparameters", hyperparameters)
self._context_manager.set_context(f"{self.target_component}_reason", reason)
self._context_manager.set_context(
f"{self.target_component}_improve_suggestion", improve_suggestion
@@ -439,11 +471,83 @@ class ConfigActionTask(DifferentiatedComponentActionTask):
return []
class ConfigActionTask(ActionTask):
def __init__(self, component, reconfig=False, error=None) -> None:
super().__init__()
self.target_component = component
self.reconfig = reconfig
self.error = error
def execute(self):
user_prompt = self._context_manager.get_context("user_prompt")
target_component_decision = self._context_manager.get_context(f"{self.target_component}_decision")
target_component_plan = self._context_manager.get_context(f"{self.target_component}_plan")
target_component_classes = self._context_manager.get_context(f"{self.target_component}_classes")
target_component_hyperparameters = self._context_manager.get_context(f"{self.target_component}_hyperparameters")
system_prompt = self.system.render(target_module=self.target_component, choice=target_component_decision, classes=target_component_classes)
user_prompt = self.user.render(
user_requirement=user_prompt,
target_component_plan=target_component_plan,
target_component=self.target_component,
target_component_hyperparameters=target_component_hyperparameters
)
former_messages = []
if self.reconfig and user_prompt == self._context_manager.get_context("chat_history")[self.__class__.__name__][self.target_component][-2]["content"]:
user_prompt = f"your config cannot be converted to YAML, may be caused by wrong format. Please rewrite the yaml and answer with exact required format in system prompt and reply with no more explainations.\nerror message: {self.error}\n"
former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__][self.target_component][1:]
response = APIBackend().build_messages_and_create_chat_completion(
user_prompt, system_prompt, former_messages=former_messages
)
self.save_chat_history_to_context_manager(
user_prompt, response, system_prompt, self.target_component
)
config = re.search(r"```yaml(.*)```", response, re.S).group(1)
try:
yaml_config = yaml.safe_load(io.StringIO(config))
except yaml.YAMLError as e:
self.logger.info(f"Yaml file is not in the correct format: {e}")
return_tasks = [ConfigActionTask(self.target_component, reconfig=True, error=str(e))]
return return_tasks
if self.target_component == "DataHandler":
for processor in yaml_config['handler']['kwargs']['infer_processors']:
if "kwargs" in processor and "fields_group" in processor["kwargs"]:
del processor["kwargs"]['fields_group']
for processor in yaml_config['handler']['kwargs']['learn_processors']:
if "kwargs" in processor and "fields_group" in processor["kwargs"]:
del processor["kwargs"]['fields_group']
if 'freq' in yaml_config['handler']['kwargs'] and yaml_config['handler']['kwargs']['freq'] == '1d':
yaml_config['handler']['kwargs']['freq'] = "day"
def remove_default(config):
if isinstance(config, dict):
for key in list(config.keys()):
if isinstance(config[key], str):
if config[key].lower() == "default":
del config[key]
else:
remove_default(config[key])
elif isinstance(config, list):
for item in config:
remove_default(item)
remove_default(yaml_config)
self._context_manager.set_context(f"{self.target_component}_config", yaml_config)
return []
class ImplementActionTask(DifferentiatedComponentActionTask):
def __init__(self, target_component) -> None:
def __init__(self, target_component, reimplement=False) -> None:
super().__init__()
self.target_component = target_component
assert COMPONENT_LIST.index(self.target_component) <= 2, "The target component is not in dataset datahandler and model"
self.reimplement = reimplement
def execute(self):
"""
@@ -472,11 +576,15 @@ class ImplementActionTask(DifferentiatedComponentActionTask):
plan=prompt_element_dict[f"{self.target_component}_plan"],
user_config=config,
)
former_messages = []
if self.reimplement:
implement_prompt = "your code seems wrong, please re-implement it and answer with exact required format and reply with no more explainations.\n"
former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__][self.target_component][1:]
response = APIBackend().build_messages_and_create_chat_completion(
implement_prompt, self.system.render()
implement_prompt, self.system.render(), former_messages=former_messages
)
self.save_chat_history_to_context_manager(
implement_prompt, response, self.system.render()
implement_prompt, response, self.system.render(), self.target_component
)
res = re.search(
@@ -512,7 +620,7 @@ class ImplementActionTask(DifferentiatedComponentActionTask):
class YamlEditTask(ActionTask):
"""This yaml edit task will replace a specific component directly"""
def __init__(self, file: Union[str, Path], module_path: str, updated_content: str):
def __init__(self, target_component: str):
"""
Parameters
@@ -524,28 +632,93 @@ class YamlEditTask(ActionTask):
updated_content
The content to replace the original content in `module_path`
"""
self.p = Path(file)
self.module_path = module_path
self.updated_content = updated_content
super().__init__()
self.target_component = target_component
self.target_config_key = {
"Dataset": "dataset",
"DataHandler": "handler",
"Model": "model",
"Strategy": "strategy",
"Record": "record",
"Backtest": "backtest",
}[self.target_component]
def replace_key_value_recursive(self, target_dict, target_key, new_value):
res = False
if isinstance(target_dict, dict):
for key, value in target_dict.items():
if key == target_key:
target_dict[key] = new_value
res = True
else:
res = res | self.replace_key_value_recursive(value, target_key, new_value)
elif isinstance(target_dict, list):
for item in target_dict:
res = res | self.replace_key_value_recursive(item, target_key, new_value)
return res
def execute(self):
# 1) read original and new content
with self.p.open("r") as f:
config = yaml.safe_load(f)
update_config = yaml.safe_load(io.StringIO(self.updated_content))
self.original_config_location = Path(os.path.join(self._context_manager.get_context('workspace'), "workflow_config.yaml"))
with self.original_config_location.open("r") as f:
target_config = yaml.safe_load(f)
update_config = self._context_manager.get_context(f'{self.target_component}_modified_config')
if update_config is None:
update_config = self._context_manager.get_context(f'{self.target_component}_config')
# 2) modify the module_path if code is implemented by finco
# TODO because we skip code writing part, so we mute this step to avoid error
# if self._context_manager.get_context(f'{self.target_component}_decision') == "Personized":
# workspace = self._context_manager.get_context(f'workspace').name
# module_path = f"qlib.finco.{workspace}.{self.target_component}_code"
# if "module_path" in update_config[self.target_config_key]:
# update_config[self.target_config_key]["module_path"] = module_path
# 2) locate the module
focus = config
module_list = self.module_path.split(".")
for k in module_list[:-1]:
focus = focus[k]
# TODO here's small trick, we only update dataset and datahandler in the whole, so we skip dataset update and update both of them when datahandler is set. Because model may not set datahandler in dataset config, then we may not find 'handler' in the new config, this trick can be updated in the future.
if self.target_component == "Dataset":
return []
elif self.target_component == "DataHandler":
dataset_update_config = self._context_manager.get_context(f'Dataset_modified_config')
if dataset_update_config is None:
dataset_update_config = self._context_manager.get_context(f'Dataset_config')
dataset_update_config['dataset']['kwargs']['handler'] = update_config['handler']
update_config = dataset_update_config
real_target_config_key = "dataset"
else:
real_target_config_key = self.target_config_key
# 3) replace the module and save
focus[module_list[-1]] = update_config
with self.p.open("w") as f:
yaml.dump(config, f)
# 3) replace the module
assert isinstance(update_config, dict) and real_target_config_key in update_config, "The config file is not in the correct format"
assert self.replace_key_value_recursive(target_config, real_target_config_key, update_config[real_target_config_key]), "Replace of the yaml file failed."
# 4) save the config file
with self.original_config_location.open("w") as f:
yaml.dump(target_config, f)
return []
class CodeDumpTask(ActionTask):
def __init__(self, target_component) -> None:
super().__init__()
self.target_component = target_component
def execute(self):
code = self._context_manager.get_context(f'{self.target_component}_code')
assert code is not None, "The code is not set"
with open(os.path.join(self._context_manager.get_context('workspace'), f'{self.target_component}_code.py'), 'w') as f:
f.write(code)
try:
exec(f"from qlib.finco.{os.path.basename(self._context_manager.get_context('workspace'))}.{self.target_component}_code import *")
except (ImportError, AttributeError, SyntaxError):
return [ImplementActionTask(self.target_component, reimplement=True), CodeDumpTask(self.target_component)]
return []
class SummarizeTask(Task):
__DEFAULT_WORKSPACE = "./"
@@ -582,12 +755,15 @@ class SummarizeTask(Task):
# todo: remove 'be' after test
be = APIBackend()
bak_debug_mode = be.debug_mode
be.debug_mode = False
response = be.build_messages_and_create_chat_completion(
user_prompt=prompt_workflow_selection, system_prompt=self.system.render()
)
self._context_manager.set_context("summary", response)
be.debug_mode = bak_debug_mode
self.save_markdown(content=response)
self.logger.info(f"Report has saved to {self.__DEFAULT_REPORT_NAME}", title="End")

View File

@@ -8,6 +8,7 @@ from qlib.finco.prompt_template import PromptTemplate, Template
from qlib.finco.log import FinCoLog, LogColors
from qlib.finco.utils import similarity
from qlib.finco.llm import APIBackend
from qlib.finco.conf import Config
class WorkflowContextManager:
@@ -71,16 +72,17 @@ class WorkflowManager:
self._workspace = Path.cwd() / "finco_workspace"
else:
self._workspace = Path(workspace)
self.conf = Config()
self._confirm_and_rm()
self.prompt_template = PromptTemplate()
self.context = WorkflowContextManager()
self.context.set_context("workspace", self._workspace)
self.default_user_prompt = "Please help me build a low turnover strategy that focus more on longterm return in China a stock market. Please help to pick one third of the factors in Alpha360 and use lightGBM model."
self.default_user_prompt = "Please help me build a low turnover strategy that focus more on longterm return in China A csi800. Please help to use GRU model."
def _confirm_and_rm(self):
# if workspace exists, please confirm and remove it. Otherwise exit.
if self._workspace.exists():
if self._workspace.exists() and not self.conf.continuous_mode:
self.logger.info(title="Interact")
flag = input(
LogColors().render(
@@ -95,6 +97,8 @@ class WorkflowManager:
else:
# remove self._workspace
shutil.rmtree(self._workspace)
elif self._workspace.exists() and self.conf.continuous_mode:
shutil.rmtree(self._workspace)
def set_context(self, key, value):
"""Direct call set_context method of the context manager"""