205 lines
7.9 KiB
Python
205 lines
7.9 KiB
Python
# 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
|