340 lines
15 KiB
Python
340 lines
15 KiB
Python
# --- Do not remove these libs ---
|
|
from freqtrade.strategy import IStrategy
|
|
from typing import Dict, List
|
|
from functools import reduce
|
|
from pandas import DataFrame, pandas
|
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
|
|
# --------------------------------
|
|
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 logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
### Now you can use logger.info('asfd') to log
|
|
|
|
# freqtrade trade -c ./user_data/Chan/config/ChanLun_SOL.json --strategy ChanLun_SOL_2 --strategy-path ./user_data/Chan/strategies
|
|
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_SOL.json --strategy ChanLun_SOL_2 --strategy-path ./user_data/Chan/strategies --timerange=20250309-
|
|
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_SOL.json -t 1m --pairs SOL/USDT:USDT --timerange=20250501-
|
|
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy ChanLun_SOL_2 --strategy-path ./user_data/strategies -c ./user_data/ChanLun_SOL.json -e 200 --timerange=20250101-20250215
|
|
|
|
# sudo docker compose run --rm chan_btc backtesting -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies --timerange=20250101-
|
|
# sudo docker compose run --rm chan_btc download-data -c ./user_data/Chan.json --pairs SOL/USDT:USDT -t 1m --timerange 20240101-
|
|
# sudo docker compose run --rm chan_btc trade -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies
|
|
|
|
class ChanLun_SOL_2(IStrategy):
|
|
INTERFACE_VERSION: int = 3
|
|
|
|
# 优化的ROI设置 - 更快速获利
|
|
minimal_roi = {
|
|
"0": 0.012, # 立即获利1.2%
|
|
"5": 0.01, # 5分钟后获利1%
|
|
"15": 0.007, # 15分钟后获利0.7%
|
|
"30": 0.005 # 30分钟后获利0.5%
|
|
}
|
|
|
|
can_short = True
|
|
stoploss = -0.007 # 降低止损为0.7%
|
|
|
|
# 追踪止损设置 - 更积极的追踪止损
|
|
trailing_stop = True
|
|
trailing_stop_positive = 0.003 # 0.3%
|
|
trailing_stop_positive_offset = 0.005 # 0.5%
|
|
trailing_only_offset_is_reached = True
|
|
|
|
# 时间周期
|
|
timeframe = '5m'
|
|
informative_timeframe = '1h'
|
|
startup_candle_count = 200
|
|
|
|
# 只做空头策略
|
|
only_short = True
|
|
|
|
def informative_pairs(self):
|
|
pairs = self.dp.current_whitelist()
|
|
informative_pairs = [(pair, self.informative_timeframe) for pair in pairs]
|
|
return informative_pairs
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
# 获取更高时间周期的数据
|
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.informative_timeframe)
|
|
|
|
# === 高时间周期指标 ===
|
|
# 三均线系统
|
|
informative['ema50'] = ta.EMA(informative, timeperiod=50)
|
|
informative['ema100'] = ta.EMA(informative, timeperiod=100)
|
|
informative['ema200'] = ta.SMA(informative, timeperiod=200) # 使用SMA作为长期趋势
|
|
|
|
# 趋势方向
|
|
informative['uptrend'] = (
|
|
(informative['ema50'] > informative['ema100']) &
|
|
(informative['ema100'] > informative['ema200']) &
|
|
(informative['close'] > informative['ema50'])
|
|
).astype(int)
|
|
|
|
informative['downtrend'] = (
|
|
(informative['ema50'] < informative['ema100']) &
|
|
(informative['ema100'] < informative['ema200']) &
|
|
(informative['close'] < informative['ema50'])
|
|
).astype(int)
|
|
|
|
# 强下降趋势
|
|
informative['strong_downtrend'] = (
|
|
(informative['ema50'] < informative['ema100']) &
|
|
(informative['ema100'] < informative['ema200']) &
|
|
(informative['close'] < informative['ema50']) &
|
|
(informative['ema50'].shift(3) < informative['ema50']) # 确认EMA50下降
|
|
).astype(int)
|
|
|
|
# 添加高时间周期的ADX指标
|
|
informative['adx'] = ta.ADX(informative, timeperiod=14)
|
|
|
|
# 添加高时间周期的波动率
|
|
informative['atr'] = ta.ATR(informative, timeperiod=14)
|
|
informative['atr_percent'] = (informative['atr'] / informative['close']) * 100
|
|
|
|
# 高时间周期RSI
|
|
informative['rsi'] = ta.RSI(informative, timeperiod=14)
|
|
|
|
# 将informative数据帧中的列重命名,以便在合并后区分
|
|
for col in informative.columns:
|
|
if col not in ['date', 'open', 'high', 'low', 'close', 'volume']:
|
|
informative[f"{col}_{self.informative_timeframe}"] = informative[col]
|
|
|
|
# 删除原始列,只保留重命名后的列和必要的日期、OHLCV列
|
|
for col in list(informative.columns):
|
|
if col not in ['date', 'open', 'high', 'low', 'close', 'volume'] and not col.endswith(f"_{self.informative_timeframe}"):
|
|
del informative[col]
|
|
|
|
# 打印列名以便调试
|
|
logger.info(f"Informative columns after renaming: {informative.columns.tolist()}")
|
|
|
|
# 合并数据 - 使用正确的参数
|
|
dataframe = resampled_merge(dataframe, informative, self.informative_timeframe)
|
|
|
|
# 打印合并后的列名以便调试
|
|
logger.info(f"Dataframe columns after merge: {dataframe.columns.tolist()}")
|
|
|
|
# === 主时间周期指标 ===
|
|
# 布林带
|
|
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_width'] = ((bollinger['upper'] - bollinger['lower']) / bollinger['mid'])
|
|
|
|
# 动量指标
|
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
|
dataframe['mfi'] = ta.MFI(dataframe, timeperiod=14)
|
|
|
|
# MACD
|
|
macd = ta.MACD(dataframe)
|
|
dataframe['macd'] = macd['macd']
|
|
dataframe['macdsignal'] = macd['macdsignal']
|
|
dataframe['macdhist'] = macd['macdhist']
|
|
|
|
# 均线
|
|
dataframe['ema9'] = ta.EMA(dataframe, timeperiod=9)
|
|
dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21)
|
|
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
|
dataframe['sma200'] = ta.SMA(dataframe, timeperiod=200)
|
|
|
|
# 成交量
|
|
dataframe['volume_mean'] = dataframe['volume'].rolling(window=20).mean()
|
|
dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_mean']
|
|
|
|
# 波动率
|
|
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
|
|
|
|
# ADX - 趋势强度指标
|
|
dataframe['adx'] = ta.ADX(dataframe, timeperiod=14)
|
|
|
|
# 价格突破
|
|
dataframe['upper_break'] = (
|
|
(dataframe['close'] > dataframe['bb_upperband']) &
|
|
(dataframe['close'].shift() <= dataframe['bb_upperband'].shift())
|
|
).astype(int)
|
|
|
|
dataframe['lower_break'] = (
|
|
(dataframe['close'] < dataframe['bb_lowerband']) &
|
|
(dataframe['close'].shift() >= dataframe['bb_lowerband'].shift())
|
|
).astype(int)
|
|
|
|
# 均线交叉
|
|
dataframe['ema_cross_up'] = (
|
|
(dataframe['ema9'] > dataframe['ema21']) &
|
|
(dataframe['ema9'].shift() <= dataframe['ema21'].shift())
|
|
).astype(int)
|
|
|
|
dataframe['ema_cross_down'] = (
|
|
(dataframe['ema9'] < dataframe['ema21']) &
|
|
(dataframe['ema9'].shift() >= dataframe['ema21'].shift())
|
|
).astype(int)
|
|
|
|
# 超买超卖区域
|
|
dataframe['rsi_oversold'] = (dataframe['rsi'] < 30).astype(int)
|
|
dataframe['rsi_overbought'] = (dataframe['rsi'] > 70).astype(int)
|
|
|
|
# 价格与均线的关系
|
|
dataframe['price_above_ema50'] = (dataframe['close'] > dataframe['ema50']).astype(int)
|
|
dataframe['price_below_ema50'] = (dataframe['close'] < dataframe['ema50']).astype(int)
|
|
|
|
# 趋势强度
|
|
dataframe['strong_trend'] = (dataframe['adx'] > 25).astype(int)
|
|
|
|
# 添加蜡烛图形态识别
|
|
dataframe['doji'] = ta.CDLDOJI(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close'])
|
|
dataframe['engulfing'] = ta.CDLENGULFING(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close'])
|
|
dataframe['hammer'] = ta.CDLHAMMER(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close'])
|
|
dataframe['shooting_star'] = ta.CDLSHOOTINGSTAR(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close'])
|
|
|
|
# 价格动量
|
|
dataframe['momentum'] = dataframe['close'] - dataframe['close'].shift(5)
|
|
|
|
return dataframe
|
|
|
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
# 检查列名是否存在
|
|
downtrend_col = 'resample_60_downtrend_1h'
|
|
strong_downtrend_col = 'resample_60_strong_downtrend_1h'
|
|
adx_col = 'resample_60_adx_1h'
|
|
rsi_col = 'resample_60_rsi_1h'
|
|
|
|
# 如果列名不存在,使用替代方案
|
|
for col, default_value in [
|
|
(downtrend_col, 0),
|
|
(strong_downtrend_col, 0),
|
|
(adx_col, 25),
|
|
(rsi_col, 50)
|
|
]:
|
|
if col not in dataframe.columns:
|
|
logger.warning(f"Column {col} not found in dataframe. Creating with default value {default_value}.")
|
|
dataframe[col] = default_value
|
|
|
|
# 禁用多头入场
|
|
dataframe['enter_long'] = 0
|
|
|
|
# 空头入场条件 - 专注于空头策略
|
|
short_conditions = (
|
|
# 高时间周期处于下降趋势
|
|
(dataframe[downtrend_col] > 0) &
|
|
|
|
# 趋势强度确认
|
|
(dataframe[adx_col] > 25) &
|
|
|
|
# 条件1: 价格突破上轨后回落 + 成交量确认
|
|
(
|
|
(dataframe['upper_break'].rolling(window=5).sum() > 0) & # 最近5根K线内有突破上轨
|
|
(dataframe['close'] < dataframe['close'].shift(2)) & # 价格开始下跌
|
|
(dataframe['close'] < dataframe['ema9']) & # 价格在短期均线下方
|
|
(dataframe['volume_ratio'] > 1.3) & # 成交量放大
|
|
(dataframe['rsi'] < 70) & # RSI不在极度超买区
|
|
(dataframe['rsi'] > 40) & # RSI不在超卖区
|
|
(dataframe[rsi_col] < 60) # 高时间周期RSI不过高
|
|
) |
|
|
|
|
# 条件2: 均线死叉 + RSI超买回落 + 趋势确认
|
|
(
|
|
(dataframe['ema_cross_down'] > 0) & # 均线死叉
|
|
(dataframe['rsi'] > 55) & # RSI相对较高
|
|
(dataframe['rsi'] < dataframe['rsi'].shift(3)) & # RSI下降
|
|
(dataframe['volume_ratio'] > 1.2) & # 成交量放大
|
|
(dataframe['adx'] > 20) & # ADX显示有一定趋势强度
|
|
((dataframe['shooting_star'] > 0) | (dataframe['engulfing'] < 0)) # 流星线或看跌吞没形态
|
|
) |
|
|
|
|
# 条件3: 价格在高点回落 + 强趋势
|
|
(
|
|
(dataframe['close'] < dataframe['high'].shift()) &
|
|
(dataframe['high'].shift() > dataframe['high'].shift(2)) &
|
|
(dataframe['close'] < dataframe['ema21']) &
|
|
(dataframe['adx'] > 30) &
|
|
(dataframe['rsi'] < dataframe['rsi'].shift()) &
|
|
(dataframe['rsi'].shift() > 65) &
|
|
(dataframe['volume_ratio'] > 1.0)
|
|
) |
|
|
|
|
# 条件4: 强下降趋势确认
|
|
(
|
|
(dataframe[strong_downtrend_col] > 0) &
|
|
(dataframe['close'] < dataframe['ema21']) &
|
|
(dataframe['close'] < dataframe['close'].shift(3)) &
|
|
(dataframe['momentum'] < 0) &
|
|
(dataframe['volume_ratio'] > 1.1) &
|
|
(dataframe['adx'] > 25)
|
|
)
|
|
)
|
|
|
|
dataframe.loc[short_conditions, 'enter_short'] = 1
|
|
dataframe.loc[short_conditions, 'enter_tag'] = 'chan_sol_short'
|
|
|
|
return dataframe
|
|
|
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
# 禁用多头出场
|
|
dataframe['exit_long'] = 0
|
|
|
|
# 空头出场条件 - 更精确的出场
|
|
short_exit_conditions = (
|
|
# 条件1: 趋势反转信号
|
|
(
|
|
(dataframe['ema_cross_up'] > 0) & # 均线金叉
|
|
(dataframe['volume_ratio'] > 1.0) # 成交量确认
|
|
) |
|
|
|
|
# 条件2: 价格突破中期均线
|
|
(
|
|
(dataframe['close'] > dataframe['ema21']) &
|
|
(dataframe['close'].shift() < dataframe['ema21'].shift()) & # 确认是刚刚突破
|
|
(dataframe['volume_ratio'] > 1.2) # 成交量确认
|
|
) |
|
|
|
|
# 条件3: 超卖信号
|
|
(
|
|
(dataframe['rsi'] < 30) & # RSI超卖
|
|
(dataframe['close'] < dataframe['bb_lowerband']) # 价格突破下轨
|
|
) |
|
|
|
|
# 条件4: 动量减弱
|
|
(
|
|
(dataframe['rsi'] < 35) &
|
|
(dataframe['rsi'] > dataframe['rsi'].shift()) &
|
|
(dataframe['rsi'].shift() > dataframe['rsi'].shift(2)) & # RSI连续两根K线上升
|
|
(dataframe['momentum'] > 0) # 价格动量转为正
|
|
) |
|
|
|
|
# 条件5: 锤子线形态 (潜在反转信号)
|
|
(
|
|
(dataframe['hammer'] > 0) &
|
|
(dataframe['volume_ratio'] > 1.3)
|
|
)
|
|
)
|
|
|
|
dataframe.loc[short_exit_conditions, 'exit_short'] = 1
|
|
dataframe.loc[short_exit_conditions, 'exit_tag'] = 'chan_sol_short_exit'
|
|
|
|
return dataframe
|
|
|
|
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" and entry_tag == "chan_sol_short":
|
|
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 1.0
|
|
|
|
def get_ticker_indicator(self):
|
|
return int(self.timeframe[:-1]) |