优化回调平仓规则
This commit is contained in:
@@ -54,6 +54,9 @@ class BitmartFuturesTransaction:
|
||||
self.retrace_ratio = 0.5 # 回撤系数,如 0.5 表示允许回撤涨幅的 50%(类似斐波那契 50% 回撤)
|
||||
self.min_rise_pct_to_activate = 0.02 # 至少涨/跌这么多才启用动态回撤,避免噪音
|
||||
self.min_drop_pct_from_high = 0.03 # 至少从最高点回撤这么多%才平仓(保底,避免过于敏感)
|
||||
# EMA(10) + EMA(20) / ATR(14) 平仓(多单优先):收盘跌破 EMA10 先平;或从最高价回撤 ≥ atr_multiplier×ATR(14) 平
|
||||
self.use_ema_atr_exit = True # 是否启用 EMA/ATR 平仓规则(多单)
|
||||
self.atr_multiplier = 1.1 # 追踪止盈:从最高价回撤 ≥ 此倍数 × ATR(14) 则平仓
|
||||
self._candle_high_seen = None # 当前K线内见过的最高价(多头用)
|
||||
self._candle_low_seen = None # 当前K线内见过的最低价(空头用)
|
||||
self._candle_id_for_high_low = None # 记录高低对应的K线 id,换线则重置
|
||||
@@ -103,6 +106,78 @@ class BitmartFuturesTransaction:
|
||||
self.ding(text="获取K线异常", error=True)
|
||||
return None, None
|
||||
|
||||
def get_klines_series(self, count=35):
|
||||
"""获取最近 count 根 5 分钟 K 线(用于 EMA/ATR),按时间正序。最后一根为当前未收盘 K 线。"""
|
||||
try:
|
||||
end_time = int(time.time())
|
||||
response = self.contractAPI.get_kline(
|
||||
contract_symbol=self.contract_symbol,
|
||||
step=5,
|
||||
start_time=end_time - 3600 * 4,
|
||||
end_time=end_time
|
||||
)[0]["data"]
|
||||
formatted = []
|
||||
for k in response:
|
||||
formatted.append({
|
||||
'id': int(k["timestamp"]),
|
||||
'open': float(k["open_price"]),
|
||||
'high': float(k["high_price"]),
|
||||
'low': float(k["low_price"]),
|
||||
'close': float(k["close_price"])
|
||||
})
|
||||
formatted.sort(key=lambda x: x['id'])
|
||||
return formatted[-count:] if len(formatted) >= count else formatted
|
||||
except Exception as e:
|
||||
logger.error(f"获取K线序列异常: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _ema(series, period):
|
||||
"""对序列 series(从旧到新)计算 EMA(period),返回最后一个 EMA 值。"""
|
||||
if not series or len(series) < period:
|
||||
return None
|
||||
alpha = 2.0 / (period + 1)
|
||||
ema = sum(series[:period]) / period # 用前 period 根收盘的 SMA 做种子
|
||||
for i in range(period, len(series)):
|
||||
ema = alpha * series[i] + (1 - alpha) * ema
|
||||
return ema
|
||||
|
||||
@staticmethod
|
||||
def _atr(klines, period=14):
|
||||
"""对 klines(从旧到新,每项含 high/low/close)计算 ATR(period),返回最后一个 ATR 值。"""
|
||||
if not klines or len(klines) < period + 1:
|
||||
return None
|
||||
tr_list = []
|
||||
for i in range(1, len(klines)):
|
||||
high, low = klines[i]['high'], klines[i]['low']
|
||||
prev_close = klines[i - 1]['close']
|
||||
tr = max(high - low, abs(high - prev_close), abs(low - prev_close))
|
||||
tr_list.append(tr)
|
||||
if len(tr_list) < period:
|
||||
return None
|
||||
# ATR = EMA(TR, period)
|
||||
alpha = 2.0 / (period + 1)
|
||||
atr = sum(tr_list[:period]) / period
|
||||
for i in range(period, len(tr_list)):
|
||||
atr = alpha * tr_list[i] + (1 - alpha) * atr
|
||||
return atr
|
||||
|
||||
def get_ema_atr_for_exit(self, kline_series):
|
||||
"""
|
||||
基于已收盘 K 线计算 EMA10、EMA20、ATR(14)。
|
||||
kline_series 最后一根可为当前未收盘 K 线,计算时用倒数第 2~N 根作为已收盘。
|
||||
返回 (ema10, ema20, atr14),任一不足则对应为 None。
|
||||
"""
|
||||
if not kline_series or len(kline_series) < 21:
|
||||
return None, None, None
|
||||
# 用除最后一根外的已收盘 K 线(若只有 21 根则用前 20 根)
|
||||
closed = kline_series[:-1] if len(kline_series) >= 21 else kline_series[:20]
|
||||
closes = [k['close'] for k in closed]
|
||||
ema10 = self._ema(closes, 10)
|
||||
ema20 = self._ema(closes, 20)
|
||||
atr14 = self._atr(closed, 14)
|
||||
return ema10, ema20, atr14
|
||||
|
||||
def get_current_price(self):
|
||||
"""获取当前最新价格"""
|
||||
try:
|
||||
@@ -622,18 +697,42 @@ class BitmartFuturesTransaction:
|
||||
|
||||
logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)")
|
||||
|
||||
# 3.5 止损/止盈/移动止损 + 当前K线从极值回落平仓
|
||||
# 3.5 止损/止盈/移动止损 + EMA/ATR 平仓 + 当前K线从极值回落平仓
|
||||
if self.start != 0:
|
||||
# 当前K线从最高/最低点回落平仓:换线重置跟踪,有持仓时更新本K线内最高/最低价并检查
|
||||
if self._candle_id_for_high_low != current_kline_time:
|
||||
self._candle_high_seen = None
|
||||
self._candle_low_seen = None
|
||||
self._candle_id_for_high_low = current_kline_time
|
||||
# 多头:先更新本 K 线最高价(供 ATR 追踪与后续回落逻辑用)
|
||||
if self.start == 1:
|
||||
self._candle_high_seen = max(self._candle_high_seen or 0, current_price)
|
||||
elif self.start == -1:
|
||||
self._candle_low_seen = min(self._candle_low_seen or float('inf'), current_price)
|
||||
|
||||
# 多单优先:EMA(10) + ATR(14) 平仓 —— 收盘跌破 EMA10 先平;或从最高价回撤 ≥ 1.1×ATR 平
|
||||
if self.start == 1 and self.use_ema_atr_exit:
|
||||
kline_series = self.get_klines_series(35)
|
||||
ema10, ema20, atr14 = self.get_ema_atr_for_exit(kline_series)
|
||||
if ema10 is not None and current_price < ema10:
|
||||
logger.info(f"多单 EMA10 平仓:当前价 {current_price:.2f} 跌破 EMA10 {ema10:.2f}(能赚点是点)")
|
||||
self.平仓()
|
||||
self.max_unrealized_pnl_seen = None
|
||||
self._candle_high_seen = None
|
||||
time.sleep(3)
|
||||
continue
|
||||
if atr14 is not None and self._candle_high_seen and (self._candle_high_seen - current_price) >= self.atr_multiplier * atr14:
|
||||
logger.info(f"多单 ATR 追踪止盈:最高 {self._candle_high_seen:.2f},当前 {current_price:.2f},回撤 {(self._candle_high_seen - current_price):.2f} >= {self.atr_multiplier}×ATR(14)={atr14:.2f}")
|
||||
self.平仓()
|
||||
self.max_unrealized_pnl_seen = None
|
||||
self._candle_high_seen = None
|
||||
time.sleep(3)
|
||||
continue
|
||||
|
||||
use_fixed = self.drop_from_high_mode == 'fixed' and self.drop_from_high_to_close and self.drop_from_high_to_close > 0
|
||||
use_pct = self.drop_from_high_mode == 'pct_retrace'
|
||||
if use_fixed or use_pct:
|
||||
if self.start == 1: # 多头:跟踪当前K线最高价,从最高点回落超过阈值则平仓
|
||||
self._candle_high_seen = max(self._candle_high_seen or 0, current_price)
|
||||
if self.start == 1: # 多头:最高价已在上面更新,这里只做回落判断
|
||||
do_close = False
|
||||
if use_fixed and self._candle_high_seen and current_price <= self._candle_high_seen - self.drop_from_high_to_close:
|
||||
do_close = True
|
||||
|
||||
Reference in New Issue
Block a user