Разработка системы paper trading для тестирования AI-стратегий
Paper trading — симуляция реальной торговли без использования реального капитала. В отличие от бэктестинга (тестирование на исторических данных), paper trading происходит в реальном времени: стратегия получает live-данные с биржи, генерирует сигналы и отправляет виртуальные ордера в симулятор брокера. Это позволяет выявить проблемы, невидимые при бэктестинге: задержки данных, проблемы с исполнением ордеров, технические сбои.
Архитектура системы
[Market Data Feed] → [Data Normalizer] → [Feature Pipeline]
↓
[AI Model Inference]
↓
[Signal Generator]
↓
[Risk Management Layer]
↓
[Paper Broker Simulator]
↓
[Portfolio State] → [P&L Calculator]
↓
[Monitoring Dashboard]
Компоненты системы
Market Data Integration:
import asyncio
import websockets
import json
from dataclasses import dataclass
@dataclass
class MarketTick:
symbol: str
timestamp: float
bid: float
ask: float
last: float
volume: float
class AlpacaMarketDataFeed:
def __init__(self, api_key: str, secret_key: str):
self.ws_url = "wss://stream.data.alpaca.markets/v2/sip"
self.headers = {
"APCA-API-KEY-ID": api_key,
"APCA-API-SECRET-KEY": secret_key
}
async def stream_quotes(self, symbols: list, callback):
async with websockets.connect(self.ws_url, extra_headers=self.headers) as ws:
# Подписка на котировки
await ws.send(json.dumps({
"action": "subscribe",
"quotes": symbols
}))
async for message in ws:
data = json.loads(message)
for item in data:
if item['T'] == 'q': # quote
tick = MarketTick(
symbol=item['S'],
timestamp=item['t'],
bid=item['bp'],
ask=item['ap'],
last=(item['bp'] + item['ap']) / 2,
volume=item.get('bs', 0)
)
await callback(tick)
Paper Broker Simulator:
class PaperBroker:
def __init__(self, initial_capital: float = 100_000):
self.cash = initial_capital
self.positions = {} # symbol -> quantity
self.orders = []
self.fill_probability = 0.95 # 95% ордеров исполняется
def submit_order(self, symbol: str, qty: int, side: str,
order_type: str = 'market', limit_price: float = None):
order_id = str(uuid.uuid4())
order = {
'id': order_id, 'symbol': symbol, 'qty': qty,
'side': side, 'type': order_type,
'limit_price': limit_price, 'status': 'pending',
'submitted_at': datetime.utcnow()
}
self.orders.append(order)
return order_id
def process_tick(self, tick: MarketTick):
for order in self.orders:
if order['status'] != 'pending':
continue
if order['symbol'] != tick.symbol:
continue
# Симуляция исполнения
if order['type'] == 'market':
fill_price = tick.ask if order['side'] == 'buy' else tick.bid
self._fill_order(order, fill_price, tick.timestamp)
elif order['type'] == 'limit':
if (order['side'] == 'buy' and tick.ask <= order['limit_price']):
self._fill_order(order, order['limit_price'], tick.timestamp)
elif (order['side'] == 'sell' and tick.bid >= order['limit_price']):
self._fill_order(order, order['limit_price'], tick.timestamp)
def _fill_order(self, order: dict, price: float, timestamp):
commission = price * order['qty'] * 0.0001
if order['side'] == 'buy':
cost = price * order['qty'] + commission
if self.cash >= cost:
self.cash -= cost
self.positions[order['symbol']] = \
self.positions.get(order['symbol'], 0) + order['qty']
order['status'] = 'filled'
order['fill_price'] = price
elif order['side'] == 'sell':
if self.positions.get(order['symbol'], 0) >= order['qty']:
self.cash += price * order['qty'] - commission
self.positions[order['symbol']] -= order['qty']
order['status'] = 'filled'
Мониторинг в реальном времени
Dashboard показывает: P&L (realized и unrealized) в реальном времени, открытые позиции, журнал сделок, equity curve, drawdown, сравнение с бенчмарком.
Ключевые метрики paper trading vs backtest
Если результаты paper trading значительно хуже бэктестинга — это признаки: overfitting к историческим данным, look-ahead bias в бэктесте, недооценённые транзакционные издержки. Цель: расхождение между paper trading и бэктестом < 15% по Sharpe Ratio.
Минимальный срок paper trading перед переходом к реальному капиталу: 3 месяца (1 квартал охватывает разные рыночные режимы).







