修改了线段中枢逻辑
This commit is contained in:
@@ -0,0 +1,444 @@
|
||||
"""
|
||||
盘整背驰策略 (PanZhengBeiChi Strategy)
|
||||
|
||||
基于缠论的盘整背驰进行交易:
|
||||
- 盘整背驰:同级别走势中,Ai与Ai+2比较力度减弱
|
||||
- 顶背驰(卖点):价格创新高或接近,但MACD力度明显减弱
|
||||
- 底背驰(买点):价格创新低或接近,但MACD力度明显减弱
|
||||
|
||||
使用命令:
|
||||
freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json \
|
||||
--strategy PanZhengBeiChiStrategy --strategy-path ./user_data/Chan/strategies \
|
||||
--timerange=20250301-
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from technical.util import resample_to_interval, resampled_merge
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PanZhengBeiChiStrategy(IStrategy):
|
||||
"""
|
||||
盘整背驰策略
|
||||
|
||||
核心逻辑:
|
||||
1. 在5分钟级别识别同级别走势段(Ai)
|
||||
2. 比较Ai与Ai+2的MACD力度,判断盘整背驰
|
||||
3. 盘整顶背驰(i+2为偶数)-> 卖出
|
||||
4. 盘整底背驰(i+2为奇数)-> 买入
|
||||
"""
|
||||
INTERFACE_VERSION: int = 3
|
||||
|
||||
# === 基础配置 ===
|
||||
timeframe = '1m'
|
||||
informative_timeframe = '5m'
|
||||
can_short = True
|
||||
can_long = True
|
||||
|
||||
startup_candle_count: int = 2000 # 需要足够的数据来识别走势段
|
||||
|
||||
# === 止损止盈配置 ===
|
||||
stoploss = -0.02 # 2% 硬止损
|
||||
use_custom_stoploss = False
|
||||
|
||||
# Trailing stop
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.008 # 回撤 0.8% 触发退出
|
||||
trailing_stop_positive_offset = 0.015 # 盈利 1.5% 后才开始追踪
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
# ROI - 调整止盈策略
|
||||
minimal_roi = {
|
||||
"0": 0.015, # 1.5% 立即止盈(更保守)
|
||||
"30": 0.01, # 30分钟后 1%
|
||||
"120": 0.008, # 2小时后 0.8%
|
||||
}
|
||||
|
||||
order_types = {
|
||||
"entry": "market",
|
||||
"exit": "market",
|
||||
"stoploss": "market",
|
||||
"stoploss_on_exchange": False,
|
||||
}
|
||||
|
||||
# === 盘整背驰参数 ===
|
||||
same_level_timeframe = 5 # 5分钟级别
|
||||
pivot_window = 4 # 转折点确认窗口(增大减少噪音)
|
||||
min_segment_length = 5 # 最小段长度(K线数)(增大减少假信号)
|
||||
|
||||
# 背驰判断参数(更严格)
|
||||
beichi_price_threshold = 1.10 # 价格涨幅/跌幅阈值(允许10%范围内,更严格)
|
||||
beichi_macd_threshold = 0.75 # MACD力度阈值(低于75%即背驰,更严格)
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""计算指标并识别盘整背驰"""
|
||||
ticker = self.get_ticker_indicator()
|
||||
|
||||
# Resample 到 5m 进行同级别分解
|
||||
dataframe_5m = resample_to_interval(dataframe, ticker * self.same_level_timeframe)
|
||||
|
||||
# 在 5m 上计算指标
|
||||
dataframe_5m = self.add_indicators_5m(dataframe_5m)
|
||||
|
||||
# 识别盘整背驰
|
||||
dataframe_5m = self.identify_panzheng_beichi(dataframe_5m)
|
||||
|
||||
# 合并回 1m dataframe
|
||||
dataframe = resampled_merge(dataframe, dataframe_5m)
|
||||
|
||||
# 在 1m 上也计算基础指标
|
||||
dataframe = self.add_indicators_1m(dataframe)
|
||||
|
||||
return dataframe
|
||||
|
||||
def add_indicators_5m(self, dataframe: DataFrame) -> DataFrame:
|
||||
"""在5m级别计算指标"""
|
||||
# MACD 用于识别背驰
|
||||
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
|
||||
dataframe['macd'] = macd['macd']
|
||||
dataframe['macdsignal'] = macd['macdsignal']
|
||||
dataframe['macdhist'] = macd['macdhist']
|
||||
|
||||
# EMA 用于识别趋势
|
||||
dataframe['ema12'] = ta.EMA(dataframe, timeperiod=12)
|
||||
dataframe['ema26'] = ta.EMA(dataframe, timeperiod=26)
|
||||
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
||||
dataframe['ema200'] = ta.EMA(dataframe, timeperiod=200)
|
||||
|
||||
# EMA趋势方向
|
||||
dataframe['ema_trend_up'] = (dataframe['ema12'] > dataframe['ema26']) & (dataframe['ema26'] > dataframe['ema50'])
|
||||
dataframe['ema_trend_dn'] = (dataframe['ema12'] < dataframe['ema26']) & (dataframe['ema26'] < dataframe['ema50'])
|
||||
|
||||
# 价格与EMA200关系
|
||||
dataframe['price_above_ema200'] = dataframe['close'] > dataframe['ema200']
|
||||
dataframe['price_below_ema200'] = dataframe['close'] < dataframe['ema200']
|
||||
|
||||
# 趋势强度
|
||||
dataframe['ema12_slope'] = dataframe['ema12'].diff(5) / dataframe['ema12'].shift(5)
|
||||
dataframe['ema26_slope'] = dataframe['ema26'].diff(5) / dataframe['ema26'].shift(5)
|
||||
|
||||
# 强趋势判断
|
||||
dataframe['strong_uptrend'] = (
|
||||
(dataframe['ema12_slope'] > 0) &
|
||||
(dataframe['ema26_slope'] > 0) &
|
||||
(dataframe['price_above_ema200'])
|
||||
)
|
||||
dataframe['strong_downtrend'] = (
|
||||
(dataframe['ema12_slope'] < 0) &
|
||||
(dataframe['ema26_slope'] < 0) &
|
||||
(dataframe['price_below_ema200'])
|
||||
)
|
||||
|
||||
# RSI
|
||||
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
||||
|
||||
# ATR 用于波动率过滤
|
||||
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
|
||||
dataframe['atr_mean'] = dataframe['atr'].rolling(window=20).mean()
|
||||
dataframe['volatility_ok'] = dataframe['atr'] > dataframe['atr_mean'] * 0.8
|
||||
|
||||
return dataframe
|
||||
|
||||
def add_indicators_1m(self, dataframe: DataFrame) -> DataFrame:
|
||||
"""在1m级别计算基础指标"""
|
||||
dataframe['rsi_1m'] = ta.RSI(dataframe, timeperiod=14)
|
||||
dataframe['volume_mean'] = dataframe['volume'].rolling(window=20).mean()
|
||||
|
||||
# MACD 用于1m级别确认
|
||||
macd_1m = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
|
||||
dataframe['macd_1m'] = macd_1m['macd']
|
||||
dataframe['macdsignal_1m'] = macd_1m['macdsignal']
|
||||
dataframe['macdhist_1m'] = macd_1m['macdhist']
|
||||
|
||||
# MACD交叉
|
||||
dataframe['macd_cross_up'] = (
|
||||
(dataframe['macd_1m'] > dataframe['macdsignal_1m']) &
|
||||
(dataframe['macd_1m'].shift(1) <= dataframe['macdsignal_1m'].shift(1))
|
||||
)
|
||||
dataframe['macd_cross_dn'] = (
|
||||
(dataframe['macd_1m'] < dataframe['macdsignal_1m']) &
|
||||
(dataframe['macd_1m'].shift(1) >= dataframe['macdsignal_1m'].shift(1))
|
||||
)
|
||||
|
||||
return dataframe
|
||||
|
||||
def identify_panzheng_beichi(self, dataframe: DataFrame) -> DataFrame:
|
||||
"""
|
||||
识别盘整背驰
|
||||
|
||||
核心逻辑:
|
||||
1. 识别局部转折点(高低点)
|
||||
2. 构建同级别走势段(Ai)
|
||||
3. 比较Ai与Ai+2的MACD力度
|
||||
4. 判断盘整背驰:价格涨幅相近但MACD力度减弱
|
||||
"""
|
||||
df = dataframe.copy()
|
||||
window = self.pivot_window
|
||||
lookback = window + 1
|
||||
|
||||
# 初始化列
|
||||
df['ai_index'] = -1
|
||||
df['ai_type'] = 0 # 1: 上涨, -1: 下跌
|
||||
df['ai_high'] = np.nan
|
||||
df['ai_low'] = np.nan
|
||||
df['ai_macd_max'] = np.nan
|
||||
df['ai_macd_min'] = np.nan
|
||||
df['beichi_long'] = False # 盘整底背驰(买入信号)
|
||||
df['beichi_short'] = False # 盘整顶背驰(卖出信号)
|
||||
|
||||
# 识别局部高点
|
||||
df['temp_high'] = df['high'].shift(window)
|
||||
df['is_pivot_high'] = (
|
||||
(df['temp_high'] == df['temp_high'].rolling(window=lookback).max()) &
|
||||
(df['temp_high'].notna())
|
||||
)
|
||||
|
||||
# 识别局部低点
|
||||
df['temp_low'] = df['low'].shift(window)
|
||||
df['is_pivot_low'] = (
|
||||
(df['temp_low'] == df['temp_low'].rolling(window=lookback).min()) &
|
||||
(df['temp_low'].notna())
|
||||
)
|
||||
|
||||
# 逐行处理,识别走势段和背驰
|
||||
ai_list = []
|
||||
current_ai_start = None
|
||||
current_ai_type = None
|
||||
last_pivot_idx = None
|
||||
|
||||
for i in range(window, len(df)):
|
||||
# 检查新的转折点
|
||||
is_new_pivot = False
|
||||
pivot_type = None
|
||||
|
||||
if df.iloc[i]['is_pivot_high']:
|
||||
is_new_pivot = True
|
||||
pivot_type = 'high'
|
||||
elif df.iloc[i]['is_pivot_low']:
|
||||
is_new_pivot = True
|
||||
pivot_type = 'low'
|
||||
|
||||
if is_new_pivot and last_pivot_idx is not None:
|
||||
# 完成一个走势段
|
||||
if current_ai_start is not None:
|
||||
seg_df = df.iloc[current_ai_start:last_pivot_idx]
|
||||
if len(seg_df) >= self.min_segment_length:
|
||||
high_val = seg_df['high'].max()
|
||||
low_val = seg_df['low'].min()
|
||||
macd_max = seg_df['macd'].max()
|
||||
macd_min = seg_df['macd'].min()
|
||||
|
||||
# 判断走势类型
|
||||
if current_ai_type is None:
|
||||
if high_val > df.iloc[current_ai_start]['close']:
|
||||
current_ai_type = 1
|
||||
else:
|
||||
current_ai_type = -1
|
||||
|
||||
ai_info = {
|
||||
'start': current_ai_start,
|
||||
'end': last_pivot_idx,
|
||||
'type': current_ai_type,
|
||||
'high': high_val,
|
||||
'low': low_val,
|
||||
'macd_max': macd_max,
|
||||
'macd_min': macd_min,
|
||||
}
|
||||
ai_list.append(ai_info)
|
||||
|
||||
# 标记该段
|
||||
df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_index')] = len(ai_list) - 1
|
||||
df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_type')] = current_ai_type
|
||||
df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_high')] = high_val
|
||||
df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_low')] = low_val
|
||||
df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_macd_max')] = macd_max
|
||||
df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_macd_min')] = macd_min
|
||||
|
||||
# 判断背驰(Ai与Ai+2比较)
|
||||
if len(ai_list) >= 3:
|
||||
ai = ai_list[-3] # Ai
|
||||
ai_plus_2 = ai_list[-1] # Ai+2
|
||||
|
||||
if ai['type'] == ai_plus_2['type']:
|
||||
# 上涨段:比较向上力度
|
||||
if ai['type'] == 1:
|
||||
price_chg = (ai_plus_2['high'] - ai_plus_2['low']) / ai_plus_2['low'] if ai_plus_2['low'] > 0 else 0
|
||||
price_chg_prev = (ai['high'] - ai['low']) / ai['low'] if ai['low'] > 0 else 0
|
||||
macd_chg = ai_plus_2['macd_max']
|
||||
macd_chg_prev = ai['macd_max']
|
||||
|
||||
# 顶背驰:价格涨幅相近但MACD力度减弱
|
||||
if price_chg <= price_chg_prev * self.beichi_price_threshold and \
|
||||
macd_chg < macd_chg_prev * self.beichi_macd_threshold:
|
||||
idx = len(ai_list) - 1 # i+2的索引
|
||||
if idx % 2 == 0: # 偶数 -> 卖出
|
||||
df.iloc[last_pivot_idx, df.columns.get_loc('beichi_short')] = True
|
||||
else: # 奇数 -> 买入
|
||||
df.iloc[last_pivot_idx, df.columns.get_loc('beichi_long')] = True
|
||||
|
||||
# 下跌段:比较向下力度
|
||||
else:
|
||||
price_chg = abs((ai_plus_2['high'] - ai_plus_2['low']) / ai_plus_2['low']) if ai_plus_2['low'] > 0 else 0
|
||||
price_chg_prev = abs((ai['high'] - ai['low']) / ai['low']) if ai['low'] > 0 else 0
|
||||
macd_chg = abs(ai_plus_2['macd_min'])
|
||||
macd_chg_prev = abs(ai['macd_min'])
|
||||
|
||||
# 底背驰:价格跌幅相近但MACD力度减弱
|
||||
if price_chg <= price_chg_prev * self.beichi_price_threshold and \
|
||||
macd_chg < macd_chg_prev * self.beichi_macd_threshold:
|
||||
idx = len(ai_list) - 1
|
||||
if idx % 2 == 0: # 偶数 -> 卖出
|
||||
df.iloc[last_pivot_idx, df.columns.get_loc('beichi_short')] = True
|
||||
else: # 奇数 -> 买入
|
||||
df.iloc[last_pivot_idx, df.columns.get_loc('beichi_long')] = True
|
||||
|
||||
# 更新当前段信息
|
||||
if pivot_type == 'high':
|
||||
current_ai_type = -1 # 高点后向下
|
||||
else:
|
||||
current_ai_type = 1 # 低点后向上
|
||||
current_ai_start = last_pivot_idx
|
||||
|
||||
if is_new_pivot:
|
||||
last_pivot_idx = i
|
||||
|
||||
return df
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
ticker = self.get_ticker_indicator()
|
||||
resample_col = f"resample_{ticker * self.same_level_timeframe}_"
|
||||
|
||||
# 5m 级别指标列名
|
||||
beichi_long_col = f"{resample_col}beichi_long"
|
||||
beichi_short_col = f"{resample_col}beichi_short"
|
||||
ai_type_col = f"{resample_col}ai_type"
|
||||
rsi_5m_col = f"{resample_col}rsi"
|
||||
ema_trend_up_col = f"{resample_col}ema_trend_up"
|
||||
ema_trend_dn_col = f"{resample_col}ema_trend_dn"
|
||||
volatility_ok_col = f"{resample_col}volatility_ok"
|
||||
strong_uptrend_col = f"{resample_col}strong_uptrend"
|
||||
strong_downtrend_col = f"{resample_col}strong_downtrend"
|
||||
price_above_ema200_col = f"{resample_col}price_above_ema200"
|
||||
price_below_ema200_col = f"{resample_col}price_below_ema200"
|
||||
|
||||
# === 做多入场 ===
|
||||
# 条件:盘整底背驰 + 强上升趋势确认
|
||||
dataframe.loc[
|
||||
(
|
||||
# 核心信号:盘整底背驰
|
||||
(dataframe[beichi_long_col] == True) &
|
||||
|
||||
# 强上升趋势确认(更严格)
|
||||
(dataframe[strong_uptrend_col] == True) &
|
||||
|
||||
# RSI 确认(更严格:只在大趋势中操作)
|
||||
(dataframe[rsi_5m_col] > 40) &
|
||||
(dataframe[rsi_5m_col] < 60) &
|
||||
|
||||
# 1m 指标确认
|
||||
(dataframe['macd_1m'] > dataframe['macdsignal_1m']) &
|
||||
|
||||
# 成交量确认
|
||||
(dataframe['volume'] > dataframe['volume_mean'] * 1.5)
|
||||
),
|
||||
['enter_long', 'enter_tag']
|
||||
] = (1, "pzbc_long")
|
||||
|
||||
# === 做空入场 ===
|
||||
# 条件:盘整顶背驰 + 强下降趋势确认(更严格)
|
||||
dataframe.loc[
|
||||
(
|
||||
# 核心信号:盘整顶背驰
|
||||
(dataframe[beichi_short_col] == True) &
|
||||
|
||||
# 强下降趋势确认
|
||||
(dataframe[strong_downtrend_col] == True) &
|
||||
|
||||
# RSI 确认
|
||||
(dataframe[rsi_5m_col] > 40) &
|
||||
(dataframe[rsi_5m_col] < 60) &
|
||||
|
||||
# 1m 指标确认
|
||||
(dataframe['macd_1m'] < dataframe['macdsignal_1m']) &
|
||||
|
||||
# 成交量确认
|
||||
(dataframe['volume'] > dataframe['volume_mean'] * 1.5)
|
||||
),
|
||||
['enter_short', 'enter_tag']
|
||||
] = (1, "pzbc_short")
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
出场逻辑
|
||||
|
||||
多头出场:
|
||||
1. 出现盘整顶背驰
|
||||
2. 趋势转弱
|
||||
|
||||
空头出场:
|
||||
1. 出现盘整底背驰
|
||||
2. 趋势转弱
|
||||
"""
|
||||
ticker = self.get_ticker_indicator()
|
||||
resample_col = f"resample_{ticker * self.same_level_timeframe}_"
|
||||
|
||||
beichi_long_col = f"{resample_col}beichi_long"
|
||||
beichi_short_col = f"{resample_col}beichi_short"
|
||||
ai_type_col = f"{resample_col}ai_type"
|
||||
rsi_5m_col = f"{resample_col}rsi"
|
||||
ema_trend_dn_col = f"{resample_col}ema_trend_dn"
|
||||
strong_downtrend_col = f"{resample_col}strong_downtrend"
|
||||
strong_uptrend_col = f"{resample_col}strong_uptrend"
|
||||
|
||||
# === 多头出场 ===
|
||||
dataframe.loc[
|
||||
(
|
||||
# 出现盘整顶背驰 -> 退出多头
|
||||
(dataframe[beichi_short_col] == True) |
|
||||
|
||||
# 趋势转弱
|
||||
(
|
||||
(dataframe[ai_type_col] == -1) &
|
||||
(dataframe[rsi_5m_col] > 55)
|
||||
) |
|
||||
|
||||
# 强下跌趋势
|
||||
(dataframe[strong_downtrend_col] == True)
|
||||
),
|
||||
['exit_long', 'exit_tag']
|
||||
] = (1, "pzbc_exit_long")
|
||||
|
||||
# === 空头出场 ===
|
||||
dataframe.loc[
|
||||
(
|
||||
# 出现盘整底背驰 -> 退出空头
|
||||
(dataframe[beichi_long_col] == True) |
|
||||
|
||||
# 趋势转弱
|
||||
(
|
||||
(dataframe[ai_type_col] == 1) &
|
||||
(dataframe[rsi_5m_col] < 45)
|
||||
) |
|
||||
|
||||
# 强上涨趋势
|
||||
(dataframe[strong_uptrend_col] == True)
|
||||
),
|
||||
['exit_short', 'exit_tag']
|
||||
] = (1, "pzbc_exit_short")
|
||||
|
||||
return dataframe
|
||||
|
||||
def get_ticker_indicator(self) -> int:
|
||||
"""获取 timeframe 的分钟数"""
|
||||
return int(self.timeframe[:-1])
|
||||
|
||||
Reference in New Issue
Block a user