Files
ai/gateway/knowledge/strategies/strategy-development.md

267 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
description: "PandasStrategy class API, order placement, backtesting, and paper trading patterns for automated crypto strategy development."
---
# Strategy Development Guide
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.
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
from dexorder.nautilus.pandas_strategy import PandasStrategy, PandasStrategyConfig
class MyStrategy(PandasStrategy):
def evaluate(self, dfs: dict[str, pd.DataFrame]) -> None:
"""Called after every new bar across all feeds.
Args:
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.
"""
df = dfs.get("BTC/USDT.BINANCE:300")
if df is None or len(df) < 20:
return # not enough data yet
close = df["close"]
# ... compute signals ...
if buy_signal:
self.buy(quantity=0.1)
elif sell_signal:
self.sell(quantity=0.1)
```
### 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
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
```
If `feed_key` is omitted, the first feed in `feed_keys` is used. `quantity` is in base currency units (e.g. 0.1 BTC).
### Available data
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)
---
## Using pandas_ta
Use `import pandas_ta as ta` for all indicator calculations. Never write manual `rolling()` or `ewm()` implementations.
```python
import pandas_ta as ta
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
ema = ta.ema(df["close"], length=20)
atr = ta.atr(df["high"], df["low"], df["close"], length=14)
```
See [`../pandas-ta-reference.md`](../pandas-ta-reference.md) for the full indicator catalog and multi-output column extraction patterns.
---
## Using Custom Indicators
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.
```python
import pandas_ta as ta
def evaluate(self, dfs):
df = dfs.get("BTC/USDT.BINANCE:3600")
if df is None or len(df) < 20:
return
vw_rsi = ta.custom_vw_rsi(df["close"], df["volume"], length=14)
if vw_rsi is None or vw_rsi.isna().all():
return
if vw_rsi.iloc[-1] < 30:
self.buy(0.01)
elif vw_rsi.iloc[-1] > 70:
self.sell(0.01)
```
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.
---
## 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,000200,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 100k200k 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.0010.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