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:
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:
Interactive Wizard¶
For guided strategy creation with recommendations:
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
talibPython package. While Freqtrade also supportspandas_ta,talibis 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¶
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¶
Note: For strategies in the
tradai-strategiesrepo, usejust test my-strategy. For standalone strategies, usecd my_momentum_strategy && uv run pytest.
Test Indicator Logic¶
Before backtesting, validate your indicators work:
Next Steps¶
After creating your strategy:
- Edit Strategy Logic - Implement your trading rules
- Test Indicators - Validate with
tradai indicator test - Lint Strategy - Check for issues with
tradai strategy lint - Run Backtest - Test with historical data (see Backtesting Guide)
Listing Strategies¶
View all available strategies:
Get detailed info about a specific strategy:
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 |