Repository navigation
Expand file tree
/
Copy pathparser.py
More file actions
121 lines (99 loc) · 3.86 KB
/
Copy pathparser.py
File metadata and controls
121 lines (99 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""Task-specific output parser for quant-concept-drift.
Handles combined training + evaluation output from qlib concept drift adaptation:
Signal metrics: lines matching
SIGNAL_METRIC IC=X.XXXXXX
SIGNAL_METRIC ICIR=X.XXXXXX
SIGNAL_METRIC Rank_IC=X.XXXXXX
SIGNAL_METRIC Rank_ICIR=X.XXXXXX
Portfolio metrics: lines matching
PORTFOLIO_METRIC annualized_return=X.XXXXXX
PORTFOLIO_METRIC max_drawdown=X.XXXXXX
PORTFOLIO_METRIC information_ratio=X.XXXXXX
Metrics keyed for leaderboard: ic_csi300, icir_csi300, rank_ic_csi300,
annualized_return_csi300, max_drawdown_csi300, information_ratio_csi300
"""
import re
import sys
from pathlib import Path
# Allow importing from mlsbench package when run standalone
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT / "src"))
from mlsbench.agent.parsers import OutputParser, ParseResult
class Parser(OutputParser):
"""Parser for the quant-concept-drift task."""
def parse(self, cmd_label: str, raw_output: str) -> ParseResult:
feedback_parts = []
metrics: dict = {}
# Parse signal metrics
signal_feedback, signal_metrics = self._parse_signal_metrics(
raw_output, cmd_label
)
if signal_feedback:
feedback_parts.append(signal_feedback)
metrics.update(signal_metrics)
# Parse portfolio metrics
port_feedback, port_metrics = self._parse_portfolio_metrics(
raw_output, cmd_label
)
if port_feedback:
feedback_parts.append(port_feedback)
metrics.update(port_metrics)
if feedback_parts:
feedback = "\n".join(feedback_parts)
else:
feedback = raw_output
return ParseResult(feedback=feedback, metrics=metrics)
def _parse_signal_metrics(
self, output: str, cmd_label: str
) -> tuple[str, dict]:
"""Extract SIGNAL_METRIC lines and return feedback + metrics."""
metrics: dict = {}
feedback_lines: list[str] = []
for line in output.splitlines():
match = re.match(
r"SIGNAL_METRIC\s+(\w+)=(-?[\d.]+(?:e[+-]?\d+)?|nan|inf|-inf)",
line.strip(),
re.IGNORECASE,
)
if match:
key = match.group(1).lower()
raw = match.group(2).lower()
value = float(raw)
feedback_lines.append(line.strip())
if not (value != value or abs(value) == float("inf")):
metric_key = f"{key}_{cmd_label}"
metrics[metric_key] = value
feedback = ""
if feedback_lines:
feedback = (
f"Signal analysis ({cmd_label}):\n"
+ "\n".join(feedback_lines)
)
return feedback, metrics
def _parse_portfolio_metrics(
self, output: str, cmd_label: str
) -> tuple[str, dict]:
"""Extract PORTFOLIO_METRIC lines and return feedback + metrics."""
metrics: dict = {}
feedback_lines: list[str] = []
for line in output.splitlines():
match = re.match(
r"PORTFOLIO_METRIC\s+(\w+)=(-?[\d.]+(?:e[+-]?\d+)?|nan|inf|-inf)",
line.strip(),
re.IGNORECASE,
)
if match:
key = match.group(1).lower()
raw = match.group(2).lower()
value = float(raw)
feedback_lines.append(line.strip())
if not (value != value or abs(value) == float("inf")):
metric_key = f"{key}_{cmd_label}"
metrics[metric_key] = value
feedback = ""
if feedback_lines:
feedback = (
f"Portfolio analysis ({cmd_label}):\n"
+ "\n".join(feedback_lines)
)
return feedback, metrics