فهرست منبع

feat(MAPatternStrategy): 增强均线聚合度处理与动态保证金调整

- 新增均线聚合度阈值配置,支持动态调整单标的最大持仓保证金
- 引入均线聚合度计算逻辑,优化开仓条件检查,提升交易策略的灵活性与安全性
- 更新初始化函数,添加预留资金比例和金融期货预留资金的配置
- 扩展日志记录,提供均线聚合度相关信息,便于后续分析与调试
maxfeng 1 ماه پیش
والد
کامیت
00ada5da83
1فایلهای تغییر یافته به همراه142 افزوده شده و 16 حذف شده
  1. 142 16
      Lib/future/MAPatternStrategy_v002.py

+ 142 - 16
Lib/future/MAPatternStrategy_v002.py

@@ -9,6 +9,13 @@ import re
 
 MA_ADDITIONAL_HISTORY_DAYS = 30  # 计算均线时额外获取的交易日数量,用于支持更长周期
 
+# 均线聚合度阈值(基于MA5/MA10/MA20),用于动态调整单标的保证金上限
+MA_COMPACTION_THRESHOLDS = {
+    'tight': 0.0045,
+    'balanced': 0.0073,
+    'loose': 0.0116
+}
+
 # 顺势交易策略 v001
 # 基于均线走势(前提条件)+ K线形态(开盘价差、当天价差)的期货交易策略
 #
@@ -47,8 +54,11 @@ def initialize(context):
     set_slippage(StepRelatedSlippage(2))
     
     # 初始化全局变量
-    g.usage_percentage = 0.8  # 最大资金使用比例
+    g.reserve_percentage = 0.2  # 预留资金比例
+    g.financial_reserve_amount = 180000  # 金融期货预留资金
     g.max_margin_per_position = 30000  # 单个标的最大持仓保证金(元)
+    g.base_max_margin_per_position = g.max_margin_per_position  # 记录基础保证金上限,便于动态调整
+    g.ma_compaction_thresholds = MA_COMPACTION_THRESHOLDS.copy()
     
     # 均线策略参数
     g.ma_periods = [5, 10, 20, 30, 60]  # 均线周期(新增MA60)
@@ -99,6 +109,8 @@ def initialize(context):
     log.info(f"  均线跟踪止盈常规偏移: {g.ma_offset_ratio_normal:.1%}")
     log.info(f"  均线跟踪止盈收盘前偏移: {g.ma_offset_ratio_close:.1%}")
     log.info(f"  持仓天数调整阈值: {g.days_for_adjustment}天")
+    log.info(f"  预留资金比例: {g.reserve_percentage:.0%}")
+    log.info(f"  金融期货预留资金: {g.financial_reserve_amount:.0f}")
     
     # 期货品种完整配置字典
     g.futures_config = {
@@ -162,16 +174,16 @@ def initialize(context):
         'P': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
         
         # 无夜盘品种
-        'IF': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 300, 'trading_start_time': '09:30'},
-        'IH': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 300, 'trading_start_time': '09:30'},
-        'IC': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 200, 'trading_start_time': '09:30'},
-        'IM': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 200, 'trading_start_time': '09:30'},
+        'IF': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 300, 'trading_start_time': '09:30', 'is_financial': True},
+        'IH': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 300, 'trading_start_time': '09:30', 'is_financial': True},
+        'IC': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 200, 'trading_start_time': '09:30', 'is_financial': True},
+        'IM': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 200, 'trading_start_time': '09:30', 'is_financial': True},
         'AP': {'has_night_session': False, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 10, 'trading_start_time': '09:00'},
         'CJ': {'has_night_session': False, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '09:00'},
         'PK': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '09:00'},
         'JD': {'has_night_session': False, 'margin_rate': {'long': 0.11, 'short': 0.11}, 'multiplier': 10, 'trading_start_time': '09:00'},
         'LH': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 16, 'trading_start_time': '09:00'},
-        'T': {'has_night_session': False, 'margin_rate': {'long': 0.03, 'short': 0.03}, 'multiplier': 1000000, 'trading_start_time': '09:30'},
+        'T': {'has_night_session': False, 'margin_rate': {'long': 0.03, 'short': 0.03}, 'multiplier': 1000000, 'trading_start_time': '09:30', 'is_financial': True},
         'PS': {'has_night_session': False, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 3, 'trading_start_time': '09:00'},
         'UR': {'has_night_session': False, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 20, 'trading_start_time': '09:00'},
         'MO': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 100, 'trading_start_time': '21:00'},
@@ -189,7 +201,7 @@ def initialize(context):
         # 'WH': {'has_night_session': False, 'margin_rate': {'long': 0.2, 'short': 0.2}, 'multiplier': 20, 'trading_start_time': '09:00'},
         'SH': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 30, 'trading_start_time': '21:00'},
         # 'RI': {'has_night_session': False, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 20, 'trading_start_time': '09:00'},
-        'TS': {'has_night_session': False, 'margin_rate': {'long': 0.015, 'short': 0.015}, 'multiplier': 2000000, 'trading_start_time': '09:30'},
+        'TS': {'has_night_session': False, 'margin_rate': {'long': 0.015, 'short': 0.015}, 'multiplier': 2000000, 'trading_start_time': '09:30', 'is_financial': True},
         # 'JR': {'has_night_session': False, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 20, 'trading_start_time': '09:00'},
         'AD': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '09:00'},
         # 'BB': {'has_night_session': False, 'margin_rate': {'long': 0.19, 'short': 0.19}, 'multiplier': 500, 'trading_start_time': '09:00'},
@@ -199,18 +211,18 @@ def initialize(context):
         # 'ZC': {'has_night_session': True, 'margin_rate': {'long': 0.56, 'short': 0.56}, 'multiplier': 100, 'trading_start_time': '21:00'},
         'SM': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '09:00'},
         'AO': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 20, 'trading_start_time': '21:00'},
-        'TL': {'has_night_session': False, 'margin_rate': {'long': 0.045, 'short': 0.045}, 'multiplier': 1000000, 'trading_start_time': '09:00'},
+        'TL': {'has_night_session': False, 'margin_rate': {'long': 0.045, 'short': 0.045}, 'multiplier': 1000000, 'trading_start_time': '09:00', 'is_financial': True},
         'SF': {'has_night_session': False, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '09:00'},
         # 'WR': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '09:00'},
         'PR': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 15, 'trading_start_time': '21:00'},
-        'TF': {'has_night_session': False, 'margin_rate': {'long': 0.022, 'short': 0.022}, 'multiplier': 1000000, 'trading_start_time': '09:00'},
+        'TF': {'has_night_session': False, 'margin_rate': {'long': 0.022, 'short': 0.022}, 'multiplier': 1000000, 'trading_start_time': '09:00', 'is_financial': True},
         # 'VF': {'has_night_session': False, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '09:00'},
         'BZ': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 30, 'trading_start_time': '09:00'},
     }
     
     # 策略品种选择策略配置
     # 方案1:全品种策略 - 考虑所有配置的期货品种
-    g.strategy_focus_symbols = []  # 空列表表示考虑所有品种
+    g.strategy_focus_symbols = ['SC']  # 空列表表示考虑所有品种
     
     # 方案2:精选品种策略 - 只交易流动性较好的特定品种(如需使用请取消下行注释)
     # g.strategy_focus_symbols = ['RM', 'CJ', 'CY', 'JD', 'L', 'LC', 'SF', 'SI']
@@ -425,9 +437,21 @@ def check_ma_trend_and_open_gap(context):
             # 步骤4:计算均线相关指标(合并优化)
             ma_values = calculate_ma_values(data_upto_yesterday, g.ma_periods)
             ma_proximity_counts = calculate_ma_proximity_counts(data_upto_yesterday, g.ma_periods, g.ma_pattern_lookback_days)
+            ma_compaction = calculate_ma_compaction_from_values(ma_values, periods=[5, 10, 20])
             
             log.info(f"{symbol}({dominant_future}) 均线检查:")
             log.info(f"  均线贴近统计: {ma_proximity_counts}")
+            if ma_compaction is not None:
+                log.info(f"  均线聚合度(MA5/MA10/MA20): {ma_compaction:.4f}")
+                if ma_compaction > g.ma_compaction_thresholds['loose']:
+                    log.info(
+                        f"  {symbol}({dominant_future}) ✗ 均线聚合度 {ma_compaction:.4f} 超过阈值 "
+                        f"{g.ma_compaction_thresholds['loose']:.4f},跳过"
+                    )
+                    add_to_excluded_contracts(dominant_future, 'ma_compaction', current_trading_day)
+                    continue
+            else:
+                log.info("  均线聚合度(MA5/MA10/MA20) 无法计算,使用默认值")
             
             # 检查均线贴近计数
             proximity_sum = ma_proximity_counts.get('MA5', 0) + ma_proximity_counts.get('MA10', 0)
@@ -524,7 +548,8 @@ def check_ma_trend_and_open_gap(context):
                 'open_price': today_open,
                 'yesterday_close': yesterday_close,
                 'yesterday_open': yesterday_open,
-                'ma_values': ma_values
+                'ma_values': ma_values,
+                'ma_compaction': ma_compaction
             }
             
             log.info(f"  ✓✓ {symbol} 通过均线和开盘价差检查,加入候选列表")
@@ -587,6 +612,7 @@ def check_open_and_stop(context):
                 open_price = candidate_info['open_price']
                 yesterday_close = candidate_info.get('yesterday_close')
                 yesterday_open = candidate_info.get('yesterday_open')
+                ma_compaction = candidate_info.get('ma_compaction')
                 
                 # 检查是否已有持仓
                 if check_symbol_prefix_match(dominant_future, context, set(g.trade_history.keys())):
@@ -605,6 +631,17 @@ def check_open_and_stop(context):
                 log.info(f"{symbol}({dominant_future}) 开仓条件检查:")
                 log.info(f"  方向: {direction}, 开盘价: {open_price:.2f}, 当前价: {current_price:.2f}, "
                         f"当天价差: {intraday_diff:.2f}, 变化比例: {intraday_diff_ratio:.2%}")
+
+                margin_limit = adjust_max_margin_per_position(ma_compaction)
+                if margin_limit is None:
+                    compaction_str = f"{ma_compaction:.4f}" if ma_compaction is not None else "NA"
+                    log.info(
+                        f"  ✗ 均线聚合度 {compaction_str} 超出允许范围(>{g.ma_compaction_thresholds['loose']:.4f}),跳过开仓"
+                    )
+                    candidates_to_remove.append(dominant_future)
+                    continue
+                else:
+                    log.info(f"  动态单标的保证金上限: {margin_limit:.0f}")
                 
                 # 判断是否满足开仓条件 - 仅检查均线穿越得分
                 should_open = True
@@ -658,7 +695,11 @@ def check_open_and_stop(context):
                     # 执行开仓
                     log.info(f"  准备开仓: {symbol} {direction}")
                     target_hands, single_hand_margin, single_hand_exceeds_limit = calculate_target_hands(
-                        context, dominant_future, direction)
+                        context,
+                        dominant_future,
+                        direction,
+                        max_margin_override=margin_limit
+                    )
                     
                     if target_hands > 0:
                         success = open_position(
@@ -683,6 +724,8 @@ def check_open_and_stop(context):
             except Exception as e:
                 log.warning(f"{dominant_future} 处理时出错: {str(e)}")
                 continue
+            finally:
+                adjust_max_margin_per_position(None)
         
         # 从候选列表中移除已开仓的品种
         for future in candidates_to_remove:
@@ -1352,6 +1395,32 @@ def get_multiplier(underlying_symbol, default_multiplier=10):
     """获取合约乘数的辅助函数"""
     return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
 
+def is_financial_underlying(underlying_symbol):
+    """判断标的是否属于金融期货"""
+    if not underlying_symbol:
+        return False
+    return bool(get_futures_config(underlying_symbol, 'is_financial', False))
+
+def has_financial_positions(context):
+    """检查当前持仓中是否包含金融期货"""
+    subportfolios = getattr(context, 'subportfolios', [])
+    if not subportfolios:
+        return False
+    subportfolio = subportfolios[0]
+    positions = list(getattr(subportfolio, 'long_positions', {}).values()) + \
+                list(getattr(subportfolio, 'short_positions', {}).values())
+    for position in positions:
+        security = getattr(position, 'security', '')
+        if not security:
+            continue
+        underlying = security.split('.')[0]
+        if not underlying:
+            continue
+        symbol = underlying[:-4] if len(underlying) > 4 else underlying
+        if is_financial_underlying(symbol):
+            return True
+    return False
+
 def add_to_excluded_contracts(dominant_future, reason, current_trading_day):
     """将合约添加到排除缓存"""
     g.excluded_contracts[dominant_future] = {
@@ -1387,7 +1456,7 @@ def has_reached_trading_start(current_dt, trading_start_time_str, has_night_sess
         return False
     return True
 
-def calculate_target_hands(context, security, direction):
+def calculate_target_hands(context, security, direction, max_margin_override=None):
     """计算目标开仓手数
     
     Returns:
@@ -1404,11 +1473,22 @@ def calculate_target_hands(context, security, direction):
     single_hand_margin = current_price * multiplier * margin_rate
     log.debug(f"计算单手保证金: {current_price:.2f} * {multiplier:.2f} * {margin_rate:.2f} = {single_hand_margin:.2f}")
     
-    # 还要考虑可用资金限制
-    available_cash = context.portfolio.available_cash * g.usage_percentage
+    # 还要考虑可用资金限制,先计算账户总资金(可用资金 + 已占用保证金)
+    total_value = context.portfolio.total_value
+    reserved_cash = total_value * g.reserve_percentage  # 按比例预留的现金
+    available_cash = max(total_value - reserved_cash, 0)
+    # log.debug(f"账户总资金: {total_value:.0f}, 预留现金: {reserved_cash:.0f}, 可用资金: {available_cash:.0f}")
+
+    # 如果当前没有持有金融期货,则额外预留指定金额,避免被其他品种占用
+    if not has_financial_positions(context):
+        reserve_amount = getattr(g, 'financial_reserve_amount', 0)
+        log.debug(f"金融期货预留资金: {reserve_amount:.0f}")
+        if reserve_amount > 0:
+            available_cash = max(available_cash - reserve_amount, 0)
+            log.debug(f"金融期货预留资金扣除后可用资金: {available_cash:.0f}")
     
     # 根据单个标的最大持仓保证金限制计算开仓数量
-    max_margin = g.max_margin_per_position
+    max_margin = max_margin_override if max_margin_override is not None else g.max_margin_per_position
     single_hand_exceeds_limit = single_hand_margin > max_margin
     
     if not single_hand_exceeds_limit:
@@ -1492,6 +1572,52 @@ def calculate_recent_average_change(security, days=5):
     return pct_changes.iloc[-days:].mean()
 
 
+def calculate_ma_compaction_from_values(ma_values, periods=(5, 10, 20)):
+    """基于给定的均线数值计算聚合度(标准差/均值)"""
+    if not ma_values:
+        return None
+    values = []
+    for period in periods:
+        key = f'MA{period}'
+        value = ma_values.get(key)
+        if value is None:
+            return None
+        values.append(value)
+    mean_val = np.mean(values)
+    if mean_val == 0:
+        return None
+    std_val = np.std(values, ddof=0)
+    return std_val / mean_val
+
+
+def determine_margin_limit_from_compaction(compaction_value):
+    """根据均线聚合度返回对应的最大持仓保证金上限"""
+    base_limit = getattr(g, 'base_max_margin_per_position', getattr(g, 'max_margin_per_position', 30000))
+    if compaction_value is None:
+        return base_limit
+    thresholds = getattr(g, 'ma_compaction_thresholds', MA_COMPACTION_THRESHOLDS)
+    tight_threshold = thresholds.get('tight', MA_COMPACTION_THRESHOLDS['tight'])
+    balanced_threshold = thresholds.get('balanced', MA_COMPACTION_THRESHOLDS['balanced'])
+    loose_threshold = thresholds.get('loose', MA_COMPACTION_THRESHOLDS['loose'])
+
+    if compaction_value <= tight_threshold:
+        return 40000
+    if compaction_value <= balanced_threshold:
+        return 30000
+    if compaction_value <= loose_threshold:
+        return 20000
+    return None
+
+
+def adjust_max_margin_per_position(compaction_value):
+    """根据聚合度动态调整 g.max_margin_per_position,并返回新的上限"""
+    margin_limit = determine_margin_limit_from_compaction(compaction_value)
+    if margin_limit is None:
+        return None
+    g.max_margin_per_position = margin_limit
+    return margin_limit
+
+
 def format_entry_details(entry_snapshot):
     """格式化记录到CSV的价格、均线及扩展信息"""
     if not entry_snapshot: