diff --git a/freqtrade/templates/freqaiprimer.json b/freqtrade/templates/freqaiprimer.json index c3874255..95324db3 100644 --- a/freqtrade/templates/freqaiprimer.json +++ b/freqtrade/templates/freqaiprimer.json @@ -8,33 +8,33 @@ "max_open_trades": 5 }, "buy": { - "ADD_POSITION_THRESHOLD": -0.01, - "BUY_THRESHOLD_MAX": -0.011, - "BUY_THRESHOLD_MIN": -0.079, - "COOLDOWN_PERIOD_MINUTES": 8, - "MAX_ENTRY_POSITION_ADJUSTMENT": 1 + "ADD_POSITION_THRESHOLD": -0.028, + "BUY_THRESHOLD_MAX": -0.003, + "BUY_THRESHOLD_MIN": -0.028, + "COOLDOWN_PERIOD_MINUTES": 4, + "MAX_ENTRY_POSITION_ADJUSTMENT": 2 }, "sell": { - "EXIT_POSITION_RATIO": 0.341, - "SELL_THRESHOLD_MAX": 0.082, - "SELL_THRESHOLD_MIN": 0.012, - "TRAILING_STOP_DISTANCE": 0.016, - "TRAILING_STOP_START": 0.026 + "EXIT_POSITION_RATIO": 0.335, + "SELL_THRESHOLD_MAX": 0.054, + "SELL_THRESHOLD_MIN": 0.001, + "TRAILING_STOP_DISTANCE": 0.005, + "TRAILING_STOP_START": 0.048 }, "protection": {}, "roi": { - "0": 0.057, - "15": 0.025, - "22": 0.013, - "39": 0 + "0": 0.156, + "20": 0.057999999999999996, + "50": 0.018, + "113": 0 }, "trailing": { "trailing_stop": true, - "trailing_stop_positive": 0.139, - "trailing_stop_positive_offset": 0.23700000000000002, + "trailing_stop_positive": 0.172, + "trailing_stop_positive_offset": 0.23199999999999998, "trailing_only_offset_is_reached": false } }, "ft_stratparam_v": 1, - "export_time": "2025-06-22 18:22:25.969981+00:00" + "export_time": "2025-06-23 00:37:56.498155+00:00" } \ No newline at end of file diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index 0a0a2d9e..dfd52010 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -537,131 +537,139 @@ class FreqaiPrimer(IStrategy): return adjusted_rate def get_market_trend(self, dataframe: DataFrame = None, metadata: dict = None) -> int: - """ 计算市场趋势得分,0 表示强熊,100 表示强牛,50 表示中性。 - 使用对数函数映射,非线性增强两端趋势。 - 综合价格趋势、K 线形态、StochRSI 和量价关系。 - + 综合 3m、15m 和 1h 时间框架的趋势,使用对数函数映射,非线性增强两端趋势。 + 融合价格趋势、K 线形态、StochRSI 和量价关系。 + 参数: - dataframe: 可选,外部传入的 BTC/USDT 数据,默认为 None(自动获取)。 - + dataframe: 可选,外部传入的 BTC/USDT 数据(3m 时间框架),默认为 None(自动获取)。 + metadata: 可选,包含币种信息。 + 返回值: int: 0-100 的趋势得分。 """ try: - # 获取 BTC/USDT 数据 - if dataframe is None: - btc_df = self.dp.get_pair_dataframe("BTC/USDT", self.timeframe) - else: - btc_df = dataframe + # 初始化得分和权重 + timeframes = ["3m", "15m", "1h"] + weights = {"3m": 0.5, "15m": 0.3, "1h": 0.2} # 短期框架权重更高 + trend_scores = {} - if len(btc_df) < 200: - logger.warning("BTC 数据不足,返回默认趋势得分:50") - return 50 - - # 示例:使用 metadata 记录币种信息 pair = metadata.get('pair', 'Unknown') if metadata else 'Unknown' - logger.debug(f"[{pair}] 正在计算市场趋势得分") - # 获取 BTC/USDT 数据 - btc_df = dataframe if dataframe is not None else self.dp.get_pair_dataframe("BTC/USDT", self.timeframe) - if len(btc_df) < 200: - logger.warning("BTC 数据不足,返回默认趋势得分:50") - return 50 + logger.debug(f"[{pair}] 正在计算多时间框架市场趋势得分") - # --- 价格趋势 --- - btc_df["ema50"] = ta.EMA(btc_df, timeperiod=50) - btc_df["ema200"] = ta.EMA(btc_df, timeperiod=200) - btc_df["ema50_slope"] = (btc_df["ema50"] - btc_df["ema50"].shift(10)) / btc_df["ema50"].shift(10) - - price_above_ema = btc_df["close"].iloc[-1] > btc_df["ema200"].iloc[-1] - ema50_above_ema200 = btc_df["ema50"].iloc[-1] > btc_df["ema200"].iloc[-1] - ema50_slope = btc_df["ema50_slope"].iloc[-1] - - price_score = 0 - if price_above_ema: - price_score += 20 - if ema50_above_ema200: - price_score += 20 - if ema50_slope > 0.005: - price_score += 15 - elif ema50_slope < -0.005: - price_score -= 15 + for tf in timeframes: + # 获取对应时间框架的数据 + if tf == "3m" and dataframe is not None: + btc_df = dataframe + else: + btc_df = self.dp.get_pair_dataframe("BTC/USDT", tf) - # --- K 线形态 --- - btc_df["bullish_engulfing"] = ( - (btc_df["close"].shift(1) < btc_df["open"].shift(1)) & # 前一天阴线 - (btc_df["close"] > btc_df["open"]) & # 当天阳线 - (btc_df["close"] > btc_df["open"].shift(1)) & # 吞没前一天开盘价 - (btc_df["open"] < btc_df["close"].shift(1)) # 吞没前一天收盘价 - ) - btc_df["bearish_engulfing"] = ( - (btc_df["close"].shift(1) > btc_df["open"].shift(1)) & # 前一天阳线 - (btc_df["close"] < btc_df["open"]) & # 当天阴线 - (btc_df["close"] < btc_df["open"].shift(1)) & # 吞没前一天开盘价 - (btc_df["open"] > btc_df["close"].shift(1)) # 吞没前一天收盘价 - ) - - kline_score = 0 - if btc_df["bullish_engulfing"].iloc[-1]: - kline_score += 15 - elif btc_df["bearish_engulfing"].iloc[-1]: - kline_score -= 15 - # 近期波动性 - volatility = btc_df["close"].pct_change(10).std() * 100 - if volatility > 0.5: - kline_score += 10 if price_score > 0 else -10 + # 检查数据量 + min_candles = 200 if tf == "3m" else 100 if tf == "15m" else 50 # 不同时间框架所需最小数据量 + if len(btc_df) < min_candles: + logger.warning(f"BTC 数据不足({tf},{len(btc_df)} 根K线),使用默认得分:50") + trend_scores[tf] = 50 + continue - # --- StochRSI --- - stochrsi = ta.STOCHRSI(btc_df, timeperiod=14, fastk_period=3, fastd_period=3) - btc_df["stochrsi_k"] = stochrsi["fastk"] - btc_df["stochrsi_d"] = stochrsi["fastd"] - - stochrsi_score = 0 - stochrsi_k = btc_df["stochrsi_k"].iloc[-1] - stochrsi_d = btc_df["stochrsi_d"].iloc[-1] - if stochrsi_k > 80 and stochrsi_k < stochrsi_d: - stochrsi_score -= 15 # 超买且 K 下穿 D - elif stochrsi_k < 20 and stochrsi_k > stochrsi_d: - stochrsi_score += 15 # 超卖且 K 上穿 D - elif stochrsi_k > 50: - stochrsi_score += 5 - elif stochrsi_k < 50: - stochrsi_score -= 5 + # --- 价格趋势 --- + ema_short_period = 50 if tf == "3m" else 20 if tf == "15m" else 12 # 调整周期以适应时间框架 + ema_long_period = 200 if tf == "3m" else 80 if tf == "15m" else 50 + btc_df["ema_short"] = ta.EMA(btc_df, timeperiod=ema_short_period) + btc_df["ema_long"] = ta.EMA(btc_df, timeperiod=ema_long_period) + btc_df["ema_short_slope"] = (btc_df["ema_short"] - btc_df["ema_short"].shift(10)) / btc_df["ema_short"].shift(10) - # --- 量价关系 --- - btc_df["volume_mean_20"] = btc_df["volume"].rolling(20).mean() - btc_df["volume_std_20"] = btc_df["volume"].rolling(20).std() - btc_df["volume_z_score"] = (btc_df["volume"] - btc_df["volume_mean_20"]) / btc_df["volume_std_20"] - btc_df["adx"] = ta.ADX(btc_df, timeperiod=14) - - volume_score = 0 - if btc_df["volume_z_score"].iloc[-1] > 1.5: - volume_score += 10 if price_score > 0 else -10 - if btc_df["adx"].iloc[-1] > 25: - volume_score += 10 if price_score > 0 else -10 + price_above_ema = btc_df["close"].iloc[-1] > btc_df["ema_long"].iloc[-1] + ema_short_above_ema_long = btc_df["ema_short"].iloc[-1] > btc_df["ema_long"].iloc[-1] + ema_short_slope = btc_df["ema_short_slope"].iloc[-1] - # --- 综合得分 --- - raw_score = price_score + kline_score + stochrsi_score + volume_score - # 归一化到 [-50, 50] - raw_score = max(min(raw_score, 50), -50) - - # 对数映射到 [0, 100],50 为中性 - if raw_score >= 0: - # 正向:50 到 100,log(x + 1) 确保非线性 - mapped_score = 50 + 50 * (np.log1p(raw_score / 50) / np.log1p(1)) - else: - # 负向:0 到 50,log(1 - x) - mapped_score = 50 * (np.log1p(-raw_score / 50) / np.log1p(1)) - - final_score = int(round(mapped_score)) + price_score = 0 + if price_above_ema: + price_score += 20 + if ema_short_above_ema_long: + price_score += 20 + if ema_short_slope > 0.005: + price_score += 15 + elif ema_short_slope < -0.005: + price_score -= 15 + + # --- K 线形态 --- + btc_df["bullish_engulfing"] = ( + (btc_df["close"].shift(1) < btc_df["open"].shift(1)) & + (btc_df["close"] > btc_df["open"]) & + (btc_df["close"] > btc_df["open"].shift(1)) & + (btc_df["open"] < btc_df["close"].shift(1)) + ) + btc_df["bearish_engulfing"] = ( + (btc_df["close"].shift(1) > btc_df["open"].shift(1)) & + (btc_df["close"] < btc_df["open"]) & + (btc_df["close"] < btc_df["open"].shift(1)) & + (btc_df["open"] > btc_df["close"].shift(1)) + ) + + kline_score = 0 + if btc_df["bullish_engulfing"].iloc[-1]: + kline_score += 15 + elif btc_df["bearish_engulfing"].iloc[-1]: + kline_score -= 15 + volatility = btc_df["close"].pct_change(10).std() * 100 + if volatility > 0.5: + kline_score += 10 if price_score > 0 else -10 + + # --- StochRSI --- + stochrsi = ta.STOCHRSI(btc_df, timeperiod=14, fastk_period=3, fastd_period=3) + btc_df["stochrsi_k"] = stochrsi["fastk"] + btc_df["stochrsi_d"] = stochrsi["fastd"] + + stochrsi_score = 0 + stochrsi_k = btc_df["stochrsi_k"].iloc[-1] + stochrsi_d = btc_df["stochrsi_d"].iloc[-1] + if stochrsi_k > 80 and stochrsi_k < stochrsi_d: + stochrsi_score -= 15 + elif stochrsi_k < 20 and stochrsi_k > stochrsi_d: + stochrsi_score += 15 + elif stochrsi_k > 50: + stochrsi_score += 5 + elif stochrsi_k < 50: + stochrsi_score -= 5 + + # --- 量价关系 --- + btc_df["volume_mean_20"] = btc_df["volume"].rolling(20).mean() + btc_df["volume_std_20"] = btc_df["volume"].rolling(20).std() + btc_df["volume_z_score"] = (btc_df["volume"] - btc_df["volume_mean_20"]) / btc_df["volume_std_20"] + btc_df["adx"] = ta.ADX(btc_df, timeperiod=14) + + volume_score = 0 + if btc_df["volume_z_score"].iloc[-1] > 1.5: + volume_score += 10 if price_score > 0 else -10 + if btc_df["adx"].iloc[-1] > 25: + volume_score += 10 if price_score > 0 else -10 + + # --- 综合得分 --- + raw_score = price_score + kline_score + stochrsi_score + volume_score + raw_score = max(min(raw_score, 50), -50) + + # 对数映射到 [0, 100] + if raw_score >= 0: + mapped_score = 50 + 50 * (np.log1p(raw_score / 50) / np.log1p(1)) + else: + mapped_score = 50 * (np.log1p(-raw_score / 50) / np.log1p(1)) + + trend_scores[tf] = max(0, min(100, int(round(mapped_score)))) + logger.debug(f"[{pair}] {tf} 趋势得分:{trend_scores[tf]}, 原始得分:{raw_score}, " + f"价格得分:{price_score}, K线得分:{kline_score}, " + f"StochRSI得分:{stochrsi_score}, 量价得分:{volume_score}") + + # 加权融合多时间框架得分 + final_score = sum(trend_scores[tf] * weights[tf] for tf in timeframes) + final_score = int(round(final_score)) final_score = max(0, min(100, final_score)) - - logger.debug(f"BTC 趋势得分:{final_score}, 原始得分:{raw_score}, " - f"价格得分:{price_score}, K线得分:{kline_score}, " - f"StochRSI得分:{stochrsi_score}, 量价得分:{volume_score}") + + logger.info(f"[{pair}] 最终趋势得分:{final_score}, " + f"3m得分:{trend_scores.get('3m', 50)}, 15m得分:{trend_scores.get('15m', 50)}, " + f"1h得分:{trend_scores.get('1h', 50)}") return final_score - + except Exception as e: - logger.error(f"获取市场趋势失败:{e}", exc_info=True) + logger.error(f"[{pair}] 获取市场趋势失败:{e}", exc_info=True) return 50