zhangkun9038@dingtalk.com 184425b1de 加入了protection
2025-05-14 18:25:20 +00:00

160 lines
6.5 KiB
Python

from freqtrade.strategy import IStrategy
from pandas import DataFrame
import talib.abstract as ta
from typing import Optional, Union
from freqtrade.persistence import Trade
from datetime import datetime
class TheForceV7(IStrategy):
# 基础参数
timeframe = '5m'
stoploss = -0.14 # 全局止损
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 5 # 卖出后禁止再次买入的K线数
},
{
"method": "MaxDrawdown",
"lookback_period_candles": 48,
"trade_limit": 20,
"stop_duration_candles": 4,
"max_allowed_drawdown": 0.2 # 最大允许回撤 20%
},
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4, # 在 lookback 内最多触发几次止损
"stop_duration_candles": 2, # 锁定多少根 K 线
"only_per_pair": False # 是否按币种统计
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 6,
"trade_limit": 2,
"stop_duration_candles": 60,
"required_profit": 0.02 # 最低平均收益 2%
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 2,
"required_profit": 0.01 # 最低平均收益 1%
}
]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['ema200c'] = ta.EMA(dataframe['close'], timeperiod=200)
dataframe['ema50c'] = ta.EMA(dataframe['close'], timeperiod=50)
dataframe['ema20c'] = ta.EMA(dataframe['close'], timeperiod=20)
dataframe['rsi7'] = ta.RSI(dataframe['close'], timeperiod=7)
macd = ta.MACD(dataframe['close'])
dataframe['macd'] = macd[0]
dataframe['macdsignal'] = macd[1]
stoch = ta.STOCH(dataframe['high'], dataframe['low'], dataframe['close'], fastk_period=14, slowk_period=3, slowd_period=3)
dataframe['slowk'] = stoch[0]
dataframe['slowd'] = stoch[1]
dataframe['adx'] = ta.ADX(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=14)
dataframe['volvar'] = dataframe['volume'].rolling(window=20).mean()
return dataframe
def crossover(self, series1: DataFrame, series2: DataFrame) -> DataFrame:
"""Detects when series1 crosses above series2."""
return (series1 > series2) & (series1.shift(1) <= series2.shift(1))
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(dataframe['close'] > dataframe['ema200c']) & # Relaxed trend
(dataframe['close'] > dataframe['ema50c']) & # Short-term trend
(dataframe['rsi7'] < 50) & # Relaxed RSI
(dataframe['macd'] > 0) & # Relaxed MACD
(dataframe['volume'] > dataframe['volvar'] * 0.5) & # Relaxed volume
(dataframe['adx'] > 20), # Trend strength
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe['slowk'] > 65) | (dataframe['slowd'] > 65) | # Relaxed STOCH
(dataframe['rsi7'] > 70) # Overbought
) &
(dataframe['close'] < dataframe['ema20c']) & # Trend reversal
(self.crossover(dataframe['macdsignal'], dataframe['macd'])) & # Custom crossover
(dataframe['macd'] < 0), # Downtrend
'exit_long'
] = 1
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe.empty or dataframe['date'].iloc[-1] < current_time: # Fixed: Use date column
return None
last_candle = dataframe.iloc[-1]
atr = ta.ATR(dataframe, timeperiod=14).iloc[-1]
duration = (current_time - trade.open_date).total_seconds() / 60 # Minutes
# Dynamic Take-Profit
take_profit = current_rate + 1.0 * atr # Lowered ATR
if current_rate >= take_profit:
return "take_profit"
# Partial Take-Profit at 2%
if current_profit >= 0.02:
return "partial_take_profit"
# Dynamic Stop-Loss
stop_loss = current_rate - 2.0 * atr # Relaxed ATR
if current_rate <= stop_loss:
return "dynamic_stop_loss"
# Trailing Stop
if current_profit > 0.005: # Lowered threshold
self.trailing_stop = True
self.trailing_stop_positive = 0.003 # 0.3% retracement
self.trailing_stop_positive_offset = 0.008 # 0.8% offset
if current_profit < trade.max_profit - self.trailing_stop_positive:
return "trailing_stop"
# Time Stop
if duration > 60: # 1 hour
return "time_stop"
return None
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: float, max_stake: float,
leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe.empty:
return proposed_stake
atr = ta.ATR(dataframe, timeperiod=14).iloc[-1]
price_std = dataframe['close'].std()
combined_volatility = atr + price_std
base_stake = self.wallets.get_total_stake_amount() * 0.0333 # 3.33% risk
base_stake = min(base_stake, 50.0) # Cap at 50 USDT
risk_factor = 1.0
if combined_volatility > current_rate * 0.03: # High volatility
risk_factor = 0.3 if pair in ['SOL/USDT', 'OKB/USDT'] else 0.5
elif combined_volatility < current_rate * 0.01: # Low volatility
risk_factor = 1.2 if pair in ['BTC/USDT', 'ETH/USDT'] else 1.0
return base_stake * risk_factor