优化完美代码,优化计算盈亏
This commit is contained in:
@@ -28,16 +28,6 @@ class BitmartFuturesTransaction:
|
||||
|
||||
self.last_kline_time = None # 上一次处理的K线时间戳,用于判断是否是新K线
|
||||
|
||||
# 反手频率控制
|
||||
self.reverse_cooldown_seconds = 1.5 * 60 # 反手冷却时间(秒)
|
||||
self.reverse_min_move_pct = 0.05 # 反手最小价差过滤(百分比)
|
||||
self.last_reverse_time = None # 上次反手时间
|
||||
|
||||
# 开仓频率控制
|
||||
self.open_cooldown_seconds = 60 # 开仓冷却时间(秒),两次开仓至少间隔此时长
|
||||
self.last_open_time = None # 上次开仓时间
|
||||
self.last_open_kline_id = None # 上次开仓所在 K 线 id,同一根 K 线只允许开仓一次
|
||||
|
||||
self.leverage = "100" # 高杠杆(全仓模式下可开更大仓位)
|
||||
self.open_type = "cross" # 全仓模式
|
||||
self.risk_percent = 0 # 未使用;若启用则可为每次开仓占可用余额的百分比
|
||||
@@ -48,12 +38,6 @@ class BitmartFuturesTransaction:
|
||||
self.bit_id = bit_id
|
||||
self.default_order_size = 25 # 开仓/反手张数,统一在此修改
|
||||
|
||||
# ATR 回调止盈参数
|
||||
self.atr_period = 14 # ATR 周期
|
||||
self.atr_multiplier = 2.5 # ATR 倍数(稳健 2.5~3,激进 1.5~2)
|
||||
self.highest_since_entry = None # 开仓以来最高价(多单追踪)
|
||||
self.lowest_since_entry = None # 开仓以来最低价(空单追踪)
|
||||
|
||||
# 策略相关变量
|
||||
self.prev_kline = None # 上一根K线
|
||||
self.current_kline = None # 当前K线
|
||||
@@ -93,33 +77,6 @@ class BitmartFuturesTransaction:
|
||||
self.ding(text="获取K线异常", error=True)
|
||||
return None, None
|
||||
|
||||
def get_all_klines(self):
|
||||
"""获取所有可用的5分钟K线(用于ATR计算等),按时间升序排列"""
|
||||
try:
|
||||
end_time = int(time.time())
|
||||
response = self.contractAPI.get_kline(
|
||||
contract_symbol=self.contract_symbol,
|
||||
step=5,
|
||||
start_time=end_time - 3600 * 3,
|
||||
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
|
||||
except Exception as e:
|
||||
logger.error(f"获取K线异常: {e}")
|
||||
self.ding(text="获取K线异常", error=True)
|
||||
return []
|
||||
|
||||
def get_current_price(self):
|
||||
"""获取当前最新价格"""
|
||||
try:
|
||||
@@ -182,14 +139,6 @@ class BitmartFuturesTransaction:
|
||||
logger.error(f"持仓查询异常: {e}")
|
||||
return False
|
||||
|
||||
def get_unrealized_pnl_usd(self):
|
||||
"""
|
||||
获取当前持仓未实现盈亏(美元),直接使用API返回值
|
||||
"""
|
||||
if self.start == 0 or self.unrealized_pnl is None:
|
||||
return None
|
||||
return self.unrealized_pnl
|
||||
|
||||
def set_leverage(self):
|
||||
"""程序启动时设置全仓 + 高杠杆"""
|
||||
try:
|
||||
@@ -299,82 +248,6 @@ class BitmartFuturesTransaction:
|
||||
'lower': min(kline['open'], kline['close']) # 实体下边
|
||||
}
|
||||
|
||||
def calculate_atr(self, klines):
|
||||
"""
|
||||
计算 ATR(14)
|
||||
klines: 按时间升序的完整K线列表(含当前未收盘K线)
|
||||
返回: ATR 值或 None
|
||||
"""
|
||||
# 排除最后一根(当前未收盘),使用已收盘K线
|
||||
closed = klines[:-1] if len(klines) > 1 else klines
|
||||
if len(closed) < self.atr_period + 1:
|
||||
logger.warning(f"已收盘K线不足({len(closed)}),需要至少 {self.atr_period + 1} 根,无法计算ATR")
|
||||
return None
|
||||
|
||||
# 计算 True Range 序列
|
||||
true_ranges = []
|
||||
for i in range(1, len(closed)):
|
||||
h = closed[i]['high']
|
||||
l = closed[i]['low']
|
||||
pc = closed[i - 1]['close']
|
||||
tr = max(h - l, abs(h - pc), abs(l - pc))
|
||||
true_ranges.append(tr)
|
||||
|
||||
# 取最近 atr_period 个 TR 的简单平均作为 ATR
|
||||
recent_trs = true_ranges[-self.atr_period:]
|
||||
atr = sum(recent_trs) / len(recent_trs)
|
||||
return atr
|
||||
|
||||
def check_trailing_stop(self, current_price, atr):
|
||||
"""
|
||||
检查 ATR 回调止盈是否触发
|
||||
- 多单: 从开仓以来最高价回落 >= ATR * k → 平仓
|
||||
- 空单: 从开仓以来最低价反弹 >= ATR * k → 平仓
|
||||
返回: True=触发止盈, False=未触发
|
||||
"""
|
||||
if self.start == 0 or atr is None:
|
||||
return False
|
||||
|
||||
if self.start == 1: # 持多仓
|
||||
# 首次追踪(程序重启等情况),从开仓均价开始
|
||||
if self.highest_since_entry is None:
|
||||
self.highest_since_entry = self.open_avg_price if self.open_avg_price else current_price
|
||||
if current_price > self.highest_since_entry:
|
||||
self.highest_since_entry = current_price
|
||||
|
||||
trailing_stop = self.highest_since_entry - atr * self.atr_multiplier
|
||||
logger.debug(f"[ATR止盈] 多单 | 最高={self.highest_since_entry:.2f} | "
|
||||
f"ATR={atr:.4f} | k={self.atr_multiplier} | "
|
||||
f"止盈线={trailing_stop:.2f} | 当前={current_price:.2f}")
|
||||
|
||||
if current_price <= trailing_stop:
|
||||
logger.info(f"ATR回调止盈触发(多)! 当前价 {current_price:.2f} <= "
|
||||
f"止盈线 {trailing_stop:.2f} "
|
||||
f"(最高 {self.highest_since_entry:.2f} - "
|
||||
f"{self.atr_multiplier} x ATR {atr:.4f})")
|
||||
return True
|
||||
|
||||
elif self.start == -1: # 持空仓
|
||||
# 首次追踪(程序重启等情况),从开仓均价开始
|
||||
if self.lowest_since_entry is None:
|
||||
self.lowest_since_entry = self.open_avg_price if self.open_avg_price else current_price
|
||||
if current_price < self.lowest_since_entry:
|
||||
self.lowest_since_entry = current_price
|
||||
|
||||
trailing_stop = self.lowest_since_entry + atr * self.atr_multiplier
|
||||
logger.debug(f"[ATR止盈] 空单 | 最低={self.lowest_since_entry:.2f} | "
|
||||
f"ATR={atr:.4f} | k={self.atr_multiplier} | "
|
||||
f"止盈线={trailing_stop:.2f} | 当前={current_price:.2f}")
|
||||
|
||||
if current_price >= trailing_stop:
|
||||
logger.info(f"ATR回调止盈触发(空)! 当前价 {current_price:.2f} >= "
|
||||
f"止盈线 {trailing_stop:.2f} "
|
||||
f"(最低 {self.lowest_since_entry:.2f} + "
|
||||
f"{self.atr_multiplier} x ATR {atr:.4f})")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def check_signal(self, current_price, prev_kline, current_kline):
|
||||
"""
|
||||
检查交易信号
|
||||
@@ -405,17 +278,17 @@ class BitmartFuturesTransaction:
|
||||
if use_current_open_as_base:
|
||||
# 以当前K线开盘价为基准计算(跳空时用当前开盘价参与计算)
|
||||
calc_lower = current_kline['open']
|
||||
calc_upper = current_kline['open'] # 同一基准,上下三分之一对称
|
||||
long_trigger = calc_lower + prev_entity / 3
|
||||
short_trigger = calc_upper - prev_entity / 3
|
||||
long_breakout = calc_upper + prev_entity / 3
|
||||
short_breakout = calc_lower - prev_entity / 3
|
||||
calc_upper = current_kline['open'] # 同一基准,上下四分之一对称
|
||||
long_trigger = calc_lower + prev_entity / 4
|
||||
short_trigger = calc_upper - prev_entity / 4
|
||||
long_breakout = calc_upper + prev_entity / 4
|
||||
short_breakout = calc_lower - prev_entity / 4
|
||||
else:
|
||||
# 原有计算方式
|
||||
long_trigger = prev_entity_lower + prev_entity / 3 # 做多触发价 = 实体下边 + 实体/3(下三分之一处)
|
||||
short_trigger = prev_entity_upper - prev_entity / 3 # 做空触发价 = 实体上边 - 实体/3(上三分之一处)
|
||||
long_breakout = prev_entity_upper + prev_entity / 3 # 做多突破价 = 实体上边 + 实体/3
|
||||
short_breakout = prev_entity_lower - prev_entity / 3 # 做空突破价 = 实体下边 - 实体/3
|
||||
long_trigger = prev_entity_lower + prev_entity / 4 # 做多触发价 = 实体下边 + 实体/4(下四分之一处)
|
||||
short_trigger = prev_entity_upper - prev_entity / 4 # 做空触发价 = 实体上边 - 实体/4(上四分之一处)
|
||||
long_breakout = prev_entity_upper + prev_entity / 4 # 做多突破价 = 实体上边 + 实体/4
|
||||
short_breakout = prev_entity_lower - prev_entity / 4 # 做空突破价 = 实体下边 - 实体/4
|
||||
|
||||
# 上一根阴线 + 当前阳线:做多形态,不按上一根K线上三分之一做空
|
||||
prev_is_bearish = prev_kline['close'] < prev_kline['open']
|
||||
@@ -433,27 +306,27 @@ class BitmartFuturesTransaction:
|
||||
logger.info(f"上一根阴线且当前开盘价({current_kline['open']:.2f})<上一根收盘价({prev_kline['close']:.2f}),以当前开盘价为基准计算")
|
||||
logger.info(f"当前价格: {current_price:.2f}, 上一根实体: {prev_entity:.4f}")
|
||||
logger.info(f"上一根实体上边: {prev_entity_upper:.2f}, 下边: {prev_entity_lower:.2f}")
|
||||
logger.info(f"做多触发价(下1/3): {long_trigger:.2f}, 做空触发价(上1/3): {short_trigger:.2f}")
|
||||
logger.info(f"突破做多价(上1/3外): {long_breakout:.2f}, 突破做空价(下1/3外): {short_breakout:.2f}")
|
||||
logger.info(f"做多触发价(下1/4): {long_trigger:.2f}, 做空触发价(上1/4): {short_trigger:.2f}")
|
||||
logger.info(f"突破做多价(上1/4外): {long_breakout:.2f}, 突破做空价(下1/4外): {short_breakout:.2f}")
|
||||
if skip_short_by_upper_third:
|
||||
logger.info("上一根阴线+当前阳线(做多形态),不按上三分之一做空")
|
||||
logger.info("上一根阴线+当前阳线(做多形态),不按上四分之一做空")
|
||||
if skip_long_by_lower_third:
|
||||
logger.info("上一根阳线+当前阴线(做空形态),不按下三分之一做多")
|
||||
logger.info("上一根阳线+当前阴线(做空形态),不按下四分之一做多")
|
||||
|
||||
# 无持仓时检查开仓信号
|
||||
if self.start == 0:
|
||||
if current_price >= long_breakout and not skip_long_by_lower_third:
|
||||
logger.info(f"触发做多信号!价格 {current_price:.2f} >= 突破价(上1/3外) {long_breakout:.2f}")
|
||||
logger.info(f"触发做多信号!价格 {current_price:.2f} >= 突破价(上1/4外) {long_breakout:.2f}")
|
||||
return ('long', long_breakout)
|
||||
elif current_price <= short_breakout and not skip_short_by_upper_third:
|
||||
logger.info(f"触发做空信号!价格 {current_price:.2f} <= 突破价(下1/3外) {short_breakout:.2f}")
|
||||
logger.info(f"触发做空信号!价格 {current_price:.2f} <= 突破价(下1/4外) {short_breakout:.2f}")
|
||||
return ('short', short_breakout)
|
||||
|
||||
# 持仓时检查反手信号
|
||||
elif self.start == 1: # 持多仓
|
||||
# 反手条件1: 价格跌到上一根K线的上三分之一处(做空触发价);上一根阴线+当前阳线做多时跳过
|
||||
if current_price <= short_trigger and not skip_short_by_upper_third:
|
||||
logger.info(f"持多反手做空!价格 {current_price:.2f} <= 触发价(上1/3) {short_trigger:.2f}")
|
||||
logger.info(f"持多反手做空!价格 {current_price:.2f} <= 触发价(上1/4) {short_trigger:.2f}")
|
||||
return ('reverse_short', short_trigger)
|
||||
|
||||
# 反手条件2: 上一根K线上阴线涨幅>0.01%,当前跌到上一根实体下边
|
||||
@@ -466,7 +339,7 @@ class BitmartFuturesTransaction:
|
||||
elif self.start == -1: # 持空仓
|
||||
# 反手条件1: 价格涨到上一根K线的下三分之一处(做多触发价);上一根阳线+当前阴线做空时跳过
|
||||
if current_price >= long_trigger and not skip_long_by_lower_third:
|
||||
logger.info(f"持空反手做多!价格 {current_price:.2f} >= 触发价(下1/3) {long_trigger:.2f}")
|
||||
logger.info(f"持空反手做多!价格 {current_price:.2f} >= 触发价(下1/4) {long_trigger:.2f}")
|
||||
return ('reverse_long', long_trigger)
|
||||
|
||||
# 反手条件2: 上一根K线下阴线跌幅>0.01%,当前涨到上一根实体上边
|
||||
@@ -478,34 +351,6 @@ class BitmartFuturesTransaction:
|
||||
|
||||
return None
|
||||
|
||||
def can_open(self, current_kline_id):
|
||||
"""开仓前过滤:同一根 K 线只开一次 + 开仓冷却时间。仅用于 long/short 新开仓。"""
|
||||
now = time.time()
|
||||
if self.last_open_kline_id is not None and self.last_open_kline_id == current_kline_id:
|
||||
logger.info(f"开仓频率控制:本 K 线({current_kline_id})已开过仓,跳过")
|
||||
return False
|
||||
if self.last_open_time is not None and now - self.last_open_time < self.open_cooldown_seconds:
|
||||
remain = self.open_cooldown_seconds - (now - self.last_open_time)
|
||||
logger.info(f"开仓冷却中,剩余 {remain:.0f} 秒")
|
||||
return False
|
||||
return True
|
||||
|
||||
def can_reverse(self, current_price, trigger_price):
|
||||
"""反手前过滤:冷却时间 + 最小价差"""
|
||||
now = time.time()
|
||||
if self.last_reverse_time and now - self.last_reverse_time < self.reverse_cooldown_seconds:
|
||||
remain = self.reverse_cooldown_seconds - (now - self.last_reverse_time)
|
||||
logger.info(f"反手冷却中,剩余 {remain:.0f} 秒")
|
||||
return False
|
||||
|
||||
if trigger_price and trigger_price > 0:
|
||||
move_pct = abs(current_price - trigger_price) / trigger_price * 100
|
||||
if move_pct < self.reverse_min_move_pct:
|
||||
logger.info(f"反手价差不足: {move_pct:.4f}% < {self.reverse_min_move_pct}%")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def verify_no_position(self, max_retries=5, retry_interval=3):
|
||||
"""
|
||||
验证当前无持仓
|
||||
@@ -565,10 +410,6 @@ class BitmartFuturesTransaction:
|
||||
|
||||
# 验证开仓是否成功
|
||||
if self.verify_position_direction(1):
|
||||
self.last_open_time = time.time()
|
||||
self.last_open_kline_id = getattr(self, "_current_kline_id_for_open", None)
|
||||
self.highest_since_entry = self.open_avg_price # ATR止盈:从开仓价开始追踪
|
||||
self.lowest_since_entry = None
|
||||
logger.success("开多成功")
|
||||
return True
|
||||
else:
|
||||
@@ -591,10 +432,6 @@ class BitmartFuturesTransaction:
|
||||
|
||||
# 验证开仓是否成功
|
||||
if self.verify_position_direction(-1):
|
||||
self.last_open_time = time.time()
|
||||
self.last_open_kline_id = getattr(self, "_current_kline_id_for_open", None)
|
||||
self.lowest_since_entry = self.open_avg_price # ATR止盈:从开仓价开始追踪
|
||||
self.highest_since_entry = None
|
||||
logger.success("开空成功")
|
||||
return True
|
||||
else:
|
||||
@@ -619,10 +456,7 @@ class BitmartFuturesTransaction:
|
||||
time.sleep(3)
|
||||
|
||||
if self.verify_position_direction(1):
|
||||
self.highest_since_entry = self.open_avg_price # ATR止盈:从开仓价开始追踪
|
||||
self.lowest_since_entry = None
|
||||
logger.success("反手做多成功")
|
||||
self.last_reverse_time = time.time()
|
||||
time.sleep(20)
|
||||
return True
|
||||
else:
|
||||
@@ -646,10 +480,7 @@ class BitmartFuturesTransaction:
|
||||
time.sleep(3)
|
||||
|
||||
if self.verify_position_direction(-1):
|
||||
self.lowest_since_entry = self.open_avg_price # ATR止盈:从开仓价开始追踪
|
||||
self.highest_since_entry = None
|
||||
logger.success("反手做空成功")
|
||||
self.last_reverse_time = time.time()
|
||||
time.sleep(20)
|
||||
return True
|
||||
else:
|
||||
@@ -661,7 +492,7 @@ class BitmartFuturesTransaction:
|
||||
def action(self):
|
||||
"""主循环"""
|
||||
|
||||
logger.info("开始运行三分之一策略交易...")
|
||||
logger.info("开始运行四分之一策略交易...")
|
||||
|
||||
# 启动时设置全仓高杠杆
|
||||
if not self.set_leverage():
|
||||
@@ -691,14 +522,12 @@ class BitmartFuturesTransaction:
|
||||
page_start = False
|
||||
|
||||
try:
|
||||
# 1. 获取完整K线数据(用于信号检测 + ATR计算)
|
||||
all_klines = self.get_all_klines()
|
||||
if len(all_klines) < 2:
|
||||
# 1. 获取K线数据(当前K线和上一根K线)
|
||||
prev_kline, current_kline = self.get_klines()
|
||||
if not prev_kline or not current_kline:
|
||||
logger.warning("获取K线失败,等待重试...")
|
||||
time.sleep(5)
|
||||
continue
|
||||
prev_kline = all_klines[-2]
|
||||
current_kline = all_klines[-1]
|
||||
|
||||
# 记录进入新的K线
|
||||
current_kline_time = current_kline['id']
|
||||
@@ -721,46 +550,10 @@ class BitmartFuturesTransaction:
|
||||
|
||||
logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)")
|
||||
|
||||
# 3.5 ATR 回调止盈检查(有持仓时)
|
||||
if self.start != 0:
|
||||
atr = self.calculate_atr(all_klines)
|
||||
if self.check_trailing_stop(current_price, atr):
|
||||
# 触发回调止盈,执行平仓
|
||||
self.平仓()
|
||||
time.sleep(3)
|
||||
# 轮询确认平仓成功(最多约10秒)
|
||||
for _ in range(10):
|
||||
if self.get_position_status() and self.start == 0:
|
||||
break
|
||||
time.sleep(1)
|
||||
if self.start == 0:
|
||||
logger.success("ATR回调止盈平仓成功")
|
||||
self.highest_since_entry = None
|
||||
self.lowest_since_entry = None
|
||||
page_start = True
|
||||
else:
|
||||
logger.warning("ATR回调止盈平仓后仍有持仓,下次循环重试")
|
||||
if page_start:
|
||||
self.page.close()
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
# 4. 检查信号
|
||||
signal = self.check_signal(current_price, prev_kline, current_kline)
|
||||
|
||||
# 5. 反手过滤:冷却时间 + 最小价差
|
||||
if signal and signal[0].startswith('reverse_'):
|
||||
if not self.can_reverse(current_price, signal[1]):
|
||||
signal = None
|
||||
|
||||
# 5.5 开仓频率过滤:同一根 K 线只开一次 + 开仓冷却
|
||||
if signal and signal[0] in ('long', 'short'):
|
||||
if not self.can_open(current_kline_time):
|
||||
signal = None
|
||||
else:
|
||||
self._current_kline_id_for_open = current_kline_time # 供 execute_trade 成功后记录
|
||||
|
||||
# 6. 有信号则执行交易
|
||||
# 5. 有信号则执行交易
|
||||
if signal:
|
||||
trade_success = self.execute_trade(signal)
|
||||
if trade_success:
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
# 四分之一策略 · ETHUSDT 5 分钟(修改版)— 详细说明
|
||||
|
||||
## 优化摘要(与初版对比)
|
||||
|
||||
- **触发方式**:由「当前价与阈值比较」改为**穿越触发**(`cross_up` / `cross_down`),即上一轮价在阈值一侧、当前轮在另一侧才触发,避免“已在阈值下方/上方”的追单。
|
||||
- **实体过滤**:由固定 `entity < 0.1` 改为**百分比或 ATR**:`entity_filter_mode` 可选 `"pct"`(entity/close ≥ min_entity_pct)或 `"atr"`(entity ≥ entity_atr_ratio × ATR14)。
|
||||
- **影线反手**:上/下影线阈值由 0.01% 提高到 **0.05%**(可调),且影线反手也要求**穿越**实体边,减少频繁反手。
|
||||
- **EMA20**:拆成 **use_ema20_entry_filter**(开仓/反手方向过滤)与 **use_ema20_force_exit**(持仓强制平仓),解耦便于调参。
|
||||
- **平仓防针洗**:**exit_use_last_close = True** 时用已收盘 K 线的 `last_close` 判断 EMA 破位,减少瞬间插针触发平仓。
|
||||
- **ATR 追踪**:由「本 K 线内最高/最低」改为**自开仓以来**的 `_highest_since_entry` / `_lowest_since_entry`,换线不重置,真正追踪止盈。
|
||||
- **开盘延迟**:可选 **open_trigger_delay_seconds**(默认 0),当前 K 线开始后经过该秒数才允许触发,减轻开盘即触发。
|
||||
|
||||
---
|
||||
|
||||
## 一、策略概述
|
||||
|
||||
- **名称**:四分之一策略(Quarter Strategy)
|
||||
- **标的**:Bitmart 合约 **ETHUSDT**
|
||||
- **周期**:**5 分钟 K 线**
|
||||
- **模式**:全仓(cross)、高杠杆(100x)
|
||||
- **思路**:用「上一根 K 线实体」的 **1/4 位置** 做突破/回落触发,结合 EMA10/EMA20/ATR 止盈与趋势过滤,支持多空开仓与反手。
|
||||
|
||||
---
|
||||
|
||||
## 二、核心价格与 K 线定义
|
||||
|
||||
### 2.1 上一根 K 线实体
|
||||
|
||||
- **实体大小**:`entity = |close - open|`(绝对值)
|
||||
- **实体上边**:`upper = max(open, close)`
|
||||
- **实体下边**:`lower = min(open, close)`
|
||||
|
||||
即:阳线时上边=收盘、下边=开盘;阴线时上边=开盘、下边=收盘。
|
||||
|
||||
### 2.2 四个关键价位(常规情况)
|
||||
|
||||
以**上一根 K 线**的实体为基准:
|
||||
|
||||
| 名称 | 计算公式 | 含义 |
|
||||
|--------------|------------------------------|--------------------------|
|
||||
| 做多触发 | `lower + entity/4` | 实体下方向上 1/4 处 |
|
||||
| 做空触发 | `upper - entity/4` | 实体上边向下 1/4 处 |
|
||||
| 突破做多 | `upper + entity/4` | 实体上边再向上 1/4 处 |
|
||||
| 突破做空 | `lower - entity/4` | 实体下边再向下 1/4 处 |
|
||||
|
||||
- **无仓时**:做多看「突破做多」价,做空看「做空触发」价。
|
||||
- **有仓时**:反手多/空用「做多触发」「做空触发」价。
|
||||
|
||||
### 2.3 跳空时的基准修正
|
||||
|
||||
当出现跳空时,用**当前 K 线开盘价**作为计算基准,使 1/4 区间对称于开盘价:
|
||||
|
||||
- **情况 1**:上一根为**阳线**且**当前开盘价 > 上一根收盘价**(跳空高开)
|
||||
- **情况 2**:上一根为**阴线**且**当前开盘价 < 上一根收盘价**(跳空低开)
|
||||
|
||||
此时:
|
||||
|
||||
- `calc_base = 当前 K 线 open`
|
||||
- 做多触发 = `calc_base + entity/4`
|
||||
- 做空触发 = `calc_base - entity/4`
|
||||
- 突破做多 = `calc_base + entity/4`(与做多触发同)
|
||||
- 突破做空 = `calc_base - entity/4`(与做空触发同)
|
||||
|
||||
即跳空时以当前开盘为轴心,上下各 1/4 实体对称。
|
||||
|
||||
---
|
||||
|
||||
## 三、开仓与反手信号逻辑
|
||||
|
||||
### 3.1 前置过滤(所有信号共用)
|
||||
|
||||
- **实体过小**:按 `entity_filter_mode` 二选一:`entity/close` 不低于 `min_entity_pct`(如 0.02%),或 `entity` 不低于 `entity_atr_ratio × ATR(14)`(如 0.2×ATR)。不满足则本根不产生信号。
|
||||
- **形态过滤**:
|
||||
- **上一根阴线 + 当前阳线**(视为做多形态):不按「做空触发(上 1/4)」做空。
|
||||
- **上一根阳线 + 当前阴线**(视为做空形态):不按「做多触发(下 1/4)」做多。
|
||||
- **穿越触发**:所有开仓/反手均要求**上一轮价格在阈值一侧、当前轮穿越到另一侧**(避免追单)。
|
||||
|
||||
### 3.2 无持仓(start == 0)
|
||||
|
||||
- **做多**:**穿越**突破做多(上一轮 < 突破价 ≤ 当前价),且未被做多形态过滤、且通过 EMA20 入场过滤 → 开多。
|
||||
- **做空**:**穿越**做空触发(上一轮 > 做空触发 ≥ 当前价),且未被做空形态过滤、且通过 EMA20 入场过滤 → 开空。
|
||||
|
||||
### 3.3 持多仓(start == 1)→ 反手做空
|
||||
|
||||
满足其一即反手空:
|
||||
|
||||
1. **穿越**做空触发,且反手价差 ≥ reverse_min_move_pct,且 EMA20 入场过滤通过。
|
||||
2. **上影线反手**:上一根**上影线比例 > min_upper_shadow_pct**(如 0.05%),且**穿越**上一根实体下边。
|
||||
|
||||
### 3.4 持空仓(start == -1)→ 反手做多
|
||||
|
||||
满足其一即反手多:
|
||||
|
||||
1. **穿越**做多触发,且反手价差 ≥ reverse_min_move_pct,且 EMA20 入场过滤通过。
|
||||
2. **下影线反手**:上一根**下影线比例 > min_lower_shadow_pct**(如 0.05%),且**穿越**上一根实体上边。
|
||||
|
||||
---
|
||||
|
||||
## 四、反手前的额外过滤
|
||||
|
||||
- **反手价差过滤**:
|
||||
`reverse_min_move_pct = 0.05%`
|
||||
反手时要求:`|当前价 - 触发价| / 触发价 * 100 >= 0.05%`,否则不执行反手(避免刚触及就反手)。
|
||||
|
||||
---
|
||||
|
||||
## 五、开仓前的过滤
|
||||
|
||||
1. **同 K 线出场不再开仓**:若本根 K 线内已经因 EMA/ATR/EMA20 平过仓(`_last_exit_kline_id == current_kline_time`),本根 K 线内不再开多/开空,等下一根 K 线再判断。
|
||||
2. **EMA20 方向过滤**(`use_ema20_filter = True`):
|
||||
- 开多 / 反手多:仅当 **当前价 > EMA20** 时允许。
|
||||
- 开空 / 反手空:仅当 **当前价 < EMA20** 时允许。
|
||||
即逆势(价在 EMA20 另一侧)时暂停开仓/反手。
|
||||
|
||||
---
|
||||
|
||||
## 六、平仓(止盈/风控)规则
|
||||
|
||||
- **最短持仓时间**:`min_hold_seconds = 90` 秒。开仓或反手后,至少持仓 90 秒才允许下面「技术性平仓」。
|
||||
- **EMA/ATR 平仓开关**:`use_ema_atr_exit = True` 时,下面规则才生效。
|
||||
- **ATR 倍数**:`atr_multiplier = 1.1`,即 1.1 × ATR(14)。
|
||||
- **防针洗**:`exit_use_last_close = True` 时,EMA10/EMA20 破位用**已收盘 K 线的 last_close** 判断,减少插针触发平仓;ATR 仍用当前价与自开仓以来极值比较。
|
||||
- **EMA20 强制平**:由独立开关 **use_ema20_force_exit** 控制,与开仓用的 **use_ema20_entry_filter** 解耦。
|
||||
|
||||
### 6.1 多单平仓(满足其一即平)
|
||||
|
||||
1. **EMA10 快出**:参考价 **< EMA10**(参考价 = last_close 或当前价,见上)→ 平多。
|
||||
2. **EMA20 强制平**(`use_ema20_force_exit = True`):参考价 **< EMA20** → 强制平多。
|
||||
3. **ATR 追踪止盈**:从**自开仓以来最高价**回撤 **≥ 1.1×ATR(14)**(用当前价)→ 平多。极值不随换线重置。
|
||||
|
||||
### 6.2 空单平仓(满足其一即平)
|
||||
|
||||
1. **EMA10 快出**:参考价 **> EMA10** → 平空。
|
||||
2. **EMA20 强制平**:参考价 **> EMA20** → 强制平空。
|
||||
3. **ATR 追踪止盈**:从**自开仓以来最低价**反弹 **≥ 1.1×ATR(14)** → 平空。
|
||||
|
||||
### 6.3 指标计算说明
|
||||
|
||||
- **EMA10 / EMA20 / ATR(14) / last_close**:均基于「已收盘 K 线」计算(最近约 35 根 5m,排除当前未收盘一根);`get_ema_atr_for_exit` 返回 dict 含上述四项。
|
||||
- **自开仓以来最高/最低**:仅在持仓期间用当前价更新,用于 ATR 追踪;开仓/反手时重置为当次入场价,换线不重置。
|
||||
|
||||
---
|
||||
|
||||
## 七、交易执行与风控细节
|
||||
|
||||
- **下单方式**:市价单;开仓/反手统一张数 `default_order_size = 25`(可在代码中修改)。
|
||||
- **反手流程**:先市价平仓 → 轮询确认无持仓(最多约 10 秒)→ 再市价开新方向;反手成功后多等约 20 秒再继续循环,避免界面/接口延迟。
|
||||
- **开仓前校验**:开多/开空前会查持仓,若已有反向或同向持仓则放弃本次开仓,避免双向持仓。
|
||||
- **杠杆与账户**:启动时设置全仓、100x 杠杆;未使用 `risk_percent`,仓位由固定张数控制。
|
||||
|
||||
---
|
||||
|
||||
## 八、主循环流程概览
|
||||
|
||||
1. 拉取最近 2 根 5 分钟 K 线(上一根 + 当前根)及当前价。
|
||||
2. 每次循环通过 API 获取真实持仓状态(避免与交易所不同步)。
|
||||
3. **若已有持仓**:
|
||||
先判断是否满足「最短持仓 90 秒」+ EMA10/EMA20/ATR 平仓条件,满足则平仓并本 K 线内不再开仓。
|
||||
4. **信号检测**:
|
||||
根据当前价与上一根实体 1/4 计算做多/做空/突破价,结合形态过滤与持仓状态,得到开多、开空、反手多、反手空之一或无信号。
|
||||
5. **反手过滤**:若为反手信号,再检查反手价差 ≥ 0.05%。
|
||||
6. **开仓过滤**:若为开多/开空,检查「本 K 线未因技术性平仓」及 EMA20 方向过滤。
|
||||
7. **执行**:有信号则执行对应开仓或反手,成功后刷新页面/状态并短暂等待再进入下一轮。
|
||||
|
||||
---
|
||||
|
||||
## 九、关键参数汇总
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
|------------------------------|-----------|----------------------------------------|
|
||||
| contract_symbol | ETHUSDT | 合约品种 |
|
||||
| 周期 | 5 分钟 | K 线步长 |
|
||||
| leverage | 100 | 杠杆倍数 |
|
||||
| open_type | cross | 全仓 |
|
||||
| default_order_size | 25 | 开仓/反手张数 |
|
||||
| reverse_min_move_pct | 0.05 | 反手最小价差(%) |
|
||||
| min_hold_seconds | 90 | 最短持仓时间(秒) |
|
||||
| use_ema_atr_exit | True | 是否启用 EMA/ATR 平仓 |
|
||||
| atr_multiplier | 1.1 | ATR 追踪止盈倍数(自开仓以来极值) |
|
||||
| use_ema20_entry_filter | True | 开仓/反手方向过滤(价在 EMA20 同侧) |
|
||||
| use_ema20_force_exit | True | 持仓强制平仓(价穿越 EMA20 则平) |
|
||||
| exit_use_last_close | True | 用已收盘 last_close 判断 EMA 破位防针洗|
|
||||
| entity_filter_mode | atr | 实体过滤:pct 或 atr |
|
||||
| min_entity_pct | 0.02 | 实体/close ≥ 此%才交易(mode=pct) |
|
||||
| entity_atr_ratio | 0.2 | 实体 ≥ 此倍数×ATR 才交易(mode=atr) |
|
||||
| min_upper_shadow_pct | 0.05 | 上影线比例 > 此值才允许上影线反手 |
|
||||
| min_lower_shadow_pct | 0.05 | 下影线比例 > 此值才允许下影线反手 |
|
||||
| open_trigger_delay_seconds | 0 | 当前 K 线开始后延迟 N 秒再允许触发 |
|
||||
|
||||
---
|
||||
|
||||
以上即为「四分之一,五分钟,反手条件充足修改版」中的完整策略逻辑说明;实际运行以代码为准,本文档便于理解和后续调参。
|
||||
Reference in New Issue
Block a user