execute_python can load any data source

This commit is contained in:
2026-03-02 18:48:54 -04:00
parent 3ffce97b3e
commit f4da40706c
4 changed files with 197 additions and 23 deletions

View File

@@ -29,6 +29,22 @@ def _get_indicator_registry():
return _indicator_registry
def _get_order_store():
"""Get the global OrderStore instance."""
registry = _get_registry()
if registry and "OrderStore" in registry.entries:
return registry.entries["OrderStore"].model
return None
def _get_chart_store():
"""Get the global ChartStore instance."""
registry = _get_registry()
if registry and "ChartStore" in registry.entries:
return registry.entries["ChartStore"].model
return None
async def _get_chart_data_impl(countback: Optional[int] = None):
"""Internal implementation for getting chart data.
@@ -60,8 +76,13 @@ async def _get_chart_data_impl(countback: Optional[int] = None):
start_time = chart_data.get("start_time")
end_time = chart_data.get("end_time")
if not symbol:
raise ValueError("No symbol set in ChartStore - user may not have loaded a chart yet")
if not symbol or symbol is None:
raise ValueError(
"No chart visible - ChartStore symbol is None. "
"The user is likely on a narrow screen (mobile) where charts are hidden. "
"Let them know they can view charts on a wider screen, or use get_historical_data() "
"if they specify a symbol and timeframe."
)
# Parse the symbol to extract exchange/source and symbol name
# Format is "EXCHANGE:SYMBOL" (e.g., "BINANCE:BTC/USDT", "DEMO:BTC/USD")
@@ -142,6 +163,11 @@ async def get_chart_data(countback: Optional[int] = None) -> Dict[str, Any]:
This is the preferred way to access chart data when helping the user analyze
what they're looking at, since it automatically uses their current chart context.
**IMPORTANT**: This tool will fail if ChartStore.symbol is None (no chart visible).
This happens when the user is on a narrow screen (mobile) where charts are hidden.
In that case, let the user know charts are only visible on wider screens, or use
get_historical_data() if they specify a symbol and timeframe.
Args:
countback: Optional limit on number of bars to return. If not specified,
returns all bars in the visible time range.
@@ -157,7 +183,7 @@ async def get_chart_data(countback: Optional[int] = None) -> Dict[str, Any]:
Raises:
ValueError: If ChartStore or DataSourceRegistry is not initialized,
or if the symbol format is invalid
if no chart is visible (symbol is None), or if the symbol format is invalid
Example:
# User is viewing BINANCE:BTC/USDT on 15min chart
@@ -191,12 +217,26 @@ async def execute_python(code: str, countback: Optional[int] = None) -> Dict[str
- `talib` : TA-Lib technical analysis library
- `indicator_registry`: 150+ registered indicators
- `plot_ohlc(df)` : Helper function for beautiful candlestick charts
- `registry` : SyncRegistry instance - access to all registered stores
- `datasource_registry`: DataSourceRegistry - access to data sources (binance, etc.)
- `order_store` : OrderStore instance - current orders list
- `chart_store` : ChartStore instance - current chart state
Auto-loaded when user has a chart open:
Auto-loaded when user has a chart visible (ChartStore.symbol is not None):
- `df` : pandas DataFrame with DatetimeIndex and columns:
open, high, low, close, volume (OHLCV data ready to use)
- `chart_context` : dict with symbol, interval, start_time, end_time
When NO chart is visible (narrow screen/mobile):
- `df` : None
- `chart_context` : None
If `df` is None, you can still load alternative data by:
- Using chart_store to see what symbol/timeframe is configured
- Using datasource_registry.get_source('binance') to access data sources
- Calling datasource.get_history(symbol, interval, start, end) to load any data
- This allows you to make plots of ANY chart even when not connected to chart view
The `plot_ohlc()` Helper:
Create professional candlestick charts instantly:
- `plot_ohlc(df)` - basic OHLC chart with volume
@@ -250,6 +290,41 @@ async def execute_python(code: str, countback: Optional[int] = None) -> Dict[str
print("Recent swing highs:")
print(swing_highs)
\"\"\")
# Load alternative data when df is None or for different symbol/timeframe
execute_python(\"\"\"
from datetime import datetime, timedelta
# Get data source
binance = datasource_registry.get_source('binance')
# Load ETH data even if viewing BTC chart
end_time = datetime.now()
start_time = end_time - timedelta(days=7)
result = await binance.get_history(
symbol='ETH/USDT',
interval='1h',
start=int(start_time.timestamp()),
end=int(end_time.timestamp())
)
# Convert to DataFrame
rows = [{'time': pd.to_datetime(bar.time, unit='s'), **bar.data} for bar in result.bars]
eth_df = pd.DataFrame(rows).set_index('time')
# Calculate RSI and plot
eth_df['RSI'] = talib.RSI(eth_df['close'], 14)
fig = plot_ohlc(eth_df, title='ETH/USDT 1h - RSI Analysis')
print(f"ETH RSI: {eth_df['RSI'].iloc[-1]:.2f}")
\"\"\")
# Access chart store to see current state
execute_python(\"\"\"
print(f"Current symbol: {chart_store.chart_state.symbol}")
print(f"Current interval: {chart_store.chart_state.interval}")
print(f"Orders: {len(order_store.orders)}")
\"\"\")
"""
import pandas as pd
import numpy as np
@@ -292,6 +367,10 @@ async def execute_python(code: str, countback: Optional[int] = None) -> Dict[str
# --- Get indicator registry ---
indicator_registry = _get_indicator_registry()
# --- Get DataStores ---
order_store = _get_order_store()
chart_store = _get_chart_store()
# --- Build globals ---
script_globals: Dict[str, Any] = {
'pd': pd,
@@ -299,6 +378,10 @@ async def execute_python(code: str, countback: Optional[int] = None) -> Dict[str
'plt': plt,
'talib': talib,
'indicator_registry': indicator_registry,
'registry': registry,
'datasource_registry': datasource_registry,
'order_store': order_store,
'chart_store': chart_store,
'df': df,
'chart_context': chart_context,
'plot_ohlc': plot_ohlc,

View File

@@ -4,18 +4,22 @@ from pydantic import BaseModel, Field
class ChartState(BaseModel):
"""Tracks the user's current chart view state.
This state is synchronized between the frontend and backend to allow
the AI agent to understand what the user is currently viewing.
All fields can be None when no chart is visible (e.g., on mobile/narrow screens).
"""
# Current symbol being viewed (e.g., "BINANCE:BTC/USDT", "BINANCE:ETH/USDT")
symbol: str = Field(default="BINANCE:BTC/USDT", description="Current trading pair symbol")
# None when chart is not visible
symbol: Optional[str] = Field(default="BINANCE:BTC/USDT", description="Current trading pair symbol, or None if no chart visible")
# Time range currently visible on chart (Unix timestamps in seconds)
# These represent the leftmost and rightmost visible candle times
start_time: Optional[int] = Field(default=None, description="Start time of visible range (Unix timestamp in seconds)")
end_time: Optional[int] = Field(default=None, description="End time of visible range (Unix timestamp in seconds)")
# Optional: Chart interval/resolution
interval: str = Field(default="15", description="Chart interval (e.g., '1', '5', '15', '60', 'D')")
# None when chart is not visible
interval: Optional[str] = Field(default="15", description="Chart interval (e.g., '1', '5', '15', '60', 'D'), or None if no chart visible")