添加k线动能理论

This commit is contained in:
jackyu66git
2025-08-14 02:29:27 +08:00
parent 285f62f1ab
commit 15a3df55db
16 changed files with 1400 additions and 170 deletions
+54 -147
View File
@@ -67,15 +67,15 @@ class ChanLun_BTC_30(IStrategy):
can_short = True
lev = 1.0
stoploss = -0.3 # 设置为很大的负值,让custom_stoploss来控制
use_custom_stoploss = False # 启用自定义止损
use_custom_stoploss = True # 启用自定义止损
trailing_stop = False
trailing_stop_positive = 0.025
trailing_stop_positive_offset = 0.045
trailing_only_offset_is_reached = False
# 启用仓位调整功能以支持分批止盈
position_adjustment_enable = True
# 关闭分批止盈/仓位调整
position_adjustment_enable = False
startup_candle_count = 2880
time5 = 5
@@ -119,9 +119,14 @@ class ChanLun_BTC_30(IStrategy):
#self.chan.plot_dual(dataframe_5, dataframe_30)
#chanpy_state = self.chanpy.get_bsp_state(dataframe_5)
#dataframe_5['chanpy_state'] = chanpy_state
state_list = self.chan.get_klc_state_list(dataframe_5)
dataframe_5['state'] = state_list
state_list = self.chan.get_klc_state_list(dataframe_15)
dataframe_15['state'] = state_list
state_list = self.chan.get_klc_state_list(dataframe_30)
dataframe_30['state'] = state_list
state_list = self.chan.get_klc_state_list(dataframe_60)
dataframe_60['state'] = state_list
dataframe_60['fx'] = state_list
#bi_list_1 = self.chan.get_bi_list(dataframe)
#bi_list_5 = self.chan.get_bi_list(dataframe_5)
#bi_list_15 = self.chan.get_bi_list(dataframe_15)
@@ -138,7 +143,7 @@ class ChanLun_BTC_30(IStrategy):
self.last_time = datetime.now()
dataframe = resampled_merge(dataframe, dataframe_3)
dataframe = resampled_merge(dataframe, dataframe_5)
#dataframe = resampled_merge(dataframe, dataframe_15)
dataframe = resampled_merge(dataframe, dataframe_15)
#dataframe = resampled_merge(dataframe, dataframe_30)
dataframe = resampled_merge(dataframe, dataframe_60)
#dataframe = resampled_merge(dataframe, dataframe_4h)
@@ -166,7 +171,8 @@ class ChanLun_BTC_30(IStrategy):
bb120 = ta.BBANDS(df, timeperiod=120, nbdevup=3.0, nbdevdn=3.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=41, nbdevup=2.3, nbdevdn=2.3, matype=0)
bb302 = ta.BBANDS(df, timeperiod=41, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb302 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
# 计算布林带中轨(移动平均线)
bb30_middle = ta.SMA(df, timeperiod=90)
@@ -230,162 +236,63 @@ class ChanLun_BTC_30(IStrategy):
new_exitprice = proposed_rate - 50
return new_exitprice
def adjust_trade_position1(self, trade: Trade, current_time: datetime,
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs) -> Optional[float]:
"""
基于布林带的分批止盈逻辑
"""
# 获取当前数据
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
if dataframe is None or len(dataframe) == 0:
return None
last_candle = dataframe.iloc[-1]
# 获取布林带数据
bb30_middle = last_candle['bbmiddle30']
bb30_upper = last_candle['bbup30']
bb30_lower = last_candle['bblow30']
bb302_upper = last_candle['bbup302']
bb302_lower = last_candle['bblow302']
# 获取交易的状态标记
first_tp_triggered = trade.get_custom_data(key="first_tp_triggered", default=False)
second_tp_triggered = trade.get_custom_data(key="second_tp_triggered", default=False)
if trade.is_short:
# 做空逻辑
if not first_tp_triggered and current_rate <= bb30_middle:
# 第一次止盈:价格跌到bb30中轨,止盈50%
logger.info(f"做空第一次止盈触发:价格{current_rate} <= BB30中轨{bb30_middle}")
trade.set_custom_data(key="first_tp_triggered", value=True)
trade.set_custom_data(key="new_stoploss", value=trade.open_rate) # 设置止损为开仓价
return -(trade.amount * 0.5) # 减少50%仓位
elif first_tp_triggered and not second_tp_triggered and current_rate <= bb302_lower:
# 第二次止盈:继续跌到bb302下轨,止盈剩余仓位的60%
logger.info(f"做空第二次止盈触发:价格{current_rate} <= BB302下轨{bb302_lower}")
trade.set_custom_data(key="second_tp_triggered", value=True)
trade.set_custom_data(key="new_stoploss", value=bb30_middle) # 移动止损到bb30中轨
remaining_amount = trade.amount * 0.5 # 剩余50%
return -(remaining_amount * 0.6) # 减少剩余仓位的60%
else:
# 做多逻辑
if not first_tp_triggered and current_rate >= bb30_middle:
# 第一次止盈:价格涨到bb30中轨,止盈50%
logger.info(f"做多第一次止盈触发:价格{current_rate} >= BB30中轨{bb30_middle}")
trade.set_custom_data(key="first_tp_triggered", value=True)
trade.set_custom_data(key="new_stoploss", value=trade.open_rate) # 设置止损为开仓价
return -(trade.amount * 0.5) # 减少50%仓位
elif first_tp_triggered and not second_tp_triggered and current_rate >= bb302_upper:
# 第二次止盈:继续涨到bb302上轨,止盈剩余仓位的60%
logger.info(f"做多第二次止盈触发:价格{current_rate} >= BB302上轨{bb302_upper}")
trade.set_custom_data(key="second_tp_triggered", value=True)
trade.set_custom_data(key="new_stoploss", value=bb30_middle) # 移动止损到bb30中轨
remaining_amount = trade.amount * 0.5 # 剩余50%
return -(remaining_amount * 0.6) # 减少剩余仓位的60%
# 关闭分批止盈,始终不调整仓位
return None
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> float | None:
"""
动态止损逻辑
止损 = 开仓价 ± 1 * ATR(开仓时的ATR)。
多单: 开仓价 - ATR;空单: 开仓价 + ATR。
"""
# 检查是否有自定义的新止损价格(分批止盈后的动态止损)
new_stoploss_price = trade.get_custom_data(key="new_stoploss")
if new_stoploss_price:
logger.info(f"使用动态止损价格: {new_stoploss_price}")
return stoploss_from_absolute(new_stoploss_price, current_rate, is_short=trade.is_short)
# 如果没有ATR数据,使用固定的5%止损作为备用
logger.warning(f"未找到开仓时ATR数据,使用默认5%止损")
return -0.05
entry_atr = trade.get_custom_data(key="entry_atr")
if entry_atr is None:
# 回退:取当前数据的 ATR 估算
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
if dataframe is not None and len(dataframe) > 0 and 'atr' in dataframe.columns:
entry_atr = float(dataframe.iloc[-1]['atr'])
else:
# 最保守的回退:5%
return -0.05
if trade.is_short:
stop_price = trade.open_rate + float(entry_atr)
else:
stop_price = trade.open_rate - float(entry_atr)
return stoploss_from_absolute(stop_price, current_rate, is_short=trade.is_short)
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs):
"""
自定义退出逻辑 - 处理最终止盈条件
"""
# 获取当前数据
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe is None or len(dataframe) == 0:
return None
last_candle = dataframe.iloc[-1]
# 获取布林带数据
bb30_upper = last_candle['bbup30']
bb30_lower = last_candle['bblow30']
# 检查是否已经触发过前两次止盈
first_tp_triggered = trade.get_custom_data(key="first_tp_triggered", default=False)
second_tp_triggered = trade.get_custom_data(key="second_tp_triggered", default=False)
if trade.is_short:
# 做空:如果价格跌到bb30下轨,全部止盈
if first_tp_triggered and second_tp_triggered and current_rate <= bb30_lower:
logger.info(f"做空最终止盈触发:价格{current_rate} <= BB30下轨{bb30_lower}")
return "short_final_tp_bb30_lower"
else:
# 做多:如果价格涨到bb30上轨,全部止盈
if first_tp_triggered and second_tp_triggered and current_rate >= bb30_upper:
logger.info(f"做多最终止盈触发:价格{current_rate} >= BB30上轨{bb30_upper}")
return "long_final_tp_bb30_upper"
# 原有退出逻辑
if trade.is_short:
last_high = trade.get_custom_data(key="entry_candle_high")
if last_high and current_rate > last_high:
return "Relay Top FX exit"
else:
last_low = trade.get_custom_data(key="entry_candle_low")
if last_low and current_rate < last_low:
return "Relay Bottom FX exit"
# 不做分批止盈/最终止盈处理,退出由策略信号/ROI/止损决定
return None
def confirm_trade_entry1(self, pair: str, order_type: str, amount: float, rate: float,
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: str | None,
side: str, **kwargs) -> bool:
if self.last_trade:
if self.last_trade.is_short:
if side == 'short':
if self.last_trade.open_date + timedelta(minutes=30) > current_time:
return False
else:
return True
else:
if side == 'long':
if self.last_trade.open_date + timedelta(minutes=30) > current_time:
return True
else:
return False
#if self.last_trade:
#print(self.last_trade.open_date, current_time, self.last_trade.open_date + timedelta(minutes=self.time5))
return True
def custom_stoploss1(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> float | None:
last_high = trade.get_custom_data(key="entry_candle_high")
last_low = trade.get_custom_data(key="entry_candle_low")
# Convert absolute price to percentage relative to current_rate
if last_high:
return stoploss_from_absolute(last_high, current_rate, is_short=trade.is_short)
if last_low:
return stoploss_from_absolute(last_low, current_rate, is_short=trade.is_short)
# return maximum stoploss value, keeping current stoploss price unchanged
return None
"""
ATR 过滤:atr < 100 不开单。
"""
try:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe is None or len(dataframe) == 0:
return False
last = dataframe.iloc[-1]
atr_str = 'resample_{}_atr'.format(self.get_ticker_indicator()*self.time30)
atr_val = float(last.get(atr_str, 0) or 0)
if atr_val < 100:
logger.info(f"ATR过滤:atr={atr_val:.2f} < 100, 拒绝进场 {pair}")
return False
return True
except Exception as e:
logger.warning(f"confirm_trade_entry 异常: {e}")
return True
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
"""
@@ -400,10 +307,10 @@ class ChanLun_BTC_30(IStrategy):
# Obtain pair dataframe (just to show how to access it)
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
atr_str = 'resample_{}_atr'.format(self.get_ticker_indicator()*self.time30)
# 保存开仓时的ATR值用于止损计算
if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side):
entry_atr = last_candle['atr']
entry_atr = last_candle[atr_str] * 4
trade.set_custom_data(key="entry_atr", value=entry_atr)
logger.info(f"保存开仓时ATR值: {entry_atr}")
return None
@@ -427,7 +334,7 @@ class ChanLun_BTC_30(IStrategy):
['enter_long', 'enter_tag']] = (1, 'long_signal_chan')
dataframe.loc[
(
(dataframe[state_str].shift(shift_time) == "10")
(dataframe[state_str].shift(shift_time) == "10")
#(dataframe[fx_str].shift(shift_time) == 1)
#(dataframe[chanpy_state_str].shift(shift_time+30) == -1)
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") &