Skip to content

9.3 模拟与实盘对接

概念详解

券商API是将量化策略从回测环境连接到真实市场的桥梁。通过券商API,策略可以实现自动化下单、撤单、查询持仓和获取成交回报等功能。不同市场和券商提供的API差异显著,选择合适的接口方案对策略执行的质量和可靠性有直接影响。

主要的券商接口类型:

海外市场接口

  • Interactive Brokers (IB) 盈透证券的TWS/IB Gateway API提供了最全面的全球市场接入能力,支持股票、期货、期权、外汇、债券等100+市场。其API支持Python(ibapi)、C++、Java、C#等语言。
  • Alpaca 专注于美股算法交易的现代化券商,提供简洁的REST API和WebSocket实时行情。适合个人量化交易者。
  • TD Ameritrade / Charles Schwab 提供美股交易API,对个人投资者友好。
  • FIX Protocol 金融信息交换协议,是机构交易的标准通信协议,支持全球所有主要市场和券商。

中国市场接口

  • CTP(综合交易平台) 中国期货市场统一接入协议,由上期技术开发,覆盖国内四大期货交易所。CTP API是C++编写的,通过原生C++接口或第三方封装(如openctp、vnpy)使用。
  • XTTP / 华锐 股票市场的极速交易柜台,提供亚微秒级延迟的订单执行。
  • QMT / MiniQMT 迅投(国金证券等)提供的量化交易终端和API,支持A股股票交易。
  • PTrade 恒生电子开发的量化交易平台,支持股票和ETF交易。

模拟交易 vs 实盘交易

  • 模拟交易(Paper Trading)在券商的模拟环境中执行订单,使用真实行情但虚拟资金。用于验证策略逻辑和系统稳定性,无资金风险。
  • 实盘交易(Live Trading)使用真实资金和真实市场执行。除了策略逻辑外,还需要考虑流动性、市场冲击、执行延迟等现实因素。
名词解释:CTP

中国期货市场统一的交易接口协议,用于连接期货公司交易系统,支持国内商品期货和金融期货。

名词解释:FIX Protocol(Financial Information eXchange)

金融行业标准的电子通信协议,用于交易前、交易中和交易后的信息传递。FIX协议已在全球范围内被大多数交易所、券商和机构投资者采用。

名词解释:IB Gateway

盈透证券提供的轻量级服务程序,作为TWS(交易工作站)的替代方案,适合服务器端的自动化交易。它去除了GUI界面,占用资源更少。

数学原理

订单执行成功率与网络延迟

在实盘交易中,订单从发送到成交的端到端延迟包括:

$$T_{total} = T_{serialize} + T_{network} + T_{broker\_process} + T_{exchange\_match}$$

对于非HFT策略,Ttotal 通常在50-500毫秒之间。延迟过高将导致:

  • 限价单在价格变化后成为"过时报价"
  • 市价单因价格移动而产生额外滑点
  • 撤单指令到达时订单已成交

资金管理与头寸计算

给定账户资金 C,单只股票的最大持仓头寸可以通过以下方式计算:

$$MaxPosition = \min\left(\frac{C \times risk\_per\_position}{ATR \times multiplier}, \frac{C \times max\_concentration}{price}\right)$$

其中 ATR 是平均真实波幅(用于动态衡量风险)。

Python实战

📌 案例1:IB下单

PYTHON10 行 · 333 B
📄此处有展示代码10 行 · 333 B展开 ▼
python
from ibapi.client import EClient
class IBApp(EWrapper, EClient):
    def nextValidId(self, orderId):
        contract = Contract()
        contract.symbol = "AAPL"
        order = Order()
        order.action = "BUY"
        order.orderType = "MKT"
        order.totalQuantity = 100
        self.placeOrder(orderId, contract, order)
点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:类 `IBApp`。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。

📌 案例2:统一的券商API抽象层

PYTHON364 行 · 11.3 KB
📄此处有展示代码364 行 · 11.3 KB展开 ▼
python
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Dict, List, Optional, Callable
from enum import Enum
import threading
import time
import logging

class OrderSide(Enum):
    BUY = "BUY"
    SELL = "SELL"

class OrderType(Enum):
    MARKET = "MKT"
    LIMIT = "LMT"
    STOP = "STP"
    STOP_LIMIT = "STP_LMT"

class OrderStatus(Enum):
    PENDING = "PENDING"
    SUBMITTED = "SUBMITTED"
    PARTIALLY_FILLED = "PARTIAL"
    FILLED = "FILLED"
    CANCELLED = "CANCELLED"
    REJECTED = "REJECTED"

@dataclass
class Order:
    """标准订单结构"""
    order_id: str
    symbol: str
    side: OrderSide
    order_type: OrderType
    quantity: int
    limit_price: float = 0.0
    stop_price: float = 0.0
    status: OrderStatus = OrderStatus.PENDING
    filled_qty: int = 0
    avg_fill_price: float = 0.0
    timestamp: float = 0.0

@dataclass
class AccountInfo:
    """账户信息"""
    account_id: str
    buying_power: float
    total_cash: float
    net_liquidation: float
    unrealized_pnl: float
    realized_pnl: float

@dataclass
class Position:
    """持仓信息"""
    symbol: str
    quantity: int
    avg_cost: float
    market_price: float
    market_value: float
    unrealized_pnl: float

class BrokerAPI(ABC):
    """券商API抽象基类"""
    
    def __init__(self, name: str):
        self.name = name
        self.is_connected = False
        self.order_callbacks: List[Callable] = []
        self.trade_callbacks: List[Callable] = []
        self.logger = logging.getLogger(f"broker.{name}")
    
    # ---- 连接管理 ----
    
    @abstractmethod
    def connect(self, host: str, port: int, **kwargs) -> bool:
        """连接到券商服务器"""
        pass
    
    @abstractmethod
    def disconnect(self) -> bool:
        """断开连接"""
        pass
    
    # ---- 账户查询 ----
    
    @abstractmethod
    def get_account_info(self) -> Optional[AccountInfo]:
        """获取账户信息"""
        pass
    
    @abstractmethod
    def get_positions(self) -> List[Position]:
        """获取当前持仓列表"""
        pass
    
    # ---- 订单操作 ----
    
    @abstractmethod
    def place_order(self, order: Order) -> str:
        """下单,返回订单ID"""
        pass
    
    @abstractmethod
    def cancel_order(self, order_id: str) -> bool:
        """取消订单"""
        pass
    
    @abstractmethod
    def cancel_all_orders(self, symbol: str = None) -> int:
        """取消所有(或某标的的)未成交订单"""
        pass
    
    @abstractmethod
    def get_open_orders(self) -> List[Order]:
        """获取所有未成交订单"""
        pass
    
    @abstractmethod
    def get_order_status(self, order_id: str) -> Optional[Order]:
        """查询订单状态"""
        pass
    
    # ---- 回调注册 ----
    
    def on_order_update(self, callback: Callable):
        """注册订单状态更新回调"""
        self.order_callbacks.append(callback)
    
    def on_trade(self, callback: Callable):
        """注册成交回报回调"""
        self.trade_callbacks.append(callback)
    
    def _notify_order_update(self, order: Order):
        for cb in self.order_callbacks:
            try:
                cb(order)
            except Exception:
                pass
    
    def _notify_trade(self, order: Order):
        for cb in self.trade_callbacks:
            try:
                cb(order)
            except Exception:
                pass

# ==================== 模拟券商实现 ====================

class SimulatedBroker(BrokerAPI):
    """模拟券商 - 用于Paper Trading"""
    
    def __init__(self, name="SimBroker", initial_cash=1000000.0):
        super().__init__(name)
        self.initial_cash = initial_cash
        self.cash = initial_cash
        self.positions: Dict[str, Position] = {}
        self.orders: Dict[str, Order] = {}
        self.order_counter = 0
        self.filled_orders: List[Order] = []
        
        # 模拟成交线程
        self._running = False
        self._match_thread = None
    
    def connect(self, host='', port=0, **kwargs):
        self.is_connected = True
        self._running = True
        self._match_thread = threading.Thread(target=self._matching_loop, daemon=True)
        self._match_thread.start()
        self.logger.info(f"模拟券商连接成功 (初始资金: {self.cash:,.0f})")
        return True
    
    def disconnect(self):
        self._running = False
        self.is_connected = False
        self.logger.info("模拟券商断开")
        return True
    
    def _matching_loop(self):
        """模拟订单匹配循环"""
        while self._running:
            for order_id, order in list(self.orders.items()):
                if order.status in (OrderStatus.PENDING, OrderStatus.SUBMITTED):
                    # 模拟:80%概率成交,市价单100%成交
                    fill_prob = 1.0 if order.order_type == OrderType.MARKET else 0.8
                    if order_id in self.orders:  # 可能已被取消
                        if order.order_type == OrderType.MARKET:
                            self._fill_order(order_id, order.quantity)
                        else:
                            self._fill_order(order_id, order.quantity)
            time.sleep(1.0)
    
    def _fill_order(self, order_id: str, fill_qty: int):
        """模拟订单成交"""
        order = self.orders.get(order_id)
        if not order or order.status in (OrderStatus.FILLED, OrderStatus.CANCELLED):
            return
        
        # 更新订单状态
        order.filled_qty = fill_qty
        order.status = OrderStatus.FILLED
        order.avg_fill_price = order.limit_price if order.limit_price > 0 else 100.0
        
        # 更新持仓和现金
        if order.symbol not in self.positions:
            self.positions[order.symbol] = Position(
                symbol=order.symbol, quantity=0, avg_cost=0,
                market_price=order.avg_fill_price, market_value=0, unrealized_pnl=0
            )
        
        pos = self.positions[order.symbol]
        fill_value = fill_qty * order.avg_fill_price
        
        if order.side == OrderSide.BUY:
            pos.quantity += fill_qty
            total_cost = pos.quantity * pos.avg_cost + fill_value
            pos.avg_cost = total_cost / pos.quantity if pos.quantity > 0 else 0
            self.cash -= fill_value
        else:
            pos.quantity -= fill_qty
            self.cash += fill_value
        
        self.filled_orders.append(order)
        self._notify_trade(order)
    
    def place_order(self, order: Order) -> str:
        order_id = f"SIM_{self.order_counter:06d}"
        self.order_counter += 1
        order.order_id = order_id
        order.status = OrderStatus.SUBMITTED
        order.timestamp = time.time()
        self.orders[order_id] = order
        self.logger.info(f"下单: {order.symbol} {order.side.value} {order.quantity} "
                        f"({order.order_type.value}) [ID: {order_id}]")
        return order_id
    
    def cancel_order(self, order_id: str) -> bool:
        if order_id in self.orders:
            order = self.orders[order_id]
            if order.status not in (OrderStatus.FILLED, OrderStatus.CANCELLED):
                order.status = OrderStatus.CANCELLED
                self.logger.info(f"撤单: {order_id}")
                return True
        return False
    
    def cancel_all_orders(self, symbol: str = None) -> int:
        count = 0
        for oid, order in list(self.orders.items()):
            if symbol is None or order.symbol == symbol:
                if self.cancel_order(oid):
                    count += 1
        return count
    
    def get_account_info(self) -> AccountInfo:
        total_market_value = sum(
            p.quantity * p.market_price for p in self.positions.values()
        )
        net_liq = self.cash + total_market_value
        return AccountInfo(
            account_id="SIM_ACCOUNT",
            buying_power=self.cash * 2,  # 模拟2倍杠杆
            total_cash=self.cash,
            net_liquidation=net_liq,
            unrealized_pnl=sum(p.unrealized_pnl for p in self.positions.values()),
            realized_pnl=0.0
        )
    
    def get_positions(self) -> List[Position]:
        return list(self.positions.values())
    
    def get_open_orders(self) -> List[Order]:
        return [o for o in self.orders.values() 
                if o.status not in (OrderStatus.FILLED, OrderStatus.CANCELLED)]
    
    def get_order_status(self, order_id: str) -> Optional[Order]:
        return self.orders.get(order_id)

# ==================== 订单管理器 ====================

class OrderManager:
    """订单管理器:在策略逻辑和券商API之间建立缓冲层"""
    
    def __init__(self, broker: BrokerAPI):
        self.broker = broker
        self.pending_orders: Dict[str, Order] = {}
        self.order_history: List[Order] = []
        self.max_retries = 3
    
    def submit_order(self, order: Order) -> Optional[str]:
        """提交订单(含自动重试)"""
        for attempt in range(self.max_retries):
            try:
                order_id = self.broker.place_order(order)
                if order_id:
                    self.pending_orders[order_id] = order
                    return order_id
            except Exception as e:
                logging.error(f"下单失败 (尝试 {attempt+1}/{self.max_retries}): {e}")
                time.sleep(1.0)
        
        logging.error(f"订单提交彻底失败: {order.symbol} {order.side}")
        return None
    
    def reconcile(self) -> dict:
        """订单对账:确保本地记录与券商状态一致"""
        broker_orders = {}
        try:
            broker_orders = {
                o.order_id: o for o in self.broker.get_open_orders()
            }
        except Exception as e:
            logging.warning(f"获取券商订单失败: {e}")
        
        # 检查本地有但券商没有的订单
        for oid, local_order in list(self.pending_orders.items()):
            if oid not in broker_orders:
                # 订单可能已成交或已被取消
                self.order_history.append(local_order)
                del self.pending_orders[oid]
        
        return {
            'pending_count': len(self.pending_orders),
            'synced': len(broker_orders)
        }

# ==================== 使用示例 ====================

# 创建模拟券商进行Paper Trading
sim_broker = SimulatedBroker(initial_cash=500000)
sim_broker.connect()

order_mgr = OrderManager(sim_broker)

# 注册成交回调
def on_fill(order):
    print(f"  [成交] {order.symbol} {order.side.value} {order.filled_qty} @ {order.avg_fill_price:.2f}")

sim_broker.on_trade(on_fill)

# 发送订单
order1 = Order(
    order_id='', symbol='AAPL', side=OrderSide.BUY,
    order_type=OrderType.MARKET, quantity=100
)
order_mgr.submit_order(order1)

order2 = Order(
    order_id='', symbol='TSLA', side=OrderSide.SELL,
    order_type=OrderType.LIMIT, quantity=50, limit_price=250.0
)
order_mgr.submit_order(order2)

time.sleep(3)  # 等待成交

# 查看状态
acc_info = sim_broker.get_account_info()
positions = sim_broker.get_positions()

print(f"\n账户概览: 现金={acc_info.total_cash:,.0f}, 净值={acc_info.net_liquidation:,.0f}")
print(f"持仓数: {len(positions)}")
for pos in positions:
    print(f"  {pos.symbol}: {pos.quantity}股 @ {pos.avg_cost:.2f}")

sim_broker.disconnect()
点击展开可浏览运行结果
[成交] AAPL BUY 100 @ 100.00
  [成交] TSLA SELL 50 @ 250.00

账户概览: 现金=502,500, 净值=500,000
持仓数: 2
  AAPL: 100股 @ 100.00
  TSLA: -50股 @ 0.00

📌 案例3:CTA策略的CTP接口抽象(概念示例)

PYTHON67 行 · 2.2 KB
📄此处有展示代码67 行 · 2.2 KB展开 ▼
python
# CTP接口通常通过C++或Cython封装调用,以下为概念性API

class CTPTraderAdapter:
    """
    CTP交易接口适配器
    
    CTP是C++ API,在Python中通常通过以下方式调用:
    1. openctp - 开源的CTP Python封装
    2. vnpy - 量化交易框架的CTP Gateway
    3. 自建Cython/SWIG封装
    """
    
    def __init__(self, broker_id, user_id, password, 
                 tcp_address, udp_address):
        self.broker_id = broker_id
        self.user_id = user_id
        self.password = password
        
        # CTP连接信息
        self.trade_front = tcp_address    # 交易前置机地址
        self.quote_front = udp_address    # 行情前置机地址
        
        # Python侧状态
        self.is_trade_connected = False
        self.is_quote_connected = False
        self.request_id = 0
    
    # ---- 连接认证 ----
    def connect_trade(self):
        """连接交易前置机并进行用户认证"""
        # 实际: CThostFtdcTraderApi.CreateFtdcTraderApi()
        # -> RegisterFront(trade_front)
        # -> ReqUserLogin()
        pass
    
    # ---- 订单操作 ----  
    def insert_order(self, instrument_id, direction, offset_flag,
                     price, volume, order_type='0'):
        """
        插入订单
        direction: '0'买 '1'卖
        offset_flag: '0'开仓 '1'平仓 '3'平今
        order_type: '0'限价单
        """
        # 实际: ReqOrderInsert()
        req_id = self.request_id
        self.request_id += 1
        return req_id
    
    # ---- 查询 ----
    def query_position(self, instrument_id=''):
        """查询持仓"""
        # 实际: ReqQryInvestorPosition()
        pass
    
    def query_account(self):
        """查询资金"""
        # 实际: ReqQryTradingAccount()
        pass

# CTP的注意事项:
# 1. CTP有流量控制,每秒最多N个请求(根据期货公司不同)
# 2. 登录需要TradingDay确认,非交易日无法登录
# 3. 平今和平昨的区别影响期货交易的手续费和限仓
# 4. CTP有两个独立连接:行情(MD)和交易(Trader),需要分别连接
print("CTP接口适配器概念示例就绪")
print("  注意: CTP的实际使用需要C++运行时环境和期货公司BrokerID")
点击展开可浏览运行结果
CTP接口适配器概念示例就绪
  注意: CTP的实际使用需要C++运行时环境和期货公司BrokerID

常见误区

误区1:Paper Trading跑得好,实盘一定没问题。 Paper Trading不包含市场冲击、流动性限制和真实订单执行延迟。许多在模拟交易中表现完美的策略,在实盘中因为无法成交、滑点过大或订单被拒绝而表现糟糕。

误区2:所有券商API都一样。 不同券商API在稳定性、延迟、功能丰富度、费率等方面差异巨大。IB提供全球市场但API学习曲线陡峭;Alpaca简洁但仅限于美股;CTP高效但有流量控制等本土限制。

误区3:下单后等回执再发下一单。 同步等待回执是低效的。订单管理应使用异步模式(回调或消息队列),策略可以连续发送多笔订单,通过回执异步更新状态。

误区4:FIX协议一定比REST API快。 FIX的优势在于标准化和可靠性,而非速度。许多券商的REST API延迟实际上低于其FIX接口,尤其是在经过CDN加速的情况下。

误区5:账户资金足够就不用检查购买力。 账户中可能有冻结资金(如未成交限价单)、保证金要求变化、或隔夜持仓的维持保证金调整,导致实际的可用购买力远低于账户现金余额。

实战练习

练习1: 实现一个"多券商订单路由器":

  • 同时连接多个券商(如IB + Alpaca + 模拟券商)
  • 根据标的、订单大小和市场情况选择最优执行券商
  • 实现"Smart Order Routing":大单拆分后发往多个券商并行执行
  • 聚合各券商回执,统一汇报成交结果

练习2: 基于模拟券商实现完整的"Paper Trading"平台:

  • 模拟市场冲击(大单会推动价格)
  • 模拟流动性限制(价格档位深度)
  • 模拟交易费用和滑点
  • 支持定时回放历史行情进行Paper Trading
  • 对比Paper Trading的PnL与纯回测PnL的差异

练习3: 设计一个"订单熔断"机制:

  • 监控订单执行异常:连续N笔订单被拒绝、撤单率超过阈值、成交价格严重偏离预期
  • 触发熔断后:暂停所有新订单、发送警报、保留已有持仓
  • 实现熔断后的自动恢复流程(需人为确认还是可自动恢复?)
  • 设计熔断的层级:单策略级 vs 账户级 vs 全局级

延伸阅读

  1. Interactive Brokers API Documentation. TWS API v10.19. — 盈透API的完整参考文档。
  2. CTP API Documentation. 上期技术发布的综合交易平台API技术说明
  3. FIX Protocol Ltd. FIX Protocol Specification v5.0 SP2. — FIX协议的官方规范。
  4. Lopez de Prado, M. (2018). Advances in Financial Machine Learning. Wiley, Ch. 20 (Backtesting and Live Execution). — 从回测到实盘的关键注意事项。
  5. Harris, L. (2003). Trading and Exchanges. Ch. 17 (Brokerage Operations). — 券商后台运作的详细说明。

本章要点

  • 券商API是策略从回测到实盘的桥梁,不同市场和券商的API差异显著
  • IB(盈透)是全球市场最通用的API,CTP是中国期货市场的标准接入协议
  • Paper Trading(模拟交易)是实盘前的必要验证步骤,但不能完全替代实盘测试
  • 订单管理应使用异步模式处理成交回执,避免同步等待阻塞策略
  • Smart Order Routing能优化跨券商/跨场所的订单执行
  • 实盘前必须验证的核心能力:资金管理错误处理重连机制订单对账