# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # flake8: noqa: F401 # isort: skip_file # --- Do not remove these libs --- import numpy as np import pandas as pd from pandas import DataFrame from datetime import datetime from typing import Optional, Union from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, IntParameter, merge_informative_pair, stoploss_from_absolute, stoploss_from_open) from freqtrade.strategy.interface import IStrategy import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib class TrendStrategy(IStrategy): """ 趋势策略 - 基于EMA和RSI的趋势跟踪策略 策略逻辑: 买入条件: 1. EMA多头排列 + 短期趋势加速:EMA10 > EMA20 且 EMA10继续上行 2. 双周期RSI深度超卖反弹:6周期RSI和12周期RSI都进入超卖区域 卖出条件: 1. EMA10连续下跌(动量走弱) 2. 空头趋势加速(EMA价差达到近期最大) """ # Strategy interface version - allow new iterations of the strategy INTERFACE_VERSION = 3 # Optimal timeframe for the strategy timeframe = '1d' # Can this strategy go short? can_short: bool = False # Minimal ROI designed for the strategy. # minimal_roi = { # "0": 0.1 # 10% ROI,让止损和卖出条件来控制退出 # } # Optimal stoploss designed for the strategy stoploss = -0.05 # 5% 止损 # Trailing stoploss # trailing_stop = False # trailing_stop_positive = None # trailing_stop_positive_offset = 0.0 # trailing_only_offset_is_reached = False # Run "populate_indicators" only for new candle # process_only_new_candles = False # These values can be overridden in the config # use_exit_signal = True # exit_profit_only = False # ignore_roi_if_entry_signal = False # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 0 # Optional order type mapping # order_types = { # 'entry': 'limit', # 'exit': 'limit', # 'stoploss': 'market', # 'stoploss_on_exchange': False # } # Optional order time in force # order_time_in_force = { # 'entry': 'gtc', # 'exit': 'gtc' # } # 策略参数 ema_10_window = IntParameter(8, 15, default=10, space="buy") ema_20_window = IntParameter(15, 25, default=20, space="buy") rsi_6_window = IntParameter(4, 8, default=6, space="buy") rsi_12_window = IntParameter(10, 15, default=12, space="buy") # RSI超卖阈值 rsi_6_oversold = DecimalParameter(15.0, 30.0, default=21.0, space="buy") rsi_12_oversold = DecimalParameter(20.0, 35.0, default=26.25, space="buy") # 趋势检查窗口(用于RSI超卖检查) rsi_check_window = IntParameter(2, 5, default=3, space="buy") # EMA下跌动量检查窗口(用于卖出条件) ema_fall_window = IntParameter(3, 7, default=5, space="sell") # EMA价差扩大检查窗口(用于卖出条件) diff_check_window = IntParameter(4, 8, default=6, space="sell") def informative_pairs(self): """ Define additional, informative pair/interval combinations to be cached from the exchange. These pairs will automatically be available for use in the `populate_indicators` method. """ return [] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds several different TA indicators to the given DataFrame Performance Note: For the best performance be frugal on the number of indicators you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. :param dataframe: Dataframe with data from the exchange :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategy """ # EMA指标 dataframe['ema_10'] = ta.EMA(dataframe, timeperiod=self.ema_10_window.value) dataframe['ema_20'] = ta.EMA(dataframe, timeperiod=self.ema_20_window.value) # RSI指标 dataframe['rsi_6'] = ta.RSI(dataframe, timeperiod=self.rsi_6_window.value) dataframe['rsi_12'] = ta.RSI(dataframe, timeperiod=self.rsi_12_window.value) # 计算EMA价差(用于卖出条件) dataframe['diff'] = dataframe['ema_20'] - dataframe['ema_10'] macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9,) dataframe["macd"] = macd["macd"] dataframe["macdsignal"] = macd["macdsignal"] dataframe["macdhist"] = macd["macdhist"] dataframe['cci'] = ta.CCI(dataframe, 14) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the entry signal for the given dataframe :param dataframe: DataFrame populated with indicators :param metadata: Additional information, like the currently traded pair :return: DataFrame with entry columns populated """ # 条件1:EMA多头排列 + 短期趋势加速 # EMA10 > EMA20:短期趋势强于长期趋势(多头排列) # EMA10昨日 > 前日:短期均线继续上行,显示动量增强 ema_bullish_cross = ( (dataframe['ema_10'] > dataframe['ema_20']) & (dataframe['ema_10'].shift(1) > dataframe['ema_10'].shift(2)) ) # 条件2:双周期RSI进入深度超卖区 # 过去3天的6周期RSI最高值 < 21,表示近期极度超卖 # 过去3天的12周期RSI最高值 < 26.25,确认中周期也处于超卖状态 rsi_oversold_bounce = ( (dataframe['rsi_6'].shift(1).rolling(window=self.rsi_check_window.value).max() < self.rsi_6_oversold.value) & (dataframe['rsi_12'].shift(1).rolling(window=self.rsi_check_window.value).max() < self.rsi_12_oversold.value) ) # 合并买入条件:任一条件满足即产生买入信号 dataframe.loc[ ema_bullish_cross, 'enter_long' ] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the exit signal for the given dataframe :param dataframe: DataFrame populated with indicators :param metadata: Additional information, like the currently traded pair :return: DataFrame with exit columns populated """ # 条件1:EMA10连续下跌(动量走弱) # 当前EMA10 < 过去5天(不含今日)的最低EMA10值 ema10_falling = ( dataframe['ema_10'] < dataframe['ema_10'].shift(1).rolling(window=self.ema_fall_window.value).min() ) # 条件2:空头趋势加速(价差达到近期最大) # 当前diff是过去6天(含今日)中的最大值,表示空头力量最强 diff_expanding = ( dataframe['diff'] == dataframe['diff'].rolling(window=self.diff_check_window.value).max() ) # 合并卖出条件:两个条件同时满足才卖出 sell_condition = ema10_falling & diff_expanding dataframe.loc[ sell_condition, 'exit_long' ] = 1 return dataframe def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: """ 自定义止损逻辑 这里保持原有的5%止损,但可以根据需要添加更复杂的止损逻辑 """ return self.stoploss