# --- Do not remove these libs --- from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, CategoricalParameter from typing import Dict, List from functools import reduce from pandas import DataFrame import numpy as np import pandas as pd # -------------------------------- # 设置pandas选项以避免FutureWarning pd.set_option('future.no_silent_downcasting', True) import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib from technical.util import resample_to_interval, resampled_merge from freqtrade.persistence import Trade, Order from datetime import datetime, timedelta from typing import Optional import logging logger = logging.getLogger(__name__) # freqtrade plot-dataframe --strategy PatternTrader --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_30.json --timerange=20250309- # freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy PatternTrader --strategy-path ./user_data/Chan/strategies # freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy PatternTrader --strategy-path ./user_data/Chan/strategies --timerange=20251023- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m --pairs BTC/USDT:USDT --timerange=20250501- # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy PatternTrader --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 200 --timerange=20250201-20250401 # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces buy sell roi stoploss --strategy PatternTrader --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 600 --timerange=20250201-20250401 # sudo docker compose run --rm chan_btc backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy PatternTrader --strategy-path ./user_data/Chan/strategies --timerange=20250101- # sudo docker compose run --rm chan_btc download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101- # sudo docker compose run --rm chan_btc trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy PatternTrader --strategy-path ./user_data/Chan/strategies class PatternTrader(IStrategy): """ 极简双均线策略 只使用双均线交叉作为唯一信号 """ INTERFACE_VERSION: int = 3 # 极简参数 fast_ma: IntParameter = IntParameter(5, 15, default=8, space='buy') # 快速均线 slow_ma: IntParameter = IntParameter(20, 50, default=30, space='buy') # 慢速均线 # 添加一个简单的sell空间参数 exit_delay: IntParameter = IntParameter(1, 10, default=3, space='sell') # 出场延迟 # 时间框架 time: IntParameter = IntParameter(15, 60, default=30, space='buy') # ROI 超参 roi_t1: IntParameter = IntParameter(10, 60, default=30, space='roi') roi_t2: IntParameter = IntParameter(60, 240, default=120, space='roi') roi_p1: DecimalParameter = DecimalParameter(0.02, 0.08, default=0.05, decimals=3, space='roi') roi_p2: DecimalParameter = DecimalParameter(0.005, 0.03, default=0.01, decimals=3, space='roi') # 合约交易参数 can_short = True stoploss = -0.02 # 2% 止损 # 杠杆设置 lev: DecimalParameter = DecimalParameter(1.0, 3.0, default=2.0, decimals=1, space='buy') # 运行设置 process_only_new_candles = False startup_candle_count: int = 100 # ROI 外部覆盖 _roi_override: Optional[Dict[str, float]] = None @property def minimal_roi(self) -> Dict[str, float]: """ 基于超参动态生成 ROI 梯度 """ if self._roi_override is not None: return self._roi_override t1 = int(self.roi_t1.value) t2 = int(self.roi_t2.value) times = sorted([t1, t2]) p1 = float(self.roi_p1.value) p2 = float(self.roi_p2.value) profits = sorted([p1, p2], reverse=True) return { "0": profits[0], str(times[0]): profits[1], str(times[1]): 0.0, } @minimal_roi.setter def minimal_roi(self, value: Dict[str, float]) -> None: # 允许框架在解析时覆盖 ROI 设置 self._roi_override = value def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ 计算技术指标(极简版) 只计算双均线 """ res = self.get_ticker_indicator() * int(self.time.value) dataframe_3 = resample_to_interval(dataframe, res) # 只计算双均线 dataframe_3['fast_ma'] = ta.SMA(dataframe_3['close'], timeperiod=int(self.fast_ma.value)) dataframe_3['slow_ma'] = ta.SMA(dataframe_3['close'], timeperiod=int(self.slow_ma.value)) # 计算金叉和死叉 dataframe_3['fast_ma_cross_slow_ma'] = (dataframe_3['fast_ma'] > dataframe_3['slow_ma']) & (dataframe_3['fast_ma'].shift(1) <= dataframe_3['slow_ma'].shift(1)) dataframe_3['fast_ma_cross_slow_ma_down'] = (dataframe_3['fast_ma'] < dataframe_3['slow_ma']) & (dataframe_3['fast_ma'].shift(1) >= dataframe_3['slow_ma'].shift(1)) dataframe = resampled_merge(dataframe, dataframe_3) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ 基于TA指标,填充进场趋势列(极简版) 只使用双均线交叉 """ res = self.get_ticker_indicator() * int(self.time.value) def _pick(df: DataFrame, name: str) -> str: col = f"resample_{res}_{name}" if col in df.columns: return col col2 = f"resample_{float(res)}_{name}" if col2 in df.columns: return col2 cand = [c for c in df.columns if c.endswith(f"_{name}")] return cand[0] if len(cand) else col fast_ma_cross_slow_ma_str = _pick(dataframe, 'fast_ma_cross_slow_ma') fast_ma_cross_slow_ma_down_str = _pick(dataframe, 'fast_ma_cross_slow_ma_down') # 检测多头信号:快线上穿慢线 dataframe.loc[ ( (dataframe[fast_ma_cross_slow_ma_str] == True) & (pd.notna(dataframe[fast_ma_cross_slow_ma_str])) ), ['enter_long', 'enter_tag']] = (1, 'long_signal_simple') # 检测空头信号:快线下穿慢线 dataframe.loc[ ( (dataframe[fast_ma_cross_slow_ma_down_str] == True) & (pd.notna(dataframe[fast_ma_cross_slow_ma_down_str])) ), ['enter_short', 'enter_tag']] = (1, 'short_signal_simple') return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ 基于TA指标,填充出场趋势列(极简版) 反向交叉出场 """ res = self.get_ticker_indicator() * int(self.time.value) def _pick(df: DataFrame, name: str) -> str: col = f"resample_{res}_{name}" if col in df.columns: return col col2 = f"resample_{float(res)}_{name}" if col2 in df.columns: return col2 cand = [c for c in df.columns if c.endswith(f"_{name}")] return cand[0] if len(cand) else col fast_ma_cross_slow_ma_str = _pick(dataframe, 'fast_ma_cross_slow_ma') fast_ma_cross_slow_ma_down_str = _pick(dataframe, 'fast_ma_cross_slow_ma_down') # 做多出场:出现死叉 dataframe.loc[ ( (dataframe[fast_ma_cross_slow_ma_down_str] == True) & (pd.notna(dataframe[fast_ma_cross_slow_ma_down_str])) ), ['exit_long', 'exit_tag']] = (1, 'long_exit_simple') # 做空出场:出现金叉 dataframe.loc[ ( (dataframe[fast_ma_cross_slow_ma_str] == True) & (pd.notna(dataframe[fast_ma_cross_slow_ma_str])) ), ['exit_short', 'exit_tag']] = (1, 'short_exit_simple') return dataframe def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ 杠杆设置 """ return float(self.lev.value) def get_ticker_indicator(self): return int(self.timeframe[:-1]) # 简单的测试函数 def test_strategy(): """ 测试策略基本功能 """ try: # 创建策略实例 strategy = PatternTrader() # 检查基本属性 print("✅ 策略实例化成功") print(f"策略名称: {strategy.__class__.__name__}") print(f"接口版本: {strategy.INTERFACE_VERSION}") print(f"支持做空: {strategy.can_short}") print(f"默认止损: {strategy.stoploss}") # 检查参数 print("\n✅ 策略参数检查:") print(f"布林带长度: {strategy.bb_length.value}") print(f"杠杆: {strategy.lev.value}") print(f"仓位比例: {strategy.position_size_pct.value}") print("\n🎉 策略测试通过!") return True except Exception as e: print(f"❌ 策略测试失败: {e}") import traceback traceback.print_exc() return False if __name__ == "__main__": test_strategy()