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¶
- TradAIStrategy (
base.py) - Base class extending IStrategy - Adds
get_metadata()abstract method (REQUIRED) - Adds
validate_configuration()hook (optional) - Includes StrategyDebugMixin for development
-
Full Freqtrade compatibility (all IStrategy hooks work)
-
StrategyMetadata (
metadata.py) - Pydantic model for strategy info - Required: name, version, description, timeframe, category
- Optional: can_short, tags, author, status, parameters
- Live trading fields: trading_modes, exchange, pairs, stake_currency
-
Methods:
to_mlflow_tags(),to_docker_labels() -
Type-Safe Enums (
enums.py) SignalDirection- LONG, SHORTStrategyStatus- ACTIVE, DEPRECATED, TESTINGStrategyCategory- TREND_FOLLOWING, MEAN_REVERSION, BREAKOUT, MOMENTUM, ARBITRAGEBacktestStatus- PENDING, RUNNING, COMPLETED, FAILEDValidationLevel- BASIC, STANDARD, STRICT-
PredictionSource- CSV, MLFLOW -
StrategyValidator (
validation.py) - Server-side validation - Validates strategy class structure
- Validates metadata completeness
-
Validates Freqtrade method signatures
-
Filters (
filters.py) - Signal filtering utilities apply_filters()- Apply multiple filters to signals-
apply_volatility_filter()- Filter by ATR/volatility -
FreqAI Integration (
freqai/) config_builder.py- FreqAI config constructionprediction_model.py- Prediction loading utilitiestraining_model.py- Training utilities-
utils.py- FreqAI helper functions -
Debug Utilities (
debug.py) StrategyDebugMixin- Debug methods for developmentcheck_nan_indicators()- Detect NaN in indicators-
print_indicator_summary()- Print latest candle values -
Quality Checks
sanity/- Runtime sanity checkspreflight/- Pre-execution validationci/- CI/CD gate validation-
linting.py- Static analysis rules -
Validation Entities (
validation_entities.py) - Unified validation types CheckSeverity- ERROR, WARNING, INFO severity levelsValidationIssue- Base class for all validation issuesMetricValidationIssue- Issues with threshold violations-
ValidationResultMixin- Shared aggregation properties -
Strategy Loader (
loading/) - Dynamic strategy class loadingStrategyLoader- Loads strategy classes from file paths or module names- Validates loaded classes extend
TradAIStrategy - Used by strategy-service for runtime strategy discovery
-
Testing Utilities (
testing/) - Test helpersdata.py- OHLCV dataframe factoriesfixtures.py- Pytest fixtures for strategy testing
What We Don't Provide¶
- Strategy implementations - Live in separate
tradai-strategiesrepo - Backtesting engine - Use Freqtrade directly
- Data fetching - Use
tradai-datalibrary - Common utilities - Use
tradai-commonlibrary
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¶
- Unit Tests (
tests/unit/) - Test metadata validation
- Test enum serialization
- Test validator logic
-
Test validation entities
-
Integration Tests (
tests/integration/) - Test strategy loading with Freqtrade
-
Test FreqAI integration
-
Example Tests (
tests/examples/) - Verify example strategies work
-
Validate with sample data
-
Testing Utilities (
testing/) Thetradai.strategy.testingmodule 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:
- Freqtrade Native Features - All risk management is handled by Freqtrade:
stoploss- Maximum loss per trademinimal_roi- Take profit targetsmax_open_trades- Position limitsstake_amount/tradable_balance_ratio- Position sizing-
stoploss_on_exchange- Exchange-level stop loss -
Platform Role - TradAI platform provides:
- Monitoring: Track drawdown, win rate, Sharpe ratio (TRD-023)
- Alerting: Notify when SLA thresholds breached (TRD-024)
- Circuit Breakers: Pause/stop trading on critical failures (TRD-022)
- 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¶
- Type Hints: 100% coverage (strict mypy)
- Docstrings: Google style for all public APIs
- Test Coverage: 85%+ target
- Absolute Imports: Enforced via Ruff
- 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