diff --git a/strategy.py b/strategy.py index 304dd16..c6e86d0 100644 --- a/strategy.py +++ b/strategy.py @@ -1,13 +1,14 @@ """ -Structure Flow Strategy v2.0 +Structure Flow Strategy v2.1 ======================= 变更记录: v1.6 (2026-06-07): 最优基线 — +3659.63%, 190笔, 69.3% trailing胜率 - v2.0 (2026-06-08): ===== B1: 入场延迟一根1H bar确认 ===== - 信号触发后,不立即入场,等下一根1H bar收盘。 - 如果收盘价仍在S/R附近(支撑上方1.5%以内 / 阻力下方1.5%以内), - 确认入场;否则放弃本次信号。 - 目的:过滤假突破,减少被噪音震出的止损交易。 + v2.0 (2026-06-08): B1 入场延迟确认 — 方向正确但降频严重 + v2.1 (2026-06-08): ===== D1: 趋势强度过滤 ===== + 在4H级别评估趋势强度:最近2个Swing Point的间距变化。 + 如果趋势在扩张(HH/HL间距增大),允许入场; + 如果趋势在收缩(HH/HL间距缩小或震荡),过滤信号。 + 目的:只在趋势明确时交易,避免震荡市反复止损。 """ from datetime import datetime @@ -18,14 +19,13 @@ from freqtrade.strategy import IStrategy, IntParameter, informative from freqtrade.persistence import Trade -class StructureFlowStrategyV20(IStrategy): +class StructureFlowStrategyV21(IStrategy): """ - Structure Flow Strategy v2.0 — B1: 入场延迟确认 + Structure Flow Strategy v2.1 — D1: 趋势强度过滤 - v2.0改动(相对于v1.6): - 1. 入场延迟1根bar:信号触发后,等下一根bar收盘确认 - 2. 确认条件:收盘价仍在S/R附近(做多在support上方1.5%内,做空在resistance下方1.5%内) - 3. 保留v1.6所有逻辑:冷却期、活支撑/阻力、D1趋势过滤 + v2.1改动(相对于v1.6): + 在4H级别计算趋势强度:最近2个Swing High间距 + Swing Low间距的变化。 + 只有趋势在扩张(或至少不收缩)时才允许入场。 """ can_short = True @@ -44,8 +44,9 @@ class StructureFlowStrategyV20(IStrategy): pin_bar_wick_ratio = IntParameter(50, 70, default=60, space="buy") max_stop_dist = IntParameter(20, 50, default=50, space="buy") cooldown_bars = IntParameter(3, 12, default=6, space="buy") - # v2.0 新增:入场确认阈值 — close必须在S/R附近多少%以内才算确认 - entry_confirm_pct = IntParameter(10, 50, default=50, space="buy") # x/1000 = 1.0%~5.0%, default 5.0% + # v2.1 新增:趋势强度最小扩张比例(x/100 = 0%~50%) + # 0 = 只要不收缩就行;越大要求趋势扩张越强 + trend_strength_min = IntParameter(-50, 20, default=-20, space="buy") # -20=允许SP轻微收缩, 最佳值 # ===================== # 工具:Swing Point 检测 @@ -229,6 +230,49 @@ class StructureFlowStrategyV20(IStrategy): resistance_tested_and_held = touched_resistance & held_resistance dataframe["resistance_alive"] = resistance_tested_and_held.rolling(3, min_periods=1).max() > 0 + # ================================ + # v2.1 新增:趋势强度评估 + # ================================ + # 计算最近2个Swing Point之间的间距变化 + # 上升趋势:HH间距 + HL间距都在扩大 → 趋势强 + # 下降趋势:LH间距 + LL间距都在扩大 → 趋势强 + # 间距缩小 → 趋势减弱/震荡 + + sh_prices = [] + sl_prices = [] + trend_strength_up = np.full(len(dataframe), np.nan) + trend_strength_down = np.full(len(dataframe), np.nan) + + for i in range(len(dataframe)): + if pd.notna(sh.iloc[i]): + sh_prices.append(sh.iloc[i]) + if len(sh_prices) > 4: + sh_prices.pop(0) + if pd.notna(sl.iloc[i]): + sl_prices.append(sl.iloc[i]) + if len(sl_prices) > 4: + sl_prices.pop(0) + + # 上升趋势强度:HH[-1] vs HH[-2], HL[-1] vs HL[-2] + if len(sh_prices) >= 2 and len(sl_prices) >= 2: + # HH间距:最近两个Swing High的差值百分比 + hh_dist = (sh_prices[-1] - sh_prices[-2]) / sh_prices[-2] if sh_prices[-2] > 0 else 0 + # HL间距:最近两个Swing Low的差值百分比 + hl_dist = (sl_prices[-1] - sl_prices[-2]) / sl_prices[-2] if sl_prices[-2] > 0 else 0 + # 上升趋势强度 = HH间距 + HL间距(都正=扩张,一正一负=不确定,都负=收缩) + trend_strength_up[i] = hh_dist + hl_dist + + # 下降趋势强度(取反:间距缩小是负值) + trend_strength_down[i] = -(hh_dist + hl_dist) + + dataframe["trend_strength_up"] = pd.Series(trend_strength_up, index=dataframe.index) + dataframe["trend_strength_down"] = pd.Series(trend_strength_down, index=dataframe.index) + + # 趋势强度是否足够(扩张中) + min_strength = self.trend_strength_min.value / 100.0 # 0~0.30 + dataframe["strong_uptrend"] = dataframe["trend_strength_up"] > min_strength + dataframe["strong_downtrend"] = dataframe["trend_strength_down"] > min_strength + return dataframe # ================================================================ @@ -261,6 +305,7 @@ class StructureFlowStrategyV20(IStrategy): "trend_up_4h", "trend_down_4h", "in_demand_4h", "in_supply_4h", "support_alive_4h", "resistance_alive_4h", + "strong_uptrend_4h", "strong_downtrend_4h", "bullish_signal", "bearish_signal", ] for col in bool_cols: @@ -270,29 +315,19 @@ class StructureFlowStrategyV20(IStrategy): return dataframe # ===================== - # 入场信号 — v2.0 延迟确认 + # 入场信号 # ===================== def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ 入场逻辑(1H 时间框架)。 - v2.0 核心改动:B1 — 入场延迟一根1H bar确认 - - 做多条件: - 1. D1 上升结构(trend_up_1d) - 2. 4H 需求区域(in_demand_4h) - 3. 1H 看涨 K 线形态(bullish_signal) - 4. 止损距离 ≤ max_stop_dist% - 5. 支撑位是"活"的(support_alive_4h) - 6. 6h内没有过同方向入场信号(冷却期) - 7. [v2.0 NEW] 上一根bar触发了信号,当前bar收盘确认: - 做多:close 仍在 support_4h 上方 entry_confirm_pct% 以内 - 做空:close 仍在 resistance_4h 下方 entry_confirm_pct% 以内 + v2.1 核心改动:D1 — 趋势强度过滤 + 做多额外条件:4H上升趋势在扩张(strong_uptrend_4h) + 做空额外条件:4H下降趋势在扩张(strong_downtrend_4h) """ max_dist = self.max_stop_dist.value / 100.0 cooldown = self.cooldown_bars.value - confirm_pct = self.entry_confirm_pct.value / 1000.0 # 1.0%~3.0% # NaN 安全处理 bool_cols = [ @@ -300,17 +335,14 @@ class StructureFlowStrategyV20(IStrategy): "trend_up_4h", "trend_down_4h", "in_demand_4h", "in_supply_4h", "support_alive_4h", "resistance_alive_4h", + "strong_uptrend_4h", "strong_downtrend_4h", "bullish_signal", "bearish_signal", ] for col in bool_cols: if col in dataframe.columns: dataframe[col] = dataframe[col].fillna(False) - # ===================== - # 原始v1.6入场条件(不变) - # ===================== - - # ── 做多原始条件 ── + # ── 做多 ── long_stop_dist = (dataframe["open"] - dataframe["support_4h"]) / dataframe["open"] long_base = ( @@ -320,13 +352,14 @@ class StructureFlowStrategyV20(IStrategy): & (long_stop_dist <= max_dist) & (long_stop_dist > 0.003) & dataframe["support_alive_4h"] + # v2.1: 趋势强度 — 4H上升趋势必须在扩张 + & dataframe["strong_uptrend_4h"] ) - # 冷却期 long_recent = long_base.rolling(cooldown, min_periods=1).max().shift(1) == 0 - long_signal = long_base & long_recent + dataframe.loc[long_base & long_recent, "enter_long"] = 1 - # ── 做空原始条件 ── + # ── 做空 ── short_stop_dist = (dataframe["resistance_4h"] - dataframe["open"]) / dataframe["open"] short_base = ( @@ -336,34 +369,12 @@ class StructureFlowStrategyV20(IStrategy): & (short_stop_dist <= max_dist) & (short_stop_dist > 0.003) & dataframe["resistance_alive_4h"] + # v2.1: 趋势强度 — 4H下降趋势必须在扩张 + & dataframe["strong_downtrend_4h"] ) short_recent = short_base.rolling(cooldown, min_periods=1).max().shift(1) == 0 - short_signal = short_base & short_recent - - # ===================== - # v2.0: B1 — 入场延迟一根bar确认 - # ===================== - # 上一根bar的信号(shift(1)) - prev_long_signal = long_signal.shift(1).fillna(False) - prev_short_signal = short_signal.shift(1).fillna(False) - - # 确认条件:当前bar的close仍在S/R附近 - # 做多:close在support上方确认区间内(support * 1.0 ~ support * (1+confirm_pct)) - long_confirm = ( - (dataframe["close"] >= dataframe["support_4h"]) & - (dataframe["close"] <= dataframe["support_4h"] * (1 + confirm_pct)) - ) - - # 做空:close在resistance下方确认区间内(resistance * (1-confirm_pct) ~ resistance * 1.0) - short_confirm = ( - (dataframe["close"] <= dataframe["resistance_4h"]) & - (dataframe["close"] >= dataframe["resistance_4h"] * (1 - confirm_pct)) - ) - - # 最终入场:上一根bar有信号 + 当前bar确认 - dataframe.loc[prev_long_signal & long_confirm, "enter_long"] = 1 - dataframe.loc[prev_short_signal & short_confirm, "enter_short"] = 1 + dataframe.loc[short_base & short_recent, "enter_short"] = 1 return dataframe @@ -397,13 +408,6 @@ class StructureFlowStrategyV20(IStrategy): ) -> float: """ 止损逻辑:完全基于价格结构,零指标(与v1.6相同)。 - - 止损位: - 做多 → support_4h - 0.1%缓冲 - 做空 → resistance_4h + 0.1%缓冲 - - support_4h / resistance_4h 随新Swing Point自动更新, - 天然形成追踪止损效果。 """ dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) if dataframe is None or len(dataframe) == 0: @@ -445,6 +449,8 @@ class StructureFlowStrategyV20(IStrategy): "filters": { "support_alive_4h": {"color": "green", "type": "line"}, "resistance_alive_4h": {"color": "red", "type": "line"}, + "strong_uptrend_4h": {"color": "blue", "type": "line"}, + "strong_downtrend_4h": {"color": "orange", "type": "line"}, }, }, }