386 lines
14 KiB
Python
386 lines
14 KiB
Python
"""
|
|
SOL/USDT 优化交易策略
|
|
|
|
采用多时间周期分析和缠论技术分析,专注于空头交易
|
|
集成了技术指标确认和风险管理功能
|
|
"""
|
|
|
|
# --- Do not remove these libs ---
|
|
from statistics import median
|
|
from freqtrade.strategy import IStrategy
|
|
import sys
|
|
import os
|
|
# 添加父目录到系统路径
|
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from ChanLun import ChanLun
|
|
from ChanLun_Classifier import ChanLunClassifier
|
|
from ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_BI_DIR, Chan_KLC_FX
|
|
# --------------------------------
|
|
from technical.util import resample_to_interval, resampled_merge
|
|
import talib.abstract as ta
|
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
from pandas import DataFrame
|
|
from datetime import datetime, timedelta
|
|
from freqtrade.persistence import Trade
|
|
from typing import Optional
|
|
import logging
|
|
import numpy as np
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# freqtrade trade -c ./user_data/Chan/config/ChanLun_SOL_Optimized.json --strategy ChanLun_SOL_Optimized --strategy-path ./user_data/Chan/strategies
|
|
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_SOL_Optimized.json --strategy ChanLun_SOL_Optimized --strategy-path ./user_data/Chan/strategies --timerange=20250201-
|
|
|
|
class ChanLun_SOL_Optimized(IStrategy):
|
|
"""
|
|
SOL/USDT 优化交易策略 - 专注于空头交易
|
|
"""
|
|
INTERFACE_VERSION: int = 3
|
|
|
|
# 优化后的ROI设置,主要针对短期交易
|
|
minimal_roi = {
|
|
"0": 0.012,
|
|
"120": 0.010,
|
|
"240": 0.007,
|
|
"360": 0.005
|
|
}
|
|
|
|
# 支持做空
|
|
can_short = True
|
|
only_short = True
|
|
|
|
# 杠杆设置(谨慎使用)
|
|
lev = 1.0
|
|
|
|
# 止损设置
|
|
stoploss = -0.007 * lev
|
|
|
|
# 追踪止损设置
|
|
trailing_stop = True
|
|
trailing_stop_positive = 0.003
|
|
trailing_stop_positive_offset = 0.005
|
|
trailing_only_offset_is_reached = True
|
|
|
|
# 仓位管理设置
|
|
position_adjustment_enable = True
|
|
max_entry_position_adjustment = 3
|
|
max_dca_multiplier = 4.0
|
|
|
|
# 策略初始化需要的K线数量
|
|
startup_candle_count = 200
|
|
|
|
# 时间周期定义
|
|
timeframe = '5m'
|
|
|
|
# 时间周期乘数
|
|
time5 = 5
|
|
time15 = 15
|
|
time30 = 30
|
|
time60 = 60
|
|
time4h = 240
|
|
time1d = 1440
|
|
|
|
# 缠论模块初始化
|
|
chan = ChanLun()
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
添加技术指标
|
|
"""
|
|
# 基础技术指标
|
|
for df in [dataframe]:
|
|
# 添加MACD指标
|
|
macd = ta.MACD(df)
|
|
df['macd'] = macd['macd']
|
|
df['macdsignal'] = macd['macdsignal']
|
|
df['macdhist'] = macd['macdhist']
|
|
|
|
# 添加移动平均线
|
|
df['ma5'] = ta.MA(df, timeperiod=5)
|
|
df['ma10'] = ta.MA(df, timeperiod=10)
|
|
df['ma20'] = ta.MA(df, timeperiod=20)
|
|
df['ma30'] = ta.EMA(df, timeperiod=30)
|
|
df['ma50'] = ta.MA(df, timeperiod=50)
|
|
df['ma200'] = ta.MA(df, timeperiod=200)
|
|
|
|
# 添加RSI指标
|
|
df['rsi'] = ta.RSI(df, timeperiod=14)
|
|
df['rsi_slow'] = ta.RSI(df, timeperiod=21)
|
|
|
|
# 添加ATR(波动率)
|
|
df['atr'] = ta.ATR(df, timeperiod=14)
|
|
|
|
# 计算布林带
|
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(df), window=20, stds=2)
|
|
df['bb_lowerband'] = bollinger['lower']
|
|
df['bb_middleband'] = bollinger['mid']
|
|
df['bb_upperband'] = bollinger['upper']
|
|
df['bb_width'] = (df['bb_upperband'] - df['bb_lowerband']) / df['bb_middleband']
|
|
|
|
# 添加ADX指标(趋势强度)
|
|
df['adx'] = ta.ADX(df, timeperiod=14)
|
|
df['plus_di'] = ta.PLUS_DI(df, timeperiod=14)
|
|
df['minus_di'] = ta.MINUS_DI(df, timeperiod=14)
|
|
|
|
# 添加量比指标
|
|
df['volume_ma20'] = df['volume'].rolling(window=20).mean()
|
|
df['volume_ratio'] = df['volume'] / df['volume_ma20']
|
|
|
|
# 计算下降趋势确认指标
|
|
dataframe['downtrend'] = (
|
|
(dataframe['ma5'] < dataframe['ma10']) &
|
|
(dataframe['ma10'] < dataframe['ma30']) &
|
|
(dataframe['close'] < dataframe['ma10']) &
|
|
(dataframe['close'].shift(1) > dataframe['close'])
|
|
)
|
|
|
|
return dataframe
|
|
|
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
入场信号逻辑 - 放宽条件以产生更多交易信号
|
|
"""
|
|
# 关闭多头交易
|
|
dataframe['enter_long'] = 0
|
|
|
|
# 空头入场条件 - 条件1:价格下跌趋势
|
|
dataframe.loc[
|
|
(
|
|
# 价格下跌趋势 - 放宽为仅需一根K线下跌
|
|
(dataframe['close'] < dataframe['close'].shift(1)) &
|
|
|
|
# 价格在均线下方 - 使用更短期均线
|
|
(dataframe['close'] < dataframe['ma20']) &
|
|
|
|
# RSI条件放宽 - 只要不是极度超卖
|
|
(dataframe['rsi'] > 30) &
|
|
|
|
# 成交量条件放宽
|
|
(dataframe['volume'] > dataframe['volume'].rolling(window=10).mean()) &
|
|
|
|
# MACD空头
|
|
(dataframe['macd'] < dataframe['macdsignal'])
|
|
),
|
|
['enter_short', 'enter_tag']] = (1, 'short_trend_simple')
|
|
|
|
# 空头入场条件 - 条件2:突破下降
|
|
dataframe.loc[
|
|
(
|
|
# 价格突破支撑位
|
|
(dataframe['close'] < dataframe['low'].shift(1).rolling(window=5).min()) &
|
|
|
|
# 下降动量增强
|
|
(dataframe['close'].pct_change() < -0.005) &
|
|
|
|
# 非超卖区
|
|
(dataframe['rsi'] > 35) &
|
|
|
|
# 确保不与第一个条件重复
|
|
(~dataframe['enter_short'].astype(bool))
|
|
),
|
|
['enter_short', 'enter_tag']] = (1, 'short_breakdown')
|
|
|
|
# 空头入场条件 - 条件3:均线死叉
|
|
dataframe.loc[
|
|
(
|
|
# 短期均线下穿长期均线
|
|
(qtpylib.crossed_below(dataframe['ma5'], dataframe['ma10'])) &
|
|
|
|
# 价格已经在中期均线下方
|
|
(dataframe['close'] < dataframe['ma20']) &
|
|
|
|
# 确保不与其他条件重复
|
|
(~dataframe['enter_short'].astype(bool))
|
|
),
|
|
['enter_short', 'enter_tag']] = (1, 'short_ma_cross')
|
|
|
|
return dataframe
|
|
|
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
出场信号逻辑 - 优化盈利能力和降低风险
|
|
"""
|
|
# 清除之前的出场条件
|
|
dataframe['exit_short'] = 0
|
|
dataframe['exit_long'] = 0
|
|
|
|
# 空头出场条件 - 价格反转
|
|
price_reversal = (
|
|
# 价格反转
|
|
(dataframe['close'] > dataframe['close'].shift(1)) &
|
|
(dataframe['close'] > dataframe['open']) & # 收阳
|
|
(dataframe['volume'] > dataframe['volume'].rolling(window=10).mean()) # 放量上涨
|
|
)
|
|
|
|
# 空头出场条件 - 超卖反弹
|
|
oversold_bounce = (
|
|
# RSI超卖
|
|
(dataframe['rsi'] < 30) &
|
|
(dataframe['rsi'] > dataframe['rsi'].shift(1)) # RSI回升
|
|
)
|
|
|
|
# 空头出场条件 - 盈利保护
|
|
profit_protection = (
|
|
# 突破下轨后快速回升
|
|
(dataframe['close'] < dataframe['bb_lowerband']) &
|
|
(dataframe['close'] > dataframe['close'].shift(1)) &
|
|
(dataframe['close'].shift(1) > dataframe['close'].shift(2)) # 连续上涨
|
|
)
|
|
|
|
# 空头出场条件 - 趋势转变
|
|
trend_change = (
|
|
# 价格突破短期均线
|
|
(qtpylib.crossed_above(dataframe['close'], dataframe['ma10'])) |
|
|
|
|
# MACD柱状图由负转正
|
|
(dataframe['macdhist'] > 0) &
|
|
(dataframe['macdhist'].shift(1) < 0)
|
|
)
|
|
|
|
# 组合所有出场条件
|
|
dataframe.loc[price_reversal, ['exit_short', 'exit_tag']] = (1, 'price_reversal')
|
|
dataframe.loc[oversold_bounce, ['exit_short', 'exit_tag']] = (1, 'oversold_bounce')
|
|
dataframe.loc[profit_protection, ['exit_short', 'exit_tag']] = (1, 'profit_protection')
|
|
dataframe.loc[trend_change, ['exit_short', 'exit_tag']] = (1, 'trend_change')
|
|
|
|
return dataframe
|
|
|
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
|
current_rate: float, current_profit: float, **kwargs) -> float:
|
|
"""
|
|
自定义止损逻辑 - 更精细的动态止损
|
|
"""
|
|
# 获取当前的dataframe
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
|
|
|
if len(dataframe) == 0:
|
|
return self.stoploss
|
|
|
|
# 获取最新的K线数据
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
|
|
# 计算ATR止损
|
|
atr_value = last_candle['atr']
|
|
|
|
# 根据盈利情况动态调整止损策略
|
|
if current_profit >= 0.03:
|
|
# 盈利较高,保护大部分利润,使用较紧的止损
|
|
return current_profit * 0.6
|
|
|
|
elif current_profit >= 0.015:
|
|
# 中等盈利,保护部分利润
|
|
return current_profit * 0.4
|
|
|
|
elif current_profit >= 0.008:
|
|
# 小额盈利,保本为主
|
|
return current_profit * 0.15
|
|
|
|
elif current_profit > 0:
|
|
# 微小盈利,保本为主
|
|
return 0
|
|
|
|
else:
|
|
# 亏损情况下,判断是否需要立即止损
|
|
|
|
# 趋势强烈反转,尽快止损
|
|
if (last_candle['close'] > last_candle['ma5']) and (last_candle['macd'] > last_candle['macdsignal']):
|
|
# 趋势向上反转,立即减小止损
|
|
return current_profit * 0.5
|
|
|
|
# 下跌动量减弱,略微放宽止损
|
|
if last_candle['rsi'] < 20 and last_candle['rsi'] > last_candle['rsi_slow']:
|
|
# RSI超卖且反弹迹象,提供更多空间
|
|
return self.stoploss * 1.3
|
|
|
|
# 默认返回原始止损设置
|
|
return self.stoploss
|
|
|
|
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
|
proposed_stake: float, min_stake: float | None, max_stake: float,
|
|
leverage: float, entry_tag: str | None, side: str,
|
|
**kwargs) -> float:
|
|
"""
|
|
自定义仓位大小计算
|
|
"""
|
|
# 为DCA预留资金空间
|
|
return proposed_stake / self.max_dca_multiplier
|
|
|
|
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
|
current_rate: float, current_profit: float,
|
|
min_stake: float | None, max_stake: float,
|
|
current_entry_rate: float, current_exit_rate: float,
|
|
current_entry_profit: float, current_exit_profit: float,
|
|
**kwargs) -> float | None | tuple[float | None, str | None]:
|
|
"""
|
|
动态调整仓位 - 优化加仓策略
|
|
"""
|
|
# 获取交易数据
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
|
|
|
|
if len(dataframe) == 0:
|
|
return None
|
|
|
|
filled_entries = trade.select_filled_orders(trade.entry_side)
|
|
|
|
if not filled_entries:
|
|
return None
|
|
|
|
last_entry = filled_entries[-1]
|
|
count_of_entries = trade.nr_of_successful_entries
|
|
|
|
# 空头加仓逻辑
|
|
if last_entry.side == "sell":
|
|
# 获取最新K线
|
|
last_candle = dataframe.iloc[-1]
|
|
prev_candle = dataframe.iloc[-2] if len(dataframe) > 1 else last_candle
|
|
|
|
# 计算加仓金额 - 基于亏损程度动态调整
|
|
stake_amount = filled_entries[0].stake_amount
|
|
|
|
# 条件1:价格突破新低 + 高阶空头趋势
|
|
if (current_profit < -0.005 and
|
|
last_candle['close'] < prev_candle['low'] and
|
|
last_candle['macd'] < last_candle['macdsignal'] and
|
|
count_of_entries < 2):
|
|
|
|
# 根据亏损程度调整加仓量 - 亏损越多加仓越少
|
|
adjustment_factor = max(0.5, 1.0 + current_profit) # 限制最低为0.5
|
|
new_stake = stake_amount * adjustment_factor
|
|
|
|
return new_stake, "short_dca_new_low"
|
|
|
|
# 条件2:小幅反弹后继续下跌
|
|
if (current_profit < -0.003 and
|
|
last_candle['close'] < last_candle['open'] and # 阴线
|
|
last_candle['close'] < last_candle['ma20'] and # 价格在中期均线下方
|
|
prev_candle['close'] > prev_candle['open'] and # 前一根是阳线
|
|
count_of_entries < 3):
|
|
|
|
# 使用标准金额加仓
|
|
return stake_amount * 0.8, "short_dca_dip_continuation"
|
|
|
|
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:
|
|
"""
|
|
在进入交易前进行额外的确认
|
|
"""
|
|
# 始终允许空头交易,不做额外检查
|
|
if side == "sell":
|
|
return True
|
|
return False
|
|
|
|
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
|
|
|
|
def get_ticker_indicator(self):
|
|
"""
|
|
获取当前时间框架的分钟数
|
|
"""
|
|
return int(self.timeframe[:-1]) |