haha
This commit is contained in:
458
bitmart/交易.py
458
bitmart/交易.py
@@ -1,5 +1,6 @@
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from tqdm import tqdm
|
||||
@@ -15,17 +16,7 @@ PRECOMPUTED_TRADE_PARAMS = {
|
||||
# 你已经算好参数时,把 enabled 改成 True,并在下面填入你的值。
|
||||
# 直接运行本文件即可生效,不需要任何命令行参数。
|
||||
"enabled": False,
|
||||
"params": {
|
||||
# 示例(按需修改):
|
||||
# "use_atr_dynamic_threshold": True,
|
||||
# "atr_length": 14,
|
||||
# "breakout_buffer_atr_mult": 0.12,
|
||||
# "shadow_threshold_atr_mult": 0.18,
|
||||
# "stop_loss_atr_mult": 0.45,
|
||||
# "take_profit_atr_mult": 0.95,
|
||||
# "trailing_start_atr_mult": 0.6,
|
||||
# "trailing_backoff_atr_mult": 0.3,
|
||||
}
|
||||
"params": {}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,28 +65,17 @@ class BitmartFuturesTransaction:
|
||||
self.current_open = None # 当前K线开盘价
|
||||
|
||||
# 策略优化参数
|
||||
self.min_prev_entity_pct = 0.1 # 上一根K线实体最小百分比(%)
|
||||
self.breakout_buffer_pct = 0.03 # 突破缓冲百分比(%),过滤假突破
|
||||
self.shadow_threshold_pct = 0.15 # 上下影线触发反手的最小阈值(%)
|
||||
self.min_prev_entity_pct = 0.1 # 上一根K线实体涨幅(%),仅当实体涨幅 > 此值才用作“上一根”
|
||||
|
||||
# ATR 动态阈值(启用后:动态值与固定值取较大者)
|
||||
self.use_atr_dynamic_threshold = True
|
||||
self.atr_length = 14
|
||||
self.current_atr = None
|
||||
self.breakout_buffer_atr_mult = 0.12
|
||||
self.shadow_threshold_atr_mult = 0.18
|
||||
|
||||
# 风控参数
|
||||
self.stop_loss_pct = 0.35 # 固定止损(%)
|
||||
self.take_profit_pct = 0.8 # 固定止盈(%)
|
||||
self.trailing_start_pct = 0.5 # 浮盈达到该值后启动移动止盈(%)
|
||||
self.trailing_backoff_pct = 0.25 # 从最优价回撤达到该值触发移动止盈(%)
|
||||
self.stop_loss_atr_mult = 0.45
|
||||
self.take_profit_atr_mult = 0.95
|
||||
self.trailing_start_atr_mult = 0.6
|
||||
self.trailing_backoff_atr_mult = 0.3
|
||||
self.max_favorable_price = None # 多仓期间的最优价格
|
||||
self.min_favorable_price = None # 空仓期间的最优价格
|
||||
# 多仓止盈:基于当前K线开盘价,涨 0.4% 后回调 0.3% 平仓,涨回 0.2% 同向开多
|
||||
self.take_profit_rise_pct = 0.4 # 触发止盈的涨幅(%)
|
||||
self.take_profit_retrace_pct = 0.3 # 回调此幅度(%)则平仓
|
||||
self.reentry_rise_pct = 0.2 # 平仓后涨回此幅度(%)则开多
|
||||
self.take_profit_triggered_kline_id = None # 本K线内是否已出现过“涨 0.4%”
|
||||
self.take_profit_reentry_threshold = None # 止盈平仓后,价格 >= 此值则开多(同向)
|
||||
# 空仓止盈:跌 0.4% 后反弹 0.3% 平空,再跌到 0.2% 同向开空(复用上面三个百分比)
|
||||
self.take_profit_triggered_kline_id_short = None # 本K线内是否已出现过“跌 0.4%”
|
||||
self.take_profit_reentry_threshold_short = None # 止盈平空后,价格 <= 此值则开空(同向)
|
||||
|
||||
self.optimized_params_file = Path(__file__).resolve().parent / "atr_best_params.json"
|
||||
self.apply_precomputed_params()
|
||||
@@ -125,7 +105,7 @@ class BitmartFuturesTransaction:
|
||||
})
|
||||
formatted.sort(key=lambda x: x['id'])
|
||||
|
||||
# 返回最近多根K线列表,供主循环按「实体>0.1%」向前选取上一根
|
||||
# 返回最近多根K线列表,供主循环按「实体涨幅>0.1%」向前选取上一根
|
||||
if len(formatted) >= 2:
|
||||
return formatted
|
||||
return None
|
||||
@@ -179,7 +159,6 @@ class BitmartFuturesTransaction:
|
||||
self.open_avg_price = None
|
||||
self.current_amount = None
|
||||
self.unrealized_pnl = None
|
||||
self.reset_trailing_state()
|
||||
return True
|
||||
pos = positions[0]
|
||||
self.start = 1 if pos['position_type'] == 1 else -1
|
||||
@@ -285,6 +264,11 @@ class BitmartFuturesTransaction:
|
||||
else:
|
||||
logger.info(text)
|
||||
|
||||
def _log_take_profit_action(self, operation: str, reason: str):
|
||||
"""止盈相关操作日志:时间、操作、原因"""
|
||||
time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
logger.info(f"[止盈日志] 时间={time_str} | 操作={operation} | 原因={reason}")
|
||||
|
||||
def load_optimized_params(self):
|
||||
"""从本地优化结果文件加载参数(可选)。"""
|
||||
try:
|
||||
@@ -298,23 +282,7 @@ class BitmartFuturesTransaction:
|
||||
logger.info(f"检测到优化参数文件但 apply_live != true,跳过加载: {self.optimized_params_file}")
|
||||
return
|
||||
params = data.get("params_for_trade_py", data)
|
||||
allow_keys = {
|
||||
"min_prev_entity_pct",
|
||||
"breakout_buffer_pct",
|
||||
"shadow_threshold_pct",
|
||||
"stop_loss_pct",
|
||||
"take_profit_pct",
|
||||
"trailing_start_pct",
|
||||
"trailing_backoff_pct",
|
||||
"use_atr_dynamic_threshold",
|
||||
"atr_length",
|
||||
"breakout_buffer_atr_mult",
|
||||
"shadow_threshold_atr_mult",
|
||||
"stop_loss_atr_mult",
|
||||
"take_profit_atr_mult",
|
||||
"trailing_start_atr_mult",
|
||||
"trailing_backoff_atr_mult",
|
||||
}
|
||||
allow_keys = {"min_prev_entity_pct"}
|
||||
applied = []
|
||||
for key, val in params.items():
|
||||
if key in allow_keys and hasattr(self, key):
|
||||
@@ -339,23 +307,7 @@ class BitmartFuturesTransaction:
|
||||
logger.warning("PRECOMPUTED_TRADE_PARAMS.params 不是字典,忽略")
|
||||
return
|
||||
|
||||
allow_keys = {
|
||||
"min_prev_entity_pct",
|
||||
"breakout_buffer_pct",
|
||||
"shadow_threshold_pct",
|
||||
"stop_loss_pct",
|
||||
"take_profit_pct",
|
||||
"trailing_start_pct",
|
||||
"trailing_backoff_pct",
|
||||
"use_atr_dynamic_threshold",
|
||||
"atr_length",
|
||||
"breakout_buffer_atr_mult",
|
||||
"shadow_threshold_atr_mult",
|
||||
"stop_loss_atr_mult",
|
||||
"take_profit_atr_mult",
|
||||
"trailing_start_atr_mult",
|
||||
"trailing_backoff_atr_mult",
|
||||
}
|
||||
allow_keys = {"min_prev_entity_pct"}
|
||||
applied = []
|
||||
for key, val in params.items():
|
||||
if key in allow_keys and hasattr(self, key):
|
||||
@@ -365,192 +317,10 @@ class BitmartFuturesTransaction:
|
||||
except Exception as e:
|
||||
logger.warning(f"应用文件内预计算参数失败,将继续使用默认参数: {e}")
|
||||
|
||||
def compute_atr(self, klines, length=None):
|
||||
"""
|
||||
计算 ATR(SMA 版本)
|
||||
klines 需按时间升序,且每根含 high/low/close
|
||||
"""
|
||||
length = self.atr_length if length is None else length
|
||||
if not klines or len(klines) < length + 1:
|
||||
return None
|
||||
tr_list = []
|
||||
for i in range(1, len(klines)):
|
||||
high = klines[i]['high']
|
||||
low = 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
|
||||
return sum(tr_list[-length:]) / length
|
||||
|
||||
def resolve_dynamic_distance(self, base_price, fixed_pct, atr_value, atr_mult):
|
||||
"""
|
||||
把阈值统一换算为价格距离:
|
||||
- 固定阈值: base_price * fixed_pct
|
||||
- ATR 阈值: atr_value * atr_mult
|
||||
启用 ATR 后取较大者,避免在高波动时阈值过窄。
|
||||
"""
|
||||
fixed_distance = base_price * fixed_pct / 100
|
||||
if self.use_atr_dynamic_threshold and atr_value and atr_value > 0:
|
||||
return max(fixed_distance, atr_value * atr_mult)
|
||||
return fixed_distance
|
||||
|
||||
def get_upper_shadow_abs(self, kline):
|
||||
"""上影线绝对长度"""
|
||||
return max(0.0, kline['high'] - max(kline['open'], kline['close']))
|
||||
|
||||
def get_lower_shadow_abs(self, kline):
|
||||
"""下影线绝对长度"""
|
||||
return max(0.0, min(kline['open'], kline['close']) - kline['low'])
|
||||
|
||||
def get_shadow_threshold_distance(self, kline, atr_value, side):
|
||||
"""
|
||||
影线触发阈值(价格距离)
|
||||
side: 'upper' / 'lower'
|
||||
"""
|
||||
if side == 'upper':
|
||||
base_price = max(kline['open'], kline['close'])
|
||||
else:
|
||||
base_price = min(kline['open'], kline['close'])
|
||||
return self.resolve_dynamic_distance(
|
||||
base_price=base_price,
|
||||
fixed_pct=self.shadow_threshold_pct,
|
||||
atr_value=atr_value,
|
||||
atr_mult=self.shadow_threshold_atr_mult
|
||||
)
|
||||
|
||||
def reset_trailing_state(self):
|
||||
"""重置移动止盈状态"""
|
||||
self.max_favorable_price = None
|
||||
self.min_favorable_price = None
|
||||
|
||||
def set_trailing_anchor_by_position(self):
|
||||
"""根据当前持仓初始化移动止盈锚点"""
|
||||
if self.start == 1 and self.open_avg_price:
|
||||
self.max_favorable_price = self.open_avg_price
|
||||
self.min_favorable_price = None
|
||||
elif self.start == -1 and self.open_avg_price:
|
||||
self.min_favorable_price = self.open_avg_price
|
||||
self.max_favorable_price = None
|
||||
else:
|
||||
self.reset_trailing_state()
|
||||
|
||||
def check_risk_exit(self, current_price, atr_value=None):
|
||||
"""
|
||||
风控退出检查:
|
||||
1) 固定止损
|
||||
2) 固定止盈
|
||||
3) 移动止盈
|
||||
返回: (reason, detail) / None
|
||||
"""
|
||||
if self.start == 0 or not self.open_avg_price:
|
||||
self.reset_trailing_state()
|
||||
return None
|
||||
|
||||
sl_distance = self.resolve_dynamic_distance(
|
||||
base_price=self.open_avg_price,
|
||||
fixed_pct=self.stop_loss_pct,
|
||||
atr_value=atr_value,
|
||||
atr_mult=self.stop_loss_atr_mult
|
||||
)
|
||||
tp_distance = self.resolve_dynamic_distance(
|
||||
base_price=self.open_avg_price,
|
||||
fixed_pct=self.take_profit_pct,
|
||||
atr_value=atr_value,
|
||||
atr_mult=self.take_profit_atr_mult
|
||||
)
|
||||
trail_start_distance = self.resolve_dynamic_distance(
|
||||
base_price=self.open_avg_price,
|
||||
fixed_pct=self.trailing_start_pct,
|
||||
atr_value=atr_value,
|
||||
atr_mult=self.trailing_start_atr_mult
|
||||
)
|
||||
trail_backoff_distance = self.resolve_dynamic_distance(
|
||||
base_price=self.open_avg_price,
|
||||
fixed_pct=self.trailing_backoff_pct,
|
||||
atr_value=atr_value,
|
||||
atr_mult=self.trailing_backoff_atr_mult
|
||||
)
|
||||
|
||||
# 多仓
|
||||
if self.start == 1:
|
||||
profit_distance = current_price - self.open_avg_price
|
||||
loss_distance = self.open_avg_price - current_price
|
||||
if self.max_favorable_price is None:
|
||||
self.max_favorable_price = max(self.open_avg_price, current_price)
|
||||
else:
|
||||
self.max_favorable_price = max(self.max_favorable_price, current_price)
|
||||
|
||||
if loss_distance >= sl_distance:
|
||||
return (
|
||||
'stop_loss',
|
||||
f"多仓回撤 {loss_distance:.3f} >= 止损阈值 {sl_distance:.3f}"
|
||||
)
|
||||
if profit_distance >= tp_distance:
|
||||
return (
|
||||
'take_profit',
|
||||
f"多仓盈利 {profit_distance:.3f} >= 止盈阈值 {tp_distance:.3f}"
|
||||
)
|
||||
|
||||
if profit_distance >= trail_start_distance and self.max_favorable_price:
|
||||
retrace_distance = self.max_favorable_price - current_price
|
||||
if retrace_distance >= trail_backoff_distance:
|
||||
return (
|
||||
'trailing_stop',
|
||||
f"多仓回撤 {retrace_distance:.3f} >= 移动回撤阈值 {trail_backoff_distance:.3f}"
|
||||
)
|
||||
|
||||
# 空仓
|
||||
elif self.start == -1:
|
||||
profit_distance = self.open_avg_price - current_price
|
||||
loss_distance = current_price - self.open_avg_price
|
||||
if self.min_favorable_price is None:
|
||||
self.min_favorable_price = min(self.open_avg_price, current_price)
|
||||
else:
|
||||
self.min_favorable_price = min(self.min_favorable_price, current_price)
|
||||
|
||||
if loss_distance >= sl_distance:
|
||||
return (
|
||||
'stop_loss',
|
||||
f"空仓回撤 {loss_distance:.3f} >= 止损阈值 {sl_distance:.3f}"
|
||||
)
|
||||
if profit_distance >= tp_distance:
|
||||
return (
|
||||
'take_profit',
|
||||
f"空仓盈利 {profit_distance:.3f} >= 止盈阈值 {tp_distance:.3f}"
|
||||
)
|
||||
|
||||
if profit_distance >= trail_start_distance and self.min_favorable_price:
|
||||
retrace_distance = current_price - self.min_favorable_price
|
||||
if retrace_distance >= trail_backoff_distance:
|
||||
return (
|
||||
'trailing_stop',
|
||||
f"空仓回撤 {retrace_distance:.3f} >= 移动回撤阈值 {trail_backoff_distance:.3f}"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def calculate_entity(self, kline):
|
||||
"""计算K线实体大小(绝对值)"""
|
||||
return abs(kline['close'] - kline['open'])
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def get_entity_edge(self, kline):
|
||||
"""获取K线实体边(收盘价或开盘价,取决于是阳线还是阴线)"""
|
||||
# 阳线(收盘>开盘):实体上边=收盘价,实体下边=开盘价
|
||||
@@ -560,12 +330,12 @@ class BitmartFuturesTransaction:
|
||||
'lower': min(kline['open'], kline['close']) # 实体下边
|
||||
}
|
||||
|
||||
def check_signal(self, current_price, prev_kline, current_kline, atr_value=None):
|
||||
def check_signal(self, current_price, prev_kline, current_kline):
|
||||
"""
|
||||
检查交易信号
|
||||
返回: ('long', trigger_price) / ('short', trigger_price) / None
|
||||
"""
|
||||
# 计算上一根K线实体(主循环已保证所选 prev 实体 > 0.1%)
|
||||
# 计算上一根K线实体(主循环已保证所选 prev 实体涨幅 > 0.1%)
|
||||
prev_entity = self.calculate_entity(prev_kline)
|
||||
|
||||
# 获取上一根K线的实体上下边
|
||||
@@ -597,15 +367,6 @@ class BitmartFuturesTransaction:
|
||||
long_breakout = prev_entity_upper + prev_entity / 3 # 做多突破价 = 实体上边 + 实体/3
|
||||
short_breakout = prev_entity_lower - prev_entity / 3 # 做空突破价 = 实体下边 - 实体/3
|
||||
|
||||
# 突破缓冲:实体比例 + 固定百分比 + ATR 动态阈值,三者取最大值
|
||||
breakout_buffer = max(
|
||||
prev_entity * 0.1,
|
||||
current_price * self.breakout_buffer_pct / 100,
|
||||
(atr_value * self.breakout_buffer_atr_mult) if (self.use_atr_dynamic_threshold and atr_value and atr_value > 0) else 0
|
||||
)
|
||||
long_breakout_effective = long_breakout + breakout_buffer
|
||||
short_breakout_effective = short_breakout - breakout_buffer
|
||||
|
||||
# 上一根阴线 + 当前阳线:做多形态,不按上一根K线上三分之一做空
|
||||
prev_is_bearish = prev_kline['close'] < prev_kline['open']
|
||||
current_is_bullish = current_kline['close'] > current_kline['open']
|
||||
@@ -624,16 +385,6 @@ class BitmartFuturesTransaction:
|
||||
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"突破缓冲: {breakout_buffer:.4f} ({self.breakout_buffer_pct:.3f}%),"
|
||||
f"有效做多突破价: {long_breakout_effective:.2f},有效做空突破价: {short_breakout_effective:.2f}"
|
||||
)
|
||||
if atr_value:
|
||||
logger.info(
|
||||
f"ATR({self.atr_length})={atr_value:.4f}, "
|
||||
f"突破ATR倍数={self.breakout_buffer_atr_mult:.3f}, "
|
||||
f"影线ATR倍数={self.shadow_threshold_atr_mult:.3f}"
|
||||
)
|
||||
if skip_short_by_upper_third:
|
||||
logger.info("上一根阴线+当前阳线(做多形态),不按上三分之一做空")
|
||||
if skip_long_by_lower_third:
|
||||
@@ -641,50 +392,30 @@ class BitmartFuturesTransaction:
|
||||
|
||||
# 无持仓时检查开仓信号
|
||||
if self.start == 0:
|
||||
if current_price >= long_breakout_effective and not skip_long_by_lower_third:
|
||||
if current_price >= long_breakout and not skip_long_by_lower_third:
|
||||
logger.info(
|
||||
f"触发做多信号!价格 {current_price:.2f} >= 有效突破价(上1/3外+缓冲) {long_breakout_effective:.2f}"
|
||||
f"触发做多信号!价格 {current_price:.2f} >= 突破价(上1/3外) {long_breakout:.2f}"
|
||||
)
|
||||
return ('long', long_breakout_effective)
|
||||
elif current_price <= short_breakout_effective and not skip_short_by_upper_third:
|
||||
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_effective:.2f}"
|
||||
f"触发做空信号!价格 {current_price:.2f} <= 突破价(下1/3外) {short_breakout:.2f}"
|
||||
)
|
||||
return ('short', short_breakout_effective)
|
||||
return ('short', short_breakout)
|
||||
|
||||
# 持仓时检查反手信号
|
||||
elif self.start == 1: # 持多仓
|
||||
# 反手条件1: 价格跌到上一根K线的上三分之一处(做空触发价);上一根阴线+当前阳线做多时跳过
|
||||
# 反手条件: 价格跌到上一根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线上影线涨幅超过阈值,当前跌到上一根实体下边
|
||||
upper_shadow_pct = self.calculate_upper_shadow(prev_kline)
|
||||
upper_shadow_abs = self.get_upper_shadow_abs(prev_kline)
|
||||
upper_shadow_threshold = self.get_shadow_threshold_distance(prev_kline, atr_value=atr_value, side='upper')
|
||||
if upper_shadow_abs > upper_shadow_threshold and current_price <= prev_entity_lower:
|
||||
logger.info(
|
||||
f"持多反手做空!上阴线涨幅 {upper_shadow_pct:.4f}%,上影线长度 {upper_shadow_abs:.4f} > 阈值 {upper_shadow_threshold:.4f},"
|
||||
f"价格 {current_price:.2f} <= 实体下边 {prev_entity_lower:.2f}")
|
||||
return ('reverse_short', prev_entity_lower)
|
||||
|
||||
elif self.start == -1: # 持空仓
|
||||
# 反手条件1: 价格涨到上一根K线的下三分之一处(做多触发价);上一根阳线+当前阴线做空时跳过
|
||||
# 反手条件: 价格涨到上一根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线下影线跌幅超过阈值,当前涨到上一根实体上边
|
||||
lower_shadow_pct = self.calculate_lower_shadow(prev_kline)
|
||||
lower_shadow_abs = self.get_lower_shadow_abs(prev_kline)
|
||||
lower_shadow_threshold = self.get_shadow_threshold_distance(prev_kline, atr_value=atr_value, side='lower')
|
||||
if lower_shadow_abs > lower_shadow_threshold and current_price >= prev_entity_upper:
|
||||
logger.info(
|
||||
f"持空反手做多!下阴线跌幅 {lower_shadow_pct:.4f}%,下影线长度 {lower_shadow_abs:.4f} > 阈值 {lower_shadow_threshold:.4f},"
|
||||
f"价格 {current_price:.2f} >= 实体上边 {prev_entity_upper:.2f}")
|
||||
return ('reverse_long', prev_entity_upper)
|
||||
|
||||
return None
|
||||
|
||||
def can_open(self, current_kline_id):
|
||||
@@ -770,7 +501,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.set_trailing_anchor_by_position()
|
||||
logger.success("开多成功")
|
||||
return True
|
||||
else:
|
||||
@@ -795,7 +525,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.set_trailing_anchor_by_position()
|
||||
logger.success("开空成功")
|
||||
return True
|
||||
else:
|
||||
@@ -822,7 +551,6 @@ class BitmartFuturesTransaction:
|
||||
if self.verify_position_direction(1):
|
||||
logger.success("反手做多成功")
|
||||
self.last_reverse_time = time.time()
|
||||
self.set_trailing_anchor_by_position()
|
||||
time.sleep(20)
|
||||
return True
|
||||
else:
|
||||
@@ -848,7 +576,6 @@ class BitmartFuturesTransaction:
|
||||
if self.verify_position_direction(-1):
|
||||
logger.success("反手做空成功")
|
||||
self.last_reverse_time = time.time()
|
||||
self.set_trailing_anchor_by_position()
|
||||
time.sleep(20)
|
||||
return True
|
||||
else:
|
||||
@@ -890,7 +617,7 @@ class BitmartFuturesTransaction:
|
||||
page_start = False
|
||||
|
||||
try:
|
||||
# 1. 获取K线数据,当前K线=最后一根;上一根=从后往前第一根实体>0.1%的K线
|
||||
# 1. 获取K线数据,当前K线=最后一根;上一根=从后往前第一根实体涨幅>0.1%的K线
|
||||
formatted = self.get_klines()
|
||||
if not formatted or len(formatted) < 2:
|
||||
logger.warning("获取K线失败,等待重试...")
|
||||
@@ -898,10 +625,6 @@ class BitmartFuturesTransaction:
|
||||
continue
|
||||
|
||||
current_kline = formatted[-1]
|
||||
# ATR 使用已完成K线优先(排除当前进行中的K线)
|
||||
atr_source = formatted[:-1] if len(formatted) > self.atr_length + 1 else formatted
|
||||
atr_value = self.compute_atr(atr_source, self.atr_length)
|
||||
self.current_atr = atr_value
|
||||
prev_kline = None
|
||||
for i in range(len(formatted) - 2, -1, -1):
|
||||
k = formatted[i]
|
||||
@@ -911,7 +634,7 @@ class BitmartFuturesTransaction:
|
||||
prev_kline = k
|
||||
break
|
||||
if prev_kline is None:
|
||||
logger.info(f"没有实体>{self.min_prev_entity_pct:.3f}%的上一根K线,跳过信号检测")
|
||||
logger.info(f"没有实体涨幅>{self.min_prev_entity_pct:.3f}%的上一根K线,跳过信号检测")
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
@@ -920,6 +643,11 @@ class BitmartFuturesTransaction:
|
||||
if self.last_kline_time != current_kline_time:
|
||||
self.last_kline_time = current_kline_time
|
||||
logger.info(f"进入新K线: {current_kline_time}")
|
||||
# 新K线内重新判断多/空止盈触发
|
||||
if self.start == 1:
|
||||
self.take_profit_triggered_kline_id = None
|
||||
if self.start == -1:
|
||||
self.take_profit_triggered_kline_id_short = None
|
||||
|
||||
# 2. 获取当前价格
|
||||
current_price = self.get_current_price()
|
||||
@@ -935,33 +663,95 @@ class BitmartFuturesTransaction:
|
||||
continue
|
||||
|
||||
logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)")
|
||||
logger.debug(f"当前 ATR({self.atr_length}): {f'{atr_value:.4f}' if atr_value else 'N/A'}")
|
||||
|
||||
# 4. 持仓中优先检查风控退出(止损/止盈/移动止盈)
|
||||
risk_exit = self.check_risk_exit(current_price, atr_value=atr_value)
|
||||
if risk_exit:
|
||||
reason, detail = risk_exit
|
||||
logger.warning(f"触发风控退出: {reason},{detail}")
|
||||
self.平仓()
|
||||
time.sleep(1)
|
||||
if self.verify_no_position(max_retries=8, retry_interval=1):
|
||||
self.last_open_time = time.time()
|
||||
self.last_open_kline_id = current_kline_time
|
||||
self.reset_trailing_state()
|
||||
logger.success("风控平仓成功")
|
||||
kline_open = current_kline['open']
|
||||
signal = None
|
||||
|
||||
# 3.5 多仓止盈:当前K线涨 0.4% 后回调 0.3% 则平仓
|
||||
if self.start == 1:
|
||||
rise_threshold = kline_open * (1 + self.take_profit_rise_pct / 100)
|
||||
retrace_close_threshold = kline_open * (1 + (self.take_profit_rise_pct - self.take_profit_retrace_pct) / 100)
|
||||
if current_price >= rise_threshold:
|
||||
self.take_profit_triggered_kline_id = current_kline_time
|
||||
if (
|
||||
self.take_profit_triggered_kline_id == current_kline_time
|
||||
and current_price <= retrace_close_threshold
|
||||
):
|
||||
reason = (
|
||||
f"当前K线开盘价={kline_open:.2f},曾涨至≥{rise_threshold:.2f}(+{self.take_profit_rise_pct}%),"
|
||||
f"现价{current_price:.2f}已回调至≤{retrace_close_threshold:.2f}(+{self.take_profit_rise_pct - self.take_profit_retrace_pct}%),按规则止盈平多"
|
||||
)
|
||||
self._log_take_profit_action("止盈平多仓", reason)
|
||||
self.平仓()
|
||||
self.take_profit_reentry_threshold = kline_open * (1 + self.reentry_rise_pct / 100)
|
||||
self.take_profit_triggered_kline_id = None
|
||||
page_start = True
|
||||
if self.page:
|
||||
try:
|
||||
self.page.close()
|
||||
time.sleep(5)
|
||||
except Exception:
|
||||
pass
|
||||
continue
|
||||
logger.error("风控平仓后仍有持仓,等待下一轮重试")
|
||||
continue
|
||||
|
||||
# 5. 检查信号
|
||||
signal = self.check_signal(current_price, prev_kline, current_kline, atr_value=atr_value)
|
||||
time.sleep(1)
|
||||
else:
|
||||
signal = self.check_signal(current_price, prev_kline, current_kline)
|
||||
# 3.6 止盈平多后:价格涨回 0.2% 则同向开多
|
||||
elif self.start == 0 and self.take_profit_reentry_threshold is not None:
|
||||
if current_price >= self.take_profit_reentry_threshold:
|
||||
reason = (
|
||||
f"止盈平仓后价格涨回≥{self.take_profit_reentry_threshold:.2f}(相对当根K线开盘+{self.reentry_rise_pct}%),按规则同向开多"
|
||||
)
|
||||
self._log_take_profit_action("止盈后同向开多", reason)
|
||||
self.开单(marketPriceLongOrder=1, size=self.default_order_size)
|
||||
time.sleep(3)
|
||||
if self.verify_position_direction(1):
|
||||
self.last_open_time = time.time()
|
||||
self.last_open_kline_id = current_kline_time
|
||||
logger.success("止盈后再开多成功")
|
||||
page_start = True
|
||||
else:
|
||||
logger.error("止盈后再开多验证失败")
|
||||
self.take_profit_reentry_threshold = None
|
||||
else:
|
||||
signal = self.check_signal(current_price, prev_kline, current_kline)
|
||||
# 3.7 空仓止盈:当前K线跌 0.4% 后反弹 0.3% 则平空
|
||||
elif self.start == -1:
|
||||
drop_threshold = kline_open * (1 - self.take_profit_rise_pct / 100)
|
||||
retrace_close_threshold_short = kline_open * (1 - (self.take_profit_rise_pct - self.take_profit_retrace_pct) / 100)
|
||||
if current_price <= drop_threshold:
|
||||
self.take_profit_triggered_kline_id_short = current_kline_time
|
||||
if (
|
||||
self.take_profit_triggered_kline_id_short == current_kline_time
|
||||
and current_price >= retrace_close_threshold_short
|
||||
):
|
||||
reason = (
|
||||
f"当前K线开盘价={kline_open:.2f},曾跌至≤{drop_threshold:.2f}(-{self.take_profit_rise_pct}%),"
|
||||
f"现价{current_price:.2f}已反弹至≥{retrace_close_threshold_short:.2f}(-{self.take_profit_rise_pct - self.take_profit_retrace_pct}%),按规则止盈平空"
|
||||
)
|
||||
self._log_take_profit_action("止盈平空仓", reason)
|
||||
self.平仓()
|
||||
self.take_profit_reentry_threshold_short = kline_open * (1 - self.reentry_rise_pct / 100)
|
||||
self.take_profit_triggered_kline_id_short = None
|
||||
page_start = True
|
||||
time.sleep(1)
|
||||
else:
|
||||
signal = self.check_signal(current_price, prev_kline, current_kline)
|
||||
# 3.8 止盈平空后:价格再跌到 0.2% 则同向开空
|
||||
elif self.start == 0 and self.take_profit_reentry_threshold_short is not None:
|
||||
if current_price <= self.take_profit_reentry_threshold_short:
|
||||
reason = (
|
||||
f"止盈平空后价格再跌至≤{self.take_profit_reentry_threshold_short:.2f}(相对当根K线开盘-{self.reentry_rise_pct}%),按规则同向开空"
|
||||
)
|
||||
self._log_take_profit_action("止盈后同向开空", reason)
|
||||
self.开单(marketPriceLongOrder=-1, size=self.default_order_size)
|
||||
time.sleep(3)
|
||||
if self.verify_position_direction(-1):
|
||||
self.last_open_time = time.time()
|
||||
self.last_open_kline_id = current_kline_time
|
||||
logger.success("止盈后再开空成功")
|
||||
page_start = True
|
||||
else:
|
||||
logger.error("止盈后再开空验证失败")
|
||||
self.take_profit_reentry_threshold_short = None
|
||||
else:
|
||||
signal = self.check_signal(current_price, prev_kline, current_kline)
|
||||
else:
|
||||
# 4. 检查信号
|
||||
signal = self.check_signal(current_price, prev_kline, current_kline)
|
||||
|
||||
# 6. 反手过滤:冷却时间
|
||||
if signal and signal[0].startswith('reverse_'):
|
||||
|
||||
Reference in New Issue
Block a user