> ## Documentation Index
> Fetch the complete documentation index at: https://condor.hummingbot.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating Routines

> Build custom routines for your trading agents

Create custom routines to extend agent capabilities with deterministic Python code.

## Routine Requirements

Every routine module needs:

1. **Config** - A Pydantic BaseModel with parameters (docstring becomes the description)
2. **run(config, context)** - Async function that executes the routine and returns a string
3. **CONTINUOUS = True** (optional) - Mark as continuous routine with internal loop

## Create a Custom Routine

### 1. Create the File

In your agent's `routines/` folder:

```python theme={null}
# trading_agents/sol_scalper/routines/momentum_scanner.py
from pydantic import BaseModel, Field
from telegram.ext import ContextTypes

from config_manager import get_client


class Config(BaseModel):
    """Scan for momentum breakouts on a trading pair."""

    connector_name: str = Field(default="binance_perpetual", description="Exchange connector")
    trading_pair: str = Field(default="SOL-USDT", description="Market to analyze")
    lookback: int = Field(default=20, description="Number of candles")


async def run(config: Config, context: ContextTypes.DEFAULT_TYPE) -> str:
    """Scan for momentum breakouts."""
    chat_id = context._chat_id if hasattr(context, "_chat_id") else None
    client = await get_client(chat_id, context=context)

    if not client:
        return "No server available"

    # Get candles via Hummingbot API
    candles = await client.market_data.get_candles(
        connector_name=config.connector_name,
        trading_pair=config.trading_pair,
        interval="1m",
        limit=config.lookback
    )

    # Calculate momentum
    closes = [c["close"] for c in candles["candles"]]
    momentum = (closes[-1] - closes[0]) / closes[0] * 100

    # Volume confirmation
    volumes = [c["volume"] for c in candles["candles"]]
    avg_volume = sum(volumes) / len(volumes)
    volume_surge = volumes[-1] > avg_volume * 1.5

    # Generate signal
    if momentum > 2 and volume_surge:
        signal = "🟢 STRONG BULLISH"
    elif momentum > 1:
        signal = "🟡 Bullish"
    elif momentum < -2 and volume_surge:
        signal = "🔴 STRONG BEARISH"
    elif momentum < -1:
        signal = "🟠 Bearish"
    else:
        signal = "⚪ Neutral"

    return (
        f"**Momentum Scanner** - {config.trading_pair}\n"
        f"Signal: {signal}\n"
        f"Momentum: {momentum:+.2f}%\n"
        f"Volume surge: {'Yes' if volume_surge else 'No'}\n"
        f"Price: ${closes[-1]:,.2f}"
    )
```

### 2. Register the Routine

Add to your agent's config or the routine will be auto-discovered.

### 3. Use in Agent

Update your `agent.md`:

```markdown theme={null}
## Entry Rules

1. Run momentum_scanner routine
2. If signal is "strong_bullish" → Open long
3. If signal is "strong_bearish" → Open short
4. Otherwise → Wait
```

### 4. Test

Run a dry run to verify:

```
/agent → SOL Scalper → Dry Run

Dry Run Result:
- Ran momentum_scanner
- Result: {signal: "strong_bullish", momentum: 2.5%}
- Decision: Would open LONG
```

## Creating with Condor

You can ask Condor to create routines for you via natural language:

```
You: Create a routine that analyzes support and resistance levels
     using EMAs of 7, 25, and 99 on 1-minute candles.
```

Condor generates the routine:

```python theme={null}
# routines/support_resistance_ema_levels.py
from pydantic import BaseModel, Field
from telegram.ext import ContextTypes

from config_manager import get_client


class Config(BaseModel):
    """Analyze support/resistance levels and EMA alignment."""

    connector_name: str = Field(default="binance_perpetual", description="Exchange connector")
    trading_pair: str = Field(default="BTC-USDT", description="Trading pair")
    candle_count: int = Field(default=100, description="Number of candles to analyze")
    lookback: int = Field(default=20, description="Lookback for S/R detection")


async def run(config: Config, context: ContextTypes.DEFAULT_TYPE) -> str:
    """Identify support/resistance levels and EMA alignment."""
    chat_id = context._chat_id if hasattr(context, "_chat_id") else None
    client = await get_client(chat_id, context=context)

    candles = await client.market_data.get_candles(
        connector_name=config.connector_name,
        trading_pair=config.trading_pair,
        interval="1m",
        limit=config.candle_count
    )

    closes = [c["close"] for c in candles["candles"]]

    # Calculate EMAs
    ema_7 = calculate_ema(closes, 7)
    ema_25 = calculate_ema(closes, 25)
    ema_99 = calculate_ema(closes, 99)

    # Determine trend from EMA alignment
    if ema_7 > ema_25 > ema_99:
        trend = "🟢 Bullish"
    elif ema_7 < ema_25 < ema_99:
        trend = "🔴 Bearish"
    else:
        trend = "🟡 Mixed"

    # Find support/resistance levels
    supports, resistances = find_sr_levels(closes, config.lookback)

    return (
        f"**EMA Analysis** - {config.trading_pair}\n"
        f"Trend: {trend}\n"
        f"EMA 7: ${ema_7:,.2f}\n"
        f"EMA 25: ${ema_25:,.2f}\n"
        f"EMA 99: ${ema_99:,.2f}\n"
        f"Support: ${supports[0]:,.2f}\n"
        f"Resistance: ${resistances[0]:,.2f}"
    )
```

The routine is auto-discovered and immediately available via `/routines`.

## Routine Best Practices

<AccordionGroup>
  <Accordion title="Keep routines deterministic">
    Same input should always produce same output. Avoid randomness or time-based logic that would make results unpredictable.
  </Accordion>

  <Accordion title="Return formatted strings">
    Return human-readable strings for Telegram display. Use markdown formatting for clarity.
  </Accordion>

  <Accordion title="Handle errors gracefully">
    Return error messages rather than raising exceptions:

    ```python theme={null}
    if not client:
        return "No server available"
    if not candles:
        return "No candle data available"
    ```
  </Accordion>

  <Accordion title="Use Config docstrings">
    The Config class docstring becomes the routine description in the UI. Use `Field(description=...)` to document each parameter.
  </Accordion>

  <Accordion title="Handle cancellation in continuous routines">
    Continuous routines must catch `asyncio.CancelledError` to clean up gracefully when stopped.
  </Accordion>
</AccordionGroup>

## Global vs Agent-Specific

| Location                          | Scope                   |
| --------------------------------- | ----------------------- |
| `~/condor/routines/`              | Available to all agents |
| `trading_agents/{slug}/routines/` | Specific to one agent   |

Agent-specific routines override global ones with the same name.
