execute_python can load any data source
This commit is contained in:
@@ -64,21 +64,27 @@ You have access to:
|
||||
### Chart Context Awareness
|
||||
When a user asks about "this chart", "the chart", "what I'm viewing", or similar references to their current view:
|
||||
1. **Chart info is automatically available** — The dynamic system prompt includes current chart state (symbol, interval, timeframe)
|
||||
2. **NEVER** ask the user to upload an image or tell you what symbol they're looking at
|
||||
3. **Just use `execute_python()`** — It automatically loads the chart data from what they're viewing
|
||||
4. Inside your Python script, `df` contains the data and `chart_context` has the metadata
|
||||
5. Use `plot_ohlc(df)` to create beautiful candlestick charts
|
||||
2. **Check if chart is visible** — If ChartStore fields (symbol, interval) are `None`, the user is on a narrow screen (mobile) and no chart is visible
|
||||
3. **When chart is visible:**
|
||||
- **NEVER** ask the user to upload an image or tell you what symbol they're looking at
|
||||
- **Just use `execute_python()`** — It automatically loads the chart data from what they're viewing
|
||||
- Inside your Python script, `df` contains the data and `chart_context` has the metadata
|
||||
- Use `plot_ohlc(df)` to create beautiful candlestick charts
|
||||
4. **When chart is NOT visible (symbol is None):**
|
||||
- Let the user know they can view charts on a wider screen
|
||||
- You can still help with analysis using `get_historical_data()` if they specify a symbol
|
||||
|
||||
This applies to questions like: "Can you see this chart?", "What are the swing highs and lows?", "Is this in an uptrend?", "What's the current price?", "Analyze this chart", "What am I looking at?"
|
||||
|
||||
### Data Analysis Workflow
|
||||
1. **Chart context is automatic** → Symbol, interval, and timeframe are in the dynamic system prompt
|
||||
2. **Use `execute_python()`** → This is your PRIMARY analysis tool
|
||||
- Automatically loads chart data into a pandas DataFrame `df`
|
||||
1. **Chart context is automatic** → Symbol, interval, and timeframe are in the dynamic system prompt (if chart is visible)
|
||||
2. **Check ChartStore** → If symbol/interval are `None`, no chart is visible (mobile view)
|
||||
3. **Use `execute_python()`** → This is your PRIMARY analysis tool
|
||||
- Automatically loads chart data into a pandas DataFrame `df` (if chart is visible)
|
||||
- Pre-imports numpy (`np`), pandas (`pd`), matplotlib (`plt`), and talib
|
||||
- Provides access to the indicator registry for computing indicators
|
||||
- Use `plot_ohlc(df)` helper for beautiful candlestick charts
|
||||
3. **Only use `get_chart_data()`** → For simple data inspection without analysis
|
||||
4. **Only use `get_chart_data()`** → For simple data inspection without analysis
|
||||
|
||||
### Python Analysis (`execute_python`) - Your Primary Tool
|
||||
|
||||
@@ -90,10 +96,13 @@ This applies to questions like: "Can you see this chart?", "What are the swing h
|
||||
- Any computational analysis of price data
|
||||
|
||||
**Why `execute_python()` is preferred:**
|
||||
- Chart data (`df`) is automatically loaded from ChartStore (visible time range)
|
||||
- Chart data (`df`) is automatically loaded from ChartStore (visible time range) when chart is visible
|
||||
- If no chart is visible (symbol is None), `df` will be None - but you can still load alternative data!
|
||||
- Full pandas/numpy/talib stack pre-imported
|
||||
- Use `plot_ohlc(df)` for instant professional candlestick charts
|
||||
- Access to 150+ indicators via `indicator_registry`
|
||||
- **Access to DataStores and registry** - order_store, chart_store, datasource_registry
|
||||
- **Can load ANY symbol/timeframe** using datasource_registry even when df is None
|
||||
- **Results include plots as image URLs** that are automatically displayed to the user
|
||||
- Prints and return values are included in the response
|
||||
|
||||
@@ -112,14 +121,14 @@ You MUST use `execute_python()` with `plot_ohlc()` or matplotlib whenever the us
|
||||
|
||||
**Example workflows:**
|
||||
```python
|
||||
# Computing an indicator and plotting
|
||||
# Computing an indicator and plotting (when chart is visible)
|
||||
execute_python("""
|
||||
df['RSI'] = talib.RSI(df['close'], 14)
|
||||
fig = plot_ohlc(df, title='Price with RSI')
|
||||
df[['close', 'RSI']].tail(10)
|
||||
""")
|
||||
|
||||
# Multi-indicator analysis
|
||||
# Multi-indicator analysis (when chart is visible)
|
||||
execute_python("""
|
||||
df['SMA20'] = df['close'].rolling(20).mean()
|
||||
df['BB_upper'] = df['close'].rolling(20).mean() + 2 * df['close'].rolling(20).std()
|
||||
@@ -128,6 +137,41 @@ fig = plot_ohlc(df, title=f"{chart_context['symbol']} with Bollinger Bands")
|
||||
print(f"Current price: {df['close'].iloc[-1]:.2f}")
|
||||
print(f"20-period SMA: {df['SMA20'].iloc[-1]:.2f}")
|
||||
""")
|
||||
|
||||
# Loading alternative data (works even when chart not visible or for different symbols)
|
||||
execute_python("""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Get data source
|
||||
binance = datasource_registry.get_source('binance')
|
||||
|
||||
# Load data for any symbol/timeframe
|
||||
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')
|
||||
|
||||
# Analyze 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 stores 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"Number of orders: {len(order_store.orders)}")
|
||||
""")
|
||||
```
|
||||
|
||||
**Only use `get_chart_data()` for:**
|
||||
@@ -143,8 +187,10 @@ print(f"20-period SMA: {df['SMA20'].iloc[-1]:.2f}")
|
||||
| "Is this bullish?" | `execute_python()` | Compute SMAs, trend, and analyze |
|
||||
| "Add Bollinger Bands" | `execute_python()` | Compute bands, use `plot_ohlc(df, title='BB')` |
|
||||
| "Find swing highs" | `execute_python()` | Use pandas logic to detect patterns |
|
||||
| "Plot ETH even though I'm viewing BTC" | `execute_python()` | Use `datasource_registry.get_source('binance')` to load ETH data |
|
||||
| "What indicators exist?" | `search_indicators()` | Search by category or query |
|
||||
| "What chart am I viewing?" | N/A - automatic | Chart info is in dynamic system prompt |
|
||||
| "Check my orders" | `execute_python()` | `print(order_store.orders)` |
|
||||
| "Read other stores" | `read_sync_state(store_name)` | For TraderState, StrategyState, etc. |
|
||||
|
||||
## Working with Users
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue'
|
||||
import Splitter from 'primevue/splitter'
|
||||
import SplitterPanel from 'primevue/splitterpanel'
|
||||
import ChartView from './components/ChartView.vue'
|
||||
@@ -14,13 +14,36 @@ const isAuthenticated = ref(false)
|
||||
const needsConfirmation = ref(false)
|
||||
const authError = ref<string>()
|
||||
const isDragging = ref(false)
|
||||
const isMobile = ref(false)
|
||||
let stateSyncCleanup: (() => void) | null = null
|
||||
|
||||
// Check screen width for mobile layout
|
||||
const checkMobile = () => {
|
||||
isMobile.value = window.innerWidth < 768
|
||||
}
|
||||
|
||||
const chartStore = useChartStore()
|
||||
|
||||
// Watch mobile state and update ChartStore
|
||||
watch(isMobile, (mobile) => {
|
||||
if (mobile) {
|
||||
// Set all chart state to null when chart is hidden
|
||||
chartStore.chart_state.symbol = null as any
|
||||
chartStore.chart_state.start_time = null
|
||||
chartStore.chart_state.end_time = null
|
||||
chartStore.chart_state.interval = null as any
|
||||
}
|
||||
})
|
||||
|
||||
// Check if we need password confirmation on first load
|
||||
onMounted(async () => {
|
||||
// Check if secrets store is initialized by trying to fetch a status endpoint
|
||||
// For now, just default to false (user will see login screen)
|
||||
needsConfirmation.value = false
|
||||
|
||||
// Initialize mobile check
|
||||
checkMobile()
|
||||
window.addEventListener('resize', checkMobile)
|
||||
})
|
||||
|
||||
const handleAuthenticate = async (
|
||||
@@ -75,6 +98,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', checkMobile)
|
||||
if (stateSyncCleanup) {
|
||||
stateSyncCleanup()
|
||||
}
|
||||
@@ -90,7 +114,7 @@ onBeforeUnmount(() => {
|
||||
:error-message="authError"
|
||||
@authenticate="handleAuthenticate"
|
||||
/>
|
||||
<Splitter v-else class="main-splitter">
|
||||
<Splitter v-else-if="!isMobile" class="main-splitter">
|
||||
<SplitterPanel :size="62" :minSize="40" class="chart-panel">
|
||||
<ChartView />
|
||||
</SplitterPanel>
|
||||
@@ -98,6 +122,9 @@ onBeforeUnmount(() => {
|
||||
<ChatPanel />
|
||||
</SplitterPanel>
|
||||
</Splitter>
|
||||
<div v-else class="mobile-layout">
|
||||
<ChatPanel />
|
||||
</div>
|
||||
<!-- Transparent overlay to prevent iframe from capturing mouse events during drag -->
|
||||
<div v-if="isDragging" class="drag-overlay"></div>
|
||||
</div>
|
||||
@@ -153,4 +180,18 @@ onBeforeUnmount(() => {
|
||||
cursor: col-resize;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.mobile-layout {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.main-splitter {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user