优化前改动
This commit is contained in:
@@ -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. 重要提示
|
||||
|
||||
- 回测撮合属于简化模型,不等于实盘撮合。
|
||||
- 参数应周期性重训(例如每天或每周)。
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user