|
@@ -9,12 +9,15 @@
|
|
|
3. 基础头寸交易 - 价格-数量网格配置,支持合约切换时重新建仓
|
|
3. 基础头寸交易 - 价格-数量网格配置,支持合约切换时重新建仓
|
|
|
4. 网格交易策略 - 限价订单网格买入卖出,合约切换时根据价格条件重新建仓
|
|
4. 网格交易策略 - 限价订单网格买入卖出,合约切换时根据价格条件重新建仓
|
|
|
5. 网格对冲策略 - 带止损的增强网格交易,合约切换时根据价格条件重新建仓
|
|
5. 网格对冲策略 - 带止损的增强网格交易,合约切换时根据价格条件重新建仓
|
|
|
-6. 统计分析对比 - 四种交易场景性能分析
|
|
|
|
|
|
|
+6. 移动平均线过滤器 - 基于MA趋势过滤对冲入场,避免强下跌趋势中的对冲
|
|
|
|
|
+7. 统计分析对比 - 四种交易场景性能分析
|
|
|
|
|
|
|
|
主要特点:
|
|
主要特点:
|
|
|
- 主力合约自动监控:每日检测主力合约变化
|
|
- 主力合约自动监控:每日检测主力合约变化
|
|
|
- 强制平仓机制:合约切换时立即平掉旧合约所有头寸
|
|
- 强制平仓机制:合约切换时立即平掉旧合约所有头寸
|
|
|
- 智能重新建仓:根据价格条件在新合约中重新建立头寸
|
|
- 智能重新建仓:根据价格条件在新合约中重新建立头寸
|
|
|
|
|
+- MA趋势过滤:检测强下跌趋势(30K<20K<10K<5K或30K<20K<5K<10K),阻止对冲入场
|
|
|
|
|
+- 智能数据扩展:当MA计算所需数据不足时,自动扩展历史数据范围并缓存
|
|
|
- 标准期货盈亏计算:使用正确的合约倍数和期货盈亏公式
|
|
- 标准期货盈亏计算:使用正确的合约倍数和期货盈亏公式
|
|
|
- 最终持仓结算:分析期结束时对所有未平仓头寸进行市值计价
|
|
- 最终持仓结算:分析期结束时对所有未平仓头寸进行市值计价
|
|
|
- 完整交易记录:记录所有交易包括合约切换引起的强制平仓
|
|
- 完整交易记录:记录所有交易包括合约切换引起的强制平仓
|
|
@@ -66,14 +69,14 @@ class GridTradingConfig:
|
|
|
|
|
|
|
|
# 化工
|
|
# 化工
|
|
|
'FG': 20, 'TA': 5, 'MA': 10, 'SA': 20, 'L': 5, 'V': 5, 'EG': 10,
|
|
'FG': 20, 'TA': 5, 'MA': 10, 'SA': 20, 'L': 5, 'V': 5, 'EG': 10,
|
|
|
- 'PP': 5, 'EB': 5, 'PG': 20,
|
|
|
|
|
|
|
+ 'PP': 5, 'EB': 5, 'PG': 20, 'UR': 20,
|
|
|
|
|
|
|
|
# 农产品
|
|
# 农产品
|
|
|
'RM': 10, 'OI': 10, 'CF': 5, 'SR': 10, 'PF': 5, 'C': 10, 'CS': 10,
|
|
'RM': 10, 'OI': 10, 'CF': 5, 'SR': 10, 'PF': 5, 'C': 10, 'CS': 10,
|
|
|
'CY': 5, 'A': 10, 'B': 10, 'M': 10, 'Y': 10, 'P': 10,
|
|
'CY': 5, 'A': 10, 'B': 10, 'M': 10, 'Y': 10, 'P': 10,
|
|
|
|
|
|
|
|
# 股指期货
|
|
# 股指期货
|
|
|
- 'IF': 300, 'IH': 300, 'IC': 200, 'IM': 200,
|
|
|
|
|
|
|
+ 'IF': 300, 'IH': 300, 'IC': 200, 'IM': 200, 'TL': 10000,
|
|
|
|
|
|
|
|
# 其他
|
|
# 其他
|
|
|
'AP': 10, 'CJ': 5, 'PK': 5, 'JD': 10, 'LH': 16
|
|
'AP': 10, 'CJ': 5, 'PK': 5, 'JD': 10, 'LH': 16
|
|
@@ -81,12 +84,11 @@ class GridTradingConfig:
|
|
|
|
|
|
|
|
# ==================== 核心商品配置 ====================
|
|
# ==================== 核心商品配置 ====================
|
|
|
CORE_COMMODITIES = {
|
|
CORE_COMMODITIES = {
|
|
|
- 'SA': ['SA2509.XZCE', 'SA2601.XZCE'], # 纯碱
|
|
|
|
|
- 'M': ['M2507.XDCE', 'M2605.XDCE'], # 豆粕
|
|
|
|
|
- # 'MA': ['MA2501.XZCE', 'MA2505.XZCE'], # 甲醇
|
|
|
|
|
- # 'AU': ['AU2412.XSGE', 'AU2502.XSGE'], # 黄金
|
|
|
|
|
- # 'AG': ['AG2412.XSGE', 'AG2502.XSGE'], # 白银
|
|
|
|
|
- # 'RU': ['RU2501.XSGE', 'RU2505.XSGE'], # 橡胶
|
|
|
|
|
|
|
+ # 'SA': ['SA2501.XZCE', 'SA2505.XZCE', 'SA2509.XZCE', 'SA2601.XZCE'], # 纯碱
|
|
|
|
|
+ # 'M': ['M2501.XDCE', 'M2505.XDCE', 'M2509.XDCE', 'M2605.XDCE'], # 豆粕
|
|
|
|
|
+ 'UR': ['UR2501.XZCE', 'UR2505.XZCE', 'UR2509.XZCE', 'UR2601.XZCE'], # 尿素
|
|
|
|
|
+ # 'LH': ['LH2501.XDCE', 'LH2505.XDCE', 'LH2509.XDCE', 'LH2601.XDCE'], # 生猪
|
|
|
|
|
+ # 'TL': ['TL2503.CCFX', 'TL2506.CCFX', 'TL2509.CCFX', 'TL2512.CCFX'] # 30年期国债
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# ==================== 合约切换配置 ====================
|
|
# ==================== 合约切换配置 ====================
|
|
@@ -96,20 +98,18 @@ class GridTradingConfig:
|
|
|
BASE_POSITION_GRID = {
|
|
BASE_POSITION_GRID = {
|
|
|
'SA': {1400: 4, 1300: 6, 1200: 8, 1100: 12, 1000: 14, 900: 16},
|
|
'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},
|
|
'M': {2800: 4, 2750: 6, 2700: 8, 2650: 12, 2600: 14, 2550: 16},
|
|
|
- # 'MA': {2500: 8, 2400: 8, 2300: 8, 2200: 8, 2100: 8, 2000: 8},
|
|
|
|
|
- # 'AU': {520: 2, 510: 2, 500: 2, 490: 2, 480: 2, 470: 2},
|
|
|
|
|
- # 'AG': {6500: 10, 6000: 10, 5500: 10, 5000: 10, 4500: 10, 4000: 10},
|
|
|
|
|
- # 'RU': {18000: 5, 17000: 5, 16000: 5, 15000: 5, 14000: 5, 13000: 5}
|
|
|
|
|
|
|
+ 'UR': {1750: 4, 1700: 6, 1650: 8, 1600: 12, 1550: 14, 1500: 16},
|
|
|
|
|
+ 'LH': {13000: 1, 12500: 1, 12000: 1, 11500: 1, 11000: 2},
|
|
|
|
|
+ 'TL': {118: 1, 117: 1, 116: 1, 115: 1, 114: 2, 113: 2},
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# 统一退出价格(无止损)
|
|
# 统一退出价格(无止损)
|
|
|
BASE_POSITION_EXIT_PRICE = {
|
|
BASE_POSITION_EXIT_PRICE = {
|
|
|
'SA': 1500,
|
|
'SA': 1500,
|
|
|
'M': 3800,
|
|
'M': 3800,
|
|
|
- # 'MA': 2600,
|
|
|
|
|
- # 'AU': 530,
|
|
|
|
|
- # 'AG': 7000,
|
|
|
|
|
- # 'RU': 19000
|
|
|
|
|
|
|
+ 'UR': 2400,
|
|
|
|
|
+ 'LH': 20000,
|
|
|
|
|
+ 'TL': 121,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# ==================== 网格交易配置 ====================
|
|
# ==================== 网格交易配置 ====================
|
|
@@ -118,44 +118,44 @@ class GridTradingConfig:
|
|
|
'start_price': 1250, # 开始价格
|
|
'start_price': 1250, # 开始价格
|
|
|
'grid_size': 50, # 网格大小
|
|
'grid_size': 50, # 网格大小
|
|
|
'quantity_per_grid': 5, # 每网格数量
|
|
'quantity_per_grid': 5, # 每网格数量
|
|
|
- 'exit_grid_size': 50 # 退出网格大小
|
|
|
|
|
|
|
+ 'exit_grid_size': 50, # 退出网格大小
|
|
|
|
|
+ 'hedge_stop_price': 1100 # 对冲停止价格(低于此价格不触发对冲)
|
|
|
},
|
|
},
|
|
|
'M': {
|
|
'M': {
|
|
|
'start_price': 2800,
|
|
'start_price': 2800,
|
|
|
'grid_size': 100,
|
|
'grid_size': 100,
|
|
|
'quantity_per_grid': 10,
|
|
'quantity_per_grid': 10,
|
|
|
- 'exit_grid_size': 100
|
|
|
|
|
|
|
+ 'exit_grid_size': 100,
|
|
|
|
|
+ 'hedge_stop_price': 2400
|
|
|
|
|
+ },
|
|
|
|
|
+ 'UR': {
|
|
|
|
|
+ 'start_price': 1800,
|
|
|
|
|
+ 'grid_size': 50,
|
|
|
|
|
+ 'quantity_per_grid': 10,
|
|
|
|
|
+ 'exit_grid_size': 50,
|
|
|
|
|
+ 'hedge_stop_price': 1600
|
|
|
|
|
+ },
|
|
|
|
|
+ 'LH': {
|
|
|
|
|
+ 'start_price': 13500,
|
|
|
|
|
+ 'grid_size': 500,
|
|
|
|
|
+ 'quantity_per_grid': 1,
|
|
|
|
|
+ 'exit_grid_size': 500,
|
|
|
|
|
+ 'hedge_stop_price': 12000
|
|
|
|
|
+ },
|
|
|
|
|
+ 'TL': {
|
|
|
|
|
+ 'start_price': 118,
|
|
|
|
|
+ 'grid_size': 1,
|
|
|
|
|
+ 'quantity_per_grid': 1,
|
|
|
|
|
+ 'exit_grid_size': 1,
|
|
|
|
|
+ 'hedge_stop_price': 113 # 对冲停止价格(低于113不触发对冲)
|
|
|
},
|
|
},
|
|
|
- # 'MA': {
|
|
|
|
|
- # 'start_price': 2300,
|
|
|
|
|
- # 'grid_size': 50,
|
|
|
|
|
- # 'quantity_per_grid': 4,
|
|
|
|
|
- # 'exit_grid_size': 50
|
|
|
|
|
- # },
|
|
|
|
|
- # 'AU': {
|
|
|
|
|
- # 'start_price': 480,
|
|
|
|
|
- # 'grid_size': 10,
|
|
|
|
|
- # 'quantity_per_grid': 1,
|
|
|
|
|
- # 'exit_grid_size': 10
|
|
|
|
|
- # },
|
|
|
|
|
- # 'AG': {
|
|
|
|
|
- # 'start_price': 5500,
|
|
|
|
|
- # 'grid_size': 200,
|
|
|
|
|
- # 'quantity_per_grid': 8,
|
|
|
|
|
- # 'exit_grid_size': 200
|
|
|
|
|
- # },
|
|
|
|
|
- # 'RU': {
|
|
|
|
|
- # 'start_price': 15000,
|
|
|
|
|
- # 'grid_size': 500,
|
|
|
|
|
- # 'quantity_per_grid': 3,
|
|
|
|
|
- # 'exit_grid_size': 500
|
|
|
|
|
- # }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# ==================== 网格对冲策略配置 ====================
|
|
# ==================== 网格对冲策略配置 ====================
|
|
|
GRID_HEDGE_CONFIG = {
|
|
GRID_HEDGE_CONFIG = {
|
|
|
- 'stop_loss_percentage': 0.02, # 2%止损百分比
|
|
|
|
|
- 'enable_hedge_strategy': True # 是否启用网格对冲策略
|
|
|
|
|
|
|
+ 'stop_loss_percentage': 0.01, # 2%止损百分比
|
|
|
|
|
+ 'enable_hedge_strategy': True, # 是否启用网格对冲策略
|
|
|
|
|
+ 'use_ma_filter': True # 是否启用移动平均线过滤器(阻止特定趋势下的对冲入场)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# ==================== 输出设置 ====================
|
|
# ==================== 输出设置 ====================
|
|
@@ -171,8 +171,14 @@ class GridTradingConfig:
|
|
|
print("核心商品列表:")
|
|
print("核心商品列表:")
|
|
|
for commodity, contracts in cls.CORE_COMMODITIES.items():
|
|
for commodity, contracts in cls.CORE_COMMODITIES.items():
|
|
|
print(f" {commodity}: {contracts}")
|
|
print(f" {commodity}: {contracts}")
|
|
|
- print(f"网格对冲策略: {'启用' if cls.GRID_HEDGE_CONFIG['enable_hedge_strategy'] else '禁用'}")
|
|
|
|
|
|
|
+ print(f"\n网格交易配置:")
|
|
|
|
|
+ for commodity, config in cls.GRID_TRADING_CONFIG.items():
|
|
|
|
|
+ hedge_stop = config.get('hedge_stop_price', '未设置')
|
|
|
|
|
+ print(f" {commodity}: 起始价{config['start_price']}, 网格大小{config['grid_size']}, "
|
|
|
|
|
+ f"对冲停止价{hedge_stop}")
|
|
|
|
|
+ print(f"\n网格对冲策略: {'启用' if cls.GRID_HEDGE_CONFIG['enable_hedge_strategy'] else '禁用'}")
|
|
|
print(f"止损百分比: {cls.GRID_HEDGE_CONFIG['stop_loss_percentage']*100}%")
|
|
print(f"止损百分比: {cls.GRID_HEDGE_CONFIG['stop_loss_percentage']*100}%")
|
|
|
|
|
+ print(f"MA过滤器: {'启用' if cls.GRID_HEDGE_CONFIG.get('use_ma_filter', False) else '禁用'}")
|
|
|
print(f"详细日志: {'开启' if cls.VERBOSE_LOGGING else '关闭'}")
|
|
print(f"详细日志: {'开启' if cls.VERBOSE_LOGGING else '关闭'}")
|
|
|
print("=" * 50)
|
|
print("=" * 50)
|
|
|
|
|
|
|
@@ -211,6 +217,9 @@ class FutureGridTradingAnalyzer:
|
|
|
'combined': []
|
|
'combined': []
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ # MA过滤器扩展数据缓存(避免重复获取)
|
|
|
|
|
+ self.ma_extended_data_cache = {}
|
|
|
|
|
+
|
|
|
if self.verbose_logging:
|
|
if self.verbose_logging:
|
|
|
print("初始化期货网格交易分析器")
|
|
print("初始化期货网格交易分析器")
|
|
|
print(f"核心商品: {list(self.core_commodities.keys())}")
|
|
print(f"核心商品: {list(self.core_commodities.keys())}")
|
|
@@ -448,6 +457,8 @@ class FutureGridTradingAnalyzer:
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
if data is not None and len(data) > 0:
|
|
if data is not None and len(data) > 0:
|
|
|
|
|
+ # print(f"第一条有数据的日期是: {data.index[0].date()},数据是: {data.iloc[0]}")
|
|
|
|
|
+ # print(f"最后一条有数据的日期是: {data.index[-1].date()}, 数据是: {data.iloc[-1]}")
|
|
|
self.price_data[commodity][contract] = data
|
|
self.price_data[commodity][contract] = data
|
|
|
|
|
|
|
|
# 检查这个数据里有多少条空值数据
|
|
# 检查这个数据里有多少条空值数据
|
|
@@ -569,10 +580,10 @@ class FutureGridTradingAnalyzer:
|
|
|
if isinstance(contract_end, datetime.date):
|
|
if isinstance(contract_end, datetime.date):
|
|
|
contract_end = datetime.datetime.combine(contract_end, datetime.time.max)
|
|
contract_end = datetime.datetime.combine(contract_end, datetime.time.max)
|
|
|
|
|
|
|
|
- # 添加缓冲期(前后各3天)以确保数据充足
|
|
|
|
|
- buffer_days = 3
|
|
|
|
|
- contract_start_buffered = contract_start - datetime.timedelta(days=buffer_days)
|
|
|
|
|
- contract_end_buffered = contract_end + datetime.timedelta(days=buffer_days)
|
|
|
|
|
|
|
+ # 添加缓冲期以确保有足够的历史数据满足最低交易日要求
|
|
|
|
|
+ # 使用REQUIRED_TRADING_DAYS作为缓冲,保证数据充足性
|
|
|
|
|
+ contract_start_buffered = contract_start - datetime.timedelta(days=self.config.REQUIRED_TRADING_DAYS)
|
|
|
|
|
+ contract_end_buffered = contract_end # + datetime.timedelta(days=self.config.REQUIRED_TRADING_DAYS)
|
|
|
|
|
|
|
|
# 确保不超出总体分析范围
|
|
# 确保不超出总体分析范围
|
|
|
contract_start_final = max(contract_start_buffered, self.start_date)
|
|
contract_start_final = max(contract_start_buffered, self.start_date)
|
|
@@ -755,6 +766,27 @@ class FutureGridTradingAnalyzer:
|
|
|
"""
|
|
"""
|
|
|
if self.verbose_logging:
|
|
if self.verbose_logging:
|
|
|
print("\n=== 步骤3: 带合约切换的交易模拟 ===")
|
|
print("\n=== 步骤3: 带合约切换的交易模拟 ===")
|
|
|
|
|
+
|
|
|
|
|
+ # 打印对冲配置信息
|
|
|
|
|
+ if self.grid_hedge_config['enable_hedge_strategy']:
|
|
|
|
|
+ print("\n网格对冲策略配置:")
|
|
|
|
|
+ for commodity, config in self.grid_trading_config.items():
|
|
|
|
|
+ start_price = config['start_price']
|
|
|
|
|
+ hedge_stop_price = config.get('hedge_stop_price', 0)
|
|
|
|
|
+ grid_size = config['grid_size']
|
|
|
|
|
+ exit_grid_size = config['exit_grid_size']
|
|
|
|
|
+
|
|
|
|
|
+ # 计算对冲范围
|
|
|
|
|
+ hedge_levels = []
|
|
|
|
|
+ current_grid = start_price
|
|
|
|
|
+ while current_grid >= hedge_stop_price and current_grid > 0:
|
|
|
|
|
+ hedge_entry = current_grid + exit_grid_size
|
|
|
|
|
+ hedge_levels.append(hedge_entry)
|
|
|
|
|
+ current_grid -= grid_size
|
|
|
|
|
+
|
|
|
|
|
+ print(f" {commodity}: 对冲范围 {min(hedge_levels)}-{max(hedge_levels)} "
|
|
|
|
|
+ f"(对应主网格 {hedge_stop_price}-{start_price}), "
|
|
|
|
|
+ f"共{len(hedge_levels)}个对冲级别")
|
|
|
|
|
|
|
|
# 按日期顺序处理所有交易日
|
|
# 按日期顺序处理所有交易日
|
|
|
current_date = self.start_date.date()
|
|
current_date = self.start_date.date()
|
|
@@ -1484,26 +1516,35 @@ class FutureGridTradingAnalyzer:
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
def _check_base_position_trading(self, commodity, contract, current_date, daily_prices):
|
|
def _check_base_position_trading(self, commodity, contract, current_date, daily_prices):
|
|
|
- """检查基础头寸交易机会"""
|
|
|
|
|
|
|
+ """检查基础头寸交易机会
|
|
|
|
|
+
|
|
|
|
|
+ 逻辑:每日主动检查所有网格水平
|
|
|
|
|
+ - 如果当前收盘价低于某个网格水平价格
|
|
|
|
|
+ - 且该网格水平没有未平仓头寸
|
|
|
|
|
+ - 则以当日收盘价在该网格水平开仓
|
|
|
|
|
+ """
|
|
|
if commodity not in self.base_position_grid:
|
|
if commodity not in self.base_position_grid:
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
- # 检查入场机会
|
|
|
|
|
|
|
+ # 检查入场机会 - 遍历所有网格水平
|
|
|
price_grid = self.base_position_grid[commodity]
|
|
price_grid = self.base_position_grid[commodity]
|
|
|
|
|
+ current_close_price = daily_prices['close']
|
|
|
|
|
+
|
|
|
for entry_price, quantity in price_grid.items():
|
|
for entry_price, quantity in price_grid.items():
|
|
|
- # 检查是否已经有这个价格水平的头寸(包括重建的头寸)
|
|
|
|
|
|
|
+ # 检查是否已经有这个价格水平的头寸
|
|
|
position_exists = False
|
|
position_exists = False
|
|
|
if commodity in self.active_positions['base_position']:
|
|
if commodity in self.active_positions['base_position']:
|
|
|
for position in self.active_positions['base_position'][commodity].values():
|
|
for position in self.active_positions['base_position'][commodity].values():
|
|
|
if (position['contract'] == contract and position['status'] == 'open'):
|
|
if (position['contract'] == contract and position['status'] == 'open'):
|
|
|
- # 检查原始价格水平或入场价格
|
|
|
|
|
|
|
+ # 检查原始价格水平
|
|
|
position_price_level = position.get('original_price_level', position['entry_price'])
|
|
position_price_level = position.get('original_price_level', position['entry_price'])
|
|
|
if position_price_level == entry_price:
|
|
if position_price_level == entry_price:
|
|
|
position_exists = True
|
|
position_exists = True
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
- if not position_exists and daily_prices['low'] <= entry_price <= daily_prices['high']:
|
|
|
|
|
- # 建立头寸
|
|
|
|
|
|
|
+ # 主动开仓逻辑:当前价格低于网格水平价格 且 该水平没有头寸
|
|
|
|
|
+ if not position_exists and current_close_price < entry_price:
|
|
|
|
|
+ # 以当日收盘价建立头寸
|
|
|
position_id = f"{commodity}_{contract}_{current_date}_base_{entry_price}"
|
|
position_id = f"{commodity}_{contract}_{current_date}_base_{entry_price}"
|
|
|
if commodity not in self.active_positions['base_position']:
|
|
if commodity not in self.active_positions['base_position']:
|
|
|
self.active_positions['base_position'][commodity] = {}
|
|
self.active_positions['base_position'][commodity] = {}
|
|
@@ -1511,17 +1552,15 @@ class FutureGridTradingAnalyzer:
|
|
|
self.active_positions['base_position'][commodity][position_id] = {
|
|
self.active_positions['base_position'][commodity][position_id] = {
|
|
|
'contract': contract,
|
|
'contract': contract,
|
|
|
'entry_date': current_date.strftime('%Y-%m-%d'),
|
|
'entry_date': current_date.strftime('%Y-%m-%d'),
|
|
|
- 'entry_price': entry_price,
|
|
|
|
|
- 'original_price_level': entry_price, # 记录原始价格水平
|
|
|
|
|
|
|
+ 'entry_price': current_close_price, # 使用收盘价作为入场价格
|
|
|
|
|
+ 'original_price_level': entry_price, # 记录原始网格水平
|
|
|
'quantity': quantity,
|
|
'quantity': quantity,
|
|
|
'status': 'open',
|
|
'status': 'open',
|
|
|
'exit_target': self.base_position_exit_price.get(commodity)
|
|
'exit_target': self.base_position_exit_price.get(commodity)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if self.verbose_logging:
|
|
if self.verbose_logging:
|
|
|
- print(f" {current_date}: {commodity} 基础头寸入场 @ {entry_price}, 数量: {quantity},当天最低价格为{daily_prices['low']},最高价格为{daily_prices['high']}")
|
|
|
|
|
- # print(f" 创建头寸: {position_id}")
|
|
|
|
|
- # print(f" 当日价格范围: 最低 {daily_prices['low']}, 最高 {daily_prices['high']}")
|
|
|
|
|
|
|
+ print(f" {current_date}: {commodity} 基础头寸入场 @ 网格{entry_price} (实际价格: {current_close_price:.2f}), 数量: {quantity},当天收盘价{current_close_price:.2f}低于网格价格{entry_price}")
|
|
|
|
|
|
|
|
# 检查退出机会
|
|
# 检查退出机会
|
|
|
exit_target = self.base_position_exit_price.get(commodity)
|
|
exit_target = self.base_position_exit_price.get(commodity)
|
|
@@ -1644,8 +1683,201 @@ class FutureGridTradingAnalyzer:
|
|
|
if self.verbose_logging:
|
|
if self.verbose_logging:
|
|
|
print(f" {current_date}: {commodity} 网格退出 {position['entry_price']} -> {exit_price:.2f}, 盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
|
|
print(f" {current_date}: {commodity} 网格退出 {position['entry_price']} -> {exit_price:.2f}, 盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
|
|
|
|
|
|
|
|
|
|
+ def _extend_price_data_for_ma(self, commodity, contract, current_date, required_days=30):
|
|
|
|
|
+ """扩展价格数据以满足MA计算需求
|
|
|
|
|
+
|
|
|
|
|
+ 参数:
|
|
|
|
|
+ commodity: 商品代码
|
|
|
|
|
+ contract: 合约代码
|
|
|
|
|
+ current_date: 当前日期
|
|
|
|
|
+ required_days: 所需的最少数据天数
|
|
|
|
|
+
|
|
|
|
|
+ 返回:
|
|
|
|
|
+ 扩展后的价格数据DataFrame,如果获取失败则返回None
|
|
|
|
|
+ """
|
|
|
|
|
+ cache_key = f"{commodity}_{contract}"
|
|
|
|
|
+
|
|
|
|
|
+ # 检查缓存
|
|
|
|
|
+ if cache_key in self.ma_extended_data_cache:
|
|
|
|
|
+ cached_data = self.ma_extended_data_cache[cache_key]
|
|
|
|
|
+ target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
|
|
|
|
|
+ historical_data = cached_data[cached_data.index.date <= target_date]
|
|
|
|
|
+
|
|
|
|
|
+ if len(historical_data) >= required_days:
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ℹ️ MA过滤器:使用缓存的扩展数据,共{len(historical_data)}天")
|
|
|
|
|
+ return historical_data
|
|
|
|
|
+
|
|
|
|
|
+ # 获取现有数据
|
|
|
|
|
+ if commodity not in self.price_data or contract not in self.price_data[commodity]:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ existing_data = self.price_data[commodity][contract]
|
|
|
|
|
+ target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
|
|
|
|
|
+ existing_historical = existing_data[existing_data.index.date <= target_date]
|
|
|
|
|
+
|
|
|
|
|
+ if len(existing_historical) >= required_days:
|
|
|
|
|
+ return existing_historical
|
|
|
|
|
+
|
|
|
|
|
+ # 数据不足,需要扩展获取
|
|
|
|
|
+ existing_count = len(existing_historical)
|
|
|
|
|
+ shortage = required_days - existing_count
|
|
|
|
|
+
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" 📊 MA过滤器:数据不足,开始扩展获取(当前{existing_count}天,需要{required_days}天,缺少{shortage}天)")
|
|
|
|
|
+
|
|
|
|
|
+ # 计算扩展的开始日期:从现有数据最早日期往前推至少30个交易日
|
|
|
|
|
+ if len(existing_data) > 0:
|
|
|
|
|
+ earliest_date = existing_data.index.min().date()
|
|
|
|
|
+ else:
|
|
|
|
|
+ earliest_date = target_date
|
|
|
|
|
+
|
|
|
|
|
+ # 往前推60个自然日(约等于40-45个交易日,提供充足缓冲)
|
|
|
|
|
+ extended_start_date = earliest_date - datetime.timedelta(days=60)
|
|
|
|
|
+ extended_end_date = target_date
|
|
|
|
|
+
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" 📊 扩展日期范围: {extended_start_date} 至 {extended_end_date}")
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 获取扩展的价格数据
|
|
|
|
|
+ extended_data = get_price(
|
|
|
|
|
+ contract,
|
|
|
|
|
+ start_date=extended_start_date,
|
|
|
|
|
+ end_date=extended_end_date,
|
|
|
|
|
+ frequency='daily',
|
|
|
|
|
+ fields=['open', 'close', 'high', 'low', 'volume'],
|
|
|
|
|
+ skip_paused=False,
|
|
|
|
|
+ panel=False
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if extended_data is None or len(extended_data) == 0:
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ⚠️ 扩展数据获取失败:未获取到数据")
|
|
|
|
|
+ return existing_historical
|
|
|
|
|
+
|
|
|
|
|
+ # 合并数据:将扩展数据与现有数据合并,保留所有日期的数据
|
|
|
|
|
+ # 使用concat合并,然后去重并按日期排序
|
|
|
|
|
+ combined_data = pd.concat([existing_data, extended_data])
|
|
|
|
|
+ combined_data = combined_data[~combined_data.index.duplicated(keep='first')]
|
|
|
|
|
+ combined_data = combined_data.sort_index()
|
|
|
|
|
+
|
|
|
|
|
+ # 过滤到当前日期(仅用于返回给MA计算)
|
|
|
|
|
+ combined_historical = combined_data[combined_data.index.date <= target_date]
|
|
|
|
|
+
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ✅ 扩展数据获取成功:从{len(existing_historical)}天扩展到{len(combined_historical)}天")
|
|
|
|
|
+ print(f" 合并后完整数据范围:{combined_data.index.min().date()} 至 {combined_data.index.max().date()}(共{len(combined_data)}天)")
|
|
|
|
|
+
|
|
|
|
|
+ # 缓存扩展后的完整数据
|
|
|
|
|
+ self.ma_extended_data_cache[cache_key] = combined_data
|
|
|
|
|
+
|
|
|
|
|
+ # 更新主price_data,保留原有数据和新扩展的数据
|
|
|
|
|
+ self.price_data[commodity][contract] = combined_data
|
|
|
|
|
+
|
|
|
|
|
+ return combined_historical
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ⚠️ 扩展数据获取异常:{str(e)}")
|
|
|
|
|
+ return existing_historical
|
|
|
|
|
+
|
|
|
|
|
+ def _should_block_hedge_by_ma_filter(self, commodity, contract, current_date):
|
|
|
|
|
+ """检查是否应该通过移动平均线过滤器阻止对冲入场
|
|
|
|
|
+
|
|
|
|
|
+ 返回值:
|
|
|
|
|
+ True: 应该阻止对冲入场(满足以下趋势之一)
|
|
|
|
|
+ False: 允许对冲入场
|
|
|
|
|
+
|
|
|
|
|
+ 阻止条件(强下跌趋势):
|
|
|
|
|
+ 1. 30K < 20K < 10K < 5K(所有均线呈下降顺序)
|
|
|
|
|
+ 2. 30K < 20K < 5K < 10K(5K与10K交叉的下跌趋势)
|
|
|
|
|
+ """
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 获取价格数据
|
|
|
|
|
+ if commodity not in self.price_data or contract not in self.price_data[commodity]:
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ⚠️ MA过滤器:无法获取 {commodity} {contract} 的价格数据")
|
|
|
|
|
+ return False # 没有数据时不阻止
|
|
|
|
|
+
|
|
|
|
|
+ price_data = self.price_data[commodity][contract]
|
|
|
|
|
+
|
|
|
|
|
+ # 获取当前日期及之前的数据(确保有足够的历史数据来计算30日均线)
|
|
|
|
|
+ target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
|
|
|
|
|
+
|
|
|
|
|
+ # 过滤出当前日期及之前的数据
|
|
|
|
|
+ historical_data = price_data[price_data.index.date <= target_date]
|
|
|
|
|
+
|
|
|
|
|
+ # 如果数据不足,尝试扩展获取
|
|
|
|
|
+ if len(historical_data) < 30:
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ⚠️ MA过滤器:{commodity} {contract} 历史数据不足(需要30天,实际{len(historical_data)}天)")
|
|
|
|
|
+
|
|
|
|
|
+ # 尝试扩展数据获取
|
|
|
|
|
+ extended_data = self._extend_price_data_for_ma(commodity, contract, current_date, required_days=30)
|
|
|
|
|
+
|
|
|
|
|
+ if extended_data is not None and len(extended_data) >= 30:
|
|
|
|
|
+ historical_data = extended_data
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ✅ 扩展数据成功,现在有{len(historical_data)}天数据,继续MA过滤器检查")
|
|
|
|
|
+ else:
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ⚠️ 扩展数据失败,数据仍不足,阻止对冲入场")
|
|
|
|
|
+ return True # 扩展失败,数据仍不足时阻止
|
|
|
|
|
+
|
|
|
|
|
+ # 使用收盘价计算移动平均线
|
|
|
|
|
+ close_prices = historical_data['close']
|
|
|
|
|
+
|
|
|
|
|
+ # 计算最近的移动平均线值
|
|
|
|
|
+ ma5 = close_prices.iloc[-5:].mean()
|
|
|
|
|
+ ma10 = close_prices.iloc[-10:].mean()
|
|
|
|
|
+ ma20 = close_prices.iloc[-20:].mean()
|
|
|
|
|
+ ma30 = close_prices.iloc[-30:].mean()
|
|
|
|
|
+
|
|
|
|
|
+ # 检查是否为NaN
|
|
|
|
|
+ if pd.isna(ma5) or pd.isna(ma10) or pd.isna(ma20) or pd.isna(ma30):
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ⚠️ MA过滤器:{commodity} {contract} 移动平均线计算结果包含NaN")
|
|
|
|
|
+ return True # 计算失败时阻止
|
|
|
|
|
+
|
|
|
|
|
+ # 检查阻止条件
|
|
|
|
|
+ # 条件1:30K < 20K < 10K < 5K(所有均线呈下降顺序的强上涨趋势)
|
|
|
|
|
+ condition1 = (ma30 < ma20) and (ma20 < ma10) and (ma10 < ma5)
|
|
|
|
|
+
|
|
|
|
|
+ # 条件2:30K < 20K < 5K < 10K(5K与10K交叉的上涨趋势)
|
|
|
|
|
+ condition2 = (ma30 < ma20) and (ma20 < ma5) and (ma5 < ma10)
|
|
|
|
|
+
|
|
|
|
|
+ should_block = condition1 or condition2
|
|
|
|
|
+
|
|
|
|
|
+ if self.verbose_logging and should_block:
|
|
|
|
|
+ print(f" 🚫 MA过滤器触发:{commodity} {contract} 检测到强上涨趋势,阻止对冲入场")
|
|
|
|
|
+ print(f" MA5={ma5:.2f}, MA10={ma10:.2f}, MA20={ma20:.2f}, MA30={ma30:.2f}")
|
|
|
|
|
+ if condition1:
|
|
|
|
|
+ print(f" 触发条件1: 30K < 20K < 10K < 5K")
|
|
|
|
|
+ if condition2:
|
|
|
|
|
+ print(f" 触发条件2: 30K < 20K < 5K < 10K")
|
|
|
|
|
+
|
|
|
|
|
+ return should_block
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" ⚠️ MA过滤器异常:{commodity} {contract} - {str(e)}")
|
|
|
|
|
+ return True # 异常时阻止
|
|
|
|
|
+
|
|
|
def _check_grid_hedge_trading(self, commodity, contract, current_date, daily_prices):
|
|
def _check_grid_hedge_trading(self, commodity, contract, current_date, daily_prices):
|
|
|
- """检查网格对冲交易机会"""
|
|
|
|
|
|
|
+ """检查网格对冲交易机会
|
|
|
|
|
+
|
|
|
|
|
+ 逻辑:独立的对冲网格系统,不依赖主网格交易执行
|
|
|
|
|
+ - 基于主网格结构创建独立的对冲网格
|
|
|
|
|
+ - 对冲网格水平 = 主网格水平 + exit_grid_size
|
|
|
|
|
+ - 当价格突破网格水平时(如118→119),立即触发对冲空头头寸
|
|
|
|
|
+ - 对冲范围受 hedge_stop_price 限制,低于该价格不触发对冲
|
|
|
|
|
+
|
|
|
|
|
+ 示例:start_price=118, hedge_stop_price=113, exit_grid_size=1
|
|
|
|
|
+ - 主网格水平:118, 117, 116, 115, 114, 113, 112, ...
|
|
|
|
|
+ - 对冲触发价格:119, 118, 117, 116, 115, 114(对应主网格113-118)
|
|
|
|
|
+ - 低于113的主网格不触发对冲
|
|
|
|
|
+ """
|
|
|
if not self.grid_hedge_config['enable_hedge_strategy'] or commodity not in self.grid_trading_config:
|
|
if not self.grid_hedge_config['enable_hedge_strategy'] or commodity not in self.grid_trading_config:
|
|
|
return
|
|
return
|
|
|
|
|
|
|
@@ -1653,40 +1885,59 @@ class FutureGridTradingAnalyzer:
|
|
|
start_price = config['start_price']
|
|
start_price = config['start_price']
|
|
|
grid_size = config['grid_size']
|
|
grid_size = config['grid_size']
|
|
|
quantity_per_grid = config['quantity_per_grid']
|
|
quantity_per_grid = config['quantity_per_grid']
|
|
|
|
|
+ exit_grid_size = config['exit_grid_size']
|
|
|
|
|
+ hedge_stop_price = config.get('hedge_stop_price', 0) # 对冲停止价格,默认为0(不限制)
|
|
|
stop_loss_pct = self.grid_hedge_config['stop_loss_percentage']
|
|
stop_loss_pct = self.grid_hedge_config['stop_loss_percentage']
|
|
|
|
|
|
|
|
- # 检查入场机会(对冲是在start_price之上)
|
|
|
|
|
- current_level = start_price + grid_size
|
|
|
|
|
- while current_level < daily_prices['high']:
|
|
|
|
|
|
|
+ # 检查入场机会(独立对冲网格系统)
|
|
|
|
|
+ # 从start_price开始,向下遍历所有可能的网格级别
|
|
|
|
|
+ # 只有在 hedge_stop_price 及以上的网格级别才会触发对冲
|
|
|
|
|
+ current_grid_level = start_price
|
|
|
|
|
+ while current_grid_level >= hedge_stop_price and current_grid_level > 0:
|
|
|
|
|
+ # 计算该网格级别对应的对冲开仓价格(独立触发,不依赖主网格交易)
|
|
|
|
|
+ hedge_entry_level = current_grid_level + exit_grid_size
|
|
|
|
|
+
|
|
|
|
|
+ # 检查是否已经有这个对冲级别的头寸
|
|
|
position_exists = False
|
|
position_exists = False
|
|
|
if commodity in self.active_positions['grid_hedge']:
|
|
if commodity in self.active_positions['grid_hedge']:
|
|
|
for position in self.active_positions['grid_hedge'][commodity].values():
|
|
for position in self.active_positions['grid_hedge'][commodity].values():
|
|
|
if (position['contract'] == contract and
|
|
if (position['contract'] == contract and
|
|
|
- position.get('original_grid_level', position['entry_price']) == current_level and
|
|
|
|
|
|
|
+ position.get('original_grid_level', position['entry_price']) == hedge_entry_level and
|
|
|
position['status'] == 'open'):
|
|
position['status'] == 'open'):
|
|
|
position_exists = True
|
|
position_exists = True
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
- if not position_exists and daily_prices['low'] <= current_level <= daily_prices['high']:
|
|
|
|
|
- position_id = f"{commodity}_{contract}_{current_date}_hedge_{current_level}"
|
|
|
|
|
|
|
+ # 当价格触及对冲级别时独立开仓(不需要等待主网格交易)
|
|
|
|
|
+ if not position_exists and daily_prices['low'] <= hedge_entry_level <= daily_prices['high']:
|
|
|
|
|
+ # 检查MA过滤器:如果启用且检测到强上涨趋势,则阻止对冲入场
|
|
|
|
|
+ if self.grid_hedge_config.get('use_ma_filter', False):
|
|
|
|
|
+ if self._should_block_hedge_by_ma_filter(commodity, contract, current_date):
|
|
|
|
|
+ if self.verbose_logging:
|
|
|
|
|
+ print(f" {current_date}: {commodity} 对冲入场被MA过滤器阻止 @ {hedge_entry_level}")
|
|
|
|
|
+ # 跳过此次对冲入场
|
|
|
|
|
+ current_grid_level -= grid_size
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ position_id = f"{commodity}_{contract}_{current_date}_hedge_{hedge_entry_level}"
|
|
|
if commodity not in self.active_positions['grid_hedge']:
|
|
if commodity not in self.active_positions['grid_hedge']:
|
|
|
self.active_positions['grid_hedge'][commodity] = {}
|
|
self.active_positions['grid_hedge'][commodity] = {}
|
|
|
|
|
|
|
|
self.active_positions['grid_hedge'][commodity][position_id] = {
|
|
self.active_positions['grid_hedge'][commodity][position_id] = {
|
|
|
'contract': contract,
|
|
'contract': contract,
|
|
|
'entry_date': current_date.strftime('%Y-%m-%d'),
|
|
'entry_date': current_date.strftime('%Y-%m-%d'),
|
|
|
- 'entry_price': current_level,
|
|
|
|
|
- 'original_grid_level': current_level,
|
|
|
|
|
|
|
+ 'entry_price': hedge_entry_level,
|
|
|
|
|
+ 'original_grid_level': hedge_entry_level,
|
|
|
|
|
+ 'base_grid_level': current_grid_level, # 记录对应的基础网格级别
|
|
|
'quantity': quantity_per_grid,
|
|
'quantity': quantity_per_grid,
|
|
|
'status': 'open',
|
|
'status': 'open',
|
|
|
- 'profit_target': current_level - grid_size,
|
|
|
|
|
- 'stop_loss': current_level * (1 + stop_loss_pct)
|
|
|
|
|
|
|
+ 'profit_target': hedge_entry_level - grid_size, # 对冲平仓目标:回到原网格级别
|
|
|
|
|
+ 'stop_loss': hedge_entry_level * (1 + stop_loss_pct)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if self.verbose_logging:
|
|
if self.verbose_logging:
|
|
|
- print(f" {current_date}: {commodity} 对冲入场 @ {current_level},数量:{quantity_per_grid},当天最低价为{daily_prices['low']},最高价为{daily_prices['high']}")
|
|
|
|
|
|
|
+ print(f" {current_date}: {commodity} 对冲入场 @ {hedge_entry_level}(对应网格级别{current_grid_level}),数量:{quantity_per_grid},目标:{hedge_entry_level - grid_size},当天价格区间[{daily_prices['low']}, {daily_prices['high']}]")
|
|
|
|
|
|
|
|
- current_level += grid_size
|
|
|
|
|
|
|
+ current_grid_level -= grid_size
|
|
|
|
|
|
|
|
# 检查退出机会
|
|
# 检查退出机会
|
|
|
if commodity in self.active_positions['grid_hedge']:
|
|
if commodity in self.active_positions['grid_hedge']:
|