Ver código fonte

更新期货左侧交易策略(带网格和对冲)v001,完善底仓左侧多头、网格多头和空头对冲的逻辑,新增待成交限价单跟踪功能,优化开仓和止盈检查时间点。同时更新README文档,详细说明各策略组件的开仓、止盈和止损条件,增强了策略的可操作性和灵活性。

maxfeng 3 semanas atrás
pai
commit
5d575f7dde
2 arquivos alterados com 322 adições e 169 exclusões
  1. 282 43
      Lib/future/FutureLeftSide_v001.py
  2. 40 126
      Lib/future/README.md

+ 282 - 43
Lib/future/FutureLeftSide_v001.py

@@ -123,21 +123,29 @@ def initialize(context):
         'PK': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '09:00'},
         'JD': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '09:00'},
         'LH': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 16, 'trading_start_time': '09:00'},
-        'UR': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'}
+        'UR': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '09:00'}
     }
     
     # 策略品种选择配置
-    g.strategy_focus_symbols = ['TL']  # 交易品种列表
+    g.strategy_focus_symbols = ['LH']  # 交易品种列表
     
     # ==================== 底仓左侧多头配置 ====================
     # 价格-数量网格:价格越低,买入手数越多
     g.base_position_grid = {
         'TL': {118: 1, 117: 1, 116: 1, 115: 1, 114: 2, 113: 2},
+        'SA': {1400: 4, 1300: 6, 1200: 8, 1100: 12, 1000: 14, 900: 16},
+        'M': {2800: 4, 2750: 6, 2700: 8, 2650: 12, 2600: 14, 2550: 16},
+        'UR': {1750: 4, 1700: 6, 1650: 8, 1600: 12, 1550: 14, 1500: 16},
+        'LH': {13000: 1, 12500: 1, 12000: 1, 11500: 1, 11000: 2},
     }
     
     # 底仓退出价格(止盈)
     g.base_position_exit_price = {
         'TL': 121,
+        'SA': 1550,
+        'M': 3800,
+        'UR': 2400,
+        'LH': 14600,
     }
     
     # ==================== 网格多头配置 ====================
@@ -148,11 +156,34 @@ def initialize(context):
             'quantity_per_grid': 1,  # 每网格数量
             'exit_grid_size': 1      # 退出网格大小
         },
+        'SA': {
+            'start_price': 1250, # 开始价格
+            'grid_size': 50, # 网格大小
+            'quantity_per_grid': 5, # 每网格数量
+            'exit_grid_size': 50 # 退出网格大小
+        },
+        'M': {
+            'start_price': 2800,
+            'grid_size': 100,
+            'quantity_per_grid': 10,
+            'exit_grid_size': 100
+        },
+        'UR': {
+            'start_price': 1800,
+            'grid_size': 50,
+            'quantity_per_grid': 10,
+            'exit_grid_size': 50
+        },
+        'LH': {
+            'start_price': 13500,
+            'grid_size': 500,
+            'quantity_per_grid': 1,
+            'exit_grid_size': 500
+        },
     }
     
     # ==================== 空头对冲配置 ====================
-    g.hedge_profit_pullback_pct = 0.003  # 止盈回撤百分比(0.3%)
-    g.hedge_stoploss_pct = 0.01          # 止损百分比(1%)
+    g.hedge_stoploss_pct = 0.02          # 止损百分比(1%)
     g.hedge_level1_recovery_pct = 0.30   # 一级止盈回升百分比(30%)
     g.hedge_level2_recovery_pct = 0.60   # 二级止盈回升百分比(60%)
     g.hedge_cost_area_pct = 0.02         # 成本区域百分比(2%)
@@ -163,6 +194,9 @@ def initialize(context):
     g.grid_positions = {}      # 网格持仓 {symbol: [{'entry_price': price, 'quantity': qty, 'target_price': target_price, 'entry_date': date}]}
     g.hedge_positions = {}     # 对冲持仓 {symbol: {level_key: {'entry_price': price, 'quantity': qty, 'entry_date': date}}}
     
+    # 底仓待成交限价单跟踪(问题2修复)
+    g.base_pending_orders = {} # {symbol: {level_key: {'order_id': xxx, 'price': xxx, 'quantity': xxx, 'submit_time': xxx}}}
+    
     # 网格状态跟踪
     g.grid_buy_levels = {}     # 网格已建仓目标价集合 {symbol: set()}
     
@@ -180,6 +214,7 @@ def initialize(context):
     run_daily(check_stop_profit_loss, time='11:05:00', reference_security='IF1808.CCFX')
     run_daily(check_stop_profit_loss, time='13:35:00', reference_security='IF1808.CCFX')
     run_daily(check_stop_profit_loss, time='14:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='14:35:00', reference_security='IF1808.CCFX')
     run_daily(check_stop_profit_loss, time='14:55:00', reference_security='IF1808.CCFX')
     
     # 建仓逻辑(开盘时执行:夜盘21:05,日盘09:05)
@@ -329,10 +364,12 @@ def check_stop_profit_loss(context):
             # 策略组件2: 网格多头止盈(可配置)
             if g.enable_grid_long:
                 check_grid_trading_close(context, symbol, dominant_future, current_price)
+                check_grid_trading_open(context, symbol, dominant_future, current_price)
             
             # 策略组件3: 空头对冲止损止盈(可配置)
             if g.enable_hedge_short:
                 check_hedge_position_close(context, symbol, dominant_future, current_price)
+                check_hedge_position_open(context, symbol, dominant_future, current_price)
             
         except Exception as e:
             log.warning(f"止损止盈检查处理{symbol}时出错: {str(e)}")
@@ -340,6 +377,78 @@ def check_stop_profit_loss(context):
 
 ############################ 策略组件1: 底仓左侧多头 ###################################
 
+def update_base_pending_orders(context, symbol, dominant_future):
+    """更新底仓待成交限价单状态
+    
+    检查待成交限价单的状态:
+    - 如果订单已完全成交,移到 g.base_positions
+    - 如果订单被取消或失败,从 g.base_pending_orders 中删除
+    - 如果订单部分成交或仍在挂单,保持在 g.base_pending_orders 中
+    
+    Args:
+        context: 上下文对象
+        symbol: 品种代码
+        dominant_future: 主力合约
+    """
+    if dominant_future not in g.base_pending_orders:
+        return
+    
+    if not g.base_pending_orders[dominant_future]:
+        return
+    
+    # 获取所有订单
+    try:
+        all_orders = get_orders()
+    except Exception as e:
+        log.warning(f"更新待成交限价单失败: 无法获取订单信息 - {str(e)}")
+        return
+    
+    levels_to_remove = []
+    levels_to_add_to_positions = []
+    
+    for level_key, pending_info in g.base_pending_orders[dominant_future].items():
+        order_id = pending_info.get('order_id')
+        if not order_id or order_id not in all_orders:
+            # 订单不存在,可能已被系统清理,从待成交列表中移除
+            log.info(f"底仓限价单 {level_key} 订单 {order_id} 不存在于订单列表,移除待成交记录")
+            levels_to_remove.append(level_key)
+            continue
+        
+        order_obj = all_orders[order_id]
+        status = str(order_obj.status).lower()
+        filled_amount = getattr(order_obj, 'filled', 0) or 0
+        order_amount = getattr(order_obj, 'amount', 0) or 0
+        
+        # 检查订单状态
+        if status in ('filled', 'held'):
+            # 订单已完全成交
+            actual_entry_price = pending_info['price']  # 限价单成交价等于限价
+            levels_to_add_to_positions.append((level_key, {
+                'entry_price': actual_entry_price,
+                'quantity': pending_info['quantity'],
+                'entry_date': pending_info['submit_time']
+            }))
+            levels_to_remove.append(level_key)
+            log.info(f"底仓限价单 {level_key} 已成交,订单 {order_id},移至持仓记录")
+        elif status in ('canceled', 'rejected'):
+            # 订单已取消或被拒绝
+            levels_to_remove.append(level_key)
+            log.info(f"底仓限价单 {level_key} 已{status},订单 {order_id},从待成交列表移除")
+        elif status in ('open', 'pending', 'new', 'part_filled'):
+            # 订单仍在等待成交或部分成交,保持在待成交列表
+            log.info(f"底仓限价单 {level_key} 状态 {status},已成交 {filled_amount}/{order_amount} 手,继续等待")
+        else:
+            # 未知状态,记录日志
+            log.warning(f"底仓限价单 {level_key} 订单 {order_id} 状态未知: {status}")
+    
+    # 将已成交的订单移到持仓记录
+    for level_key, position_info in levels_to_add_to_positions:
+        g.base_positions[dominant_future][level_key] = position_info
+    
+    # 从待成交列表中移除
+    for level_key in levels_to_remove:
+        del g.base_pending_orders[dominant_future][level_key]
+
 def check_base_position_open(context, symbol, dominant_future, current_price):
     """检查底仓左侧多头建仓
     
@@ -365,8 +474,17 @@ def check_base_position_open(context, symbol, dominant_future, current_price):
     if dominant_future not in g.base_positions:
         g.base_positions[dominant_future] = {}
     
+    # 初始化待成交限价单记录(问题2修复)
+    if dominant_future not in g.base_pending_orders:
+        g.base_pending_orders[dominant_future] = {}
+    
+    # 问题2修复:检查待成交限价单的状态,将已成交的订单移到持仓记录
+    update_base_pending_orders(context, symbol, dominant_future)
+    
     log.info(f"底仓建仓检查: {symbol} 当前价格 {current_price}")
     log.info(f"底仓持仓: {g.base_positions[dominant_future]}")
+    log.info(f"底仓待成交限价单: {g.base_pending_orders[dominant_future]}")
+    
     # 收集未建仓的档位
     market_order_levels = []  # 需要市价单的档位
     limit_order_levels = []   # 需要限价单的档位
@@ -378,6 +496,17 @@ def check_base_position_open(context, symbol, dominant_future, current_price):
         if level_key in g.base_positions[dominant_future]:
             continue
         
+        # 问题2修复:跳过已有待成交限价单的档位
+        if level_key in g.base_pending_orders[dominant_future]:
+            log.info(f"  档位 {price_level} 已有待成交限价单,跳过")
+            continue
+        
+        # 问题1修复:过滤价格过低的档位,避免无效挂单
+        # 当档位价格 < 当前价格的95%时,跳过该档位(相当于当前价格 > 档位价格 * 1.05)
+        if price_level < current_price * 0.95:
+            log.info(f"  档位 {price_level} 过低(< 当前价 {current_price} * 0.95 = {current_price * 0.95:.2f}),跳过挂单")
+            continue
+        
         # 判断使用市价单还是限价单
         if price_level >= current_price:
             # 档位价格 >= 当前价格,使用市价单
@@ -391,7 +520,7 @@ def check_base_position_open(context, symbol, dominant_future, current_price):
     for price_level, quantity in market_order_levels:
         level_key = f"{price_level}"
         strategy_info = {'strategy_type': '基础左侧', 'target_price': price_level}
-        success = open_position(context, dominant_future, quantity, 'long', 
+        _, success = open_position(context, dominant_future, quantity, 'long', 
                               f'底仓市价建仓(目标档位{price_level})', 
                               strategy_info=strategy_info)
         if success:
@@ -413,17 +542,19 @@ def check_base_position_open(context, symbol, dominant_future, current_price):
         for price_level, quantity in top_two_levels:
             level_key = f"{price_level}"
             strategy_info = {'strategy_type': '基础左侧', 'target_price': price_level}
-            success = open_position(context, dominant_future, quantity, 'long', 
+            order_id, success = open_position(context, dominant_future, quantity, 'long', 
                                   f'底仓限价建仓@{price_level}', 
                                   limit_price=price_level,
                                   strategy_info=strategy_info)
             if success:
-                g.base_positions[dominant_future][level_key] = {
-                    'entry_price': price_level,  # 限价单按委托价记录
+                # 问题2修复:限价单提交成功后,记录到待成交列表,而不是直接记录到持仓
+                g.base_pending_orders[dominant_future][level_key] = {
+                    'order_id': order_id,
+                    'price': price_level,
                     'quantity': quantity,
-                    'entry_date': context.current_dt
+                    'submit_time': context.current_dt
                 }
-                log.info(f"底仓限价单挂单: {symbol} 档位 {price_level}, 数量 {quantity}")
+                log.info(f"底仓限价单挂单: {symbol} 档位 {price_level}, 数量 {quantity}, order_id={order_id}, 待成交确认")
 
 def check_base_position_close(context, symbol, dominant_future, current_price):
     """检查底仓左侧多头平仓(止盈)"""
@@ -521,7 +652,7 @@ def check_grid_trading_open(context, symbol, dominant_future, current_price):
     # 执行市价单
     for _, target_price in market_order_grids:
         strategy_info = {'strategy_type': '网格', 'target_price': target_price}
-        success = open_position(context, dominant_future, quantity_per_grid, 'long', 
+        _, success = open_position(context, dominant_future, quantity_per_grid, 'long', 
                               f'网格市价买入(目标价{target_price})',
                               strategy_info=strategy_info)
         if success:
@@ -543,7 +674,7 @@ def check_grid_trading_open(context, symbol, dominant_future, current_price):
 
         for _, target_price in top_two_grids:
             strategy_info = {'strategy_type': '网格', 'target_price': target_price}
-            success = open_position(context, dominant_future, quantity_per_grid, 'long', 
+            _, success = open_position(context, dominant_future, quantity_per_grid, 'long', 
                                   f'网格限价买入@目标价{target_price}', 
                                   limit_price=target_price,
                                   strategy_info=strategy_info)
@@ -610,6 +741,7 @@ def check_hedge_position_open(context, symbol, dominant_future, current_price):
     1. 对冲数量只对应基础左侧持仓,不包括网格持仓
     2. 对冲永远使用限价单,价格为各档位的目标价格
     3. 针对每个基础左侧档位分别建立对应的空头对冲单
+    4. **关键约束**:只对今天开立的左侧持仓建立对冲
     """
     
     # 时间检查:判断是否已达到该品种的交易开始时间
@@ -634,22 +766,112 @@ def check_hedge_position_open(context, symbol, dominant_future, current_price):
         log.info(f"对冲建仓检查: {symbol} 当前无基础左侧持仓,无需对冲")
         return
     
-    log.info(f"对冲建仓检查: {symbol} 当前价格 {current_price}")
+    # **关键检查**:筛选今天开立的左侧持仓
+    today_date = context.current_dt.date()
+    today_opened_positions = {}
     
-    # 针对每个基础左侧档位建立对应的对冲单
     for level_key, position_info in g.base_positions[dominant_future].items():
+        entry_date = position_info.get('entry_date')
+        if entry_date is None:
+            log.warning(f"对冲建仓检查: {symbol} 档位 {level_key} 缺少entry_date,跳过")
+            continue
+        
+        # 将entry_date转换为date对象进行比较
+        if hasattr(entry_date, 'date'):
+            entry_date_obj = entry_date.date()
+        elif hasattr(entry_date, 'to_pydatetime'):
+            entry_date_obj = entry_date.to_pydatetime().date()
+        else:
+            entry_date_obj = entry_date
+        
+        # 只记录今天开立的持仓
+        if entry_date_obj == today_date:
+            today_opened_positions[level_key] = position_info
+            log.info(f"对冲建仓检查: {symbol} 档位 {level_key} 今天开立,可建立对冲")
+        else:
+            log.info(f"对冲建仓检查: {symbol} 档位 {level_key} 开立日期 {entry_date_obj} ≠ 今天 {today_date},跳过对冲")
+    
+    # **关键判断**:如果今天没有开立任何左侧持仓,完全跳过对冲逻辑
+    if not today_opened_positions:
+        # log.info(f"对冲建仓检查: {symbol} 今天未开立任何左侧持仓,跳过所有对冲建仓逻辑")
+        return
+    
+    log.info(f"对冲建仓检查: {symbol} 当前价格 {current_price}, 今天开立的左侧持仓档位: {list(today_opened_positions.keys())}")
+
+    # 收集对应基础左侧订单的成交情况,仅在订单成交后才建立对冲
+    level_fill_status = {}
+    order_strategy_info_map = getattr(g, 'order_strategy_info', {}) if hasattr(g, 'order_strategy_info') else {}
+    try:
+        all_orders = get_orders()
+    except Exception as e:
+        log.warning(f"对冲建仓检查: {symbol} 获取订单信息失败,等待下次检查。错误: {str(e)}")
+        all_orders = {}
+
+    if all_orders:
+        for order_id, order_obj in all_orders.items():
+            if order_id not in order_strategy_info_map:
+                continue
+            strategy_info = order_strategy_info_map[order_id]
+            if strategy_info.get('strategy_type') != '基础左侧':
+                continue
+            if getattr(order_obj, 'security', None) != dominant_future:
+                continue
+            target_price_key = f"{strategy_info.get('target_price')}"
+            if target_price_key not in today_opened_positions:
+                continue
+            status_value = getattr(order_obj, 'status', '')
+            status_str = str(status_value)
+            status_lower = status_str.lower()
+            filled_amount = getattr(order_obj, 'filled', 0) or 0
+            order_amount = getattr(order_obj, 'amount', 0) or 0
+            info = level_fill_status.setdefault(target_price_key, {
+                'filled': 0,
+                'amount': 0,
+                'pending': False,
+                'waiting': False,
+                'statuses': set()
+            })
+            info['filled'] += filled_amount
+            info['amount'] += order_amount
+            info['statuses'].add(status_str)
+            if status_lower in ('open', 'part_filled', 'pending', 'submitted'):
+                info['pending'] = True
+            if status_lower in ('open', 'submitted') and filled_amount == 0:
+                info['waiting'] = True
+
+    # 只针对今天开立的基础左侧档位建立对应的对冲单
+    for level_key, position_info in today_opened_positions.items():
         # 跳过已经建立对冲的档位
         if level_key in g.hedge_positions[dominant_future]:
+            log.info(f"  对冲档位 {level_key}: 已存在对冲持仓,跳过")
+            continue
+
+        fill_info = level_fill_status.get(level_key)
+        if not fill_info:
+            log.info(f"  对冲档位 {level_key}: 底仓订单未找到成交记录,等待成交后再建立对冲")
+            continue
+
+        required_quantity = position_info['quantity']
+        filled_quantity = fill_info.get('filled', 0)
+
+        status_list = sorted(fill_info.get('statuses'))
+
+        if fill_info.get('waiting', False):
+            log.info(f"  对冲档位 {level_key}: 底仓限价单仍在挂单等待,状态集合 {status_list}")
+            continue
+
+        if filled_quantity < required_quantity:
+            # log.info(f"  对冲档位 {level_key}: 底仓订单成交量不足 (成交 {filled_quantity} 手 / 目标 {required_quantity} 手,状态集合 {status_list}),跳过对冲")
             continue
         
         target_price = level_key # 目标价格本质就是对冲档位
         quantity = position_info['quantity']
         
-        log.info(f"  对冲档位 {level_key}: 目标价格 {target_price}, 数量 {quantity}手")
+        log.info(f"  对冲档位 {level_key}: 目标价格 {target_price}, 数量 {quantity}手(对应今天开立的左侧持仓)")
         
         # 建立空头对冲(永远使用限价单,价格为档位目标价)
         strategy_info = {'strategy_type': '对冲', 'target_price': target_price}
-        success = open_position(context, dominant_future, quantity, 'short', 
+        _, success = open_position(context, dominant_future, quantity, 'short', 
                               f'建立空头对冲@{target_price}', 
                               limit_price=target_price,
                               strategy_info=strategy_info)
@@ -691,9 +913,28 @@ def check_hedge_position_close(context, symbol, dominant_future, current_price):
     if not has_hedge_position:
         return
     
+    # 问题1修复:获取当前日期,用于过滤当日新开仓的对冲头寸
+    today_date = context.current_dt.date()
+    
     # 遍历每个档位的对冲持仓
     levels_to_remove = []
     for level_key, hedge_info in g.hedge_positions[dominant_future].items():
+        # 问题1修复:检查对冲头寸的开仓日期
+        entry_date = hedge_info.get('entry_date')
+        if entry_date is not None:
+            # 将entry_date转换为date对象进行比较
+            if hasattr(entry_date, 'date'):
+                entry_date_obj = entry_date.date()
+            elif hasattr(entry_date, 'to_pydatetime'):
+                entry_date_obj = entry_date.to_pydatetime().date()
+            else:
+                entry_date_obj = entry_date
+            
+            # 如果是当日新开仓的对冲头寸,跳过止损止盈检查
+            if entry_date_obj == today_date:
+                log.info(f"对冲止损止盈检查: {symbol} 档位 {level_key} 于今日开仓,跳过当日止损止盈检查")
+                continue
+        
         try:
             entry_price = float(hedge_info['entry_price'])
         except (TypeError, ValueError):
@@ -710,9 +951,9 @@ def check_hedge_position_close(context, symbol, dominant_future, current_price):
             g.hedge_state[dominant_future]['max_profit_price'] = current_price
             
             # 更新盈利阶段
-            if profit_rate >= g.hedge_level2_recovery_pct * g.hedge_profit_pullback_pct:
+            if profit_rate >= g.hedge_level2_recovery_pct:
                 g.hedge_state[dominant_future]['profit_stage'] = 2
-            elif profit_rate >= g.hedge_level1_recovery_pct * g.hedge_profit_pullback_pct:
+            elif profit_rate >= g.hedge_level1_recovery_pct:
                 g.hedge_state[dominant_future]['profit_stage'] = 1
         
         # 检查止损条件
@@ -722,17 +963,6 @@ def check_hedge_position_close(context, symbol, dominant_future, current_price):
             levels_to_remove.append(level_key)
             continue
         
-        # 检查回撤止盈条件
-        max_profit = g.hedge_state[dominant_future]['max_profit']
-        profit_drawdown = max_profit - profit_rate
-        
-        if profit_drawdown >= g.hedge_profit_pullback_pct:
-            log.info(f"对冲触发回撤止盈: {symbol} 档位 {level_key}, 最大盈利 {max_profit:.2%}, "
-                    f"当前盈利 {profit_rate:.2%}, 回撤 {profit_drawdown:.2%}")
-            close_position(context, dominant_future, quantity, 'short', f'对冲回撤止盈@档位{level_key}')
-            levels_to_remove.append(level_key)
-            continue
-        
         # 检查成本区域止盈条件
         if abs(profit_rate) <= g.hedge_cost_area_pct:
             profit_stage = g.hedge_state[dominant_future]['profit_stage']
@@ -765,6 +995,9 @@ def open_position(context, security, quantity, direction, reason='', limit_price
         limit_price: 限价单价格,None表示使用市价单
         strategy_info: 策略信息字典 {'strategy_type': '策略类型', 'target_price': 目标价格}
         
+    Returns:
+        tuple: (order_id, success) - 订单ID和是否成功
+        
     注:订单提交后filled=0,实际成交信息需要在收盘后通过get_trades()获取
     """
     try:
@@ -790,15 +1023,15 @@ def open_position(context, security, quantity, direction, reason='', limit_price
                 g.order_strategy_info[order_obj.order_id] = strategy_info
                 log.info(f"订单策略信息已记录: {strategy_info}")
             
-            return True
+            return order_obj.order_id, True
         else:
             log.warning(f"订单提交失败: {security} {direction} {quantity}手")
-            return False
+            return None, False
             
     except Exception as e:
         log.warning(f"开仓异常 {security}: {str(e)}")
     
-    return False
+    return None, False
 
 def close_position(context, security, quantity, direction, reason=''):
     """平仓
@@ -910,15 +1143,15 @@ def is_trading_time_reached(context, symbol):
             else:
                 # 当前在凌晨(已经过了配置的开始时间)
                 result = True
-            log.info(f"[时间检查] {symbol} 配置夜盘开始 {trading_start_time}, 当前时间 {current_time.strftime('%H:%M:%S')}, 判断结果: {result}")
+            # log.debug(f"[时间检查] {symbol} 配置夜盘开始 {trading_start_time}, 当前时间 {current_time.strftime('%H:%M:%S')}, 判断结果: {result}")
             return result
         elif is_current_day:
             # 夜盘品种在日盘时段,允许交易
-            log.info(f"[时间检查] {symbol} 夜盘品种在日盘时段 {current_time.strftime('%H:%M:%S')}, 允许交易")
+            # log.debug(f"[时间检查] {symbol} 夜盘品种在日盘时段 {current_time.strftime('%H:%M:%S')}, 允许交易")
             return True
         else:
             # 其他时段不允许交易
-            log.info(f"[时间检查] {symbol} 夜盘品种在非交易时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
+            # log.debug(f"[时间检查] {symbol} 夜盘品种在非交易时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
             return False
     else:
         # 配置为日盘开始时间
@@ -1118,9 +1351,9 @@ def print_daily_trading_summary(context, trades, open_orders):
                 continue
             entry_price = float(entry_info.get('entry_price', 0))
             entry_date = entry_info.get('entry_date')
-            # log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
-            #         f"策略:基础左侧, 策略对应价格:{level_key}, 当前标的价格:{current_price:.2f}, "
-            #         f"成交日期时间: {format_datetime_value(entry_date)}")
+            log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
+                    f"策略:基础左侧, 策略对应价格:{level_key}, 当前标的价格:{current_price:.2f}, "
+                    f"成交日期时间: {format_datetime_value(entry_date)}")
             symbol_logged = True
             holdings_logged = True
         grid_entries = g.grid_positions.get(security, []) if hasattr(g, 'grid_positions') else []
@@ -1131,14 +1364,14 @@ def print_daily_trading_summary(context, trades, open_orders):
             entry_price = float(entry_info.get('entry_price', 0))
             target_price = entry_info.get('target_price', 0)
             entry_date = entry_info.get('entry_date')
-            # log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
-            #         f"策略:网格, 策略对应价格:{target_price}, 当前标的价格:{current_price:.2f}, "
-            #         f"成交日期时间: {format_datetime_value(entry_date)}")
+            log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
+                    f"策略:网格, 策略对应价格:{target_price}, 当前标的价格:{current_price:.2f}, "
+                    f"成交日期时间: {format_datetime_value(entry_date)}")
             symbol_logged = True
             holdings_logged = True
         if not symbol_logged:
-            # log.info(f"标的种类: {display_symbol}, 数量:{position.total_amount}手, 成交价:{position.avg_cost:.2f}, "
-            #         f"策略:未分类, 策略对应价格:--, 当前标的价格:{current_price:.2f}, 成交日期时间: --")
+            log.info(f"标的种类: {display_symbol}, 数量:{position.total_amount}手, 成交价:{position.avg_cost:.2f}, "
+                    f"策略:未分类, 策略对应价格:--, 当前标的价格:{current_price:.2f}, 成交日期时间: --")
             holdings_logged = True
     
     for security, position in subportfolio.short_positions.items():
@@ -1295,6 +1528,12 @@ def update_positions_after_switch(old_symbol, new_symbol, side):
         del g.base_positions[old_symbol]
         log.info(f"底仓持仓记录更新: {old_symbol} -> {new_symbol}")
     
+    # 更新底仓待成交限价单记录(问题2修复)
+    if old_symbol in g.base_pending_orders:
+        g.base_pending_orders[new_symbol] = g.base_pending_orders[old_symbol]
+        del g.base_pending_orders[old_symbol]
+        log.info(f"底仓待成交限价单记录更新: {old_symbol} -> {new_symbol}")
+    
     # 更新网格持仓记录
     if old_symbol in g.grid_positions:
         g.grid_positions[new_symbol] = g.grid_positions[old_symbol]

+ 40 - 126
Lib/future/README.md

@@ -524,129 +524,43 @@ g.opposite_hatch_ratio = 0.5  # 相反方向影线与实体长度比率阈值
 - 最大单品种保证金:20000元
 
 ## 期货左侧交易策略(带网格和对冲)v001
-
-### 具体策略概述
-
-`GridTradingWithHedge_v001.py` 是一个综合性期货交易策略,结合了三个可配置的策略组件:
-
-1. **底仓左侧多头**(始终启用)- 价格越低建仓越多的左侧交易策略
-2. **网格多头策略**(可配置开关)- 基于价格网格的多头网格交易
-3. **空头对冲策略**(可配置开关)- 针对底仓多头的空头对冲保护
-
-### 策略组件详解
-
-#### 1. 底仓左侧多头(Base Position)
-
-**特点:**
-- 始终启用,不可关闭
-- 价格越低,建仓手数越多(左侧交易理念)
-- 基于预设的价格-数量网格进行分批建仓
-- 设置统一的止盈价格退出
-
-**配置参数:**
-```python
-g.base_position_grid = {
-    'TL': {118: 1, 117: 1, 116: 1, 115: 1, 114: 2, 113: 2},
-}
-
-g.base_position_exit_price = {
-    'TL': 121,
-}
-```
-
-#### 2. 网格多头策略(Grid Trading)
-
-**特点:**
-- 可通过 `g.enable_grid_long` 开关控制
-- 在起始价格以下按固定网格大小分批买入
-- 每个网格持仓独立管理,达到盈利目标后卖出
-- 卖出后该网格层级可重新买入(循环网格)
-
-**配置参数:**
-```python
-g.enable_grid_long = True  # 启用/禁用网格多头
-
-g.grid_config = {
-    'TL': {
-        'start_price': 118,      # 网格开始价格
-        'grid_size': 1,          # 网格大小
-        'quantity_per_grid': 1,  # 每网格数量
-        'exit_grid_size': 1      # 退出网格大小
-    },
-}
-```
-
-#### 3. 空头对冲策略(Hedge Short)
-
-**特点:**
-- 可通过 `g.enable_hedge_short` 开关控制
-- 在底仓建立后自动建立等量空头对冲
-- 多重止盈机制:回撤止盈、成本区域止盈
-- 固定止损保护
-
-**配置参数:**
-```python
-g.enable_hedge_short = True  # 启用/禁用空头对冲
-
-g.hedge_profit_pullback_pct = 0.003  # 止盈回撤百分比(0.3%)
-g.hedge_stoploss_pct = 0.01          # 止损百分比(1%)
-g.hedge_level1_recovery_pct = 0.30   # 一级止盈回升百分比(30%)
-g.hedge_level2_recovery_pct = 0.60   # 二级止盈回升百分比(60%)
-g.hedge_cost_area_pct = 0.02         # 成本区域百分比(2%)
-```
-
-**止盈逻辑:**
-1. **回撤止盈**:盈利后回撤达到设定百分比时平仓
-2. **成本区域止盈**:曾达到盈利阶段后回到成本区域时平仓
-3. **固定止损**:亏损达到设定百分比时平仓
-
-### 关键特性
-
-#### 1. 合约自动滚动
-- 采用参考策略(`CandlestickHatchReverseStrategy_v001.py`)中的合约滚动机制
-- 主力合约切换时自动平掉旧合约并在新合约重建持仓
-- 保持策略状态的连续性
-
-#### 2. 夜盘/日盘适配
-- 根据品种配置自动识别是否支持夜盘交易
-- 夜盘时段仅交易支持夜盘的品种
-- 日盘时段所有品种均可交易
-
-#### 3. 持仓状态管理
-- 独立跟踪三个策略组件的持仓状态
-- 合约切换时自动更新所有持仓记录
-- 详细的交易日志记录
-
-#### 4. 多品种支持
-- 支持贵金属、有色金属、黑色系、能源化工、农产品等多个品种
-- 预配置了详细的保证金率和合约乘数
-- 可轻松扩展支持新品种
-
-### 使用方法
-
-#### 基础配置
-
-1. **选择交易品种:**
-```python
-g.strategy_focus_symbols = ['TL']  # 可添加多个品种
-```
-
-2. **配置策略组件开关:**
-```python
-g.enable_grid_long = True   # 启用网格多头
-g.enable_hedge_short = True # 启用空头对冲
-```
-
-3. **配置资金管理:**
-```python
-g.usage_percentage = 0.8  # 最大资金使用比例
-g.max_margin_per_position = 20000  # 单个标的最大持仓保证金
-```
-
-### 技术支持
-
-如有问题或建议,请参考:
-- 研究策略:`Lib/research/future_grid_trading_analysis2.py`
-- 参考策略:`Lib/future/CandlestickHatchReverseStrategy_v001.py`
-- 其他策略文档:`Lib/future/README.md`
-
+`FutureLeftSide_v001.py` 将仓位拆分为底仓左侧多头、网格多头和空头对冲三大组件,所有任务在执行前会优先尝试移仓换月,确保逻辑仅在可交易时间与主力合约下运行。
+
+### 底仓左侧多头
+
+- **开仓条件**
+  - 达到品种配置的开盘时间(`is_trading_time_reached`)。
+  - 遍历 `g.base_position_grid` 未成交档位:档位价 ≥ 当前价立即市价买入;档位价 < 当前价仅保留价格最高的两个档位挂限价单。
+  - 仅对尚未记录的档位下单,避免重复委托。
+- **止盈 / 止损条件**
+  - 市价 ≥ `g.base_position_exit_price[symbol]` 时一次性平掉底仓多头。
+  - 策略未设置固定止损,风险由网格和对冲组件共同覆盖。
+- **检查时间点**
+  - 开仓检查:09:05(日盘)、09:35(日盘金融类标的)、21:05(夜盘)。
+  - 止盈检查:09:35、10:05、10:35、11:05、13:35、14:05、14:35、14:55、21:05、21:35、22:05。
+
+### 网格多头(可选)
+
+- **开仓条件**
+  - 达到交易时间且当前价低于 `start_price`。
+  - 计算应触发的网格层级:目标价 ≥ 当前价使用市价单,其余只选择最高两个目标价挂限价单。
+  - 成交后在 `g.grid_buy_levels` 中标记目标价,防止重复下单。
+- **止盈 / 止损条件**
+  - 市价 ≥ `target_price + exit_grid_size` 时对应仓位全部卖出并解除标记。
+  - 未额外设定止损。
+- **检查时间点**
+  - 开仓检查:09:05(日盘)、09:35(日盘金融类标的)、21:05(夜盘)。
+  - 止盈检查:与底仓止盈相同的全部时间点。
+
+### 空头对冲(可选)
+
+- **开仓条件**
+  - 当天存在已成交的底仓档位(依据订单状态与成交量确认)。
+  - 对每个符合条件的档位按档位价挂空单,数量等于底仓手数,仅首次成功时记录。
+- **止盈 / 止损条件**
+  - 固定止损:收益率 ≤ -1% 时立即平仓。
+  - 盈利回撤止盈:当前收益距离历史最大收益回撤 ≥ 0.3% 时平仓。
+  - 成本区域止盈:达到盈利阶段后,收益回落至 ±2% 区间内触发。
+- **检查时间点**
+  - 开仓检查:09:05(日盘)、09:35(日盘金融类标的)、21:05(夜盘) 以及全部止盈检查时间点(用于补挂漏单)。
+  - 止盈检查:与网格、底仓相同的全部时间点。