major agent refactoring: wiki knowledge base, no RAG, no Qdrant, no Ollama
This commit is contained in:
500
gateway/knowledge/api-reference.md
Normal file
500
gateway/knowledge/api-reference.md
Normal file
@@ -0,0 +1,500 @@
|
||||
# Dexorder Research API Reference
|
||||
|
||||
This file contains the complete Python API source code with full docstrings.
|
||||
These files are copied verbatim from `sandbox/dexorder/api/`.
|
||||
|
||||
The API provides access to market data and charting capabilities for research scripts.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Research scripts access the API via:
|
||||
```python
|
||||
from dexorder.api import get_api
|
||||
api = get_api()
|
||||
```
|
||||
|
||||
The API instance provides:
|
||||
- `api.data` - DataAPI for fetching OHLC market data
|
||||
- `api.charting` - ChartingAPI for creating financial charts
|
||||
|
||||
---
|
||||
|
||||
## Complete API Source Code
|
||||
|
||||
The following sections contain the verbatim Python source files with complete
|
||||
type hints, docstrings, and examples.
|
||||
|
||||
|
||||
### api.py
|
||||
```python
|
||||
"""
|
||||
Main Dexorder API - provides access to market data and charting.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from .charting_api import ChartingAPI
|
||||
from .data_api import DataAPI
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class API:
|
||||
"""
|
||||
Main API for accessing market data and creating charts.
|
||||
|
||||
This is the primary interface for research scripts and trading strategies.
|
||||
Access this via get_api() in research scripts.
|
||||
|
||||
Attributes:
|
||||
data: DataAPI for fetching historical and current market data
|
||||
charting: ChartingAPI for creating candlestick charts and visualizations
|
||||
|
||||
Example:
|
||||
from dexorder.api import get_api
|
||||
import asyncio
|
||||
|
||||
api = get_api()
|
||||
|
||||
# Fetch data
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time="2021-12-20",
|
||||
end_time="2021-12-21"
|
||||
))
|
||||
|
||||
# Create chart
|
||||
fig, ax = api.charting.plot_ohlc(df, title="BTC/USDT 1H")
|
||||
"""
|
||||
|
||||
def __init__(self, charting: ChartingAPI, data: DataAPI):
|
||||
self.charting: ChartingAPI = charting
|
||||
self.data: DataAPI = data
|
||||
```
|
||||
|
||||
|
||||
### data_api.py
|
||||
```python
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, List
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from dexorder.utils import TimestampInput
|
||||
|
||||
|
||||
class DataAPI(ABC):
|
||||
"""
|
||||
API for accessing market data.
|
||||
|
||||
Provides methods to query OHLC (Open, High, Low, Close) candlestick data
|
||||
for cryptocurrency markets.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def historical_ohlc(
|
||||
self,
|
||||
ticker: str,
|
||||
period_seconds: int,
|
||||
start_time: TimestampInput,
|
||||
end_time: TimestampInput,
|
||||
extra_columns: Optional[List[str]] = None,
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
Fetch historical OHLC candlestick data for a market.
|
||||
|
||||
Args:
|
||||
ticker: Market identifier in format "MARKET.EXCHANGE"
|
||||
Examples: "BTC/USDT.BINANCE", "ETH/USD.COINBASE"
|
||||
period_seconds: Candle period in seconds
|
||||
Common values:
|
||||
- 60 (1 minute)
|
||||
- 300 (5 minutes)
|
||||
- 900 (15 minutes)
|
||||
- 3600 (1 hour)
|
||||
- 86400 (1 day)
|
||||
- 604800 (1 week)
|
||||
start_time: Start of time range. Accepts:
|
||||
- Unix timestamp in seconds (int/float): 1640000000
|
||||
- Date string: "2021-12-20" or "2021-12-20 12:00:00"
|
||||
- datetime object: datetime(2021, 12, 20)
|
||||
- pandas Timestamp: pd.Timestamp("2021-12-20")
|
||||
end_time: End of time range. Same formats as start_time.
|
||||
extra_columns: Optional additional columns to include beyond the standard
|
||||
OHLC columns. Available options:
|
||||
- "volume" - Total volume (decimal float)
|
||||
- "buy_vol" - Buy-side volume (decimal float)
|
||||
- "sell_vol" - Sell-side volume (decimal float)
|
||||
- "open_time", "high_time", "low_time", "close_time" (timestamps)
|
||||
- "open_interest" (for futures markets)
|
||||
- "ticker", "period_seconds"
|
||||
|
||||
Returns:
|
||||
DataFrame with candlestick data sorted by timestamp (ascending).
|
||||
Standard columns (always included):
|
||||
- timestamp: Period start time in nanoseconds
|
||||
- open: Opening price (decimal float)
|
||||
- high: Highest price (decimal float)
|
||||
- low: Lowest price (decimal float)
|
||||
- close: Closing price (decimal float)
|
||||
|
||||
Plus any columns specified in extra_columns.
|
||||
|
||||
All prices and volumes are automatically converted to decimal floats
|
||||
using market metadata. No manual conversion is needed.
|
||||
|
||||
Returns empty DataFrame if no data is available.
|
||||
|
||||
Examples:
|
||||
# Basic OHLC with Unix timestamp
|
||||
df = await api.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time=1640000000,
|
||||
end_time=1640086400
|
||||
)
|
||||
|
||||
# Using date strings with volume
|
||||
df = await api.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time="2021-12-20",
|
||||
end_time="2021-12-21",
|
||||
extra_columns=["volume"]
|
||||
)
|
||||
|
||||
# Using datetime objects
|
||||
from datetime import datetime
|
||||
df = await api.historical_ohlc(
|
||||
ticker="ETH/USD.COINBASE",
|
||||
period_seconds=300,
|
||||
start_time=datetime(2021, 12, 20, 9, 30),
|
||||
end_time=datetime(2021, 12, 20, 16, 30),
|
||||
extra_columns=["volume", "buy_vol", "sell_vol"]
|
||||
)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def latest_ohlc(
|
||||
self,
|
||||
ticker: str,
|
||||
period_seconds: int,
|
||||
length: int = 1,
|
||||
extra_columns: Optional[List[str]] = None,
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
Query the most recent OHLC candles for a ticker.
|
||||
|
||||
This method fetches the latest N completed candles without needing to
|
||||
specify exact timestamps. Useful for real-time analysis and indicators.
|
||||
|
||||
Args:
|
||||
ticker: Market identifier in format "MARKET.EXCHANGE"
|
||||
Examples: "BTC/USDT.BINANCE", "ETH/USD.COINBASE"
|
||||
period_seconds: OHLC candle period in seconds
|
||||
Common values: 60 (1m), 300 (5m), 900 (15m), 3600 (1h),
|
||||
86400 (1d), 604800 (1w)
|
||||
length: Number of most recent candles to return (default: 1)
|
||||
extra_columns: Optional list of additional column names to include.
|
||||
Same column options as historical_ohlc:
|
||||
- "volume", "buy_vol", "sell_vol"
|
||||
- "open_time", "high_time", "low_time", "close_time"
|
||||
- "open_interest", "ticker", "period_seconds"
|
||||
|
||||
Returns:
|
||||
Pandas DataFrame with the same column structure as historical_ohlc,
|
||||
containing the N most recent completed candles sorted by timestamp.
|
||||
Returns empty DataFrame if no data is available.
|
||||
|
||||
Examples:
|
||||
# Get the last candle
|
||||
df = await api.latest_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600
|
||||
)
|
||||
# Returns: timestamp, open, high, low, close
|
||||
|
||||
# Get the last 50 5-minute candles with volume
|
||||
df = await api.latest_ohlc(
|
||||
ticker="ETH/USD.COINBASE",
|
||||
period_seconds=300,
|
||||
length=50,
|
||||
extra_columns=["volume", "buy_vol", "sell_vol"]
|
||||
)
|
||||
|
||||
# Get recent candles with all timing data
|
||||
df = await api.latest_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=60,
|
||||
length=100,
|
||||
extra_columns=["open_time", "high_time", "low_time", "close_time"]
|
||||
)
|
||||
|
||||
Note:
|
||||
This method returns only completed candles. The current (incomplete)
|
||||
candle is not included.
|
||||
"""
|
||||
pass
|
||||
|
||||
```
|
||||
|
||||
|
||||
### charting_api.py
|
||||
```python
|
||||
import logging
|
||||
from abc import abstractmethod, ABC
|
||||
from typing import Optional, Tuple, List
|
||||
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
|
||||
class ChartingAPI(ABC):
|
||||
"""
|
||||
API for creating financial charts and visualizations.
|
||||
|
||||
Provides methods to create candlestick charts, add technical indicator panels,
|
||||
and build custom visualizations. All figures are automatically captured and
|
||||
returned to the client as images.
|
||||
|
||||
Basic workflow:
|
||||
1. Create a chart with plot_ohlc() → returns Figure and Axes
|
||||
2. Optionally overlay indicators on the main axes (e.g., moving averages)
|
||||
3. Optionally add indicator panels below with add_indicator_panel()
|
||||
4. Figures are automatically captured (no need to save manually)
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def plot_ohlc(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
title: Optional[str] = None,
|
||||
volume: bool = False,
|
||||
style: str = "charles",
|
||||
figsize: Tuple[int, int] = (12, 8),
|
||||
**kwargs
|
||||
) -> Tuple[Figure, plt.Axes]:
|
||||
"""
|
||||
Create a candlestick chart from OHLC data.
|
||||
|
||||
Args:
|
||||
df: DataFrame with OHLC data. Required columns: open, high, low, close.
|
||||
Column names are case-insensitive.
|
||||
title: Chart title (optional)
|
||||
volume: If True, shows volume bars below the candlesticks (requires 'volume' column)
|
||||
style: Visual style for the chart. Available styles:
|
||||
"charles" (default), "binance", "blueskies", "brasil", "checkers",
|
||||
"classic", "mike", "nightclouds", "sas", "starsandstripes", "yahoo"
|
||||
figsize: Figure size as (width, height) in inches. Default: (12, 8)
|
||||
**kwargs: Additional styling arguments
|
||||
|
||||
Returns:
|
||||
Tuple of (Figure, Axes):
|
||||
- Figure: matplotlib Figure object
|
||||
- Axes: Main candlestick axes (use for overlaying indicators)
|
||||
|
||||
Examples:
|
||||
# Basic chart
|
||||
fig, ax = api.plot_ohlc(df)
|
||||
|
||||
# With volume and title
|
||||
fig, ax = api.plot_ohlc(
|
||||
df,
|
||||
title="BTC/USDT 1H",
|
||||
volume=True,
|
||||
style="binance"
|
||||
)
|
||||
|
||||
# Overlay moving average
|
||||
# NOTE: mplfinance uses integer x-positions (0..N-1) internally,
|
||||
# so overlays must use range(len(df)), not df.index.
|
||||
fig, ax = api.plot_ohlc(df)
|
||||
ax.plot(range(len(df)), df['sma_20'], label="SMA 20", color="blue")
|
||||
ax.legend()
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_indicator_panel(
|
||||
self,
|
||||
fig: Figure,
|
||||
df: pd.DataFrame,
|
||||
columns: Optional[List[str]] = None,
|
||||
ylabel: Optional[str] = None,
|
||||
height_ratio: float = 0.3,
|
||||
ylim: Optional[Tuple[float, float]] = None,
|
||||
**kwargs
|
||||
) -> plt.Axes:
|
||||
"""
|
||||
Add an indicator panel below the chart with time-aligned x-axis.
|
||||
|
||||
Use this to display indicators that should be shown separately from the
|
||||
price chart (e.g., RSI, MACD, volume).
|
||||
|
||||
Args:
|
||||
fig: Figure object from plot_ohlc()
|
||||
df: DataFrame with indicator data (must have same index as OHLC data)
|
||||
columns: Column names to plot. If None, plots all numeric columns.
|
||||
ylabel: Y-axis label (e.g., "RSI", "MACD")
|
||||
height_ratio: Panel height relative to main chart (default: 0.3 = 30%)
|
||||
ylim: Y-axis limits as (min, max). If None, auto-scales.
|
||||
**kwargs: Line styling options (color, linewidth, linestyle, alpha)
|
||||
|
||||
Returns:
|
||||
Axes object for the new panel (use for further customization)
|
||||
|
||||
Examples:
|
||||
# Add RSI panel with reference lines
|
||||
fig, ax = api.plot_ohlc(df)
|
||||
rsi_ax = api.add_indicator_panel(
|
||||
fig, df,
|
||||
columns=["rsi"],
|
||||
ylabel="RSI",
|
||||
ylim=(0, 100)
|
||||
)
|
||||
rsi_ax.axhline(30, color='green', linestyle='--', alpha=0.5)
|
||||
rsi_ax.axhline(70, color='red', linestyle='--', alpha=0.5)
|
||||
|
||||
# Add MACD panel
|
||||
fig, ax = api.plot_ohlc(df)
|
||||
api.add_indicator_panel(
|
||||
fig, df,
|
||||
columns=["macd", "macd_signal"],
|
||||
ylabel="MACD"
|
||||
)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_figure(
|
||||
self,
|
||||
figsize: Tuple[int, int] = (12, 8),
|
||||
style: str = "charles"
|
||||
) -> Tuple[Figure, plt.Axes]:
|
||||
"""
|
||||
Create a styled figure for custom visualizations.
|
||||
|
||||
Use this when you want to create charts other than candlesticks
|
||||
(e.g., histograms, scatter plots, heatmaps).
|
||||
|
||||
Args:
|
||||
figsize: Figure size as (width, height) in inches. Default: (12, 8)
|
||||
style: Style name for consistent theming. Default: "charles"
|
||||
|
||||
Returns:
|
||||
Tuple of (Figure, Axes) ready for plotting
|
||||
|
||||
Examples:
|
||||
# Histogram
|
||||
fig, ax = api.create_figure()
|
||||
ax.hist(returns, bins=50)
|
||||
ax.set_title("Return Distribution")
|
||||
|
||||
# Heatmap
|
||||
fig, ax = api.create_figure(figsize=(10, 10))
|
||||
import seaborn as sns
|
||||
sns.heatmap(correlation_matrix, ax=ax)
|
||||
ax.set_title("Correlation Matrix")
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
### __init__.py
|
||||
```python
|
||||
"""
|
||||
Dexorder API - market data and charting for research and trading.
|
||||
|
||||
For research scripts, import and use get_api() to access the API:
|
||||
|
||||
from dexorder.api import get_api
|
||||
import asyncio
|
||||
|
||||
api = get_api()
|
||||
df = asyncio.run(api.data.historical_ohlc(...))
|
||||
fig, ax = api.charting.plot_ohlc(df)
|
||||
"""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from typing import Optional
|
||||
|
||||
from dexorder.api.api import API
|
||||
from dexorder.api.charting_api import ChartingAPI
|
||||
from dexorder.api.data_api import DataAPI
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Global API instance - managed by main.py
|
||||
_global_api: Optional[API] = None
|
||||
|
||||
# Thread-local API — used by harness threads so they don't overwrite the global
|
||||
_thread_local = threading.local()
|
||||
|
||||
|
||||
def get_api() -> API:
|
||||
"""
|
||||
Get the API instance for accessing market data and charts.
|
||||
|
||||
Use this in research scripts to access the data and charting APIs.
|
||||
|
||||
Returns:
|
||||
API instance with data and charting capabilities
|
||||
|
||||
Raises:
|
||||
RuntimeError: If called before API initialization (should not happen in research scripts)
|
||||
|
||||
Example:
|
||||
from dexorder.api import get_api
|
||||
import asyncio
|
||||
|
||||
api = get_api()
|
||||
|
||||
# Fetch data
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time="2021-12-20",
|
||||
end_time="2021-12-21"
|
||||
))
|
||||
|
||||
# Create chart
|
||||
fig, ax = api.charting.plot_ohlc(df, title="BTC/USDT")
|
||||
"""
|
||||
# Thread-local takes priority (set by harness threads)
|
||||
api = getattr(_thread_local, 'api', None)
|
||||
if api is not None:
|
||||
return api
|
||||
if _global_api is None:
|
||||
raise RuntimeError("API not initialized")
|
||||
return _global_api
|
||||
|
||||
|
||||
def set_api(api: API) -> None:
|
||||
"""Set the API instance.
|
||||
|
||||
When called from the main thread, sets the global API used by all threads.
|
||||
When called from a non-main thread (e.g. harness threads), sets a thread-local
|
||||
API so the global is not overwritten.
|
||||
"""
|
||||
if threading.current_thread() is threading.main_thread():
|
||||
global _global_api
|
||||
_global_api = api
|
||||
else:
|
||||
_thread_local.api = api
|
||||
|
||||
|
||||
__all__ = ['API', 'ChartingAPI', 'DataAPI', 'get_api', 'set_api']
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
For practical usage patterns and complete working examples, see [`usage-examples.md`](usage-examples.md).
|
||||
|
||||
For the pandas-ta indicator catalog used in research scripts, see [`pandas-ta-reference.md`](pandas-ta-reference.md).
|
||||
@@ -1,142 +1,164 @@
|
||||
# Indicator Development Guide
|
||||
# Custom Indicator Development
|
||||
|
||||
Custom indicators in Dexorder are Python functions that process OHLCV data and return signals or values.
|
||||
Custom indicators are Python scripts saved in the `indicator` category. They compute values from OHLCV data and are plotted live on the TradingView chart alongside built-in indicators.
|
||||
|
||||
## Indicator Structure
|
||||
See [`../pandas-ta-reference.md`](../pandas-ta-reference.md) for the full catalog of built-in indicators available via `pandas_ta`.
|
||||
|
||||
---
|
||||
|
||||
## Function Signature
|
||||
|
||||
A custom indicator must define a **top-level function** whose name is the lowercase, snake_case form of the `name` passed to `PythonWrite`. For example, `name="VW RSI"` → function `def vw_rsi(...)`.
|
||||
|
||||
The function receives the OHLCV columns listed in `input_series` as positional arguments and must return either:
|
||||
- A `pd.Series` (single-output indicator), or
|
||||
- A `pd.DataFrame` with column names matching `output_columns` in the metadata (multi-output)
|
||||
|
||||
```python
|
||||
def my_indicator(df, **params):
|
||||
"""
|
||||
Calculate custom indicator
|
||||
import pandas as pd
|
||||
import pandas_ta as ta
|
||||
|
||||
Args:
|
||||
df: DataFrame with columns [open, high, low, close, volume]
|
||||
**params: Indicator parameters
|
||||
|
||||
Returns:
|
||||
Series or DataFrame with indicator values
|
||||
"""
|
||||
# Implementation
|
||||
return result
|
||||
# Single-output: volume-weighted RSI
|
||||
def vw_rsi(close: pd.Series, volume: pd.Series, length: int = 14) -> pd.Series:
|
||||
rsi = ta.rsi(close, length=length)
|
||||
vol_weight = volume / volume.rolling(length).mean()
|
||||
return (rsi * vol_weight).rolling(3).mean()
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Simple Moving Average
|
||||
```python
|
||||
def sma(df, period=20):
|
||||
return df['close'].rolling(window=period).mean()
|
||||
```
|
||||
|
||||
### Exponential Moving Average
|
||||
```python
|
||||
def ema(df, period=20):
|
||||
return df['close'].ewm(span=period, adjust=False).mean()
|
||||
```
|
||||
|
||||
### RSI (Relative Strength Index)
|
||||
```python
|
||||
def rsi(df, period=14):
|
||||
delta = df['close'].diff()
|
||||
gain = delta.where(delta > 0, 0).rolling(window=period).mean()
|
||||
loss = -delta.where(delta < 0, 0).rolling(window=period).mean()
|
||||
rs = gain / loss
|
||||
return 100 - (100 / (1 + rs))
|
||||
```
|
||||
|
||||
### MACD
|
||||
```python
|
||||
def macd(df, fast=12, slow=26, signal=9):
|
||||
ema_fast = df['close'].ewm(span=fast).mean()
|
||||
ema_slow = df['close'].ewm(span=slow).mean()
|
||||
macd_line = ema_fast - ema_slow
|
||||
signal_line = macd_line.ewm(span=signal).mean()
|
||||
histogram = macd_line - signal_line
|
||||
import pandas as pd
|
||||
import pandas_ta as ta
|
||||
|
||||
# Multi-output: custom Bollinger Bands
|
||||
def vol_bands(close: pd.Series, length: int = 20, std: float = 2.0) -> pd.DataFrame:
|
||||
bb = ta.bbands(close, length=length, std=std)
|
||||
return pd.DataFrame({
|
||||
'macd': macd_line,
|
||||
'signal': signal_line,
|
||||
'histogram': histogram
|
||||
"upper": bb.iloc[:, 2],
|
||||
"mid": bb.iloc[:, 1],
|
||||
"lower": bb.iloc[:, 0],
|
||||
})
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
**Always use `pandas_ta` for standard indicator calculations.** Never write manual `rolling().mean()` or `ewm()` implementations — use `ta.sma()`, `ta.ema()`, `ta.rsi()`, etc.
|
||||
|
||||
### Data Handling
|
||||
- Always validate input DataFrame has required columns
|
||||
- Handle NaN values appropriately
|
||||
- Use `.copy()` to avoid modifying original data
|
||||
- Consider edge cases (not enough data, etc.)
|
||||
---
|
||||
|
||||
### Performance
|
||||
- Vectorize operations when possible (avoid loops)
|
||||
- Use pandas/numpy built-in functions
|
||||
- Cache expensive calculations
|
||||
- Test on large datasets
|
||||
## Required Metadata
|
||||
|
||||
### Parameters
|
||||
- Provide sensible defaults
|
||||
- Document parameter ranges
|
||||
- Validate parameter values
|
||||
- Consider optimization bounds
|
||||
When writing a custom indicator with `PythonWrite`, supply complete metadata so the web client can build the TradingView plotter automatically:
|
||||
|
||||
### Testing
|
||||
```python
|
||||
def test_indicator():
|
||||
# Create sample data
|
||||
df = pd.DataFrame({
|
||||
'close': [100, 102, 101, 103, 105]
|
||||
})
|
||||
PythonWrite(
|
||||
category="indicator",
|
||||
name="VW RSI",
|
||||
description="RSI weighted by relative volume.",
|
||||
details="""## Volume-Weighted RSI
|
||||
|
||||
# Test calculation
|
||||
result = my_indicator(df, param=10)
|
||||
Computes RSI on close prices, scales by relative volume, applies 3-bar smoothing.
|
||||
|
||||
# Validate output
|
||||
assert not result.isna().all()
|
||||
assert len(result) == len(df)
|
||||
**Formula:** (rsi * (volume / volume.rolling(length).mean())).rolling(3).mean()
|
||||
**Inputs:** close, volume
|
||||
**Output:** single Series — smoothed volume-weighted RSI (separate pane)
|
||||
**Parameters:** length (int, default 14)""",
|
||||
code="""...""",
|
||||
metadata={
|
||||
"parameters": {
|
||||
"length": {"type": "int", "default": 14, "min": 2, "max": 200, "description": "RSI period"}
|
||||
},
|
||||
"input_series": ["close", "volume"],
|
||||
"output_columns": [
|
||||
{"name": "value", "display_name": "VW-RSI", "plot": {"style": 0}}
|
||||
],
|
||||
"pane": "separate" # "price" = overlay on candles; "separate" = sub-pane
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Plot styles
|
||||
|
||||
| Value | Renders as |
|
||||
|---|---|
|
||||
| `0` | Line (default) |
|
||||
| `1` | Histogram bars |
|
||||
| `4` | Area (filled under line) |
|
||||
| `5` | Columns (vertical bars) |
|
||||
| `9` | Step line |
|
||||
|
||||
### Filled areas (shaded bands)
|
||||
|
||||
To shade between two output series (e.g. upper/lower bands), add a `filled_areas` list. The two bounding series must appear at consecutive even/odd positions in `output_columns`:
|
||||
|
||||
```python
|
||||
"filled_areas": [
|
||||
{"id": "fill", "type": "plot_plot", "series1": "upper", "series2": "lower",
|
||||
"color": "#2196F3", "opacity": 0.08}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Check for existing indicators** before writing: `PythonList(category="indicator")`. If one already exists with the same sanitized name, update it with `PythonEdit` rather than creating a duplicate.
|
||||
|
||||
2. **Write** with `PythonWrite(category="indicator", ...)`. The system automatically runs the script against synthetic test data to catch compile/runtime errors — no separate validation call needed.
|
||||
|
||||
3. **Add to workspace** with `WorkspacePatch("indicators", ...)` using `pandas_ta_name: "custom_<sanitized_name>"`. Include `custom_metadata` in the patch value so the web client can render it.
|
||||
|
||||
4. **Use in strategies** via `ta.custom_<sanitized_name>(...)`. See [`../strategies/strategy-development.md`](strategy-development.md) for details.
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
The workspace `pandas_ta_name` is `"custom_"` + the sanitized indicator name. Sanitization: lowercase + spaces/hyphens → underscores. For example:
|
||||
|
||||
| `name` | function name | `pandas_ta_name` |
|
||||
|---|---|---|
|
||||
| `"VW RSI"` | `vw_rsi` | `custom_vw_rsi` |
|
||||
| `"TrendFlex"` | `trendflex` | `custom_trendflex` |
|
||||
| `"Vol-Bands"` | `vol_bands` | `custom_vol_bands` |
|
||||
|
||||
Two names that sanitize to the same value will conflict — check with `PythonList` first.
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Look-Ahead Bias
|
||||
Never use future data:
|
||||
```python
|
||||
# WRONG - uses future data
|
||||
df['signal'] = df['close'].shift(-1) > df['close']
|
||||
### Look-ahead bias
|
||||
|
||||
# CORRECT - only past data
|
||||
df['signal'] = df['close'] > df['close'].shift(1)
|
||||
Never use future data in the computation. Indicator values for bar N may only depend on data available at bar N or earlier.
|
||||
|
||||
```python
|
||||
# WRONG — uses future price
|
||||
signal = close.shift(-1) > close
|
||||
|
||||
# CORRECT — only past data
|
||||
signal = close > close.shift(1)
|
||||
```
|
||||
|
||||
### Repainting
|
||||
Indicator values should not change for closed bars:
|
||||
|
||||
Indicator values for already-closed bars should not change as new bars arrive. Avoid calculations that recalculate over a sliding window that can retrospectively alter past values in non-obvious ways.
|
||||
|
||||
### NaN handling
|
||||
|
||||
Indicators need a warm-up period. The first `length - 1` values will be `NaN`. Strategies that consume custom indicators should guard with:
|
||||
```python
|
||||
# Ensure calculations are based on closed candles
|
||||
# Avoid using unstable data sources
|
||||
if vw_rsi.isna().all() or len(df) < min_required:
|
||||
return
|
||||
```
|
||||
|
||||
### Overfitting
|
||||
- Don't optimize on same data you test on
|
||||
- Use separate train/validation/test sets
|
||||
- Walk-forward analysis for robustness
|
||||
- Simple is often better than complex
|
||||
|
||||
## Integration with Strategies
|
||||
- Keep indicator logic simple and parameter-lean
|
||||
- Validate on out-of-sample data, not the same window used to tune parameters
|
||||
- Prefer indicators with a clear mechanical rationale over curve-fit formulas
|
||||
|
||||
Indicators are used in strategy signals:
|
||||
```python
|
||||
def my_strategy(df):
|
||||
# Calculate indicators
|
||||
df['rsi'] = rsi(df, period=14)
|
||||
df['sma_fast'] = sma(df, period=20)
|
||||
df['sma_slow'] = sma(df, period=50)
|
||||
---
|
||||
|
||||
# Generate signals
|
||||
df['signal'] = 0
|
||||
df.loc[(df['rsi'] < 30) & (df['sma_fast'] > df['sma_slow']), 'signal'] = 1
|
||||
df.loc[(df['rsi'] > 70) & (df['sma_fast'] < df['sma_slow']), 'signal'] = -1
|
||||
## See Also
|
||||
|
||||
return df
|
||||
```
|
||||
|
||||
Store indicators in your git repository under `indicators/` directory.
|
||||
- [`../pandas-ta-reference.md`](../pandas-ta-reference.md) — Full catalog of built-in indicators and calling conventions
|
||||
- [`../api-reference.md`](../api-reference.md) — DataAPI and ChartingAPI for research scripts
|
||||
- [`../strategies/strategy-development.md`](strategy-development.md) — Using custom indicators in strategies via `ta.custom_*`
|
||||
|
||||
229
gateway/knowledge/pandas-ta-reference.md
Normal file
229
gateway/knowledge/pandas-ta-reference.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# pandas-ta Reference for Research Scripts
|
||||
|
||||
This catalog applies to both research scripts and custom indicators. For usage in research scripts see [`usage-examples.md`](usage-examples.md). For writing custom indicator scripts (with metadata for the TradingView plotter) see [`indicators/indicator-development.md`](indicators/indicator-development.md).
|
||||
|
||||
The sandbox environment uses **pandas-ta** as the standard indicator library. Always use it for technical indicator calculations; do not write manual rolling/ewm implementations.
|
||||
|
||||
```python
|
||||
import pandas_ta as ta
|
||||
```
|
||||
|
||||
## Calling Convention
|
||||
|
||||
pandas-ta functions accept a Series (or OHLCV columns) plus keyword parameters that match pandas-ta's documented argument names:
|
||||
|
||||
```python
|
||||
# Single-series indicator
|
||||
rsi = ta.rsi(df['close'], length=14) # returns Series
|
||||
|
||||
# OHLCV indicator
|
||||
atr = ta.atr(df['high'], df['low'], df['close'], length=14)
|
||||
|
||||
# Multi-output indicator (returns DataFrame)
|
||||
macd_df = ta.macd(df['close'], fast=12, slow=26, signal=9)
|
||||
# columns: MACD_12_26_9, MACDh_12_26_9, MACDs_12_26_9
|
||||
|
||||
bbands_df = ta.bbands(df['close'], length=20, std=2.0)
|
||||
# columns: BBL_20_2.0, BBM_20_2.0, BBU_20_2.0, BBB_20_2.0, BBP_20_2.0
|
||||
```
|
||||
|
||||
## Default Parameters
|
||||
|
||||
Key defaults to keep in mind:
|
||||
- Most period/length indicators: `length=14` (use `length=` not `timeperiod=`)
|
||||
- `bbands`: `length=20, std=2.0` (note: single `std`, not separate upper/lower)
|
||||
- `macd`: `fast=12, slow=26, signal=9`
|
||||
- `stoch`: `k=14, d=3, smooth_k=3`
|
||||
- `psar`: `af0=0.02, af=0.02, max_af=0.2`
|
||||
- `vwap`: `anchor='D'` (requires DatetimeIndex)
|
||||
- `ichimoku`: `tenkan=9, kijun=26, senkou=52`
|
||||
|
||||
## Available Indicators
|
||||
|
||||
These match the indicators supported by the TradingView web client. Use the pandas-ta function name shown here (lowercase):
|
||||
|
||||
### Overlap / Moving Averages — plotted on the price pane
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `sma` | Simple Moving Average — plain arithmetic mean over `length` periods |
|
||||
| `ema` | Exponential Moving Average — more weight on recent prices |
|
||||
| `wma` | Weighted Moving Average — linearly increasing weights |
|
||||
| `dema` | Double EMA — two layers of EMA to reduce lag |
|
||||
| `tema` | Triple EMA — three layers of EMA, even less lag than DEMA |
|
||||
| `trima` | Triangular MA — double-smoothed SMA, very smooth |
|
||||
| `kama` | Kaufman Adaptive MA — adapts speed to market noise/trending conditions |
|
||||
| `t3` | T3 Moving Average — Tillson's smooth, low-lag MA using six EMAs |
|
||||
| `hma` | Hull MA — very low-lag MA using WMAs |
|
||||
| `alma` | Arnaud Legoux MA — Gaussian-weighted MA with reduced lag and noise |
|
||||
| `midpoint` | Midpoint of close over `length` periods: (highest + lowest) / 2 |
|
||||
| `midprice` | Midpoint of high/low over `length` periods |
|
||||
| `supertrend` | Trend-following band (ATR-based) that flips above/below price |
|
||||
| `ichimoku` | Ichimoku Cloud — multi-line Japanese trend/support/resistance system |
|
||||
| `vwap` | Volume-Weighted Average Price — average price weighted by volume, resets on `anchor` |
|
||||
| `vwma` | Volume-Weighted MA — like SMA but candles weighted by volume |
|
||||
| `bbands` | Bollinger Bands — SMA ± N standard deviations; returns upper, mid, lower bands |
|
||||
|
||||
### Momentum — typically plotted in a separate pane
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `rsi` | Relative Strength Index — 0–100 oscillator measuring speed of price changes |
|
||||
| `macd` | MACD — difference of two EMAs plus signal line and histogram |
|
||||
| `stoch` | Stochastic Oscillator — %K/%D, measures close vs recent high/low range |
|
||||
| `stochrsi` | Stochastic RSI — applies stochastic formula to RSI values |
|
||||
| `cci` | Commodity Channel Index — deviation of price from its statistical mean |
|
||||
| `willr` | Williams %R — inverse stochastic, −100 to 0 oscillator |
|
||||
| `mom` | Momentum — raw price change over `length` periods |
|
||||
| `roc` | Rate of Change — percentage price change over `length` periods |
|
||||
| `trix` | TRIX — 1-period % change of a triple-smoothed EMA |
|
||||
| `cmo` | Chande Momentum Oscillator — ratio of up/down momentum, −100 to 100 |
|
||||
| `adx` | Average Directional Index — strength of trend (0–100, direction-agnostic) |
|
||||
| `aroon` | Aroon — measures how recently the highest/lowest price occurred; returns Up, Down, Oscillator |
|
||||
| `ao` | Awesome Oscillator — difference of 5- and 34-period simple MAs of midprice |
|
||||
| `bop` | Balance of Power — measures buying vs selling pressure: (close−open)/(high−low) |
|
||||
| `uo` | Ultimate Oscillator — weighted combo of three period (fast/medium/slow) buying pressure ratios |
|
||||
| `apo` | Absolute Price Oscillator — difference between two EMAs (like MACD without signal line) |
|
||||
| `mfi` | Money Flow Index — RSI-like oscillator using price × volume |
|
||||
| `coppock` | Coppock Curve — long-term momentum oscillator based on rate-of-change |
|
||||
| `dpo` | Detrended Price Oscillator — removes trend to show cycle oscillations |
|
||||
| `fisher` | Fisher Transform — converts price into a Gaussian normal distribution |
|
||||
| `rvgi` | Relative Vigor Index — compares close−open to high−low to measure trend vigor |
|
||||
| `kst` | Know Sure Thing — momentum oscillator from four ROC periods, smoothed |
|
||||
|
||||
### Volatility — plotted on price pane or separate
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `atr` | Average True Range — average of true range (greatest of H−L, H−prevC, L−prevC) |
|
||||
| `kc` | Keltner Channels — EMA ± N × ATR bands around price |
|
||||
| `donchian` | Donchian Channels — highest high / lowest low over `length` periods |
|
||||
|
||||
### Volume — plotted in separate pane
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `obv` | On Balance Volume — cumulative volume, added on up days, subtracted on down days |
|
||||
| `ad` | Accumulation/Distribution — running total of the money flow multiplier × volume |
|
||||
| `adosc` | Chaikin Oscillator — EMA difference of the A/D line |
|
||||
| `cmf` | Chaikin Money Flow — sum of (money flow volume) / sum of volume over `length` |
|
||||
| `eom` | Ease of Movement — relates price change to volume; high = price moves easily |
|
||||
| `efi` | Elder's Force Index — combines price change direction with volume magnitude |
|
||||
| `kvo` | Klinger Volume Oscillator — EMA difference of volume force |
|
||||
| `pvt` | Price Volume Trend — cumulative: volume × percentage price change |
|
||||
|
||||
### Statistics / Price Transforms
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `stdev` | Standard Deviation of close over `length` periods |
|
||||
| `linreg` | Linear Regression Curve — least-squares line endpoint value over `length` periods |
|
||||
| `slope` | Linear Regression Slope — gradient of the regression line |
|
||||
| `hl2` | Median Price — (high + low) / 2 |
|
||||
| `hlc3` | Typical Price — (high + low + close) / 3 |
|
||||
| `ohlc4` | Average Price — (open + high + low + close) / 4 |
|
||||
|
||||
### Trend
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `psar` | Parabolic SAR — trailing stop-and-reverse dots that follow price |
|
||||
| `vortex` | Vortex Indicator — VI+ / VI− lines measuring upward vs downward trend movement |
|
||||
| `chop` | Choppiness Index — 0–100, high = choppy/sideways, low = strong trend |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Single-output indicators
|
||||
|
||||
```python
|
||||
import pandas_ta as ta
|
||||
|
||||
df['rsi'] = ta.rsi(df['close'], length=14)
|
||||
df['ema_20'] = ta.ema(df['close'], length=20)
|
||||
df['sma_50'] = ta.sma(df['close'], length=50)
|
||||
df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=14)
|
||||
df['obv'] = ta.obv(df['close'], df['volume'])
|
||||
df['adx'] = ta.adx(df['high'], df['low'], df['close'], length=14)['ADX_14']
|
||||
```
|
||||
|
||||
### Multi-output indicators — extract columns by position
|
||||
|
||||
```python
|
||||
# MACD → MACD_12_26_9, MACDh_12_26_9, MACDs_12_26_9
|
||||
macd_df = ta.macd(df['close'], fast=12, slow=26, signal=9)
|
||||
df['macd'] = macd_df.iloc[:, 0] # MACD line
|
||||
df['macd_hist'] = macd_df.iloc[:, 1] # Histogram
|
||||
df['macd_signal'] = macd_df.iloc[:, 2] # Signal line
|
||||
|
||||
# Bollinger Bands → BBL, BBM, BBU, BBB, BBP
|
||||
bb_df = ta.bbands(df['close'], length=20, std=2.0)
|
||||
df['bb_lower'] = bb_df.iloc[:, 0] # BBL
|
||||
df['bb_mid'] = bb_df.iloc[:, 1] # BBM
|
||||
df['bb_upper'] = bb_df.iloc[:, 2] # BBU
|
||||
|
||||
# Stochastic → STOCHk, STOCHd
|
||||
stoch_df = ta.stoch(df['high'], df['low'], df['close'], k=14, d=3, smooth_k=3)
|
||||
df['stoch_k'] = stoch_df.iloc[:, 0]
|
||||
df['stoch_d'] = stoch_df.iloc[:, 1]
|
||||
|
||||
# Keltner Channels → KCLe, KCBe, KCUe
|
||||
kc_df = ta.kc(df['high'], df['low'], df['close'], length=20)
|
||||
df['kc_lower'] = kc_df.iloc[:, 0]
|
||||
df['kc_mid'] = kc_df.iloc[:, 1]
|
||||
df['kc_upper'] = kc_df.iloc[:, 2]
|
||||
|
||||
# ADX → ADX_14, DMP_14, DMN_14
|
||||
adx_df = ta.adx(df['high'], df['low'], df['close'], length=14)
|
||||
df['adx'] = adx_df.iloc[:, 0] # ADX strength
|
||||
df['dmp'] = adx_df.iloc[:, 1] # +DI
|
||||
df['dmn'] = adx_df.iloc[:, 2] # -DI
|
||||
|
||||
# Aroon → AROOND_14, AROONU_14, AROONOSC_14
|
||||
aroon_df = ta.aroon(df['high'], df['low'], length=14)
|
||||
df['aroon_down'] = aroon_df.iloc[:, 0]
|
||||
df['aroon_up'] = aroon_df.iloc[:, 1]
|
||||
|
||||
# Donchian Channels → DCL, DCM, DCU
|
||||
dc_df = ta.donchian(df['high'], df['low'], lower_length=20, upper_length=20)
|
||||
df['dc_lower'] = dc_df.iloc[:, 0]
|
||||
df['dc_mid'] = dc_df.iloc[:, 1]
|
||||
df['dc_upper'] = dc_df.iloc[:, 2]
|
||||
```
|
||||
|
||||
### Charting with indicators
|
||||
|
||||
```python
|
||||
import pandas_ta as ta
|
||||
from dexorder.api import get_api
|
||||
import asyncio
|
||||
|
||||
api = get_api()
|
||||
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time="2024-01-01",
|
||||
end_time="2024-01-08",
|
||||
extra_columns=["volume"]
|
||||
))
|
||||
|
||||
# Compute indicators
|
||||
df['ema_20'] = ta.ema(df['close'], length=20)
|
||||
df['rsi'] = ta.rsi(df['close'], length=14)
|
||||
macd_df = ta.macd(df['close'])
|
||||
df['macd'] = macd_df.iloc[:, 0]
|
||||
df['macd_signal'] = macd_df.iloc[:, 2]
|
||||
|
||||
# Main price chart with EMA overlay
|
||||
fig, ax = api.charting.plot_ohlc(df, title="BTC/USDT 1H", volume=True)
|
||||
ax.plot(range(len(df)), df['ema_20'], label="EMA 20", color="orange", linewidth=1.5) # range(len(df)), not df.index
|
||||
ax.legend()
|
||||
|
||||
# RSI panel
|
||||
rsi_ax = api.charting.add_indicator_panel(fig, df, columns=["rsi"], ylabel="RSI", ylim=(0, 100))
|
||||
rsi_ax.axhline(70, color='red', linestyle='--', alpha=0.5)
|
||||
rsi_ax.axhline(30, color='green', linestyle='--', alpha=0.5)
|
||||
|
||||
# MACD panel
|
||||
api.charting.add_indicator_panel(fig, df, columns=["macd", "macd_signal"], ylabel="MACD")
|
||||
```
|
||||
@@ -1,71 +0,0 @@
|
||||
# Agent System Architecture
|
||||
|
||||
The Dexorder AI platform uses a sophisticated agent harness that orchestrates between user interactions, LLM models, and user-specific tools.
|
||||
|
||||
## Core Components
|
||||
|
||||
### Gateway
|
||||
Multi-channel gateway supporting:
|
||||
- WebSocket connections for web/mobile
|
||||
- Telegram integration
|
||||
- Real-time event streaming
|
||||
|
||||
### Agent Harness
|
||||
Stateless orchestrator that:
|
||||
1. Fetches context from user's MCP server
|
||||
2. Routes to appropriate LLM model based on license
|
||||
3. Calls LLM with embedded context
|
||||
4. Routes tool calls to user's MCP or platform tools
|
||||
5. Saves conversation history back to MCP
|
||||
|
||||
### Memory Architecture
|
||||
Three-tier storage system:
|
||||
- **Redis**: Hot state for active sessions and checkpoints
|
||||
- **Qdrant**: Vector search for RAG and semantic memory
|
||||
- **Iceberg**: Cold storage for durable conversations and analytics
|
||||
|
||||
### User Context
|
||||
Every interaction includes:
|
||||
- User ID and license information
|
||||
- Active channel (websocket, telegram, etc.)
|
||||
- Channel capabilities (markdown, images, buttons)
|
||||
- Conversation history
|
||||
- Relevant memories from RAG
|
||||
- Workspace state
|
||||
|
||||
## Skills vs Subagents
|
||||
|
||||
### Skills
|
||||
Self-contained capabilities for specific tasks:
|
||||
- Market analysis
|
||||
- Strategy validation
|
||||
- Indicator development
|
||||
- Defined in markdown + TypeScript
|
||||
- Use when task is well-defined and scoped
|
||||
|
||||
### Subagents
|
||||
Specialized agents with dedicated memory:
|
||||
- Code reviewer with review guidelines
|
||||
- Risk analyzer with risk models
|
||||
- Multi-file knowledge base
|
||||
- Custom system prompts
|
||||
- Use when domain expertise is needed
|
||||
|
||||
## Global vs User Memory
|
||||
|
||||
### Global Memory (user_id="0")
|
||||
Platform-wide knowledge available to all users:
|
||||
- Trading concepts and terminology
|
||||
- Platform capabilities
|
||||
- Indicator documentation
|
||||
- Strategy patterns
|
||||
- Best practices
|
||||
|
||||
### User Memory
|
||||
Personal context specific to each user:
|
||||
- Conversation history
|
||||
- Preferences and trading style
|
||||
- Custom indicators and strategies
|
||||
- Workspace state
|
||||
|
||||
All RAG queries automatically search both global and user-specific memories.
|
||||
@@ -1,88 +1,17 @@
|
||||
# Model Context Protocol (MCP) Integration
|
||||
# User Sandbox
|
||||
|
||||
Dexorder uses the Model Context Protocol for user-specific tool execution and state management.
|
||||
Each user has a dedicated sandbox environment that persists their data across sessions.
|
||||
|
||||
## Container Architecture
|
||||
## Persistent Storage
|
||||
|
||||
Each user has a dedicated Kubernetes pod running:
|
||||
- **Agent Container**: Python environment with conda packages
|
||||
- **Lifecycle Sidecar**: Manages container lifecycle and communication
|
||||
- **Persistent Storage**: User's git repository with indicators/strategies
|
||||
User scripts (indicators, strategies, research) are stored in a git repository inside the user's sandbox. They survive session disconnects and reconnections.
|
||||
|
||||
## Authentication Modes
|
||||
- Indicators are in the `indicator` category and can be listed with `PythonList(category="indicator")`
|
||||
- Strategies are in the `strategy` category and can be listed with `PythonList(category="strategy")`
|
||||
- Research scripts are in the `research` category and can be listed with `PythonList(category="research")`
|
||||
|
||||
Three MCP authentication modes:
|
||||
## Session Lifecycle
|
||||
|
||||
### 1. Public Mode (Free Tier)
|
||||
- No authentication required
|
||||
- Container creates anonymous session
|
||||
- Limited to read-only resources
|
||||
- Session expires after timeout
|
||||
|
||||
### 2. Gateway Auth Mode (Standard)
|
||||
- Gateway authenticates user
|
||||
- Passes verified user ID to container
|
||||
- Container trusts gateway's authentication
|
||||
- Full access to user's tools and data
|
||||
|
||||
### 3. Direct Auth Mode (Enterprise)
|
||||
- User authenticates directly with container
|
||||
- Gateway forwards encrypted credentials
|
||||
- Container validates credentials independently
|
||||
- Highest security for sensitive operations
|
||||
|
||||
## MCP Resources
|
||||
|
||||
The container exposes standard resources:
|
||||
|
||||
### context://user-profile
|
||||
User preferences and trading style
|
||||
|
||||
### context://conversation-summary
|
||||
Recent conversation context and history
|
||||
|
||||
### context://workspace-state
|
||||
Current chart, indicators, and analysis state
|
||||
|
||||
### context://system-prompt
|
||||
User's custom agent instructions
|
||||
|
||||
### indicators://list
|
||||
Available indicators with signatures
|
||||
|
||||
### strategies://list
|
||||
User's trading strategies
|
||||
|
||||
## Tool Execution Flow
|
||||
|
||||
1. User sends message to gateway
|
||||
2. Gateway queries user's MCP resources for context
|
||||
3. LLM generates response with tool calls
|
||||
4. Gateway routes tool calls:
|
||||
- Platform tools → handled by gateway
|
||||
- User tools → proxied to MCP container
|
||||
5. Tool results returned to LLM
|
||||
6. Final response sent to user
|
||||
7. Conversation saved to MCP container
|
||||
|
||||
## Container Lifecycle
|
||||
|
||||
### Startup
|
||||
1. Gateway receives user connection
|
||||
2. Checks if container exists
|
||||
3. Creates pod if needed (cold start ~5-10s)
|
||||
4. Waits for container ready
|
||||
5. Establishes MCP connection
|
||||
|
||||
### Active
|
||||
- Container stays alive during active session
|
||||
- Receives tool calls via MCP
|
||||
- Maintains workspace state
|
||||
- Saves files to persistent storage
|
||||
|
||||
### Shutdown
|
||||
- Free users: timeout after 15 minutes idle
|
||||
- Paid users: longer timeout based on license
|
||||
- Graceful shutdown saves state
|
||||
- Persistent storage retained
|
||||
- Fast restart on next connection
|
||||
- Sandbox starts automatically when the user connects
|
||||
- Cold start takes a few seconds if the sandbox was idle
|
||||
- All workspace state and scripts are preserved across reconnects
|
||||
|
||||
132
gateway/knowledge/platform/workspace.md
Normal file
132
gateway/knowledge/platform/workspace.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Workspace
|
||||
|
||||
The Workspace is the user's current UI context — what they are looking at, what is selected, and what persistent state belongs to their session. It is a collection of named **stores** that are kept in sync between the web client, the gateway, and the user's sandbox container.
|
||||
|
||||
Use `WorkspaceRead(store_name)` to read any store and `WorkspacePatch(store_name, patch)` to update it. Patches use JSON Patch (RFC 6902) format.
|
||||
|
||||
---
|
||||
|
||||
## Stores
|
||||
|
||||
### `chartState` — Current chart view (persistent)
|
||||
|
||||
Tracks what the user is currently looking at on the TradingView chart.
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `symbol` | string | Active trading pair in `SYMBOL.EXCHANGE` format (e.g. `BTC/USDT.BINANCE`) |
|
||||
| `period` | number | OHLC bar period in seconds (e.g. `900` = 15 min, `3600` = 1 h) |
|
||||
| `start_time` | number \| null | Unix timestamp of left edge of visible range, or null for auto |
|
||||
| `end_time` | number \| null | Unix timestamp of right edge of visible range, or null for auto |
|
||||
| `selected_shapes` | string[] | IDs of currently selected drawing/annotation shapes |
|
||||
|
||||
When the user says "the current chart" or "what's selected", read `chartState` first.
|
||||
|
||||
---
|
||||
|
||||
### `indicators` — Active indicators on the chart (persistent)
|
||||
|
||||
A flat map of `indicator_id → IndicatorInstance`. Each entry represents one study currently plotted on the TradingView chart.
|
||||
|
||||
**`IndicatorInstance` fields:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `id` | string | Unique ID for this instance |
|
||||
| `pandas_ta_name` | string | Internal name used in strategy/indicator scripts (e.g. `RSI_14`, `custom_MyIndicator`) |
|
||||
| `instance_name` | string | Human-readable label shown on chart |
|
||||
| `parameters` | object | Key/value parameter map (e.g. `{ length: 14 }`) |
|
||||
| `tv_study_id` | string? | TradingView study ID (assigned by TV after the study is added) |
|
||||
| `tv_indicator_name` | string? | TradingView indicator name for built-in studies |
|
||||
| `tv_inputs` | object? | TradingView input overrides keyed by TV input name |
|
||||
| `visible` | boolean | Whether the study is visible on the chart |
|
||||
| `pane` | string | `"price"` to overlay on price pane, `"separate"` for its own panel |
|
||||
| `symbol` | string? | Override symbol if different from `chartState.symbol` |
|
||||
| `created_at` | number? | Unix timestamp when added |
|
||||
| `modified_at` | number? | Unix timestamp when last changed |
|
||||
| `custom_metadata` | object? | Present only for `custom_*` indicators; drives TradingView custom study construction (see below) |
|
||||
|
||||
**`custom_metadata` sub-fields** (for custom indicators only):
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `display_name` | string | Human-readable indicator title shown in TV |
|
||||
| `parameters` | object | Parameter schema: `{ name: { type, default, description, min?, max? } }` |
|
||||
| `input_series` | string[] | Input price series required (e.g. `["close"]`) |
|
||||
| `output_columns` | array | Each entry: `{ name, display_name?, description?, plot? }` where `plot` has `{ style, color?, linewidth?, visible? }` |
|
||||
| `pane` | `"price"` \| `"separate"` | Default pane placement |
|
||||
| `filled_areas` | array? | Shaded regions between two plots or hlines |
|
||||
| `bands` | array? | Horizontal reference lines (e.g. RSI 70/30) |
|
||||
|
||||
---
|
||||
|
||||
### `shapes` — Chart drawings and annotations (persistent)
|
||||
|
||||
```json
|
||||
{ "shapes": { "<shape_id>": Shape } }
|
||||
```
|
||||
|
||||
Each `Shape` has:
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `id` | string | Unique shape ID |
|
||||
| `type` | string | Drawing type (e.g. `"trend_line"`, `"horizontal_line"`, `"rectangle"`) |
|
||||
| `points` | array | Control points: `{ time: unix_ts, price: number, channel?: string }` |
|
||||
| `color` | string? | Hex color |
|
||||
| `line_width` | number? | Line thickness |
|
||||
| `line_style` | string? | `"solid"`, `"dotted"`, `"dashed"` |
|
||||
| `properties` | object? | Additional type-specific properties |
|
||||
| `symbol` | string? | Symbol the shape belongs to |
|
||||
| `created_at` | number? | Unix timestamp |
|
||||
| `modified_at` | number? | Unix timestamp |
|
||||
|
||||
---
|
||||
|
||||
### `indicator_types` — Custom indicator registry (persistent)
|
||||
|
||||
```json
|
||||
{ "types": { "<script_name>": CustomIndicatorMetadata } }
|
||||
```
|
||||
|
||||
Maps custom indicator script names to their `CustomIndicatorMetadata` (same structure as `custom_metadata` above). Populated when a custom indicator is created or updated by the indicator agent. The web client uses this to register custom TradingView studies.
|
||||
|
||||
---
|
||||
|
||||
### `strategy_types` — Strategy registry (persistent)
|
||||
|
||||
```json
|
||||
{ "types": { "<script_name>": StrategyMetadata } }
|
||||
```
|
||||
|
||||
Maps strategy script names to their metadata. Used by the web client to know which strategies are available.
|
||||
|
||||
---
|
||||
|
||||
### `research_types` — Research script registry (persistent)
|
||||
|
||||
```json
|
||||
{ "types": { "<script_name>": ResearchMetadata } }
|
||||
```
|
||||
|
||||
Maps research script names to their metadata.
|
||||
|
||||
---
|
||||
|
||||
### `channelState` — Connected channels (transient, gateway-only)
|
||||
|
||||
Tracks which communication channels (WebSocket, Telegram, etc.) are connected to the current session. **Not synced to web clients.**
|
||||
|
||||
```json
|
||||
{ "connected": { "<channel_id>": { type, connectedAt, capabilities } } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sync Protocol
|
||||
|
||||
Stores are kept in sync using JSON Patch (RFC 6902) messages:
|
||||
- **snapshot** — full state dump sent on connect or after missed patches
|
||||
- **patch** — incremental change with a monotonic sequence number
|
||||
|
||||
Stores marked `persistent` are saved to the user's container at `/data/workspace/{store_name}.json` and survive session reconnects.
|
||||
@@ -1,188 +1,262 @@
|
||||
# Strategy Development Guide
|
||||
|
||||
Trading strategies in Dexorder define entry/exit rules and position management logic.
|
||||
Strategies on Dexorder are `PandasStrategy` subclasses that receive a live stream of OHLCV bars and call `self.buy()` / `self.sell()` / `self.flatten()` to place orders.
|
||||
|
||||
## Strategy Structure
|
||||
See [`../api-reference.md`](../api-reference.md) for the DataAPI and ChartingAPI used in research scripts. For indicator calculations, see [`../pandas-ta-reference.md`](../pandas-ta-reference.md).
|
||||
|
||||
---
|
||||
|
||||
## PandasStrategy API
|
||||
|
||||
```python
|
||||
class Strategy:
|
||||
def __init__(self, **params):
|
||||
"""Initialize strategy with parameters"""
|
||||
self.params = params
|
||||
from dexorder.nautilus.pandas_strategy import PandasStrategy, PandasStrategyConfig
|
||||
|
||||
def generate_signals(self, df):
|
||||
"""
|
||||
Generate trading signals
|
||||
class MyStrategy(PandasStrategy):
|
||||
|
||||
def evaluate(self, dfs: dict[str, pd.DataFrame]) -> None:
|
||||
"""Called after every new bar across all feeds.
|
||||
|
||||
Args:
|
||||
df: DataFrame with OHLCV + indicator columns
|
||||
|
||||
Returns:
|
||||
DataFrame with 'signal' column:
|
||||
1 = long entry
|
||||
-1 = short entry
|
||||
0 = no action
|
||||
dfs: dict mapping feed_key → pd.DataFrame
|
||||
Columns: timestamp (ns), open, high, low, close, volume,
|
||||
buy_vol, sell_vol, open_interest
|
||||
Rows accumulate over time — last row = latest bar.
|
||||
"""
|
||||
pass
|
||||
df = dfs.get("BTC/USDT.BINANCE:300")
|
||||
if df is None or len(df) < 20:
|
||||
return # not enough data yet
|
||||
|
||||
def calculate_position_size(self, capital, price, risk_pct):
|
||||
"""Calculate position size based on risk"""
|
||||
pass
|
||||
close = df["close"]
|
||||
# ... compute signals ...
|
||||
|
||||
def get_stop_loss(self, entry_price, direction):
|
||||
"""Calculate stop loss level"""
|
||||
pass
|
||||
|
||||
def get_take_profit(self, entry_price, direction):
|
||||
"""Calculate take profit level"""
|
||||
pass
|
||||
if buy_signal:
|
||||
self.buy(quantity=0.1)
|
||||
elif sell_signal:
|
||||
self.sell(quantity=0.1)
|
||||
```
|
||||
|
||||
## Example: Simple Moving Average Crossover
|
||||
### Feed key format
|
||||
|
||||
`"{SYMBOL.EXCHANGE}:{period_seconds}"` — e.g. `"BTC/USDT.BINANCE:900"` for 15-minute bars.
|
||||
|
||||
Access all feeds via `self.config.feed_keys` (tuple of strings).
|
||||
|
||||
### Order methods
|
||||
|
||||
```python
|
||||
class SMACrossoverStrategy:
|
||||
def __init__(self, fast_period=20, slow_period=50, risk_pct=0.02):
|
||||
self.fast_period = fast_period
|
||||
self.slow_period = slow_period
|
||||
self.risk_pct = risk_pct
|
||||
|
||||
def generate_signals(self, df):
|
||||
# Calculate moving averages
|
||||
df['sma_fast'] = df['close'].rolling(self.fast_period).mean()
|
||||
df['sma_slow'] = df['close'].rolling(self.slow_period).mean()
|
||||
|
||||
# Generate signals
|
||||
df['signal'] = 0
|
||||
|
||||
# Long when fast crosses above slow
|
||||
df.loc[
|
||||
(df['sma_fast'] > df['sma_slow']) &
|
||||
(df['sma_fast'].shift(1) <= df['sma_slow'].shift(1)),
|
||||
'signal'
|
||||
] = 1
|
||||
|
||||
# Short when fast crosses below slow
|
||||
df.loc[
|
||||
(df['sma_fast'] < df['sma_slow']) &
|
||||
(df['sma_fast'].shift(1) >= df['sma_slow'].shift(1)),
|
||||
'signal'
|
||||
] = -1
|
||||
|
||||
return df
|
||||
|
||||
def calculate_position_size(self, capital, price, atr):
|
||||
# Risk-based position sizing
|
||||
risk_amount = capital * self.risk_pct
|
||||
stop_distance = 2 * atr
|
||||
position_size = risk_amount / stop_distance
|
||||
return position_size
|
||||
|
||||
def get_stop_loss(self, entry_price, direction, atr):
|
||||
if direction == 1: # Long
|
||||
return entry_price - (2 * atr)
|
||||
else: # Short
|
||||
return entry_price + (2 * atr)
|
||||
|
||||
def get_take_profit(self, entry_price, direction, atr):
|
||||
if direction == 1: # Long
|
||||
return entry_price + (4 * atr) # 2:1 risk/reward
|
||||
else: # Short
|
||||
return entry_price - (4 * atr)
|
||||
self.buy(quantity: float, feed_key: str = None)
|
||||
self.sell(quantity: float, feed_key: str = None)
|
||||
self.flatten(feed_key: str = None) # close all open positions
|
||||
```
|
||||
|
||||
## Strategy Components
|
||||
If `feed_key` is omitted, the first feed in `feed_keys` is used. `quantity` is in base currency units (e.g. 0.1 BTC).
|
||||
|
||||
### Signal Generation
|
||||
Entry conditions based on:
|
||||
- Indicator crossovers
|
||||
- Price patterns
|
||||
- Volume confirmation
|
||||
- Multiple timeframe confluence
|
||||
### Available data
|
||||
|
||||
### Risk Management
|
||||
Essential elements:
|
||||
- **Position Sizing**: Based on account risk percentage
|
||||
- **Stop Losses**: ATR-based or support/resistance
|
||||
- **Take Profits**: Multiple targets or trailing stops
|
||||
- **Max Positions**: Limit concurrent trades
|
||||
Strategies may only use data in the `dfs` feeds: crypto OHLCV + buy/sell volume split + open interest. The following are **not available**:
|
||||
- TradFi data (equities, forex, bonds, options, macro indicators)
|
||||
- Alternative data (news, social sentiment, on-chain metrics, economic calendars)
|
||||
|
||||
### Filters
|
||||
Reduce false signals:
|
||||
- **Trend Filter**: Only trade with the trend
|
||||
- **Volatility Filter**: Avoid low volatility periods
|
||||
- **Time Filter**: Specific trading hours
|
||||
- **Volume Filter**: Minimum volume requirements
|
||||
---
|
||||
|
||||
### Exit Rules
|
||||
Multiple exit types:
|
||||
- **Stop Loss**: Protect capital
|
||||
- **Take Profit**: Lock in gains
|
||||
- **Trailing Stop**: Follow profitable moves
|
||||
- **Time Exit**: Close at end of period
|
||||
- **Signal Exit**: Opposite signal
|
||||
## Using pandas_ta
|
||||
|
||||
## Backtesting Considerations
|
||||
Use `import pandas_ta as ta` for all indicator calculations. Never write manual `rolling()` or `ewm()` implementations.
|
||||
|
||||
### Data Quality
|
||||
- Use clean, validated data
|
||||
- Handle missing data appropriately
|
||||
- Account for survivorship bias
|
||||
- Include realistic spreads and slippage
|
||||
```python
|
||||
import pandas_ta as ta
|
||||
|
||||
### Performance Metrics
|
||||
Track key metrics:
|
||||
- **Total Return**: Cumulative profit/loss
|
||||
- **Sharpe Ratio**: Risk-adjusted returns
|
||||
- **Max Drawdown**: Largest peak-to-trough decline
|
||||
- **Win Rate**: Percentage of profitable trades
|
||||
- **Profit Factor**: Gross profit / gross loss
|
||||
- **Expectancy**: Average $ per trade
|
||||
rsi = ta.rsi(df["close"], length=14)
|
||||
macd_df = ta.macd(df["close"], fast=12, slow=26, signal=9)
|
||||
hist = macd_df.iloc[:, 2] # histogram column
|
||||
|
||||
### Validation
|
||||
Prevent overfitting:
|
||||
- **Train/Test Split**: 70/30 or 60/40
|
||||
- **Walk-Forward**: Rolling windows
|
||||
- **Out-of-Sample**: Test on recent unseen data
|
||||
- **Monte Carlo**: Randomize trade order
|
||||
- **Paper Trading**: Live validation
|
||||
ema = ta.ema(df["close"], length=20)
|
||||
atr = ta.atr(df["high"], df["low"], df["close"], length=14)
|
||||
```
|
||||
|
||||
## Common Strategy Types
|
||||
See [`../pandas-ta-reference.md`](../pandas-ta-reference.md) for the full indicator catalog and multi-output column extraction patterns.
|
||||
|
||||
### Trend Following
|
||||
Follow sustained price movements:
|
||||
- Moving average crossovers
|
||||
- Breakout strategies
|
||||
- Trend channels
|
||||
- Works best in trending markets
|
||||
---
|
||||
|
||||
### Mean Reversion
|
||||
Profit from price returning to average:
|
||||
- Bollinger Band reversals
|
||||
- RSI extremes
|
||||
- Statistical arbitrage
|
||||
- Works best in ranging markets
|
||||
## Using Custom Indicators
|
||||
|
||||
### Momentum
|
||||
Trade in direction of strong moves:
|
||||
- Relative strength
|
||||
- Price acceleration
|
||||
- Volume surges
|
||||
- Breakout confirmation
|
||||
Prefer referencing a custom indicator that already exists in the `indicator` category rather than duplicating the logic inline. Custom indicators appear on the user's chart, making the signal transparent.
|
||||
|
||||
### Arbitrage
|
||||
Exploit price discrepancies:
|
||||
- Cross-exchange spreads
|
||||
- Funding rate arbitrage
|
||||
- Statistical pairs trading
|
||||
- Requires low latency
|
||||
```python
|
||||
import pandas_ta as ta
|
||||
|
||||
## Integration with Platform
|
||||
def evaluate(self, dfs):
|
||||
df = dfs.get("BTC/USDT.BINANCE:3600")
|
||||
if df is None or len(df) < 20:
|
||||
return
|
||||
|
||||
Store strategies in your git repository under `strategies/` directory.
|
||||
vw_rsi = ta.custom_vw_rsi(df["close"], df["volume"], length=14)
|
||||
if vw_rsi is None or vw_rsi.isna().all():
|
||||
return
|
||||
|
||||
Test using the backtesting tools provided by the platform.
|
||||
if vw_rsi.iloc[-1] < 30:
|
||||
self.buy(0.01)
|
||||
elif vw_rsi.iloc[-1] > 70:
|
||||
self.sell(0.01)
|
||||
```
|
||||
|
||||
Deploy live strategies through the execution engine with proper risk controls.
|
||||
Custom indicator names follow the pattern `ta.custom_{sanitized_name}`. See [`../indicators/indicator-development.md`](../indicators/indicator-development.md) for naming rules and how to create custom indicators.
|
||||
|
||||
Monitor performance and adjust parameters as market conditions change.
|
||||
---
|
||||
|
||||
## Strategy Metadata
|
||||
|
||||
When writing a strategy with `PythonWrite(category="strategy", ...)`, always provide:
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `description` | yes | One-sentence summary |
|
||||
| `details` | yes | Full markdown: algorithm, entry/exit logic, parameters, data feeds, position sizing. Enough detail to reproduce the code from scratch. |
|
||||
|
||||
```python
|
||||
PythonWrite(
|
||||
category="strategy",
|
||||
name="RSI Mean Reversion",
|
||||
description="Buy oversold, sell overbought based on RSI(14) on BTC/USDT 5m bars.",
|
||||
details="""## RSI Mean Reversion
|
||||
...""",
|
||||
code="""...""",
|
||||
metadata={
|
||||
"data_feeds": [
|
||||
{"symbol": "BTC/USDT.BINANCE", "period_seconds": 300, "description": "BTC/USDT 5m"}
|
||||
],
|
||||
"parameters": {
|
||||
"rsi_length": {"default": 14, "description": "RSI lookback period"},
|
||||
"oversold": {"default": 30, "description": "Buy threshold"},
|
||||
"overbought": {"default": 70, "description": "Sell threshold"},
|
||||
"trade_qty": {"default": 0.01, "description": "Trade quantity in BTC"}
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backtest Workflow
|
||||
|
||||
1. **Check existing indicators** first: `PythonList(category="indicator")` — reuse signals already on the chart.
|
||||
2. **Write** the strategy: `PythonWrite(...)` — runs against synthetic data automatically.
|
||||
3. **Run a backtest** targeting 100,000–200,000 bars (max 5 years):
|
||||
```
|
||||
BacktestStrategy(
|
||||
strategy_name="RSI Mean Reversion",
|
||||
feeds=[{"symbol": "BTC/USDT.BINANCE", "period_seconds": 900}],
|
||||
from_time="2023-01-01",
|
||||
to_time="2024-12-31",
|
||||
initial_capital=10000
|
||||
)
|
||||
```
|
||||
4. **Interpret results**:
|
||||
- `summary.total_return` — total fractional return (0.15 = +15%)
|
||||
- `summary.sharpe_ratio` — annualized Sharpe (>1.0 good, >2.0 excellent)
|
||||
- `summary.max_drawdown` — maximum peak-to-trough loss
|
||||
- `summary.win_rate` — fraction of profitable trades
|
||||
- `statistics.profit_factor` — gross profit / gross loss (>1.5 good)
|
||||
5. **Iterate** with `PythonEdit`, re-run backtest.
|
||||
6. **Activate** (paper first): `ActivateStrategy(..., paper=True)`
|
||||
|
||||
### Bar resolution and backtest window
|
||||
|
||||
Choose the resolution appropriate to the strategy's signal frequency, then set the date range to hit 100k–200k bars:
|
||||
|
||||
| Resolution | ~100k bars | ~200k bars |
|
||||
|---|---|---|
|
||||
| 5m | 1 year | 2 years |
|
||||
| 15m | 2.9 years | 5 years |
|
||||
| 1h | cap at 5 yr (≈44k bars) | — |
|
||||
| 4h | cap at 5 yr (≈11k bars) | — |
|
||||
|
||||
---
|
||||
|
||||
## Strategy Patterns
|
||||
|
||||
### Trend following
|
||||
|
||||
Follow sustained price movements using moving average crossovers, breakout of price channels, or trend-direction filters:
|
||||
|
||||
```python
|
||||
ema_fast = ta.ema(df["close"], length=20)
|
||||
ema_slow = ta.ema(df["close"], length=50)
|
||||
bullish = ema_fast.iloc[-1] > ema_slow.iloc[-1]
|
||||
crossover = ema_fast.iloc[-2] <= ema_slow.iloc[-2]
|
||||
|
||||
if bullish and crossover:
|
||||
self.buy(qty)
|
||||
```
|
||||
|
||||
### Mean reversion
|
||||
|
||||
Profit from price returning to an average after extremes:
|
||||
|
||||
```python
|
||||
rsi = ta.rsi(df["close"], length=14)
|
||||
if rsi.iloc[-1] < 30:
|
||||
self.buy(qty)
|
||||
elif rsi.iloc[-1] > 70:
|
||||
self.sell(qty)
|
||||
```
|
||||
|
||||
### Multi-timeframe confluence
|
||||
|
||||
Use a higher-timeframe trend filter with a lower-timeframe entry signal:
|
||||
|
||||
```python
|
||||
df_4h = dfs.get("BTC/USDT.BINANCE:14400")
|
||||
df_15m = dfs.get("BTC/USDT.BINANCE:900")
|
||||
if df_4h is None or df_15m is None:
|
||||
return
|
||||
|
||||
ema_4h = ta.ema(df_4h["close"], length=20)
|
||||
bullish_trend = df_4h["close"].iloc[-1] > ema_4h.iloc[-1]
|
||||
|
||||
macd_df = ta.macd(df_15m["close"])
|
||||
hist = macd_df.iloc[:, 2]
|
||||
|
||||
if bullish_trend and hist.iloc[-1] > 0 and hist.iloc[-2] <= 0:
|
||||
self.buy(qty, feed_key="BTC/USDT.BINANCE:900")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Important Rules
|
||||
|
||||
- **`evaluate()` must be fast, lightweight, and deterministic** — no model inference, file I/O, network calls, or randomness. It runs on every bar during backtests over potentially hundreds of thousands of bars.
|
||||
- **No LLM calls inside strategies** — strategies must be fully reproducible.
|
||||
- **Guard for insufficient data** — always check `len(df) >= min_required` before computing indicators with a lookback period.
|
||||
- **Use `.get()` for feeds** — multi-feed strategies may have feeds missing during warm-up.
|
||||
- **Size conservatively** — a typical trade quantity is `0.001–0.01 * initial_capital / price`.
|
||||
- **No `import` from `dexorder` inside `evaluate()`** — the strategy file is exec'd in a sandbox; PandasStrategy and pandas_ta are pre-loaded.
|
||||
|
||||
---
|
||||
|
||||
## Performance Metrics Reference
|
||||
|
||||
| Metric | Good | Excellent |
|
||||
|---|---|---|
|
||||
| Sharpe ratio | > 1.0 | > 2.0 |
|
||||
| Profit factor | > 1.5 | > 2.0 |
|
||||
| Max drawdown | < 20% | < 10% |
|
||||
| Win rate | context-dependent | — |
|
||||
|
||||
A strategy with a lower win rate can still be profitable if winners are larger than losers (profit factor > 1). Focus on Sharpe and max drawdown as primary quality metrics.
|
||||
|
||||
### Avoiding overfitting
|
||||
|
||||
- Do not optimize parameters on the same data used for validation
|
||||
- Use a held-out out-of-sample period to verify results
|
||||
- Prefer fewer parameters — simpler strategies generalize better
|
||||
- Walk-forward analysis: re-fit on a rolling window, evaluate on the next
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [`../pandas-ta-reference.md`](../pandas-ta-reference.md) — Indicator catalog and usage examples
|
||||
- [`../indicators/indicator-development.md`](../indicators/indicator-development.md) — Creating custom indicators
|
||||
- [`../api-reference.md`](../api-reference.md) — DataAPI reference (for research scripts)
|
||||
- [`../usage-examples.md`](../usage-examples.md) — Research script patterns
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Technical analysis is the study of historical price and volume data to identify patterns and predict future market movements.
|
||||
|
||||
> **Dexorder scope**: Only crypto OHLCV data is available. TradFi data (equities, forex, bonds, options, macro) and alternative data (news, sentiment, on-chain) are **not** supported. See [`../pandas-ta-reference.md`](../pandas-ta-reference.md) for the full catalog of built-in indicators, and [`../indicators/indicator-development.md`](../indicators/indicator-development.md) for creating custom indicators.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Price Action
|
||||
@@ -70,3 +72,11 @@ Essential principles:
|
||||
- **Diversification**: Multiple uncorrelated positions
|
||||
|
||||
Never trade without a plan and defined risk parameters.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [`../pandas-ta-reference.md`](../pandas-ta-reference.md) — Full indicator catalog with calling conventions
|
||||
- [`../indicators/indicator-development.md`](../indicators/indicator-development.md) — Custom indicator development
|
||||
- [`../strategies/strategy-development.md`](../strategies/strategy-development.md) — Strategy development and backtesting
|
||||
|
||||
264
gateway/knowledge/usage-examples.md
Normal file
264
gateway/knowledge/usage-examples.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Research Script API Usage
|
||||
|
||||
See [`api-reference.md`](api-reference.md) for the full DataAPI and ChartingAPI source with all method signatures and docstrings. See [`pandas-ta-reference.md`](pandas-ta-reference.md) for the indicator catalog.
|
||||
|
||||
Research scripts executed via the `ExecuteResearch` MCP tool have access to the global API instance, which provides both data fetching and charting capabilities.
|
||||
|
||||
## Accessing the API
|
||||
|
||||
```python
|
||||
from dexorder.api import get_api
|
||||
import asyncio
|
||||
|
||||
# Get the global API instance
|
||||
api = get_api()
|
||||
```
|
||||
|
||||
## Using the Data API
|
||||
|
||||
The data API provides access to historical OHLC (Open, High, Low, Close) market data with smart caching via Iceberg.
|
||||
|
||||
### Fetching Historical Data
|
||||
|
||||
The API accepts flexible timestamp formats for convenience:
|
||||
|
||||
```python
|
||||
from dexorder.api import get_api
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
api = get_api()
|
||||
|
||||
# Method 1: Using Unix timestamps (seconds)
|
||||
# 1609459200 = 2021-01-01, 1735689600 = 2025-01-01
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600, # 1 hour candles
|
||||
start_time=1609459200, # 2021-01-01
|
||||
end_time=1735689600, # 2025-01-01 (~4 years, ~35,000 bars)
|
||||
extra_columns=["volume"]
|
||||
))
|
||||
|
||||
# Method 2: Using date strings
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time="2021-01-01",
|
||||
end_time="2025-01-01", # ~4 years of 1h bars ≈ 35,000 bars
|
||||
extra_columns=["volume"]
|
||||
))
|
||||
|
||||
# Method 3: Using date strings with time
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time="2021-01-01 00:00:00",
|
||||
end_time="2025-01-01 00:00:00",
|
||||
extra_columns=["volume"]
|
||||
))
|
||||
|
||||
# Method 4: Using datetime objects
|
||||
from datetime import datetime, timedelta
|
||||
end_time = datetime.now()
|
||||
start_time = end_time - timedelta(days=4*365) # 4 years back
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
extra_columns=["volume"]
|
||||
))
|
||||
|
||||
print(f"Loaded {len(df)} candles from {df.index[0]} to {df.index[-1]}")
|
||||
print(df.head())
|
||||
```
|
||||
|
||||
### Available Extra Columns
|
||||
|
||||
- `"volume"` - Total volume
|
||||
- `"buy_vol"` - Buy-side volume
|
||||
- `"sell_vol"` - Sell-side volume
|
||||
- `"open_time"`, `"high_time"`, `"low_time"`, `"close_time"` - Timestamps for each price point
|
||||
- `"open_interest"` - Open interest (for futures)
|
||||
- `"ticker"` - Market identifier
|
||||
- `"period_seconds"` - Period in seconds
|
||||
|
||||
## Using the Charting API
|
||||
|
||||
The charting API provides styled financial charts with OHLC candlesticks and technical indicators.
|
||||
|
||||
### Creating a Basic Candlestick Chart
|
||||
|
||||
```python
|
||||
from dexorder.api import get_api
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
api = get_api()
|
||||
|
||||
# Fetch data
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time="2021-01-01",
|
||||
end_time="2025-01-01", # ~4 years of 1h bars
|
||||
extra_columns=["volume"]
|
||||
))
|
||||
|
||||
# Create candlestick chart (synchronous)
|
||||
fig, ax = api.charting.plot_ohlc(
|
||||
df,
|
||||
title="BTC/USDT 1H",
|
||||
volume=True, # Show volume bars
|
||||
style="charles" # Chart style
|
||||
)
|
||||
|
||||
# The figure is automatically captured and returned to the MCP client
|
||||
```
|
||||
|
||||
### Adding Indicator Panels
|
||||
|
||||
Use **pandas-ta** for all indicator calculations. Do not write manual rolling/ewm implementations.
|
||||
|
||||
```python
|
||||
from dexorder.api import get_api
|
||||
import asyncio
|
||||
import pandas_ta as ta
|
||||
|
||||
api = get_api()
|
||||
|
||||
# Fetch data
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600,
|
||||
start_time="2021-01-01",
|
||||
end_time="2025-01-01"
|
||||
))
|
||||
|
||||
# Calculate indicators using pandas-ta
|
||||
df['sma_20'] = ta.sma(df['close'], length=20)
|
||||
df['rsi'] = ta.rsi(df['close'], length=14)
|
||||
|
||||
# Create chart
|
||||
fig, ax = api.charting.plot_ohlc(df, title="BTC/USDT with SMA")
|
||||
|
||||
# Overlay the SMA on the price chart
|
||||
# NOTE: mplfinance uses integer x-positions (0..N-1); use range(len(df)), not df.index.
|
||||
ax.plot(range(len(df)), df['sma_20'], label="SMA 20", color="blue", linewidth=2)
|
||||
ax.legend()
|
||||
|
||||
# Add RSI indicator panel below
|
||||
rsi_ax = api.charting.add_indicator_panel(
|
||||
fig, df,
|
||||
columns=["rsi"],
|
||||
ylabel="RSI",
|
||||
ylim=(0, 100)
|
||||
)
|
||||
rsi_ax.axhline(70, color='red', linestyle='--', alpha=0.5)
|
||||
rsi_ax.axhline(30, color='green', linestyle='--', alpha=0.5)
|
||||
```
|
||||
|
||||
### Multi-Output Indicators
|
||||
|
||||
Some pandas-ta indicators return a DataFrame. Extract the columns you need:
|
||||
|
||||
```python
|
||||
import pandas_ta as ta
|
||||
|
||||
# MACD returns: MACD_12_26_9, MACDh_12_26_9, MACDs_12_26_9
|
||||
macd_df = ta.macd(df['close'], fast=12, slow=26, signal=9)
|
||||
df['macd'] = macd_df.iloc[:, 0] # MACD line
|
||||
df['macd_hist'] = macd_df.iloc[:, 1] # Histogram
|
||||
df['macd_signal'] = macd_df.iloc[:, 2] # Signal line
|
||||
|
||||
# Bollinger Bands returns: BBL, BBM, BBU, BBB, BBP
|
||||
bb_df = ta.bbands(df['close'], length=20, std=2.0)
|
||||
df['bb_upper'] = bb_df.iloc[:, 2] # BBU
|
||||
df['bb_mid'] = bb_df.iloc[:, 1] # BBM
|
||||
df['bb_lower'] = bb_df.iloc[:, 0] # BBL
|
||||
|
||||
# Stochastic returns: STOCHk, STOCHd
|
||||
stoch_df = ta.stoch(df['high'], df['low'], df['close'], k=14, d=3, smooth_k=3)
|
||||
df['stoch_k'] = stoch_df.iloc[:, 0]
|
||||
df['stoch_d'] = stoch_df.iloc[:, 1]
|
||||
|
||||
# ATR (uses high, low, close)
|
||||
df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=14)
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
```python
|
||||
from dexorder.api import get_api
|
||||
import asyncio
|
||||
import pandas_ta as ta
|
||||
|
||||
# Get API instance
|
||||
api = get_api()
|
||||
|
||||
# Fetch historical data — use max history for research (target 100k-200k bars)
|
||||
from datetime import datetime, timedelta
|
||||
end_time = datetime.now()
|
||||
start_time = end_time - timedelta(days=3*365) # 3 years of 1h bars ≈ 26,000 bars
|
||||
|
||||
df = asyncio.run(api.data.historical_ohlc(
|
||||
ticker="BTC/USDT.BINANCE",
|
||||
period_seconds=3600, # 1 hour
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
extra_columns=["volume"]
|
||||
))
|
||||
print(f"[Data] {len(df)} bars | {df.index[0]} → {df.index[-1]} | period=3600s")
|
||||
|
||||
# Add moving averages using pandas-ta
|
||||
df['sma_20'] = ta.sma(df['close'], length=20)
|
||||
df['ema_50'] = ta.ema(df['close'], length=50)
|
||||
|
||||
# Create chart with volume
|
||||
fig, ax = api.charting.plot_ohlc(
|
||||
df,
|
||||
title="BTC/USDT Analysis",
|
||||
volume=True,
|
||||
style="charles"
|
||||
)
|
||||
|
||||
# Overlay moving averages
|
||||
# NOTE: mplfinance uses integer x-positions (0..N-1); use range(len(df)), not df.index.
|
||||
ax.plot(range(len(df)), df['sma_20'], label="SMA 20", color="blue", linewidth=1.5)
|
||||
ax.plot(range(len(df)), df['ema_50'], label="EMA 50", color="red", linewidth=1.5)
|
||||
ax.legend()
|
||||
|
||||
# Print summary statistics
|
||||
print(f"[Data] {len(df)} bars | {df.index[0]} → {df.index[-1]} | period=3600s")
|
||||
print(f"High: {df['high'].max()}")
|
||||
print(f"Low: {df['low'].min()}")
|
||||
print(f"Mean Volume: {df['volume'].mean():.2f}")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- **Async vs Sync**: Data API methods are async and require `asyncio.run()`. Charting API methods are synchronous.
|
||||
- **Figure Capture**: All matplotlib figures created during script execution are automatically captured and returned as PNG images.
|
||||
- **Print Statements**: All `print()` output is captured and returned as text content.
|
||||
- **Errors**: Exceptions are caught and reported in the execution results.
|
||||
- **Timestamps**: The API accepts flexible timestamp formats:
|
||||
- Unix timestamps in **seconds** (int or float) - e.g., `1640000000`
|
||||
- Date strings - e.g., `"2021-12-20"` or `"2021-12-20 12:00:00"`
|
||||
- datetime objects - e.g., `datetime(2021, 12, 20)`
|
||||
- pandas Timestamp objects
|
||||
- Internally, the system uses microseconds since epoch, but you don't need to worry about this conversion.
|
||||
- **Price/Volume Values**: All prices and volumes are returned as decimal floats, automatically converted from internal storage format using market metadata. No manual conversion is needed.
|
||||
|
||||
## Available Chart Styles
|
||||
|
||||
- `"charles"` (default)
|
||||
- `"binance"`
|
||||
- `"blueskies"`
|
||||
- `"brasil"`
|
||||
- `"checkers"`
|
||||
- `"classic"`
|
||||
- `"mike"`
|
||||
- `"nightclouds"`
|
||||
- `"sas"`
|
||||
- `"starsandstripes"`
|
||||
- `"yahoo"`
|
||||
Reference in New Issue
Block a user