Closing Position

GF API Documentation

Robust position closing is important in a trading system. Below is a sample of how to do it with GF API

Position Closer

The idea is simple:

  • Cancel all working orders for a specified average position

  • Wait until they cancelled (or completed)

  • Place an exit order, if needed

PositionCloser
internal class PositionCloser
{
    private readonly GF.Api.IGFClient _client;

    private readonly Dictionary<GF.Api.Positions.IPosition, Action> _resultActions = new Dictionary<GF.Api.Positions.IPosition, Action>();

    public PositionCloser(GF.Api.IGFClient client)
    {
        _client = client;
        _client.Connection.Aggregate.Disconnected += GFClient_OnDisconnected;
        _client.Orders.OrderStateChanged += GFClient_OnOrderStateChanged;
    }

    public IEnumerable<GF.Api.Orders.IOrder> WorkingOrders
    {
        get { return _client.Orders.Get().Where(order => !order.IsFinalState); }
    }

    public void Close(GF.Api.Positions.IPosition position, Action action)
    {
        if (_resultActions.ContainsKey(position))
            return;

        var orders = GetPositionOrders(position).ToList();
        if (orders.Any())
        {
            CancelAll(orders);
        }
        else
        {
            if (position.Net.Volume == 0)
            {
                action();
                return;
            }

            PlaceExitOrder(position);
        }

        _resultActions[position] = action;
    }

    public void CancelAll(IEnumerable<GF.Api.Orders.IOrder> orders)
    {
        foreach (var order in orders.Where(o => !HasPendingCommands(o)))
            _client.Orders.CancelOrder(order.ID, SubmissionType.Automatic);
    }

    public IEnumerable<GF.Api.Orders.IOrder> GetPositionOrders(GF.Api.Positions.IPosition position)
    {
        return GetPositionOrders(position.Account, position.Contract);
    }

    public IEnumerable<GF.Api.Orders.IOrder> GetPositionOrders(GF.Api.Accounts.IAccount account, GF.Api.Contracts.IContract contract)
    {
        return WorkingOrders.Where(order =>
            order.Account.ID == account.ID
            && order.Contract.PositionContract.ID == contract.PositionContract.ID);
    }

    public void Dispose()
    {
        _resultActions.Clear();
        _client.Connection.Aggregate.Disconnected -= GFClient_OnDisconnected;
    }

    private void GFClient_OnDisconnected(GF.Api.IGFClient client, GF.Api.Connection.DisconnectedEventArgs disconnectedEventArgs)
    {
        _resultActions.Clear();
    }

    private void GFClient_OnOrderStateChanged(GF.Api.IGFClient client, GF.Api.Orders.OrderStateChangedEventArgs e)
    {
        if (!e.Order.IsFinalState)
            return;

        var avgPosition = e.Order.Account.AvgPositions[e.Order.Contract];
        if (avgPosition == null)
            return;

        if (_resultActions.TryGetValue(avgPosition, out var action))
        {
            var orders = GetPositionOrders(e.Order.Account, e.Order.Contract).ToList();
            if (orders.Any())
            {
                CancelAll(orders);
            }
            else
            {
                if (avgPosition.Net.Volume == 0)
                {
                    _resultActions.Remove(avgPosition);
                    action();
                }
                else
                {
                    PlaceExitOrder(avgPosition);
                }
            }
        }
    }

    private void PlaceExitOrder(GF.Api.Positions.IPosition avgPosition)
    {
        OrderDraft orderDraft = new OrderDraftBuilder()
            .WithAccountID(avgPosition.Account.ID)
            .WithContractID(avgPosition.Contract.ID)
            .WithSide(avgPosition.Net.Volume > 0 ? OrderSide.Sell : OrderSide.Buy)
            .WithOrderType(OrderType.Market)
            .WithQuantity(Math.Abs(avgPosition.Net.Volume))
            .WithComments("Exit")
            .Build();

        IReadOnlyList<OrderDraftValidationError> validationErrors = _client.Orders.Drafts.Validate(orderDraft);
        if (validationErrors.Any())
            throw new Exception($"Exit Order Draft validation error: {validationErrors.First()}");

        _client.Orders.SendOrder(orderDraft);
    }

    private bool HasPendingCommands(GF.Api.Orders.IOrder order)
    {
        return order.Commands.Any(cmd => cmd.State == CommandState.Sent);
    }
}

In this example we use it for closing the first available position of the first available account immediately after login:

Close Position on Login
internal class PositionCloserProgram
{
    private static PositionCloser _positionCloser;

    private static void Main(string[] args)
    {
        var client = GF.Api.Impl.GFApi.CreateClient();
        _positionCloser = new PositionCloser(client);

        var runner = new GF.Api.Threading.GFClientRunner(client);
        runner.Start();

        client.Connection.Aggregate.LoginCompleted += GFClient_OnLoginCompleted;

        Console.WriteLine("Connecting...");

        client.Connection.Aggregate.Connect(
            new GF.Api.Connection.ConnectionContextBuilder()
                .WithUserName("username")
                .WithPassword("password")
                .WithPort(9210)
                .WithHost("api.gainfutures.com")
                .WithUUID("9e61a8bc-0a31-4542-ad85-33ebab0e4e86")
                .WithForceLogin(true)
                .Build());

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();

        runner.Stop();
    }

    private static void GFClient_OnLoginCompleted(GF.Api.IGFClient client, GF.Api.Connection.LoginCompleteEventArgs e)
    {
        var position = client.Accounts.Get().First().AvgPositions.First().Value;
        if (position != null)
            _positionCloser.Close(position, () => Console.WriteLine($"{position.Account.Spec}:{position.Contract.Symbol} closed!"));
    }
}

Unfortunately, this snippet doesn't cover situations such as attempting to cancel an order outside of trading sessions, unexpected disconnection, cancel request failure, etc.