套利策略

概览

ArbitrageStrategy 是一种期货合约与其基础资产之间的套利策略。它跟踪工具之间的价差,并在出现套利机会时开仓。

主要组件

该策略继承自 Strategy 并使用参数进行配置:

public class ArbitrageStrategy : Strategy
{
	private enum ArbitrageState
	{
		Contango,        // 期货价格高于标的资产
		Backwardation,   // 标的资产价格高于期货
		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 - 期货工具
  • 股票类交易品种 - 基础资产
  • FuturePortfolio - 期货交易组合
  • 股票投资组合 - 用于基础资产交易的投资组合
  • 股票乘数 - 标的资产的乘数(例如,合约单位)
  • 期货交易量 - 期货交易的交易量
  • 股票交易量 - 基础资产交易的交易量
  • ProfitToExit - 平仓的利润阈值
  • SpreadToGenerateSignal - 产生入场信号的点差阈值

策略初始化

OnStarted2 方法中,对参数进行了验证,并创建了对订单簿和自身交易的订阅:

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

	if (FutureSecurity == null)
		throw new InvalidOperationException("未指定期货证券。");

	if (StockSecurity == null)
		throw new InvalidOperationException("未指定股票证券。");

	if (FuturePortfolio == null)
		throw new InvalidOperationException("未指定期货投资组合。");

	if (StockPortfolio == null)
		throw new InvalidOperationException("未指定股票投资组合。");

	_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(OnOwnTradeReceived)
		.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 backwardationSpread = _stBid - _futAck;   // 标的资产价格 > 期货价格

	decimal spread;
	ArbitrageState arbitrageSignal;

	// 确定最佳套利机会
	if (backwardationSpread > contangoSpread)
	{
		arbitrageSignal = ArbitrageState.Backwardation;
		spread = backwardationSpread;
	}
	else
	{
		arbitrageSignal = ArbitrageState.Contango;
		spread = contangoSpread;
	}

	// 记录当前状态和价差
	LogInfo($"Current state {_currentState}, enter spread = {_enterSpread}");
	LogInfo($"{ArbitrageState.Backwardation} spread = {backwardationSpread}");
	LogInfo($"{ArbitrageState.Contango}        spread = {contangoSpread}");
	LogInfo($"从价差入场:{SpreadToGenerateSignal}。从利润出场:{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.Backwardation)
		{
			ExecuteBackwardation();
		}
		else
		{
			ExecuteContango();
		}
	}
	// 达到利润阈值时退出 Backwardation 持仓
	else if (_currentState == ArbitrageState.Backwardation && _profit >= ProfitToExit)
	{
		_currentState = ArbitrageState.OrderRegistration;
		CloseBackwardationPosition();
	}
	// 达到利润阈值时退出 Contango 持仓
	else if (_currentState == ArbitrageState.Contango && _profit >= ProfitToExit)
	{
		_currentState = ArbitrageState.OrderRegistration;
		CloseContangoPosition();
	}
}

利润计算

CalculateProfit 方法根据入场价格和当前价格计算当前利润:

private void CalculateProfit()
{
	switch (_currentState)
	{
		case ArbitrageState.Backwardation:
			// 买入期货、卖出标的资产 - 期货价格上涨且标的资产价格下跌时获利
			_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) GenerateOrdersBackwardation()
{
	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) 用于跟踪订单执行
  • 成交量加权平均价格是根据成交量计算以获得更准确的价格
  • 套利逻辑同时考虑正向(现货溢价)和反向(期货贴水)价差
  • 支持在达到目标阈值时自动计算利润并退出