//TraderHelper.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com
using System.Linq.Expressions;
namespace StockSharp.Algo
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using Ecng.Net;
using Ecng.Common;
using Ecng.ComponentModel;
using Ecng.Collections;
using StockSharp.Algo.PnL;
using StockSharp.Algo.Positions;
using StockSharp.Algo.Storages;
using StockSharp.Algo.Strategies;
using StockSharp.Algo.Testing;
using StockSharp.BusinessEntities;
using StockSharp.Logging;
using Wintellect.PowerCollections;
///
/// Правила округления цены.
///
public enum ShrinkRules
{
///
/// Автоматически определять, к меньшему или большему значению округлять.
///
Auto,
///
/// Округлять к меньшему значению.
///
Less,
///
/// Округлять к большему значению.
///
More,
}
///
/// Вспомогательный класс для предоставления различной алгоритмической функциональности.
///
public static class TraderHelper
{
///
/// Получить стакан котировок для заданного инструмента.
///
/// Инструмент, по которому необходимо получить стакан.
/// Cтакан.
public static MarketDepth GetMarketDepth(this Security security)
{
return security.CheckTrader().GetMarketDepth(security);
}
///
/// Получить отфильтрованный стакан инструмента от всех активных заявок.
///
/// Инструмент, по которому необходимо получить стакан.
/// Заявки, которые необходимо игнорировать.
/// Отфильтрованный стакан.
public static IEnumerable GetFilteredQuotes(this Security security, IEnumerable orders)
{
return security.GetMarketDepth().SyncGet(c => c.GetFilteredQuotes(orders));
}
///
/// Получить отфильтрованный стакан от всех активных заявок.
///
/// Исходный стакан, который необходимо отфильтровать.
/// Заявки, которые необходимо игнорировать.
/// Отфильтрованный стакан.
public static IEnumerable GetFilteredQuotes(this IEnumerable quotes, IEnumerable orders)
{
if (quotes == null)
throw new ArgumentNullException("quotes");
if (orders == null)
throw new ArgumentNullException("orders");
var firstQuote = quotes.FirstOrDefault();
if (firstQuote == null)
return new Quote[0];
var trader = firstQuote.Security.Trader;
if (trader == null)
throw new ArgumentException("Котировка не имеет информацию о шлюзе.");
// pyh GetFilteredQuotes для EmulationTrader пропускает процедуру фильтрации т.к. наш симуляционный ордер в стакан не попадает
if (trader is IEmulationTrader)
return quotes;
return quotes.GetFilteredQuotes(trader.Orders.Where(o => o.State == OrderStates.Active || o.State == OrderStates.None), orders);
}
///
/// Отфильтровать стакан от собственных заявок.
///
/// Исходный стакан, который необходимо отфильтровать.
/// Активные заявки по данному инструменту.
/// Заявки, которые необходимо игнорировать.
/// Отфильтрованный стакан.
public static IEnumerable GetFilteredQuotes(this IEnumerable quotes, IEnumerable ownOrders, IEnumerable orders)
{
if (quotes == null)
throw new ArgumentNullException("quotes");
if (ownOrders == null)
throw new ArgumentNullException("ownOrders");
if (orders == null)
throw new ArgumentNullException("orders");
var dict = new MultiDictionary, Order>(false);
foreach (var order in ownOrders)
{
dict.Add(new Tuple(order.Direction, order.Price), order);
}
var retVal = new List(quotes.Select(q => q.Clone()));
foreach (var quote in retVal.ToArray())
{
var o = dict.TryGetValue(new Tuple(quote.OrderDirection, quote.Price));
if (o != null)
{
foreach (var order in o)
{
if (!orders.Contains(order))
quote.Volume -= order.Balance;
}
if (quote.Volume <= 0)
retVal.Remove(quote);
}
}
return retVal;
}
///
/// Получить рыночную цену для инструмента.
///
/// Инструмент, по которому вычисляется рыночная цена.
/// Направление заявки.
/// Рыночная цена. Если по инструменту возможна регистрация заявок типа , будет возвращено 0.
public static decimal GetMarketPrice(this Security security, OrderDirections direction)
{
var board = security.CheckExchangeBoard();
if (board.IsSupportMarketOrders)
return 0;
if (direction == OrderDirections.Buy && security.MaxPrice != 0)
return security.MaxPrice;
else if (direction == OrderDirections.Sell && security.MinPrice != 0)
return security.MinPrice;
else
throw new ArgumentException("У инструмента {0} отсутствует информация о планках.".Put(security), "security");
}
///
/// Высчитать текущую цену по инструменту в зависимости от направления заявки.
///
/// Инструмент, по которому вычисляется текущая цена.
/// Направление заявки.
/// Тип рыночной цены.
/// Заявки, которые необходимо игнорировать.
/// Текущая цена. Если информации в стакане недостаточно, будет возвращено 0.
public static Unit GetCurrentPrice(this Security security, OrderDirections? direction = null, MarketPriceTypes priceType = MarketPriceTypes.Following, IEnumerable orders = null)
{
var depth = security.GetMarketDepth();
decimal? currentPrice = null;
if (direction != null)
{
var result = depth.GetCurrentPrice((OrderDirections)direction, priceType, orders);
if (result != 0)
return result;
if (direction == OrderDirections.Buy && security.BestAsk != null)
currentPrice = security.BestAsk.Price;
else if (direction == OrderDirections.Sell && security.BestBid != null)
currentPrice = security.BestBid.Price;
}
if (currentPrice == null && security.LastTrade != null)
currentPrice = security.LastTrade.Price;
if (currentPrice == null)
currentPrice = 0;
return new Unit((decimal)currentPrice) { Security = security };
}
///
/// Высчитать текущую цену по стакану в зависимости от направления заявки.
///
/// Для корректной работы метода необходимо запустить экспорт стакана.
/// Стакан, по которому нужно высчитать текущую цену.
/// Направление заявки. Если это покупка, то будет использоваться
/// значение , иначе .
/// Тип текущей цены.
/// Заявки, которые необходимо игнорировать.
/// Текущая цена. Если информации в стакане недостаточно, будет возвращено 0.
public static Unit GetCurrentPrice(this MarketDepth depth, OrderDirections direction, MarketPriceTypes priceType = MarketPriceTypes.Following, IEnumerable orders = null)
{
if (depth == null)
throw new ArgumentNullException("depth");
if (orders != null)
{
var quotes = depth.GetFilteredQuotes(orders);
depth = new MarketDepth(depth.Security).Update(quotes, depth.LastChangeTime);
}
return depth.BestPair.GetCurrentPrice(direction, priceType);
}
///
/// Высчитать текущую цену по лучшей паре котировок в зависимости от направления заявки.
///
/// Для корректной работы метода необходимо запустить экспорт стакана.
/// Лучшая пара котировок, по которой вычисляется текущая цена.
/// Направление заявки. Если это покупка, то будет использоваться
/// значение , иначе .
/// Тип текущей цены.
/// Текущая цена. Если информации в стакане недостаточно, будет возвращено 0.
public static Unit GetCurrentPrice(this MarketDepthPair bestPair, OrderDirections direction, MarketPriceTypes priceType = MarketPriceTypes.Following)
{
if (bestPair == null)
throw new ArgumentNullException("bestPair");
Quote quote;
decimal currentPrice;
switch (priceType)
{
case MarketPriceTypes.Opposite:
quote = (direction == OrderDirections.Buy ? bestPair.Ask : bestPair.Bid);
currentPrice = quote == null ? 0 : quote.Price;
break;
case MarketPriceTypes.Following:
quote = (direction == OrderDirections.Buy ? bestPair.Bid : bestPair.Ask);
currentPrice = quote == null ? 0 : quote.Price;
break;
case MarketPriceTypes.Middle:
if (bestPair.Bid == null || bestPair.Ask == null)
currentPrice = 0;
else
currentPrice = bestPair.Bid.Price + bestPair.SpreadPrice / 2;
break;
default:
throw new ArgumentOutOfRangeException("priceType");
}
return new Unit(currentPrice) { Security = bestPair.Security };
}
///
/// Применить для цены сдвиг в зависимости от направления .
///
/// Цена.
/// Направление заявки, котороые использует в качестве направления для сдвига (для покупки сдвиг прибавляется, для продажи - вычитается).
/// Сдвиг цены.
/// Новая цена.
public static decimal ApplyOffset(this Unit price, OrderDirections direction, Unit offset)
{
if (price == null)
throw new ArgumentNullException("price");
return price.Security.ShrinkPrice((decimal)(direction == OrderDirections.Buy ? price + offset : price - offset));
}
///
/// Обрезать цену для заявки, чтобы она стала кратной минимальному шагу, а так же ограничить количество знаков после запятой.
///
/// Заявка, для которой будет обрезана цена .
/// Правило округления цены.
public static void ShrinkPrice(this Order order, ShrinkRules rule = ShrinkRules.Auto)
{
if (order == null)
throw new ArgumentNullException("order");
order.Price = order.Security.ShrinkPrice(order.Price, rule);
}
///
/// Обрезать цену, чтобы она стала кратной минимальному шагу, а так же ограничить количество знаков после запятой.
///
/// Инструмент, из которого берется значения и .
/// Цена, которую нужно сделать кратной.
/// Правило округления цены.
/// Кратная цена.
public static decimal ShrinkPrice(this Security security, decimal price, ShrinkRules rule = ShrinkRules.Auto)
{
security.CheckMinStepSize();
return price.Round(security.MinStepSize, security.Decimals,
rule == ShrinkRules.Auto
? (MidpointRounding?)null
: (rule == ShrinkRules.Less ? MidpointRounding.AwayFromZero : MidpointRounding.ToEven)).RemoveTrailingZeros();
}
///
/// Получить позицию по Моей сделке.
///
/// Моя сделка, по которой рассчитывается позиция. При покупке объем сделки
/// берется с положительным знаком, при продаже - с отрицательным.
/// Позиция.
public static decimal GetPosition(this MyTrade trade)
{
if (trade == null)
throw new ArgumentNullException("trade");
return trade.Order.Direction == OrderDirections.Buy ? trade.Trade.Volume : -trade.Trade.Volume;
}
///
/// Получить позицию по заявке.
///
/// Заявка, по которой рассчитывается позиция. При покупке позиция берется с положительным знаком, при продаже - с отрицательным.
/// Позиция.
public static decimal GetPosition(this Order order)
{
if (order == null)
throw new ArgumentNullException("order");
var volume = order.GetMatchedVolume();
return order.Direction == OrderDirections.Buy ? volume : -volume;
}
///
/// Получить позицию по портфелю.
///
/// Портфель, для которого необходимо получить позицию.
/// Позиция по портфелю.
public static decimal GetPosition(this Portfolio portfolio)
{
if (portfolio == null)
throw new ArgumentNullException("portfolio");
if (portfolio.Trader == null)
throw new ArgumentException("Портфель не имеет информацию о шлюзе.");
return portfolio.Trader.Positions.Filter(portfolio).Sum(p => p.CurrentValue);
}
///
/// Получить позицию по Моим сделкам.
///
/// Мои сделки, по которым рассчитывается позиция через метод .
/// Позиция.
public static decimal GetPosition(this IEnumerable trades)
{
return trades.Sum(t => t.GetPosition());
}
///
/// Получить объем заявки, сопоставимый с размером позиции.
///
/// Позиция по инструменту.
/// Объем заявки.
public static decimal GetOrderVolume(this Position position)
{
if (position == null)
throw new ArgumentNullException("position");
return (position.CurrentValue / position.Security.MinLotSize).Abs();
}
///
/// Сгруппировать заявки по инструменту и портфелю.
///
/// Рекомендуется использовать для уменьшения транзакционных издержек.
/// Исходные заявки.
/// Сгруппированные заявки.
public static IEnumerable Join(this IEnumerable orders)
{
if (orders == null)
throw new ArgumentNullException("orders");
return orders.GroupBy(o => new Tuple(o.Security, o.Portfolio)).Select(g =>
{
Order firstOrder = null;
foreach (var order in g)
{
if (firstOrder == null)
{
firstOrder = order;
}
else
{
var sameDir = firstOrder.Direction == order.Direction;
firstOrder.Volume += (sameDir ? 1 : -1) * order.Volume;
if (firstOrder.Volume < 0)
{
firstOrder.Direction = firstOrder.Direction.Invert();
firstOrder.Volume = firstOrder.Volume.Abs();
}
firstOrder.Price = sameDir ? firstOrder.Price.GetMiddle(order.Price) : order.Price;
}
}
if (firstOrder == null)
throw new InvalidOperationException("Заявки отсутствуют.");
if (firstOrder.Volume == 0)
return null;
firstOrder.ShrinkPrice();
return firstOrder;
})
.Where(o => o != null);
}
///
/// Рассчитать прибыль-убыток на основе сделок.
///
/// Сделки, по которым необходимо рассчитывать прибыль-убыток.
/// Прибыль-убыток.
public static decimal GetPnL(this IEnumerable trades)
{
return trades.GroupBy(t => t.Trade.Security).Sum(g =>
{
var queue = new PnLQueue(g.Key);
g.OrderBy(t => t.Trade.Time).ForEach(t => queue.Process(t, false));
return queue.RealizedPnL + queue.UnrealizedPnL;
});
}
///
/// Рассчитать прибыль-убыток для сделки.
///
/// Сделка, для которой необходимо рассчитывать прибыль-убыток.
/// Прибыль-убыток.
public static decimal GetPnL(this MyTrade trade)
{
if (trade == null)
throw new ArgumentNullException("trade");
return GetPnL(trade.Trade.Price, trade.Trade.Volume, trade.Order.Direction, (decimal)trade.Order.Security.GetCurrentPrice(trade.Order.Direction.Invert()));
}
internal static decimal GetPnL(decimal price, decimal volume, OrderDirections direction, decimal marketPrice)
{
return (price - marketPrice) * volume * (direction == OrderDirections.Sell ? 1 : -1);
}
///
/// Рассчитать прибыль-убыток на основе портфеля.
///
/// Портфель, для которого необходимо расcчитать прибыль-убыток.
/// Прибыль-убыток.
public static decimal GetPnL(this Portfolio portfolio)
{
if (portfolio == null)
throw new ArgumentNullException("portfolio");
return portfolio.CurrentValue - portfolio.BeginValue;
}
///
/// Рассчитать стоимость позиции.
///
/// Позиция.
/// Стоимость позиции.
public static decimal GetPrice(this Position position)
{
if (position == null)
throw new ArgumentNullException("position");
var security = position.Security;
return (decimal)security.GetCurrentPrice(position.GetDirection()) * position.CurrentValue * security.MinStepPrice / security.MinStepSize;
}
private static decimal GetPrice(decimal money, CurrencyTypes currency, IEnumerable positions)
{
foreach (var position in positions)
{
var price = position.GetPrice();
if (currency != position.Portfolio.Currency)
price *= Convert(currency, position.Portfolio.Currency);
money += price;
}
return money;
}
///
/// Рассчитать стоимость портфеля.
///
/// Портфель.
/// Стоимость портфеля.
public static decimal GetPrice(this Portfolio portfolio)
{
if (portfolio == null)
throw new ArgumentNullException("portfolio");
var basket = portfolio as BasketPortfolio;
return basket == null
? GetPrice(portfolio.GetFreeMoney(), portfolio.Currency, portfolio.Trader.Positions.Filter(portfolio))
: GetPrice(basket.CurrentValue, basket.Currency, basket.InnerPositions);
}
///
/// Рассчитать проскальзывание по сделке. В качестве ожидаемой цены берется .
///
/// Моя сделка, для которой необходимо рассчитать проскальзывание.
/// Считать ли отрицательное проскальзывание. Если false, то отрицательное значение проскальзывания заменяется нулем.
/// Значение проскальзывания.
public static decimal GetSlippage(this MyTrade trade, bool calcNegative = false)
{
if (trade == null)
throw new ArgumentNullException("trade");
return trade.GetSlippage(trade.Order.Price, calcNegative);
}
///
/// Рассчитать проскальзывание по сделке.
///
/// Моя сделка, для которой необходимо рассчитать проскальзывание.
/// Ожидаемая цена заявки.
/// Считать ли отрицательное проскальзывание. Если false, то отрицательное значение проскальзывания заменяется нулем.
/// Значение проскальзывания.
public static decimal GetSlippage(this MyTrade trade, decimal estimatedPrice, bool calcNegative = false)
{
if (trade == null)
throw new ArgumentNullException("trade");
if (trade.Order.Type == OrderTypes.Market)
return 0;
if (estimatedPrice <= 0)
throw new ArgumentOutOfRangeException("estimatedPrice", estimatedPrice, "Неправильное значение для ожидаемой цены.");
var slippage = (trade.Order.Direction == OrderDirections.Buy)
? trade.Trade.Price - estimatedPrice
: estimatedPrice - trade.Trade.Price;
//
// заявка была кинута глубоко в рынок
// http://stocksharp.com/forum/default.aspx?g=posts&m=3739#post3739
//
if (!calcNegative && slippage < 0)
slippage = 0;
return slippage * trade.Trade.Volume;
}
///
/// Проверить, является ли переданное время торгуемым (началась ли сессия, не закончилась ли, нет ти клиринга).
///
/// Временные интервалы работы биржи. Например, для FORTS можно передавать значения 10:00-13:59, 14:03-18:49 и 19:00-23:49.
/// Передаваемое время, которое нужно проверить.
/// True, если торгуемое время, иначе, неторгуемое.
public static bool IsTradeTime(this IEnumerable> timeBounds, DateTime dateTime)
{
if (timeBounds == null)
throw new ArgumentNullException("timeBounds");
return new WorkingTime { Times = timeBounds.ToArray() }.IsTradeTime(dateTime);
}
///
/// Проверить, является ли текущее время торгуемым (началась ли сессия, не закончилась ли, нет ти клиринга).
///
/// Инструмент, хранящий информацию о бирже . Если она равна null, то инструмент торгуется
/// круглосуточно. Иначе, информация передается в метод .
/// Передаваемое время, которое нужно проверить.
/// True, если торгуемое время, иначе, неторгуемое.
public static bool IsTradeTime(this Security security, DateTime dateTime)
{
if (security == null)
throw new ArgumentNullException("security");
return security.ExchangeBoard == null || security.ExchangeBoard.IsTradeTime(dateTime);
}
///
/// Проверить, является ли текущее время торгуемым (началась ли сессия, не закончилась ли, нет ти клиринга).
///
/// Информация о площадке, хранящая временные интервалы работы биржи .
/// Шлюз с торговой системой. В качестве текущего времени берется результат выполнения метода .
/// True, если торгуемое время, иначе, неторгуемое.
public static bool IsTradeTime(this ExchangeBoard board, ITrader trader)
{
if (trader == null)
throw new ArgumentNullException("trader");
return board.IsTradeTime(trader.GetMarketTime(board.Exchange));
}
///
/// Проверить, является ли текущее время торгуемым (началась ли сессия, не закончилась ли, нет ти клиринга).
///
/// Информация о площадке, хранящая временные интервалы работы биржи .
/// Например, для FORTS будут значения 10:00-13:59, 14:03-18:49 и 19:00-23:49.
/// Передаваемое время, которое нужно проверить.
/// True, если торгуемое время, иначе, неторгуемое.
public static bool IsTradeTime(this ExchangeBoard board, DateTime dateTime)
{
if (board == null)
throw new ArgumentNullException("board");
return board.WorkingTime.IsTradeTime(dateTime);
}
///
/// Проверить, является ли время торгуемым (началась ли сессия, не закончилась ли, нет ли клиринга).
///
/// Информация о режиме работы биржи.
/// Передаваемое время, которое нужно проверить.
/// True, если торгуемое время, иначе, неторгуемое.
public static bool IsTradeTime(this WorkingTime workingTime, DateTime dateTime)
{
var isWorkingDay = workingTime.IsTradeDate(dateTime.Date);
if (!isWorkingDay)
return false;
var time = dateTime.TimeOfDay;
var timeBounds = workingTime.Times;
return timeBounds.IsEmpty() || timeBounds.Any(r => r.Contains(time));
}
///
/// Проверить, является ли дата торгуемой.
///
/// Информация о режиме работы биржи.
/// Передаваемая дата, которую необходимо проверить.
/// Проверять ли переданную дату на день недели (суббота и воскресенье являются выходными и для них будет возвращено false).
/// True, если торгуемая дата, иначе, неторгуемая.
public static bool IsTradeDate(this WorkingTime workingTime, DateTime date, bool checkHolidays = false)
{
if (workingTime == null)
throw new ArgumentNullException("workingTime");
if (workingTime.Times.Length == 0 && workingTime.SpecialWorkingDays.Length == 0 && workingTime.SpecialHolidays.Length == 0)
return true;
bool isWorkingDay;
if (checkHolidays && (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday))
isWorkingDay = workingTime.SpecialWorkingDays.Contains(date.Date);
else
isWorkingDay = !workingTime.SpecialHolidays.Contains(date.Date);
return isWorkingDay;
}
///
/// Заполнить праздниками по-умолчанию.
///
/// Площадка.
/// Начальный год.
/// Конечный год.
public static void ApplyDefaultHolidays(this ExchangeBoard board, DateTime startYear, DateTime endYear)
{
if (board == null)
throw new ArgumentNullException("board");
if (startYear >= endYear)
throw new ArgumentOutOfRangeException("endYear");
var holidays = new List();
for (var year = startYear.Year; year <= endYear.Year; year++)
{
for (var i = 1; i <= 10; i++)
holidays.Add(new DateTime(year, 1, i));
holidays.Add(new DateTime(year, 2, 23));
holidays.Add(new DateTime(year, 3, 8));
holidays.Add(new DateTime(year, 5, 1));
holidays.Add(new DateTime(year, 5, 2));
holidays.Add(new DateTime(year, 5, 9));
holidays.Add(new DateTime(year, 6, 13));
holidays.Add(new DateTime(year, 11, 4));
}
board.WorkingTime.SpecialHolidays = holidays.ToArray();
}
///
/// Получить биржевое время.
///
/// Инструмент, из которого будет взята информация о бирже .
/// Биржевое время.
public static DateTime GetMarketTime(this Security security)
{
if (security == null)
throw new ArgumentNullException("security");
return security.CheckTrader().GetMarketTime(security.CheckExchangeBoard().Exchange);
}
///
/// Создать копию заявки для перерегистрации с новым объемом, равным заявки .
///
/// Оригинальная заявка.
/// Цена новой заявки.
/// Новая заявка.
public static Order ReRegisterClone(this Order oldOrder, decimal newPrice)
{
return oldOrder.ReRegisterClone(newPrice, oldOrder.Balance);
}
///
/// Создать копию заявки для перерегистрации.
///
/// Оригинальная заявка.
/// Цена новой заявки.
/// Объем новой заявки.
/// Новая заявка.
public static Order ReRegisterClone(this Order oldOrder, decimal newPrice, decimal newVolume)
{
if (oldOrder == null)
throw new ArgumentNullException("oldOrder");
var newOrder = oldOrder.Clone();
newOrder.Price = newPrice;
newOrder.Volume = newVolume;
return newOrder;
}
private static readonly ChannelFactory _dailyInfoFactory = new ChannelFactory(new BasicHttpBinding(), new EndpointAddress("http://www.cbr.ru/dailyinfowebserv/dailyinfo.asmx"));
private static readonly Dictionary> _rateInfo = new Dictionary>();
///
/// Сконвертировать одну валюту в другую.
///
/// Валюта, из которой нужно произвести конвертацию.
/// Код валюты, в которую нужно произвести конвертацию.
/// Сконвертированная валюта.
public static Currency Convert(this Currency currencyFrom, CurrencyTypes currencyTypeTo)
{
if (currencyFrom == null)
throw new ArgumentNullException("currencyFrom");
return new Currency { Type = currencyTypeTo, Value = currencyFrom.Value * currencyFrom.Type.Convert(currencyTypeTo) };
}
///
/// Получить курс конвертации одной валюту в другую.
///
/// Код валюты, из которой нужно произвести конвертацию.
/// Код валюты, в которую нужно произвести конвертацию.
/// Курс.
public static decimal Convert(this CurrencyTypes from, CurrencyTypes to)
{
return from.Convert(to, DateTime.Today);
}
///
/// Получить курс конвертации одной валюту в другую на определенную дату.
///
/// Код валюты, из которой нужно произвести конвертацию.
/// Код валюты, в которую нужно произвести конвертацию.
/// Дата курса.
/// Курс.
public static decimal Convert(this CurrencyTypes from, CurrencyTypes to, DateTime date)
{
if (from == to)
return 1;
var info = _rateInfo.SafeAdd(date, key =>
{
var i = _dailyInfoFactory.Invoke(c => c.GetCursOnDate(key));
return i.Tables[0].Rows.Cast().ToDictionary(r => r[4].To(), r => r[2].To());
});
if (from != CurrencyTypes.RUB && !info.ContainsKey(from))
throw new ArgumentException("Валюта '{0}' не поддерживается.".Put(from), "from");
if (to != CurrencyTypes.RUB && !info.ContainsKey(to))
throw new ArgumentException("Валюта '{0}' не поддерживается.".Put(to), "to");
if (from == CurrencyTypes.RUB)
return 1 / info[to];
else if (to == CurrencyTypes.RUB)
return info[from];
else
return info[from] / info[to];
}
///
/// Создать из обычного стакана разреженный с минимальным шагом цены равный .
///
/// В разреженном стакане показаны котировки на те цены, по которым не выставлены заявки. Объем таких котировок равен 0.
///
///
/// Обычный стакан.
/// Разреженный стакан.
public static MarketDepth Sparse(this MarketDepth depth)
{
if (depth == null)
throw new ArgumentNullException("depth");
return depth.Sparse(depth.Security.MinStepSize);
}
///
/// Создать из обычного стакана разреженный.
///
/// В разреженном стакане показаны котировки на те цены, по которым не выставлены заявки. Объем таких котировок равен 0.
///
///
/// Обычный стакан.
/// Минимальный шаг цены.
/// Разреженный стакан.
public static MarketDepth Sparse(this MarketDepth depth, decimal minStepSize)
{
if (depth == null)
throw new ArgumentNullException("depth");
var bids = depth.Bids.Sparse(minStepSize);
var asks = depth.Asks.Sparse(minStepSize);
var spreadQuotes = depth.BestPair.Sparse(minStepSize);
return new MarketDepth(depth.Security).Update(
bids.Concat(spreadQuotes.Where(q => q.OrderDirection == OrderDirections.Buy)),
asks.Concat(spreadQuotes.Where(q => q.OrderDirection == OrderDirections.Sell)),
false, depth.LastChangeTime);
}
///
/// Создать из пары котировок разреженную коллекцию котировок, которая будет входить в диапазон между парой.
///
/// В разреженной коллекции показаны котировки на те цены, по которым не выставлены заявки. Объем таких котировок равен 0.
///
///
/// Пара обычных котировок.
/// Минимальный шаг цены.
/// Разреженная коллекция котировок.
public static IEnumerable Sparse(this MarketDepthPair pair, decimal minStepSize)
{
if (pair == null)
throw new ArgumentNullException("pair");
if (minStepSize <= 0)
throw new ArgumentOutOfRangeException("minStepSize", minStepSize, "Размер шага цены меньше допустимого.");
if (pair.SpreadPrice == 0 && pair.SpreadPrice <= minStepSize)
return new Quote[0];
var security = pair.Bid.Security;
var retVal = new List();
var bidPrice = pair.Bid.Price;
var askPrice = pair.Ask.Price;
var minStepDecimal = minStepSize;
while (true)
{
bidPrice += minStepDecimal;
askPrice -= minStepDecimal;
if (bidPrice > askPrice)
break;
retVal.Add(new Quote
{
Security = security,
Price = bidPrice,
OrderDirection = OrderDirections.Buy,
});
if (bidPrice == askPrice)
break;
retVal.Add(new Quote
{
Security = security,
Price = askPrice,
OrderDirection = OrderDirections.Sell,
});
}
return retVal.OrderBy(q => q.Price);
}
///
/// Создать из обычных котировок разреженную коллекцию котировок.
///
/// В разреженной коллекции показаны котировки на те цены, по которым не выставлены заявки. Объем таких котировок равен 0.
///
///
/// Обычные котировки. Коллекция должна содержать одинаково направленные котировки (только биды или только оффера).
/// Минимальный шаг цены.
/// Разреженная коллекция котировок.
public static IEnumerable Sparse(this IEnumerable quotes, decimal minStepSize)
{
if (quotes == null)
throw new ArgumentNullException("quotes");
if (minStepSize <= 0)
throw new ArgumentOutOfRangeException("minStepSize", minStepSize, "Размер шага цены меньше допустимого.");
var list = quotes.OrderBy(q => q.Price).ToList();
if (list.Count < 2)
return new Quote[0];
var firstQuote = list[0];
var retVal = new List();
for (var i = 0; i < (list.Count - 1); i++)
{
var from = list[i];
if (from.OrderDirection != firstQuote.OrderDirection)
throw new ArgumentException("Коллекция котировок не одинаково направлена.", "quotes");
var toPrice = list[i + 1].Price;
for (var price = (from.Price + minStepSize); price < toPrice; price += minStepSize)
{
retVal.Add(new Quote
{
Security = firstQuote.Security,
Price = price,
OrderDirection = firstQuote.OrderDirection,
});
}
}
if (firstQuote.OrderDirection == OrderDirections.Buy)
return retVal.OrderByDescending(q => q.Price);
else
return retVal;
}
///
/// Объединить первоначальный стакан, и его разреженное представление.
///
/// Первоначальный стакан.
/// Разреженный стакан.
/// Объединенный стакан.
public static MarketDepth Join(this MarketDepth original, MarketDepth rare)
{
if (original == null)
throw new ArgumentNullException("original");
if (rare == null)
throw new ArgumentNullException("rare");
return new MarketDepth(original.Security).Update(original.Concat(rare), original.LastChangeTime);
}
///
/// Сгруппировать стакан по ценовому диапазону.
///
/// Стакан, который необходимо сгруппировать.
/// Ценовой диапазон, по которому необходимо произвести группировку.
/// Сгруппированный стакан.
public static MarketDepth Group(this MarketDepth depth, Unit priceRange)
{
return new MarketDepth(depth.Security).Update(depth.Bids.Group(priceRange), depth.Asks.Group(priceRange), true, depth.LastChangeTime);
}
///
/// Разгруппировать стакан, сгруппированный через метод .
///
/// Сгруппированный стакан.
/// Разгруппированный стакан.
public static MarketDepth UnGroup(this MarketDepth depth)
{
return new MarketDepth(depth.Security).Update(
depth.Bids.Cast().SelectMany(gq => gq.InnerQuotes),
depth.Asks.Cast().SelectMany(gq => gq.InnerQuotes),
false, depth.LastChangeTime);
}
///
/// Удалить в стакане те уровни, которые должны исчезнуть в случае появления сделок .
///
/// Стакан, который необходимо очистить.
/// Сделки.
public static void EmulateTrades(this MarketDepth depth, IEnumerable trades)
{
if (depth == null)
throw new ArgumentNullException("depth");
if (trades == null)
throw new ArgumentNullException("trades");
var changedVolume = new Dictionary();
var maxTradePrice = decimal.MinValue;
var minTradePrice = decimal.MaxValue;
foreach (var trade in trades)
{
minTradePrice = minTradePrice.Min(trade.Price);
maxTradePrice = maxTradePrice.Max(trade.Price);
var quote = depth.GetQuote(trade.Price);
if (null == quote)
continue;
decimal vol;
if (!changedVolume.TryGetValue(trade.Price, out vol))
vol = quote.Volume;
vol -= trade.Volume;
changedVolume[quote.Price] = vol;
}
var bids = new Quote[depth.Bids.Length];
Action a1 = () =>
{
var i = 0;
var count = 0;
for (; i < depth.Bids.Length; i++)
{
var quote = depth.Bids[i];
var price = quote.Price;
if (price > minTradePrice)
continue;
if (price == minTradePrice)
{
decimal vol;
if (changedVolume.TryGetValue(price, out vol))
{
if (vol <= 0)
continue;
quote = quote.Clone();
quote.Volume = vol;
}
}
bids[count++] = quote;
i++;
break;
}
Array.Copy(depth.Bids, i, bids, count, depth.Bids.Length - i);
Array.Resize(ref bids, count + (depth.Bids.Length - i));
};
a1();
var asks = new Quote[depth.Asks.Length];
Action a2 = () =>
{
var i = 0;
var count = 0;
for (; i < depth.Asks.Length; i++)
{
var quote = depth.Asks[i];
var price = quote.Price;
if (price < maxTradePrice)
continue;
if (price == maxTradePrice)
{
decimal vol;
if (changedVolume.TryGetValue(price, out vol))
{
if (vol <= 0)
continue;
quote = quote.Clone();
quote.Volume = vol;
}
}
asks[count++] = quote;
i++;
break;
}
Array.Copy(depth.Asks, i, asks, count, depth.Asks.Length - i);
Array.Resize(ref asks, count + (depth.Asks.Length - i));
};
a2();
depth.Update(bids, asks, depth.LastChangeTime);
}
///
/// Сгруппировать котировки по ценовому диапазону.
///
/// Котировки, которые необходимо сгруппировать.
/// Ценовой диапазон, по которому необходимо произвести группировку.
/// Сгруппированные котировки.
public static IEnumerable Group(this IEnumerable quotes, Unit priceRange)
{
if (quotes == null)
throw new ArgumentNullException("quotes");
if (priceRange == null)
throw new ArgumentNullException("priceRange");
//if (priceRange.Value < double.Epsilon)
// throw new ArgumentOutOfRangeException("priceRange", priceRange, "Размер группировки меньше допустимого.");
if (quotes.Count() < 2)
return new AggregatedQuote[0];
var firstQuote = quotes.First();
var retVal = quotes.GroupBy(q => priceRange.AlignPrice(firstQuote.Price, q.Price)).Select(g =>
{
var aggQuote = new AggregatedQuote { Price = g.Key };
aggQuote.InnerQuotes.AddRange(g);
return aggQuote;
});
retVal = firstQuote.OrderDirection == OrderDirections.Buy ? retVal.OrderBy(q => q.Price) : retVal.OrderByDescending(q => q.Price);
return retVal;
}
internal static decimal AlignPrice(this Unit priceRange, decimal firstPrice, decimal price)
{
if (priceRange == null)
throw new ArgumentNullException("priceRange");
decimal priceLevel;
if (priceRange.Type == UnitTypes.Percent)
priceLevel = (decimal)(firstPrice + MathHelper.Floor((((price - firstPrice) * 100) / firstPrice), priceRange.Value).Percents());
else
priceLevel = MathHelper.Floor(price, (decimal)priceRange);
return priceLevel;
}
///
/// Вычислить изменение между стаканами.
///
/// Первый стакан.
/// Второй стакан.
/// Стакан, хранищий только изменения.
public static MarketDepth GetDelta(this MarketDepth from, MarketDepth to)
{
if (from == null)
throw new ArgumentNullException("from");
if (to == null)
throw new ArgumentNullException("to");
var delta = new MarketDepth(from.Security);
delta.Update(GetDelta(from.Bids, to.Bids), GetDelta(from.Asks, to.Asks), false, to.LastChangeTime);
return delta;
}
///
/// Вычислить изменение между котировками.
///
/// Первые котировки.
/// Вторые котировки.
/// Изменения.
public static Quote[] GetDelta(IEnumerable from, IEnumerable to)
{
var mapTo = to.ToDictionary(q => q.Price);
var mapFrom = from.ToDictionary(q => q.Price);
foreach (var pair in mapFrom)
{
var price = pair.Key;
var quoteFrom = pair.Value;
var quoteTo = mapTo.TryGetValue(price);
if (quoteTo != null)
{
if (quoteTo.Volume == quoteFrom.Volume)
mapTo.Remove(price); // то же самое
}
else
{
var empty = quoteFrom.Clone();
empty.Volume = 0; // была а теперь нет
mapTo[price] = empty;
}
}
return mapTo.Values.ToArray();
}
///
/// Вычислить приращение между стаканами.
///
/// Первый стакан.
/// Второй стакан.
/// Стакан, хранищий только приращения.
public static MarketDepth GetDiff(this MarketDepth from, MarketDepth to)
{
if (from == null)
throw new ArgumentNullException("from");
if (to == null)
throw new ArgumentNullException("to");
var diff = new MarketDepth(from.Security);
diff.Update(GetDiff(from.Bids, to.Bids), GetDiff(from.Asks, to.Asks), false, to.LastChangeTime);
return diff;
}
///
/// Вычислить приращение между котировками.
///
/// Первые котировки.
/// Вторые котировки.
/// Изменения.
public static Quote[] GetDiff(IEnumerable from, IEnumerable to)
{
var mapTo = to.ToDictionary(q => q.Price);
var mapFrom = from.ToDictionary(q => q.Price);
foreach (var pair in mapFrom)
{
var price = pair.Key;
var quoteFrom = pair.Value;
var quoteTo = mapTo.TryGetValue(price);
if (quoteTo != null)
{
if (quoteTo.Volume == quoteFrom.Volume)
mapTo.Remove(price); // то же самое
else
{
var d = quoteTo.Clone();
d.Volume -= quoteFrom.Volume;
mapTo[price] = d;
}
}
else
{
var d = quoteFrom.Clone();
d.Volume = - quoteFrom.Volume; // была а теперь нет
mapTo[price] = d;
}
}
return mapTo.Values.ToArray();
}
///
/// Прибавить изменение к первому стакану.
///
/// Первый стакан.
/// Изменение.
/// Измененный стакан.
public static MarketDepth AddDelta(this MarketDepth from, MarketDepth delta)
{
if (from == null)
throw new ArgumentNullException("from");
if (delta == null)
throw new ArgumentNullException("delta");
var depth = new MarketDepth(from.Security);
depth.Update(
AddDelta(from.Bids, delta.Bids, true),
AddDelta(from.Asks, delta.Asks, false),
delta.LastChangeTime);
return depth;
}
///
/// Прибавить изменение к котировки.
///
/// Котировки.
/// Изменения.
/// Признак направления котировок.
/// Измененные котировки.
public static Quote[] AddDelta(Quote[] from, Quote[] delta, bool isBids)
{
if (from.Length == 0)
return delta;
if (delta.Length == 0)
return from;
var result = new Quote[from.Length + delta.Length];
var fromIndex = 0;
var deltaIndex = 0;
var outIndex = 0;
var currFrom = from[fromIndex];
var currDelta = delta[deltaIndex];
var currFromPrice = currFrom.Price;
var currDeltaPrice = currDelta.Price;
while (true)
{
var cmp = Decimal.Compare(currFromPrice, currDeltaPrice);
if (cmp < 0 && isBids || cmp > 0 && !isBids) //currDeltaPrice > currFromPrice
{
result[outIndex++] = currDelta;
deltaIndex++;
if (deltaIndex < delta.Length)
{
currDelta = delta[deltaIndex];
currDeltaPrice = currDelta.Price;
}
else
break;
}
else if (cmp < 0 && !isBids || cmp > 0 && isBids)
{
result[outIndex++] = currFrom;
fromIndex++;
if (fromIndex < from.Length)
{
currFrom = from[fromIndex];
currFromPrice = currFrom.Price;
}
else
break;
}
else // cmp==0
{
if (currDelta.Volume != 0)
result[outIndex++] = currDelta;
deltaIndex++;
fromIndex++;
if (deltaIndex < delta.Length)
{
currDelta = delta[deltaIndex];
currDeltaPrice = currDelta.Price;
}
else
break;
if (fromIndex < from.Length)
{
currFrom = from[fromIndex];
currFromPrice = currFrom.Price;
}
else
break;
}
}
Array.Copy(from, fromIndex, result, outIndex, from.Length - fromIndex);
outIndex += from.Length - fromIndex;
Array.Copy(delta, deltaIndex, result, outIndex, delta.Length - deltaIndex);
outIndex += delta.Length - deltaIndex;
Array.Resize(ref result, outIndex);
return result;
}
///
/// Проверить, отменена ли заявка.
///
/// Заявка, которую необходимо проверить.
/// True, если заявка отменена, иначе, false.
public static bool IsCanceled(this Order order)
{
if (order == null)
throw new ArgumentNullException("order");
if (order.State != OrderStates.Done) // для ускорения в эмуляторе
return false;
using (order.BeginRead())
return (order.State == OrderStates.Done && order.Balance != 0);
}
///
/// Проверить, исполнена ли полностью заявка.
///
/// Заявка, которую необходимо проверить.
/// True, если заявка полностью исполнена, иначе, false.
public static bool IsMatched(this Order order)
{
if (order == null)
throw new ArgumentNullException("order");
using (order.BeginRead())
return (order.State == OrderStates.Done && order.Balance == 0);
}
///
/// Проверить, реализована ли часть объема в заявке.
///
/// Заявка, которую необходимо проверить.
/// True, если часть объема реализована, иначе, false.
public static bool IsMatchedPartially(this Order order)
{
if (order == null)
throw new ArgumentNullException("order");
using (order.BeginRead())
return (order.Balance > 0 && order.Balance != order.Volume);
}
///
/// Проверить, что не реализован ни один контракт в заявке.
///
/// Заявка, которую необходимо проверить.
/// True, если ни один контракт не реализована, иначе, false.
public static bool IsMatchedEmpty(this Order order)
{
if (order == null)
throw new ArgumentNullException("order");
using (order.BeginRead())
return (order.Balance > 0 && order.Balance == order.Volume);
}
///
/// Получить сделки заявки.
///
/// Заявки.
/// Сделки.
public static IEnumerable GetTrades(this Order order)
{
return order.CheckTrader().MyTrades.Filter(order);
}
///
/// Расcчитать реализованную часть объема для заявки.
///
/// Заявка, для которой необходимо расcчитать реализованную часть объема.
/// Проверять реализованный объем по балансу заявке () или по полученным сделкам.
/// По-умолчанию проверяется по заявке.
/// Реализованная часть объема.
public static decimal GetMatchedVolume(this Order order, bool byOrder = true)
{
if (order == null)
throw new ArgumentNullException("order");
if (order.Type == OrderTypes.Conditional)
{
//throw new ArgumentException("Стоп-заявки не могут иметь реализованный объем.", "order");
order = order.DerivedOrder;
if (order == null)
return 0;
}
return order.Volume - (byOrder ? order.Balance : order.GetTrades().Sum(o => o.Trade.Volume));
}
///
/// Получить средневзрешанную цену исполнения заявки.
///
/// Заявка, для которой необходимо получить средневзрешанную цену исполнения.
/// Средневзвешанная цена. Если заявка не существует ни одной сделки, то возвращается 0.
public static decimal GetAveragePrice(this Order order)
{
return order.GetTrades().GetAveragePrice();
}
///
/// Получить средневзрешанную цену исполнения по собственным сделкам.
///
/// Сделки, для которых необходимо получить средневзрешанную цену исполнения.
/// Средневзвешанная цена. Если сделки отсутствуют, то возвращается 0.
public static decimal GetAveragePrice(this IEnumerable trades)
{
if (trades == null)
throw new ArgumentNullException("trades");
return trades.Select(t => t.Trade).GetAveragePrice();
}
///
/// Получить средневзрешанную цену исполнения по тиковым сделкам.
///
/// Сделки, для которых необходимо получить средневзрешанную цену исполнения.
/// Средневзвешанная цена. Если сделки отсутствуют, то возвращается 0.
public static decimal GetAveragePrice(this IEnumerable trades)
{
if (trades == null)
throw new ArgumentNullException("trades");
var nominator = 0m;
var denominator = 0m;
foreach (var trade in trades)
{
nominator += trade.Price * trade.Volume;
denominator += trade.Volume;
}
if (denominator == 0)
return 0;
return nominator / denominator;
}
///
/// Получить вероятные сделки по стакану для заданной заявки.
///
/// Стакан, который в момент вызова функции отражает ситуацию на рынке.
/// Заявку, для которой необходимо расcчитать вероятные сделки.
/// Вероятные сделки.
public static IEnumerable GetTheoreticalTrades(this MarketDepth depth, Order order)
{
if (depth == null)
throw new ArgumentNullException("depth");
if (depth.Trader == null)
throw new ArgumentException("Стакан не имеет информацию о шлюзе.", "depth");
if (order == null)
throw new ArgumentNullException("order");
using (var emulator = new MarketEmulator(depth.Trader))
{
emulator.Reset(LoggingHelper.Now);
emulator.Start();
emulator.RegisterOrder(order.Clone());
emulator.UpdateQuotes(depth.Clone(), null);
return emulator.MyTrades;
}
}
///
/// Получить вероятные сделки по стакану для рыночной цены и заданного объема.
///
/// Стакан, который в момент вызова функции отражает ситуацию на рынке.
/// Направление заявки.
/// Объем, который предполагается реализовать.
/// Вероятные сделки.
public static IEnumerable GetTheoreticalTrades(this MarketDepth depth, OrderDirections orderDirection, decimal volume)
{
return depth.GetTheoreticalTrades(orderDirection, volume, 0);
}
private static readonly Portfolio _testPf = new Portfolio { Name = "test account" };
///
/// Получить вероятные сделки по стакану для заданных цены и объема.
///
/// Стакан, который в момент вызова функции отражает ситуацию на рынке.
/// Направление заявки.
/// Объем, который предполагается реализовать.
/// Цена, по которой предполагает выставить заявку. Если она равна 0, то будет рассматриваться вариант рыночной заявки.
/// Вероятные сделки.
public static IEnumerable GetTheoreticalTrades(this MarketDepth depth, OrderDirections orderDirection, decimal volume, decimal price)
{
if (depth == null)
throw new ArgumentNullException("depth");
return depth.GetTheoreticalTrades(new Order
{
Direction = orderDirection,
Type = price == 0 ? OrderTypes.Market : OrderTypes.Limit,
Security = depth.Security,
Price = price,
Volume = volume,
Portfolio = _testPf
});
}
///
/// Поменять направление заявки на противоположное.
///
/// Первоначальное направление.
/// Противоположное направление.
public static OrderDirections Invert(this OrderDirections direction)
{
return direction == OrderDirections.Buy ? OrderDirections.Sell : OrderDirections.Buy;
}
///
/// Получить направление заявки для позиции.
///
///
/// Положительное значение равно , отрицательное - , нулевое - null.
///
/// Значение позиции.
/// Направление заявки.
public static OrderDirections? GetDirection(this Position position)
{
if (position == null)
throw new ArgumentNullException("position");
return position.CurrentValue.GetDirection();
}
///
/// Получить направление заявки для позиции.
///
///
/// Положительное значение равно , отрицательное - , нулевое - null.
///
/// Значение позиции.
/// Направление заявки.
public static OrderDirections? GetDirection(this decimal position)
{
if (position == 0)
return null;
return position > 0 ? OrderDirections.Buy : OrderDirections.Sell;
}
///
/// Отменить группу заявок на бирже по фильтру.
///
/// Шлюз взаимодействия с торговыми системами.
/// Группа заявок, из которой необходимо найти требуемые заявки и отменить их.
/// True, если нужно отменить только стоп-заявки, false - если только обычный и null - если оба типа.
/// Портфель. Если значение равно null, то портфель не попадает в фильтр снятия заявок.
/// Направление заявки. Если значение равно null, то направление не попадает в фильтр снятия заявок.
/// Код класса. Если переданная строка пустая, то код не попадает в фильтр снятия заявок.
/// Инструмент. Если значение равно null, то инструмент не попадает в фильтр снятия заявок.
public static void CancelOrders(this ITrader trader, IEnumerable orders, bool? isStopOrder = null, Portfolio portfolio = null, OrderDirections? direction = null, string classCode = null, Security security = null)
{
if (trader == null)
throw new ArgumentNullException("trader");
if (orders == null)
throw new ArgumentNullException("orders");
foreach (var order in orders.Where(o => o.State != OrderStates.Done).ToArray())
{
if (isStopOrder == null || (order.Type == OrderTypes.Conditional) == isStopOrder)
{
if (portfolio == null || (order.Portfolio == portfolio))
{
if (direction == null || order.Direction == direction)
{
if (classCode.IsEmpty() || order.Security.Class == classCode)
{
if (security == null || order.Security == security)
{
trader.CancelOrder(order);
}
}
}
}
}
}
}
///
/// Отфильтровать заявки для заданного инструмента.
///
/// Все заявки, в которых необходимо искать требуемые.
/// Инструмент, для которого нужно отфильтровать заявки.
/// Отфильтрованные заявки.
public static IEnumerable Filter(this IEnumerable orders, Security security)
{
if (orders == null)
throw new ArgumentNullException("orders");
if (security == null)
throw new ArgumentNullException("security");
var basket = security as BasketSecurity;
return basket == null ? orders.Where(o => o.Security == security) : basket.InnerSecurities.SelectMany(s => Filter(orders, s));
}
///
/// Отфильтровать заявки для заданного портфеля.
///
/// Все заявки, в которых необходимо искать требуемые.
/// Портфель, для которого нужно отфильтровать заявки.
/// Отфильтрованные заявки.
public static IEnumerable Filter(this IEnumerable orders, Portfolio portfolio)
{
if (orders == null)
throw new ArgumentNullException("orders");
if (portfolio == null)
throw new ArgumentNullException("portfolio");
return orders.Where(p => p.Portfolio == portfolio);
}
///
/// Отфильтровать заявки для заданного состояния.
///
/// Все заявки, в которых необходимо искать требуемые.
/// Условие выборки заявки.
/// Отфильтрованные заявки.
public static IEnumerable Filter(this IEnumerable orders, Func query)
{
if (orders == null)
throw new ArgumentNullException("orders");
return orders.Where(query);
//return orders.Where(p => p.State == state);
}
///
/// Отфильтровать заявки для заданного направления.
///
/// Все заявки, в которых необходимо искать требуемые.
/// Направление заявки.
/// Отфильтрованные заявки.
public static IEnumerable Filter(this IEnumerable orders, OrderDirections direction)
{
if (orders == null)
throw new ArgumentNullException("orders");
return orders.Where(p => p.Direction == direction);
}
///
/// Отфильтровать сделки для заданного инструмента.
///
/// Все сделки, в которых необходимо искать требуемые.
/// Инструмент, для которого нужно отфильтровать сделки.
/// Отфильтрованные сделки.
public static IEnumerable Filter(this IEnumerable trades, Security security)
{
if (trades == null)
throw new ArgumentNullException("trades");
if (security == null)
throw new ArgumentNullException("security");
var basket = security as BasketSecurity;
return basket == null ? trades.Where(t => t.Security == security) : basket.InnerSecurities.SelectMany(s => Filter(trades, s));
}
///
/// Отфильтровать сделки для заданного временного периода.
///
/// Все сделки, в которых необходимо искать требуемые.
/// Дата, с которой нужно искать сделки.
/// Дата, до которой нужно искать сделки.
/// Отфильтрованные сделки.
public static IEnumerable Filter(this IEnumerable trades, DateTime from, DateTime to)
{
if (trades == null)
throw new ArgumentNullException("trades");
return trades.Where(trade => trade.Time >= from && trade.Time < to);
}
///
/// Отфильтровать позиции для заданного инструмента.
///
/// Все позиции, в которых необходимо искать требуемые.
/// Инструмент, для которого нужно отфильтровать позиции.
/// Отфильтрованные позиции.
public static IEnumerable Filter(this IEnumerable positions, Security security)
{
if (positions == null)
throw new ArgumentNullException("positions");
if (security == null)
throw new ArgumentNullException("security");
var basket = security as BasketSecurity;
return basket == null ? positions.Where(p => p.Security == security) : basket.InnerSecurities.SelectMany(s => Filter(positions, s));
}
///
/// Отфильтровать позиции для заданного портфеля.
///
/// Все позиции, в которых необходимо искать требуемые.
/// Портфель, для которого нужно отфильтровать позиции.
/// Отфильтрованные позиции.
public static IEnumerable Filter(this IEnumerable positions, Portfolio portfolio)
{
if (positions == null)
throw new ArgumentNullException("positions");
if (portfolio == null)
throw new ArgumentNullException("portfolio");
return positions.Where(p => p.Portfolio == portfolio);
}
///
/// Отфильтровать собственные сделки для заданного инструмента.
///
/// Все собственные сделки, в которых необходимо искать требуемые.
/// Инструмент, по которому нужно найти сделки.
/// Отфильтрованные сделки.
public static IEnumerable Filter(this IEnumerable myTrades, Security security)
{
if (myTrades == null)
throw new ArgumentNullException("myTrades");
if (security == null)
throw new ArgumentNullException("security");
var basket = security as BasketSecurity;
return basket == null ? myTrades.Where(t => t.Order.Security == security) : basket.InnerSecurities.SelectMany(s => Filter(myTrades, s));
}
///
/// Отфильтровать собственные сделки для заданного портфеля.
///
/// Все собственные сделки, в которых необходимо искать требуемые.
/// Портфель, для которого нужно отфильтровать сделки.
/// Отфильтрованные сделки.
public static IEnumerable Filter(this IEnumerable myTrades, Portfolio portfolio)
{
if (myTrades == null)
throw new ArgumentNullException("myTrades");
if (portfolio == null)
throw new ArgumentNullException("portfolio");
return myTrades.Where(t => t.Order.Portfolio == portfolio);
}
///
/// Отфильтровать собственные сделки для заданной заявки.
///
/// Все собственные сделки, в которых необходимо искать требуемые.
/// Заявка, для которой нужно отфильтровать сделки.
/// Отфильтрованные заявки.
public static IEnumerable Filter(this IEnumerable myTrades, Order order)
{
if (myTrades == null)
throw new ArgumentNullException("myTrades");
if (order == null)
throw new ArgumentNullException("order");
return myTrades.Where(t => t.Order == order);
}
///
/// Определить, является ли стакан пустым.
///
/// Стакан.
/// True, если стакан пустой, иначе, false.
public static bool IsFullEmpty(this MarketDepth depth)
{
if (depth == null)
throw new ArgumentNullException("depth");
return depth.Bids.Length ==0 && depth.Asks.Length == 0;
}
///
/// Определить, является ли стакан пустым на половину.
///
/// Стакан.
/// True, если стакан пустой на половину, иначе, false.
public static bool IsHalfEmpty(this MarketDepth depth)
{
if (depth == null)
throw new ArgumentNullException("depth");
return (depth.BestPair.Bid == null || depth.BestPair.Ask == null) && (depth.BestPair.Bid != depth.BestPair.Ask);
}
///
/// Получить T+N дату.
///
/// Информация о режиме работы биржи.
/// Начальная дата T.
/// Размер N.
/// Конечная дата T+N.
public static DateTime GetTPlusNDate(this WorkingTime time, DateTime date, int n)
{
if (time == null)
throw new ArgumentNullException("time");
date = date.Date;
while (n > 0)
{
if (time.IsTradeDate(date))
n--;
date = date.AddDays(1);
}
return date;
}
///
/// Перевести локальное время в биржевое.
///
/// Информация о бирже.
/// Локальное время.
/// Время с биржевым сдвигом.
public static DateTime ToExchangeTime(this Exchange exchange, DateTime time)
{
return exchange.ToExchangeTime(time, TimeZoneInfo.Local);
}
///
/// Перевести локальное время в биржевое.
///
/// Информация о бирже.
/// Локальное время.
/// Времемнная зона, в которой записано значение .
/// Время с биржевым сдвигом.
public static DateTime ToExchangeTime(this Exchange exchange, DateTime time, TimeZoneInfo sourceZone)
{
if (exchange == null)
throw new ArgumentNullException("exchange");
return TimeZoneInfo.ConvertTime(time, sourceZone, exchange.TimeZoneInfo);
}
///
/// Перевести биржевое время в локальное.
///
/// Информация о бирже, из которой будет использоваться .
/// Биржевое время.
/// Локальное время.
public static DateTime ToLocalTime(this Exchange exchange, DateTime exchangeTime)
{
if (exchange == null)
throw new ArgumentNullException("exchange");
return TimeZoneInfo.ConvertTime(exchangeTime, exchange.TimeZoneInfo, TimeZoneInfo.Local);
}
///
/// Перевести биржевое время в UTC.
///
/// Информация о бирже, из которой будет использоваться .
/// Биржевое время.
/// Биржевое время в UTC.
public static DateTime ToUtc(this Exchange exchange, DateTime exchangeTime)
{
if (exchange == null)
throw new ArgumentNullException("exchange");
return TimeZoneInfo.ConvertTimeToUtc(exchangeTime, exchange.TimeZoneInfo);
}
///
/// Инициализировать . Задержка равна разнице между и .
///
/// Стакан.
public static MarketDepth InitLatency(this MarketDepth depth)
{
if (depth == null)
throw new ArgumentNullException("depth");
depth.Latency = depth.LastChangeTime.GetLatency(depth.Security);
return depth;
}
///
/// Инициализировать . Задержка равна разнице между и .
///
/// Тиковая сделка.
public static Trade InitLatency(this Trade trade)
{
if (trade == null)
throw new ArgumentNullException("trade");
trade.Latency = trade.Time.GetLatency(trade.Security);
return trade;
}
///
/// Инициализировать . Задержка равна разнице между и .
///
/// Собственная сделка.
public static MyTrade InitLatency(this MyTrade trade)
{
if (trade == null)
throw new ArgumentNullException("trade");
trade.Latency = (trade is OrderLogItem ? trade.Order.Time : trade.Trade.Time).GetLatency(trade.Order.Security);
return trade;
}
private static TimeSpan GetLatency(this DateTime time, Security security)
{
return GetLatency(LoggingHelper.Now, time, security.CheckExchangeBoard().Exchange);
}
///
/// Рассчитать задержку.
///
/// Текущее время.
/// Биржевое время.
/// Информация о бирже.
/// Задержка.
public static TimeSpan GetLatency(DateTime now, DateTime time, Exchange exchange)
{
return now - exchange.ToLocalTime(time);
}
///
/// Получить цену, которую необходимо оплатить для совершения торговой операции.
///
/// Инструмент.
/// Направление торговой операции.
/// Цена.
public static decimal GetMarginPrice(this Security security, OrderDirections? direction = null)
{
if (security == null)
throw new ArgumentNullException("security");
if (direction == null)
return security.LastTrade != null ? security.LastTrade.Price : 0;
else
return direction == OrderDirections.Buy ? security.MarginBuy : security.MarginSell;
}
///
/// Получить размер свободных денежных средств в портфеле.
///
/// Портфель
/// Использовать ли для рассчета размер плеча.
/// Размер свободных денежных средств.
public static decimal GetFreeMoney(this Portfolio portfolio, bool useLeverage = false)
{
if (portfolio == null)
throw new ArgumentNullException("portfolio");
var freeMoney = portfolio.ExchangeBoard == ExchangeBoard.Forts
? portfolio.BeginValue - portfolio.CurrentValue + portfolio.VariationMargin
: portfolio.CurrentValue;
return useLeverage ? freeMoney * portfolio.Leverage : freeMoney;
}
///
/// Получить для непрерывного инструмента реальные экспирирующиеся инструменты.
///
/// Непрерывный инструмент.
/// Базовая часть кода инструмента.
/// Начало диапазона экспираций.
/// Окончание диапазона экспираций.
/// Сгенерировать исключение, если какой-либо из инструментов для переданного отсутствует.
/// Экспирирующиеся инструменты.
public static IEnumerable GetFortsJumps(this ContinuousSecurity continuousSecurity, string baseCode, DateTime from, DateTime to, bool throwIfNotExists = true)
{
if (continuousSecurity == null)
throw new ArgumentNullException("continuousSecurity");
if (baseCode.IsEmpty())
throw new ArgumentNullException("baseCode");
if (from > to)
throw new ArgumentOutOfRangeException("from");
var trader = continuousSecurity.CheckTrader();
for (var year = from.Year; year <= to.Year; year++)
{
var monthFrom = year == from.Year ? from.Month : 1;
var monthTo = year == to.Year ? to.Month : 12;
for (var month = monthFrom; month <= monthTo; month++)
{
char monthCode;
switch (month)
{
case 3:
monthCode = 'H';
break;
case 6:
monthCode = 'M';
break;
case 9:
monthCode = 'U';
break;
case 12:
monthCode = 'Z';
break;
default:
continue;
}
var yearStr = year.To();
var code = baseCode + monthCode + yearStr.Substring(yearStr.Length - 1, 1);
foreach (var security in trader.Securities)
{
if (security.Code.CompareIgnoreCase(code))
yield return security;
}
if (throwIfNotExists)
throw new InvalidOperationException("Инструмент с кодом {0} не найден.".Put(code));
}
}
}
///
/// Заполнить переходы .
///
/// Непрерывный инструмент.
/// Базовая часть кода инструмента.
/// Начало диапазона экспираций.
/// Окончание диапазона экспираций.
public static void FillFortsJumps(this ContinuousSecurity continuousSecurity, string baseCode, DateTime from, DateTime to)
{
var securities = continuousSecurity.GetFortsJumps(baseCode, from, to);
foreach (var security in securities)
{
if (security.ExpiryDate == null)
throw new InvalidOperationException("Инструмент {0} не имеет информацию о времени экспирации.".Put(security.Id));
continuousSecurity.ExpirationJumps.Add(security, (DateTime)security.ExpiryDate);
}
}
private sealed class CashPosition : Position, IDisposable
{
private readonly Portfolio _portfolio;
public CashPosition(Portfolio portfolio)
{
if (portfolio == null)
throw new ArgumentNullException("portfolio");
_portfolio = portfolio;
Portfolio = _portfolio;
Security = new Security
{
Id = _portfolio.Name,
Name = _portfolio.Name,
};
UpdatePosition();
_portfolio.Trader.PortfoliosChanged += TraderOnPortfoliosChanged;
}
private void UpdatePosition()
{
BeginValue = _portfolio.BeginValue;
CurrentValue = _portfolio.CurrentValue;
BlockedValue = _portfolio.Commission;
}
private void TraderOnPortfoliosChanged(IEnumerable portfolios)
{
if (portfolios.Contains(_portfolio))
UpdatePosition();
}
void IDisposable.Dispose()
{
_portfolio.Trader.PortfoliosChanged -= TraderOnPortfoliosChanged;
}
}
///
/// Сконвертировать портфель в денежную позицию.
///
/// Портфель с торговым счетом.
/// Денежная позиция.
public static Position ToCashPosition(this Portfolio portfolio)
{
return new CashPosition(portfolio);
}
private sealed class NativePositionManager : IPositionManager
{
private readonly Position _position;
public NativePositionManager(Position position)
{
if (position == null)
throw new ArgumentNullException("position");
_position = position;
}
///
/// Суммарное значение позиции.
///
decimal IPositionManager.Position
{
get { return _position.CurrentValue; }
set { throw new NotSupportedException(); }
}
event Action IPositionManager.NewPosition
{
add { }
remove { }
}
event Action IPositionManager.PositionChanged
{
add { }
remove { }
}
///
/// Рассчитать позицию по заявке.
///
/// Заявка.
/// Позиция по заявке.
decimal IPositionManager.ProcessOrder(Order order)
{
throw new NotSupportedException();
}
///
/// Рассчитать позицию по сделке.
///
/// Сделка.
/// Позиция по сделке.
decimal IPositionManager.ProcessMyTrade(MyTrade trade)
{
throw new NotSupportedException();
}
IEnumerable IPositionManager.Positions
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
void IPositionManager.Reset()
{
throw new NotSupportedException();
}
}
///
/// Сконвертировать позицию в объект типа .
///
/// Позиция.
/// Менеджера расчета позиции.
public static IPositionManager ToPositionManager(this Position position)
{
if (position == null)
throw new ArgumentNullException("position");
return new NativePositionManager(position);
}
private sealed class EquityStrategy : Strategy
{
private readonly Dictionary _orders;
private readonly Dictionary, Strategy> _childStrategies = new Dictionary, Strategy>();
public EquityStrategy(IEnumerable orders, IDictionary openedPositions)
{
_orders = orders.GroupBy(o => o.Time).ToDictionary(g => g.Key, g => g.ToArray());
_childStrategies = orders.ToDictionary(GetKey, o => new Strategy
{
Portfolio = o.Portfolio,
Security = o.Security,
Position = openedPositions.TryGetValue2(o.Security) ?? 0,
});
ChildStrategies.AddRange(_childStrategies.Values);
}
protected override void OnStarted()
{
base.OnStarted();
Security
.WhenTimeCome(_orders.Keys)
.Do(time => _orders[time].ForEach(o => _childStrategies[GetKey(o)].RegisterOrder(o)))
.Apply(this);
}
private static Tuple GetKey(Order order)
{
return new Tuple(order.Security, order.Portfolio);
}
}
///
/// Сэмулировать заявки на истории.
///
/// Заявки, которые необходимо сэмулировать на истории.
/// Внешнеее хранилище для доступа к исторических данным.
/// Сделки, описывающие начальные открытые позиции.
/// Виртуальная стратегии, содержащая в себе ход эмуляционных торгов.
public static Strategy EmulateOrders(this IEnumerable orders, IStorageRegistry storageRegistry, IDictionary openedPositions)
{
if (openedPositions == null)
throw new ArgumentNullException("openedPositions");
if (storageRegistry == null)
throw new ArgumentNullException("storageRegistry");
if (orders == null)
throw new ArgumentNullException("orders");
if (orders.IsEmpty())
throw new ArgumentOutOfRangeException("orders");
using (var trader = new RealTimeEmulationTrader(new DataFeedTrader(orders.Select(o => o.Security).Distinct(), orders.Select(o => o.Portfolio).Distinct())
{
StorageRegistry = storageRegistry
}))
{
var from = orders.Min(o => o.Time).Date;
var to = from + TimeSpanHelper.LessOneDay;
var strategy = new EquityStrategy(orders, openedPositions) { Trader = trader };
var waitHandle = new object();
trader.UnderlyingTrader.StateChanged += (oldState, newState) =>
{
if (trader.UnderlyingTrader.State == EmulationStates.Started)
strategy.Start();
if (trader.UnderlyingTrader.State == EmulationStates.Stopped)
{
strategy.Stop();
lock (waitHandle)
Monitor.Pulse(waitHandle);
}
};
trader.Connect();
trader.StartExport();
trader.UnderlyingTrader.Start(from, to);
lock (waitHandle)
{
if (trader.UnderlyingTrader.State != EmulationStates.Stopped)
Monitor.Wait(waitHandle);
}
return strategy;
}
}
///
/// Записать сообщение о заявке в лог.
///
/// Получатель логов.
/// Заявка.
/// Операция, которая проводится в заявокй.
/// Дополнительная информация о заявке.
public static void AddOrderInfoLog(this ILogReceiver receiver, Order order, string operation, Func getAdditionalInfo = null)
{
receiver.AddOrderLog(LogLevels.Info, order, operation, getAdditionalInfo);
}
///
/// Записать ошибку о заявке в лог.
///
/// Получатель логов.
/// Заявка.
/// Операция, которая проводится в заявокй.
/// Дополнительная информация о заявке.
public static void AddOrderErrorLog(this ILogReceiver receiver, Order order, string operation, Func getAdditionalInfo = null)
{
receiver.AddOrderLog(LogLevels.Error, order, operation, getAdditionalInfo);
}
private static void AddOrderLog(this ILogReceiver receiver, LogLevels type, Order order, string operation, Func getAdditionalInfo)
{
if (receiver == null)
throw new ArgumentNullException("receiver");
if (order == null)
throw new ArgumentNullException("order");
var orderDescription = order.ToString();
var additionalInfo = getAdditionalInfo == null ? string.Empty : getAdditionalInfo();
receiver.AddLog(new LogMessage(receiver, receiver.CurrentTime, type, () => "{0}: {1} {2}".Put(operation, orderDescription, additionalInfo)));
}
}
}