436 lines
14 KiB
Python
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
|
|
]
|