Skip to content

Creating a Trading Strategy

Step-by-step guide to creating a new trading strategy with TradAI.

Two-Repository Model

Platform code (this repo, tradai-uv) contains the framework, services, and infrastructure. Proprietary strategies live in a separate repository (tradai-strategies) for IP isolation. This guide works in both repos — use tradai-uv for learning/testing and tradai-strategies for production strategies. See Strategy Repository for details.

Quick Start

Create a new strategy with a single command:

tradai strategy new MyMomentumStrategy

This creates a complete strategy package in the current directory with: - Strategy implementation (src/my_momentum_strategy/strategy.py) - Configuration files (configs/backtest.json, tradai.yaml) - Tests (tests/) - Dockerfile for deployment

Strategy Creation Options

Basic Options

# Create strategy with specific timeframe and category
tradai strategy new MyStrategy --timeframe 4h --category momentum

# Create scalping strategy with short positions
tradai strategy new ScalpBot --category scalping --timeframe 5m --can-short

# Create ML-powered strategy
tradai strategy new MLStrategy --ml freqai

# Skip test generation
tradai strategy new QuickTest --no-tests

Configuration Options

Customize initial configuration during creation:

tradai strategy new MyStrategy \
  --exchange bybit \
  --trading-mode futures \
  --stake-currency USDT \
  --symbols "BTC/USDT:USDT,ETH/USDT:USDT" \
  --max-trades 5 \
  --stake-amount 1000 \
  --stoploss -0.05 \
  --trailing-stop

Non-Interactive Mode

Skip all prompts with -y:

tradai strategy new MyStrategy -y --category trend-following

Interactive Wizard

For guided strategy creation with recommendations:

tradai strategy wizard

The wizard walks you through: 1. Strategy Name - Your strategy identifier 2. Category Selection - Trading style with descriptions 3. Timeframe - Candle interval with category-based recommendations 4. Exchange - Target exchange (Binance, Bybit, OKX, etc.) 5. Trading Mode - Spot or futures 6. Risk Settings - Stoploss, stake amount 7. Summary - Review before creation

Strategy Categories

Category Best For Recommended Timeframe
trend-following Capturing directional moves 1h, 4h, 1d
momentum Riding strong movements 15m, 1h
mean-reversion Fading extreme moves 1h, 4h
breakout Trading range expansions 15m, 1h
scalping Quick small trades 1m, 5m
grid-trading Range-bound markets 15m, 1h
dca Dollar-cost averaging 4h, 1d
arbitrage Price discrepancies 1m

Generated Strategy Structure

The cookiecutter template generates a complete package with modular files:

my_momentum_strategy/
├── MyMomentumStrategy.py          # Root-level strategy (Freqtrade discovery)
├── Dockerfile
├── README.md
├── .pre-commit-config.yaml
├── pyproject.toml
├── tradai.yaml                     # Strategy configuration
├── configs/
│   ├── backtest.json
│   └── freqai_training.json
├── src/my_momentum_strategy/
│   ├── __init__.py
│   ├── strategy.py
│   ├── constants.py
│   ├── entities.py
│   └── exceptions.py
└── tests/
    ├── __init__.py
    ├── conftest.py
    └── test_strategy.py

Implementing Your Strategy

Edit src/my_momentum_strategy/strategy.py:

Note: This project uses TA-Lib via the talib Python package. While Freqtrade also supports pandas_ta, talib is the project standard.

"""MyMomentumStrategy - EMA crossover with RSI confirmation."""

from typing import Any

from freqtrade.strategy import IntParameter
from pandas import DataFrame
from tradai.common.logger_mixin import LoggerMixin
from tradai.strategy import StrategyCategory, StrategyMetadata, TradAIStrategy


class MyMomentumStrategy(TradAIStrategy, LoggerMixin):
    """Momentum strategy using EMA crossover with RSI confirmation."""

    # Trading parameters
    timeframe = "1h"
    can_short = True
    stoploss = -0.05
    startup_candle_count = 50
    minimal_roi = {"0": 0.10, "30": 0.05, "60": 0.02}
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.02

    # Hyperopt parameters (optimized during hyperparameter search)
    ema_fast = IntParameter(
        5, 20, default=12, space="buy", optimize=True
    )
    ema_slow = IntParameter(
        20, 50, default=26, space="buy", optimize=True
    )
    rsi_period = IntParameter(
        10, 20, default=14, space="buy", optimize=True
    )

    def __init__(self, config: dict[str, Any]) -> None:
        super().__init__(config)
        LoggerMixin.__init__(self)

    def get_metadata(self) -> StrategyMetadata:
        """Return strategy metadata (REQUIRED for TradAIStrategy)."""
        return StrategyMetadata(
            name="MyMomentumStrategy",
            version="1.0.0",
            description="EMA crossover with RSI confirmation",
            timeframe=self.timeframe,
            category=StrategyCategory.MOMENTUM,
            can_short=self.can_short,
            tags=["ema", "rsi", "momentum"],
            author="TradAI Team",
            parameters={
                "ema_fast": self.ema_fast.value,
                "ema_slow": self.ema_slow.value,
                "rsi_period": self.rsi_period.value,
            },
        )

    def populate_indicators(
        self, dataframe: DataFrame, metadata: dict
    ) -> DataFrame:
        """Calculate technical indicators."""
        import talib

        dataframe["ema_fast"] = talib.EMA(
            dataframe["close"], timeperiod=self.ema_fast.value
        )
        dataframe["ema_slow"] = talib.EMA(
            dataframe["close"], timeperiod=self.ema_slow.value
        )
        dataframe["rsi"] = talib.RSI(
            dataframe["close"], timeperiod=self.rsi_period.value
        )
        self.log_info(f"Indicators calculated for {metadata.get('pair')}")
        return dataframe

    def populate_entry_trend(
        self, dataframe: DataFrame, metadata: dict
    ) -> DataFrame:
        """Generate entry signals."""
        # Long: Fast EMA crosses above slow + RSI not overbought
        dataframe.loc[
            (dataframe["ema_fast"] > dataframe["ema_slow"])
            & (dataframe["ema_fast"].shift(1)
               <= dataframe["ema_slow"].shift(1))
            & (dataframe["rsi"] < 70),
            "enter_long",
        ] = 1

        # Short: Fast EMA crosses below slow + RSI not oversold
        if self.can_short:
            dataframe.loc[
                (dataframe["ema_fast"] < dataframe["ema_slow"])
                & (dataframe["ema_fast"].shift(1)
                   >= dataframe["ema_slow"].shift(1))
                & (dataframe["rsi"] > 30),
                "enter_short",
            ] = 1

        return dataframe

    def populate_exit_trend(
        self, dataframe: DataFrame, metadata: dict
    ) -> DataFrame:
        """Generate exit signals."""
        dataframe.loc[dataframe["rsi"] > 80, "exit_long"] = 1
        if self.can_short:
            dataframe.loc[dataframe["rsi"] < 20, "exit_short"] = 1
        return dataframe

Modular Strategy Structure

For complex strategies, split logic across the generated module files:

File Purpose
strategy.py Main class, orchestrates indicators/signals
indicators.py Pure functions for indicator calculations
signals.py generate_entry_signals(), generate_exit_signals()
filters.py Trade filters (volume, time-of-day, volatility)
constants.py All parameter defaults and magic numbers

Example indicators.py:

"""Technical indicator calculations."""

from pandas import DataFrame
import talib


def calculate_ema_crossover(
    dataframe: DataFrame,
    fast_length: int,
    slow_length: int,
) -> DataFrame:
    """Calculate EMA crossover indicators."""
    dataframe["ema_fast"] = talib.EMA(dataframe["close"], timeperiod=fast_length)
    dataframe["ema_slow"] = talib.EMA(dataframe["close"], timeperiod=slow_length)
    return dataframe

Then in strategy.py:

from my_momentum_strategy.indicators import calculate_ema_crossover

def populate_indicators(self, dataframe, metadata):
    dataframe = calculate_ema_crossover(
        dataframe, self.ema_fast.value, self.ema_slow.value
    )
    return dataframe

Testing Your Strategy

Lint for Common Issues

tradai strategy lint ./my_momentum_strategy/

Checks for: - Look-ahead bias (using future data) - Missing startup_candle_count - Repainting indicators - Invalid parameter combinations

Writing Tests

The generated tests/conftest.py provides shared fixtures. Add tests in tests/test_strategy.py:

"""Tests for MyMomentumStrategy."""

import pytest
import numpy as np
from pandas import DataFrame
from my_momentum_strategy.strategy import MyMomentumStrategy


@pytest.fixture
def strategy():
    """Create strategy instance for testing."""
    config = {"stake_currency": "USDT", "trading_mode": "futures"}
    return MyMomentumStrategy(config)


@pytest.fixture
def sample_dataframe():
    """Create sample OHLCV dataframe for testing."""
    n = 100
    return DataFrame({
        "open": np.random.uniform(40000, 50000, n),
        "high": np.random.uniform(40000, 50000, n),
        "low": np.random.uniform(40000, 50000, n),
        "close": np.random.uniform(40000, 50000, n),
        "volume": np.random.uniform(100, 1000, n),
    })


@pytest.mark.unit
def test_get_metadata(strategy):
    """Test metadata is correctly defined."""
    metadata = strategy.get_metadata()
    assert metadata.name == "MyMomentumStrategy"
    assert metadata.version == "1.0.0"
    assert metadata.timeframe == "1h"
    assert metadata.category == "momentum"


@pytest.mark.unit
def test_populate_indicators(strategy, sample_dataframe):
    """Test indicators are calculated without errors."""
    df = strategy.populate_indicators(sample_dataframe, {})
    assert "ema_fast" in df.columns
    assert "ema_slow" in df.columns
    assert "rsi" in df.columns
    assert not df["rsi"].isna().all()


@pytest.mark.unit
def test_populate_entry_trend(strategy, sample_dataframe):
    """Test entry signals are generated."""
    df = strategy.populate_indicators(sample_dataframe, {})
    df = strategy.populate_entry_trend(df, {})
    assert "enter_long" in df.columns

Run Tests

# Run strategy tests
cd my_momentum_strategy && uv run pytest tests/ -v

Note: For strategies in the tradai-strategies repo, use just test my-strategy. For standalone strategies, use cd my_momentum_strategy && uv run pytest.

Test Indicator Logic

Before backtesting, validate your indicators work:

tradai indicator test "ta.RSI(df['close'], 14)" --symbol BTC/USDT:USDT

Next Steps

After creating your strategy:

  1. Edit Strategy Logic - Implement your trading rules
  2. Test Indicators - Validate with tradai indicator test
  3. Lint Strategy - Check for issues with tradai strategy lint
  4. Run Backtest - Test with historical data (see Backtesting Guide)

Listing Strategies

View all available strategies:

tradai strategy list

Get detailed info about a specific strategy:

tradai strategy info MyMomentumStrategy

Quick Reference

Command Description
tradai strategy new NAME Create new strategy
tradai strategy wizard Interactive creation
tradai strategy list List all strategies
tradai strategy info NAME Strategy details
tradai strategy lint PATH Check for issues