VeighNa量化社区
你的开源社区量化交易平台 | vn.py | vnpy
Member
avatar
加入于:
帖子: 12
声望: 0

使用 vnpy_portfoliostrategy 策略,在 calculate(self, bars: Dict[str, BarData], freq) 方法中调用了self.rebalance_portfolio(bars),但在回测时发现并不一定能回调到 update_trade方法,调试发现在调用rebalance_portfolio时,也执行了 self.buy(vt_symbol, order_price, buy_volume),本次回测为单个标的,数据库K线数据未发现缺失。calculate 函数代码如下:

def calculate(self, bars: Dict[str, BarData], freq):

    # 因calculate可能在多个周期K线更新时调用,上一笔交易未完成时直接返回
    if not self.trade_finish_signal:
        logging.debug(f"上一笔交易未完成")
        return

    for vt_symbol, bar in bars.items():
        if vt_symbol == None or bar == None:
            continue

        current_time = bar.datetime
        if current_time.hour >= 15:
            # 15点不做交易
            continue
        else:
            # 其它时间段只在15分钟级别上调用
            if freq != "15m":
                continue

        current_pos = self.holdings_pos.get(vt_symbol, SymbolPos.Empty)
        trade_type, pos = algo(vt_symbol, current_time, current_pos, self)
        if trade_type == TradeType.Unknown or pos == SymbolPos.Empty:
            continue

        if trade_type == TradeType.Buy:
            # 判断持仓标的数量是否超限
            is_new_target = vt_symbol not in self.holdings_pos
            if len(self.holdings_pos) >= self.holdings_max_count and is_new_target:
                logging.warning(f"持仓标的数量已达 {self.holdings_max_count} 个,剩余资金:{self.total_capital:.2f},不再买入")
                continue

            if current_pos + pos > SymbolPos.Full:
                logging.debug(f"已经有 {current_pos} 仓位,此次买入 {pos} 后将超满仓,减量买入")
                pos = SymbolPos.Full - current_pos

            self.buy_targets_pos[vt_symbol] = pos
            logging.debug(f"{vt_symbol} 买入 {pos} 仓位")

        elif trade_type == TradeType.Sell:
            record:StockRecord = self.get_record(vt_symbol)
            # total_vol = record.get_total_vol()
            total_vol = self.get_pos(vt_symbol)     # 当前总持仓
            sell_vol:int = 0
            sell_pos = SymbolPos.Empty
            index = 0

            while index < len(record.buy_info_list) and sell_pos < pos:
                item: BuyInfo = record.buy_info_list[index]

                # 检查是否是当天买入的
                if current_time and current_time.date() == item.buy_time.date():
                    logging.debug(f"{current_time:%F %X} 买入的 {item.buy_vol} 股不能卖出,跳过")
                    index += 1  # 移动到下一个元素
                    continue

                sell_vol += item.buy_vol
                sell_pos += item.buy_pos
                del record.buy_info_list[index]

            if sell_pos <= 0:
                continue

            dest_vol = total_vol - sell_vol
            self.set_target(vt_symbol, dest_vol)
            self.sell_targets_pos[vt_symbol] = sell_pos

    if len(self.sell_targets_pos) > 0:
        logging.debug("有卖出,先执行卖出,下次再执行买入操作")
        self.trade_finish_signal = False
        self.rebalance_portfolio(bars)
        self.put_event()
        return

    if len(self.buy_targets_pos) == 0:
        logging.debug("buy_targets_pos == 0,不执行买入")
        return

    # 计算能够买入的仓位
    for vt_symbol, pos in self.buy_targets_pos.items():
        # 当前标的已满仓
        current_pos = self.holdings_pos.get(vt_symbol, SymbolPos.Empty)
        if current_pos == SymbolPos.Full:
            logging.warning(f"{vt_symbol} 已满仓")
            continue

        # 计算平均资金买入数量
        cur_price = self.get_last_price(vt_symbol, 'h')
        if cur_price <= 0:
            dt = 0
            for vt_symbol, bar in bars.items():
                if isinstance(bar.datetime, datetime):
                    dt = bar.datetime
                    break
            logging.warning(f"异常!获取 {vt_symbol} 的最新价格为:{cur_price:.2f},时间:{dt:%F %X}")
            continue

        record:StockRecord = self.get_record(vt_symbol)

        # 每次买入前需要重新计算,去掉已经买入的标的
        total_holdings_pos = sum(self.holdings_pos.values())
        if self.capital_shares_num > total_holdings_pos + pos:
            self.target_capital = self.total_capital / (self.capital_shares_num - total_holdings_pos) * pos
        elif self.capital_shares_num == total_holdings_pos + pos:
            self.target_capital = self.total_capital
        else:
            self.target_capital = 0

        target = math.floor((self.target_capital / cur_price) / 100) * 100

        if target < 100:
            current_time = bars[vt_symbol].datetime
            logging.warning(f"资金不足。total_capital = {self.total_capital:.2f}, target_capital = {self.target_capital:.2f} 代码:{vt_symbol} 时间:{get_dt_string(current_time)}")
            continue

        cur_pos = self.get_pos(vt_symbol)
        target += cur_pos
        self.set_target(vt_symbol, target)
        logging.warning(f"{vt_symbol} cur_pos = {cur_pos}, target = {target}")

    # 执行买入 
    self.trade_finish_signal = False
    self.rebalance_portfolio(bars)
    self.put_event()
    logging.debug("after put_event")

部分日志输出如下:

2026-03-21 17:47:02 [ DEBUG line:5650 ] 600645.SSE 买入 3 仓位
2026-03-21 17:47:02 [ WARNING line:5732 ] 600645.SSE cur_pos = 0, target = 2300
2026-03-21 17:50:20 [ DEBUG line:5738 ] after put_event
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成
2026-03-21 17:50:20 [ DEBUG line:5612 ] 上一笔交易未完成

vnpy环境为2025年9月的版本(应该是3.9)请问大佬,这可能是什么原因?

Member
avatar
加入于:
帖子: 12
声望: 0

补充:
calculate函数是在 on_15min_bars 中调用的:

def on_15min_bars(self, bars: Dict[str, BarData]):
self.update_array_man_bars(bars, "15m")
self.calculate(bars, "15m") # 只在15分钟级别调用

on_15min_bars 在init函数中做了绑定:
self.pbg15 = PortfolioBarGenerator(self.on_bars, 15, self.on_15min_bars)

Member
avatar
加入于:
帖子: 6053
声望: 371

可以在cross_limit_order函数里打印排查看看

© 2015-2022 上海韦纳软件科技有限公司
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

【用户协议】
【隐私政策】
【免责条款】