问题描述
模拟交易和实盘交易共享同一账户,资金、持仓无法隔离,多策略同时运行可能导致重复交易或逻辑冲突。
解决方案
在 Ptrade 量化交易平台中,由于模拟交易和实盘交易通常共享同一个底层资金账户,当多个策略同时运行时,极易出现资金抢占和持仓逻辑冲突的问题。特别是当多个策略操作同一只股票,或者使用了依赖全局持仓的下单函数时,干扰尤为严重。
为什么会产生多策略干扰?
- 全局持仓依赖:如
order_target和order_target_value函数会读取账户的总持仓。如果策略 A 买入了 100 股,策略 B 想要买入到 100 股,策略 B 发现账户里已经有 100 股了,就不会再买入,导致策略 B 逻辑失效。 - 资金共享:策略 A 可能把账户里的可用资金全部用完,导致策略 B 触发买入信号时因资金不足而废单。
解决多策略并发干扰的实用方案
1. 标的隔离(最简单有效)
最直接的方法是让不同的策略操作完全不同的股票池。例如,策略 A 专门做沪深 300 成分股,策略 B 专门做中证 500 成分股。只要标的不重合,就不会出现持仓冲突。
2. 弃用目标仓位函数,改用相对增减函数
在多策略环境下,强烈建议不要使用 order_target 和 order_target_value。请改用 order(按数量买卖)或 order_value(按金额买卖)。
3. 策略内部维护虚拟持仓与资金(逻辑隔离)
如果多个策略必须操作同一只股票,就必须在策略内部(通过全局变量 g)维护该策略专属的虚拟资金和虚拟持仓,并结合 pickle 模块进行持久化保存,防止重启丢失。
代码示例:基于持久化的虚拟持仓管理
以下示例展示了如何在策略内部维护专属的持仓记录,避免受账户真实总持仓的影响:
import pickle
from collections import defaultdict
NOTEBOOK_PATH = get_research_path()
PKL_FILE = NOTEBOOK_PATH + 'strategy_A_positions.pkl'
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
# 尝试加载策略专属的虚拟持仓
try:
with open(PKL_FILE, 'rb') as f:
g.my_positions = pickle.load(f)
except:
# 如果没有记录,初始化为空字典
g.my_positions = defaultdict(int)
# 为本策略分配虚拟初始资金
if 'virtual_cash' not in g.my_positions:
g.my_positions['virtual_cash'] = 100000.0
def handle_data(context, data):
security = g.security
current_price = data[security]['close']
# 获取策略专属持仓和资金
my_amount = g.my_positions.get(security, 0)
my_cash = g.my_positions['virtual_cash']
# 简单的买入逻辑:如果没有持仓且虚拟资金充足
if my_amount == 0 and my_cash > current_price * 100:
order(security, 100)
# 更新虚拟持仓和资金
g.my_positions[security] += 100
g.my_positions['virtual_cash'] -= current_price * 100
log.info("策略A买入 %s 100股" % security)
# 简单的卖出逻辑:如果有持仓
elif my_amount > 0 and current_price > 50: # 假设50为卖出条件
order(security, -my_amount)
# 更新虚拟持仓和资金
g.my_positions['virtual_cash'] += current_price * my_amount
g.my_positions[security] = 0
log.info("策略A卖出 %s 全部专属持仓" % security)
# 每天收盘前持久化保存虚拟持仓状态
with open(PKL_FILE, 'wb') as f:
pickle.dump(g.my_positions, f, -1)
总结建议
- 资金分配:在
initialize中为每个策略硬编码分配最大可用资金,并在下单前校验,防止单个策略耗尽账户资金。 - 主推回调处理:如果使用了
on_order_response或on_trade_response,请注意 Ptrade 默认可能会推送非本策略产生的订单(可通过set_parameters(receive_other_response="0")关闭),务必通过维护策略自身的order_id列表来过滤属于本策略的订单。