优化完美代码,优化计算盈亏

This commit is contained in:
27942
2026-02-08 17:39:13 +08:00
parent 31cfb4ad5a
commit e8ebfacaff

View File

@@ -8,6 +8,21 @@ from DrissionPage import ChromiumOptions
from bitmart.api_contract import APIContract
# ---------- 策略常量(影线 ATR 自适应 · 趋势/震荡双模式 · 高胜率版)----------
K_PERIOD = 5
ATR_LENGTH = 14
MODE_LOOKBACK = 6
MIN_BODY = 0.1
DEFAULT_ORDER_SIZE = 25
WICK_ORDER_RATIO = 0.3
WICK_VALID = 0.9 # ≥ 0.9 ATR 才认为是可交易影线
WICK_EXTREME = 1.2 # ≥ 1.2 ATR 为极端扫单
TRAIL_RANGE = 0.2 # 震荡模式回撤止盈 = ATR * 0.2
TRAIL_TREND = 0.35 # 趋势模式回撤止盈 = ATR * 0.35
TP_ACTIVATION = 0.3 # 启动止盈需浮盈 ≥ ATR * 0.3
SL_WICK_ATR = 0.3 # 影线单止损:多单 SL = low - ATR*0.3,空单 SL = high + ATR*0.3
MODE_LOCK_BARS = 3 # 趋势模式锁定 K 根数
class BitmartFuturesTransaction:
def __init__(self, bit_id):
@@ -24,51 +39,58 @@ class BitmartFuturesTransaction:
self.start = 0 # 持仓状态: -1 空, 0 无, 1 多
self.pbar = tqdm(total=30, desc="等待K线", ncols=80) # 可选:用于长时间等待时展示进度
self.pbar = tqdm(total=30, desc="等待K线", ncols=80)
self.last_kline_time = None # 上一次处理的K线时间戳用于判断是否是新K线
self.last_kline_time = None
self.leverage = "100" # 高杠杆(全仓模式下可开更大仓位)
self.open_type = "cross" # 全仓模式
self.risk_percent = 0 # 未使用;若启用则可为每次开仓占可用余额的百分比
self.leverage = "100"
self.open_type = "cross"
self.risk_percent = 0
self.open_avg_price = None # 开仓价格
self.current_amount = None # 持仓量
self.open_avg_price = None
self.current_amount = None
self.bit_id = bit_id
self.default_order_size = 25 # 开仓/反手张数,统一在此修改
self.default_order_size = DEFAULT_ORDER_SIZE
# 策略相关变量
self.prev_kline = None # 上一根K线
self.current_kline = None # 当前K线
self.prev_entity = None # 上一根K线实体大小
self.current_open = None # 当前K线开盘价
self.prev_kline = None
self.current_kline = None
# 趋势/震荡模式状态
self.mode = "TREND" # TREND | RANGE
self.mode_lock_remaining = 0 # 趋势模式锁定剩余 K 数
# 止盈用:入场价、持仓期间最优价(多=最高,空=最低)
self.entry_price = None
self.best_price = None
self.is_wick_position = False # 当前仓位是否为影线单(用于影线单止损)
def _format_kline_list(self, response_data):
"""将 API 返回的 K 线转为统一格式列表"""
formatted = []
for k in response_data:
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
def get_klines(self):
"""获取最近2根K线(当前K线和上一根K线"""
"""获取最近 2 根 K 线(当前上一根)"""
try:
end_time = int(time.time())
# 获取足够多的条目确保有最新的K线
response = self.contractAPI.get_kline(
contract_symbol=self.contract_symbol,
step=5, # 5分钟
start_time=end_time - 3600 * 3, # 取最近3小时
step=K_PERIOD,
start_time=end_time - 3600 * 3,
end_time=end_time
)[0]["data"]
# 每根: [timestamp, open, high, low, close, volume]
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'])
# 返回最近2根K线倒数第二根上一根和最后一根当前
formatted = self._format_kline_list(response)
if len(formatted) >= 2:
return formatted[-2], formatted[-1]
return None, None
@@ -77,6 +99,97 @@ class BitmartFuturesTransaction:
self.ding(text="获取K线异常", error=True)
return None, None
def get_klines_batch(self, count):
"""获取最近 count 根 5 分钟 K 线,用于 ATR 与模式判定。至少需要 ATR_LENGTH + MODE_LOOKBACK。"""
try:
end_time = int(time.time())
response = self.contractAPI.get_kline(
contract_symbol=self.contract_symbol,
step=K_PERIOD,
start_time=end_time - 3600 * 4,
end_time=end_time
)[0]["data"]
formatted = self._format_kline_list(response)
if len(formatted) >= count:
return formatted[-count:]
return formatted
except Exception as e:
logger.error(f"获取K线批量异常: {e}")
return []
def compute_atr(self, klines, length=ATR_LENGTH):
"""ATR(length)klines 为按时间升序的 K 线列表,每根含 high, low, close。"""
if not klines or len(klines) < length + 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) < length:
return None
# 前 length 个 TR 的均值作为第一个 ATR然后平滑这里用 SMA 简化)
atr = sum(tr_list[-length:]) / length
return atr
def get_prev_kline_struct(self, prev_kline):
"""上一根 K 线基础结构(与策略文档一致)"""
body_high = max(prev_kline['open'], prev_kline['close'])
body_low = min(prev_kline['open'], prev_kline['close'])
body_size = body_high - body_low
upper_wick = prev_kline['high'] - body_high
lower_wick = body_low - prev_kline['low']
full_range = prev_kline['high'] - prev_kline['low']
return {
'body_high': body_high,
'body_low': body_low,
'body_size': body_size,
'upper_wick': upper_wick,
'lower_wick': lower_wick,
'full_range': full_range if full_range > 0 else 1e-8,
}
def detect_mode(self, klines):
"""基于最近 MODE_LOOKBACK 根 K 线判定趋势/震荡,并更新 self.mode 与锁定。"""
if not klines or len(klines) < MODE_LOOKBACK:
return
lookback = klines[-MODE_LOOKBACK:]
body_sizes = []
full_ranges = []
upper_wicks = []
lower_wicks = []
bullish_count = sum(1 for k in lookback if k['close'] > k['open'])
bearish_count = len(lookback) - bullish_count
direction_consistency = max(bullish_count, bearish_count) / MODE_LOOKBACK
for k in lookback:
s = self.get_prev_kline_struct(k)
body_sizes.append(s['body_size'])
full_ranges.append(s['full_range'])
upper_wicks.append(s['upper_wick'])
lower_wicks.append(s['lower_wick'])
avg_body = sum(body_sizes) / len(body_sizes)
avg_range = sum(full_ranges) / len(full_ranges)
avg_wick = sum(upper_wicks) / len(upper_wicks) + sum(lower_wicks) / len(lower_wicks)
body_ratio = avg_body / avg_range if avg_range > 0 else 0
wick_ratio = avg_wick / avg_range if avg_range > 0 else 0
trend_score = sum([body_ratio >= 0.6, wick_ratio <= 0.35, direction_consistency >= 0.67])
range_score = sum([body_ratio <= 0.4, wick_ratio >= 0.5, direction_consistency <= 0.5])
if self.mode_lock_remaining > 0:
return
if trend_score >= 2:
self.mode = "TREND"
self.mode_lock_remaining = MODE_LOCK_BARS
logger.info(f"模式切换为 TREND锁定 {MODE_LOCK_BARS} 根K线")
elif range_score >= 2:
self.mode = "RANGE"
self.mode_lock_remaining = 0
logger.info("模式切换为 RANGE")
# 其他情况沿用上一模式
def get_current_price(self):
"""获取当前最新价格"""
try:
@@ -112,7 +225,7 @@ class BitmartFuturesTransaction:
return None
def get_position_status(self):
"""获取当前持仓方向"""
"""获取当前持仓方向,并维护 entry_price / best_price无仓时清空"""
try:
response = self.contractAPI.get_position(contract_symbol=self.contract_symbol)[0]
if response['code'] == 1000:
@@ -122,15 +235,21 @@ class BitmartFuturesTransaction:
self.open_avg_price = None
self.current_amount = None
self.unrealized_pnl = None
self.entry_price = None
self.best_price = None
self.is_wick_position = False
return True
pos = positions[0]
self.start = 1 if pos['position_type'] == 1 else -1
self.open_avg_price = float(pos['open_avg_price'])
self.current_amount = float(pos['current_amount'])
self.position_cross = pos["position_cross"]
# 直接从API获取未实现盈亏Bitmart返回的是 unrealized_value 字段)
self.unrealized_pnl = float(pos.get('unrealized_value', 0))
logger.debug(f"持仓详情: 方向={self.start}, 开仓均价={self.open_avg_price}, "
if self.entry_price is None:
self.entry_price = self.open_avg_price
self.best_price = self.open_avg_price
self.is_wick_position = False
logger.debug(f"持仓: 方向={self.start}, 开仓均价={self.open_avg_price}, "
f"持仓量={self.current_amount}, 未实现盈亏={self.unrealized_pnl:.2f}")
return True
else:
@@ -187,16 +306,18 @@ class BitmartFuturesTransaction:
def 开单(self, marketPriceLongOrder=0, limitPriceShortOrder=0, size=None, price=None):
"""
marketPriceLongOrder 市价做多或做空1是做多,-1是做
limitPriceShortOrder 限价做多或者做空
marketPriceLongOrder 市价做多或做空1=多,-1=
limitPriceShortOrder 限价
size 市价单张数,不传则用 default_order_size
"""
order_size = size if size is not None else self.default_order_size
if marketPriceLongOrder == -1:
# self.click_safe('x://button[normalize-space(text()) ="市价"]')
# self.page.ele('x://*[@id="size_0"]').input(vals=size, clear=True)
self.click_safe('x://button[normalize-space(text()) ="市价"]')
self.page.ele('x://*[@id="size_0"]').input(vals=order_size, clear=True)
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
elif marketPriceLongOrder == 1:
# self.click_safe('x://button[normalize-space(text()) ="市价"]')
# self.page.ele('x://*[@id="size_0"]').input(vals=size, clear=True)
self.click_safe('x://button[normalize-space(text()) ="市价"]')
self.page.ele('x://*[@id="size_0"]').input(vals=order_size, clear=True)
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
if limitPriceShortOrder == -1:
@@ -219,135 +340,142 @@ class BitmartFuturesTransaction:
else:
logger.info(text)
def calculate_entity(self, kline):
"""计算K线实体大小绝对值"""
return abs(kline['close'] - kline['open'])
def compute_third_prices(self, prev_kline, current_kline):
"""
三分之一关键价位与形态过滤(与策略文档一致)。
返回: dict(long_trigger, short_trigger, break_long, break_short,
skip_long_by_lower_third, skip_short_by_upper_third, body_size, prev_struct)
"""
s = self.get_prev_kline_struct(prev_kline)
body_high, body_low, body_size = s['body_high'], s['body_low'], s['body_size']
def calculate_upper_shadow(self, kline):
"""计算上阴线(上影线)涨幅百分比"""
# 上阴线 = (最高价 - max(开盘价, 收盘价)) / max(开盘价, 收盘价)
body_top = max(kline['open'], kline['close'])
if body_top == 0:
return 0
return (kline['high'] - body_top) / body_top * 100
prev_bull = prev_kline['close'] > prev_kline['open']
prev_bear = prev_kline['close'] < prev_kline['open']
curr_open_above = current_kline['open'] > prev_kline['close']
curr_open_below = current_kline['open'] < prev_kline['close']
use_curr_open = (prev_bull and curr_open_above) or (prev_bear and curr_open_below)
def calculate_lower_shadow(self, kline):
"""计算下阴线(下影线)跌幅百分比"""
# 下阴线 = (min(开盘价, 收盘价) - 最低价) / min(开盘价, 收盘价)
body_bottom = min(kline['open'], kline['close'])
if body_bottom == 0:
return 0
return (body_bottom - kline['low']) / body_bottom * 100
if use_curr_open:
co = current_kline['open']
long_trigger = co - body_size / 3
short_trigger = co + body_size / 3
break_long = co + body_size / 3
break_short = co - body_size / 3
else:
long_trigger = body_low + body_size / 3
short_trigger = body_high - body_size / 3
break_long = body_high + body_size / 3
break_short = body_low - body_size / 3
prev_bear_curr_bull = prev_bear and (current_kline['close'] > current_kline['open'])
prev_bull_curr_bear = prev_bull and (current_kline['close'] < current_kline['open'])
skip_short_by_upper_third = prev_bear_curr_bull
skip_long_by_lower_third = prev_bull_curr_bear
def get_entity_edge(self, kline):
"""获取K线实体边收盘价或开盘价取决于是阳线还是阴线"""
# 阳线(收盘>开盘):实体上边=收盘价,实体下边=开盘价
# 阴线(收盘<开盘):实体上边=开盘价,实体下边=收盘价
return {
'upper': max(kline['open'], kline['close']), # 实体上边
'lower': min(kline['open'], kline['close']) # 实体下边
'long_trigger': long_trigger,
'short_trigger': short_trigger,
'break_long': break_long,
'break_short': break_short,
'skip_long_by_lower_third': skip_long_by_lower_third,
'skip_short_by_upper_third': skip_short_by_upper_third,
'body_size': body_size,
'prev_struct': s,
}
def check_signal(self, current_price, prev_kline, current_kline):
"""
检查交易信号
返回: ('long', trigger_price) / ('short', trigger_price) / None
"""
# 计算上一根K线实体
prev_entity = self.calculate_entity(prev_kline)
def check_sl(self, current_price, prev_kline, atr):
"""影线单止损(优先级 1。仅对 is_wick_position 生效。返回 'close' 或 None。"""
if atr is None or atr <= 0 or not self.is_wick_position:
return None
if self.start == 1:
sl = prev_kline['low'] - atr * SL_WICK_ATR
if current_price <= sl:
logger.info(f"影线多单止损: 价格 {current_price:.2f} <= SL {sl:.2f} (low - ATR*{SL_WICK_ATR})")
return 'close'
elif self.start == -1:
sl = prev_kline['high'] + atr * SL_WICK_ATR
if current_price >= sl:
logger.info(f"影线空单止损: 价格 {current_price:.2f} >= SL {sl:.2f} (high + ATR*{SL_WICK_ATR})")
return 'close'
return None
# 实体过小不交易(实体 < 0.1
if prev_entity < 0.1:
logger.info(f"上一根K线实体过小: {prev_entity:.4f},跳过信号检测")
def check_tp(self, current_price, atr):
"""回撤止盈(优先级 2。返回 'close' 或 None。"""
if atr is None or atr <= 0 or self.entry_price is None or self.best_price is None:
return None
if abs(self.best_price - self.entry_price) < atr * TP_ACTIVATION:
return None
trail = TRAIL_RANGE * atr if self.mode == "RANGE" else TRAIL_TREND * atr
if self.start == 1:
if current_price <= self.best_price - trail:
logger.info(f"多单止盈: 价格 {current_price:.2f} <= best_price - TRAIL ({self.best_price:.2f} - {trail:.2f})")
return 'close'
elif self.start == -1:
if current_price >= self.best_price + trail:
logger.info(f"空单止盈: 价格 {current_price:.2f} >= best_price + TRAIL ({self.best_price:.2f} + {trail:.2f})")
return 'close'
return None
def check_reversal(self, current_price, prev_kline, atr, third):
"""反手信号(优先级 3仅趋势模式。返回 (reverse_long/reverse_short, trigger_price) 或 None。"""
if self.mode != "TREND" or atr is None or atr <= 0:
return None
s = third['prev_struct']
body_high, body_low = s['body_high'], s['body_low']
upper_wick_strength = s['upper_wick'] / atr
lower_wick_strength = s['lower_wick'] / atr
if self.start == 1:
if current_price <= third['short_trigger'] and not third['skip_short_by_upper_third']:
logger.info(f"持多反手做空(1/3): 价格 {current_price:.2f} <= short_trigger {third['short_trigger']:.2f}")
return ('reverse_short', third['short_trigger'])
if upper_wick_strength >= WICK_EXTREME and current_price <= body_low:
logger.info(f"持多反手做空(影线失败): upper_wick_strength={upper_wick_strength:.2f}, 价格<=body_low {body_low:.2f}")
return ('reverse_short', body_low)
elif self.start == -1:
if current_price >= third['long_trigger'] and not third['skip_long_by_lower_third']:
logger.info(f"持空反手做多(1/3): 价格 {current_price:.2f} >= long_trigger {third['long_trigger']:.2f}")
return ('reverse_long', third['long_trigger'])
if lower_wick_strength >= WICK_EXTREME and current_price >= body_high:
logger.info(f"持空反手做多(影线失败): lower_wick_strength={lower_wick_strength:.2f}, 价格>=body_high {body_high:.2f}")
return ('reverse_long', body_high)
return None
def check_open(self, current_price, prev_kline, current_kline, atr, third):
"""
新开仓(优先级 4。趋势模式只吃实体突破震荡模式只吃影线回撤。
返回: (signal_type, trigger_price, size, is_wick) 或 None。
"""
if self.start != 0:
return None
s = third['prev_struct']
body_high, body_low = s['body_high'], s['body_low']
body_size = third['body_size']
if body_size < MIN_BODY:
return None
# 获取上一根K线的实体上下边
prev_entity_edge = self.get_entity_edge(prev_kline)
prev_entity_upper = prev_entity_edge['upper'] # 实体上边
prev_entity_lower = prev_entity_edge['lower'] # 实体下边
if self.mode == "TREND":
if current_price >= third['break_long'] and not third['skip_long_by_lower_third']:
logger.info(f"趋势开多: 价格 {current_price:.2f} >= break_long {third['break_long']:.2f}")
return ('long', third['break_long'], self.default_order_size, False)
if current_price <= third['break_short'] and not third['skip_short_by_upper_third']:
logger.info(f"趋势开空: 价格 {current_price:.2f} <= break_short {third['break_short']:.2f}")
return ('short', third['break_short'], self.default_order_size, False)
# 优化:以下两种情况以当前这根的开盘价作为计算基准
# 1) 上一根阳线 且 当前开盘价 > 上一根收盘价(跳空高开)
# 2) 上一根阴线 且 当前开盘价 < 上一根收盘价(跳空低开)
prev_is_bullish_for_calc = prev_kline['close'] > prev_kline['open']
prev_is_bearish_for_calc = prev_kline['close'] < prev_kline['open']
current_open_above_prev_close = current_kline['open'] > prev_kline['close']
current_open_below_prev_close = current_kline['open'] < prev_kline['close']
use_current_open_as_base = (prev_is_bullish_for_calc and current_open_above_prev_close) or (prev_is_bearish_for_calc and current_open_below_prev_close)
elif self.mode == "RANGE" and atr and atr > 0:
upper_wick_strength = s['upper_wick'] / atr
lower_wick_strength = s['lower_wick'] / atr
low, high = prev_kline['low'], prev_kline['high']
wick_long_level = low + min(s['lower_wick'] * 0.5, atr * 0.6)
wick_short_level = high - min(s['upper_wick'] * 0.5, atr * 0.6)
size_wick = max(1, int(self.default_order_size * WICK_ORDER_RATIO))
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
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
# 上一根阴线 + 当前阳线做多形态不按上一根K线上三分之一做空
prev_is_bearish = prev_kline['close'] < prev_kline['open']
current_is_bullish = current_kline['close'] > current_kline['open']
skip_short_by_upper_third = prev_is_bearish and current_is_bullish
# 上一根阳线 + 当前阴线做空形态不按上一根K线下三分之一做多
prev_is_bullish = prev_kline['close'] > prev_kline['open']
current_is_bearish = current_kline['close'] < current_kline['open']
skip_long_by_lower_third = prev_is_bullish and current_is_bearish
if use_current_open_as_base:
if prev_is_bullish_for_calc and current_open_above_prev_close:
logger.info(f"上一根阳线且当前开盘价({current_kline['open']:.2f})>上一根收盘价({prev_kline['close']:.2f}),以当前开盘价为基准计算")
else:
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}")
if skip_short_by_upper_third:
logger.info("上一根阴线+当前阳线(做多形态),不按上三分之一做空")
if skip_long_by_lower_third:
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}")
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}")
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}")
return ('reverse_short', short_trigger)
# 反手条件2: 上一根K线上阴线涨幅>0.01%,当前跌到上一根实体下边
upper_shadow_pct = self.calculate_upper_shadow(prev_kline)
if upper_shadow_pct > 0.01 and current_price <= prev_entity_lower:
logger.info(f"持多反手做空!上阴线涨幅 {upper_shadow_pct:.4f}% > 0.01%"
f"价格 {current_price:.2f} <= 实体下边 {prev_entity_lower:.2f}")
return ('reverse_short', prev_entity_lower)
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}")
return ('reverse_long', long_trigger)
# 反手条件2: 上一根K线下阴线跌幅>0.01%,当前涨到上一根实体上边
lower_shadow_pct = self.calculate_lower_shadow(prev_kline)
if lower_shadow_pct > 0.01 and current_price >= prev_entity_upper:
logger.info(f"持空反手做多!下阴线跌幅 {lower_shadow_pct:.4f}% > 0.01%"
f"价格 {current_price:.2f} >= 实体上边 {prev_entity_upper:.2f}")
return ('reverse_long', prev_entity_upper)
if lower_wick_strength >= WICK_VALID and current_price >= wick_long_level and current_price > body_low:
logger.info(f"震荡影线开多: lower_wick_strength={lower_wick_strength:.2f}, 价格>={wick_long_level:.2f}")
return ('long', wick_long_level, size_wick, True)
if upper_wick_strength >= WICK_VALID and current_price <= wick_short_level and current_price < body_high:
logger.info(f"震荡影线开空: upper_wick_strength={upper_wick_strength:.2f}, 价格<={wick_short_level:.2f}")
return ('short', wick_short_level, size_wick, True)
return None
@@ -389,10 +517,35 @@ class BitmartFuturesTransaction:
logger.error("查询持仓状态失败")
return False
def execute_trade(self, signal, size=None):
"""执行交易。size 不传或为 None 时使用 default_order_size"""
signal_type, trigger_price = signal
size = self.default_order_size if size is None else size
def execute_close(self):
"""执行平仓(用于止盈/止损)"""
self.平仓()
time.sleep(1)
for _ in range(10):
if self.get_position_status() and self.start == 0:
break
time.sleep(1)
if self.start == 0:
self.entry_price = None
self.best_price = None
self.is_wick_position = False
logger.success("平仓成功")
return True
logger.warning("平仓后仍有持仓")
return False
def execute_trade(self, signal, size=None, is_wick=False):
"""
执行交易。signal = (type, trigger_price) 或 (type, trigger_price, size, is_wick)。
size 不传或 None 时使用 default_order_size开仓成功后设置 self.is_wick_position = is_wick。
"""
signal_type = signal[0]
trigger_price = signal[1]
if len(signal) >= 4:
size = signal[2]
is_wick = signal[3]
else:
size = size if size is not None else self.default_order_size
if signal_type == 'long':
# 开多前先确认无持仓
@@ -404,12 +557,13 @@ class BitmartFuturesTransaction:
logger.warning(f"开多前发现已有持仓 (方向: {self.start}),放弃开仓避免双向持仓")
return False
logger.info(f"确认无持仓,执行开多")
logger.info(f"确认无持仓,执行开多 (size={size}, is_wick={is_wick})")
self.开单(marketPriceLongOrder=1, size=size)
time.sleep(3) # 等待订单执行
# 验证开仓是否成功
time.sleep(3)
if self.verify_position_direction(1):
self.is_wick_position = is_wick
self.entry_price = self.open_avg_price
self.best_price = self.open_avg_price
logger.success("开多成功")
return True
else:
@@ -426,12 +580,13 @@ class BitmartFuturesTransaction:
logger.warning(f"开空前发现已有持仓 (方向: {self.start}),放弃开仓避免双向持仓")
return False
logger.info(f"确认无持仓,执行开空")
logger.info(f"确认无持仓,执行开空 (size={size}, is_wick={is_wick})")
self.开单(marketPriceLongOrder=-1, size=size)
time.sleep(3) # 等待订单执行
# 验证开仓是否成功
time.sleep(3)
if self.verify_position_direction(-1):
self.is_wick_position = is_wick
self.entry_price = self.open_avg_price
self.best_price = self.open_avg_price
logger.success("开空成功")
return True
else:
@@ -456,6 +611,9 @@ class BitmartFuturesTransaction:
time.sleep(3)
if self.verify_position_direction(1):
self.is_wick_position = False
self.entry_price = self.open_avg_price
self.best_price = self.open_avg_price
logger.success("反手做多成功")
time.sleep(20)
return True
@@ -480,6 +638,9 @@ class BitmartFuturesTransaction:
time.sleep(3)
if self.verify_position_direction(-1):
self.is_wick_position = False
self.entry_price = self.open_avg_price
self.best_price = self.open_avg_price
logger.success("反手做空成功")
time.sleep(20)
return True
@@ -490,11 +651,10 @@ class BitmartFuturesTransaction:
return False
def action(self):
"""主循环"""
"""主循环:优先级 1 止损 -> 2 止盈 -> 3 反手 -> 4 新开仓"""
logger.info("开始运行三分之一策略交易...")
logger.info("开始运行三分之一 + 五分钟 + 反手(影线 ATR 自适应 · 趋势/震荡双模式 · 高胜率版)")
# 启动时设置全仓高杠杆
if not self.set_leverage():
logger.error("杠杆设置失败,程序继续运行但可能下单失败")
return
@@ -504,7 +664,6 @@ class BitmartFuturesTransaction:
while True:
if page_start:
# 打开浏览器
for i in range(5):
if self.openBrowser():
logger.info("浏览器打开成功")
@@ -512,59 +671,92 @@ class BitmartFuturesTransaction:
else:
self.ding("打开浏览器失败!", error=True)
return
# 进入交易页面
self.page.get("https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT")
self.click_safe('x://button[normalize-space(text()) ="市价"]')
self.page.ele('x://*[@id="size_0"]').input(vals=25, clear=True)
self.page.ele('x://*[@id="size_0"]').input(vals=self.default_order_size, clear=True)
page_start = False
try:
# 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
# 记录进入新的K线
current_kline_time = current_kline['id']
if self.last_kline_time != current_kline_time:
self.last_kline_time = current_kline_time
if self.mode_lock_remaining > 0:
self.mode_lock_remaining -= 1
logger.info(f"进入新K线: {current_kline_time}")
# 2. 获取当前价格
current_price = self.get_current_price()
if not current_price:
logger.warning("获取价格失败,等待重试...")
time.sleep(2)
continue
# 3. 每次循环都通过SDK获取真实持仓状态避免状态不同步导致双向持仓
if not self.get_position_status():
logger.warning("获取持仓状态失败,等待重试...")
time.sleep(2)
continue
logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)")
# 批量 K 线用于 ATR 与模式判定
batch_count = ATR_LENGTH + MODE_LOOKBACK + 2
klines_batch = self.get_klines_batch(batch_count)
atr = self.compute_atr(klines_batch, ATR_LENGTH) if len(klines_batch) >= ATR_LENGTH + 1 else None
if len(klines_batch) >= MODE_LOOKBACK:
self.detect_mode(klines_batch)
# 4. 检查信号
signal = self.check_signal(current_price, prev_kline, current_kline)
third = self.compute_third_prices(prev_kline, current_kline)
# 5. 有信号则执行交易
if signal:
trade_success = self.execute_trade(signal)
if trade_success:
logger.success(f"交易执行完成: {signal[0]}, 当前持仓状态: {self.start}")
# 持仓时更新 best_price多=最高价,空=最低价)
if self.start == 1 and self.best_price is not None:
self.best_price = max(self.best_price, current_price)
elif self.start == -1 and self.best_price is not None:
self.best_price = min(self.best_price, current_price)
logger.debug(f"持仓: {self.start}, 模式: {self.mode}, ATR: {f'{atr:.4f}' if atr else 'N/A'}")
acted = False
# 1. 止损(仅影线单)
if self.start != 0:
sl_action = self.check_sl(current_price, prev_kline, atr)
if sl_action == 'close':
self.execute_close()
acted = True
page_start = True
else:
logger.warning(f"交易执行失败或被阻止: {signal[0]}")
# 短暂等待后继续循环同一根K线遇到信号就操作
# 2. 止盈
if not acted and self.start != 0:
tp_action = self.check_tp(current_price, atr)
if tp_action == 'close':
self.execute_close()
acted = True
page_start = True
# 3. 反手(仅趋势模式)
if not acted and self.start != 0:
rev_signal = self.check_reversal(current_price, prev_kline, atr, third)
if rev_signal:
if self.execute_trade(rev_signal):
acted = True
page_start = True
else:
logger.warning(f"反手执行失败: {rev_signal[0]}")
# 4. 新开仓(趋势=实体突破,震荡=影线回撤)
if not acted and self.start == 0:
open_signal = self.check_open(current_price, prev_kline, current_kline, atr, third)
if open_signal:
if self.execute_trade(open_signal):
acted = True
page_start = True
else:
logger.warning(f"开仓执行失败: {open_signal[0]}")
time.sleep(0.1)
if page_start:
self.page.close()
time.sleep(5)