title: CTP 实盘交易指南 description: 使用 CTP 接口进行中国期货实盘和模拟交易
CTP 实盘交易指南¶
CTP (Comprehensive Transaction Platform) 是中国期货市场最广泛使用的交易接口。本指南介绍如何使用 backtrader 的 CTP 模块进行实盘和模拟交易。
CTP 介绍¶
CTP 是上海期货信息技术有限公司开发的综合交易平台,为期货公司提供标准化的交易和行情接口。
功能特性¶
支持中国所有期货交易所 (上期所、大商所、郑商所、中金所、能源中心、广期所)
实时行情订阅 (Tick 数据)
订单报单、撤单、查询
持仓查询、资金查询
自动重连机制
SimNow 模拟环境支持
系统要求¶
# 安装依赖
pip install ctp-python akshare
# ctp-python: CTP 官方 C++ API 的 Python 包装
# akshare: 用于历史数据回填
```bash
## SimNow 模拟环境
SimNow 是上期技术提供的免费模拟交易环境,适合测试策略。
### 模拟环境地址
| 环境 | 交易服务器 | 行情服务器 | 数据时间 |
|------|-----------|-----------|---------|
| 电信 7x24 | `tcp://180.168.146.187:10101` | `tcp://180.168.146.187:10111` | 7x24 小时 |
| 电信 (最近) | `tcp://182.254.243.31:30001` | `tcp://182.254.243.31:30011` | 实盘时段 |
| 移动 7x24 | `tcp://180.168.146.187:10102` | `tcp://180.168.146.187:10112` | 7x24 小时 |
### 模拟账户配置
```python
# SimNow 默认配置
DEFAULT_BROKER_ID = "9999"
DEFAULT_APP_ID = "simnow_client_test"
DEFAULT_AUTH_CODE = "0000000000000000"
# 用户 ID 和密码需要在 SimNow 官网注册
# <https://www.simnow.com.cn/>
```bash
## 配置说明
### 基本配置参数
| 参数 | 说明 | 示例 |
|------|------|------|
| `td_front` | 交易前置地址 | `tcp://180.168.146.187:10101` |
| `md_front` | 行情前置地址 | `tcp://180.168.146.187:10111` |
| `broker_id` | 期货公司代码 | `"9999"` (SimNow) |
| `user_id` | 用户代码 | `"your_id"` |
| `password` | 密码 | `"your_password"` |
| `app_id` | 应用代码 | `"simnow_client_test"` |
| `auth_code` | 授权码 | `"0000000000000000"` |
### 合约代码格式
CTP 合约代码格式:`合约代码.交易所代码`
```python
"rb2501.SHFE" # 螺纹钢 2025 年 1 月,上期所
"IF2506.CFFEX" # 沪深 300 股指期货,中金所
"m2505.DCE" # 豆粕,大商所
"TA501.CZCE" # PTA,郑商所
"nr2411.INE" # 20 号胶,能源中心
"pb2501.GFEX" # 工业硅,广期所
```bash
### 交易所代码
| 交易所 | 代码 | 主要品种 |
|--------|------|---------|
| 上海期货交易所 | SHFE | 铜、铝、锌、铅、镍、锡、黄金、白银、螺纹钢、热卷、燃油、沥青、橡胶 |
| 大连商品交易所 | DCE | 豆粕、豆油、棕榈油、玉米、淀粉、焦炭、焦煤、铁矿石、聚乙烯、聚丙烯、PVC |
| 郑州商品交易所 | CZCE | 白糖、棉花、PTA、菜油、菜粕、甲醇、玻璃、纯碱、尿素、短纤 |
| 中国金融期货交易所 | CFFEX | 沪深 300 股指、上证 50 股指、中证 500 股指、国债期货 |
| 上海国际能源交易中心 | INE | 原油、20 号胶 |
| 广州期货交易所 | GFEX | 工业硅、碳酸锂 |
## 数据源设置
### 创建 CTP 数据源
```python
import backtrader as bt
# 方式 1: 通过 Store 创建
store = bt.stores.CTPStore(
td_front='tcp://180.168.146.187:10101',
md_front='tcp://180.168.146.187:10111',
broker_id='9999',
user_id='your_id',
password='your_password',
app_id='simnow_client_test',
auth_code='0000000000000000',
)
data = store.getdata(
dataname='rb2501.SHFE', # 合约代码
timeframe=bt.TimeFrame.Minutes, # 时间周期
compression=1, # 压缩比例
num_init_backfill=100, # 回填 K 线数量
)
```bash
### 数据源参数
| 参数 | 默认值 | 说明 |
|------|-------|------|
| `historical` | `False` | 是否只使用历史数据,不接收实时行情 |
| `num_init_backfill` | `100` | 历史 K 线回填数量 |
| `tick_mode` | `False` | 是否使用 Tick 模式 (每笔交易一根 K 线) |
| `backfill_retries` | `2` | 历史数据回填重试次数 |
### 添加到 Cerebro
```python
cerebro = bt.Cerebro()
# 添加数据
cerebro.adddata(data)
# 或直接添加多个合约
for symbol in ['rb2501.SHFE', 'IF2506.CFFEX', 'm2505.DCE']:
data = store.getdata(dataname=symbol, timeframe=bt.TimeFrame.Minutes)
cerebro.adddata(data)
```bash
## 经纪人设置
### 设置 CTP 经纪人
```python
# 方式 1: 通过 Store 获取
broker = store.getbroker()
cerebro.setbroker(broker)
# 方式 2: 直接设置
cerebro.setbroker(bt.brokers.CTPBroker(
td_front='tcp://180.168.146.187:10101',
md_front='tcp://180.168.146.187:10111',
broker_id='9999',
user_id='your_id',
password='your_password',
app_id='simnow_client_test',
auth_code='0000000000000000',
))
```bash
### 经纪人参数
| 参数 | 默认值 | 说明 |
|------|-------|------|
| `use_positions` | `True` | 启动时使用账户现有持仓 |
| `commission` | `0.0` | 每手手续费 (绝对值) |
| `stop_slippage_ticks` | `0.0` | 止损单最大滑点跳动数 |
## 订单管理
### 市价单
```python
class MyStrategy(bt.Strategy):
def next(self):
# 市价买入 1 手
self.buy(size=1)
# 市价卖出 1 手
self.sell(size=1)
```bash
### 限价单
```python
class MyStrategy(bt.Strategy):
def next(self):
# 限价买入
price = self.data.close[0] - 10 # 低于当前价格 10 点
self.buy(price=price, size=1)
# 限价卖出
price = self.data.close[0] + 10
self.sell(price=price, size=1)
```bash
### 止损单
```python
class MyStrategy(bt.Strategy):
def __init__(self):
self.entry_price = None
def next(self):
if not self.position:
# 开仓
self.buy(size=1)
self.entry_price = self.data.close[0]
else:
# 止损: 价格跌破开仓价 2%
stop_price = self.entry_price *0.98
self.sell(price=stop_price, exectype=bt.Order.Stop, size=1)
# 止损限价单
# self.sell(price=stop_price, plimit=stop_price-5,
# exectype=bt.Order.StopLimit, size=1)
```bash
### 撤单
```python
class MyStrategy(bt.Strategy):
def __init__(self):
self.order = None
def next(self):
if self.order:
# 撤销待处理订单
self.cancel(self.order)
self.order = None
# 下新单
self.order = self.buy(size=1)
```bash
### 订单状态监控
```python
class MyStrategy(bt.Strategy):
def notify_order(self, order):
"""订单状态变化通知"""
status = order.getstatusname()
if order.status in [order.Submitted, order.Accepted]:
print(f'订单已提交: {order.ref}')
elif order.status in [order.Completed]:
if order.isbuy():
print(f'买入成交: 价格={order.executed.price:.2f}, '
f'数量={order.executed.size}, 手续费={order.executed.comm:.2f}')
else:
print(f'卖出成交: 价格={order.executed.price:.2f}, '
f'数量={order.executed.size}, 手续费={order.executed.comm:.2f}')
elif order.status in [order.Canceled]:
print(f'订单已撤销: {order.ref}')
elif order.status in [order.Rejected]:
print(f'订单被拒绝: {order.ref}')
```bash
## 持仓管理
### 获取持仓信息
```python
class MyStrategy(bt.Strategy):
def next(self):
# 获取当前持仓
pos = self.getposition()
print(f'持仓数量: {pos.size}, 开仓均价: {pos.price:.2f}')
# 获取可用资金
cash = self.getcash()
print(f'可用资金: {cash:.2f}')
# 获取总资产
value = self.getvalue()
print(f'总资产: {value:.2f}')
```bash
### 自动平仓逻辑
```python
class MyStrategy(bt.Strategy):
params = (
('max_hold_bars', 10), # 最大持仓 K 线数
('target_profit_pct', 0.02), # 目标利润 2%
)
def __init__(self):
self.hold_bars = 0
self.entry_price = None
def next(self):
if not self.position:
self.buy(size=1)
self.entry_price = self.data.close[0]
self.hold_bars = 0
else:
self.hold_bars += 1
# 止盈
if self.data.close[0] >= self.entry_price*(1 + self.p.target_profit_pct):
self.sell(size=1)
return
# 时间止损
if self.hold_bars >= self.p.max_hold_bars:
self.sell(size=1)
```bash
## 完整代码示例
### 简单双均线策略
```python
# !/usr/bin/env python
"""CTP 实盘交易示例 - 双均线策略"""
import backtrader as bt
import logging
logging.basicConfig(level=logging.INFO)
class DualMovingAverage(bt.Strategy):
"""双均线策略"""
params = (
('fast_period', 5),
('slow_period', 20),
)
def __init__(self):
# 计算均线
self.fast_ma = bt.indicators.SMA(self.data.close, period=self.p.fast_period)
self.slow_ma = bt.indicators.SMA(self.data.close, period=self.p.slow_period)
self.crossover = bt.indicators.CrossOver(self.fast_ma, self.slow_ma)
self.order = None
def next(self):
# 只在无持仓时交易
if self.order:
return
if not self.position:
# 金叉买入
if self.crossover > 0:
self.order = self.buy(size=1)
print(f'[金叉] 买入: 价格={self.data.close[0]:.2f}')
else:
# 死叉卖出
if self.crossover < 0:
self.order = self.sell(size=1)
print(f'[死叉] 卖出: 价格={self.data.close[0]:.2f}')
def notify_order(self, order):
if order.status in [order.Completed]:
if order.isbuy():
print(f'[成交] 买入: 价格={order.executed.price:.2f}, '
f'数量={order.executed.size}')
else:
print(f'[成交] 卖出: 价格={order.executed.price:.2f}, '
f'数量={order.executed.size}')
self.order = None
def main():
# 创建 Cerebro
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(DualMovingAverage, fast_period=5, slow_period=20)
# 创建 CTP Store
store = bt.stores.CTPStore(
td_front='tcp://180.168.146.187:10101',
md_front='tcp://180.168.146.187:10111',
broker_id='9999',
user_id='your_id',
password='your_password',
app_id='simnow_client_test',
auth_code='0000000000000000',
)
# 添加数据
data = store.getdata(
dataname='rb2501.SHFE',
timeframe=bt.TimeFrame.Minutes,
compression=1,
num_init_backfill=100,
)
cerebro.adddata(data)
# 设置经纪人
cerebro.setbroker(store.getbroker())
# 设置初始资金
cerebro.broker.setcash(100000.0)
# 添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
print('='*50)
print('开始实盘交易...')
print(f'初始资金: {cerebro.broker.getvalue():.2f}')
print('='*50)
# 运行
try:
results = cerebro.run()
except KeyboardInterrupt:
print('\n 交易已停止')
print('='*50)
print(f'最终资金: {cerebro.broker.getvalue():.2f}')
print('='* 50)
if __name__ == '__main__':
main()
```bash
### 多品种策略
```python
# !/usr/bin/env python
"""多品种 CTP 实盘交易"""
import backtrader as bt
class MultiSymbolStrategy(bt.Strategy):
"""多品种策略"""
def __init__(self):
# 为每个数据源计算指标
for data in self.datas:
data.ma20 = bt.indicators.SMA(data.close, period=20)
def next(self):
for data in self.datas:
# 检查该品种是否有持仓
pos = self.getposition(data)
if not pos:
# 价格低于 20 日均线时买入
if data.close[0] < data.ma20[0]:
self.buy(data=data, size=1)
print(f'{data._name}: 买入 @ {data.close[0]:.2f}')
else:
# 价格高于 20 日均线时卖出
if data.close[0] > data.ma20[0]:
self.sell(data=data, size=1)
print(f'{data._name}: 卖出 @ {data.close[0]:.2f}')
def main():
cerebro = bt.Cerebro()
cerebro.addstrategy(MultiSymbolStrategy)
# 创建 Store
store = bt.stores.CTPStore(
td_front='tcp://180.168.146.187:10101',
md_front='tcp://180.168.146.187:10111',
broker_id='9999',
user_id='your_id',
password='your_password',
app_id='simnow_client_test',
auth_code='0000000000000000',
)
# 添加多个品种
symbols = ['rb2501.SHFE', 'm2505.DCE', 'IF2506.CFFEX']
for symbol in symbols:
data = store.getdata(
dataname=symbol,
timeframe=bt.TimeFrame.Minutes,
compression=5,
)
cerebro.adddata(data, name=symbol)
# 设置经纪人
cerebro.setbroker(store.getbroker())
cerebro.broker.setcash(1000000.0)
# 运行
cerebro.run()
if __name__ == '__main__':
main()
```bash
## 风险控制
### 单品种最大持仓
```python
class RiskControlStrategy(bt.Strategy):
params = (
('max_size', 5), # 单品种最大持仓手数
)
def next(self):
pos = self.getposition()
# 检查持仓限制
if abs(pos.size) >= self.p.max_size:
return
# 正常交易逻辑
if not self.position:
self.buy(size=1)
```bash
### 总持仓限制
```python
class TotalPositionLimitStrategy(bt.Strategy):
params = (
('max_total_size', 10), # 总持仓手数限制
)
def __init__(self):
self.total_position = 0
def next(self):
# 计算总持仓
self.total_position = sum(
abs(self.getposition(data).size) for data in self.datas
)
if self.total_position >= self.p.max_total_size:
return # 达到总持仓限制
# 正常交易逻辑
...
```bash
### 每日亏损限制
```python
class DailyLossLimitStrategy(bt.Strategy):
params = (
('max_daily_loss', 5000), # 最大日亏损
)
def __init__(self):
self.daily_pnl = 0
self.last_date = None
def next(self):
current_date = self.data.datetime.date(0)
# 新的一天,重置盈亏
if self.last_date != current_date:
self.daily_pnl = 0
self.last_date = current_date
# 检查日亏损限制
if self.daily_pnl <= -self.p.max_daily_loss:
print(f'达到日亏损限制 {self.p.max_daily_loss},停止交易')
# 平掉所有持仓
for data in self.datas:
pos = self.getposition(data)
if pos.size > 0:
self.sell(data=data, size=abs(pos.size))
elif pos.size < 0:
self.buy(data=data, size=abs(pos.size))
return
def notify_trade(self, trade):
if trade.isclosed:
self.daily_pnl += trade.pnl
```bash
## 断线重连
CTP 模块内置自动重连机制,连接断开后会自动尝试重新连接。
### 监听连接状态
```python
class ConnectionMonitorStrategy(bt.Strategy):
def __init__(self):
# 注册断线回调
self.o.store.on_disconnect(self.on_disconnect)
self.o.store.on_reconnect(self.on_reconnect)
def on_disconnect(self, reason):
print(f'连接断开: 原因代码={reason}')
def on_reconnect(self):
print('连接已恢复')
def next(self):
# 检查连接状态
if not self.o.store.is_connected:
print('未连接,等待重连...')
return
# 正常交易逻辑
...
```bash
## 故障排除
### 常见问题
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| 连接超时 | 服务器地址错误或网络问题 | 检查服务器地址,确认网络连接 |
| 登录失败 | 用户名密码错误或账户被禁用 | 检查账户信息,联系期货公司 |
| 订单被拒 | 持仓不足、资金不足或合约不允许 | 检查账户状态,确认合约可交易 |
| 行情断流 | 行情服务器问题 | 系统会自动重连 |
| 数据回填失败 | akshare 未安装或网络问题 | 安装 akshare,检查网络连接 |
### 错误代码
| 错误代码 | 说明 |
|---------|------|
| 0 | 成功 |
| 3 | 未找到请求的合约 |
| 11 | 不支持的功能 |
| 17 | 不支持的行情 |
| 26 | 账户不存在 |
| 39 | 客户端未登录 |
| 42 | 报单被拒绝 |
| 45 | 平仓数量超过持仓 |
| 47 | 客户交易权限被暂停 |
| 48 | 客户开户状态被拒绝 |
| 75 | 登录失败 (频繁登录被禁) |
| 91 | 合约不存在或已下架 |
### 调试技巧
```python
# 启用详细日志
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(message)s',
)
# 检查连接状态
store = bt.stores.CTPStore(...)
print(f'已连接: {store.is_connected}')
print(f'交易端已登录: {store.trader_spi.loggedin}')
print(f'行情端已登录: {store.md_spi.loggedin}')
# 检查账户信息
store.get_balance()
print(f'可用资金: {store.get_cash()}')
print(f'总资产: {store.get_value()}')
# 检查持仓
positions = store.get_positions()
for pos in positions:
print(f'{pos["instrument"]} {pos["direction"]} '
f'持仓:{pos["volume"]} 均价:{pos["avg_price"]:.2f}')
```bash
### 查询频率限制
CTP 接口有查询频率限制 (约 1 秒 1 次),频繁查询可能被限流:
```python
# 错误: 在 next() 中频繁查询
def next(self):
cash = self.getcash() # 每个 K 线都查询
# 正确: 缓存查询结果
def __init__(self):
self._last_cash = None
self._cash_counter = 0
def next(self):
self._cash_counter += 1
if self._cash_counter % 10 == 0: # 每 10 个 K 线查询一次
self._last_cash = self.getcash()
```bash
## 实盘注意事项
1. **测试充分**: 在模拟环境充分测试后再接入实盘
2. **资金安全**: 首次使用小额资金测试
3. **异常处理**: 做好异常捕获和日志记录
4. **监控告警**: 设置持仓、盈亏、异常情况的告警
5. **数据备份**: 定期备份交易日志和策略参数
6. **网络稳定**: 确保网络连接稳定,考虑使用专线
7. **合约换月**: 注意合约到期换月处理
8. **交易时段**: 避开集合竞价和收盘前最后几分钟
9. **滑点风险**: 实盘滑点可能大于模拟,预留滑点空间
## 下一步学习
- [策略开发](strategies_zh.md) - 构建有效的交易策略
- [分析器](analyzers_zh.md) - 评估策略性能
- [观察器](observers_zh.md) - 监控策略行为