添加k线动能理论
This commit is contained in:
+54
-147
@@ -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") &
|
||||
|
||||
Reference in New Issue
Block a user