3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个基于**期货期限结构(Term Structure)**的量化交易策略设计方案。
期货期限结构反映了同一品种不同交割月份合约的价格差异。
核心指标:展期收益率 (Roll Yield)
$$ \text{Roll Yield} = \frac{\ln(\text{近月价格}) - \ln(\text{远月价格})}{\text{远月到期时间} - \text{近月到期时间}} $$
或者简化版:
$$ \text{Roll Yield} = \frac{\text{近月价格} - \text{远月价格}}{\text{近月价格}} $$
以下代码实现了一个多品种期限结构策略。
注意:由于PTrade API中获取“某品种所有合约列表”的功能较为复杂,本示例采用手动指定/定期更新主力与次主力合约的方式,这是实盘中最稳健的做法。
import numpy as np
import pandas as pd
def initialize(context):
"""
策略初始化函数
"""
# 1. 设定基准和手续费
set_benchmark('000300.SS')
# 设置期货保证金比例(示例:10%)
set_margin_rate('RB', 0.10)
set_margin_rate('I', 0.10)
set_margin_rate('CU', 0.10)
set_margin_rate('TA', 0.10)
# 2. 定义交易品种的合约对 {品种: [近月主力, 远月次主力]}
# 注意:在实盘中,这些代码需要根据主力换月情况定期更新(例如每月更新一次)
# 这里仅为示例代码,请替换为当前实际交易的合约代码
g.future_pairs = {
'RB': ['RB2310.XSGE', 'RB2401.XSGE'], # 螺纹钢
'I': ['I2309.XDCE', 'I2401.XDCE'], # 铁矿石
'CU': ['CU2308.XSGE', 'CU2309.XSGE'], # 沪铜
'TA': ['TA309.XZCE', 'TA401.XZCE'] # PTA
}
# 提取所有涉及的合约加入股票池,以便获取行情
all_contracts = []
for pair in g.future_pairs.values():
all_contracts.extend(pair)
set_universe(all_contracts)
# 3. 策略参数
g.long_count = 1 # 做多排名前1的品种
g.short_count = 1 # 做空排名倒数第1的品种
g.trade_lot = 1 # 每次交易手数(简化演示,实盘应根据资金计算)
# 设置定时任务,每天开盘后运行一次(例如 10:00)
run_daily(context, trade_logic, time='10:00')
def get_roll_yield(context):
"""
计算展期收益率
返回 DataFrame: index=品种, columns=['roll_yield', 'near_contract']
"""
yields = {}
# 获取所有合约的快照数据
all_contracts = []
for pair in g.future_pairs.values():
all_contracts.extend(pair)
snapshots = get_snapshot(all_contracts)
for variety, pair in g.future_pairs.items():
near_c = pair[0]
far_c = pair[1]
# 检查数据有效性
if near_c not in snapshots or far_c not in snapshots:
continue
p_near = snapshots[near_c]['last_px']
p_far = snapshots[far_c]['last_px']
# 排除停牌或无价格的情况
if p_near <= 0 or p_far <= 0:
continue
# 计算简化版展期收益率: (近月 - 远月) / 近月
# 正值越大,Backwardation越强(适合做多)
# 负值越小,Contango越强(适合做空)
r_yield = (p_near - p_far) / p_near
yields[variety] = {
'roll_yield': r_yield,
'near_contract': near_c # 我们交易近月主力合约
}
# 记录日志
log.info("品种: %s, 近月: %s (%.2f), 远月: %s (%.2f), Yield: %.4f" % (
variety, near_c, p_near, far_c, p_far, r_yield
))
return pd.DataFrame(yields).T
def trade_logic(context):
"""
核心交易逻辑
"""
# 1. 获取因子数据
df_yield = get_roll_yield(context)
if df_yield.empty:
return
# 2. 排序
df_yield = df_yield.sort_values(by='roll_yield', ascending=False)
# 3. 筛选目标持仓
# 做多列表:收益率最高的 g.long_count 个
target_long = df_yield.head(g.long_count).index.tolist()
# 做空列表:收益率最低的 g.short_count 个
target_short = df_yield.tail(g.short_count).index.tolist()
# 获取具体的合约代码
target_long_contracts = [df_yield.loc[v, 'near_contract'] for v in target_long]
target_short_contracts = [df_yield.loc[v, 'near_contract'] for v in target_short]
log.info("目标做多品种: %s" % target_long)
log.info("目标做空品种: %s" % target_short)
# 4. 交易执行:先平仓非目标,再开仓目标
# --- 平仓逻辑 ---
# 遍历当前所有持仓
positions = context.portfolio.positions
for contract in list(positions.keys()):
pos = positions[contract]
# 如果有多单
if pos.long_amount > 0:
# 如果该合约不在目标做多列表中,平多
if contract not in target_long_contracts:
log.info("平多仓: %s" % contract)
sell_close(contract, pos.long_amount)
# 如果有空单
if pos.short_amount > 0:
# 如果该合约不在目标做空列表中,平空
if contract not in target_short_contracts:
log.info("平空仓: %s" % contract)
buy_close(contract, pos.short_amount)
# --- 开仓逻辑 ---
# 执行做多
for contract in target_long_contracts:
pos = get_position(contract)
# 如果当前没有多单,则开多
if pos.long_amount == 0:
log.info("开多仓: %s" % contract)
buy_open(contract, g.trade_lot)
# 执行做空
for contract in target_short_contracts:
pos = get_position(contract)
# 如果当前没有空单,则开空
if pos.short_amount == 0:
log.info("开空仓: %s" % contract)
sell_open(contract, g.trade_lot)
def handle_data(context, data):
"""
盘中tick级别处理(本策略为日级别,此处留空或用于风控)
"""
pass
g.future_pairs):
before_trading_start 中动态计算主力合约代码(如果具备相应算法),本例为了代码清晰,采用了字典配置方式。get_roll_yield):
(近月 - 远月) / 近月 的公式。buy_open/sell_close 等):
get_snapshot):
run_daily)时比 get_history 更具时效性。g.trade_lot = 1。实际应用中应根据账户总权益和各品种的保证金占用、波动率(ATR)来动态计算开仓手数。Q: 如何自动获取主力合约而不是手动输入?
A: PTrade API 中没有直接返回“当前主力合约”的函数。通常的做法是:
get_instruments 结合规则生成代码,或查询外部数据源)。open_interest(持仓量)。Q: 为什么使用 run_daily 而不是 handle_data?
A: 期限结构是基于日线级别的逻辑,不需要每分钟或每笔Tick都计算。使用 run_daily 定时在上午10点或下午收盘前运行一次,既节省计算资源,又能过滤掉开盘瞬间的噪音。