优化前改动

This commit is contained in:
ddrwode
2026-02-06 13:42:39 +08:00
parent cfcf8f3136
commit 275b1319f8
3 changed files with 162 additions and 2 deletions

View File

@@ -95,7 +95,30 @@ pip3 install websocket-client
- `BITMART_WS_TOPIC=...`:自定义订阅 topic
- `BITMART_WS_PRICE_TTL=2.0`:价格新鲜度阈值(秒)
## 5. 费用模型说明
## 5. 动态止盈止损
保守模式已支持按近期波动率自动调整:
- `take_profit_usd`
- `stop_loss_usd`
- `trailing_activation_usd`
- `trailing_distance_usd`
- `break_even_activation_usd`
- `break_even_floor_usd`
触发方式:
- 每进入一根新的 5 分钟 K 线,自动计算近 N 根K线平均振幅trimmed mean
- 用当前波动率和目标波动率比值计算缩放系数,再更新上述参数
可选环境变量:
- `BITMART_DYNAMIC_RISK_ENABLED=1`:开启/关闭动态风控
- `BITMART_DYNAMIC_RISK_WINDOW_BARS=36`波动率窗口5m K线根数
- `BITMART_DYNAMIC_VOL_TARGET_PCT=0.25`:目标波动率(%
- `BITMART_DYNAMIC_SCALE_MIN=0.7`:最小缩放倍数
- `BITMART_DYNAMIC_SCALE_MAX=1.8`:最大缩放倍数
这些参数也可写到 `current_params.json``params` 中。
## 6. 费用模型说明
优化器会按下面公式计入手续费返佣:
@@ -110,7 +133,7 @@ pip3 install websocket-client
- `--raw-fee-rate`
- `--rebate-ratio`
## 6. 重要提示
## 7. 重要提示
- 回测撮合属于简化模型,不等于实盘撮合。
- 参数应周期性重训(例如每天或每周)。

View File

@@ -29,6 +29,11 @@
"open_cooldown_seconds": 300,
"reverse_cooldown_seconds": 300,
"reverse_min_move_pct": 0.15,
"dynamic_risk_enabled": true,
"dynamic_risk_window_bars": 36,
"dynamic_vol_target_pct": 0.25,
"dynamic_scale_min": 0.7,
"dynamic_scale_max": 1.8,
"order_notional_usd": 100.0,
"pnl_per_usd_move": 1.0,
"slippage_pct": 0.01

View File

@@ -93,8 +93,18 @@ class BitmartFuturesTransactionConservative:
self.ws_stop_event = threading.Event()
self.ws_lock = threading.Lock()
# 动态风控:按近期波动率自动调整 TP/SL/移动止损
self.dynamic_risk_enabled = os.getenv("BITMART_DYNAMIC_RISK_ENABLED", "1").strip().lower() not in {"0", "false", "off", "no"}
self.dynamic_risk_window_bars = int(os.getenv("BITMART_DYNAMIC_RISK_WINDOW_BARS", "36")) # 5m * 36 = 3小时
self.dynamic_vol_target_pct = float(os.getenv("BITMART_DYNAMIC_VOL_TARGET_PCT", "0.25")) # 目标波动率(%)
self.dynamic_scale_min = float(os.getenv("BITMART_DYNAMIC_SCALE_MIN", "0.7"))
self.dynamic_scale_max = float(os.getenv("BITMART_DYNAMIC_SCALE_MAX", "1.8"))
self.last_dynamic_risk_kline_id = None
self.base_risk_params = {}
# 启动时尝试读取动态参数(可由优化脚本自动生成)
self.load_runtime_params()
self.capture_base_risk_params()
def load_runtime_params(self):
"""
@@ -124,6 +134,11 @@ class BitmartFuturesTransactionConservative:
"open_cooldown_seconds",
"reverse_cooldown_seconds",
"reverse_min_move_pct",
"dynamic_risk_enabled",
"dynamic_risk_window_bars",
"dynamic_vol_target_pct",
"dynamic_scale_min",
"dynamic_scale_max",
}
try:
@@ -139,6 +154,8 @@ class BitmartFuturesTransactionConservative:
continue
if key == "leverage":
setattr(self, key, str(value))
elif key == "dynamic_risk_enabled":
setattr(self, key, self._to_bool(value))
else:
setattr(self, key, value)
@@ -155,6 +172,111 @@ class BitmartFuturesTransactionConservative:
except Exception as e:
logger.error(f"加载动态参数文件失败: {e} | path={params_path}")
def capture_base_risk_params(self):
"""记录参数基线,动态风控在此基线之上按波动率缩放。"""
self.base_risk_params = {
"take_profit_usd": float(self.take_profit_usd),
"stop_loss_usd": float(self.stop_loss_usd),
"trailing_activation_usd": float(self.trailing_activation_usd),
"trailing_distance_usd": float(self.trailing_distance_usd),
"break_even_activation_usd": float(self.break_even_activation_usd),
"break_even_floor_usd": float(self.break_even_floor_usd),
}
def get_recent_volatility_pct(self, bars=None):
"""
计算近 N 根 5m K线的平均振幅百分比trimmed mean
返回: 波动率百分比,如 0.25 表示 0.25%
"""
bars = bars or self.dynamic_risk_window_bars
bars = max(12, int(bars))
now_ts = int(time.time())
window_seconds = (bars + 10) * 5 * 60
try:
response = self.contractAPI.get_kline(
contract_symbol=self.contract_symbol,
step=5,
start_time=now_ts - window_seconds,
end_time=now_ts,
)[0]
if response.get("code") != 1000:
return None
data = response.get("data", [])
if len(data) < 12:
return None
ranges = []
for k in data[-bars:]:
high = self._safe_float(k.get("high_price"))
low = self._safe_float(k.get("low_price"))
close = self._safe_float(k.get("close_price"))
if high is None or low is None or close is None or close <= 0:
continue
ranges.append((high - low) / close * 100)
if len(ranges) < 12:
return None
ranges.sort()
trim = int(len(ranges) * 0.1)
core = ranges[trim: len(ranges) - trim] if len(ranges) > trim * 2 else ranges
if not core:
return None
return sum(core) / len(core)
except Exception:
return None
def update_dynamic_risk_params(self, current_kline_id):
"""
每根新K线按近期波动率动态更新风险参数避免固定 TP/SL 不适配行情。
"""
if not self.dynamic_risk_enabled:
return
if self.last_dynamic_risk_kline_id == current_kline_id:
return
if not self.base_risk_params:
self.capture_base_risk_params()
if not self.base_risk_params:
return
vol_pct = self.get_recent_volatility_pct(self.dynamic_risk_window_bars)
if vol_pct is None or vol_pct <= 0:
logger.warning("动态风控: 波动率计算失败,继续使用当前风险参数")
self.last_dynamic_risk_kline_id = current_kline_id
return
target = max(self.dynamic_vol_target_pct, 0.01)
scale = vol_pct / target
scale = max(self.dynamic_scale_min, min(self.dynamic_scale_max, scale))
base = self.base_risk_params
self.take_profit_usd = round(base["take_profit_usd"] * scale, 4)
self.stop_loss_usd = round(-abs(base["stop_loss_usd"]) * scale, 4)
self.trailing_activation_usd = round(base["trailing_activation_usd"] * scale, 4)
self.trailing_distance_usd = round(base["trailing_distance_usd"] * scale, 4)
self.break_even_activation_usd = round(base["break_even_activation_usd"] * scale, 4)
self.break_even_floor_usd = round(base["break_even_floor_usd"] * scale, 4)
# 保持参数关系合理,防止逻辑冲突
if self.trailing_activation_usd <= self.break_even_activation_usd:
self.trailing_activation_usd = round(self.break_even_activation_usd + 0.2, 4)
if self.take_profit_usd <= self.trailing_activation_usd:
self.take_profit_usd = round(self.trailing_activation_usd + 0.4, 4)
max_floor = self.break_even_activation_usd * 0.9
if self.break_even_floor_usd > max_floor:
self.break_even_floor_usd = round(max_floor, 4)
if self.trailing_distance_usd >= self.trailing_activation_usd:
self.trailing_distance_usd = round(max(0.1, self.trailing_activation_usd * 0.6), 4)
self.last_dynamic_risk_kline_id = current_kline_id
logger.info(
"动态风控更新: "
f"vol={vol_pct:.4f}% scale={scale:.3f} "
f"TP={self.take_profit_usd} SL={self.stop_loss_usd} "
f"TrailAct={self.trailing_activation_usd} TrailDist={self.trailing_distance_usd} "
f"BEAct={self.break_even_activation_usd} BEFloor={self.break_even_floor_usd}"
)
def _safe_float(self, value):
try:
f = float(value)
@@ -164,6 +286,15 @@ class BitmartFuturesTransactionConservative:
except Exception:
return None
def _to_bool(self, value):
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return bool(value)
if isinstance(value, str):
return value.strip().lower() in {"1", "true", "yes", "y", "on"}
return False
def _extract_price_from_ws_payload(self, payload):
"""
尽量从 WS 消息中提取价格,兼容不同字段结构。
@@ -856,6 +987,7 @@ class BitmartFuturesTransactionConservative:
if self.last_kline_time != current_kline_time:
self.last_kline_time = current_kline_time
logger.info(f"进入新K线: {current_kline_time}")
self.update_dynamic_risk_params(current_kline_time)
# 2. 获取当前价格
current_price = self.get_current_price()