title: CTP Live Trading description: Trading Chinese futures live via CTP API


CTP Live Trading

CTP (Comprehensive Transaction Platform) is the standard API for trading Chinese futures. This guide covers connecting backtrader to CTP for live futures trading through the ctp-python package.

Introduction

The CTP integration provides:

  • Live Market Data: Real-time tick data from Chinese futures exchanges

  • Order Management: Submit, cancel, and track orders

  • Position Tracking: Real-time position and account updates

  • SimNow Support: Free simulation environment for testing

Supported Exchanges

  • SHFE (Shanghai Futures Exchange) - e.g., rb, cu, al, au

  • DCE (Dalian Commodity Exchange) - e.g., m, y, p, a

  • CZCE (Zhengzhou Commodity Exchange) - e.g., SR, CF, MA

  • INE (Shanghai International Energy Exchange) - e.g., sc, lu

  • CFFEX (China Financial Futures Exchange) - e.g., IF, IH, IC, T, TF

Prerequisites

Install the required package:

pip install ctp-python

```bash
For historical data backfill (optional but recommended):

```bash
pip install akshare

```bash

## Configuration

### Connection Parameters

| Parameter | Description | Example |

|-----------|-------------|---------|

| `td_front` | Trader front address | `tcp://182.254.243.31:30001` |

| `md_front` | Market data front address | `tcp://182.254.243.31:30011` |

| `broker_id` | Broker ID assigned by CTP | `9999` |

| `user_id` | Your trading account ID | `your_username` |

| `password` | Your trading account password | `your_password` |

| `app_id` | Application ID for authentication | `simnow_client_test` |

| `auth_code` | Authentication code | `0000000000000000` |

### SimNow Environment

SimNow provides a free simulation environment for testing:

```python

# SimNow 7x24 ( penetrates front)

SIMNOW_TD_7x24 = "tcp://182.254.243.31:30001"
SIMNOW_MD_7x24 = "tcp://182.254.243.31:30011"

# SimNow Regular trading hours

SIMNOW_TD = "tcp://180.168.146.187:10130"
SIMNOW_MD = "tcp://180.168.146.187:10131"

# Common SimNow credentials

BROKER_ID = "9999"
APP_ID = "simnow_client_test"
AUTH_CODE = "0000000000000000"

```bash
To get SimNow credentials:

1. Visit [SimNow official website](<http://www.simnow.com.cn/)>
2. Register for a demo account
3. Use the provided broker ID, user ID, and password

## Data Feed Setup

### Basic Data Feed

```python
import backtrader as bt

# Create CTP store

store = bt.stores.CTPStore(
    td_front='tcp://182.254.243.31:30001',
    md_front='tcp://182.254.243.31:30011',
    broker_id='9999',
    user_id='your_id',
    password='your_password',
    app_id='simnow_client_test',
    auth_code='0000000000000000',
)

# Create data feed

data = store.getdata(
    dataname='rb2501.SHFE',  # instrument.exchange format
    timeframe=bt.TimeFrame.Minutes,
    compression=1,
)

cerebro = bt.Cerebro()
cerebro.adddata(data)

```bash

### Data Feed Parameters

```python
data = store.getdata(
    dataname='rb2501.SHFE',
    timeframe=bt.TimeFrame.Minutes,  # Minutes or Days
    compression=1,                    # Bar compression
    historical=False,                 # True = stop after backfill
    num_init_backfill=100,           # Number of historical bars to load
    tick_mode=False,                  # True = emit raw ticks
    backfill_retries=2,              # Retry attempts for backfill

)

```bash

### Multiple Instruments

```python

# Subscribe to multiple instruments

instruments = [
    'rb2501.SHFE',  # Rebar
    'hc2501.SHFE',  # Hot rolled coil
    'IF2502.CFFEX', # CSI 300 index

]

for symbol in instruments:
    data = store.getdata(dataname=symbol, timeframe=bt.TimeFrame.Minutes)
    cerebro.adddata(data)

```bash

## Broker Setup

### Basic Broker Configuration

```python
import backtrader as bt

cerebro = bt.Cerebro()

# Create store and set broker

store = bt.stores.CTPStore(
    td_front='tcp://182.254.243.31:30001',
    md_front='tcp://182.254.243.31:30011',
    broker_id='9999',
    user_id='your_id',
    password='your_password',
    app_id='simnow_client_test',
    auth_code='0000000000000000',
)

cerebro.setbroker(store.getbroker(
    use_positions=True,   # Use existing positions on start
    commission=1.0,       # Commission per contract

))

```bash

### Broker Parameters

| Parameter | Default | Description |

|-----------|---------|-------------|

| `use_positions` | `True` | Load existing positions on startup |

| `commission` | `0.0` | Commission per contract (absolute value) |

| `stop_slippage_ticks` | `0.0` | Max slippage for stop orders (0=market) |

## Order Management

### Order Types

```python
class MyStrategy(bt.Strategy):
    def next(self):

# Market order (uses AnyPrice in CTP)
        self.buy(size=1, exectype=bt.Order.Market)

# Limit order
        self.buy(size=1, price=3800.0, exectype=bt.Order.Limit)

# Stop order (triggers when price crosses stop price)
        self.sell(size=1, price=3750.0, exectype=bt.Order.Stop)

# Stop-limit order
        self.sell(size=1,
                 price=3750.0,    # Stop trigger price
                 plimit=3748.0,   # Limit price after trigger
                 exectype=bt.Order.StopLimit)

```bash

### Order Tracking

```python
class MyStrategy(bt.Strategy):
    def __init__(self):
        self.order = None

    def next(self):
        if self.order:
            return  # Wait for pending order

# Place order
        self.order = self.buy(size=1)

    def notify_order(self, order):
        """Called when order status changes."""
        if order.status in [order.Submitted, order.Accepted]:
            return

        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(f'BUY EXECUTED, Price: {order.executed.price:.2f}, '
                        f'Cost: {order.executed.value:.2f}, '
                        f'Comm: {order.executed.comm:.2f}')
            else:
                self.log(f'SELL EXECUTED, Price: {order.executed.price:.2f}, '
                        f'Cost: {order.executed.value:.2f}, '
                        f'Comm: {order.executed.comm:.2f}')

        elif order.status in [order.Canceled]:
            self.log('Order Canceled')
        elif order.status in [order.Rejected]:
            self.log('Order Rejected')

        self.order = None

```bash

### Order Cancellation

```python
class MyStrategy(bt.Strategy):
    def __init__(self):
        self.order = None
        self.cancel_after = 10  # bars

    def next(self):
        if self.order:
            self.cancel_after -= 1
            if self.cancel_after <= 0:
                self.cancel(self.order)
                self.order = None
            return

        self.order = self.buy(size=1)
        self.cancel_after = 10

```bash

## SHFE/INE Close Offset Handling

For SHFE and INE exchanges, CTP requires distinguishing between closing today's positions vs. yesterday's positions. The broker handles this automatically:

```python

# The broker automatically:

# 1. Tracks today's vs yesterday's positions

# 2. Uses CloseToday for closing positions opened today

# 3. Uses CloseYesterday for closing positions from prior days

# 4. Splits orders when closing mixed positions

# No special code needed - just place orders normally

self.buy(size=5)   # Opens 5 long positions

self.sell(size=3)  # Closes 3 (automatically uses CloseToday/CloseYesterday)

```bash

## Complete Live Trading Example

```python

# !/usr/bin/env python

"""CTP Live Trading Example"""

import backtrader as bt
import logging

# Enable debug logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class SimpleMAStrategy(bt.Strategy):
    """Simple moving average crossover strategy for CTP live trading."""

    params = (
        ('fast_period', 10),
        ('slow_period', 30),
        ('size', 1),
    )

    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 log(self, txt, dt=None):
        """Log strategy messages."""
        dt = dt or self.data.datetime[0]
        logger.info(f'{dt} {txt}')

    def notify_order(self, order):
        """Handle order status changes."""
        if order.status in [order.Submitted, order.Accepted]:
            return

        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(f'BUY EXECUTED, Price: {order.executed.price:.2f}, '
                        f'Size: {order.executed.size}')
            else:
                self.log(f'SELL EXECUTED, Price: {order.executed.price:.2f}, '
                        f'Size: {order.executed.size}')

        elif order.status in [order.Canceled]:
            self.log('Order Canceled')
        elif order.status in [order.Rejected]:
            self.log('Order Rejected - check margin and position limits')

        self.order = None

    def notify_trade(self, trade):
        """Handle trade completion."""
        if not trade.isclosed:
            return
        self.log(f'TRADE CLOSED, P&L: {trade.pnl:.2f}, Comm: {trade.commission:.2f}')

    def next(self):
        """Main strategy logic."""

# Wait for pending order
        if self.order:
            return

# Wait for indicators to be ready
        if len(self) < self.p.slow_period:
            return

# Check if we have a position
        if not self.position:

# No position - look for entry
            if self.crossover > 0:

# Fast MA crosses above slow MA - buy signal
                self.order = self.buy(size=self.p.size)
                self.log(f'BUY SIGNAL, Price: {self.data.close[0]:.2f}')
            elif self.crossover < 0:

# Fast MA crosses below slow MA - sell signal
                self.order = self.sell(size=self.p.size)
                self.log(f'SELL SIGNAL, Price: {self.data.close[0]:.2f}')
        else:

# Have position - look for exit on opposite crossover
            if self.position.size > 0 and self.crossover < 0:
                self.order = self.close(size=self.p.size)
                self.log(f'CLOSE LONG, Price: {self.data.close[0]:.2f}')
            elif self.position.size < 0 and self.crossover > 0:
                self.order = self.close(size=self.p.size)
                self.log(f'CLOSE SHORT, Price: {self.data.close[0]:.2f}')


def run_live():
    """Run CTP live trading."""

# CEP connection settings
    ctp_setting = {
        'td_front': 'tcp://182.254.243.31:30001',
        'md_front': 'tcp://182.254.243.31:30011',
        'broker_id': '9999',
        'user_id': 'your_id',
        'password': 'your_password',
        'app_id': 'simnow_client_test',
        'auth_code': '0000000000000000',
    }

# Create cerebro
    cerebro = bt.Cerebro()

# Create store
    store = bt.stores.CTPStore(**ctp_setting)

# Add data feed
    data = store.getdata(
        dataname='rb2505.SHFE',
        timeframe=bt.TimeFrame.Minutes,
        compression=1,
        num_init_backfill=100,
    )
    cerebro.adddata(data)

# Set broker
    cerebro.setbroker(store.getbroker(
        use_positions=True,
        commission=1.0,
    ))

# Add strategy
    cerebro.addstrategy(SimpleMAStrategy, fast_period=10, slow_period=30, size=1)

# Add analyzers
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
    cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')

# Run (live trading doesn't end - use Ctrl+C to stop)
    try:
        logger.info('Starting CTP live trading...')
        results = cerebro.run()
    except KeyboardInterrupt:
        logger.info('Stopped by user')

# Print results
    strat = results[0]
    logger.info(f'Final Value: {cerebro.broker.getvalue():.2f}')
    logger.info(f'DrawDown: {strat.analyzers.drawdown.get().max.drawdown:.2f}%')


if __name__ == '__main__':
    run_live()

```bash

## Risk Control

### Position Sizing

```python
class RiskControlStrategy(bt.Strategy):
    params = (
        ('max_pos', 5),           # Maximum contracts
        ('max_loss_pct', 0.02),   # 2% max loss
    )

    def __init__(self):
        self.entry_price = None

    def next(self):

# Check position size limit
        current_pos = abs(self.position.size)
        if current_pos >= self.p.max_pos:
            return  # Max position reached

# Calculate position size based on risk
        cash = self.broker.getcash()
        risk_amount = cash *self.p.max_loss_pct
        price = self.data.close[0]
        size = int(risk_amount / price)

# Limit size
        size = min(size, self.p.max_pos - current_pos)
        if size > 0:
            self.buy(size=size)

```bash

### Stop Loss

```python
class StopLossStrategy(bt.Strategy):
    params = (
        ('stop_loss_pct', 0.01),  # 1% stop loss
    )

    def __init__(self):
        self.entry_price = None

    def next(self):
        if not self.position:
            self.entry_price = self.data.close[0]
            self.buy(size=1)
        else:

# Check stop loss
            if self.position.size > 0:  # Long position
                stop_price = self.entry_price*(1 - self.p.stop_loss_pct)
                if self.data.close[0] <= stop_price:
                    self.log(f'Stop loss triggered at {self.data.close[0]:.2f}')
                    self.close()
            else:  # Short position
                stop_price = self.entry_price*(1 + self.p.stop_loss_pct)
                if self.data.close[0] >= stop_price:
                    self.log(f'Stop loss triggered at {self.data.close[0]:.2f}')
                    self.close()

```bash

## Troubleshooting

### Connection Issues

- *Problem**: Cannot connect to CTP front

```python

# Solution 1: Check network connectivity

import socket
def check_host(host, port):
    try:
        socket.create_connection((host, port), timeout=5)
        print(f"{host}:{port} is reachable")
    except OSError:
        print(f"{host}:{port} is NOT reachable")

check_host("182.254.243.31", 30001)  # TD front

check_host("182.254.243.31", 30011)  # MD front

```bash

- *Problem**: Login timeout

```python

# Solution: Increase wait time or check credentials

store = bt.stores.CTPStore(
    td_front='tcp://182.254.243.31:30001',
    md_front='tcp://182.254.243.31:30011',
    broker_id='9999',
    user_id='your_id',
    password='your_password',
    app_id='simnow_client_test',
    auth_code='0000000000000000',
)

# Check connection status

if store.is_connected:
    print("CTP connected successfully")
else:
    print("CTP connection failed - check credentials")

```bash

### Order Rejection

- *Problem**: Orders are rejected

Common causes:

- Insufficient margin
- Position limits exceeded
- Invalid instrument ID
- Market closed (trading hours)
- Price outside limit range

```python

# Enable logging to see rejection reasons

logging.basicConfig(level=logging.DEBUG)

# Check account balance before ordering

cash = cerebro.broker.getcash()
print(f"Available cash: {cash}")

# Check if market is open

def is_market_open():
    """Simple check for day session hours."""
    now = datetime.now().time()
    morning_start = time(9, 0)
    morning_end = time(11, 30)
    afternoon_start = time(13, 30)
    afternoon_end = time(15, 0)
    return (morning_start <= now <= morning_end or
            afternoon_start <= now <= afternoon_end)

```bash

### No Data Received

- *Problem**: Data feed receives no ticks

```python

# Solution 1: Check instrument ID is correct

# Common format: symbol + month + exchange

# Examples: rb2505.SHFE, m2505.DCE, SR505.CZCE

# Solution 2: Verify subscription

data = store.getdata(dataname='rb2505.SHFE')

# The store automatically subscribes via data.start()

# Solution 3: Check backfill is working

data = store.getdata(
    dataname='rb2505.SHFE',
    num_init_backfill=100,  # Load historical data
    historical=False,       # Continue to live after backfill

)

```bash

### Memory Issues

- *Problem**: Memory grows over time

The CTP implementation uses bounded queues to prevent memory overflow:

```python

# Queues are automatically bounded to 10000 items

# Old ticks are discarded when queue is full

# Monitor memory usage

import psutil
import os

process = psutil.Process(os.getpid())
print(f"Memory usage: {process.memory_info().rss / 1024 / 1024:.2f} MB")

```bash

## Trading Hours Reference

| Exchange | Day Session | Night Session |

|----------|-------------|---------------|

| SHFE/INE | 09:00-10:15, 10:30-11:30, 13:30-15:00 | 21:00-23:00, 23:00-02:30 (next day) |

| DCE | 09:00-10:15, 10:30-11:30, 13:30-15:00 | 21:00-23:00 |

| CZCE | 09:00-10:15, 10:30-11:30, 13:30-15:00 | 21:00-23:00 |

| CFFEX | 09:15-11:30, 13:00-15:15 | - |

- Note: Trading hours may vary by product and exchange announcements.*

## Next Steps

- [Data Feeds](data-feeds.md) - More data feed options
- [Strategies](strategies.md) - Strategy development patterns
- [Analyzers](analyzers.md) - Performance analysis tools
- [Plotting](plotting.md) - Visualize live trading results