问题描述
解决方案
在聚宽(JoinQuant)中实现“每周一固定调仓”的策略,核心在于使用 run_weekly 定时运行函数。
核心思路
- 初始化 (
initialize): 设置基准、手续费、滑点等,并定义要操作的股票池。 - 定时任务 (
run_weekly): 使用run_weekly(func, weekday=1, time='09:30')来指定在每周的第一个交易日(周一)开盘时执行调仓函数。 - 调仓逻辑 (
weekly_adjustment):- 卖出不在目标股票池中的持仓。
- 将资金等分,买入目标股票池中的股票(使用
order_target_value自动调整仓位)。
策略代码示例
以下是一个完整的策略代码,该策略设定了一个固定的股票池(如平安银行和贵州茅台),并在每周一开盘时将仓位调整为等权重持有。
# -*- coding: utf-8 -*-
# 导入函数库
from jqdata import *
def initialize(context):
# 1. 设定基准(这里使用沪深300)
set_benchmark('000300.XSHG')
# 2. 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 3. 设置手续费(股票:买入万三,卖出万三+千一印花税,最低5元)
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 4. 定义全局变量:目标股票池
# 这里示例选取了 平安银行(000001.XSHE) 和 贵州茅台(600519.XSHG)
g.target_stocks = ['000001.XSHE', '600519.XSHG']
# 5. 设定定时运行:每周一 09:30 执行 weekly_adjustment 函数
# weekday=1 代表周一,time='09:30' 代表开盘时运行
run_weekly(weekly_adjustment, weekday=1, time='09:30')
# 打印日志
log.info("策略初始化完成,将在每周一进行调仓")
def weekly_adjustment(context):
"""
周一调仓函数
"""
log.info("检测到周一,开始执行调仓...")
# 获取当前账户的总资产(现金 + 持仓价值)
total_value = context.portfolio.total_value
# 获取目标股票数量
target_count = len(g.target_stocks)
# 防止除以0的错误
if target_count == 0:
log.warn("目标股票池为空,不执行买入操作")
return
# 计算每只股票的目标持仓价值(等权重分配)
target_value_per_stock = total_value / target_count
# --- 步骤1:卖出操作 ---
# 遍历当前持仓,如果持仓股票不在目标池中,则清仓
for stock in context.portfolio.positions:
if stock not in g.target_stocks:
order_target_value(stock, 0)
log.info("卖出非目标股票: %s" % stock)
# --- 步骤2:买入/调整操作 ---
# 遍历目标股票池,将每只股票调整到目标价值
for stock in g.target_stocks:
# order_target_value 会自动计算买卖数量
# 如果持有价值低于目标价值,它会买入;如果高于,它会卖出
order_target_value(stock, target_value_per_stock)
log.info("调整股票 %s 持仓至目标价值: %.2f" % (stock, target_value_per_stock))
def before_trading_start(context):
"""
(可选) 每天开盘前运行,用于处理一些盘前逻辑或日志
"""
pass
def after_trading_end(context):
"""
(可选) 每天收盘后运行
"""
pass
代码关键点解析
-
run_weekly(func, weekday=1, ...):- 这是实现周频策略的关键 API。
weekday=1表示周一。如果周一休市(例如节假日),聚宽系统通常会顺延到该周的下一个交易日执行(取决于force参数,默认为 True 时会尝试在当周执行)。time='09:30'确保在开盘时刻立即触发信号。
-
order_target_value(security, value):- 这是最适合“调仓”的下单函数。
- 它不需要你计算要买多少股或卖多少股,你只需要告诉它“我希望这只股票最终持有多少钱”。
- 例如:如果你持有 8000 元的茅台,目标是 10000 元,它会自动买入 2000 元;如果持有 12000 元,它会自动卖出 2000 元。
-
资金分配逻辑:
total_value = context.portfolio.total_value获取当前账户总权益。target_value_per_stock = total_value / len(g.target_stocks)实现了简单的等权重分配。
常见问题 (Q&A)
Q: 如果周一是节假日怎么办?
A: run_weekly 函数在回测中,如果指定的 weekday 是非交易日,系统会根据该周剩余的交易日情况进行调整。通常情况下,如果周一休市,策略可能会在周二开盘运行(视具体年份的节假日安排和 API 内部逻辑而定),或者该周跳过。如果对调仓日期有极其严格的要求(必须是周一,否则不调),可以在函数内部增加 context.current_dt.isoweekday() == 1 的判断。
Q: 如何改成每月1号调仓?
A: 将 run_weekly 替换为 run_monthly(func, monthday=1, time='09:30') 即可。
Q: 为什么使用 order_target_value 而不是 order_value?
A: order_value 是“买入/卖出指定金额”,是增量操作;而 order_target_value 是“调整至指定金额”,是存量调整。在定期再平衡(Rebalance)策略中,我们需要根据股价波动将仓位恢复到设定比例,因此必须使用 order_target_value。