mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-08-06 03:27:44 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3522a2eca1 | |||
| a5f091f1ca | |||
| 528d470754 | |||
| 910fbea27e | |||
| ab3f5f111d | |||
| a910d70d40 | |||
| 31a75eeb07 | |||
| 11f5dadd2d | |||
| 51a624c31e | |||
| 9947ea3928 | |||
| bc96d26371 | |||
| c6e8f3d3a3 | |||
| 35d2b81158 | |||
| 4fd5117af6 | |||
| 96d6923433 | |||
| ef12b33aca | |||
| a1e9417658 | |||
| a65ab828c4 | |||
| 840e12e6aa | |||
| 1d1b7b6984 |
@@ -12,7 +12,7 @@ jobs:
|
|||||||
release-please:
|
release-please:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: googleapis/release-please-action@v4
|
- uses: googleapis/release-please-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
config-file: release-please-config.json
|
config-file: release-please-config.json
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
".": "1.3.5"
|
".": "1.3.7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,28 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.3.7](https://github.com/TPTBusiness/Predix/compare/v1.3.6...v1.3.7) (2026-04-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **security:** nosec for B608/B701 false positives in UI and template code ([5eb5d7e](https://github.com/TPTBusiness/Predix/commit/5eb5d7e8fdbe90e0dced83fef4e09f5a33e96b2b))
|
||||||
|
* **security:** replace eval() with ast.literal_eval and add request timeouts (B307, B113) ([3301ada](https://github.com/TPTBusiness/Predix/commit/3301ada697ca7d3afa1a188d2a76a87ae98b4529))
|
||||||
|
* **security:** replace shell=True subprocess calls with list args (B602) ([13c08f4](https://github.com/TPTBusiness/Predix/commit/13c08f4ce6813eb7c314087921ec8c0f40074bd7))
|
||||||
|
|
||||||
|
## [1.3.6](https://github.com/TPTBusiness/Predix/compare/v1.3.5...v1.3.6) (2026-04-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **security:** real fix for B110 (logging in factor_proposal.py [#746](https://github.com/TPTBusiness/Predix/issues/746)) ([16624e0](https://github.com/TPTBusiness/Predix/commit/16624e0bd966ae4d24c4a3eb42bbc31c11da3136))
|
||||||
|
* **security:** real fix for B110 (logging in factor_runner.py [#744](https://github.com/TPTBusiness/Predix/issues/744)) ([88cf0fb](https://github.com/TPTBusiness/Predix/commit/88cf0fb8828b11c97f2f3ae2881a4900b020c6f0))
|
||||||
|
* **security:** real fix for B110 (logging in quant_proposal.py [#741](https://github.com/TPTBusiness/Predix/issues/741)) ([7cf2a64](https://github.com/TPTBusiness/Predix/commit/7cf2a644f553b054bd4b0607ea51e5372e68d90a))
|
||||||
|
* **security:** real fix for B110 (logging in quant_proposal.py [#741](https://github.com/TPTBusiness/Predix/issues/741)) ([ef985f8](https://github.com/TPTBusiness/Predix/commit/ef985f86035d8dca707c60137e6508349a0c4ae6))
|
||||||
|
* **security:** real fix for B404/B603 (sys.executable in factor_runner.py [#745](https://github.com/TPTBusiness/Predix/issues/745)) ([819655a](https://github.com/TPTBusiness/Predix/commit/819655aaa3efa76596d60501d0e8ca365df3e5e2))
|
||||||
|
* **security:** revert broken read_pickle encoding arg in kaggle template (B301) ([3574907](https://github.com/TPTBusiness/Predix/commit/35749073c91e69f63ddaad61dae3f2b799327e63))
|
||||||
|
* **security:** validate SQL identifiers in _add_column_if_not_exists (B608) ([e10dfa2](https://github.com/TPTBusiness/Predix/commit/e10dfa2576038e911f83595d3b466c261bc0cd54))
|
||||||
|
* **security:** whitelist-validate metric column in get_top_factors (B608) ([e50519f](https://github.com/TPTBusiness/Predix/commit/e50519fe066e68aec2f19b83df4f643c3c22053d))
|
||||||
|
|
||||||
## [1.3.5](https://github.com/TPTBusiness/Predix/compare/v1.3.4...v1.3.5) (2026-04-27)
|
## [1.3.5](https://github.com/TPTBusiness/Predix/compare/v1.3.4...v1.3.5) (2026-04-27)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -54,11 +54,11 @@ def rdagent_info():
|
|||||||
current_version = importlib.metadata.version("rdagent")
|
current_version = importlib.metadata.version("rdagent")
|
||||||
logger.info(f"RD-Agent version: {current_version}")
|
logger.info(f"RD-Agent version: {current_version}")
|
||||||
api_url = f"https://api.github.com/repos/microsoft/RD-Agent/contents/requirements.txt?ref=main"
|
api_url = f"https://api.github.com/repos/microsoft/RD-Agent/contents/requirements.txt?ref=main"
|
||||||
response = requests.get(api_url)
|
response = requests.get(api_url, timeout=30)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
files = response.json()
|
files = response.json()
|
||||||
file_url = files["download_url"]
|
file_url = files["download_url"]
|
||||||
file_response = requests.get(file_url)
|
file_response = requests.get(file_url, timeout=30)
|
||||||
if file_response.status_code == 200:
|
if file_response.status_code == 200:
|
||||||
all_file_contents = file_response.text.split("\n")
|
all_file_contents = file_response.text.split("\n")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ class ResultsDatabase:
|
|||||||
|
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
|
_ALLOWED_TABLES = frozenset({"factors", "backtest_runs", "loop_results"})
|
||||||
|
_ALLOWED_COL_TYPES = frozenset({"REAL", "TEXT", "INTEGER", "BLOB"})
|
||||||
|
|
||||||
def _add_column_if_not_exists(self, table: str, column: str, col_type: str) -> None:
|
def _add_column_if_not_exists(self, table: str, column: str, col_type: str) -> None:
|
||||||
"""
|
"""
|
||||||
Add a column to a table if it doesn't already exist.
|
Add a column to a table if it doesn't already exist.
|
||||||
@@ -78,20 +81,24 @@ class ResultsDatabase:
|
|||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
table : str
|
table : str
|
||||||
Table name
|
Table name (must be in _ALLOWED_TABLES)
|
||||||
column : str
|
column : str
|
||||||
Column name to add
|
Column name to add (alphanumeric + underscore only)
|
||||||
col_type : str
|
col_type : str
|
||||||
SQL column type (e.g., 'REAL', 'TEXT')
|
SQL column type (must be in _ALLOWED_COL_TYPES)
|
||||||
"""
|
"""
|
||||||
|
if table not in self._ALLOWED_TABLES:
|
||||||
|
raise ValueError(f"Unknown table: {table!r}")
|
||||||
|
if not column.replace("_", "").isalnum():
|
||||||
|
raise ValueError(f"Invalid column name: {column!r}")
|
||||||
|
if col_type not in self._ALLOWED_COL_TYPES:
|
||||||
|
raise ValueError(f"Invalid column type: {col_type!r}")
|
||||||
|
|
||||||
c = self.conn.cursor()
|
c = self.conn.cursor()
|
||||||
try:
|
c.execute("SELECT name FROM pragma_table_info(?)", (table,))
|
||||||
# Try to query the column - if it fails, it doesn't exist
|
existing = {row[0] for row in c.fetchall()}
|
||||||
# nosec B608: Internal schema migration, column names are controlled
|
if column not in existing:
|
||||||
c.execute(f"SELECT {column} FROM {table} LIMIT 1") # nosec B608
|
c.execute(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}")
|
||||||
except sqlite3.OperationalError:
|
|
||||||
# Column doesn't exist, add it
|
|
||||||
c.execute(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}") # nosec B608
|
|
||||||
|
|
||||||
def add_factor(self, name: str, type: str = "unknown") -> int:
|
def add_factor(self, name: str, type: str = "unknown") -> int:
|
||||||
c = self.conn.cursor()
|
c = self.conn.cursor()
|
||||||
@@ -183,16 +190,18 @@ class ResultsDatabase:
|
|||||||
pd.DataFrame
|
pd.DataFrame
|
||||||
DataFrame with factor names and metrics
|
DataFrame with factor names and metrics
|
||||||
"""
|
"""
|
||||||
# Map shorthand to full column name
|
_ALLOWED_METRICS = frozenset({
|
||||||
|
'sharpe', 'ic', 'annual_return', 'max_drawdown',
|
||||||
|
'win_rate', 'information_ratio', 'volatility',
|
||||||
|
})
|
||||||
metric_map = {
|
metric_map = {
|
||||||
'sharpe': 'sharpe',
|
'sharpe': 'sharpe', 'ic': 'ic', 'return': 'annual_return',
|
||||||
'ic': 'ic',
|
'drawdown': 'max_drawdown', 'win_rate': 'win_rate',
|
||||||
'return': 'annual_return',
|
|
||||||
'drawdown': 'max_drawdown',
|
|
||||||
'win_rate': 'win_rate',
|
|
||||||
'information_ratio': 'information_ratio',
|
'information_ratio': 'information_ratio',
|
||||||
}
|
}
|
||||||
col = metric_map.get(metric, metric)
|
col = metric_map.get(metric, metric)
|
||||||
|
if col not in _ALLOWED_METRICS:
|
||||||
|
raise ValueError(f"Unknown metric: {metric!r}")
|
||||||
|
|
||||||
return pd.read_sql_query(
|
return pd.read_sql_query(
|
||||||
f"""SELECT factor_name, ic, sharpe, annual_return, max_drawdown,
|
f"""SELECT factor_name, ic, sharpe, annual_return, max_drawdown,
|
||||||
@@ -201,7 +210,7 @@ class ResultsDatabase:
|
|||||||
JOIN factors ON factor_id = factors.id
|
JOIN factors ON factor_id = factors.id
|
||||||
WHERE {col} IS NOT NULL
|
WHERE {col} IS NOT NULL
|
||||||
ORDER BY {col} DESC
|
ORDER BY {col} DESC
|
||||||
LIMIT ?""",
|
LIMIT ?""", # nosec B608 — col is validated against _ALLOWED_METRICS above
|
||||||
self.conn,
|
self.conn,
|
||||||
params=[limit]
|
params=[limit]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -161,8 +161,7 @@ class FactorFBWorkspace(FBWorkspace):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.check_output(
|
subprocess.check_output(
|
||||||
f"{FACTOR_COSTEER_SETTINGS.python_bin} {execution_code_path}",
|
[FACTOR_COSTEER_SETTINGS.python_bin, str(execution_code_path)],
|
||||||
shell=True,
|
|
||||||
cwd=self.workspace_path,
|
cwd=self.workspace_path,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
timeout=FACTOR_COSTEER_SETTINGS.file_based_execution_timeout,
|
timeout=FACTOR_COSTEER_SETTINGS.file_based_execution_timeout,
|
||||||
|
|||||||
+22
-15
@@ -27,6 +27,7 @@ Usage:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json as _json
|
import json as _json
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
@@ -36,21 +37,24 @@ from typing import Any
|
|||||||
|
|
||||||
from loguru import logger as _root
|
from loguru import logger as _root
|
||||||
|
|
||||||
# ── paths ─────────────────────────────────────────────────────────────────────
|
# ── paths ─────────────────────────────────────────────────────────────────────────────────
|
||||||
LOGS_ROOT: Path = Path(__file__).parent.parent.parent / "logs"
|
LOGS_ROOT: Path = Path(__file__).parent.parent.parent / "logs"
|
||||||
|
|
||||||
# ── format ────────────────────────────────────────────────────────────────────
|
# ── format ────────────────────────────────────────────────────────────────────────────────
|
||||||
_FILE_FMT = (
|
_FILE_FMT = (
|
||||||
"{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[cmd]: <18} | {message}"
|
"{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[cmd]: <18} | {message}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── internal state ─────────────────────────────────────────────────────────────
|
# ── internal state ─────────────────────────────────────────────────────────────────────────────
|
||||||
_registered: set[str] = set() # command keys that already have a file sink
|
_registered: set[str] = set() # command keys that already have a file sink
|
||||||
_all_added: bool = False # whether the combined all.log sink is active
|
_all_added: bool = False # whether the combined all.log sink is active
|
||||||
_llm_log_lock = threading.Lock() # guards concurrent writes to llm_calls.jsonl
|
_llm_log_lock = threading.Lock() # guards concurrent writes to llm_calls.jsonl
|
||||||
|
|
||||||
|
# Maximum characters stored per field in llm_calls.jsonl to prevent GB-scale files.
|
||||||
|
_LLM_CALL_MAX_CHARS = 500
|
||||||
|
|
||||||
# ── helpers ───────────────────────────────────────────────────────────────────
|
|
||||||
|
# ── helpers ────────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _today_dir() -> Path:
|
def _today_dir() -> Path:
|
||||||
d = LOGS_ROOT / datetime.now().strftime("%Y-%m-%d")
|
d = LOGS_ROOT / datetime.now().strftime("%Y-%m-%d")
|
||||||
@@ -79,7 +83,7 @@ def _banner(log, title: str, meta: dict[str, Any]) -> None:
|
|||||||
log.info(sep)
|
log.info(sep)
|
||||||
|
|
||||||
|
|
||||||
# ── public API ────────────────────────────────────────────────────────────────
|
# ── public API ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def log_llm_call(
|
def log_llm_call(
|
||||||
system: str | None,
|
system: str | None,
|
||||||
@@ -88,16 +92,19 @@ def log_llm_call(
|
|||||||
start_time: Any = None,
|
start_time: Any = None,
|
||||||
end_time: Any = None,
|
end_time: Any = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Append one complete LLM call to logs/YYYY-MM-DD/llm_calls.jsonl.
|
"""Append one LLM call summary to logs/YYYY-MM-DD/llm_calls.jsonl.
|
||||||
|
|
||||||
|
Prompt/response content is capped at _LLM_CALL_MAX_CHARS to prevent
|
||||||
|
GB-scale log files from long-running loops.
|
||||||
|
|
||||||
Each line is a self-contained JSON object so the file is grep/jq-friendly:
|
Each line is a self-contained JSON object so the file is grep/jq-friendly:
|
||||||
jq 'select(.duration_ms > 5000)' logs/2026-04-17/llm_calls.jsonl
|
jq 'select(.duration_ms > 5000)' logs/2026-04-17/llm_calls.jsonl
|
||||||
"""
|
"""
|
||||||
entry: dict[str, Any] = {
|
entry: dict[str, Any] = {
|
||||||
"ts": datetime.now().isoformat(timespec="milliseconds"),
|
"ts": datetime.now().isoformat(timespec="milliseconds"),
|
||||||
"system": system or "",
|
"system": (system or "")[:_LLM_CALL_MAX_CHARS],
|
||||||
"user": user,
|
"user": user[:_LLM_CALL_MAX_CHARS],
|
||||||
"response": response,
|
"response": response[:_LLM_CALL_MAX_CHARS],
|
||||||
}
|
}
|
||||||
if start_time is not None and end_time is not None:
|
if start_time is not None and end_time is not None:
|
||||||
try:
|
try:
|
||||||
@@ -130,13 +137,13 @@ def setup(command: str, **context: Any):
|
|||||||
key = command.lower()
|
key = command.lower()
|
||||||
|
|
||||||
if key not in _registered:
|
if key not in _registered:
|
||||||
# Per-command rotating file
|
|
||||||
_root.add(
|
_root.add(
|
||||||
str(log_dir / f"{key}.log"),
|
str(log_dir / f"{key}.log"),
|
||||||
format=_FILE_FMT,
|
format=_FILE_FMT,
|
||||||
filter=lambda r, k=key: r["extra"].get("cmd", "").lower() == k,
|
filter=lambda r, k=key: r["extra"].get("cmd", "").lower() == k,
|
||||||
rotation="00:00", # new file at midnight
|
rotation="50 MB",
|
||||||
retention="30 days",
|
compression="gz",
|
||||||
|
retention="7 days",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
enqueue=True,
|
enqueue=True,
|
||||||
backtrace=False,
|
backtrace=False,
|
||||||
@@ -145,13 +152,13 @@ def setup(command: str, **context: Any):
|
|||||||
_registered.add(key)
|
_registered.add(key)
|
||||||
|
|
||||||
if not _all_added:
|
if not _all_added:
|
||||||
# Combined log — all commands
|
|
||||||
_root.add(
|
_root.add(
|
||||||
str(log_dir / "all.log"),
|
str(log_dir / "all.log"),
|
||||||
format=_FILE_FMT,
|
format=_FILE_FMT,
|
||||||
filter=lambda r: "cmd" in r["extra"],
|
filter=lambda r: "cmd" in r["extra"],
|
||||||
rotation="00:00",
|
rotation="100 MB",
|
||||||
retention="60 days",
|
compression="gz",
|
||||||
|
retention="7 days",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
enqueue=True,
|
enqueue=True,
|
||||||
backtrace=False,
|
backtrace=False,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,306 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import pickle # nosec
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from streamlit import session_state
|
||||||
|
|
||||||
|
from rdagent.log.ui.conf import UI_SETTING
|
||||||
|
from rdagent.log.utils import extract_evoid, extract_loopid_func_name
|
||||||
|
|
||||||
|
st.set_page_config(layout="wide", page_title="debug_llm", page_icon="🎓", initial_sidebar_state="expanded")
|
||||||
|
|
||||||
|
# 获取 log_path 参数
|
||||||
|
parser = argparse.ArgumentParser(description="RD-Agent Streamlit App")
|
||||||
|
parser.add_argument("--log_dir", type=str, help="Path to the log directory")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def get_folders_sorted(log_path):
|
||||||
|
"""缓存并返回排序后的文件夹列表,并加入进度打印"""
|
||||||
|
with st.spinner("正在加载文件夹列表..."):
|
||||||
|
folders = sorted(
|
||||||
|
(folder for folder in log_path.iterdir() if folder.is_dir() and list(folder.iterdir())),
|
||||||
|
key=lambda folder: folder.stat().st_mtime,
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
st.write(f"找到 {len(folders)} 个文件夹")
|
||||||
|
return [folder.name for folder in folders]
|
||||||
|
|
||||||
|
|
||||||
|
if UI_SETTING.enable_cache:
|
||||||
|
get_folders_sorted = st.cache_data(get_folders_sorted)
|
||||||
|
|
||||||
|
|
||||||
|
# 设置主日志路径
|
||||||
|
main_log_path = Path(args.log_dir) if args.log_dir else Path("./log")
|
||||||
|
if not main_log_path.exists():
|
||||||
|
st.error(f"Log dir {main_log_path} does not exist!")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
if "data" not in session_state:
|
||||||
|
session_state.data = []
|
||||||
|
if "log_path" not in session_state:
|
||||||
|
session_state.log_path = None
|
||||||
|
|
||||||
|
tlist = []
|
||||||
|
|
||||||
|
|
||||||
|
def load_data():
|
||||||
|
"""加载数据到 session_state 并显示进度"""
|
||||||
|
log_file = main_log_path / session_state.log_path / "debug_llm.pkl"
|
||||||
|
try:
|
||||||
|
with st.spinner(f"正在加载数据文件 {log_file}..."):
|
||||||
|
start_time = time.time()
|
||||||
|
with open(log_file, "rb") as f:
|
||||||
|
session_state.data = pickle.load(f, encoding="utf-8") # nosec
|
||||||
|
st.success(f"数据加载完成!耗时 {time.time() - start_time:.2f} 秒")
|
||||||
|
st.session_state["current_loop"] = 1
|
||||||
|
except Exception as e:
|
||||||
|
session_state.data = [{"error": str(e)}]
|
||||||
|
st.error(f"加载数据失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# UI - Sidebar
|
||||||
|
with st.sidebar:
|
||||||
|
st.markdown(":blue[**Log Path**]")
|
||||||
|
manually = st.toggle("Manual Input")
|
||||||
|
if manually:
|
||||||
|
st.text_input("log path", key="log_path", label_visibility="collapsed")
|
||||||
|
else:
|
||||||
|
folders = get_folders_sorted(main_log_path)
|
||||||
|
st.selectbox(f"**Select from {main_log_path.absolute()}**", folders, key="log_path") # nosec B608 — not SQL, Bandit false positive on "Select" in UI label
|
||||||
|
|
||||||
|
if st.button("Refresh Data"):
|
||||||
|
load_data()
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
def show_text(text, lang=None):
|
||||||
|
"""显示文本代码块"""
|
||||||
|
if lang:
|
||||||
|
st.code(text, language=lang, wrap_lines=True)
|
||||||
|
elif "\n" in text:
|
||||||
|
st.code(text, language="python", wrap_lines=True)
|
||||||
|
else:
|
||||||
|
st.code(text, language="html", wrap_lines=True)
|
||||||
|
|
||||||
|
|
||||||
|
def highlight_prompts_uri(uri):
|
||||||
|
"""高亮 URI 的格式"""
|
||||||
|
parts = uri.split(":")
|
||||||
|
return f"**{parts[0]}:**:green[**{parts[1]}**]"
|
||||||
|
|
||||||
|
|
||||||
|
# Display Data
|
||||||
|
progress_text = st.empty()
|
||||||
|
progress_bar = st.progress(0)
|
||||||
|
|
||||||
|
# 每页展示一个 Loop
|
||||||
|
LOOPS_PER_PAGE = 1
|
||||||
|
|
||||||
|
# 获取所有的 Loop ID
|
||||||
|
loop_groups = {}
|
||||||
|
for i, d in enumerate(session_state.data):
|
||||||
|
tag = d["tag"]
|
||||||
|
loop_id, _ = extract_loopid_func_name(tag)
|
||||||
|
if loop_id:
|
||||||
|
if loop_id not in loop_groups:
|
||||||
|
loop_groups[loop_id] = []
|
||||||
|
loop_groups[loop_id].append(d)
|
||||||
|
|
||||||
|
# 按 Loop ID 排序
|
||||||
|
sorted_loop_ids = sorted(loop_groups.keys(), key=int) # 假设 Loop ID 是数字
|
||||||
|
total_loops = len(sorted_loop_ids)
|
||||||
|
total_pages = total_loops # 每页展示一个 Loop
|
||||||
|
|
||||||
|
|
||||||
|
# simple display
|
||||||
|
# FIXME: Delete this simple UI if trace have tag(evo_id & loop_id)
|
||||||
|
# with st.sidebar:
|
||||||
|
# start = int(st.text_input("start", 0))
|
||||||
|
# end = int(st.text_input("end", 100))
|
||||||
|
# for m in session_state.data[start:end]:
|
||||||
|
# if "tpl" in m["tag"]:
|
||||||
|
# obj = m["obj"]
|
||||||
|
# uri = obj["uri"]
|
||||||
|
# tpl = obj["template"]
|
||||||
|
# cxt = obj["context"]
|
||||||
|
# rd = obj["rendered"]
|
||||||
|
# with st.expander(highlight_prompts_uri(uri), expanded=False, icon="⚙️"):
|
||||||
|
# t1, t2, t3 = st.tabs([":green[**Rendered**]", ":blue[**Template**]", ":orange[**Context**]"])
|
||||||
|
# with t1:
|
||||||
|
# show_text(rd)
|
||||||
|
# with t2:
|
||||||
|
# show_text(tpl, lang="django")
|
||||||
|
# with t3:
|
||||||
|
# st.json(cxt)
|
||||||
|
# if "llm" in m["tag"]:
|
||||||
|
# obj = m["obj"]
|
||||||
|
# system = obj.get("system", None)
|
||||||
|
# user = obj["user"]
|
||||||
|
# resp = obj["resp"]
|
||||||
|
# with st.expander(f"**LLM**", expanded=False, icon="🤖"):
|
||||||
|
# t1, t2, t3 = st.tabs([":green[**Response**]", ":blue[**User**]", ":orange[**System**]"])
|
||||||
|
# with t1:
|
||||||
|
# try:
|
||||||
|
# rdict = json.loads(resp)
|
||||||
|
# if "code" in rdict:
|
||||||
|
# code = rdict["code"]
|
||||||
|
# st.markdown(":red[**Code in response dict:**]")
|
||||||
|
# st.code(code, language="python", wrap_lines=True, line_numbers=True)
|
||||||
|
# rdict.pop("code")
|
||||||
|
# elif "spec" in rdict:
|
||||||
|
# spec = rdict["spec"]
|
||||||
|
# st.markdown(":red[**Spec in response dict:**]")
|
||||||
|
# st.markdown(spec)
|
||||||
|
# rdict.pop("spec")
|
||||||
|
# else:
|
||||||
|
# # show model codes
|
||||||
|
# showed_keys = []
|
||||||
|
# for k, v in rdict.items():
|
||||||
|
# if k.startswith("model_") and k.endswith(".py"):
|
||||||
|
# st.markdown(f":red[**{k}**]")
|
||||||
|
# st.code(v, language="python", wrap_lines=True, line_numbers=True)
|
||||||
|
# showed_keys.append(k)
|
||||||
|
# for k in showed_keys:
|
||||||
|
# rdict.pop(k)
|
||||||
|
# st.write(":red[**Other parts (except for the code or spec) in response dict:**]")
|
||||||
|
# st.json(rdict)
|
||||||
|
# except:
|
||||||
|
# st.json(resp)
|
||||||
|
# with t2:
|
||||||
|
# show_text(user)
|
||||||
|
# with t3:
|
||||||
|
# show_text(system or "No system prompt available")
|
||||||
|
|
||||||
|
|
||||||
|
if total_pages:
|
||||||
|
# 初始化 current_loop
|
||||||
|
if "current_loop" not in st.session_state:
|
||||||
|
st.session_state["current_loop"] = 1
|
||||||
|
|
||||||
|
# Loop 导航按钮
|
||||||
|
col1, col2, col3, col4, col5 = st.sidebar.columns([1.2, 1, 2, 1, 1.2])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if st.button("|<"): # 首页
|
||||||
|
st.session_state["current_loop"] = 1
|
||||||
|
with col2:
|
||||||
|
if st.button("<") and st.session_state["current_loop"] > 1: # 上一页
|
||||||
|
st.session_state["current_loop"] -= 1
|
||||||
|
with col3:
|
||||||
|
# 下拉列表显示所有 Loop
|
||||||
|
st.session_state["current_loop"] = st.selectbox(
|
||||||
|
"选择 Loop",
|
||||||
|
options=list(range(1, total_loops + 1)),
|
||||||
|
index=st.session_state["current_loop"] - 1, # 默认选中当前 Loop
|
||||||
|
label_visibility="collapsed", # 隐藏标签
|
||||||
|
)
|
||||||
|
with col4:
|
||||||
|
if st.button("\>") and st.session_state["current_loop"] < total_loops: # 下一页
|
||||||
|
st.session_state["current_loop"] += 1
|
||||||
|
with col5:
|
||||||
|
if st.button("\>|"): # 最后一页
|
||||||
|
st.session_state["current_loop"] = total_loops
|
||||||
|
|
||||||
|
# 获取当前 Loop
|
||||||
|
current_loop = st.session_state["current_loop"]
|
||||||
|
|
||||||
|
# 渲染当前 Loop 数据
|
||||||
|
loop_id = sorted_loop_ids[current_loop - 1]
|
||||||
|
progress_text = st.empty()
|
||||||
|
progress_text.text(f"正在处理 Loop {loop_id}...")
|
||||||
|
progress_bar.progress(current_loop / total_loops, text=f"Loop :green[**{current_loop}**] / {total_loops}")
|
||||||
|
|
||||||
|
# 渲染 Loop Header
|
||||||
|
loop_anchor = f"Loop_{loop_id}"
|
||||||
|
if loop_anchor not in tlist:
|
||||||
|
tlist.append(loop_anchor)
|
||||||
|
st.header(loop_anchor, anchor=loop_anchor, divider="blue")
|
||||||
|
|
||||||
|
# 渲染当前 Loop 的所有数据
|
||||||
|
loop_data = loop_groups[loop_id]
|
||||||
|
for d in loop_data:
|
||||||
|
tag = d["tag"]
|
||||||
|
obj = d["obj"]
|
||||||
|
_, func_name = extract_loopid_func_name(tag)
|
||||||
|
evo_id = extract_evoid(tag)
|
||||||
|
|
||||||
|
func_anchor = f"loop_{loop_id}.{func_name}"
|
||||||
|
if func_anchor not in tlist:
|
||||||
|
tlist.append(func_anchor)
|
||||||
|
st.header(f"in *{func_name}*", anchor=func_anchor, divider="green")
|
||||||
|
|
||||||
|
evo_anchor = f"loop_{loop_id}.evo_step_{evo_id}"
|
||||||
|
if evo_id and evo_anchor not in tlist:
|
||||||
|
tlist.append(evo_anchor)
|
||||||
|
st.subheader(f"evo_step_{evo_id}", anchor=evo_anchor, divider="orange")
|
||||||
|
|
||||||
|
# 根据 tag 渲染内容
|
||||||
|
if "debug_exp_gen" in tag:
|
||||||
|
with st.expander(
|
||||||
|
f"Exp in :violet[**{obj.experiment_workspace.workspace_path}**]", expanded=False, icon="🧩"
|
||||||
|
):
|
||||||
|
st.write(obj)
|
||||||
|
elif "debug_tpl" in tag:
|
||||||
|
uri = obj["uri"]
|
||||||
|
tpl = obj["template"]
|
||||||
|
cxt = obj["context"]
|
||||||
|
rd = obj["rendered"]
|
||||||
|
with st.expander(highlight_prompts_uri(uri), expanded=False, icon="⚙️"):
|
||||||
|
t1, t2, t3 = st.tabs([":green[**Rendered**]", ":blue[**Template**]", ":orange[**Context**]"])
|
||||||
|
with t1:
|
||||||
|
show_text(rd)
|
||||||
|
with t2:
|
||||||
|
show_text(tpl, lang="django")
|
||||||
|
with t3:
|
||||||
|
st.json(cxt)
|
||||||
|
elif "debug_llm" in tag:
|
||||||
|
system = obj.get("system", None)
|
||||||
|
user = obj["user"]
|
||||||
|
resp = obj["resp"]
|
||||||
|
with st.expander(f"**LLM**", expanded=False, icon="🤖"):
|
||||||
|
t1, t2, t3 = st.tabs([":green[**Response**]", ":blue[**User**]", ":orange[**System**]"])
|
||||||
|
with t1:
|
||||||
|
try:
|
||||||
|
rdict = json.loads(resp)
|
||||||
|
if "code" in rdict:
|
||||||
|
code = rdict["code"]
|
||||||
|
st.markdown(":red[**Code in response dict:**]")
|
||||||
|
st.code(code, language="python", wrap_lines=True, line_numbers=True)
|
||||||
|
rdict.pop("code")
|
||||||
|
elif "spec" in rdict:
|
||||||
|
spec = rdict["spec"]
|
||||||
|
st.markdown(":red[**Spec in response dict:**]")
|
||||||
|
st.markdown(spec)
|
||||||
|
rdict.pop("spec")
|
||||||
|
else:
|
||||||
|
# show model codes
|
||||||
|
showed_keys = []
|
||||||
|
for k, v in rdict.items():
|
||||||
|
if k.startswith("model_") and k.endswith(".py"):
|
||||||
|
st.markdown(f":red[**{k}**]")
|
||||||
|
st.code(v, language="python", wrap_lines=True, line_numbers=True)
|
||||||
|
showed_keys.append(k)
|
||||||
|
for k in showed_keys:
|
||||||
|
rdict.pop(k)
|
||||||
|
st.write(":red[**Other parts (except for the code or spec) in response dict:**]")
|
||||||
|
st.json(rdict)
|
||||||
|
except:
|
||||||
|
st.json(resp)
|
||||||
|
with t2:
|
||||||
|
show_text(user)
|
||||||
|
with t3:
|
||||||
|
show_text(system or "No system prompt available")
|
||||||
|
|
||||||
|
progress_text.text("当前 Loop 数据处理完成!")
|
||||||
|
|
||||||
|
# Sidebar TOC
|
||||||
|
with st.sidebar:
|
||||||
|
toc = "\n".join([f"- [{t}](#{t})" if t.startswith("L") else f" - [{t.split('.')[1]}](#{t})" for t in tlist])
|
||||||
|
st.markdown(toc, unsafe_allow_html=True)
|
||||||
@@ -182,7 +182,7 @@ class ExpGen2Hypothesis(DSProposalV2ExpGen):
|
|||||||
|
|
||||||
success_fb_list = list(set(trace_fbs))
|
success_fb_list = list(set(trace_fbs))
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Merge Hypothesis: select {len(success_fb_list)} from {len(trace_fbs)} SOTA experiments found in {len(leaves)} traces"
|
f"Merge Hypothesis: select {len(success_fb_list)} from {len(trace_fbs)} SOTA experiments found in {len(leaves)} traces" # nosec B608 — not SQL, Bandit false positive on "select" in log message
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(success_fb_list) > 0:
|
if len(success_fb_list) > 0:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import ast
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
@@ -587,8 +588,8 @@ def _parsing_score(grade_stdout: str) -> Optional[float]:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
# Priority 2: Eval dict
|
# Priority 2: safe literal eval for Python-style dicts
|
||||||
return float(eval(json_str)["score"])
|
return float(ast.literal_eval(json_str)["score"])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class KGModelFeatureSelectionCoder(Developer[KGModelExperiment]):
|
|||||||
assert target_model_type in KG_SELECT_MAPPING
|
assert target_model_type in KG_SELECT_MAPPING
|
||||||
if len(exp.experiment_workspace.data_description) == 1:
|
if len(exp.experiment_workspace.data_description) == 1:
|
||||||
code = (
|
code = (
|
||||||
Environment(undefined=StrictUndefined)
|
Environment(undefined=StrictUndefined) # nosec B701 — renders Python code templates, not HTML; autoescape would corrupt code
|
||||||
.from_string(DEFAULT_SELECTION_CODE)
|
.from_string(DEFAULT_SELECTION_CODE)
|
||||||
.render(feature_index_list=None)
|
.render(feature_index_list=None)
|
||||||
)
|
)
|
||||||
@@ -62,7 +62,7 @@ class KGModelFeatureSelectionCoder(Developer[KGModelExperiment]):
|
|||||||
chosen_index_to_list_index = [i - 1 for i in chosen_index]
|
chosen_index_to_list_index = [i - 1 for i in chosen_index]
|
||||||
|
|
||||||
code = (
|
code = (
|
||||||
Environment(undefined=StrictUndefined)
|
Environment(undefined=StrictUndefined) # nosec B701 — renders Python code templates, not HTML; autoescape would corrupt code
|
||||||
.from_string(DEFAULT_SELECTION_CODE)
|
.from_string(DEFAULT_SELECTION_CODE)
|
||||||
.render(feature_index_list=chosen_index_to_list_index)
|
.render(feature_index_list=chosen_index_to_list_index)
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-6
@@ -79,12 +79,12 @@ def preprocess_script():
|
|||||||
This method applies the preprocessing steps to the training, validation, and test datasets.
|
This method applies the preprocessing steps to the training, validation, and test datasets.
|
||||||
"""
|
"""
|
||||||
if os.path.exists("/kaggle/input/X_train.pkl"):
|
if os.path.exists("/kaggle/input/X_train.pkl"):
|
||||||
X_train = pd.read_pickle("/kaggle/input/X_train.pkl")
|
X_train = pd.read_pickle("/kaggle/input/X_train.pkl") # nosec B301 — trusted Kaggle input
|
||||||
X_valid = pd.read_pickle("/kaggle/input/X_valid.pkl")
|
X_valid = pd.read_pickle("/kaggle/input/X_valid.pkl") # nosec B301
|
||||||
y_train = pd.read_pickle("/kaggle/input/y_train.pkl")
|
y_train = pd.read_pickle("/kaggle/input/y_train.pkl") # nosec B301
|
||||||
y_valid = pd.read_pickle("/kaggle/input/y_valid.pkl")
|
y_valid = pd.read_pickle("/kaggle/input/y_valid.pkl") # nosec B301
|
||||||
X_test = pd.read_pickle("/kaggle/input/X_test.pkl")
|
X_test = pd.read_pickle("/kaggle/input/X_test.pkl") # nosec B301
|
||||||
others = pd.read_pickle("/kaggle/input/others.pkl")
|
others = pd.read_pickle("/kaggle/input/others.pkl") # nosec B301
|
||||||
y_train = pd.Series(y_train).reset_index(drop=True)
|
y_train = pd.Series(y_train).reset_index(drop=True)
|
||||||
y_valid = pd.Series(y_valid).reset_index(drop=True)
|
y_valid = pd.Series(y_valid).reset_index(drop=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
"""
|
"""
|
||||||
Qlib Factor Runner - Executes factor backtests in Docker.
|
Qlib Factor Runner - Executes factor backtests in Docker.
|
||||||
@@ -965,7 +967,7 @@ class QlibFactorRunner(CachedRunner[QlibFactorExperiment]):
|
|||||||
shutil.copy(str(full_data), str(tmp / "intraday_pv.h5"))
|
shutil.copy(str(full_data), str(tmp / "intraday_pv.h5"))
|
||||||
|
|
||||||
ret = subprocess.run(
|
ret = subprocess.run(
|
||||||
["python", "factor.py"],
|
["sys.executable", "factor.py"],
|
||||||
cwd=str(tmp),
|
cwd=str(tmp),
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
timeout=300,
|
timeout=300,
|
||||||
@@ -1000,7 +1002,7 @@ class QlibFactorRunner(CachedRunner[QlibFactorExperiment]):
|
|||||||
series.to_frame().to_parquet(str(parquet_path))
|
series.to_frame().to_parquet(str(parquet_path))
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
logging.debug("Error in save_factor_values_to_parquet", exc_info=True)
|
||||||
|
|
||||||
def _log_result_warnings(self, factor_name: str, result, metrics: dict) -> None:
|
def _log_result_warnings(self, factor_name: str, result, metrics: dict) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ def get_file_desc(p: Path, variable_list=[]) -> str:
|
|||||||
"""
|
"""
|
||||||
p = Path(p)
|
p = Path(p)
|
||||||
|
|
||||||
JJ_TPL = Environment(undefined=StrictUndefined).from_string("""
|
JJ_TPL = Environment(undefined=StrictUndefined).from_string(""" # nosec B701 — renders plain text description, not HTML; autoescape not applicable
|
||||||
# {{file_name}}
|
# {{file_name}}
|
||||||
|
|
||||||
## File Type
|
## File Type
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
@@ -38,7 +39,7 @@ def _build_compressed_history(trace: Trace, max_history: int) -> str:
|
|||||||
if exp.result is not None and "IC" in exp.result.index:
|
if exp.result is not None and "IC" in exp.result.index:
|
||||||
ic_str = f" IC={exp.result.loc['IC']:.4f}"
|
ic_str = f" IC={exp.result.loc['IC']:.4f}"
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
logging.debug("Exception caught", exc_info=True)
|
||||||
decision = "PASS" if fb.decision else "FAIL"
|
decision = "PASS" if fb.decision else "FAIL"
|
||||||
obs = (fb.observations or "")[:120].replace("\n", " ")
|
obs = (fb.observations or "")[:120].replace("\n", " ")
|
||||||
lines.append(f"- [{decision}]{ic_str} {', '.join(names) or 'unknown'}: {obs}")
|
lines.append(f"- [{decision}]{ic_str} {', '.join(names) or 'unknown'}: {obs}")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
@@ -175,7 +176,7 @@ class QlibQuantHypothesisGen(FactorAndModelHypothesisGen):
|
|||||||
ic_val = exp.result.loc["IC"] if "IC" in exp.result.index else ""
|
ic_val = exp.result.loc["IC"] if "IC" in exp.result.index else ""
|
||||||
ic_str = f" IC={ic_val:.4f}" if ic_val != "" else ""
|
ic_str = f" IC={ic_val:.4f}" if ic_val != "" else ""
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
logging.debug("Error getting IC", exc_info=True)
|
||||||
decision_str = "PASS" if fb.decision else "FAIL"
|
decision_str = "PASS" if fb.decision else "FAIL"
|
||||||
obs_short = (fb.observations or "")[:120].replace("\n", " ")
|
obs_short = (fb.observations or "")[:120].replace("\n", " ")
|
||||||
summary_lines.append(f"- [{decision_str}]{ic_str} {names_str}: {obs_short}")
|
summary_lines.append(f"- [{decision_str}]{ic_str} {names_str}: {obs_short}")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ peft>=0.18.1
|
|||||||
|
|
||||||
# Evaluation
|
# Evaluation
|
||||||
opencompass==0.5.1
|
opencompass==0.5.1
|
||||||
setuptools<75 # uv venv doesn't include, opencompass depends on pkg_resources
|
setuptools>=78.1.1 # Security fix: GHSA-8g6x-3r52-4m6c (path traversal in PackageIndex.download, arbitrary file write/RCE)
|
||||||
|
|
||||||
# Inference acceleration (optional, TRL supports 0.10.2-0.12.0)
|
# Inference acceleration (optional, TRL supports 0.10.2-0.12.0)
|
||||||
# Security: Version >=0.14.0 fixes CVE-2026-22807 (RCE via auto_map dynamic module loading)
|
# Security: Version >=0.14.0 fixes CVE-2026-22807 (RCE via auto_map dynamic module loading)
|
||||||
|
|||||||
@@ -850,24 +850,22 @@ class QlibCondaEnv(LocalEnv[QlibCondaConf]):
|
|||||||
def prepare(self) -> None:
|
def prepare(self) -> None:
|
||||||
"""Prepare the conda environment if not already created."""
|
"""Prepare the conda environment if not already created."""
|
||||||
try:
|
try:
|
||||||
envs = subprocess.run("conda env list", capture_output=True, text=True, shell=True)
|
envs = subprocess.run(["conda", "env", "list"], capture_output=True, text=True)
|
||||||
if self.conf.conda_env_name not in envs.stdout:
|
if self.conf.conda_env_name not in envs.stdout:
|
||||||
print(f"[yellow]Conda env '{self.conf.conda_env_name}' not found, creating...[/yellow]")
|
print(f"[yellow]Conda env '{self.conf.conda_env_name}' not found, creating...[/yellow]")
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
f"conda create -y -n {self.conf.conda_env_name} python=3.10",
|
["conda", "create", "-y", "-n", self.conf.conda_env_name, "python=3.10"],
|
||||||
shell=True,
|
|
||||||
)
|
)
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
f"conda run -n {self.conf.conda_env_name} pip install --upgrade pip cython",
|
["conda", "run", "-n", self.conf.conda_env_name, "pip", "install", "--upgrade", "pip", "cython"],
|
||||||
shell=True,
|
|
||||||
)
|
)
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
f"conda run -n {self.conf.conda_env_name} pip install git+https://github.com/microsoft/qlib.git@2fb9380b342556ddb50a4b24e4fe8655d548b2b8",
|
["conda", "run", "-n", self.conf.conda_env_name, "pip", "install",
|
||||||
shell=True,
|
"git+https://github.com/microsoft/qlib.git@2fb9380b342556ddb50a4b24e4fe8655d548b2b8"],
|
||||||
)
|
)
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
f"conda run -n {self.conf.conda_env_name} pip install catboost xgboost tables torch",
|
["conda", "run", "-n", self.conf.conda_env_name, "pip", "install",
|
||||||
shell=True,
|
"catboost", "xgboost", "tables", "torch"],
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
+4
-4
@@ -9,8 +9,8 @@ psutil
|
|||||||
fire
|
fire
|
||||||
fuzzywuzzy
|
fuzzywuzzy
|
||||||
openai
|
openai
|
||||||
litellm>=1.73 # to support `from litellm import get_valid_models`
|
litellm>=1.83.14 # to support `from litellm import get_valid_models`
|
||||||
aiohttp>=3.13.4 # CVE-2026-22815, CVE-2026-34515, CVE-2026-34516, CVE-2026-34525
|
aiohttp>=3.13.5 # CVE-2026-22815, CVE-2026-34515, CVE-2026-34516, CVE-2026-34525
|
||||||
azure.identity
|
azure.identity
|
||||||
pyarrow
|
pyarrow
|
||||||
rich
|
rich
|
||||||
@@ -98,8 +98,8 @@ optuna>=3.5.0
|
|||||||
beautifulsoup4>=4.12.0
|
beautifulsoup4>=4.12.0
|
||||||
|
|
||||||
# ML Training Pipeline
|
# ML Training Pipeline
|
||||||
lightgbm>=3.3.0
|
lightgbm>=3.3.5
|
||||||
scipy>=1.9.0
|
scipy>=1.15.3
|
||||||
|
|
||||||
# RL Trading (optional - system works without these)
|
# RL Trading (optional - system works without these)
|
||||||
# Install for full RL training: pip install stable-baselines3[extra] gymnasium
|
# Install for full RL training: pip install stable-baselines3[extra] gymnasium
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
# Only install if you want to use full PPO/A2C/SAC training.
|
# Only install if you want to use full PPO/A2C/SAC training.
|
||||||
|
|
||||||
# Core RL library
|
# Core RL library
|
||||||
stable-baselines3[extra]>=2.0.0
|
stable-baselines3[extra]>=2.8.0
|
||||||
|
|
||||||
# Gymnasium environment (OpenAI Gym successor)
|
# Gymnasium environment (OpenAI Gym successor)
|
||||||
gymnasium>=0.29.0
|
gymnasium>=0.29.0
|
||||||
|
|||||||
Reference in New Issue
Block a user