Стратегия арбитража

Обзор

ArbitrageStrategy - это стратегия арбитража между фьючерсом и базовым активом. Она отслеживает спреды между инструментами и открывает позиции при возникновении арбитражных возможностей.

Основные компоненты

Стратегия наследуется от Strategy и использует параметры для настройки:

public class ArbitrageStrategy : Strategy
{
	private enum ArbitrageState
	{
		Contango,        // Фьючерс дороже базового актива
		Backvordation,   // Базовый актив дороже фьючерса
		None,            // Нет позиции
		OrderRegistration // В процессе регистрации заявок
	}

	// Параметры стратегии
	private readonly StrategyParam<Security> _futureSecurity;
	private readonly StrategyParam<Security> _stockSecurity;
	private readonly StrategyParam<Portfolio> _futurePortfolio;
	private readonly StrategyParam<Portfolio> _stockPortfolio;
	private readonly StrategyParam<decimal> _stockMultiplicator;
	private readonly StrategyParam<decimal> _futureVolume;
	private readonly StrategyParam<decimal> _stockVolume;
	private readonly StrategyParam<decimal> _profitToExit;
	private readonly StrategyParam<decimal> _spreadToGenerateSignal;
}

Параметры стратегии

Стратегия позволяет настраивать следующие параметры:

  • FutureSecurity - инструмент фьючерса
  • StockSecurity - инструмент базового актива
  • FuturePortfolio - портфель для торговли фьючерсом
  • StockPortfolio - портфель для торговли базовым активом
  • StockMultiplicator - мультипликатор для базового актива (например, размер лота)
  • FutureVolume - объем для торговли фьючерсом
  • StockVolume - объем для торговли базовым активом
  • ProfitToExit - порог прибыли для выхода из позиции
  • SpreadToGenerateSignal - порог спреда для генерации сигнала входа

Инициализация стратегии

В методе OnStarted2 проверяются параметры, создаются подписки на стаканы и собственные сделки:

protected override void OnStarted2(DateTime time)
{
	base.OnStarted2(time);

	if (FutureSecurity == null)
		throw new InvalidOperationException("Future security is not specified.");

	if (StockSecurity == null)
		throw new InvalidOperationException("Stock security is not specified.");

	if (FuturePortfolio == null)
		throw new InvalidOperationException("Future portfolio is not specified.");

	if (StockPortfolio == null)
		throw new InvalidOperationException("Stock portfolio is not specified.");

	_futId = FutureSecurity.ToSecurityId();
	_stockId = StockSecurity.ToSecurityId();

	// Подписка на обновления стаканов для обоих инструментов
	var futureDepthSubscription = new Subscription(DataType.MarketDepth, FutureSecurity);
	var stockDepthSubscription = new Subscription(DataType.MarketDepth, StockSecurity);

	futureDepthSubscription.WhenOrderBookReceived(this).Do(ProcessMarketDepth).Apply(this);
	stockDepthSubscription.WhenOrderBookReceived(this).Do(ProcessMarketDepth).Apply(this);

	// Подписка на собственные сделки для отслеживания цен исполнения
	this
		.WhenOwnTradeReceived()
		.Do(OnNewMyTrade)
		.Apply(this);

	// Отправка запросов на подписку на рыночные данные
	Subscribe(futureDepthSubscription);
	Subscribe(stockDepthSubscription);
}

Обработка рыночных данных

Метод ProcessMarketDepth вызывается при обновлении стакана и реализует основную логику:

private void ProcessMarketDepth(IOrderBookMessage depth)
{
	// Обновление последнего стакана для каждого инструмента
	if (depth.SecurityId == _futId)
		_lastFut = depth;
	else if (depth.SecurityId == _stockId)
		_lastSt = depth;

	// Ожидание данных для обоих инструментов
	if (_lastFut is null || _lastSt is null)
		return;

	// Расчёт средневзвешенных цен для определённых объёмов
	_futBid = GetAveragePrice(_lastFut, Sides.Sell, FutureVolume);
	_futAck = GetAveragePrice(_lastFut, Sides.Buy, FutureVolume);
	_stBid = GetAveragePrice(_lastSt, Sides.Sell, StockVolume) * StockMultiplicator;
	_stAsk = GetAveragePrice(_lastSt, Sides.Buy, StockVolume) * StockMultiplicator;

	// Проверка валидности цен
	if (_futBid == 0 || _futAck == 0 || _stBid == 0 || _stAsk == 0)
		return;

	// Расчёт спредов
	var contangoSpread = _futBid - _stAsk;        // Цена фьючерса > цены базового актива
	var backvordationSpread = _stBid - _futAck;   // Цена базового актива > цены фьючерса

	decimal spread;
	ArbitrageState arbitrageSignal;

	// Определение лучшей арбитражной возможности
	if (backvordationSpread > contangoSpread)
	{
		arbitrageSignal = ArbitrageState.Backvordation;
		spread = backvordationSpread;
	}
	else
	{
		arbitrageSignal = ArbitrageState.Contango;
		spread = contangoSpread;
	}

	// Логирование текущего состояния и спредов
	LogInfo($"Current state {_currentState}, enter spread = {_enterSpread}");
	LogInfo($"{ArbitrageState.Backvordation} spread = {backvordationSpread}");
	LogInfo($"{ArbitrageState.Contango}        spread = {contangoSpread}");
	LogInfo($"Entry from spread:{SpreadToGenerateSignal}. Exit from profit:{ProfitToExit}");

	// Пересчёт прибыли на основе текущих рыночных условий
	if (_currentState != ArbitrageState.None && _currentState != ArbitrageState.OrderRegistration)
	{
		CalculateProfit();
		LogInfo($"Profit: {_profit}");
	}

	// Обработка сигналов на основе текущего состояния и рыночных условий
	ProcessSignals(arbitrageSignal, spread);
}

Логика торговли

Обработка сигналов и принятие решений о входе/выходе реализованы в методе ProcessSignals:

private void ProcessSignals(ArbitrageState arbitrageSignal, decimal spread)
{
	// Вход в новую позицию, когда нет открытой позиции и спред превышает порог
	if (_currentState == ArbitrageState.None && spread > SpreadToGenerateSignal)
	{
		_currentState = ArbitrageState.OrderRegistration;

		if (arbitrageSignal == ArbitrageState.Backvordation)
		{
			ExecuteBackvardation();
		}
		else
		{
			ExecuteContango();
		}
	}
	// Выход из позиции Backvordation, когда достигнут порог прибыли
	else if (_currentState == ArbitrageState.Backvordation && _profit >= ProfitToExit)
	{
		_currentState = ArbitrageState.OrderRegistration;
		CloseBackvardationPosition();
	}
	// Выход из позиции Contango, когда достигнут порог прибыли
	else if (_currentState == ArbitrageState.Contango && _profit >= ProfitToExit)
	{
		_currentState = ArbitrageState.OrderRegistration;
		CloseContangoPosition();
	}
}

Расчёт прибыли

Метод CalculateProfit рассчитывает текущую прибыль на основе цен входа и текущих цен:

private void CalculateProfit()
{
	switch (_currentState)
	{
		case ArbitrageState.Backvordation:
			// Купить фьючерс, продать базовый актив - прибыль, когда цена фьючерса растёт и цена базового актива падает
			_profit = (_stockExitPrice * StockMultiplicator - _stAsk) + (_futBid - _futureBuyPrice);
			break;

		case ArbitrageState.Contango:
			// Продать фьючерс, купить базовый актив - прибыль, когда цена фьючерса падает и цена базового актива растёт
			_profit = (_futureExitPrice - _futAck) + (_stBid - _stockBuyPrice * StockMultiplicator);
			break;

		default:
			_profit = 0;
			break;
	}
}

Генерация заявок

Для выполнения арбитражных стратегий используются методы для генерации заявок:

private (Order buy, Order sell) GenerateOrdersBackvardation()
{
	var futureBuy = CreateOrder(Sides.Buy, FutureVolume);
	futureBuy.Portfolio = FuturePortfolio;
	futureBuy.Security = FutureSecurity;
	futureBuy.Type = OrderTypes.Market;

	var stockSell = CreateOrder(Sides.Sell, StockVolume);
	stockSell.Portfolio = StockPortfolio;
	stockSell.Security = StockSecurity;
	stockSell.Type = OrderTypes.Market;

	return (futureBuy, stockSell);
}

private (Order sell, Order buy) GenerateOrdersContango()
{
	var futureSell = CreateOrder(Sides.Sell, FutureVolume);
	futureSell.Portfolio = FuturePortfolio;
	futureSell.Security = FutureSecurity;
	futureSell.Type = OrderTypes.Market;

	var stockBuy = CreateOrder(Sides.Buy, StockVolume);
	stockBuy.Portfolio = StockPortfolio;
	stockBuy.Security = StockSecurity;
	stockBuy.Type = OrderTypes.Market;

	return (futureSell, stockBuy);
}

Особенности

  • Стратегия поддерживает работу с двумя разными инструментами и двумя портфелями
  • Используются рыночные заявки для быстрого исполнения
  • Для отслеживания исполнения заявок используются правила (IMarketRule)
  • Рассчитывается средневзвешенная цена на основе объёма для получения более точных цен
  • Логика арбитража учитывает как прямой (контанго), так и обратный (бэквордация) спреды
  • Поддерживается автоматический расчет прибыли и выход при достижении целевого порога