"""Create strategy wireframe command implementation.""" import logging from pathlib import Path from typing import Any, Dict, Optional try: from .base import BaseCommand, validate_strategy_name except ImportError: import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) from commands.base import BaseCommand, validate_strategy_name logger = logging.getLogger(__name__) class CreateStrategyWireframeCommand(BaseCommand): """Command to create minimal strategy wireframes for maximum LLM flexibility.""" async def execute( self, strategy_name: str, strategy_path: Optional[str] = None, style: str = "minimal", # minimal, guided, structured include_comments: bool = True, include_examples: bool = False, description: Optional[str] = None, **kwargs, ) -> Dict[str, Any]: """Execute create strategy wireframe command. Args: strategy_name: Name of the strategy class and file strategy_path: Custom path for strategy file (optional) style: Wireframe style (minimal, guided, structured) include_comments: Include TODO comments and guidance include_examples: Include example code snippets description: Strategy description to include in docstring Returns: Command execution result with created wireframe details """ try: await self.mcp_log("info", f"Creating strategy wireframe: {strategy_name}") validate_strategy_name(strategy_name) # Determine strategy file path (defaults to the configured strategies directory, # which is where backtest/hyperopt look for strategies) if strategy_path: strategy_file_path = self.resolve_workspace_path(strategy_path) if strategy_file_path.suffix != ".py": raise ValueError("strategy_path must point to a .py file") else: strategies_dir = self.config.full_strategy_dir strategies_dir.mkdir(parents=True, exist_ok=True) strategy_file_path = strategies_dir / f"{strategy_name}.py" # Check if file already exists if strategy_file_path.exists(): return { "command": "create_strategy_wireframe", "success": False, "error": f"Strategy file {strategy_file_path} already exists. Remove it first or use a different name.", "strategy_path": str(strategy_file_path), } # Generate wireframe code wireframe_code = self._generate_wireframe_code( strategy_name=strategy_name, style=style, include_comments=include_comments, include_examples=include_examples, description=description, ) # Create directory if it doesn't exist strategy_file_path.parent.mkdir(parents=True, exist_ok=True) # Write wireframe to file strategy_file_path.write_text(wireframe_code) await self.mcp_log( "info", f"Strategy wireframe created successfully at {strategy_file_path}" ) return { "command": "create_strategy_wireframe", "success": True, "strategy_name": strategy_name, "strategy_path": str(strategy_file_path), "style": style, "wireframe_info": { "lines_of_code": len(wireframe_code.split("\n")), "includes_comments": include_comments, "includes_examples": include_examples, "ready_for_customization": True, }, "next_steps": [ "Customize populate_indicators() with your technical indicators", "Define entry logic in populate_entry_trend()", "Define exit logic in populate_exit_trend()", "Adjust strategy parameters (ROI, stoploss, timeframe)", "Test with backtesting and optimize with hyperopt", ], } except Exception as e: await self.mcp_log("error", f"Create strategy wireframe command failed: {e}") return { "command": "create_strategy_wireframe", "success": False, "error": str(e), "strategy_name": strategy_name, } def _generate_wireframe_code( self, strategy_name: str, style: str, include_comments: bool, include_examples: bool, description: Optional[str], ) -> str: """Generate wireframe code based on style and options.""" if style == "minimal": return self._generate_minimal_wireframe(strategy_name, description, include_comments) elif style == "guided": return self._generate_guided_wireframe( strategy_name, description, include_comments, include_examples ) elif style == "structured": return self._generate_structured_wireframe( strategy_name, description, include_comments, include_examples ) else: return self._generate_minimal_wireframe(strategy_name, description, include_comments) def _generate_minimal_wireframe( self, strategy_name: str, description: Optional[str], include_comments: bool ) -> str: """Generate minimal wireframe - maximum flexibility for LLMs.""" desc = description or f"TODO: Describe your {strategy_name} strategy" comments = { "class_comment": ( f''' \"\"\" {desc} TODO: Implement your trading strategy logic \"\"\"''' if include_comments else "" ), "indicators_comment": ( ''' """ TODO: Add your technical indicators here Examples: RSI, MACD, Bollinger Bands, Moving Averages, etc. """''' if include_comments else "" ), "entry_comment": ( ''' """ TODO: Define your entry conditions Combine indicators and market conditions to create entry signals """''' if include_comments else "" ), "exit_comment": ( ''' """ TODO: Define your exit conditions Set conditions for taking profits or cutting losses """''' if include_comments else "" ), } return f'''\ """{strategy_name} Strategy {desc} Generated wireframe - customize as needed. """ from freqtrade.strategy.interface import IStrategy from pandas import DataFrame import talib.abstract as ta class {strategy_name}(IStrategy):{comments["class_comment"]} # Strategy interface version INTERFACE_VERSION: int = 3 # TODO: Set your preferred timeframe timeframe = '5m' # TODO: Enable if strategy can short can_short: bool = False # TODO: Define your ROI table minimal_roi = {{ "60": 0.01, "30": 0.02, "0": 0.04 }} # TODO: Set your stop loss stoploss = -0.10 # TODO: Configure trailing stop if needed trailing_stop = False def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:{comments["indicators_comment"]} # TODO: Add your indicators here return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:{comments["entry_comment"]} # TODO: Define entry conditions # Example structure: # dataframe.loc[ # ( # # Your conditions here # (dataframe['volume'] > 0) # Always include volume check # ), # 'enter_long'] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:{comments["exit_comment"]} # TODO: Define exit conditions # Example structure: # dataframe.loc[ # ( # # Your exit conditions here # (dataframe['volume'] > 0) # Always include volume check # ), # 'exit_long'] = 1 return dataframe ''' def _generate_guided_wireframe( self, strategy_name: str, description: Optional[str], include_comments: bool, include_examples: bool, ) -> str: """Generate guided wireframe with more hints and examples.""" desc = description or f"Guided wireframe for {strategy_name} strategy" examples = ( """ # Example indicators - uncomment and modify as needed: # dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) # dataframe['macd'] = ta.MACD(dataframe)['macd'] # dataframe['bb_lower'] = ta.BBANDS(dataframe)['lowerband'] # dataframe['sma20'] = ta.SMA(dataframe, timeperiod=20) # dataframe['ema12'] = ta.EMA(dataframe, timeperiod=12) """ if include_examples else "" ) entry_examples = ( """ # Example entry conditions - uncomment and modify: # dataframe.loc[ # ( # (dataframe['rsi'] < 30) & # Oversold # (dataframe['close'] > dataframe['sma20']) & # Above trend # (dataframe['volume'] > dataframe['volume'].rolling(20).mean()) & # High volume # (dataframe['volume'] > 0) # Volume check # ), # 'enter_long'] = 1 """ if include_examples else "" ) exit_examples = ( """ # Example exit conditions - uncomment and modify: # dataframe.loc[ # ( # (dataframe['rsi'] > 70) | # Overbought # (dataframe['close'] < dataframe['sma20']) | # Below trend # (dataframe['volume'] > 0) # Volume check # ), # 'exit_long'] = 1 """ if include_examples else "" ) return f'''\ """{strategy_name} Strategy {desc} This guided wireframe provides structure and examples to help you build your strategy. Follow the TODO comments and uncomment/modify example code as needed. """ from freqtrade.strategy.interface import IStrategy from pandas import DataFrame import talib.abstract as ta import numpy as np class {strategy_name}(IStrategy): """ Guided strategy wireframe with examples and hints. Step 1: Choose your indicators in populate_indicators() Step 2: Define entry logic in populate_entry_trend() Step 3: Define exit logic in populate_exit_trend() Step 4: Tune parameters (ROI, stoploss, etc.) Step 5: Backtest and optimize """ # Strategy interface version - always use 3 INTERFACE_VERSION: int = 3 # TODO: Choose optimal timeframe for your strategy # Common: '1m', '5m', '15m', '1h', '4h', '1d' timeframe = '5m' # TODO: Set to True if strategy can go short can_short: bool = False # TODO: Define ROI table - profits at which to exit # Format: "minutes": profit_ratio minimal_roi = {{ "60": 0.01, # 1% after 60 minutes "30": 0.02, # 2% after 30 minutes "0": 0.04 # 4% immediately }} # TODO: Set stop loss (negative value, e.g., -0.10 = 10% loss) stoploss = -0.10 # TODO: Enable trailing stop to lock in profits trailing_stop = False # trailing_stop_positive = 0.01 # trailing_stop_positive_offset = 0.02 # TODO: Add strategy-specific parameters for optimization # buy_rsi = IntParameter(20, 40, default=30, space="buy") # sell_rsi = IntParameter(60, 80, default=70, space="sell") def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Add technical indicators to the dataframe. This is where you calculate all the indicators you'll use in your entry/exit logic. Common indicators: RSI, MACD, Bollinger Bands, Moving Averages, Volume indicators """ # TODO: Add your indicators here{examples} return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Define entry signals for your strategy. Set 'enter_long' = 1 where you want to enter long positions. Set 'enter_short' = 1 where you want to enter short positions (if can_short=True). Combine multiple indicators for better signals. Always include volume > 0 check. """ # TODO: Define your entry conditions{entry_examples} return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Define exit signals for your strategy. Set 'exit_long' = 1 where you want to exit long positions. Set 'exit_short' = 1 where you want to exit short positions. Note: ROI and stoploss will also trigger exits automatically. """ # TODO: Define your exit conditions{exit_examples} return dataframe # TODO: Uncomment and implement custom methods if needed # def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, # current_rate: float, current_profit: float, **kwargs) -> float: # """Custom stoploss logic""" # return self.stoploss # def confirm_trade_entry(self, pair: str, order_type: str, amount: float, # rate: float, time_in_force: str, current_time: datetime, # entry_tag: Optional[str], side: str, **kwargs) -> bool: # """Confirm trade entry""" # return True ''' def _generate_structured_wireframe( self, strategy_name: str, description: Optional[str], include_comments: bool, include_examples: bool, ) -> str: """Generate structured wireframe with organized sections and advanced features.""" desc = ( description or f"Structured wireframe for {strategy_name} strategy with advanced features" ) return f'''\ """{strategy_name} Strategy {desc} This structured wireframe provides a comprehensive template with organized sections for building sophisticated trading strategies. """ from freqtrade.strategy.interface import IStrategy from freqtrade.strategy import merge_informative_pair, DecimalParameter, IntParameter, BooleanParameter from pandas import DataFrame import talib.abstract as ta import numpy as np from typing import Optional from datetime import datetime class {strategy_name}(IStrategy): """ Structured strategy template with advanced features. Features included: - Hyperopt parameter optimization - Informative timeframes - Custom stoploss and ROI - Entry/exit confirmation - Risk management """ # Strategy interface version INTERFACE_VERSION: int = 3 # === BASIC CONFIGURATION === timeframe = '5m' can_short: bool = False # Startup candle count for indicators startup_candle_count: int = 30 # === ROI & STOPLOSS === minimal_roi = {{ "60": 0.01, "30": 0.02, "0": 0.04 }} stoploss = -0.10 trailing_stop = False # === HYPEROPT PARAMETERS === # TODO: Define parameters for optimization # buy_rsi_enabled = BooleanParameter(default=True, space="buy") # buy_rsi = IntParameter(20, 40, default=30, space="buy") # sell_rsi = IntParameter(60, 80, default=70, space="sell") # roi_p1 = DecimalParameter(0.01, 0.05, default=0.01, space="sell") # roi_p2 = DecimalParameter(0.01, 0.07, default=0.02, space="sell") # roi_p3 = DecimalParameter(0.01, 0.20, default=0.04, space="sell") # === INFORMATIVE TIMEFRAMES === def informative_pairs(self): """ Define additional pairs and timeframes for context. """ pairs = self.dp.current_whitelist() informative_pairs = [] # TODO: Add informative timeframes # for pair in pairs: # informative_pairs.append((pair, '1h')) return informative_pairs def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Add technical indicators organized by category. """ # === TREND INDICATORS === # TODO: Add trend indicators # dataframe['sma20'] = ta.SMA(dataframe, timeperiod=20) # dataframe['ema12'] = ta.EMA(dataframe, timeperiod=12) # dataframe['ema26'] = ta.EMA(dataframe, timeperiod=26) # === MOMENTUM INDICATORS === # TODO: Add momentum indicators # dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) # macd = ta.MACD(dataframe) # dataframe['macd'] = macd['macd'] # dataframe['macdsignal'] = macd['macdsignal'] # === VOLATILITY INDICATORS === # TODO: Add volatility indicators # bollinger = ta.BBANDS(dataframe, timeperiod=20) # dataframe['bb_lower'] = bollinger['lowerband'] # dataframe['bb_middle'] = bollinger['middleband'] # dataframe['bb_upper'] = bollinger['upperband'] # dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) # === VOLUME INDICATORS === # TODO: Add volume indicators # dataframe['volume_sma'] = ta.SMA(dataframe['volume'], timeperiod=20) # === INFORMATIVE TIMEFRAME DATA === # TODO: Merge informative timeframe data # if self.dp: # inf_tf = '1h' # informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf) # informative['rsi_1h'] = ta.RSI(informative, timeperiod=14) # dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Define entry conditions with organized logic. """ conditions_long = [] conditions_short = [] # === LONG ENTRY CONDITIONS === # TODO: Add long entry conditions # Trend conditions # conditions_long.append(dataframe['close'] > dataframe['sma20']) # Momentum conditions # conditions_long.append(dataframe['rsi'] < self.buy_rsi.value) # Volume conditions # conditions_long.append(dataframe['volume'] > dataframe['volume_sma']) # Volatility conditions # conditions_long.append(dataframe['close'] < dataframe['bb_lower']) # Base conditions (always required) conditions_long.append(dataframe['volume'] > 0) # === SHORT ENTRY CONDITIONS === if self.can_short: # TODO: Add short entry conditions # conditions_short.append(dataframe['close'] < dataframe['sma20']) # conditions_short.append(dataframe['rsi'] > 70) conditions_short.append(dataframe['volume'] > 0) # Apply conditions if conditions_long: dataframe.loc[ reduce(lambda x, y: x & y, conditions_long), 'enter_long' ] = 1 if conditions_short: dataframe.loc[ reduce(lambda x, y: x & y, conditions_short), 'enter_short' ] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Define exit conditions with organized logic. """ conditions_long_exit = [] conditions_short_exit = [] # === LONG EXIT CONDITIONS === # TODO: Add long exit conditions # conditions_long_exit.append(dataframe['rsi'] > self.sell_rsi.value) # conditions_long_exit.append(dataframe['close'] > dataframe['bb_upper']) # Base conditions conditions_long_exit.append(dataframe['volume'] > 0) # === SHORT EXIT CONDITIONS === if self.can_short: # TODO: Add short exit conditions # conditions_short_exit.append(dataframe['rsi'] < 30) conditions_short_exit.append(dataframe['volume'] > 0) # Apply conditions if conditions_long_exit: dataframe.loc[ reduce(lambda x, y: x & y, conditions_long_exit), 'exit_long' ] = 1 if conditions_short_exit: dataframe.loc[ reduce(lambda x, y: x & y, conditions_short_exit), 'exit_short' ] = 1 return dataframe # === CUSTOM METHODS === def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: """ Custom stoploss logic - TODO: Implement if needed """ # TODO: Implement custom stoploss logic # Example: Trailing stoploss that becomes more aggressive over time return self.stoploss def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, entry_tag: Optional[str], side: str, **kwargs) -> bool: """ Entry confirmation - TODO: Implement if needed """ # TODO: Add entry confirmation logic # Example: Check for sufficient volume, spread, etc. return True def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, current_time: datetime, **kwargs) -> bool: """ Exit confirmation - TODO: Implement if needed """ # TODO: Add exit confirmation logic return True # === HELPER METHODS === def get_market_condition(self, dataframe: DataFrame) -> str: """ Determine market condition - TODO: Implement if needed """ # TODO: Implement market condition detection # Example: trending, ranging, volatile return "unknown" # TODO: Import reduce function if using conditions from functools import reduce '''