修改了线段中枢逻辑

This commit is contained in:
jackyu66git
2026-03-11 03:08:10 +08:00
parent 1c099be23e
commit 5226c551d5
19 changed files with 3386 additions and 324 deletions
+82 -138
View File
@@ -9,68 +9,44 @@ from typing import Optional
from freqtrade.persistence import Trade
import warnings
# 抑制 pandas FutureWarning 关于 fillna 的隐式降级警告
# 这个警告来自 freqtrade 库的 strategy_helper.py
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
# 或者启用未来行为(推荐)
pd.set_option('future.no_silent_downcasting', True)
# freqtrade trade -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategy --strategy-path ./user_data/Chan/strategies
# freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategy --strategy-path ./user_data/Chan/strategies --timerange=20260304-
# freqtrade download-data -c ./user_data/Chan/config/Local_Test.json -t 1m 5m --data-format-ohlcv json --pairs SOL/USDT:USDT --timerange=20260201-
class CryptoFutures1m5mStrategy(IStrategy):
"""
SOL/USDT 合约策略 - 1分钟+5分钟双时间框架 V12e (Short Only)
SOL/USDT 合约策略 - 只做多版 (默认策略)
14个月回测 (2025-01 ~ 2026-03): +107.11%, PF 1.37, DD 23.96%
每个季度均盈利,市场下跌-54%期间持续获利
核心设计:
1. 纯做空策略 - 价格必须低于EMA200至少1%才允许做空
2. 5分钟趋势确认:EMA12<EMA26<EMA50 + ADX 25-50 + RSI 30-48
3. ATR自适应波动率过滤:ATR < 长期均值 * 1.5(避免极端波动)
4. 1分钟精确入场:顶背离 / EMA死叉 / 熊市回调
5. 双重MACD确认(5分钟+1分钟MACD柱状图均为负)
6. trailing_stop_positive_offset = 0.030
7. 时间止损:持仓过久且亏损时提前退出
基于V5修改:禁用做空,只做多
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = True
can_short = False # 禁用做空
can_long = True
lev = 1.0
# 止损止盈
stoploss = -0.025 # 2.5% 硬止损
stoploss = -0.035
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.030
trailing_stop_positive_offset = 0.035
trailing_only_offset_is_reached = True
# 不使用custom_stoploss(会干扰trailing_stop
use_custom_stoploss = False
# 完全禁用 exit_signal
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [
("SOL/USDT:USDT", "5m"),
]
return [("SOL/USDT:USDT", "5m")]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# ==================== 5分钟指标 ====================
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA趋势
# EMA
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
# EMA12斜率(3根K线变化率,用于确认趋势方向的动量)
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
@@ -79,66 +55,54 @@ class CryptoFutures1m5mStrategy(IStrategy):
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX趋势强度
# ADX
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI5分钟)
# RSI
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR5分钟)
# ATR
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
# ATR 长期均值(用于自适应波动率过滤)
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# ===== EMA200 大趋势过滤 =====
# EMA200
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
# EMA200斜率(20根5分钟K线 = 100分钟趋势方向)
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# ===== 大趋势过滤(Short Only =====
# 做空需要价格低于EMA200至少1%
informative['below_ema200'] = informative['ema200_dist_pct'] < -1.0
# 牛市暂停:EMA200上升 + 价格在EMA200上方 → 完全停止做空
informative['bull_pause'] = (
(informative['ema200_slope'] > 0) &
(informative['ema200_dist_pct'] > 0)
# 做多趋势
informative['trend_bull_5m'] = (
(informative['ema12'] > informative['ema26']) &
(informative['ema26'] > informative['ema50']) &
(informative['ema12_slope'] > 0.05) &
(informative['adx_5m'] > 24) &
(informative['adx_5m'] < 51) &
(informative['close'] > informative['ema12']) &
(informative['rsi_5m'] > 52) &
(informative['rsi_5m'] < 72)
)
# ===== 5分钟趋势判断(仅Short =====
informative['trend_bear_5m'] = (
(informative['ema12'] < informative['ema26']) &
(informative['ema26'] < informative['ema50']) &
(informative['ema12_slope'] < 0) &
(informative['adx_5m'] > 25) &
(informative['adx_5m'] < 50) &
(informative['close'] < informative['ema12']) &
(informative['rsi_5m'] < 48) &
(informative['rsi_5m'] > 30)
)
# 大趋势过滤
informative['above_ema200'] = informative['ema200_dist_pct'] > 1.0
# 做条件:短期趋势 + EMA200大趋势方向一致 + 非牛市
informative['can_long_5m'] = False
informative['can_short_5m'] = (
informative['trend_bear_5m'] &
informative['below_ema200'] &
(~informative['bull_pause'])
)
# 做条件
informative['can_long_5m'] = informative['trend_bull_5m'] & informative['above_ema200']
# ATR波动率过滤(自适应)
# ATR过滤
informative['atr_ok_5m'] = (
(informative['atr_pct_5m'] > 0.1) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 1.5)
(informative['atr_pct_5m'] > 0.07) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 2.2)
)
# 合并5分钟数据到1分钟
# 成交量
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * 0.75
# 合并
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# ==================== 1分钟指标 ====================
# 1分钟指标
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
@@ -148,116 +112,96 @@ class CryptoFutures1m5mStrategy(IStrategy):
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
# ===== 1分钟MACD斜率 =====
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# ===== 1分钟做空入场信号 =====
dataframe['price_high_5'] = dataframe['high'].rolling(window=5).max()
dataframe['macd_high_5'] = dataframe['macd'].rolling(window=5).max()
# 做多信号
dataframe['price_low_5'] = dataframe['low'].rolling(window=5).min()
dataframe['macd_low_5'] = dataframe['macd'].rolling(window=5).min()
dataframe['top_divergence'] = (
(dataframe['high'] >= dataframe['price_high_5'] * 0.999) &
(dataframe['macd'] < dataframe['macd_high_5']) &
(dataframe['macd_slope'] < 0) &
(dataframe['macd'] < dataframe['macd_signal']) &
dataframe['bottom_divergence'] = (
(dataframe['low'] <= dataframe['price_low_5'] * 1.001) &
(dataframe['macd'] > dataframe['macd_low_5']) &
(dataframe['macd_slope'] > 0) &
(dataframe['macd'] > dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
dataframe['ema_cross_down'] = (
(dataframe['ema9'] < dataframe['ema21']) &
(dataframe['ema9'].shift(1) >= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] < 55) & (dataframe['rsi'] > 35) &
dataframe['ema_cross_up'] = (
(dataframe['ema9'] > dataframe['ema21']) &
(dataframe['ema9'].shift(1) <= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] > 45) &
(dataframe['rsi'] < 70) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
dataframe['is_bear_candle'] = (
(dataframe['close'] < dataframe['open']) &
((dataframe['open'] - dataframe['close']) / dataframe['open'] > 0.008)
)
dataframe['bear_pullback'] = (
dataframe['is_bear_candle'].shift(2) &
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
(dataframe['high'] < dataframe['high'].shift(2)) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['ema9'])
dataframe['is_bull_candle'] = (dataframe['close'] > dataframe['open']) & ((dataframe['close'] - dataframe['open']) / dataframe['open'] > 0.008)
dataframe['bull_pullback'] = (
dataframe['is_bull_candle'].shift(2) &
(dataframe['close'].shift(1) < dataframe['open'].shift(1)) &
(dataframe['low'] > dataframe['low'].shift(2)) &
(dataframe['close'] > dataframe['open']) &
(dataframe['close'] > dataframe['ema9'])
)
# ==================== 时间过滤 ====================
# 时间过滤
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 安全转换5分钟布尔列
bool_cols = [
'can_long_5m_5m', 'can_short_5m_5m',
'trend_bear_5m_5m',
'atr_ok_5m_5m',
'below_ema200_5m', 'bull_pause_5m',
]
# 类型转换
bool_cols = ['can_long_5m_5m', 'trend_bull_5m_5m', 'atr_ok_5m_5m', 'above_ema200_5m', 'volume_ok_5m_5m']
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
num_cols = ['atr_pct_5m_5m', 'rsi_5m_5m', 'macd_hist_5m_5m', 'atr_pct_ma_5m_5m',
'ema200_dist_pct_5m', 'ema200_slope_5m']
for col in num_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(float).fillna(0.0)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
atr_ok = dataframe['atr_ok_5m_5m']
volume_ok = dataframe['volume_ok_5m_5m']
# 5分钟MACD方向确认
macd_bear_5m = dataframe['macd_hist_5m_5m'] < 0
# 只做多
macd_bull_5m = dataframe['macd_hist_5m_5m'] > 0
macd_bull_1m = dataframe['macd_hist'] > 0
# 1分钟MACD方向确认(双重确认)
macd_bear_1m = dataframe['macd_hist'] < 0
# ===== 做空入场 =====
dataframe.loc[
(time_ok) &
(atr_ok) &
(dataframe['can_short_5m_5m']) &
(macd_bear_5m) &
(macd_bear_1m) &
(dataframe['rsi'] > 30) &
(
dataframe['top_divergence'] |
dataframe['ema_cross_down'] |
dataframe['bear_pullback']
) &
(time_ok) & (atr_ok) & (dataframe['can_long_5m_5m']) &
(macd_bull_5m) & (macd_bull_1m) & (volume_ok) &
(dataframe['rsi'] < 70) & (dataframe['rsi'] > 40) &
(dataframe['bottom_divergence'] | dataframe['ema_cross_up'] | dataframe['bull_pullback']) &
(dataframe['volume'] > 0),
'enter_short'
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
dataframe.loc[:, 'exit_short'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
"""时间止损:持仓过久且亏损时提前退出"""
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
if trade_duration > 8 and current_profit < -0.005:
return 'time_stop_8h'
if trade_duration > 16 and current_profit < 0:
return 'time_stop_16h'
# 做多时间止损 - 宽松
if trade_duration > 10 and current_profit < -0.006:
return 'time_stop_long_10h'
if trade_duration > 20 and current_profit < 0:
return 'time_stop_long_20h'
if trade_duration > 30:
return 'time_stop_long_30h'
return None
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:
"""入场确认 - 时间过滤安全网"""
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
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 self.lev