3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个非常经典的动量策略(Momentum Strategy)。
以下是基于 QMT Python API 的完整策略代码。
# -*- coding: gbk -*-
import pandas as pd
import time
def init(ContextInfo):
# 1. 设置股票池,这里示例使用沪深300,您可以修改为 '我的自选' 等
# 注意:实盘或回测前请确保已下载相关板块的历史数据
ContextInfo.index_code = '000300.SH'
ContextInfo.stock_list = ContextInfo.get_stock_list_in_sector('沪深300')
# 2. 设置策略参数
ContextInfo.lookback_days = 20 # 回溯天数:20天
ContextInfo.select_ratio = 0.2 # 筛选比例:前20%
ContextInfo.hold_percent = 0.98 # 目标总仓位(留一点现金防止手续费不够)
# 3. 记录上一次调仓的月份,用于判断是否换月
ContextInfo.last_month = None
# 4. 设置资金账号 (请修改为您自己的资金账号)
# ContextInfo.set_account('您的资金账号')
def handlebar(ContextInfo):
# 获取当前K线的时间
current_timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
current_date_str = timetag_to_datetime(current_timetag, '%Y%m%d')
current_month = current_date_str[0:6] # 提取年份和月份,如 '202305'
# 判断是否换月:如果当前月份与上一次记录的月份不同,则触发调仓
# 注意:这里是在每个月出现的第一个K线(即月初)进行调仓
if current_month != ContextInfo.last_month:
print(f'检测到月份变化,开始执行调仓逻辑。当前日期: {current_date_str}')
rebalance(ContextInfo)
ContextInfo.last_month = current_month
def rebalance(ContextInfo):
# 1. 获取历史行情数据
# 我们需要获取过去21天的数据(包含今天),以便计算 (今天收盘 - 20天前收盘) / 20天前收盘
# dividend_type='front' 使用前复权数据,保证收益率计算准确
count = ContextInfo.lookback_days + 1
market_data = ContextInfo.get_market_data_ex(
['close'],
ContextInfo.stock_list,
period='1d',
count=count,
dividend_type='front'
)
# 2. 计算涨幅
momentum_data = []
for stock in ContextInfo.stock_list:
if stock in market_data:
df = market_data[stock]
# 确保数据长度足够
if len(df) >= count:
price_now = df.iloc[-1]['close'] # 最新收盘价
price_prev = df.iloc[0]['close'] # 20天前的收盘价
# 避免除以0或停牌导致的数据异常
if price_prev > 0 and price_now > 0:
pct_change = (price_now - price_prev) / price_prev
momentum_data.append({
'code': stock,
'pct_change': pct_change
})
# 如果没有有效数据,直接返回
if not momentum_data:
print("未获取到有效行情数据,跳过本次调仓")
return
# 3. 排序与筛选
# 按涨幅从大到小排序
df_momentum = pd.DataFrame(momentum_data)
df_momentum = df_momentum.sort_values(by='pct_change', ascending=False)
# 计算买入数量
target_count = int(len(df_momentum) * ContextInfo.select_ratio)
if target_count == 0:
target_count = 1
# 获取目标买入列表(前20%)
target_list = df_momentum.iloc[:target_count]['code'].tolist()
print(f"本次选股数量: {len(target_list)}, 排名首位: {target_list[0]}")
# 4. 执行交易
# 获取当前持仓
positions = ContextInfo.get_trade_detail_data(ContextInfo.accid, 'stock', 'position')
current_holding_codes = [pos.m_strInstrumentID + '.' + pos.m_strExchangeID for pos in positions]
# 4.1 卖出逻辑:不在目标列表中的股票全部卖出
# 这涵盖了“卖出排名后20%”以及“卖出中间排名”的股票,只保留最强的
for stock in current_holding_codes:
if stock not in target_list:
# 目标价值设为0,即清仓
order_target_value(stock, 0, ContextInfo, ContextInfo.accid)
print(f"卖出(不在前20%): {stock}")
# 4.2 买入逻辑:对目标列表中的股票进行等权买入
# 获取当前账户总资产
account_info = ContextInfo.get_trade_detail_data(ContextInfo.accid, 'stock', 'account')
if len(account_info) > 0:
total_asset = account_info[0].m_dBalance
# 计算每只股票的目标持仓市值
# 留出少量资金防止手续费不足导致废单
target_value_per_stock = (total_asset * ContextInfo.hold_percent) / len(target_list)
for stock in target_list:
order_target_value(stock, target_value_per_stock, ContextInfo, ContextInfo.accid)
print(f"买入/调仓: {stock}, 目标市值: {target_value_per_stock:.2f}")
get_market_data_ex:
count=21:因为要计算过去20天的涨幅,我们需要第0天(20天前)和第20天(今天)的数据,所以总共需要取21根K线。dividend_type='front':非常重要。计算收益率必须使用前复权数据,否则分红派息会导致价格跳空,从而计算出错误的暴跌。调仓逻辑 (rebalance):
交易函数 (order_target_value):
order_target_value(目标价值下单)比指定股数下单更方便。它会自动计算需要买入或卖出多少股才能达到设定的金额。运行频率:
if current_month != ContextInfo.last_month: 逻辑保证了虽然每天运行,但只在每个月的第一天触发交易。init 函数中,取消 ContextInfo.set_account 的注释,并填入您的真实资金账号。init 中修改 ContextInfo.stock_list 为自定义的代码列表。Q: 为什么不直接用 order_target_percent?
A: order_target_percent 在回测中很好用,但在实盘中,如果账户中有非策略持仓(比如你自己手动买的股票),直接用百分比可能会干扰其他持仓。使用 order_target_value 基于总资产计算具体金额通常更稳健。
Q: 如何改成每周调仓?
A: 可以将 handlebar 中的换月判断逻辑修改为判断周。例如使用 timetag_to_datetime 获取当前是周几,如果是周一且上一次交易不是周一,则触发调仓。
Q: 为什么卖出逻辑是“不在目标列表中就卖出”?
A: 题目要求是“卖掉排名后20%,买入排名前20%”。如果只执行这个操作,那么中间60%的股票会一直滞留在账户里,导致资金被占用,无法最大化利用动量效应。标准的动量策略通常是轮动,即始终持有最强的那部分。我的代码实现的是标准的轮动逻辑。