|
|
@@ -50,15 +50,16 @@ def initialize(context):
|
|
|
# 均线策略参数
|
|
|
g.ma_periods = [5, 10, 20, 30] # 均线周期
|
|
|
g.ma_historical_days = 60 # 获取历史数据天数(确保足够计算MA30)
|
|
|
- g.ma_open_gap_threshold = 0.002 # 方案1开盘价差阈值(0.2%)
|
|
|
+ g.ma_open_gap_threshold = 0.001 # 方案1开盘价差阈值(0.2%)
|
|
|
g.ma_pattern_lookback_days = 10 # 历史均线模式一致性检查的天数
|
|
|
g.ma_pattern_consistency_threshold = 0.8 # 历史均线模式一致性阈值(80%)
|
|
|
g.check_intraday_spread = False # 是否检查日内价差(True: 检查, False: 跳过)
|
|
|
g.ma_proximity_min_threshold = 8 # MA5与MA10贴近计数和的最低阈值
|
|
|
+ g.ma_pattern_extreme_days_threshold = 4 # 极端趋势天数阈值
|
|
|
|
|
|
# 均线价差策略方案选择
|
|
|
- g.ma_gap_strategy_mode = 2 # 策略模式选择(1: 原方案, 2: 新方案)
|
|
|
- g.ma_open_gap_threshold2 = 0.002 # 方案2开盘价差阈值(0.2%)
|
|
|
+ g.ma_gap_strategy_mode = 3 # 策略模式选择(1: 原方案, 2: 新方案, 3: 方案3)
|
|
|
+ g.ma_open_gap_threshold2 = 0.001 # 方案2开盘价差阈值(0.2%)
|
|
|
g.ma_intraday_threshold_scheme2 = 0.005 # 方案2日内变化阈值(0.5%)
|
|
|
|
|
|
# 止损止盈策略参数
|
|
|
@@ -76,6 +77,7 @@ def initialize(context):
|
|
|
log.info(f" 方案2日内变化阈值: {g.ma_intraday_threshold_scheme2:.1%}")
|
|
|
log.info(f" 历史均线模式检查天数: {g.ma_pattern_lookback_days}天")
|
|
|
log.info(f" 历史均线模式一致性阈值: {g.ma_pattern_consistency_threshold:.1%}")
|
|
|
+ log.info(f" 极端趋势天数阈值: {g.ma_pattern_extreme_days_threshold}")
|
|
|
log.info(f" 均线贴近计数阈值: {g.ma_proximity_min_threshold}")
|
|
|
log.info(f" 是否检查日内价差: {g.check_intraday_spread}")
|
|
|
log.info(f" 固定止损: {g.fixed_stop_loss_rate:.1%}")
|
|
|
@@ -180,27 +182,23 @@ def initialize(context):
|
|
|
run_daily(check_ma_trend_and_open_gap, time='09:05:00', reference_security='IF1808.CCFX')
|
|
|
run_daily(check_ma_trend_and_open_gap, time='09:35:00', reference_security='IF1808.CCFX')
|
|
|
|
|
|
- # 盘中价差检查和开仓(14:35和14:55)
|
|
|
- run_daily(check_intraday_price_diff, time='14:35:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_intraday_price_diff, time='14:55:00', reference_security='IF1808.CCFX')
|
|
|
-
|
|
|
- # 夜盘止损止盈检查
|
|
|
- run_daily(check_stop_loss_profit, time='21:05:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='21:35:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='22:05:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='22:35:00', reference_security='IF1808.CCFX')
|
|
|
-
|
|
|
- # 日盘止损止盈检查
|
|
|
- run_daily(check_stop_loss_profit, time='09:05:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='09:35:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='10:05:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='10:35:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='11:05:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='11:25:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='13:35:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='14:05:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='14:35:00', reference_security='IF1808.CCFX')
|
|
|
- run_daily(check_stop_loss_profit, time='14:55:00', reference_security='IF1808.CCFX')
|
|
|
+ # 夜盘开仓和止损止盈检查
|
|
|
+ run_daily(check_open_and_stop, time='21:05:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='21:35:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='22:05:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='22:35:00', reference_security='IF1808.CCFX')
|
|
|
+
|
|
|
+ # 日盘开仓和止损止盈检查
|
|
|
+ run_daily(check_open_and_stop, time='09:05:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='09:35:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='10:05:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='10:35:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='11:05:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='11:25:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='13:35:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='14:05:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='14:35:00', reference_security='IF1808.CCFX')
|
|
|
+ run_daily(check_open_and_stop, time='14:55:00', reference_security='IF1808.CCFX')
|
|
|
|
|
|
# 收盘后
|
|
|
run_daily(after_market_close, time='15:30:00', reference_security='IF1808.CCFX')
|
|
|
@@ -389,6 +387,28 @@ def check_ma_trend_and_open_gap(context):
|
|
|
}
|
|
|
continue
|
|
|
|
|
|
+ extreme_above_count, extreme_below_count = calculate_extreme_trend_days(
|
|
|
+ data_upto_yesterday,
|
|
|
+ g.ma_periods,
|
|
|
+ g.ma_pattern_lookback_days
|
|
|
+ )
|
|
|
+ extreme_total = extreme_above_count + extreme_below_count
|
|
|
+ min_extreme = min(extreme_above_count, extreme_below_count)
|
|
|
+ filter_threshold = max(2, g.ma_pattern_extreme_days_threshold)
|
|
|
+ log.info(
|
|
|
+ f" 极端趋势天数统计: 收盘在所有均线上方 {extreme_above_count} 天, 收盘在所有均线下方 {extreme_below_count} 天, "
|
|
|
+ f"合计 {extreme_total} 天, min(A,B)={min_extreme} (过滤阈值: {filter_threshold})"
|
|
|
+ )
|
|
|
+ if extreme_above_count > 0 and extreme_below_count > 0 and min_extreme >= filter_threshold:
|
|
|
+ log.info(
|
|
|
+ f" {symbol}({dominant_future}) ✗ 极端趋势多空同时出现且 min(A,B)={min_extreme} ≥ {filter_threshold},跳过"
|
|
|
+ )
|
|
|
+ g.excluded_contracts[dominant_future] = {
|
|
|
+ 'reason': 'ma_extreme_trend',
|
|
|
+ 'trading_day': current_trading_day
|
|
|
+ }
|
|
|
+ continue
|
|
|
+
|
|
|
# 判断均线走势(使用新的灵活模式检查)
|
|
|
direction = None
|
|
|
if check_ma_pattern(ma_values, 'long'):
|
|
|
@@ -440,13 +460,13 @@ def check_ma_trend_and_open_gap(context):
|
|
|
elif direction == 'short' and open_gap_ratio <= -g.ma_open_gap_threshold:
|
|
|
log.info(f" {symbol}({dominant_future}) ✓ 方案1空头开盘价差检查通过 ({open_gap_ratio:.2%} <= {-g.ma_open_gap_threshold:.2%})")
|
|
|
gap_check_passed = True
|
|
|
- elif g.ma_gap_strategy_mode == 2:
|
|
|
- # 方案2:多头检查下跳,空头检查上跳
|
|
|
+ elif g.ma_gap_strategy_mode == 2 or g.ma_gap_strategy_mode == 3:
|
|
|
+ # 方案2和方案3:多头检查下跳,空头检查上跳
|
|
|
if direction == 'long' and open_gap_ratio <= -g.ma_open_gap_threshold2:
|
|
|
- log.info(f" {symbol}({dominant_future}) ✓ 方案2多头开盘价差检查通过 ({open_gap_ratio:.2%} <= {-g.ma_open_gap_threshold2:.2%})")
|
|
|
+ log.info(f" {symbol}({dominant_future}) ✓ 方案{g.ma_gap_strategy_mode}多头开盘价差检查通过 ({open_gap_ratio:.2%} <= {-g.ma_open_gap_threshold2:.2%})")
|
|
|
gap_check_passed = True
|
|
|
elif direction == 'short' and open_gap_ratio >= g.ma_open_gap_threshold2:
|
|
|
- log.info(f" {symbol}({dominant_future}) ✓ 方案2空头开盘价差检查通过 ({open_gap_ratio:.2%} >= {g.ma_open_gap_threshold2:.2%})")
|
|
|
+ log.info(f" {symbol}({dominant_future}) ✓ 方案{g.ma_gap_strategy_mode}空头开盘价差检查通过 ({open_gap_ratio:.2%} >= {g.ma_open_gap_threshold2:.2%})")
|
|
|
gap_check_passed = True
|
|
|
|
|
|
if not gap_check_passed:
|
|
|
@@ -458,12 +478,16 @@ def check_ma_trend_and_open_gap(context):
|
|
|
}
|
|
|
continue
|
|
|
|
|
|
+ # 获取前一日开盘价(用于方案3)
|
|
|
+ yesterday_open = yesterday_data['open']
|
|
|
+
|
|
|
# 将通过检查的品种加入候选列表
|
|
|
g.daily_ma_candidates[dominant_future] = {
|
|
|
'symbol': symbol,
|
|
|
'direction': direction,
|
|
|
'open_price': today_open,
|
|
|
'yesterday_close': yesterday_close,
|
|
|
+ 'yesterday_open': yesterday_open,
|
|
|
'ma_values': ma_values
|
|
|
}
|
|
|
|
|
|
@@ -477,106 +501,8 @@ def check_ma_trend_and_open_gap(context):
|
|
|
log.info(f"候选列表更新完成,当前候选品种: {list(g.daily_ma_candidates.keys())}")
|
|
|
log.info("=" * 60)
|
|
|
|
|
|
-def check_intraday_price_diff(context):
|
|
|
- """阶段二:盘中价差检查和开仓(14:35和14:55)"""
|
|
|
- log.info("=" * 60)
|
|
|
- log.info(f"执行当天价差检查和开仓逻辑 - 时间: {context.current_dt}")
|
|
|
- log.info("=" * 60)
|
|
|
-
|
|
|
- # 先检查换月移仓
|
|
|
- position_auto_switch(context)
|
|
|
-
|
|
|
- if not g.daily_ma_candidates:
|
|
|
- log.info("当前无候选品种,跳过")
|
|
|
- return
|
|
|
-
|
|
|
- log.info(f"候选品种数量: {len(g.daily_ma_candidates)}")
|
|
|
-
|
|
|
- # 遍历候选品种
|
|
|
- candidates_to_remove = []
|
|
|
-
|
|
|
- for dominant_future, candidate_info in g.daily_ma_candidates.items():
|
|
|
- try:
|
|
|
- symbol = candidate_info['symbol']
|
|
|
- direction = candidate_info['direction']
|
|
|
- open_price = candidate_info['open_price']
|
|
|
-
|
|
|
- # 再次检查是否已有持仓
|
|
|
- if check_symbol_prefix_match(dominant_future, set(g.trade_history.keys())):
|
|
|
- log.info(f"{symbol} 已有持仓,从候选列表移除")
|
|
|
- candidates_to_remove.append(dominant_future)
|
|
|
- continue
|
|
|
-
|
|
|
- # 获取当前价格
|
|
|
- current_data = get_current_data()[dominant_future]
|
|
|
- current_price = current_data.last_price
|
|
|
-
|
|
|
- # 计算当天价差
|
|
|
- intraday_diff = current_price - open_price
|
|
|
- intraday_diff_ratio = intraday_diff / open_price # 计算相对变化比例
|
|
|
-
|
|
|
- log.info(f"{symbol}({dominant_future}) 当天价差检查:")
|
|
|
- log.info(f" 方向: {direction}, 开盘价: {open_price:.2f}, 当前价: {current_price:.2f}, "
|
|
|
- f"当天价差: {intraday_diff:.2f}, 变化比例: {intraday_diff_ratio:.2%}")
|
|
|
-
|
|
|
- # 判断是否满足开仓条件
|
|
|
- should_open = False
|
|
|
-
|
|
|
- if g.ma_gap_strategy_mode == 1:
|
|
|
- # 方案1:根据参数决定是否检查日内价差
|
|
|
- if not g.check_intraday_spread:
|
|
|
- # 跳过日内价差检查,直接允许开仓
|
|
|
- log.info(f" 方案1跳过日内价差检查(check_intraday_spread=False)")
|
|
|
- should_open = True
|
|
|
- elif direction == 'long' and intraday_diff > 0:
|
|
|
- log.info(f" ✓ 方案1多头当天价差检查通过 ({intraday_diff:.2f} > 0)")
|
|
|
- should_open = True
|
|
|
- elif direction == 'short' and intraday_diff < 0:
|
|
|
- log.info(f" ✓ 方案1空头当天价差检查通过 ({intraday_diff:.2f} < 0)")
|
|
|
- should_open = True
|
|
|
- else:
|
|
|
- log.info(f" ✗ 方案1当天价差不符合{direction}方向要求")
|
|
|
- elif g.ma_gap_strategy_mode == 2:
|
|
|
- # 方案2:强制检查日内变化,使用专用阈值
|
|
|
- if direction == 'long' and intraday_diff_ratio >= g.ma_intraday_threshold_scheme2:
|
|
|
- log.info(f" ✓ 方案2多头日内变化检查通过 ({intraday_diff_ratio:.2%} >= {g.ma_intraday_threshold_scheme2:.2%})")
|
|
|
- should_open = True
|
|
|
- elif direction == 'short' and intraday_diff_ratio <= -g.ma_intraday_threshold_scheme2:
|
|
|
- log.info(f" ✓ 方案2空头日内变化检查通过 ({intraday_diff_ratio:.2%} <= {-g.ma_intraday_threshold_scheme2:.2%})")
|
|
|
- should_open = True
|
|
|
- else:
|
|
|
- log.info(f" ✗ 方案2日内变化不符合{direction}方向要求(阈值: ±{g.ma_intraday_threshold_scheme2:.2%})")
|
|
|
-
|
|
|
- if should_open:
|
|
|
- # 执行开仓
|
|
|
- log.info(f" 准备开仓: {symbol} {direction}")
|
|
|
- target_hands = calculate_target_hands(context, dominant_future, direction)
|
|
|
-
|
|
|
- if target_hands > 0:
|
|
|
- success = open_position(context, dominant_future, target_hands, direction,
|
|
|
- f'均线形态开仓')
|
|
|
- if success:
|
|
|
- log.info(f" ✓✓ {symbol} 开仓成功,从候选列表移除")
|
|
|
- candidates_to_remove.append(dominant_future)
|
|
|
- else:
|
|
|
- log.warning(f" ✗ {symbol} 开仓失败")
|
|
|
- else:
|
|
|
- log.warning(f" ✗ {symbol} 计算目标手数为0,跳过开仓")
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- log.warning(f"{dominant_future} 处理时出错: {str(e)}")
|
|
|
- continue
|
|
|
-
|
|
|
- # 从候选列表中移除已开仓的品种
|
|
|
- for future in candidates_to_remove:
|
|
|
- if future in g.daily_ma_candidates:
|
|
|
- del g.daily_ma_candidates[future]
|
|
|
-
|
|
|
- log.info(f"剩余候选品种: {list(g.daily_ma_candidates.keys())}")
|
|
|
- log.info("=" * 60)
|
|
|
-
|
|
|
-def check_stop_loss_profit(context):
|
|
|
- """阶段三:止损止盈检查(所有时间点)"""
|
|
|
+def check_open_and_stop(context):
|
|
|
+ """统一的开仓和止损止盈检查函数"""
|
|
|
# 先检查换月移仓
|
|
|
position_auto_switch(context)
|
|
|
|
|
|
@@ -586,7 +512,120 @@ def check_stop_loss_profit(context):
|
|
|
# 判断是否为夜盘时间
|
|
|
is_night_session = (current_time in ['21', '22', '23', '00', '01', '02'])
|
|
|
|
|
|
- # 遍历所有持仓进行止损止盈检查
|
|
|
+ # 第一步:检查开仓条件
|
|
|
+ if g.daily_ma_candidates:
|
|
|
+ log.info("=" * 60)
|
|
|
+ log.info(f"执行开仓检查 - 时间: {context.current_dt}, 候选品种数量: {len(g.daily_ma_candidates)}")
|
|
|
+
|
|
|
+ # 遍历候选品种
|
|
|
+ candidates_to_remove = []
|
|
|
+
|
|
|
+ for dominant_future, candidate_info in g.daily_ma_candidates.items():
|
|
|
+ try:
|
|
|
+ symbol = candidate_info['symbol']
|
|
|
+ direction = candidate_info['direction']
|
|
|
+ open_price = candidate_info['open_price']
|
|
|
+ yesterday_close = candidate_info.get('yesterday_close')
|
|
|
+ yesterday_open = candidate_info.get('yesterday_open')
|
|
|
+
|
|
|
+ # 检查是否已有持仓
|
|
|
+ if check_symbol_prefix_match(dominant_future, set(g.trade_history.keys())):
|
|
|
+ log.info(f"{symbol} 已有持仓,从候选列表移除")
|
|
|
+ candidates_to_remove.append(dominant_future)
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 获取当前价格
|
|
|
+ current_data = get_current_data()[dominant_future]
|
|
|
+ current_price = current_data.last_price
|
|
|
+
|
|
|
+ # 计算当天价差
|
|
|
+ intraday_diff = current_price - open_price
|
|
|
+ intraday_diff_ratio = intraday_diff / open_price
|
|
|
+
|
|
|
+ log.info(f"{symbol}({dominant_future}) 开仓条件检查:")
|
|
|
+ log.info(f" 方向: {direction}, 开盘价: {open_price:.2f}, 当前价: {current_price:.2f}, "
|
|
|
+ f"当天价差: {intraday_diff:.2f}, 变化比例: {intraday_diff_ratio:.2%}")
|
|
|
+
|
|
|
+ # 判断是否满足开仓条件
|
|
|
+ should_open = False
|
|
|
+
|
|
|
+ if g.ma_gap_strategy_mode == 1:
|
|
|
+ # 方案1:根据参数决定是否检查日内价差
|
|
|
+ if not g.check_intraday_spread:
|
|
|
+ log.info(f" 方案1跳过日内价差检查(check_intraday_spread=False)")
|
|
|
+ should_open = True
|
|
|
+ elif direction == 'long' and intraday_diff > 0:
|
|
|
+ log.info(f" ✓ 方案1多头当天价差检查通过 ({intraday_diff:.2f} > 0)")
|
|
|
+ should_open = True
|
|
|
+ elif direction == 'short' and intraday_diff < 0:
|
|
|
+ log.info(f" ✓ 方案1空头当天价差检查通过 ({intraday_diff:.2f} < 0)")
|
|
|
+ should_open = True
|
|
|
+ else:
|
|
|
+ log.info(f" ✗ 方案1当天价差不符合{direction}方向要求")
|
|
|
+
|
|
|
+ elif g.ma_gap_strategy_mode == 2:
|
|
|
+ # 方案2:强制检查日内变化,使用专用阈值
|
|
|
+ if direction == 'long' and intraday_diff_ratio >= g.ma_intraday_threshold_scheme2:
|
|
|
+ log.info(f" ✓ 方案2多头日内变化检查通过 ({intraday_diff_ratio:.2%} >= {g.ma_intraday_threshold_scheme2:.2%})")
|
|
|
+ should_open = True
|
|
|
+ elif direction == 'short' and intraday_diff_ratio <= -g.ma_intraday_threshold_scheme2:
|
|
|
+ log.info(f" ✓ 方案2空头日内变化检查通过 ({intraday_diff_ratio:.2%} <= {-g.ma_intraday_threshold_scheme2:.2%})")
|
|
|
+ should_open = True
|
|
|
+ else:
|
|
|
+ log.info(f" ✗ 方案2日内变化不符合{direction}方向要求(阈值: ±{g.ma_intraday_threshold_scheme2:.2%})")
|
|
|
+
|
|
|
+ elif g.ma_gap_strategy_mode == 3:
|
|
|
+ # 方案3:下跳后上涨(多头)或上跳后下跌(空头),并检查当前价格与前一日开盘收盘均值的关系
|
|
|
+ if yesterday_open is not None and yesterday_close is not None:
|
|
|
+ prev_day_avg = (yesterday_open + yesterday_close) / 2
|
|
|
+ log.debug(f" 前一日开盘价: {yesterday_open:.2f}, 前一日收盘价: {yesterday_close:.2f}, 前一日开盘收盘均值: {prev_day_avg:.2f}")
|
|
|
+
|
|
|
+ if direction == 'long':
|
|
|
+ # 多头:当前价格 >= 前一日开盘收盘均值
|
|
|
+ if current_price >= prev_day_avg:
|
|
|
+ log.info(f" ✓ 方案3多头入场条件通过: 当前价 {current_price:.2f} >= 前日均值 {prev_day_avg:.2f}")
|
|
|
+ should_open = True
|
|
|
+ else:
|
|
|
+ log.info(f" ✗ 方案3多头入场条件未通过: 当前价 {current_price:.2f} < 前日均值 {prev_day_avg:.2f}")
|
|
|
+ elif direction == 'short':
|
|
|
+ # 空头:当前价格 <= 前一日开盘收盘均值
|
|
|
+ if current_price <= prev_day_avg:
|
|
|
+ log.info(f" ✓ 方案3空头入场条件通过: 当前价 {current_price:.2f} <= 前日均值 {prev_day_avg:.2f}")
|
|
|
+ should_open = True
|
|
|
+ else:
|
|
|
+ log.info(f" ✗ 方案3空头入场条件未通过: 当前价 {current_price:.2f} > 前日均值 {prev_day_avg:.2f}")
|
|
|
+ else:
|
|
|
+ log.info(f" ✗ 方案3缺少前一日开盘或收盘价数据")
|
|
|
+
|
|
|
+ if should_open:
|
|
|
+ # 执行开仓
|
|
|
+ log.info(f" 准备开仓: {symbol} {direction}")
|
|
|
+ target_hands = calculate_target_hands(context, dominant_future, direction)
|
|
|
+
|
|
|
+ if target_hands > 0:
|
|
|
+ success = open_position(context, dominant_future, target_hands, direction,
|
|
|
+ f'均线形态开仓')
|
|
|
+ if success:
|
|
|
+ log.info(f" ✓✓ {symbol} 开仓成功,从候选列表移除")
|
|
|
+ candidates_to_remove.append(dominant_future)
|
|
|
+ else:
|
|
|
+ log.warning(f" ✗ {symbol} 开仓失败")
|
|
|
+ else:
|
|
|
+ log.warning(f" ✗ {symbol} 计算目标手数为0,跳过开仓")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ log.warning(f"{dominant_future} 处理时出错: {str(e)}")
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 从候选列表中移除已开仓的品种
|
|
|
+ for future in candidates_to_remove:
|
|
|
+ if future in g.daily_ma_candidates:
|
|
|
+ del g.daily_ma_candidates[future]
|
|
|
+
|
|
|
+ log.info(f"剩余候选品种: {list(g.daily_ma_candidates.keys())}")
|
|
|
+ log.info("=" * 60)
|
|
|
+
|
|
|
+ # 第二步:检查止损止盈
|
|
|
subportfolio = context.subportfolios[0]
|
|
|
long_positions = list(subportfolio.long_positions.values())
|
|
|
short_positions = list(subportfolio.short_positions.values())
|
|
|
@@ -627,6 +666,13 @@ def check_position_stop_loss_profit(context, position):
|
|
|
direction = trade_info['direction']
|
|
|
entry_price = trade_info['entry_price']
|
|
|
entry_time = trade_info['entry_time']
|
|
|
+ entry_trading_day = trade_info.get('entry_trading_day')
|
|
|
+ if entry_trading_day is None:
|
|
|
+ entry_trading_day = get_current_trading_day(entry_time)
|
|
|
+ trade_info['entry_trading_day'] = entry_trading_day
|
|
|
+ if entry_trading_day is not None:
|
|
|
+ entry_trading_day = normalize_trade_day_value(entry_trading_day)
|
|
|
+ current_trading_day = normalize_trade_day_value(get_current_trading_day(context.current_dt))
|
|
|
current_price = position.price
|
|
|
|
|
|
# 计算当前盈亏比率
|
|
|
@@ -641,7 +687,11 @@ def check_position_stop_loss_profit(context, position):
|
|
|
f"成本价: {entry_price:.2f}, 当前价格: {current_price:.2f}")
|
|
|
close_position(context, security, direction)
|
|
|
return True
|
|
|
-
|
|
|
+
|
|
|
+ if entry_trading_day is not None and entry_trading_day == current_trading_day:
|
|
|
+ log.info(f"{security} 建仓交易日内跳过动态止盈检查")
|
|
|
+ return False
|
|
|
+
|
|
|
# 检查是否启用均线跟踪止盈
|
|
|
if not trade_info.get('ma_trailing_enabled', True):
|
|
|
return False
|
|
|
@@ -755,6 +805,42 @@ def calculate_ma_proximity_counts(data, periods, lookback_days):
|
|
|
return proximity_counts
|
|
|
|
|
|
|
|
|
+def calculate_extreme_trend_days(data, periods, lookback_days):
|
|
|
+ """统计过去 lookback_days 天收盘价相对所有均线的极端趋势天数"""
|
|
|
+ if len(data) < lookback_days:
|
|
|
+ return 0, 0
|
|
|
+
|
|
|
+ recent_closes = data['close'].iloc[-lookback_days:]
|
|
|
+ ma_series = {
|
|
|
+ period: data['close'].rolling(window=period).mean().iloc[-lookback_days:]
|
|
|
+ for period in periods
|
|
|
+ }
|
|
|
+
|
|
|
+ above_count = 0
|
|
|
+ below_count = 0
|
|
|
+
|
|
|
+ for idx, close_price in enumerate(recent_closes):
|
|
|
+ ma_values = []
|
|
|
+ valid = True
|
|
|
+
|
|
|
+ for period in periods:
|
|
|
+ ma_value = ma_series[period].iloc[idx]
|
|
|
+ if pd.isna(ma_value):
|
|
|
+ valid = False
|
|
|
+ break
|
|
|
+ ma_values.append(ma_value)
|
|
|
+
|
|
|
+ if not valid or not ma_values:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if all(close_price > ma_value for ma_value in ma_values):
|
|
|
+ above_count += 1
|
|
|
+ elif all(close_price < ma_value for ma_value in ma_values):
|
|
|
+ below_count += 1
|
|
|
+
|
|
|
+ return above_count, below_count
|
|
|
+
|
|
|
+
|
|
|
def check_ma_pattern(ma_values, direction):
|
|
|
"""检查均线排列模式是否符合方向要求
|
|
|
|
|
|
@@ -858,13 +944,15 @@ def open_position(context, security, target_hands, direction, reason=''):
|
|
|
})
|
|
|
|
|
|
# 记录交易信息
|
|
|
+ entry_trading_day = get_current_trading_day(context.current_dt)
|
|
|
g.trade_history[security] = {
|
|
|
'entry_price': order_price,
|
|
|
'target_hands': target_hands,
|
|
|
'actual_hands': order_amount,
|
|
|
'actual_margin': cash_change,
|
|
|
'direction': direction,
|
|
|
- 'entry_time': context.current_dt
|
|
|
+ 'entry_time': context.current_dt,
|
|
|
+ 'entry_trading_day': entry_trading_day
|
|
|
}
|
|
|
|
|
|
ma_trailing_enabled = True
|
|
|
@@ -941,6 +1029,35 @@ def get_multiplier(underlying_symbol, default_multiplier=10):
|
|
|
"""获取合约乘数的辅助函数"""
|
|
|
return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
|
|
|
|
|
|
+
|
|
|
+def has_reached_trading_start(current_dt, trading_start_time_str, has_night_session=False):
|
|
|
+ """判断当前是否已到达合约允许交易的起始时间"""
|
|
|
+ if not trading_start_time_str:
|
|
|
+ return True
|
|
|
+
|
|
|
+ try:
|
|
|
+ hour, minute = [int(part) for part in trading_start_time_str.split(':')[:2]]
|
|
|
+ except Exception:
|
|
|
+ return True
|
|
|
+
|
|
|
+ start_time = time(hour, minute)
|
|
|
+ current_time = current_dt.time()
|
|
|
+
|
|
|
+ if has_night_session:
|
|
|
+ if current_time >= start_time:
|
|
|
+ return True
|
|
|
+ if current_time < time(12, 0):
|
|
|
+ return True
|
|
|
+ if time(8, 30) <= current_time <= time(15, 30):
|
|
|
+ return True
|
|
|
+ return False
|
|
|
+
|
|
|
+ if current_time < start_time:
|
|
|
+ return False
|
|
|
+ if current_time >= time(20, 0):
|
|
|
+ return False
|
|
|
+ return True
|
|
|
+
|
|
|
def calculate_target_hands(context, security, direction):
|
|
|
"""计算目标开仓手数"""
|
|
|
current_price = get_current_data()[security].last_price
|
|
|
@@ -1060,7 +1177,19 @@ def position_auto_switch(context, pindex=0, switch_func=None, callback=None):
|
|
|
if not match:
|
|
|
raise ValueError("未知期货标的: {}".format(symbol))
|
|
|
else:
|
|
|
- dominant = get_dominant_future(match.groupdict()["underlying_symbol"])
|
|
|
+ underlying_symbol = match.groupdict()["underlying_symbol"]
|
|
|
+ trading_start = get_futures_config(underlying_symbol, 'trading_start_time', None)
|
|
|
+ has_night_session = get_futures_config(underlying_symbol, 'has_night_session', False)
|
|
|
+ log.debug(f"移仓换月: {symbol}, 交易开始时间: {trading_start}, 夜盘: {has_night_session}")
|
|
|
+ if trading_start and not has_reached_trading_start(context.current_dt, trading_start, has_night_session):
|
|
|
+ log.info("{} 当前时间 {} 未到达交易开始时间 {} (夜盘:{} ),跳过移仓".format(
|
|
|
+ symbol,
|
|
|
+ context.current_dt.strftime('%H:%M:%S'),
|
|
|
+ trading_start,
|
|
|
+ has_night_session
|
|
|
+ ))
|
|
|
+ continue
|
|
|
+ dominant = get_dominant_future(underlying_symbol)
|
|
|
cur = get_current_data()
|
|
|
symbol_last_price = cur[symbol].last_price
|
|
|
dominant_last_price = cur[dominant].last_price
|