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

请教:能否实现手动开仓后,系统自动发出指定价平仓呢?譬如在成交价+1或者-1,指定价,不滑点那种。
我注册了simnow,然后安装了veighna station。我登录交易连接进去之后,完全是懵的。接下来如何实现一步步这个需求呢?有知道的大神吗?谢谢!

description

Super Moderator
avatar
加入于:
帖子: 112
声望: 12

CTA 策略模块会过滤掉非本策略发出的委托和成交,所以 on_trade 回调无法收到手动交易的成交推送。

以下是完整的解决方案:


解决方案:使用 ScriptTrader 轮询检测

改用 ScriptTrader 脚本交易模块,通过轮询持仓变化成交记录来检测手动交易,然后自动挂限价平仓单。

方案一:基于持仓变化(推荐,更稳定)

创建文件 auto_exit_script.py

"""
手动开仓自动平仓脚本
通过轮询持仓变化检测手动交易,自动挂指定价平仓单
"""

import time
from vnpy.trader.constant import Direction

def run(engine):
    """
    脚本主函数,由 ScriptTrader 调用
    """
    # ==================== 配置参数 ====================
    vt_symbol = "rb2505.SHFE"      # 修改为你交易的合约
    offset_price = 1.0             # 平仓偏移价格(盈利1个点)
    check_interval = 1             # 轮询间隔(秒)
    # =================================================

    # 状态记录
    last_positions = {}            # 上次持仓状态 {pos_id: volume}
    entry_prices = {}              # 开仓价格记录 {pos_id: price}
    exit_orders = {}               # 已挂平仓单记录 {pos_id: order_id}

    engine.write_log("=" * 60)
    engine.write_log("自动平仓脚本启动")
    engine.write_log(f"监控合约: {vt_symbol}")
    engine.write_log(f"平仓偏移: {offset_price} 个点")
    engine.write_log(f"轮询间隔: {check_interval} 秒")
    engine.write_log("=" * 60)

    # 主循环
    while engine.strategy_active:
        try:
            # 获取当前持仓
            current_positions = get_positions_dict(engine, vt_symbol)

            # 检测新开仓(持仓增加或新持仓)
            for pos_id, pos in current_positions.items():
                last_vol = last_positions.get(pos_id, 0)

                if pos.volume > last_vol:
                    # 发现新开仓或加仓
                    new_volume = pos.volume - last_vol
                    entry_price = pos.price  # 使用持仓均价作为开仓价

                    engine.write_log(f"【开仓检测】{pos_id} | "
                                   f"方向: {pos.direction.value} | "
                                   f"数量: {new_volume} | "
                                   f"均价: {entry_price}")

                    # 计算平仓价格
                    if pos.direction == Direction.LONG:
                        # 多头:在更高价格平仓(盈利)
                        exit_price = entry_price + offset_price
                        # 发送卖出平仓限价单
                        order_id = engine.sell(vt_symbol, exit_price, new_volume)
                        action = "多头平仓"
                    else:
                        # 空头:在更低价格平仓(盈利)
                        exit_price = entry_price - offset_price
                        # 发送买入平仓限价单
                        order_id = engine.cover(vt_symbol, exit_price, new_volume)
                        action = "空头平仓"

                    engine.write_log(f"【挂单】{action} | "
                                   f"价格: {exit_price} | "
                                   f"数量: {new_volume} | "
                                   f"订单号: {order_id}")

                    # 记录
                    entry_prices[pos_id] = entry_price
                    exit_orders[pos_id] = order_id

            # 检测已平仓(持仓减少或清零)
            for pos_id in list(last_positions.keys()):
                if pos_id not in current_positions:
                    engine.write_log(f"【持仓清零】{pos_id},清理记录")
                    entry_prices.pop(pos_id, None)
                    exit_orders.pop(pos_id, None)
                elif current_positions[pos_id].volume < last_positions[pos_id]:
                    engine.write_log(f"【部分平仓】{pos_id},更新记录")

            # 更新状态
            last_positions = {k: v.volume for k, v in current_positions.items()}

            # 休眠
            time.sleep(check_interval)

        except Exception as e:
            engine.write_log(f"【错误】{e}")
            time.sleep(check_interval)

    engine.write_log("脚本已停止")

def get_positions_dict(engine, vt_symbol: str) -> dict:
    """
    获取指定合约的持仓字典
    key: gateway_name + direction
    value: PositionData
    """
    positions = {}
    all_positions = engine.get_all_positions()

    if not all_positions:
        return positions

    for pos in all_positions:
        if pos.vt_symbol == vt_symbol:
            pos_id = f"{pos.gateway_name}_{pos.direction.value}"
            positions[pos_id] = pos

    return positions

方案二:基于成交记录(更精确,知道每笔成交价格)

如果需要在成交瞬间立即挂单,可以轮询成交记录:

"""
基于成交记录的自动平仓脚本
能精确获取每笔成交的价格,立即挂平仓单
"""

import time

def run(engine):
    # ==================== 配置参数 ====================
    vt_symbol = "rb2505.SHFE"
    offset_price = 1.0
    check_interval = 0.5           # 成交检测需要更频繁
    # =================================================

    processed_trades = set()       # 已处理的成交ID

    engine.write_log("=" * 60)
    engine.write_log("自动平仓脚本启动(基于成交记录)")
    engine.write_log(f"监控合约: {vt_symbol}")
    engine.write_log("=" * 60)

    while engine.strategy_active:
        try:
            # 通过 main_engine 获取所有成交
            all_trades = engine.main_engine.get_all_trades()

            for trade in all_trades:
                # 筛选条件:指定合约、未处理过、开仓成交
                if (trade.vt_symbol == vt_symbol and 
                    trade.tradeid not in processed_trades and
                    trade.offset.value == "开"):

                    processed_trades.add(trade.tradeid)

                    engine.write_log(f"【成交检测】"
                                   f"方向: {trade.direction.value} | "
                                   f"价格: {trade.price} | "
                                   f"数量: {trade.volume} | "
                                   f"时间: {trade.datetime}")

                    # 计算平仓价格并下单
                    if trade.direction.value == "多":
                        exit_price = trade.price + offset_price
                        order_id = engine.sell(vt_symbol, exit_price, trade.volume)
                        engine.write_log(f"【挂单】多头平仓 | 价格: {exit_price}")
                    else:
                        exit_price = trade.price - offset_price
                        order_id = engine.cover(vt_symbol, exit_price, trade.volume)
                        engine.write_log(f"【挂单】空头平仓 | 价格: {exit_price}")

            time.sleep(check_interval)

        except Exception as e:
            engine.write_log(f"【错误】{e}")
            time.sleep(check_interval)

使用步骤

第一步:连接 SimNow

在 VeighNa Station 中配置 CTP 连接:

配置项
用户名 6位 InvestorID(非手机号)
密码 修改后的密码
经纪商代码 9999
交易服务器 182.254.243.31:30001
行情服务器 182.254.243.31:30011
产品名称 simnow_client_test
授权编码 0000000000000000

路径:菜单栏【系统】->【连接CTP】

第二步:启动 ScriptTrader

方法 A:通过 VeighNa Station GUI

  1. 确保已加载 ScriptTrader 模块(在 vt_setting.json 中启用)
  2. 连接 CTP 成功后,点击【功能】->【脚本交易】
  3. 在脚本交易界面,选择 auto_exit_script.py
  4. 点击【运行】

方法 B:命令行运行

python -m vnpy_scripttrader auto_exit_script.py

第三步:测试

  1. 手动开仓:在 VeighNa 交易界面手动买入/卖出开仓
  2. 观察日志:脚本会检测到持仓变化并自动挂平仓单
  3. 查看委托:在【委托】组件中查看自动挂的限价平仓单
  4. 等待成交:当价格达到指定价位时自动成交

关键说明

1. 为什么用 ScriptTrader 而不是 CTA 策略?

对比项 CTA 策略 ScriptTrader
成交监听 只接收本策略成交(过滤外部) 可轮询所有成交/持仓
手动交易支持 ❌ 不支持 ✅ 支持
实现方式 事件驱动(on_trade) 主动轮询
适用场景 全自动策略 手动+自动辅助

2. 限价单确保不滑点

代码中使用:

  • engine.sell(vt_symbol, exit_price, volume) - 限价卖出平仓
  • engine.cover(vt_symbol, exit_price, volume) - 限价买入平仓

这些都是限价单(Limit Order),确保以指定价格或更好价格成交,不会产生滑点。

3. 参数调整建议

  • 螺纹钢(rb)offset_price = 1.0(1个点)
  • 股指期货(IF)offset_price = 10.0(10个点,即1个指数点)
  • 商品期货:根据品种波动性和最小变动价位调整

4. 注意事项

  1. SimNow 延迟:仿真环境持仓同步可能有 1-3 秒延迟
  2. 重复挂单:脚本已做去重处理,同一笔持仓不会重复挂单
  3. 部分成交:如果手动交易是部分成交,脚本会分别处理每笔
  4. 价格精度:确保 exit_price 符合合约的最小变动价位(如螺纹钢是 1,股指是 0.2)

总结

通过 ScriptTrader + 轮询检测 的方案,你可以实现:

  • ✅ 手动开仓后自动检测
  • ✅ 在成交价 ±1 的位置挂限价平仓单
  • ✅ 无滑点,指定价成交
  • ✅ 支持 SimNow 仿真和实盘

将代码保存为 .py 文件,在 VeighNa Station 的脚本交易模块中运行即可。

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

沪公网安备 31011502017034号

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