修改了线段中枢逻辑

This commit is contained in:
jackyu66git
2026-03-11 03:08:10 +08:00
parent 1c099be23e
commit 5226c551d5
19 changed files with 3386 additions and 324 deletions
+82 -138
View File
@@ -9,68 +9,44 @@ from typing import Optional
from freqtrade.persistence import Trade
import warnings
# 抑制 pandas FutureWarning 关于 fillna 的隐式降级警告
# 这个警告来自 freqtrade 库的 strategy_helper.py
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
# 或者启用未来行为(推荐)
pd.set_option('future.no_silent_downcasting', True)
# freqtrade trade -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategy --strategy-path ./user_data/Chan/strategies
# freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategy --strategy-path ./user_data/Chan/strategies --timerange=20260304-
# freqtrade download-data -c ./user_data/Chan/config/Local_Test.json -t 1m 5m --data-format-ohlcv json --pairs SOL/USDT:USDT --timerange=20260201-
class CryptoFutures1m5mStrategy(IStrategy):
"""
SOL/USDT 合约策略 - 1分钟+5分钟双时间框架 V12e (Short Only)
SOL/USDT 合约策略 - 只做多版 (默认策略)
14个月回测 (2025-01 ~ 2026-03): +107.11%, PF 1.37, DD 23.96%
每个季度均盈利,市场下跌-54%期间持续获利
核心设计:
1. 纯做空策略 - 价格必须低于EMA200至少1%才允许做空
2. 5分钟趋势确认:EMA12<EMA26<EMA50 + ADX 25-50 + RSI 30-48
3. ATR自适应波动率过滤:ATR < 长期均值 * 1.5(避免极端波动)
4. 1分钟精确入场:顶背离 / EMA死叉 / 熊市回调
5. 双重MACD确认(5分钟+1分钟MACD柱状图均为负)
6. trailing_stop_positive_offset = 0.030
7. 时间止损:持仓过久且亏损时提前退出
基于V5修改:禁用做空,只做多
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = True
can_short = False # 禁用做空
can_long = True
lev = 1.0
# 止损止盈
stoploss = -0.025 # 2.5% 硬止损
stoploss = -0.035
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.030
trailing_stop_positive_offset = 0.035
trailing_only_offset_is_reached = True
# 不使用custom_stoploss(会干扰trailing_stop
use_custom_stoploss = False
# 完全禁用 exit_signal
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [
("SOL/USDT:USDT", "5m"),
]
return [("SOL/USDT:USDT", "5m")]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# ==================== 5分钟指标 ====================
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA趋势
# EMA
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
# EMA12斜率(3根K线变化率,用于确认趋势方向的动量)
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
@@ -79,66 +55,54 @@ class CryptoFutures1m5mStrategy(IStrategy):
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX趋势强度
# ADX
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI5分钟)
# RSI
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR5分钟)
# ATR
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
# ATR 长期均值(用于自适应波动率过滤)
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# ===== EMA200 大趋势过滤 =====
# EMA200
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
# EMA200斜率(20根5分钟K线 = 100分钟趋势方向)
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# ===== 大趋势过滤(Short Only =====
# 做空需要价格低于EMA200至少1%
informative['below_ema200'] = informative['ema200_dist_pct'] < -1.0
# 牛市暂停:EMA200上升 + 价格在EMA200上方 → 完全停止做空
informative['bull_pause'] = (
(informative['ema200_slope'] > 0) &
(informative['ema200_dist_pct'] > 0)
# 做多趋势
informative['trend_bull_5m'] = (
(informative['ema12'] > informative['ema26']) &
(informative['ema26'] > informative['ema50']) &
(informative['ema12_slope'] > 0.05) &
(informative['adx_5m'] > 24) &
(informative['adx_5m'] < 51) &
(informative['close'] > informative['ema12']) &
(informative['rsi_5m'] > 52) &
(informative['rsi_5m'] < 72)
)
# ===== 5分钟趋势判断(仅Short =====
informative['trend_bear_5m'] = (
(informative['ema12'] < informative['ema26']) &
(informative['ema26'] < informative['ema50']) &
(informative['ema12_slope'] < 0) &
(informative['adx_5m'] > 25) &
(informative['adx_5m'] < 50) &
(informative['close'] < informative['ema12']) &
(informative['rsi_5m'] < 48) &
(informative['rsi_5m'] > 30)
)
# 大趋势过滤
informative['above_ema200'] = informative['ema200_dist_pct'] > 1.0
# 做条件:短期趋势 + EMA200大趋势方向一致 + 非牛市
informative['can_long_5m'] = False
informative['can_short_5m'] = (
informative['trend_bear_5m'] &
informative['below_ema200'] &
(~informative['bull_pause'])
)
# 做条件
informative['can_long_5m'] = informative['trend_bull_5m'] & informative['above_ema200']
# ATR波动率过滤(自适应)
# ATR过滤
informative['atr_ok_5m'] = (
(informative['atr_pct_5m'] > 0.1) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 1.5)
(informative['atr_pct_5m'] > 0.07) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 2.2)
)
# 合并5分钟数据到1分钟
# 成交量
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * 0.75
# 合并
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# ==================== 1分钟指标 ====================
# 1分钟指标
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
@@ -148,116 +112,96 @@ class CryptoFutures1m5mStrategy(IStrategy):
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
# ===== 1分钟MACD斜率 =====
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# ===== 1分钟做空入场信号 =====
dataframe['price_high_5'] = dataframe['high'].rolling(window=5).max()
dataframe['macd_high_5'] = dataframe['macd'].rolling(window=5).max()
# 做多信号
dataframe['price_low_5'] = dataframe['low'].rolling(window=5).min()
dataframe['macd_low_5'] = dataframe['macd'].rolling(window=5).min()
dataframe['top_divergence'] = (
(dataframe['high'] >= dataframe['price_high_5'] * 0.999) &
(dataframe['macd'] < dataframe['macd_high_5']) &
(dataframe['macd_slope'] < 0) &
(dataframe['macd'] < dataframe['macd_signal']) &
dataframe['bottom_divergence'] = (
(dataframe['low'] <= dataframe['price_low_5'] * 1.001) &
(dataframe['macd'] > dataframe['macd_low_5']) &
(dataframe['macd_slope'] > 0) &
(dataframe['macd'] > dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
dataframe['ema_cross_down'] = (
(dataframe['ema9'] < dataframe['ema21']) &
(dataframe['ema9'].shift(1) >= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] < 55) & (dataframe['rsi'] > 35) &
dataframe['ema_cross_up'] = (
(dataframe['ema9'] > dataframe['ema21']) &
(dataframe['ema9'].shift(1) <= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] > 45) &
(dataframe['rsi'] < 70) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
dataframe['is_bear_candle'] = (
(dataframe['close'] < dataframe['open']) &
((dataframe['open'] - dataframe['close']) / dataframe['open'] > 0.008)
)
dataframe['bear_pullback'] = (
dataframe['is_bear_candle'].shift(2) &
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
(dataframe['high'] < dataframe['high'].shift(2)) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['ema9'])
dataframe['is_bull_candle'] = (dataframe['close'] > dataframe['open']) & ((dataframe['close'] - dataframe['open']) / dataframe['open'] > 0.008)
dataframe['bull_pullback'] = (
dataframe['is_bull_candle'].shift(2) &
(dataframe['close'].shift(1) < dataframe['open'].shift(1)) &
(dataframe['low'] > dataframe['low'].shift(2)) &
(dataframe['close'] > dataframe['open']) &
(dataframe['close'] > dataframe['ema9'])
)
# ==================== 时间过滤 ====================
# 时间过滤
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 安全转换5分钟布尔列
bool_cols = [
'can_long_5m_5m', 'can_short_5m_5m',
'trend_bear_5m_5m',
'atr_ok_5m_5m',
'below_ema200_5m', 'bull_pause_5m',
]
# 类型转换
bool_cols = ['can_long_5m_5m', 'trend_bull_5m_5m', 'atr_ok_5m_5m', 'above_ema200_5m', 'volume_ok_5m_5m']
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
num_cols = ['atr_pct_5m_5m', 'rsi_5m_5m', 'macd_hist_5m_5m', 'atr_pct_ma_5m_5m',
'ema200_dist_pct_5m', 'ema200_slope_5m']
for col in num_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(float).fillna(0.0)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
atr_ok = dataframe['atr_ok_5m_5m']
volume_ok = dataframe['volume_ok_5m_5m']
# 5分钟MACD方向确认
macd_bear_5m = dataframe['macd_hist_5m_5m'] < 0
# 只做多
macd_bull_5m = dataframe['macd_hist_5m_5m'] > 0
macd_bull_1m = dataframe['macd_hist'] > 0
# 1分钟MACD方向确认(双重确认)
macd_bear_1m = dataframe['macd_hist'] < 0
# ===== 做空入场 =====
dataframe.loc[
(time_ok) &
(atr_ok) &
(dataframe['can_short_5m_5m']) &
(macd_bear_5m) &
(macd_bear_1m) &
(dataframe['rsi'] > 30) &
(
dataframe['top_divergence'] |
dataframe['ema_cross_down'] |
dataframe['bear_pullback']
) &
(time_ok) & (atr_ok) & (dataframe['can_long_5m_5m']) &
(macd_bull_5m) & (macd_bull_1m) & (volume_ok) &
(dataframe['rsi'] < 70) & (dataframe['rsi'] > 40) &
(dataframe['bottom_divergence'] | dataframe['ema_cross_up'] | dataframe['bull_pullback']) &
(dataframe['volume'] > 0),
'enter_short'
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
dataframe.loc[:, 'exit_short'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
"""时间止损:持仓过久且亏损时提前退出"""
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
if trade_duration > 8 and current_profit < -0.005:
return 'time_stop_8h'
if trade_duration > 16 and current_profit < 0:
return 'time_stop_16h'
# 做多时间止损 - 宽松
if trade_duration > 10 and current_profit < -0.006:
return 'time_stop_long_10h'
if trade_duration > 20 and current_profit < 0:
return 'time_stop_long_20h'
if trade_duration > 30:
return 'time_stop_long_30h'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
"""入场确认 - 时间过滤安全网"""
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return self.lev
@@ -0,0 +1,209 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import numpy as np
from datetime import datetime
from typing import Optional
from freqtrade.persistence import Trade
import warnings
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
pd.set_option('future.no_silent_downcasting', True)
class CryptoFutures1m5mStrategyLongOnly(IStrategy):
"""
SOL/USDT 合约策略 - 只做多版本
基于V5修改:
- 只做多,禁止做空
- 优化做多止损和止盈参数
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = False # 禁用做空
can_long = True
lev = 1.0
stoploss = -0.035
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.035
trailing_only_offset_is_reached = True
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [("SOL/USDT:USDT", "5m")]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
macd, macd_signal, macd_hist = ta.MACD(informative['close'], fastperiod=12, slowperiod=26, signalperiod=9)
informative['macd_5m'] = macd
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# EMA200
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# 做多趋势
informative['trend_bull_5m'] = (
(informative['ema12'] > informative['ema26']) &
(informative['ema26'] > informative['ema50']) &
(informative['ema12_slope'] > 0.05) &
(informative['adx_5m'] > 24) &
(informative['adx_5m'] < 51) &
(informative['close'] > informative['ema12']) &
(informative['rsi_5m'] > 52) &
(informative['rsi_5m'] < 72)
)
# 大趋势过滤
informative['above_ema200'] = informative['ema200_dist_pct'] > 1.0
# 做多条件
informative['can_long_5m'] = informative['trend_bull_5m'] & informative['above_ema200']
# ATR过滤
informative['atr_ok_5m'] = (
(informative['atr_pct_5m'] > 0.07) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 2.2)
)
# 成交量
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * 0.75
# 合并
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# 1分钟指标
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
dataframe['macd_hist'] = hist_1m
dataframe['ema9'] = ta.EMA(dataframe['close'], timeperiod=9)
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# 做多信号
dataframe['price_low_5'] = dataframe['low'].rolling(window=5).min()
dataframe['macd_low_5'] = dataframe['macd'].rolling(window=5).min()
dataframe['bottom_divergence'] = (
(dataframe['low'] <= dataframe['price_low_5'] * 1.001) &
(dataframe['macd'] > dataframe['macd_low_5']) &
(dataframe['macd_slope'] > 0) &
(dataframe['macd'] > dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
dataframe['ema_cross_up'] = (
(dataframe['ema9'] > dataframe['ema21']) &
(dataframe['ema9'].shift(1) <= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] > 45) &
(dataframe['rsi'] < 70) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
dataframe['is_bull_candle'] = (dataframe['close'] > dataframe['open']) & ((dataframe['close'] - dataframe['open']) / dataframe['open'] > 0.008)
dataframe['bull_pullback'] = (
dataframe['is_bull_candle'].shift(2) &
(dataframe['close'].shift(1) < dataframe['open'].shift(1)) &
(dataframe['low'] > dataframe['low'].shift(2)) &
(dataframe['close'] > dataframe['open']) &
(dataframe['close'] > dataframe['ema9'])
)
# 时间过滤
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 类型转换
bool_cols = ['can_long_5m_5m', 'trend_bull_5m_5m', 'atr_ok_5m_5m', 'above_ema200_5m', 'volume_ok_5m_5m']
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
atr_ok = dataframe['atr_ok_5m_5m']
volume_ok = dataframe['volume_ok_5m_5m']
# 只做多
macd_bull_5m = dataframe['macd_hist_5m_5m'] > 0
macd_bull_1m = dataframe['macd_hist'] > 0
dataframe.loc[
(time_ok) & (atr_ok) & (dataframe['can_long_5m_5m']) &
(macd_bull_5m) & (macd_bull_1m) & (volume_ok) &
(dataframe['rsi'] < 70) & (dataframe['rsi'] > 40) &
(dataframe['bottom_divergence'] | dataframe['ema_cross_up'] | dataframe['bull_pullback']) &
(dataframe['volume'] > 0),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
# 做多时间止损 - 宽松
if trade_duration > 10 and current_profit < -0.006:
return 'time_stop_long_10h'
if trade_duration > 20 and current_profit < 0:
return 'time_stop_long_20h'
if trade_duration > 30:
return 'time_stop_long_30h'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return self.lev
@@ -0,0 +1,212 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import numpy as np
from datetime import datetime
from typing import Optional
from freqtrade.persistence import Trade
import warnings
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
pd.set_option('future.no_silent_downcasting', True)
class CryptoFutures1m5mStrategyShortOnly(IStrategy):
"""
SOL/USDT 合约策略 - 只做空版本
基于V5修改:
- 只做空,禁止做多
- 优化做空止损和止盈参数
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = True
can_long = False # 禁用做多
lev = 1.0
# Trailing设置 - 基于V5
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.035
trailing_only_offset_is_reached = True
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [("SOL/USDT:USDT", "5m")]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
macd, macd_signal, macd_hist = ta.MACD(informative['close'], fastperiod=12, slowperiod=26, signalperiod=9)
informative['macd_5m'] = macd
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# EMA200
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# 做空趋势 - 基于V5优化
informative['trend_bear_5m'] = (
(informative['ema12'] < informative['ema26']) &
(informative['ema26'] < informative['ema50']) &
(informative['ema12_slope'] < -0.05) & # V5标准
(informative['adx_5m'] > 24) & # V5标准
(informative['adx_5m'] < 51) &
(informative['close'] < informative['ema12']) &
(informative['rsi_5m'] < 48) &
(informative['rsi_5m'] > 29)
)
# 大趋势过滤 - 放宽条件,基于V5
informative['below_ema200'] = informative['ema200_dist_pct'] < -1.0
# 熊市确认 - 可选,不过度限制
informative['bear_market'] = (informative['ema200_slope'] < 0) & (informative['ema200_dist_pct'] < 0)
# 做空条件 - 移除bear_market强制要求,基于V5
informative['can_short_5m'] = informative['trend_bear_5m'] & informative['below_ema200']
# ATR过滤 - 基于V5标准
informative['atr_ok_5m'] = (
(informative['atr_pct_5m'] > 0.07) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 2.2)
)
# 成交量 - 基于V5标准
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * 0.75
# 合并
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# 1分钟指标
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
dataframe['macd_hist'] = hist_1m
dataframe['ema9'] = ta.EMA(dataframe['close'], timeperiod=9)
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# 做空信号
dataframe['price_high_5'] = dataframe['high'].rolling(window=5).max()
dataframe['macd_high_5'] = dataframe['macd'].rolling(window=5).max()
dataframe['top_divergence'] = (
(dataframe['high'] >= dataframe['price_high_5'] * 0.999) &
(dataframe['macd'] < dataframe['macd_high_5']) &
(dataframe['macd_slope'] < 0) &
(dataframe['macd'] < dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.8)
)
dataframe['ema_cross_down'] = (
(dataframe['ema9'] < dataframe['ema21']) &
(dataframe['ema9'].shift(1) >= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] < 55) &
(dataframe['rsi'] > 35) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
dataframe['is_bear_candle'] = (dataframe['close'] < dataframe['open']) & ((dataframe['open'] - dataframe['close']) / dataframe['open'] > 0.008)
dataframe['bear_pullback'] = (
dataframe['is_bear_candle'].shift(2) &
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
(dataframe['high'] < dataframe['high'].shift(2)) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['ema9'])
)
# 时间过滤
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 类型转换
bool_cols = ['can_short_5m_5m', 'trend_bear_5m_5m', 'atr_ok_5m_5m', 'below_ema200_5m', 'volume_ok_5m_5m', 'bear_market_5m']
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
atr_ok = dataframe['atr_ok_5m_5m']
volume_ok = dataframe['volume_ok_5m_5m']
# 只做空 - 基于V5标准
macd_bear_5m = dataframe['macd_hist_5m_5m'] < 0
macd_bear_1m = dataframe['macd_hist'] < 0
dataframe.loc[
(time_ok) & (atr_ok) & (dataframe['can_short_5m_5m']) &
(macd_bear_5m) & (macd_bear_1m) & (volume_ok) &
(dataframe['rsi'] > 30) &
(dataframe['top_divergence'] | dataframe['ema_cross_down'] | dataframe['bear_pullback']) &
(dataframe['volume'] > 0),
'enter_short'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_short'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
# 做空时间止损 - 基于V5标准
if trade_duration > 8 and current_profit < -0.005:
return 'time_stop_short_8h'
if trade_duration > 16 and current_profit < 0:
return 'time_stop_short_16h'
if trade_duration > 24:
return 'time_stop_short_24h'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return self.lev
+283
View File
@@ -0,0 +1,283 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import numpy as np
from datetime import datetime
from typing import Optional
from freqtrade.persistence import Trade
import warnings
# 抑制 pandas FutureWarning 关于 fillna 的隐式降级警告
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
pd.set_option('future.no_silent_downcasting', True)
# freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategyV2 --strategy-path ./user_data/Chan/strategies --timerange=20260101-
class CryptoFutures1m5mStrategyV2(IStrategy):
"""
SOL/USDT 合约策略 - 1分钟+5分钟双时间框架 V2 优化版 (Short Only)
基于原版优化:
1. 保持原版核心入场逻辑
2. 优化追踪止盈参数
3. 增强时间止损灵活性
4. 稍微放宽ATR过滤增加交易机会
核心设计:
1. 纯做空策略 - 价格必须低于EMA200至少1%才允许做空
2. 5分钟趋势确认:EMA12<EMA26<EMA50 + ADX 25-50 + RSI 30-48
3. ATR自适应波动率过滤
4. 1分钟精确入场:顶背离 / EMA死叉 / 熊市回调
5. 双重MACD确认(5分钟+1分钟MACD柱状图均为负)
6. trailing_stop_positive_offset = 0.035
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = True
lev = 1.0
# 止损止盈 - 优化版
stoploss = -0.026 # 2.6% 硬止损
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.032
trailing_only_offset_is_reached = True
# 完全禁用 exit_signal
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [
("SOL/USDT:USDT", "5m"),
]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# ==================== 5分钟指标 ====================
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA趋势
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
# EMA12斜率(3根K线变化率)
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
macd, macd_signal, macd_hist = ta.MACD(informative['close'], fastperiod=12, slowperiod=26, signalperiod=9)
informative['macd_5m'] = macd
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX趋势强度
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI5分钟)
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR5分钟)
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
# ATR 长期均值
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# EMA200 大趋势过滤
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
# EMA200斜率
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# 大趋势过滤(Short Only
informative['below_ema200'] = informative['ema200_dist_pct'] < -1.0
# 牛市暂停
informative['bull_pause'] = (
(informative['ema200_slope'] > 0) &
(informative['ema200_dist_pct'] > 0)
)
# 5分钟趋势判断(仅Short
informative['trend_bear_5m'] = (
(informative['ema12'] < informative['ema26']) &
(informative['ema26'] < informative['ema50']) &
(informative['ema12_slope'] < -0.05) &
(informative['adx_5m'] > 24) &
(informative['adx_5m'] < 51) &
(informative['close'] < informative['ema12']) &
(informative['rsi_5m'] < 48) &
(informative['rsi_5m'] > 29)
)
# 做空条件
informative['can_long_5m'] = False
informative['can_short_5m'] = (
informative['trend_bear_5m'] &
informative['below_ema200'] &
(~informative['bull_pause'])
)
# ATR波动率过滤 - 继续放宽
informative['atr_ok_5m'] = (
(informative['atr_pct_5m'] > 0.07) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 2.2)
)
# 成交量确认
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * 0.75
# 合并5分钟数据到1分钟
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# ==================== 1分钟指标 ====================
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
dataframe['macd_hist'] = hist_1m
dataframe['ema9'] = ta.EMA(dataframe['close'], timeperiod=9)
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
# 1分钟MACD斜率
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# 1分钟做空入场信号
dataframe['price_high_5'] = dataframe['high'].rolling(window=5).max()
dataframe['macd_high_5'] = dataframe['macd'].rolling(window=5).max()
# 顶背离
dataframe['top_divergence'] = (
(dataframe['high'] >= dataframe['price_high_5'] * 0.999) &
(dataframe['macd'] < dataframe['macd_high_5']) &
(dataframe['macd_slope'] < 0) &
(dataframe['macd'] < dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
# EMA死叉
dataframe['ema_cross_down'] = (
(dataframe['ema9'] < dataframe['ema21']) &
(dataframe['ema9'].shift(1) >= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] < 55) &
(dataframe['rsi'] > 35) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
# 熊市回调
dataframe['is_bear_candle'] = (
(dataframe['close'] < dataframe['open']) &
((dataframe['open'] - dataframe['close']) / dataframe['open'] > 0.008)
)
dataframe['bear_pullback'] = (
dataframe['is_bear_candle'].shift(2) &
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
(dataframe['high'] < dataframe['high'].shift(2)) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['ema9'])
)
# 时间过滤
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 安全转换5分钟布尔列
bool_cols = [
'can_long_5m_5m', 'can_short_5m_5m',
'trend_bear_5m_5m',
'atr_ok_5m_5m',
'below_ema200_5m', 'bull_pause_5m',
'volume_ok_5m_5m',
]
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
num_cols = ['atr_pct_5m_5m', 'rsi_5m_5m', 'macd_hist_5m_5m', 'atr_pct_ma_5m_5m',
'ema200_dist_pct_5m', 'ema200_slope_5m']
for col in num_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(float).fillna(0.0)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
atr_ok = dataframe['atr_ok_5m_5m']
# 5分钟MACD方向确认
macd_bear_5m = dataframe['macd_hist_5m_5m'] < 0
# 1分钟MACD方向确认
macd_bear_1m = dataframe['macd_hist'] < 0
# 成交量确认
volume_ok = dataframe['volume_ok_5m_5m']
# 做空入场
dataframe.loc[
(time_ok) &
(atr_ok) &
(dataframe['can_short_5m_5m']) &
(macd_bear_5m) &
(macd_bear_1m) &
(volume_ok) &
(dataframe['rsi'] > 30) &
(
dataframe['top_divergence'] |
dataframe['ema_cross_down'] |
dataframe['bear_pullback']
) &
(dataframe['volume'] > 0),
'enter_short'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
dataframe.loc[:, 'exit_short'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
"""自定义出场逻辑:时间止损"""
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
# 时间止损:持仓过久且亏损
if trade_duration > 8 and current_profit < -0.005:
return 'time_stop_8h'
if trade_duration > 16 and current_profit < 0:
return 'time_stop_16h'
# 持仓超过24小时强制平仓
if trade_duration > 24:
return 'time_stop_24h'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
"""入场确认 - 时间过滤安全网"""
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return self.lev
@@ -0,0 +1,36 @@
{
"strategy_name": "CryptoFutures1m5mStrategyV2Hyperopt",
"params": {
"roi": {},
"stoploss": {
"stoploss": -0.025
},
"trailing": {
"trailing_stop": true,
"trailing_stop_positive": 0.008,
"trailing_stop_positive_offset": 0.032,
"trailing_only_offset_is_reached": true
},
"max_open_trades": {
"max_open_trades": 1
},
"buy": {
"adx_max": 54,
"adx_min": 28,
"atr_max_mult": 1.6,
"atr_min": 0.07,
"ema200_dist": -1.5,
"entry_rsi_min": 24,
"rsi_max": 55,
"rsi_min": 27,
"time_stop_1": 11,
"time_stop_2": 18,
"time_stop_3": 22,
"volume_threshold": 1.4
},
"sell": {},
"protection": {}
},
"ft_stratparam_v": 1,
"export_time": "2026-03-06 15:30:11.046917+00:00"
}
@@ -0,0 +1,305 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from freqtrade.strategy import IStrategy, merge_informative_pair, IntParameter, DecimalParameter, BooleanParameter
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import numpy as np
from datetime import datetime
from typing import Optional
from freqtrade.persistence import Trade
import warnings
# 抑制 pandas FutureWarning 关于 fillna 的隐式降级警告
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
pd.set_option('future.no_silent_downcasting', True)
# freqtrade hyperopt -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategyV2Hyperopt --strategy-path ./user_data/Chan/strategies --timerange=20260101- --epochs 200 -j 4 --space buy
# freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategyV2Hyperopt --strategy-path ./user_data/Chan/strategies --timerange=20260101-
class CryptoFutures1m5mStrategyV2Hyperopt(IStrategy):
"""
SOL/USDT 合约策略 - 1分钟+5分钟双时间框架 V2 Hyperopt优化版 (Short Only)
基于V2优化版添加Hyperopt参数:
1. ATR波动率过滤参数
2. 时间止损参数
3. 趋势确认参数(ADX, RSI, EMA200距离)
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = True
lev = 1.0
# 硬止损
stoploss = -0.025
# 追踪止盈 - 固定值
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.032
trailing_only_offset_is_reached = True
# ==================== Hyperoptable Parameters ====================
# ATR波动率过滤 - 可优化
atr_min = DecimalParameter(low=0.03, high=0.15, default=0.07, decimals=2, space='buy', optimize=True)
atr_max_mult = DecimalParameter(low=1.5, high=3.5, default=2.2, decimals=1, space='buy', optimize=True)
# EMA200距离阈值 - 可优化
ema200_dist = DecimalParameter(low=-3.0, high=-0.5, default=-1.0, decimals=1, space='buy', optimize=True)
# 5分钟ADX范围 - 可优化
adx_min = IntParameter(low=15, high=30, default=24, space='buy', optimize=True)
adx_max = IntParameter(low=35, high=60, default=51, space='buy', optimize=True)
# 5分钟RSI范围 - 可优化
rsi_min = IntParameter(low=20, high=40, default=29, space='buy', optimize=True)
rsi_max = IntParameter(low=40, high=60, default=48, space='buy', optimize=True)
# 时间止损 - 可优化
time_stop_1 = IntParameter(low=4, high=12, default=8, space='buy', optimize=True)
time_stop_2 = IntParameter(low=12, high=20, default=16, space='buy', optimize=True)
time_stop_3 = IntParameter(low=20, high=36, default=24, space='buy', optimize=True)
# 1分钟RSI入场阈值 - 可优化
entry_rsi_min = IntParameter(low=20, high=45, default=30, space='buy', optimize=True)
# 成交量确认阈值 - 可优化
volume_threshold = DecimalParameter(low=0.5, high=1.5, default=0.75, decimals=2, space='buy', optimize=True)
# 完全禁用 exit_signal
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [
("SOL/USDT:USDT", "5m"),
]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# ==================== 5分钟指标 ====================
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA趋势
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
# EMA12斜率(3根K线变化率)
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
macd, macd_signal, macd_hist = ta.MACD(informative['close'], fastperiod=12, slowperiod=26, signalperiod=9)
informative['macd_5m'] = macd
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX趋势强度
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI5分钟)
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR5分钟)
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
# ATR 长期均值
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# EMA200 大趋势过滤
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
# EMA200斜率
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# 大趋势过滤(Short Only- 使用hyperopt参数
informative['below_ema200'] = informative['ema200_dist_pct'] < self.ema200_dist.value
# 牛市暂停
informative['bull_pause'] = (
(informative['ema200_slope'] > 0) &
(informative['ema200_dist_pct'] > 0)
)
# 5分钟趋势判断(仅Short- 使用hyperopt参数
informative['trend_bear_5m'] = (
(informative['ema12'] < informative['ema26']) &
(informative['ema26'] < informative['ema50']) &
(informative['ema12_slope'] < -0.05) &
(informative['adx_5m'] > self.adx_min.value) &
(informative['adx_5m'] < self.adx_max.value) &
(informative['close'] < informative['ema12']) &
(informative['rsi_5m'] < self.rsi_max.value) &
(informative['rsi_5m'] > self.rsi_min.value)
)
# 做空条件
informative['can_long_5m'] = False
informative['can_short_5m'] = (
informative['trend_bear_5m'] &
informative['below_ema200'] &
(~informative['bull_pause'])
)
# ATR波动率过滤 - 使用hyperopt参数
informative['atr_ok_5m'] = (
(informative['atr_pct_5m'] > self.atr_min.value) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * self.atr_max_mult.value)
)
# 成交量确认 - 使用hyperopt参数
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * self.volume_threshold.value
# 合并5分钟数据到1分钟
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# ==================== 1分钟指标 ====================
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
dataframe['macd_hist'] = hist_1m
dataframe['ema9'] = ta.EMA(dataframe['close'], timeperiod=9)
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
# 1分钟MACD斜率
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# 1分钟做空入场信号
dataframe['price_high_5'] = dataframe['high'].rolling(window=5).max()
dataframe['macd_high_5'] = dataframe['macd'].rolling(window=5).max()
# 顶背离
dataframe['top_divergence'] = (
(dataframe['high'] >= dataframe['price_high_5'] * 0.999) &
(dataframe['macd'] < dataframe['macd_high_5']) &
(dataframe['macd_slope'] < 0) &
(dataframe['macd'] < dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
# EMA死叉
dataframe['ema_cross_down'] = (
(dataframe['ema9'] < dataframe['ema21']) &
(dataframe['ema9'].shift(1) >= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] < 55) &
(dataframe['rsi'] > 35) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
# 熊市回调
dataframe['is_bear_candle'] = (
(dataframe['close'] < dataframe['open']) &
((dataframe['open'] - dataframe['close']) / dataframe['open'] > 0.008)
)
dataframe['bear_pullback'] = (
dataframe['is_bear_candle'].shift(2) &
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
(dataframe['high'] < dataframe['high'].shift(2)) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['ema9'])
)
# 时间过滤
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 安全转换5分钟布尔列
bool_cols = [
'can_long_5m_5m', 'can_short_5m_5m',
'trend_bear_5m_5m',
'atr_ok_5m_5m',
'below_ema200_5m', 'bull_pause_5m',
'volume_ok_5m_5m',
]
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
num_cols = ['atr_pct_5m_5m', 'rsi_5m_5m', 'macd_hist_5m_5m', 'atr_pct_ma_5m_5m',
'ema200_dist_pct_5m', 'ema200_slope_5m']
for col in num_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(float).fillna(0.0)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
atr_ok = dataframe['atr_ok_5m_5m']
# 5分钟MACD方向确认
macd_bear_5m = dataframe['macd_hist_5m_5m'] < 0
# 1分钟MACD方向确认
macd_bear_1m = dataframe['macd_hist'] < 0
# 成交量确认
volume_ok = dataframe['volume_ok_5m_5m']
# 做空入场 - 使用hyperopt参数
dataframe.loc[
(time_ok) &
(atr_ok) &
(dataframe['can_short_5m_5m']) &
(macd_bear_5m) &
(macd_bear_1m) &
(volume_ok) &
(dataframe['rsi'] > self.entry_rsi_min.value) &
(
dataframe['top_divergence'] |
dataframe['ema_cross_down'] |
dataframe['bear_pullback']
) &
(dataframe['volume'] > 0),
'enter_short'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
dataframe.loc[:, 'exit_short'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
"""自定义出场逻辑:时间止损 - 使用hyperopt参数"""
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
# 时间止损:持仓过久且亏损
if trade_duration > self.time_stop_1.value and current_profit < -0.005:
return 'time_stop_1'
if trade_duration > self.time_stop_2.value and current_profit < 0:
return 'time_stop_2'
# 持仓超过24小时强制平仓
if trade_duration > self.time_stop_3.value:
return 'time_stop_3'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
"""入场确认 - 时间过滤安全网"""
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return self.lev
+364
View File
@@ -0,0 +1,364 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import numpy as np
from datetime import datetime
from typing import Optional
from freqtrade.persistence import Trade
import warnings
# 抑制 pandas FutureWarning 关于 fillna 的隐式降级警告
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
pd.set_option('future.no_silent_downcasting', True)
# freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategyV3 --strategy-path ./user_data/Chan/strategies --timerange=20250101-
class CryptoFutures1m5mStrategyV3(IStrategy):
"""
SOL/USDT 合约策略 - 1分钟+5分钟双时间框架 V3 多空双开版
基于V2优化:
1. 多空双开 - 牛市做多,熊市做空
2. 做多:EMA多头排列 + ADX确认 + RSI超卖反弹
3. 做空:保持V2核心逻辑
核心设计:
1. 5分钟趋势确认:
- 做多:EMA12>EMA26>EMA50 + ADX>25 + RSI 52-70
- 做空:EMA12<EMA26<EMA50 + ADX>25 + RSI 30-48
2. ATR自适应波动率过滤
3. 1分钟精确入场
4. trailing_stop_positive_offset = 0.035
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = True
can_long = True
lev = 1.0
# 止损止盈
stoploss = -0.028 # 2.8% 硬止损
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.035
trailing_only_offset_is_reached = True
# 完全禁用 exit_signal
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [
("SOL/USDT:USDT", "5m"),
]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# ==================== 5分钟指标 ====================
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA趋势
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
# EMA12斜率(3根K线变化率)
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
macd, macd_signal, macd_hist = ta.MACD(informative['close'], fastperiod=12, slowperiod=26, signalperiod=9)
informative['macd_5m'] = macd
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX趋势强度
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI5分钟)
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR5分钟)
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
# ATR 长期均值
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# EMA200 大趋势过滤
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
# EMA200斜率
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# ==================== 多空趋势判断 ====================
# 5分钟趋势判断 - 做多 (Bull)
informative['trend_bull_5m'] = (
(informative['ema12'] > informative['ema26']) &
(informative['ema26'] > informative['ema50']) &
(informative['ema12_slope'] > 0.05) &
(informative['adx_5m'] > 24) &
(informative['adx_5m'] < 51) &
(informative['close'] > informative['ema12']) &
(informative['rsi_5m'] > 52) &
(informative['rsi_5m'] < 72)
)
# 5分钟趋势判断 - 做空 (Bear) - 保持V2逻辑
informative['trend_bear_5m'] = (
(informative['ema12'] < informative['ema26']) &
(informative['ema26'] < informative['ema50']) &
(informative['ema12_slope'] < -0.05) &
(informative['adx_5m'] > 24) &
(informative['adx_5m'] < 51) &
(informative['close'] < informative['ema12']) &
(informative['rsi_5m'] < 48) &
(informative['rsi_5m'] > 29)
)
# 大趋势过滤
informative['above_ema200'] = informative['ema200_dist_pct'] > 1.0 # 做多需要高于EMA200
informative['below_ema200'] = informative['ema200_dist_pct'] < -1.0 # 做空需要低于EMA200
# 牛市环境 (仅做多)
informative['bull_market'] = (
(informative['ema200_slope'] > 0) &
(informative['ema200_dist_pct'] > 0)
)
# 熊市环境 (仅做空)
informative['bear_market'] = (
(informative['ema200_slope'] < 0) &
(informative['ema200_dist_pct'] < 0)
)
# 做多条件
informative['can_long_5m'] = (
informative['trend_bull_5m'] &
informative['above_ema200']
)
# 做空条件 - 保持V2逻辑
informative['can_short_5m'] = (
informative['trend_bear_5m'] &
informative['below_ema200']
)
# ATR波动率过滤
informative['atr_ok_5m'] = (
(informative['atr_pct_5m'] > 0.07) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 2.2)
)
# 成交量确认
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * 0.75
# 合并5分钟数据到1分钟
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# ==================== 1分钟指标 ====================
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
dataframe['macd_hist'] = hist_1m
dataframe['ema9'] = ta.EMA(dataframe['close'], timeperiod=9)
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
# 1分钟MACD斜率
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# ==================== 做空信号 (保持V2) ====================
# 1分钟价格/MACD
dataframe['price_high_5'] = dataframe['high'].rolling(window=5).max()
dataframe['macd_high_5'] = dataframe['macd'].rolling(window=5).max()
# 顶背离 (做空)
dataframe['top_divergence'] = (
(dataframe['high'] >= dataframe['price_high_5'] * 0.999) &
(dataframe['macd'] < dataframe['macd_high_5']) &
(dataframe['macd_slope'] < 0) &
(dataframe['macd'] < dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
# EMA死叉 (做空)
dataframe['ema_cross_down'] = (
(dataframe['ema9'] < dataframe['ema21']) &
(dataframe['ema9'].shift(1) >= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] < 55) &
(dataframe['rsi'] > 35) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
# 熊市回调 (做空)
dataframe['is_bear_candle'] = (
(dataframe['close'] < dataframe['open']) &
((dataframe['open'] - dataframe['close']) / dataframe['open'] > 0.008)
)
dataframe['bear_pullback'] = (
dataframe['is_bear_candle'].shift(2) &
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
(dataframe['high'] < dataframe['high'].shift(2)) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['ema9'])
)
# ==================== 做多信号 (新增) ====================
dataframe['price_low_5'] = dataframe['low'].rolling(window=5).min()
dataframe['macd_low_5'] = dataframe['macd'].rolling(window=5).min()
# 底背离 (做多)
dataframe['bottom_divergence'] = (
(dataframe['low'] <= dataframe['price_low_5'] * 1.001) &
(dataframe['macd'] > dataframe['macd_low_5']) &
(dataframe['macd_slope'] > 0) &
(dataframe['macd'] > dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
# EMA金叉 (做多)
dataframe['ema_cross_up'] = (
(dataframe['ema9'] > dataframe['ema21']) &
(dataframe['ema9'].shift(1) <= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] > 45) &
(dataframe['rsi'] < 70) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
# 牛市回调 (做多)
dataframe['is_bull_candle'] = (
(dataframe['close'] > dataframe['open']) &
((dataframe['close'] - dataframe['open']) / dataframe['open'] > 0.008)
)
dataframe['bull_pullback'] = (
dataframe['is_bull_candle'].shift(2) &
(dataframe['close'].shift(1) < dataframe['open'].shift(1)) &
(dataframe['low'] > dataframe['low'].shift(2)) &
(dataframe['close'] > dataframe['open']) &
(dataframe['close'] > dataframe['ema9'])
)
# 时间过滤
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 安全转换5分钟布尔列
bool_cols = [
'can_long_5m_5m', 'can_short_5m_5m',
'trend_bull_5m_5m', 'trend_bear_5m_5m',
'atr_ok_5m_5m',
'above_ema200_5m', 'below_ema200_5m',
'bull_market_5m', 'bear_market_5m',
'volume_ok_5m_5m',
]
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
num_cols = ['atr_pct_5m_5m', 'rsi_5m_5m', 'macd_hist_5m_5m', 'atr_pct_ma_5m_5m',
'ema200_dist_pct_5m', 'ema200_slope_5m']
for col in num_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(float).fillna(0.0)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
atr_ok = dataframe['atr_ok_5m_5m']
volume_ok = dataframe['volume_ok_5m_5m']
# ========== 做空入场 (保持V2逻辑) ==========
macd_bear_5m = dataframe['macd_hist_5m_5m'] < 0
macd_bear_1m = dataframe['macd_hist'] < 0
dataframe.loc[
(time_ok) &
(atr_ok) &
(dataframe['can_short_5m_5m']) &
(macd_bear_5m) &
(macd_bear_1m) &
(volume_ok) &
(dataframe['rsi'] > 30) &
(
dataframe['top_divergence'] |
dataframe['ema_cross_down'] |
dataframe['bear_pullback']
) &
(dataframe['volume'] > 0),
'enter_short'
] = 1
# ========== 做多入场 (新增) ==========
macd_bull_5m = dataframe['macd_hist_5m_5m'] > 0
macd_bull_1m = dataframe['macd_hist'] > 0
dataframe.loc[
(time_ok) &
(atr_ok) &
(dataframe['can_long_5m_5m']) &
(macd_bull_5m) &
(macd_bull_1m) &
(volume_ok) &
(dataframe['rsi'] < 70) &
(dataframe['rsi'] > 40) &
(
dataframe['bottom_divergence'] |
dataframe['ema_cross_up'] |
dataframe['bull_pullback']
) &
(dataframe['volume'] > 0),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
dataframe.loc[:, 'exit_short'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
"""自定义出场逻辑:时间止损"""
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
# 时间止损:持仓过久且亏损
if trade_duration > 8 and current_profit < -0.005:
return 'time_stop_8h'
if trade_duration > 16 and current_profit < 0:
return 'time_stop_16h'
# 持仓超过24小时强制平仓
if trade_duration > 24:
return 'time_stop_24h'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
"""入场确认 - 时间过滤安全网"""
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return self.lev
+404
View File
@@ -0,0 +1,404 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from freqtrade.strategy import IStrategy, merge_informative_pair, IntParameter, CategoricalParameter
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import numpy as np
from datetime import datetime
from typing import Optional
from freqtrade.persistence import Trade
import warnings
# 抑制 pandas FutureWarning 关于 fillna 的隐式降级警告
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
pd.set_option('future.no_silent_downcasting', True)
# freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategyV4 --strategy-path ./user_data/Chan/strategies --timerange=20250101-
class CryptoFutures1m5mStrategyV4(IStrategy):
"""
SOL/USDT 合约策略 - 1分钟+5分钟双时间框架 V4 多空完全分离版
基于V3优化:
1. 多空参数完全分离
2. 分别优化做多做空的风险参数
核心设计:
1. 5分钟趋势确认 + 1分钟精确入场
2. ATR自适应波动率过滤
3. 多空trailing参数分离
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = True
can_long = True
lev = 1.0
# ==================== 多空分离参数 ====================
# 做多止损 (更宽松,因为牛市回调幅度大)
stoploss_long = -0.035
# 做空止损 (相对紧凑,熊市反弹快)
stoploss_short = -0.025
# 统一下跌止损(取两者较宽松值)
stoploss = -0.035
# Trailing Stop - 做多
trailing_stop_long = True
trailing_stop_positive_long = 0.006
trailing_stop_positive_offset_long = 0.030
# Trailing Stop - 做空
trailing_stop_short = True
trailing_stop_positive_short = 0.010
trailing_stop_positive_offset_short = 0.038
# 统一设置
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.035
trailing_only_offset_is_reached = True
# 完全禁用 exit_signal
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [
("SOL/USDT:USDT", "5m"),
]
def get_stoploss(self, side: str, trade: Optional[Trade] = None, current_rate: float = 0,
current_time: datetime = None, after_fill: bool = False, **kwargs) -> float:
"""动态获取多空不同的止损"""
if side == "long":
return self.stoploss_long
elif side == "short":
return self.stoploss_short
return self.stoploss
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# ==================== 5分钟指标 ====================
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA趋势
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
# EMA12斜率(3根K线变化率)
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
macd, macd_signal, macd_hist = ta.MACD(informative['close'], fastperiod=12, slowperiod=26, signalperiod=9)
informative['macd_5m'] = macd
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX趋势强度
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI5分钟)
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR5分钟)
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
# ATR 长期均值
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# ATR 短期均值 (用于做空过滤 - 更严格)
informative['atr_pct_ma_short_5m'] = informative['atr_pct_5m'].rolling(window=20).mean()
# EMA200 大趋势过滤
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
# EMA200斜率
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# ==================== 多空趋势判断 ====================
# 5分钟趋势判断 - 做多 (Bull)
informative['trend_bull_5m'] = (
(informative['ema12'] > informative['ema26']) &
(informative['ema26'] > informative['ema50']) &
(informative['ema12_slope'] > 0.05) &
(informative['adx_5m'] > 22) &
(informative['adx_5m'] < 55) &
(informative['close'] > informative['ema12']) &
(informative['rsi_5m'] > 50) &
(informative['rsi_5m'] < 75)
)
# 5分钟趋势判断 - 做空 (Bear)
informative['trend_bear_5m'] = (
(informative['ema12'] < informative['ema26']) &
(informative['ema26'] < informative['ema50']) &
(informative['ema12_slope'] < -0.05) &
(informative['adx_5m'] > 26) &
(informative['adx_5m'] < 50) &
(informative['close'] < informative['ema12']) &
(informative['rsi_5m'] < 50) &
(informative['rsi_5m'] > 28)
)
# 大趋势过滤
informative['above_ema200'] = informative['ema200_dist_pct'] > 1.0
informative['below_ema200'] = informative['ema200_dist_pct'] < -1.0
# 牛市/熊市环境
informative['bull_market'] = (
(informative['ema200_slope'] > 0) &
(informative['ema200_dist_pct'] > 0)
)
informative['bear_market'] = (
(informative['ema200_slope'] < 0) &
(informative['ema200_dist_pct'] < 0)
)
# 做多条件
informative['can_long_5m'] = (
informative['trend_bull_5m'] &
informative['above_ema200']
)
# 做空条件
informative['can_short_5m'] = (
informative['trend_bear_5m'] &
informative['below_ema200']
)
# ==================== 多空分离的ATR过滤 ====================
# 做多ATR过滤 - 允许更大波动(牛市波动大)
informative['atr_ok_long_5m'] = (
(informative['atr_pct_5m'] > 0.08) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 2.5)
)
# 做空ATR过滤 - 稍微严格(需要更明确的趋势)
informative['atr_ok_short_5m'] = (
(informative['atr_pct_5m'] > 0.06) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_short_5m'] * 2.0)
)
# 成交量确认
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * 0.75
# 合并5分钟数据到1分钟
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# ==================== 1分钟指标 ====================
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
dataframe['macd_hist'] = hist_1m
dataframe['ema9'] = ta.EMA(dataframe['close'], timeperiod=9)
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
# 1分钟MACD斜率
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# ==================== 做空信号 ====================
dataframe['price_high_5'] = dataframe['high'].rolling(window=5).max()
dataframe['macd_high_5'] = dataframe['macd'].rolling(window=5).max()
# 顶背离 (做空)
dataframe['top_divergence'] = (
(dataframe['high'] >= dataframe['price_high_5'] * 0.999) &
(dataframe['macd'] < dataframe['macd_high_5']) &
(dataframe['macd_slope'] < 0) &
(dataframe['macd'] < dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
# EMA死叉 (做空)
dataframe['ema_cross_down'] = (
(dataframe['ema9'] < dataframe['ema21']) &
(dataframe['ema9'].shift(1) >= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] < 58) &
(dataframe['rsi'] > 35) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
# 熊市回调 (做空)
dataframe['is_bear_candle'] = (
(dataframe['close'] < dataframe['open']) &
((dataframe['open'] - dataframe['close']) / dataframe['open'] > 0.008)
)
dataframe['bear_pullback'] = (
dataframe['is_bear_candle'].shift(2) &
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
(dataframe['high'] < dataframe['high'].shift(2)) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['ema9'])
)
# ==================== 做多信号 ====================
dataframe['price_low_5'] = dataframe['low'].rolling(window=5).min()
dataframe['macd_low_5'] = dataframe['macd'].rolling(window=5).min()
# 底背离 (做多)
dataframe['bottom_divergence'] = (
(dataframe['low'] <= dataframe['price_low_5'] * 1.001) &
(dataframe['macd'] > dataframe['macd_low_5']) &
(dataframe['macd_slope'] > 0) &
(dataframe['macd'] > dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
# EMA金叉 (做多)
dataframe['ema_cross_up'] = (
(dataframe['ema9'] > dataframe['ema21']) &
(dataframe['ema9'].shift(1) <= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] > 42) &
(dataframe['rsi'] < 72) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
# 牛市回调 (做多)
dataframe['is_bull_candle'] = (
(dataframe['close'] > dataframe['open']) &
((dataframe['close'] - dataframe['open']) / dataframe['open'] > 0.008)
)
dataframe['bull_pullback'] = (
dataframe['is_bull_candle'].shift(2) &
(dataframe['close'].shift(1) < dataframe['open'].shift(1)) &
(dataframe['low'] > dataframe['low'].shift(2)) &
(dataframe['close'] > dataframe['open']) &
(dataframe['close'] > dataframe['ema9'])
)
# ==================== 时间过滤 ====================
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 安全转换5分钟布尔列
bool_cols = [
'can_long_5m_5m', 'can_short_5m_5m',
'trend_bull_5m_5m', 'trend_bear_5m_5m',
'atr_ok_long_5m_5m', 'atr_ok_short_5m_5m',
'above_ema200_5m', 'below_ema200_5m',
'bull_market_5m', 'bear_market_5m',
'volume_ok_5m_5m',
]
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
num_cols = ['atr_pct_5m_5m', 'rsi_5m_5m', 'macd_hist_5m_5m',
'atr_pct_ma_5m_5m', 'atr_pct_ma_short_5m_5m',
'ema200_dist_pct_5m', 'ema200_slope_5m']
for col in num_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(float).fillna(0.0)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
volume_ok = dataframe['volume_ok_5m_5m']
# ========== 做空入场 ==========
atr_ok_short = dataframe['atr_ok_short_5m_5m']
macd_bear_5m = dataframe['macd_hist_5m_5m'] < 0
macd_bear_1m = dataframe['macd_hist'] < 0
dataframe.loc[
(time_ok) &
(atr_ok_short) &
(dataframe['can_short_5m_5m']) &
(macd_bear_5m) &
(macd_bear_1m) &
(volume_ok) &
(dataframe['rsi'] > 32) &
(
dataframe['top_divergence'] |
dataframe['ema_cross_down'] |
dataframe['bear_pullback']
) &
(dataframe['volume'] > 0),
'enter_short'
] = 1
# ========== 做多入场 ==========
atr_ok_long = dataframe['atr_ok_long_5m_5m']
macd_bull_5m = dataframe['macd_hist_5m_5m'] > 0
macd_bull_1m = dataframe['macd_hist'] > 0
dataframe.loc[
(time_ok) &
(atr_ok_long) &
(dataframe['can_long_5m_5m']) &
(macd_bull_5m) &
(macd_bull_1m) &
(volume_ok) &
(dataframe['rsi'] < 72) &
(dataframe['rsi'] > 38) &
(
dataframe['bottom_divergence'] |
dataframe['ema_cross_up'] |
dataframe['bull_pullback']
) &
(dataframe['volume'] > 0),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
dataframe.loc[:, 'exit_short'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
"""自定义出场逻辑 - 多空不同的时间止损"""
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
# ==================== 做空时间止损 (更激进) ====================
if trade.trade_direction == 'short':
if trade_duration > 6 and current_profit < -0.004:
return 'time_stop_short_6h'
if trade_duration > 12 and current_profit < 0:
return 'time_stop_short_12h'
if trade_duration > 20:
return 'time_stop_short_20h'
# ==================== 做多时间止损 (更宽松) ====================
else: # long
if trade_duration > 10 and current_profit < -0.006:
return 'time_stop_long_10h'
if trade_duration > 20 and current_profit < 0:
return 'time_stop_long_20h'
if trade_duration > 30:
return 'time_stop_long_30h'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
"""入场确认 - 时间过滤安全网"""
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return self.lev
+307
View File
@@ -0,0 +1,307 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import numpy as np
from datetime import datetime
from typing import Optional
from freqtrade.persistence import Trade
import warnings
# 抑制 pandas FutureWarning 关于 fillna 的隐式降级警告
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
pd.set_option('future.no_silent_downcasting', True)
# freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategyV5 --strategy-path ./user_data/Chan/strategies --timerange=20250101-
class CryptoFutures1m5mStrategyV5(IStrategy):
"""
SOL/USDT 合约策略 - 1分钟+5分钟双时间框架 V5 多空分离版
基于V3优化:
1. 多空止损完全分离
2. 保持V3的入场逻辑不变
多空参数分离:
- 做多止损: -3.5% (更宽松)
- 做空止损: -2.5% (更紧凑)
- 做多时间止损更宽松
- 做空时间止损更激进
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = True
can_long = True
lev = 1.0
# 统一止损(兜底)
stoploss = -0.035
# Trailing设置
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.035
trailing_only_offset_is_reached = True
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [
("SOL/USDT:USDT", "5m"),
]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# ==================== 5分钟指标 ====================
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA趋势
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
# EMA12斜率
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
macd, macd_signal, macd_hist = ta.MACD(informative['close'], fastperiod=12, slowperiod=26, signalperiod=9)
informative['macd_5m'] = macd
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# EMA200
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# ==================== 多空趋势 ====================
# 做多趋势
informative['trend_bull_5m'] = (
(informative['ema12'] > informative['ema26']) &
(informative['ema26'] > informative['ema50']) &
(informative['ema12_slope'] > 0.05) &
(informative['adx_5m'] > 24) &
(informative['adx_5m'] < 51) &
(informative['close'] > informative['ema12']) &
(informative['rsi_5m'] > 52) &
(informative['rsi_5m'] < 72)
)
# 做空趋势
informative['trend_bear_5m'] = (
(informative['ema12'] < informative['ema26']) &
(informative['ema26'] < informative['ema50']) &
(informative['ema12_slope'] < -0.05) &
(informative['adx_5m'] > 24) &
(informative['adx_5m'] < 51) &
(informative['close'] < informative['ema12']) &
(informative['rsi_5m'] < 48) &
(informative['rsi_5m'] > 29)
)
# 大趋势过滤
informative['above_ema200'] = informative['ema200_dist_pct'] > 1.0
informative['below_ema200'] = informative['ema200_dist_pct'] < -1.0
# 牛熊市
informative['bull_market'] = (informative['ema200_slope'] > 0) & (informative['ema200_dist_pct'] > 0)
informative['bear_market'] = (informative['ema200_slope'] < 0) & (informative['ema200_dist_pct'] < 0)
# 做多/做空条件
informative['can_long_5m'] = informative['trend_bull_5m'] & informative['above_ema200']
informative['can_short_5m'] = informative['trend_bear_5m'] & informative['below_ema200']
# ATR过滤 (保持V3)
informative['atr_ok_5m'] = (
(informative['atr_pct_5m'] > 0.07) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 2.2)
)
# 成交量
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * 0.75
# 合并
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# ==================== 1分钟指标 ====================
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
dataframe['macd_hist'] = hist_1m
dataframe['ema9'] = ta.EMA(dataframe['close'], timeperiod=9)
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# ==================== 做空信号 ====================
dataframe['price_high_5'] = dataframe['high'].rolling(window=5).max()
dataframe['macd_high_5'] = dataframe['macd'].rolling(window=5).max()
dataframe['top_divergence'] = (
(dataframe['high'] >= dataframe['price_high_5'] * 0.999) &
(dataframe['macd'] < dataframe['macd_high_5']) &
(dataframe['macd_slope'] < 0) &
(dataframe['macd'] < dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
dataframe['ema_cross_down'] = (
(dataframe['ema9'] < dataframe['ema21']) &
(dataframe['ema9'].shift(1) >= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] < 55) &
(dataframe['rsi'] > 35) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
dataframe['is_bear_candle'] = (dataframe['close'] < dataframe['open']) & ((dataframe['open'] - dataframe['close']) / dataframe['open'] > 0.008)
dataframe['bear_pullback'] = (
dataframe['is_bear_candle'].shift(2) &
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
(dataframe['high'] < dataframe['high'].shift(2)) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['ema9'])
)
# ==================== 做多信号 ====================
dataframe['price_low_5'] = dataframe['low'].rolling(window=5).min()
dataframe['macd_low_5'] = dataframe['macd'].rolling(window=5).min()
dataframe['bottom_divergence'] = (
(dataframe['low'] <= dataframe['price_low_5'] * 1.001) &
(dataframe['macd'] > dataframe['macd_low_5']) &
(dataframe['macd_slope'] > 0) &
(dataframe['macd'] > dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
dataframe['ema_cross_up'] = (
(dataframe['ema9'] > dataframe['ema21']) &
(dataframe['ema9'].shift(1) <= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] > 45) &
(dataframe['rsi'] < 70) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
dataframe['is_bull_candle'] = (dataframe['close'] > dataframe['open']) & ((dataframe['close'] - dataframe['open']) / dataframe['open'] > 0.008)
dataframe['bull_pullback'] = (
dataframe['is_bull_candle'].shift(2) &
(dataframe['close'].shift(1) < dataframe['open'].shift(1)) &
(dataframe['low'] > dataframe['low'].shift(2)) &
(dataframe['close'] > dataframe['open']) &
(dataframe['close'] > dataframe['ema9'])
)
# 时间过滤
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 类型转换
bool_cols = ['can_long_5m_5m', 'can_short_5m_5m', 'trend_bull_5m_5m', 'trend_bear_5m_5m',
'atr_ok_5m_5m', 'above_ema200_5m', 'below_ema200_5m', 'bull_market_5m', 'bear_market_5m', 'volume_ok_5m_5m']
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
num_cols = ['atr_pct_5m_5m', 'rsi_5m_5m', 'macd_hist_5m_5m', 'atr_pct_ma_5m_5m', 'ema200_dist_pct_5m', 'ema200_slope_5m']
for col in num_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(float).fillna(0.0)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
atr_ok = dataframe['atr_ok_5m_5m']
volume_ok = dataframe['volume_ok_5m_5m']
# 做空入场 (完全保持V3)
macd_bear_5m = dataframe['macd_hist_5m_5m'] < 0
macd_bear_1m = dataframe['macd_hist'] < 0
dataframe.loc[
(time_ok) & (atr_ok) & (dataframe['can_short_5m_5m']) &
(macd_bear_5m) & (macd_bear_1m) & (volume_ok) &
(dataframe['rsi'] > 30) &
(dataframe['top_divergence'] | dataframe['ema_cross_down'] | dataframe['bear_pullback']) &
(dataframe['volume'] > 0),
'enter_short'
] = 1
# 做多入场 (完全保持V3)
macd_bull_5m = dataframe['macd_hist_5m_5m'] > 0
macd_bull_1m = dataframe['macd_hist'] > 0
dataframe.loc[
(time_ok) & (atr_ok) & (dataframe['can_long_5m_5m']) &
(macd_bull_5m) & (macd_bull_1m) & (volume_ok) &
(dataframe['rsi'] < 70) & (dataframe['rsi'] > 40) &
(dataframe['bottom_divergence'] | dataframe['ema_cross_up'] | dataframe['bull_pullback']) &
(dataframe['volume'] > 0),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
dataframe.loc[:, 'exit_short'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
"""多空分离的时间止损"""
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
# 做空时间止损 - 更激进
if trade.trade_direction == 'short':
if trade_duration > 8 and current_profit < -0.005:
return 'time_stop_short_8h'
if trade_duration > 16 and current_profit < 0:
return 'time_stop_short_16h'
if trade_duration > 24:
return 'time_stop_short_24h'
# 做多时间止损 - 更宽松
else:
if trade_duration > 10 and current_profit < -0.006:
return 'time_stop_long_10h'
if trade_duration > 20 and current_profit < 0:
return 'time_stop_long_20h'
if trade_duration > 30:
return 'time_stop_long_30h'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return self.lev
+304
View File
@@ -0,0 +1,304 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import numpy as np
from datetime import datetime
from typing import Optional
from freqtrade.persistence import Trade
import warnings
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays.*')
pd.set_option('future.no_silent_downcasting', True)
# freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy CryptoFutures1m5mStrategyV6 --strategy-path ./user_data/Chan/strategies --timerange=20250101-
class CryptoFutures1m5mStrategyV6(IStrategy):
"""
SOL/USDT 合约策略 - V6 强化做空版
基于V5优化:
1. 做空条件更严格 - 需要更强的趋势确认
2. 做空ATR过滤更严格 - 避免震荡市
3. 做空入场增加"超跌反弹"信号
核心改动:
- Short: 只做"主跌浪",不抄反弹
- Long: 保持原有逻辑
"""
INTERFACE_VERSION = 3
timeframe = '1m'
informative_timeframe = '5m'
can_short = True
can_long = True
lev = 1.0
stoploss = -0.030
trailing_stop = True
trailing_stop_positive = 0.008
trailing_stop_positive_offset = 0.035
trailing_only_offset_is_reached = True
use_exit_signal = False
process_only_new_candles = True
startup_candle_count: int = 1100
def informative_pairs(self):
return [("SOL/USDT:USDT", "5m")]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
inf_tf = self.informative_timeframe
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# EMA
informative['ema12'] = ta.EMA(informative['close'], timeperiod=12)
informative['ema26'] = ta.EMA(informative['close'], timeperiod=26)
informative['ema50'] = ta.EMA(informative['close'], timeperiod=50)
informative['ema12_slope'] = (informative['ema12'] - informative['ema12'].shift(3)) / informative['ema12'].shift(3) * 100
# MACD
macd, macd_signal, macd_hist = ta.MACD(informative['close'], fastperiod=12, slowperiod=26, signalperiod=9)
informative['macd_5m'] = macd
informative['macd_signal_5m'] = macd_signal
informative['macd_hist_5m'] = macd_hist
# ADX
informative['adx_5m'] = ta.ADX(informative['high'], informative['low'], informative['close'], timeperiod=14)
# RSI
informative['rsi_5m'] = ta.RSI(informative['close'], timeperiod=14)
# ATR
informative['atr_5m'] = ta.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)
informative['atr_pct_5m'] = informative['atr_5m'] / informative['close'] * 100
informative['atr_pct_ma_5m'] = informative['atr_pct_5m'].rolling(window=100).mean()
# EMA200
informative['ema200'] = ta.EMA(informative['close'], timeperiod=200)
informative['ema200_dist_pct'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100
informative['ema200_slope'] = (informative['ema200'] - informative['ema200'].shift(20)) / informative['ema200'].shift(20) * 100
# ==================== 趋势判断 - 做空更严格 ====================
# 做多趋势 - 保持不变
informative['trend_bull_5m'] = (
(informative['ema12'] > informative['ema26']) &
(informative['ema26'] > informative['ema50']) &
(informative['ema12_slope'] > 0.05) &
(informative['adx_5m'] > 24) &
(informative['adx_5m'] < 51) &
(informative['close'] > informative['ema12']) &
(informative['rsi_5m'] > 52) &
(informative['rsi_5m'] < 72)
)
# 做空趋势 - 更严格!需要更强的ADX
informative['trend_bear_5m'] = (
(informative['ema12'] < informative['ema26']) &
(informative['ema26'] < informative['ema50']) &
(informative['ema12_slope'] < -0.08) & # 更陡的斜率
(informative['adx_5m'] > 28) & # 更强的趋势确认
(informative['adx_5m'] < 50) &
(informative['close'] < informative['ema12']) &
(informative['rsi_5m'] < 45) & # 更低RSI
(informative['rsi_5m'] > 25)
)
# 大趋势过滤
informative['above_ema200'] = informative['ema200_dist_pct'] > 1.0
informative['below_ema200'] = informative['ema200_dist_pct'] < -1.0
# 牛熊市
informative['bull_market'] = (informative['ema200_slope'] > 0) & (informative['ema200_dist_pct'] > 0)
informative['bear_market'] = (informative['ema200_slope'] < 0) & (informative['ema200_dist_pct'] < 0)
# 做空条件 - 必须确认在熊市
informative['can_long_5m'] = informative['trend_bull_5m'] & informative['above_ema200']
informative['can_short_5m'] = (
informative['trend_bear_5m'] &
informative['below_ema200'] &
informative['bear_market'] # 必须确认熊市
)
# ==================== ATR过滤 - 做空更严格 ====================
# 做多ATR - 保持宽松
informative['atr_ok_5m'] = (
(informative['atr_pct_5m'] > 0.07) &
(informative['atr_pct_5m'] < informative['atr_pct_ma_5m'] * 2.2)
)
# 成交量
informative['volume_ma_5m'] = ta.SMA(informative['volume'], timeperiod=20)
informative['volume_ok_5m'] = informative['volume'] > informative['volume_ma_5m'] * 0.75
# 合并
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# ==================== 1分钟指标 ====================
macd_1m, signal_1m, hist_1m = ta.MACD(dataframe['close'], fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd_1m
dataframe['macd_signal'] = signal_1m
dataframe['macd_hist'] = hist_1m
dataframe['ema9'] = ta.EMA(dataframe['close'], timeperiod=9)
dataframe['ema21'] = ta.EMA(dataframe['close'], timeperiod=21)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe['vol_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
dataframe['macd_slope'] = (dataframe['macd'] - dataframe['macd'].shift(3)) / 3
# ==================== 做空信号 ====================
dataframe['price_high_5'] = dataframe['high'].rolling(window=5).max()
dataframe['macd_high_5'] = dataframe['macd'].rolling(window=5).max()
# 顶背离 - 强化版
dataframe['top_divergence'] = (
(dataframe['high'] >= dataframe['price_high_5'] * 0.999) &
(dataframe['macd'] < dataframe['macd_high_5']) &
(dataframe['macd_slope'] < 0) &
(dataframe['macd'] < dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.8) # 更强成交量确认
)
# EMA死叉
dataframe['ema_cross_down'] = (
(dataframe['ema9'] < dataframe['ema21']) &
(dataframe['ema9'].shift(1) >= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] < 55) &
(dataframe['rsi'] > 35) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
# 熊市回调
dataframe['is_bear_candle'] = (dataframe['close'] < dataframe['open']) & ((dataframe['open'] - dataframe['close']) / dataframe['open'] > 0.008)
dataframe['bear_pullback'] = (
dataframe['is_bear_candle'].shift(2) &
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
(dataframe['high'] < dataframe['high'].shift(2)) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['ema9'])
)
# ==================== 做多信号 ====================
dataframe['price_low_5'] = dataframe['low'].rolling(window=5).min()
dataframe['macd_low_5'] = dataframe['macd'].rolling(window=5).min()
dataframe['bottom_divergence'] = (
(dataframe['low'] <= dataframe['price_low_5'] * 1.001) &
(dataframe['macd'] > dataframe['macd_low_5']) &
(dataframe['macd_slope'] > 0) &
(dataframe['macd'] > dataframe['macd_signal']) &
(dataframe['volume'] > dataframe['vol_ma20'] * 0.6)
)
dataframe['ema_cross_up'] = (
(dataframe['ema9'] > dataframe['ema21']) &
(dataframe['ema9'].shift(1) <= dataframe['ema21'].shift(1)) &
(dataframe['rsi'] > 45) &
(dataframe['rsi'] < 70) &
(dataframe['volume'] > dataframe['vol_ma20'] * 1.0)
)
dataframe['is_bull_candle'] = (dataframe['close'] > dataframe['open']) & ((dataframe['close'] - dataframe['open']) / dataframe['open'] > 0.008)
dataframe['bull_pullback'] = (
dataframe['is_bull_candle'].shift(2) &
(dataframe['close'].shift(1) < dataframe['open'].shift(1)) &
(dataframe['low'] > dataframe['low'].shift(2)) &
(dataframe['close'] > dataframe['open']) &
(dataframe['close'] > dataframe['ema9'])
)
# 时间过滤
dataframe['hour_utc'] = dataframe['date'].dt.hour
dataframe['is_bad_hour'] = dataframe['hour_utc'].isin([4, 5, 6, 7])
# 类型转换
bool_cols = ['can_long_5m_5m', 'can_short_5m_5m', 'trend_bull_5m_5m', 'trend_bear_5m_5m',
'atr_ok_5m_5m', 'above_ema200_5m', 'below_ema200_5m', 'bull_market_5m', 'bear_market_5m', 'volume_ok_5m_5m']
for col in bool_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(bool).fillna(False)
num_cols = ['atr_pct_5m_5m', 'rsi_5m_5m', 'macd_hist_5m_5m', 'atr_pct_ma_5m_5m', 'ema200_dist_pct_5m', 'ema200_slope_5m']
for col in num_cols:
if col in dataframe.columns:
dataframe[col] = dataframe[col].astype(float).fillna(0.0)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
time_ok = ~dataframe['is_bad_hour']
atr_ok = dataframe['atr_ok_5m_5m']
volume_ok = dataframe['volume_ok_5m_5m']
# 做空入场 - 更严格的熊市条件
macd_bear_5m = dataframe['macd_hist_5m_5m'] < 0
macd_bear_1m = dataframe['macd_hist'] < 0
dataframe.loc[
(time_ok) & (atr_ok) & (dataframe['can_short_5m_5m']) &
(macd_bear_5m) & (macd_bear_1m) & (volume_ok) &
(dataframe['rsi'] > 28) & # 更低RSI
(dataframe['top_divergence'] | dataframe['ema_cross_down'] | dataframe['bear_pullback']) &
(dataframe['volume'] > 0),
'enter_short'
] = 1
# 做多入场
macd_bull_5m = dataframe['macd_hist_5m_5m'] > 0
macd_bull_1m = dataframe['macd_hist'] > 0
dataframe.loc[
(time_ok) & (atr_ok) & (dataframe['can_long_5m_5m']) &
(macd_bull_5m) & (macd_bull_1m) & (volume_ok) &
(dataframe['rsi'] < 70) & (dataframe['rsi'] > 40) &
(dataframe['bottom_divergence'] | dataframe['ema_cross_up'] | dataframe['bull_pullback']) &
(dataframe['volume'] > 0),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
dataframe.loc[:, 'exit_short'] = 0
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> str | bool | None:
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
# 做空 - 更激进的时间止损
if trade.trade_direction == 'short':
if trade_duration > 6 and current_profit < -0.004:
return 'time_stop_short_6h'
if trade_duration > 12 and current_profit < 0:
return 'time_stop_short_12h'
if trade_duration > 20:
return 'time_stop_short_20h'
# 做多 - 保持宽松
else:
if trade_duration > 10 and current_profit < -0.006:
return 'time_stop_long_10h'
if trade_duration > 20 and current_profit < 0:
return 'time_stop_long_20h'
if trade_duration > 30:
return 'time_stop_long_30h'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
hour_utc = current_time.utcnow().hour if current_time.tzinfo is None else current_time.hour
if hour_utc in {4, 5, 6, 7}:
return False
return True
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return self.lev
+444
View File
@@ -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])
+204
View File
@@ -0,0 +1,204 @@
"""
纯随机规则策略 (PureRandomRuleStrategy)
完全不看 K 线不看指标不看量价不看趋势不看形态的纯规则交易系统
规则
- 固定时间周期开仓例如 4 小时一单
- 方向随机多空不做任何行情判断
- 每次只开1个方向不对冲
- 固定止盈2%
- 固定止损1%
- 到价立即平仓不移动不修改
- 单笔仓位总资金的 5%
- 单笔最大风险总资金的 0.05%
- 连续止损 3 当天停止交易
- 总持仓不超过 20%
使用命令
freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy PureRandomRuleStrategy --strategy-path ./user_data/Chan/strategies --timerange=20260101-
实盘命令
freqtrade trade -c ./user_data/Chan/config/Chan.json \
--strategy PureRandomRuleStrategy --strategy-path ./user_data/Chan/strategies
"""
import logging
from datetime import datetime
from typing import Optional
import random
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from freqtrade.strategy import IStrategy
logger = logging.getLogger(__name__)
class PureRandomRuleStrategy(IStrategy):
"""
纯随机规则策略
核心特点
1. 不看任何行情数据
2. 固定时间开仓可配置间隔
3. 随机选择多空方向
4. 固定止盈止损
5. 风险控制连续止损持仓限制
"""
INTERFACE_VERSION: int = 3
# === 基础配置 ===
timeframe = '1m' # 主时间框架
informative_timeframe = '1h' # 1小时作为参考(需要数据支持)
can_short = True
can_long = True
startup_candle_count = 200 # 需要更多数据计算 EMA
# === 交易时间间隔配置 ===
trade_interval_hours = 4
# === 止盈止损配置 ===
take_profit_pct = 0.024
stop_loss_pct = 0.01
# === 仓位配置 ===
entry_percent = 0.05
max_position_pct = 0.20
# === 风险控制 ===
max_consecutive_losses = 3
# === 订单类型 ===
order_types = {
"entry": "market",
"exit": "market",
"stoploss": "market",
"stoploss_on_exchange": False,
}
# === 最小 ROI ===
minimal_roi = {
"0": take_profit_pct,
}
# === 止损 ===
stoploss = -stop_loss_pct
# === 追踪止损 ===
trailing_stop = False
# === 策略状态 ===
_last_entry_time: Optional[datetime] = None
_consecutive_losses: int = 0
_last_loss_date: Optional[datetime] = None
_today_loss_count: int = 0
def __init__(self, config: dict) -> None:
super().__init__(config)
random.seed()
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
计算 1h EMA26 和波动幅度
使用 resampled_merge 合并 1h 数据
"""
from technical.util import resample_to_interval, resampled_merge
# 重采样到 1h (1m * 60 = 60)
dataframe_1h = resample_to_interval(dataframe, 60)
# 计算 1h EMA26
dataframe_1h['ema26'] = ta.EMA(dataframe_1h, timeperiod=26)
# 计算 1h 波动幅度: (high - low) / open * 100%
dataframe_1h['volatility'] = (dataframe_1h['high'] - dataframe_1h['low']) / dataframe_1h['open']
# 合并到主 dataframe
# 列名格式: resample_60_ema26, resample_60_volatility
dataframe = resampled_merge(dataframe, dataframe_1h)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
入场逻辑固定时间 + 1h EMA26方向过滤 + 波动过滤 + 时间过滤
规则
1. 1h EMA26 上方 -> 只做多
2. 1h EMA26 下方 -> 只做空
3. 固定时间间隔开仓4小时
4. 1h 波动幅度 > 0.5% < 5%
5. UTC 8:00-20:00
"""
dataframe['enter_long'] = 0
dataframe['enter_short'] = 0
dataframe['enter_tag'] = ''
last_entry_idx = None
# 1h EMA26 列名
ema26_col = 'resample_60_ema26'
# 1h 波动幅度列名
volatility_col = 'resample_60_volatility'
# 波动幅度阈值
min_volatility = 0.005 # 0.5%
max_volatility = 0.05 # 5%
for i in range(len(dataframe)):
current_time = dataframe['date'].iloc[i]
current_price = dataframe['close'].iloc[i]
# 使用 resample 后的 EMA26 列
ema26_1h = dataframe[ema26_col].iloc[i]
# 波动幅度
volatility = dataframe[volatility_col].iloc[i]
# 跳过没有 EMA 数据的情况
if pd.isna(ema26_1h):
continue
# 检查时间间隔(4小时)
can_entry = True
if last_entry_idx is not None:
hours_since_last = (current_time - dataframe['date'].iloc[last_entry_idx]).total_seconds() / 3600
if hours_since_last < self.trade_interval_hours:
can_entry = False
# 检查当天连续止损
if self._today_loss_count >= self.max_consecutive_losses:
can_entry = False
# 检查波动幅度(>0.5% 且 <5%
if not pd.isna(volatility):
if volatility < min_volatility or volatility > max_volatility:
can_entry = False
else:
can_entry = False
# 检查时间过滤(UTC 8:00-20:00
utc_hour = current_time.hour
#if utc_hour < 8 or utc_hour >= 20:
#can_entry = False
if can_entry:
# 判断方向:价格 > 1h EMA26 做多,价格 < 1h EMA26 做空
if current_price > ema26_1h:
dataframe.loc[dataframe.index[i], 'enter_long'] = 1
dataframe.loc[dataframe.index[i], 'enter_tag'] = 'long_above_ema'
elif current_price < ema26_1h:
dataframe.loc[dataframe.index[i], 'enter_short'] = 1
dataframe.loc[dataframe.index[i], 'enter_tag'] = 'short_below_ema'
if dataframe.loc[dataframe.index[i], 'enter_long'] == 1 or dataframe.loc[dataframe.index[i], 'enter_short'] == 1:
last_entry_idx = i
self._last_entry_time = current_time
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['exit_long'] = 0
dataframe['exit_short'] = 0
return dataframe