2025-05-29 16:16:51 +08:00
import random
2024-09-29 18:43:17 +08:00
import re
2024-08-05 21:01:59 +08:00
import shutil
from pathlib import Path
import pandas as pd
from jinja2 import Environment , StrictUndefined
2024-11-25 16:27:34 +08:00
from rdagent.components.coder.factor_coder.config import FACTOR_COSTEER_SETTINGS
2024-08-05 21:01:59 +08:00
from rdagent.utils.env import QTDockerEnv
def generate_data_folder_from_qlib ():
template_path = Path ( __file__ ) . parent / "factor_data_template"
qtde = QTDockerEnv ()
qtde . prepare ()
# Run the Qlib backtest
2025-07-02 15:11:18 +08:00
execute_log = qtde . check_output (
2024-08-05 21:01:59 +08:00
local_path = str ( template_path ),
entry = f "python generate.py" ,
)
2026-04-04 08:20:58 +02:00
assert ( Path ( __file__ ) . parent / "factor_data_template" / "intraday_pv_all.h5" ) . exists (), (
"intraday_pv_all.h5 is not generated. It means rdagent/scenarios/qlib/experiment/factor_data_template/generate.py is not executed correctly. Please check the log: \n "
2025-06-12 17:34:51 +08:00
+ execute_log
)
2026-04-04 08:20:58 +02:00
assert ( Path ( __file__ ) . parent / "factor_data_template" / "intraday_pv_debug.h5" ) . exists (), (
"intraday_pv_debug.h5 is not generated. It means rdagent/scenarios/qlib/experiment/factor_data_template/generate.py is not executed correctly. Please check the log: \n "
2025-06-12 17:34:51 +08:00
+ execute_log
)
2024-08-05 21:01:59 +08:00
2024-11-25 16:27:34 +08:00
Path ( FACTOR_COSTEER_SETTINGS . data_folder ) . mkdir ( parents = True , exist_ok = True )
2024-08-05 21:01:59 +08:00
shutil . copy (
2026-04-04 08:20:58 +02:00
Path ( __file__ ) . parent / "factor_data_template" / "intraday_pv_all.h5" ,
Path ( FACTOR_COSTEER_SETTINGS . data_folder ) / "intraday_pv.h5" ,
2024-08-05 21:01:59 +08:00
)
shutil . copy (
Path ( __file__ ) . parent / "factor_data_template" / "README.md" ,
2024-11-25 16:27:34 +08:00
Path ( FACTOR_COSTEER_SETTINGS . data_folder ) / "README.md" ,
2024-08-05 21:01:59 +08:00
)
2024-11-25 16:27:34 +08:00
Path ( FACTOR_COSTEER_SETTINGS . data_folder_debug ) . mkdir ( parents = True , exist_ok = True )
2024-08-05 21:01:59 +08:00
shutil . copy (
2026-04-04 08:20:58 +02:00
Path ( __file__ ) . parent / "factor_data_template" / "intraday_pv_debug.h5" ,
Path ( FACTOR_COSTEER_SETTINGS . data_folder_debug ) / "intraday_pv.h5" ,
2024-08-05 21:01:59 +08:00
)
shutil . copy (
Path ( __file__ ) . parent / "factor_data_template" / "README.md" ,
2024-11-25 16:27:34 +08:00
Path ( FACTOR_COSTEER_SETTINGS . data_folder_debug ) / "README.md" ,
2024-08-05 21:01:59 +08:00
)
2024-11-06 13:14:35 +08:00
def get_file_desc ( p : Path , variable_list = []) -> str :
2024-10-08 02:07:20 +08:00
"""
Get the description of a file based on its type.
Parameters
----------
p : Path
The path of the file.
Returns
-------
str
The description of the file.
"""
p = Path ( p )
2026-02-13 10:50:46 +08:00
JJ_TPL = Environment ( undefined = StrictUndefined ) . from_string ( """
2025-05-29 16:16:51 +08:00
# {{file_name}}
## File Type
{{type_desc}}
## Content Overview
2024-10-08 02:07:20 +08:00
{{content}}
2026-02-13 10:50:46 +08:00
""" )
2024-10-08 02:07:20 +08:00
if p . name . endswith ( ".h5" ):
df = pd . read_hdf ( p )
2025-05-29 16:16:51 +08:00
pd . set_option ( "display.max_columns" , None )
pd . set_option ( "display.max_rows" , None )
pd . set_option ( "display.max_colwidth" , None )
df_info = "### Data Structure \n "
df_info += (
f "- Index: MultiIndex with levels { df . index . names } \n "
if isinstance ( df . index , pd . MultiIndex )
else f "- Index: { df . index . name } \n "
)
2024-10-10 20:29:17 +08:00
2025-05-29 16:16:51 +08:00
df_info += " \n ### Columns \n "
2024-11-06 13:14:35 +08:00
columns = df . dtypes . to_dict ()
2025-05-29 16:16:51 +08:00
grouped_columns = {}
for col in columns :
if col . startswith ( "$" ):
prefix = col . split ( "_" )[ 0 ] if "_" in col else col
grouped_columns . setdefault ( prefix , []) . append ( col )
else :
grouped_columns . setdefault ( "other" , []) . append ( col )
if variable_list :
df_info += "#### Relevant Columns: \n "
relevant_line = ", " . join ( f " { col } : { columns [ col ] } " for col in variable_list if col in columns )
df_info += relevant_line + " \n "
2024-11-06 13:14:35 +08:00
else :
2025-05-29 16:16:51 +08:00
df_info += "#### All Columns: \n "
grouped_items = list ( grouped_columns . items ())
random . shuffle ( grouped_items )
for prefix , cols in grouped_items :
header = "Other Columns" if prefix == "other" else f " { prefix } Related Columns"
df_info += f " \n #### { header } : \n "
random . shuffle ( cols )
line = ", " . join ( f " { col } : { columns [ col ] } " for col in cols )
df_info += line + " \n "
2024-10-16 16:00:55 +08:00
if "REPORT_PERIOD" in df . columns :
one_instrument = df . index . get_level_values ( "instrument" )[ 0 ]
df_on_one_instrument = df . loc [ pd . IndexSlice [:, one_instrument ], [ "REPORT_PERIOD" ]]
2025-05-29 16:16:51 +08:00
df_info += " \n ### Sample Data \n "
df_info += f "Showing data for instrument { one_instrument } : \n "
df_info += str ( df_on_one_instrument . head ( 5 ))
2024-10-08 02:07:20 +08:00
return JJ_TPL . render (
file_name = p . name ,
2025-05-29 16:16:51 +08:00
type_desc = "HDF5 Data File" ,
2024-10-16 16:00:55 +08:00
content = df_info ,
2024-10-08 02:07:20 +08:00
)
2025-05-29 16:16:51 +08:00
2024-10-08 02:07:20 +08:00
elif p . name . endswith ( ".md" ):
with open ( p ) as f :
content = f . read ()
return JJ_TPL . render (
file_name = p . name ,
2025-05-29 16:16:51 +08:00
type_desc = "Markdown Documentation" ,
2024-10-08 02:07:20 +08:00
content = content ,
)
2025-05-29 16:16:51 +08:00
2024-10-08 02:07:20 +08:00
else :
raise NotImplementedError (
f "file type { p . name } is not supported. Please implement its description function." ,
)
2024-11-06 13:14:35 +08:00
def get_data_folder_intro ( fname_reg : str = ".*" , flags = 0 , variable_mapping = None ) -> str :
2024-09-29 18:43:17 +08:00
"""
Directly get the info of the data folder.
2024-08-05 21:01:59 +08:00
It is for preparing prompting message.
2024-09-29 18:43:17 +08:00
Parameters
----------
fname_reg : str
a regular expression to filter the file name.
flags: str
flags for re.match
Returns
-------
str
The description of the data folder.
2024-08-05 21:01:59 +08:00
"""
if (
2024-11-25 16:27:34 +08:00
not Path ( FACTOR_COSTEER_SETTINGS . data_folder ) . exists ()
or not Path ( FACTOR_COSTEER_SETTINGS . data_folder_debug ) . exists ()
2024-08-05 21:01:59 +08:00
):
2024-09-29 18:43:17 +08:00
# FIXME: (xiao) I think this is writing in a hard-coded way.
# get data folder intro does not imply that we are generating the data folder.
2024-08-05 21:01:59 +08:00
generate_data_folder_from_qlib ()
content_l = []
2024-11-25 16:27:34 +08:00
for p in Path ( FACTOR_COSTEER_SETTINGS . data_folder_debug ) . iterdir ():
2024-09-29 18:43:17 +08:00
if re . match ( fname_reg , p . name , flags ) is not None :
2024-11-06 13:14:35 +08:00
if variable_mapping :
content_l . append ( get_file_desc ( p , variable_mapping . get ( p . stem , [])))
else :
content_l . append ( get_file_desc ( p ))
2024-09-29 18:43:17 +08:00
return " \n ----------------- file splitter ------------- \n " . join ( content_l )