diff --git a/freqtrade/templates/df.diff b/freqtrade/templates/df.diff new file mode 100644 index 00000000..a51307bf --- /dev/null +++ b/freqtrade/templates/df.diff @@ -0,0 +1,64 @@ +--- original_strategy.py ++++ modified_strategy.py +@@ -297,6 +297,9 @@ class FreqaiPrimer(IStrategy): + dataframe["bb_lowerband"] = lowerband + dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) + dataframe["volume_mean_20"] = dataframe["volume"].rolling(20).mean() + dataframe["volume_std_20"] = dataframe["volume"].rolling(20).std() + dataframe["volume_z_score"] = (dataframe["volume"] - dataframe["volume_mean_20"]) / dataframe["volume_std_20"] ++ # 计算15分钟ATR和3分钟EMA10、EMA20 ++ dataframe_15m = self.dp.get_pair_dataframe(pair=pair, timeframe="15m") ++ cuyuan ++ dataframe_15m['atr15m'] = ta.ATR(dataframe_15m, timeperiod=14) ++ dataframe['atr15m'] = dataframe_15m['atr15m'].reindex(dataframe.index, method="ffill") ++ dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) ++ dataframe['ema20'] = ta.EMA(dataframe, timeperiod=20) + +@@ -390,8 +390,11 @@ class FreqaiPrimer(IStrategy): + conditions = [] + + if "&-price_value_divergence" in dataframe.columns: + cond1 = (dataframe["&-price_value_divergence"] > self.sell_threshold * 1.025) +- cond2 = (dataframe["rsi"] > 75) ++ rsi_threshold = 70 if trend_score > 50 else 65 # 动态RSI阈值 ++ cond2 = (dataframe["rsi"] > rsi_threshold) ++ cond3 = (dataframe['ema10'] < dataframe['ema20']) # 短期均线交叉 + sell_condition = cond1 | cond2 + conditions.append(sell_condition) + +@@ -442,6 +442,13 @@ class FreqaiPrimer(IStrategy): + initial_stake_amount = trade.stake_amount / 3 + logger.debug(f"{pair} 首次入场金额: {initial_stake_amount:.2f}, 当前持仓金额: {trade.stake_amount:.2f}, " + f"加仓次数: {trade.nr_of_successful_entries - 1}") + ++ # 分批出场:短期涨幅触发 ++ short_term_return = (current_rate - dataframe['close'].shift(3).iloc[-1]) / dataframe['close'].shift(3).iloc[-1] ++ if short_term_return >= 0.03: # 3%涨幅 ++ reduce_amount = -0.5 * trade.stake_amount # 卖出50%仓位 ++ logger.info(f"{pair} 短期涨幅 {short_term_return*100:.2f}%,卖出50%仓位 {abs(reduce_amount):.2f}") ++ return (reduce_amount, f"Short-term gain {short_term_return*100:.2f}%") ++ + # 加仓逻辑 + max_entry_adjustments = self.MAX_ENTRY_POSITION_ADJUSTMENT.value +@@ -484,12 +484,11 @@ class FreqaiPrimer(IStrategy): + # 追踪止损逻辑 + trailing_stop_start = self.TRAILING_STOP_START.value + trailing_stop_distance = self.TRAILING_STOP_DISTANCE.value +- # 使用 Sigmoid 映射调整追踪止损参数 +- sigmoid = 1 / (1 + np.exp(-0.1 * (trend_score - 50))) +- trailing_factor = 0.8 + (1.2 - 0.8) * sigmoid # 牛市(100) -> 1.2, 熊市(0) -> 0.8 +- distance_factor = 0.7 + (1.5 - 0.7) * sigmoid # 牛市(100) -> 1.5, 熊市(0) -> 0.7 +- trailing_stop_start *= trailing_factor +- trailing_stop_distance *= distance_factor ++ # 使用15分钟ATR动态调整止损距离 ++ atr15m = dataframe['atr15m'].iloc[-1] ++ trailing_stop_distance = min(max(2 * atr15m / current_rate, 0.01), 0.05) # 2倍ATR,限制在0.01-0.05 ++ if trend_score > 50: # 强趋势时放宽止损 ++ trailing_stop_distance *= 1.2 ++ trailing_stop_price = max_rate * (1 - trailing_stop_distance) + + if profit_ratio >= trailing_stop_start and not self.trailing_stop_enabled: + self.trailing_stop_enabled = True + trade.adjust_min_max_rates(current_rate, current_rate) + logger.info(f"{pair} 价格上涨超过 {trailing_stop_start*100:.1f}%,启动追踪止损") + return None diff --git a/freqtrade/templates/freqaiprimer.json.orig b/freqtrade/templates/freqaiprimer.json.orig new file mode 100644 index 00000000..a2e0241e --- /dev/null +++ b/freqtrade/templates/freqaiprimer.json.orig @@ -0,0 +1,35 @@ +{ + "strategy_name": "FreqaiPrimer", + "params": { + "roi": {}, + "stoploss": { + "stoploss": -0.05 + }, + "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.015, + "TRAILING_STOP_START": 0.016 + }, + "protection": {}, + "trailing": { + "trailing_stop": true, + "trailing_stop_positive": 0.106, + "trailing_stop_positive_offset": 0.196, + "trailing_only_offset_is_reached": false + } + }, + "ft_stratparam_v": 1, + "export_time": "2025-07-01 14:51:29.420394+00:00" +} diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index aefc89e2..0c956332 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -352,20 +352,67 @@ class FreqaiPrimer(IStrategy): conditions = [] if "&-price_value_divergence" in dataframe.columns: - cond1 = (dataframe["&-price_value_divergence"] > self.sell_threshold * 1.025) - cond2 = (dataframe["rsi"] > 75) - sell_condition = cond1 | cond2 + # 计算额外指标:StochRSI、ADX 和短期价格变化 + stochrsi = ta.STOCHRSI(dataframe, timeperiod=14, fastk_period=3, fastd_period=3) + dataframe["stochrsi_k"] = stochrsi["fastk"] + dataframe["adx"] = ta.ADX(dataframe, timeperiod=14) + # 计算短期价格涨幅(最近 5 根 K 线,约 15 分钟) + dataframe["short_term_return"] = dataframe["close"].pct_change(5) * 100 # 百分比回报 + + # 获取市场趋势得分 + trend_score = self.get_market_trend(dataframe=dataframe, metadata={'pair': pair}) + + # 条件 1:高阈值 &-price_value_divergence + cond1 = ( + (dataframe["&-price_value_divergence"] > self.sell_threshold * 1.06) & # 提高到 1.06 + (dataframe["adx"] > 20) # 趋势强度过滤 + ) + + # 条件 2:超买信号 + cond2 = ( + (dataframe["rsi"] > 65) & + (dataframe["stochrsi_k"] > 70) & # StochRSI 超买 + (dataframe["adx"] > 25) # 趋势强度 + ) + + # 条件 3:快速拉升退出 + # 检测最近 5 根 K 线(约 15 分钟)涨幅超过 3%,且有最低 2% 利润,结合 StochRSI 超买 + min_profit = 0.02 # 最低利润 2% + rapid_rise_threshold = self.linear_map(trend_score, 0, 100, 4.0, 2.5) # 熊市 4%,牛市 2.5% + cond3 = ( + (dataframe["short_term_return"] > rapid_rise_threshold) & # 短期快速拉升 + (dataframe["close"] / dataframe["close"].shift(5) - 1 > min_profit) & # 确保最低利润 + (dataframe["stochrsi_k"] > 80) & # 超买确认 + (dataframe["adx"] > 20) # 趋势强度 + ) + + # 综合卖出条件:根据 trend_score 调整逻辑 + if trend_score > 85: + sell_condition = (cond1 & cond2) | (cond1 & cond3) | (cond2 & cond3) # 中等趋势,至少两个条件满足 + logger.debug(f"[{pair}] 趋势得分 {trend_score:.2f} > 55,需满足至少两个条件") + else: + sell_condition = cond1 | cond2 | cond3 # 弱势趋势,任一条件满足 + logger.debug(f"[{pair}] 趋势得分 {trend_score:.2f} <= 55,任一条件满足") + conditions.append(sell_condition) + # 调试日志 + divergence_value = dataframe["&-price_value_divergence"].iloc[-1] if not dataframe["&-price_value_divergence"].isna().all() else np.nan + rsi_value = dataframe["rsi"].iloc[-1] if not dataframe["rsi"].isna().all() else np.nan + stochrsi_value = dataframe["stochrsi_k"].iloc[-1] if not dataframe["stochrsi_k"].isna().all() else np.nan + adx_value = dataframe["adx"].iloc[-1] if not dataframe["adx"].isna().all() else np.nan + short_term_return = dataframe["short_term_return"].iloc[-1] if not dataframe["short_term_return"].isna().all() else np.nan logger.debug(f"[{pair}] 卖出条件检查 - " - f"&-price_value_divergence > {self.sell_threshold:.6f}: {cond1.iloc[-1]}, " - f"rsi > 75: {cond2.iloc[-1]}") + f"&-price_value_divergence={divergence_value:.6f} > {self.sell_threshold * 1.06:.6f}: {cond1.iloc[-1]}, " + f"rsi={rsi_value:.2f} > 75 & stochrsi_k={stochrsi_value:.2f} > 80: {cond2.iloc[-1]}, " + f"short_term_return={short_term_return:.2f}% > {rapid_rise_threshold:.2f}% & profit > {min_profit*100:.2f}%: {cond3.iloc[-1]}, " + f"adx={adx_value:.2f}, trend_score={trend_score:.2f}") else: logger.warning(f"[{pair}] ⚠️ &-price_value_divergence 列缺失,跳过该条件") if len(conditions) > 0: dataframe.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1 - logger.debug(f"[{pair}] 出场信号触发,条件满足") + logger.info(f"[{pair}] 出场信号触发,条件满足,趋势得分:{trend_score:.2f}") else: logger.debug(f"[{pair}] 无有效卖出条件") diff --git a/tools/exchanges.py b/tools/exchanges.py new file mode 100644 index 00000000..fcdd94f7 --- /dev/null +++ b/tools/exchanges.py @@ -0,0 +1,45 @@ +import pandas as pd + +# 加载回测结果 +df = pd.read_csv('../result/backtest_trades.csv') + +# 转换时间为 datetime 类型 +df['open_date'] = pd.to_datetime(df['open_date']) +df['close_date'] = pd.to_datetime(df['close_date']) + +# 按币种分组 +grouped = df.groupby('pair') + +# 存储最终结果 +results = [] + +for pair, group in grouped: + # 按照开仓时间排序 + group = group.sort_values(by='open_date') + + for _, trade in group.iterrows(): + entry_time = trade['open_date'] + exit_time = trade['close_date'] + entry_price = trade['open_rate'] + exit_price = trade['close_rate'] + profit_abs = trade['profit_abs'] + profit_ratio = trade['profit_ratio'] + + results.append({ + '币种': pair, + '入场时间': entry_time, + '入场价格': entry_price, + '出场时间': exit_time, + '出场价格': exit_price, + '盈利金额': profit_abs, + '盈利比例': f"{profit_ratio * 100:.2f}%" + }) + +# 输出为 DataFrame 并保存到 CSV +trade_log = pd.DataFrame(results) +trade_log.to_csv('../result/trade_log_detailed.csv', index=False) + +print("✅ 已生成详细交易日志:../result/trade_log_detailed.csv") +print(trade_log) + + diff --git a/tools/exchanges.sh b/tools/exchanges.sh new file mode 100755 index 00000000..91209355 --- /dev/null +++ b/tools/exchanges.sh @@ -0,0 +1,4 @@ +cd ../ +source .venv/bin/activate +cd - +python exchanges.py