Files
ai/backend.old/src/agent/tools/indicator_tools.py
2026-03-11 18:47:11 -04:00

436 lines
14 KiB
Python

"""Technical indicator tools.
These tools allow the agent to:
1. Discover available indicators (list, search, get info)
2. Add indicators to the chart
3. Update/remove indicators
4. Query currently applied indicators
"""
from typing import Dict, Any, List, Optional
from langchain_core.tools import tool
import logging
import time
logger = logging.getLogger(__name__)
def _get_indicator_registry():
"""Get the global indicator registry instance."""
from . import _indicator_registry
return _indicator_registry
def _get_registry():
"""Get the global sync registry instance."""
from . import _registry
return _registry
def _get_indicator_store():
"""Get the global IndicatorStore instance."""
registry = _get_registry()
if registry and "IndicatorStore" in registry.entries:
return registry.entries["IndicatorStore"].model
return None
@tool
def list_indicators() -> List[str]:
"""List all available technical indicators.
Returns:
List of indicator names that can be used in analysis and strategies
"""
registry = _get_indicator_registry()
if not registry:
return []
return registry.list_indicators()
@tool
def get_indicator_info(indicator_name: str) -> Dict[str, Any]:
"""Get detailed information about a specific indicator.
Retrieves metadata including description, parameters, category, use cases,
input/output schemas, and references.
Args:
indicator_name: Name of the indicator (e.g., "RSI", "SMA", "MACD")
Returns:
Dictionary containing:
- name: Indicator name
- display_name: Human-readable name
- description: What the indicator computes and why it's useful
- category: Category (momentum, trend, volatility, volume, etc.)
- parameters: List of configurable parameters with types and defaults
- use_cases: Common trading scenarios where this indicator helps
- tags: Searchable tags
- input_schema: Required input columns (e.g., OHLCV requirements)
- output_schema: Columns this indicator produces
Raises:
ValueError: If indicator_name is not found
"""
registry = _get_indicator_registry()
if not registry:
raise ValueError("IndicatorRegistry not initialized")
metadata = registry.get_metadata(indicator_name)
if not metadata:
total_count = len(registry.list_indicators())
raise ValueError(
f"Indicator '{indicator_name}' not found. "
f"Total available: {total_count} indicators. "
f"Use search_indicators() to find indicators by name, category, or tag."
)
input_schema = registry.get_input_schema(indicator_name)
output_schema = registry.get_output_schema(indicator_name)
result = metadata.model_dump()
result["input_schema"] = input_schema.model_dump() if input_schema else None
result["output_schema"] = output_schema.model_dump() if output_schema else None
return result
@tool
def search_indicators(
query: Optional[str] = None,
category: Optional[str] = None,
tag: Optional[str] = None
) -> List[Dict[str, Any]]:
"""Search for indicators by text query, category, or tag.
Returns lightweight summaries - use get_indicator_info() for full details on specific indicators.
Use this to discover relevant indicators for your trading strategy or analysis.
Can filter by category (momentum, trend, volatility, etc.) or search by keywords.
Args:
query: Optional text search across names, descriptions, and use cases
category: Optional category filter (momentum, trend, volatility, volume, pattern, etc.)
tag: Optional tag filter (e.g., "oscillator", "moving-average", "talib")
Returns:
List of lightweight indicator summaries. Each contains:
- name: Indicator name (use with get_indicator_info() for full details)
- display_name: Human-readable name
- description: Brief one-line description
- category: Category (momentum, trend, volatility, etc.)
Example:
# Find all momentum indicators
results = search_indicators(category="momentum")
# Returns [{name: "RSI", display_name: "RSI", description: "...", category: "momentum"}, ...]
# Then get details on interesting ones
rsi_details = get_indicator_info("RSI") # Full parameters, schemas, use cases
# Search for moving average indicators
search_indicators(query="moving average")
# Find all TA-Lib indicators
search_indicators(tag="talib")
"""
registry = _get_indicator_registry()
if not registry:
raise ValueError("IndicatorRegistry not initialized")
results = []
if query:
results = registry.search_by_text(query)
elif category:
results = registry.search_by_category(category)
elif tag:
results = registry.search_by_tag(tag)
else:
# Return all indicators if no filter
results = registry.get_all_metadata()
# Return lightweight summaries only
return [
{
"name": r.name,
"display_name": r.display_name,
"description": r.description,
"category": r.category
}
for r in results
]
@tool
def get_indicator_categories() -> Dict[str, int]:
"""Get all indicator categories and their counts.
Returns a summary of available indicator categories, useful for
exploring what types of indicators are available.
Returns:
Dictionary mapping category name to count of indicators in that category.
Example: {"momentum": 25, "trend": 15, "volatility": 8, ...}
"""
registry = _get_indicator_registry()
if not registry:
raise ValueError("IndicatorRegistry not initialized")
categories: Dict[str, int] = {}
for metadata in registry.get_all_metadata():
category = metadata.category
categories[category] = categories.get(category, 0) + 1
return categories
@tool
async def add_indicator_to_chart(
indicator_id: str,
talib_name: str,
parameters: Optional[Dict[str, Any]] = None,
symbol: Optional[str] = None
) -> Dict[str, Any]:
"""Add a technical indicator to the chart.
This will create a new indicator instance and display it on the TradingView chart.
The indicator will be synchronized with the frontend in real-time.
Args:
indicator_id: Unique identifier for this indicator instance (e.g., 'rsi_14', 'sma_50')
talib_name: Name of the TA-Lib indicator (e.g., 'RSI', 'SMA', 'MACD', 'BBANDS')
Use search_indicators() or get_indicator_info() to find available indicators
parameters: Optional dictionary of indicator parameters
Example for RSI: {'timeperiod': 14}
Example for SMA: {'timeperiod': 50}
Example for MACD: {'fastperiod': 12, 'slowperiod': 26, 'signalperiod': 9}
Example for BBANDS: {'timeperiod': 20, 'nbdevup': 2, 'nbdevdn': 2}
symbol: Optional symbol to apply the indicator to (defaults to current chart symbol)
Returns:
Dictionary with:
- status: 'created' or 'updated'
- indicator: The complete indicator object
Example:
# Add RSI(14)
await add_indicator_to_chart(
indicator_id='rsi_14',
talib_name='RSI',
parameters={'timeperiod': 14}
)
# Add 50-period SMA
await add_indicator_to_chart(
indicator_id='sma_50',
talib_name='SMA',
parameters={'timeperiod': 50}
)
# Add MACD with default parameters
await add_indicator_to_chart(
indicator_id='macd_default',
talib_name='MACD'
)
"""
from schema.indicator import IndicatorInstance
registry = _get_registry()
if not registry:
raise ValueError("SyncRegistry not initialized")
indicator_store = _get_indicator_store()
if not indicator_store:
raise ValueError("IndicatorStore not initialized")
# Verify the indicator exists
indicator_registry = _get_indicator_registry()
if not indicator_registry:
raise ValueError("IndicatorRegistry not initialized")
metadata = indicator_registry.get_metadata(talib_name)
if not metadata:
raise ValueError(
f"Indicator '{talib_name}' not found. "
f"Use search_indicators() to find available indicators."
)
# Check if updating existing indicator
existing_indicator = indicator_store.indicators.get(indicator_id)
is_update = existing_indicator is not None
# If symbol is not provided, try to get it from ChartStore
if symbol is None and "ChartStore" in registry.entries:
chart_store = registry.entries["ChartStore"].model
if hasattr(chart_store, 'chart_state') and hasattr(chart_store.chart_state, 'symbol'):
symbol = chart_store.chart_state.symbol
logger.info(f"Using current chart symbol for indicator: {symbol}")
now = int(time.time())
# Create indicator instance
indicator = IndicatorInstance(
id=indicator_id,
talib_name=talib_name,
instance_name=f"{talib_name}_{indicator_id}",
parameters=parameters or {},
visible=True,
pane='chart', # Most indicators go on the chart pane
symbol=symbol,
created_at=existing_indicator.get('created_at') if existing_indicator else now,
modified_at=now
)
# Update the store
indicator_store.indicators[indicator_id] = indicator.model_dump(mode="json")
# Trigger sync
await registry.push_all()
logger.info(
f"{'Updated' if is_update else 'Created'} indicator '{indicator_id}' "
f"(TA-Lib: {talib_name}) with parameters: {parameters}"
)
return {
"status": "updated" if is_update else "created",
"indicator": indicator.model_dump(mode="json")
}
@tool
async def remove_indicator_from_chart(indicator_id: str) -> Dict[str, str]:
"""Remove an indicator from the chart.
Args:
indicator_id: ID of the indicator instance to remove
Returns:
Dictionary with status message
Raises:
ValueError: If indicator doesn't exist
Example:
await remove_indicator_from_chart('rsi_14')
"""
registry = _get_registry()
if not registry:
raise ValueError("SyncRegistry not initialized")
indicator_store = _get_indicator_store()
if not indicator_store:
raise ValueError("IndicatorStore not initialized")
if indicator_id not in indicator_store.indicators:
raise ValueError(f"Indicator '{indicator_id}' not found")
# Delete the indicator
del indicator_store.indicators[indicator_id]
# Trigger sync
await registry.push_all()
logger.info(f"Removed indicator '{indicator_id}'")
return {
"status": "success",
"message": f"Indicator '{indicator_id}' removed"
}
@tool
def list_chart_indicators(symbol: Optional[str] = None) -> List[Dict[str, Any]]:
"""List all indicators currently applied to the chart.
Args:
symbol: Optional filter by symbol (defaults to current chart symbol)
Returns:
List of indicator instances, each containing:
- id: Indicator instance ID
- talib_name: TA-Lib indicator name
- instance_name: Display name
- parameters: Current parameter values
- visible: Whether indicator is visible
- pane: Which pane it's displayed in
- symbol: Symbol it's applied to
Example:
# List all indicators on current symbol
indicators = list_chart_indicators()
# List indicators on specific symbol
btc_indicators = list_chart_indicators(symbol='BINANCE:BTC/USDT')
"""
indicator_store = _get_indicator_store()
if not indicator_store:
raise ValueError("IndicatorStore not initialized")
logger.info(f"list_chart_indicators: Raw store indicators: {indicator_store.indicators}")
# If symbol is not provided, try to get it from ChartStore
if symbol is None:
registry = _get_registry()
if registry and "ChartStore" in registry.entries:
chart_store = registry.entries["ChartStore"].model
if hasattr(chart_store, 'chart_state') and hasattr(chart_store.chart_state, 'symbol'):
symbol = chart_store.chart_state.symbol
indicators = list(indicator_store.indicators.values())
logger.info(f"list_chart_indicators: Converted to list: {indicators}")
logger.info(f"list_chart_indicators: Filtering by symbol: {symbol}")
# Filter by symbol if provided
if symbol:
indicators = [ind for ind in indicators if ind.get('symbol') == symbol]
logger.info(f"list_chart_indicators: Returning {len(indicators)} indicators")
return indicators
@tool
def get_chart_indicator(indicator_id: str) -> Dict[str, Any]:
"""Get details of a specific indicator on the chart.
Args:
indicator_id: ID of the indicator instance
Returns:
Dictionary containing the indicator data
Raises:
ValueError: If indicator doesn't exist
Example:
indicator = get_chart_indicator('rsi_14')
print(f"Indicator: {indicator['talib_name']}")
print(f"Parameters: {indicator['parameters']}")
"""
indicator_store = _get_indicator_store()
if not indicator_store:
raise ValueError("IndicatorStore not initialized")
indicator = indicator_store.indicators.get(indicator_id)
if not indicator:
raise ValueError(f"Indicator '{indicator_id}' not found")
return indicator
INDICATOR_TOOLS = [
# Discovery tools
list_indicators,
get_indicator_info,
search_indicators,
get_indicator_categories,
# Chart indicator management tools
add_indicator_to_chart,
remove_indicator_from_chart,
list_chart_indicators,
get_chart_indicator
]