Prechádzať zdrojové kódy

feat: enforce minimum layers for adaptive grid

helium3@sina.com 2 mesiacov pred
rodič
commit
bdc7e84e28

+ 2 - 1
apps/runner/src/index.ts

@@ -458,7 +458,8 @@ const defaultGridSymbol = cfg.grid?.symbol || primarySymbol;
           minSamples: cfg.grid.adaptive.min_samples,
           maxCadenceMs: cfg.grid.adaptive.max_cadence_ms,
           hedgePendingTimeoutMs: cfg.grid.adaptive.hedge_pending_timeout_ms,
-          postOnlyCushionBps: cfg.grid.adaptive.post_only_cushion_bps ?? 5
+          postOnlyCushionBps: cfg.grid.adaptive.post_only_cushion_bps ?? 5,
+          minLayers: cfg.grid.adaptive.min_layers
         }
       : undefined;
 

+ 2 - 0
config/config.example.yaml

@@ -60,6 +60,8 @@ grid:
     min_step_change_ratio: 0.2
     tick_interval_ms: 60000
     hedge_pending_timeout_ms: 30000
+    post_only_cushion_bps: 5
+    min_layers: 8
 
   # 趋势检测与暂停(M2.5)
   trend_filter:

+ 2 - 1
config/config.yaml

@@ -27,7 +27,7 @@ grid:
   grid_step_bps: 15   # 网格间距 1%(基于波动率调整:BTC 0.8-1.2%, ETH 1.0-1.5%, SOL 1.5-2.5%)
   grid_range_bps: 200  # 网格范围 4%(基于极端波动:BTC 3-5%, ETH 4-6%, SOL 6-10%)
   base_clip_usd: 100   # 单层订单大小(USD)
-  max_layers: 10        # 单边最大层数
+  max_layers: 15        # 单边最大层数
   hedge_threshold_base: 0.12  # 累积 0.3 BTC 触发对冲(批量对冲模式)
   tick_size: 1         # 价格步长
   lot_size: 0.00001    # 最小数量步长
@@ -60,6 +60,7 @@ grid:
     tick_interval_ms: 100000        # 自适应检查间隔(2分钟,降低检查频率)
     hedge_pending_timeout_ms: 30000 # 对冲挂单超过阈值仍未成交则告警
     post_only_cushion_bps: 3        # PostOnly 保护缓冲
+    min_layers: 8                   # 最少层数,步长增大时保持档位数量
 
   # 趋势检测与暂停(M2.5)
   trend_filter:

+ 5 - 0
docs/CONFIG_REFERENCE.md

@@ -99,6 +99,9 @@ grid:
     recenter_cooldown_ms: 300000
     min_step_change_ratio: 0.2
     tick_interval_ms: 60000
+    hedge_pending_timeout_ms: 30000
+    post_only_cushion_bps: 5
+    min_layers: 8
 ```
 
 | 字段 | 取值 / 默认 | 说明 | 热更新 |
@@ -119,6 +122,8 @@ grid:
 | `adaptive.recenter_cooldown_ms` | ≥60_000 | 单次重心重置后的冷却时间 | 支持 |
 | `adaptive.tick_interval_ms` | ≥10_000 | 自适应检查间隔 | 支持 |
 | `adaptive.hedge_pending_timeout_ms` | 10_000–120_000 | 对冲挂单超时阈值 | 支持 |
+| `adaptive.post_only_cushion_bps` | 0–20 | Post-only 保护缓冲,计算挂价时加到盘口价差上 | 支持 |
+| `adaptive.min_layers` | 2–`max_layers` | 目标最少层数,步长放大时自动限制以保持档位 | 支持 |
 
 > 🎯 **推荐起始配置**:参考 `config/grid.example.yaml`,适用于测试网或小额实盘。根据账户规模调整 `base_clip_usd` 与 `hedge_threshold_base`,并保持 `max_base_abs ≥ hedge_threshold_base × 1.5` 以预留缓冲。
 

+ 54 - 4
packages/strategies/src/gridMaker.ts

@@ -97,12 +97,39 @@ export class GridMaker {
     this.logger.info({ mid, config: this.config, currentGridStepBps: this.currentGridStepBps }, 'Initializing grid');
 
     const { gridRangeBps, maxLayers, baseClipUsd } = this.config;
+    const minLayers = this.adaptiveConfig?.minLayers;
+    const minGridStepBps = this.adaptiveConfig?.minGridStepBps ?? 0;
+    const maxGridStepBps = this.adaptiveConfig?.maxGridStepBps ?? Number.POSITIVE_INFINITY;
+
+    if (minLayers && minLayers > 0 && maxLayers < minLayers) {
+      this.logger.warn({
+        configuredMaxLayers: maxLayers,
+        requestedMinLayers: minLayers
+      }, 'Configured maxLayers is lower than adaptive minLayers; min layers target cannot be fully satisfied');
+    }
+
+    if (minLayers && minLayers > 0) {
+      const maxStepForMinLayers = gridRangeBps / minLayers;
+      if (this.currentGridStepBps > maxStepForMinLayers) {
+        const adjustedStep = clamp(maxStepForMinLayers, minGridStepBps, maxGridStepBps);
+        if (adjustedStep < this.currentGridStepBps - 1e-6) {
+          this.logger.info({
+            previousStepBps: this.currentGridStepBps,
+            adjustedStepBps: adjustedStep,
+            minLayers
+          }, 'Reducing grid step to preserve minimum layer count');
+          this.currentGridStepBps = adjustedStep;
+        }
+      }
+    }
+
     const stepRatio = this.currentGridStepBps / 1e4;
     const rangeRatio = gridRangeBps / 1e4;
 
     // 计算实际层数(不超过 maxLayers)
-    const calculatedLayers = Math.floor(rangeRatio / stepRatio);
-    const actualLayers = Math.min(calculatedLayers, maxLayers);
+    const calculatedLayers = Math.floor(rangeRatio / stepRatio + 1e-9);
+    const minLayerTarget = minLayers ? Math.min(minLayers, maxLayers) : 0;
+    const actualLayers = Math.max(Math.min(calculatedLayers, maxLayers), minLayerTarget);
 
     // 计算单层订单大小(base asset)
     const baseSz = baseClipUsd / mid;
@@ -112,7 +139,12 @@ export class GridMaker {
       throw new Error(`Grid base size ${baseSz} rounded to zero with lot size ${this.lotSize}`);
     }
 
-    this.logger.info({ actualLayers, baseSz: normalizedBaseSz, gridStepBps: this.currentGridStepBps }, 'Grid parameters calculated');
+    this.logger.info({
+      actualLayers,
+      baseSz: normalizedBaseSz,
+      gridStepBps: this.currentGridStepBps,
+      minLayerTarget: minLayers
+    }, 'Grid parameters calculated');
 
     // 重置连续失败计数
     this.consecutivePlaceFailures = 0;
@@ -725,7 +757,8 @@ export class GridMaker {
       maxVolatilityBps,
       minGridStepBps,
       maxGridStepBps,
-      minStepChangeRatio
+      minStepChangeRatio,
+      minLayers
     } = this.adaptiveConfig;
 
     const clampedVol = clamp(hourlyVolBps, minVolatilityBps, maxVolatilityBps);
@@ -759,6 +792,22 @@ export class GridMaker {
     let finalTargetStep = Math.max(targetStep, effectiveMinStep);
     finalTargetStep = clamp(finalTargetStep, minGridStepBps, maxGridStepBps);
 
+    if (minLayers && minLayers > 0) {
+      const maxStepForMinLayers = this.config.gridRangeBps / minLayers;
+      if (finalTargetStep > maxStepForMinLayers) {
+        const adjusted = Math.max(minGridStepBps, Math.min(maxStepForMinLayers, maxGridStepBps));
+        if (adjusted < finalTargetStep - 1e-6) {
+          this.logger.info({
+            requestedStepBps: finalTargetStep,
+            adjustedStepBps: adjusted,
+            minLayers,
+            gridRangeBps: this.config.gridRangeBps
+          }, 'Limiting grid step to preserve minimum layer count');
+        }
+        finalTargetStep = adjusted;
+      }
+    }
+
     const changeRatio = Math.abs(finalTargetStep - this.currentGridStepBps) / this.currentGridStepBps;
     if (changeRatio < (minStepChangeRatio ?? 0.2)) {
       this.logger.debug({
@@ -841,6 +890,7 @@ export interface AdaptiveGridConfig {
   maxCadenceMs?: number;
   hedgePendingTimeoutMs?: number;
   postOnlyCushionBps?: number;
+  minLayers?: number;
 }
 
 function clamp(value: number, min: number, max: number): number {