This commit is contained in:
Ubuntu 2025-09-06 00:51:06 +08:00
parent b7065f7292
commit 8210a74b25
3 changed files with 103 additions and 108 deletions

View File

@ -5,65 +5,28 @@
"stoploss": {
"stoploss": -0.14
},
"max_open_trades": {
"max_open_trades": 5
},
"buy": {
"ADD_POSITION_THRESHOLD": -0.021,
"BUY_THRESHOLD_MAX": -0.001,
"BUY_THRESHOLD_MIN": -0.035,
"COOLDOWN_PERIOD_MINUTES": 9,
"MAX_ENTRY_POSITION_ADJUSTMENT": 3
},
"sell": {
"EXIT_POSITION_RATIO": 0.472,
"SELL_THRESHOLD_MAX": 0.065,
"SELL_THRESHOLD_MIN": 0.002,
"TRAILING_STOP_DISTANCE": 0.0125,
"TRAILING_STOP_START": 0.045
},
"protection": {},
"model_training_parameters": {
"price_value_divergence": {
"model": "LightGBMRegressor",
"model_params": {
"n_estimators": 200,
"learning_rate": 0.05,
"num_leaves": 31,
"verbose": -1
}
},
"optimal_first_length": {
"model": "LightGBMClassifier",
"model_params": {
"n_estimators": 150,
"learning_rate": 0.1,
"num_leaves": 15,
"max_depth": 8,
"min_child_samples": 10,
"class_weight": "balanced",
"verbose": -1
}
},
"volatility_forecast": {
"model": "LightGBMRegressor",
"model_params": {
"n_estimators": 180,
"learning_rate": 0.08,
"num_leaves": 25,
"max_depth": 6,
"verbose": -1
}
}
},
"trailing": {
"trailing_stop": true,
"trailing_stop_positive": 0.0125,
"trailing_stop_positive_offset": 0.045,
"trailing_only_offset_is_reached": false
}
},
"max_open_trades": {
"max_open_trades": 5
},
"buy": {
"H1_MAX_CANDLES": 161,
"H1_MAX_CONSECUTIVE_CANDLES": 2,
"H1_RAPID_RISE_THRESHOLD": 0.13,
"bb_length": 17,
"bb_std": 2.0,
"rsi_length": 20,
"rsi_overbought": 70,
"rsi_oversold": 32
},
"sell": {},
"protection": {}
},
"ft_stratparam_v": 1,
"export_time": "2025-08-14 06:30:51.352733+00:00"
}
"export_time": "2025-09-05 16:43:20.987720+00:00"
}

View File

@ -2,13 +2,13 @@ import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="pandas_ta")
import logging
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import pandas_ta as ta
from freqtrade.persistence import Trade
import numpy as np
import datetime
import pandas as pd # 确保导入pandas
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
logger = logging.getLogger(__name__)
@ -83,17 +83,18 @@ class FreqaiPrimer(IStrategy):
timeframe = "3m" # 主时间框架为 3 分钟
can_short = False # 禁用做空
# 自定义指标参数
bb_length = 20
bb_std = 2.0
rsi_length = 14
rsi_overbought = 58 # 超买阈值
rsi_oversold = 42 # 放宽超卖阈值,增加入场信号
bb_length = IntParameter(10, 30, default=20, space="buy", optimize=True, load=True)
bb_std = DecimalParameter(1.0, 3.0, default=2.0, decimals=1, space="buy", optimize=True, load=True)
rsi_length = IntParameter(7, 21, default=14, space="buy", optimize=True, load=True)
rsi_overbought = IntParameter(50, 70, default=58, space="buy", optimize=True, load=True) # 超买阈值
rsi_oversold = IntParameter(30, 50, default=42, space="buy", optimize=True, load=True) # 放宽超卖阈值,增加入场信号
# 剧烈拉升检测参数
H1_MAX_CANDLES = 240 # 检查最近200根1h K线
H1_RAPID_RISE_THRESHOLD = 0.12 # 5%的抬升阈值
H1_MAX_CONSECUTIVE_CANDLES = 1 # 最多连续3根K线内检查
H1_MAX_CANDLES = IntParameter(100, 300, default=240, space="buy", optimize=True, load=True) # 检查最近200根1h K线
H1_RAPID_RISE_THRESHOLD = DecimalParameter(0.05, 0.20, default=0.12, decimals=2, space="buy", optimize=True, load=True) # 5%的抬升阈值
H1_MAX_CONSECUTIVE_CANDLES = IntParameter(1, 5, default=1, space="buy", optimize=True, load=True) # 最多连续3根K线内检查
def informative_pairs(self):
pairs = self.dp.current_whitelist()
@ -110,15 +111,24 @@ class FreqaiPrimer(IStrategy):
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# 计算 3m 周期的指标
bb_3m = ta.bbands(dataframe['close'], length=self.bb_length, std=self.bb_std)
dataframe['bb_lower_3m'] = bb_3m[f'BBL_{self.bb_length}_{self.bb_std}']
dataframe['bb_upper_3m'] = bb_3m[f'BBU_{self.bb_length}_{self.bb_std}']
dataframe['rsi_3m'] = ta.rsi(dataframe['close'], length=self.rsi_length)
bb_3m = ta.bbands(dataframe['close'], length=self.bb_length.value, std=self.bb_std.value)
dataframe['bb_lower_3m'] = bb_3m[f'BBL_{self.bb_length.value}_{self.bb_std.value}']
dataframe['bb_upper_3m'] = bb_3m[f'BBU_{self.bb_length.value}_{self.bb_std.value}']
dataframe['rsi_3m'] = ta.rsi(dataframe['close'], length=self.rsi_length.value)
# 新增 StochRSI 指标
stochrsi_3m = ta.stochrsi(dataframe['close'], length=14, rsi_length=14)
dataframe['stochrsi_k_3m'] = stochrsi_3m['STOCHRSIk_14_14_3_3']
dataframe['stochrsi_d_3m'] = stochrsi_3m['STOCHRSId_14_14_3_3']
stochrsi_3m = ta.stochrsi(dataframe['close'], length=self.rsi_length.value, rsi_length=self.rsi_length.value)
# Dynamically retrieve column names
stochrsi_k_col = [col for col in stochrsi_3m.columns if 'STOCHRSIk' in col][0]
stochrsi_d_col = [col for col in stochrsi_3m.columns if 'STOCHRSId' in col][0]
# Log the dynamically retrieved column names for debugging
logger.info(f"[{metadata['pair']}] Dynamically retrieved StochRSI column names for 3m: K={stochrsi_k_col}, D={stochrsi_d_col}")
# Assign StochRSI values to dataframe
dataframe['stochrsi_k_3m'] = stochrsi_3m[stochrsi_k_col]
dataframe['stochrsi_d_3m'] = stochrsi_3m[stochrsi_d_col]
# 新增 MACD 指标
macd_3m = ta.macd(dataframe['close'], fast=12, slow=26, signal=9)
@ -138,15 +148,28 @@ class FreqaiPrimer(IStrategy):
# 获取 15m 数据
df_15m = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='15m')
df_15m['rsi_15m'] = ta.rsi(df_15m['close'], length=self.rsi_length)
df_15m['rsi_15m'] = ta.rsi(df_15m['close'], length=self.rsi_length.value)
# 计算15m时间框架的EMA50和EMA200
df_15m['ema_50_15m'] = ta.ema(df_15m['close'], length=50)
df_15m['ema_200_15m'] = ta.ema(df_15m['close'], length=200)
# 新增 StochRSI 指标
stochrsi_15m = ta.stochrsi(df_15m['close'], length=14, rsi_length=14)
df_15m['stochrsi_k_15m'] = stochrsi_15m['STOCHRSIk_14_14_3_3']
df_15m['stochrsi_d_15m'] = stochrsi_15m['STOCHRSId_14_14_3_3']
# Calculate StochRSI for 15m timeframe
stochrsi_15m = ta.stochrsi(df_15m['close'], length=self.rsi_length.value, rsi_length=self.rsi_length.value)
# Dynamically retrieve column names for STOCHRSIk and STOCHRSId
stochrsi_k_col = [col for col in stochrsi_15m.columns if 'STOCHRSIk' in col][0]
stochrsi_d_col = [col for col in stochrsi_15m.columns if 'STOCHRSId' in col][0]
# Log the dynamically retrieved column names for debugging
logger.info(f"[{metadata['pair']}] Dynamically retrieved StochRSI column names for 15m: K={stochrsi_k_col}, D={stochrsi_d_col}")
# Assign StochRSI values to dataframe
df_15m['stochrsi_k_15m'] = stochrsi_15m[stochrsi_k_col]
df_15m['stochrsi_d_15m'] = stochrsi_15m[stochrsi_d_col]
# Log the dynamically retrieved column names for debugging
logger.info(f"[{metadata['pair']}] Dynamically retrieved StochRSI column names: K={stochrsi_k_col}, D={stochrsi_d_col}")
# 新增 MACD 指标
macd_15m = ta.macd(df_15m['close'], fast=12, slow=26, signal=9)
@ -171,20 +194,29 @@ class FreqaiPrimer(IStrategy):
df_1h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1h')
# 计算 1h 布林带
bb_1h = ta.bbands(df_1h['close'], length=self.bb_length, std=self.bb_std)
df_1h['bb_lower_1h'] = bb_1h[f'BBL_{self.bb_length}_{self.bb_std}']
df_1h['bb_upper_1h'] = bb_1h[f'BBU_{self.bb_length}_{self.bb_std}']
bb_1h = ta.bbands(df_1h['close'], length=self.bb_length.value, std=self.bb_std.value)
df_1h['bb_lower_1h'] = bb_1h[f'BBL_{self.bb_length.value}_{self.bb_std.value}']
df_1h['bb_upper_1h'] = bb_1h[f'BBU_{self.bb_length.value}_{self.bb_std.value}']
# 计算 1h RSI 和 EMA
df_1h['rsi_1h'] = ta.rsi(df_1h['close'], length=self.rsi_length)
df_1h['rsi_1h'] = ta.rsi(df_1h['close'], length=self.rsi_length.value)
df_1h['ema_50_1h'] = ta.ema(df_1h['close'], length=50) # 1h 50周期EMA
df_1h['ema_200_1h'] = ta.ema(df_1h['close'], length=200) # 1h 200周期EMA
df_1h['trend_1h'] = df_1h['close'] > df_1h['ema_50_1h'] # 1h上涨趋势
# 新增 StochRSI 指标
stochrsi_1h = ta.stochrsi(df_1h['close'], length=14, rsi_length=14)
df_1h['stochrsi_k_1h'] = stochrsi_1h['STOCHRSIk_14_14_3_3']
df_1h['stochrsi_d_1h'] = stochrsi_1h['STOCHRSId_14_14_3_3']
stochrsi_1h = ta.stochrsi(df_1h['close'], length=self.rsi_length.value, rsi_length=self.rsi_length.value)
# Dynamically retrieve column names for STOCHRSIk and STOCHRSId
stochrsi_k_col = [col for col in stochrsi_1h.columns if 'STOCHRSIk' in col][0]
stochrsi_d_col = [col for col in stochrsi_1h.columns if 'STOCHRSId' in col][0]
# Log the dynamically retrieved column names for debugging
logger.info(f"[{metadata['pair']}] Dynamically retrieved StochRSI column names for 1h: K={stochrsi_k_col}, D={stochrsi_d_col}")
# Assign StochRSI values to dataframe
df_1h['stochrsi_k_1h'] = stochrsi_1h[stochrsi_k_col]
df_1h['stochrsi_d_1h'] = stochrsi_1h[stochrsi_d_col]
# 新增 MACD 指标
macd_1h = ta.macd(df_1h['close'], fast=12, slow=26, signal=9)
@ -192,14 +224,6 @@ class FreqaiPrimer(IStrategy):
df_1h['macd_signal_1h'] = macd_1h['MACDs_12_26_9']
df_1h['macd_hist_1h'] = macd_1h['MACDh_12_26_9']
# 验证 MACD 列是否正确生成
logger.info(f"[{metadata['pair']}] 1小时 MACD 列: {list(macd_1h.columns)}")
# 新增 StochRSI 指标
stochrsi_1h = ta.stochrsi(df_1h['close'], length=14, rsi_length=14)
df_1h['stochrsi_k_1h'] = stochrsi_1h['STOCHRSIk_14_14_3_3']
df_1h['stochrsi_d_1h'] = stochrsi_1h['STOCHRSId_14_14_3_3']
# 将 1h 数据重新索引到主时间框架 (3m),增强版时间对齐处理
# 确保重新索引时不引入未来数据
df_1h_indexed = df_1h.set_index('date')
@ -462,7 +486,7 @@ class FreqaiPrimer(IStrategy):
macd_downward = dataframe['macd_1h'] < dataframe['macd_signal_1h']
# 条件4: RSI 进入超买区域
rsi_overbought = dataframe['rsi_1h'] > self.rsi_overbought
rsi_overbought = dataframe['rsi_1h'] > self.rsi_overbought.value
# 合并所有条件
final_condition = breakout_condition | volume_spike | macd_downward | rsi_overbought
@ -490,30 +514,38 @@ class FreqaiPrimer(IStrategy):
return dataframe
# 市场状态已在 populate_indicators 中计算,无需重复获取
# 条件1: 价格接近布林带下轨(允许一定偏差)
close_to_bb_lower_1h = (dataframe['close'] <= dataframe['bb_lower_1h'] * 1.03) # 放宽到3%偏差
bb_deviation = DecimalParameter(0.95, 1.05, default=1.03, decimals=2, space="buy", optimize=True, load=True)
close_to_bb_lower_1h = (dataframe['close'] <= dataframe['bb_lower_1h'] * bb_deviation.value) # 动态优化布林带偏差
# 条件2: RSI 不高于阈值(根据市场状态动态调整)
rsi_strong_bull = IntParameter(40, 60, default=50, space="buy", optimize=True, load=True)
rsi_weak_bull = IntParameter(35, 55, default=45, space="buy", optimize=True, load=True)
rsi_threshold = np.where(
dataframe['market_state'].isin(['strong_bull', 'weak_bull']), 50, 45
dataframe['market_state'].isin(['strong_bull', 'weak_bull']), rsi_strong_bull.value, rsi_weak_bull.value
)
rsi_condition_1h = dataframe['rsi_1h'] < rsi_threshold
# 条件3: StochRSI 处于超卖区域(根据市场状态动态调整)
stochrsi_strong_bull = IntParameter(25, 45, default=35, space="buy", optimize=True, load=True)
stochrsi_weak_bull = IntParameter(15, 35, default=25, space="buy", optimize=True, load=True)
stochrsi_threshold = np.where(
dataframe['market_state'].isin(['strong_bull', 'weak_bull']), 35, 25
dataframe['market_state'].isin(['strong_bull', 'weak_bull']), stochrsi_strong_bull.value, stochrsi_weak_bull.value
)
stochrsi_condition_1h = (dataframe['stochrsi_k_1h'] < stochrsi_threshold) & (dataframe['stochrsi_d_1h'] < stochrsi_threshold)
# 条件4: MACD 上升趋势
macd_condition_1h = dataframe['macd_1h'] > dataframe['macd_signal_1h']
macd_min_diff = DecimalParameter(0.001, 0.02, default=0.01, decimals=3, space="buy", optimize=True, load=True)
macd_condition_1h = (dataframe['macd_1h'] - dataframe['macd_signal_1h']) > macd_min_diff.value
# 条件5: 成交量显著放大(可选条件)
volume_spike = dataframe['volume'] > dataframe['volume_ma'] * 1.5
volume_spike_multiplier = DecimalParameter(1.0, 3.0, default=1.5, decimals=1, space="buy", optimize=True, load=True)
volume_spike = dataframe['volume'] > dataframe['volume_ma'] * volume_spike_multiplier.value
# 条件6: 布林带宽度过滤(避免窄幅震荡)
bb_width_min = DecimalParameter(0.01, 0.05, default=0.02, decimals=3, space="buy", optimize=True, load=True)
bb_width = (dataframe['bb_upper_1h'] - dataframe['bb_lower_1h']) / dataframe['close']
bb_width_condition = bb_width > 0.02 # 布林带宽度大于2%
bb_width_condition = bb_width > bb_width_min.value
# 辅助条件: 3m 和 15m 趋势确认(允许部分时间框架不一致)
trend_confirmation = (dataframe['trend_3m'] == 1) | (dataframe['trend_15m'] == 1)
@ -528,7 +560,7 @@ class FreqaiPrimer(IStrategy):
(volume_spike | bb_width_condition).astype(int) + # 成交量或布林带宽度满足其一即可
trend_confirmation.astype(int)
)
final_condition = condition_count >= 3
final_condition = condition_count >= 3
# 设置入场信号
dataframe.loc[final_condition, 'enter_long'] = 1
@ -564,20 +596,20 @@ class FreqaiPrimer(IStrategy):
df_1h = self.dp.get_pair_dataframe(pair=pair, timeframe='1h')
# 确保有足够的K线数据
if len(df_1h) < self.H1_MAX_CANDLES:
logger.warning(f"[{pair}] 1h K线数据不足 {self.H1_MAX_CANDLES} 根,当前只有 {len(df_1h)} 根,无法完整检测剧烈拉升")
if len(df_1h) < self.H1_MAX_CANDLES.value:
logger.warning(f"[{pair}] 1h K线数据不足 {self.H1_MAX_CANDLES.value} 根,当前只有 {len(df_1h)} 根,无法完整检测剧烈拉升")
return False, None
# 获取最近的K线
recent_data = df_1h.iloc[-self.H1_MAX_CANDLES:].copy()
recent_data = df_1h.iloc[-self.H1_MAX_CANDLES.value:].copy()
# 计算滚动窗口的最大涨幅
window_high = recent_data['high'].rolling(window=self.H1_MAX_CONSECUTIVE_CANDLES).max()
window_low = recent_data['low'].rolling(window=self.H1_MAX_CONSECUTIVE_CANDLES).min()
window_high = recent_data['high'].rolling(window=self.H1_MAX_CONSECUTIVE_CANDLES.value).max()
window_low = recent_data['low'].rolling(window=self.H1_MAX_CONSECUTIVE_CANDLES.value).min()
rise_percentage = (window_high - window_low) / window_low
# 检测剧烈拉升
rapid_rise_detected = (rise_percentage >= self.H1_RAPID_RISE_THRESHOLD).any()
rapid_rise_detected = (rise_percentage >= self.H1_RAPID_RISE_THRESHOLD.value).any()
# 判断是否处于不稳固区域
is_unstable_region = rapid_rise_detected
@ -590,7 +622,7 @@ class FreqaiPrimer(IStrategy):
# 检查是否已过回看期
if unstable_time is not None:
time_since_unstable = (datetime.datetime.now() - unstable_time).total_seconds() / 3600 # 转换为小时
if time_since_unstable > self.H1_MAX_CANDLES * 3: # 回看期为 H1_MAX_CANDLES 根1小时K线
if time_since_unstable > self.H1_MAX_CANDLES.value * 3: # 回看期为 H1_MAX_CANDLES 根1小时K线
is_unstable_region = False # 已过回看期,解封币对
logger.info(f"[{pair}] 剧烈拉升检测结果: {'不稳固' if is_unstable_region else '稳固'}")

View File

@ -246,5 +246,5 @@ docker-compose run --rm freqtrade hyperopt $PAIRS_FLAG \
-e 100 \
-j 4 \
--hyperopt-loss SharpeHyperOptLoss \
--spaces buy sell trailing \
--spaces buy \
--fee 0.0016