/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Util;
namespace QuantConnect.Securities.Positions
{
///
/// Provides extension methods for
///
public static class PositionGroupExtensions
{
///
/// Gets the position in the matching the provided
///
public static IPosition GetPosition(this IPositionGroup group, Symbol symbol)
{
IPosition position;
if (!group.TryGetPosition(symbol, out position))
{
throw new KeyNotFoundException($"No position with symbol '{symbol}' exists in the group: {group}");
}
return position;
}
///
/// Creates a new with the specified .
/// If the quantity provided equals the template's quantity then the template is returned.
///
/// The group template
/// The quantity of the new group
/// The position manager to use to resolve positions
/// A position group with the same position ratios as the template but with the specified group quantity
public static IPositionGroup WithQuantity(this IPositionGroup template, decimal groupQuantity, SecurityPositionGroupModel positionMananger)
{
var positions = template.ToArray(p => p.WithLots(groupQuantity));
// Could result in an inverse strategy that would not get resolved by using the same key
if (groupQuantity < 0)
{
return positionMananger.ResolvePositionGroups(new PositionCollection(positions)).Single();
}
return new PositionGroup(template.Key, groupQuantity, positions);
}
///
/// Creates a new with each position's quantity equaling it's unit quantity
///
/// The group template
/// A position group with the same position ratios as the template but with the specified group quantity
public static IPositionGroup CreateUnitGroup(this IPositionGroup template, SecurityPositionGroupModel positionMananger)
{
return template.WithQuantity(1, positionMananger);
}
///
/// Determines whether the position group is empty
///
/// The position group
/// True if the position group is empty, that is, it has no positions, false otherwise
public static bool IsEmpty(this IPositionGroup positionGroup)
{
return positionGroup.Count == 0;
}
///
/// Checks whether the provided groups are in opposite sides, that is, each of their positions are in opposite sides.
///
/// The group to check
/// The group to check against
///
/// Whether the position groups are the inverted version of each other, that is, contain the same positions each on the opposite side
///
public static bool IsInvertedOf(this IPositionGroup group, IPositionGroup other)
{
return group.Count == other.Count
&& group.All(position => Math.Sign(position.Quantity) == -Math.Sign(other.GetPosition(position.Symbol).Quantity));
}
///
/// Checks whether the provided groups are closing/reducing each other, that is, each of their positions are in opposite sides.
///
/// The final position group that would result from a trade
/// The initial position group before a trade
/// Whether final resulting position group is a reduction of the initial one
public static bool Closes(this IPositionGroup finalGroup, IPositionGroup initialGroup)
{
// Liquidating
if (finalGroup.IsEmpty())
{
return true;
}
if (finalGroup.Count != initialGroup.Count)
{
return false;
}
// Liquidating
if (finalGroup.Quantity == 0 &&
// The initial group includes all positions being liquidated
finalGroup.All(position => initialGroup.TryGetPosition(position.Symbol, out _)))
{
return true;
}
// Each of the positions have opposite quantity signs
if (finalGroup.IsInvertedOf(initialGroup))
{
return true;
}
// The final group has a smaller quantity than the initial group
return Math.Abs(finalGroup.Quantity) < Math.Abs(initialGroup.Quantity) &&
finalGroup.All(position => Math.Sign(position.Quantity) == Math.Sign(initialGroup.GetPosition(position.Symbol).Quantity));
}
///
/// Gets a user friendly name for the provided
///
public static string GetUserFriendlyName(this IPositionGroup group)
{
if (group.Count == 1)
{
return group.Single().Symbol.ToString();
}
return string.Join("|", group.Select(p => p.Symbol.ToString()));
}
}
}