Example #1
0
		/// <summary>
		/// To calculate commission.
		/// </summary>
		/// <param name="message">The message containing the information about the order or own trade.</param>
		/// <returns>The commission. If the commission can not be calculated then <see langword="null" /> will be returned.</returns>
		protected override decimal? OnProcessExecution(ExecutionMessage message)
		{
			if (message.ExecutionType != ExecutionTypes.Trade)
				return null;

			_currentTurnOver += message.GetTradePrice() * message.SafeGetVolume();

			if (_currentTurnOver < TurnOver)
				return null;

			return (decimal)Value;
		}
Example #2
0
		/// <summary>
		/// To calculate trade profitability. If the trade was already processed earlier, previous information returns.
		/// </summary>
		/// <param name="trade">Trade.</param>
		/// <returns>Information on new trade.</returns>
		public PnLInfo Process(ExecutionMessage trade)
		{
			if (trade == null)
				throw new ArgumentNullException(nameof(trade));

			var closedVolume = 0m;
			var pnl = 0m;
			var volume = trade.SafeGetVolume();
			var price = trade.GetTradePrice();

			_unrealizedPnL = null;

			lock (_openedTrades.SyncRoot)
			{
				if (_openedTrades.Count > 0)
				{
					var currTrade = _openedTrades.Peek();

					if (_openedPosSide != trade.Side)
					{
						while (volume > 0)
						{
							if (currTrade == null)
								currTrade = _openedTrades.Peek();

							var diff = currTrade.Second.Min(volume);
							closedVolume += diff;

							pnl += TraderHelper.GetPnL(currTrade.First, diff, _openedPosSide, price);

							volume -= diff;
							currTrade.Second -= diff;

							if (currTrade.Second != 0)
								continue;

							currTrade = null;
							_openedTrades.Pop();

							if (_openedTrades.Count == 0)
								break;
						}
					}
				}

				if (volume > 0)
				{
					_openedPosSide = trade.Side;
					_openedTrades.Push(RefTuple.Create(price, volume));
				}

				RealizedPnL += _multiplier * pnl;
			}

			return new PnLInfo(trade, closedVolume, pnl);
		}
		/// <summary>
		/// To convert the tick trade.
		/// </summary>
		/// <param name="message">Tick trade.</param>
		/// <returns>Stream <see cref="ExecutionMessage"/>.</returns>
		public IEnumerable<ExecutionMessage> ToExecutionLog(ExecutionMessage message)
		{
			if (message == null)
				throw new ArgumentNullException(nameof(message));

			if (!_priceStepUpdated)
			{
				_securityDefinition.PriceStep = message.GetTradePrice().GetDecimalInfo().EffectiveScale.GetPriceStep();
				_priceStepUpdated = true;
			}

			if (!_volumeStepUpdated)
			{
				_securityDefinition.VolumeStep = message.SafeGetVolume().GetDecimalInfo().EffectiveScale.GetPriceStep();
				_volumeStepUpdated = true;
			}

			//if (message.DataType != ExecutionDataTypes.Trade)
			//	throw new ArgumentOutOfRangeException("Тип данных не может быть {0}.".Put(message.DataType), "message");

			//_lastTradeDate = message.LocalTime.Date;

			return ProcessExecution(message);
		}
		private IEnumerable<ExecutionMessage> ProcessExecution(ExecutionMessage message)
		{
			var retVal = new List<ExecutionMessage>();

			var bestBid = _bids.FirstOrDefault();
			var bestAsk = _asks.FirstOrDefault();

			var tradePrice = message.GetTradePrice();
			var volume = message.Volume ?? 1;
			var time = message.LocalTime;

			if (bestBid.Value != null && tradePrice <= bestBid.Key)
			{
				// тик попал в биды, значит была крупная заявка по рынку на продажу,
				// которая возможна исполнила наши заявки

				ProcessMarketOrder(retVal, _bids, message.ServerTime, message.LocalTime, Sides.Sell, tradePrice, volume);

				// подтягиваем противоположные котировки и снимаем лишние заявки
				TryCreateOppositeOrder(retVal, _asks, time, message.ServerTime, tradePrice, volume, Sides.Buy);
			}
			else if (bestAsk.Value != null && tradePrice >= bestAsk.Key)
			{
				// тик попал в аски, значит была крупная заявка по рынку на покупку,
				// которая возможна исполнила наши заявки

				ProcessMarketOrder(retVal, _asks, message.ServerTime, message.LocalTime, Sides.Buy, tradePrice, volume);

				TryCreateOppositeOrder(retVal, _bids, time, message.ServerTime, tradePrice, volume, Sides.Sell);
			}
			else if (bestBid.Value != null && bestAsk.Value != null && bestBid.Key < tradePrice && tradePrice < bestAsk.Key)
			{
				// тик попал в спред, значит в спреде до сделки была заявка.
				// создаем две лимитки с разных сторон, но одинаковой ценой.
				// если в эмуляторе есть наша заявка на этом уровне, то она исполниться.
				// если нет, то эмулятор взаимно исполнит эти заявки друг об друга

				var originSide = GetOrderSide(message);

				retVal.Add(CreateMessage(time, message.ServerTime, originSide, tradePrice, volume + (_securityDefinition.VolumeStep ?? 1 * _settings.VolumeMultiplier), tif: TimeInForce.MatchOrCancel));

				var spreadStep = _settings.SpreadSize * GetPriceStep();

				// try to fill depth gaps

				var newBestPrice = tradePrice + spreadStep;

				var depth = _settings.MaxDepth;
				while (--depth > 0)
				{
					var diff = bestAsk.Key - newBestPrice;

					if (diff > 0)
					{
						retVal.Add(CreateMessage(time, message.ServerTime, Sides.Sell, newBestPrice, 0));
						newBestPrice += spreadStep * _priceRandom.Next(1, _settings.SpreadSize);
					}
					else
						break;
				}

				newBestPrice = tradePrice - spreadStep;

				depth = _settings.MaxDepth;
				while (--depth > 0)
				{
					var diff = newBestPrice - bestBid.Key;

					if (diff > 0)
					{
						retVal.Add(CreateMessage(time, message.ServerTime, Sides.Buy, newBestPrice, 0));
						newBestPrice -= spreadStep * _priceRandom.Next(1, _settings.SpreadSize);
					}
					else
						break;
				}

				retVal.Add(CreateMessage(time, message.ServerTime, originSide.Invert(), tradePrice, volume, tif: TimeInForce.MatchOrCancel));
			}
			else
			{
				// если у нас стакан был полу пустой, то тик формирует некий ценовой уровень в стакана,
				// так как прошедщая заявка должна была обо что-то удариться. допускаем, что после
				// прохождения сделки на этом ценовом уровне остался объем равный тиковой сделки

				var hasOpposite = true;

				Sides originSide;

				// определяем направление псевдо-ранее существовавшей заявки, из которой получился тик
				if (bestBid.Value != null)
					originSide = Sides.Sell;
				else if (bestAsk.Value != null)
					originSide = Sides.Buy;
				else
				{
					originSide = GetOrderSide(message);
					hasOpposite = false;
				}

				retVal.Add(CreateMessage(time, message.ServerTime, originSide, tradePrice, volume));

				// если стакан был полностью пустой, то формируем сразу уровень с противоположной стороны
				if (!hasOpposite)
				{
					var oppositePrice = tradePrice + _settings.SpreadSize * GetPriceStep() * (originSide == Sides.Buy ? 1 : -1);

					if (oppositePrice > 0)
						retVal.Add(CreateMessage(time, message.ServerTime, originSide.Invert(), oppositePrice, volume));
				}
			}

			if (!HasDepth(time))
			{
				// если стакан слишком разросся, то удаляем его хвосты (не удаляя пользовательские заявки)
				CancelWorstQuote(retVal, time, message.ServerTime, Sides.Buy, _bids);
				CancelWorstQuote(retVal, time, message.ServerTime, Sides.Sell, _asks);	
			}

			_prevTickPrice = tradePrice;

			return retVal;
		}
		private void ProcessMarketOrder(List<ExecutionMessage> retVal, SortedDictionary<decimal, RefPair<List<ExecutionMessage>, QuoteChange>> quotes, ExecutionMessage tradeMessage, Sides orderSide)
		{
			// вычисляем объем заявки по рынку, который смог бы пробить текущие котировки.

			var tradePrice = tradeMessage.GetTradePrice();
			// bigOrder - это наша большая рыночная заявка, которая способствовала появлению tradeMessage
			var bigOrder = CreateMessage(tradeMessage.LocalTime, tradeMessage.ServerTime, orderSide, tradePrice, 0, tif: TimeInForce.MatchOrCancel);
			var sign = orderSide == Sides.Buy ? -1 : 1;
			var hasQuotes = false;

			foreach (var pair in quotes)
			{
				var quote = pair.Value.Second;

				if (quote.Price * sign > tradeMessage.TradePrice * sign)
				{
					bigOrder.Volume += quote.Volume;
				}
				else
				{
					if (quote.Price == tradeMessage.TradePrice)
					{
						bigOrder.Volume += tradeMessage.Volume;

						//var diff = tradeMessage.Volume - quote.Volume;

						//// если объем котиовки был меньше объема сделки
						//if (diff > 0)
						//	retVal.Add(CreateMessage(tradeMessage.LocalTime, quote.Side, quote.Price, diff));
					}
					else
					{
						if ((tradePrice - quote.Price).Abs() == _securityDefinition.PriceStep)
						{
							// если на один шаг цены выше/ниже есть котировка, то не выполняем никаких действий
							// иначе добавляем новый уровень в стакан, чтобы не было большого расхождения цен.
							hasQuotes = true;
						}
					
						break;
					}

					//// если котировки с ценой сделки вообще не было в стакане
					//else if (quote.Price * sign < tradeMessage.TradePrice * sign)
					//{
					//	retVal.Add(CreateMessage(tradeMessage.LocalTime, quote.Side, tradeMessage.Price, tradeMessage.Volume));
					//}
				}
			}

			retVal.Add(bigOrder);

			// если собрали все котировки, то оставляем заявку в стакане по цене сделки
			if (!hasQuotes)
				retVal.Add(CreateMessage(tradeMessage.LocalTime, tradeMessage.ServerTime, orderSide.Invert(), tradePrice, tradeMessage.SafeGetVolume()));
		}