subagent thinking accordion; indicator fixes; script details & edit
This commit is contained in:
@@ -144,10 +144,25 @@ async def evaluate_indicator(
|
||||
)
|
||||
}))]
|
||||
|
||||
# Get input_series from the indicator's metadata
|
||||
indicator_name = pandas_ta_name[len("custom_"):]
|
||||
# Get input_series from the indicator's metadata.
|
||||
# Look up by the stored pandas_ta_name field (written at creation time),
|
||||
# which is the reliable reverse mapping from ta_name → directory.
|
||||
# Fall back to display-name matching for indicators created before this
|
||||
# field was added.
|
||||
mgr = get_category_manager()
|
||||
read_result = mgr.read("indicator", indicator_name)
|
||||
all_items = mgr.list_items("indicator")
|
||||
read_result = {"exists": False}
|
||||
for item in all_items.get("items", []):
|
||||
meta = item.get("metadata", {})
|
||||
# Primary: match stored pandas_ta_name field (exact, set at write time)
|
||||
if meta.get("pandas_ta_name") == name_lower:
|
||||
read_result = mgr.read("indicator", item.get("name", ""))
|
||||
break
|
||||
# Fallback: infer from display name (legacy indicators without the field)
|
||||
item_name = item.get("name", "")
|
||||
if "custom_" + item_name.lower().replace("-", "_").replace(" ", "_") == name_lower:
|
||||
read_result = mgr.read("indicator", item_name)
|
||||
break
|
||||
if read_result.get("exists") and read_result.get("metadata"):
|
||||
raw_series = read_result["metadata"].get("input_series") or ["close"]
|
||||
input_cols = tuple(raw_series)
|
||||
|
||||
@@ -62,6 +62,12 @@ class BaseMetadata:
|
||||
"""Base metadata for all categories."""
|
||||
name: str # Display name (can have special chars)
|
||||
description: str # LLM-generated description
|
||||
details: str = "" # Full markdown description with enough detail to reproduce the code
|
||||
conda_packages: list[str] = None # Additional conda packages required
|
||||
|
||||
def __post_init__(self):
|
||||
if self.conda_packages is None:
|
||||
self.conda_packages = []
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -69,21 +75,21 @@ class StrategyMetadata(BaseMetadata):
|
||||
"""Metadata for trading strategies."""
|
||||
data_feeds: list[dict] = None # Required data feeds: [{"symbol": "BTC/USDT.BINANCE", "period_seconds": 3600, "description": "..."}]
|
||||
parameters: dict = None # Strategy parameters: {"param_name": {"default": value, "description": "..."}}
|
||||
conda_packages: list[str] = None # Additional conda packages required
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
if self.data_feeds is None:
|
||||
self.data_feeds = []
|
||||
if self.parameters is None:
|
||||
self.parameters = {}
|
||||
if self.conda_packages is None:
|
||||
self.conda_packages = []
|
||||
|
||||
|
||||
@dataclass
|
||||
class IndicatorMetadata(BaseMetadata):
|
||||
"""Metadata for technical indicators."""
|
||||
conda_packages: list[str] = None # Additional conda packages required
|
||||
# Canonical pandas-ta name, e.g. "custom_trendflex". Set automatically
|
||||
# by CategoryFileManager.write() — do not pass manually.
|
||||
pandas_ta_name: str = ""
|
||||
|
||||
# Fields for TradingView custom study auto-construction:
|
||||
parameters: dict = None
|
||||
@@ -143,8 +149,7 @@ class IndicatorMetadata(BaseMetadata):
|
||||
# Example (RSI levels): [{"id": "ob", "value": 70}, {"id": "os", "value": 30}]
|
||||
|
||||
def __post_init__(self):
|
||||
if self.conda_packages is None:
|
||||
self.conda_packages = []
|
||||
super().__post_init__()
|
||||
if self.input_series is None:
|
||||
self.input_series = ["close"]
|
||||
if self.output_columns is None:
|
||||
@@ -160,11 +165,7 @@ class IndicatorMetadata(BaseMetadata):
|
||||
@dataclass
|
||||
class ResearchMetadata(BaseMetadata):
|
||||
"""Metadata for research scripts."""
|
||||
conda_packages: list[str] = None # Additional conda packages required
|
||||
|
||||
def __post_init__(self):
|
||||
if self.conda_packages is None:
|
||||
self.conda_packages = []
|
||||
pass
|
||||
|
||||
|
||||
# Metadata class registry
|
||||
@@ -503,6 +504,7 @@ class CategoryFileManager:
|
||||
category: str,
|
||||
name: str,
|
||||
description: str,
|
||||
details: str,
|
||||
code: str,
|
||||
metadata: Optional[dict] = None
|
||||
) -> dict[str, Any]:
|
||||
@@ -513,6 +515,7 @@ class CategoryFileManager:
|
||||
category: Category name (strategy, indicator, research)
|
||||
name: Display name for the item
|
||||
description: LLM-generated description (required)
|
||||
details: Full markdown description with enough detail to reproduce the code (required)
|
||||
code: Python implementation code
|
||||
metadata: Additional category-specific metadata fields
|
||||
|
||||
@@ -547,6 +550,13 @@ class CategoryFileManager:
|
||||
meta_dict = metadata or {}
|
||||
meta_dict["name"] = name
|
||||
meta_dict["description"] = description
|
||||
meta_dict["details"] = details
|
||||
|
||||
# For indicators, store the canonical pandas_ta_name so the reverse
|
||||
# mapping (ta_name → directory) is reliable regardless of name casing.
|
||||
if cat == Category.INDICATOR:
|
||||
sanitized = sanitize_name(name).lower()
|
||||
meta_dict["pandas_ta_name"] = f"custom_{sanitized}"
|
||||
|
||||
# Validate and write metadata
|
||||
try:
|
||||
@@ -592,6 +602,8 @@ class CategoryFileManager:
|
||||
code: Optional[str] = None,
|
||||
patches: Optional[list[dict]] = None,
|
||||
description: Optional[str] = None,
|
||||
details: Optional[str] = None,
|
||||
detail_patches: Optional[list[dict]] = None,
|
||||
metadata: Optional[dict] = None
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
@@ -603,6 +615,8 @@ class CategoryFileManager:
|
||||
code: Full Python implementation code to replace existing (optional)
|
||||
patches: List of {old_string, new_string} replacements (optional, preferred for small changes)
|
||||
description: Updated description (optional, omit to keep existing)
|
||||
details: Full replacement for the details field (optional, mutually exclusive with detail_patches)
|
||||
detail_patches: List of {old_string, new_string} replacements applied to the details field (optional)
|
||||
metadata: Additional metadata updates (optional)
|
||||
|
||||
Returns:
|
||||
@@ -614,6 +628,8 @@ class CategoryFileManager:
|
||||
"""
|
||||
if code is not None and patches is not None:
|
||||
return {"success": False, "error": "Provide either 'code' or 'patches', not both"}
|
||||
if details is not None and detail_patches is not None:
|
||||
return {"success": False, "error": "Provide either 'details' or 'detail_patches', not both"}
|
||||
|
||||
try:
|
||||
cat = Category(category)
|
||||
@@ -664,10 +680,25 @@ class CategoryFileManager:
|
||||
except Exception as e:
|
||||
return {"success": False, "error": f"Failed to write implementation: {e}"}
|
||||
|
||||
# Apply text-replacement patches to details field if provided
|
||||
if detail_patches is not None:
|
||||
current_details = existing_meta.get("details", "")
|
||||
for i, patch in enumerate(detail_patches):
|
||||
old = patch.get("old_string", "")
|
||||
new = patch.get("new_string", "")
|
||||
if old not in current_details:
|
||||
return {"success": False, "error": f"Detail patch {i}: old_string not found in details"}
|
||||
if current_details.count(old) > 1:
|
||||
return {"success": False, "error": f"Detail patch {i}: old_string is not unique — add more surrounding context"}
|
||||
current_details = current_details.replace(old, new, 1)
|
||||
details = current_details
|
||||
|
||||
# Update metadata
|
||||
updated_meta = existing_meta.copy()
|
||||
if description is not None:
|
||||
updated_meta["description"] = description
|
||||
if details is not None:
|
||||
updated_meta["details"] = details
|
||||
if metadata:
|
||||
updated_meta.update(metadata)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user