3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在聚宽(JoinQuant)平台进行多股组合调仓时,直接遍历股票池并调用 order_target_value 极易触发**资金不足(Insufficient Cash)**的警告。这是因为默认的执行顺序可能是先尝试买入新股票,而此时旧股票尚未卖出,导致可用资金(available_cash)不足。此外,若某些目标股因停牌、涨跌停无法买入,会导致整体仓位无法打满。
为了解决这一痛点,实战中必须采用**“先卖后买、动态计算、备选递补”**的精细化调仓控制流。
total_value)和可用资金(available_cash),重新计算每个买入标的的目标绝对价值。以下是一个完整的、可直接在聚宽回测运行的策略示例。该策略展示了如何在每月初进行再平衡,采用“先卖后买”并引入备选股票池递补的机制:
import pandas as pd
from jqdata import *
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 设定交易税费
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 策略参数
g.target_num = 5 # 目标持仓股票数量
# 每月第一个交易日开盘运行调仓
run_monthly(rebalance_portfolio, monthday=1, time='09:30')
def rebalance_portfolio(context):
log.info("====== 开始执行组合再平衡 ======")
# 1. 获取候选股票池与备选股票池
# 假设我们以中证500成分股作为选股池,按某种规则排序(此处简化为取前15只)
all_candidates = get_index_stocks('000905.XSHG')[:15]
# 过滤掉停牌、ST的股票
current_data = get_current_data()
valid_candidates = [s for s in all_candidates if not current_data[s].paused and not current_data[s].is_st]
# 首选目标买入列表(前5只)
preferred_targets = valid_candidates[:g.target_num]
# 备选递补列表(其余的作为备选)
backup_targets = valid_candidates[g.target_num:]
log.info("首选目标: %s" % preferred_targets)
log.info("备选目标: %s" % backup_targets[:5])
# 2. 区分“卖出”与“买入”队列
current_positions = list(context.portfolio.positions.keys())
# 需要完全清仓的股票
to_sell_completely = set(current_positions) - set(preferred_targets)
# 执行完全清仓(先卖)
for stock in to_sell_completely:
log.info("清仓卖出: %s" % stock)
order_target_value(stock, 0)
# 3. 重新评估可用资金并分配买入
# 此时卖出单已提交(回测中市价单会立即撮合回收资金)
total_value = context.portfolio.total_value
single_target_value = total_value / g.target_num # 每只股票计划分配的价值
active_holdings = [] # 记录最终成功建仓的股票
# 4. 尝试买入首选股票
for stock in preferred_targets:
# 检查是否可以交易
if current_data[stock].paused or current_data[stock].last_price == 0:
continue
# 计算当前持仓价值
current_val = context.portfolio.positions[stock].value
# 如果当前价值与目标价值差异较大,进行调仓
if abs(current_val - single_target_value) > (single_target_value * 0.1):
order_target_value(stock, single_target_value)
log.info("调仓/买入首选股: %s, 目标价值: %.2f" % (stock, single_target_value))
active_holdings.append(stock)
# 5. 备选递补机制
# 如果首选股有因停牌等原因未买入的,用备选股补齐至 g.target_num 只
needed_count = g.target_num - len(active_holdings)
if needed_count > 0:
log.info("首选股未满,启动备选递补,需递补数量: %d" % needed_count)
for backup_stock in backup_targets:
if needed_count <= 0:
break
if backup_stock in context.portfolio.positions:
continue # 已持有的不重复买入
if not current_data[backup_stock].paused:
order_target_value(backup_stock, single_target_value)
log.info("递补买入备选股: %s, 目标价值: %.2f" % (backup_stock, single_target_value))
active_holdings.append(backup_stock)
needed_count -= 1
log.info("====== 调仓结束,当前持仓: %s ======" % list(context.portfolio.positions.keys()))
order_target_value(stock, 0) 市价单会立即同步撮合,资金瞬间到账,因此紧接着的买入操作可以直接使用这部分资金。但在实盘或高仿真模拟盘中,订单提交到撮合成交有时间差,建议在卖出后,通过事件回调或在下一个分钟 Bar 再进行买入操作,以防资金未及时到账。get_current_data():get_current_data() 获取当日的 paused(停牌)和 is_st(ST)状态,防止将资金浪费在无法交易的标的上。g.target_num 份。在实际极端情况下,由于印花税和佣金扣除,最后一只股票可能会因差几块钱而无法完全买入。可以适当将 single_target_value 乘以 0.99(预留 1% 的安全资金缓冲)。