add more strategies
This commit is contained in:
+28
-16
@@ -42,17 +42,17 @@ class ChanLun_SOL_5(IStrategy):
|
||||
}
|
||||
# 5m and 15m
|
||||
minimal_roi_1 = {
|
||||
"0": 0.253,
|
||||
"60": 0.159,
|
||||
"120": 0.052,
|
||||
"0": 0.1,
|
||||
"60": 0.05,
|
||||
"120": 0.02,
|
||||
"240": 0
|
||||
}
|
||||
# 15m and 30m
|
||||
minimal_roi_1 = {
|
||||
"0": 0.253,
|
||||
"120": 0.159,
|
||||
"240": 0.052,
|
||||
"360": 0
|
||||
"0": 0.1,
|
||||
"240": 0.05,
|
||||
"480": 0.03,
|
||||
"600": 0
|
||||
}
|
||||
minimal_roi_2 = {
|
||||
"0": 0.10,
|
||||
@@ -60,8 +60,8 @@ class ChanLun_SOL_5(IStrategy):
|
||||
"2400": 0.025,
|
||||
"3600": 0
|
||||
}
|
||||
can_short = False
|
||||
lev = 5.0
|
||||
can_short = True
|
||||
lev = 1.0
|
||||
stoploss = -0.3 * lev
|
||||
trailing_stop = False
|
||||
trailing_stop_positive = 0.025
|
||||
@@ -471,12 +471,16 @@ class ChanLun_SOL_5(IStrategy):
|
||||
#print(df['date'][index], df['macdhist'][index], df['macd'][index], df['macdsignal'][index], df['masub'][index])
|
||||
# (1,1) = 1, (1,0) = 2, (-1,1) = 3, (-1, 0) = 4, (0,0) = 0
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
|
||||
state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time5)
|
||||
close_str = 'resample_{}_close'.format(self.get_ticker_indicator()*self.time5)
|
||||
volume_str = 'resample_{}_volume'.format(self.get_ticker_indicator()*self.time5)
|
||||
dataframe.loc[
|
||||
(
|
||||
#(dataframe['state'] == "-30")
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") |
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "99")
|
||||
(dataframe[state_str].shift(self.time5) == "-10") &
|
||||
(dataframe[close_str].pct_change().abs() < 0.05) &
|
||||
(dataframe[close_str] > dataframe[close_str].shift(self.time5)) &
|
||||
(dataframe[volume_str] > dataframe[volume_str].rolling(window=20).mean())
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10")
|
||||
@@ -486,7 +490,10 @@ class ChanLun_SOL_5(IStrategy):
|
||||
dataframe.loc[
|
||||
(
|
||||
#(dataframe['state'] == "30")
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10")
|
||||
(dataframe[state_str].shift(self.time5) == "10") &
|
||||
(dataframe[close_str].pct_change().abs() < 0.05) &
|
||||
(dataframe[close_str] < dataframe[close_str].shift(self.time5)) &
|
||||
(dataframe[volume_str] > dataframe[volume_str].rolling(window=20).mean())
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10")
|
||||
@@ -495,11 +502,14 @@ class ChanLun_SOL_5(IStrategy):
|
||||
['enter_short', 'enter_tag']] = (1, 'short_signal_chan')
|
||||
return dataframe
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time5)
|
||||
close_str = 'resample_{}_close'.format(self.get_ticker_indicator()*self.time5)
|
||||
dataframe.loc[
|
||||
(
|
||||
#(dataframe['state']== "30")
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") |
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "-99")
|
||||
(dataframe[state_str].shift(self.time5) == "10") |
|
||||
(dataframe[close_str] < dataframe[close_str].rolling(window=20).min()) |
|
||||
(dataframe[close_str] > dataframe[close_str].rolling(window=20).max()*1.05)
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10")
|
||||
),
|
||||
@@ -507,7 +517,9 @@ class ChanLun_SOL_5(IStrategy):
|
||||
dataframe.loc[
|
||||
(
|
||||
#(dataframe['state'] == "-30")
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10")
|
||||
(dataframe[state_str].shift(self.time5) == "-10") |
|
||||
(dataframe[close_str] > dataframe[close_str].rolling(window=20).max()) |
|
||||
(dataframe[close_str] < dataframe[close_str].rolling(window=20).min()*0.95)
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10")
|
||||
),
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
"""
|
||||
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])
|
||||
+289
-145
@@ -5,13 +5,6 @@ from functools import reduce
|
||||
from pandas import DataFrame, pandas
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
|
||||
import sys
|
||||
import os
|
||||
#sys.setrecursionlimit(1000000) #例如这里设置为一百万
|
||||
#sys.path.append(os.path.abspath("/freqtrade/user_data/Chan"))
|
||||
sys.path.append(os.path.abspath("/Users/jack/Project/freqtrade/user_data/Chan"))
|
||||
#sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/freqtrade/user_data/Chan"))
|
||||
from ChanLun import ChanLun
|
||||
# --------------------------------
|
||||
from technical.util import resample_to_interval, resampled_merge
|
||||
import talib.abstract as ta
|
||||
@@ -19,174 +12,325 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from freqtrade.persistence import Trade, Order
|
||||
from typing import Optional
|
||||
from ChanPY import ChanPY
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
### Now you can use logger.info('asfd') to log
|
||||
|
||||
# freqtrade trade -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies
|
||||
# freqtrade backtesting -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies --timerange=20250309-
|
||||
# freqtrade download-data -c ./user_data/Chan.json -t 1m --pairs SOL/USDT:USDT --timerange=20240101-
|
||||
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy Chan_SOL_2 --strategy-path ./user_data/strategies -c ./user_data/Chan.json -e 200 --timerange=20250101-20250215
|
||||
# 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 Chan_SOL_2(IStrategy):
|
||||
class ChanLun_SOL_2(IStrategy):
|
||||
INTERFACE_VERSION: int = 3
|
||||
# Minimal ROI designed for the strategy.
|
||||
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||
|
||||
# 优化的ROI设置 - 更快速获利
|
||||
minimal_roi = {
|
||||
"0": 0.253,
|
||||
"120": 0.159,
|
||||
"240": 0.052,
|
||||
"360": 0
|
||||
"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
|
||||
# Optimal stoploss designed for the strategy
|
||||
# This attribute will be overridden if the config file contains "stoploss"
|
||||
stoploss = -0.21
|
||||
|
||||
trailing_stop = False
|
||||
trailing_stop_positive = 0.015
|
||||
trailing_stop_positive_offset = 0.043
|
||||
trailing_only_offset_is_reached = False
|
||||
|
||||
# Optimal timeframe for the strategy
|
||||
# timeframe = '15m'
|
||||
startup_candle_count = 600
|
||||
|
||||
time5 = 5
|
||||
time15 = 15
|
||||
time30 = 30
|
||||
time60 = 60
|
||||
time240 = 240
|
||||
last_time = datetime.now()
|
||||
big_size = 0
|
||||
big_state = "00"
|
||||
big_state_list = []
|
||||
chanpy = ChanPY()
|
||||
chan = ChanLun()
|
||||
small_size = 0
|
||||
small_state = "00"
|
||||
small_state_list = []
|
||||
|
||||
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):
|
||||
|
||||
# get access to all pairs available in whitelist.
|
||||
pairs = self.dp.current_whitelist()
|
||||
# Assign tf to each pair so they can be downloaded and cached for strategy.
|
||||
informative_pairs = [(pair, '1h') for pair in pairs]
|
||||
# Optionally Add additional "static" pairs
|
||||
#informative_pairs += [("ETH/USDT:USDT", "5m"),("ETH/USDT:USDT", "15m"),]
|
||||
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)
|
||||
|
||||
# resample our dataframes
|
||||
dataframe_5 = resample_to_interval(dataframe, self.get_ticker_indicator() * 5)
|
||||
#dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15)
|
||||
#dataframe_30 = resample_to_interval(dataframe, self.get_ticker_indicator() * 30)
|
||||
dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60)
|
||||
#dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240)
|
||||
|
||||
#dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d')
|
||||
#dataframe_1w = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 10080)
|
||||
#dataframe_1m = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 43200)
|
||||
|
||||
#dataframe_1d = resample_to_interval(dataframe, self.get_ticker_indicator() * 1440)
|
||||
#dataframe_1w = resample_to_interval(dataframe, self.get_ticker_indicator() * 10080)
|
||||
#dataframe_1m = resample_to_interval(dataframe, self.get_ticker_indicator() * 43200)
|
||||
self.local_print(dataframe_5)
|
||||
|
||||
dataframe_5['state'] = self.chan.resample_klc_list(dataframe_5)
|
||||
#dataframe_15['state'] = self.chan.resample_klc_list(dataframe_15)
|
||||
#dataframe_30['state'] = self.chan.resample_klc_list(dataframe_30)
|
||||
dataframe_60['state'] = self.chan.resample_klc_list(dataframe_60)
|
||||
#dataframe_4h['state'] = self.chan.resample_klc_list(dataframe_4h)
|
||||
#dataframe_5['bsps'], dataframe_5['updown'], dataframe_5['bi_sure'] = self.chanpy.get_bsps(dataframe_5)
|
||||
print("===================================================")
|
||||
#print(dataframe_60['high'].rolling(window).max())
|
||||
#print(dataframe_60['low'].rolling(window).min())
|
||||
#for index in range(0, len(dataframe_5)):
|
||||
#print(dataframe_5[dataframe_5['bi_sure'] == 1][dataframe_5['state'] == "-10"])
|
||||
#print(dataframe_5[dataframe_5['bi_sure'] == 1][dataframe_5['state'] == "10"])
|
||||
dataframe = resampled_merge(dataframe, dataframe_5)
|
||||
#dataframe = resampled_merge(dataframe, dataframe_15)
|
||||
#dataframe = resampled_merge(dataframe, dataframe_30)
|
||||
dataframe = resampled_merge(dataframe, dataframe_60)
|
||||
#dataframe = resampled_merge(dataframe, dataframe_4h)
|
||||
# === 高时间周期指标 ===
|
||||
# 三均线系统
|
||||
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 print_df(self, df):
|
||||
for index in range(0, len(df)):
|
||||
print(df['date'][index], df['rsi'][index], df['state'][index])
|
||||
def print_resample_df(self, df, time):
|
||||
for index in range(0, len(df)):
|
||||
cn1 = 'resample_{}_date'.format(self.get_ticker_indicator()*time)
|
||||
cn2 = 'resample_{}_rsi'.format(self.get_ticker_indicator()*time)
|
||||
cn3 = 'resample_{}_state'.format(self.get_ticker_indicator()*time)
|
||||
print(df[cn1][index], df[cn2][index], df[cn3][index])
|
||||
def local_print(self, df):
|
||||
fast = 7
|
||||
slow = 14
|
||||
macd = ta.MACD(df, fast=fast, slow=slow)
|
||||
df['macd'] = macd['macd']
|
||||
df['macdsignal'] = macd['macdsignal']
|
||||
df['macdhist'] = macd['macdhist']
|
||||
df['ema26'] = ta.EMA(df, timeperiod=26)
|
||||
df['ema52'] = ta.EMA(df, timeperiod=52)
|
||||
df['ma5'] = ta.MA(df, timeperiod=5)
|
||||
df['ma10'] = ta.MA(df, timeperiod=10)
|
||||
df['masub'] = df['ma5'].subtract(df['ma10'])
|
||||
for index in range(0, len(df)):
|
||||
print(df['date'][index], df['macdhist'][index], df['macd'][index], df['macdsignal'][index], df['masub'][index])
|
||||
# (1,1) = 1, (1,0) = 2, (-1,1) = 3, (-1, 0) = 4, (0,0) = 0
|
||||
|
||||
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'
|
||||
|
||||
dataframe.loc[
|
||||
# 如果列名不存在,使用替代方案
|
||||
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['state'].shift(1) == "-10") &
|
||||
#((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "-11") |
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") &
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "11") &
|
||||
(dataframe['resample_{}_bsps'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) > 0) &
|
||||
(dataframe['resample_{}_bsps'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) < 10)
|
||||
#(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal']))
|
||||
),
|
||||
['enter_long', 'enter_tag']] = (1, 'long_signal_chan')
|
||||
dataframe.loc[
|
||||
(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['state'].shift(1) == "10") &
|
||||
#((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "11") |
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") &
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "-11") &
|
||||
(dataframe['resample_{}_bsps'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) < 0)
|
||||
#(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal']))
|
||||
),
|
||||
['enter_short', 'enter_tag']] = (1, 'short_signal_chan')
|
||||
(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.loc[
|
||||
# 禁用多头出场
|
||||
dataframe['exit_long'] = 0
|
||||
|
||||
# 空头出场条件 - 更精确的出场
|
||||
short_exit_conditions = (
|
||||
# 条件1: 趋势反转信号
|
||||
(
|
||||
#(dataframe['state'].shift(1) == "10") &
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "10")
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10")
|
||||
),
|
||||
['exit_long', 'exit_tag']] = (1, 'long_close_signal_chan')
|
||||
dataframe.loc[
|
||||
(dataframe['ema_cross_up'] > 0) & # 均线金叉
|
||||
(dataframe['volume_ratio'] > 1.0) # 成交量确认
|
||||
) |
|
||||
|
||||
# 条件2: 价格突破中期均线
|
||||
(
|
||||
#(dataframe['state'].shift(1) == "-10") &
|
||||
(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "-10")
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") &
|
||||
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10")
|
||||
),
|
||||
['exit_short', 'exit_tag']] = (1, 'short_close_signal_chan')
|
||||
(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:
|
||||
|
||||
Reference in New Issue
Block a user