Files
Chan/strategies/ChanLun_SOL_Optimized.py
T
PorterandCursor 2c1232555e refactor: 缠论引擎迁入 chan/ 分层解耦,指标外置
将核心结构、指标与分析拆到 chan/{core,indicators,analysis,pipeline};
根目录保留兼容 shim;strategies 改为从 chan 包导入;买卖点经 bsp_macd 与 MACD 接合。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 14:47:13 +08:00

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 chan.pipeline.ChanLun import ChanLun
from chan.analysis.ChanLun_Classifier import ChanLunClassifier
from chan.core.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])