# -*- coding: utf-8 -*-
"""
QMT A股 T+1 高频因子轮动策略
策略名称:XGBoost多因子轮动 + 半仓隔夜 + 极端风控
作者:QuantTeam
日期:2025-11-25
核心逻辑:
1. 每日从近5日涨幅榜Top100中选股(剔除北交所、ST)
2. 使用500+因子XGBoost模型打分,选出20只股票(10+10形态多元)
3. 每只5%仓位,总仓位50%(半仓隔夜)
4. 9:24:50 集合竞价末期下单,确保成交
5. 账户总权益回撤 ≥3% 时自动全平(阈值可配置)
6. 支持手动紧急暂停(通过 context.emergency_stop = True)
"""
import datetime
import time
import os
import pandas as pd
from typing import List, Dict
# QMT 内置模块
from QMT import *
# ======================
# 全局参数配置
# ======================
MAX_STOCKS = 20 # 总持仓股票数(10+10)
POSITION_PER_STOCK = 0.05 # 单票仓位5%
OVERNIGHT_RATIO = 0.5 # 隔夜总仓位比例(50%)
DRAWDOWN_STOP_THRESHOLD = 0.03 # 全平触发阈值(3%,可由客户设置)
MIN_TRADE_AMOUNT = 5e7 # 最小日成交额(5000万)
RISING_STOCKS_LOOKBACK = 5 # 近5日涨幅榜
# ======================
# 策略主类
# ======================
class HighFrequencyFactorStrategy:
def __init__(self, context):
self.context = context
self.today = context.current_time.strftime("%Y-%m-%d")
self.account_id = context.account_id
self.log(f"策略初始化完成 | 日期: {self.today}")
# 初始化紧急停止标志(可通过外部设置)
if not hasattr(context, 'emergency_stop'):
context.emergency_stop = False
def log(self, msg):
"""统一日志输出"""
print(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}")
def get_rising_stocks_top100(self) -> List[str]:
"""获取近5日涨幅榜前100名股票(剔除北交所、ST)"""
try:
# 获取全市场股票(排除北交所:8开头)
all_a_stocks = [s for s in get_stock_list_in_sector('沪深A股') if not s.startswith('8')]
# 获取每只股票近5日累计涨幅
stock_returns = {}
for code in all_a_stocks:
try:
hist = get_market_data_ex(
stock_code=code,
period='1d',
count=RISING_STOCKS_LOOKBACK + 1,
fields=['close']
)
if len(hist) < RISING_STOCKS_LOOKBACK + 1:
continue
ret = (hist['close'].iloc[-1] - hist['close'].iloc[0]) / hist['close'].iloc[0]
stock_returns[code] = ret
except:
continue
# 按涨幅排序取前100
sorted_stocks = sorted(stock_returns.items(), key=lambda x: x[1], reverse=True)
top100 = [code for code, _ in sorted_stocks[:100]]
# 过滤ST、停牌、流动性不足
basic_info = get_stock_basic_info(top100, ['is_st', 'suspension'])
filtered = []
for code in top100:
info = basic_info[basic_info['sec_code'] == code]
if info.empty:
continue
if info.iloc[0]['is_st'] or info.iloc[0]['suspension']:
continue
# 流动性检查
try:
amount_hist = get_market_data_ex(code, '1d', 5, ['amount'])
if amount_hist['amount'].mean() < MIN_TRADE_AMOUNT:
continue
except:
continue
filtered.append(code)
self.log(f"涨幅榜初筛:{len(top100)} → 过滤后:{len(filtered)}")
return filtered[:100] # 确保不超过100
except Exception as e:
self.log(f"获取涨幅榜失败: {e}")
return []
def load_model_predictions(self, candidate_stocks: List[str]) -> List[str]:
"""
加载XGBoost模型预测结果(实际部署时由离线系统生成)
此处模拟:按随机打分排序(实际应替换为真实模型推理)
"""
# === 实际部署时替换以下代码 ===
# 假设每日18:00已生成 /data/predictions_{today}.csv
pred_file = f"/data/predictions_{self.today}.csv"
if os.path.exists(pred_file):
df = pd.read_csv(pred_file)
df = df[df['code'].isin(candidate_stocks)]
df = df.sort_values('score', ascending=False)
selected = df['code'].head(MAX_STOCKS).tolist()
self.log(f"加载模型预测结果: {len(selected)}只股票")
return selected
else:
self.log(f"未找到预测文件 {pred_file},回退至随机选股")
import random
random.shuffle(candidate_stocks)
return candidate_stocks[:MAX_STOCKS]
def calculate_total_equity(self) -> float:
"""计算账户总权益(现金 + 持仓市值)"""
account = get_account(self.account_id)
positions = get_position(self.account_id)
market_value = sum(pos.m_dMarketValue for pos in positions if pos.m_nVolume > 0)
return account.m_dAvailable + market_value
def check_drawdown_stop(self) -> bool:
"""检查是否触发全局回撤止损"""
if not hasattr(self.context, 'initial_equity'):
# 首日记录初始权益
self.context.initial_equity = self.calculate_total_equity()
return False
current_equity = self.calculate_total_equity()
peak = getattr(self.context, 'peak_equity', self.context.initial_equity)
# 更新历史峰值
if current_equity > peak:
self.context.peak_equity = current_equity
peak = current_equity
drawdown = (peak - current_equity) / peak
if drawdown >= DRAWDOWN_STOP_THRESHOLD:
self.log(f"⚠️ 触发全局回撤止损!当前回撤: {drawdown:.2%},阈值: {DRAWDOWN_STOP_THRESHOLD:.2%}")
return True
return False
def close_all_positions(self):
"""全仓平仓"""
positions = get_position(self.account_id)
for pos in positions:
if pos.m_nVolume > 0:
code = pos.m_strCode
quote = get_quote(code)
if quote and quote.m_dAskPrice1 > 0:
price = quote.m_dAskPrice1
order_id = order_stock(
account_id=self.account_id,
stock_code=code,
order_type=OrderType_Sell,
order_volume=pos.m_nVolume,
price=price,
strategy_name="DrawdownStop"
)
self.log(f"全平订单提交: {code}, {pos.m_nVolume}股, 价格: {price}, ID: {order_id}")
time.sleep(0.05)
def execute_buy_orders(self, buy_list: List[str]):
"""在9:24:50集合竞价末期下单买入"""
if self.context.emergency_stop:
self.log("⚠️ 紧急停止标志已激活,跳过买入")
return
total_cash = get_account(self.account_id).m_dAvailable
target_total_value = total_cash * OVERNIGHT_RATIO # 半仓
per_stock_value = target_total_value * POSITION_PER_STOCK # 每只5% of total
for code in buy_list:
try:
# 获取最新买一价(集合竞价期间可能为昨收)
quote = get_quote(code)
if not quote or quote.m_dBidPrice1 <= 0:
continue
price = quote.m_dBidPrice1
volume = int(per_stock_value // price // 100 * 100)
if volume < 100:
continue
order_id = order_stock(
account_id=self.account_id,
stock_code=code,
order_type=OrderType_Buy,
order_volume=volume,
price=price,
strategy_name="HF_Factor"
)
self.log(f"买入订单: {code}, {volume}股, 价格: {price}, ID: {order_id}")
time.sleep(0.05) # 避免下单过快
except Exception as e:
self.log(f"买入{code}失败: {e}")
def execute_half_sell_before_close(self):
"""收盘前卖出半仓(保留50%隔夜)"""
positions = get_position(self.account_id)
for pos in positions:
if pos.m_nVolume > 0:
code = pos.m_strCode
half_volume = pos.m_nVolume // 2
if half_volume < 100:
continue
quote = get_quote(code)
if quote and quote.m_dAskPrice1 > 0:
price = quote.m_dAskPrice1
order_id = order_stock(
account_id=self.account_id,
stock_code=code,
order_type=OrderType_Sell,
order_volume=half_volume,
price=price,
strategy_name="HalfSell"
)
self.log(f"半仓卖出: {code}, {half_volume}股, ID: {order_id}")
time.sleep(0.05)
def run(self):
"""主运行逻辑"""
ct = self.context.current_time
hour, minute, second = ct.hour, ct.minute, ct.second
# 紧急情况:人工触发停止
if self.context.emergency_stop:
if hour == 9 and minute == 25:
self.log("⚠️ 紧急停止生效,今日不交易")
return
# 步骤1:检查全局回撤止损(全天监控)
if self.check_drawdown_stop():
self.close_all_positions()
return
# 步骤2:早盘买入(9:24:50)
if hour == 9 and minute == 24 and second >= 50:
if not hasattr(self.context, 'has_bought_today'):
# 获取候选池
candidates = self.get_rising_stocks_top100()
if not candidates:
self.log("无有效候选股票,跳过买入")
return
# 加载模型预测结果
selected = self.load_model_predictions(candidates)
if selected:
self.execute_buy_orders(selected)
self.context.has_bought_today = True
self.log("✅ 今日买入完成")
# 步骤3:尾盘半仓卖出(14:55)
if hour == 14 and minute == 55 and not getattr(self.context, 'has_sold_half', False):
self.execute_half_sell_before_close()
self.context.has_sold_half = True
self.log("✅ 尾盘半仓卖出完成")
# ======================
# QMT 策略入口
# ======================
def init(context):
"""初始化:每天开盘前调用"""
context.strategy = HighFrequencyFactorStrategy(context)
# 可由客户动态设置止损阈值(示例)
# context.strategy.DRAWDOWN_STOP_THRESHOLD = 0.05 # 5%
def handlebar(context):
"""主循环:每秒调用"""
context.strategy.run()
# ======================
# 紧急停止接口(供人工调用)
# ======================
def emergency_stop(context):
"""外部调用此函数可立即停止交易"""
context.emergency_stop = True
print("🚨 紧急停止已激活!")
最新发布