From 016ddbf5b13481db2b9b06cbd3071fa1ba75ca65 Mon Sep 17 00:00:00 2001 From: Beast Trader Date: Sun, 7 Jun 2026 23:47:00 +0800 Subject: [PATCH] =?UTF-8?q?v1.4:=20=E8=BF=9B=E4=B8=80=E6=AD=A5=E7=B2=BE?= =?UTF-8?q?=E7=AE=80=E5=85=A5=E5=9C=BA=E6=9D=A1=E4=BB=B6=20+=20=E6=AD=A2?= =?UTF-8?q?=E6=8D=9F=E6=94=B6=E7=B4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- strategy.py | 224 +++++++++++++++++++++------------------------------- 1 file changed, 91 insertions(+), 133 deletions(-) diff --git a/strategy.py b/strategy.py index 381a554..572b3b9 100644 --- a/strategy.py +++ b/strategy.py @@ -1,19 +1,17 @@ """ -Structure Flow Strategy v1.3 +Structure Flow Strategy v1.4 ======================= 变更记录: - v1.0 (2026-06-07): 纯价格结构策略,D1定方向→4H定位→1H入场,支撑/阻力来自Swing Point - v1.1 (2026-06-07): 修复freqtrade 2026.2 Binance futures bug(use_order_book:true), - 硬止损改为结构失效点,首次futures回测成功(+61.52%) - v1.2 (2026-06-07): 尝试Entry Candle止损(入场K线低点/高点),增加时间止损 - 结果:50笔硬止损全亏,Entry Candle查找有bug,return None退回到25%宽止损 - v1.3 (2026-06-07): ===== 重写 custom_stoploss ===== - 弃用脆弱的Entry Candle查找,改用ATR动态止损: - - 初始止损:入场价 ± 1.0 ATR(紧,快速认输) - - 盈利>1%:移动止损至保本线 - - 盈利>2%:ATR追踪止损,锁定利润 - - 硬止损安全网:-5%(stoploss属性) - 核心哲学:预估错误的交易,早早认输止损离场,不硬扛单 + v1.0 (2026-06-07): 纯价格结构策略,D1定方向→4H定位→1H入场 + v1.1 (2026-06-07): 1H futures,结构止损,首次回测成功(+61.52%) + v1.2 (2026-06-07): Entry Candle止损,bug导致50笔硬止损全亏 + v1.3 (2026-06-07): ATR动态止损,结果-63.72%,胜率20.2% + v1.4 (2026-06-07): ===== 回归纯价格结构止损 ===== + - 完全移除ATR(违背价格行为内核) + - 止损 = support_4h(resistance_4h) ± 缓冲 + - support_4h随新Swing Low自动更新 → 天然追踪止损 + - 新增入场过滤:止损距离>3%则跳过(赔率太差) + 核心哲学:止损必须在价格结构位,不在指标计算结果 """ from datetime import datetime @@ -24,32 +22,21 @@ from freqtrade.strategy import IStrategy, IntParameter, informative from freqtrade.persistence import Trade -class StructureFlowStrategyV13(IStrategy): +class StructureFlowStrategyV14(IStrategy): """ - Structure Flow Strategy v1.3 + Structure Flow Strategy v1.4 — 纯价格结构,零指标 - 核心逻辑: - D1 定宏观方向(HH/HL 上升,LH/LL 下降) - ↓ - 4H 定位结构位(Swing Point → 支撑/阻力区域) - ↓ - 1H 找入场时机(K线形态 + 在结构区域内) - - 止损逻辑(v1.3重写): - - 初始止损:入场价 ± 1.0 ATR(紧,快速认输) - - 盈利 > 1%:移动止损至保本(open_rate ± 0.1%) - - 盈利 > 2%:ATR追踪止损(current_rate ∓ 1.0 ATR) - - 硬止损安全网:-5%(防止极端行情) + 止损逻辑(v1.4重写,完全移除ATR): + - 做多止损 = support_4h - 0.1%缓冲 + - 做空止损 = resistance_4h + 0.1%缓冲 + - support_4h / resistance_4h 随时间更新 → 天然追踪止损 + - 硬止损安全网:-5%(stoploss属性) """ - # ===================== - # 基础属性 - # ===================== - can_short = True - stoploss = -0.05 # 硬止损安全网 5%,实际由 custom_stoploss 动态管理 + stoploss = -0.05 use_custom_stoploss = True - minimal_roi = {"0": 100} # 不设时间止盈,靠移动止损出场 + minimal_roi = {"0": 100} max_open_trades = 1 timeframe = "1h" @@ -60,6 +47,8 @@ class StructureFlowStrategyV13(IStrategy): swing_lookback_d1 = IntParameter(8, 14, default=10, space="buy") swing_lookback_h4 = IntParameter(5, 10, default=8, space="buy") pin_bar_wick_ratio = IntParameter(50, 70, default=60, space="buy") + # 最大可接受止损距离(超过则跳过入场) + max_stop_dist = IntParameter(20, 50, default=30, space="buy") # ===================== # 工具:Swing Point 检测 @@ -71,7 +60,6 @@ class StructureFlowStrategyV13(IStrategy): low: pd.Series, window: int = 5, ) -> tuple[pd.Series, pd.Series]: - """检测 Swing High / Swing Low。""" n = len(high) sh = pd.Series(np.nan, index=high.index, dtype=float) sl = pd.Series(np.nan, index=low.index, dtype=float) @@ -96,7 +84,6 @@ class StructureFlowStrategyV13(IStrategy): swing_high: pd.Series, swing_low: pd.Series, ) -> DataFrame: - """从 Swing Points 构建市场结构信息。""" n = len(high) trend_up_arr = np.full(n, False) @@ -110,40 +97,33 @@ class StructureFlowStrategyV13(IStrategy): sl_prices = [] for i in range(n): - if swing_high.iloc[i] and not np.isnan(high.iloc[i]): - sh_prices.append(high.iloc[i]) + if pd.notna(swing_high.iloc[i]): + sh_prices.append(swing_high.iloc[i]) if len(sh_prices) > 4: sh_prices.pop(0) - if swing_low.iloc[i] and not np.isnan(low.iloc[i]): - sl_prices.append(low.iloc[i]) + if pd.notna(swing_low.iloc[i]): + sl_prices.append(swing_low.iloc[i]) if len(sl_prices) > 4: sl_prices.pop(0) - # 趋势判断 if len(sh_prices) >= 2 and len(sl_prices) >= 2: - latest_sh, prev_sh = sh_prices[-1], sh_prices[-2] - latest_sl, prev_sl = sl_prices[-1], sl_prices[-2] - - if latest_sh > prev_sh and latest_sl > prev_sl: + if sh_prices[-1] > sh_prices[-2] and sl_prices[-1] > sl_prices[-2]: trend_up_arr[i] = True - elif latest_sh < prev_sh and latest_sl < prev_sl: + elif sh_prices[-1] < sh_prices[-2] and sl_prices[-1] < sl_prices[-2]: trend_down_arr[i] = True - else: - # 沿用上一根K线的状态 - trend_up_arr[i] = trend_up_arr[i - 1] if i > 0 else False - trend_down_arr[i] = trend_down_arr[i - 1] if i > 0 else False + elif i > 0: + trend_up_arr[i] = trend_up_arr[i - 1] + trend_down_arr[i] = trend_down_arr[i - 1] elif i > 0: trend_up_arr[i] = trend_up_arr[i - 1] trend_down_arr[i] = trend_down_arr[i - 1] - # 支撑/阻力 if sl_prices: nearest_support[i] = sl_prices[-1] if sh_prices: nearest_resistance[i] = sh_prices[-1] - # 需求/供给区域 c = close.iloc[i] if not np.isnan(nearest_support[i]) and not np.isnan(nearest_resistance[i]): zone_range = nearest_resistance[i] - nearest_support[i] @@ -173,7 +153,6 @@ class StructureFlowStrategyV13(IStrategy): close: pd.Series, pin_bar_wick_ratio: float = 0.6, ) -> tuple[pd.Series, pd.Series, pd.Series, pd.Series]: - """检测 Pin Bar 和 Engulfing 形态。""" body = (close - open_).abs() total_range = (high - low).replace(0, 0.0001) @@ -191,25 +170,6 @@ class StructureFlowStrategyV13(IStrategy): return bullish_pin, bearish_pin, bullish_engulf, bearish_engulf - # ===================== - # 工具:ATR 计算 - # ===================== - - @staticmethod - def _calc_atr( - high: pd.Series, - low: pd.Series, - close: pd.Series, - period: int = 14, - ) -> pd.Series: - """计算 ATR。""" - prev_close = close.shift(1) - tr = pd.concat( - [high - low, (high - prev_close).abs(), (low - prev_close).abs()], - axis=1, - ).max(axis=1) - return tr.rolling(period).mean() - # ================================================================ # 信息时间框架 — D1 宏观结构 # ================================================================ @@ -252,11 +212,6 @@ class StructureFlowStrategyV13(IStrategy): dataframe["resistance"] = structure["resistance"] dataframe["in_demand"] = structure["in_demand"] dataframe["in_supply"] = structure["in_supply"] - - # 4H ATR(保留,可能用于未来优化) - dataframe["atr"] = self._calc_atr( - dataframe["high"], dataframe["low"], dataframe["close"], period=14 - ) return dataframe # ================================================================ @@ -266,7 +221,7 @@ class StructureFlowStrategyV13(IStrategy): def populate_indicators( self, dataframe: DataFrame, metadata: dict ) -> DataFrame: - """1H 级别:K线形态 + ATR。""" + """1H 级别:K线形态(零指标)。""" bullish_pin, bearish_pin, bullish_engulf, bearish_engulf = ( self._detect_candle_patterns( dataframe["open"], @@ -283,11 +238,6 @@ class StructureFlowStrategyV13(IStrategy): dataframe["bullish_signal"] = bullish_pin | bullish_engulf dataframe["bearish_signal"] = bearish_pin | bearish_engulf - # 1H ATR(用于动态止损) - dataframe["atr_1h"] = self._calc_atr( - dataframe["high"], dataframe["low"], dataframe["close"], period=14 - ) - # NaN 安全处理 bool_cols = [ "trend_up_1d", "trend_down_1d", @@ -310,15 +260,19 @@ class StructureFlowStrategyV13(IStrategy): 入场逻辑(1H 时间框架)。 做多条件: - 1. D1 上升结构(trend_up_1d) - 2. 4H 下半区 / 需求区域(in_demand_4h) + 1. D1 上升结构(trend_up_1d)— 宏观方向 + 2. 4H 需求区域(in_demand_4h)— 在支撑附近 3. 1H 看涨 K 线形态(bullish_signal) + 4. 止损距离 ≤ max_stop_dist% — 赔率过滤 做空条件: 1. D1 下降结构(trend_down_1d) - 2. 4H 上半区 / 供给区域(in_supply_4h) + 2. 4H 供给区域(in_supply_4h) 3. 1H 看跌 K 线形态(bearish_signal) + 4. 止损距离 ≤ max_stop_dist% """ + max_dist = self.max_stop_dist.value / 100.0 + # NaN 安全处理 bool_cols = [ "trend_up_1d", "trend_down_1d", @@ -330,19 +284,29 @@ class StructureFlowStrategyV13(IStrategy): if col in dataframe.columns: dataframe[col] = dataframe[col].fillna(False) - # 做多 + # ── 做多 ── + # 止损距离 = (入场价 - support_4h) / 入场价 + # support_4h 已 ffilled,取当前值 + long_stop_dist = (dataframe["open"] - dataframe["support_4h"]) / dataframe["open"] + long_conditions = ( dataframe["trend_up_1d"] & dataframe["in_demand_4h"] & dataframe["bullish_signal"] + & (long_stop_dist <= max_dist) + & (long_stop_dist > 0.003) # 至少0.3%距离(避免support就在眼前) ) dataframe.loc[long_conditions, "enter_long"] = 1 - # 做空 + # ── 做空 ── + short_stop_dist = (dataframe["resistance_4h"] - dataframe["open"]) / dataframe["open"] + short_conditions = ( dataframe["trend_down_1d"] & dataframe["in_supply_4h"] & dataframe["bearish_signal"] + & (short_stop_dist <= max_dist) + & (short_stop_dist > 0.003) ) dataframe.loc[short_conditions, "enter_short"] = 1 @@ -363,7 +327,7 @@ class StructureFlowStrategyV13(IStrategy): return dataframe # ===================== - # 动态止损 — v1.3 重写 + # 动态止损 — v1.4 重写:纯价格结构 # ===================== def custom_stoploss( @@ -377,63 +341,57 @@ class StructureFlowStrategyV13(IStrategy): **kwargs, ) -> float: """ - v1.3 止损逻辑(完全重写): + v1.4 止损逻辑:完全基于价格结构,零指标。 - 核心哲学:「预估错误的交易,早早认输止损离场,而不要硬扛单」 + 止损位: + 做多 → support_4h - 0.1%缓冲(最近4H Swing Low下方) + 做空 → resistance_4h + 0.1%缓冲(最近4H Swing High上方) - 三阶段止损: + support_4h / resistance_4h 随新Swing Point自动更新, + 天然形成追踪止损效果。 - 阶段一(无盈利或微盈利 < 1%): - 止损 = 入场价 ± 1.0 ATR - → 距离近,价格稍有不利变动就止损,快速认输 - - 阶段二(盈利 1% ~ 2%): - 止损移动至保本线(open_rate ± 0.1%) - → 这笔交易已经不亏了,卸下心理负担 - - 阶段三(盈利 > 2%): - 追踪止损 = current_rate ∓ 1.0 ATR - → 价格回调超过1ATR才出场,给趋势足够的呼吸空间 - - 参数说明: - - ATR 来自当前1H K线的 atr_1h 值 - - 如果 ATR 为 NaN,fallback 到 2% 固定止损 - - 最终返回的止损比率不会超过 -5%(硬止损安全网) + 永不返回 None,始终返回显式止损比率。 + 最终截断在 -5% / +5% 安全网内。 """ dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) if dataframe is None or len(dataframe) == 0: + # 极端情况:返回2%固定止损 return -0.02 if not trade.is_short else 0.02 - last_candle = dataframe.iloc[-1] - atr = last_candle.get("atr_1h", np.nan) - - if pd.isna(atr) or atr <= 0: - atr = current_rate * 0.02 - else: - atr = float(atr) - - open_rate = trade.open_rate + last = dataframe.iloc[-1] if not trade.is_short: - # ── 做多 ── - if current_profit <= 0.01: - sl_price = open_rate - atr * 1.0 - elif current_profit <= 0.02: - sl_price = open_rate * 0.999 - else: - sl_price = current_rate - atr * 1.0 - + support = last.get("support_4h", np.nan) + if pd.isna(support) or support <= 0: + return -0.02 # fallback + # 止损 = support_4h 下方 0.1% + sl_price = support * 0.999 sl_ratio = (sl_price / current_rate) - 1.0 return max(sl_ratio, -0.05) - else: - # ── 做空 ── - if current_profit <= 0.01: - sl_price = open_rate + atr * 1.0 - elif current_profit <= 0.02: - sl_price = open_rate * 1.001 - else: - sl_price = current_rate + atr * 1.0 - + resistance = last.get("resistance_4h", np.nan) + if pd.isna(resistance) or resistance <= 0: + return 0.02 # fallback + # 止损 = resistance_4h 上方 0.1% + sl_price = resistance * 1.001 sl_ratio = 1.0 - (sl_price / current_rate) return min(sl_ratio, 0.05) + + # ===================== + # Plot config + # ===================== + + @staticmethod + def plot_config() -> dict: + return { + "main_plot": { + "support_4h": {"color": "green", "type": "line"}, + "resistance_4h": {"color": "red", "type": "line"}, + }, + "subplots": { + "signals": { + "bullish_pinbar": {"color": "green", "type": "scatter"}, + "bearish_pinbar": {"color": "red", "type": "scatter"}, + }, + }, + }