v1.4: 进一步精简入场条件 + 止损收紧

This commit is contained in:
2026-06-07 23:47:00 +08:00
parent e59aa09c49
commit 016ddbf5b1

View File

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