355 lines
14 KiB
Python
355 lines
14 KiB
Python
# --- Do not remove these libs ---
|
|
from freqtrade.strategy import IStrategy
|
|
from typing import Dict, List, Tuple, Optional
|
|
from functools import reduce
|
|
from pandas import DataFrame
|
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
import pandas as pd
|
|
|
|
# --------------------------------
|
|
from technical.util import resample_to_interval, resampled_merge
|
|
import talib.abstract as ta
|
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
from datetime import datetime, timedelta, timezone
|
|
from freqtrade.persistence import Trade, Order
|
|
from typing import Optional
|
|
import numpy as np
|
|
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_SOL.json --strategy Chan_SOL_2 --strategy-path ./user_data/Chan/strategies --timerange=20240801-20241201
|
|
|
|
class Chan_SOL_2(IStrategy):
|
|
"""
|
|
稳定盈利交易策略 - 基于多重技术分析
|
|
结合趋势跟踪、动量指标和风险管理
|
|
"""
|
|
INTERFACE_VERSION: int = 3
|
|
|
|
# 优化的ROI设置 - 阶梯式获利了结
|
|
minimal_roi = {
|
|
"0": 0.15, # 15%快速获利
|
|
"30": 0.08, # 30分钟后8%
|
|
"60": 0.05, # 1小时后5%
|
|
"120": 0.03, # 2小时后3%
|
|
"240": 0.02, # 4小时后2%
|
|
"480": 0.015, # 8小时后1.5%
|
|
"960": 0.01 # 16小时后1%
|
|
}
|
|
|
|
can_short = True
|
|
stoploss = -0.08 # 8%止损
|
|
|
|
# 动态追踪止损
|
|
trailing_stop = True
|
|
trailing_stop_positive = 0.015 # 1.5%开始追踪
|
|
trailing_stop_positive_offset = 0.025 # 2.5%偏移
|
|
trailing_only_offset_is_reached = True
|
|
|
|
# 仓位管理
|
|
position_adjustment_enable = True
|
|
max_entry_position_adjustment = 2
|
|
max_dca_multiplier = 3.0
|
|
|
|
timeframe = '5m'
|
|
startup_candle_count = 200
|
|
|
|
# 自定义参数
|
|
buy_volume_threshold = 1.5
|
|
sell_volume_threshold = 1.2
|
|
rsi_oversold = 25
|
|
rsi_overbought = 75
|
|
adx_trend_threshold = 25
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
添加技术指标 - 多维度分析
|
|
"""
|
|
# === 趋势指标 ===
|
|
# 多周期移动平均线
|
|
dataframe['ema_8'] = ta.EMA(dataframe, timeperiod=8)
|
|
dataframe['ema_21'] = ta.EMA(dataframe, timeperiod=21)
|
|
dataframe['ema_50'] = ta.EMA(dataframe, timeperiod=50)
|
|
dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200)
|
|
|
|
# === 动量指标 ===
|
|
# RSI - 超买超卖
|
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
|
dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=9)
|
|
dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=21)
|
|
|
|
# MACD - 趋势动量
|
|
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
|
|
dataframe['macd'] = macd['macd']
|
|
dataframe['macdsignal'] = macd['macdsignal']
|
|
dataframe['macdhist'] = macd['macdhist']
|
|
|
|
# === 波动率指标 ===
|
|
# ATR - 真实波动幅度
|
|
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
|
|
|
|
# 布林带
|
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
|
dataframe['bb_lowerband'] = bollinger['lower']
|
|
dataframe['bb_middleband'] = bollinger['mid']
|
|
dataframe['bb_upperband'] = bollinger['upper']
|
|
dataframe['bb_percent'] = (dataframe['close'] - dataframe['bb_lowerband']) / (dataframe['bb_upperband'] - dataframe['bb_lowerband'])
|
|
dataframe['bb_width'] = (dataframe['bb_upperband'] - dataframe['bb_lowerband']) / dataframe['bb_middleband']
|
|
|
|
# === 趋势强度指标 ===
|
|
# ADX - 趋势强度
|
|
dataframe['adx'] = ta.ADX(dataframe, timeperiod=14)
|
|
dataframe['plus_di'] = ta.PLUS_DI(dataframe, timeperiod=14)
|
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe, timeperiod=14)
|
|
|
|
# === 成交量指标 ===
|
|
# 成交量移动平均
|
|
dataframe['volume_sma_20'] = dataframe['volume'].rolling(window=20).mean()
|
|
dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma_20']
|
|
|
|
# OBV - 能量潮
|
|
dataframe['obv'] = ta.OBV(dataframe)
|
|
dataframe['obv_ema'] = ta.EMA(dataframe['obv'], timeperiod=20)
|
|
|
|
# === 价格行为指标 ===
|
|
# 价格变化率
|
|
dataframe['price_change'] = dataframe['close'].pct_change()
|
|
dataframe['price_change_5'] = dataframe['close'].pct_change(periods=5)
|
|
|
|
# 高低点分析
|
|
dataframe['high_20'] = dataframe['high'].rolling(window=20).max()
|
|
dataframe['low_20'] = dataframe['low'].rolling(window=20).min()
|
|
|
|
# === 自定义复合指标 ===
|
|
# 趋势确认信号
|
|
dataframe['trend_up'] = (
|
|
(dataframe['ema_8'] > dataframe['ema_21']) &
|
|
(dataframe['ema_21'] > dataframe['ema_50']) &
|
|
(dataframe['close'] > dataframe['ema_8'])
|
|
)
|
|
|
|
dataframe['trend_down'] = (
|
|
(dataframe['ema_8'] < dataframe['ema_21']) &
|
|
(dataframe['ema_21'] < dataframe['ema_50']) &
|
|
(dataframe['close'] < dataframe['ema_8'])
|
|
)
|
|
|
|
# 动量强度评分
|
|
dataframe['momentum_score'] = (
|
|
((dataframe['rsi'] > 50).astype(int) * 1) +
|
|
((dataframe['macd'] > dataframe['macdsignal']).astype(int) * 1) +
|
|
((dataframe['adx'] > self.adx_trend_threshold).astype(int) * 1) +
|
|
((dataframe['volume_ratio'] > 1.0).astype(int) * 1)
|
|
)
|
|
|
|
# 波动率适应性指标
|
|
dataframe['volatility_high'] = dataframe['atr'] > dataframe['atr'].rolling(window=20).mean() * 1.5
|
|
|
|
return dataframe
|
|
|
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
入场信号 - 多条件确认系统
|
|
"""
|
|
# === 多头入场条件 ===
|
|
|
|
# 条件1: 强势突破入场
|
|
dataframe.loc[
|
|
(
|
|
# 趋势确认
|
|
(dataframe['trend_up']) &
|
|
(dataframe['close'] > dataframe['ema_21']) &
|
|
|
|
# 动量确认
|
|
(dataframe['rsi'] > 45) & (dataframe['rsi'] < 75) &
|
|
(dataframe['macd'] > dataframe['macdsignal']) &
|
|
(dataframe['macdhist'] > dataframe['macdhist'].shift(1)) &
|
|
|
|
# 成交量确认
|
|
(dataframe['volume_ratio'] > self.buy_volume_threshold) &
|
|
(dataframe['obv'] > dataframe['obv_ema']) &
|
|
|
|
# 价格行为确认
|
|
(dataframe['close'] > dataframe['bb_middleband']) &
|
|
(dataframe['bb_percent'] > 0.2) & (dataframe['bb_percent'] < 0.8) &
|
|
|
|
# 趋势强度确认
|
|
(dataframe['adx'] > self.adx_trend_threshold) &
|
|
(dataframe['plus_di'] > dataframe['minus_di'])
|
|
),
|
|
['enter_long', 'enter_tag']] = (1, 'breakout_long')
|
|
|
|
# 条件2: 超卖反弹入场
|
|
dataframe.loc[
|
|
(
|
|
# 超卖反弹
|
|
(dataframe['rsi'] < self.rsi_oversold + 10) &
|
|
(dataframe['rsi'] > dataframe['rsi'].shift(1)) &
|
|
(dataframe['bb_percent'] < 0.2) &
|
|
|
|
# 趋势不能太差
|
|
(dataframe['ema_8'] >= dataframe['ema_50']) &
|
|
(dataframe['close'] > dataframe['low_20'] * 1.02) &
|
|
|
|
# 成交量支持
|
|
(dataframe['volume_ratio'] > 1.2) &
|
|
|
|
# MACD底背离迹象
|
|
(dataframe['macdhist'] > dataframe['macdhist'].shift(1)) &
|
|
|
|
# 不与第一个条件重复
|
|
(~dataframe['enter_long'].astype(bool))
|
|
),
|
|
['enter_long', 'enter_tag']] = (1, 'oversold_long')
|
|
|
|
# === 空头入场条件 ===
|
|
|
|
# 条件1: 强势下跌入场
|
|
dataframe.loc[
|
|
(
|
|
# 趋势确认
|
|
(dataframe['trend_down']) &
|
|
(dataframe['close'] < dataframe['ema_21']) &
|
|
|
|
# 动量确认
|
|
(dataframe['rsi'] < 55) & (dataframe['rsi'] > 25) &
|
|
(dataframe['macd'] < dataframe['macdsignal']) &
|
|
(dataframe['macdhist'] < dataframe['macdhist'].shift(1)) &
|
|
|
|
# 成交量确认
|
|
(dataframe['volume_ratio'] > self.sell_volume_threshold) &
|
|
(dataframe['obv'] < dataframe['obv_ema']) &
|
|
|
|
# 价格行为确认
|
|
(dataframe['close'] < dataframe['bb_middleband']) &
|
|
(dataframe['bb_percent'] > 0.2) & (dataframe['bb_percent'] < 0.8) &
|
|
|
|
# 趋势强度确认
|
|
(dataframe['adx'] > self.adx_trend_threshold) &
|
|
(dataframe['minus_di'] > dataframe['plus_di'])
|
|
),
|
|
['enter_short', 'enter_tag']] = (1, 'breakdown_short')
|
|
|
|
# 条件2: 超买回调入场
|
|
dataframe.loc[
|
|
(
|
|
# 超买回调
|
|
(dataframe['rsi'] > self.rsi_overbought - 10) &
|
|
(dataframe['rsi'] < dataframe['rsi'].shift(1)) &
|
|
(dataframe['bb_percent'] > 0.8) &
|
|
|
|
# 趋势不能太好
|
|
(dataframe['ema_8'] <= dataframe['ema_50']) &
|
|
(dataframe['close'] < dataframe['high_20'] * 0.98) &
|
|
|
|
# 成交量支持
|
|
(dataframe['volume_ratio'] > 1.2) &
|
|
|
|
# MACD顶背离迹象
|
|
(dataframe['macdhist'] < dataframe['macdhist'].shift(1)) &
|
|
|
|
# 不与第一个条件重复
|
|
(~dataframe['enter_short'].astype(bool))
|
|
),
|
|
['enter_short', 'enter_tag']] = (1, 'overbought_short')
|
|
|
|
return dataframe
|
|
|
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
出场信号 - 及时止盈止损
|
|
"""
|
|
# === 多头出场条件 ===
|
|
|
|
# 条件1: 趋势转弱
|
|
dataframe.loc[
|
|
(
|
|
(
|
|
(dataframe['rsi'] > self.rsi_overbought) |
|
|
(dataframe['macd'] < dataframe['macdsignal']) |
|
|
(dataframe['close'] < dataframe['ema_8']) |
|
|
(dataframe['bb_percent'] > 0.95) |
|
|
(dataframe['adx'] < 20)
|
|
) &
|
|
(dataframe['volume_ratio'] > 1.0)
|
|
),
|
|
['exit_long', 'exit_tag']] = (1, 'trend_weak_long')
|
|
|
|
# === 空头出场条件 ===
|
|
|
|
# 条件1: 趋势转强
|
|
dataframe.loc[
|
|
(
|
|
(
|
|
(dataframe['rsi'] < self.rsi_oversold) |
|
|
(dataframe['macd'] > dataframe['macdsignal']) |
|
|
(dataframe['close'] > dataframe['ema_8']) |
|
|
(dataframe['bb_percent'] < 0.05) |
|
|
(dataframe['adx'] < 20)
|
|
) &
|
|
(dataframe['volume_ratio'] > 1.0)
|
|
),
|
|
['exit_short', 'exit_tag']] = (1, 'trend_strong_short')
|
|
|
|
return dataframe
|
|
|
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
|
current_rate: float, current_profit: float, **kwargs) -> float:
|
|
"""
|
|
动态止损策略
|
|
"""
|
|
# 基础止损
|
|
if current_profit < -0.05: # 如果亏损超过5%,严格止损
|
|
return -0.08
|
|
|
|
# 盈利后的动态止损
|
|
if current_profit > 0.02: # 盈利超过2%后,调整止损至成本价附近
|
|
return 0.005
|
|
elif current_profit > 0.05: # 盈利超过5%后,保证1%利润
|
|
return -current_profit + 0.01
|
|
elif current_profit > 0.10: # 盈利超过10%后,保证5%利润
|
|
return -current_profit + 0.05
|
|
|
|
return self.stoploss
|
|
|
|
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
|
current_rate: float, current_profit: float,
|
|
min_stake: float, max_stake: float,
|
|
current_entry_rate: float, current_exit_rate: float,
|
|
current_entry_profit: float, current_exit_profit: float,
|
|
**kwargs) -> Optional[float]:
|
|
"""
|
|
仓位调整策略 - 金字塔加仓
|
|
"""
|
|
# 如果亏损超过3%,不加仓
|
|
if current_profit < -0.03:
|
|
return None
|
|
|
|
# 如果盈利超过2%且趋势持续,可以加仓
|
|
if current_profit > 0.02 and len(trade.select_filled_orders(trade.entry_side)) < self.max_entry_position_adjustment:
|
|
# 获取当前数据进行趋势确认
|
|
try:
|
|
# 简单的趋势确认逻辑
|
|
if trade.is_short:
|
|
return max_stake * 0.5 # 空头加仓
|
|
else:
|
|
return max_stake * 0.5 # 多头加仓
|
|
except:
|
|
pass
|
|
|
|
return None
|
|
|
|
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:
|
|
"""
|
|
杠杆设置 - 保守策略
|
|
"""
|
|
# 根据入场类型调整杠杆
|
|
if entry_tag and 'breakout' in entry_tag:
|
|
return min(2.0, max_leverage) # 突破信号使用较高杠杆
|
|
elif entry_tag and ('oversold' in entry_tag or 'overbought' in entry_tag):
|
|
return min(1.5, max_leverage) # 超买超卖信号使用中等杠杆
|
|
else:
|
|
return 1.0 # 默认无杠杆 |