Skip to content

tradai-strategy Design Document

Overview

tradai-strategy provides the strategy framework for the TradAI platform. It extends Freqtrade's IStrategy with TradAI-specific features: standardized metadata, type-safe enums, validation, and MLflow/Docker integration.

Design Philosophy: Extend, don't wrap. TradAIStrategy IS an IStrategy - it works directly with Freqtrade without adapters.

Architecture Decisions

What We Provide

  1. TradAIStrategy (base.py) - Base class extending IStrategy
  2. Adds get_metadata() abstract method (REQUIRED)
  3. Adds validate_configuration() hook (optional)
  4. Includes StrategyDebugMixin for development
  5. Full Freqtrade compatibility (all IStrategy hooks work)

  6. StrategyMetadata (metadata.py) - Pydantic model for strategy info

  7. Required: name, version, description, timeframe, category
  8. Optional: can_short, tags, author, status, parameters
  9. Live trading fields: trading_modes, exchange, pairs, stake_currency
  10. Methods: to_mlflow_tags(), to_docker_labels()

  11. Type-Safe Enums (enums.py)

  12. SignalDirection - LONG, SHORT
  13. StrategyStatus - ACTIVE, DEPRECATED, TESTING
  14. StrategyCategory - TREND_FOLLOWING, MEAN_REVERSION, BREAKOUT, MOMENTUM, ARBITRAGE
  15. BacktestStatus - PENDING, RUNNING, COMPLETED, FAILED
  16. ValidationLevel - BASIC, STANDARD, STRICT
  17. PredictionSource - CSV, MLFLOW

  18. StrategyValidator (validation.py) - Server-side validation

  19. Validates strategy class structure
  20. Validates metadata completeness
  21. Validates Freqtrade method signatures

  22. Filters (filters.py) - Signal filtering utilities

  23. apply_filters() - Apply multiple filters to signals
  24. apply_volatility_filter() - Filter by ATR/volatility

  25. FreqAI Integration (freqai/)

  26. config_builder.py - FreqAI config construction
  27. prediction_model.py - Prediction loading utilities
  28. training_model.py - Training utilities
  29. utils.py - FreqAI helper functions

  30. Debug Utilities (debug.py)

  31. StrategyDebugMixin - Debug methods for development
  32. check_nan_indicators() - Detect NaN in indicators
  33. print_indicator_summary() - Print latest candle values

  34. Quality Checks

  35. sanity/ - Runtime sanity checks
  36. preflight/ - Pre-execution validation
  37. ci/ - CI/CD gate validation
  38. linting.py - Static analysis rules

  39. Validation Entities (validation_entities.py) - Unified validation types

  40. CheckSeverity - ERROR, WARNING, INFO severity levels
  41. ValidationIssue - Base class for all validation issues
  42. MetricValidationIssue - Issues with threshold violations
  43. ValidationResultMixin - Shared aggregation properties

  44. Strategy Loader (loading/) - Dynamic strategy class loading

    • StrategyLoader - Loads strategy classes from file paths or module names
    • Validates loaded classes extend TradAIStrategy
    • Used by strategy-service for runtime strategy discovery
  45. Testing Utilities (testing/) - Test helpers

    • data.py - OHLCV dataframe factories
    • fixtures.py - Pytest fixtures for strategy testing

What We Don't Provide

  • Strategy implementations - Live in separate tradai-strategies repo
  • Backtesting engine - Use Freqtrade directly
  • Data fetching - Use tradai-data library
  • Common utilities - Use tradai-common library

Module Organization

tradai/strategy/
├── __init__.py              # Public API exports
├── base.py                  # TradAIStrategy base class
├── metadata.py              # StrategyMetadata Pydantic model
├── enums.py                 # Type-safe enums
├── validation.py            # StrategyValidator
├── validation_entities.py   # Unified validation types (CheckSeverity, ValidationIssue)
├── exceptions.py            # StrategyError hierarchy
├── filters.py               # Signal filtering utilities
├── dataframe_validators.py  # DataFrame/NaN validation (analyze_nan_indicators)
├── debug.py                 # StrategyDebugMixin
├── linting.py               # Static analysis rules
├── freqai/                  # FreqAI integration
│   ├── __init__.py
│   ├── config_builder.py    # FreqAI config construction
│   ├── prediction_model.py  # Prediction loading
│   ├── training_model.py    # Training utilities
│   └── utils.py             # Helper functions
├── sanity/                  # Runtime sanity checks
│   ├── __init__.py
│   ├── service.py           # Sanity check service
│   └── entities.py          # SanityCheckResult, SanityWarning
├── preflight/               # Pre-execution validation
│   ├── __init__.py
│   ├── service.py           # Preflight validation service
│   └── entities.py          # PreflightResult, CheckResult
├── ci/                      # CI/CD gate validation
│   ├── __init__.py
│   └── backtest_gate.py     # CIBacktestGate, CIValidationResult
├── loading/                 # Strategy loading utilities
│   ├── __init__.py          # Public API (StrategyLoader)
│   └── loader.py            # Dynamic strategy class loading from files/modules
└── testing/                 # Test utilities
    ├── __init__.py
    ├── data.py              # OHLCV dataframe factories
    └── fixtures.py          # Pytest fixtures

Usage Patterns

Creating a Strategy

from tradai.strategy import TradAIStrategy, StrategyMetadata
from tradai.strategy.enums import StrategyCategory
import talib

class MyStrategy(TradAIStrategy):
    """Example trading strategy."""

    timeframe = '1h'
    can_short = True
    stoploss = -0.08

    def get_metadata(self) -> StrategyMetadata:
        return StrategyMetadata(
            name="MyStrategy",
            version="1.0.0",
            description="RSI mean reversion",
            timeframe=self.timeframe,
            can_short=self.can_short,
            category=StrategyCategory.MEAN_REVERSION,
            tags=["rsi", "mean-reversion"],
        )

    def populate_indicators(self, dataframe, metadata):
        dataframe['rsi'] = talib.RSI(dataframe['close'])
        return dataframe

    def populate_entry_trend(self, dataframe, metadata):
        dataframe.loc[dataframe['rsi'] < 30, 'enter_long'] = 1
        return dataframe

    def populate_exit_trend(self, dataframe, metadata):
        dataframe.loc[dataframe['rsi'] > 70, 'exit_long'] = 1
        return dataframe

Live Trading Strategy

def get_metadata(self) -> StrategyMetadata:
    return StrategyMetadata(
        name="LiveMomentumBot",
        version="2.1.0",
        description="Production momentum strategy",
        timeframe="4h",
        category=StrategyCategory.MOMENTUM,
        # Live trading fields (LM001)
        trading_modes=["backtest", "dry-run", "live"],
        exchange="binance",
        pairs=["BTC/USDT:USDT", "ETH/USDT:USDT"],
        stake_currency="USDT",
        status=StrategyStatus.ACTIVE,
    )

With FreqAI

from tradai.strategy.freqai import FreqAIConfigBuilder

class MLStrategy(TradAIStrategy):
    """FreqAI-enabled strategy."""

    def get_metadata(self) -> StrategyMetadata:
        return StrategyMetadata(
            name="MLStrategy",
            version="1.0.0",
            description="ML-based predictions",
            timeframe="1h",
            category=StrategyCategory.TREND_FOLLOWING,
            tags=["freqai", "ml", "gradient-boosting"],
        )

    @property
    def freqai_config(self) -> dict:
        return FreqAIConfigBuilder(
            model_name="LightGBMClassifier",
            label_period_candles=24,
            feature_parameters={
                "include_corr_pairlist": ["BTC/USDT:USDT"],
                "indicator_periods_candles": [10, 20, 50],
            },
        ).build()

Validation

from tradai.strategy import StrategyValidator
from tradai.strategy.enums import ValidationLevel

validator = StrategyValidator(level=ValidationLevel.STANDARD)
result = validator.validate(MyStrategy)

if not result.is_valid:
    for error in result.errors:
        print(f"Error: {error}")

Relationship to tradai-strategies

tradai-strategy (this library) provides the framework: - TradAIStrategy base class - Metadata schema - Validation utilities

tradai-strategies (separate private repo) contains implementations: - Production strategies (PascalStrategy, RadStrategy, etc.) - Example strategies for learning - Strategy-specific tests

tradai-uv/libs/tradai-strategy/    # Framework (public in workspace)
    └── src/tradai/strategy/

tradai-strategies/                  # Implementations (private repo)
    ├── strategies/                 # Production strategies
    │   ├── pascal-strategy/
    │   ├── rad-strategy/
    │   └── stochastic-strategy/
    └── examples/                   # Learning examples
        ├── minimal_strategy/
        └── momentum_strategy/

Design Patterns Applied

1. Extension Pattern (not Adapter)

# TradAIStrategy IS an IStrategy
class TradAIStrategy(IStrategy, StrategyDebugMixin):
    # All IStrategy methods work directly
    # Additional TradAI features added on top

2. Mixin Pattern (Composition)

class StrategyDebugMixin:
    """Debug utilities mixed into TradAIStrategy."""

    def check_nan_indicators(self, df, pair): ...
    def print_indicator_summary(self, df, pair): ...

class TradAIStrategy(IStrategy, StrategyDebugMixin):
    # Gets debug methods without inheritance hierarchy

3. Frozen Entities (Immutability)

class StrategyMetadata(BaseModel):
    model_config = ConfigDict(frozen=True)
    # All fields immutable after creation

4. Type-Safe Enums

class StrategyCategory(str, Enum):
    """str inheritance for JSON serialization."""
    TREND_FOLLOWING = "trend-following"
    MEAN_REVERSION = "mean-reversion"

5. Unified Validation Entity Hierarchy

All validation systems (preflight, sanity, CI gate) share a common entity hierarchy:

# Base types (validation_entities.py)
class CheckSeverity(str, Enum):
    ERROR = "error"      # Blocks execution/merge
    WARNING = "warning"  # Review required, doesn't block
    INFO = "info"        # Informational only

class ValidationIssue(BaseModel):
    """Base class for all validation issues."""
    rule_id: str
    severity: CheckSeverity
    message: str
    details: dict[str, Any] = {}

class MetricValidationIssue(ValidationIssue):
    """For threshold violations (sanity, CI)."""
    actual_value: float | int | None
    threshold: float | int
    suggestion: str = ""

# Concrete implementations
class SanityWarning(MetricValidationIssue):     # sanity/entities.py
    rule_id: SanityRuleId

class CIViolation(MetricValidationIssue):       # ci/backtest_gate.py
    rule_id: CIGateRuleId
    is_blocking: bool = True

6. ValidationResultMixin Pattern

Result classes use a mixin for consistent aggregation:

class ValidationResultMixin:
    """Provides consistent filtering/counting across all validation results."""

    @property
    def errors(self) -> list[ValidationIssue]:
        """Get all ERROR-level issues."""

    @property
    def warnings_only(self) -> list[ValidationIssue]:
        """Get all WARNING-level issues."""

    @property
    def error_count(self) -> int: ...
    @property
    def warning_count(self) -> int: ...
    @property
    def has_errors(self) -> bool: ...
    @property
    def has_warnings(self) -> bool: ...

# Usage in result classes
class SanityCheckResult(BaseModel, ValidationResultMixin):
    passed: bool
    warnings: list[SanityWarning]  # Mixin finds this automatically

class CIValidationResult(BaseModel, ValidationResultMixin):
    passed: bool
    violations: list[CIViolation]  # Mixin finds this automatically

Benefits: - DRY: No duplicate filtering logic across modules - Consistency: Same property names everywhere - Extensibility: New validation systems get these properties automatically

Dependencies

Core: - freqtrade>=2024.1 (strategy framework) - pydantic>=2.0.0 (metadata validation) - pandas>=2.0.0 (dataframe operations)

Optional: - ta-lib (technical indicators) - pandas-ta (additional indicators)

Testing Strategy

  1. Unit Tests (tests/unit/)
  2. Test metadata validation
  3. Test enum serialization
  4. Test validator logic
  5. Test validation entities

  6. Integration Tests (tests/integration/)

  7. Test strategy loading with Freqtrade
  8. Test FreqAI integration

  9. Example Tests (tests/examples/)

  10. Verify example strategies work
  11. Validate with sample data

  12. Testing Utilities (testing/) The tradai.strategy.testing module provides helpers for strategy tests:

from tradai.strategy.testing import (
    create_ohlcv_dataframe,      # Realistic OHLCV data
    create_trending_dataframe,   # Trending price data
    create_oversold_dataframe,   # Oversold conditions
    sample_ohlcv_fixture,        # Pytest fixture
)

# Create test data with reproducible seeds
df = create_ohlcv_dataframe(
    n_candles=200,
    base_price=50000.0,
    seed=42,  # Uses np.random.Generator for thread-safety
)

Test structure:

tests/
├── unit/
│   ├── test_metadata.py
│   ├── test_enums.py
│   ├── test_validation.py
│   ├── test_validation_entities.py
│   ├── test_dataframe_validators.py
│   ├── test_testing_data.py
│   ├── sanity/
│   │   └── test_service.py
│   └── preflight/
│       └── test_entities.py
├── freqai/
│   ├── test_config_builder.py
│   ├── test_prediction_model.py
│   └── test_training_model.py
├── conftest.py              # Shared fixtures
├── test_base.py
├── test_filters.py
├── test_debug.py
└── test_linting.py

Risk Management Architecture

Design Decision: Strategy-Level Risk Enforcement

Key Principle: Risk management is the STRATEGY's responsibility, not the platform's.

This is an intentional design choice:

  1. Freqtrade Native Features - All risk management is handled by Freqtrade:
  2. stoploss - Maximum loss per trade
  3. minimal_roi - Take profit targets
  4. max_open_trades - Position limits
  5. stake_amount / tradable_balance_ratio - Position sizing
  6. stoploss_on_exchange - Exchange-level stop loss

  7. Platform Role - TradAI platform provides:

  8. Monitoring: Track drawdown, win rate, Sharpe ratio (TRD-023)
  9. Alerting: Notify when SLA thresholds breached (TRD-024)
  10. Circuit Breakers: Pause/stop trading on critical failures (TRD-022)
  11. Audit Trail: Log all decisions for compliance

Why This Architecture?

Alternative Problem
Platform-level enforcement Adds latency, conflicts with Freqtrade
Wrapper around Freqtrade Complexity, maintenance burden
Duplicate logic Inconsistency, bugs, drift

Chosen approach: Let Freqtrade handle execution-level risk, platform handles monitoring/alerting.

Risk Configuration in Strategies

class MyStrategy(TradAIStrategy):
    """Example with proper risk configuration."""

    # Required risk parameters
    stoploss = -0.08  # -8% max loss per trade
    minimal_roi = {
        "0": 0.20,   # 20% take profit immediately
        "60": 0.10,  # 10% after 1 hour
        "120": 0.05, # 5% after 2 hours
    }

    # Position limits
    max_open_trades = 3
    stake_amount = "unlimited"  # Or fixed amount
    tradable_balance_ratio = 0.2  # Use 20% of balance

    # Advanced: Trailing stoploss
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_only_offset_is_reached = True

    # Exchange-level protection
    stoploss_on_exchange = True  # Server-side stoploss

Platform Monitoring Thresholds (TRD-023)

Metric Warning Critical Action
Drawdown >20% >30% Alert, then pause
Win Rate <40% <30% Alert
Sharpe Ratio <1.0 <0.5 Alert, auto-rollback
Profit Factor <1.2 <1.0 Alert

Emergency Controls (TRD-022)

Platform provides circuit breakers: - tradai live pause <strategy> - Pause trading - tradai live stop <strategy> - Stop trading - Emergency stop Lambda - SNS-triggered emergency stop

Template for tradai-strategies

When creating new strategies, use the risk parameter template:

# Copy from: cookiecutter-tradai-strategy/{{cookiecutter.strategy_name}}/strategy.py

# Risk Management (REQUIRED - configure before live trading)
stoploss = -0.05  # -5% max loss per trade (adjust for your risk tolerance)
minimal_roi = {"0": 0.10, "60": 0.05}  # Take profit targets

# Position Limits (REQUIRED)
max_open_trades = 2  # Maximum concurrent positions
stake_amount = "unlimited"  # Or fixed: 100.0 for $100 per trade

# Optional Protections
trailing_stop = False  # Enable for trend-following strategies
stoploss_on_exchange = True  # Recommended for live trading
use_exit_signal = True  # Use exit signals from strategy

Code Quality Standards

  1. Type Hints: 100% coverage (strict mypy)
  2. Docstrings: Google style for all public APIs
  3. Test Coverage: 85%+ target
  4. Absolute Imports: Enforced via Ruff
  5. Frozen Entities: All Pydantic models use frozen=True

Success Criteria

  • [ ] All strategies extend TradAIStrategy
  • [ ] All strategies implement get_metadata()
  • [ ] 85%+ test coverage
  • [ ] 100% type hint coverage
  • [ ] Server-side validation works
  • [ ] MLflow tags generate correctly
  • [ ] Docker labels generate correctly
  • [ ] FreqAI integration works